Skip to content

Commit

Permalink
Add block build trigger from beacon node api (ethereum#17)
Browse files Browse the repository at this point in the history
* Add block build trigger from beacon node

* remove empty block building

Co-authored-by: avalonche <[email protected]>
  • Loading branch information
avalonche and avalonche committed Feb 6, 2023
1 parent fdec233 commit 14db761
Show file tree
Hide file tree
Showing 11 changed files with 296 additions and 153 deletions.
163 changes: 74 additions & 89 deletions builder/builder.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package builder

import (
"encoding/json"
_ "os"

"github.com/ethereum/go-ethereum/common/hexutil"
Expand Down Expand Up @@ -33,120 +32,106 @@ type IRelay interface {
GetValidatorForSlot(nextSlot uint64) (ValidatorData, error)
}

type IBuilder interface {
OnPayloadAttribute(attrs *BuilderPayloadAttributes) error
}

type Builder struct {
beaconClient IBeaconClient
relay IRelay
eth IEthereumService

builderSecretKey *bls.SecretKey
builderPublicKey boostTypes.PublicKey
builderSigningDomain boostTypes.Domain
}

func NewBuilder(sk *bls.SecretKey, bc IBeaconClient, relay IRelay, builderSigningDomain boostTypes.Domain) *Builder {
func NewBuilder(sk *bls.SecretKey, bc IBeaconClient, relay IRelay, builderSigningDomain boostTypes.Domain, eth IEthereumService) *Builder {
pkBytes := bls.PublicKeyFromSecretKey(sk).Compress()
pk := boostTypes.PublicKey{}
pk.FromSlice(pkBytes)

_, err := bc.onForkchoiceUpdate()
if err != nil {
log.Error("could not initialize beacon client", "err", err)
}

return &Builder{
beaconClient: bc,
relay: relay,
eth: eth,
builderSecretKey: sk,
builderPublicKey: pk,

builderSigningDomain: builderSigningDomain,
}
}

func (b *Builder) onForkchoice(payloadAttributes *beacon.PayloadAttributesV1) {
dataJson, err := json.Marshal(payloadAttributes)
if err == nil {
log.Info("FCU", "data", string(dataJson))
} else {
log.Info("FCU", "data", payloadAttributes, "parsingError", err)

}

nextSlot, err := b.beaconClient.onForkchoiceUpdate()
if err != nil {
log.Error("FCU hook failed", "err", err)
return
}

if payloadAttributes != nil {
payloadAttributes.Slot = nextSlot
if vd, err := b.relay.GetValidatorForSlot(nextSlot); err == nil {
payloadAttributes.SuggestedFeeRecipient = [20]byte(vd.FeeRecipient)
payloadAttributes.GasLimit = vd.GasLimit
func (b *Builder) OnPayloadAttribute(attrs *BuilderPayloadAttributes) error {
if attrs != nil {
vd, err := b.relay.GetValidatorForSlot(attrs.Slot)
if err != nil {
log.Info("could not get validator while submitting block", "err", err, "slot", attrs.Slot)
return err
}
}
}

func (b *Builder) newSealedBlock(data *beacon.ExecutableDataV1, block *types.Block, payloadAttributes *beacon.PayloadAttributesV1) {
dataJson, err := json.Marshal(data)
if err == nil {
log.Info("newSealedBlock", "data", string(dataJson))
} else {
log.Info("newSealedBlock", "data", data, "parsingError", err)
}
payload, err := executableDataToExecutionPayload(data)
if err != nil {
log.Error("could not format execution payload", "err", err)
return
}

vd, err := b.relay.GetValidatorForSlot(payloadAttributes.Slot)
if err != nil {
log.Error("could not get validator while submitting block", "err", err, "slot", payloadAttributes.Slot)
return
}

pubkey, err := boostTypes.HexToPubkey(string(vd.Pubkey))
if err != nil {
log.Error("could not parse pubkey", "err", err, "pubkey", vd.Pubkey)
return
}

value := new(boostTypes.U256Str)
err = value.FromBig(block.Profit)
if err != nil {
log.Error("could not set block value", "err", err)
return
}

blockBidMsg := boostTypes.BidTrace{
Slot: payloadAttributes.Slot,
ParentHash: payload.ParentHash,
BlockHash: payload.BlockHash,
BuilderPubkey: b.builderPublicKey,
ProposerPubkey: pubkey,
ProposerFeeRecipient: boostTypes.Address(payloadAttributes.SuggestedFeeRecipient),
GasLimit: data.GasLimit,
GasUsed: data.GasUsed,
Value: *value,
}

signature, err := boostTypes.SignMessage(&blockBidMsg, b.builderSigningDomain, b.builderSecretKey)
if err != nil {
log.Error("could not sign builder bid", "err", err)
return
}

blockSubmitReq := boostTypes.BuilderSubmitBlockRequest{
Signature: signature,
Message: &blockBidMsg,
ExecutionPayload: payload,
}

err = b.relay.SubmitBlock(&blockSubmitReq)
if err != nil {
log.Error("could not submit block", "err", err)
return
attrs.SuggestedFeeRecipient = [20]byte(vd.FeeRecipient)
attrs.GasLimit = vd.GasLimit

if b.eth.Synced() {
block := b.eth.GetBlockByHash(attrs.HeadHash)
if block == nil {
log.Info("Block hash not found in blocktree", "head block hash", attrs.HeadHash)
return err
}

executableData := b.eth.BuildBlock(attrs)
payload, err := executableDataToExecutionPayload(executableData)
if err != nil {
log.Error("could not format execution payload", "err", err)
return err
}

pubkey, err := boostTypes.HexToPubkey(string(vd.Pubkey))
if err != nil {
log.Error("could not parse pubkey", "err", err, "pubkey", vd.Pubkey)
return err
}

value := new(boostTypes.U256Str)
err = value.FromBig(block.Profit)
if err != nil {
log.Error("could not set block value", "err", err)
return err
}

blockBidMsg := boostTypes.BidTrace{
Slot: attrs.Slot,
ParentHash: payload.ParentHash,
BlockHash: payload.BlockHash,
BuilderPubkey: b.builderPublicKey,
ProposerPubkey: pubkey,
ProposerFeeRecipient: boostTypes.Address(attrs.SuggestedFeeRecipient),
GasLimit: executableData.GasLimit,
GasUsed: executableData.GasUsed,
Value: *value,
}

signature, err := boostTypes.SignMessage(&blockBidMsg, b.builderSigningDomain, b.builderSecretKey)
if err != nil {
log.Error("could not sign builder bid", "err", err)
return err
}

blockSubmitReq := boostTypes.BuilderSubmitBlockRequest{
Signature: signature,
Message: &blockBidMsg,
ExecutionPayload: payload,
}

err = b.relay.SubmitBlock(&blockSubmitReq)
if err != nil {
log.Error("could not submit block", "err", err)
return err
}
}
}
return nil
}

func executableDataToExecutionPayload(data *beacon.ExecutableDataV1) (*boostTypes.ExecutionPayload, error) {
Expand Down
24 changes: 13 additions & 11 deletions builder/builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
"github.com/stretchr/testify/require"
)

func TestOnNewSealedBlock(t *testing.T) {
func TestOnPayloadAttributes(t *testing.T) {
vsk, err := bls.SecretKeyFromBytes(hexutil.MustDecode("0x370bb8c1a6e62b2882f6ec76762a67b39609002076b95aae5b023997cf9b2dc9"))
require.NoError(t, err)
validator := &ValidatorPrivateData{
Expand Down Expand Up @@ -41,11 +41,9 @@ func TestOnNewSealedBlock(t *testing.T) {

bDomain := boostTypes.ComputeDomain(boostTypes.DomainTypeAppBuilder, [4]byte{0x02, 0x0, 0x0, 0x0}, boostTypes.Hash{})

builder := NewBuilder(sk, &testBeacon, &testRelay, bDomain)

testExecutableData := &beacon.ExecutableDataV1{
ParentHash: common.Hash{0x02, 0x03},
FeeRecipient: common.Address{0x06, 0x15},
FeeRecipient: common.Address(feeRecipient),
StateRoot: common.Hash{0x07, 0x16},
ReceiptsRoot: common.Hash{0x08, 0x20},
LogsBloom: hexutil.MustDecode("0x000000000000000000000000000000"),
Expand All @@ -65,18 +63,21 @@ func TestOnNewSealedBlock(t *testing.T) {
Profit: big.NewInt(10),
}

testPayloadAttributes := &beacon.PayloadAttributesV1{
Timestamp: uint64(104),
testPayloadAttributes := &BuilderPayloadAttributes{
Timestamp: hexutil.Uint64(104),
Random: common.Hash{0x05, 0x10},
SuggestedFeeRecipient: common.Address{0x04, 0x10},
GasLimit: uint64(21),
Slot: uint64(25),
}

builder.newSealedBlock(testExecutableData, testBlock, testPayloadAttributes)
testEthService := &testEthereumService{synced: true, testExecutableData: testExecutableData, testBlock: testBlock}

require.NotNil(t, testRelay.submittedMsg)
builder := NewBuilder(sk, &testBeacon, &testRelay, bDomain, testEthService)

builder.OnPayloadAttribute(testPayloadAttributes)

require.NotNil(t, testRelay.submittedMsg)
expectedProposerPubkey, err := boostTypes.HexToPubkey(testBeacon.validator.Pk.String())
require.NoError(t, err)

Expand All @@ -86,7 +87,7 @@ func TestOnNewSealedBlock(t *testing.T) {
BlockHash: boostTypes.Hash{0x09, 0xff},
BuilderPubkey: builder.builderPublicKey,
ProposerPubkey: expectedProposerPubkey,
ProposerFeeRecipient: boostTypes.Address{0x04, 0x10},
ProposerFeeRecipient: feeRecipient,
GasLimit: uint64(50),
GasUsed: uint64(100),
Value: boostTypes.U256Str{0x0a},
Expand All @@ -96,7 +97,7 @@ func TestOnNewSealedBlock(t *testing.T) {

expectedExecutionPayload := boostTypes.ExecutionPayload{
ParentHash: [32]byte(testExecutableData.ParentHash),
FeeRecipient: boostTypes.Address{0x6, 0x15},
FeeRecipient: feeRecipient,
StateRoot: [32]byte(testExecutableData.StateRoot),
ReceiptsRoot: [32]byte(testExecutableData.ReceiptsRoot),
LogsBloom: [256]byte{},
Expand All @@ -110,9 +111,10 @@ func TestOnNewSealedBlock(t *testing.T) {
BlockHash: boostTypes.Hash{0x09, 0xff},
Transactions: []hexutil.Bytes{},
}

require.Equal(t, expectedExecutionPayload, *testRelay.submittedMsg.ExecutionPayload)

expectedSignature, err := boostTypes.HexToSignature("0xadebce714127deea6b04c8f63e650ad6b4c0d3df14ecd9759bef741cd6d72509090f5e172033ce40475c322c0c0e3fae0e78a880a66cb324913ea490472d93e187a9a91284b05137f1554688c5e9b1ee73539a2b005b103e8bd50e973e8e0f49")
expectedSignature, err := boostTypes.HexToSignature("0xb086abc231a515559128122a6618ad316a76195ad39aa28195c9e8921b98561ca4fd12e2e1ea8d50d8e22f7e36d42ee1084fef26672beceda7650a87061e412d7742705077ac3af3ca1a1c3494eccb22fe7c234fd547a285ba699ff87f0e7759")

require.NoError(t, err)
require.Equal(t, expectedSignature, testRelay.submittedMsg.Signature)
Expand Down
60 changes: 60 additions & 0 deletions builder/eth_service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package builder

import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/beacon"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/eth/catalyst"
"github.com/ethereum/go-ethereum/log"
)

type IEthereumService interface {
BuildBlock(attrs *BuilderPayloadAttributes) *beacon.ExecutableDataV1
GetBlockByHash(hash common.Hash) *types.Block
Synced() bool
}

type testEthereumService struct {
synced bool
testExecutableData *beacon.ExecutableDataV1
testBlock *types.Block
}

func (t *testEthereumService) BuildBlock(attrs *BuilderPayloadAttributes) *beacon.ExecutableDataV1 {
return t.testExecutableData
}

func (t *testEthereumService) GetBlockByHash(hash common.Hash) *types.Block { return t.testBlock }

func (t *testEthereumService) Synced() bool { return t.synced }

type EthereumService struct {
eth *eth.Ethereum
}

func NewEthereumService(eth *eth.Ethereum) *EthereumService {
return &EthereumService{eth: eth}
}

func (s *EthereumService) BuildBlock(attrs *BuilderPayloadAttributes) *beacon.ExecutableDataV1 {
// Send a request to generate a full block in the background.
// The result can be obtained via the returned channel.
resCh, err := s.eth.Miner().GetSealingBlockAsync(attrs.HeadHash, uint64(attrs.Timestamp), attrs.SuggestedFeeRecipient, attrs.GasLimit, attrs.Random, false)
if err != nil {
log.Error("Failed to create async sealing payload", "err", err)
return nil
}

resultPayload := catalyst.NewPayload(resCh)
executableData, _ := resultPayload.Resolve()
return executableData
}

func (s *EthereumService) GetBlockByHash(hash common.Hash) *types.Block {
return s.eth.BlockChain().GetBlockByHash(hash)
}

func (s *EthereumService) Synced() bool {
return s.eth.Synced()
}
Loading

0 comments on commit 14db761

Please sign in to comment.