diff --git a/builder/builder.go b/builder/builder.go index c4c415c1fe72..1aa197d90040 100644 --- a/builder/builder.go +++ b/builder/builder.go @@ -41,6 +41,7 @@ type IBuilder interface { } type Builder struct { + ds IDatabaseService beaconClient IBeaconClient relay IRelay eth IEthereumService @@ -55,12 +56,13 @@ type Builder struct { bestBlockProfit *big.Int } -func NewBuilder(sk *bls.SecretKey, bc IBeaconClient, relay IRelay, builderSigningDomain boostTypes.Domain, eth IEthereumService) *Builder { +func NewBuilder(sk *bls.SecretKey, ds IDatabaseService, bc IBeaconClient, relay IRelay, builderSigningDomain boostTypes.Domain, eth IEthereumService) *Builder { pkBytes := bls.PublicKeyFromSecretKey(sk).Compress() pk := boostTypes.PublicKey{} pk.FromSlice(pkBytes) return &Builder{ + ds: ds, beaconClient: bc, relay: relay, eth: eth, @@ -73,7 +75,7 @@ func NewBuilder(sk *bls.SecretKey, bc IBeaconClient, relay IRelay, builderSignin } } -func (b *Builder) onSealedBlock(executableData *beacon.ExecutableDataV1, block *types.Block, proposerPubkey boostTypes.PublicKey, proposerFeeRecipient boostTypes.Address, attrs *BuilderPayloadAttributes) error { +func (b *Builder) onSealedBlock(block *types.Block, bundles []types.SimulatedBundle, proposerPubkey boostTypes.PublicKey, proposerFeeRecipient boostTypes.Address, attrs *BuilderPayloadAttributes) error { b.bestMu.Lock() defer b.bestMu.Unlock() @@ -88,6 +90,7 @@ func (b *Builder) onSealedBlock(executableData *beacon.ExecutableDataV1, block * } } + executableData := beacon.BlockToExecutableData(block) payload, err := executableDataToExecutionPayload(executableData) if err != nil { log.Error("could not format execution payload", "err", err) @@ -133,6 +136,7 @@ func (b *Builder) onSealedBlock(executableData *beacon.ExecutableDataV1, block * b.bestBlockProfit.Set(block.Profit) + go b.ds.ConsumeBuiltBlock(block, bundles, &blockBidMsg) return nil } @@ -166,20 +170,15 @@ func (b *Builder) OnPayloadAttribute(attrs *BuilderPayloadAttributes) error { return errors.New("parent block not found in blocktree") } - firstBlockResult := b.resubmitter.newTask(12*time.Second, time.Second, func() error { - executableData, block := b.eth.BuildBlock(attrs) - if executableData == nil || block == nil { - log.Error("did not receive the payload") - return errors.New("did not receive the payload") - } - - err := b.onSealedBlock(executableData, block, proposerPubkey, vd.FeeRecipient, attrs) + blockHook := func(block *types.Block, bundles []types.SimulatedBundle) { + err := b.onSealedBlock(block, bundles, proposerPubkey, vd.FeeRecipient, attrs) if err != nil { - log.Error("could not run block hook", "err", err) - return err + log.Error("could not run sealed block hook", "err", err) } + } - return nil + firstBlockResult := b.resubmitter.newTask(12*time.Second, time.Second, func() error { + return b.eth.BuildBlock(attrs, blockHook) }) return firstBlockResult diff --git a/builder/builder_test.go b/builder/builder_test.go index 599352ee5222..8bd98aab7b1f 100644 --- a/builder/builder_test.go +++ b/builder/builder_test.go @@ -47,7 +47,7 @@ func TestOnPayloadAttributes(t *testing.T) { FeeRecipient: common.Address(feeRecipient), StateRoot: common.Hash{0x07, 0x16}, ReceiptsRoot: common.Hash{0x08, 0x20}, - LogsBloom: hexutil.MustDecode("0x000000000000000000000000000000"), + LogsBloom: types.Bloom{}.Bytes(), Number: uint64(10), GasLimit: uint64(50), GasUsed: uint64(100), @@ -56,13 +56,13 @@ func TestOnPayloadAttributes(t *testing.T) { BaseFeePerGas: big.NewInt(16), - BlockHash: common.Hash{0x09, 0xff}, + BlockHash: common.HexToHash("0xca4147f0d4150183ece9155068f34ee3c375448814e4ca557d482b1d40ee5407"), Transactions: [][]byte{}, } - testBlock := &types.Block{ - Profit: big.NewInt(10), - } + testBlock, err := beacon.ExecutableDataToBlock(*testExecutableData) + require.NoError(t, err) + testBlock.Profit = big.NewInt(10) testPayloadAttributes := &BuilderPayloadAttributes{ Timestamp: hexutil.Uint64(104), @@ -74,7 +74,7 @@ func TestOnPayloadAttributes(t *testing.T) { testEthService := &testEthereumService{synced: true, testExecutableData: testExecutableData, testBlock: testBlock} - builder := NewBuilder(sk, &testBeacon, &testRelay, bDomain, testEthService) + builder := NewBuilder(sk, NilDbService{}, &testBeacon, &testRelay, bDomain, testEthService) builder.OnPayloadAttribute(testPayloadAttributes) @@ -85,7 +85,6 @@ func TestOnPayloadAttributes(t *testing.T) { expectedMessage := boostTypes.BidTrace{ Slot: uint64(25), ParentHash: boostTypes.Hash{0x02, 0x03}, - BlockHash: boostTypes.Hash{0x09, 0xff}, BuilderPubkey: builder.builderPublicKey, ProposerPubkey: expectedProposerPubkey, ProposerFeeRecipient: feeRecipient, @@ -93,6 +92,7 @@ func TestOnPayloadAttributes(t *testing.T) { GasUsed: uint64(100), Value: boostTypes.U256Str{0x0a}, } + expectedMessage.BlockHash.FromSlice(hexutil.MustDecode("0xca4147f0d4150183ece9155068f34ee3c375448814e4ca557d482b1d40ee5407")[:]) require.Equal(t, expectedMessage, *testRelay.submittedMsg.Message) @@ -109,13 +109,13 @@ func TestOnPayloadAttributes(t *testing.T) { Timestamp: testExecutableData.Timestamp, ExtraData: hexutil.MustDecode("0x0042fafc"), BaseFeePerGas: boostTypes.U256Str{0x10}, - BlockHash: boostTypes.Hash{0x09, 0xff}, + BlockHash: expectedMessage.BlockHash, Transactions: []hexutil.Bytes{}, } require.Equal(t, expectedExecutionPayload, *testRelay.submittedMsg.ExecutionPayload) - expectedSignature, err := boostTypes.HexToSignature("0xb086abc231a515559128122a6618ad316a76195ad39aa28195c9e8921b98561ca4fd12e2e1ea8d50d8e22f7e36d42ee1084fef26672beceda7650a87061e412d7742705077ac3af3ca1a1c3494eccb22fe7c234fd547a285ba699ff87f0e7759") + expectedSignature, err := boostTypes.HexToSignature("0xad09f171b1da05636acfc86778c319af69e39c79515d44bdfed616ba2ef677ffd4d155d87b3363c6bae651ce1e92786216b75f1ac91dd65f3b1d1902bf8485e742170732dd82ffdf4decb0151eeb7926dd053efa9794b2ebed1a203e62bb13e9") require.NoError(t, err) require.Equal(t, expectedSignature, testRelay.submittedMsg.Signature) diff --git a/builder/database.go b/builder/database.go new file mode 100644 index 000000000000..f4799208e5d3 --- /dev/null +++ b/builder/database.go @@ -0,0 +1,136 @@ +package builder + +import ( + "context" + "database/sql" + "math/big" + "time" + + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" + boostTypes "github.com/flashbots/go-boost-utils/types" + "github.com/jmoiron/sqlx" + _ "github.com/lib/pq" +) + +type IDatabaseService interface { + ConsumeBuiltBlock(block *types.Block, bundles []types.SimulatedBundle, bidTrace *boostTypes.BidTrace) +} + +type NilDbService struct{} + +func (NilDbService) ConsumeBuiltBlock(*types.Block, []types.SimulatedBundle, *boostTypes.BidTrace) {} + +type DatabaseService struct { + db *sqlx.DB + + insertBuiltBlockStmt *sqlx.NamedStmt + insertBlockBuiltBundleNoIdStmt *sqlx.NamedStmt + insertBlockBuiltBundleWithIdStmt *sqlx.NamedStmt + insertMissingBundleStmt *sqlx.NamedStmt +} + +func NewDatabaseService(postgresDSN string) (*DatabaseService, error) { + db, err := sqlx.Connect("postgres", postgresDSN) + if err != nil { + return nil, err + } + + insertBuiltBlockStmt, err := db.PrepareNamed("insert into built_blocks (block_number, profit, slot, hash, gas_limit, gas_used, base_fee, parent_hash, proposer_pubkey, proposer_fee_recipient, builder_pubkey, timestamp, timestamp_datetime) values (:block_number, :profit, :slot, :hash, :gas_limit, :gas_used, :base_fee, :parent_hash, :proposer_pubkey, :proposer_fee_recipient, :builder_pubkey, :timestamp, to_timestamp(:timestamp)) returning block_id") + if err != nil { + return nil, err + } + + insertBlockBuiltBundleNoIdStmt, err := db.PrepareNamed("insert into built_blocks_bundles (block_id, bundle_id) select :block_id, id from bundles where bundle_hash = :bundle_hash and param_block_number = :block_number returning bundle_id") + if err != nil { + return nil, err + } + + insertBlockBuiltBundleWithIdStmt, err := db.PrepareNamed("insert into built_blocks_bundles (block_id, bundle_id) select :block_id, :bundle_id returning bundle_id") + if err != nil { + return nil, err + } + + insertMissingBundleStmt, err := db.PrepareNamed("insert into bundles (bundle_hash, param_signed_txs, param_block_number, param_timestamp, received_timestamp, param_reverting_tx_hashes, coinbase_diff, total_gas_used, state_block_number, gas_fees, eth_sent_to_coinbase) values (:bundle_hash, :param_signed_txs, :param_block_number, :param_timestamp, :received_timestamp, :param_reverting_tx_hashes, :coinbase_diff, :total_gas_used, :state_block_number, :gas_fees, :eth_sent_to_coinbase) on conflict (bundle_hash, param_block_number) do nothing returning id") + if err != nil { + return nil, err + } + + return &DatabaseService{ + db: db, + insertBuiltBlockStmt: insertBuiltBlockStmt, + insertBlockBuiltBundleNoIdStmt: insertBlockBuiltBundleNoIdStmt, + insertBlockBuiltBundleWithIdStmt: insertBlockBuiltBundleWithIdStmt, + insertMissingBundleStmt: insertMissingBundleStmt, + }, nil +} + +func (ds *DatabaseService) ConsumeBuiltBlock(block *types.Block, bundles []types.SimulatedBundle, bidTrace *boostTypes.BidTrace) { + tx, err := ds.db.Beginx() + + blockData := BuiltBlock{ + BlockNumber: block.NumberU64(), + Profit: new(big.Rat).SetFrac(block.Profit, big.NewInt(1e18)).FloatString(18), + Slot: bidTrace.Slot, + Hash: block.Hash().String(), + GasLimit: block.GasLimit(), + GasUsed: block.GasUsed(), + BaseFee: block.BaseFee().Uint64(), + ParentHash: block.ParentHash().String(), + ProposerPubkey: bidTrace.ProposerPubkey.String(), + ProposerFeeRecipient: bidTrace.ProposerFeeRecipient.String(), + BuilderPubkey: bidTrace.BuilderPubkey.String(), + Timestamp: block.Time(), + } + + ctx, cancel := context.WithTimeout(context.Background(), 12*time.Second) + defer cancel() + var blockId uint64 + if err = tx.NamedStmtContext(ctx, ds.insertBuiltBlockStmt).GetContext(ctx, &blockId, blockData); err != nil { + log.Error("could not insert built block", "err", err) + tx.Rollback() + return + } + + for _, bundle := range bundles { + bundleData := BuiltBlockBundle{ + BlockId: blockId, + BundleId: nil, + BlockNumber: blockData.BlockNumber, + BundleHash: bundle.OriginalBundle.Hash.String(), + } + + var bundleId uint64 + err := tx.NamedStmtContext(ctx, ds.insertBlockBuiltBundleNoIdStmt).GetContext(ctx, &bundleId, bundleData) + if err == nil { + continue + } + + if err != sql.ErrNoRows { + log.Error("could not insert bundle", "err", err) + // Try anyway + } + + missingBundleData := SimulatedBundleToDbBundle(&bundle) + err = ds.insertMissingBundleStmt.GetContext(ctx, &bundleId, missingBundleData) // not using the tx as it relies on the unique constraint! + if err == nil { + bundleData.BundleId = &bundleId + _, err = tx.NamedStmtContext(ctx, ds.insertBlockBuiltBundleWithIdStmt).ExecContext(ctx, bundleData) + if err != nil { + log.Error("could not insert built block bundle after inserting missing bundle", "err", err) + } + } else if err == sql.ErrNoRows /* conflict, someone else inserted the bundle before we could */ { + if err := tx.NamedStmtContext(ctx, ds.insertBlockBuiltBundleNoIdStmt).GetContext(ctx, &bundleId, bundleData); err != nil { + log.Error("could not insert bundle on retry", "err", err) + continue + } + } else { + log.Error("could not insert missing bundle", "err", err) + } + } + + err = tx.Commit() + if err != nil { + log.Error("could not commit DB trasnaction", "err", err) + } +} diff --git a/builder/database_test.go b/builder/database_test.go new file mode 100644 index 000000000000..2ff2c7bb8a27 --- /dev/null +++ b/builder/database_test.go @@ -0,0 +1,71 @@ +package builder + +import ( + "math/big" + "os" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + boostTypes "github.com/flashbots/go-boost-utils/types" + "github.com/stretchr/testify/require" +) + +func TestDatabaseBlockInsertion(t *testing.T) { + dsn := os.Getenv("FLASHBOTS_TEST_POSTGRES_DSN") + if dsn == "" { + return + } + + ds, err := NewDatabaseService(dsn) + require.NoError(t, err) + + _, err = ds.db.Exec("insert into bundles (id, param_block_number, bundle_hash) values (10, 20, '0x1078')") + require.NoError(t, err) + + block := types.NewBlock( + &types.Header{ + ParentHash: common.HexToHash("0xafafafa"), + Number: big.NewInt(132), + GasLimit: uint64(10000), + GasUsed: uint64(1000), + Time: 16000000, + BaseFee: big.NewInt(7), + }, nil, nil, nil, nil) + block.Profit = big.NewInt(10) + + simBundle1 := types.SimulatedBundle{ + MevGasPrice: big.NewInt(9), + TotalEth: big.NewInt(11), + EthSentToCoinbase: big.NewInt(10), + TotalGasUsed: uint64(100), + OriginalBundle: types.MevBundle{ + Txs: types.Transactions{types.NewTransaction(uint64(50), common.Address{0x60}, big.NewInt(19), uint64(67), big.NewInt(43), []byte{})}, + BlockNumber: big.NewInt(12), + MinTimestamp: uint64(1000000), + RevertingTxHashes: []common.Hash{common.Hash{0x10, 0x17}}, + Hash: common.Hash{0x09, 0x78}, + }, + } + simBundle2 := types.SimulatedBundle{ + MevGasPrice: big.NewInt(90), + TotalEth: big.NewInt(110), + EthSentToCoinbase: big.NewInt(100), + TotalGasUsed: uint64(1000), + OriginalBundle: types.MevBundle{ + Txs: types.Transactions{types.NewTransaction(uint64(51), common.Address{0x61}, big.NewInt(109), uint64(167), big.NewInt(433), []byte{})}, + BlockNumber: big.NewInt(20), + MinTimestamp: uint64(1000020), + RevertingTxHashes: []common.Hash{common.Hash{0x11, 0x17}}, + Hash: common.Hash{0x10, 0x78}, + }, + } + + bidTrace := &boostTypes.BidTrace{} + + ds.ConsumeBuiltBlock(block, []types.SimulatedBundle{simBundle1, simBundle2}, bidTrace) + + var dbBlock BuiltBlock + ds.db.Get(&dbBlock, "select * from built_blocks where hash = '0x24e6998e4d2b4fd85f7f0d27ac4b87aaf0ec18e156e4b6e194ab5dadee0cd004'") + t.Logf("block %v", dbBlock) +} diff --git a/builder/database_types.go b/builder/database_types.go new file mode 100644 index 000000000000..96965616b028 --- /dev/null +++ b/builder/database_types.go @@ -0,0 +1,77 @@ +package builder + +import ( + "math/big" + "strings" + "time" + + "github.com/ethereum/go-ethereum/core/types" +) + +type BuiltBlock struct { + BlockId uint64 `db:"block_id"` + BlockNumber uint64 `db:"block_number"` + Profit string `db:"profit"` + Slot uint64 `db:"slot"` + Hash string `db:"hash"` + GasLimit uint64 `db:"gas_limit"` + GasUsed uint64 `db:"gas_used"` + BaseFee uint64 `db:"base_fee"` + ParentHash string `db:"parent_hash"` + ProposerPubkey string `db:"proposer_pubkey"` + ProposerFeeRecipient string `db:"proposer_fee_recipient"` + BuilderPubkey string `db:"builder_pubkey"` + Timestamp uint64 `db:"timestamp"` + TimestampDatetime time.Time `db:"timestamp_datetime"` +} + +type BuiltBlockBundle struct { + BlockId uint64 `db:"block_id"` + BundleId *uint64 `db:"bundle_id"` + BlockNumber uint64 `db:"block_number"` + BundleHash string `db:"bundle_hash"` +} + +type DbBundle struct { + DbId uint64 `db:"id"` + BundleHash string `db:"bundle_hash"` + + ParamSignedTxs string `db:"param_signed_txs"` + ParamBlockNumber uint64 `db:"param_block_number"` + ParamTimestamp uint64 `db:"param_timestamp"` + ReceivedTimestamp time.Time `db:"received_timestamp"` + ParamRevertingTxHashes string `db:"param_reverting_tx_hashes"` + + CoinbaseDiff string `db:"coinbase_diff"` + TotalGasUsed uint64 `db:"total_gas_used"` + StateBlockNumber uint64 `db:"state_block_number"` + GasFees string `db:"gas_fees"` + EthSentToCoinbase string `db:"eth_sent_to_coinbase"` +} + +func SimulatedBundleToDbBundle(bundle *types.SimulatedBundle) DbBundle { + revertingTxHashes := make([]string, len(bundle.OriginalBundle.RevertingTxHashes)) + for i, rTxHash := range bundle.OriginalBundle.RevertingTxHashes { + revertingTxHashes[i] = rTxHash.String() + } + + signedTxsStrings := make([]string, len(bundle.OriginalBundle.Txs)) + for i, tx := range bundle.OriginalBundle.Txs { + signedTxsStrings[i] = tx.Hash().String() + } + + return DbBundle{ + BundleHash: bundle.OriginalBundle.Hash.String(), + + ParamSignedTxs: strings.Join(signedTxsStrings, ","), + ParamBlockNumber: bundle.OriginalBundle.BlockNumber.Uint64(), + ParamTimestamp: bundle.OriginalBundle.MinTimestamp, + ParamRevertingTxHashes: strings.Join(revertingTxHashes, ","), + + CoinbaseDiff: new(big.Rat).SetFrac(bundle.TotalEth, big.NewInt(1e18)).FloatString(18), + TotalGasUsed: bundle.TotalGasUsed, + StateBlockNumber: bundle.OriginalBundle.BlockNumber.Uint64(), + GasFees: new(big.Int).Mul(big.NewInt(int64(bundle.TotalGasUsed)), bundle.MevGasPrice).String(), + EthSentToCoinbase: new(big.Rat).SetFrac(bundle.EthSentToCoinbase, big.NewInt(1e18)).FloatString(18), + } +} diff --git a/builder/eth_service.go b/builder/eth_service.go index 6ea8b7d3ca61..48490b0ce57d 100644 --- a/builder/eth_service.go +++ b/builder/eth_service.go @@ -1,6 +1,7 @@ package builder import ( + "errors" "time" "github.com/ethereum/go-ethereum/common" @@ -11,7 +12,7 @@ import ( ) type IEthereumService interface { - BuildBlock(attrs *BuilderPayloadAttributes) (*beacon.ExecutableDataV1, *types.Block) + BuildBlock(attrs *BuilderPayloadAttributes, sealedBlockCallback func(*types.Block, []types.SimulatedBundle)) error GetBlockByHash(hash common.Hash) *types.Block Synced() bool } @@ -20,10 +21,12 @@ type testEthereumService struct { synced bool testExecutableData *beacon.ExecutableDataV1 testBlock *types.Block + testBundlesMerged []types.SimulatedBundle } -func (t *testEthereumService) BuildBlock(attrs *BuilderPayloadAttributes) (*beacon.ExecutableDataV1, *types.Block) { - return t.testExecutableData, t.testBlock +func (t *testEthereumService) BuildBlock(attrs *BuilderPayloadAttributes, sealedBlockCallback func(*types.Block, []types.SimulatedBundle)) error { + sealedBlockCallback(t.testBlock, t.testBundlesMerged) + return nil } func (t *testEthereumService) GetBlockByHash(hash common.Hash) *types.Block { return t.testBlock } @@ -38,13 +41,13 @@ func NewEthereumService(eth *eth.Ethereum) *EthereumService { return &EthereumService{eth: eth} } -func (s *EthereumService) BuildBlock(attrs *BuilderPayloadAttributes) (*beacon.ExecutableDataV1, *types.Block) { +func (s *EthereumService) BuildBlock(attrs *BuilderPayloadAttributes, sealedBlockCallback func(*types.Block, []types.SimulatedBundle)) error { // 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) + resCh, err := s.eth.Miner().GetSealingBlockAsync(attrs.HeadHash, uint64(attrs.Timestamp), attrs.SuggestedFeeRecipient, attrs.GasLimit, attrs.Random, false, sealedBlockCallback) if err != nil { log.Error("Failed to create async sealing payload", "err", err) - return nil, nil + return err } timer := time.NewTimer(4 * time.Second) @@ -53,13 +56,12 @@ func (s *EthereumService) BuildBlock(attrs *BuilderPayloadAttributes) (*beacon.E select { case block := <-resCh: if block == nil { - log.Error("received nil block from sealing work") - return nil, nil + return errors.New("received nil block from sealing work") } - return beacon.BlockToExecutableData(block), block + return nil case <-timer.C: log.Error("timeout waiting for block", "parent hash", attrs.HeadHash, "slot", attrs.Slot) - return nil, nil + return errors.New("timeout waiting for block result") } } diff --git a/builder/eth_service_test.go b/builder/eth_service_test.go index d3e0daad8eea..9443a19f0820 100644 --- a/builder/eth_service_test.go +++ b/builder/eth_service_test.go @@ -9,6 +9,7 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/beacon" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth" @@ -90,13 +91,18 @@ func TestBuildBlock(t *testing.T) { } service := NewEthereumService(ethservice) - executableData, block := service.BuildBlock(testPayloadAttributes) + service.eth.APIBackend.Miner().SetEtherbase(common.Address{0x05, 0x11}) - //require.Equal(t, common.Address{0x04, 0x10}, executableData.FeeRecipient) - require.Equal(t, common.Hash{0x05, 0x10}, executableData.Random) - require.Equal(t, parent.Hash(), executableData.ParentHash) - require.Equal(t, parent.Time()+1, executableData.Timestamp) - require.Equal(t, block.ParentHash(), parent.Hash()) - require.Equal(t, block.Hash(), executableData.BlockHash) - require.Equal(t, block.Profit.Uint64(), uint64(0)) + err := service.BuildBlock(testPayloadAttributes, func(block *types.Block, _ []types.SimulatedBundle) { + executableData := beacon.BlockToExecutableData(block) + require.Equal(t, common.Address{0x05, 0x11}, executableData.FeeRecipient) + require.Equal(t, common.Hash{0x05, 0x10}, executableData.Random) + require.Equal(t, parent.Hash(), executableData.ParentHash) + require.Equal(t, parent.Time()+1, executableData.Timestamp) + require.Equal(t, block.ParentHash(), parent.Hash()) + require.Equal(t, block.Hash(), executableData.BlockHash) + require.Equal(t, block.Profit.Uint64(), uint64(0)) + }) + + require.NoError(t, err) } diff --git a/builder/local_relay_test.go b/builder/local_relay_test.go index 3e706f5bfedc..b101029b4816 100644 --- a/builder/local_relay_test.go +++ b/builder/local_relay_test.go @@ -29,7 +29,7 @@ func newTestBackend(t *testing.T, forkchoiceData *beacon.ExecutableDataV1, block beaconClient := &testBeaconClient{validator: validator} localRelay := NewLocalRelay(sk, beaconClient, bDomain, cDomain, ForkData{}, true) ethService := &testEthereumService{synced: true, testExecutableData: forkchoiceData, testBlock: block} - backend := NewBuilder(sk, beaconClient, localRelay, bDomain, ethService) + backend := NewBuilder(sk, NilDbService{}, beaconClient, localRelay, bDomain, ethService) // service := NewService("127.0.0.1:31545", backend) return backend, localRelay, validator @@ -115,15 +115,16 @@ func TestGetHeader(t *testing.T) { forkchoiceData := &beacon.ExecutableDataV1{ ParentHash: common.HexToHash("0xafafafa"), FeeRecipient: common.Address{0x01}, - BlockHash: common.HexToHash("0xbfbfbfb"), + LogsBloom: types.Bloom{0x00, 0x05, 0x10}.Bytes(), + BlockHash: common.HexToHash("0x24e6998e4d2b4fd85f7f0d27ac4b87aaf0ec18e156e4b6e194ab5dadee0cd004"), BaseFeePerGas: big.NewInt(12), ExtraData: []byte{}, - LogsBloom: []byte{0x00, 0x05, 0x10}, - } - forkchoiceBlock := &types.Block{ - Profit: big.NewInt(10), } + forkchoiceBlock, err := beacon.ExecutableDataToBlock(*forkchoiceData) + require.NoError(t, err) + forkchoiceBlock.Profit = big.NewInt(10) + backend, relay, validator := newTestBackend(t, forkchoiceData, forkchoiceBlock) path := fmt.Sprintf("/eth/v1/builder/header/%d/%s/%s", 0, forkchoiceData.ParentHash.Hex(), validator.Pk.String()) @@ -147,7 +148,7 @@ func TestGetHeader(t *testing.T) { require.Equal(t, http.StatusOK, rr.Code) bid := new(boostTypes.GetHeaderResponse) - err := json.Unmarshal(rr.Body.Bytes(), bid) + err = json.Unmarshal(rr.Body.Bytes(), bid) require.NoError(t, err) executionPayload, err := executableDataToExecutionPayload(forkchoiceData) @@ -174,13 +175,15 @@ func TestGetPayload(t *testing.T) { forkchoiceData := &beacon.ExecutableDataV1{ ParentHash: common.HexToHash("0xafafafa"), FeeRecipient: common.Address{0x01}, - BlockHash: common.HexToHash("0xbfbfbfb"), + LogsBloom: types.Bloom{}.Bytes(), + BlockHash: common.HexToHash("0xc4a012b67027b3ab6c00acd31aeee24aa1515d6a5d7e81b0ee2e69517fdc387f"), BaseFeePerGas: big.NewInt(12), ExtraData: []byte{}, } - forkchoiceBlock := &types.Block{ - Profit: big.NewInt(10), - } + + forkchoiceBlock, err := beacon.ExecutableDataToBlock(*forkchoiceData) + require.NoError(t, err) + forkchoiceBlock.Profit = big.NewInt(10) backend, relay, validator := newTestBackend(t, forkchoiceData, forkchoiceBlock) @@ -192,7 +195,7 @@ func TestGetPayload(t *testing.T) { require.Equal(t, http.StatusOK, rr.Code) bid := new(boostTypes.GetHeaderResponse) - err := json.Unmarshal(rr.Body.Bytes(), bid) + err = json.Unmarshal(rr.Body.Bytes(), bid) require.NoError(t, err) // Create request payload diff --git a/builder/service.go b/builder/service.go index 0e459decd9f6..131b8d5640e9 100644 --- a/builder/service.go +++ b/builder/service.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "net/http" + "os" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" @@ -159,7 +160,15 @@ func Register(stack *node.Node, backend *eth.Ethereum, cfg *BuilderConfig) error ethereumService := NewEthereumService(backend) - builderBackend := NewBuilder(builderSk, beaconClient, relay, builderSigningDomain, ethereumService) + // TODO: move to proper flags + var ds IDatabaseService + ds, err = NewDatabaseService(os.Getenv("FLASHBOTS_POSTGRES_DSN")) + if err != nil { + log.Error("could not connect to the DB", "err", err) + ds = NilDbService{} + } + + builderBackend := NewBuilder(builderSk, ds, beaconClient, relay, builderSigningDomain, ethereumService) builderService := NewService(cfg.ListenAddr, localRelay, builderBackend) builderService.Start() diff --git a/core/txpool/txpool.go b/core/txpool/txpool.go index a02a6117dcfd..5dccb489c275 100644 --- a/core/txpool/txpool.go +++ b/core/txpool/txpool.go @@ -36,6 +36,7 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/params" + "golang.org/x/crypto/sha3" ) const ( @@ -620,6 +621,12 @@ func (pool *TxPool) MevBundles(blockNumber *big.Int, blockTimestamp uint64) ([]t // AddMevBundle adds a mev bundle to the pool func (pool *TxPool) AddMevBundle(txs types.Transactions, blockNumber *big.Int, minTimestamp, maxTimestamp uint64, revertingTxHashes []common.Hash) error { + bundleHasher := sha3.NewLegacyKeccak256() + for _, tx := range txs { + bundleHasher.Write(tx.Hash().Bytes()) + } + bundleHash := common.BytesToHash(bundleHasher.Sum(nil)) + pool.mu.Lock() defer pool.mu.Unlock() @@ -629,6 +636,7 @@ func (pool *TxPool) AddMevBundle(txs types.Transactions, blockNumber *big.Int, m MinTimestamp: minTimestamp, MaxTimestamp: maxTimestamp, RevertingTxHashes: revertingTxHashes, + Hash: bundleHash, }) return nil } diff --git a/core/types/transaction.go b/core/types/transaction.go index 70d1106344e5..1ec1470b4a0e 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -664,4 +664,13 @@ type MevBundle struct { MinTimestamp uint64 MaxTimestamp uint64 RevertingTxHashes []common.Hash + Hash common.Hash +} + +type SimulatedBundle struct { + MevGasPrice *big.Int + TotalEth *big.Int + EthSentToCoinbase *big.Int + TotalGasUsed uint64 + OriginalBundle MevBundle } diff --git a/eth/block-validation/api_test.go b/eth/block-validation/api_test.go index 961e0911815e..98f8646c029e 100644 --- a/eth/block-validation/api_test.go +++ b/eth/block-validation/api_test.go @@ -210,7 +210,7 @@ func startEthService(t *testing.T, genesis *core.Genesis, blocks []*types.Block) } func assembleBlock(api *BlockValidationAPI, parentHash common.Hash, params *beacon.PayloadAttributesV1) (*beacon.ExecutableDataV1, error) { - block, err := api.eth.Miner().GetSealingBlockSync(parentHash, params.Timestamp, params.SuggestedFeeRecipient, 0, params.Random, false) + block, err := api.eth.Miner().GetSealingBlockSync(parentHash, params.Timestamp, params.SuggestedFeeRecipient, 0, params.Random, false, nil) if err != nil { return nil, err } diff --git a/go.mod b/go.mod index cb0e7769e5e6..091be76c0d10 100644 --- a/go.mod +++ b/go.mod @@ -42,8 +42,10 @@ require ( github.com/influxdata/influxdb-client-go/v2 v2.4.0 github.com/jackpal/go-nat-pmp v1.0.2 github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e + github.com/jmoiron/sqlx v1.3.5 github.com/julienschmidt/httprouter v1.3.0 github.com/karalabe/usb v0.0.2 + github.com/lib/pq v1.2.0 github.com/mattn/go-colorable v0.1.9 github.com/mattn/go-isatty v0.0.14 github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416 diff --git a/go.sum b/go.sum index 361198127593..294b59dfa147 100644 --- a/go.sum +++ b/go.sum @@ -270,6 +270,8 @@ github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e h1:UvSe12bq+U github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e/go.mod h1:G1CVv03EnqU1wYL2dFwXxW2An0az9JTl/ZsqXQeBlkU= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= +github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= @@ -309,6 +311,8 @@ github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= @@ -330,6 +334,7 @@ github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzp github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mattn/go-tty v0.0.0-20180907095812-13ff1204f104/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g= diff --git a/miner/multi_worker.go b/miner/multi_worker.go index cd0068c1e901..4ab9a3619c1c 100644 --- a/miner/multi_worker.go +++ b/miner/multi_worker.go @@ -91,11 +91,11 @@ type resChPair struct { errCh chan error } -func (w *multiWorker) GetSealingBlockAsync(parent common.Hash, timestamp uint64, coinbase common.Address, gasLimit uint64, random common.Hash, noTxs bool, noExtra bool) (chan *types.Block, error) { +func (w *multiWorker) GetSealingBlockAsync(parent common.Hash, timestamp uint64, coinbase common.Address, gasLimit uint64, random common.Hash, noTxs bool, noExtra bool, blockHook func(*types.Block, []types.SimulatedBundle)) (chan *types.Block, error) { resChans := []resChPair{} for _, worker := range append(w.workers, w.regularWorker) { - resCh, errCh, err := worker.getSealingBlock(parent, timestamp, coinbase, gasLimit, random, noTxs, noExtra) + resCh, errCh, err := worker.getSealingBlock(parent, timestamp, coinbase, gasLimit, random, noTxs, noExtra, blockHook) if err != nil { log.Error("could not start async block construction", "isFlashbotsWorker", worker.flashbots.isFlashbots, "#bundles", worker.flashbots.maxMergedBundles) continue @@ -128,8 +128,8 @@ func (w *multiWorker) GetSealingBlockAsync(parent common.Hash, timestamp uint64, return resCh, nil } -func (w *multiWorker) GetSealingBlockSync(parent common.Hash, timestamp uint64, coinbase common.Address, gasLimit uint64, random common.Hash, noTxs bool, noExtra bool) (*types.Block, error) { - resCh, err := w.GetSealingBlockAsync(parent, timestamp, coinbase, gasLimit, random, noTxs, noExtra) +func (w *multiWorker) GetSealingBlockSync(parent common.Hash, timestamp uint64, coinbase common.Address, gasLimit uint64, random common.Hash, noTxs bool, noExtra bool, blockHook func(*types.Block, []types.SimulatedBundle)) (*types.Block, error) { + resCh, err := w.GetSealingBlockAsync(parent, timestamp, coinbase, gasLimit, random, noTxs, noExtra, blockHook) if err != nil { return nil, err } diff --git a/miner/worker.go b/miner/worker.go index 9ee5a9f2c3a8..4c326ebea3f8 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -1275,8 +1275,7 @@ func (w *worker) prepareWork(genParams *generateParams) (*environment, error) { // fillTransactions retrieves the pending transactions from the txpool and fills them // into the given sealing block. The transaction selection and ordering strategy can // be customized with the plugin in the future. - -func (w *worker) fillTransactions(interrupt *int32, env *environment, validatorCoinbase *common.Address) error { +func (w *worker) fillTransactions(interrupt *int32, env *environment, validatorCoinbase *common.Address) (error, []types.SimulatedBundle) { // Split the pending transactions into locals and remotes // Fill the block with all available pending transactions. pending := w.eth.TxPool().Pending(true) @@ -1294,43 +1293,47 @@ func (w *worker) fillTransactions(interrupt *int32, env *environment, validatorC if validatorCoinbase != nil { builderCoinbaseBalanceBefore = env.state.GetBalance(w.coinbase) if err := env.gasPool.SubGas(params.TxGas); err != nil { - return err + return err, nil } } + + var blockBundles []types.SimulatedBundle if w.flashbots.isFlashbots { bundles, err := w.eth.TxPool().MevBundles(env.header.Number, env.header.Time) if err != nil { log.Error("Failed to fetch pending transactions", "err", err) - return err + return err, nil } - bundleTxs, bundle, numBundles, err := w.generateFlashbotsBundle(env, bundles, pending) + bundleTxs, bundle, mergedBundles, numBundles, err := w.generateFlashbotsBundle(env, bundles, pending) if err != nil { log.Error("Failed to generate flashbots bundle", "err", err) - return err + return err, nil } - log.Info("Flashbots bundle", "ethToCoinbase", ethIntToFloat(bundle.totalEth), "gasUsed", bundle.totalGasUsed, "bundleScore", bundle.mevGasPrice, "bundleLength", len(bundleTxs), "numBundles", numBundles, "worker", w.flashbots.maxMergedBundles) + log.Info("Flashbots bundle", "ethToCoinbase", ethIntToFloat(bundle.TotalEth), "gasUsed", bundle.TotalGasUsed, "bundleScore", bundle.MevGasPrice, "bundleLength", len(bundleTxs), "numBundles", numBundles, "worker", w.flashbots.maxMergedBundles) if len(bundleTxs) == 0 { - return errors.New("no bundles to apply") + return errors.New("no bundles to apply"), nil } if err := w.commitBundle(env, bundleTxs, interrupt); err != nil { - return err + return err, nil } - env.profit.Add(env.profit, bundle.ethSentToCoinbase) + blockBundles = mergedBundles + env.profit.Add(env.profit, bundle.EthSentToCoinbase) } if len(localTxs) > 0 { txs := types.NewTransactionsByPriceAndNonce(env.signer, localTxs, env.header.BaseFee) if err := w.commitTransactions(env, txs, interrupt); err != nil { - return err + return err, nil } } if len(remoteTxs) > 0 { txs := types.NewTransactionsByPriceAndNonce(env.signer, remoteTxs, env.header.BaseFee) if err := w.commitTransactions(env, txs, interrupt); err != nil { - return err + return err, nil } } + if validatorCoinbase != nil && w.config.BuilderTxSigningKey != nil { builderCoinbaseBalanceAfter := env.state.GetBalance(w.coinbase) log.Info("Before creating validator profit", "validatorCoinbase", validatorCoinbase.String(), "builderCoinbase", w.coinbase.String(), "builderCoinbaseBalanceBefore", builderCoinbaseBalanceBefore.String(), "builderCoinbaseBalanceAfter", builderCoinbaseBalanceAfter.String()) @@ -1341,7 +1344,7 @@ func (w *worker) fillTransactions(interrupt *int32, env *environment, validatorC tx, err := w.createProposerPayoutTx(env, validatorCoinbase, profit) if err != nil { log.Error("Proposer payout create tx failed", "err", err) - return fmt.Errorf("proposer payout create tx failed - %v", err) + return fmt.Errorf("proposer payout create tx failed - %v", err), nil } if tx != nil { log.Info("Proposer payout create tx succeeded, proceeding to commit tx") @@ -1349,20 +1352,21 @@ func (w *worker) fillTransactions(interrupt *int32, env *environment, validatorC _, err = w.commitTransaction(env, tx) if err != nil { log.Error("Proposer payout commit tx failed", "hash", tx.Hash().String(), "err", err) - return fmt.Errorf("proposer payout commit tx failed - %v", err) + return fmt.Errorf("proposer payout commit tx failed - %v", err), nil } log.Info("Proposer payout commit tx succeeded", "hash", tx.Hash().String()) env.tcount++ } else { - return errors.New("proposer payout create tx failed due to tx is nil") + return errors.New("proposer payout create tx failed due to tx is nil"), nil } } else { log.Warn("Proposer payout create tx failed due to not enough balance", "profit", profit.String()) - return errors.New("proposer payout create tx failed due to not enough balance") + return errors.New("proposer payout create tx failed due to not enough balance"), nil } } - return nil + + return nil, blockBundles } // generateWork generates a sealing block based on the given parameters. @@ -1373,6 +1377,7 @@ func (w *worker) generateWork(params *generateParams) (*types.Block, *big.Int, e } defer work.discard() + var blockBundles []types.SimulatedBundle if !params.noTxs { interrupt := new(int32) timer := time.AfterFunc(w.newpayloadTimeout, func() { @@ -1384,6 +1389,7 @@ func (w *worker) generateWork(params *generateParams) (*types.Block, *big.Int, e if errors.Is(err, errBlockInterruptedByTimeout) { log.Warn("Block building is interrupted", "allowance", common.PrettyDuration(w.newpayloadTimeout)) } + blockBundles = mergedBundles } block, err := w.engine.FinalizeAndAssemble(w.chain, work.header, work.state, work.txs, work.unclelist(), work.receipts, params.withdrawals) if err != nil { @@ -1535,28 +1541,23 @@ func (w *worker) isTTDReached(header *types.Header) bool { return td != nil && ttd != nil && td.Cmp(ttd) >= 0 } -type simulatedBundle struct { - mevGasPrice *big.Int - totalEth *big.Int - ethSentToCoinbase *big.Int - totalGasUsed uint64 - originalBundle types.MevBundle -} +type simulatedBundle = types.SimulatedBundle -func (w *worker) generateFlashbotsBundle(env *environment, bundles []types.MevBundle, pendingTxs map[common.Address]types.Transactions) (types.Transactions, simulatedBundle, int, error) { +func (w *worker) generateFlashbotsBundle(env *environment, bundles []types.MevBundle, pendingTxs map[common.Address]types.Transactions) (types.Transactions, simulatedBundle, []types.SimulatedBundle, int, error) { simulatedBundles, err := w.simulateBundles(env, bundles, pendingTxs) if err != nil { - return nil, simulatedBundle{}, 0, err + return nil, simulatedBundle{}, nil, 0, err } sort.SliceStable(simulatedBundles, func(i, j int) bool { - return simulatedBundles[j].mevGasPrice.Cmp(simulatedBundles[i].mevGasPrice) < 0 + return simulatedBundles[j].MevGasPrice.Cmp(simulatedBundles[i].MevGasPrice) < 0 }) return w.mergeBundles(env, simulatedBundles, pendingTxs) } -func (w *worker) mergeBundles(env *environment, bundles []simulatedBundle, pendingTxs map[common.Address]types.Transactions) (types.Transactions, simulatedBundle, int, error) { +func (w *worker) mergeBundles(env *environment, bundles []simulatedBundle, pendingTxs map[common.Address]types.Transactions) (types.Transactions, simulatedBundle, []types.SimulatedBundle, int, error) { + mergedBundles := []types.SimulatedBundle{} finalBundle := types.Transactions{} currentState := env.state.Copy() @@ -1566,8 +1567,8 @@ func (w *worker) mergeBundles(env *environment, bundles []simulatedBundle, pendi var prevGasPool *core.GasPool mergedBundle := simulatedBundle{ - totalEth: new(big.Int), - ethSentToCoinbase: new(big.Int), + TotalEth: new(big.Int), + EthSentToCoinbase: new(big.Int), } count := 0 @@ -1576,22 +1577,22 @@ func (w *worker) mergeBundles(env *environment, bundles []simulatedBundle, pendi prevGasPool = new(core.GasPool).AddGas(gasPool.Gas()) // the floor gas price is 99/100 what was simulated at the top of the block - floorGasPrice := new(big.Int).Mul(bundle.mevGasPrice, big.NewInt(99)) + floorGasPrice := new(big.Int).Mul(bundle.MevGasPrice, big.NewInt(99)) floorGasPrice = floorGasPrice.Div(floorGasPrice, big.NewInt(100)) - simmed, err := w.computeBundleGas(env, bundle.originalBundle, currentState, gasPool, pendingTxs, len(finalBundle)) - if err != nil || simmed.mevGasPrice.Cmp(floorGasPrice) <= 0 { + simmed, err := w.computeBundleGas(env, bundle.OriginalBundle, currentState, gasPool, pendingTxs, len(finalBundle)) + if err != nil || simmed.MevGasPrice.Cmp(floorGasPrice) <= 0 { currentState = prevState gasPool = prevGasPool continue } - log.Info("Included bundle", "ethToCoinbase", ethIntToFloat(simmed.totalEth), "gasUsed", simmed.totalGasUsed, "bundleScore", simmed.mevGasPrice, "bundleLength", len(simmed.originalBundle.Txs), "worker", w.flashbots.maxMergedBundles) - - finalBundle = append(finalBundle, bundle.originalBundle.Txs...) - mergedBundle.totalEth.Add(mergedBundle.totalEth, simmed.totalEth) - mergedBundle.ethSentToCoinbase.Add(mergedBundle.ethSentToCoinbase, simmed.ethSentToCoinbase) - mergedBundle.totalGasUsed += simmed.totalGasUsed + log.Info("Included bundle", "ethToCoinbase", ethIntToFloat(simmed.TotalEth), "gasUsed", simmed.TotalGasUsed, "bundleScore", simmed.MevGasPrice, "bundleLength", len(simmed.OriginalBundle.Txs), "worker", w.flashbots.maxMergedBundles) + mergedBundles = append(mergedBundles, simmed) + finalBundle = append(finalBundle, bundle.OriginalBundle.Txs...) + mergedBundle.TotalEth.Add(mergedBundle.TotalEth, simmed.TotalEth) + mergedBundle.EthSentToCoinbase.Add(mergedBundle.EthSentToCoinbase, simmed.EthSentToCoinbase) + mergedBundle.TotalGasUsed += simmed.TotalGasUsed count++ if count >= w.flashbots.maxMergedBundles { @@ -1600,15 +1601,15 @@ func (w *worker) mergeBundles(env *environment, bundles []simulatedBundle, pendi } if len(finalBundle) == 0 || count != w.flashbots.maxMergedBundles { - return nil, simulatedBundle{}, count, nil + return nil, simulatedBundle{}, nil, count, nil } return finalBundle, simulatedBundle{ - mevGasPrice: new(big.Int).Div(mergedBundle.totalEth, new(big.Int).SetUint64(mergedBundle.totalGasUsed)), - totalEth: mergedBundle.totalEth, - ethSentToCoinbase: mergedBundle.ethSentToCoinbase, - totalGasUsed: mergedBundle.totalGasUsed, - }, count, nil + MevGasPrice: new(big.Int).Div(mergedBundle.TotalEth, new(big.Int).SetUint64(mergedBundle.TotalGasUsed)), + TotalEth: mergedBundle.TotalEth, + EthSentToCoinbase: mergedBundle.EthSentToCoinbase, + TotalGasUsed: mergedBundle.TotalGasUsed, + }, mergedBundles, count, nil } func (w *worker) simulateBundles(env *environment, bundles []types.MevBundle, pendingTxs map[common.Address]types.Transactions) ([]simulatedBundle, error) { @@ -1716,11 +1717,11 @@ func (w *worker) computeBundleGas(env *environment, bundle types.MevBundle, stat totalEth := new(big.Int).Add(ethSentToCoinbase, gasFees) return simulatedBundle{ - mevGasPrice: new(big.Int).Div(totalEth, new(big.Int).SetUint64(totalGasUsed)), - totalEth: totalEth, - ethSentToCoinbase: ethSentToCoinbase, - totalGasUsed: totalGasUsed, - originalBundle: bundle, + MevGasPrice: new(big.Int).Div(totalEth, new(big.Int).SetUint64(totalGasUsed)), + TotalEth: totalEth, + EthSentToCoinbase: ethSentToCoinbase, + TotalGasUsed: totalGasUsed, + OriginalBundle: bundle, }, nil }