diff --git a/pkg/store/store.go b/pkg/store/store.go index 4104662..12a6802 100644 --- a/pkg/store/store.go +++ b/pkg/store/store.go @@ -13,7 +13,7 @@ import ( var settlementsTable = ` CREATE TABLE IF NOT EXISTS settlements ( commitment_index BYTEA PRIMARY KEY, - transaction VARCHAR(255), + transaction TEXT, block_number BIGINT, builder_address BYTEA, is_slash BOOLEAN, diff --git a/pkg/updater/updater.go b/pkg/updater/updater.go index 16ced6f..c547907 100644 --- a/pkg/updater/updater.go +++ b/pkg/updater/updater.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "math/big" + "strings" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" @@ -118,9 +119,9 @@ func (u *Updater) Start(ctx context.Context) <-chan struct{} { return fmt.Errorf("failed to get block by number: %w", err) } - txnsInBlock := make(map[string]struct{}) - for _, tx := range blk.Transactions() { - txnsInBlock[tx.Hash().Hex()] = struct{}{} + txnsInBlock := make(map[string]int) + for posInBlock, tx := range blk.Transactions() { + txnsInBlock[tx.Hash().Hex()] = posInBlock } commitmentIndexes, err := u.preconfClient.GetCommitmentsByBlockNumber( @@ -144,7 +145,16 @@ func (u *Updater) Start(ctx context.Context) <-chan struct{} { } if commitment.Commiter.Cmp(builderAddr) == 0 { - _, ok := txnsInBlock[commitment.TxnHash] + commitmentTxnHashes := strings.Split(commitment.TxnHash, ",") + ok := true + + // Ensure Bundle is atomic and present in the block + for i := 0; i < len(commitmentTxnHashes) && ok; i++ { + if newPos, found := txnsInBlock[commitmentTxnHashes[i]]; !found || newPos != txnsInBlock[commitmentTxnHashes[0]]+i { + ok = false + break + } + } err = u.winnerRegister.AddSettlement( ctx, index[:], diff --git a/pkg/updater/updater_test.go b/pkg/updater/updater_test.go index c2c92cf..263eca3 100644 --- a/pkg/updater/updater_test.go +++ b/pkg/updater/updater_test.go @@ -73,12 +73,28 @@ func TestUpdater(t *testing.T) { commitments := make(map[string]preconf.PreConfCommitmentStorePreConfCommitment) for i, txn := range txns { idxBytes := getIdxBytes(int64(i)) + commitments[string(idxBytes[:])] = preconf.PreConfCommitmentStorePreConfCommitment{ Commiter: builderAddr, TxnHash: txn.Hash().Hex(), } } + // constructing bundles + for i := 0; i < 10; i++ { + idxBytes := getIdxBytes(int64(i + 10)) + + bundle := txns[i].Hash().Hex() + for j := i + 1; j < 10; j++ { + bundle += "," + txns[j].Hash().Hex() + } + + commitments[string(idxBytes[:])] = preconf.PreConfCommitmentStorePreConfCommitment{ + Commiter: builderAddr, + TxnHash: bundle, + } + } + testWinnerRegister := &testWinnerRegister{ winners: make(chan updater.BlockWinner), settlements: make(chan testSettlement), @@ -117,7 +133,7 @@ func TestUpdater(t *testing.T) { count := 0 for { - if count == 10 { + if count == 20 { break } settlement := <-testWinnerRegister.settlements @@ -147,6 +163,112 @@ func TestUpdater(t *testing.T) { } } +func TestUpdaterBundlesFailure(t *testing.T) { + t.Parallel() + + key, err := crypto.GenerateKey() + if err != nil { + t.Fatal(err) + } + + builderAddr := common.HexToAddress("0xabcd") + + signer := types.NewLondonSigner(big.NewInt(5)) + var txns []*types.Transaction + for i := 0; i < 10; i++ { + txns = append(txns, types.MustSignNewTx(key, signer, &types.DynamicFeeTx{ + Nonce: uint64(i + 1), + Gas: 1000000, + Value: big.NewInt(1), + GasTipCap: big.NewInt(500), + GasFeeCap: big.NewInt(500), + })) + } + + commitments := make(map[string]preconf.PreConfCommitmentStorePreConfCommitment) + // constructing bundles + for i := 1; i < 10; i++ { + idxBytes := getIdxBytes(int64(i)) + + bundle := txns[i].Hash().Hex() + for j := 10 - i; j > 0; j-- { + bundle += "," + txns[j].Hash().Hex() + } + + commitments[string(idxBytes[:])] = preconf.PreConfCommitmentStorePreConfCommitment{ + Commiter: builderAddr, + TxnHash: bundle, + } + } + + testWinnerRegister := &testWinnerRegister{ + winners: make(chan updater.BlockWinner), + settlements: make(chan testSettlement), + done: make(chan int64, 1), + } + + testL1Client := &testL1Client{ + blockNum: 5, + block: types.NewBlock(&types.Header{}, txns, nil, nil, NewHasher()), + } + + testOracle := &testOracle{ + builder: "test", + builderAddr: builderAddr, + } + + testPreconf := &testPreconf{ + blockNum: 5, + commitments: commitments, + } + + updtr := updater.NewUpdater( + testL1Client, + testWinnerRegister, + testOracle, + testPreconf, + ) + + ctx, cancel := context.WithCancel(context.Background()) + done := updtr.Start(ctx) + + testWinnerRegister.winners <- updater.BlockWinner{ + BlockNumber: 5, + Winner: "test", + } + + count := 0 + for { + if count == 9 { + break + } + settlement := <-testWinnerRegister.settlements + if settlement.blockNum != 5 { + t.Fatal("wrong block number") + } + if settlement.builder != "test" { + t.Fatal("wrong builder") + } + if !settlement.isSlash { + t.Fatal("should be slash") + } + count++ + } + + select { + case <-testWinnerRegister.done: + case <-time.After(5 * time.Second): + t.Fatal("timeout") + } + + cancel() + select { + case <-done: + case <-time.After(5 * time.Second): + t.Fatal("timeout") + } +} + type testSettlement struct { commitmentIdx []byte txHash string