Skip to content
This repository was archived by the owner on Oct 25, 2024. It is now read-only.
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
uses: actions/checkout@v2

- name: Test
run: go test ./core ./miner/... ./internal/ethapi/... ./les/...
run: go test ./core ./miner/... ./internal/ethapi/... ./les/... ./builder/... ./eth/block-validation/...

- name: Build
run: make geth
Expand Down
19 changes: 15 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,9 @@ $ geth --help
--builder (default: false)
Enable the builder

--builder.beacon_endpoint value (default: "http://127.0.0.1:5052")
Beacon endpoint to connect to for beacon chain data [$BUILDER_BEACON_ENDPOINT]
--builder.beacon_endpoints value (default: "http://127.0.0.1:5052")
Comma separated list of beacon endpoints to connect to for beacon chain data
[$BUILDER_BEACON_ENDPOINTS]

--builder.bellatrix_fork_version value (default: "0x02000000")
Bellatrix fork version. [$BUILDER_BELLATRIX_FORK_VERSION]
Expand All @@ -53,6 +54,10 @@ $ geth --help
--builder.genesis_validators_root value (default: "0x0000000000000000000000000000000000000000000000000000000000000000")
Genesis validators root of the network. [$BUILDER_GENESIS_VALIDATORS_ROOT]

--builder.ignore_late_payload_attributes (default: false)
Builder will ignore all but the first payload attributes. Use if your CL sends
non-canonical head updates.

--builder.listen_addr value (default: ":28545")
Listening address for builder endpoint [$BUILDER_LISTEN_ADDR]

Expand All @@ -74,11 +79,17 @@ $ geth --help
missing from the primary remote relay, and to push blocks for registrations
missing from or matching the primary [$BUILDER_SECONDARY_REMOTE_RELAY_ENDPOINTS]

--builder.seconds_in_slot value (default: 12)
Set the number of seconds in a slot in the local relay

--builder.secret_key value (default: "0x2fc12ae741f29701f8e30f5de6350766c020cb80768a0ff01e6838ffd2431e11")
Builder key used for signing blocks [$BUILDER_SECRET_KEY]

--builder.validation_blacklist value (default: "")
Path to file containing blacklisted addresses, json-encoded list of strings.
--builder.slots_in_epoch value (default: 32)
Set the number of slots in an epoch in the local relay

--builder.validation_blacklist value
Path to file containing blacklisted addresses, json-encoded list of strings

--builder.validator_checks (default: false)
Enable the validator checks
Expand Down
7 changes: 3 additions & 4 deletions builder/beacon_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,11 +109,10 @@ func (m *MultiBeaconClient) getProposerForNextSlot(requestedSlot uint64) (Pubkey

func payloadAttributesMatch(l types.BuilderPayloadAttributes, r types.BuilderPayloadAttributes) bool {
if l.Timestamp != r.Timestamp ||
l.Random != r.Random ||
l.SuggestedFeeRecipient != r.SuggestedFeeRecipient ||
l.Slot != r.Slot ||
l.HeadHash != r.HeadHash ||
l.GasLimit != r.GasLimit {
l.GasLimit != r.GasLimit ||
l.Random != r.Random ||
l.HeadHash != r.HeadHash {
return false
}

Expand Down
66 changes: 31 additions & 35 deletions builder/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,46 +52,46 @@ type IBuilder interface {
}

type Builder struct {
ds flashbotsextra.IDatabaseService
relay IRelay
eth IEthereumService
dryRun bool
validator *blockvalidation.BlockValidationAPI
beaconClient IBeaconClient
builderSecretKey *bls.SecretKey
builderPublicKey boostTypes.PublicKey
builderSigningDomain boostTypes.Domain
ds flashbotsextra.IDatabaseService
relay IRelay
eth IEthereumService
dryRun bool
ignoreLatePayloadAttributes bool
validator *blockvalidation.BlockValidationAPI
beaconClient IBeaconClient
builderSecretKey *bls.SecretKey
builderPublicKey boostTypes.PublicKey
builderSigningDomain boostTypes.Domain

limiter *rate.Limiter

slotMu sync.Mutex
slot uint64
slotAttrs []types.BuilderPayloadAttributes
slotAttrs types.BuilderPayloadAttributes
slotCtx context.Context
slotCtxCancel context.CancelFunc

stop chan struct{}
}

func NewBuilder(sk *bls.SecretKey, ds flashbotsextra.IDatabaseService, relay IRelay, builderSigningDomain boostTypes.Domain, eth IEthereumService, dryRun bool, validator *blockvalidation.BlockValidationAPI, beaconClient IBeaconClient) *Builder {
func NewBuilder(sk *bls.SecretKey, ds flashbotsextra.IDatabaseService, relay IRelay, builderSigningDomain boostTypes.Domain, eth IEthereumService, dryRun bool, ignoreLatePayloadAttributes bool, validator *blockvalidation.BlockValidationAPI, beaconClient IBeaconClient) *Builder {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can refactor params to a struct

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should pass in the config for sure

pkBytes := bls.PublicKeyFromSecretKey(sk).Compress()
pk := boostTypes.PublicKey{}
pk.FromSlice(pkBytes)

slotCtx, slotCtxCancel := context.WithCancel(context.Background())
return &Builder{
ds: ds,
relay: relay,
eth: eth,
dryRun: dryRun,
validator: validator,
beaconClient: beaconClient,
builderSecretKey: sk,
builderPublicKey: pk,
builderSigningDomain: builderSigningDomain,
ds: ds,
relay: relay,
eth: eth,
dryRun: dryRun,
ignoreLatePayloadAttributes: ignoreLatePayloadAttributes,
validator: validator,
beaconClient: beaconClient,
builderSecretKey: sk,
builderPublicKey: pk,
builderSigningDomain: builderSigningDomain,

limiter: rate.NewLimiter(rate.Every(time.Millisecond), 510),
slot: 0,
slotCtx: slotCtx,
slotCtxCancel: slotCtxCancel,

Expand All @@ -116,7 +116,10 @@ func (b *Builder) Start() error {
if payloadAttributes.Slot < currentSlot {
continue
} else if payloadAttributes.Slot == currentSlot {
b.OnPayloadAttribute(&payloadAttributes)
// Subsequent sse events should only be canonical!
if !b.ignoreLatePayloadAttributes {
b.OnPayloadAttribute(&payloadAttributes)
}
} else if payloadAttributes.Slot > currentSlot {
currentSlot = payloadAttributes.Slot
b.OnPayloadAttribute(&payloadAttributes)
Expand Down Expand Up @@ -302,27 +305,20 @@ func (b *Builder) OnPayloadAttribute(attrs *types.BuilderPayloadAttributes) erro
b.slotMu.Lock()
defer b.slotMu.Unlock()

// Forcibly cancel previous building job, build on top of reorgable blocks as this is the behaviour relays expect.
// This will change in the future
if attrs.Equal(&b.slotAttrs) {
log.Debug("ignoring known payload attribute", "slot", attrs.Slot, "hash", attrs.HeadHash)
return nil
}

if b.slotCtxCancel != nil {
b.slotCtxCancel()
}

slotCtx, slotCtxCancel := context.WithTimeout(context.Background(), 12*time.Second)
b.slot = attrs.Slot
b.slotAttrs = nil
b.slotAttrs = *attrs
b.slotCtx = slotCtx
b.slotCtxCancel = slotCtxCancel

for _, currentAttrs := range b.slotAttrs {
if attrs.Equal(&currentAttrs) {
log.Debug("ignoring known payload attribute", "slot", attrs.Slot, "hash", attrs.HeadHash)
return nil
}
}
b.slotAttrs = append(b.slotAttrs, *attrs)

go b.runBuildingJob(b.slotCtx, proposerPubkey, vd, attrs)
return nil
}
Expand Down
2 changes: 1 addition & 1 deletion builder/builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ func TestOnPayloadAttributes(t *testing.T) {

testEthService := &testEthereumService{synced: true, testExecutableData: testExecutableData, testBlock: testBlock, testBlockValue: big.NewInt(10)}

builder := NewBuilder(sk, flashbotsextra.NilDbService{}, &testRelay, bDomain, testEthService, false, nil, &testBeacon)
builder := NewBuilder(sk, flashbotsextra.NilDbService{}, &testRelay, bDomain, testEthService, false, false, nil, &testBeacon)
builder.Start()
defer builder.Stop()

Expand Down
2 changes: 2 additions & 0 deletions builder/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ type Config struct {
SecondsInSlot uint64 `toml:",omitempty"`
DisableBundleFetcher bool `toml:",omitempty"`
DryRun bool `toml:",omitempty"`
IgnoreLatePayloadAttributes bool `toml:",omitempty"`
BuilderSecretKey string `toml:",omitempty"`
RelaySecretKey string `toml:",omitempty"`
ListenAddr string `toml:",omitempty"`
Expand All @@ -29,6 +30,7 @@ var DefaultConfig = Config{
SecondsInSlot: 12,
DisableBundleFetcher: false,
DryRun: false,
IgnoreLatePayloadAttributes: false,
BuilderSecretKey: "0x2fc12ae741f29701f8e30f5de6350766c020cb80768a0ff01e6838ffd2431e11",
RelaySecretKey: "0x2fc12ae741f29701f8e30f5de6350766c020cb80768a0ff01e6838ffd2431e11",
ListenAddr: ":28545",
Expand Down
2 changes: 1 addition & 1 deletion builder/local_relay_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func newTestBackend(t *testing.T, forkchoiceData *engine.ExecutableData, block *
beaconClient := &testBeaconClient{validator: validator}
localRelay := NewLocalRelay(sk, beaconClient, bDomain, cDomain, ForkData{}, true)
ethService := &testEthereumService{synced: true, testExecutableData: forkchoiceData, testBlock: block, testBlockValue: blockValue}
backend := NewBuilder(sk, flashbotsextra.NilDbService{}, localRelay, bDomain, ethService, false, nil, beaconClient)
backend := NewBuilder(sk, flashbotsextra.NilDbService{}, localRelay, bDomain, ethService, false, false, nil, beaconClient)
// service := NewService("127.0.0.1:31545", backend)

backend.limiter = rate.NewLimiter(rate.Inf, 0)
Expand Down
2 changes: 1 addition & 1 deletion builder/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ func Register(stack *node.Node, backend *eth.Ethereum, cfg *Config) error {
return errors.New("incorrect builder API secret key provided")
}

builderBackend := NewBuilder(builderSk, ds, relay, builderSigningDomain, ethereumService, cfg.DryRun, validator, beaconClient)
builderBackend := NewBuilder(builderSk, ds, relay, builderSigningDomain, ethereumService, cfg.DryRun, cfg.IgnoreLatePayloadAttributes, validator, beaconClient)
builderService := NewService(cfg.ListenAddr, localRelay, builderBackend)

stack.RegisterAPIs([]rpc.API{
Expand Down
1 change: 1 addition & 0 deletions cmd/geth/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ var (
utils.BuilderSlotsInEpoch,
utils.BuilderDisableBundleFetcher,
utils.BuilderDryRun,
utils.BuilderIgnoreLatePayloadAttributes,
utils.BuilderSecretKey,
utils.BuilderRelaySecretKey,
utils.BuilderListenAddr,
Expand Down
6 changes: 6 additions & 0 deletions cmd/utils/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -736,6 +736,11 @@ var (
Usage: "Builder only validates blocks without submission to the relay",
Category: flags.BuilderCategory,
}
BuilderIgnoreLatePayloadAttributes = &cli.BoolFlag{
Name: "builder.ignore_late_payload_attributes",
Usage: "Builder will ignore all but the first payload attributes. Use if your CL sends non-canonical head updates.",
Category: flags.BuilderCategory,
}
BuilderSecretKey = &cli.StringFlag{
Name: "builder.secret_key",
Usage: "Builder key used for signing blocks",
Expand Down Expand Up @@ -1602,6 +1607,7 @@ func SetBuilderConfig(ctx *cli.Context, cfg *builder.Config) {
cfg.SecondsInSlot = ctx.Uint64(BuilderSecondsInSlot.Name)
cfg.DisableBundleFetcher = ctx.IsSet(BuilderDisableBundleFetcher.Name)
cfg.DryRun = ctx.IsSet(BuilderDryRun.Name)
cfg.IgnoreLatePayloadAttributes = ctx.IsSet(BuilderIgnoreLatePayloadAttributes.Name)
cfg.BuilderSecretKey = ctx.String(BuilderSecretKey.Name)
cfg.RelaySecretKey = ctx.String(BuilderRelaySecretKey.Name)
cfg.ListenAddr = ctx.String(BuilderListenAddr.Name)
Expand Down
27 changes: 18 additions & 9 deletions eth/block-validation/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package blockvalidation

import (
"encoding/json"
"errors"
"io/ioutil"
"math/big"
"os"
Expand Down Expand Up @@ -166,6 +167,7 @@ func TestValidateBuilderSubmissionV1(t *testing.T) {

func TestValidateBuilderSubmissionV2(t *testing.T) {
genesis, preMergeBlocks := generatePreMergeChain(20)
os.Setenv("BUILDER_TX_SIGNING_KEY", "0x28c3cd61b687fdd03488e167a5d84f50269df2a4c29a2cfb1390903aa775c5d0")
time := preMergeBlocks[len(preMergeBlocks)-1].Time() + 5
genesis.Config.ShanghaiTime = &time
n, ethservice := startEthService(t, genesis, preMergeBlocks)
Expand All @@ -175,6 +177,8 @@ func TestValidateBuilderSubmissionV2(t *testing.T) {
api := NewBlockValidationAPI(ethservice, nil)
parent := preMergeBlocks[len(preMergeBlocks)-1]

api.eth.APIBackend.Miner().SetEtherbase(testValidatorAddr)

// This EVM code generates a log when the contract is created.
logCode := common.Hex2Bytes("60606040525b7f24ec1d3ff24c2f6ff210738839dbc339cd45a5294d85c79361016243157aae7b60405180905060405180910390a15b600a8060416000396000f360606040526008565b00")

Expand Down Expand Up @@ -224,18 +228,19 @@ func TestValidateBuilderSubmissionV2(t *testing.T) {
// require.ErrorContains(t, api.ValidateBuilderSubmissionV2(blockRequest), "withdrawals mismatch")

execData, err := assembleBlock(api, parent.Hash(), &engine.PayloadAttributes{
Timestamp: parent.Time() + 5,
Withdrawals: withdrawals,
Timestamp: parent.Time() + 5,
Withdrawals: withdrawals,
SuggestedFeeRecipient: testValidatorAddr,
})
require.NoError(t, err)
require.EqualValues(t, len(execData.Withdrawals), 2)
require.EqualValues(t, len(execData.Transactions), 3)
require.EqualValues(t, len(execData.Transactions), 4)

payload, err := ExecutableDataToExecutionPayloadV2(execData)
require.NoError(t, err)

proposerAddr := bellatrix.ExecutionAddress{}
copy(proposerAddr[:], testAddr.Bytes())
copy(proposerAddr[:], testValidatorAddr.Bytes())

blockRequest = &BuilderBlockValidationRequestV2{
SubmitBlockRequest: capellaapi.SubmitBlockRequest{
Expand All @@ -255,7 +260,7 @@ func TestValidateBuilderSubmissionV2(t *testing.T) {
}

require.ErrorContains(t, api.ValidateBuilderSubmissionV2(blockRequest), "inaccurate payment")
blockRequest.Message.Value = uint256.NewInt(10)
blockRequest.Message.Value = uint256.NewInt(149842511727212)
require.NoError(t, api.ValidateBuilderSubmissionV2(blockRequest))

blockRequest.Message.GasLimit += 1
Expand Down Expand Up @@ -288,7 +293,7 @@ func TestValidateBuilderSubmissionV2(t *testing.T) {
api.accessVerifier = nil

blockRequest.Message.GasUsed = 10
require.ErrorContains(t, api.ValidateBuilderSubmissionV2(blockRequest), "incorrect GasUsed 10, expected 98996")
require.ErrorContains(t, api.ValidateBuilderSubmissionV2(blockRequest), "incorrect GasUsed 10, expected 119996")
blockRequest.Message.GasUsed = execData.GasUsed

newTestKey, _ := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f290")
Expand All @@ -306,8 +311,7 @@ func TestValidateBuilderSubmissionV2(t *testing.T) {
copy(invalidPayload.ReceiptsRoot[:], hexutil.MustDecode("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")[:32])
blockRequest.ExecutionPayload = invalidPayload
updatePayloadHashV2(t, blockRequest)
require.ErrorContains(t, api.ValidateBuilderSubmissionV2(blockRequest), "could not apply tx 3", "insufficient funds for gas * price + value")

require.ErrorContains(t, api.ValidateBuilderSubmissionV2(blockRequest), "could not apply tx 4", "insufficient funds for gas * price + value")
}

func updatePayloadHash(t *testing.T, blockRequest *BuilderBlockValidationRequest) {
Expand Down Expand Up @@ -399,7 +403,12 @@ func assembleBlock(api *BlockValidationAPI, parentHash common.Hash, params *engi
if err != nil {
return nil, err
}
return payload.ResolveFull().ExecutionPayload, nil

if payload := payload.ResolveFull(); payload != nil {
return payload.ExecutionPayload, nil
}

return nil, errors.New("payload did not resolve")
}

func TestBlacklistLoad(t *testing.T) {
Expand Down
3 changes: 3 additions & 0 deletions miner/multi_worker.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,9 @@ func (w *multiWorker) buildPayload(args *BuildPayloadArgs) (*Payload, error) {
block, fees, err := w.getSealingBlock(args.Parent, args.Timestamp, args.FeeRecipient, args.GasLimit, args.Random, args.Withdrawals, false, args.BlockHook)
if err == nil {
workerPayload.update(block, fees, time.Since(start))
} else {
log.Error("Error while sealing block", "err", err)
workerPayload.Cancel()
}
}(w)
}
Expand Down