Skip to content
This repository was archived by the owner on Oct 25, 2024. It is now read-only.

Commit 1890262

Browse files
Ruteribhakiyakalimuthu
authored andcommitted
Implement pushing blocks data to the DB (#18)
Co-authored-by: Bhakiyaraj Kalimuthu <[email protected]>
1 parent cb82f07 commit 1890262

16 files changed

+436
-108
lines changed

builder/builder.go

+12-13
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ type IBuilder interface {
4141
}
4242

4343
type Builder struct {
44+
ds IDatabaseService
4445
beaconClient IBeaconClient
4546
relay IRelay
4647
eth IEthereumService
@@ -55,12 +56,13 @@ type Builder struct {
5556
bestBlockProfit *big.Int
5657
}
5758

58-
func NewBuilder(sk *bls.SecretKey, bc IBeaconClient, relay IRelay, builderSigningDomain boostTypes.Domain, eth IEthereumService) *Builder {
59+
func NewBuilder(sk *bls.SecretKey, ds IDatabaseService, bc IBeaconClient, relay IRelay, builderSigningDomain boostTypes.Domain, eth IEthereumService) *Builder {
5960
pkBytes := bls.PublicKeyFromSecretKey(sk).Compress()
6061
pk := boostTypes.PublicKey{}
6162
pk.FromSlice(pkBytes)
6263

6364
return &Builder{
65+
ds: ds,
6466
beaconClient: bc,
6567
relay: relay,
6668
eth: eth,
@@ -73,7 +75,7 @@ func NewBuilder(sk *bls.SecretKey, bc IBeaconClient, relay IRelay, builderSignin
7375
}
7476
}
7577

76-
func (b *Builder) onSealedBlock(executableData *beacon.ExecutableDataV1, block *types.Block, proposerPubkey boostTypes.PublicKey, proposerFeeRecipient boostTypes.Address, attrs *BuilderPayloadAttributes) error {
78+
func (b *Builder) onSealedBlock(block *types.Block, bundles []types.SimulatedBundle, proposerPubkey boostTypes.PublicKey, proposerFeeRecipient boostTypes.Address, attrs *BuilderPayloadAttributes) error {
7779
b.bestMu.Lock()
7880
defer b.bestMu.Unlock()
7981

@@ -88,6 +90,7 @@ func (b *Builder) onSealedBlock(executableData *beacon.ExecutableDataV1, block *
8890
}
8991
}
9092

93+
executableData := beacon.BlockToExecutableData(block)
9194
payload, err := executableDataToExecutionPayload(executableData)
9295
if err != nil {
9396
log.Error("could not format execution payload", "err", err)
@@ -133,6 +136,7 @@ func (b *Builder) onSealedBlock(executableData *beacon.ExecutableDataV1, block *
133136

134137
b.bestBlockProfit.Set(block.Profit)
135138

139+
go b.ds.ConsumeBuiltBlock(block, bundles, &blockBidMsg)
136140
return nil
137141
}
138142

@@ -166,20 +170,15 @@ func (b *Builder) OnPayloadAttribute(attrs *BuilderPayloadAttributes) error {
166170
return errors.New("parent block not found in blocktree")
167171
}
168172

169-
firstBlockResult := b.resubmitter.newTask(12*time.Second, time.Second, func() error {
170-
executableData, block := b.eth.BuildBlock(attrs)
171-
if executableData == nil || block == nil {
172-
log.Error("did not receive the payload")
173-
return errors.New("did not receive the payload")
174-
}
175-
176-
err := b.onSealedBlock(executableData, block, proposerPubkey, vd.FeeRecipient, attrs)
173+
blockHook := func(block *types.Block, bundles []types.SimulatedBundle) {
174+
err := b.onSealedBlock(block, bundles, proposerPubkey, vd.FeeRecipient, attrs)
177175
if err != nil {
178-
log.Error("could not run block hook", "err", err)
179-
return err
176+
log.Error("could not run sealed block hook", "err", err)
180177
}
178+
}
181179

182-
return nil
180+
firstBlockResult := b.resubmitter.newTask(12*time.Second, time.Second, func() error {
181+
return b.eth.BuildBlock(attrs, blockHook)
183182
})
184183

185184
return firstBlockResult

builder/builder_test.go

+9-9
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ func TestOnPayloadAttributes(t *testing.T) {
4747
FeeRecipient: common.Address(feeRecipient),
4848
StateRoot: common.Hash{0x07, 0x16},
4949
ReceiptsRoot: common.Hash{0x08, 0x20},
50-
LogsBloom: hexutil.MustDecode("0x000000000000000000000000000000"),
50+
LogsBloom: types.Bloom{}.Bytes(),
5151
Number: uint64(10),
5252
GasLimit: uint64(50),
5353
GasUsed: uint64(100),
@@ -56,13 +56,13 @@ func TestOnPayloadAttributes(t *testing.T) {
5656

5757
BaseFeePerGas: big.NewInt(16),
5858

59-
BlockHash: common.Hash{0x09, 0xff},
59+
BlockHash: common.HexToHash("0xca4147f0d4150183ece9155068f34ee3c375448814e4ca557d482b1d40ee5407"),
6060
Transactions: [][]byte{},
6161
}
6262

63-
testBlock := &types.Block{
64-
Profit: big.NewInt(10),
65-
}
63+
testBlock, err := beacon.ExecutableDataToBlock(*testExecutableData)
64+
require.NoError(t, err)
65+
testBlock.Profit = big.NewInt(10)
6666

6767
testPayloadAttributes := &BuilderPayloadAttributes{
6868
Timestamp: hexutil.Uint64(104),
@@ -74,7 +74,7 @@ func TestOnPayloadAttributes(t *testing.T) {
7474

7575
testEthService := &testEthereumService{synced: true, testExecutableData: testExecutableData, testBlock: testBlock}
7676

77-
builder := NewBuilder(sk, &testBeacon, &testRelay, bDomain, testEthService)
77+
builder := NewBuilder(sk, NilDbService{}, &testBeacon, &testRelay, bDomain, testEthService)
7878

7979
builder.OnPayloadAttribute(testPayloadAttributes)
8080

@@ -85,14 +85,14 @@ func TestOnPayloadAttributes(t *testing.T) {
8585
expectedMessage := boostTypes.BidTrace{
8686
Slot: uint64(25),
8787
ParentHash: boostTypes.Hash{0x02, 0x03},
88-
BlockHash: boostTypes.Hash{0x09, 0xff},
8988
BuilderPubkey: builder.builderPublicKey,
9089
ProposerPubkey: expectedProposerPubkey,
9190
ProposerFeeRecipient: feeRecipient,
9291
GasLimit: uint64(50),
9392
GasUsed: uint64(100),
9493
Value: boostTypes.U256Str{0x0a},
9594
}
95+
expectedMessage.BlockHash.FromSlice(hexutil.MustDecode("0xca4147f0d4150183ece9155068f34ee3c375448814e4ca557d482b1d40ee5407")[:])
9696

9797
require.Equal(t, expectedMessage, *testRelay.submittedMsg.Message)
9898

@@ -109,13 +109,13 @@ func TestOnPayloadAttributes(t *testing.T) {
109109
Timestamp: testExecutableData.Timestamp,
110110
ExtraData: hexutil.MustDecode("0x0042fafc"),
111111
BaseFeePerGas: boostTypes.U256Str{0x10},
112-
BlockHash: boostTypes.Hash{0x09, 0xff},
112+
BlockHash: expectedMessage.BlockHash,
113113
Transactions: []hexutil.Bytes{},
114114
}
115115

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

118-
expectedSignature, err := boostTypes.HexToSignature("0xb086abc231a515559128122a6618ad316a76195ad39aa28195c9e8921b98561ca4fd12e2e1ea8d50d8e22f7e36d42ee1084fef26672beceda7650a87061e412d7742705077ac3af3ca1a1c3494eccb22fe7c234fd547a285ba699ff87f0e7759")
118+
expectedSignature, err := boostTypes.HexToSignature("0xad09f171b1da05636acfc86778c319af69e39c79515d44bdfed616ba2ef677ffd4d155d87b3363c6bae651ce1e92786216b75f1ac91dd65f3b1d1902bf8485e742170732dd82ffdf4decb0151eeb7926dd053efa9794b2ebed1a203e62bb13e9")
119119

120120
require.NoError(t, err)
121121
require.Equal(t, expectedSignature, testRelay.submittedMsg.Signature)

builder/database.go

+136
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
package builder
2+
3+
import (
4+
"context"
5+
"database/sql"
6+
"math/big"
7+
"time"
8+
9+
"github.com/ethereum/go-ethereum/core/types"
10+
"github.com/ethereum/go-ethereum/log"
11+
boostTypes "github.com/flashbots/go-boost-utils/types"
12+
"github.com/jmoiron/sqlx"
13+
_ "github.com/lib/pq"
14+
)
15+
16+
type IDatabaseService interface {
17+
ConsumeBuiltBlock(block *types.Block, bundles []types.SimulatedBundle, bidTrace *boostTypes.BidTrace)
18+
}
19+
20+
type NilDbService struct{}
21+
22+
func (NilDbService) ConsumeBuiltBlock(*types.Block, []types.SimulatedBundle, *boostTypes.BidTrace) {}
23+
24+
type DatabaseService struct {
25+
db *sqlx.DB
26+
27+
insertBuiltBlockStmt *sqlx.NamedStmt
28+
insertBlockBuiltBundleNoIdStmt *sqlx.NamedStmt
29+
insertBlockBuiltBundleWithIdStmt *sqlx.NamedStmt
30+
insertMissingBundleStmt *sqlx.NamedStmt
31+
}
32+
33+
func NewDatabaseService(postgresDSN string) (*DatabaseService, error) {
34+
db, err := sqlx.Connect("postgres", postgresDSN)
35+
if err != nil {
36+
return nil, err
37+
}
38+
39+
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")
40+
if err != nil {
41+
return nil, err
42+
}
43+
44+
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")
45+
if err != nil {
46+
return nil, err
47+
}
48+
49+
insertBlockBuiltBundleWithIdStmt, err := db.PrepareNamed("insert into built_blocks_bundles (block_id, bundle_id) select :block_id, :bundle_id returning bundle_id")
50+
if err != nil {
51+
return nil, err
52+
}
53+
54+
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")
55+
if err != nil {
56+
return nil, err
57+
}
58+
59+
return &DatabaseService{
60+
db: db,
61+
insertBuiltBlockStmt: insertBuiltBlockStmt,
62+
insertBlockBuiltBundleNoIdStmt: insertBlockBuiltBundleNoIdStmt,
63+
insertBlockBuiltBundleWithIdStmt: insertBlockBuiltBundleWithIdStmt,
64+
insertMissingBundleStmt: insertMissingBundleStmt,
65+
}, nil
66+
}
67+
68+
func (ds *DatabaseService) ConsumeBuiltBlock(block *types.Block, bundles []types.SimulatedBundle, bidTrace *boostTypes.BidTrace) {
69+
tx, err := ds.db.Beginx()
70+
71+
blockData := BuiltBlock{
72+
BlockNumber: block.NumberU64(),
73+
Profit: new(big.Rat).SetFrac(block.Profit, big.NewInt(1e18)).FloatString(18),
74+
Slot: bidTrace.Slot,
75+
Hash: block.Hash().String(),
76+
GasLimit: block.GasLimit(),
77+
GasUsed: block.GasUsed(),
78+
BaseFee: block.BaseFee().Uint64(),
79+
ParentHash: block.ParentHash().String(),
80+
ProposerPubkey: bidTrace.ProposerPubkey.String(),
81+
ProposerFeeRecipient: bidTrace.ProposerFeeRecipient.String(),
82+
BuilderPubkey: bidTrace.BuilderPubkey.String(),
83+
Timestamp: block.Time(),
84+
}
85+
86+
ctx, cancel := context.WithTimeout(context.Background(), 12*time.Second)
87+
defer cancel()
88+
var blockId uint64
89+
if err = tx.NamedStmtContext(ctx, ds.insertBuiltBlockStmt).GetContext(ctx, &blockId, blockData); err != nil {
90+
log.Error("could not insert built block", "err", err)
91+
tx.Rollback()
92+
return
93+
}
94+
95+
for _, bundle := range bundles {
96+
bundleData := BuiltBlockBundle{
97+
BlockId: blockId,
98+
BundleId: nil,
99+
BlockNumber: blockData.BlockNumber,
100+
BundleHash: bundle.OriginalBundle.Hash.String(),
101+
}
102+
103+
var bundleId uint64
104+
err := tx.NamedStmtContext(ctx, ds.insertBlockBuiltBundleNoIdStmt).GetContext(ctx, &bundleId, bundleData)
105+
if err == nil {
106+
continue
107+
}
108+
109+
if err != sql.ErrNoRows {
110+
log.Error("could not insert bundle", "err", err)
111+
// Try anyway
112+
}
113+
114+
missingBundleData := SimulatedBundleToDbBundle(&bundle)
115+
err = ds.insertMissingBundleStmt.GetContext(ctx, &bundleId, missingBundleData) // not using the tx as it relies on the unique constraint!
116+
if err == nil {
117+
bundleData.BundleId = &bundleId
118+
_, err = tx.NamedStmtContext(ctx, ds.insertBlockBuiltBundleWithIdStmt).ExecContext(ctx, bundleData)
119+
if err != nil {
120+
log.Error("could not insert built block bundle after inserting missing bundle", "err", err)
121+
}
122+
} else if err == sql.ErrNoRows /* conflict, someone else inserted the bundle before we could */ {
123+
if err := tx.NamedStmtContext(ctx, ds.insertBlockBuiltBundleNoIdStmt).GetContext(ctx, &bundleId, bundleData); err != nil {
124+
log.Error("could not insert bundle on retry", "err", err)
125+
continue
126+
}
127+
} else {
128+
log.Error("could not insert missing bundle", "err", err)
129+
}
130+
}
131+
132+
err = tx.Commit()
133+
if err != nil {
134+
log.Error("could not commit DB trasnaction", "err", err)
135+
}
136+
}

builder/database_test.go

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package builder
2+
3+
import (
4+
"math/big"
5+
"os"
6+
"testing"
7+
8+
"github.com/ethereum/go-ethereum/common"
9+
"github.com/ethereum/go-ethereum/core/types"
10+
boostTypes "github.com/flashbots/go-boost-utils/types"
11+
"github.com/stretchr/testify/require"
12+
)
13+
14+
func TestDatabaseBlockInsertion(t *testing.T) {
15+
dsn := os.Getenv("FLASHBOTS_TEST_POSTGRES_DSN")
16+
if dsn == "" {
17+
return
18+
}
19+
20+
ds, err := NewDatabaseService(dsn)
21+
require.NoError(t, err)
22+
23+
_, err = ds.db.Exec("insert into bundles (id, param_block_number, bundle_hash) values (10, 20, '0x1078')")
24+
require.NoError(t, err)
25+
26+
block := types.NewBlock(
27+
&types.Header{
28+
ParentHash: common.HexToHash("0xafafafa"),
29+
Number: big.NewInt(132),
30+
GasLimit: uint64(10000),
31+
GasUsed: uint64(1000),
32+
Time: 16000000,
33+
BaseFee: big.NewInt(7),
34+
}, nil, nil, nil, nil)
35+
block.Profit = big.NewInt(10)
36+
37+
simBundle1 := types.SimulatedBundle{
38+
MevGasPrice: big.NewInt(9),
39+
TotalEth: big.NewInt(11),
40+
EthSentToCoinbase: big.NewInt(10),
41+
TotalGasUsed: uint64(100),
42+
OriginalBundle: types.MevBundle{
43+
Txs: types.Transactions{types.NewTransaction(uint64(50), common.Address{0x60}, big.NewInt(19), uint64(67), big.NewInt(43), []byte{})},
44+
BlockNumber: big.NewInt(12),
45+
MinTimestamp: uint64(1000000),
46+
RevertingTxHashes: []common.Hash{common.Hash{0x10, 0x17}},
47+
Hash: common.Hash{0x09, 0x78},
48+
},
49+
}
50+
simBundle2 := types.SimulatedBundle{
51+
MevGasPrice: big.NewInt(90),
52+
TotalEth: big.NewInt(110),
53+
EthSentToCoinbase: big.NewInt(100),
54+
TotalGasUsed: uint64(1000),
55+
OriginalBundle: types.MevBundle{
56+
Txs: types.Transactions{types.NewTransaction(uint64(51), common.Address{0x61}, big.NewInt(109), uint64(167), big.NewInt(433), []byte{})},
57+
BlockNumber: big.NewInt(20),
58+
MinTimestamp: uint64(1000020),
59+
RevertingTxHashes: []common.Hash{common.Hash{0x11, 0x17}},
60+
Hash: common.Hash{0x10, 0x78},
61+
},
62+
}
63+
64+
bidTrace := &boostTypes.BidTrace{}
65+
66+
ds.ConsumeBuiltBlock(block, []types.SimulatedBundle{simBundle1, simBundle2}, bidTrace)
67+
68+
var dbBlock BuiltBlock
69+
ds.db.Get(&dbBlock, "select * from built_blocks where hash = '0x24e6998e4d2b4fd85f7f0d27ac4b87aaf0ec18e156e4b6e194ab5dadee0cd004'")
70+
t.Logf("block %v", dbBlock)
71+
}

0 commit comments

Comments
 (0)