Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

porting builder changes - code compiles #1

Draft
wants to merge 7 commits into
base: branch-v1.0.4
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# with Go source code. If you know what GOPATH is then you probably
# don't need to bother with make.

.PHONY: geth android ios geth-cross evm all test clean docs
.PHONY: geth android ios geth-cross evm all test clean docs builder
.PHONY: geth-linux geth-linux-386 geth-linux-amd64 geth-linux-mips64 geth-linux-mips64le
.PHONY: geth-linux-arm geth-linux-arm-5 geth-linux-arm-6 geth-linux-arm-7 geth-linux-arm64
.PHONY: geth-darwin geth-darwin-386 geth-darwin-amd64
Expand All @@ -26,7 +26,13 @@ GOTEST = GODEBUG=cgocheck=0 go test $(GO_FLAGS) $(GO_LDFLAGS) -p 1
bor:
mkdir -p $(GOPATH)/bin/
go build -o $(GOBIN)/bor $(GO_LDFLAGS) ./cmd/cli/main.go
cp $(GOBIN)/bor $(GOPATH)/bin/
#cp $(GOBIN)/bor $(GOPATH)/bin/
@echo "Done building."

builder:
mkdir -p $(GOPATH)/bin/
go build -o $(GOBIN)/builder $(GO_LDFLAGS) ./cmd/cli/main.go
#cp $(GOBIN)/bor $(GOPATH)/bin/
@echo "Done building."

protoc:
Expand Down
96 changes: 96 additions & 0 deletions beacon/engine/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ package engine

import (
"fmt"
"github.com/attestantio/go-eth2-client/spec/bellatrix"
"github.com/attestantio/go-eth2-client/spec/capella"
"math/big"

"github.com/ethereum/go-ethereum/common"
Expand All @@ -35,6 +37,8 @@ type PayloadAttributes struct {
Random common.Hash `json:"prevRandao" gencodec:"required"`
SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
Withdrawals []*types.Withdrawal `json:"withdrawals"`
GasLimit uint64
Slot uint64
}

// JSON type overrides for PayloadAttributes.
Expand Down Expand Up @@ -247,3 +251,95 @@ type ExecutionPayloadBodyV1 struct {
TransactionData []hexutil.Bytes `json:"transactions"`
Withdrawals []*types.Withdrawal `json:"withdrawals"`
}

func ExecutionPayloadToBlock(payload *bellatrix.ExecutionPayload) (*types.Block, error) {
// TODO: consolidate this into one function that handles all forks
transactionBytes := make([][]byte, len(payload.Transactions))
for i, txHexBytes := range payload.Transactions {
transactionBytes[i] = txHexBytes[:]
}
txs, err := decodeTransactions(transactionBytes)
if err != nil {
return nil, err
}

// base fee per gas is stored little-endian but we need it
// big-endian for big.Int.
var baseFeePerGasBytes [32]byte
for i := 0; i < 32; i++ {
baseFeePerGasBytes[i] = payload.BaseFeePerGas[32-1-i]
}
baseFeePerGas := new(big.Int).SetBytes(baseFeePerGasBytes[:])

header := &types.Header{
ParentHash: common.Hash(payload.ParentHash),
UncleHash: types.EmptyUncleHash,
Coinbase: common.Address(payload.FeeRecipient),
Root: common.Hash(payload.StateRoot),
TxHash: types.DeriveSha(types.Transactions(txs), trie.NewStackTrie(nil)),
ReceiptHash: common.Hash(payload.ReceiptsRoot),
Bloom: types.BytesToBloom(payload.LogsBloom[:]),
Difficulty: common.Big0,
Number: new(big.Int).SetUint64(payload.BlockNumber),
GasLimit: payload.GasLimit,
GasUsed: payload.GasUsed,
Time: payload.Timestamp,
BaseFee: baseFeePerGas,
Extra: payload.ExtraData,
MixDigest: common.Hash(payload.PrevRandao),
}
block := types.NewBlockWithHeader(header).WithBody(txs, nil /* uncles */)
return block, nil
}

func ExecutionPayloadV2ToBlock(payload *capella.ExecutionPayload) (*types.Block, error) {
// TODO: separate decode function to avoid allocating twice
transactionBytes := make([][]byte, len(payload.Transactions))
for i, txHexBytes := range payload.Transactions {
transactionBytes[i] = txHexBytes[:]
}
txs, err := decodeTransactions(transactionBytes)
if err != nil {
return nil, err
}

withdrawals := make([]*types.Withdrawal, len(payload.Withdrawals))
for i, withdrawal := range payload.Withdrawals {
withdrawals[i] = &types.Withdrawal{
Index: uint64(withdrawal.Index),
Validator: uint64(withdrawal.ValidatorIndex),
Address: common.Address(withdrawal.Address),
Amount: uint64(withdrawal.Amount),
}
}
withdrawalsHash := types.DeriveSha(types.Withdrawals(withdrawals), trie.NewStackTrie(nil))

// base fee per gas is stored little-endian but we need it
// big-endian for big.Int.
var baseFeePerGasBytes [32]byte
for i := 0; i < 32; i++ {
baseFeePerGasBytes[i] = payload.BaseFeePerGas[32-1-i]
}
baseFeePerGas := new(big.Int).SetBytes(baseFeePerGasBytes[:])

header := &types.Header{
ParentHash: common.Hash(payload.ParentHash),
UncleHash: types.EmptyUncleHash,
Coinbase: common.Address(payload.FeeRecipient),
Root: common.Hash(payload.StateRoot),
TxHash: types.DeriveSha(types.Transactions(txs), trie.NewStackTrie(nil)),
ReceiptHash: common.Hash(payload.ReceiptsRoot),
Bloom: types.BytesToBloom(payload.LogsBloom[:]),
Difficulty: common.Big0,
Number: new(big.Int).SetUint64(payload.BlockNumber),
GasLimit: payload.GasLimit,
GasUsed: payload.GasUsed,
Time: payload.Timestamp,
BaseFee: baseFeePerGas,
Extra: payload.ExtraData,
MixDigest: common.Hash(payload.PrevRandao),
WithdrawalsHash: &withdrawalsHash,
}
block := types.NewBlockWithHeader(header).WithBody(txs, nil /* uncles */).WithWithdrawals(withdrawals)
return block, nil
}
51 changes: 51 additions & 0 deletions builder/beacon_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package builder

import (
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
)

type IBeaconClient interface {
isValidator(pubkey PubkeyHex) bool
getProposerForNextSlot(requestedSlot uint64) (PubkeyHex, error)
SubscribeToPayloadAttributesEvents(payloadAttrC chan types.BuilderPayloadAttributes)
Start() error
Stop()
}

type testBeaconClient struct {
validator *ValidatorPrivateData
slot uint64
}

func (b *testBeaconClient) Stop() {}

func (b *testBeaconClient) isValidator(pubkey PubkeyHex) bool {
return true
}

func (b *testBeaconClient) getProposerForNextSlot(requestedSlot uint64) (PubkeyHex, error) {
return PubkeyHex(hexutil.Encode(b.validator.Pk)), nil
}

func (b *testBeaconClient) SubscribeToPayloadAttributesEvents(payloadAttrC chan types.BuilderPayloadAttributes) {
}

func (b *testBeaconClient) Start() error { return nil }

type NilBeaconClient struct{}

func (b *NilBeaconClient) isValidator(pubkey PubkeyHex) bool {
return false
}

func (b *NilBeaconClient) getProposerForNextSlot(requestedSlot uint64) (PubkeyHex, error) {
return PubkeyHex(""), nil
}

func (b *NilBeaconClient) SubscribeToPayloadAttributesEvents(payloadAttrC chan types.BuilderPayloadAttributes) {
}

func (b *NilBeaconClient) Start() error { return nil }

func (b *NilBeaconClient) Stop() {}
176 changes: 176 additions & 0 deletions builder/beacon_client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
package builder

import (
"net/http"
"net/http/httptest"
"strconv"
"testing"

"github.com/gorilla/mux"
"github.com/stretchr/testify/require"
)

type mockBeaconNode struct {
srv *httptest.Server

proposerDuties map[int][]byte
forkResp map[int][]byte
headersCode int
headersResp []byte
}

func newMockBeaconNode() *mockBeaconNode {
r := mux.NewRouter()
srv := httptest.NewServer(r)

mbn := &mockBeaconNode{
srv: srv,

proposerDuties: make(map[int][]byte),
forkResp: make(map[int][]byte),
headersCode: 200,
headersResp: []byte{},
}

r.HandleFunc("/eth/v1/validator/duties/proposer/{epoch}", func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
epochStr, ok := vars["epoch"]
if !ok {
http.Error(w, `{ "code": 400, "message": "Invalid epoch" }`, 400)
return
}
epoch, err := strconv.Atoi(epochStr)
if err != nil {
http.Error(w, `{ "code": 400, "message": "Invalid epoch" }`, 400)
return
}

resp, found := mbn.proposerDuties[epoch]
if !found {
http.Error(w, `{ "code": 400, "message": "Invalid epoch" }`, 400)
return
}

w.Header().Set("Content-Type", "application/json")
w.Write(resp)
})

r.HandleFunc("/eth/v1/beacon/headers", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(mbn.headersCode)
w.Write(mbn.headersResp)
})

return mbn
}

func TestFetchBeacon(t *testing.T) {
mbn := newMockBeaconNode()
defer mbn.srv.Close()

mbn.headersCode = 200
mbn.headersResp = []byte(`{ "data": [ { "header": { "message": { "slot": "10", "proposer_index": "1" } } } ] }`)

// Green path
headersResp := struct {
Data []struct {
Header struct {
Message struct {
Slot string `json:"slot"`
} `json:"message"`
} `json:"header"`
} `json:"data"`
}{}
err := fetchBeacon(mbn.srv.URL+"/eth/v1/beacon/headers", &headersResp)
require.NoError(t, err)
require.Equal(t, "10", headersResp.Data[0].Header.Message.Slot)

// Wrong dst
wrongForkResp := struct {
Data []struct {
Slot string `json:"slot"`
}
}{}
err = fetchBeacon(mbn.srv.URL+"/eth/v1/beacon/headers", &wrongForkResp)
require.NoError(t, err)
require.Equal(t, wrongForkResp.Data[0].Slot, "")

mbn.headersCode = 400
mbn.headersResp = []byte(`{ "code": 400, "message": "Invalid call" }`)
err = fetchBeacon(mbn.srv.URL+"/eth/v1/beacon/headers", &headersResp)
require.EqualError(t, err, "Invalid call")
}

func TestFetchCurrentSlot(t *testing.T) {
mbn := newMockBeaconNode()
defer mbn.srv.Close()

mbn.headersResp = []byte(`{
"execution_optimistic": false,
"data": [
{
"root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
"canonical": true,
"header": {
"message": {
"slot": "101",
"proposer_index": "1",
"parent_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
"state_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
"body_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2"
},
"signature": "0x1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505cc411d61252fb6cb3fa0017b679f8bb2305b26a285fa2737f175668d0dff91cc1b66ac1fb663c9bc59509846d6ec05345bd908eda73e670af888da41af171505"
}
}
]
}`)

slot, err := fetchCurrentSlot(mbn.srv.URL)
require.NoError(t, err)
require.Equal(t, uint64(101), slot)

mbn.headersResp = []byte(`{
"execution_optimistic": false,
"data": [
{
"header": {
"message": {
"slot": "xxx"
}
}
}
]
}`)

slot, err = fetchCurrentSlot(mbn.srv.URL)
require.EqualError(t, err, "invalid response")
require.Equal(t, uint64(0), slot)
}

func TestFetchEpochProposersMap(t *testing.T) {
mbn := newMockBeaconNode()
defer mbn.srv.Close()

mbn.proposerDuties[10] = []byte(`{
"dependent_root": "0xcf8e0d4e9587369b2301d0790347320302cc0943d5a1884560367e8208d920f2",
"execution_optimistic": false,
"data": [
{
"pubkey": "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a",
"validator_index": "1",
"slot": "1"
},
{
"pubkey": "0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74b",
"validator_index": "2",
"slot": "2"
}
]
}`)

proposersMap, err := fetchEpochProposersMap(mbn.srv.URL, 10)
require.NoError(t, err)
require.Equal(t, 2, len(proposersMap))
require.Equal(t, PubkeyHex("0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74a"), proposersMap[1])
require.Equal(t, PubkeyHex("0x93247f2209abcacf57b75a51dafae777f9dd38bc7053d1af526f220a7489a6d3a2753e5f3e8b1cfe39b56f43611df74b"), proposersMap[2])
}
Loading