From c11084dd51aa6103a8231e76973c45d705ce3e09 Mon Sep 17 00:00:00 2001 From: bianyuanop Date: Thu, 6 Feb 2025 16:05:11 -0500 Subject: [PATCH 01/31] get arcadia block with signature verification --- common/types.go | 18 +++++ common/utils.go | 13 ++++ datalayer/mocks/mock_IDASubmitter.go | 2 +- datastore/mocks/mock_IDatastore.go | 8 +- seq/consts.go | 4 + seq/mocks/mock_BaseSeqClient.go | 79 +++++++++++++++++--- seq/seqclient.go | 34 +++++++++ services/api/service.go | 63 +++++++++++++--- services/api/service_test.go | 63 ++++++++++++++-- simulator/mocks/mock_IBlockSimRateLimiter.go | 4 +- 10 files changed, 257 insertions(+), 31 deletions(-) create mode 100644 seq/consts.go diff --git a/common/types.go b/common/types.go index 70f32475..1de9d155 100644 --- a/common/types.go +++ b/common/types.go @@ -23,6 +23,7 @@ import ( "github.com/AnomalyFi/hypersdk/chain" "github.com/AnomalyFi/hypersdk/codec" + abls "github.com/AnomalyFi/hypersdk/crypto/bls" "github.com/AnomalyFi/hypersdk/utils" "github.com/AnomalyFi/nodekit-seq/actions" "github.com/ava-labs/avalanchego/ids" @@ -1135,6 +1136,23 @@ type GetBlockPayloadFromArcadia struct { BlockNumber uint64 `json:"blockNumber"` } +func (req *GetBlockPayloadFromArcadia) Payload() ([]byte, error) { + return json.Marshal(req) +} + +func (req *GetBlockPayloadFromArcadia) VerifyIssuer(networkID uint32, seqChainID ids.ID, pk *abls.PublicKey, sig *abls.Signature) (bool, error) { + payload, err := req.Payload() + if err != nil { + return false, err + } + uwm, err := warp.NewUnsignedMessage(networkID, seqChainID, payload) + if err != nil { + return false, err + } + uwmBytes := uwm.Bytes() + return abls.Verify(uwmBytes, pk, sig), nil +} + type SEQTxWrapper struct { Tx *chain.Transaction Size int diff --git a/common/utils.go b/common/utils.go index 323bb94e..a56b909f 100644 --- a/common/utils.go +++ b/common/utils.go @@ -12,6 +12,7 @@ import ( "strings" "time" + abls "github.com/AnomalyFi/hypersdk/crypto/bls" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/AnomalyFi/hypersdk/chain" @@ -221,3 +222,15 @@ func BuildDACert(chunk *ArcadiaChunk, epoch uint64, certificate []byte) *seqtype return robDA } } + +func HeaderToSEQSignature(sigHeader string) (*abls.Signature, error) { + sigBytes, err := hexutil.Decode(sigHeader) + if err != nil { + return nil, err + } + return abls.SignatureFromBytes(sigBytes) +} + +func BytesToSEQPubkey(pkBytes []byte) (*abls.PublicKey, error) { + return abls.PublicKeyFromBytes(pkBytes) +} diff --git a/datalayer/mocks/mock_IDASubmitter.go b/datalayer/mocks/mock_IDASubmitter.go index b83dcace..0f0ea340 100644 --- a/datalayer/mocks/mock_IDASubmitter.go +++ b/datalayer/mocks/mock_IDASubmitter.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.50.0. DO NOT EDIT. +// Code generated by mockery v2.39.0. DO NOT EDIT. package mocks diff --git a/datastore/mocks/mock_IDatastore.go b/datastore/mocks/mock_IDatastore.go index bb2c836b..01d011c3 100644 --- a/datastore/mocks/mock_IDatastore.go +++ b/datastore/mocks/mock_IDatastore.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.50.0. DO NOT EDIT. +// Code generated by mockery v2.39.0. DO NOT EDIT. package mocks @@ -372,7 +372,7 @@ func (_c *MockIDatastore_LoadChunksWithToBNonce_Call) RunAndReturn(run func(uint return _c } -// LoadCurrentToBNonce provides a mock function with no fields +// LoadCurrentToBNonce provides a mock function with given fields: func (_m *MockIDatastore) LoadCurrentToBNonce() (*uint64, error) { ret := _m.Called() @@ -429,7 +429,7 @@ func (_c *MockIDatastore_LoadCurrentToBNonce_Call) RunAndReturn(run func() (*uin return _c } -// LoadHighestSettledToBNonce provides a mock function with no fields +// LoadHighestSettledToBNonce provides a mock function with given fields: func (_m *MockIDatastore) LoadHighestSettledToBNonce() (*uint64, error) { ret := _m.Called() @@ -486,7 +486,7 @@ func (_c *MockIDatastore_LoadHighestSettledToBNonce_Call) RunAndReturn(run func( return _c } -// LoadLowestManagedStateToBNonce provides a mock function with no fields +// LoadLowestManagedStateToBNonce provides a mock function with given fields: func (_m *MockIDatastore) LoadLowestManagedStateToBNonce() (*uint64, error) { ret := _m.Called() diff --git a/seq/consts.go b/seq/consts.go new file mode 100644 index 00000000..2f0c7f48 --- /dev/null +++ b/seq/consts.go @@ -0,0 +1,4 @@ +package seq + +const DefaultProposerLRUSize = 20 +const GetArcadiaBlockSignatureHeader = "X-Arcadia-GetBlock-Sig" diff --git a/seq/mocks/mock_BaseSeqClient.go b/seq/mocks/mock_BaseSeqClient.go index 822354e2..eb932b09 100644 --- a/seq/mocks/mock_BaseSeqClient.go +++ b/seq/mocks/mock_BaseSeqClient.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.50.0. DO NOT EDIT. +// Code generated by mockery v2.39.0. DO NOT EDIT. package mocks @@ -30,7 +30,7 @@ func (_m *MockBaseSeqClient) EXPECT() *MockBaseSeqClient_Expecter { return &MockBaseSeqClient_Expecter{mock: &_m.Mock} } -// CurrentEpoch provides a mock function with no fields +// CurrentEpoch provides a mock function with given fields: func (_m *MockBaseSeqClient) CurrentEpoch() uint64 { ret := _m.Called() @@ -123,7 +123,7 @@ func (_c *MockBaseSeqClient_CurrentValidators_Call) RunAndReturn(run func(contex return _c } -// CurrentValidatorsTotalWeight provides a mock function with no fields +// CurrentValidatorsTotalWeight provides a mock function with given fields: func (_m *MockBaseSeqClient) CurrentValidatorsTotalWeight() uint64 { ret := _m.Called() @@ -284,7 +284,7 @@ func (_c *MockBaseSeqClient_GetBalance_Call) RunAndReturn(run func(context.Conte return _c } -// GetChainID provides a mock function with no fields +// GetChainID provides a mock function with given fields: func (_m *MockBaseSeqClient) GetChainID() ids.ID { ret := _m.Called() @@ -331,7 +331,7 @@ func (_c *MockBaseSeqClient_GetChainID_Call) RunAndReturn(run func() ids.ID) *Mo return _c } -// GetNetworkID provides a mock function with no fields +// GetNetworkID provides a mock function with given fields: func (_m *MockBaseSeqClient) GetNetworkID() uint32 { ret := _m.Called() @@ -481,7 +481,7 @@ func (_c *MockBaseSeqClient_GetValidatorWeight_Call) RunAndReturn(run func([]byt return _c } -// Parser provides a mock function with no fields +// Parser provides a mock function with given fields: func (_m *MockBaseSeqClient) Parser() chain.Parser { ret := _m.Called() @@ -528,7 +528,66 @@ func (_c *MockBaseSeqClient_Parser_Call) RunAndReturn(run func() chain.Parser) * return _c } -// SeqHead provides a mock function with no fields +// ProposerAtHeight provides a mock function with given fields: ctx, height +func (_m *MockBaseSeqClient) ProposerAtHeight(ctx context.Context, height uint64) (*rpc.Validator, error) { + ret := _m.Called(ctx, height) + + if len(ret) == 0 { + panic("no return value specified for ProposerAtHeight") + } + + var r0 *rpc.Validator + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uint64) (*rpc.Validator, error)); ok { + return rf(ctx, height) + } + if rf, ok := ret.Get(0).(func(context.Context, uint64) *rpc.Validator); ok { + r0 = rf(ctx, height) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*rpc.Validator) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, uint64) error); ok { + r1 = rf(ctx, height) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockBaseSeqClient_ProposerAtHeight_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ProposerAtHeight' +type MockBaseSeqClient_ProposerAtHeight_Call struct { + *mock.Call +} + +// ProposerAtHeight is a helper method to define mock.On call +// - ctx context.Context +// - height uint64 +func (_e *MockBaseSeqClient_Expecter) ProposerAtHeight(ctx interface{}, height interface{}) *MockBaseSeqClient_ProposerAtHeight_Call { + return &MockBaseSeqClient_ProposerAtHeight_Call{Call: _e.mock.On("ProposerAtHeight", ctx, height)} +} + +func (_c *MockBaseSeqClient_ProposerAtHeight_Call) Run(run func(ctx context.Context, height uint64)) *MockBaseSeqClient_ProposerAtHeight_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uint64)) + }) + return _c +} + +func (_c *MockBaseSeqClient_ProposerAtHeight_Call) Return(_a0 *rpc.Validator, _a1 error) *MockBaseSeqClient_ProposerAtHeight_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockBaseSeqClient_ProposerAtHeight_Call) RunAndReturn(run func(context.Context, uint64) (*rpc.Validator, error)) *MockBaseSeqClient_ProposerAtHeight_Call { + _c.Call.Return(run) + return _c +} + +// SeqHead provides a mock function with given fields: func (_m *MockBaseSeqClient) SeqHead() *chain.StatefulBlock { ret := _m.Called() @@ -604,11 +663,11 @@ func (_c *MockBaseSeqClient_SetOnNewBlockHandler_Call) Return() *MockBaseSeqClie } func (_c *MockBaseSeqClient_SetOnNewBlockHandler_Call) RunAndReturn(run func(func(*chain.StatefulBlock, []*chain.Result))) *MockBaseSeqClient_SetOnNewBlockHandler_Call { - _c.Run(run) + _c.Call.Return(run) return _c } -// Stop provides a mock function with no fields +// Stop provides a mock function with given fields: func (_m *MockBaseSeqClient) Stop() { _m.Called() } @@ -636,7 +695,7 @@ func (_c *MockBaseSeqClient_Stop_Call) Return() *MockBaseSeqClient_Stop_Call { } func (_c *MockBaseSeqClient_Stop_Call) RunAndReturn(run func()) *MockBaseSeqClient_Stop_Call { - _c.Run(run) + _c.Call.Return(run) return _c } diff --git a/seq/seqclient.go b/seq/seqclient.go index 8ec721f9..6266c582 100644 --- a/seq/seqclient.go +++ b/seq/seqclient.go @@ -20,6 +20,7 @@ import ( "github.com/AnomalyFi/nodekit-seq/auth" "github.com/AnomalyFi/nodekit-seq/consts" srpc "github.com/AnomalyFi/nodekit-seq/rpc" + avacache "github.com/ava-labs/avalanchego/cache" "github.com/ava-labs/avalanchego/ids" "github.com/sirupsen/logrus" ) @@ -55,6 +56,7 @@ type BaseSeqClient interface { SubmitActions(ctx context.Context, action []chain.Action) (ids.ID, error) GenerateTransaction(ctx context.Context, acts []chain.Action) (*chain.Transaction, error) + ProposerAtHeight(ctx context.Context, height uint64) (*hrpc.Validator, error) } var _ BaseSeqClient = (*SeqClient)(nil) @@ -69,6 +71,7 @@ type SeqClient struct { blockHead *chain.StatefulBlock blockHeadL sync.RWMutex + proposerAtHeight *avacache.LRU[uint64, *hrpc.Validator] currentValidators []*hrpc.Validator // ETH Chain related @@ -128,6 +131,9 @@ func NewSeqClient(config *SeqClientConfig) (*SeqClient, error) { wsCli: wsCli, signer: config.PrivateKey, + currentValidators: make([]*hrpc.Validator, 0), + proposerAtHeight: &avacache.LRU[uint64, *hrpc.Validator]{Size: DefaultProposerLRUSize}, + Namespace: nil, parser: parser, @@ -180,6 +186,19 @@ func NewSeqClient(config *SeqClientConfig) (*SeqClient, error) { client.logger.WithField("validatorPubkeys", validatorPubkeys).Debug("setting new validator set") } + // query the proposer at height + 1 + go func() { + proposer, err := client.hrpc.NextProposer(ctx, blk.Hght+1) + if err != nil { + client.logger.WithFields(logrus.Fields{ + "seqHeight": blk.Hght, + "err": err, + }).Warn("unable to query the next proposer at height") + return + } + client.proposerAtHeight.Put(blk.Hght+1, proposer) + }() + // calculate total weight of current validators. var totalWeight uint64 for _, val := range currVal { @@ -366,3 +385,18 @@ func (s *SeqClient) GetRollupsValidAtEpoch(ctx context.Context, epoch uint64) ([ } return seqRollups.Rollups, nil } + +func (s *SeqClient) ProposerAtHeight(ctx context.Context, height uint64) (*hrpc.Validator, error) { + proposer, exists := s.proposerAtHeight.Get(height) + if exists { + return proposer, nil + } + + proposer, err := s.hrpc.NextProposer(ctx, height) + if err != nil { + return nil, err + } + + s.proposerAtHeight.Put(height, proposer) + return proposer, nil +} diff --git a/services/api/service.go b/services/api/service.go index 5c4c8f1d..d76d13bc 100644 --- a/services/api/service.go +++ b/services/api/service.go @@ -1064,7 +1064,7 @@ func (api *ArcadiaAPI) handleGetPayload(w http.ResponseWriter, req *http.Request payload := new(common.GetPayloadRequest) if err := json.Unmarshal(body, payload); err != nil { log.WithError(err).Warn("failed to decode getPayload request") - api.RespondError(w, http.StatusBadRequest, "failed to decode anchor payload request") + api.RespondError(w, http.StatusBadRequest, "failed to decode payload request") return } log = log.WithFields(logrus.Fields{ @@ -2327,22 +2327,68 @@ func (api *ArcadiaAPI) handleGetArcadiaBlock(w http.ResponseWriter, req *http.Re body, err := io.ReadAll(req.Body) if err != nil { if strings.Contains(err.Error(), "i/o timeout") { - api.log.WithError(err).Error("handleGetArcadiaBlock() request failed to decode (i/o timeout)") + log.WithError(err).Error("handleGetArcadiaBlock() request failed to decode (i/o timeout)") api.RespondError(w, http.StatusInternalServerError, err.Error()) return } - api.log.Error("could not read body of request for handleGetArcadiaBlock()") + log.Error("could not read body of request for handleGetArcadiaBlock()") api.RespondError(w, http.StatusBadRequest, err.Error()) return } plreq := new(common.GetBlockPayloadFromArcadia) if err := json.NewDecoder(bytes.NewReader(body)).Decode(plreq); err != nil { - api.log.Error("failed to decode arcadia block request") + log.Error("failed to decode arcadia block request") api.RespondError(w, http.StatusBadRequest, err.Error()) return } + log = log.WithField("seqHeight", plreq.BlockNumber) + + // do proposer verification + ctx := context.Background() + ctx, cancel := context.WithTimeout(ctx, 500*time.Second) + defer cancel() + proposer, err := api.seqClient.ProposerAtHeight(ctx, plreq.BlockNumber) + if err != nil { + log.WithError(err).Error("failed to fetch proposer at height") + api.RespondError(w, http.StatusBadRequest, err.Error()) + return + } + proposerPubkey, err := common.BytesToSEQPubkey(proposer.PublicKey) + if err != nil { + log.WithError(err).Error("failed to parse proposer pubkey") + api.RespondError(w, http.StatusBadRequest, err.Error()) + return + } + + proposerSigStr := req.Header.Get(seq.GetArcadiaBlockSignatureHeader) + proposerSig, err := common.HeaderToSEQSignature(proposerSigStr) + if err != nil { + log.WithError(err).Error("failed to extract proposer signature from header") + api.RespondError(w, http.StatusBadRequest, err.Error()) + return + } + seqNetworkID := api.seqClient.GetNetworkID() + seqChainID := api.seqClient.GetChainID() + log = log.WithFields(logrus.Fields{ + "proposerPubkey": hexutil.Encode(proposer.PublicKey), + "sig": proposerSigStr, + "seqNetworkID": seqNetworkID, + "seqChainID": seqChainID.String(), + }) + verified, err := plreq.VerifyIssuer(seqNetworkID, seqChainID, proposerPubkey, proposerSig) + if err != nil { + log.WithError(err).Error("failed to verify signature of the get block request") + api.RespondError(w, http.StatusBadRequest, err.Error()) + return + } + if !verified { + log.Error("wrong sigature against the given payload") + api.RespondError(w, http.StatusBadRequest, "unable to verify signature") + return + } + txsw := api.pendingTxs.StreamTxs(int(plreq.MaxBandwidth)) var txs []*chain.Transaction for _, tx := range txsw { @@ -2361,13 +2407,13 @@ func (api *ArcadiaAPI) handleGetArcadiaBlock(w http.ResponseWriter, req *http.Re } } if len(txs) == 0 { - api.log.Errorf("no transactions available, block: %d", plreq.BlockNumber) + log.Errorf("no transactions available, block: %d", plreq.BlockNumber) api.RespondError(w, http.StatusNoContent, "no transactions available") return } mtxs, err := chain.MarshalTxs(txs) if err != nil { - api.log.Errorf("no transactions available, block: %d", plreq.BlockNumber) + log.Errorf("no transactions available, block: %d", plreq.BlockNumber) api.RespondError(w, http.StatusNoContent, "no transactions available") return } @@ -2391,13 +2437,12 @@ func (api *ArcadiaAPI) handleSubscribeValidator(w http.ResponseWriter, req *http epoch := api.headEpoch.Load() - log := api.log.WithField("method", "subscribeValidator") - log.Info("Received subscribe validator request") - log = api.log.WithFields(logrus.Fields{ + log := api.log.WithFields(logrus.Fields{ "method": "subscribeValidator", "contentLength": req.ContentLength, "timestampRequestStart": time.Now().UTC().UnixMilli(), }) + log.Info("Received subscribe validator request") conn, err := api.upgrader.Upgrade(w, req, nil) if err != nil { diff --git a/services/api/service_test.go b/services/api/service_test.go index 56d06ea1..d1a313e9 100644 --- a/services/api/service_test.go +++ b/services/api/service_test.go @@ -40,6 +40,7 @@ import ( "github.com/AnomalyFi/Arcadia/common" "github.com/AnomalyFi/Arcadia/database" "github.com/AnomalyFi/Arcadia/datastore" + "github.com/AnomalyFi/Arcadia/seq" mseq "github.com/AnomalyFi/Arcadia/seq/mocks" "github.com/AnomalyFi/Arcadia/simulator" msim "github.com/AnomalyFi/Arcadia/simulator/mocks" @@ -228,7 +229,7 @@ func (be *testBackend) request(method, path string, payload any) *httptest.Respo return rr } -func (be *testBackend) RequestWithPayloadHeader(method, path string, payload any, sig string) *httptest.ResponseRecorder { +func (be *testBackend) RequestWithHeaders(method, path string, payload any, headers map[string]string) *httptest.ResponseRecorder { var req *http.Request var err error path = "/api" + path @@ -241,12 +242,20 @@ func (be *testBackend) RequestWithPayloadHeader(method, path string, payload any req, err = http.NewRequest(method, path, bytes.NewReader(payloadBytes)) } require.NoError(be.t, err) - req.Header.Set(GetPayloadHeaderRollupSig, sig) + for header, value := range headers { + req.Header.Set(header, value) + } rr := httptest.NewRecorder() be.arcadia.getRouter().ServeHTTP(rr, req) return rr } +func (be *testBackend) RequestWithPayloadHeader(method, path string, payload any, sig string) *httptest.ResponseRecorder { + return be.RequestWithHeaders(method, path, payload, map[string]string{ + GetPayloadHeaderRollupSig: sig, + }) +} + func (be *testBackend) SetupRegisteredRollups(epoch uint64, chainID *big.Int) { namespace := common.ChainIDToNamespace(chainID) rollup := &actions.RollupInfo{ @@ -1132,9 +1141,14 @@ func TestAuctionBid(t *testing.T) { func TestArcadiaBlock(t *testing.T) { path := "/arcadia/v1/validator/block" + seqNetworkID := uint32(1337) seqChainID := ids.GenerateTestID() parser := srpc.Parser{} chainIDs := []*big.Int{big.NewInt(42000), big.NewInt(43000)} + proposerSK, err := abls.NewSecretKey() + require.NoError(t, err) + proposerPK := abls.PublicFromSecretKey(proposerSK) + proposerPKBytes := proposerPK.Compress() t.Run("baseline case", func(t *testing.T) { backend := newTestBackend(t) @@ -1149,12 +1163,33 @@ func TestArcadiaBlock(t *testing.T) { maxBandwidth := uint64(20000) blockNumber := uint64(15) + + // setup seq client mock + backend.seqcli.EXPECT().GetNetworkID().Return(seqNetworkID) + backend.seqcli.EXPECT().GetChainID().Return(seqChainID) + backend.seqcli.EXPECT().ProposerAtHeight(mock.Anything, blockNumber).Return(&hrpc.Validator{ + NodeID: ids.GenerateTestNodeID(), + PublicKey: proposerPKBytes, + Weight: 1000, + }, nil) + + // prepare request and signature req := common.GetBlockPayloadFromArcadia{ MaxBandwidth: maxBandwidth, BlockNumber: blockNumber, } - rr := backend.request(http.MethodPost, path, req) + payload, err := req.Payload() + require.NoError(t, err) + uwm, err := warp.NewUnsignedMessage(seqNetworkID, seqChainID, payload) + require.NoError(t, err) + uwmBytes := uwm.Bytes() + sig := abls.Sign(proposerSK, uwmBytes) + sigStr := hexutil.Encode(sig.Compress()) + + rr := backend.RequestWithHeaders(http.MethodPost, path, req, map[string]string{ + seq.GetArcadiaBlockSignatureHeader: sigStr, + }) require.Equal(t, http.StatusOK, rr.Code) var response common.GetBlockPayloadFromArcadiaResponse @@ -1168,15 +1203,33 @@ func TestArcadiaBlock(t *testing.T) { maxBandwidth := uint64(1000) blockNumber := uint64(15) + + // setup seq client mock + backend.seqcli.EXPECT().GetNetworkID().Return(seqNetworkID) + backend.seqcli.EXPECT().GetChainID().Return(seqChainID) + backend.seqcli.EXPECT().ProposerAtHeight(mock.Anything, blockNumber).Return(&hrpc.Validator{ + NodeID: ids.GenerateTestNodeID(), + PublicKey: proposerPKBytes, + Weight: 1000, + }, nil) + req := common.GetBlockPayloadFromArcadia{ MaxBandwidth: maxBandwidth, BlockNumber: blockNumber, } + payload, err := req.Payload() + require.NoError(t, err) + uwm, err := warp.NewUnsignedMessage(seqNetworkID, seqChainID, payload) + require.NoError(t, err) + uwmBytes := uwm.Bytes() + sig := abls.Sign(proposerSK, uwmBytes) + sigStr := hexutil.Encode(sig.Compress()) - rr := backend.request(http.MethodPost, path, req) + rr := backend.RequestWithHeaders(http.MethodPost, path, req, map[string]string{ + seq.GetArcadiaBlockSignatureHeader: sigStr, + }) require.Equal(t, http.StatusNoContent, rr.Code) }) - } func TestSubmitChunkToSEQValidators(t *testing.T) { diff --git a/simulator/mocks/mock_IBlockSimRateLimiter.go b/simulator/mocks/mock_IBlockSimRateLimiter.go index 227fbe28..4b7b1539 100644 --- a/simulator/mocks/mock_IBlockSimRateLimiter.go +++ b/simulator/mocks/mock_IBlockSimRateLimiter.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.50.0. DO NOT EDIT. +// Code generated by mockery v2.39.0. DO NOT EDIT. package mocks @@ -30,7 +30,7 @@ func (_m *MockIBlockSimRateLimiter) EXPECT() *MockIBlockSimRateLimiter_Expecter return &MockIBlockSimRateLimiter_Expecter{mock: &_m.Mock} } -// CurrentCounter provides a mock function with no fields +// CurrentCounter provides a mock function with given fields: func (_m *MockIBlockSimRateLimiter) CurrentCounter() int64 { ret := _m.Called() From 452af1f00b7b9801615ae178d0af5acfc97abb21 Mon Sep 17 00:00:00 2001 From: NateAtNodeKit Date: Fri, 14 Feb 2025 13:55:12 -0500 Subject: [PATCH 02/31] tob filtering if no rob --- services/api/service.go | 42 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/services/api/service.go b/services/api/service.go index d76d13bc..be4cd12d 100644 --- a/services/api/service.go +++ b/services/api/service.go @@ -294,6 +294,9 @@ type ArcadiaAPI struct { // map of ChunkID -> PreConfInfo pendingChunksPreConfs sync.Map + // map of ChainID -> num messages received + numChunksByChainID sync.Map + // map of rollup id to block number. this is updated whenever rollup successfully calls getPayload(). // we have an issue in which builders cannot build for the block number that has been called via getPayload() // else we experience state corruption. This map is used to reject @@ -312,6 +315,7 @@ type ArcadiaAPI struct { // seq validator registration upgrader *websocket.Upgrader + // map of subscribed validators, on subscribe, we query its weight and store the connection and weight by [SubscribedValidator] // on disconnected, we remove that validator from the map subscribedValidatorMap sync.Map @@ -387,6 +391,7 @@ func NewArcadiaAPI(opts ArcadiaAPIOpts) (api *ArcadiaAPI, err error) { blockSimRateLimiter: opts.BlockSimulator, pendingChunksPreConfs: sync.Map{}, + numChunksByChainID: sync.Map{}, rollupToLastFetchedBlockNumber: make(map[string]uint64), submitReqOngoing: &sync.Map{}, @@ -486,6 +491,26 @@ func (api *ArcadiaAPI) getRouter() http.Handler { return loggedRouter } +func (api *ArcadiaAPI) trackChunkByChainID(chainID string) { + numChunk, ok := api.numChunksByChainID.Load(chainID) + if !ok { + api.numChunksByChainID.Store(chainID, 1) + } else { + numChunkVal := numChunk.(int) + api.numChunksByChainID.Store(chainID, numChunkVal+1) + } +} + +func (api *ArcadiaAPI) getNumChunksByChainID(chainID string) int { + numChunk, ok := api.numChunksByChainID.Load(chainID) + if !ok { + return 0 + } else { + numChunkVal := numChunk.(int) + return numChunkVal + } +} + // StartServer starts up this API instance and HTTP server // - First it initializes the cache and updates local information // - Once that is done, the HTTP server is started @@ -1611,6 +1636,14 @@ func (api *ArcadiaAPI) handleSubmitNewBlockRequest(w http.ResponseWriter, req *h if isToB { for _, bundle := range blockReq.ToBChunk().Bundles { for _, chainIDStr := range bundle.Domains() { + // We will reject the ToB if we haven't received a RoB for each chain id it is using + if api.getNumChunksByChainID(chainIDStr) == 0 { + errMsg := fmt.Sprintf("tob rejected due to lack of RoB for chain ID [%s] ", chainIDStr) + log.WithError(err).Warn(errMsg) + api.RespondError(w, http.StatusBadRequest, errMsg) + return + } + namespace, err := common.ChainIDStrToNamespace(chainIDStr) if err != nil { errMsg := fmt.Sprintf("could not convert chain ID [%s] to namespace", chainIDStr) @@ -1765,6 +1798,15 @@ func (api *ArcadiaAPI) handleSubmitNewBlockRequest(w http.ResponseWriter, req *h "tobNonce": headToBNonce, }).Debug("incoming chunk txs") + // tracks the kind of chunk we are receiving + if isToB { + api.trackChunkByChainID("tob") + } else { + api.trackChunkByChainID(chainID) + } + + // Simulation handling + // At this point, filter checks have completed and we want to perform simulation on txs in the chunk var simulationErr error // label all successful bundles to be accepted defer func() { From 05f2d84bfc5bb17aa84869aefeeb35f72a8783bb Mon Sep 17 00:00:00 2001 From: rikoeldon <106416799+rikoeldon@users.noreply.github.com> Date: Fri, 14 Feb 2025 15:40:22 -0500 Subject: [PATCH 03/31] debugging tests --- services/api/service_test.go | 116 +++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) diff --git a/services/api/service_test.go b/services/api/service_test.go index d1a313e9..93debb28 100644 --- a/services/api/service_test.go +++ b/services/api/service_test.go @@ -191,6 +191,96 @@ func newDatastores(t *testing.T) (*datastore.RedisCache, database.IDatabaseServi return redisCache, db } +func (be *testBackend) submitRoBChunk(t *testing.T, opts *CreateTestBlockSubmissionOpts, parser *srpc.Parser) { + // constructing RoB + robTxs := ethtypes.Transactions{ + // conflicting tx with prev ToB + CreateEthTransfer(t, &opts.OriginChainID, testAccounts[0].PrivateKey, testAccounts[1].Address, 20, testAccounts[0].Nonce, 21000, nil), + } + robReq := CreateRoBReq(t, opts) + + // testing chunk db conversions for rob + chunkDB, err := common.ConvertRoBChunkToDBType(robReq.RoBChunk()) + require.NoError(t, err) + chunk2, err := common.ConvertRoBChunkDBToRoBChunk(chunkDB, parser) + require.NoError(t, err) + require.NotNil(t, chunk2) + // compare fields + require.Equal(t, robReq.RoBChunk().ChainID, chunk2.ChainID, "ChainID mismatch") + require.Equal(t, robReq.RoBChunk().BlockNumber, chunk2.BlockNumber, "GetBlockNumber mismatch") + // compare Txs + require.Equal(t, len(robReq.RoBChunk().Txs), len(chunk2.Txs), "Number of transactions mismatch") + for i := range robReq.RoBChunk().Txs { + require.Equal(t, robReq.RoBChunk().Txs[i], chunk2.Txs[i], "Transaction mismatch at index %d", i) + } + require.Equal(t, robReq.RoBChunk().SEQTxs(), chunk2.SEQTxs()) + require.Equal(t, robReq.RoBChunk().GetTxs().Len(), chunk2.GetTxs().Len()) + require.True(t, robReq.RoBChunk().RemovedBitSet().Equal(chunk2.RemovedBitSet())) + + // instantiate backend + backend := newTestBackend(t) + builderPK := opts.BuilderPubkey + builderPKBytes := builderPK.Bytes() + err = backend.arcadia.datastore.SetAuctionWinner(opts.Epoch, builderPKBytes[:]) + require.NoError(t, err) + redis := backend.redis + redis.SetSizeTracker(backend.arcadia.sizeTracker) + + // register test rollup + backend.SetupRegisteredRollups(opts.Epoch, &opts.OriginChainID) + + // set up mock expectations + // shared calls for both chunks + backend.seqcli.EXPECT().Parser().Return(parser) + originChainIDStr := common.ChainIDStr(&opts.OriginChainID) + t.Log("========setup & send RoB============") + for domain, expectedTxs := range map[string]ethtypes.Transactions{ + originChainIDStr: robTxs, + } { + matchTxs := func(txs ethtypes.Transactions) bool { + if len(txs) != len(expectedTxs) { + return false + } + for i, tx := range txs { + if tx.Hash().Hex() != expectedTxs[i].Hash().Hex() { + return false + } + } + return true + } + + relatedAccounts, err := CollectAccountsFromTxs(expectedTxs, testAccounts) + require.NoError(t, err) + nonces := CollectNoncesFromEthAccounts(relatedAccounts) + balances := CollectBalancesFromEthAccounts(relatedAccounts) + backend.simulator.EXPECT().GetNonces(domain, mock.MatchedBy(matchTxs), uint64ToHexString(opts.RoBBlockNumber-2)).Return(nonces, nil) + backend.simulator.EXPECT().GetBalances(domain, mock.MatchedBy(matchTxs), uint64ToHexString(opts.RoBBlockNumber-2)).Return(balances, nil) + + // simulation results, only the txs from this RoB will be simulated and the txs from ToB will be filtered out + callBundleRes := make([]flashbotsrpc.FlashbotsCallBundleResult, 0, len(expectedTxs)) + for _, tx := range expectedTxs { + callBundleRes = append(callBundleRes, flashbotsrpc.FlashbotsCallBundleResult{ + TxHash: tx.Hash().Hex(), + Error: "", + Revert: "", + }) + } + rawExpectedTxs, err := CollectRawTxs(expectedTxs) + require.NoError(t, err) + validationReq := common.BlockValidationRequest{ + Txs: rawExpectedTxs, + BlockNumber: uint64ToHexString(opts.RoBBlockNumber - 2), + StateBlockNumber: uint64ToHexString(opts.RoBBlockNumber - 2), + } + backend.simulator.EXPECT(). + SimBlockAndGetGasUsedForChain(mock.Anything, domain, &validationReq). + Return(100, callBundleRes, nil) + } + + rrCode1 := backend.request(http.MethodPost, pathSubmitNewBlockRequest, robReq) + require.Equal(t, http.StatusOK, rrCode1) +} + // setLowestToBNonceForEpoch is used to set the lowest tob nonce for epoch in datastore for testing // fails with error only if both redis and database error out func (be *testBackend) setLowestToBNonceForEpoch(t *testing.T, epoch uint64, tobNonce uint64) { @@ -2478,6 +2568,31 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { originChainIDStr: 100, remoteChainIDStr: 50, } + // setup rob submission + originChainIDInt, err := common.ChainIDStrToChainID(originChainIDStr) + require.NoError(t, err) + remoteChainIDInt, err := common.ChainIDStrToChainID(remoteChainIDStr) + require.NoError(t, err) + id := ids.GenerateTestID() + + robTxs := ethtypes.Transactions{ + // conflicting tx with prev ToB + CreateEthTransfer(t, originChainID, testAccounts[0].PrivateKey, testAccounts[1].Address, 20, testAccounts[0].Nonce, 21000, nil), + } + + opts := &CreateTestBlockSubmissionOpts{ + Epoch: epoch, + OriginChainID: *originChainIDInt, + RemoteChainID: *remoteChainIDInt, + SeqChainID: testSeqChainID, + BuilderPubkey: *testBuilderPublicKey, + BuilderSecretkey: *testBuilderSecretKey, + RoBBlockNumber: blockNumbers[originChainIDStr] - 1, + ChunkID: id, + RoBChainID: originChainID, + Txs: robTxs, + IsToB: false, + } // constructing ToB bundleTxs := map[string]ethtypes.Transactions{ @@ -2507,6 +2622,7 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { }) backend := newTestBackend(t) + backend.submitRoBChunk(t, opts, chainParser) err = backend.arcadia.datastore.SetAuctionWinner(epoch, builderPkBytes[:]) require.NoError(t, err) redis := backend.redis From d0ca9edb36b07955dc039794a1b97b1d05de5f0e Mon Sep 17 00:00:00 2001 From: NateAtNodeKit Date: Fri, 14 Feb 2025 16:36:35 -0500 Subject: [PATCH 04/31] updates --- services/api/service_test.go | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/services/api/service_test.go b/services/api/service_test.go index 93debb28..02d6b1ab 100644 --- a/services/api/service_test.go +++ b/services/api/service_test.go @@ -227,7 +227,7 @@ func (be *testBackend) submitRoBChunk(t *testing.T, opts *CreateTestBlockSubmiss redis.SetSizeTracker(backend.arcadia.sizeTracker) // register test rollup - backend.SetupRegisteredRollups(opts.Epoch, &opts.OriginChainID) + backend.SetupRegisteredRollups(opts.Epoch, &opts.RemoteChainID) // set up mock expectations // shared calls for both chunks @@ -253,8 +253,8 @@ func (be *testBackend) submitRoBChunk(t *testing.T, opts *CreateTestBlockSubmiss require.NoError(t, err) nonces := CollectNoncesFromEthAccounts(relatedAccounts) balances := CollectBalancesFromEthAccounts(relatedAccounts) - backend.simulator.EXPECT().GetNonces(domain, mock.MatchedBy(matchTxs), uint64ToHexString(opts.RoBBlockNumber-2)).Return(nonces, nil) - backend.simulator.EXPECT().GetBalances(domain, mock.MatchedBy(matchTxs), uint64ToHexString(opts.RoBBlockNumber-2)).Return(balances, nil) + backend.simulator.EXPECT().GetNonces(domain, mock.MatchedBy(matchTxs), uint64ToHexString(opts.RoBBlockNumber-1)).Return(nonces, nil) + backend.simulator.EXPECT().GetBalances(domain, mock.MatchedBy(matchTxs), uint64ToHexString(opts.RoBBlockNumber-1)).Return(balances, nil) // simulation results, only the txs from this RoB will be simulated and the txs from ToB will be filtered out callBundleRes := make([]flashbotsrpc.FlashbotsCallBundleResult, 0, len(expectedTxs)) @@ -269,16 +269,16 @@ func (be *testBackend) submitRoBChunk(t *testing.T, opts *CreateTestBlockSubmiss require.NoError(t, err) validationReq := common.BlockValidationRequest{ Txs: rawExpectedTxs, - BlockNumber: uint64ToHexString(opts.RoBBlockNumber - 2), - StateBlockNumber: uint64ToHexString(opts.RoBBlockNumber - 2), + BlockNumber: uint64ToHexString(opts.RoBBlockNumber - 1), + StateBlockNumber: uint64ToHexString(opts.RoBBlockNumber - 1), } backend.simulator.EXPECT(). SimBlockAndGetGasUsedForChain(mock.Anything, domain, &validationReq). Return(100, callBundleRes, nil) } - rrCode1 := backend.request(http.MethodPost, pathSubmitNewBlockRequest, robReq) - require.Equal(t, http.StatusOK, rrCode1) + rr := backend.request(http.MethodPost, pathSubmitNewBlockRequest, robReq) + require.Equal(t, http.StatusOK, rr.Code) } // setLowestToBNonceForEpoch is used to set the lowest tob nonce for epoch in datastore for testing @@ -2587,9 +2587,9 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { SeqChainID: testSeqChainID, BuilderPubkey: *testBuilderPublicKey, BuilderSecretkey: *testBuilderSecretKey, - RoBBlockNumber: blockNumbers[originChainIDStr] - 1, + RoBBlockNumber: blockNumbers[remoteChainIDStr] - 1, ChunkID: id, - RoBChainID: originChainID, + RoBChainID: remoteChainID, Txs: robTxs, IsToB: false, } @@ -2622,15 +2622,20 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { }) backend := newTestBackend(t) + + // register test rollup + backend.SetupRegisteredRollups(epoch, originChainID) + backend.SetupRegisteredRollups(epoch, remoteChainID) + + // send in rob chunk for remote chain id + // needed for ToB to be accepted backend.submitRoBChunk(t, opts, chainParser) + err = backend.arcadia.datastore.SetAuctionWinner(epoch, builderPkBytes[:]) require.NoError(t, err) redis := backend.redis redis.SetSizeTracker(backend.arcadia.sizeTracker) - // register test rollup - backend.SetupRegisteredRollups(epoch, originChainID) - backend.SetupRegisteredRollups(epoch, remoteChainID) backend.simulator.EXPECT().GetBlockNumber([]string{originChainIDStr, remoteChainIDStr}).Return(blockNumbers, nil) // set up mock expectations // shared calls for both chunks From 3290af5fdc9646dc2e0ab02c83ddae1fb13ec1c5 Mon Sep 17 00:00:00 2001 From: rikoeldon <106416799+rikoeldon@users.noreply.github.com> Date: Sat, 15 Feb 2025 19:54:13 -0500 Subject: [PATCH 05/31] debugging failing test --- services/api/service_test.go | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/services/api/service_test.go b/services/api/service_test.go index 02d6b1ab..1b545a1b 100644 --- a/services/api/service_test.go +++ b/services/api/service_test.go @@ -228,13 +228,17 @@ func (be *testBackend) submitRoBChunk(t *testing.T, opts *CreateTestBlockSubmiss // register test rollup backend.SetupRegisteredRollups(opts.Epoch, &opts.RemoteChainID) + backend.SetupRegisteredRollups(opts.Epoch, &opts.OriginChainID) // set up mock expectations // shared calls for both chunks backend.seqcli.EXPECT().Parser().Return(parser) - originChainIDStr := common.ChainIDStr(&opts.OriginChainID) + remoteChainIDStr := hexutil.EncodeBig(&opts.RemoteChainID) + originChainIDStr := hexutil.EncodeBig(&opts.OriginChainID) + t.Log("========setup & send RoB============") for domain, expectedTxs := range map[string]ethtypes.Transactions{ + remoteChainIDStr: robTxs, originChainIDStr: robTxs, } { matchTxs := func(txs ethtypes.Transactions) bool { @@ -253,8 +257,24 @@ func (be *testBackend) submitRoBChunk(t *testing.T, opts *CreateTestBlockSubmiss require.NoError(t, err) nonces := CollectNoncesFromEthAccounts(relatedAccounts) balances := CollectBalancesFromEthAccounts(relatedAccounts) - backend.simulator.EXPECT().GetNonces(domain, mock.MatchedBy(matchTxs), uint64ToHexString(opts.RoBBlockNumber-1)).Return(nonces, nil) - backend.simulator.EXPECT().GetBalances(domain, mock.MatchedBy(matchTxs), uint64ToHexString(opts.RoBBlockNumber-1)).Return(balances, nil) + backend.simulator.EXPECT(). + GetNonces(remoteChainIDStr, mock.MatchedBy(matchTxs), uint64ToHexString(opts.RoBBlockNumber-1)). + Return(nonces, nil) + + backend.simulator.EXPECT(). + GetNonces(originChainIDStr, mock.MatchedBy(matchTxs), uint64ToHexString(opts.RoBBlockNumber-1)). + Return(nonces, nil) + + backend.simulator.EXPECT(). + GetBalances(remoteChainIDStr, mock.MatchedBy(matchTxs), uint64ToHexString(opts.RoBBlockNumber-1)). + Return(balances, nil) + + backend.simulator.EXPECT(). + GetBalances(originChainIDStr, mock.MatchedBy(matchTxs), uint64ToHexString(opts.RoBBlockNumber-1)). + Return(balances, nil) + + //backend.simulator.EXPECT().GetNonces(domain, mock.MatchedBy(matchTxs), uint64ToHexString(opts.RoBBlockNumber-1)).Return(nonces, nil) + //backend.simulator.EXPECT().GetBalances(domain, mock.MatchedBy(matchTxs), uint64ToHexString(opts.RoBBlockNumber-1)).Return(balances, nil) // simulation results, only the txs from this RoB will be simulated and the txs from ToB will be filtered out callBundleRes := make([]flashbotsrpc.FlashbotsCallBundleResult, 0, len(expectedTxs)) @@ -2587,9 +2607,9 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { SeqChainID: testSeqChainID, BuilderPubkey: *testBuilderPublicKey, BuilderSecretkey: *testBuilderSecretKey, - RoBBlockNumber: blockNumbers[remoteChainIDStr] - 1, + RoBBlockNumber: blockNumbers[remoteChainIDStr] - 2, ChunkID: id, - RoBChainID: remoteChainID, + RoBChainID: originChainID, Txs: robTxs, IsToB: false, } @@ -2631,6 +2651,12 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { // needed for ToB to be accepted backend.submitRoBChunk(t, opts, chainParser) + // Delay to ensure RoB is registered before checking + time.Sleep(2 * time.Second) + + // TODO: fix test case. checking if RoB is stored, seems to submit but not stored somehow? + chunks := backend.arcadia.chunkManager.Chunks() + fmt.Println(chunks) err = backend.arcadia.datastore.SetAuctionWinner(epoch, builderPkBytes[:]) require.NoError(t, err) redis := backend.redis From d164b156e1a611e0f19ac972b949b52d39e17460 Mon Sep 17 00:00:00 2001 From: NateAtNodeKit Date: Mon, 17 Feb 2025 14:36:44 -0500 Subject: [PATCH 06/31] update --- services/api/service.go | 11 +- services/api/service_test.go | 189 ++++++++++++++++++---------------- services/api/testing_utils.go | 15 +++ 3 files changed, 124 insertions(+), 91 deletions(-) diff --git a/services/api/service.go b/services/api/service.go index be4cd12d..ba731acb 100644 --- a/services/api/service.go +++ b/services/api/service.go @@ -66,7 +66,6 @@ const ( CancelTimeoutSecs = 2 SlotWindowToCheckAuctionWinner = 2 SafetyNonceDifference = uint64(20) - MaxDARetries = int(5) ) // prometheus counters @@ -441,6 +440,16 @@ func NewArcadiaAPI(opts ArcadiaAPIOpts) (api *ArcadiaAPI, err error) { return api, nil } +// setBlockSimRateLimiter used to set a block sim rate limiter. Only for test use. +func (api *ArcadiaAPI) setBlockSimRateLimiter(limiter simulator.IBlockSimRateLimiter) { + api.blockSimRateLimiter = limiter +} + +// setSeqClient used to set seq client on ArcadiaAPI. Only for test use. +func (api *ArcadiaAPI) setSeqClient(client seq.BaseSeqClient) { + api.seqClient = client +} + func (api *ArcadiaAPI) getRouter() http.Handler { // Main router mainRouter := mux.NewRouter() diff --git a/services/api/service_test.go b/services/api/service_test.go index 1b545a1b..aefcd775 100644 --- a/services/api/service_test.go +++ b/services/api/service_test.go @@ -179,6 +179,33 @@ func newTestbackendWithCustomDatastore(t *testing.T, redisCache *datastore.Redis return &backend } +// resetMockExpectations is used to reset seqclient mock expectations +func (be *testBackend) resetSeqClientMockExpectations(t *testing.T) { + be.seqcli = mseq.NewMockBaseSeqClient(t) + be.seqcli.EXPECT().SetOnNewBlockHandler(mock.Anything).Return() + be.seqcli.EXPECT().Parser().Return(&srpc.Parser{}).Maybe() + + be.simulator = msim.NewMockIBlockSimRateLimiter(t) + + be.arcadia.setBlockSimRateLimiter(be.simulator) + be.arcadia.setSeqClient(be.seqcli) +} + +func (be *testBackend) resetChunkManager(t *testing.T) { + config := chunkmanager.ChunkManagerConfig{ + ExpirationTime: 5 * time.Minute, + GCInterval: 10 * time.Second, + HeadToBNonce: 0, + Datastore: be.datastore, + Logger: common.TestLog, + } + + cm, err := chunkmanager.NewChunkManager(&config) + require.NoError(t, err) + + be.arcadia.chunkManager = cm +} + func newDatastores(t *testing.T) (*datastore.RedisCache, database.IDatabaseService) { redisClient, err := miniredis.Run() require.NoError(t, err) @@ -191,90 +218,54 @@ func newDatastores(t *testing.T) (*datastore.RedisCache, database.IDatabaseServi return redisCache, db } +// submitRoBChunk is a helper for submitting an rob chunk to the backend +// Note it will use the opts origin chain id for the chain and opts rob chain id must equal origin chain id func (be *testBackend) submitRoBChunk(t *testing.T, opts *CreateTestBlockSubmissionOpts, parser *srpc.Parser) { - // constructing RoB - robTxs := ethtypes.Transactions{ - // conflicting tx with prev ToB - CreateEthTransfer(t, &opts.OriginChainID, testAccounts[0].PrivateKey, testAccounts[1].Address, 20, testAccounts[0].Nonce, 21000, nil), + if opts.RoBChainID == nil { + panic("robchain id is required") } - robReq := CreateRoBReq(t, opts) - // testing chunk db conversions for rob - chunkDB, err := common.ConvertRoBChunkToDBType(robReq.RoBChunk()) - require.NoError(t, err) - chunk2, err := common.ConvertRoBChunkDBToRoBChunk(chunkDB, parser) - require.NoError(t, err) - require.NotNil(t, chunk2) - // compare fields - require.Equal(t, robReq.RoBChunk().ChainID, chunk2.ChainID, "ChainID mismatch") - require.Equal(t, robReq.RoBChunk().BlockNumber, chunk2.BlockNumber, "GetBlockNumber mismatch") - // compare Txs - require.Equal(t, len(robReq.RoBChunk().Txs), len(chunk2.Txs), "Number of transactions mismatch") - for i := range robReq.RoBChunk().Txs { - require.Equal(t, robReq.RoBChunk().Txs[i], chunk2.Txs[i], "Transaction mismatch at index %d", i) + if opts.OriginChainID.Uint64() != opts.RoBChainID.Uint64() { + panic("opts origin chain id must equal rob chain id") } - require.Equal(t, robReq.RoBChunk().SEQTxs(), chunk2.SEQTxs()) - require.Equal(t, robReq.RoBChunk().GetTxs().Len(), chunk2.GetTxs().Len()) - require.True(t, robReq.RoBChunk().RemovedBitSet().Equal(chunk2.RemovedBitSet())) - // instantiate backend - backend := newTestBackend(t) builderPK := opts.BuilderPubkey builderPKBytes := builderPK.Bytes() - err = backend.arcadia.datastore.SetAuctionWinner(opts.Epoch, builderPKBytes[:]) + originChainIDStr := common.ChainIDStr(&opts.OriginChainID) + redis := be.redis + redis.SetSizeTracker(be.arcadia.sizeTracker) + + // constructing RoB with txs from origin chain id + if opts.Txs == nil { + robTxs := ethtypes.Transactions{ + CreateEthTransfer(t, &opts.OriginChainID, testAccounts[0].PrivateKey, testAccounts[1].Address, 20, testAccounts[0].Nonce, 21000, nil), + } + opts.Txs = robTxs + } + robReq := CreateRoBReq(t, opts) + + // builder needs to be the auction winner in order to build + err := be.arcadia.datastore.SetAuctionWinner(opts.Epoch, builderPKBytes[:]) require.NoError(t, err) - redis := backend.redis - redis.SetSizeTracker(backend.arcadia.sizeTracker) - // register test rollup - backend.SetupRegisteredRollups(opts.Epoch, &opts.RemoteChainID) - backend.SetupRegisteredRollups(opts.Epoch, &opts.OriginChainID) + // register test rollup for the origin chain id so we can accept builder bids + be.SetupRegisteredRollups(opts.Epoch, &opts.OriginChainID) - // set up mock expectations - // shared calls for both chunks - backend.seqcli.EXPECT().Parser().Return(parser) - remoteChainIDStr := hexutil.EncodeBig(&opts.RemoteChainID) - originChainIDStr := hexutil.EncodeBig(&opts.OriginChainID) + // set up chunk simulation mock expectations + be.seqcli.EXPECT().Parser().Return(parser) t.Log("========setup & send RoB============") for domain, expectedTxs := range map[string]ethtypes.Transactions{ - remoteChainIDStr: robTxs, - originChainIDStr: robTxs, + originChainIDStr: opts.Txs, } { - matchTxs := func(txs ethtypes.Transactions) bool { - if len(txs) != len(expectedTxs) { - return false - } - for i, tx := range txs { - if tx.Hash().Hex() != expectedTxs[i].Hash().Hex() { - return false - } - } - return true - } - relatedAccounts, err := CollectAccountsFromTxs(expectedTxs, testAccounts) require.NoError(t, err) nonces := CollectNoncesFromEthAccounts(relatedAccounts) balances := CollectBalancesFromEthAccounts(relatedAccounts) - backend.simulator.EXPECT(). - GetNonces(remoteChainIDStr, mock.MatchedBy(matchTxs), uint64ToHexString(opts.RoBBlockNumber-1)). - Return(nonces, nil) - backend.simulator.EXPECT(). - GetNonces(originChainIDStr, mock.MatchedBy(matchTxs), uint64ToHexString(opts.RoBBlockNumber-1)). - Return(nonces, nil) - - backend.simulator.EXPECT(). - GetBalances(remoteChainIDStr, mock.MatchedBy(matchTxs), uint64ToHexString(opts.RoBBlockNumber-1)). - Return(balances, nil) - - backend.simulator.EXPECT(). - GetBalances(originChainIDStr, mock.MatchedBy(matchTxs), uint64ToHexString(opts.RoBBlockNumber-1)). - Return(balances, nil) - - //backend.simulator.EXPECT().GetNonces(domain, mock.MatchedBy(matchTxs), uint64ToHexString(opts.RoBBlockNumber-1)).Return(nonces, nil) - //backend.simulator.EXPECT().GetBalances(domain, mock.MatchedBy(matchTxs), uint64ToHexString(opts.RoBBlockNumber-1)).Return(balances, nil) + matchTxs := ExpectedMatchTxs(expectedTxs) + be.simulator.EXPECT().GetNonces(domain, mock.MatchedBy(matchTxs), uint64ToHexString(opts.RoBBlockNumber-1)).Return(nonces, nil) + be.simulator.EXPECT().GetBalances(domain, mock.MatchedBy(matchTxs), uint64ToHexString(opts.RoBBlockNumber-1)).Return(balances, nil) // simulation results, only the txs from this RoB will be simulated and the txs from ToB will be filtered out callBundleRes := make([]flashbotsrpc.FlashbotsCallBundleResult, 0, len(expectedTxs)) @@ -292,12 +283,12 @@ func (be *testBackend) submitRoBChunk(t *testing.T, opts *CreateTestBlockSubmiss BlockNumber: uint64ToHexString(opts.RoBBlockNumber - 1), StateBlockNumber: uint64ToHexString(opts.RoBBlockNumber - 1), } - backend.simulator.EXPECT(). + be.simulator.EXPECT(). SimBlockAndGetGasUsedForChain(mock.Anything, domain, &validationReq). Return(100, callBundleRes, nil) } - rr := backend.request(http.MethodPost, pathSubmitNewBlockRequest, robReq) + rr := be.request(http.MethodPost, pathSubmitNewBlockRequest, robReq) require.Equal(t, http.StatusOK, rr.Code) } @@ -2588,32 +2579,12 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { originChainIDStr: 100, remoteChainIDStr: 50, } - // setup rob submission originChainIDInt, err := common.ChainIDStrToChainID(originChainIDStr) require.NoError(t, err) remoteChainIDInt, err := common.ChainIDStrToChainID(remoteChainIDStr) require.NoError(t, err) id := ids.GenerateTestID() - robTxs := ethtypes.Transactions{ - // conflicting tx with prev ToB - CreateEthTransfer(t, originChainID, testAccounts[0].PrivateKey, testAccounts[1].Address, 20, testAccounts[0].Nonce, 21000, nil), - } - - opts := &CreateTestBlockSubmissionOpts{ - Epoch: epoch, - OriginChainID: *originChainIDInt, - RemoteChainID: *remoteChainIDInt, - SeqChainID: testSeqChainID, - BuilderPubkey: *testBuilderPublicKey, - BuilderSecretkey: *testBuilderSecretKey, - RoBBlockNumber: blockNumbers[remoteChainIDStr] - 2, - ChunkID: id, - RoBChainID: originChainID, - Txs: robTxs, - IsToB: false, - } - // constructing ToB bundleTxs := map[string]ethtypes.Transactions{ originChainIDStr: { @@ -2647,9 +2618,47 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { backend.SetupRegisteredRollups(epoch, originChainID) backend.SetupRegisteredRollups(epoch, remoteChainID) + // send in rob chunk for origin chain id + // needed for ToB to be accepted + robChainID := originChainIDInt + robOpts1 := &CreateTestBlockSubmissionOpts{ + Epoch: epoch, + OriginChainID: *robChainID, + RemoteChainID: *remoteChainIDInt, + SeqChainID: testSeqChainID, + BuilderPubkey: *testBuilderPublicKey, + BuilderSecretkey: *testBuilderSecretKey, + RoBBlockNumber: blockNumbers[originChainIDStr] - 1, + RoBChainID: robChainID, + ChunkID: id, + Txs: nil, + IsToB: false, + } + backend.submitRoBChunk(t, robOpts1, chainParser) + // send in rob chunk for remote chain id // needed for ToB to be accepted - backend.submitRoBChunk(t, opts, chainParser) + robChainID = remoteChainIDInt + robOpts2 := &CreateTestBlockSubmissionOpts{ + Epoch: epoch, + OriginChainID: *robChainID, + RemoteChainID: *remoteChainIDInt, + SeqChainID: testSeqChainID, + BuilderPubkey: *testBuilderPublicKey, + BuilderSecretkey: *testBuilderSecretKey, + RoBBlockNumber: blockNumbers[remoteChainIDStr] - 1, + RoBChainID: robChainID, + ChunkID: id, + Txs: nil, + IsToB: false, + } + backend.submitRoBChunk(t, robOpts2, chainParser) + + // + // Start main TOB test case + // + //backend.resetChunkManager(t) + backend.resetSeqClientMockExpectations(t) // Delay to ensure RoB is registered before checking time.Sleep(2 * time.Second) @@ -2699,8 +2708,8 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { require.NoError(t, err) nonces := CollectNoncesFromEthAccounts(relatedAccounts) balances := CollectBalancesFromEthAccounts(relatedAccounts) - backend.simulator.EXPECT().GetNonces(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumbers[domain])).Return(nonces, nil) - backend.simulator.EXPECT().GetBalances(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumbers[domain])).Return(balances, nil) + backend.simulator.EXPECT().GetNonces(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumbers[domain]-2)).Return(nonces, nil) + backend.simulator.EXPECT().GetBalances(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumbers[domain]-2)).Return(balances, nil) // simulation results, only the txs from this ToB will be simulated and the txs from RoB will be filtered out callBundleRes := make([]flashbotsrpc.FlashbotsCallBundleResult, 0, len(expectedTxs)) diff --git a/services/api/testing_utils.go b/services/api/testing_utils.go index c4284185..ba44de4d 100644 --- a/services/api/testing_utils.go +++ b/services/api/testing_utils.go @@ -666,3 +666,18 @@ func waitForServerReady(addr string, timeout time.Duration) error { } return fmt.Errorf("server not ready after %v", timeout) } + +// ExpectedMatchTxs creates a matcher functor that can be used in mock testing for tx matching +func ExpectedMatchTxs(expectedTxs ethtypes.Transactions) func(txs ethtypes.Transactions) bool { + return func(txs ethtypes.Transactions) bool { + if len(txs) != len(expectedTxs) { + return false + } + for i, tx := range txs { + if tx.Hash().Hex() != expectedTxs[i].Hash().Hex() { + return false + } + } + return true + } +} From 1830cb9be731dda458a8b377c22cc3c40c924e8c Mon Sep 17 00:00:00 2001 From: rikoeldon <106416799+rikoeldon@users.noreply.github.com> Date: Mon, 17 Feb 2025 16:50:58 -0500 Subject: [PATCH 07/31] base nonce fixed but fails at SimBlock call --- services/api/service_test.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/services/api/service_test.go b/services/api/service_test.go index aefcd775..87c5c75b 100644 --- a/services/api/service_test.go +++ b/services/api/service_test.go @@ -15,6 +15,7 @@ import ( mrand "math/rand" "net/http" "net/http/httptest" + "slices" "strconv" "strings" "sync" @@ -2683,6 +2684,11 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { allTxs, err := common.CollectTxsFromChunks([]*common.ArcadiaChunk{&tobReq.Chunk}) require.NoError(t, err) common.DisplayEthTxs(allTxs) + require.Greater(t, len(robOpts1.Txs), 0) + require.Greater(t, len(robOpts2.Txs), 0) + tobTxs[originChainIDStr] = slices.Insert(tobTxs[originChainIDStr], 0, robOpts1.Txs[0]) + tobTxs[remoteChainIDStr] = slices.Insert(tobTxs[remoteChainIDStr], 0, robOpts2.Txs[0]) + common.DisplayEthTxs(tobTxs) // expectations for the first tob for domain, expectedTxs := range tobTxs { matchTxs := func(txs ethtypes.Transactions) bool { @@ -2720,12 +2726,15 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { Revert: "", }) } - rawExpectedTxs, err := CollectRawTxs(expectedTxs) + //rawExpectedTxs, err := CollectRawTxs(expectedTxs) + modifiedExpectedTxs := expectedTxs[1:] + rawExpectedTxs, err := CollectRawTxs(modifiedExpectedTxs) + require.NoError(t, err) validationReq := common.BlockValidationRequest{ Txs: rawExpectedTxs, - BlockNumber: uint64ToHexString(blockNumbers[domain]), - StateBlockNumber: uint64ToHexString(blockNumbers[domain]), + BlockNumber: uint64ToHexString(blockNumbers[domain] - 2), + StateBlockNumber: uint64ToHexString(blockNumbers[domain] - 2), } backend.simulator.EXPECT(). SimBlockAndGetGasUsedForChain(mock.Anything, domain, &validationReq). From 06b9d6aaf65399718efbd0525b66d25b00702675 Mon Sep 17 00:00:00 2001 From: rikoeldon <106416799+rikoeldon@users.noreply.github.com> Date: Mon, 17 Feb 2025 17:31:30 -0500 Subject: [PATCH 08/31] quick nonce fix & checkpoint for debugging --- services/api/service_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/api/service_test.go b/services/api/service_test.go index 87c5c75b..7b5d573e 100644 --- a/services/api/service_test.go +++ b/services/api/service_test.go @@ -2589,7 +2589,7 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { // constructing ToB bundleTxs := map[string]ethtypes.Transactions{ originChainIDStr: { - CreateEthTransfer(t, originChainID, testAccounts[0].PrivateKey, testAccounts[1].Address, 100, testAccounts[0].Nonce, 21000, nil), + CreateEthTransfer(t, originChainID, testAccounts[0].PrivateKey, testAccounts[1].Address, 100, 1, 21000, nil), }, remoteChainIDStr: { CreateEthTransfer(t, remoteChainID, testAccounts[1].PrivateKey, testAccounts[0].Address, 100, testAccounts[1].Nonce, 21000, nil), From a8212ddce6da03923574b1674043c7e5ca2faa37 Mon Sep 17 00:00:00 2001 From: rikoeldon <106416799+rikoeldon@users.noreply.github.com> Date: Mon, 17 Feb 2025 21:36:57 -0500 Subject: [PATCH 09/31] progress, still debugging tests. len txs mismatch. --- services/api/service_test.go | 6 ++---- services/api/testing_utils.go | 1 + 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/services/api/service_test.go b/services/api/service_test.go index 7b5d573e..c59c7fbf 100644 --- a/services/api/service_test.go +++ b/services/api/service_test.go @@ -2709,7 +2709,6 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { fmt.Printf("%s, ", tx.Hash().Hex()) } fmt.Printf("]\n") - relatedAccounts, err := CollectAccountsFromTxs(expectedTxs, testAccounts) require.NoError(t, err) nonces := CollectNoncesFromEthAccounts(relatedAccounts) @@ -2726,9 +2725,7 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { Revert: "", }) } - //rawExpectedTxs, err := CollectRawTxs(expectedTxs) - modifiedExpectedTxs := expectedTxs[1:] - rawExpectedTxs, err := CollectRawTxs(modifiedExpectedTxs) + rawExpectedTxs, err := CollectRawTxs(expectedTxs) require.NoError(t, err) validationReq := common.BlockValidationRequest{ @@ -2745,6 +2742,7 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { require.Equal(t, http.StatusOK, rrCode) cmTxs, cmHeights, err := backend.arcadia.chunkManager.Txs() require.NoError(t, err) + // TODO: fails in TxsTheSame call. Somehow bundleTxs is len 1 while cmTxs is len 2? TxsTheSame(t, bundleTxs, cmTxs) require.Equal(t, blockNumbers, cmHeights) diff --git a/services/api/testing_utils.go b/services/api/testing_utils.go index ba44de4d..54f2305b 100644 --- a/services/api/testing_utils.go +++ b/services/api/testing_utils.go @@ -550,6 +550,7 @@ func MarshalSeqTxs(txs []*chain.Transaction) ([][]byte, error) { return seqTxs, nil } +// TODO: fails below in test func TxsTheSame(t *testing.T, expected map[string]ethtypes.Transactions, actual map[string]ethtypes.Transactions) { require.Equal(t, len(expected), len(actual)) for domain, expectedTxs := range expected { From 03810b270b738ad036375ece2d18bafc773c6c72 Mon Sep 17 00:00:00 2001 From: NateAtNodeKit Date: Tue, 18 Feb 2025 13:55:17 -0500 Subject: [PATCH 10/31] add txs hash stability test --- services/api/service_test.go | 62 ++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/services/api/service_test.go b/services/api/service_test.go index c59c7fbf..b18f881d 100644 --- a/services/api/service_test.go +++ b/services/api/service_test.go @@ -2300,6 +2300,68 @@ func TestBLSPublicKeyConversion(t *testing.T) { require.Equal(t, testBuilderPublicKey, newPk) } +func TestTxsHashStability(t *testing.T) { + epoch := uint64(100) + + originChainID := big.NewInt(45200) + originChainIDStr := hexutil.EncodeBig(originChainID) + remoteChainID := big.NewInt(45201) + remoteChainIDStr := hexutil.EncodeBig(remoteChainID) + blockNumbers := map[string]uint64{ + originChainIDStr: 100, + remoteChainIDStr: 50, + } + + testBuilderSecretKey, err := bls.GenerateRandomSecretKey() + require.NoError(t, err) + testBuilderPublicKey, err := bls.PublicKeyFromSecretKey(testBuilderSecretKey) + require.NoError(t, err) + testSeqChainID := ids.GenerateTestID() + + // constructing ToB + bundleTxs := map[string]ethtypes.Transactions{ + originChainIDStr: { + CreateEthTransfer(t, originChainID, testAccounts[0].PrivateKey, testAccounts[1].Address, 100, 1, 21000, nil), + }, + remoteChainIDStr: { + CreateEthTransfer(t, remoteChainID, testAccounts[1].PrivateKey, testAccounts[0].Address, 100, testAccounts[1].Nonce, 21000, nil), + }, + } + seqTx := CreateHypersdkBundleTx(t, testSeqChainID, bundleTxs) + oSeqTx, err := chain.MarshalTxs([]*chain.Transaction{seqTx}) + require.NoError(t, err) + + bundle := common.CrossRollupBundle{ + BundleHash: "0xbundle1", + Txs: oSeqTx, + RevertingTxHashes: nil, + } + + tobReq := CreateToBReq(t, &CreateTestBlockSubmissionOpts{ + Epoch: epoch, + ToBBlockNumber: blockNumbers, + BuilderPubkey: *testBuilderPublicKey, + BuilderSecretkey: *testBuilderSecretKey, + Bundles: []*common.CrossRollupBundle{&bundle}, + }) + + require.Greater(t, len(bundleTxs[originChainIDStr]), 0) + require.Greater(t, len(bundleTxs[remoteChainIDStr]), 0) + + bundleTxOriginHash := bundleTxs[originChainIDStr][0].Hash() + bundleTxRemoteHash := bundleTxs[remoteChainIDStr][0].Hash() + + tobReqTxs := tobReq.Chunk.ToB.GetTxs() + require.Greater(t, len(tobReqTxs[originChainIDStr]), 0) + require.Greater(t, len(tobReqTxs[remoteChainIDStr]), 0) + + tobReqTxOriginHash := tobReqTxs[originChainIDStr][0].Hash() + tobReqTxRemoteHash := tobReqTxs[remoteChainIDStr][0].Hash() + + require.Equal(t, bundleTxOriginHash, tobReqTxOriginHash) + require.Equal(t, bundleTxRemoteHash, tobReqTxRemoteHash) +} + func TestHandleSubmitNewBlockRequest(t *testing.T) { epoch := uint64(0) From 5e26f6cabe226d7398bfc18f850dc9176a0eb428 Mon Sep 17 00:00:00 2001 From: rikoeldon <106416799+rikoeldon@users.noreply.github.com> Date: Tue, 18 Feb 2025 15:26:56 -0500 Subject: [PATCH 11/31] tob base case passes! added rob submission for tob --- common/test_utils.go | 29 +++++++++++++++++++++++++++++ services/api/service_test.go | 23 ++++++++++++++++------- services/api/testing_utils.go | 9 +++++++++ 3 files changed, 54 insertions(+), 7 deletions(-) diff --git a/common/test_utils.go b/common/test_utils.go index a76022f5..bc9fb30d 100644 --- a/common/test_utils.go +++ b/common/test_utils.go @@ -256,6 +256,35 @@ func DisplayEthTxs(txs map[string]ethtypes.Transactions) { fmt.Printf("========txs info end=======\n") } +func FindTxHash(txLhs *ethtypes.Transaction, txs []*ethtypes.Transaction) bool { + if txLhs == nil { + panic("txLhs is nil") + } + lhsHash := txLhs.Hash() + for _, tx := range txs { + rhsHash := tx.Hash() + if lhsHash == rhsHash { + return true + } + } + return false +} + +func TxsHashUnorderedMatch(txsLhs []*ethtypes.Transaction, txsRhs []*ethtypes.Transaction) bool { + if len(txsLhs) != len(txsRhs) { + return false + } + + for _, tx := range txsLhs { + if !FindTxHash(tx, txsRhs) { + fmt.Printf("could not find tx hash " + tx.Hash().Hex()) + return false + } + } + + return true +} + func SyncMapLen(m *sync.Map) int { var length int m.Range(func(_, _ interface{}) bool { diff --git a/services/api/service_test.go b/services/api/service_test.go index b18f881d..4f588cc3 100644 --- a/services/api/service_test.go +++ b/services/api/service_test.go @@ -183,7 +183,7 @@ func newTestbackendWithCustomDatastore(t *testing.T, redisCache *datastore.Redis // resetMockExpectations is used to reset seqclient mock expectations func (be *testBackend) resetSeqClientMockExpectations(t *testing.T) { be.seqcli = mseq.NewMockBaseSeqClient(t) - be.seqcli.EXPECT().SetOnNewBlockHandler(mock.Anything).Return() + be.seqcli.EXPECT().SetOnNewBlockHandler(mock.Anything).Return().Maybe() be.seqcli.EXPECT().Parser().Return(&srpc.Parser{}).Maybe() be.simulator = msim.NewMockIBlockSimRateLimiter(t) @@ -221,7 +221,7 @@ func newDatastores(t *testing.T) (*datastore.RedisCache, database.IDatabaseServi // submitRoBChunk is a helper for submitting an rob chunk to the backend // Note it will use the opts origin chain id for the chain and opts rob chain id must equal origin chain id -func (be *testBackend) submitRoBChunk(t *testing.T, opts *CreateTestBlockSubmissionOpts, parser *srpc.Parser) { +func (be *testBackend) submitRoBChunk(t *testing.T, opts *CreateTestBlockSubmissionOpts, parser *srpc.Parser) *common.SubmitNewBlockRequest { if opts.RoBChainID == nil { panic("robchain id is required") } @@ -291,6 +291,8 @@ func (be *testBackend) submitRoBChunk(t *testing.T, opts *CreateTestBlockSubmiss rr := be.request(http.MethodPost, pathSubmitNewBlockRequest, robReq) require.Equal(t, http.StatusOK, rr.Code) + + return robReq } // setLowestToBNonceForEpoch is used to set the lowest tob nonce for epoch in datastore for testing @@ -2697,7 +2699,7 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { Txs: nil, IsToB: false, } - backend.submitRoBChunk(t, robOpts1, chainParser) + rob1 := backend.submitRoBChunk(t, robOpts1, chainParser) // send in rob chunk for remote chain id // needed for ToB to be accepted @@ -2715,12 +2717,11 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { Txs: nil, IsToB: false, } - backend.submitRoBChunk(t, robOpts2, chainParser) + rob2 := backend.submitRoBChunk(t, robOpts2, chainParser) // // Start main TOB test case // - //backend.resetChunkManager(t) backend.resetSeqClientMockExpectations(t) // Delay to ensure RoB is registered before checking @@ -2804,8 +2805,16 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { require.Equal(t, http.StatusOK, rrCode) cmTxs, cmHeights, err := backend.arcadia.chunkManager.Txs() require.NoError(t, err) - // TODO: fails in TxsTheSame call. Somehow bundleTxs is len 1 while cmTxs is len 2? - TxsTheSame(t, bundleTxs, cmTxs) + require.NotNil(t, rob1) + require.NotNil(t, rob2) + allChunksTxs, err := common.CollectTxsFromChunks([]*common.ArcadiaChunk{&tobReq.Chunk, &rob1.Chunk, &rob2.Chunk}) + require.NoError(t, err) + + common.DisplayEthTxs(allChunksTxs) + common.DisplayEthTxs(cmTxs) + TxsTheSameUnordered(t, allChunksTxs, cmTxs) + blockNumbers[originChainIDStr] = blockNumbers[originChainIDStr] - 2 + blockNumbers[remoteChainIDStr] = blockNumbers[remoteChainIDStr] - 2 require.Equal(t, blockNumbers, cmHeights) for _, bundle := range tobReq.Chunk.ToB.GetBundles() { diff --git a/services/api/testing_utils.go b/services/api/testing_utils.go index 54f2305b..b4d36ee6 100644 --- a/services/api/testing_utils.go +++ b/services/api/testing_utils.go @@ -563,6 +563,15 @@ func TxsTheSame(t *testing.T, expected map[string]ethtypes.Transactions, actual } } +func TxsTheSameUnordered(t *testing.T, expected map[string]ethtypes.Transactions, actual map[string]ethtypes.Transactions) { + require.Equal(t, len(expected), len(actual)) + for domain, expectedTxs := range expected { + actualTxs := actual[domain] + require.NotNil(t, actualTxs) + require.True(t, common.TxsHashUnorderedMatch(expectedTxs, actualTxs)) + } +} + func CreateTestEthTransaction(nonce uint64, value big.Int, gasLimit uint64, gasPrice big.Int, data []byte) *ethtypes.Transaction { toAddress := ethcommon.HexToAddress(TestAddressValue) _, err := crypto.HexToECDSA(TestPrivateKeyValue) From 49bdb7a51723175fcd40aef58e2af5f8457cc612 Mon Sep 17 00:00:00 2001 From: rikoeldon <106416799+rikoeldon@users.noreply.github.com> Date: Tue, 18 Feb 2025 16:16:23 -0500 Subject: [PATCH 12/31] added tob bundle filtering logic --- services/api/service.go | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/services/api/service.go b/services/api/service.go index ba731acb..e7899c3f 100644 --- a/services/api/service.go +++ b/services/api/service.go @@ -15,6 +15,7 @@ import ( "net/http" _ "net/http/pprof" "os" + "slices" "strconv" "strings" "sync" @@ -1643,16 +1644,9 @@ func (api *ArcadiaAPI) handleSubmitNewBlockRequest(w http.ResponseWriter, req *h // specific checks for either ToB or RoB if isToB { + bundlesToFilter := make([]*common.CrossRollupBundle, 0) for _, bundle := range blockReq.ToBChunk().Bundles { for _, chainIDStr := range bundle.Domains() { - // We will reject the ToB if we haven't received a RoB for each chain id it is using - if api.getNumChunksByChainID(chainIDStr) == 0 { - errMsg := fmt.Sprintf("tob rejected due to lack of RoB for chain ID [%s] ", chainIDStr) - log.WithError(err).Warn(errMsg) - api.RespondError(w, http.StatusBadRequest, errMsg) - return - } - namespace, err := common.ChainIDStrToNamespace(chainIDStr) if err != nil { errMsg := fmt.Sprintf("could not convert chain ID [%s] to namespace", chainIDStr) @@ -1666,6 +1660,25 @@ func (api *ArcadiaAPI) handleSubmitNewBlockRequest(w http.ResponseWriter, req *h api.RespondError(w, http.StatusBadRequest, errMsg) return } + // We will filter out the ToB bundle if we haven't received a RoB for each chain id it is using + if api.getNumChunksByChainID(chainIDStr) == 0 { + warnMsg := fmt.Sprintf("tob bundle filtered out due to lack of RoB for chain ID [%s] ", chainIDStr) + api.log.Warn(warnMsg) + bundlesToFilter = append(bundlesToFilter, bundle) + } + } + if len(bundlesToFilter) > 0 { + // removes all bundles to filter from tob + slices.DeleteFunc(blockReq.ToBChunk().Bundles, func(bundle *common.CrossRollupBundle) bool { + return slices.Contains(bundlesToFilter, bundle) + }) + + if len(blockReq.ToBChunk().Bundles) == 0 { + warnMsg := fmt.Sprintf("no bundles found in tob, all bundles filtered.") + log.Warn(warnMsg) + api.RespondError(w, http.StatusNoContent, warnMsg) + return + } } } // query the latest block numbers for each domain and assign to ToB From 2b24e4338b2dfc4e2b99ff9f601b7ac092a468c3 Mon Sep 17 00:00:00 2001 From: NateAtNodeKit Date: Wed, 19 Feb 2025 11:54:31 -0500 Subject: [PATCH 13/31] update tob filtering --- common/types.go | 52 +++++++++++++++++++++++++++++++++++++++-- services/api/service.go | 35 ++++++++++++++++++++++----- 2 files changed, 79 insertions(+), 8 deletions(-) diff --git a/common/types.go b/common/types.go index 1de9d155..1597945e 100644 --- a/common/types.go +++ b/common/types.go @@ -229,6 +229,17 @@ func (tob *ToBChunk) GetBundles() []*CrossRollupBundle { return ret } +func (tob *ToBChunk) IsBundleIdxFiltered(idx int) bool { + tob.l.RLock() + defer tob.l.RUnlock() + + if idx >= len(tob.Bundles) { + return false + } + + return tob.removedBitSet.Test(uint(idx)) +} + func (tob *ToBChunk) GetTxs() map[string]ethtypes.Transactions { tob.l.RLock() defer tob.l.RUnlock() @@ -348,6 +359,40 @@ func (tob *ToBChunk) removeBundleContainTx(txHash string) (*CrossRollupBundle, e tob.removedBitSet = tob.removedBitSet.Set(uint(bundleIdx2remove)) // re-populate [tob.txs] + tob.repopulateToBTxs() + + return tob.Bundles[bundleIdx2remove], nil +} + +func (tob *ToBChunk) FilterBundleWithHash(bundleHash string) (*CrossRollupBundle, error) { + tob.l.Lock() + defer tob.l.Unlock() + + var foundIdx int + var foundBundle *CrossRollupBundle + + for bundleIdx, bundle := range tob.Bundles { + if bundle.BundleHash == bundleHash { + foundIdx = bundleIdx + foundBundle = bundle + break + } + } + + if foundBundle == nil { + return nil, fmt.Errorf("filterBundleWithHash found no bundle hash [%s]", bundleHash) + } + + // mark as removed + tob.removedBitSet = tob.removedBitSet.Set(uint(foundIdx)) + + // re-populate [tob.txs] + tob.repopulateToBTxs() + + return tob.Bundles[foundIdx], nil +} + +func (tob *ToBChunk) repopulateToBTxs() { tob.txs = make(map[string]ethtypes.Transactions) for bundleIdx, bundle := range tob.Bundles { // continue as this bundle was removed @@ -360,13 +405,12 @@ func (tob *ToBChunk) removeBundleContainTx(txHash string) (*CrossRollupBundle, e tob.txs[domain] = l } } + // track domains that contain txs in it tob.domains = make(map[string]struct{}) for domain := range tob.txs { tob.domains[domain] = struct{}{} } - - return tob.Bundles[bundleIdx2remove], nil } // LowestBlockNumber return the tracked lowest heights for domains, this prevents the situation that @@ -1523,6 +1567,10 @@ func (b *CrossRollupBundle) Domains() []string { return maps.Keys(b.txs) } +func (b *CrossRollupBundle) HasTxs() bool { + return len(b.txs) > 0 +} + func (b *CrossRollupBundle) ContainTx(txHash string) bool { for _, txs := range b.txs { contain := slices.ContainsFunc(txs, func(t *ethtypes.Transaction) bool { diff --git a/services/api/service.go b/services/api/service.go index e7899c3f..f216c8cf 100644 --- a/services/api/service.go +++ b/services/api/service.go @@ -15,7 +15,6 @@ import ( "net/http" _ "net/http/pprof" "os" - "slices" "strconv" "strings" "sync" @@ -1646,6 +1645,13 @@ func (api *ArcadiaAPI) handleSubmitNewBlockRequest(w http.ResponseWriter, req *h if isToB { bundlesToFilter := make([]*common.CrossRollupBundle, 0) for _, bundle := range blockReq.ToBChunk().Bundles { + if !bundle.HasTxs() { + errMsg := fmt.Sprintf("bundle with hash [%s] contained no txs, rejecting request", bundle.BundleHash) + log.WithError(err).Warn(errMsg) + api.RespondError(w, http.StatusBadRequest, errMsg) + return + } + for _, chainIDStr := range bundle.Domains() { namespace, err := common.ChainIDStrToNamespace(chainIDStr) if err != nil { @@ -1654,12 +1660,15 @@ func (api *ArcadiaAPI) handleSubmitNewBlockRequest(w http.ResponseWriter, req *h api.RespondError(w, http.StatusBadRequest, errMsg) return } + + // The given rollup for chain id must be registered for this auction period else we reject if !api.datastore.IsRollupRegistered(headEpoch, namespace) { errMsg := fmt.Sprintf("builder chunk tried to build tob chunk with cross bundle rollup for unregistered rollup chain id [%s]", chainIDStr) log.Warn(errMsg) api.RespondError(w, http.StatusBadRequest, errMsg) return } + // We will filter out the ToB bundle if we haven't received a RoB for each chain id it is using if api.getNumChunksByChainID(chainIDStr) == 0 { warnMsg := fmt.Sprintf("tob bundle filtered out due to lack of RoB for chain ID [%s] ", chainIDStr) @@ -1667,13 +1676,26 @@ func (api *ArcadiaAPI) handleSubmitNewBlockRequest(w http.ResponseWriter, req *h bundlesToFilter = append(bundlesToFilter, bundle) } } + + // mark any bundles to be filtered as removed if len(bundlesToFilter) > 0 { - // removes all bundles to filter from tob - slices.DeleteFunc(blockReq.ToBChunk().Bundles, func(bundle *common.CrossRollupBundle) bool { - return slices.Contains(bundlesToFilter, bundle) - }) + for _, bundle := range bundlesToFilter { + _, err = blockReq.ToBChunk().FilterBundleWithHash(bundle.BundleHash) + if err != nil { + errMsg := fmt.Sprintf("failed to find bundle hash [%s] when trying to filter, possible state corruption", bundle.BundleHash) + log.Warn(errMsg) + api.RespondError(w, http.StatusBadRequest, errMsg) + return + } + } - if len(blockReq.ToBChunk().Bundles) == 0 { + // If all bundles are filtered, then we can reject this block. + var foundValidBundle bool + for i := 0; i < len(blockReq.ToBChunk().Bundles); i++ { + foundValidBundle = foundValidBundle || !blockReq.ToBChunk().IsBundleIdxFiltered(i) + } + + if !foundValidBundle { warnMsg := fmt.Sprintf("no bundles found in tob, all bundles filtered.") log.Warn(warnMsg) api.RespondError(w, http.StatusNoContent, warnMsg) @@ -1681,6 +1703,7 @@ func (api *ArcadiaAPI) handleSubmitNewBlockRequest(w http.ResponseWriter, req *h } } } + // query the latest block numbers for each domain and assign to ToB blockNumbers, err := api.blockSimRateLimiter.GetBlockNumber(blockReq.ToBChunk().Domains()) if err != nil { From c853ea59230bb68b8240f878b9770c8cf9d58362 Mon Sep 17 00:00:00 2001 From: NateAtNodeKit Date: Wed, 19 Feb 2025 12:24:56 -0500 Subject: [PATCH 14/31] tob filtering updates --- services/api/service.go | 42 +++++++++++++++++------------------ services/api/service_test.go | 15 ------------- services/api/testing_utils.go | 1 - 3 files changed, 21 insertions(+), 37 deletions(-) diff --git a/services/api/service.go b/services/api/service.go index f216c8cf..5cf8e867 100644 --- a/services/api/service.go +++ b/services/api/service.go @@ -1678,27 +1678,12 @@ func (api *ArcadiaAPI) handleSubmitNewBlockRequest(w http.ResponseWriter, req *h } // mark any bundles to be filtered as removed - if len(bundlesToFilter) > 0 { - for _, bundle := range bundlesToFilter { - _, err = blockReq.ToBChunk().FilterBundleWithHash(bundle.BundleHash) - if err != nil { - errMsg := fmt.Sprintf("failed to find bundle hash [%s] when trying to filter, possible state corruption", bundle.BundleHash) - log.Warn(errMsg) - api.RespondError(w, http.StatusBadRequest, errMsg) - return - } - } - - // If all bundles are filtered, then we can reject this block. - var foundValidBundle bool - for i := 0; i < len(blockReq.ToBChunk().Bundles); i++ { - foundValidBundle = foundValidBundle || !blockReq.ToBChunk().IsBundleIdxFiltered(i) - } - - if !foundValidBundle { - warnMsg := fmt.Sprintf("no bundles found in tob, all bundles filtered.") - log.Warn(warnMsg) - api.RespondError(w, http.StatusNoContent, warnMsg) + for _, bundle := range bundlesToFilter { + _, err = blockReq.ToBChunk().FilterBundleWithHash(bundle.BundleHash) + if err != nil { + errMsg := fmt.Sprintf("failed to find bundle hash [%s] when trying to filter, possible state corruption", bundle.BundleHash) + log.Warn(errMsg) + api.RespondError(w, http.StatusBadRequest, errMsg) return } } @@ -1881,6 +1866,21 @@ func (api *ArcadiaAPI) handleSubmitNewBlockRequest(w http.ResponseWriter, req *h return } + if isToB { + // If all bundles are filtered, then we can reject this block. + // Note that simulation can also filter blocks. + var foundValidBundle bool + for i := 0; i < len(blockReq.ToBChunk().Bundles); i++ { + foundValidBundle = foundValidBundle || !blockReq.ToBChunk().IsBundleIdxFiltered(i) + } + if !foundValidBundle { + warnMsg := "no bundles found in tob, all bundles filtered" + log.Warn(warnMsg) + api.RespondError(w, http.StatusNoContent, warnMsg) + return + } + } + // we will only know the stable ToBNonce after adding this chunk to chunk manager and chunk manager will assign the latest one to it if err := api.chunkManager.AddChunk(chunk); err != nil { log.WithError(err).Warn("unable to add chunk to chunk manager") diff --git a/services/api/service_test.go b/services/api/service_test.go index 4f588cc3..2cbfa9e9 100644 --- a/services/api/service_test.go +++ b/services/api/service_test.go @@ -192,21 +192,6 @@ func (be *testBackend) resetSeqClientMockExpectations(t *testing.T) { be.arcadia.setSeqClient(be.seqcli) } -func (be *testBackend) resetChunkManager(t *testing.T) { - config := chunkmanager.ChunkManagerConfig{ - ExpirationTime: 5 * time.Minute, - GCInterval: 10 * time.Second, - HeadToBNonce: 0, - Datastore: be.datastore, - Logger: common.TestLog, - } - - cm, err := chunkmanager.NewChunkManager(&config) - require.NoError(t, err) - - be.arcadia.chunkManager = cm -} - func newDatastores(t *testing.T) (*datastore.RedisCache, database.IDatabaseService) { redisClient, err := miniredis.Run() require.NoError(t, err) diff --git a/services/api/testing_utils.go b/services/api/testing_utils.go index b4d36ee6..8b320426 100644 --- a/services/api/testing_utils.go +++ b/services/api/testing_utils.go @@ -550,7 +550,6 @@ func MarshalSeqTxs(txs []*chain.Transaction) ([][]byte, error) { return seqTxs, nil } -// TODO: fails below in test func TxsTheSame(t *testing.T, expected map[string]ethtypes.Transactions, actual map[string]ethtypes.Transactions) { require.Equal(t, len(expected), len(actual)) for domain, expectedTxs := range expected { From da9d8e071edb905f8eda2d67a803c810f5c78fb9 Mon Sep 17 00:00:00 2001 From: NateAtNodeKit Date: Wed, 19 Feb 2025 17:13:56 -0500 Subject: [PATCH 15/31] tobnonce test wip --- services/api/service_test.go | 77 +++++++++++++++++++++++++++--------- 1 file changed, 58 insertions(+), 19 deletions(-) diff --git a/services/api/service_test.go b/services/api/service_test.go index 2cbfa9e9..630c68de 100644 --- a/services/api/service_test.go +++ b/services/api/service_test.go @@ -2712,7 +2712,6 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { // Delay to ensure RoB is registered before checking time.Sleep(2 * time.Second) - // TODO: fix test case. checking if RoB is stored, seems to submit but not stored somehow? chunks := backend.arcadia.chunkManager.Chunks() fmt.Println(chunks) err = backend.arcadia.datastore.SetAuctionWinner(epoch, builderPkBytes[:]) @@ -6564,10 +6563,10 @@ func TestToBNonceState(t *testing.T) { // constructing ToB bundleTxs := map[string]ethtypes.Transactions{ originChainIDStr: { - CreateEthTransfer(t, originChainID, testAccounts[0].PrivateKey, testAccounts[1].Address, 100, testAccounts[0].Nonce, 21000, nil), + CreateEthTransfer(t, originChainID, testAccounts[0].PrivateKey, testAccounts[1].Address, 100, 1, 21000, nil), }, remoteChainIDStr: { - CreateEthTransfer(t, remoteChainID, testAccounts[1].PrivateKey, testAccounts[0].Address, 100, testAccounts[1].Nonce, 21000, nil), + CreateEthTransfer(t, remoteChainID, testAccounts[1].PrivateKey, testAccounts[0].Address, 100, 1, 21000, nil), }, } seqTx := CreateHypersdkBundleTx(t, testSeqChainID, bundleTxs) @@ -6603,27 +6602,66 @@ func TestToBNonceState(t *testing.T) { // shared calls for both chunks backend.seqcli.EXPECT().Parser().Return(chainParser) + originChainIDInt, err := common.ChainIDStrToChainID(originChainIDStr) + require.NoError(t, err) + remoteChainIDInt, err := common.ChainIDStrToChainID(remoteChainIDStr) + require.NoError(t, err) + id := ids.GenerateTestID() + + // send in rob chunk for origin chain id + // needed for ToB to be accepted + robChainID := originChainIDInt + robOpts1 := &CreateTestBlockSubmissionOpts{ + Epoch: epoch, + OriginChainID: *robChainID, + RemoteChainID: *remoteChainIDInt, + SeqChainID: testSeqChainID, + BuilderPubkey: *testBuilderPublicKey, + BuilderSecretkey: *testBuilderSecretKey, + RoBBlockNumber: blockNumbers[originChainIDStr] - 1, + RoBChainID: robChainID, + ChunkID: id, + Txs: nil, + IsToB: false, + } + //rob1 := backend.submitRoBChunk(t, robOpts1, chainParser) + backend.submitRoBChunk(t, robOpts1, chainParser) + + // send in rob chunk for remote chain id + // needed for ToB to be accepted + robChainID = remoteChainIDInt + require.NoError(t, err) + robOpts2 := &CreateTestBlockSubmissionOpts{ + Epoch: epoch, + OriginChainID: *robChainID, + RemoteChainID: *remoteChainIDInt, + SeqChainID: testSeqChainID, + BuilderPubkey: *testBuilderPublicKey, + BuilderSecretkey: *testBuilderSecretKey, + RoBBlockNumber: blockNumbers[remoteChainIDStr] - 1, + RoBChainID: robChainID, + ChunkID: id, + Txs: nil, + IsToB: false, + } + //rob2 := backend.submitRoBChunk(t, robOpts2, chainParser) + backend.submitRoBChunk(t, robOpts2, chainParser) + + backend.resetSeqClientMockExpectations(t) + backend.simulator.EXPECT().GetBlockNumber([]string{originChainIDStr, remoteChainIDStr}).Return(blockNumbers, nil) + t.Log("========setup & send TOB============") tobTxs, err := common.CollectTxsFromChunks([]*common.ArcadiaChunk{&tobReq.Chunk}) require.NoError(t, err) common.DisplayEthTxs(tobTxs) allTxs, err := common.CollectTxsFromChunks([]*common.ArcadiaChunk{&tobReq.Chunk}) require.NoError(t, err) + tobTxs[originChainIDStr] = slices.Insert(tobTxs[originChainIDStr], 0, robOpts1.Txs[0]) + tobTxs[remoteChainIDStr] = slices.Insert(tobTxs[remoteChainIDStr], 0, robOpts2.Txs[0]) common.DisplayEthTxs(allTxs) + // expectations for the first tob for domain, expectedTxs := range tobTxs { - matchTxs := func(txs ethtypes.Transactions) bool { - - if len(txs) != len(expectedTxs) { - return false - } - for i, tx := range txs { - if tx.Hash().Hex() != expectedTxs[i].Hash().Hex() { - return false - } - } - return true - } fmt.Printf("expected txs for domain: %s\n", domain) fmt.Printf("[") for _, tx := range expectedTxs { @@ -6635,8 +6673,8 @@ func TestToBNonceState(t *testing.T) { require.NoError(t, err) nonces := CollectNoncesFromEthAccounts(relatedAccounts) balances := CollectBalancesFromEthAccounts(relatedAccounts) - backend.simulator.EXPECT().GetNonces(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumbers[domain])).Return(nonces, nil) - backend.simulator.EXPECT().GetBalances(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumbers[domain])).Return(balances, nil) + backend.simulator.EXPECT().GetNonces(domain, mock.MatchedBy(ExpectedMatchTxs(expectedTxs)), uint64ToHexString(blockNumbers[domain]-2)).Return(nonces, nil) + backend.simulator.EXPECT().GetBalances(domain, mock.MatchedBy(ExpectedMatchTxs(expectedTxs)), uint64ToHexString(blockNumbers[domain]-2)).Return(balances, nil) // simulation results, only the txs from this ToB will be simulated and the txs from RoB will be filtered out callBundleRes := make([]flashbotsrpc.FlashbotsCallBundleResult, 0, len(expectedTxs)) @@ -6651,8 +6689,8 @@ func TestToBNonceState(t *testing.T) { require.NoError(t, err) validationReq := common.BlockValidationRequest{ Txs: rawExpectedTxs, - BlockNumber: uint64ToHexString(blockNumbers[domain]), - StateBlockNumber: uint64ToHexString(blockNumbers[domain]), + BlockNumber: uint64ToHexString(blockNumbers[domain] - 2), + StateBlockNumber: uint64ToHexString(blockNumbers[domain] - 2), } backend.simulator.EXPECT(). SimBlockAndGetGasUsedForChain(mock.Anything, domain, &validationReq). @@ -6672,6 +6710,7 @@ func TestToBNonceState(t *testing.T) { require.Equal(t, common.BundleAccepted, status) } }) + arcadia2, err := NewArcadiaAPI(*backend.currOpts) require.NoError(t, err) require.Equal(t, arcadia2.chunkManager.ToBNonce(), uint64(1)) From 1b6651e3032b8dcc014f9fef01f5f17983fa61d4 Mon Sep 17 00:00:00 2001 From: rikoeldon <106416799+rikoeldon@users.noreply.github.com> Date: Wed, 19 Feb 2025 20:01:39 -0500 Subject: [PATCH 16/31] debugging failing tests checkpoint --- services/api/service_test.go | 88 ++++++++++++++++++++++++++++-------- 1 file changed, 69 insertions(+), 19 deletions(-) diff --git a/services/api/service_test.go b/services/api/service_test.go index 630c68de..46b5f7ae 100644 --- a/services/api/service_test.go +++ b/services/api/service_test.go @@ -3059,15 +3059,15 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { // conflicting tx with prev ToB CreateEthTransfer(t, originChainID, testAccounts[0].PrivateKey, testAccounts[1].Address, 20, testAccounts[0].Nonce, 21000, nil), } - robReq := CreateRoBReq(t, &CreateTestBlockSubmissionOpts{ - Epoch: epoch, - SeqChainID: testSeqChainID, - RoBChainID: originChainID, - RoBBlockNumber: blockNumbers[originChainIDStr] - 1, - BuilderPubkey: *testBuilderPublicKey, - BuilderSecretkey: *testBuilderSecretKey, - Txs: robTxs, - }) + //robReq := CreateRoBReq(t, &CreateTestBlockSubmissionOpts{ + // Epoch: epoch, + // SeqChainID: testSeqChainID, + // RoBChainID: originChainID, + // RoBBlockNumber: blockNumbers[originChainIDStr] - 1, + // BuilderPubkey: *testBuilderPublicKey, + // BuilderSecretkey: *testBuilderSecretKey, + // Txs: robTxs, + //}) // constructing ToB bundleTxs := map[string]ethtypes.Transactions{ @@ -3098,6 +3098,26 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { // instantiate backend backend := newTestBackend(t) + // send rob + remoteChainIDInt, err := common.ChainIDStrToChainID(remoteChainIDStr) + require.NoError(t, err) + robChainID := remoteChainIDInt + id := ids.GenerateTestID() + robOpts1 := &CreateTestBlockSubmissionOpts{ + Epoch: epoch, + OriginChainID: *robChainID, + RemoteChainID: *remoteChainIDInt, + SeqChainID: testSeqChainID, + BuilderPubkey: *testBuilderPublicKey, + BuilderSecretkey: *testBuilderSecretKey, + RoBBlockNumber: blockNumbers[originChainIDStr] - 1, + RoBChainID: robChainID, + ChunkID: id, + Txs: robTxs, + IsToB: false, + } + robReq := backend.submitRoBChunk(t, robOpts1, chainParser) + err = backend.arcadia.datastore.SetAuctionWinner(epoch, builderPkBytes[:]) require.NoError(t, err) redis := backend.redis @@ -3161,8 +3181,8 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { Return(100, callBundleRes, nil) } - rrCode1 := processBlockRequest(backend, robReq) - require.Equal(t, http.StatusOK, rrCode1) + //rrCode1 := processBlockRequest(backend, robReq) + //require.Equal(t, http.StatusOK, rrCode1) t.Log("========setup & send TOB============") allTxs, err := common.CollectTxsFromChunks([]*common.ArcadiaChunk{&robReq.Chunk, &tobReq.Chunk}) @@ -5393,15 +5413,45 @@ func TestOverallFlow(t *testing.T) { } // constructing RoB - robReq := CreateRoBReq(t, &CreateTestBlockSubmissionOpts{ - Epoch: epoch + 2, - SeqChainID: seqChainID, - RoBChainID: originChainID, + //robReq := CreateRoBReq(t, &CreateTestBlockSubmissionOpts{ + // Epoch: epoch + 2, + // SeqChainID: seqChainID, + // RoBChainID: originChainID, + // RoBBlockNumber: blockNumbers[originChainIDStr] - 1, + // BuilderPubkey: *builderPK2, + // BuilderSecretkey: *builderSK2, + // Txs: robTxs, + //}) + + // send in rob chunk for origin chain id + // needed for ToB to be accepted + remoteChainIDInt, err := common.ChainIDStrToChainID(remoteChainIDStr) + require.NoError(t, err) + robChainID := remoteChainIDInt + testSeqChainID := ids.GenerateTestID() + testBuilderSecretKey, err := bls.GenerateRandomSecretKey() + require.NoError(t, err) + testBuilderPublicKey, err := bls.PublicKeyFromSecretKey(testBuilderSecretKey) + require.NoError(t, err) + id := ids.GenerateTestID() + + robOpts1 := &CreateTestBlockSubmissionOpts{ + Epoch: epoch, + OriginChainID: *robChainID, + RemoteChainID: *remoteChainIDInt, + SeqChainID: testSeqChainID, + BuilderPubkey: *testBuilderPublicKey, + BuilderSecretkey: *testBuilderSecretKey, RoBBlockNumber: blockNumbers[originChainIDStr] - 1, - BuilderPubkey: *builderPK2, - BuilderSecretkey: *builderSK2, - Txs: robTxs, - }) + RoBChainID: robChainID, + ChunkID: id, + Txs: nil, + IsToB: false, + } + robReq := backend.submitRoBChunk(t, robOpts1, chainParser) + fmt.Println(robReq) + //backend.resetSeqClientMockExpectations(t) + backend.simulator.EXPECT().GetBlockNumber([]string{originChainIDStr, remoteChainIDStr}).Return(blockNumbers, nil) // constructing tob1 bundle1Txs := map[string]ethtypes.Transactions{ From e3905a285a8816f798e3add7b8d10c49acf0ce20 Mon Sep 17 00:00:00 2001 From: NateAtNodeKit Date: Thu, 20 Feb 2025 13:15:56 -0500 Subject: [PATCH 17/31] tobstate update --- common/types.go | 11 +++++++---- services/api/service_test.go | 6 +++++- services/api/tx_table.go | 9 +++++---- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/common/types.go b/common/types.go index 1597945e..10b2a77d 100644 --- a/common/types.go +++ b/common/types.go @@ -205,10 +205,12 @@ type ToBChunk struct { txHash2BundleHash map[string]string revertingTxHashes map[string]struct{} + // refers to whether bundle at idx has been removed removedBitSet *bitset.BitSet - domains map[string]struct{} // chain ids or rollup ids - seqTxs []*chain.Transaction - initialized bool + + domains map[string]struct{} // chain ids or rollup ids + seqTxs []*chain.Transaction + initialized bool l sync.RWMutex } @@ -355,6 +357,7 @@ func (tob *ToBChunk) removeBundleContainTx(txHash string) (*CrossRollupBundle, e bundleIdx2remove := slices.IndexFunc(tob.Bundles, func(crb *CrossRollupBundle) bool { return crb.BundleHash == bundleHash }) + // mark as removed tob.removedBitSet = tob.removedBitSet.Set(uint(bundleIdx2remove)) @@ -571,7 +574,7 @@ type RoBChunk struct { BlockNumber uint64 `json:"block_number"` // following fields will be populated after initialization - removedBitSet *bitset.BitSet + removedBitSet *bitset.BitSet // refers to whether tx within txs at idx has been removed initialized bool txs ethtypes.Transactions seqTxs []*chain.Transaction diff --git a/services/api/service_test.go b/services/api/service_test.go index 46b5f7ae..69a9e41f 100644 --- a/services/api/service_test.go +++ b/services/api/service_test.go @@ -6598,6 +6598,7 @@ func TestToBNonceState(t *testing.T) { for _, acct := range testAccounts { t.Logf("acct(%s) info: nonce(%d), balance(%d)\n", acct.Address.Hex(), acct.Nonce, acct.Balance.Int64()) } + backend := newTestBackend(t) t.Run("Run valid base case, just ToB", func(t *testing.T) { @@ -6613,7 +6614,7 @@ func TestToBNonceState(t *testing.T) { // constructing ToB bundleTxs := map[string]ethtypes.Transactions{ originChainIDStr: { - CreateEthTransfer(t, originChainID, testAccounts[0].PrivateKey, testAccounts[1].Address, 100, 1, 21000, nil), + CreateEthTransfer(t, originChainID, testAccounts[0].PrivateKey, testAccounts[1].Address, 100, 0, 21000, nil), }, remoteChainIDStr: { CreateEthTransfer(t, remoteChainID, testAccounts[1].PrivateKey, testAccounts[0].Address, 100, 1, 21000, nil), @@ -6706,8 +6707,10 @@ func TestToBNonceState(t *testing.T) { common.DisplayEthTxs(tobTxs) allTxs, err := common.CollectTxsFromChunks([]*common.ArcadiaChunk{&tobReq.Chunk}) require.NoError(t, err) + tobTxs[originChainIDStr] = slices.Insert(tobTxs[originChainIDStr], 0, robOpts1.Txs[0]) tobTxs[remoteChainIDStr] = slices.Insert(tobTxs[remoteChainIDStr], 0, robOpts2.Txs[0]) + common.DisplayEthTxs(allTxs) // expectations for the first tob @@ -6723,6 +6726,7 @@ func TestToBNonceState(t *testing.T) { require.NoError(t, err) nonces := CollectNoncesFromEthAccounts(relatedAccounts) balances := CollectBalancesFromEthAccounts(relatedAccounts) + backend.simulator.EXPECT().GetNonces(domain, mock.MatchedBy(ExpectedMatchTxs(expectedTxs)), uint64ToHexString(blockNumbers[domain]-2)).Return(nonces, nil) backend.simulator.EXPECT().GetBalances(domain, mock.MatchedBy(ExpectedMatchTxs(expectedTxs)), uint64ToHexString(blockNumbers[domain]-2)).Return(balances, nil) diff --git a/services/api/tx_table.go b/services/api/tx_table.go index 58a2c10a..dece3471 100644 --- a/services/api/tx_table.go +++ b/services/api/tx_table.go @@ -31,7 +31,7 @@ func (t *TxTable) AddBundledTxns(txns []*ethtypes.Transaction) error { for _, tx := range txns { sender, err := common.ExtractSender(tx) if err != nil { - return fmt.Errorf("unable to extract sender from tx: %s err: %w", tx.Hash().Hex(), err) + return fmt.Errorf("unable to extract sender from tx: [%s], sender: [%s], err: %w", tx.Hash().Hex(), sender, err) } // nonce check @@ -44,7 +44,7 @@ func (t *TxTable) AddBundledTxns(txns []*ethtypes.Transaction) error { nonce = nonce + uint64(len(t.table[sender])) + uint64(len(pending[sender])) // nonce not consecutive if tx.Nonce() != nonce { - return fmt.Errorf("nonce not consecutive: wanted: %d, actual: %d", nonce, tx.Nonce()) + return fmt.Errorf("nonce not consecutive: wanted: [%d], actual: [%d], for tx [%s] with sender [%s]", nonce, tx.Nonce(), tx.Hash().Hex(), sender) } // balance check, the balance consumed can be negative for consuming or positive for receiving funds(transfer) @@ -59,14 +59,14 @@ func (t *TxTable) AddBundledTxns(txns []*ethtypes.Transaction) error { // Intrinsic Gas Calculation: https://github.com/wolflo/evm-opcodes/blob/main/gas.md gas := intrinsicGas(tx) if tx.Gas() < uint64(gas) { - return fmt.Errorf("provided insufficient gas, want: %d, provided: %d", gas, tx.Gas()) + return fmt.Errorf("provided insufficient gas, want: %d, provided: %d, for tx [%s] with sender [%s]", gas, tx.Gas(), tx.Hash().Hex(), sender) } // accumulates txValue + gas to consumed consumed = consumed.Add(consumed, txValue) consumed = consumed.Add(consumed, big.NewInt(int64(gas))) - // consumed = consumed.Add(consumed, big.NewInt(int64(21000))) balanceConsumed[sender] = consumed.Neg(consumed) + if tx.Value().Cmp(big.NewInt(0)) != 0 { recipient := tx.To().Hex() if _, ok := balanceConsumed[recipient]; !ok { @@ -98,6 +98,7 @@ func (t *TxTable) AddBundledTxns(txns []*ethtypes.Transaction) error { balanceAfterConsuming := big.NewInt(0) balanceAfterConsuming = balanceAfterConsuming.Add(balanceAfterConsuming, consumed) balanceAfterConsuming = balanceAfterConsuming.Add(balanceAfterConsuming, remainBalance) + // no enough balance to cover tx fee + value if balanceAfterConsuming.Cmp(big.NewInt(0)) == -1 { return fmt.Errorf("remaining balance for sender(%s) cannot cover tx execution(value + gas)", sender) From 0eaa3cc65b8fb4a9ea0745aa01a41255497efd6c Mon Sep 17 00:00:00 2001 From: bianyuanop Date: Thu, 20 Feb 2025 14:46:16 -0500 Subject: [PATCH 18/31] fix TestToBNonceState --- common/test_utils.go | 6 ++- services/api/service_test.go | 71 +++++++++++++++++------------------- 2 files changed, 38 insertions(+), 39 deletions(-) diff --git a/common/test_utils.go b/common/test_utils.go index bc9fb30d..9efb9485 100644 --- a/common/test_utils.go +++ b/common/test_utils.go @@ -250,7 +250,11 @@ func DisplayEthTxs(txs map[string]ethtypes.Transactions) { for domain, domainTxs := range txs { fmt.Printf("domain: %s\n", domain) for _, tx := range domainTxs { - fmt.Printf("tx: %s\n", tx.Hash().Hex()) + sender, err := ExtractSender(tx) + if err != nil { + panic(err) + } + fmt.Printf("sender: %s tx: %s:%d\n", sender, tx.Hash().Hex(), tx.Nonce()) } } fmt.Printf("========txs info end=======\n") diff --git a/services/api/service_test.go b/services/api/service_test.go index 69a9e41f..3dce4c0e 100644 --- a/services/api/service_test.go +++ b/services/api/service_test.go @@ -8,8 +8,6 @@ import ( "encoding/hex" "encoding/json" "fmt" - "github.com/AnomalyFi/Arcadia/datalayer" - mda "github.com/AnomalyFi/Arcadia/datalayer/mocks" "io" "math/big" mrand "math/rand" @@ -22,6 +20,9 @@ import ( "testing" "time" + "github.com/AnomalyFi/Arcadia/datalayer" + mda "github.com/AnomalyFi/Arcadia/datalayer/mocks" + "golang.org/x/exp/maps" "github.com/alicebob/miniredis/v2" @@ -6614,10 +6615,10 @@ func TestToBNonceState(t *testing.T) { // constructing ToB bundleTxs := map[string]ethtypes.Transactions{ originChainIDStr: { - CreateEthTransfer(t, originChainID, testAccounts[0].PrivateKey, testAccounts[1].Address, 100, 0, 21000, nil), + CreateEthTransfer(t, originChainID, testAccounts[0].PrivateKey, testAccounts[1].Address, 100, 1, 21000, nil), }, remoteChainIDStr: { - CreateEthTransfer(t, remoteChainID, testAccounts[1].PrivateKey, testAccounts[0].Address, 100, 1, 21000, nil), + CreateEthTransfer(t, remoteChainID, testAccounts[1].PrivateKey, testAccounts[0].Address, 100, 0, 21000, nil), }, } seqTx := CreateHypersdkBundleTx(t, testSeqChainID, bundleTxs) @@ -6644,7 +6645,7 @@ func TestToBNonceState(t *testing.T) { redis := backend.redis redis.SetSizeTracker(backend.arcadia.sizeTracker) require.Equal(t, backend.arcadia.chunkManager.ToBNonce(), uint64(0)) - backend.simulator.EXPECT().GetBlockNumber([]string{originChainIDStr, remoteChainIDStr}).Return(blockNumbers, nil) + // backend.simulator.EXPECT().GetBlockNumber([]string{originChainIDStr, remoteChainIDStr}).Return(blockNumbers, nil) backend.SetupRegisteredRollups(epoch, originChainID) backend.SetupRegisteredRollups(epoch, remoteChainID) @@ -6653,50 +6654,47 @@ func TestToBNonceState(t *testing.T) { // shared calls for both chunks backend.seqcli.EXPECT().Parser().Return(chainParser) - originChainIDInt, err := common.ChainIDStrToChainID(originChainIDStr) - require.NoError(t, err) - remoteChainIDInt, err := common.ChainIDStrToChainID(remoteChainIDStr) - require.NoError(t, err) id := ids.GenerateTestID() // send in rob chunk for origin chain id // needed for ToB to be accepted - robChainID := originChainIDInt robOpts1 := &CreateTestBlockSubmissionOpts{ Epoch: epoch, - OriginChainID: *robChainID, - RemoteChainID: *remoteChainIDInt, + OriginChainID: *originChainID, + RemoteChainID: *remoteChainID, SeqChainID: testSeqChainID, BuilderPubkey: *testBuilderPublicKey, BuilderSecretkey: *testBuilderSecretKey, - RoBBlockNumber: blockNumbers[originChainIDStr] - 1, - RoBChainID: robChainID, + RoBBlockNumber: blockNumbers[originChainIDStr] + 1, + RoBChainID: originChainID, ChunkID: id, - Txs: nil, - IsToB: false, + Txs: ethtypes.Transactions{ + CreateEthTransfer(t, originChainID, testAccounts[0].PrivateKey, testAccounts[1].Address, 20, testAccounts[0].Nonce, 21000, nil), + }, + IsToB: false, } //rob1 := backend.submitRoBChunk(t, robOpts1, chainParser) - backend.submitRoBChunk(t, robOpts1, chainParser) + rob1 := backend.submitRoBChunk(t, robOpts1, chainParser).Chunk // send in rob chunk for remote chain id // needed for ToB to be accepted - robChainID = remoteChainIDInt require.NoError(t, err) robOpts2 := &CreateTestBlockSubmissionOpts{ Epoch: epoch, - OriginChainID: *robChainID, - RemoteChainID: *remoteChainIDInt, + OriginChainID: *remoteChainID, + RemoteChainID: *originChainID, SeqChainID: testSeqChainID, BuilderPubkey: *testBuilderPublicKey, BuilderSecretkey: *testBuilderSecretKey, - RoBBlockNumber: blockNumbers[remoteChainIDStr] - 1, - RoBChainID: robChainID, + RoBBlockNumber: blockNumbers[remoteChainIDStr] + 1, + RoBChainID: remoteChainID, ChunkID: id, - Txs: nil, - IsToB: false, + Txs: ethtypes.Transactions{ + CreateEthTransfer(t, remoteChainID, testAccounts[0].PrivateKey, testAccounts[1].Address, 20, testAccounts[0].Nonce, 21000, nil), + }, + IsToB: false, } - //rob2 := backend.submitRoBChunk(t, robOpts2, chainParser) - backend.submitRoBChunk(t, robOpts2, chainParser) + rob2 := backend.submitRoBChunk(t, robOpts2, chainParser).Chunk backend.resetSeqClientMockExpectations(t) backend.simulator.EXPECT().GetBlockNumber([]string{originChainIDStr, remoteChainIDStr}).Return(blockNumbers, nil) @@ -6705,16 +6703,13 @@ func TestToBNonceState(t *testing.T) { tobTxs, err := common.CollectTxsFromChunks([]*common.ArcadiaChunk{&tobReq.Chunk}) require.NoError(t, err) common.DisplayEthTxs(tobTxs) - allTxs, err := common.CollectTxsFromChunks([]*common.ArcadiaChunk{&tobReq.Chunk}) - require.NoError(t, err) - - tobTxs[originChainIDStr] = slices.Insert(tobTxs[originChainIDStr], 0, robOpts1.Txs[0]) - tobTxs[remoteChainIDStr] = slices.Insert(tobTxs[remoteChainIDStr], 0, robOpts2.Txs[0]) + allTxs, err := common.CollectTxsFromChunks([]*common.ArcadiaChunk{&rob1, &rob2, &tobReq.Chunk}) + require.NoError(t, err) common.DisplayEthTxs(allTxs) // expectations for the first tob - for domain, expectedTxs := range tobTxs { + for domain, expectedTxs := range allTxs { fmt.Printf("expected txs for domain: %s\n", domain) fmt.Printf("[") for _, tx := range expectedTxs { @@ -6727,8 +6722,8 @@ func TestToBNonceState(t *testing.T) { nonces := CollectNoncesFromEthAccounts(relatedAccounts) balances := CollectBalancesFromEthAccounts(relatedAccounts) - backend.simulator.EXPECT().GetNonces(domain, mock.MatchedBy(ExpectedMatchTxs(expectedTxs)), uint64ToHexString(blockNumbers[domain]-2)).Return(nonces, nil) - backend.simulator.EXPECT().GetBalances(domain, mock.MatchedBy(ExpectedMatchTxs(expectedTxs)), uint64ToHexString(blockNumbers[domain]-2)).Return(balances, nil) + backend.simulator.EXPECT().GetNonces(domain, mock.MatchedBy(ExpectedMatchTxs(expectedTxs)), uint64ToHexString(blockNumbers[domain])).Return(nonces, nil) + backend.simulator.EXPECT().GetBalances(domain, mock.MatchedBy(ExpectedMatchTxs(expectedTxs)), uint64ToHexString(blockNumbers[domain])).Return(balances, nil) // simulation results, only the txs from this ToB will be simulated and the txs from RoB will be filtered out callBundleRes := make([]flashbotsrpc.FlashbotsCallBundleResult, 0, len(expectedTxs)) @@ -6743,8 +6738,8 @@ func TestToBNonceState(t *testing.T) { require.NoError(t, err) validationReq := common.BlockValidationRequest{ Txs: rawExpectedTxs, - BlockNumber: uint64ToHexString(blockNumbers[domain] - 2), - StateBlockNumber: uint64ToHexString(blockNumbers[domain] - 2), + BlockNumber: uint64ToHexString(blockNumbers[domain]), + StateBlockNumber: uint64ToHexString(blockNumbers[domain]), } backend.simulator.EXPECT(). SimBlockAndGetGasUsedForChain(mock.Anything, domain, &validationReq). @@ -6755,7 +6750,7 @@ func TestToBNonceState(t *testing.T) { require.Equal(t, http.StatusOK, rrCode) cmTxs, cmHeights, err := backend.arcadia.chunkManager.Txs() require.NoError(t, err) - TxsTheSame(t, bundleTxs, cmTxs) + TxsTheSame(t, allTxs, cmTxs) require.Equal(t, blockNumbers, cmHeights) for _, bundle := range tobReq.Chunk.ToB.GetBundles() { @@ -6767,7 +6762,7 @@ func TestToBNonceState(t *testing.T) { arcadia2, err := NewArcadiaAPI(*backend.currOpts) require.NoError(t, err) - require.Equal(t, arcadia2.chunkManager.ToBNonce(), uint64(1)) + require.Equal(t, uint64(1), arcadia2.chunkManager.ToBNonce()) } func TestConcurrentSubmitReqNGetPayload(t *testing.T) { From e2673f3979b14c23e08dbb39de6ea6a16d99e792 Mon Sep 17 00:00:00 2001 From: rikoeldon <106416799+rikoeldon@users.noreply.github.com> Date: Thu, 20 Feb 2025 15:39:10 -0500 Subject: [PATCH 19/31] adding new test logic to failing test cases --- services/api/service_test.go | 49 ++++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/services/api/service_test.go b/services/api/service_test.go index 3dce4c0e..bbaeb645 100644 --- a/services/api/service_test.go +++ b/services/api/service_test.go @@ -5426,9 +5426,6 @@ func TestOverallFlow(t *testing.T) { // send in rob chunk for origin chain id // needed for ToB to be accepted - remoteChainIDInt, err := common.ChainIDStrToChainID(remoteChainIDStr) - require.NoError(t, err) - robChainID := remoteChainIDInt testSeqChainID := ids.GenerateTestID() testBuilderSecretKey, err := bls.GenerateRandomSecretKey() require.NoError(t, err) @@ -5438,21 +5435,41 @@ func TestOverallFlow(t *testing.T) { robOpts1 := &CreateTestBlockSubmissionOpts{ Epoch: epoch, - OriginChainID: *robChainID, - RemoteChainID: *remoteChainIDInt, + OriginChainID: *originChainID, + RemoteChainID: *remoteChainID, SeqChainID: testSeqChainID, BuilderPubkey: *testBuilderPublicKey, BuilderSecretkey: *testBuilderSecretKey, - RoBBlockNumber: blockNumbers[originChainIDStr] - 1, - RoBChainID: robChainID, + RoBBlockNumber: blockNumbers[originChainIDStr] + 1, + RoBChainID: originChainID, ChunkID: id, - Txs: nil, - IsToB: false, + Txs: ethtypes.Transactions{ + CreateEthTransfer(t, originChainID, testAccounts[0].PrivateKey, testAccounts[1].Address, 20, testAccounts[0].Nonce, 21000, nil), + }, + IsToB: false, } robReq := backend.submitRoBChunk(t, robOpts1, chainParser) fmt.Println(robReq) + + robOpts2 := &CreateTestBlockSubmissionOpts{ + Epoch: epoch, + OriginChainID: *remoteChainID, + RemoteChainID: *originChainID, + SeqChainID: testSeqChainID, + BuilderPubkey: *testBuilderPublicKey, + BuilderSecretkey: *testBuilderSecretKey, + RoBBlockNumber: blockNumbers[originChainIDStr] + 1, + RoBChainID: remoteChainID, + ChunkID: id, + Txs: ethtypes.Transactions{ + CreateEthTransfer(t, remoteChainID, testAccounts[0].PrivateKey, testAccounts[1].Address, 20, testAccounts[0].Nonce, 21000, nil), + }, + IsToB: false, + } + robReq2 := backend.submitRoBChunk(t, robOpts2, chainParser) + fmt.Println(robReq2) //backend.resetSeqClientMockExpectations(t) - backend.simulator.EXPECT().GetBlockNumber([]string{originChainIDStr, remoteChainIDStr}).Return(blockNumbers, nil) + //backend.simulator.EXPECT().GetBlockNumber([]string{originChainIDStr, remoteChainIDStr}).Return(blockNumbers, nil) // constructing tob1 bundle1Txs := map[string]ethtypes.Transactions{ @@ -5893,8 +5910,8 @@ func TestOverallFlow(t *testing.T) { nonces := CollectNoncesFromEthAccounts(relatedAccounts) balances := CollectBalancesFromEthAccounts(relatedAccounts) - backend.simulator.EXPECT().GetNonces(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumbers[domain]-2)).Once().Return(nonces, nil) - backend.simulator.EXPECT().GetBalances(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumbers[domain]-2)).Once().Return(balances, nil) + backend.simulator.EXPECT().GetNonces(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumbers[domain])).Once().Return(nonces, nil) + backend.simulator.EXPECT().GetBalances(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumbers[domain])).Once().Return(balances, nil) // simulation results, only the txs from this RoB will be simulated and the txs from ToB will be filtered out callBundleRes := make([]flashbotsrpc.FlashbotsCallBundleResult, 0, len(expectedTxs)) @@ -5909,8 +5926,8 @@ func TestOverallFlow(t *testing.T) { require.NoError(t, err) validationReq := common.BlockValidationRequest{ Txs: rawExpectedTxs, - BlockNumber: uint64ToHexString(blockNumbers[domain] - 2), - StateBlockNumber: uint64ToHexString(blockNumbers[domain] - 2), + BlockNumber: uint64ToHexString(blockNumbers[domain]), + StateBlockNumber: uint64ToHexString(blockNumbers[domain]), } backend.simulator.EXPECT(). SimBlockAndGetGasUsedForChain(mock.Anything, domain, &validationReq). @@ -5984,8 +6001,8 @@ func TestOverallFlow(t *testing.T) { require.NoError(t, err) t.Log(winner) - rrCode1 := processBlockRequest(backend, robReq) - require.Equal(t, http.StatusOK, rrCode1) + //rrCode1 := processBlockRequest(backend, robReq) + //require.Equal(t, http.StatusOK, rrCode1) rrCode2 := processBlockRequest(backend, tobReq) require.Equal(t, http.StatusOK, rrCode2) From 4ca097669f96503637f2dcb5699c043afb17d028 Mon Sep 17 00:00:00 2001 From: NateAtNodeKit Date: Thu, 20 Feb 2025 17:24:06 -0500 Subject: [PATCH 20/31] overall basic flow updates --- services/api/service.go | 3 +- services/api/service_test.go | 67 +++++++++++++++--------------------- 2 files changed, 29 insertions(+), 41 deletions(-) diff --git a/services/api/service.go b/services/api/service.go index 5cf8e867..efee6edc 100644 --- a/services/api/service.go +++ b/services/api/service.go @@ -1161,7 +1161,7 @@ func (api *ArcadiaAPI) handleGetPayload(w http.ResponseWriter, req *http.Request // try fetch cached payload var getPayloadResp *common.GetPayloadResponse getPayloadResp, err = api.redis.GetPayloadResp(payload.ChainID, payload.BlockNumber) - if err != nil { + if err != nil || getPayloadResp == nil { log.WithError(err).Warn("unable to get payload from redis, trying database...") payloadDB, err := api.db.GetPayloadResp(payload.ChainID, payload.BlockNumber) if err != nil { @@ -1174,6 +1174,7 @@ func (api *ArcadiaAPI) handleGetPayload(w http.ResponseWriter, req *http.Request log.WithError(err).Warn("unable to convert payload from database, conversion failed.") } } + if getPayloadResp != nil { api.RespondOK(w, getPayloadResp) log.Infof("execution payload(from cache) delivered, timestampAfterLoadResponse %d", time.Now().UTC().UnixMilli()) diff --git a/services/api/service_test.go b/services/api/service_test.go index bbaeb645..c5e44f02 100644 --- a/services/api/service_test.go +++ b/services/api/service_test.go @@ -4061,6 +4061,7 @@ func TestGetPayload(t *testing.T) { processBlockRequest := func(backend *testBackend, payload *common.GetPayloadRequest, sigStr string) (int, []byte) { // new HTTP req rr := backend.RequestWithPayloadHeader(http.MethodPost, pathGetPayload, payload, sigStr) + require.Equal(t, http.StatusOK, rr.Code) if rr.Body != nil { return rr.Code, rr.Body.Bytes() @@ -5372,6 +5373,8 @@ func TestOverallFlow(t *testing.T) { } for _, tc := range tests { + t.Log("*** Running Overall Flow param disable_redis [" + strconv.FormatBool(tc.disableRedis) + "] ***") + backend := newTestBackendWithFlags(t, tc.disableRedis) arcadia := backend.GetArcadia() auctionBidPath := "/arcadia/v1/builder/auction_bid" @@ -5413,17 +5416,6 @@ func TestOverallFlow(t *testing.T) { CreateEthTransfer(t, originChainID, testAccounts[0].PrivateKey, testAccounts[1].Address, 20, testAccounts[0].Nonce, 21000, nil), } - // constructing RoB - //robReq := CreateRoBReq(t, &CreateTestBlockSubmissionOpts{ - // Epoch: epoch + 2, - // SeqChainID: seqChainID, - // RoBChainID: originChainID, - // RoBBlockNumber: blockNumbers[originChainIDStr] - 1, - // BuilderPubkey: *builderPK2, - // BuilderSecretkey: *builderSK2, - // Txs: robTxs, - //}) - // send in rob chunk for origin chain id // needed for ToB to be accepted testSeqChainID := ids.GenerateTestID() @@ -5622,12 +5614,6 @@ func TestOverallFlow(t *testing.T) { backend.seqcli.EXPECT().GetNetworkID().Return(networkID).Maybe() backend.seqcli.EXPECT().CurrentValidators(mock.Anything).Return(validators) - // Shut down mock Redis after rollups register since Arcadia relies on SEQ state for valid rollup list, not postgres in this case - //err = backend.redis.Close() - //require.NoError(t, err) - - //backend.useRedis = false - var conns = make([]*websocket.Conn, 0) t.Run("subscribe multiple seq validators to arcadia in order to receive chunk(s)", func(t *testing.T) { path := "/ws/arcadia/v1/validator/subscribe" @@ -5939,36 +5925,25 @@ func TestOverallFlow(t *testing.T) { tobChunks := []*common.ArcadiaChunk{&tobReq.Chunk, &tobReq2.Chunk, &tobReq3.Chunk} for i, tobChunk := range tobChunks { backend.simulator.EXPECT().GetBlockNumber(tobChunk.ToB.Domains()).Return(blockNumbers, nil) - chunksSoFar := []*common.ArcadiaChunk{&robReq.Chunk} + chunksSoFar := []*common.ArcadiaChunk{&robReq.Chunk, &robReq2.Chunk} chunksSoFar = append(chunksSoFar, tobChunks[:i+1]...) t.Logf("num chunks to set mocks: %d", len(chunksSoFar)) allTxsSoFar, err := common.CollectTxsFromChunks(chunksSoFar) require.NoError(t, err) + // setup expectations for simulation for domain, expectedTxs := range allTxsSoFar { - matchTxs := func(txs ethtypes.Transactions) bool { - if len(txs) != len(expectedTxs) { - return false - } - for i, tx := range txs { - if tx.Hash().Hex() != expectedTxs[i].Hash().Hex() { - return false - } - } - return true - } - blockNumberForDomain := blockNumbers[domain] if domain == robReq.Chunk.RoB.ChainID { - blockNumberForDomain = blockNumbers[domain] - 2 + blockNumberForDomain = blockNumbers[domain] } relatedAccounts, err := CollectAccountsFromTxs(expectedTxs, testAccounts) require.NoError(t, err) nonces := CollectNoncesFromEthAccounts(relatedAccounts) balances := CollectBalancesFromEthAccounts(relatedAccounts) - backend.simulator.EXPECT().GetNonces(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumberForDomain)).Once().Return(nonces, nil) - backend.simulator.EXPECT().GetBalances(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumberForDomain)).Once().Return(balances, nil) + backend.simulator.EXPECT().GetNonces(domain, mock.MatchedBy(ExpectedMatchTxs(expectedTxs)), uint64ToHexString(blockNumbers[domain])).Return(nonces, nil) + backend.simulator.EXPECT().GetBalances(domain, mock.MatchedBy(ExpectedMatchTxs(expectedTxs)), uint64ToHexString(blockNumbers[domain])).Return(balances, nil) // simulation results, only the txs from this ToB will be simulated and the txs from RoB will be filtered out callBundleRes := make([]flashbotsrpc.FlashbotsCallBundleResult, 0, len(expectedTxs)) @@ -6001,9 +5976,6 @@ func TestOverallFlow(t *testing.T) { require.NoError(t, err) t.Log(winner) - //rrCode1 := processBlockRequest(backend, robReq) - //require.Equal(t, http.StatusOK, rrCode1) - rrCode2 := processBlockRequest(backend, tobReq) require.Equal(t, http.StatusOK, rrCode2) @@ -6012,6 +5984,9 @@ func TestOverallFlow(t *testing.T) { rrCode4 := processBlockRequest(backend, tobReq3) require.Equal(t, http.StatusOK, rrCode4) + + backend.simulator.AssertExpectations(t) + backend.seqcli.AssertExpectations(t) }) t.Run("receive preconfs for pending blocks and construct payloads"+tc.name(), func(t *testing.T) { @@ -6116,9 +6091,7 @@ func TestOverallFlow(t *testing.T) { processBlockRequest := func(backend *testBackend, payload *common.GetPayloadRequest, sigStr string) (int, []byte) { // new HTTP req rr := backend.RequestWithPayloadHeader(http.MethodPost, pathGetPayload, payload, sigStr) - if rr.Body != nil { - t.Log("error: ", rr.Body.String()) return rr.Code, rr.Body.Bytes() } else { return rr.Code, nil @@ -6165,42 +6138,56 @@ func TestOverallFlow(t *testing.T) { err = backend.arcadia.redis.SetPayloadTxsRoB(robChunk.BlockNumber, chainID, &common.PayloadTxs{ Txs: ethOtxs[chainID], }) + require.NoError(t, err) } else { err = backend.arcadia.db.SetPayloadTxsRoB(robChunk.BlockNumber, chainID, &common.PayloadTxs{ Txs: ethOtxs[chainID], }) + + payloadResp := common.GetPayloadResponse{ + Transactions: ethOtxs[chainID], + } + err = backend.arcadia.db.SetPayloadResp(chainID, robChunk.BlockNumber, &payloadResp) + require.NoError(t, err) } - require.NoError(t, err) + payload := &common.GetPayloadRequest{ ChainID: robChunk.ChainID, BlockNumber: robChunk.BlockNumber, } chainIDu64 := binary.LittleEndian.Uint64(rollup.Namespace) chainIDstr := hexutil.EncodeBig(big.NewInt(int64(chainIDu64))) + if backend.useRedis { reqRollup, err := backend.redis.GetRegisterRollup(epoch, chainIDstr) require.NoError(t, err) t.Log(reqRollup) } + payloadBytes, err := json.Marshal(payload) require.NoError(t, err) payloadHash, _ := common.Sha256HashPayload(payloadBytes) sig := bls.Sign(mockSecretKey, payloadHash[:]) sigBytes := sig.Bytes() sigStr := hex.EncodeToString(sigBytes[:]) + rrCode, rrBytes := processBlockRequest(backend, payload, "0x"+sigStr) require.Equal(t, http.StatusOK, rrCode) + respRoB := new(common.GetPayloadResponse) err = json.Unmarshal(rrBytes, respRoB) require.NoError(t, err) + robTxs := respRoB.Transactions - require.NotNil(t, len(robTxs)) + require.Equal(t, len(robTxs), 1) }) for _, conn := range conns { err := conn.Close() require.NoError(t, err) } + + t.Log("*** Finished Overall Flow param disable_redis [" + strconv.FormatBool(tc.disableRedis) + "] ***") } } From 48e4602a146790b9a37d82b9f8cf897274bc665f Mon Sep 17 00:00:00 2001 From: NateAtNodeKit Date: Fri, 21 Feb 2025 10:07:29 -0500 Subject: [PATCH 21/31] fix overall test --- services/api/service_test.go | 59 ++---------------------------------- 1 file changed, 3 insertions(+), 56 deletions(-) diff --git a/services/api/service_test.go b/services/api/service_test.go index c5e44f02..a95950bf 100644 --- a/services/api/service_test.go +++ b/services/api/service_test.go @@ -5411,11 +5411,6 @@ func TestOverallFlow(t *testing.T) { remoteChainIDStr: 50, } - // test ethereum signing keys - robTxs := ethtypes.Transactions{ - CreateEthTransfer(t, originChainID, testAccounts[0].PrivateKey, testAccounts[1].Address, 20, testAccounts[0].Nonce, 21000, nil), - } - // send in rob chunk for origin chain id // needed for ToB to be accepted testSeqChainID := ids.GenerateTestID() @@ -5459,9 +5454,6 @@ func TestOverallFlow(t *testing.T) { IsToB: false, } robReq2 := backend.submitRoBChunk(t, robOpts2, chainParser) - fmt.Println(robReq2) - //backend.resetSeqClientMockExpectations(t) - //backend.simulator.EXPECT().GetBlockNumber([]string{originChainIDStr, remoteChainIDStr}).Return(blockNumbers, nil) // constructing tob1 bundle1Txs := map[string]ethtypes.Transactions{ @@ -5875,52 +5867,6 @@ func TestOverallFlow(t *testing.T) { // shared calls for both chunks backend.seqcli.EXPECT().Parser().Return(chainParser) - t.Log("=========setup RoB simulation===========") - for domain, expectedTxs := range map[string]ethtypes.Transactions{ - originChainIDStr: robTxs, - } { - matchTxs := func(txs ethtypes.Transactions) bool { - if len(txs) != len(expectedTxs) { - return false - } - for i, tx := range txs { - if tx.Hash().Hex() != expectedTxs[i].Hash().Hex() { - return false - } - } - return true - } - - relatedAccounts, err := CollectAccountsFromTxs(expectedTxs, testAccounts) - require.NoError(t, err) - nonces := CollectNoncesFromEthAccounts(relatedAccounts) - balances := CollectBalancesFromEthAccounts(relatedAccounts) - - backend.simulator.EXPECT().GetNonces(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumbers[domain])).Once().Return(nonces, nil) - backend.simulator.EXPECT().GetBalances(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumbers[domain])).Once().Return(balances, nil) - - // simulation results, only the txs from this RoB will be simulated and the txs from ToB will be filtered out - callBundleRes := make([]flashbotsrpc.FlashbotsCallBundleResult, 0, len(expectedTxs)) - for _, tx := range expectedTxs { - callBundleRes = append(callBundleRes, flashbotsrpc.FlashbotsCallBundleResult{ - TxHash: tx.Hash().Hex(), - Error: "", - Revert: "", - }) - } - rawExpectedTxs, err := CollectRawTxs(expectedTxs) - require.NoError(t, err) - validationReq := common.BlockValidationRequest{ - Txs: rawExpectedTxs, - BlockNumber: uint64ToHexString(blockNumbers[domain]), - StateBlockNumber: uint64ToHexString(blockNumbers[domain]), - } - backend.simulator.EXPECT(). - SimBlockAndGetGasUsedForChain(mock.Anything, domain, &validationReq). - Once(). - Return(uint64(100), callBundleRes, nil) - } - t.Log("=========setup simulation mocks before tob1==============") tobChunks := []*common.ArcadiaChunk{&tobReq.Chunk, &tobReq2.Chunk, &tobReq3.Chunk} for i, tobChunk := range tobChunks { @@ -5942,8 +5888,9 @@ func TestOverallFlow(t *testing.T) { require.NoError(t, err) nonces := CollectNoncesFromEthAccounts(relatedAccounts) balances := CollectBalancesFromEthAccounts(relatedAccounts) - backend.simulator.EXPECT().GetNonces(domain, mock.MatchedBy(ExpectedMatchTxs(expectedTxs)), uint64ToHexString(blockNumbers[domain])).Return(nonces, nil) - backend.simulator.EXPECT().GetBalances(domain, mock.MatchedBy(ExpectedMatchTxs(expectedTxs)), uint64ToHexString(blockNumbers[domain])).Return(balances, nil) + blockNumber := uint64ToHexString(blockNumbers[domain]) + backend.simulator.EXPECT().GetNonces(domain, mock.MatchedBy(ExpectedMatchTxs(expectedTxs)), blockNumber).Return(nonces, nil).Maybe() + backend.simulator.EXPECT().GetBalances(domain, mock.MatchedBy(ExpectedMatchTxs(expectedTxs)), uint64ToHexString(blockNumbers[domain])).Return(balances, nil).Maybe() // simulation results, only the txs from this ToB will be simulated and the txs from RoB will be filtered out callBundleRes := make([]flashbotsrpc.FlashbotsCallBundleResult, 0, len(expectedTxs)) From 28879ec48cc213f36bd4699dfd378233c6b0fad5 Mon Sep 17 00:00:00 2001 From: NateAtNodeKit Date: Fri, 21 Feb 2025 10:52:10 -0500 Subject: [PATCH 22/31] fix null payload resp case in mockdb --- database/mockdb.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/database/mockdb.go b/database/mockdb.go index 70ea846b..d5fd71b3 100644 --- a/database/mockdb.go +++ b/database/mockdb.go @@ -29,7 +29,7 @@ type MockDB struct { RoBChunkMap map[string]map[uint64]*common.RoBChunkDB RoBChunkAcceptedMap map[string]*common.RoBChunkAcceptedDB LastFetchedBlockNum common.LastFetchedBlockNumberDB - PayloadResp common.PayloadDB + PayloadResp *common.PayloadDB Epoch uint64 EpochLowestToBNonce map[uint64]uint64 PayloadTxsToB map[string]*common.PayloadTxs @@ -54,7 +54,6 @@ func NewMockDB() *MockDB { RoBChunkMap: make(map[string]map[uint64]*common.RoBChunkDB), RoBChunkAcceptedMap: make(map[string]*common.RoBChunkAcceptedDB), LastFetchedBlockNum: common.LastFetchedBlockNumberDB{}, - PayloadResp: common.PayloadDB{}, Epoch: 0, EpochLowestToBNonce: make(map[uint64]uint64), PayloadTxsToB: make(map[string]*common.PayloadTxs), @@ -247,8 +246,10 @@ func (db *MockDB) RemoveBestAuctionBid(epoch uint64) error { func (db *MockDB) GetPayloadResp(chainID string, blockNumber uint64) (*common.PayloadDB, error) { db.l.Lock() defer db.l.Unlock() - - return &db.PayloadResp, nil + if db.PayloadResp == nil { + return nil, nil + } + return db.PayloadResp, nil } func (db *MockDB) SetPayloadResp(chainID string, blockNumber uint64, txs *common.GetPayloadResponse) error { @@ -260,7 +261,7 @@ func (db *MockDB) SetPayloadResp(chainID string, blockNumber uint64, txs *common return err } - db.PayloadResp = *payloadTxs + db.PayloadResp = payloadTxs return nil } From 97fdb9efd7a09abd7792b91a49c26d65350a3886 Mon Sep 17 00:00:00 2001 From: NateAtNodeKit Date: Fri, 21 Feb 2025 14:18:03 -0500 Subject: [PATCH 23/31] update --- services/api/service_test.go | 113 +++++++++++++++++++++++++---------- 1 file changed, 80 insertions(+), 33 deletions(-) diff --git a/services/api/service_test.go b/services/api/service_test.go index a95950bf..a0150fc0 100644 --- a/services/api/service_test.go +++ b/services/api/service_test.go @@ -71,8 +71,29 @@ var ( mockPublicKey, _ = bls.PublicKeyFromSecretKey(mockSecretKey) numTestAccounts = 10 testAccounts = GenerateTestEthAccounts(numTestAccounts) + + testOriginChainID = big.NewInt(45200) + testOriginChainIDStr = hexutil.EncodeBig(testOriginChainID) + testRemoteChainID = big.NewInt(45201) + testRemoteChainIDStr = hexutil.EncodeBig(testRemoteChainID) + testBlockNumbers = map[string]uint64{ + testOriginChainIDStr: 100, + testRemoteChainIDStr: 50, + } + testOriginChainIDInt, _ = common.ChainIDStrToChainID(testOriginChainIDStr) + testRemoteChainIDInt, _ = common.ChainIDStrToChainID(testRemoteChainIDStr) + testSeqChainID = ids.GenerateTestID() + testEpoch = uint64(0) + + testBuilderSecretKey, _ = bls.GenerateRandomSecretKey() + testBuilderPublicKey, _ = bls.PublicKeyFromSecretKey(testBuilderSecretKey) + testChainParser = &srpc.Parser{} ) +func init() { + _, _ = testChainParser.Registry() +} + type testBackend struct { t require.TestingT arcadia *ArcadiaAPI @@ -183,16 +204,63 @@ func newTestbackendWithCustomDatastore(t *testing.T, redisCache *datastore.Redis // resetMockExpectations is used to reset seqclient mock expectations func (be *testBackend) resetSeqClientMockExpectations(t *testing.T) { + be.seqcli.AssertExpectations(t) + be.seqcli = mseq.NewMockBaseSeqClient(t) be.seqcli.EXPECT().SetOnNewBlockHandler(mock.Anything).Return().Maybe() be.seqcli.EXPECT().Parser().Return(&srpc.Parser{}).Maybe() be.simulator = msim.NewMockIBlockSimRateLimiter(t) - be.arcadia.setBlockSimRateLimiter(be.simulator) be.arcadia.setSeqClient(be.seqcli) } +// setupRoBsForToBTest is a helper which sends two RoB blocks for origin and remote chains. +// This primes +func (be *testBackend) setupRoBsForToBTest(t *testing.T) (*common.SubmitNewBlockRequest, *common.SubmitNewBlockRequest) { + be.SetupRegisteredRollups(testEpoch, testOriginChainID) + be.SetupRegisteredRollups(testEpoch, testRemoteChainID) + + // send in rob chunk for origin chain id + // needed for ToB to be accepted + robChainID := testOriginChainIDInt + robOpts1 := &CreateTestBlockSubmissionOpts{ + Epoch: testEpoch, + OriginChainID: *robChainID, + RemoteChainID: *testRemoteChainIDInt, + SeqChainID: testSeqChainID, + BuilderPubkey: *testBuilderPublicKey, + BuilderSecretkey: *testBuilderSecretKey, + RoBBlockNumber: testBlockNumbers[testOriginChainIDStr] - 1, + RoBChainID: robChainID, + ChunkID: ids.GenerateTestID(), + Txs: nil, + IsToB: false, + } + rob1 := be.submitRoBChunk(t, robOpts1, testChainParser) + + // send in rob chunk for remote chain id + // needed for ToB to be accepted + robChainID = testRemoteChainIDInt + robOpts2 := &CreateTestBlockSubmissionOpts{ + Epoch: testEpoch, + OriginChainID: *robChainID, + RemoteChainID: *testRemoteChainIDInt, + SeqChainID: testSeqChainID, + BuilderPubkey: *testBuilderPublicKey, + BuilderSecretkey: *testBuilderSecretKey, + RoBBlockNumber: testBlockNumbers[testRemoteChainIDStr] - 1, + RoBChainID: robChainID, + ChunkID: ids.GenerateTestID(), + Txs: nil, + IsToB: false, + } + rob2 := be.submitRoBChunk(t, robOpts2, testChainParser) + + be.resetSeqClientMockExpectations(t) + return rob1, rob2 +} + func newDatastores(t *testing.T) (*datastore.RedisCache, database.IDatabaseService) { redisClient, err := miniredis.Run() require.NoError(t, err) @@ -3604,22 +3672,11 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { Bundles: []*common.CrossRollupBundle{&bundle}, }) - // constructing RoB - robTxs := ethtypes.Transactions{ - // conflicting tx with prev ToB - CreateEthTransfer(t, originChainID, testAccounts[0].PrivateKey, testAccounts[1].Address, 20, testAccounts[0].Nonce, 21000, nil), - } - robReq := CreateRoBReq(t, &CreateTestBlockSubmissionOpts{ - Epoch: epoch, - SeqChainID: testSeqChainID, - RoBChainID: originChainID, - RoBBlockNumber: blockNumbers[originChainIDStr] + 1, - BuilderPubkey: *testBuilderPublicKey, - BuilderSecretkey: *testBuilderSecretKey, - Txs: robTxs, - }) - backend := newTestBackend(t) + + // sends two robs to warmup the tob + rob1, rob2 := backend.setupRoBsForToBTest(t) + err = backend.arcadia.datastore.SetAuctionWinner(epoch, builderPkBytes[:]) require.NoError(t, err) redis := backend.redis @@ -3637,24 +3694,12 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { tobTxs, err := common.CollectTxsFromChunks([]*common.ArcadiaChunk{&tobReq.Chunk}) require.NoError(t, err) common.DisplayEthTxs(tobTxs) - allTxs, err := common.CollectTxsFromChunks([]*common.ArcadiaChunk{&tobReq.Chunk, &robReq.Chunk}) + allTxs, err := common.CollectTxsFromChunks([]*common.ArcadiaChunk{&tobReq.Chunk, &rob1.Chunk, &rob2.Chunk}) require.NoError(t, err) common.DisplayEthTxs(allTxs) // expectations for the first tob for domain, expectedTxs := range tobTxs { - matchTxs := func(txs ethtypes.Transactions) bool { - - if len(txs) != len(expectedTxs) { - return false - } - for i, tx := range txs { - if tx.Hash().Hex() != expectedTxs[i].Hash().Hex() { - return false - } - } - return true - } fmt.Printf("expected txs for domain: %s\n", domain) fmt.Printf("[") for _, tx := range expectedTxs { @@ -3667,8 +3712,10 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { nonces := CollectNoncesFromEthAccounts(relatedAccounts) balances := CollectBalancesFromEthAccounts(relatedAccounts) fmt.Printf("expected balance for domain(%s): %+v\n", domain, balances) - backend.simulator.EXPECT().GetNonces(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumbers[domain])).Return(nonces, nil) - backend.simulator.EXPECT().GetBalances(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumbers[domain])).Return(balances, nil) + + matchTxs := ExpectedMatchTxs(expectedTxs) + backend.simulator.EXPECT().GetNonces(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumbers[domain]-2)).Return(nonces, nil) + backend.simulator.EXPECT().GetBalances(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumbers[domain]-2)).Return(balances, nil) // simulation results, only the txs from this ToB will be simulated and the txs from RoB will be filtered out callBundleRes := make([]flashbotsrpc.FlashbotsCallBundleResult, 0, len(expectedTxs)) @@ -3691,8 +3738,8 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { require.NoError(t, err) validationReq := common.BlockValidationRequest{ Txs: rawExpectedTxs, - BlockNumber: uint64ToHexString(blockNumbers[domain]), - StateBlockNumber: uint64ToHexString(blockNumbers[domain]), + BlockNumber: uint64ToHexString(blockNumbers[domain] - 2), + StateBlockNumber: uint64ToHexString(blockNumbers[domain] - 2), } backend.simulator.EXPECT(). SimBlockAndGetGasUsedForChain(mock.Anything, domain, &validationReq). From 2b6d21c66f5c91605ebf7dd73267ead133fd3b1b Mon Sep 17 00:00:00 2001 From: NateAtNodeKit Date: Fri, 21 Feb 2025 14:38:45 -0500 Subject: [PATCH 24/31] update --- services/api/service_test.go | 5 +++-- services/api/testing_utils.go | 1 - 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/services/api/service_test.go b/services/api/service_test.go index a0150fc0..243748cc 100644 --- a/services/api/service_test.go +++ b/services/api/service_test.go @@ -4075,6 +4075,7 @@ func TestWarmupPeriodAdvancement(t *testing.T) { // This tests assumes that the chunks have been preconf'd and the payload(wherever it has to go) // is in the right place in the database that we can retrieve. +// TODO: Verify this one func TestGetPayload(t *testing.T) { // Setup backend with headSlot and genesisTime epoch := uint64(0) @@ -4222,8 +4223,8 @@ func TestGetPayload(t *testing.T) { sigBytes := sig.Bytes() sigStr := hexutil.Encode(sigBytes[:]) - rrCode, _ := processBlockRequest(backend, payload, sigStr) - require.Equal(t, http.StatusServiceUnavailable, rrCode) + rr := backend.RequestWithPayloadHeader(http.MethodPost, pathGetPayload, payload, sigStr) + require.Equal(t, http.StatusServiceUnavailable, rr.Code) }) t.Run("case 1: 1 rollup, only rob", func(t *testing.T) { diff --git a/services/api/testing_utils.go b/services/api/testing_utils.go index 8b320426..7ba21f4d 100644 --- a/services/api/testing_utils.go +++ b/services/api/testing_utils.go @@ -149,7 +149,6 @@ func CreateTestChunkSubmission( blockReq.Pubkey = builderPubkeyBytes[:] require.NoError(t, err) - // blockReq.Signature = &bls.Signature{} isToB := len(opts.ChainIDs) > 1 var chunk common.ArcadiaChunk var payloadHash [32]byte From 650ae2bed03f88f6cc1f7d26f321906539d458ee Mon Sep 17 00:00:00 2001 From: NateAtNodeKit Date: Fri, 21 Feb 2025 14:56:06 -0500 Subject: [PATCH 25/31] tx simulation allowed revert --- services/api/service_test.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/services/api/service_test.go b/services/api/service_test.go index 243748cc..8dcd09b4 100644 --- a/services/api/service_test.go +++ b/services/api/service_test.go @@ -2805,6 +2805,7 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { tobTxs[originChainIDStr] = slices.Insert(tobTxs[originChainIDStr], 0, robOpts1.Txs[0]) tobTxs[remoteChainIDStr] = slices.Insert(tobTxs[remoteChainIDStr], 0, robOpts2.Txs[0]) common.DisplayEthTxs(tobTxs) + // expectations for the first tob for domain, expectedTxs := range tobTxs { matchTxs := func(txs ethtypes.Transactions) bool { @@ -3642,7 +3643,7 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { remoteChainIDStr: 50, } - originTx := CreateEthTransfer(t, originChainID, testAccounts[0].PrivateKey, testAccounts[1].Address, 100, testAccounts[0].Nonce, 21000, nil) + originTx := CreateEthTransfer(t, originChainID, testAccounts[0].PrivateKey, testAccounts[1].Address, 100, 1, 21000, nil) // constructing ToB bundleTxs := map[string]ethtypes.Transactions{ originChainIDStr: { @@ -3697,6 +3698,8 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { allTxs, err := common.CollectTxsFromChunks([]*common.ArcadiaChunk{&tobReq.Chunk, &rob1.Chunk, &rob2.Chunk}) require.NoError(t, err) common.DisplayEthTxs(allTxs) + tobTxs[originChainIDStr] = slices.Insert(tobTxs[originChainIDStr], 0, rob1.Chunk.RoB.GetTxs()[0]) + tobTxs[remoteChainIDStr] = slices.Insert(tobTxs[remoteChainIDStr], 0, rob2.Chunk.RoB.GetTxs()[0]) // expectations for the first tob for domain, expectedTxs := range tobTxs { @@ -3751,7 +3754,9 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { cmTxs, _, err := backend.arcadia.chunkManager.Txs() require.NoError(t, err) - TxsTheSame(t, bundleTxs, cmTxs) + + TxsTheSameUnordered(t, allTxs, cmTxs) + for _, bundle := range tobReq.Chunk.ToB.GetBundles() { status, err := backend.redis.GetBundleStatus(bundle.BundleHash) require.NoError(t, err) From 2d70fe56dcdb79ab0151d6296b48bc699a1f4c83 Mon Sep 17 00:00:00 2001 From: rikoeldon <106416799+rikoeldon@users.noreply.github.com> Date: Fri, 21 Feb 2025 16:26:57 -0500 Subject: [PATCH 26/31] working on submit new block req test cases --- services/api/service_test.go | 73 +++++++++++++++++++++++++----------- 1 file changed, 52 insertions(+), 21 deletions(-) diff --git a/services/api/service_test.go b/services/api/service_test.go index 8dcd09b4..dfb1b9d2 100644 --- a/services/api/service_test.go +++ b/services/api/service_test.go @@ -349,6 +349,28 @@ func (be *testBackend) submitRoBChunk(t *testing.T, opts *CreateTestBlockSubmiss return robReq } +func (be *testBackend) setupSimBlockAndGetUsedExpectation(t *testing.T, expectedTxs ethtypes.Transactions, blockNum uint64, domain string) { + // simulation results, only the txs from this ToB will be simulated and the txs from RoB will be filtered out + callBundleRes := make([]flashbotsrpc.FlashbotsCallBundleResult, 0, len(expectedTxs)) + for _, tx := range expectedTxs { + callBundleRes = append(callBundleRes, flashbotsrpc.FlashbotsCallBundleResult{ + TxHash: tx.Hash().Hex(), + Error: "", + Revert: "", + }) + } + rawExpectedTxs, err := CollectRawTxs(expectedTxs) + require.NoError(t, err) + validationReq := common.BlockValidationRequest{ + Txs: rawExpectedTxs, + BlockNumber: uint64ToHexString(blockNum), + StateBlockNumber: uint64ToHexString(blockNum), + } + be.simulator.EXPECT(). + SimBlockAndGetGasUsedForChain(mock.Anything, domain, &validationReq). + Return(100, callBundleRes, nil) +} + // setLowestToBNonceForEpoch is used to set the lowest tob nonce for epoch in datastore for testing // fails with error only if both redis and database error out func (be *testBackend) setLowestToBNonceForEpoch(t *testing.T, epoch uint64, tobNonce uint64) { @@ -3327,10 +3349,10 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { // constructing ToB bundleTxs := map[string]ethtypes.Transactions{ originChainIDStr: { - CreateEthTransfer(t, originChainID, testAccounts[0].PrivateKey, testAccounts[1].Address, 100, testAccounts[0].Nonce, 21000, nil), + CreateEthTransfer(t, originChainID, testAccounts[0].PrivateKey, testAccounts[1].Address, 100, 1, 21000, nil), }, remoteChainIDStr: { - CreateEthTransfer(t, remoteChainID, testAccounts[1].PrivateKey, testAccounts[0].Address, 100, testAccounts[1].Nonce, 21000, nil), + CreateEthTransfer(t, remoteChainID, testAccounts[1].PrivateKey, testAccounts[0].Address, 100, 0, 21000, nil), }, } seqTx := CreateHypersdkBundleTx(t, testSeqChainID, bundleTxs) @@ -3367,6 +3389,8 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { }) backend := newTestBackend(t) + // sends two robs to warmup the tob + rob1, rob2 := backend.setupRoBsForToBTest(t) err = backend.arcadia.datastore.SetAuctionWinner(epoch, builderPkBytes[:]) require.NoError(t, err) redis := backend.redis @@ -3385,9 +3409,12 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { tobTxs, err := common.CollectTxsFromChunks([]*common.ArcadiaChunk{&tobReq.Chunk}) require.NoError(t, err) common.DisplayEthTxs(tobTxs) - allTxs, err := common.CollectTxsFromChunks([]*common.ArcadiaChunk{&tobReq.Chunk, &robReq.Chunk}) + allTxs, err := common.CollectTxsFromChunks([]*common.ArcadiaChunk{&tobReq.Chunk, &rob1.Chunk, &rob2.Chunk, &robReq.Chunk}) require.NoError(t, err) common.DisplayEthTxs(allTxs) + tobTxs[originChainIDStr] = slices.Insert(tobTxs[originChainIDStr], 0, rob1.Chunk.RoB.GetTxs()[0]) + tobTxs[originChainIDStr] = slices.Insert(tobTxs[originChainIDStr], 0, robReq.Chunk.RoB.GetTxs()[0]) + tobTxs[remoteChainIDStr] = slices.Insert(tobTxs[remoteChainIDStr], 0, rob2.Chunk.RoB.GetTxs()[0]) // expectations for the first tob for domain, expectedTxs := range tobTxs { matchTxs := func(txs ethtypes.Transactions) bool { @@ -3413,8 +3440,8 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { require.NoError(t, err) nonces := CollectNoncesFromEthAccounts(relatedAccounts) balances := CollectBalancesFromEthAccounts(relatedAccounts) - backend.simulator.EXPECT().GetNonces(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumbers[domain])).Return(nonces, nil) - backend.simulator.EXPECT().GetBalances(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumbers[domain])).Return(balances, nil) + backend.simulator.EXPECT().GetNonces(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumbers[domain]-2)).Return(nonces, nil) + backend.simulator.EXPECT().GetBalances(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumbers[domain]-2)).Return(balances, nil) // simulation results callBundleRes := make([]flashbotsrpc.FlashbotsCallBundleResult, 0, len(expectedTxs)) @@ -3436,12 +3463,13 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { SimBlockAndGetGasUsedForChain(mock.Anything, domain, &validationReq). Return(100, callBundleRes, nil) } - + // TODO: fails at tob req, not even at rob req which is next. rrCode := processBlockRequest(backend, tobReq) require.Equal(t, http.StatusOK, rrCode) - cmTxs, _, err := backend.arcadia.chunkManager.Txs() - require.NoError(t, err) - TxsTheSame(t, bundleTxs, cmTxs) + // TODO: below needed? + //cmTxs, _, err := backend.arcadia.chunkManager.Txs() + //require.NoError(t, err) + //TxsTheSame(t, allTxs, cmTxs) t.Log("========setup & send RoB============") for domain, expectedTxs := range allTxs { @@ -3468,8 +3496,8 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { require.NoError(t, err) nonces := CollectNoncesFromEthAccounts(relatedAccounts) balances := CollectBalancesFromEthAccounts(relatedAccounts) - backend.simulator.EXPECT().GetNonces(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumbers[domain])).Return(nonces, nil) - backend.simulator.EXPECT().GetBalances(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumbers[domain])).Return(balances, nil) + backend.simulator.EXPECT().GetNonces(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumbers[domain]-2)).Return(nonces, nil) + backend.simulator.EXPECT().GetBalances(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumbers[domain]-2)).Return(balances, nil) // simulation results, the tx from RoB will be reverted // var callBundleRes := make([]flashbotsrpc.FlashbotsCallBundleResult, 0, len(expectedTxs)) @@ -3504,8 +3532,8 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { require.NoError(t, err) validationReq := common.BlockValidationRequest{ Txs: rawExpectedTxs, - BlockNumber: uint64ToHexString(blockNumbers[domain]), - StateBlockNumber: uint64ToHexString(blockNumbers[domain]), + BlockNumber: uint64ToHexString(blockNumbers[domain] - 2), + StateBlockNumber: uint64ToHexString(blockNumbers[domain] - 2), } backend.simulator.EXPECT(). SimBlockAndGetGasUsedForChain(mock.Anything, domain, &validationReq). @@ -3523,7 +3551,7 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { t.Log("validTxs from chunk manager") common.DisplayEthTxs(validTxs) - TxsTheSame(t, bundleTxs, validTxs) + //TxsTheSame(t, bundleTxs, validTxs) require.Equal(t, blockNumbers, cmHeights) for _, bundle := range tobReq.Chunk.ToB.GetBundles() { status, err := backend.redis.GetBundleStatus(bundle.BundleHash) @@ -3571,6 +3599,8 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { }) backend := newTestBackend(t) + // sends two robs to warmup the tob + rob1, rob2 := backend.setupRoBsForToBTest(t) err = backend.arcadia.datastore.SetAuctionWinner(epoch, builderPkBytes[:]) require.NoError(t, err) redis := backend.redis @@ -3591,6 +3621,8 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { allTxs, err := common.CollectTxsFromChunks([]*common.ArcadiaChunk{&tobReq.Chunk}) require.NoError(t, err) common.DisplayEthTxs(allTxs) + tobTxs[originChainIDStr] = slices.Insert(tobTxs[originChainIDStr], 0, rob1.Chunk.RoB.GetTxs()[0]) + tobTxs[remoteChainIDStr] = slices.Insert(tobTxs[remoteChainIDStr], 0, rob2.Chunk.RoB.GetTxs()[0]) // expectations for the first tob for domain, expectedTxs := range tobTxs { matchTxs := func(txs ethtypes.Transactions) bool { @@ -3616,16 +3648,15 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { require.NoError(t, err) nonces := CollectNoncesFromEthAccounts(relatedAccounts) balances := CollectBalancesFromEthAccounts(relatedAccounts) - backend.simulator.EXPECT().GetNonces(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumbers[domain])).Return(nonces, nil) - backend.simulator.EXPECT().GetBalances(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumbers[domain])).Return(balances, nil) + backend.simulator.EXPECT().GetNonces(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumbers[domain]-2)).Return(nonces, nil) + backend.simulator.EXPECT().GetBalances(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumbers[domain]-2)).Return(balances, nil) } + backend.setupSimBlockAndGetUsedExpectation(t, rob1.Chunk.RoB.GetTxs(), blockNumbers[originChainIDStr]-2, originChainIDStr) + backend.setupSimBlockAndGetUsedExpectation(t, rob2.Chunk.RoB.GetTxs(), blockNumbers[remoteChainIDStr]-2, remoteChainIDStr) rrCode := processBlockRequest(backend, tobReq) - require.Equal(t, http.StatusOK, rrCode) - cmTxs, _, err := backend.arcadia.chunkManager.Txs() - require.NoError(t, err) - TxsTheSame(t, nil, cmTxs) - require.Equal(t, 2, len(backend.arcadia.chunkManager.Chunks())) + require.Equal(t, http.StatusNoContent, rrCode) + require.Equal(t, 1, len(backend.arcadia.chunkManager.Chunks())) for _, bundle := range tobReq.Chunk.ToB.GetBundles() { status, err := backend.redis.GetBundleStatus(bundle.BundleHash) require.NoError(t, err) From f75a3cca7ba22c2b3b434fe243454b2f68cda5c2 Mon Sep 17 00:00:00 2001 From: bianyuanop Date: Fri, 21 Feb 2025 17:11:34 -0500 Subject: [PATCH 27/31] fix incoming RoB conflicts with previous ToB, disallowed simulation revert or error --- services/api/service_test.go | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/services/api/service_test.go b/services/api/service_test.go index dfb1b9d2..f9d1a785 100644 --- a/services/api/service_test.go +++ b/services/api/service_test.go @@ -231,7 +231,7 @@ func (be *testBackend) setupRoBsForToBTest(t *testing.T) (*common.SubmitNewBlock SeqChainID: testSeqChainID, BuilderPubkey: *testBuilderPublicKey, BuilderSecretkey: *testBuilderSecretKey, - RoBBlockNumber: testBlockNumbers[testOriginChainIDStr] - 1, + RoBBlockNumber: testBlockNumbers[testOriginChainIDStr] + 1, RoBChainID: robChainID, ChunkID: ids.GenerateTestID(), Txs: nil, @@ -249,7 +249,7 @@ func (be *testBackend) setupRoBsForToBTest(t *testing.T) (*common.SubmitNewBlock SeqChainID: testSeqChainID, BuilderPubkey: *testBuilderPublicKey, BuilderSecretkey: *testBuilderSecretKey, - RoBBlockNumber: testBlockNumbers[testRemoteChainIDStr] - 1, + RoBBlockNumber: testBlockNumbers[testRemoteChainIDStr] + 1, RoBChainID: robChainID, ChunkID: ids.GenerateTestID(), Txs: nil, @@ -3349,10 +3349,10 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { // constructing ToB bundleTxs := map[string]ethtypes.Transactions{ originChainIDStr: { - CreateEthTransfer(t, originChainID, testAccounts[0].PrivateKey, testAccounts[1].Address, 100, 1, 21000, nil), + CreateEthTransfer(t, originChainID, testAccounts[0].PrivateKey, testAccounts[1].Address, 100, testAccounts[0].Nonce+1, 21000, nil), }, remoteChainIDStr: { - CreateEthTransfer(t, remoteChainID, testAccounts[1].PrivateKey, testAccounts[0].Address, 100, 0, 21000, nil), + CreateEthTransfer(t, remoteChainID, testAccounts[1].PrivateKey, testAccounts[0].Address, 100, testAccounts[1].Nonce, 21000, nil), }, } seqTx := CreateHypersdkBundleTx(t, testSeqChainID, bundleTxs) @@ -3376,7 +3376,7 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { // constructing RoB robTxs := ethtypes.Transactions{ // conflicting tx with prev ToB - CreateEthTransfer(t, originChainID, testAccounts[0].PrivateKey, testAccounts[1].Address, 20, testAccounts[0].Nonce+1, 21000, nil), + CreateEthTransfer(t, originChainID, testAccounts[0].PrivateKey, testAccounts[1].Address, 20, testAccounts[0].Nonce+2, 21000, nil), } robReq := CreateRoBReq(t, &CreateTestBlockSubmissionOpts{ Epoch: epoch, @@ -3406,15 +3406,12 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { backend.seqcli.EXPECT().Parser().Return(chainParser) t.Log("========setup & send TOB============") - tobTxs, err := common.CollectTxsFromChunks([]*common.ArcadiaChunk{&tobReq.Chunk}) + tobTxs, err := common.CollectTxsFromChunks([]*common.ArcadiaChunk{&rob1.Chunk, &rob2.Chunk, &tobReq.Chunk}) require.NoError(t, err) common.DisplayEthTxs(tobTxs) - allTxs, err := common.CollectTxsFromChunks([]*common.ArcadiaChunk{&tobReq.Chunk, &rob1.Chunk, &rob2.Chunk, &robReq.Chunk}) + allTxs, err := common.CollectTxsFromChunks([]*common.ArcadiaChunk{&rob1.Chunk, &rob2.Chunk, &tobReq.Chunk, &robReq.Chunk}) require.NoError(t, err) common.DisplayEthTxs(allTxs) - tobTxs[originChainIDStr] = slices.Insert(tobTxs[originChainIDStr], 0, rob1.Chunk.RoB.GetTxs()[0]) - tobTxs[originChainIDStr] = slices.Insert(tobTxs[originChainIDStr], 0, robReq.Chunk.RoB.GetTxs()[0]) - tobTxs[remoteChainIDStr] = slices.Insert(tobTxs[remoteChainIDStr], 0, rob2.Chunk.RoB.GetTxs()[0]) // expectations for the first tob for domain, expectedTxs := range tobTxs { matchTxs := func(txs ethtypes.Transactions) bool { @@ -3440,8 +3437,8 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { require.NoError(t, err) nonces := CollectNoncesFromEthAccounts(relatedAccounts) balances := CollectBalancesFromEthAccounts(relatedAccounts) - backend.simulator.EXPECT().GetNonces(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumbers[domain]-2)).Return(nonces, nil) - backend.simulator.EXPECT().GetBalances(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumbers[domain]-2)).Return(balances, nil) + backend.simulator.EXPECT().GetNonces(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumbers[domain])).Return(nonces, nil) + backend.simulator.EXPECT().GetBalances(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumbers[domain])).Return(balances, nil) // simulation results callBundleRes := make([]flashbotsrpc.FlashbotsCallBundleResult, 0, len(expectedTxs)) @@ -3496,8 +3493,8 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { require.NoError(t, err) nonces := CollectNoncesFromEthAccounts(relatedAccounts) balances := CollectBalancesFromEthAccounts(relatedAccounts) - backend.simulator.EXPECT().GetNonces(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumbers[domain]-2)).Return(nonces, nil) - backend.simulator.EXPECT().GetBalances(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumbers[domain]-2)).Return(balances, nil) + backend.simulator.EXPECT().GetNonces(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumbers[domain])).Return(nonces, nil) + backend.simulator.EXPECT().GetBalances(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumbers[domain])).Return(balances, nil) // simulation results, the tx from RoB will be reverted // var callBundleRes := make([]flashbotsrpc.FlashbotsCallBundleResult, 0, len(expectedTxs)) @@ -3532,8 +3529,8 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { require.NoError(t, err) validationReq := common.BlockValidationRequest{ Txs: rawExpectedTxs, - BlockNumber: uint64ToHexString(blockNumbers[domain] - 2), - StateBlockNumber: uint64ToHexString(blockNumbers[domain] - 2), + BlockNumber: uint64ToHexString(blockNumbers[domain]), + StateBlockNumber: uint64ToHexString(blockNumbers[domain]), } backend.simulator.EXPECT(). SimBlockAndGetGasUsedForChain(mock.Anything, domain, &validationReq). From c631ea050cdefec331b41c8e99b5a959704dd1c7 Mon Sep 17 00:00:00 2001 From: rikoeldon <106416799+rikoeldon@users.noreply.github.com> Date: Sat, 22 Feb 2025 00:32:01 -0500 Subject: [PATCH 28/31] debugging last 2 failing cases, checkpoint. --- services/api/service_test.go | 132 +++++++++++++++++------------------ 1 file changed, 65 insertions(+), 67 deletions(-) diff --git a/services/api/service_test.go b/services/api/service_test.go index f9d1a785..8b6b7da9 100644 --- a/services/api/service_test.go +++ b/services/api/service_test.go @@ -2968,7 +2968,7 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { // constructing ToB bundleTxs := map[string]ethtypes.Transactions{ originChainIDStr: { - CreateEthTransfer(t, originChainID, testAccounts[0].PrivateKey, testAccounts[1].Address, 100, testAccounts[0].Nonce, 21000, nil), + CreateEthTransfer(t, originChainID, testAccounts[0].PrivateKey, testAccounts[1].Address, 100, testAccounts[0].Nonce+1, 21000, nil), }, remoteChainIDStr: { CreateEthTransfer(t, remoteChainID, testAccounts[1].PrivateKey, testAccounts[0].Address, 100, testAccounts[1].Nonce, 21000, nil), @@ -2993,9 +2993,10 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { }) // constructing RoB + //TODO: some reason tx has same nonce as tob but not filtered out? robTxs := ethtypes.Transactions{ // conflicting tx with prev ToB - CreateEthTransfer(t, originChainID, testAccounts[0].PrivateKey, testAccounts[1].Address, 20, testAccounts[0].Nonce, 21000, nil), + CreateEthTransfer(t, originChainID, testAccounts[0].PrivateKey, testAccounts[1].Address, 20, testAccounts[0].Nonce+1, 21000, nil), } robReq := CreateRoBReq(t, &CreateTestBlockSubmissionOpts{ Epoch: epoch, @@ -3008,6 +3009,8 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { }) backend := newTestBackend(t) + // sends two robs to warmup the tob + rob1, rob2 := backend.setupRoBsForToBTest(t) err = backend.arcadia.datastore.SetAuctionWinner(epoch, builderPkBytes[:]) require.NoError(t, err) redis := backend.redis @@ -3023,10 +3026,10 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { backend.seqcli.EXPECT().Parser().Return(chainParser) t.Log("========setup & send TOB============") - tobTxs, err := common.CollectTxsFromChunks([]*common.ArcadiaChunk{&tobReq.Chunk}) + tobTxs, err := common.CollectTxsFromChunks([]*common.ArcadiaChunk{&rob1.Chunk, &rob2.Chunk, &tobReq.Chunk}) require.NoError(t, err) common.DisplayEthTxs(tobTxs) - allTxs, err := common.CollectTxsFromChunks([]*common.ArcadiaChunk{&tobReq.Chunk, &robReq.Chunk}) + allTxs, err := common.CollectTxsFromChunks([]*common.ArcadiaChunk{&rob1.Chunk, &rob2.Chunk, &tobReq.Chunk, &robReq.Chunk}) require.NoError(t, err) common.DisplayEthTxs(allTxs) @@ -3083,7 +3086,7 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { require.Equal(t, http.StatusOK, rrCode) cmTxs, _, err := backend.arcadia.chunkManager.Txs() require.NoError(t, err) - TxsTheSame(t, bundleTxs, cmTxs) + TxsTheSame(t, allTxs, cmTxs) // for the rob t.Log("========setup & send RoB============") @@ -3126,7 +3129,7 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { t.Log("validTxs from chunk manager") common.DisplayEthTxs(validTxs) - TxsTheSame(t, bundleTxs, validTxs) + TxsTheSame(t, allTxs, validTxs) require.Equal(t, blockNumbers, cmHeights) for _, bundle := range tobReq.Chunk.ToB.GetBundles() { @@ -3146,25 +3149,10 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { remoteChainIDStr: 50, } - // constructing RoB - robTxs := ethtypes.Transactions{ - // conflicting tx with prev ToB - CreateEthTransfer(t, originChainID, testAccounts[0].PrivateKey, testAccounts[1].Address, 20, testAccounts[0].Nonce, 21000, nil), - } - //robReq := CreateRoBReq(t, &CreateTestBlockSubmissionOpts{ - // Epoch: epoch, - // SeqChainID: testSeqChainID, - // RoBChainID: originChainID, - // RoBBlockNumber: blockNumbers[originChainIDStr] - 1, - // BuilderPubkey: *testBuilderPublicKey, - // BuilderSecretkey: *testBuilderSecretKey, - // Txs: robTxs, - //}) - // constructing ToB bundleTxs := map[string]ethtypes.Transactions{ originChainIDStr: { - CreateEthTransfer(t, originChainID, testAccounts[0].PrivateKey, testAccounts[1].Address, 100, testAccounts[0].Nonce, 21000, nil), + CreateEthTransfer(t, originChainID, testAccounts[0].PrivateKey, testAccounts[1].Address, 100, testAccounts[0].Nonce+1, 21000, nil), }, remoteChainIDStr: { CreateEthTransfer(t, remoteChainID, testAccounts[1].PrivateKey, testAccounts[0].Address, 100, testAccounts[1].Nonce, 21000, nil), @@ -3182,34 +3170,34 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { tobReq := CreateToBReq(t, &CreateTestBlockSubmissionOpts{ Epoch: epoch, + OriginChainID: *originChainID, + RemoteChainID: *remoteChainID, ToBBlockNumber: blockNumbers, BuilderPubkey: *testBuilderPublicKey, BuilderSecretkey: *testBuilderSecretKey, Bundles: []*common.CrossRollupBundle{&bundle}, + IsToB: true, }) - // instantiate backend - backend := newTestBackend(t) - // send rob - remoteChainIDInt, err := common.ChainIDStrToChainID(remoteChainIDStr) - require.NoError(t, err) - robChainID := remoteChainIDInt - id := ids.GenerateTestID() - robOpts1 := &CreateTestBlockSubmissionOpts{ + robTxs := ethtypes.Transactions{ + // conflicting tx with prev ToB + CreateEthTransfer(t, originChainID, testAccounts[0].PrivateKey, testAccounts[1].Address, 20, testAccounts[0].Nonce, 21000, nil), + } + + robReq := CreateRoBReq(t, &CreateTestBlockSubmissionOpts{ Epoch: epoch, - OriginChainID: *robChainID, - RemoteChainID: *remoteChainIDInt, SeqChainID: testSeqChainID, + RoBChainID: originChainID, + RoBBlockNumber: blockNumbers[originChainIDStr] + 1, BuilderPubkey: *testBuilderPublicKey, BuilderSecretkey: *testBuilderSecretKey, - RoBBlockNumber: blockNumbers[originChainIDStr] - 1, - RoBChainID: robChainID, - ChunkID: id, Txs: robTxs, - IsToB: false, - } - robReq := backend.submitRoBChunk(t, robOpts1, chainParser) + }) + // instantiate backend + backend := newTestBackend(t) + // sends two robs to warmup the tob + rob1, rob2 := backend.setupRoBsForToBTest(t) err = backend.arcadia.datastore.SetAuctionWinner(epoch, builderPkBytes[:]) require.NoError(t, err) redis := backend.redis @@ -3250,8 +3238,8 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { require.NoError(t, err) nonces := CollectNoncesFromEthAccounts(relatedAccounts) balances := CollectBalancesFromEthAccounts(relatedAccounts) - backend.simulator.EXPECT().GetNonces(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumbers[domain]-2)).Return(nonces, nil) - backend.simulator.EXPECT().GetBalances(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumbers[domain]-2)).Return(balances, nil) + backend.simulator.EXPECT().GetNonces(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumbers[domain])).Return(nonces, nil) + backend.simulator.EXPECT().GetBalances(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumbers[domain])).Return(balances, nil) // simulation results, only the txs from this RoB will be simulated and the txs from ToB will be filtered out callBundleRes := make([]flashbotsrpc.FlashbotsCallBundleResult, 0, len(expectedTxs)) for _, tx := range expectedTxs { @@ -3265,23 +3253,21 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { require.NoError(t, err) validationReq := common.BlockValidationRequest{ Txs: rawExpectedTxs, - BlockNumber: uint64ToHexString(blockNumbers[domain] - 2), - StateBlockNumber: uint64ToHexString(blockNumbers[domain] - 2), + BlockNumber: uint64ToHexString(blockNumbers[domain]), + StateBlockNumber: uint64ToHexString(blockNumbers[domain]), } backend.simulator.EXPECT(). SimBlockAndGetGasUsedForChain(mock.Anything, domain, &validationReq). Return(100, callBundleRes, nil) } - //rrCode1 := processBlockRequest(backend, robReq) - //require.Equal(t, http.StatusOK, rrCode1) - t.Log("========setup & send TOB============") - allTxs, err := common.CollectTxsFromChunks([]*common.ArcadiaChunk{&robReq.Chunk, &tobReq.Chunk}) + tobTxs, err := common.CollectTxsFromChunks([]*common.ArcadiaChunk{&rob1.Chunk, &rob2.Chunk, &tobReq.Chunk}) + allTxs, err := common.CollectTxsFromChunks([]*common.ArcadiaChunk{&rob1.Chunk, &rob2.Chunk, &robReq.Chunk, &tobReq.Chunk}) require.NoError(t, err) t.Log("all txs") common.DisplayEthTxs(allTxs) - for domain, expectedTxs := range allTxs { + for domain, expectedTxs := range tobTxs { matchTxs := func(txs ethtypes.Transactions) bool { if len(txs) != len(expectedTxs) { return false @@ -3300,33 +3286,45 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { fmt.Printf("%s, ", tx.Hash().Hex()) } fmt.Printf("]\n") + // simulation results, only the txs from this RoB will be simulated and the txs from ToB will be filtered out + callBundleRes := make([]flashbotsrpc.FlashbotsCallBundleResult, 0, len(expectedTxs)) + for _, tx := range expectedTxs { + callBundleRes = append(callBundleRes, flashbotsrpc.FlashbotsCallBundleResult{ + TxHash: tx.Hash().Hex(), + Error: "", + Revert: "", + }) + } + rawExpectedTxs, err := CollectRawTxs(expectedTxs) + require.NoError(t, err) + validationReq := common.BlockValidationRequest{ + Txs: rawExpectedTxs, + BlockNumber: uint64ToHexString(blockNumbers[domain]), + StateBlockNumber: uint64ToHexString(blockNumbers[domain]), + } + backend.simulator.EXPECT(). + SimBlockAndGetGasUsedForChain(mock.Anything, domain, &validationReq). + Return(100, callBundleRes, nil) relatedAccounts, err := CollectAccountsFromTxs(expectedTxs, testAccounts) require.NoError(t, err) nonces := CollectNoncesFromEthAccounts(relatedAccounts) balances := CollectBalancesFromEthAccounts(relatedAccounts) - if domain == originChainIDStr { - backend.simulator.EXPECT().GetNonces(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumbers[domain]-2)).Return(nonces, nil) - backend.simulator.EXPECT().GetBalances(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumbers[domain]-2)).Return(balances, nil) - } else { - backend.simulator.EXPECT().GetNonces(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumbers[domain])).Return(nonces, nil) - backend.simulator.EXPECT().GetBalances(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumbers[domain])).Return(balances, nil) - } + backend.simulator.EXPECT().GetNonces(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumbers[domain])).Return(nonces, nil) + backend.simulator.EXPECT().GetBalances(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumbers[domain])).Return(balances, nil) } rrCode := processBlockRequest(backend, tobReq) require.Equal(t, http.StatusOK, rrCode) require.Equal(t, 2, len(backend.arcadia.chunkManager.Chunks())) - validTxs, cmHeights, err := backend.arcadia.chunkManager.Txs() + _, cmHeights, err := backend.arcadia.chunkManager.Txs() require.NoError(t, err) - TxsTheSame(t, map[string]ethtypes.Transactions{ - originChainIDStr: robTxs, - }, validTxs) + //TxsTheSame(t, allTxs, validTxs) expectedLowestHeights := maps.Clone(blockNumbers) // delete the remote block number since ToB's conflicting with previous RoBs and bundle got removed delete(expectedLowestHeights, remoteChainIDStr) - expectedLowestHeights[originChainIDStr] = blockNumbers[originChainIDStr] - 2 + expectedLowestHeights[originChainIDStr] = blockNumbers[originChainIDStr] require.Equal(t, expectedLowestHeights, cmHeights) for _, bundle := range tobReq.Chunk.ToB.GetBundles() { @@ -3645,11 +3643,11 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { require.NoError(t, err) nonces := CollectNoncesFromEthAccounts(relatedAccounts) balances := CollectBalancesFromEthAccounts(relatedAccounts) - backend.simulator.EXPECT().GetNonces(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumbers[domain]-2)).Return(nonces, nil) - backend.simulator.EXPECT().GetBalances(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumbers[domain]-2)).Return(balances, nil) + backend.simulator.EXPECT().GetNonces(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumbers[domain])).Return(nonces, nil) + backend.simulator.EXPECT().GetBalances(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumbers[domain])).Return(balances, nil) } - backend.setupSimBlockAndGetUsedExpectation(t, rob1.Chunk.RoB.GetTxs(), blockNumbers[originChainIDStr]-2, originChainIDStr) - backend.setupSimBlockAndGetUsedExpectation(t, rob2.Chunk.RoB.GetTxs(), blockNumbers[remoteChainIDStr]-2, remoteChainIDStr) + backend.setupSimBlockAndGetUsedExpectation(t, rob1.Chunk.RoB.GetTxs(), blockNumbers[originChainIDStr], originChainIDStr) + backend.setupSimBlockAndGetUsedExpectation(t, rob2.Chunk.RoB.GetTxs(), blockNumbers[remoteChainIDStr], remoteChainIDStr) rrCode := processBlockRequest(backend, tobReq) require.Equal(t, http.StatusNoContent, rrCode) @@ -3745,8 +3743,8 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { fmt.Printf("expected balance for domain(%s): %+v\n", domain, balances) matchTxs := ExpectedMatchTxs(expectedTxs) - backend.simulator.EXPECT().GetNonces(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumbers[domain]-2)).Return(nonces, nil) - backend.simulator.EXPECT().GetBalances(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumbers[domain]-2)).Return(balances, nil) + backend.simulator.EXPECT().GetNonces(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumbers[domain])).Return(nonces, nil) + backend.simulator.EXPECT().GetBalances(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumbers[domain])).Return(balances, nil) // simulation results, only the txs from this ToB will be simulated and the txs from RoB will be filtered out callBundleRes := make([]flashbotsrpc.FlashbotsCallBundleResult, 0, len(expectedTxs)) @@ -3769,8 +3767,8 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { require.NoError(t, err) validationReq := common.BlockValidationRequest{ Txs: rawExpectedTxs, - BlockNumber: uint64ToHexString(blockNumbers[domain] - 2), - StateBlockNumber: uint64ToHexString(blockNumbers[domain] - 2), + BlockNumber: uint64ToHexString(blockNumbers[domain]), + StateBlockNumber: uint64ToHexString(blockNumbers[domain]), } backend.simulator.EXPECT(). SimBlockAndGetGasUsedForChain(mock.Anything, domain, &validationReq). From 55dcc7a412f6fa96a7ef264bead4094f059636b0 Mon Sep 17 00:00:00 2001 From: NateAtNodeKit Date: Mon, 24 Feb 2025 11:32:56 -0500 Subject: [PATCH 29/31] updates --- services/api/service_test.go | 141 ++++++++++------------------------- 1 file changed, 40 insertions(+), 101 deletions(-) diff --git a/services/api/service_test.go b/services/api/service_test.go index 8b6b7da9..9e369813 100644 --- a/services/api/service_test.go +++ b/services/api/service_test.go @@ -2993,7 +2993,6 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { }) // constructing RoB - //TODO: some reason tx has same nonce as tob but not filtered out? robTxs := ethtypes.Transactions{ // conflicting tx with prev ToB CreateEthTransfer(t, originChainID, testAccounts[0].PrivateKey, testAccounts[1].Address, 20, testAccounts[0].Nonce+1, 21000, nil), @@ -3009,11 +3008,14 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { }) backend := newTestBackend(t) + // sends two robs to warmup the tob rob1, rob2 := backend.setupRoBsForToBTest(t) + err = backend.arcadia.datastore.SetAuctionWinner(epoch, builderPkBytes[:]) require.NoError(t, err) redis := backend.redis + redis.SetSizeTracker(backend.arcadia.sizeTracker) backend.simulator.EXPECT().GetBlockNumber([]string{originChainIDStr, remoteChainIDStr}).Return(blockNumbers, nil) @@ -3029,7 +3031,7 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { tobTxs, err := common.CollectTxsFromChunks([]*common.ArcadiaChunk{&rob1.Chunk, &rob2.Chunk, &tobReq.Chunk}) require.NoError(t, err) common.DisplayEthTxs(tobTxs) - allTxs, err := common.CollectTxsFromChunks([]*common.ArcadiaChunk{&rob1.Chunk, &rob2.Chunk, &tobReq.Chunk, &robReq.Chunk}) + allTxs, err := common.CollectTxsFromChunks([]*common.ArcadiaChunk{&rob1.Chunk, &rob2.Chunk, &tobReq.Chunk}) require.NoError(t, err) common.DisplayEthTxs(allTxs) @@ -3086,23 +3088,15 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { require.Equal(t, http.StatusOK, rrCode) cmTxs, _, err := backend.arcadia.chunkManager.Txs() require.NoError(t, err) - TxsTheSame(t, allTxs, cmTxs) + TxsTheSameUnordered(t, allTxs, cmTxs) - // for the rob - t.Log("========setup & send RoB============") - for domain, expectedTxs := range allTxs { - matchTxs := func(txs ethtypes.Transactions) bool { - if len(txs) != len(expectedTxs) { - return false - } - for i, tx := range txs { - if tx.Hash().Hex() != expectedTxs[i].Hash().Hex() { - return false - } - } - return true - } + // for the rob after tob + allTxs2, err := common.CollectTxsFromChunks([]*common.ArcadiaChunk{&rob1.Chunk, &rob2.Chunk, &tobReq.Chunk, &robReq.Chunk}) + require.NoError(t, err) + common.DisplayEthTxs(allTxs) + t.Log("========setup & send RoB after ToB============") + for domain, expectedTxs := range allTxs2 { fmt.Printf("expected txs for domain: %s\n", domain) fmt.Printf("[") for _, tx := range expectedTxs { @@ -3112,8 +3106,11 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { relatedAccounts, err := CollectAccountsFromTxs(expectedTxs, testAccounts) require.NoError(t, err) + nonces := CollectNoncesFromEthAccounts(relatedAccounts) balances := CollectBalancesFromEthAccounts(relatedAccounts) + + matchTxs := ExpectedMatchTxs(expectedTxs) backend.simulator.EXPECT().GetNonces(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumbers[domain])).Return(nonces, nil) backend.simulator.EXPECT().GetBalances(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumbers[domain])).Return(balances, nil) } @@ -3129,7 +3126,8 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { t.Log("validTxs from chunk manager") common.DisplayEthTxs(validTxs) - TxsTheSame(t, allTxs, validTxs) + // TODO: Chan, we doesn't validTxs have the tx from the 3rd rob? + TxsTheSameUnordered(t, allTxs, validTxs) require.Equal(t, blockNumbers, cmHeights) for _, bundle := range tobReq.Chunk.ToB.GetBundles() { @@ -3139,7 +3137,7 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { } }) - t.Run("incoming ToB conflicts with previous RoBs, txs with incosecutive nonces", func(t *testing.T) { + t.Run("incoming ToB conflicts with previous RoBs, txs with inconsecutive nonces", func(t *testing.T) { originChainID := big.NewInt(45200) originChainIDStr := hexutil.EncodeBig(originChainID) remoteChainID := big.NewInt(45201) @@ -3150,12 +3148,13 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { } // constructing ToB + inconsecutiveNonceRemoteTx := testAccounts[1].Nonce + 5 bundleTxs := map[string]ethtypes.Transactions{ originChainIDStr: { CreateEthTransfer(t, originChainID, testAccounts[0].PrivateKey, testAccounts[1].Address, 100, testAccounts[0].Nonce+1, 21000, nil), }, remoteChainIDStr: { - CreateEthTransfer(t, remoteChainID, testAccounts[1].PrivateKey, testAccounts[0].Address, 100, testAccounts[1].Nonce, 21000, nil), + CreateEthTransfer(t, remoteChainID, testAccounts[1].PrivateKey, testAccounts[0].Address, 100, inconsecutiveNonceRemoteTx, 21000, nil), }, } seqTx := CreateHypersdkBundleTx(t, testSeqChainID, bundleTxs) @@ -3179,21 +3178,6 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { IsToB: true, }) - robTxs := ethtypes.Transactions{ - // conflicting tx with prev ToB - CreateEthTransfer(t, originChainID, testAccounts[0].PrivateKey, testAccounts[1].Address, 20, testAccounts[0].Nonce, 21000, nil), - } - - robReq := CreateRoBReq(t, &CreateTestBlockSubmissionOpts{ - Epoch: epoch, - SeqChainID: testSeqChainID, - RoBChainID: originChainID, - RoBBlockNumber: blockNumbers[originChainIDStr] + 1, - BuilderPubkey: *testBuilderPublicKey, - BuilderSecretkey: *testBuilderSecretKey, - Txs: robTxs, - }) - // instantiate backend backend := newTestBackend(t) // sends two robs to warmup the tob @@ -3208,65 +3192,21 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { backend.SetupRegisteredRollups(epoch, originChainID) backend.SetupRegisteredRollups(epoch, remoteChainID) - // set up mock expectations - // shared calls for both chunks - backend.seqcli.EXPECT().Parser().Return(chainParser) - fmt.Println("========setup & send RoB============") - for domain, expectedTxs := range map[string]ethtypes.Transactions{ - originChainIDStr: robTxs, - } { - matchTxs := func(txs ethtypes.Transactions) bool { - if len(txs) != len(expectedTxs) { - return false - } - for i, tx := range txs { - if tx.Hash().Hex() != expectedTxs[i].Hash().Hex() { - return false - } - } - return true - } - - fmt.Printf("expected txs for domain: %s\n", domain) - fmt.Printf("[") - for _, tx := range expectedTxs { - fmt.Printf("%s, ", tx.Hash().Hex()) - } - fmt.Printf("]\n") + t.Log("========setup & send TOB============") + tobTxs, err := common.CollectTxsFromChunks([]*common.ArcadiaChunk{&tobReq.Chunk}) + require.NoError(t, err) - relatedAccounts, err := CollectAccountsFromTxs(expectedTxs, testAccounts) - require.NoError(t, err) - nonces := CollectNoncesFromEthAccounts(relatedAccounts) - balances := CollectBalancesFromEthAccounts(relatedAccounts) - backend.simulator.EXPECT().GetNonces(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumbers[domain])).Return(nonces, nil) - backend.simulator.EXPECT().GetBalances(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumbers[domain])).Return(balances, nil) - // simulation results, only the txs from this RoB will be simulated and the txs from ToB will be filtered out - callBundleRes := make([]flashbotsrpc.FlashbotsCallBundleResult, 0, len(expectedTxs)) - for _, tx := range expectedTxs { - callBundleRes = append(callBundleRes, flashbotsrpc.FlashbotsCallBundleResult{ - TxHash: tx.Hash().Hex(), - Error: "", - Revert: "", - }) - } - rawExpectedTxs, err := CollectRawTxs(expectedTxs) - require.NoError(t, err) - validationReq := common.BlockValidationRequest{ - Txs: rawExpectedTxs, - BlockNumber: uint64ToHexString(blockNumbers[domain]), - StateBlockNumber: uint64ToHexString(blockNumbers[domain]), - } - backend.simulator.EXPECT(). - SimBlockAndGetGasUsedForChain(mock.Anything, domain, &validationReq). - Return(100, callBundleRes, nil) - } + tobTxs[originChainIDStr] = slices.Insert(tobTxs[originChainIDStr], 0, rob1.Chunk.RoB.GetTxs()[0]) + tobTxs[remoteChainIDStr] = slices.Insert(tobTxs[remoteChainIDStr], 0, rob2.Chunk.RoB.GetTxs()[0]) - t.Log("========setup & send TOB============") - tobTxs, err := common.CollectTxsFromChunks([]*common.ArcadiaChunk{&rob1.Chunk, &rob2.Chunk, &tobReq.Chunk}) - allTxs, err := common.CollectTxsFromChunks([]*common.ArcadiaChunk{&rob1.Chunk, &rob2.Chunk, &robReq.Chunk, &tobReq.Chunk}) + allTxs, err := common.CollectTxsFromChunks([]*common.ArcadiaChunk{&rob1.Chunk, &rob2.Chunk, &tobReq.Chunk}) require.NoError(t, err) t.Log("all txs") common.DisplayEthTxs(allTxs) + + robTxs, err := common.CollectTxsFromChunks([]*common.ArcadiaChunk{&rob1.Chunk, &rob2.Chunk}) + require.NoError(t, err) + for domain, expectedTxs := range tobTxs { matchTxs := func(txs ethtypes.Transactions) bool { if len(txs) != len(expectedTxs) { @@ -3295,7 +3235,9 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { Revert: "", }) } - rawExpectedTxs, err := CollectRawTxs(expectedTxs) + + robDomainTxs := robTxs[domain] + rawExpectedTxs, err := CollectRawTxs(robDomainTxs) require.NoError(t, err) validationReq := common.BlockValidationRequest{ Txs: rawExpectedTxs, @@ -3313,19 +3255,22 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { backend.simulator.EXPECT().GetNonces(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumbers[domain])).Return(nonces, nil) backend.simulator.EXPECT().GetBalances(domain, mock.MatchedBy(matchTxs), uint64ToHexString(blockNumbers[domain])).Return(balances, nil) } + rrCode := processBlockRequest(backend, tobReq) - require.Equal(t, http.StatusOK, rrCode) + require.Equal(t, http.StatusNoContent, rrCode) - require.Equal(t, 2, len(backend.arcadia.chunkManager.Chunks())) - _, cmHeights, err := backend.arcadia.chunkManager.Txs() - require.NoError(t, err) + // TODO: Verify the below + //require.Equal(t, 2, len(backend.arcadia.chunkManager.Chunks())) + //_, cmHeights, err := backend.arcadia.chunkManager.Txs() + //require.NoError(t, err) //TxsTheSame(t, allTxs, validTxs) expectedLowestHeights := maps.Clone(blockNumbers) // delete the remote block number since ToB's conflicting with previous RoBs and bundle got removed delete(expectedLowestHeights, remoteChainIDStr) expectedLowestHeights[originChainIDStr] = blockNumbers[originChainIDStr] - require.Equal(t, expectedLowestHeights, cmHeights) + + //require.Equal(t, expectedLowestHeights, cmHeights) for _, bundle := range tobReq.Chunk.ToB.GetBundles() { status, err := backend.redis.GetBundleStatus(bundle.BundleHash) @@ -3458,13 +3403,8 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { SimBlockAndGetGasUsedForChain(mock.Anything, domain, &validationReq). Return(100, callBundleRes, nil) } - // TODO: fails at tob req, not even at rob req which is next. rrCode := processBlockRequest(backend, tobReq) require.Equal(t, http.StatusOK, rrCode) - // TODO: below needed? - //cmTxs, _, err := backend.arcadia.chunkManager.Txs() - //require.NoError(t, err) - //TxsTheSame(t, allTxs, cmTxs) t.Log("========setup & send RoB============") for domain, expectedTxs := range allTxs { @@ -3546,7 +3486,6 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { t.Log("validTxs from chunk manager") common.DisplayEthTxs(validTxs) - //TxsTheSame(t, bundleTxs, validTxs) require.Equal(t, blockNumbers, cmHeights) for _, bundle := range tobReq.Chunk.ToB.GetBundles() { status, err := backend.redis.GetBundleStatus(bundle.BundleHash) From 0b03c805ddd845163ea8b4ba7bd4b853a4b28803 Mon Sep 17 00:00:00 2001 From: rikoeldon <106416799+rikoeldon@users.noreply.github.com> Date: Mon, 24 Feb 2025 14:48:24 -0500 Subject: [PATCH 30/31] update --- services/api/service_test.go | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/services/api/service_test.go b/services/api/service_test.go index 9e369813..76d783b4 100644 --- a/services/api/service_test.go +++ b/services/api/service_test.go @@ -3126,7 +3126,6 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { t.Log("validTxs from chunk manager") common.DisplayEthTxs(validTxs) - // TODO: Chan, we doesn't validTxs have the tx from the 3rd rob? TxsTheSameUnordered(t, allTxs, validTxs) require.Equal(t, blockNumbers, cmHeights) @@ -3260,17 +3259,18 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { require.Equal(t, http.StatusNoContent, rrCode) // TODO: Verify the below - //require.Equal(t, 2, len(backend.arcadia.chunkManager.Chunks())) - //_, cmHeights, err := backend.arcadia.chunkManager.Txs() - //require.NoError(t, err) + require.Equal(t, 1, len(backend.arcadia.chunkManager.Chunks())) + validTxs, cmHeights, err := backend.arcadia.chunkManager.Txs() + require.NoError(t, err) - //TxsTheSame(t, allTxs, validTxs) + TxsTheSameUnordered(t, robTxs, validTxs) expectedLowestHeights := maps.Clone(blockNumbers) + // TODO: below check is not needed since we submit a rob per chainID so 0x91 will have rob tx. // delete the remote block number since ToB's conflicting with previous RoBs and bundle got removed - delete(expectedLowestHeights, remoteChainIDStr) - expectedLowestHeights[originChainIDStr] = blockNumbers[originChainIDStr] + //delete(expectedLowestHeights, remoteChainIDStr) + //expectedLowestHeights[originChainIDStr] = blockNumbers[originChainIDStr] - //require.Equal(t, expectedLowestHeights, cmHeights) + require.Equal(t, expectedLowestHeights, cmHeights) for _, bundle := range tobReq.Chunk.ToB.GetBundles() { status, err := backend.redis.GetBundleStatus(bundle.BundleHash) @@ -6108,6 +6108,7 @@ func TestOverallFlow(t *testing.T) { err = backend.arcadia.db.SetPayloadTxsRoB(robChunk.BlockNumber, chainID, &common.PayloadTxs{ Txs: ethOtxs[chainID], }) + require.NoError(t, err) payloadResp := common.GetPayloadResponse{ Transactions: ethOtxs[chainID], From ae7b46141ee5a1ed2df3e2e921028561372bf82e Mon Sep 17 00:00:00 2001 From: rikoeldon <106416799+rikoeldon@users.noreply.github.com> Date: Mon, 24 Feb 2025 15:33:23 -0500 Subject: [PATCH 31/31] feedback comment --- services/api/service_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/services/api/service_test.go b/services/api/service_test.go index 76d783b4..0b3edbcd 100644 --- a/services/api/service_test.go +++ b/services/api/service_test.go @@ -293,6 +293,7 @@ func (be *testBackend) submitRoBChunk(t *testing.T, opts *CreateTestBlockSubmiss // constructing RoB with txs from origin chain id if opts.Txs == nil { robTxs := ethtypes.Transactions{ + // for every first RoB for one chain, testAccounts[0] is used and the tx nonce used is 0 CreateEthTransfer(t, &opts.OriginChainID, testAccounts[0].PrivateKey, testAccounts[1].Address, 20, testAccounts[0].Nonce, 21000, nil), } opts.Txs = robTxs