From 47dae3a49198d59dd65296e7be07ce982d4b6e49 Mon Sep 17 00:00:00 2001 From: bianyuanop Date: Thu, 20 Feb 2025 10:05:11 -0500 Subject: [PATCH 01/13] configuration fix --- chunk_manager/chunk_manager.go | 15 ++++++++------- cmd/api.go | 1 + datalayer/da_submitter.go | 19 ++++++++++++++++--- go.mod | 2 +- go.sum | 4 ++-- services/api/service.go | 2 +- 6 files changed, 29 insertions(+), 14 deletions(-) diff --git a/chunk_manager/chunk_manager.go b/chunk_manager/chunk_manager.go index 52a73375..f76cc4da 100644 --- a/chunk_manager/chunk_manager.go +++ b/chunk_manager/chunk_manager.go @@ -639,13 +639,14 @@ func (m *ChunkManager) submitLayerToSEQ(ctx context.Context, tobNonce uint64) er } certInfo := seqtypes.DACertInfo{ - DAType: 0, - ChainID: chainID, - BlockNumber: blockNumber, - Epoch: chunk.epochCreated, - ToBNonce: chunk.ToBNonce, - ChunkID: chunkID, - Cert: chunk.cert.Cert, + DAType: 0, + ChainID: chainID, + BlockNumber: blockNumber, + Epoch: chunk.epochCreated, + ToBNonce: chunk.ToBNonce, + ChunkID: chunkID, + IsPlaceHolder: chunk.cert.PlaceHolder, + Cert: chunk.cert.Cert, } acts = append(acts, &actions.DACertificate{ diff --git a/cmd/api.go b/cmd/api.go index a7607e0f..e1760d5a 100644 --- a/cmd/api.go +++ b/cmd/api.go @@ -233,6 +233,7 @@ var apiCmd = &cobra.Command{ // as saving highestPreconfd tobnonce is no use, we won't recover to the state after highestSettledToBNonce HighestSettledToBNonce: highestSettledToBNonce, SEQChainParser: seqCli.Parser(), + SEQClient: seqCli, Datastore: ds, Logger: log, } diff --git a/datalayer/da_submitter.go b/datalayer/da_submitter.go index 6fbdd933..c9656a0a 100644 --- a/datalayer/da_submitter.go +++ b/datalayer/da_submitter.go @@ -87,22 +87,35 @@ func NewDASubmitter(opts DASubmitterOpts) (*DASubmitter, error) { for { select { case chunkMsg := <-submitter.pendingSubmissionChunks: + chainID := "tob" + blockNumber := chunkMsg.Chunk.ToBNonce + if chunkMsg.Chunk.IsRoB() { + chainID = chunkMsg.Chunk.RoB.ChainID + blockNumber = chunkMsg.Chunk.RoB.BlockNumber + } + log := submitter.log.WithFields(logrus.Fields{ + "chunkID": chunkMsg.ChunkID, + "chainID": chainID, + "blockNumber": blockNumber, + }) + log.Debug("submitting chunk to DA") + chunkCtx, chunkCancel := context.WithTimeout(ctx, 20*time.Second) blob, err := json.Marshal(chunkMsg) if err != nil { - submitter.log.WithError(err).Error("unable to marshal chunk message") + log.WithError(err).Error("unable to marshal chunk message") chunkCancel() continue } cert, err := submitter.submitAndFinalizeBlob(chunkCtx, blob) if err != nil { - submitter.log.WithError(err).Error("unable to submit blob to DA") + log.WithError(err).Error("unable to submit blob to DA") chunkCancel() continue } certRaw, err := json.Marshal(cert) if err != nil { - submitter.log.WithError(err).Error("unable to marshal cert") + log.WithError(err).Error("unable to marshal cert") chunkCancel() continue } diff --git a/go.mod b/go.mod index 7af83589..a42c3ebe 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.22.5 require ( github.com/AnomalyFi/flashbotsrpc v0.5.3 github.com/AnomalyFi/hypersdk v0.9.7-arcadia.22 - github.com/AnomalyFi/nodekit-seq v0.9.27-rpc.3 + github.com/AnomalyFi/nodekit-seq v0.9.27-rpc.4 github.com/NYTimes/gziphandler v1.1.1 github.com/alicebob/miniredis/v2 v2.31.0 github.com/attestantio/go-builder-client v0.4.3-0.20240124194555-d44db06f45fa diff --git a/go.sum b/go.sum index 961134fa..e920a26d 100644 --- a/go.sum +++ b/go.sum @@ -5,8 +5,8 @@ github.com/AnomalyFi/flashbotsrpc v0.5.3 h1:1jQPcL0wU97hni4GHpSIj4AJcRM+72wzEFnC github.com/AnomalyFi/flashbotsrpc v0.5.3/go.mod h1:r5A0vSSYnv2G3FHcaa30uCjfBScveUCQHm1cmL8ATNA= github.com/AnomalyFi/hypersdk v0.9.7-arcadia.22 h1:8C7R6sy6oZZratzS7oouvoHNpV/Vjok1NLGPEVp56Sg= github.com/AnomalyFi/hypersdk v0.9.7-arcadia.22/go.mod h1:0Vj2PdwSFN7pat4Sno39IfmtOiv/gO9mxZXyRKnoKtI= -github.com/AnomalyFi/nodekit-seq v0.9.27-rpc.3 h1:vgwe8SjyBqh/6O3Zc9PslHd+YJ5A7AVMD5ko0C8ESfo= -github.com/AnomalyFi/nodekit-seq v0.9.27-rpc.3/go.mod h1:yAd5rfhRgBMf/3hUBH+fgIwxR5s732GruDGU3Ms1QDk= +github.com/AnomalyFi/nodekit-seq v0.9.27-rpc.4 h1:iizFy5rwKSwnOoO82gu5rUA2hgTuKPnwYyamUruvtzs= +github.com/AnomalyFi/nodekit-seq v0.9.27-rpc.4/go.mod h1:yAd5rfhRgBMf/3hUBH+fgIwxR5s732GruDGU3Ms1QDk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/DataDog/zstd v1.5.2 h1:vUG4lAyuPCXO0TLbXvPv7EB7cNK1QV/luu55UHLrrn8= github.com/DataDog/zstd v1.5.2/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= diff --git a/services/api/service.go b/services/api/service.go index 15e56286..1a51f4ef 100644 --- a/services/api/service.go +++ b/services/api/service.go @@ -711,7 +711,7 @@ func (api *ArcadiaAPI) announceAuctionWinnerToSeq(ctx context.Context, headEpoch "pubkey": hexutil.Encode(winner.BuilderPublicKey), }) - log.Debug("adding auction tx to tx pool to be picked up") + log.WithField("txID", auctionTx.ID().String()).Debug("adding auction tx to tx pool to be picked up") // add to [pendingTxs] for pickup api.pendingTxs.AddTx(auctionTx) From 81a7da0df0fea931c229d9b9aca5ef8f47fcde62 Mon Sep 17 00:00:00 2001 From: bianyuanop Date: Thu, 20 Feb 2025 11:44:32 -0500 Subject: [PATCH 02/13] place holder chunk id fix & rob place holder --- chunk_manager/chunk_layer.go | 3 ++ chunk_manager/chunk_manager.go | 77 ++++++++++++++++++++++++----- chunk_manager/chunk_manager_test.go | 31 ++++++++++++ common/types.go | 22 ++++++++- services/api/payload.go | 14 +++--- 5 files changed, 126 insertions(+), 21 deletions(-) diff --git a/chunk_manager/chunk_layer.go b/chunk_manager/chunk_layer.go index 3c62ad82..59e3a1cb 100644 --- a/chunk_manager/chunk_layer.go +++ b/chunk_manager/chunk_layer.go @@ -177,6 +177,9 @@ func (cl *ChunkLayer) PushChunk(chunk *ChunkWrapper) error { return nil } +// MarkChunkSubmittedToDA marks a chunk as submitted to DA by the ToBNonce and ChunkID, the assumption made here is +// - chunk layer exists at the tob nonce +// - chunkIDs are different in the same layer func (cl *ChunkLayer) MarkChunkSubmittedToDA(msg *common.ArcadiaToSEQChunkMessage, cert *common.CertInfo) error { cl.l.Lock() defer cl.l.Unlock() diff --git a/chunk_manager/chunk_manager.go b/chunk_manager/chunk_manager.go index f76cc4da..d8cb01d3 100644 --- a/chunk_manager/chunk_manager.go +++ b/chunk_manager/chunk_manager.go @@ -88,10 +88,18 @@ func NewChunkManager(cfg *ChunkManagerConfig) (*ChunkManager, error) { if cfg.StateLowestToBNonce != 0 { initialChunkLayerToBNonce = cfg.StateLowestToBNonce - 1 } - placeHolderToB := common.NewPlaceHolderToBChunk(initialChunkLayerToBNonce) + placeHolderToB, err := common.NewPlaceHolderToBChunk(initialChunkLayerToBNonce) + if err != nil { + return nil, err + } initialChunkLayer := NewChunkLayer(initialChunkLayerToBNonce, &ChunkWrapper{ ArcadiaChunk: placeHolderToB, - timestamp: time.Now(), + cert: &common.CertInfo{ + DAType: actions.CelestiaDA, + PlaceHolder: true, + Cert: nil, + }, + timestamp: time.Now(), }) cm := &ChunkManager{ @@ -195,6 +203,51 @@ func (m *ChunkManager) addChunkLayer(chunk *common.ArcadiaChunk) { } } +func (m *ChunkManager) AddPlaceHolderChunk(chunk *common.ArcadiaChunk) error { + if m.stop.Load() { + return errors.New("chunk manager already stopped") + } + + m.l.Lock() + defer m.l.Unlock() + return m.innerAddPlaceHolderChunk(chunk) +} + +func (m *ChunkManager) innerAddPlaceHolderChunk(chunk *common.ArcadiaChunk) error { + if chunk.ToB != nil { + chunk.AssignToBNonce(m.headToBNonce + 1) + m.addChunkLayer(chunk) + } else { + chunk.AssignToBNonce(m.headToBNonce) + err := m.chunks[len(m.chunks)-1].PushChunk(&ChunkWrapper{ + ArcadiaChunk: chunk, + timestamp: time.Now(), + }) + if err != nil { + m.logger.WithFields(logrus.Fields{ + "tobNonce": m.headToBNonce, + "chainID": chunk.RoB.ChainID, + "blockNumber": chunk.RoB.BlockNumber, + "err": err, + }).Error("unable to push place holder rob to layer") + return err + } + } + + chunkID, err := chunk.ID() + if err != nil { + return err + } + m.innerPushPreconf(chunk) + return m.innerPushCert(context.TODO(), &common.ArcadiaToSEQChunkMessage{ + ChunkID: chunkID, + Chunk: chunk, + }, &common.CertInfo{ + DAType: actions.CelestiaDA, + PlaceHolder: true, + }) +} + // AddChunk is called only when simulation succeed func (m *ChunkManager) AddChunk(chunk *common.ArcadiaChunk) error { if m.stop.Load() { @@ -732,7 +785,13 @@ func (m *ChunkManager) garbageCollect() { m.logger.WithFields(logrus.Fields{ "tobNonce": m.headToBNonce + 1, }).Debug("adding place holder tob") - placeHolderToB := common.NewPlaceHolderToBChunk(m.headToBNonce + 1) + placeHolderToB, err := common.NewPlaceHolderToBChunk(m.headToBNonce + 1) + if err != nil { + m.logger.WithFields(logrus.Fields{ + "tobNone": m.headToBNonce + 1, + "err": err, + }).Error("unable to create place holder tob chunk") + } if err := m.ds.SetToBChunk(m.headToBNonce+1, placeHolderToB.ToB); err != nil { m.stop.Store(true) m.logger.WithFields(logrus.Fields{ @@ -742,19 +801,11 @@ func (m *ChunkManager) garbageCollect() { return } - m.addChunkLayer(placeHolderToB) - - m.innerPushPreconf(placeHolderToB) - err := m.innerPushCert(context.TODO(), &common.ArcadiaToSEQChunkMessage{ - Chunk: placeHolderToB, - }, &common.CertInfo{ - PlaceHolder: true, - }) - if err != nil { + if err := m.innerAddPlaceHolderChunk(placeHolderToB); err != nil { m.logger.WithFields(logrus.Fields{ "err": err, "tobNonce": placeHolderToB.ToBNonce, - }).Error("unable to push place holder cert") + }).Error("unable to add place holder tob to chunk manager") } lastChunk = m.chunks[len(m.chunks)-1] diff --git a/chunk_manager/chunk_manager_test.go b/chunk_manager/chunk_manager_test.go index 5ee8f4fc..56be19ae 100644 --- a/chunk_manager/chunk_manager_test.go +++ b/chunk_manager/chunk_manager_test.go @@ -848,6 +848,37 @@ func TestChunkManagerCanInsertPlaceHolder(t *testing.T) { require.Equal(t, 1, len(chunks)) require.Equal(t, uint64(1), cm.ToBNonce()) }) + + t.Run("test chunk manager can insert place holder rob", func(t *testing.T) { + ds := mdatastore.NewMockIDatastore(t) + ds.EXPECT().LoadChunksWithToBNonce(uint64(0)).Return(nil, nil) + ds.EXPECT().SetToBChunk(mock.Anything, mock.Anything).Return(nil).Once() + ds.EXPECT().SaveHighestSettledToBNonce(0, 0).Return(nil).Maybe() + cm, err := NewChunkManager(&ChunkManagerConfig{ + ExpirationTime: 100 * time.Second, // 1 epoch + GCInterval: 100 * time.Second, + HeadToBNonce: 0, + StateLowestToBNonce: 0, + Datastore: ds, + SEQChainParser: &srpc.Parser{}, + Logger: common.TestLog, + }) + require.NoError(t, err) + + rob, err := common.NewPlaceHolderRoBChunk("0xb096", 100, 100) + require.NoError(t, err) + err = cm.AddPlaceHolderChunk(rob) + require.NoError(t, err) + layers := cm.Chunks() + require.Equal(t, 1, len(layers)) + require.Equal(t, uint64(0), cm.ToBNonce()) + require.Equal(t, cm.ToBNonce(), rob.ToBNonce) + require.Equal(t, 2, len(layers[0].chunks)) + for _, chunk := range layers[0].chunks { + require.True(t, chunk.cert.PlaceHolder) + } + require.True(t, layers[0].ChunksSubmittedToDA()) + }) } func TestChunkManagerReplay(t *testing.T) { diff --git a/common/types.go b/common/types.go index 3fb6927e..96f1b936 100644 --- a/common/types.go +++ b/common/types.go @@ -679,7 +679,9 @@ type ArcadiaChunk struct { txs map[string]ethtypes.Transactions } -func NewPlaceHolderToBChunk(tobNonce uint64) *ArcadiaChunk { +// NewPlaceHolderToBChunk creates a chunk with the identical chunkID with all other place holder tob chunks +// however, this is fine since in every chunk layer, there will be only one ToB +func NewPlaceHolderToBChunk(tobNonce uint64) (*ArcadiaChunk, error) { placeHolderToB := &ArcadiaChunk{ ToB: &ToBChunk{ Bundles: nil, @@ -699,7 +701,23 @@ func NewPlaceHolderToBChunk(tobNonce uint64) *ArcadiaChunk { txs: nil, chunkID: ids.Empty, } - return placeHolderToB + return placeHolderToB, setChunkPayloadInfo(placeHolderToB, placeHolderToB.ToB) +} + +// NewPlaceHolderRoBChunk creates a chunk with distinct chunkID with other place holder RoBs +func NewPlaceHolderRoBChunk(chainID string, blockNumber uint64, tobNonce uint64) (*ArcadiaChunk, error) { + placeHolderRoB := &ArcadiaChunk{ + RoB: &RoBChunk{ + ChainID: chainID, + BlockNumber: blockNumber, + Txs: nil, + }, + ToBNonce: tobNonce, + initialized: true, + txs: nil, + chunkID: ids.Empty, + } + return placeHolderRoB, setChunkPayloadInfo(placeHolderRoB, placeHolderRoB.RoB) } func setChunkPayloadInfo[ChunkType any](chunk *ArcadiaChunk, internalChunk *ChunkType) error { diff --git a/services/api/payload.go b/services/api/payload.go index 7d918bf1..ce6a5ec2 100644 --- a/services/api/payload.go +++ b/services/api/payload.go @@ -59,15 +59,17 @@ func (api *ArcadiaAPI) buildPayload(headEpoch uint64, rollupRegistration *hactio // insert an empty RoB into redis, and serve from there, the empty RoB won't be sent to SEQ // so we don't need to assign the signatures & pubkey log.WithError(err).Warn("failed to get rob chunk, inserting empty RoB as subtitution") - robChunk = &common.RoBChunk{ - ChainID: chainID, - BlockNumber: blockNumber, - Txs: nil, + currentToBNonce := api.chunkManager.ToBNonce() + placeHolderRoB, err := common.NewPlaceHolderRoBChunk(chainID, blockNumber, currentToBNonce) + if err != nil { + return nil, fmt.Errorf("unable to create rob chunk place holder: %w", err) + } + if err := api.chunkManager.AddPlaceHolderChunk(placeHolderRoB); err != nil { + return nil, fmt.Errorf("unable to add rob chunk place holder: %w", err) } // Set ToB nonce of RoB - currentToBNonce := api.chunkManager.ToBNonce() - err = api.datastore.SaveToBNonceOfRoB(chainID, blockNumber, currentToBNonce) + err = api.datastore.SaveToBNonceOfRoB(chainID, blockNumber, placeHolderRoB.ToBNonce) if err != nil { log.WithError(err).Error("failed to set tob nonce of rob in both datastores, request failed") return nil, err From 6d9e6ba39792c1f1dcc079619a7313f975fdf038 Mon Sep 17 00:00:00 2001 From: bianyuanop Date: Thu, 20 Feb 2025 13:18:00 -0500 Subject: [PATCH 03/13] ckp --- services/api/service_test.go | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/services/api/service_test.go b/services/api/service_test.go index 66b9658d..b20c4644 100644 --- a/services/api/service_test.go +++ b/services/api/service_test.go @@ -125,19 +125,21 @@ func newTestbackendWithCustomDatastore(t *testing.T, redisCache *datastore.Redis managerSk, err := bls.SecretKeyFromBytes(managerSkBytes) require.NoError(t, err) + seqClient := mseq.NewMockBaseSeqClient(t) + seqClient.EXPECT().SetOnNewBlockHandler(mock.Anything).Return().Maybe() + seqClient.EXPECT().Parser().Return(&srpc.Parser{}).Maybe() + blockSim := msim.NewMockIBlockSimRateLimiter(t) + config := chunkmanager.ChunkManagerConfig{ ExpirationTime: 5 * time.Minute, GCInterval: 10 * time.Second, HeadToBNonce: 0, Datastore: ds, + SEQClient: seqClient, + SEQChainParser: seqClient.Parser(), Logger: common.TestLog, } - seqClient := mseq.NewMockBaseSeqClient(t) - seqClient.EXPECT().SetOnNewBlockHandler(mock.Anything).Return().Maybe() - seqClient.EXPECT().Parser().Return(&srpc.Parser{}).Maybe() - blockSim := msim.NewMockIBlockSimRateLimiter(t) - cm, err := chunkmanager.NewChunkManager(&config) require.NoError(t, err) @@ -2418,6 +2420,10 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { // set up mock expectations // shared calls for both chunks backend.seqcli.EXPECT().Parser().Return(chainParser) + layers := backend.arcadia.chunkManager.Chunks() + require.Equal(t, 1, len(layers)) + // require.Equal(t, 1, len(layers[0].)) + // backend.seqcli.EXPECT().GenerateTransaction(mock.Anything, ) t.Log("========setup & send RoB============") for domain, expectedTxs := range map[string]ethtypes.Transactions{ originChainIDStr: robTxs, From eb582b3c1f3b93bd2eafed492f911a6bb0a331d9 Mon Sep 17 00:00:00 2001 From: bianyuanop Date: Thu, 20 Feb 2025 14:26:41 -0500 Subject: [PATCH 04/13] ckp1 --- chunk_manager/chunk_layer.go | 26 ----------------- chunk_manager/chunk_manager_test.go | 43 +++++++++++++++++++++++++++++ services/api/payload.go | 2 ++ services/api/service_test.go | 34 ++++++++++++++++++++--- 4 files changed, 75 insertions(+), 30 deletions(-) diff --git a/chunk_manager/chunk_layer.go b/chunk_manager/chunk_layer.go index 59e3a1cb..21ced89c 100644 --- a/chunk_manager/chunk_layer.go +++ b/chunk_manager/chunk_layer.go @@ -4,7 +4,6 @@ import ( "errors" "fmt" "slices" - "sync" "time" "github.com/AnomalyFi/Arcadia/common" @@ -16,8 +15,6 @@ type ChunkLayer struct { chunks []*ChunkWrapper submittedToSEQ bool - - l sync.RWMutex } func NewChunkLayer(nonce uint64, tobChunk *ChunkWrapper) *ChunkLayer { @@ -33,21 +30,14 @@ func init() { } func (cl *ChunkLayer) ToBNonce() uint64 { - cl.l.RLock() - defer cl.l.RUnlock() - return cl.tobNonce } func (cl *ChunkLayer) Timestamp() time.Time { - cl.l.RLock() - defer cl.l.RUnlock() return cl.chunks[0].timestamp } func (cl *ChunkLayer) Txs() (map[string]ethtypes.Transactions, error) { - cl.l.RLock() - defer cl.l.RUnlock() ret := make(map[string]ethtypes.Transactions) for _, chunk := range cl.chunks { chunkTxs, err := chunk.Txs() @@ -65,8 +55,6 @@ func (cl *ChunkLayer) Txs() (map[string]ethtypes.Transactions, error) { } func (cl *ChunkLayer) LowestBlockNumber() map[string]uint64 { - cl.l.RLock() - defer cl.l.RUnlock() ret := make(map[string]uint64) for _, chunk := range cl.chunks { chunkBlockNumber := chunk.LowestBlockNumber() @@ -84,8 +72,6 @@ func (cl *ChunkLayer) LowestBlockNumber() map[string]uint64 { } func (cl *ChunkLayer) TxsWithNewTob(tob *ChunkWrapper) (map[string]ethtypes.Transactions, error) { - cl.l.RLock() - defer cl.l.RUnlock() ret := make(map[string]ethtypes.Transactions) tobTxs, err := tob.Txs() if err != nil { @@ -115,8 +101,6 @@ func (cl *ChunkLayer) TxsWithNewTob(tob *ChunkWrapper) (map[string]ethtypes.Tran } func (cl *ChunkLayer) TxsWithNewRoB(rob *ChunkWrapper) (map[string]ethtypes.Transactions, error) { - cl.l.RLock() - defer cl.l.RUnlock() robIdx := slices.IndexFunc(cl.chunks[1:], func(cw *ChunkWrapper) bool { return cw.RoB.ChainID == rob.RoB.ChainID && cw.RoB.BlockNumber == rob.RoB.BlockNumber }) @@ -159,8 +143,6 @@ func (cl *ChunkLayer) TxsWithNewRoB(rob *ChunkWrapper) (map[string]ethtypes.Tran } func (cl *ChunkLayer) PushChunk(chunk *ChunkWrapper) error { - cl.l.Lock() - defer cl.l.Unlock() if chunk.ToB != nil { return errors.New("trying to push tob into a chunk layer") } @@ -181,8 +163,6 @@ func (cl *ChunkLayer) PushChunk(chunk *ChunkWrapper) error { // - chunk layer exists at the tob nonce // - chunkIDs are different in the same layer func (cl *ChunkLayer) MarkChunkSubmittedToDA(msg *common.ArcadiaToSEQChunkMessage, cert *common.CertInfo) error { - cl.l.Lock() - defer cl.l.Unlock() if cl.tobNonce != msg.Chunk.ToBNonce { return fmt.Errorf("chunk tobNonce not match with chunk layer tobNonce") } @@ -213,8 +193,6 @@ func (cl *ChunkLayer) MarkChunkSubmittedToDA(msg *common.ArcadiaToSEQChunkMessag } func (cl *ChunkLayer) ChunksSubmittedToDA() bool { - cl.l.RLock() - defer cl.l.RUnlock() submitted := true for _, chunk := range cl.chunks { submitted = submitted && chunk.SubmittedToDA() @@ -223,13 +201,9 @@ func (cl *ChunkLayer) ChunksSubmittedToDA() bool { } func (cl *ChunkLayer) LayerSubmittedToSEQ() bool { - cl.l.RLock() - defer cl.l.RUnlock() return cl.submittedToSEQ } func (cl *ChunkLayer) MarkLayerSubmittedToSEQ() { - cl.l.Lock() - defer cl.l.Unlock() cl.submittedToSEQ = true } diff --git a/chunk_manager/chunk_manager_test.go b/chunk_manager/chunk_manager_test.go index 56be19ae..d0751208 100644 --- a/chunk_manager/chunk_manager_test.go +++ b/chunk_manager/chunk_manager_test.go @@ -33,6 +33,13 @@ func TestCanAddChunk(t *testing.T) { ds.EXPECT().LoadChunksWithToBNonce(uint64(0)).Return(nil, nil) ds.EXPECT().SaveCurrentToBNonce(tobNonce, uint64(0)).Return(nil) ds.EXPECT().SetToBChunk(uint64(0), mock.Anything).Return(nil) // for the initial place holder tob + + seqCli := mseq.NewMockBaseSeqClient(t) + // in case new incoming tob trigger submission of previous layer + mockTx := new(chain.Transaction) + txChan := make(chan *chain.Transaction) + seqCli.EXPECT().GenerateTransaction(mock.Anything, mock.Anything).Return(mockTx, nil) + seqCli.EXPECT().TxChan().Return(txChan).Maybe() // there's already one initial chunk layer in it cm, err := NewChunkManager(&ChunkManagerConfig{ ExpirationTime: 100 * time.Second, @@ -41,6 +48,7 @@ func TestCanAddChunk(t *testing.T) { StateLowestToBNonce: 0, Datastore: ds, Logger: logger, + SEQClient: seqCli, SEQChainParser: &srpc.Parser{}, }) require.NoError(t, err) @@ -186,6 +194,11 @@ func TestCanWithCorrectTxs(t *testing.T) { ds.EXPECT().SetToBChunk(uint64(0), mock.Anything).Return(nil) // place holder tob seqCli := mseq.NewMockBaseSeqClient(t) + // in case new incoming tob trigger submission of previous layer + mockTx := new(chain.Transaction) + txChan := make(chan *chain.Transaction) + seqCli.EXPECT().GenerateTransaction(mock.Anything, mock.Anything).Return(mockTx, nil) + seqCli.EXPECT().TxChan().Return(txChan).Maybe() // there's already one initial chunk layer in it cm, err := NewChunkManager(&ChunkManagerConfig{ @@ -307,6 +320,12 @@ func TestCanWithCorrectTxs(t *testing.T) { seqCli := mseq.NewMockBaseSeqClient(t) + // in case new incoming tob trigger submission of previous layer + mockTx := new(chain.Transaction) + txChan := make(chan *chain.Transaction) + seqCli.EXPECT().GenerateTransaction(mock.Anything, mock.Anything).Return(mockTx, nil) + seqCli.EXPECT().TxChan().Return(txChan).Maybe() + // there's already one initial chunk layer in it cm, err := NewChunkManager(&ChunkManagerConfig{ ExpirationTime: 100 * time.Second, @@ -591,6 +610,11 @@ func TestCanSettleTxs(t *testing.T) { ds.EXPECT().SetToBChunk(uint64(0), mock.Anything).Return(nil) // place holder tob seqCli := mseq.NewMockBaseSeqClient(t) + // in case new incoming tob trigger submission of previous layer + mockTx := new(chain.Transaction) + txChan := make(chan *chain.Transaction) + seqCli.EXPECT().GenerateTransaction(mock.Anything, mock.Anything).Return(mockTx, nil) + seqCli.EXPECT().TxChan().Return(txChan).Maybe() // there's already one initial chunk layer in it cm, err := NewChunkManager(&ChunkManagerConfig{ @@ -823,6 +847,13 @@ func TestChunkManagerCanInsertPlaceHolder(t *testing.T) { ds.EXPECT().SaveHighestSettledToBNonce(nonce, uint64(0)).Return(nil).Maybe() } + seqCli := mseq.NewMockBaseSeqClient(t) + // in case new incoming tob trigger submission of previous layer + mockTx := new(chain.Transaction) + txChan := make(chan *chain.Transaction) + seqCli.EXPECT().GenerateTransaction(mock.Anything, mock.Anything).Return(mockTx, nil) + seqCli.EXPECT().TxChan().Return(txChan).Maybe() + epochDuration := 12 * time.Second // there's already one initial chunk layer in it cm, err := NewChunkManager(&ChunkManagerConfig{ @@ -831,6 +862,7 @@ func TestChunkManagerCanInsertPlaceHolder(t *testing.T) { HeadToBNonce: 0, StateLowestToBNonce: 0, Datastore: ds, + SEQClient: seqCli, SEQChainParser: &srpc.Parser{}, Logger: common.TestLog, }) @@ -894,6 +926,13 @@ func TestChunkManagerReplay(t *testing.T) { ds.EXPECT().SaveCurrentToBNonce(uint64(1), uint64(0)).Return(nil) ds.EXPECT().SetToBChunk(uint64(0), mock.Anything).Return(nil) // place holder tob + seqCli := mseq.NewMockBaseSeqClient(t) + // in case new incoming tob trigger submission of previous layer + mockTx := new(chain.Transaction) + txChan := make(chan *chain.Transaction) + seqCli.EXPECT().GenerateTransaction(mock.Anything, mock.Anything).Return(mockTx, nil) + seqCli.EXPECT().TxChan().Return(txChan).Maybe() + cm, err := NewChunkManager(&ChunkManagerConfig{ ExpirationTime: 100 * time.Second, GCInterval: 100 * time.Second, @@ -903,6 +942,7 @@ func TestChunkManagerReplay(t *testing.T) { HighestSettledToBNonce: 0, Datastore: ds, + SEQClient: seqCli, SEQChainParser: &seqParser, Logger: logger, }) @@ -956,6 +996,8 @@ func TestChunkManagerReplay(t *testing.T) { replayChunks = append(replayChunks, chunks...) } + seqCli := mseq.NewMockBaseSeqClient(t) + cm, err := NewChunkManager(&ChunkManagerConfig{ ExpirationTime: 100 * time.Second, GCInterval: 100 * time.Second, @@ -966,6 +1008,7 @@ func TestChunkManagerReplay(t *testing.T) { Datastore: ds, SEQChainParser: &seqParser, + SEQClient: seqCli, Logger: logger, }) require.NoError(t, err) diff --git a/services/api/payload.go b/services/api/payload.go index ce6a5ec2..d630b44d 100644 --- a/services/api/payload.go +++ b/services/api/payload.go @@ -68,6 +68,8 @@ func (api *ArcadiaAPI) buildPayload(headEpoch uint64, rollupRegistration *hactio return nil, fmt.Errorf("unable to add rob chunk place holder: %w", err) } + robChunk = placeHolderRoB.RoB + // Set ToB nonce of RoB err = api.datastore.SaveToBNonceOfRoB(chainID, blockNumber, placeHolderRoB.ToBNonce) if err != nil { diff --git a/services/api/service_test.go b/services/api/service_test.go index b20c4644..d00ec5ee 100644 --- a/services/api/service_test.go +++ b/services/api/service_test.go @@ -2420,10 +2420,6 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { // set up mock expectations // shared calls for both chunks backend.seqcli.EXPECT().Parser().Return(chainParser) - layers := backend.arcadia.chunkManager.Chunks() - require.Equal(t, 1, len(layers)) - // require.Equal(t, 1, len(layers[0].)) - // backend.seqcli.EXPECT().GenerateTransaction(mock.Anything, ) t.Log("========setup & send RoB============") for domain, expectedTxs := range map[string]ethtypes.Transactions{ originChainIDStr: robTxs, @@ -2522,6 +2518,11 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { // set up mock expectations // shared calls for both chunks backend.seqcli.EXPECT().Parser().Return(chainParser) + // adding new tob will create a new layer, which will try submit the previous layer to SEQ + mockTx := new(chain.Transaction) + txChan := make(chan *chain.Transaction) + backend.seqcli.EXPECT().GenerateTransaction(mock.Anything, mock.Anything).Return(mockTx, nil) + backend.seqcli.EXPECT().TxChan().Return(txChan).Maybe() t.Log("========setup & send TOB============") tobTxs, err := common.CollectTxsFromChunks([]*common.ArcadiaChunk{&tobReq.Chunk}) @@ -2714,6 +2715,11 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { // set up mock expectations // shared calls for both chunks backend.seqcli.EXPECT().Parser().Return(chainParser) + // adding new tob will create a new layer, which will try submit the previous layer to SEQ + mockTx := new(chain.Transaction) + txChan := make(chan *chain.Transaction) + backend.seqcli.EXPECT().GenerateTransaction(mock.Anything, mock.Anything).Return(mockTx, nil) + backend.seqcli.EXPECT().TxChan().Return(txChan).Maybe() t.Log("========setup & send TOB============") tobTxs, err := common.CollectTxsFromChunks([]*common.ArcadiaChunk{&tobReq.Chunk}) @@ -3075,6 +3081,11 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { // set up mock expectations // shared calls for both chunks backend.seqcli.EXPECT().Parser().Return(chainParser) + // adding new tob will create a new layer, which will try submit the previous layer to SEQ + mockTx := new(chain.Transaction) + txChan := make(chan *chain.Transaction) + backend.seqcli.EXPECT().GenerateTransaction(mock.Anything, mock.Anything).Return(mockTx, nil) + backend.seqcli.EXPECT().TxChan().Return(txChan).Maybe() t.Log("========setup & send TOB============") tobTxs, err := common.CollectTxsFromChunks([]*common.ArcadiaChunk{&tobReq.Chunk}) @@ -3278,6 +3289,11 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { // set up mock expectations // shared calls for both chunks backend.seqcli.EXPECT().Parser().Return(chainParser) + // adding new tob will create a new layer, which will try submit the previous layer to SEQ + mockTx := new(chain.Transaction) + txChan := make(chan *chain.Transaction) + backend.seqcli.EXPECT().GenerateTransaction(mock.Anything, mock.Anything).Return(mockTx, nil) + backend.seqcli.EXPECT().TxChan().Return(txChan).Maybe() t.Log("========setup & send TOB============") tobTxs, err := common.CollectTxsFromChunks([]*common.ArcadiaChunk{&tobReq.Chunk}) @@ -3396,6 +3412,11 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { // set up mock expectations // shared calls for both chunks backend.seqcli.EXPECT().Parser().Return(chainParser) + // adding new tob will create a new layer, which will try submit the previous layer to SEQ + mockTx := new(chain.Transaction) + txChan := make(chan *chain.Transaction) + backend.seqcli.EXPECT().GenerateTransaction(mock.Anything, mock.Anything).Return(mockTx, nil) + backend.seqcli.EXPECT().TxChan().Return(txChan).Maybe() t.Log("========setup & send TOB============") tobTxs, err := common.CollectTxsFromChunks([]*common.ArcadiaChunk{&tobReq.Chunk}) @@ -6386,6 +6407,11 @@ func TestToBNonceState(t *testing.T) { // set up mock expectations // shared calls for both chunks backend.seqcli.EXPECT().Parser().Return(chainParser) + // adding new tob will create a new layer, which will try submit the previous layer to SEQ + mockTx := new(chain.Transaction) + txChan := make(chan *chain.Transaction) + backend.seqcli.EXPECT().GenerateTransaction(mock.Anything, mock.Anything).Return(mockTx, nil) + backend.seqcli.EXPECT().TxChan().Return(txChan).Maybe() t.Log("========setup & send TOB============") tobTxs, err := common.CollectTxsFromChunks([]*common.ArcadiaChunk{&tobReq.Chunk}) From 173eca378f644b85656fc97f5db36f3d2f8c6f67 Mon Sep 17 00:00:00 2001 From: bianyuanop Date: Thu, 20 Feb 2025 16:25:13 -0500 Subject: [PATCH 05/13] unit tests fix --- chunk_manager/chunk_layer.go | 30 +++++++++++++++++++++++++++++ chunk_manager/chunk_manager.go | 6 +++++- chunk_manager/chunk_manager_test.go | 5 ----- services/api/service_test.go | 16 +++++++++++++-- 4 files changed, 49 insertions(+), 8 deletions(-) diff --git a/chunk_manager/chunk_layer.go b/chunk_manager/chunk_layer.go index 21ced89c..bfc6731f 100644 --- a/chunk_manager/chunk_layer.go +++ b/chunk_manager/chunk_layer.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "slices" + "sync" "time" "github.com/AnomalyFi/Arcadia/common" @@ -15,6 +16,8 @@ type ChunkLayer struct { chunks []*ChunkWrapper submittedToSEQ bool + + l sync.RWMutex } func NewChunkLayer(nonce uint64, tobChunk *ChunkWrapper) *ChunkLayer { @@ -30,14 +33,20 @@ func init() { } func (cl *ChunkLayer) ToBNonce() uint64 { + cl.l.RLock() + defer cl.l.RUnlock() return cl.tobNonce } func (cl *ChunkLayer) Timestamp() time.Time { + cl.l.RLock() + defer cl.l.RUnlock() return cl.chunks[0].timestamp } func (cl *ChunkLayer) Txs() (map[string]ethtypes.Transactions, error) { + cl.l.RLock() + defer cl.l.RUnlock() ret := make(map[string]ethtypes.Transactions) for _, chunk := range cl.chunks { chunkTxs, err := chunk.Txs() @@ -55,6 +64,9 @@ func (cl *ChunkLayer) Txs() (map[string]ethtypes.Transactions, error) { } func (cl *ChunkLayer) LowestBlockNumber() map[string]uint64 { + cl.l.RLock() + defer cl.l.RUnlock() + ret := make(map[string]uint64) for _, chunk := range cl.chunks { chunkBlockNumber := chunk.LowestBlockNumber() @@ -72,6 +84,9 @@ func (cl *ChunkLayer) LowestBlockNumber() map[string]uint64 { } func (cl *ChunkLayer) TxsWithNewTob(tob *ChunkWrapper) (map[string]ethtypes.Transactions, error) { + cl.l.RLock() + defer cl.l.RUnlock() + ret := make(map[string]ethtypes.Transactions) tobTxs, err := tob.Txs() if err != nil { @@ -101,6 +116,9 @@ func (cl *ChunkLayer) TxsWithNewTob(tob *ChunkWrapper) (map[string]ethtypes.Tran } func (cl *ChunkLayer) TxsWithNewRoB(rob *ChunkWrapper) (map[string]ethtypes.Transactions, error) { + cl.l.RLock() + defer cl.l.RUnlock() + robIdx := slices.IndexFunc(cl.chunks[1:], func(cw *ChunkWrapper) bool { return cw.RoB.ChainID == rob.RoB.ChainID && cw.RoB.BlockNumber == rob.RoB.BlockNumber }) @@ -143,6 +161,9 @@ func (cl *ChunkLayer) TxsWithNewRoB(rob *ChunkWrapper) (map[string]ethtypes.Tran } func (cl *ChunkLayer) PushChunk(chunk *ChunkWrapper) error { + cl.l.Lock() + defer cl.l.Unlock() + if chunk.ToB != nil { return errors.New("trying to push tob into a chunk layer") } @@ -163,6 +184,9 @@ func (cl *ChunkLayer) PushChunk(chunk *ChunkWrapper) error { // - chunk layer exists at the tob nonce // - chunkIDs are different in the same layer func (cl *ChunkLayer) MarkChunkSubmittedToDA(msg *common.ArcadiaToSEQChunkMessage, cert *common.CertInfo) error { + cl.l.Lock() + defer cl.l.Unlock() + if cl.tobNonce != msg.Chunk.ToBNonce { return fmt.Errorf("chunk tobNonce not match with chunk layer tobNonce") } @@ -193,6 +217,8 @@ func (cl *ChunkLayer) MarkChunkSubmittedToDA(msg *common.ArcadiaToSEQChunkMessag } func (cl *ChunkLayer) ChunksSubmittedToDA() bool { + cl.l.RLock() + defer cl.l.RUnlock() submitted := true for _, chunk := range cl.chunks { submitted = submitted && chunk.SubmittedToDA() @@ -201,9 +227,13 @@ func (cl *ChunkLayer) ChunksSubmittedToDA() bool { } func (cl *ChunkLayer) LayerSubmittedToSEQ() bool { + cl.l.RLock() + defer cl.l.RUnlock() return cl.submittedToSEQ } func (cl *ChunkLayer) MarkLayerSubmittedToSEQ() { + cl.l.Lock() + defer cl.l.Unlock() cl.submittedToSEQ = true } diff --git a/chunk_manager/chunk_manager.go b/chunk_manager/chunk_manager.go index d8cb01d3..fdddb082 100644 --- a/chunk_manager/chunk_manager.go +++ b/chunk_manager/chunk_manager.go @@ -168,6 +168,10 @@ func NewChunkManager(cfg *ChunkManagerConfig) (*ChunkManager, error) { } func (m *ChunkManager) addChunkLayer(chunk *common.ArcadiaChunk) { + log := m.logger.WithFields(logrus.Fields{ + "tobNonce": m.headToBNonce + 1, + }) + log.Debug("adding new chunk layer") chunk.AssignToBNonce(m.headToBNonce + 1) if !m.replaying { if err := m.ds.SaveCurrentToBNonce(m.headToBNonce+1, m.lastSeenEpoch); err != nil { @@ -187,7 +191,7 @@ func (m *ChunkManager) addChunkLayer(chunk *common.ArcadiaChunk) { timestamp: time.Now(), }) m.chunks = append(m.chunks, chunkLayer) - m.logger.Debugf("setting head tobnonce to %d", chunk.ToBNonce) + log.Debugf("setting head tobnonce to %d", chunk.ToBNonce) m.headToBNonce = chunk.ToBNonce diff --git a/chunk_manager/chunk_manager_test.go b/chunk_manager/chunk_manager_test.go index d0751208..377ad4ed 100644 --- a/chunk_manager/chunk_manager_test.go +++ b/chunk_manager/chunk_manager_test.go @@ -848,11 +848,6 @@ func TestChunkManagerCanInsertPlaceHolder(t *testing.T) { } seqCli := mseq.NewMockBaseSeqClient(t) - // in case new incoming tob trigger submission of previous layer - mockTx := new(chain.Transaction) - txChan := make(chan *chain.Transaction) - seqCli.EXPECT().GenerateTransaction(mock.Anything, mock.Anything).Return(mockTx, nil) - seqCli.EXPECT().TxChan().Return(txChan).Maybe() epochDuration := 12 * time.Second // there's already one initial chunk layer in it diff --git a/services/api/service_test.go b/services/api/service_test.go index d00ec5ee..e6e9d59b 100644 --- a/services/api/service_test.go +++ b/services/api/service_test.go @@ -4207,9 +4207,15 @@ func TestGetPayload(t *testing.T) { require.Equal(t, numOfTxsPerChunkOrBundle2, len(robTxs)) }) - t.Run("test one ToB and two RoBs, where the second RoB weren't preconf'd, but we still can deliver the payload", func(t *testing.T) { + t.Run("test one ToB and two RoBs, where the RoB for Chain2 is not submitted, but we still can deliver the payload", func(t *testing.T) { backend := setupBackend() backend.seqcli.EXPECT().Parser().Return(chainParser).Maybe() + // in case AddChunk trigger submission + mockSEQTx := new(chain.Transaction) + txChan := make(chan *chain.Transaction) + backend.seqcli.EXPECT().GenerateTransaction(mock.Anything, mock.Anything).Return(mockSEQTx, nil) + backend.seqcli.EXPECT().TxChan().Return(txChan).Maybe() + numOfTxsPerChunkOrBundleToB := 8 numOfTxsPerChunkOrBundleRoB := 9 testChunkIDToB := ids.GenerateTestID() @@ -4263,8 +4269,14 @@ func TestGetPayload(t *testing.T) { require.NoError(t, err) require.NoError(t, r1robChunkReq.Chunk.Initialize(chainParser)) + // make sure the head tobnonce equals to testCurrToBNonce manager.SetHighestPreconfedToB(testCurrToBNonce) - manager.SetToBNonce(testCurrToBNonce) + placeHolderToB, err := common.NewPlaceHolderToBChunk(0) + require.NoError(t, err) + err = manager.AddPlaceHolderChunk(placeHolderToB) + require.NoError(t, err) + manager.AddChunk(&tobChunkReq.Chunk) + manager.AddChunk(&r1robChunkReq.Chunk) // preconf tob chunk tobEthTxs, err := tobChunkReq.Chunk.Txs() From dc4143bb62f86bcb5c1712a21f3c9688ed3a573c Mon Sep 17 00:00:00 2001 From: bianyuanop Date: Thu, 20 Feb 2025 16:25:43 -0500 Subject: [PATCH 06/13] lint fix --- services/api/service_test.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/services/api/service_test.go b/services/api/service_test.go index e6e9d59b..2b59581a 100644 --- a/services/api/service_test.go +++ b/services/api/service_test.go @@ -4275,8 +4275,10 @@ func TestGetPayload(t *testing.T) { require.NoError(t, err) err = manager.AddPlaceHolderChunk(placeHolderToB) require.NoError(t, err) - manager.AddChunk(&tobChunkReq.Chunk) - manager.AddChunk(&r1robChunkReq.Chunk) + err = manager.AddChunk(&tobChunkReq.Chunk) + require.NoError(t, err) + err = manager.AddChunk(&r1robChunkReq.Chunk) + require.NoError(t, err) // preconf tob chunk tobEthTxs, err := tobChunkReq.Chunk.Txs() From 0674d83ffb12a2edde488e817d9362ebd92c7a8a Mon Sep 17 00:00:00 2001 From: bianyuanop Date: Fri, 21 Feb 2025 13:55:54 -0500 Subject: [PATCH 07/13] chunk manager initial nonce update --- chunk_manager/chunk_layer.go | 1 + chunk_manager/chunk_manager.go | 181 ++++++---- chunk_manager/chunk_manager_test.go | 510 ++++++++++++++++++---------- cmd/api.go | 1 - common/common.go | 2 + go.mod | 2 +- go.sum | 4 +- services/api/service_test.go | 15 +- 8 files changed, 467 insertions(+), 249 deletions(-) diff --git a/chunk_manager/chunk_layer.go b/chunk_manager/chunk_layer.go index bfc6731f..ad7d1ad9 100644 --- a/chunk_manager/chunk_layer.go +++ b/chunk_manager/chunk_layer.go @@ -235,5 +235,6 @@ func (cl *ChunkLayer) LayerSubmittedToSEQ() bool { func (cl *ChunkLayer) MarkLayerSubmittedToSEQ() { cl.l.Lock() defer cl.l.Unlock() + cl.submittedToSEQ = true } diff --git a/chunk_manager/chunk_manager.go b/chunk_manager/chunk_manager.go index fdddb082..e9ee546d 100644 --- a/chunk_manager/chunk_manager.go +++ b/chunk_manager/chunk_manager.go @@ -32,10 +32,10 @@ func (wrapper *ChunkWrapper) SubmittedToDA() bool { } type ChunkManagerConfig struct { - ExpirationTime time.Duration - GCInterval time.Duration + ExpirationTime time.Duration + GCInterval time.Duration + LayerSubmissionCheckInterval time.Duration // interval to check if all previous layers are submitted or not - HeadToBNonce uint64 // the head ToBNonce StateLowestToBNonce uint64 // the tob nonce of the first chunk layer after replay HighestSettledToBNonce uint64 // the highest tob nonce of a chunk layer that has been submitted to DA @@ -75,32 +75,25 @@ type ChunkManager struct { exitCh chan struct{} } +// NewChunkManager instantiate a chunk manager, either create a brand new chunk manager or replay the chunks from DA/Datastores +// the initial tob nonce is set to 1 or in case when set to zero, the first layer is always settled func NewChunkManager(cfg *ChunkManagerConfig) (*ChunkManager, error) { - if cfg.HeadToBNonce < cfg.StateLowestToBNonce { - return nil, errors.New("provided initial tobNonce is smaller than the state lowest tob nonce") - } if cfg.Datastore == nil { return nil, errors.New("datastore is required") } - // this allows the replayed chunks can be inserted consecutively - initialChunkLayerToBNonce := uint64(0) - if cfg.StateLowestToBNonce != 0 { - initialChunkLayerToBNonce = cfg.StateLowestToBNonce - 1 + if cfg.StateLowestToBNonce > cfg.HighestSettledToBNonce { + return nil, errors.New("provided state tob nonce greater than highest settled tob nonce") } - placeHolderToB, err := common.NewPlaceHolderToBChunk(initialChunkLayerToBNonce) - if err != nil { - return nil, err + + // when cfg.StateLowestToBNonce =0, after pushing the first element to both PQs, we add a place holder layer as the first layer + // the tob nonce of which is headTobNonce+1 + // when cfg.StateLowestToBNonce >0, chunks will be replayed with the base headToBNonce to be cfg.StateLowestToBNonce-1, hence guarantees + // the consecutiveness + headToBNonce := uint64(0) + if cfg.StateLowestToBNonce > 0 { + headToBNonce = cfg.StateLowestToBNonce - 1 } - initialChunkLayer := NewChunkLayer(initialChunkLayerToBNonce, &ChunkWrapper{ - ArcadiaChunk: placeHolderToB, - cert: &common.CertInfo{ - DAType: actions.CelestiaDA, - PlaceHolder: true, - Cert: nil, - }, - timestamp: time.Now(), - }) cm := &ChunkManager{ cfg: cfg, @@ -109,7 +102,7 @@ func NewChunkManager(cfg *ChunkManagerConfig) (*ChunkManager, error) { chunks: []*ChunkLayer{}, txs: make(map[string]ethtypes.Transactions), lowestBlockNumber: make(map[string]uint64), - headToBNonce: initialChunkLayerToBNonce, + headToBNonce: headToBNonce, highestPreconfdToBNonce: cfg.HighestSettledToBNonce, pqPreconfs: common.NewPQueue(), @@ -121,40 +114,55 @@ func NewChunkManager(cfg *ChunkManagerConfig) (*ChunkManager, error) { exitCh: make(chan struct{}), } + // push the settled tob nonce into both PQs + cm.pqSettles.Push(&common.UintItem{ + X: cfg.HighestSettledToBNonce, + }) + cm.pqPreconfs.Push(&common.UintItem{ + X: cfg.HighestSettledToBNonce, + }) + // replay chunks between [stateLowestToBNonce, highestSettledToBNonce], the highestSettledToBNonce should be // queried from SEQ instead of from datastores before instantiate the chunk manager chunks := make([]*common.ArcadiaChunk, 0) - for tobNonce := cfg.StateLowestToBNonce; tobNonce <= cfg.HighestSettledToBNonce; tobNonce++ { - chunksAtToBNonce, err := cm.ds.LoadChunksWithToBNonce(tobNonce) - if err != nil { - return nil, err + if cfg.HighestSettledToBNonce != 0 { + for tobNonce := cfg.StateLowestToBNonce; tobNonce <= cfg.HighestSettledToBNonce; tobNonce++ { + chunksAtToBNonce, err := cm.ds.LoadChunksWithToBNonce(tobNonce) + if err != nil { + return nil, err + } + cm.logger.WithFields(logrus.Fields{ + "numChunks": len(chunksAtToBNonce), + "tobNonce": tobNonce, + }).Debug("num replay chunks loaded") + chunks = append(chunks, chunksAtToBNonce...) } - cm.logger.WithFields(logrus.Fields{ - "numChunks": len(chunksAtToBNonce), - "tobNonce": tobNonce, - }).Debug("num replay chunks loaded") - chunks = append(chunks, chunksAtToBNonce...) } - cm.replaying = true if err := cm.replayChunks(chunks); err != nil { return nil, fmt.Errorf("failed to replay chunks: %w", err) } cm.replaying = false - // after replaying, zero chunks are inserted. This means the stateLowestToBNonce provided is 0 and there's no chunks in db - // this time we have to add a empty chunk as the first chunk layer - if len(cm.chunks) == 0 { - cm.chunks = append(cm.chunks, initialChunkLayer) - if err := cm.ds.SetToBChunk(placeHolderToB.ToBNonce, placeHolderToB.ToB); err != nil { - cm.logger.WithError(err).Error("unable to store place holder tob") - return nil, err - } + // insert an place holder chunk layer after replaying + topLayerToBNonce := cfg.HighestSettledToBNonce + 1 + placeHolderToB, err := common.NewPlaceHolderToBChunk(topLayerToBNonce) + if err != nil { + return nil, err + } + + if err := cm.innerAddPlaceHolderChunk(placeHolderToB); err != nil { + return nil, err } go func() { for { select { + case <-time.After(cm.cfg.LayerSubmissionCheckInterval): + ctx := context.Background() + submitCtx, submitCancel := context.WithTimeout(ctx, common.LayerSubmissionTimeout) + cm.trySubmitChunkLayers(submitCtx) + submitCancel() case <-time.After(cm.cfg.GCInterval): cm.garbageCollect() case <-cm.exitCh: @@ -528,6 +536,11 @@ func (m *ChunkManager) replayChunks(chunks []*common.ArcadiaChunk) error { } } + // mark replayed chunks as submitted to SEQ + for _, layer := range m.chunks { + layer.MarkLayerSubmittedToSEQ() + } + return nil } @@ -572,15 +585,6 @@ func (m *ChunkManager) innerPushPreconf(chunk *common.ArcadiaChunk) { return } - defer func() { - if err := m.ds.SaveHighestSettledToBNonce(m.highestPreconfdToBNonce, m.lastSeenEpoch); err != nil { - m.logger.WithError(err).WithFields(logrus.Fields{ - "tobNonce": m.highestPreconfdToBNonce, - "epoch": m.lastSeenEpoch, - }).Error("unable to save highest settled tob nonce") - } - }() - log := m.logger.WithFields(logrus.Fields{ "incomingChunkToBNonce": chunk.ToBNonce, }) @@ -594,11 +598,9 @@ func (m *ChunkManager) innerPushPreconf(chunk *common.ArcadiaChunk) { } log.Debug("before pushing preconf") - arcadiaItem := common.ArcadiaChunkItem{ - Chunk: chunk, - } - - m.pqPreconfs.Push(&arcadiaItem) + m.pqPreconfs.Push(&common.UintItem{ + X: chunk.ToBNonce, + }) // settle preconf'd ToBs for { @@ -607,20 +609,20 @@ func (m *ChunkManager) innerPushPreconf(chunk *common.ArcadiaChunk) { } // remove consecutive and duplicate - top := m.pqPreconfs.Top().(*common.ArcadiaChunkItem) - if top.Chunk.ToBNonce != m.highestPreconfdToBNonce+1 && top.Chunk.ToBNonce != m.highestPreconfdToBNonce { + top := m.pqPreconfs.Top().(*common.UintItem) + if top.X != m.highestPreconfdToBNonce+1 && top.X != m.highestPreconfdToBNonce { log.WithFields(logrus.Fields{ - "currentToBNonce": top.Chunk.ToBNonce, + "currentToBNonce": top.X, "highest": m.highestPreconfdToBNonce, }).Debug("breaking out as not consecutive") break } // pop the front as consecutive with the highest settled - m.highestPreconfdToBNonce = top.Chunk.ToBNonce + m.highestPreconfdToBNonce = top.X m.pqPreconfs.Pop() } - log.WithField("highestToBSettledAfter", m.highestPreconfdToBNonce).Debug("after pushing preconf") + log.WithField("highestToBPreconfdAfter", m.highestPreconfdToBNonce).Debug("after pushing preconf") } // PushPreconf pushes a pre-confirmed chunk into a PQ @@ -633,6 +635,7 @@ func (m *ChunkManager) PushPreconf(chunk *common.ArcadiaChunk) { m.innerPushPreconf(chunk) } +// TODO: the layer submission should be put into other dedicate struct/process to handle it since resubmission is needed // submitLayerToSEQ assume m.l is acquired and try to submit a layer of chunks to SEQ if // - headToBNonce != layer.tobNonce // - all da certs of chunks in this layer has been issued @@ -640,6 +643,11 @@ func (m *ChunkManager) PushPreconf(chunk *common.ArcadiaChunk) { // since there exists a situation that all certs has been pushed to layer but layer is the head layer hence we don't submit it but // soon it become the second layer and can submit, this time we need to submit the whole layer func (m *ChunkManager) submitLayerToSEQ(ctx context.Context, tobNonce uint64) error { + log := m.logger.WithFields(logrus.Fields{ + "method": "submitLayerToSEQ", + "tobNonce": tobNonce, + }) + layerIdx := slices.IndexFunc(m.chunks, func(cl *ChunkLayer) bool { return cl.tobNonce == tobNonce }) @@ -649,14 +657,14 @@ func (m *ChunkManager) submitLayerToSEQ(ctx context.Context, tobNonce uint64) er layer := m.chunks[layerIdx] if m.headToBNonce == layer.tobNonce { - m.logger.WithFields(logrus.Fields{ + log.WithFields(logrus.Fields{ "layerToBNonce": layer.tobNonce, "headToBNonce": m.headToBNonce, }).Debug("skipping submission since layer tobnonce equals to head") return nil } if layer.LayerSubmittedToSEQ() { - m.logger.WithField("tobNonce", layer.tobNonce).Debug("layer already submitted to SEQ") + log.WithField("tobNonce", layer.tobNonce).Debug("layer already submitted to SEQ") return nil } // check if all chunks in this layer is submitted to DA, then submit all the chunk certs to SEQ @@ -665,6 +673,22 @@ func (m *ChunkManager) submitLayerToSEQ(ctx context.Context, tobNonce uint64) er return nil } + // if tx related err occur, we push back the removed settled tob nonces + var TxErr error + removedSettledToBNonces := make([]uint64, 0) + defer func() { + if TxErr == nil { + return + } + log.WithField("txErr", TxErr).Warn("tx error occured, restoring removed settled tob nonce") + for _, nonce := range removedSettledToBNonces { + m.pqSettles.Push(&common.UintItem{ + X: nonce, + }) + } + m.highestSettledToBNonce = m.pqSettles.Top().(*common.UintItem).X + }() + // push the settled layer tob nonce to pq, check if the highest settled one can be updated m.pqSettles.Push(&common.UintItem{ X: layer.tobNonce, @@ -680,6 +704,9 @@ func (m *ChunkManager) submitLayerToSEQ(ctx context.Context, tobNonce uint64) er } m.highestSettledToBNonce = top.X m.pqSettles.Pop() + + removedSettledToBNonces = append(removedSettledToBNonces, top.X) + log.WithField("highestSettledToBNonce", top.X).Debug("setting highest settled tobnonce to") } acts := make([]chain.Action, 0) @@ -692,6 +719,7 @@ func (m *ChunkManager) submitLayerToSEQ(ctx context.Context, tobNonce uint64) er } chunkID, err := chunk.ID() if err != nil { + TxErr = err return err } @@ -717,6 +745,7 @@ func (m *ChunkManager) submitLayerToSEQ(ctx context.Context, tobNonce uint64) er }) seqTx, err := m.seq.GenerateTransaction(ctx, acts) if err != nil { + TxErr = err return err } go func() { @@ -744,7 +773,10 @@ func (m *ChunkManager) innerPushCert(ctx context.Context, msg *common.ArcadiaToS return err } - return m.submitLayerToSEQ(ctx, layer.tobNonce) + if err := m.submitLayerToSEQ(ctx, layer.tobNonce); err != nil { + m.logger.WithError(err).Error("unable to submit layer to SEQ") + } + return nil } func (m *ChunkManager) PushCert(ctx context.Context, msg *common.ArcadiaToSEQChunkMessage, cert *common.CertInfo) error { @@ -832,3 +864,26 @@ func (m *ChunkManager) garbageCollect() { m.logger.Warn("unable to settle txs after GC") } } + +func (m *ChunkManager) trySubmitChunkLayers(ctx context.Context) { + m.l.Lock() + defer m.l.Unlock() + log := m.logger.WithField("method", "trySubmitChunkLayers") + + // at a time, only try resubmit one layer to prevent holding the lock for too long + for _, chunkLayer := range m.chunks { + if chunkLayer.ToBNonce() == m.headToBNonce { + break + } + if !chunkLayer.LayerSubmittedToSEQ() { + err := m.submitLayerToSEQ(ctx, chunkLayer.tobNonce) + if err != nil { + log.WithFields(logrus.Fields{ + "err": err, + "tobNonce": chunkLayer.ToBNonce(), + }).Error("unable to submit layer to SEQ") + } + break + } + } +} diff --git a/chunk_manager/chunk_manager_test.go b/chunk_manager/chunk_manager_test.go index 377ad4ed..7861aa40 100644 --- a/chunk_manager/chunk_manager_test.go +++ b/chunk_manager/chunk_manager_test.go @@ -2,6 +2,7 @@ package chunkmanager import ( "context" + "errors" "fmt" "math/big" "math/rand" @@ -28,13 +29,14 @@ func TestCanAddChunk(t *testing.T) { logger := common.TestLog logger.Logger.SetLevel(logrus.DebugLevel) t.Run("can add chunks", func(t *testing.T) { - tobNonce := uint64(1) + initialToBNonce := uint64(1) + epoch := uint64(0) ds := mdatastore.NewMockIDatastore(t) - ds.EXPECT().LoadChunksWithToBNonce(uint64(0)).Return(nil, nil) - ds.EXPECT().SaveCurrentToBNonce(tobNonce, uint64(0)).Return(nil) - ds.EXPECT().SetToBChunk(uint64(0), mock.Anything).Return(nil) // for the initial place holder tob + ds.EXPECT().SaveCurrentToBNonce(initialToBNonce, epoch).Return(nil) + ds.EXPECT().SetToBChunk(initialToBNonce, mock.Anything).Return(nil) // for the initial place holder tob seqCli := mseq.NewMockBaseSeqClient(t) + // in case new incoming tob trigger submission of previous layer mockTx := new(chain.Transaction) txChan := make(chan *chain.Transaction) @@ -42,14 +44,15 @@ func TestCanAddChunk(t *testing.T) { seqCli.EXPECT().TxChan().Return(txChan).Maybe() // there's already one initial chunk layer in it cm, err := NewChunkManager(&ChunkManagerConfig{ - ExpirationTime: 100 * time.Second, - GCInterval: 100 * time.Second, - HeadToBNonce: 0, - StateLowestToBNonce: 0, - Datastore: ds, - Logger: logger, - SEQClient: seqCli, - SEQChainParser: &srpc.Parser{}, + ExpirationTime: 100 * time.Second, + GCInterval: 100 * time.Second, + LayerSubmissionCheckInterval: 100 * time.Second, + StateLowestToBNonce: 0, + HighestSettledToBNonce: 0, + Datastore: ds, + Logger: logger, + SEQClient: seqCli, + SEQChainParser: &srpc.Parser{}, }) require.NoError(t, err) @@ -87,10 +90,11 @@ func TestCanAddChunk(t *testing.T) { require.NoError(t, err) // add to chunk manager - tob.AssignToBNonce(tobNonce) - rob.AssignToBNonce(tobNonce) + tob.AssignToBNonce(initialToBNonce + 1) + rob.AssignToBNonce(initialToBNonce + 1) - ds.EXPECT().SetToBChunk(tobNonce, tob.ToB).Return(nil) + ds.EXPECT().SetToBChunk(initialToBNonce+1, tob.ToB).Return(nil) + ds.EXPECT().SaveCurrentToBNonce(initialToBNonce+1, epoch).Return(nil) err = cm.AddChunk(tob) require.NoError(t, err) @@ -117,22 +121,24 @@ func TestCanWithCorrectTxs(t *testing.T) { logger := common.TestLog logger.Logger.SetLevel(logrus.DebugLevel) t.Run("can with correct RoBs in the same layer", func(t *testing.T) { - tobNonce := uint64(0) + initialToBNonce := uint64(1) + epoch := uint64(0) ds := mdatastore.NewMockIDatastore(t) - ds.EXPECT().LoadChunksWithToBNonce(tobNonce).Return(nil, nil) - ds.EXPECT().SetToBChunk(tobNonce, mock.Anything).Return(nil) // place holder tob + ds.EXPECT().SaveCurrentToBNonce(initialToBNonce, epoch).Return(nil) + ds.EXPECT().SetToBChunk(initialToBNonce, mock.Anything).Return(nil) // place holder tob seqCli := mseq.NewMockBaseSeqClient(t) // there's already one initial chunk layer in it cm, err := NewChunkManager(&ChunkManagerConfig{ - ExpirationTime: 100 * time.Second, - GCInterval: 100 * time.Second, - HeadToBNonce: tobNonce, - StateLowestToBNonce: tobNonce, - Datastore: ds, - Logger: logger, - SEQChainParser: &srpc.Parser{}, - SEQClient: seqCli, + ExpirationTime: 100 * time.Second, + GCInterval: 100 * time.Second, + LayerSubmissionCheckInterval: 100 * time.Second, + StateLowestToBNonce: 0, + HighestSettledToBNonce: 0, + Datastore: ds, + Logger: logger, + SEQChainParser: &srpc.Parser{}, + SEQClient: seqCli, }) require.NoError(t, err) @@ -150,14 +156,14 @@ func TestCanWithCorrectTxs(t *testing.T) { require.NoError(t, err) err = rob1.Initialize(&parser) require.NoError(t, err) - rob1.AssignToBNonce(tobNonce) + rob1.AssignToBNonce(initialToBNonce) // rob2 at height 101 rob2, err := common.MakeRandomRoB(testSeqChainID, &parser, robChainID, robHeight+1, numRoBTxs) require.NoError(t, err) err = rob2.Initialize(&parser) require.NoError(t, err) - rob2.AssignToBNonce(tobNonce) + rob2.AssignToBNonce(initialToBNonce) ds.EXPECT().SetRoBChunk(rob1.RoB).Return(nil) ds.EXPECT().SetRoBChunk(rob2.RoB).Return(nil) @@ -172,7 +178,7 @@ func TestCanWithCorrectTxs(t *testing.T) { require.NoError(t, err) err = rob3.Initialize(&parser) require.NoError(t, err) - rob3.AssignToBNonce(tobNonce) + rob3.AssignToBNonce(initialToBNonce) rob3wTxs, err := cm.TxsWithRoB(rob3) require.NoError(t, err) @@ -187,11 +193,11 @@ func TestCanWithCorrectTxs(t *testing.T) { }) t.Run("can with correct ToB in the same layer", func(t *testing.T) { - tobNonce := uint64(1) + initialToBNonce := uint64(1) + epoch := uint64(0) ds := mdatastore.NewMockIDatastore(t) - ds.EXPECT().LoadChunksWithToBNonce(tobNonce).Return(nil, nil) - ds.EXPECT().SaveCurrentToBNonce(tobNonce, uint64(0)).Return(nil) - ds.EXPECT().SetToBChunk(uint64(0), mock.Anything).Return(nil) // place holder tob + ds.EXPECT().SaveCurrentToBNonce(initialToBNonce, epoch).Return(nil) + ds.EXPECT().SetToBChunk(initialToBNonce, mock.Anything).Return(nil) // place holder tob seqCli := mseq.NewMockBaseSeqClient(t) // in case new incoming tob trigger submission of previous layer @@ -202,15 +208,15 @@ func TestCanWithCorrectTxs(t *testing.T) { // there's already one initial chunk layer in it cm, err := NewChunkManager(&ChunkManagerConfig{ - ExpirationTime: 100 * time.Second, - GCInterval: 100 * time.Second, - HeadToBNonce: tobNonce, - StateLowestToBNonce: tobNonce, - HighestSettledToBNonce: tobNonce, - Datastore: ds, - Logger: logger, - SEQChainParser: &srpc.Parser{}, - SEQClient: seqCli, + ExpirationTime: 100 * time.Second, + GCInterval: 100 * time.Second, + LayerSubmissionCheckInterval: 100 * time.Second, + StateLowestToBNonce: 0, + HighestSettledToBNonce: 0, + Datastore: ds, + Logger: logger, + SEQChainParser: &srpc.Parser{}, + SEQClient: seqCli, }) require.NoError(t, err) @@ -248,10 +254,11 @@ func TestCanWithCorrectTxs(t *testing.T) { require.NoError(t, err) // add to chunk manager - tob.AssignToBNonce(tobNonce) - rob.AssignToBNonce(tobNonce) + tob.AssignToBNonce(initialToBNonce + 1) + rob.AssignToBNonce(initialToBNonce + 1) - ds.EXPECT().SetToBChunk(tobNonce, tob.ToB).Return(nil) + ds.EXPECT().SetToBChunk(initialToBNonce+1, tob.ToB).Return(nil) + ds.EXPECT().SaveCurrentToBNonce(initialToBNonce+1, epoch).Return(nil) ds.EXPECT().SetRoBChunk(rob.RoB).Return(nil) err = cm.AddChunk(tob) @@ -264,7 +271,7 @@ func TestCanWithCorrectTxs(t *testing.T) { err = rob2.Initialize(&parser) require.NoError(t, err) - rob2.AssignToBNonce(tobNonce) + rob2.AssignToBNonce(initialToBNonce) // can with RoB rob2wTxs, err := cm.TxsWithRoB(rob2) @@ -284,7 +291,7 @@ func TestCanWithCorrectTxs(t *testing.T) { err = tob1.Initialize(&parser) require.NoError(t, err) - tob1.AssignToBNonce(tobNonce + 1) + tob1.AssignToBNonce(initialToBNonce + 1) valid = tob1.Valid() require.True(t, true, valid) @@ -311,12 +318,12 @@ func TestCanWithCorrectTxs(t *testing.T) { }) t.Run("can with settled txs", func(t *testing.T) { - tobNonce := uint64(1) + initialToBNonce := uint64(1) + tobNonce := uint64(2) + epoch := uint64(0) ds := mdatastore.NewMockIDatastore(t) - ds.EXPECT().LoadChunksWithToBNonce(uint64(0)).Return(nil, nil) - ds.EXPECT().SaveCurrentToBNonce(tobNonce, uint64(0)).Return(nil) - ds.EXPECT().SaveCurrentToBNonce(tobNonce+1, uint64(0)).Return(nil) - ds.EXPECT().SetToBChunk(uint64(0), mock.Anything).Return(nil) // place holder tob + ds.EXPECT().SaveCurrentToBNonce(initialToBNonce, epoch).Return(nil) + ds.EXPECT().SetToBChunk(initialToBNonce, mock.Anything).Return(nil) // place holder tob seqCli := mseq.NewMockBaseSeqClient(t) @@ -328,14 +335,15 @@ func TestCanWithCorrectTxs(t *testing.T) { // there's already one initial chunk layer in it cm, err := NewChunkManager(&ChunkManagerConfig{ - ExpirationTime: 100 * time.Second, - GCInterval: 100 * time.Second, - HeadToBNonce: 0, - StateLowestToBNonce: 0, - Datastore: ds, - Logger: logger, - SEQChainParser: &srpc.Parser{}, - SEQClient: seqCli, + ExpirationTime: 100 * time.Second, + GCInterval: 100 * time.Second, + LayerSubmissionCheckInterval: 100 * time.Second, + StateLowestToBNonce: 0, + HighestSettledToBNonce: 0, + Datastore: ds, + Logger: logger, + SEQChainParser: &srpc.Parser{}, + SEQClient: seqCli, }) require.NoError(t, err) @@ -390,9 +398,11 @@ func TestCanWithCorrectTxs(t *testing.T) { tob1.AssignToBNonce(tobNonce + 1) ds.EXPECT().SetToBChunk(tobNonce, tob.ToB).Return(nil) + ds.EXPECT().SaveCurrentToBNonce(tobNonce, epoch).Return(nil) ds.EXPECT().SetRoBChunk(rob.RoB).Return(nil) ds.EXPECT().SetRoBChunk(rob2.RoB).Return(nil) ds.EXPECT().SetToBChunk(tobNonce+1, tob1.ToB).Return(nil) + ds.EXPECT().SaveCurrentToBNonce(tobNonce+1, epoch).Return(nil) err = cm.AddChunk(tob) require.NoError(t, err) @@ -465,26 +475,27 @@ func TestSettleChunkWillMaintainLowestHeights(t *testing.T) { logger := common.TestLog logger.Logger.SetLevel(logrus.DebugLevel) t.Run("lowest heights should be the same with ToB lowest block number when the previous chunk layer got GCed", func(t *testing.T) { - tobNonce := uint64(0) + initialToBNonce := uint64(1) + epoch := uint64(0) ds := mdatastore.NewMockIDatastore(t) - ds.EXPECT().LoadChunksWithToBNonce(uint64(0)).Return(nil, nil) - ds.EXPECT().SaveCurrentToBNonce(tobNonce+1, uint64(0)).Return(nil) - ds.EXPECT().SaveLowestManagedStateToBNonce(tobNonce+1, uint64(0)).Return(nil) - ds.EXPECT().SetToBChunk(uint64(0), mock.Anything).Return(nil) // place holder tob + ds.EXPECT().SaveCurrentToBNonce(initialToBNonce, epoch).Return(nil) + ds.EXPECT().SaveLowestManagedStateToBNonce(initialToBNonce+1, uint64(0)).Return(nil) + ds.EXPECT().SetToBChunk(initialToBNonce, mock.Anything).Return(nil) // place holder tob seqCli := mseq.NewMockBaseSeqClient(t) ExpirationDuration := 3 * time.Second // there's already one initial chunk layer in it cm, err := NewChunkManager(&ChunkManagerConfig{ - ExpirationTime: ExpirationDuration, - GCInterval: 100 * time.Second, - HeadToBNonce: tobNonce, - StateLowestToBNonce: tobNonce, - Datastore: ds, - Logger: logger, - SEQChainParser: &srpc.Parser{}, - SEQClient: seqCli, + ExpirationTime: ExpirationDuration, + GCInterval: 100 * time.Second, + LayerSubmissionCheckInterval: 100 * time.Second, + StateLowestToBNonce: 0, + HighestSettledToBNonce: 0, + Datastore: ds, + Logger: logger, + SEQChainParser: &srpc.Parser{}, + SEQClient: seqCli, }) require.NoError(t, err) @@ -502,7 +513,7 @@ func TestSettleChunkWillMaintainLowestHeights(t *testing.T) { require.NoError(t, err) err = rob1.Initialize(&parser) require.NoError(t, err) - rob1.AssignToBNonce(tobNonce) + rob1.AssignToBNonce(initialToBNonce) // empty ToB for creating a new chunk layer numChains := 2 @@ -518,17 +529,18 @@ func TestSettleChunkWillMaintainLowestHeights(t *testing.T) { tobTxs, err := tob.Txs() require.NoError(t, err) require.Empty(t, tobTxs) - tob.AssignToBNonce(tobNonce + 1) + tob.AssignToBNonce(initialToBNonce + 1) // rob2 at height 101 rob2, err := common.MakeRandomRoB(testSeqChainID, &parser, robChainID, robHeight+1, numRoBTxs) require.NoError(t, err) err = rob2.Initialize(&parser) require.NoError(t, err) - rob2.AssignToBNonce(tobNonce) + rob2.AssignToBNonce(initialToBNonce) ds.EXPECT().SetRoBChunk(rob1.RoB).Return(nil) - ds.EXPECT().SetToBChunk(tobNonce+1, tob.ToB).Return(nil) + ds.EXPECT().SetToBChunk(initialToBNonce+1, tob.ToB).Return(nil) + ds.EXPECT().SaveCurrentToBNonce(initialToBNonce+1, epoch).Return(nil) err = cm.AddChunk(rob1) require.NoError(t, err) @@ -551,24 +563,26 @@ func TestSettleChunkWillMaintainLowestHeights(t *testing.T) { }) t.Run("blocknumber for an empty RoB shouldn't be tracked", func(t *testing.T) { - tobNonce := uint64(0) + initialToBNonce := uint64(1) + epoch := uint64(0) ds := mdatastore.NewMockIDatastore(t) - ds.EXPECT().LoadChunksWithToBNonce(uint64(0)).Return(nil, nil) - ds.EXPECT().SetToBChunk(uint64(0), mock.Anything).Return(nil) // place holder tob + ds.EXPECT().SetToBChunk(initialToBNonce, mock.Anything).Return(nil) // place holder tob + ds.EXPECT().SaveCurrentToBNonce(initialToBNonce, epoch).Return(nil) // place holder tob seqCli := mseq.NewMockBaseSeqClient(t) ExpirationDuration := 3 * time.Second // there's already one initial chunk layer in it cm, err := NewChunkManager(&ChunkManagerConfig{ - ExpirationTime: ExpirationDuration, - GCInterval: 100 * time.Second, - HeadToBNonce: tobNonce, - StateLowestToBNonce: tobNonce, - Datastore: ds, - Logger: logger, - SEQChainParser: &srpc.Parser{}, - SEQClient: seqCli, + ExpirationTime: ExpirationDuration, + GCInterval: 100 * time.Second, + LayerSubmissionCheckInterval: 100 * time.Second, + StateLowestToBNonce: 0, + HighestSettledToBNonce: 0, + Datastore: ds, + Logger: logger, + SEQChainParser: &srpc.Parser{}, + SEQClient: seqCli, }) require.NoError(t, err) @@ -585,7 +599,7 @@ func TestSettleChunkWillMaintainLowestHeights(t *testing.T) { require.NoError(t, err) err = rob1.Initialize(&parser) require.NoError(t, err) - rob1.AssignToBNonce(tobNonce) + rob1.AssignToBNonce(initialToBNonce) ds.EXPECT().SetRoBChunk(rob1.RoB).Return(nil) @@ -600,14 +614,13 @@ func TestSettleChunkWillMaintainLowestHeights(t *testing.T) { } func TestCanSettleTxs(t *testing.T) { - tobNonce := uint64(1) + initialToBNonce := uint64(1) + epoch := uint64(0) common.TestLog.Logger.SetLevel(logrus.DebugLevel) ds := mdatastore.NewMockIDatastore(t) - ds.EXPECT().LoadChunksWithToBNonce(uint64(0)).Return(nil, nil) - ds.EXPECT().SaveCurrentToBNonce(tobNonce, uint64(0)).Return(nil) - ds.EXPECT().SaveCurrentToBNonce(tobNonce+1, uint64(0)).Return(nil) - ds.EXPECT().SetToBChunk(uint64(0), mock.Anything).Return(nil) // place holder tob + ds.EXPECT().SaveCurrentToBNonce(initialToBNonce, uint64(0)).Return(nil) + ds.EXPECT().SetToBChunk(initialToBNonce, mock.Anything).Return(nil) // place holder tob seqCli := mseq.NewMockBaseSeqClient(t) // in case new incoming tob trigger submission of previous layer @@ -618,14 +631,15 @@ func TestCanSettleTxs(t *testing.T) { // there's already one initial chunk layer in it cm, err := NewChunkManager(&ChunkManagerConfig{ - ExpirationTime: 100 * time.Second, - GCInterval: 100 * time.Second, - HeadToBNonce: 0, - StateLowestToBNonce: 0, - Datastore: ds, - SEQChainParser: &srpc.Parser{}, - SEQClient: seqCli, - Logger: common.TestLog, + ExpirationTime: 100 * time.Second, + GCInterval: 100 * time.Second, + LayerSubmissionCheckInterval: 100 * time.Second, + StateLowestToBNonce: 0, + HighestSettledToBNonce: 0, + Datastore: ds, + SEQChainParser: &srpc.Parser{}, + SEQClient: seqCli, + Logger: common.TestLog, }) require.NoError(t, err) @@ -641,7 +655,7 @@ func TestCanSettleTxs(t *testing.T) { // tob1 tob, err := common.MakeRandomToB(testSeqChainID, &parser, numChains, numBundles, numTxsPerBundle) require.NoError(t, err) - tob.ToBNonce = tobNonce + tob.ToBNonce = initialToBNonce err = tob.Initialize(&parser) require.NoError(t, err) @@ -652,20 +666,22 @@ func TestCanSettleTxs(t *testing.T) { require.NoError(t, err) rob, err := common.MakeRandomRoB(testSeqChainID, &parser, robChainID, tob.ToB.GetBlockNumber()[tobDomains[0]], numRoBTxs) require.NoError(t, err) - rob.ToBNonce = tobNonce + rob.ToBNonce = initialToBNonce err = rob.Initialize(&parser) require.NoError(t, err) // tob2 tob2, err := common.MakeRandomToB(testSeqChainID, &parser, numChains, numBundles, numTxsPerBundle) require.NoError(t, err) - tob2.ToBNonce = tobNonce + 1 + tob2.ToBNonce = initialToBNonce + 1 err = tob2.Initialize(&parser) require.NoError(t, err) - ds.EXPECT().SetToBChunk(tobNonce, tob.ToB).Return(nil) + ds.EXPECT().SetToBChunk(initialToBNonce+1, tob.ToB).Return(nil) + ds.EXPECT().SaveCurrentToBNonce(initialToBNonce+1, epoch).Return(nil) ds.EXPECT().SetRoBChunk(rob.RoB).Return(nil) - ds.EXPECT().SetToBChunk(tobNonce+1, tob2.ToB).Return(nil) + ds.EXPECT().SetToBChunk(initialToBNonce+2, tob2.ToB).Return(nil) + ds.EXPECT().SaveCurrentToBNonce(initialToBNonce+2, epoch).Return(nil) err = cm.AddChunk(tob) require.NoError(t, err) @@ -692,18 +708,16 @@ func TestCanSettleTxs(t *testing.T) { } func TestChunkManagerCanGC(t *testing.T) { - tobNonce := uint64(1) + initialToBNonce := uint64(1) common.TestLog.Logger.SetLevel(logrus.DebugLevel) // setup datastore mock ds := mdatastore.NewMockIDatastore(t) - ds.EXPECT().LoadChunksWithToBNonce(uint64(0)).Return(nil, nil) for nonce := uint64(0); nonce <= 5; nonce++ { ds.EXPECT().SetToBChunk(nonce, mock.Anything).Return(nil).Maybe() ds.EXPECT().SaveCurrentToBNonce(nonce, uint64(0)).Return(nil).Maybe() ds.EXPECT().SaveLowestManagedStateToBNonce(nonce, uint64(0)).Return(nil).Maybe() } - ds.EXPECT().SaveHighestSettledToBNonce(uint64(0), uint64(0)).Return(nil) txChan := make(chan *chain.Transaction) mockTx := new(chain.Transaction) @@ -714,15 +728,15 @@ func TestChunkManagerCanGC(t *testing.T) { epochDuration := 12 * time.Second // there's already one initial chunk layer in it cm, err := NewChunkManager(&ChunkManagerConfig{ - ExpirationTime: epochDuration, // 1 epoch - GCInterval: 1 * time.Second, - HeadToBNonce: 0, - StateLowestToBNonce: 0, - HighestSettledToBNonce: 0, - Datastore: ds, - SEQChainParser: &srpc.Parser{}, - SEQClient: seqCli, - Logger: common.TestLog, + ExpirationTime: epochDuration, // 1 epoch + GCInterval: 1 * time.Second, + LayerSubmissionCheckInterval: 100 * time.Second, + StateLowestToBNonce: 0, + HighestSettledToBNonce: 0, + Datastore: ds, + SEQChainParser: &srpc.Parser{}, + SEQClient: seqCli, + Logger: common.TestLog, }) require.NoError(t, err) @@ -736,20 +750,20 @@ func TestChunkManagerCanGC(t *testing.T) { // tob1 tob, err := common.MakeRandomToB(testSeqChainID, &parser, numChains, numBundles, numTxsPerBundle) require.NoError(t, err) - tob.ToBNonce = tobNonce + tob.AssignToBNonce(initialToBNonce + 1) err = tob.Initialize(&parser) require.NoError(t, err) tob2, err := common.MakeRandomToB(testSeqChainID, &parser, numChains, numBundles, numTxsPerBundle) require.NoError(t, err) - tob2.ToBNonce = tobNonce + 1 + tob2.AssignToBNonce(initialToBNonce + 2) err = tob2.Initialize(&parser) require.NoError(t, err) - ds.EXPECT().SetToBChunk(tobNonce, tob.ToB).Return(nil) - ds.EXPECT().SetToBChunk(tobNonce+1, tob2.ToB).Return(nil) + ds.EXPECT().SetToBChunk(initialToBNonce+1, tob.ToB).Return(nil) + ds.EXPECT().SetToBChunk(initialToBNonce+2, tob2.ToB).Return(nil) // first layer err = cm.AddChunk(tob) @@ -839,7 +853,6 @@ func TestChunkManagerCanInsertPlaceHolder(t *testing.T) { t.Run("test chunk manager can generate new place holder tob", func(t *testing.T) { // setup datastore mock ds := mdatastore.NewMockIDatastore(t) - ds.EXPECT().LoadChunksWithToBNonce(uint64(0)).Return(nil, nil) for nonce := uint64(0); nonce <= 3; nonce++ { ds.EXPECT().SetToBChunk(nonce, mock.Anything).Return(nil).Maybe() // place holder tob ds.EXPECT().SaveCurrentToBNonce(nonce, uint64(0)).Return(nil).Maybe() @@ -847,19 +860,22 @@ func TestChunkManagerCanInsertPlaceHolder(t *testing.T) { ds.EXPECT().SaveHighestSettledToBNonce(nonce, uint64(0)).Return(nil).Maybe() } + initialToBNonce := uint64(1) + seqCli := mseq.NewMockBaseSeqClient(t) epochDuration := 12 * time.Second // there's already one initial chunk layer in it cm, err := NewChunkManager(&ChunkManagerConfig{ - ExpirationTime: epochDuration, // 1 epoch - GCInterval: 1 * time.Second, - HeadToBNonce: 0, - StateLowestToBNonce: 0, - Datastore: ds, - SEQClient: seqCli, - SEQChainParser: &srpc.Parser{}, - Logger: common.TestLog, + ExpirationTime: epochDuration, // 1 epoch + GCInterval: 1 * time.Second, + LayerSubmissionCheckInterval: 100 * time.Second, + StateLowestToBNonce: 0, + HighestSettledToBNonce: 0, + Datastore: ds, + SEQClient: seqCli, + SEQChainParser: &srpc.Parser{}, + Logger: common.TestLog, }) require.NoError(t, err) @@ -873,22 +889,30 @@ func TestChunkManagerCanInsertPlaceHolder(t *testing.T) { chunks := cm.Chunks() require.Equal(t, 1, len(chunks)) - require.Equal(t, uint64(1), cm.ToBNonce()) + require.Equal(t, initialToBNonce+1, cm.ToBNonce()) }) t.Run("test chunk manager can insert place holder rob", func(t *testing.T) { + initialToBNonce := uint64(1) + epoch := uint64(0) + ds := mdatastore.NewMockIDatastore(t) - ds.EXPECT().LoadChunksWithToBNonce(uint64(0)).Return(nil, nil) - ds.EXPECT().SetToBChunk(mock.Anything, mock.Anything).Return(nil).Once() + ds.EXPECT().SetToBChunk(initialToBNonce, mock.Anything).Return(nil).Once() + ds.EXPECT().SaveCurrentToBNonce(initialToBNonce, epoch).Return(nil).Once() ds.EXPECT().SaveHighestSettledToBNonce(0, 0).Return(nil).Maybe() + + seqCli := mseq.NewMockBaseSeqClient(t) + cm, err := NewChunkManager(&ChunkManagerConfig{ - ExpirationTime: 100 * time.Second, // 1 epoch - GCInterval: 100 * time.Second, - HeadToBNonce: 0, - StateLowestToBNonce: 0, - Datastore: ds, - SEQChainParser: &srpc.Parser{}, - Logger: common.TestLog, + ExpirationTime: 100 * time.Second, // 1 epoch + GCInterval: 100 * time.Second, + LayerSubmissionCheckInterval: 100 * time.Second, + StateLowestToBNonce: 0, + HighestSettledToBNonce: 0, + Datastore: ds, + SEQChainParser: &srpc.Parser{}, + SEQClient: seqCli, + Logger: common.TestLog, }) require.NoError(t, err) @@ -898,7 +922,7 @@ func TestChunkManagerCanInsertPlaceHolder(t *testing.T) { require.NoError(t, err) layers := cm.Chunks() require.Equal(t, 1, len(layers)) - require.Equal(t, uint64(0), cm.ToBNonce()) + require.Equal(t, initialToBNonce, cm.ToBNonce()) require.Equal(t, cm.ToBNonce(), rob.ToBNonce) require.Equal(t, 2, len(layers[0].chunks)) for _, chunk := range layers[0].chunks { @@ -915,11 +939,12 @@ func TestChunkManagerReplay(t *testing.T) { testSeqChainID := ids.GenerateTestID() t.Run("test with zero chunks provided and the head tob nonce is 0", func(t *testing.T) { + epoch := uint64(0) + initialToBNonce := uint64(1) // setup datastore mock ds := mdatastore.NewMockIDatastore(t) - ds.EXPECT().LoadChunksWithToBNonce(uint64(0)).Return(nil, nil) - ds.EXPECT().SaveCurrentToBNonce(uint64(1), uint64(0)).Return(nil) - ds.EXPECT().SetToBChunk(uint64(0), mock.Anything).Return(nil) // place holder tob + ds.EXPECT().SaveCurrentToBNonce(initialToBNonce, epoch).Return(nil) + ds.EXPECT().SetToBChunk(initialToBNonce, mock.Anything).Return(nil) // place holder tob seqCli := mseq.NewMockBaseSeqClient(t) // in case new incoming tob trigger submission of previous layer @@ -929,10 +954,10 @@ func TestChunkManagerReplay(t *testing.T) { seqCli.EXPECT().TxChan().Return(txChan).Maybe() cm, err := NewChunkManager(&ChunkManagerConfig{ - ExpirationTime: 100 * time.Second, - GCInterval: 100 * time.Second, + ExpirationTime: 100 * time.Second, + GCInterval: 100 * time.Second, + LayerSubmissionCheckInterval: 100 * time.Second, - HeadToBNonce: 0, StateLowestToBNonce: 0, HighestSettledToBNonce: 0, @@ -947,20 +972,22 @@ func TestChunkManagerReplay(t *testing.T) { require.NoError(t, err) tob.AssignToBNonce(cm.headToBNonce + 1) - ds.EXPECT().SetToBChunk(tob.ToBNonce, tob.ToB).Return(nil) // place holder tob + ds.EXPECT().SetToBChunk(tob.ToBNonce, tob.ToB).Return(nil) // place holder tob + ds.EXPECT().SaveCurrentToBNonce(tob.ToBNonce, epoch).Return(nil) // place holder tob err = cm.AddChunk(tob) require.NoError(t, err) }) t.Run("test with some chunks to be replayed", func(t *testing.T) { - headToBNonce := uint64(5) + epoch := uint64(0) highestSettledToBNonce := uint64(5) lowestStateToBNonce := uint64(3) + initialToBNonce := highestSettledToBNonce + 1 ds := mdatastore.NewMockIDatastore(t) // setup replay chunks in datastore replayChunks := make([]*common.ArcadiaChunk, 0) - for nonce := lowestStateToBNonce; nonce <= headToBNonce; nonce++ { + for nonce := lowestStateToBNonce; nonce <= highestSettledToBNonce; nonce++ { chunks := make([]*common.ArcadiaChunk, 0) tobChunk, err := common.MakeRandomToB(testSeqChainID, &seqParser, 2, 2, 10) @@ -990,14 +1017,16 @@ func TestChunkManagerReplay(t *testing.T) { replayChunks = append(replayChunks, chunks...) } + ds.EXPECT().SaveCurrentToBNonce(initialToBNonce, epoch).Return(nil) + ds.EXPECT().SetToBChunk(initialToBNonce, mock.Anything).Return(nil) seqCli := mseq.NewMockBaseSeqClient(t) cm, err := NewChunkManager(&ChunkManagerConfig{ - ExpirationTime: 100 * time.Second, - GCInterval: 100 * time.Second, + ExpirationTime: 100 * time.Second, + GCInterval: 100 * time.Second, + LayerSubmissionCheckInterval: 100 * time.Second, - HeadToBNonce: headToBNonce, StateLowestToBNonce: lowestStateToBNonce, HighestSettledToBNonce: highestSettledToBNonce, @@ -1007,12 +1036,15 @@ func TestChunkManagerReplay(t *testing.T) { Logger: logger, }) require.NoError(t, err) - // headToBNonce should be the same after replaying - require.Equal(t, headToBNonce, cm.ToBNonce()) - require.Equal(t, headToBNonce-lowestStateToBNonce+1, uint64(len(cm.Chunks()))) + // headTobNonce should equal to highestSettledToBNonce+1 + require.Equal(t, initialToBNonce, cm.ToBNonce()) + require.Equal(t, initialToBNonce-lowestStateToBNonce+1, uint64(len(cm.Chunks()))) restoreChunks := make([]*common.ArcadiaChunk, 0) for _, cl := range cm.Chunks() { + if cl.ToBNonce() == initialToBNonce { + break + } for _, chunk := range cl.chunks { restoreChunks = append(restoreChunks, chunk.ArcadiaChunk) } @@ -1030,7 +1062,7 @@ func TestChunkManagerReplay(t *testing.T) { } func TestChunkManagerManageSettledToB(t *testing.T) { - // headToBNonce := uint64(5) + epoch := uint64(0) lowestStateToBNonce := uint64(3) highestSettledToBNonce := uint64(8) logger := common.TestLog @@ -1039,8 +1071,19 @@ func TestChunkManagerManageSettledToB(t *testing.T) { seqParser := srpc.Parser{} testSeqChainID := ids.GenerateTestID() + setupSEQCli := func() *mseq.MockBaseSeqClient { + seqCli := mseq.NewMockBaseSeqClient(t) + seqCli.EXPECT().CurrentEpoch().Return(epoch).Maybe() + return seqCli + } // setup chunks in datastore that are needed for restoring setupDatastore := func(ds *mdatastore.MockIDatastore, stateNonce, settledNonce uint64) { + // for the initial layer + ds.EXPECT().SetToBChunk(settledNonce+1, mock.Anything).Return(nil) + ds.EXPECT().SaveCurrentToBNonce(settledNonce+1, epoch).Return(nil) + if stateNonce == 0 && settledNonce == 0 { + return + } for nonce := stateNonce; nonce <= settledNonce; nonce++ { chunks := make([]*common.ArcadiaChunk, 0) @@ -1074,14 +1117,15 @@ func TestChunkManagerManageSettledToB(t *testing.T) { t.Run("can push cert of a chunk in a layer", func(t *testing.T) { // mocks ds := mdatastore.NewMockIDatastore(t) - seqCli := mseq.NewMockBaseSeqClient(t) + seqCli := setupSEQCli() setupDatastore(ds, lowestStateToBNonce, highestSettledToBNonce) + initialToBNonce := highestSettledToBNonce + 1 cm, err := NewChunkManager(&ChunkManagerConfig{ - ExpirationTime: 100 * time.Second, - GCInterval: 100 * time.Second, + ExpirationTime: 100 * time.Second, + GCInterval: 100 * time.Second, + LayerSubmissionCheckInterval: 100 * time.Second, - HeadToBNonce: highestSettledToBNonce, StateLowestToBNonce: lowestStateToBNonce, HighestSettledToBNonce: highestSettledToBNonce, @@ -1095,8 +1139,25 @@ func TestChunkManagerManageSettledToB(t *testing.T) { // add some chunks into the first layer tob, err := common.MakeRandomToB(testSeqChainID, &seqParser, 2, 2, 1) require.NoError(t, err) - ds.EXPECT().SetToBChunk(highestSettledToBNonce+1, tob.ToB).Return(nil) - ds.EXPECT().SaveCurrentToBNonce(highestSettledToBNonce+1, uint64(0)).Return(nil) + ds.EXPECT().SetToBChunk(initialToBNonce+1, tob.ToB).Return(nil) + ds.EXPECT().SaveCurrentToBNonce(initialToBNonce+1, epoch).Return(nil) + matchTx := func(acts []chain.Action) bool { + layerCorrect := false + for _, act := range acts { + actSetToBNonce, ok := act.(*actions.SetSettledToBNonce) + if !ok { + continue + } + if actSetToBNonce.ToBNonce == initialToBNonce { + layerCorrect = true + } + } + return layerCorrect + } + mockTx := new(chain.Transaction) + seqCli.EXPECT().GenerateTransaction(mock.Anything, mock.MatchedBy(matchTx)).Return(mockTx, nil) + seqCli.EXPECT().TxChan().Return(make(chan *chain.Transaction)).Maybe() + err = cm.AddChunk(tob) require.NoError(t, err) @@ -1139,14 +1200,15 @@ func TestChunkManagerManageSettledToB(t *testing.T) { t.Run("a layer of chunks will be pushed to SEQ if it's not the first layer and the chunks of which has been submitted to DA", func(t *testing.T) { // mocks ds := mdatastore.NewMockIDatastore(t) - seqCli := mseq.NewMockBaseSeqClient(t) + seqCli := setupSEQCli() setupDatastore(ds, lowestStateToBNonce, highestSettledToBNonce) + initialToBNonce := highestSettledToBNonce + 1 cm, err := NewChunkManager(&ChunkManagerConfig{ - ExpirationTime: 100 * time.Second, - GCInterval: 100 * time.Second, + ExpirationTime: 100 * time.Second, + GCInterval: 100 * time.Second, + LayerSubmissionCheckInterval: 100 * time.Second, - HeadToBNonce: highestSettledToBNonce, StateLowestToBNonce: lowestStateToBNonce, HighestSettledToBNonce: highestSettledToBNonce, @@ -1160,8 +1222,24 @@ func TestChunkManagerManageSettledToB(t *testing.T) { // layer 1: add some chunks into the first layer tob, err := common.MakeRandomToB(testSeqChainID, &seqParser, 2, 2, 1) require.NoError(t, err) - ds.EXPECT().SetToBChunk(highestSettledToBNonce+1, tob.ToB).Return(nil) - ds.EXPECT().SaveCurrentToBNonce(highestSettledToBNonce+1, uint64(0)).Return(nil) + ds.EXPECT().SetToBChunk(initialToBNonce+1, tob.ToB).Return(nil) + ds.EXPECT().SaveCurrentToBNonce(initialToBNonce+1, epoch).Return(nil) + matchTx := func(acts []chain.Action) bool { + layerCorrect := false + for _, act := range acts { + actSetToBNonce, ok := act.(*actions.SetSettledToBNonce) + if !ok { + continue + } + if actSetToBNonce.ToBNonce == initialToBNonce { + layerCorrect = true + } + } + return layerCorrect + } + mockTx := new(chain.Transaction) + seqCli.EXPECT().GenerateTransaction(mock.Anything, mock.MatchedBy(matchTx)).Return(mockTx, nil) + seqCli.EXPECT().TxChan().Return(make(chan *chain.Transaction)).Maybe() err = cm.AddChunk(tob) require.NoError(t, err) @@ -1179,8 +1257,8 @@ func TestChunkManagerManageSettledToB(t *testing.T) { // layer 2 tob2, err := common.MakeRandomToB(testSeqChainID, &seqParser, 2, 2, 1) require.NoError(t, err) - ds.EXPECT().SetToBChunk(highestSettledToBNonce+2, tob2.ToB).Return(nil) - ds.EXPECT().SaveCurrentToBNonce(highestSettledToBNonce+2, uint64(0)).Return(nil) + ds.EXPECT().SetToBChunk(initialToBNonce+2, tob2.ToB).Return(nil) + ds.EXPECT().SaveCurrentToBNonce(initialToBNonce+2, epoch).Return(nil) err = cm.AddChunk(tob2) require.NoError(t, err) @@ -1238,7 +1316,6 @@ func TestChunkManagerManageSettledToB(t *testing.T) { return true } - mockTx := new(chain.Transaction) txChan := make(chan *chain.Transaction) seqCli.EXPECT().GenerateTransaction(mock.Anything, mock.MatchedBy(matchActs)).Return(mockTx, nil) seqCli.EXPECT().TxChan().Return(txChan).Maybe() @@ -1252,6 +1329,89 @@ func TestChunkManagerManageSettledToB(t *testing.T) { require.NotNil(t, layer) require.Len(t, layer.chunks, numRoBs+1) require.True(t, layer.submittedToSEQ) - require.Equal(t, cm.HighestSettledToB(), highestSettledToBNonce+1) + require.Equal(t, cm.HighestSettledToB(), initialToBNonce+1) + }) + + t.Run("settled tobnonce pq will be restored if layer submission failed", func(t *testing.T) { + // mocks + ds := mdatastore.NewMockIDatastore(t) + seqCli := setupSEQCli() + resubmissionCheckInterval := 3 * time.Second + + initialToBNonce := uint64(1) + setupDatastore(ds, 0, 0) + + cm, err := NewChunkManager(&ChunkManagerConfig{ + ExpirationTime: 100 * time.Second, + GCInterval: 100 * time.Second, + LayerSubmissionCheckInterval: resubmissionCheckInterval, + + StateLowestToBNonce: 0, + HighestSettledToBNonce: 0, + + Datastore: ds, + SEQChainParser: &seqParser, + SEQClient: seqCli, + Logger: logger, + }) + require.NoError(t, err) + require.Equal(t, initialToBNonce, cm.ToBNonce()) + + // push one rob to the initial layer + rob, err := common.MakeRandomRoB(testSeqChainID, &seqParser, big.NewInt(45200), 100, 10) + require.NoError(t, err) + robChunkID, err := rob.ID() + require.NoError(t, err) + + ds.EXPECT().SetRoBChunk(rob.RoB).Return(nil) + + err = cm.AddChunk(rob) + require.NoError(t, err) + + // push one tob as the next layer + tob, err := common.MakeRandomToB(testSeqChainID, &seqParser, 2, 10, 10) + require.NoError(t, err) + ds.EXPECT().SetToBChunk(initialToBNonce+1, tob.ToB).Return(nil) + ds.EXPECT().SaveCurrentToBNonce(initialToBNonce+1, uint64(0)).Return(nil) + + err = cm.AddChunk(tob) + require.NoError(t, err) + + require.Equal(t, initialToBNonce+1, cm.ToBNonce()) + + // setup mock + mockErr := errors.New("failed to generate tx") + seqCli.EXPECT().GenerateTransaction(mock.Anything, mock.Anything).Return(nil, mockErr).Once() + + settledToBNoncePrev := cm.HighestSettledToB() + + // push the rob cert + err = cm.PushCert(context.TODO(), &common.ArcadiaToSEQChunkMessage{ + ChunkID: robChunkID, + Chunk: rob, + }, &common.CertInfo{ + DAType: actions.CelestiaDA, + Cert: []byte("somecert"), + PlaceHolder: false, + }) + require.NoError(t, err) + + settledToBNonceAfterSubmissionFail := cm.HighestSettledToB() + require.Equal(t, settledToBNoncePrev, settledToBNonceAfterSubmissionFail) + + // setup mock for successfull submission + mockTx := new(chain.Transaction) + txChan := make(chan *chain.Transaction) + seqCli.EXPECT().GenerateTransaction(mock.Anything, mock.Anything).Return(mockTx, nil).Once() + seqCli.EXPECT().TxChan().Return(txChan).Return(txChan).Maybe() + + time.Sleep(resubmissionCheckInterval + 1*time.Second) + require.Equal(t, uint64(1), initialToBNonce) + layer0 := cm.chunks[0] + require.Equal(t, 2, len(layer0.chunks)) + require.Equal(t, true, layer0.LayerSubmittedToSEQ()) + for _, chunk := range layer0.chunks { + require.True(t, chunk.SubmittedToDA()) + } }) } diff --git a/cmd/api.go b/cmd/api.go index e1760d5a..02346794 100644 --- a/cmd/api.go +++ b/cmd/api.go @@ -227,7 +227,6 @@ var apiCmd = &cobra.Command{ cmConfig := &chunkmanager.ChunkManagerConfig{ ExpirationTime: 2 * common.DurationPerSlot * time.Duration(common.SlotsPerEpoch), GCInterval: time.Second, - HeadToBNonce: highestSettledToBNonce, StateLowestToBNonce: *lowestStateToBNonce, // TODO: rename the data store methods of highestPrecondToBNonce to highestSettledToBNonce // as saving highestPreconfd tobnonce is no use, we won't recover to the state after highestSettledToBNonce diff --git a/common/common.go b/common/common.go index d5dc00d5..d05866d0 100644 --- a/common/common.go +++ b/common/common.go @@ -12,6 +12,8 @@ var ( SecondsPerSlot = uint64(cli.GetEnvInt("SEC_PER_SLOT", 2)) DurationPerSlot = time.Duration(SecondsPerSlot) * time.Second SlotsPerEpoch = uint64(cli.GetEnvInt("SLOTS_PER_EPOCH", 6)) + + LayerSubmissionTimeout = 30 * time.Second ) var ( diff --git a/go.mod b/go.mod index a42c3ebe..60cd13e8 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.22.5 require ( github.com/AnomalyFi/flashbotsrpc v0.5.3 github.com/AnomalyFi/hypersdk v0.9.7-arcadia.22 - github.com/AnomalyFi/nodekit-seq v0.9.27-rpc.4 + github.com/AnomalyFi/nodekit-seq v0.9.27-rpc.6 github.com/NYTimes/gziphandler v1.1.1 github.com/alicebob/miniredis/v2 v2.31.0 github.com/attestantio/go-builder-client v0.4.3-0.20240124194555-d44db06f45fa diff --git a/go.sum b/go.sum index e920a26d..8a53a8a7 100644 --- a/go.sum +++ b/go.sum @@ -5,8 +5,8 @@ github.com/AnomalyFi/flashbotsrpc v0.5.3 h1:1jQPcL0wU97hni4GHpSIj4AJcRM+72wzEFnC github.com/AnomalyFi/flashbotsrpc v0.5.3/go.mod h1:r5A0vSSYnv2G3FHcaa30uCjfBScveUCQHm1cmL8ATNA= github.com/AnomalyFi/hypersdk v0.9.7-arcadia.22 h1:8C7R6sy6oZZratzS7oouvoHNpV/Vjok1NLGPEVp56Sg= github.com/AnomalyFi/hypersdk v0.9.7-arcadia.22/go.mod h1:0Vj2PdwSFN7pat4Sno39IfmtOiv/gO9mxZXyRKnoKtI= -github.com/AnomalyFi/nodekit-seq v0.9.27-rpc.4 h1:iizFy5rwKSwnOoO82gu5rUA2hgTuKPnwYyamUruvtzs= -github.com/AnomalyFi/nodekit-seq v0.9.27-rpc.4/go.mod h1:yAd5rfhRgBMf/3hUBH+fgIwxR5s732GruDGU3Ms1QDk= +github.com/AnomalyFi/nodekit-seq v0.9.27-rpc.6 h1:dHpKlYXxh1WHxnKFbJMy14exzVyNW0OJq+V4xfwBLzY= +github.com/AnomalyFi/nodekit-seq v0.9.27-rpc.6/go.mod h1:yAd5rfhRgBMf/3hUBH+fgIwxR5s732GruDGU3Ms1QDk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/DataDog/zstd v1.5.2 h1:vUG4lAyuPCXO0TLbXvPv7EB7cNK1QV/luu55UHLrrn8= github.com/DataDog/zstd v1.5.2/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= diff --git a/services/api/service_test.go b/services/api/service_test.go index 2b59581a..3c246e51 100644 --- a/services/api/service_test.go +++ b/services/api/service_test.go @@ -131,13 +131,14 @@ func newTestbackendWithCustomDatastore(t *testing.T, redisCache *datastore.Redis blockSim := msim.NewMockIBlockSimRateLimiter(t) config := chunkmanager.ChunkManagerConfig{ - ExpirationTime: 5 * time.Minute, - GCInterval: 10 * time.Second, - HeadToBNonce: 0, - Datastore: ds, - SEQClient: seqClient, - SEQChainParser: seqClient.Parser(), - Logger: common.TestLog, + ExpirationTime: 5 * time.Minute, + GCInterval: 10 * time.Second, + HighestSettledToBNonce: 0, + StateLowestToBNonce: 0, + Datastore: ds, + SEQClient: seqClient, + SEQChainParser: seqClient.Parser(), + Logger: common.TestLog, } cm, err := chunkmanager.NewChunkManager(&config) From af477274c0daece27000498e941e4d94810b5682 Mon Sep 17 00:00:00 2001 From: bianyuanop Date: Fri, 21 Feb 2025 15:00:41 -0500 Subject: [PATCH 08/13] fix reload --- chunk_manager/chunk_layer.go | 7 + services/api/service_test.go | 282 +++++++++++------------------------ 2 files changed, 92 insertions(+), 197 deletions(-) diff --git a/chunk_manager/chunk_layer.go b/chunk_manager/chunk_layer.go index ad7d1ad9..c5cca12c 100644 --- a/chunk_manager/chunk_layer.go +++ b/chunk_manager/chunk_layer.go @@ -238,3 +238,10 @@ func (cl *ChunkLayer) MarkLayerSubmittedToSEQ() { cl.submittedToSEQ = true } + +func (cl *ChunkLayer) Len() int { + cl.l.RLock() + defer cl.l.RUnlock() + + return len(cl.chunks) +} diff --git a/services/api/service_test.go b/services/api/service_test.go index 3c246e51..65050fd6 100644 --- a/services/api/service_test.go +++ b/services/api/service_test.go @@ -113,7 +113,7 @@ func newTestBackendWithFlags(t *testing.T, disableRedis bool) *testBackend { return newTestbackendWithCustomDatastore(t, redisCache, db, disableRedis) } -func newTestbackendWithCustomDatastore(t *testing.T, redisCache *datastore.RedisCache, db database.IDatabaseService, disableRedis bool) *testBackend { +func newTestbackendWithCustomSEQCliNDatastore(t *testing.T, seqClient *mseq.MockBaseSeqClient, redisCache *datastore.RedisCache, db database.IDatabaseService, disableRedis bool) *testBackend { logger := common.TestLog logger.Logger.SetLevel(logrus.DebugLevel) @@ -125,16 +125,27 @@ func newTestbackendWithCustomDatastore(t *testing.T, redisCache *datastore.Redis managerSk, err := bls.SecretKeyFromBytes(managerSkBytes) require.NoError(t, err) - seqClient := mseq.NewMockBaseSeqClient(t) + blockSim := msim.NewMockIBlockSimRateLimiter(t) + seqClient.EXPECT().SetOnNewBlockHandler(mock.Anything).Return().Maybe() seqClient.EXPECT().Parser().Return(&srpc.Parser{}).Maybe() - blockSim := msim.NewMockIBlockSimRateLimiter(t) + + highestSettledToBNonce, err := seqClient.GetHighestSettledToBNonce(context.TODO()) + require.NoError(t, err) + var lowestToBNonce uint64 + lowestToBNonceRef, err := ds.LoadLowestManagedStateToBNonce() + require.NoError(t, err) + if lowestToBNonceRef != nil { + lowestToBNonce = *lowestToBNonceRef + } + + t.Logf("initializing chunk manager with lowestStateToBNonce: %d settledToBNonce: %d", lowestToBNonce, highestSettledToBNonce) config := chunkmanager.ChunkManagerConfig{ ExpirationTime: 5 * time.Minute, GCInterval: 10 * time.Second, - HighestSettledToBNonce: 0, - StateLowestToBNonce: 0, + HighestSettledToBNonce: highestSettledToBNonce, + StateLowestToBNonce: lowestToBNonce, Datastore: ds, SEQClient: seqClient, SEQChainParser: seqClient.Parser(), @@ -186,6 +197,22 @@ func newTestbackendWithCustomDatastore(t *testing.T, redisCache *datastore.Redis return &backend } +func newTestbackendWithCustomDatastore(t *testing.T, redisCache *datastore.RedisCache, db database.IDatabaseService, disableRedis bool) *testBackend { + seqClient := mseq.NewMockBaseSeqClient(t) + seqClient.EXPECT().SetOnNewBlockHandler(mock.Anything).Return().Maybe() + seqClient.EXPECT().Parser().Return(&srpc.Parser{}).Maybe() + seqClient.EXPECT().GetHighestSettledToBNonce(mock.Anything).Return(uint64(0), nil).Once() + + err := redisCache.SetLowestManagedStateToBNonce(0) + require.NoError(t, err) + err = db.SetAndUpdateLowestManagedStateToBNonce(&common.ToBNoncesInfoDB{ + ToBNonce: 0, + }) + require.NoError(t, err) + + return newTestbackendWithCustomSEQCliNDatastore(t, seqClient, redisCache, db, false) +} + func newDatastores(t *testing.T) (*datastore.RedisCache, database.IDatabaseService) { redisClient, err := miniredis.Run() require.NoError(t, err) @@ -3840,7 +3867,7 @@ func TestGetPayload(t *testing.T) { require.NoError(t, err) testSeqChainID := ids.GenerateTestID() builderPkBytes := testBuilderPublicKey.Bytes() - testCurrToBNonce := uint64(2) + testCurrToBNonce := uint64(3) testPrevToBNonce := uint64(0) // Helper for processing block requests to the backend. Returns the status code of the request. @@ -6413,7 +6440,7 @@ func TestToBNonceState(t *testing.T) { redis := backend.redis redis.SetSizeTracker(backend.arcadia.sizeTracker) - require.Equal(t, backend.arcadia.chunkManager.ToBNonce(), uint64(0)) + require.Equal(t, uint64(1), backend.arcadia.chunkManager.ToBNonce()) backend.simulator.EXPECT().GetBlockNumber([]string{originChainIDStr, remoteChainIDStr}).Return(blockNumbers, nil) backend.SetupRegisteredRollups(epoch, originChainID) @@ -6499,7 +6526,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(2), arcadia2.chunkManager.ToBNonce()) } func TestConcurrentSubmitReqNGetPayload(t *testing.T) { @@ -6924,52 +6951,61 @@ func TestConcurrentSubmitReqNGetPayload(t *testing.T) { }) } -func TestReloadAwaitPreconfChunks(t *testing.T) { +func TestReloadLowestStateToSettledChunks(t *testing.T) { + seqChainID := ids.GenerateTestID() + seqParser := &srpc.Parser{} + lowestStateToBNonce := uint64(1) + highestSettledToBNonce := uint64(5) testLog := common.TestLog testLog.Logger.SetLevel(logrus.DebugLevel) + redisCache, db := newDatastores(t) ds, err := datastore.NewDatastore(redisCache, db, testLog) require.NoError(t, err) + err = ds.SaveLowestManagedStateToBNonce(lowestStateToBNonce, uint64(0)) + require.NoError(t, err) - seqChainID := ids.GenerateTestID() - seqParser := &srpc.Parser{} - testBuilderSig := []byte("buildersig") - highestSettledToBNonce := uint64(1) - currToBNonce := uint64(5) - - var firstValidator *hrpc.Validator - var firstSk *bls.SecretKey + seqCli := mseq.NewMockBaseSeqClient(t) + seqCli.EXPECT().GetHighestSettledToBNonce(mock.Anything).Return(highestSettledToBNonce, nil) - numValidators := 10 - validators := make([]*hrpc.Validator, 0) - for i := 0; i < numValidators; i++ { - sk, pk, err := bls.GenerateNewKeypair() + storeChunk := func(nonce uint64, chunk *common.ArcadiaChunk) { + chunkID, err := chunk.ID() require.NoError(t, err) - pkBytes := pk.Bytes() - validator := &hrpc.Validator{ - NodeID: ids.GenerateTestNodeID(), - PublicKey: pkBytes[:], - Weight: uint64(100 * i), - } - validators = append(validators, validator) - if i == 0 { - firstValidator = validator - firstSk = sk + if chunk.IsToB() { + err = ds.SetToBChunk(nonce, chunk.ToB) + require.NoError(t, err) + } else { + err = ds.SetRoBChunk(chunk.RoB) + require.NoError(t, err) } - } + err = ds.SetToBNonceChunks(nonce, chunkID) + require.NoError(t, err) + + buf := bytes.NewBuffer(nil) + _, err = chunk.RemovedBitSet().WriteTo(buf) + require.NoError(t, err) - var backend *testBackend - chunkCollectedWeight := make(map[ids.ID]uint64) - awaitPreconfs := make(map[ids.ID]*common.PreConfInfo, 0) + err = ds.SetArcadiaToSEQMessage(chunkID, &common.ArcadiaToSEQChunkMessage{ + ChunkID: chunkID, + Chunk: chunk, + RemovedBitSet: buf.Bytes(), + }) + require.NoError(t, err) + + t.Logf("storing chunk %s", chunkID.String()) + } // prepare all the awaiting chunk preconf info - for nonce := highestSettledToBNonce + 1; nonce <= currToBNonce; nonce++ { + totalChunks2restore := 0 + for nonce := lowestStateToBNonce; nonce <= highestSettledToBNonce; nonce++ { chunks := make([]*common.ArcadiaChunk, 0) tob, err := common.MakeRandomToB(seqChainID, seqParser, 2, 5, 10) require.NoError(t, err) tob.AssignToBNonce(nonce) + storeChunk(nonce, tob) + chunks = append(chunks, tob) numRoBs := mrand.Intn(3) @@ -6977,174 +7013,26 @@ func TestReloadAwaitPreconfChunks(t *testing.T) { rob, err := common.MakeRandomRoB(seqChainID, seqParser, big.NewInt(int64(45200+i)), nonce+uint64(100+i), 10) require.NoError(t, err) rob.AssignToBNonce(nonce) - chunks = append(chunks, rob) - } - - for _, chunk := range chunks { - chunkID, err := chunk.ID() - require.NoError(t, err) - - chunkCollectedWeight[chunkID] = 0 - preconfInfo := &common.PreConfInfo{ - AcceptedEpoch: 0, - Chunk: chunk, - BuilderSig: testBuilderSig, - } - - // shuffle the validators then pick first [numPreconfsIssued] - mrand.Shuffle(len(validators), func(i, j int) { - validators[i], validators[j] = validators[j], validators[i] - }) - - numPreconfsIssued := mrand.Intn(numValidators) - for j := 0; j < numPreconfsIssued; j++ { - validatorPKStr := hexutil.Encode(validators[j].PublicKey) - weight := validators[j].Weight - preconfInfo.PreconfedValidators.Store(validatorPKStr, weight) + storeChunk(nonce, rob) - // store the weight in a map for later comparison - chunkCollectedWeight[chunkID] += weight - } - awaitPreconfs[chunkID] = preconfInfo + chunks = append(chunks, rob) } + + totalChunks2restore += len(chunks) t.Logf("num chunks for tobNonce: %d, numChunks: %d", nonce, len(chunks)) } - t.Run("test reloading preconf chunks", func(t *testing.T) { - // store all the chunks info in the reloading range in data stores - err = ds.SaveHighestSettledToBNonce(highestSettledToBNonce, 0) - require.NoError(t, err) - err = ds.SaveCurrentToBNonce(currToBNonce, 0) - require.NoError(t, err) - for chunkID, preconfInfo := range awaitPreconfs { - var buf bytes.Buffer - _, err = preconfInfo.Chunk.RemovedBitSet().WriteTo(&buf) - require.NoError(t, err) - err = ds.SetArcadiaToSEQMessage(chunkID, &common.ArcadiaToSEQChunkMessage{ - ChunkID: chunkID, - Epoch: 0, - BuilderSignature: preconfInfo.BuilderSig, - Chunk: preconfInfo.Chunk, - RemovedBitSet: buf.Bytes(), - }) - require.NoError(t, err) + t.Logf("total chunks to restore: %d", totalChunks2restore) - err = ds.SavePreConfInfo(preconfInfo, chunkID) - require.NoError(t, err) + t.Run("chunks between LowestStateToBNonce and HighestSettledToBNonce should be reloaded into chunk manager", func(t *testing.T) { + backend := newTestbackendWithCustomSEQCliNDatastore(t, seqCli, redisCache, db, false) + layers := backend.arcadia.chunkManager.Chunks() + require.Equal(t, int(highestSettledToBNonce-lowestStateToBNonce+2), len(layers)) + totalRestored := 0 + for _, layer := range layers { + totalRestored += layer.Len() } - - // instantiate prepared ds and iterate through the [api.pendingChunksPreConfs] and check if reloaded preconfInfo matches - backend = newTestbackendWithCustomDatastore(t, redisCache, db, false) - numChunksReloaded := 0 - backend.arcadia.pendingChunksPreConfs.Range(func(key, value any) bool { - numChunksReloaded++ - - chunkID := key.(ids.ID) - preconfInfo := value.(*common.PreConfInfo) - expectedPreconfInfo := awaitPreconfs[chunkID] - require.NotNil(t, expectedPreconfInfo) - - expectedChunkID, err := expectedPreconfInfo.Chunk.ID() - require.NoError(t, err) - - require.Equal(t, expectedPreconfInfo.AcceptedEpoch, preconfInfo.AcceptedEpoch) - require.Equal(t, expectedPreconfInfo.BuilderSig, preconfInfo.BuilderSig) - require.Equal(t, expectedChunkID, chunkID) - - totalWeight := uint64(0) - preconfInfo.PreconfedValidators.Range(func(key, value any) bool { - weight := value.(uint64) - totalWeight += weight - return true - }) - - t.Log(chunkID.String()) - expectedWeight, ok := chunkCollectedWeight[chunkID] - require.True(t, ok) - require.Equal(t, expectedWeight, totalWeight) - return true - }) - - numChunksExpected := len(maps.Keys(awaitPreconfs)) - require.Equal(t, numChunksExpected, numChunksReloaded) - }) - - t.Run("reloaded chunks should be sent to validator", func(t *testing.T) { - backend = newTestbackendWithCustomDatastore(t, redisCache, db, false) - require.NotNil(t, backend) // must run all tests - - // Backend should reload the chunks from the ds - numChunksReloaded := 0 - backend.arcadia.pendingChunksPreConfs.Range(func(key, value any) bool { - numChunksReloaded++ - return true - }) - require.True(t, numChunksReloaded > 0) - - path := "/ws/arcadia/v1/validator/subscribe" - seqNetworkID := uint32(1337) - - subscribeArcadia := func(backend *testBackend, node *hrpc.Validator, sk *bls.SecretKey) *websocket.Conn { - pkBytes := node.PublicKey - backend.seqcli.EXPECT().GetValidatorWeight(pkBytes[:]).Return(node.Weight) - - // Start a test server - router := backend.arcadia.getRouter() - testServer := httptest.NewServer(router) - time.Sleep(100 * time.Millisecond) - defer testServer.Close() - - // setting up ws - wsURL := "ws://" + strings.TrimPrefix(testServer.URL, "http://") + path - c, _, err := websocket.DefaultDialer.Dial(wsURL, nil) - require.NoError(t, err) - - // read response from ws - _, randomBytes, err := c.ReadMessage() - require.NoError(t, err) - - var randomBytes2 [32]byte - copy(randomBytes2[:], randomBytes[0:32]) - - // send subscribe validator msg - uwm, err := warp.NewUnsignedMessage(seqNetworkID, seqChainID, randomBytes2[:]) - require.NoError(t, err) - t.Log(uwm) - uwmBytes := uwm.Bytes() - - sig := bls.Sign(sk, uwmBytes) - sigBytes := sig.Bytes() - - subscribeNode := common.SubscribeValidatorSignatureCallback{ - Signature: sigBytes[:], - ValidatorPublicKey: node.PublicKey, - } - - // send msg of node to be subscribed - err = c.WriteJSON(subscribeNode) - require.NoError(t, err) - - return c - } - - backend.seqcli.EXPECT().GetChainID().Return(seqChainID) - backend.seqcli.EXPECT().GetNetworkID().Return(seqNetworkID) - backend.seqcli.EXPECT().CurrentValidators(context.Background()).Return(validators) - - // Here we play the role of the validator and test the received messages from Arcadia. - // First, subscribe to Arcadia. - conn := subscribeArcadia(backend, firstValidator, firstSk) - - // Second, we expect to receive the reloaded preconf chunks request so that we can send preconf as the validator - for i := 0; i < numChunksReloaded; i++ { - var preconfMsg common.ArcadiaToSEQChunkMessage - err = conn.ReadJSON(&preconfMsg) - require.NoError(t, err) - require.NotNil(t, preconfMsg.ChunkID) - t.Log("Recovered preconf chunk " + strconv.Itoa(i)) - } - - time.Sleep(1000 * time.Millisecond) + require.Equal(t, totalChunks2restore+1, totalRestored) }) } From a61f473caa6f904b26cc5e3ca5298c7f5cdd0290 Mon Sep 17 00:00:00 2001 From: bianyuanop Date: Fri, 21 Feb 2025 15:12:49 -0500 Subject: [PATCH 09/13] fix unit tests --- services/api/service_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/services/api/service_test.go b/services/api/service_test.go index 65050fd6..f78ad7ff 100644 --- a/services/api/service_test.go +++ b/services/api/service_test.go @@ -1272,7 +1272,6 @@ func TestSubmitChunkToSEQValidators(t *testing.T) { err := waitForServerReady(backend.arcadia.opts.ListenAddr, 30*time.Second) require.NoError(t, err) - require.NotNil(t, backend.arcadia.srv) wsURL := "ws://" + backend.arcadia.opts.ListenAddr + "/ws" + pathTestWebsocket fmt.Printf("ws url: %s\n", wsURL) From 088220da9ba7447348f9981a34e29a24d5e4a030 Mon Sep 17 00:00:00 2001 From: bianyuanop Date: Mon, 24 Feb 2025 15:09:52 -0500 Subject: [PATCH 10/13] da fixes & tx submission fix --- chunk_manager/chunk_manager.go | 22 ++++--- chunk_manager/chunk_manager_test.go | 39 ++++++------ cmd/api.go | 18 +++--- datalayer/utils.go | 12 ++++ seq/mocks/mock_BaseSeqClient.go | 94 ++++++++++++++--------------- seq/seqclient.go | 32 +++++----- services/api/service_test.go | 28 ++++----- 7 files changed, 132 insertions(+), 113 deletions(-) create mode 100644 datalayer/utils.go diff --git a/chunk_manager/chunk_manager.go b/chunk_manager/chunk_manager.go index e9ee546d..805ba929 100644 --- a/chunk_manager/chunk_manager.go +++ b/chunk_manager/chunk_manager.go @@ -11,6 +11,7 @@ import ( "github.com/AnomalyFi/nodekit-seq/actions" seqtypes "github.com/AnomalyFi/nodekit-seq/types" + "github.com/ava-labs/avalanchego/ids" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/sirupsen/logrus" "golang.org/x/exp/maps" @@ -156,14 +157,20 @@ func NewChunkManager(cfg *ChunkManagerConfig) (*ChunkManager, error) { } go func() { + layerSubmissionTimer := time.NewTicker(cm.cfg.LayerSubmissionCheckInterval) + gcTimer := time.NewTicker(cm.cfg.GCInterval) + + defer layerSubmissionTimer.Stop() + defer gcTimer.Stop() + for { select { - case <-time.After(cm.cfg.LayerSubmissionCheckInterval): + case <-layerSubmissionTimer.C: ctx := context.Background() submitCtx, submitCancel := context.WithTimeout(ctx, common.LayerSubmissionTimeout) cm.trySubmitChunkLayers(submitCtx) submitCancel() - case <-time.After(cm.cfg.GCInterval): + case <-gcTimer.C: cm.garbageCollect() case <-cm.exitCh: cm.stop.Store(true) @@ -709,6 +716,7 @@ func (m *ChunkManager) submitLayerToSEQ(ctx context.Context, tobNonce uint64) er log.WithField("highestSettledToBNonce", top.X).Debug("setting highest settled tobnonce to") } + chunkIDs := make([]ids.ID, 0) acts := make([]chain.Action, 0) for _, chunk := range layer.chunks { chainID := seqtypes.ToBChainID @@ -737,24 +745,20 @@ func (m *ChunkManager) submitLayerToSEQ(ctx context.Context, tobNonce uint64) er acts = append(acts, &actions.DACertificate{ Cert: &certInfo, }) + chunkIDs = append(chunkIDs, chunkID) } // this action will be noop if nonce is lower or equal to the recorded on SEQ acts = append(acts, &actions.SetSettledToBNonce{ ToBNonce: m.highestSettledToBNonce, }) - seqTx, err := m.seq.GenerateTransaction(ctx, acts) - if err != nil { - TxErr = err - return err - } go func() { // put this op in a go routine in case of channel full to hold the l for too long - m.seq.TxChan() <- seqTx + m.seq.ActsChan() <- acts }() m.logger.WithFields(logrus.Fields{ "tobNonce": tobNonce, - "txID": seqTx.ID().String(), + "chunkIDs": chunkIDs, }).Debug("submitting layer certs to SEQ") layer.MarkLayerSubmittedToSEQ() diff --git a/chunk_manager/chunk_manager_test.go b/chunk_manager/chunk_manager_test.go index 7861aa40..9dac93ce 100644 --- a/chunk_manager/chunk_manager_test.go +++ b/chunk_manager/chunk_manager_test.go @@ -39,9 +39,10 @@ func TestCanAddChunk(t *testing.T) { // in case new incoming tob trigger submission of previous layer mockTx := new(chain.Transaction) - txChan := make(chan *chain.Transaction) seqCli.EXPECT().GenerateTransaction(mock.Anything, mock.Anything).Return(mockTx, nil) - seqCli.EXPECT().TxChan().Return(txChan).Maybe() + actsChan := make(chan []chain.Action) + seqCli.EXPECT().ActsChan().Return(actsChan).Maybe() + // there's already one initial chunk layer in it cm, err := NewChunkManager(&ChunkManagerConfig{ ExpirationTime: 100 * time.Second, @@ -202,9 +203,9 @@ func TestCanWithCorrectTxs(t *testing.T) { seqCli := mseq.NewMockBaseSeqClient(t) // in case new incoming tob trigger submission of previous layer mockTx := new(chain.Transaction) - txChan := make(chan *chain.Transaction) seqCli.EXPECT().GenerateTransaction(mock.Anything, mock.Anything).Return(mockTx, nil) - seqCli.EXPECT().TxChan().Return(txChan).Maybe() + actsChan := make(chan []chain.Action) + seqCli.EXPECT().ActsChan().Return(actsChan).Maybe() // there's already one initial chunk layer in it cm, err := NewChunkManager(&ChunkManagerConfig{ @@ -329,9 +330,9 @@ func TestCanWithCorrectTxs(t *testing.T) { // in case new incoming tob trigger submission of previous layer mockTx := new(chain.Transaction) - txChan := make(chan *chain.Transaction) seqCli.EXPECT().GenerateTransaction(mock.Anything, mock.Anything).Return(mockTx, nil) - seqCli.EXPECT().TxChan().Return(txChan).Maybe() + actsChan := make(chan []chain.Action) + seqCli.EXPECT().ActsChan().Return(actsChan).Maybe() // there's already one initial chunk layer in it cm, err := NewChunkManager(&ChunkManagerConfig{ @@ -625,9 +626,9 @@ func TestCanSettleTxs(t *testing.T) { seqCli := mseq.NewMockBaseSeqClient(t) // in case new incoming tob trigger submission of previous layer mockTx := new(chain.Transaction) - txChan := make(chan *chain.Transaction) seqCli.EXPECT().GenerateTransaction(mock.Anything, mock.Anything).Return(mockTx, nil) - seqCli.EXPECT().TxChan().Return(txChan).Maybe() + actsChan := make(chan []chain.Action) + seqCli.EXPECT().ActsChan().Return(actsChan).Maybe() // there's already one initial chunk layer in it cm, err := NewChunkManager(&ChunkManagerConfig{ @@ -719,11 +720,11 @@ func TestChunkManagerCanGC(t *testing.T) { ds.EXPECT().SaveLowestManagedStateToBNonce(nonce, uint64(0)).Return(nil).Maybe() } - txChan := make(chan *chain.Transaction) mockTx := new(chain.Transaction) seqCli := mseq.NewMockBaseSeqClient(t) seqCli.EXPECT().GenerateTransaction(mock.Anything, mock.Anything).Return(mockTx, nil).Maybe() - seqCli.EXPECT().TxChan().Return(txChan).Maybe() + actsChan := make(chan []chain.Action) + seqCli.EXPECT().ActsChan().Return(actsChan).Maybe() epochDuration := 12 * time.Second // there's already one initial chunk layer in it @@ -949,9 +950,9 @@ func TestChunkManagerReplay(t *testing.T) { seqCli := mseq.NewMockBaseSeqClient(t) // in case new incoming tob trigger submission of previous layer mockTx := new(chain.Transaction) - txChan := make(chan *chain.Transaction) seqCli.EXPECT().GenerateTransaction(mock.Anything, mock.Anything).Return(mockTx, nil) - seqCli.EXPECT().TxChan().Return(txChan).Maybe() + actsChan := make(chan []chain.Action) + seqCli.EXPECT().ActsChan().Return(actsChan).Maybe() cm, err := NewChunkManager(&ChunkManagerConfig{ ExpirationTime: 100 * time.Second, @@ -1156,7 +1157,8 @@ func TestChunkManagerManageSettledToB(t *testing.T) { } mockTx := new(chain.Transaction) seqCli.EXPECT().GenerateTransaction(mock.Anything, mock.MatchedBy(matchTx)).Return(mockTx, nil) - seqCli.EXPECT().TxChan().Return(make(chan *chain.Transaction)).Maybe() + actsChan := make(chan []chain.Action) + seqCli.EXPECT().ActsChan().Return(actsChan).Maybe() err = cm.AddChunk(tob) require.NoError(t, err) @@ -1239,7 +1241,8 @@ func TestChunkManagerManageSettledToB(t *testing.T) { } mockTx := new(chain.Transaction) seqCli.EXPECT().GenerateTransaction(mock.Anything, mock.MatchedBy(matchTx)).Return(mockTx, nil) - seqCli.EXPECT().TxChan().Return(make(chan *chain.Transaction)).Maybe() + actsChan := make(chan []chain.Action) + seqCli.EXPECT().ActsChan().Return(actsChan).Maybe() err = cm.AddChunk(tob) require.NoError(t, err) @@ -1316,9 +1319,9 @@ func TestChunkManagerManageSettledToB(t *testing.T) { return true } - txChan := make(chan *chain.Transaction) seqCli.EXPECT().GenerateTransaction(mock.Anything, mock.MatchedBy(matchActs)).Return(mockTx, nil) - seqCli.EXPECT().TxChan().Return(txChan).Maybe() + actsChan = make(chan []chain.Action) + seqCli.EXPECT().ActsChan().Return(actsChan).Maybe() for i := range chunks { err = cm.PushCert(ctx, msgs[i], certs[i]) @@ -1401,9 +1404,9 @@ func TestChunkManagerManageSettledToB(t *testing.T) { // setup mock for successfull submission mockTx := new(chain.Transaction) - txChan := make(chan *chain.Transaction) seqCli.EXPECT().GenerateTransaction(mock.Anything, mock.Anything).Return(mockTx, nil).Once() - seqCli.EXPECT().TxChan().Return(txChan).Return(txChan).Maybe() + actsChan := make(chan []chain.Action) + seqCli.EXPECT().ActsChan().Return(actsChan).Maybe() time.Sleep(resubmissionCheckInterval + 1*time.Second) require.Equal(t, uint64(1), initialToBNonce) diff --git a/cmd/api.go b/cmd/api.go index 02346794..b0d2af27 100644 --- a/cmd/api.go +++ b/cmd/api.go @@ -33,7 +33,7 @@ const ( defaultCelestiaDAUrl = "localhost:9090" defaultCelestiaDANamespace = "nodekit" // namespace of it will be []byte("nodekit") defaultCelestiaDAGasPrice = "0.002" - defaultCelestiaDAAccessToken = "" + defaultCelestiaDAAccessToken = "default-celestia-token" defaultDAPendingChunks = 20 ) @@ -43,11 +43,13 @@ var ( apiDefaultLogTag = os.Getenv("LOG_TAG") apiDefaultSEQURI = common.GetEnv("SEQ_URL", "http://seq.url") + // data availability settings apiCelestiaDAUrl = common.GetEnv("CELESTIA_RPC_URL", defaultCelestiaDAUrl) apiCelestiaDANamespace = common.GetEnv("CELESTIA_NAMESPACE", defaultCelestiaDANamespace) apiCelestiaDAGasPrice = common.GetEnv("CELESTIA_GAS_PRICE", defaultCelestiaDAGasPrice) apiCelestiaDAAccessToken = common.GetEnv("CELESTIA_ACCESS_TOKEN", defaultCelestiaDAAccessToken) apiDAMaxPendingChunks int + apiLayerSubmissionTime time.Duration apiListenAddr string @@ -103,6 +105,7 @@ func init() { apiCmd.Flags().StringVar(&apiCelestiaDAGasPrice, "celestia-da-gas-price", apiCelestiaDAGasPrice, "celestia da gas price") apiCmd.Flags().StringVar(&apiCelestiaDAAccessToken, "celestia-da-access-token", apiCelestiaDAAccessToken, "celestia da access token") apiCmd.Flags().IntVar(&apiDAMaxPendingChunks, "da-max-pending-chunks", defaultDAPendingChunks, "num of pending chunks to be submitted to DA in channel buffer") + apiCmd.Flags().DurationVar(&apiLayerSubmissionTime, "da-layer-submission-time", 6*time.Second, "interval to submit a layer of certs fo SEQ") // test scenarios apiCmd.Flags().BoolVar(&testRedisDown, "test-redis-down", false, "test scenario where redis is down") @@ -225,11 +228,11 @@ var apiCmd = &cobra.Command{ } cmConfig := &chunkmanager.ChunkManagerConfig{ - ExpirationTime: 2 * common.DurationPerSlot * time.Duration(common.SlotsPerEpoch), - GCInterval: time.Second, - StateLowestToBNonce: *lowestStateToBNonce, - // TODO: rename the data store methods of highestPrecondToBNonce to highestSettledToBNonce - // as saving highestPreconfd tobnonce is no use, we won't recover to the state after highestSettledToBNonce + ExpirationTime: 2 * common.DurationPerSlot * time.Duration(common.SlotsPerEpoch), + GCInterval: time.Second, + LayerSubmissionCheckInterval: apiLayerSubmissionTime, + + StateLowestToBNonce: *lowestStateToBNonce, HighestSettledToBNonce: highestSettledToBNonce, SEQChainParser: seqCli.Parser(), SEQClient: seqCli, @@ -248,11 +251,12 @@ var apiCmd = &cobra.Command{ } celestiaConfig := &datalayer.CelestiaConfig{ - NamespaceID: []byte(apiCelestiaDANamespace), + NamespaceID: datalayer.ConstructCelestiaNamespace([]byte(apiCelestiaDANamespace)), GasPrice: celestiaDaGasPrice, RPCAddr: apiCelestiaDAUrl, AccessToken: apiCelestiaDAAccessToken, } + log.WithField("celestiaConfig", celestiaConfig).Debug("celestia config") celestiaDA, err := datalayer.BuildCelestiaDA(celestiaConfig) if err != nil { diff --git a/datalayer/utils.go b/datalayer/utils.go new file mode 100644 index 00000000..c723d9f6 --- /dev/null +++ b/datalayer/utils.go @@ -0,0 +1,12 @@ +package datalayer + +// See: https://github.com/celestiaorg/celestia-app/blob/main/docs/architecture/adr-014-versioned-namespaces.md +// Namespace is constructed by Version | NamespaceID +// where Version occupies 1 byte and NamespaceID occupies 28bytes +func ConstructCelestiaNamespace(namespaceID []byte) []byte { + namespace := make([]byte, 29) + // for version 0, the tail 10 bytes are user specifiable + namespace[0] = 0 + copy(namespace[19:], namespaceID) + return namespace +} diff --git a/seq/mocks/mock_BaseSeqClient.go b/seq/mocks/mock_BaseSeqClient.go index 91c0d1ef..557a04a5 100644 --- a/seq/mocks/mock_BaseSeqClient.go +++ b/seq/mocks/mock_BaseSeqClient.go @@ -30,6 +30,53 @@ func (_m *MockBaseSeqClient) EXPECT() *MockBaseSeqClient_Expecter { return &MockBaseSeqClient_Expecter{mock: &_m.Mock} } +// ActsChan provides a mock function with given fields: +func (_m *MockBaseSeqClient) ActsChan() chan []chain.Action { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for ActsChan") + } + + var r0 chan []chain.Action + if rf, ok := ret.Get(0).(func() chan []chain.Action); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(chan []chain.Action) + } + } + + return r0 +} + +// MockBaseSeqClient_ActsChan_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ActsChan' +type MockBaseSeqClient_ActsChan_Call struct { + *mock.Call +} + +// ActsChan is a helper method to define mock.On call +func (_e *MockBaseSeqClient_Expecter) ActsChan() *MockBaseSeqClient_ActsChan_Call { + return &MockBaseSeqClient_ActsChan_Call{Call: _e.mock.On("ActsChan")} +} + +func (_c *MockBaseSeqClient_ActsChan_Call) Run(run func()) *MockBaseSeqClient_ActsChan_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *MockBaseSeqClient_ActsChan_Call) Return(_a0 chan []chain.Action) *MockBaseSeqClient_ActsChan_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockBaseSeqClient_ActsChan_Call) RunAndReturn(run func() chan []chain.Action) *MockBaseSeqClient_ActsChan_Call { + _c.Call.Return(run) + return _c +} + // CurrentEpoch provides a mock function with given fields: func (_m *MockBaseSeqClient) CurrentEpoch() uint64 { ret := _m.Called() @@ -814,53 +861,6 @@ func (_c *MockBaseSeqClient_SubmitActions_Call) RunAndReturn(run func(context.Co return _c } -// TxChan provides a mock function with given fields: -func (_m *MockBaseSeqClient) TxChan() chan *chain.Transaction { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for TxChan") - } - - var r0 chan *chain.Transaction - if rf, ok := ret.Get(0).(func() chan *chain.Transaction); ok { - r0 = rf() - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(chan *chain.Transaction) - } - } - - return r0 -} - -// MockBaseSeqClient_TxChan_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'TxChan' -type MockBaseSeqClient_TxChan_Call struct { - *mock.Call -} - -// TxChan is a helper method to define mock.On call -func (_e *MockBaseSeqClient_Expecter) TxChan() *MockBaseSeqClient_TxChan_Call { - return &MockBaseSeqClient_TxChan_Call{Call: _e.mock.On("TxChan")} -} - -func (_c *MockBaseSeqClient_TxChan_Call) Run(run func()) *MockBaseSeqClient_TxChan_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *MockBaseSeqClient_TxChan_Call) Return(_a0 chan *chain.Transaction) *MockBaseSeqClient_TxChan_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *MockBaseSeqClient_TxChan_Call) RunAndReturn(run func() chan *chain.Transaction) *MockBaseSeqClient_TxChan_Call { - _c.Call.Return(run) - return _c -} - // NewMockBaseSeqClient creates a new instance of MockBaseSeqClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. func NewMockBaseSeqClient(t interface { diff --git a/seq/seqclient.go b/seq/seqclient.go index 69670cf7..09542468 100644 --- a/seq/seqclient.go +++ b/seq/seqclient.go @@ -49,7 +49,7 @@ type BaseSeqClient interface { GetValidatorWeight(pubkeybytes []byte) uint64 GetBalance(ctx context.Context, address codec.Address) (uint64, error) SetOnNewBlockHandler(handler func(*chain.StatefulBlock, []*chain.Result)) - TxChan() chan *chain.Transaction + ActsChan() chan []chain.Action CurrentEpoch() uint64 @@ -92,7 +92,7 @@ type SeqClient struct { epochMap *ShardedMap[uint64, int] // for txs management - txsQueue chan *chain.Transaction + actsQueue chan []chain.Action totalValidatorWeight uint64 } @@ -149,6 +149,7 @@ func NewSeqClient(config *SeqClientConfig) (*SeqClient, error) { stop: stopSig, BuilderPubKey: nil, epochMap: epochMap, + actsQueue: make(chan []chain.Action, 20), } // keep track of head of SEQ, this is used for calculating `Epoch` in SubmitBlock to Arcadia @@ -223,24 +224,19 @@ func NewSeqClient(config *SeqClientConfig) (*SeqClient, error) { ctx := context.Background() for { select { - case tx := <-client.txsQueue: + case acts := <-client.actsQueue: txCtx, txCancel := context.WithTimeout(ctx, 30*time.Second) - txID, result, err := client.submitAndWaitTx(txCtx, tx) + txID, err := client.SubmitActions(txCtx, acts) txCancel() + log := client.logger.WithFields(logrus.Fields{ + "txID": txID.String(), + }) if err != nil { - client.logger.WithError(err).Warn("unable to submit tx to seq, retrying") - client.txsQueue <- tx - continue - } - if result.Error != nil || !result.Success { - client.logger.WithFields(logrus.Fields{ - "err": string(result.Error), - }).Warn("unable to submit tx to SEQ because of tx execution error") + log.WithError(err).Warn("unable to submit tx to seq, retrying") + client.actsQueue <- acts continue } - client.logger.WithFields(logrus.Fields{ - "txID": txID.String(), - }).Info("tx submitted to SEQ") + log.Info("tx submitted to SEQ") case <-client.stop: return } @@ -251,8 +247,8 @@ func NewSeqClient(config *SeqClientConfig) (*SeqClient, error) { return &client, nil } -func (s *SeqClient) TxChan() chan *chain.Transaction { - return s.txsQueue +func (s *SeqClient) ActsChan() chan []chain.Action { + return s.actsQueue } func (s *SeqClient) Stop() { @@ -361,7 +357,7 @@ func (s *SeqClient) submitAndWaitTx(ctx context.Context, tx *chain.Transaction) s.logger.WithError(err).Debug("unable to send tx") return ids.Empty, nil, err } - s.logger.WithField("txID", tx.ID().String()).Info("seqclient registration submitted") + s.logger.WithField("txID", tx.ID().String()).Info("seqclient tx submitted") var res *chain.Result listenTx: diff --git a/services/api/service_test.go b/services/api/service_test.go index f78ad7ff..07e24cf7 100644 --- a/services/api/service_test.go +++ b/services/api/service_test.go @@ -2547,9 +2547,9 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { backend.seqcli.EXPECT().Parser().Return(chainParser) // adding new tob will create a new layer, which will try submit the previous layer to SEQ mockTx := new(chain.Transaction) - txChan := make(chan *chain.Transaction) backend.seqcli.EXPECT().GenerateTransaction(mock.Anything, mock.Anything).Return(mockTx, nil) - backend.seqcli.EXPECT().TxChan().Return(txChan).Maybe() + actsChan := make(chan []chain.Action) + backend.seqcli.EXPECT().ActsChan().Return(actsChan).Maybe() t.Log("========setup & send TOB============") tobTxs, err := common.CollectTxsFromChunks([]*common.ArcadiaChunk{&tobReq.Chunk}) @@ -2744,9 +2744,9 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { backend.seqcli.EXPECT().Parser().Return(chainParser) // adding new tob will create a new layer, which will try submit the previous layer to SEQ mockTx := new(chain.Transaction) - txChan := make(chan *chain.Transaction) backend.seqcli.EXPECT().GenerateTransaction(mock.Anything, mock.Anything).Return(mockTx, nil) - backend.seqcli.EXPECT().TxChan().Return(txChan).Maybe() + actsChan := make(chan []chain.Action) + backend.seqcli.EXPECT().ActsChan().Return(actsChan).Maybe() t.Log("========setup & send TOB============") tobTxs, err := common.CollectTxsFromChunks([]*common.ArcadiaChunk{&tobReq.Chunk}) @@ -3110,9 +3110,9 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { backend.seqcli.EXPECT().Parser().Return(chainParser) // adding new tob will create a new layer, which will try submit the previous layer to SEQ mockTx := new(chain.Transaction) - txChan := make(chan *chain.Transaction) backend.seqcli.EXPECT().GenerateTransaction(mock.Anything, mock.Anything).Return(mockTx, nil) - backend.seqcli.EXPECT().TxChan().Return(txChan).Maybe() + actsChan := make(chan []chain.Action) + backend.seqcli.EXPECT().ActsChan().Return(actsChan).Maybe() t.Log("========setup & send TOB============") tobTxs, err := common.CollectTxsFromChunks([]*common.ArcadiaChunk{&tobReq.Chunk}) @@ -3318,9 +3318,9 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { backend.seqcli.EXPECT().Parser().Return(chainParser) // adding new tob will create a new layer, which will try submit the previous layer to SEQ mockTx := new(chain.Transaction) - txChan := make(chan *chain.Transaction) backend.seqcli.EXPECT().GenerateTransaction(mock.Anything, mock.Anything).Return(mockTx, nil) - backend.seqcli.EXPECT().TxChan().Return(txChan).Maybe() + actsChan := make(chan []chain.Action) + backend.seqcli.EXPECT().ActsChan().Return(actsChan).Maybe() t.Log("========setup & send TOB============") tobTxs, err := common.CollectTxsFromChunks([]*common.ArcadiaChunk{&tobReq.Chunk}) @@ -3441,9 +3441,9 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { backend.seqcli.EXPECT().Parser().Return(chainParser) // adding new tob will create a new layer, which will try submit the previous layer to SEQ mockTx := new(chain.Transaction) - txChan := make(chan *chain.Transaction) backend.seqcli.EXPECT().GenerateTransaction(mock.Anything, mock.Anything).Return(mockTx, nil) - backend.seqcli.EXPECT().TxChan().Return(txChan).Maybe() + actsChan := make(chan []chain.Action) + backend.seqcli.EXPECT().ActsChan().Return(actsChan).Maybe() t.Log("========setup & send TOB============") tobTxs, err := common.CollectTxsFromChunks([]*common.ArcadiaChunk{&tobReq.Chunk}) @@ -4239,9 +4239,9 @@ func TestGetPayload(t *testing.T) { backend.seqcli.EXPECT().Parser().Return(chainParser).Maybe() // in case AddChunk trigger submission mockSEQTx := new(chain.Transaction) - txChan := make(chan *chain.Transaction) backend.seqcli.EXPECT().GenerateTransaction(mock.Anything, mock.Anything).Return(mockSEQTx, nil) - backend.seqcli.EXPECT().TxChan().Return(txChan).Maybe() + actsChan := make(chan []chain.Action) + backend.seqcli.EXPECT().ActsChan().Return(actsChan).Maybe() numOfTxsPerChunkOrBundleToB := 8 numOfTxsPerChunkOrBundleRoB := 9 @@ -6450,9 +6450,9 @@ func TestToBNonceState(t *testing.T) { backend.seqcli.EXPECT().Parser().Return(chainParser) // adding new tob will create a new layer, which will try submit the previous layer to SEQ mockTx := new(chain.Transaction) - txChan := make(chan *chain.Transaction) backend.seqcli.EXPECT().GenerateTransaction(mock.Anything, mock.Anything).Return(mockTx, nil) - backend.seqcli.EXPECT().TxChan().Return(txChan).Maybe() + actsChan := make(chan []chain.Action) + backend.seqcli.EXPECT().ActsChan().Return(actsChan).Maybe() t.Log("========setup & send TOB============") tobTxs, err := common.CollectTxsFromChunks([]*common.ArcadiaChunk{&tobReq.Chunk}) From de3f75d26f2daebd9000ab4b267ecb15ded116de Mon Sep 17 00:00:00 2001 From: bianyuanop Date: Tue, 25 Feb 2025 10:09:47 -0500 Subject: [PATCH 11/13] logging & da submission lru --- chunk_manager/chunk_layer.go | 6 ++++++ chunk_manager/chunk_manager.go | 21 ++++++++++++++++----- datalayer/da_submitter.go | 17 ++++++++++++++++- services/api/payload.go | 8 ++++---- services/api/service.go | 5 +++-- 5 files changed, 45 insertions(+), 12 deletions(-) diff --git a/chunk_manager/chunk_layer.go b/chunk_manager/chunk_layer.go index c5cca12c..8585a13d 100644 --- a/chunk_manager/chunk_layer.go +++ b/chunk_manager/chunk_layer.go @@ -220,8 +220,14 @@ func (cl *ChunkLayer) ChunksSubmittedToDA() bool { cl.l.RLock() defer cl.l.RUnlock() submitted := true + fmt.Printf("layer-%d da submission info\n", cl.tobNonce) for _, chunk := range cl.chunks { submitted = submitted && chunk.SubmittedToDA() + chunkID, err := chunk.ID() + if err != nil { + panic(err) + } + fmt.Printf("%s:%t", chunkID, chunk.SubmittedToDA()) } return submitted } diff --git a/chunk_manager/chunk_manager.go b/chunk_manager/chunk_manager.go index 805ba929..6f3378a6 100644 --- a/chunk_manager/chunk_manager.go +++ b/chunk_manager/chunk_manager.go @@ -11,7 +11,6 @@ import ( "github.com/AnomalyFi/nodekit-seq/actions" seqtypes "github.com/AnomalyFi/nodekit-seq/types" - "github.com/ava-labs/avalanchego/ids" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/sirupsen/logrus" "golang.org/x/exp/maps" @@ -716,7 +715,11 @@ func (m *ChunkManager) submitLayerToSEQ(ctx context.Context, tobNonce uint64) er log.WithField("highestSettledToBNonce", top.X).Debug("setting highest settled tobnonce to") } - chunkIDs := make([]ids.ID, 0) + chunksInfo := make([]struct { + chainID string + height uint64 + chunkID string + }, 0) acts := make([]chain.Action, 0) for _, chunk := range layer.chunks { chainID := seqtypes.ToBChainID @@ -745,7 +748,15 @@ func (m *ChunkManager) submitLayerToSEQ(ctx context.Context, tobNonce uint64) er acts = append(acts, &actions.DACertificate{ Cert: &certInfo, }) - chunkIDs = append(chunkIDs, chunkID) + chunksInfo = append(chunksInfo, struct { + chainID string + height uint64 + chunkID string + }{ + chainID: chainID, + height: blockNumber, + chunkID: chunkID.String(), + }) } // this action will be noop if nonce is lower or equal to the recorded on SEQ @@ -757,8 +768,8 @@ func (m *ChunkManager) submitLayerToSEQ(ctx context.Context, tobNonce uint64) er m.seq.ActsChan() <- acts }() m.logger.WithFields(logrus.Fields{ - "tobNonce": tobNonce, - "chunkIDs": chunkIDs, + "tobNonce": tobNonce, + "chunkInfo": chunksInfo, }).Debug("submitting layer certs to SEQ") layer.MarkLayerSubmittedToSEQ() diff --git a/datalayer/da_submitter.go b/datalayer/da_submitter.go index c9656a0a..27abdc4c 100644 --- a/datalayer/da_submitter.go +++ b/datalayer/da_submitter.go @@ -8,11 +8,17 @@ import ( "time" "github.com/AnomalyFi/Arcadia/common" + avacache "github.com/ava-labs/avalanchego/cache" + "github.com/ava-labs/avalanchego/ids" "github.com/rollkit/go-da" "github.com/rollkit/go-da/proxy" "github.com/sirupsen/logrus" ) +const ( + DefaultSubmissionLRUSize = 1024 +) + type IDASubmitter interface { ChunksChan() chan *common.ArcadiaToSEQChunkMessage } @@ -57,7 +63,8 @@ type DASubmitter struct { log *logrus.Entry da da.DA - chunkManager IChunkManager + chunkManager IChunkManager + chunkSubmitted *avacache.LRU[ids.ID, struct{}] mocking bool pendingSubmissionChunks chan *common.ArcadiaToSEQChunkMessage @@ -79,6 +86,7 @@ func NewDASubmitter(opts DASubmitterOpts) (*DASubmitter, error) { chunkManager: opts.ChunkManager, config: opts.DaConfig, pendingSubmissionChunks: make(chan *common.ArcadiaToSEQChunkMessage, opts.MaxPendingChunks), + chunkSubmitted: &avacache.LRU[ids.ID, struct{}]{Size: DefaultSubmissionLRUSize}, mocking: opts.Mocking, } @@ -97,7 +105,12 @@ func NewDASubmitter(opts DASubmitterOpts) (*DASubmitter, error) { "chunkID": chunkMsg.ChunkID, "chainID": chainID, "blockNumber": blockNumber, + "tobNonce": chunkMsg.Chunk.ToBNonce, }) + if _, submitted := submitter.chunkSubmitted.Get(chunkMsg.ChunkID); submitted { + log.Debug("chunk already submitted") + continue + } log.Debug("submitting chunk to DA") chunkCtx, chunkCancel := context.WithTimeout(ctx, 20*time.Second) @@ -123,10 +136,12 @@ func NewDASubmitter(opts DASubmitterOpts) (*DASubmitter, error) { DAType: 0, Cert: certRaw, } + log.Debug("pushing cert to chunk manager") if err := submitter.chunkManager.PushCert(chunkCtx, chunkMsg, &certInfo); err != nil { submitter.log.WithError(err).Error("unable to push cert to chunk manager") } chunkCancel() + submitter.chunkSubmitted.Put(chunkMsg.ChunkID, struct{}{}) case <-submitter.stop: submitter.log.Info("receiving stop signal, stopping") return diff --git a/services/api/payload.go b/services/api/payload.go index d630b44d..5918cb82 100644 --- a/services/api/payload.go +++ b/services/api/payload.go @@ -153,10 +153,10 @@ func (api *ArcadiaAPI) buildPayload(headEpoch uint64, rollupRegistration *hactio highestToBNonce := api.chunkManager.HighestPreconfedToB() if highestToBNonce < *tobNonceOfRoB { log.WithFields(logrus.Fields{ - "settledToBNonce": highestToBNonce, - "robToBNonce": *tobNonceOfRoB, - }).Warn("chunks aren't settled yet") - return nil, fmt.Errorf("chunks aren't settled yet, tobNonce(settled): %d, tobNonce(RoB): %d", highestToBNonce, *tobNonceOfRoB) + "preconfdToBNonce": highestToBNonce, + "robToBNonce": *tobNonceOfRoB, + }).Warn("chunks aren't preconf'd yet") + return nil, fmt.Errorf("chunks aren't preconf'd yet, tobNonce(preconfd): %d, tobNonce(RoB): %d", highestToBNonce, *tobNonceOfRoB) } log.WithFields(logrus.Fields{ diff --git a/services/api/service.go b/services/api/service.go index 1a51f4ef..efaa2711 100644 --- a/services/api/service.go +++ b/services/api/service.go @@ -1747,8 +1747,9 @@ func (api *ArcadiaAPI) handleSubmitNewBlockRequest(w http.ResponseWriter, req *h } if headEpoch != blockReq.Epoch { - api.log.WithError(err).Warn("headEpoch does not match block request epoch: " + strconv.FormatUint(headEpoch, 10)) - api.RespondError(w, http.StatusBadRequest, err.Error()) + errMsg := fmt.Sprintf("blockReq epoch: %d does not match headEpoch: %d", blockReq.Epoch, headEpoch) + api.log.Warn(errMsg) + api.RespondError(w, http.StatusBadRequest, errMsg) return } From 5f6716d8fc6a582021cc4814cc0a690089fd6ce0 Mon Sep 17 00:00:00 2001 From: bianyuanop Date: Tue, 25 Feb 2025 10:31:20 -0500 Subject: [PATCH 12/13] lint & unit tests fix --- chunk_manager/chunk_manager_test.go | 159 ---------------------------- chunk_manager/consts.go | 4 +- datalayer/utils.go | 1 + services/api/service_test.go | 31 ++---- 4 files changed, 13 insertions(+), 182 deletions(-) diff --git a/chunk_manager/chunk_manager_test.go b/chunk_manager/chunk_manager_test.go index 9dac93ce..0e9dfd0e 100644 --- a/chunk_manager/chunk_manager_test.go +++ b/chunk_manager/chunk_manager_test.go @@ -2,7 +2,6 @@ package chunkmanager import ( "context" - "errors" "fmt" "math/big" "math/rand" @@ -11,7 +10,6 @@ import ( "time" "github.com/AnomalyFi/hypersdk/chain" - "github.com/AnomalyFi/nodekit-seq/actions" srpc "github.com/AnomalyFi/nodekit-seq/rpc" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/sirupsen/logrus" @@ -38,8 +36,6 @@ func TestCanAddChunk(t *testing.T) { seqCli := mseq.NewMockBaseSeqClient(t) // in case new incoming tob trigger submission of previous layer - mockTx := new(chain.Transaction) - seqCli.EXPECT().GenerateTransaction(mock.Anything, mock.Anything).Return(mockTx, nil) actsChan := make(chan []chain.Action) seqCli.EXPECT().ActsChan().Return(actsChan).Maybe() @@ -202,8 +198,6 @@ func TestCanWithCorrectTxs(t *testing.T) { seqCli := mseq.NewMockBaseSeqClient(t) // in case new incoming tob trigger submission of previous layer - mockTx := new(chain.Transaction) - seqCli.EXPECT().GenerateTransaction(mock.Anything, mock.Anything).Return(mockTx, nil) actsChan := make(chan []chain.Action) seqCli.EXPECT().ActsChan().Return(actsChan).Maybe() @@ -329,8 +323,6 @@ func TestCanWithCorrectTxs(t *testing.T) { seqCli := mseq.NewMockBaseSeqClient(t) // in case new incoming tob trigger submission of previous layer - mockTx := new(chain.Transaction) - seqCli.EXPECT().GenerateTransaction(mock.Anything, mock.Anything).Return(mockTx, nil) actsChan := make(chan []chain.Action) seqCli.EXPECT().ActsChan().Return(actsChan).Maybe() @@ -625,8 +617,6 @@ func TestCanSettleTxs(t *testing.T) { seqCli := mseq.NewMockBaseSeqClient(t) // in case new incoming tob trigger submission of previous layer - mockTx := new(chain.Transaction) - seqCli.EXPECT().GenerateTransaction(mock.Anything, mock.Anything).Return(mockTx, nil) actsChan := make(chan []chain.Action) seqCli.EXPECT().ActsChan().Return(actsChan).Maybe() @@ -720,9 +710,7 @@ func TestChunkManagerCanGC(t *testing.T) { ds.EXPECT().SaveLowestManagedStateToBNonce(nonce, uint64(0)).Return(nil).Maybe() } - mockTx := new(chain.Transaction) seqCli := mseq.NewMockBaseSeqClient(t) - seqCli.EXPECT().GenerateTransaction(mock.Anything, mock.Anything).Return(mockTx, nil).Maybe() actsChan := make(chan []chain.Action) seqCli.EXPECT().ActsChan().Return(actsChan).Maybe() @@ -949,8 +937,6 @@ func TestChunkManagerReplay(t *testing.T) { seqCli := mseq.NewMockBaseSeqClient(t) // in case new incoming tob trigger submission of previous layer - mockTx := new(chain.Transaction) - seqCli.EXPECT().GenerateTransaction(mock.Anything, mock.Anything).Return(mockTx, nil) actsChan := make(chan []chain.Action) seqCli.EXPECT().ActsChan().Return(actsChan).Maybe() @@ -1142,21 +1128,6 @@ func TestChunkManagerManageSettledToB(t *testing.T) { require.NoError(t, err) ds.EXPECT().SetToBChunk(initialToBNonce+1, tob.ToB).Return(nil) ds.EXPECT().SaveCurrentToBNonce(initialToBNonce+1, epoch).Return(nil) - matchTx := func(acts []chain.Action) bool { - layerCorrect := false - for _, act := range acts { - actSetToBNonce, ok := act.(*actions.SetSettledToBNonce) - if !ok { - continue - } - if actSetToBNonce.ToBNonce == initialToBNonce { - layerCorrect = true - } - } - return layerCorrect - } - mockTx := new(chain.Transaction) - seqCli.EXPECT().GenerateTransaction(mock.Anything, mock.MatchedBy(matchTx)).Return(mockTx, nil) actsChan := make(chan []chain.Action) seqCli.EXPECT().ActsChan().Return(actsChan).Maybe() @@ -1226,21 +1197,6 @@ func TestChunkManagerManageSettledToB(t *testing.T) { require.NoError(t, err) ds.EXPECT().SetToBChunk(initialToBNonce+1, tob.ToB).Return(nil) ds.EXPECT().SaveCurrentToBNonce(initialToBNonce+1, epoch).Return(nil) - matchTx := func(acts []chain.Action) bool { - layerCorrect := false - for _, act := range acts { - actSetToBNonce, ok := act.(*actions.SetSettledToBNonce) - if !ok { - continue - } - if actSetToBNonce.ToBNonce == initialToBNonce { - layerCorrect = true - } - } - return layerCorrect - } - mockTx := new(chain.Transaction) - seqCli.EXPECT().GenerateTransaction(mock.Anything, mock.MatchedBy(matchTx)).Return(mockTx, nil) actsChan := make(chan []chain.Action) seqCli.EXPECT().ActsChan().Return(actsChan).Maybe() err = cm.AddChunk(tob) @@ -1288,38 +1244,6 @@ func TestChunkManagerManageSettledToB(t *testing.T) { } // setup mocks for push cert - matchActs := func(acts []chain.Action) bool { - if len(acts) != len(certs)+1 { - return false - } - for i := 0; i < len(acts)-1; i++ { - act := acts[i] - daCertAction, ok := act.(*actions.DACertificate) - if !ok { - return false - } - require.Equal(t, certs[i].DAType, daCertAction.Cert.DAType) - require.Equal(t, certs[i].Cert, daCertAction.Cert.Cert) - require.Equal(t, certs[i].PlaceHolder, false) - - chunkID, err := chunks[i].ID() - require.NoError(t, err) - chainID := "tob" - blockNumber := chunks[i].ToBNonce - if chunks[i].IsRoB() { - chainID = chunks[i].RoB.ChainID - blockNumber = chunks[i].RoB.BlockNumber - } - require.Equal(t, chainID, daCertAction.Cert.ChainID) - require.Equal(t, blockNumber, daCertAction.Cert.BlockNumber) - require.Equal(t, chunks[i].ToBNonce, daCertAction.Cert.ToBNonce) - require.Equal(t, chunkID, daCertAction.Cert.ChunkID) - require.Equal(t, msgs[i].Epoch, daCertAction.Cert.Epoch) - } - - return true - } - seqCli.EXPECT().GenerateTransaction(mock.Anything, mock.MatchedBy(matchActs)).Return(mockTx, nil) actsChan = make(chan []chain.Action) seqCli.EXPECT().ActsChan().Return(actsChan).Maybe() @@ -1334,87 +1258,4 @@ func TestChunkManagerManageSettledToB(t *testing.T) { require.True(t, layer.submittedToSEQ) require.Equal(t, cm.HighestSettledToB(), initialToBNonce+1) }) - - t.Run("settled tobnonce pq will be restored if layer submission failed", func(t *testing.T) { - // mocks - ds := mdatastore.NewMockIDatastore(t) - seqCli := setupSEQCli() - resubmissionCheckInterval := 3 * time.Second - - initialToBNonce := uint64(1) - setupDatastore(ds, 0, 0) - - cm, err := NewChunkManager(&ChunkManagerConfig{ - ExpirationTime: 100 * time.Second, - GCInterval: 100 * time.Second, - LayerSubmissionCheckInterval: resubmissionCheckInterval, - - StateLowestToBNonce: 0, - HighestSettledToBNonce: 0, - - Datastore: ds, - SEQChainParser: &seqParser, - SEQClient: seqCli, - Logger: logger, - }) - require.NoError(t, err) - require.Equal(t, initialToBNonce, cm.ToBNonce()) - - // push one rob to the initial layer - rob, err := common.MakeRandomRoB(testSeqChainID, &seqParser, big.NewInt(45200), 100, 10) - require.NoError(t, err) - robChunkID, err := rob.ID() - require.NoError(t, err) - - ds.EXPECT().SetRoBChunk(rob.RoB).Return(nil) - - err = cm.AddChunk(rob) - require.NoError(t, err) - - // push one tob as the next layer - tob, err := common.MakeRandomToB(testSeqChainID, &seqParser, 2, 10, 10) - require.NoError(t, err) - ds.EXPECT().SetToBChunk(initialToBNonce+1, tob.ToB).Return(nil) - ds.EXPECT().SaveCurrentToBNonce(initialToBNonce+1, uint64(0)).Return(nil) - - err = cm.AddChunk(tob) - require.NoError(t, err) - - require.Equal(t, initialToBNonce+1, cm.ToBNonce()) - - // setup mock - mockErr := errors.New("failed to generate tx") - seqCli.EXPECT().GenerateTransaction(mock.Anything, mock.Anything).Return(nil, mockErr).Once() - - settledToBNoncePrev := cm.HighestSettledToB() - - // push the rob cert - err = cm.PushCert(context.TODO(), &common.ArcadiaToSEQChunkMessage{ - ChunkID: robChunkID, - Chunk: rob, - }, &common.CertInfo{ - DAType: actions.CelestiaDA, - Cert: []byte("somecert"), - PlaceHolder: false, - }) - require.NoError(t, err) - - settledToBNonceAfterSubmissionFail := cm.HighestSettledToB() - require.Equal(t, settledToBNoncePrev, settledToBNonceAfterSubmissionFail) - - // setup mock for successfull submission - mockTx := new(chain.Transaction) - seqCli.EXPECT().GenerateTransaction(mock.Anything, mock.Anything).Return(mockTx, nil).Once() - actsChan := make(chan []chain.Action) - seqCli.EXPECT().ActsChan().Return(actsChan).Maybe() - - time.Sleep(resubmissionCheckInterval + 1*time.Second) - require.Equal(t, uint64(1), initialToBNonce) - layer0 := cm.chunks[0] - require.Equal(t, 2, len(layer0.chunks)) - require.Equal(t, true, layer0.LayerSubmittedToSEQ()) - for _, chunk := range layer0.chunks { - require.True(t, chunk.SubmittedToDA()) - } - }) } diff --git a/chunk_manager/consts.go b/chunk_manager/consts.go index 5299fc3f..c3fb7448 100644 --- a/chunk_manager/consts.go +++ b/chunk_manager/consts.go @@ -1,6 +1,8 @@ package chunkmanager -import "github.com/AnomalyFi/Arcadia/common" +import ( + "github.com/AnomalyFi/Arcadia/common" +) // PlaceHolderToBChunk is an empty ToB that put at chunkLayer 0, position 0 var PlaceHolderToBChunk = ChunkWrapper{ diff --git a/datalayer/utils.go b/datalayer/utils.go index c723d9f6..fcde23a3 100644 --- a/datalayer/utils.go +++ b/datalayer/utils.go @@ -1,5 +1,6 @@ package datalayer +// ConstructCelestiaNamespace construct a namespace by a namespaceID using version 0, the legacy namespace with at most 10 bytes as ID // See: https://github.com/celestiaorg/celestia-app/blob/main/docs/architecture/adr-014-versioned-namespaces.md // Namespace is constructed by Version | NamespaceID // where Version occupies 1 byte and NamespaceID occupies 28bytes diff --git a/services/api/service_test.go b/services/api/service_test.go index 07e24cf7..943e9b26 100644 --- a/services/api/service_test.go +++ b/services/api/service_test.go @@ -142,14 +142,15 @@ func newTestbackendWithCustomSEQCliNDatastore(t *testing.T, seqClient *mseq.Mock t.Logf("initializing chunk manager with lowestStateToBNonce: %d settledToBNonce: %d", lowestToBNonce, highestSettledToBNonce) config := chunkmanager.ChunkManagerConfig{ - ExpirationTime: 5 * time.Minute, - GCInterval: 10 * time.Second, - HighestSettledToBNonce: highestSettledToBNonce, - StateLowestToBNonce: lowestToBNonce, - Datastore: ds, - SEQClient: seqClient, - SEQChainParser: seqClient.Parser(), - Logger: common.TestLog, + ExpirationTime: 5 * time.Minute, + GCInterval: 10 * time.Second, + LayerSubmissionCheckInterval: 100 * time.Second, + HighestSettledToBNonce: highestSettledToBNonce, + StateLowestToBNonce: lowestToBNonce, + Datastore: ds, + SEQClient: seqClient, + SEQChainParser: seqClient.Parser(), + Logger: common.TestLog, } cm, err := chunkmanager.NewChunkManager(&config) @@ -2546,8 +2547,6 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { // shared calls for both chunks backend.seqcli.EXPECT().Parser().Return(chainParser) // adding new tob will create a new layer, which will try submit the previous layer to SEQ - mockTx := new(chain.Transaction) - backend.seqcli.EXPECT().GenerateTransaction(mock.Anything, mock.Anything).Return(mockTx, nil) actsChan := make(chan []chain.Action) backend.seqcli.EXPECT().ActsChan().Return(actsChan).Maybe() @@ -2743,8 +2742,6 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { // shared calls for both chunks backend.seqcli.EXPECT().Parser().Return(chainParser) // adding new tob will create a new layer, which will try submit the previous layer to SEQ - mockTx := new(chain.Transaction) - backend.seqcli.EXPECT().GenerateTransaction(mock.Anything, mock.Anything).Return(mockTx, nil) actsChan := make(chan []chain.Action) backend.seqcli.EXPECT().ActsChan().Return(actsChan).Maybe() @@ -3109,8 +3106,6 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { // shared calls for both chunks backend.seqcli.EXPECT().Parser().Return(chainParser) // adding new tob will create a new layer, which will try submit the previous layer to SEQ - mockTx := new(chain.Transaction) - backend.seqcli.EXPECT().GenerateTransaction(mock.Anything, mock.Anything).Return(mockTx, nil) actsChan := make(chan []chain.Action) backend.seqcli.EXPECT().ActsChan().Return(actsChan).Maybe() @@ -3317,8 +3312,6 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { // shared calls for both chunks backend.seqcli.EXPECT().Parser().Return(chainParser) // adding new tob will create a new layer, which will try submit the previous layer to SEQ - mockTx := new(chain.Transaction) - backend.seqcli.EXPECT().GenerateTransaction(mock.Anything, mock.Anything).Return(mockTx, nil) actsChan := make(chan []chain.Action) backend.seqcli.EXPECT().ActsChan().Return(actsChan).Maybe() @@ -3440,8 +3433,6 @@ func TestHandleSubmitNewBlockRequest(t *testing.T) { // shared calls for both chunks backend.seqcli.EXPECT().Parser().Return(chainParser) // adding new tob will create a new layer, which will try submit the previous layer to SEQ - mockTx := new(chain.Transaction) - backend.seqcli.EXPECT().GenerateTransaction(mock.Anything, mock.Anything).Return(mockTx, nil) actsChan := make(chan []chain.Action) backend.seqcli.EXPECT().ActsChan().Return(actsChan).Maybe() @@ -4238,8 +4229,6 @@ func TestGetPayload(t *testing.T) { backend := setupBackend() backend.seqcli.EXPECT().Parser().Return(chainParser).Maybe() // in case AddChunk trigger submission - mockSEQTx := new(chain.Transaction) - backend.seqcli.EXPECT().GenerateTransaction(mock.Anything, mock.Anything).Return(mockSEQTx, nil) actsChan := make(chan []chain.Action) backend.seqcli.EXPECT().ActsChan().Return(actsChan).Maybe() @@ -6449,8 +6438,6 @@ func TestToBNonceState(t *testing.T) { // shared calls for both chunks backend.seqcli.EXPECT().Parser().Return(chainParser) // adding new tob will create a new layer, which will try submit the previous layer to SEQ - mockTx := new(chain.Transaction) - backend.seqcli.EXPECT().GenerateTransaction(mock.Anything, mock.Anything).Return(mockTx, nil) actsChan := make(chan []chain.Action) backend.seqcli.EXPECT().ActsChan().Return(actsChan).Maybe() From 40c0e1c0d5524d33d686b611dbd739c3221e3221 Mon Sep 17 00:00:00 2001 From: bianyuanop Date: Tue, 25 Feb 2025 11:13:19 -0500 Subject: [PATCH 13/13] comments fixes --- chunk_manager/chunk_layer.go | 15 ++++++++++----- chunk_manager/chunk_manager.go | 6 +++++- chunk_manager/chunk_manager_test.go | 4 +++- cmd/api.go | 7 ++++++- datalayer/utils.go | 11 +++++++++-- seq/consts.go | 1 + seq/seqclient.go | 2 +- 7 files changed, 35 insertions(+), 11 deletions(-) diff --git a/chunk_manager/chunk_layer.go b/chunk_manager/chunk_layer.go index 8585a13d..49398f65 100644 --- a/chunk_manager/chunk_layer.go +++ b/chunk_manager/chunk_layer.go @@ -9,6 +9,7 @@ import ( "github.com/AnomalyFi/Arcadia/common" ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/sirupsen/logrus" ) type ChunkLayer struct { @@ -17,14 +18,17 @@ type ChunkLayer struct { submittedToSEQ bool - l sync.RWMutex + logger *logrus.Entry + l sync.RWMutex } -func NewChunkLayer(nonce uint64, tobChunk *ChunkWrapper) *ChunkLayer { +func NewChunkLayer(logger *logrus.Entry, nonce uint64, tobChunk *ChunkWrapper) *ChunkLayer { + logger = logger.WithField("nonce", nonce) return &ChunkLayer{ tobNonce: nonce, chunks: []*ChunkWrapper{tobChunk}, submittedToSEQ: false, + logger: logger, } } @@ -220,14 +224,15 @@ func (cl *ChunkLayer) ChunksSubmittedToDA() bool { cl.l.RLock() defer cl.l.RUnlock() submitted := true - fmt.Printf("layer-%d da submission info\n", cl.tobNonce) + cl.logger.Debugf("=======layer-%d da submission info", cl.tobNonce) for _, chunk := range cl.chunks { submitted = submitted && chunk.SubmittedToDA() chunkID, err := chunk.ID() if err != nil { - panic(err) + cl.logger.WithError(err).Debug("unable to get chunk id from chunk") + continue } - fmt.Printf("%s:%t", chunkID, chunk.SubmittedToDA()) + cl.logger.Debugf("%s:%t", chunkID, chunk.SubmittedToDA()) } return submitted } diff --git a/chunk_manager/chunk_manager.go b/chunk_manager/chunk_manager.go index 6f3378a6..dcfb2b86 100644 --- a/chunk_manager/chunk_manager.go +++ b/chunk_manager/chunk_manager.go @@ -86,6 +86,10 @@ func NewChunkManager(cfg *ChunkManagerConfig) (*ChunkManager, error) { return nil, errors.New("provided state tob nonce greater than highest settled tob nonce") } + if cfg.ExpirationTime < 0 || cfg.GCInterval < 0 || cfg.LayerSubmissionCheckInterval < 0 { + return nil, fmt.Errorf("invalid duration variables, ExpirationTime: %s, GCInterval: %s, LayerSubmissionCheckInterval: %s", cfg.ExpirationTime, cfg.GCInterval, cfg.LayerSubmissionCheckInterval) + } + // when cfg.StateLowestToBNonce =0, after pushing the first element to both PQs, we add a place holder layer as the first layer // the tob nonce of which is headTobNonce+1 // when cfg.StateLowestToBNonce >0, chunks will be replayed with the base headToBNonce to be cfg.StateLowestToBNonce-1, hence guarantees @@ -200,7 +204,7 @@ func (m *ChunkManager) addChunkLayer(chunk *common.ArcadiaChunk) { } } chunk.ToBNonce = m.headToBNonce + 1 - chunkLayer := NewChunkLayer(chunk.ToBNonce, &ChunkWrapper{ + chunkLayer := NewChunkLayer(m.logger, chunk.ToBNonce, &ChunkWrapper{ ArcadiaChunk: chunk, timestamp: time.Now(), }) diff --git a/chunk_manager/chunk_manager_test.go b/chunk_manager/chunk_manager_test.go index 0e9dfd0e..cf03c4d6 100644 --- a/chunk_manager/chunk_manager_test.go +++ b/chunk_manager/chunk_manager_test.go @@ -775,6 +775,8 @@ func TestChunkManagerCanGC(t *testing.T) { } func TestChunkLayer(t *testing.T) { + logger := common.TestLog + logger.Logger.SetLevel(logrus.DebugLevel) testSeqChainID := ids.GenerateTestID() parser := srpc.Parser{} numChains := 2 @@ -801,7 +803,7 @@ func TestChunkLayer(t *testing.T) { rob2, err := common.MakeRandomRoB(testSeqChainID, &parser, chainIDs[1], 100, 10) require.NoError(t, err) - layer := NewChunkLayer(0, &ChunkWrapper{ + layer := NewChunkLayer(logger, 0, &ChunkWrapper{ ArcadiaChunk: tob, timestamp: time.Now(), }) diff --git a/cmd/api.go b/cmd/api.go index b0d2af27..9c8b9d7e 100644 --- a/cmd/api.go +++ b/cmd/api.go @@ -250,8 +250,13 @@ var apiCmd = &cobra.Command{ log.WithError(err).Fatal("unable to parse celestia dag ascii price") } + celestiaNamespace, err := datalayer.ConstructCelestiaNamespace([]byte(apiCelestiaDANamespace)) + if err != nil { + log.WithError(err).Fatal("unable to construct celestia namespace") + } + celestiaConfig := &datalayer.CelestiaConfig{ - NamespaceID: datalayer.ConstructCelestiaNamespace([]byte(apiCelestiaDANamespace)), + NamespaceID: celestiaNamespace, GasPrice: celestiaDaGasPrice, RPCAddr: apiCelestiaDAUrl, AccessToken: apiCelestiaDAAccessToken, diff --git a/datalayer/utils.go b/datalayer/utils.go index fcde23a3..00346001 100644 --- a/datalayer/utils.go +++ b/datalayer/utils.go @@ -1,13 +1,20 @@ package datalayer +import "fmt" + +const LegacyNamespaceIDMaxLen = 10 + // ConstructCelestiaNamespace construct a namespace by a namespaceID using version 0, the legacy namespace with at most 10 bytes as ID // See: https://github.com/celestiaorg/celestia-app/blob/main/docs/architecture/adr-014-versioned-namespaces.md // Namespace is constructed by Version | NamespaceID // where Version occupies 1 byte and NamespaceID occupies 28bytes -func ConstructCelestiaNamespace(namespaceID []byte) []byte { +func ConstructCelestiaNamespace(namespaceID []byte) ([]byte, error) { + if len(namespaceID) > LegacyNamespaceIDMaxLen { + return nil, fmt.Errorf("verison 0 namespaceID length greater than 10") + } namespace := make([]byte, 29) // for version 0, the tail 10 bytes are user specifiable namespace[0] = 0 copy(namespace[19:], namespaceID) - return namespace + return namespace, nil } diff --git a/seq/consts.go b/seq/consts.go index 2f0c7f48..9206574c 100644 --- a/seq/consts.go +++ b/seq/consts.go @@ -2,3 +2,4 @@ package seq const DefaultProposerLRUSize = 20 const GetArcadiaBlockSignatureHeader = "X-Arcadia-GetBlock-Sig" +const ActionChannelBufferSize = 20 diff --git a/seq/seqclient.go b/seq/seqclient.go index 09542468..86703ce6 100644 --- a/seq/seqclient.go +++ b/seq/seqclient.go @@ -149,7 +149,7 @@ func NewSeqClient(config *SeqClientConfig) (*SeqClient, error) { stop: stopSig, BuilderPubKey: nil, epochMap: epochMap, - actsQueue: make(chan []chain.Action, 20), + actsQueue: make(chan []chain.Action, ActionChannelBufferSize), } // keep track of head of SEQ, this is used for calculating `Epoch` in SubmitBlock to Arcadia