From aa24e5b2480e57170f81bb9d9149fb132ecaab4b Mon Sep 17 00:00:00 2001 From: Sergi Rene Date: Fri, 4 Oct 2024 22:23:31 +0200 Subject: [PATCH 01/29] syncing refactor --- block/manager.go | 55 ++++++++++++++++++++--------------------- block/retriever.go | 34 ++++++------------------- da/avail/avail.go | 5 ++-- da/celestia/celestia.go | 6 ++--- da/da.go | 4 +-- da/grpc/grpc.go | 6 ++--- da/local/local.go | 6 ++--- 7 files changed, 49 insertions(+), 67 deletions(-) diff --git a/block/manager.go b/block/manager.go index 388171479..43d9b7074 100644 --- a/block/manager.go +++ b/block/manager.go @@ -30,6 +30,8 @@ import ( "github.com/dymensionxyz/dymint/p2p" "github.com/dymensionxyz/dymint/settlement" "github.com/dymensionxyz/dymint/types" + + uchannel "github.com/dymensionxyz/dymint/utils/channel" ) // Manager is responsible for aggregating transactions into blocks. @@ -67,9 +69,8 @@ type Manager struct { // Protect against processing two blocks at once when there are two routines handling incoming gossiped blocks, // and incoming DA blocks, respectively. retrieverMu sync.Mutex - // Protect against syncing twice from DA in case new batch is posted but it did not finish to sync yet. - syncFromDaMu sync.Mutex - Retriever da.BatchRetriever + + Retriever da.BatchRetriever // Cached blocks and commits for applying at future heights. The blocks may not be valid, because // we can only do full validation in sequential order. blockCache *Cache @@ -85,6 +86,10 @@ type Manager struct { // indexer indexerService *txindex.IndexerService + + syncingC chan uint64 + + synced *uchannel.Nudger } // NewManager creates new block Manager. @@ -128,6 +133,8 @@ func NewManager( }, FraudHandler: nil, // TODO: create a default handler pruningC: make(chan int64, 10), // use of buffered channel to avoid blocking applyBlock thread. In case channel is full, pruning will be skipped, but the retain height can be pruned in the next iteration. + syncingC: make(chan uint64, 1), + synced: uchannel.NewNudger(), } err = m.LoadStateOnInit(store, genesis, logger) @@ -175,21 +182,22 @@ func (m *Manager) Start(ctx context.Context) error { return m.PruningLoop(ctx) }) + uerrors.ErrGroupGoLog(eg, m.logger, func() error { + return m.SyncTargetLoop(ctx) + }) + + err = m.syncFromSettlement() + if err != nil { + return fmt.Errorf("sync block manager from settlement: %w", err) + } + /* ----------------------------- full node mode ----------------------------- */ if !isProposer { - // Full-nodes can sync from DA but it is not necessary to wait for it, since it can sync from P2P as well in parallel. - go func() { - err := m.syncFromSettlement() - if err != nil { - m.logger.Error("sync block manager from settlement", "err", err) - } - // DA Sync. Subscribe to SL next batch events - go uevent.MustSubscribe(ctx, m.Pubsub, "syncTargetLoop", settlement.EventQueryNewSettlementBatchAccepted, m.onNewStateUpdate, m.logger) - }() - + go uevent.MustSubscribe(ctx, m.Pubsub, "syncTargetLoop", settlement.EventQueryNewSettlementBatchAccepted, m.onNewStateUpdate, m.logger) // P2P Sync. Subscribe to P2P received blocks events go uevent.MustSubscribe(ctx, m.Pubsub, "applyGossipedBlocksLoop", p2p.EventQueryNewGossipedBlock, m.OnReceivedBlock, m.logger) go uevent.MustSubscribe(ctx, m.Pubsub, "applyBlockSyncBlocksLoop", p2p.EventQueryNewBlockSyncBlock, m.OnReceivedBlock, m.logger) + return nil } @@ -198,11 +206,10 @@ func (m *Manager) Start(ctx context.Context) error { go uevent.MustSubscribe(ctx, m.Pubsub, "updateSubmittedHeightLoop", settlement.EventQueryNewSettlementBatchAccepted, m.UpdateLastSubmittedHeight, m.logger) // Sequencer must wait till DA is synced to start submitting blobs - <-m.DAClient.Synced() - err = m.syncFromSettlement() - if err != nil { - return fmt.Errorf("sync block manager from settlement: %w", err) - } + m.DAClient.WaitForSyncing() + + // Sequencer must wait till node is synced till last submittedHeight, in case is not + m.waitForSyncing() // check if sequencer in the middle of rotation nextSeqAddr, missing, err := m.MissingLastBatch() if err != nil { @@ -265,10 +272,6 @@ func (m *Manager) NextHeightToSubmit() uint64 { // syncFromSettlement enforces the node to be synced on initial run from SL and DA. func (m *Manager) syncFromSettlement() error { - err := m.UpdateSequencerSetFromSL() - if err != nil { - return fmt.Errorf("update bonded sequencer set: %w", err) - } res, err := m.SLClient.GetLatestBatch() if errors.Is(err, gerrc.ErrNotFound) { @@ -282,14 +285,10 @@ func (m *Manager) syncFromSettlement() error { // TODO: separate between fresh rollapp and non-registered rollapp return err } + m.LastSubmittedHeight.Store(res.EndHeight) - err = m.syncToTargetHeight(res.EndHeight) m.UpdateTargetHeight(res.EndHeight) - if err != nil { - return err - } - - m.logger.Info("Synced.", "current height", m.State.Height(), "last submitted height", m.LastSubmittedHeight.Load()) + m.syncingC <- res.EndHeight return nil } diff --git a/block/retriever.go b/block/retriever.go index 405fde927..e8900caed 100644 --- a/block/retriever.go +++ b/block/retriever.go @@ -9,38 +9,20 @@ import ( "github.com/dymensionxyz/dymint/da" "github.com/dymensionxyz/dymint/node/events" - "github.com/dymensionxyz/dymint/settlement" "github.com/dymensionxyz/dymint/types" uevent "github.com/dymensionxyz/dymint/utils/event" - "github.com/tendermint/tendermint/libs/pubsub" ) -// onNewStateUpdate will try to sync to new height, if not already synced -func (m *Manager) onNewStateUpdate(event pubsub.Message) { - eventData, ok := event.Data().(*settlement.EventDataNewBatchAccepted) - if !ok { - m.logger.Error("onReceivedBatch", "err", "wrong event data received") - return - } - h := eventData.EndHeight - m.UpdateTargetHeight(h) - err := m.syncToTargetHeight(h) - if err != nil { - m.logger.Error("sync until target", "err", err) - } -} - // syncToTargetHeight syncs blocks until the target height is reached. // It fetches the batches from the settlement, gets the DA height and gets // the actual blocks from the DA. -func (m *Manager) syncToTargetHeight(targetHeight uint64) error { - defer m.syncFromDaMu.Unlock() - m.syncFromDaMu.Lock() - for currH := m.State.NextHeight(); currH <= targetHeight; currH = m.State.NextHeight() { +func (m *Manager) syncToLastSubmittedHeight() error { + + for currH := m.State.NextHeight(); currH <= m.LastSubmittedHeight.Load(); currH = m.State.NextHeight() { // if we have the block locally, we don't need to fetch it from the DA err := m.applyLocalBlock(currH) if err == nil { - m.logger.Info("Synced from local", "store height", currH, "target height", targetHeight) + m.logger.Info("Synced from local", "store height", currH, "target height", m.LastSubmittedHeight.Load()) continue } if !errors.Is(err, gerrc.ErrNotFound) { @@ -49,14 +31,14 @@ func (m *Manager) syncToTargetHeight(targetHeight uint64) error { err = m.syncFromDABatch() if err != nil { - return fmt.Errorf("process next DA batch: %w", err) + m.logger.Error("process next DA batch", "err", err) } // if height havent been updated, we are stuck - if m.State.NextHeight() == currH { + /*if m.State.NextHeight() == currH { return fmt.Errorf("stuck at height %d", currH) - } - m.logger.Info("Synced from DA", "store height", m.State.Height(), "target height", targetHeight) + }*/ + m.logger.Info("Synced from DA", "store height", m.State.Height(), "target height", m.LastSubmittedHeight.Load()) } diff --git a/da/avail/avail.go b/da/avail/avail.go index a31e4a7b2..ea932c756 100644 --- a/da/avail/avail.go +++ b/da/avail/avail.go @@ -156,8 +156,9 @@ func (c *DataAvailabilityLayerClient) Stop() error { return nil } -func (c *DataAvailabilityLayerClient) Synced() <-chan struct{} { - return c.synced +// WaitForSyncing is used to check when the DA light client finished syncing +func (m *DataAvailabilityLayerClient) WaitForSyncing() { + <-m.synced } // GetClientType returns client type. diff --git a/da/celestia/celestia.go b/da/celestia/celestia.go index c9b979909..0120678ec 100644 --- a/da/celestia/celestia.go +++ b/da/celestia/celestia.go @@ -163,9 +163,9 @@ func (c *DataAvailabilityLayerClient) Stop() error { return nil } -// Started returns channel for on start event -func (c *DataAvailabilityLayerClient) Synced() <-chan struct{} { - return c.synced +// WaitForSyncing is used to check when the DA light client finished syncing +func (m *DataAvailabilityLayerClient) WaitForSyncing() { + <-m.synced } // GetClientType returns client type. diff --git a/da/da.go b/da/da.go index b07441a0d..14dec8d9f 100644 --- a/da/da.go +++ b/da/da.go @@ -216,8 +216,8 @@ type DataAvailabilityLayerClient interface { // CheckBatchAvailability checks the availability of the blob submitted getting proofs and validating them CheckBatchAvailability(daMetaData *DASubmitMetaData) ResultCheckBatch - // Channel used to check when the DA light client finished syncing - Synced() <-chan struct{} + // Used to check when the DA light client finished syncing + WaitForSyncing() // Returns the maximum allowed blob size in the DA, used to check the max batch size configured GetMaxBlobSizeBytes() uint32 diff --git a/da/grpc/grpc.go b/da/grpc/grpc.go index aff1d2819..4724d37f2 100644 --- a/da/grpc/grpc.go +++ b/da/grpc/grpc.go @@ -80,9 +80,9 @@ func (d *DataAvailabilityLayerClient) Stop() error { return d.conn.Close() } -// Synced returns channel for on sync event -func (m *DataAvailabilityLayerClient) Synced() <-chan struct{} { - return m.synced +// WaitForSyncing is used to check when the DA light client finished syncing +func (m *DataAvailabilityLayerClient) WaitForSyncing() { + <-m.synced } // GetClientType returns client type. diff --git a/da/local/local.go b/da/local/local.go index 65c63fc93..87aab4ada 100644 --- a/da/local/local.go +++ b/da/local/local.go @@ -76,9 +76,9 @@ func (m *DataAvailabilityLayerClient) Stop() error { return nil } -// Synced returns channel for on start event -func (m *DataAvailabilityLayerClient) Synced() <-chan struct{} { - return m.synced +// WaitForSyncing is used to check when the DA light client finished syncing +func (m *DataAvailabilityLayerClient) WaitForSyncing() { + <-m.synced } // GetClientType returns client type. From 8dcdd9445fa097a9b19ce25a719b953ba16e257b Mon Sep 17 00:00:00 2001 From: Sergi Rene Date: Fri, 4 Oct 2024 22:32:28 +0200 Subject: [PATCH 02/29] missing file --- block/sync.go | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 block/sync.go diff --git a/block/sync.go b/block/sync.go new file mode 100644 index 000000000..99b5b38dd --- /dev/null +++ b/block/sync.go @@ -0,0 +1,56 @@ +package block + +import ( + "context" + "fmt" + + "github.com/dymensionxyz/dymint/settlement" + "github.com/tendermint/tendermint/libs/pubsub" +) + +// onNewStateUpdate will try to sync to new height, if not already synced +func (m *Manager) onNewStateUpdate(event pubsub.Message) { + eventData, ok := event.Data().(*settlement.EventDataNewBatchAccepted) + if !ok { + m.logger.Error("onReceivedBatch", "err", "wrong event data received") + return + } + m.LastSubmittedHeight.Store(eventData.EndHeight) + m.UpdateTargetHeight(eventData.EndHeight) + select { + case m.syncingC <- eventData.EndHeight: + default: + m.logger.Debug("disregarding new state update, since node is still syncing") + } +} + +// SyncTargetLoop is responsible for getting real time updates about settlement batch submissions. +// For non aggregator: updating the sync target which will be used by retrieveLoop to sync until this target. +// It publishes new sync height targets which will then be synced by another process. +func (m *Manager) SyncTargetLoop(ctx context.Context) error { + + for { + select { + case <-ctx.Done(): + return nil + case targetHeight := <-m.syncingC: + err := m.UpdateSequencerSetFromSL() + if err != nil { + return fmt.Errorf("update bonded sequencer set: %w", err) + } + + err = m.syncToLastSubmittedHeight() + if err != nil { + return fmt.Errorf("syncing to target height: %d err:%w", targetHeight, err) + } + m.synced.Nudge() + m.logger.Info("Synced.", "current height", m.State.Height(), "last submitted height", m.LastSubmittedHeight.Load()) + } + } +} + +func (m *Manager) waitForSyncing() { + if m.State.Height() < m.LastSubmittedHeight.Load() { + <-m.synced.C + } +} From daa7ca7209c553173b5522561dc2d49437df7e6c Mon Sep 17 00:00:00 2001 From: Sergi Rene Date: Sat, 5 Oct 2024 09:31:03 +0200 Subject: [PATCH 03/29] validation store --- block/block.go | 9 ++++++++ block/manager_test.go | 18 +++++++++++++-- block/retriever.go | 18 ++++++++++----- block/sync.go | 30 +++++++++++++++---------- settlement/dymension/events.go | 5 +++-- settlement/events.go | 1 + store/store.go | 40 ++++++++++++++++++++++++++++++++++ store/storeIface.go | 4 ++++ 8 files changed, 103 insertions(+), 22 deletions(-) diff --git a/block/block.go b/block/block.go index f0fcd8ad0..321a5d3db 100644 --- a/block/block.go +++ b/block/block.go @@ -92,6 +92,15 @@ func (m *Manager) applyBlock(block *types.Block, commit *types.Commit, blockMeta return fmt.Errorf("save block responses: %w", err) } + validated := false + if blockMetaData.Source == types.DA { + validated = true + } + _, err = m.Store.SaveBlockValidation(block.Header.Height, validated, nil) + if err != nil { + return fmt.Errorf("save block responses: %w", err) + } + // Commit block to app appHash, retainHeight, err = m.Executor.Commit(m.State, block, responses) if err != nil { diff --git a/block/manager_test.go b/block/manager_test.go index e5d14962e..6667e2406 100644 --- a/block/manager_test.go +++ b/block/manager_test.go @@ -404,7 +404,14 @@ func TestRetrieveDaBatchesFailed(t *testing.T) { Client: da.Mock, Height: 1, } - err = manager.ProcessNextDABatch(daMetaData) + batch := &settlement.ResultRetrieveBatch{ + Batch: &settlement.Batch{ + MetaData: &settlement.BatchMetaData{ + DA: daMetaData, + }, + }, + } + err = manager.ProcessNextDABatch(batch) t.Log(err) assert.ErrorIs(t, err, da.ErrBlobNotFound) } @@ -746,7 +753,14 @@ func TestDAFetch(t *testing.T) { LastBlockHeight: int64(batch.EndHeight()), LastBlockAppHash: commitHash[:], }) - err := manager.ProcessNextDABatch(c.daMetaData) + batch := &settlement.ResultRetrieveBatch{ + Batch: &settlement.Batch{ + MetaData: &settlement.BatchMetaData{ + DA: c.daMetaData, + }, + }, + } + err := manager.ProcessNextDABatch(batch) require.Equal(c.err, err) }) } diff --git a/block/retriever.go b/block/retriever.go index e8900caed..f3f9b3c70 100644 --- a/block/retriever.go +++ b/block/retriever.go @@ -9,6 +9,7 @@ import ( "github.com/dymensionxyz/dymint/da" "github.com/dymensionxyz/dymint/node/events" + "github.com/dymensionxyz/dymint/settlement" "github.com/dymensionxyz/dymint/types" uevent "github.com/dymensionxyz/dymint/utils/event" ) @@ -34,10 +35,6 @@ func (m *Manager) syncToLastSubmittedHeight() error { m.logger.Error("process next DA batch", "err", err) } - // if height havent been updated, we are stuck - /*if m.State.NextHeight() == currH { - return fmt.Errorf("stuck at height %d", currH) - }*/ m.logger.Info("Synced from DA", "store height", m.State.Height(), "target height", m.LastSubmittedHeight.Load()) } @@ -74,7 +71,7 @@ func (m *Manager) syncFromDABatch() error { } m.State.Sequencers.SetProposer(proposer) - err = m.ProcessNextDABatch(settlementBatch.MetaData.DA) + err = m.ProcessNextDABatch(settlementBatch) if err != nil { return fmt.Errorf("process next DA batch: %w", err) } @@ -102,7 +99,9 @@ func (m *Manager) applyLocalBlock(height uint64) error { return nil } -func (m *Manager) ProcessNextDABatch(daMetaData *da.DASubmitMetaData) error { +func (m *Manager) ProcessNextDABatch(batch *settlement.ResultRetrieveBatch) error { + + daMetaData := batch.MetaData.DA m.logger.Debug("trying to retrieve batch from DA", "daHeight", daMetaData.Height) batchResp := m.fetchBatch(daMetaData) if batchResp.Code != da.StatusSuccess { @@ -111,6 +110,13 @@ func (m *Manager) ProcessNextDABatch(daMetaData *da.DASubmitMetaData) error { m.logger.Debug("retrieved batches", "n", len(batchResp.Batches), "daHeight", daMetaData.Height) + if !m.isHeightFinalized(batchResp.Batches[len(batchResp.Batches)-1].EndHeight()) { + err := m.validateBatch(batch, batchResp.Batches) + if err != nil { + return err + } + } + m.retrieverMu.Lock() defer m.retrieverMu.Unlock() diff --git a/block/sync.go b/block/sync.go index 99b5b38dd..97153a814 100644 --- a/block/sync.go +++ b/block/sync.go @@ -8,7 +8,7 @@ import ( "github.com/tendermint/tendermint/libs/pubsub" ) -// onNewStateUpdate will try to sync to new height, if not already synced +// onNewStateUpdate will update the last submitted height and will func (m *Manager) onNewStateUpdate(event pubsub.Message) { eventData, ok := event.Data().(*settlement.EventDataNewBatchAccepted) if !ok { @@ -20,13 +20,12 @@ func (m *Manager) onNewStateUpdate(event pubsub.Message) { select { case m.syncingC <- eventData.EndHeight: default: - m.logger.Debug("disregarding new state update, since node is still syncing") + m.logger.Debug("disregarding new state update, node is still syncing") } } -// SyncTargetLoop is responsible for getting real time updates about settlement batch submissions. -// For non aggregator: updating the sync target which will be used by retrieveLoop to sync until this target. -// It publishes new sync height targets which will then be synced by another process. +// SyncTargetLoop listens for syncing events (from new state update or from initial syncing) and syncs to the last submitted height. +// In case the node is already synced, it validate func (m *Manager) SyncTargetLoop(ctx context.Context) error { for { @@ -36,19 +35,26 @@ func (m *Manager) SyncTargetLoop(ctx context.Context) error { case targetHeight := <-m.syncingC: err := m.UpdateSequencerSetFromSL() if err != nil { - return fmt.Errorf("update bonded sequencer set: %w", err) + m.logger.Error("update bonded sequencer set", "error", err) } - - err = m.syncToLastSubmittedHeight() - if err != nil { - return fmt.Errorf("syncing to target height: %d err:%w", targetHeight, err) + if m.State.Height() < targetHeight { + err = m.syncToLastSubmittedHeight() + if err != nil { + m.logger.Error("syncing to target height", "targetHeight", targetHeight, "error", err) + } + m.synced.Nudge() + m.logger.Info("Synced.", "current height", m.State.Height(), "last submitted height", m.LastSubmittedHeight.Load()) + } else { + err = m.validateToLastSubmittedHeight(targetHeight) + if err != nil { + return fmt.Errorf("syncing to target height: %d err:%w", targetHeight, err) + } } - m.synced.Nudge() - m.logger.Info("Synced.", "current height", m.State.Height(), "last submitted height", m.LastSubmittedHeight.Load()) } } } +// waitForSyncing waits for synced nudge (in case it needs to because it was syncing) func (m *Manager) waitForSyncing() { if m.State.Height() < m.LastSubmittedHeight.Load() { <-m.synced.C diff --git a/settlement/dymension/events.go b/settlement/dymension/events.go index 18f53cdef..05e31c06a 100644 --- a/settlement/dymension/events.go +++ b/settlement/dymension/events.go @@ -119,8 +119,9 @@ func convertToNewBatchEvent(rawEventData ctypes.ResultEvent) (*settlement.EventD } endHeight := uint64(startHeight + numBlocks - 1) NewBatchEvent := &settlement.EventDataNewBatchAccepted{ - EndHeight: endHeight, - StateIndex: uint64(stateIndex), + StartHeight: uint64(startHeight), + EndHeight: endHeight, + StateIndex: uint64(stateIndex), } return NewBatchEvent, nil } diff --git a/settlement/events.go b/settlement/events.go index f5e31f8ef..d019c6022 100644 --- a/settlement/events.go +++ b/settlement/events.go @@ -33,6 +33,7 @@ var ( // Data type EventDataNewBatchAccepted struct { + StartHeight uint64 // EndHeight is the height of the last accepted batch EndHeight uint64 // StateIndex is the rollapp-specific index the batch was saved in the SL diff --git a/store/store.go b/store/store.go index 9bdb8075d..dfafe7d1f 100644 --- a/store/store.go +++ b/store/store.go @@ -1,6 +1,7 @@ package store import ( + "bytes" "encoding/binary" "errors" "fmt" @@ -23,6 +24,12 @@ var ( responsesPrefix = [1]byte{5} sequencersPrefix = [1]byte{6} cidPrefix = [1]byte{7} + validationPrefix = [1]byte{8} +) + +var ( + blockNonValidated = [1]byte{0} + blockValidated = [1]byte{1} ) // DefaultStore is a default store implementation. @@ -116,6 +123,33 @@ func (s *DefaultStore) LoadBlockByHash(hash [32]byte) (*types.Block, error) { return block, nil } +// SaveBlockValidation saves block validation in Store. +func (s *DefaultStore) SaveBlockValidation(height uint64, validation bool, batch KVBatch) (KVBatch, error) { + + var data []byte + if validation { + data = blockValidated[:] + } else { + data = blockNonValidated[:] + } + err := batch.Set(getValidationKey(height), data) + return batch, err +} + +// LoadBlockValidation returns block validation in Store. +func (s *DefaultStore) LoadBlockValidation(height uint64) (bool, error) { + + data, err := s.db.Get(getValidationKey(height)) + if err != nil { + return false, fmt.Errorf("retrieve block results from height %v: %w", height, err) + } + if bytes.Equal(data[:], blockValidated[:]) { + return true, nil + } + return false, nil + +} + // SaveBlockResponses saves block responses (events, tx responses, etc) in Store. func (s *DefaultStore) SaveBlockResponses(height uint64, responses *tmstate.ABCIResponses, batch KVBatch) (KVBatch, error) { data, err := responses.Marshal() @@ -332,3 +366,9 @@ func getCidKey(height uint64) []byte { binary.BigEndian.PutUint64(buf, height) return append(cidPrefix[:], buf[:]...) } + +func getValidationKey(height uint64) []byte { + buf := make([]byte, 8) + binary.BigEndian.PutUint64(buf, height) + return append(validationPrefix[:], buf[:]...) +} diff --git a/store/storeIface.go b/store/storeIface.go index 3061fc936..f8e9e4780 100644 --- a/store/storeIface.go +++ b/store/storeIface.go @@ -78,4 +78,8 @@ type Store interface { SaveBlockCid(height uint64, cid cid.Cid, batch KVBatch) (KVBatch, error) LoadBlockCid(height uint64) (cid.Cid, error) + + SaveBlockValidation(height uint64, validation bool, batch KVBatch) (KVBatch, error) + + LoadBlockValidation(height uint64) (bool, error) } From 619c080a0c2e8bc9e26755de0fdea418f8305a09 Mon Sep 17 00:00:00 2001 From: Sergi Rene Date: Sat, 5 Oct 2024 11:28:36 +0200 Subject: [PATCH 04/29] fix + comments --- block/sync.go | 20 +++++++++++++------- store/store.go | 3 +++ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/block/sync.go b/block/sync.go index 97153a814..7fb6bc600 100644 --- a/block/sync.go +++ b/block/sync.go @@ -8,13 +8,14 @@ import ( "github.com/tendermint/tendermint/libs/pubsub" ) -// onNewStateUpdate will update the last submitted height and will +// onNewStateUpdate will update the last submitted height and will update sequencers list from SL func (m *Manager) onNewStateUpdate(event pubsub.Message) { eventData, ok := event.Data().(*settlement.EventDataNewBatchAccepted) if !ok { m.logger.Error("onReceivedBatch", "err", "wrong event data received") return } + m.LastSubmittedHeight.Store(eventData.EndHeight) m.UpdateTargetHeight(eventData.EndHeight) select { @@ -22,6 +23,12 @@ func (m *Manager) onNewStateUpdate(event pubsub.Message) { default: m.logger.Debug("disregarding new state update, node is still syncing") } + + err := m.UpdateSequencerSetFromSL() + if err != nil { + m.logger.Error("update bonded sequencer set", "error", err) + } + } // SyncTargetLoop listens for syncing events (from new state update or from initial syncing) and syncs to the last submitted height. @@ -33,19 +40,18 @@ func (m *Manager) SyncTargetLoop(ctx context.Context) error { case <-ctx.Done(): return nil case targetHeight := <-m.syncingC: - err := m.UpdateSequencerSetFromSL() - if err != nil { - m.logger.Error("update bonded sequencer set", "error", err) - } + + // if the node is still not at the state update end height, it will try to sync to it (validation is included in syncing from DA) + // if the node is already in the height, it will check if any pending validations heights (because blocks were applied from P2P) and validate them if m.State.Height() < targetHeight { - err = m.syncToLastSubmittedHeight() + err := m.syncToLastSubmittedHeight() if err != nil { m.logger.Error("syncing to target height", "targetHeight", targetHeight, "error", err) } m.synced.Nudge() m.logger.Info("Synced.", "current height", m.State.Height(), "last submitted height", m.LastSubmittedHeight.Load()) } else { - err = m.validateToLastSubmittedHeight(targetHeight) + err := m.validateToLastSubmittedHeight(targetHeight) if err != nil { return fmt.Errorf("syncing to target height: %d err:%w", targetHeight, err) } diff --git a/store/store.go b/store/store.go index dfafe7d1f..8f1597822 100644 --- a/store/store.go +++ b/store/store.go @@ -132,6 +132,9 @@ func (s *DefaultStore) SaveBlockValidation(height uint64, validation bool, batch } else { data = blockNonValidated[:] } + if batch == nil { + return nil, s.db.Set(getValidationKey(height), data) + } err := batch.Set(getValidationKey(height), data) return batch, err } From fdd82c4f51a993448eecf3dc4deb25da22e370b4 Mon Sep 17 00:00:00 2001 From: Sergi Rene Date: Sun, 6 Oct 2024 08:57:19 +0200 Subject: [PATCH 05/29] adding validation --- block/manager.go | 2 +- block/retriever.go | 21 ++++------------- block/sync.go | 44 +++++++++++++++++++---------------- settlement/dymension/utils.go | 13 +++++++++++ settlement/settlement.go | 22 ++++++++++++++---- 5 files changed, 60 insertions(+), 42 deletions(-) diff --git a/block/manager.go b/block/manager.go index 43d9b7074..488a9d151 100644 --- a/block/manager.go +++ b/block/manager.go @@ -183,7 +183,7 @@ func (m *Manager) Start(ctx context.Context) error { }) uerrors.ErrGroupGoLog(eg, m.logger, func() error { - return m.SyncTargetLoop(ctx) + return m.SyncLoop(ctx) }) err = m.syncFromSettlement() diff --git a/block/retriever.go b/block/retriever.go index f3f9b3c70..1b80e1efe 100644 --- a/block/retriever.go +++ b/block/retriever.go @@ -79,6 +79,7 @@ func (m *Manager) syncFromDABatch() error { } func (m *Manager) applyLocalBlock(height uint64) error { + defer m.retrieverMu.Unlock() m.retrieverMu.Lock() @@ -99,24 +100,12 @@ func (m *Manager) applyLocalBlock(height uint64) error { return nil } -func (m *Manager) ProcessNextDABatch(batch *settlement.ResultRetrieveBatch) error { - - daMetaData := batch.MetaData.DA - m.logger.Debug("trying to retrieve batch from DA", "daHeight", daMetaData.Height) - batchResp := m.fetchBatch(daMetaData) - if batchResp.Code != da.StatusSuccess { - return batchResp.Error - } +func (m *Manager) ProcessNextDABatch(slBatch *settlement.ResultRetrieveBatch) error { - m.logger.Debug("retrieved batches", "n", len(batchResp.Batches), "daHeight", daMetaData.Height) + batchResp, err := m.validateStateUpdate(slBatch) + if err != nil { - if !m.isHeightFinalized(batchResp.Batches[len(batchResp.Batches)-1].EndHeight()) { - err := m.validateBatch(batch, batchResp.Batches) - if err != nil { - return err - } } - m.retrieverMu.Lock() defer m.retrieverMu.Unlock() @@ -132,7 +121,7 @@ func (m *Manager) ProcessNextDABatch(batch *settlement.ResultRetrieveBatch) erro } // We dont validate because validateBlockBeforeApply already checks if the block is already applied, and we don't need to fail there. - err := m.applyBlockWithFraudHandling(block, batch.Commits[i], types.BlockMetaData{Source: types.DA, DAHeight: daMetaData.Height}, false) + err := m.applyBlockWithFraudHandling(block, batch.Commits[i], types.BlockMetaData{Source: types.DA, DAHeight: slBatch.Batch.MetaData.DA.Height}, false) if err != nil { return fmt.Errorf("apply block: height: %d: %w", block.Header.Height, err) } diff --git a/block/sync.go b/block/sync.go index 7fb6bc600..1d5191f72 100644 --- a/block/sync.go +++ b/block/sync.go @@ -2,7 +2,6 @@ package block import ( "context" - "fmt" "github.com/dymensionxyz/dymint/settlement" "github.com/tendermint/tendermint/libs/pubsub" @@ -18,10 +17,23 @@ func (m *Manager) onNewStateUpdate(event pubsub.Message) { m.LastSubmittedHeight.Store(eventData.EndHeight) m.UpdateTargetHeight(eventData.EndHeight) - select { - case m.syncingC <- eventData.EndHeight: - default: - m.logger.Debug("disregarding new state update, node is still syncing") + + // If node is not synced trigger syncing from DA (includes validation). If it is already synced validate state update + if m.State.Height() < eventData.EndHeight { + select { + case m.syncingC <- eventData.EndHeight: + default: + m.logger.Debug("disregarding new state update, node is still syncing") + } + } else { + batch, err := m.SLClient.GetBatchAtIndex(eventData.StateIndex) + if err != nil { + m.logger.Error("state update retrieval", "error", err) + } + _, err = m.validateStateUpdate(batch) + if err != nil { + m.logger.Error("state update validation", "error", err) + } } err := m.UpdateSequencerSetFromSL() @@ -33,7 +45,7 @@ func (m *Manager) onNewStateUpdate(event pubsub.Message) { // SyncTargetLoop listens for syncing events (from new state update or from initial syncing) and syncs to the last submitted height. // In case the node is already synced, it validate -func (m *Manager) SyncTargetLoop(ctx context.Context) error { +func (m *Manager) SyncLoop(ctx context.Context) error { for { select { @@ -41,21 +53,13 @@ func (m *Manager) SyncTargetLoop(ctx context.Context) error { return nil case targetHeight := <-m.syncingC: - // if the node is still not at the state update end height, it will try to sync to it (validation is included in syncing from DA) - // if the node is already in the height, it will check if any pending validations heights (because blocks were applied from P2P) and validate them - if m.State.Height() < targetHeight { - err := m.syncToLastSubmittedHeight() - if err != nil { - m.logger.Error("syncing to target height", "targetHeight", targetHeight, "error", err) - } - m.synced.Nudge() - m.logger.Info("Synced.", "current height", m.State.Height(), "last submitted height", m.LastSubmittedHeight.Load()) - } else { - err := m.validateToLastSubmittedHeight(targetHeight) - if err != nil { - return fmt.Errorf("syncing to target height: %d err:%w", targetHeight, err) - } + err := m.syncToLastSubmittedHeight() + if err != nil { + m.logger.Error("syncing to target height", "targetHeight", targetHeight, "error", err) } + m.synced.Nudge() + m.logger.Info("Synced.", "current height", m.State.Height(), "last submitted height", m.LastSubmittedHeight.Load()) + } } } diff --git a/settlement/dymension/utils.go b/settlement/dymension/utils.go index d071dfe9a..01ad60c6d 100644 --- a/settlement/dymension/utils.go +++ b/settlement/dymension/utils.go @@ -38,6 +38,16 @@ func convertStateInfoToResultRetrieveBatch(stateInfo *rollapptypes.StateInfo) (* if err != nil { return nil, err } + bds := []settlement.BlockDescriptor{} + for _, blockDescriptor := range stateInfo.BDs.BD { + bd := settlement.BlockDescriptor{ + Height: blockDescriptor.Height, + StateRoot: blockDescriptor.StateRoot, + Timestamp: blockDescriptor.Timestamp, + } + bds = append(bds, bd) + } + batchResult := &settlement.Batch{ Sequencer: stateInfo.Sequencer, StartHeight: stateInfo.StartHeight, @@ -45,7 +55,10 @@ func convertStateInfoToResultRetrieveBatch(stateInfo *rollapptypes.StateInfo) (* MetaData: &settlement.BatchMetaData{ DA: daMetaData, }, + DRSVersion: stateInfo.DrsVersion, + BlockDescriptors: bds, } + return &settlement.ResultRetrieveBatch{ ResultBase: settlement.ResultBase{Code: settlement.StatusSuccess, StateIndex: stateInfo.StateInfoIndex.Index}, Batch: batchResult, diff --git a/settlement/settlement.go b/settlement/settlement.go index 91dea7fdb..b01647ac0 100644 --- a/settlement/settlement.go +++ b/settlement/settlement.go @@ -1,6 +1,8 @@ package settlement import ( + "time" + "github.com/dymensionxyz/dymint/da" "github.com/dymensionxyz/dymint/types" "github.com/tendermint/tendermint/libs/pubsub" @@ -31,14 +33,24 @@ type BatchMetaData struct { DA *da.DASubmitMetaData } +type BlockDescriptor struct { + // height is the height of the block + Height uint64 + // stateRoot is a 32 byte array of the hash of the block (state root of the block) + StateRoot []byte + // timestamp is the time from the block header + Timestamp time.Time +} + type Batch struct { // sequencer is the bech32-encoded address of the sequencer sent the update - Sequencer string - StartHeight uint64 - EndHeight uint64 - AppHashes [][32]byte + Sequencer string + StartHeight uint64 + EndHeight uint64 + BlockDescriptors []BlockDescriptor // MetaData about the batch in the DA layer - MetaData *BatchMetaData + MetaData *BatchMetaData + DRSVersion string } type ResultRetrieveBatch struct { From dfa22a53e62a8404239fdd96d499f10d591c8ca6 Mon Sep 17 00:00:00 2001 From: Sergi Rene Date: Sun, 6 Oct 2024 09:08:21 +0200 Subject: [PATCH 06/29] mock and grpc sl fix --- block/retriever.go | 2 +- p2p/block.go | 5 ++--- settlement/grpc/grpc.go | 16 +++++++++++++--- settlement/local/local.go | 15 ++++++++++++--- 4 files changed, 28 insertions(+), 10 deletions(-) diff --git a/block/retriever.go b/block/retriever.go index 1b80e1efe..fddbe9f83 100644 --- a/block/retriever.go +++ b/block/retriever.go @@ -104,7 +104,7 @@ func (m *Manager) ProcessNextDABatch(slBatch *settlement.ResultRetrieveBatch) er batchResp, err := m.validateStateUpdate(slBatch) if err != nil { - + //TODO (srene): handle fraud } m.retrieverMu.Lock() defer m.retrieverMu.Unlock() diff --git a/p2p/block.go b/p2p/block.go index b74f92368..031827ba5 100644 --- a/p2p/block.go +++ b/p2p/block.go @@ -55,12 +55,11 @@ func (b *BlockData) FromProto(other *pb.BlockData) error { // Validate run basic validation on the p2p block received func (b *BlockData) Validate(proposerPubKey tmcrypto.PubKey) error { + if err := b.Block.ValidateBasic(); err != nil { return err } - if err := b.Commit.ValidateBasic(); err != nil { - return err - } + if err := b.Commit.ValidateWithHeader(proposerPubKey, &b.Block.Header); err != nil { return err } diff --git a/settlement/grpc/grpc.go b/settlement/grpc/grpc.go index 67c62e0d3..ff863212e 100644 --- a/settlement/grpc/grpc.go +++ b/settlement/grpc/grpc.go @@ -273,6 +273,17 @@ func (c *Client) saveBatch(batch *settlement.Batch) error { } func (c *Client) convertBatchtoSettlementBatch(batch *types.Batch, daResult *da.ResultSubmitBatch) *settlement.Batch { + + bds := []settlement.BlockDescriptor{} + for _, block := range batch.Blocks { + bd := settlement.BlockDescriptor{ + Height: block.Header.Height, + StateRoot: block.Header.AppHash[:], + Timestamp: block.Header.GetTimestamp(), + } + bds = append(bds, bd) + } + settlementBatch := &settlement.Batch{ Sequencer: c.GetProposer().SettlementAddress, StartHeight: batch.StartHeight(), @@ -283,10 +294,9 @@ func (c *Client) convertBatchtoSettlementBatch(batch *types.Batch, daResult *da. Client: daResult.SubmitMetaData.Client, }, }, + BlockDescriptors: bds, } - for _, block := range batch.Blocks { - settlementBatch.AppHashes = append(settlementBatch.AppHashes, block.Header.AppHash) - } + return settlementBatch } diff --git a/settlement/local/local.go b/settlement/local/local.go index b085a567b..78663a26f 100644 --- a/settlement/local/local.go +++ b/settlement/local/local.go @@ -267,6 +267,16 @@ func (c *Client) retrieveBatchAtStateIndex(slStateIndex uint64) (*settlement.Res } func (c *Client) convertBatchToSettlementBatch(batch *types.Batch, daResult *da.ResultSubmitBatch) *settlement.Batch { + bds := []settlement.BlockDescriptor{} + for _, block := range batch.Blocks { + bd := settlement.BlockDescriptor{ + Height: block.Header.Height, + StateRoot: block.Header.AppHash[:], + Timestamp: block.Header.GetTimestamp(), + } + bds = append(bds, bd) + } + settlementBatch := &settlement.Batch{ Sequencer: c.GetProposer().SettlementAddress, StartHeight: batch.StartHeight(), @@ -277,10 +287,9 @@ func (c *Client) convertBatchToSettlementBatch(batch *types.Batch, daResult *da. Client: daResult.SubmitMetaData.Client, }, }, + BlockDescriptors: bds, } - for _, block := range batch.Blocks { - settlementBatch.AppHashes = append(settlementBatch.AppHashes, block.Header.AppHash) - } + return settlementBatch } From 132c4199480a39f491364f007d2c53c4d95f4301 Mon Sep 17 00:00:00 2001 From: Sergi Rene Date: Sun, 6 Oct 2024 22:11:19 +0200 Subject: [PATCH 07/29] validator --- block/manager.go | 4 ++++ block/retriever.go | 2 +- block/sync.go | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/block/manager.go b/block/manager.go index 488a9d151..72b2d4bf2 100644 --- a/block/manager.go +++ b/block/manager.go @@ -90,6 +90,8 @@ type Manager struct { syncingC chan uint64 synced *uchannel.Nudger + + validator *StateUpdateValidator } // NewManager creates new block Manager. @@ -153,6 +155,8 @@ func NewManager( return nil, err } + m.validator = NewStateUpdateValidator(m.logger, m.Retriever) + return m, nil } diff --git a/block/retriever.go b/block/retriever.go index fddbe9f83..b7bc5ef64 100644 --- a/block/retriever.go +++ b/block/retriever.go @@ -102,7 +102,7 @@ func (m *Manager) applyLocalBlock(height uint64) error { func (m *Manager) ProcessNextDABatch(slBatch *settlement.ResultRetrieveBatch) error { - batchResp, err := m.validateStateUpdate(slBatch) + batchResp, err := m.validator.ValidateStateUpdate(slBatch) if err != nil { //TODO (srene): handle fraud } diff --git a/block/sync.go b/block/sync.go index 1d5191f72..c665dd10a 100644 --- a/block/sync.go +++ b/block/sync.go @@ -30,7 +30,7 @@ func (m *Manager) onNewStateUpdate(event pubsub.Message) { if err != nil { m.logger.Error("state update retrieval", "error", err) } - _, err = m.validateStateUpdate(batch) + _, err = m.validator.ValidateStateUpdate(batch) if err != nil { m.logger.Error("state update validation", "error", err) } From d54ab5eea2beecc766e9c4e3a017cf5d50a1ae6d Mon Sep 17 00:00:00 2001 From: Sergi Rene Date: Sun, 6 Oct 2024 22:12:40 +0200 Subject: [PATCH 08/29] mising file --- block/validator.go | 135 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 block/validator.go diff --git a/block/validator.go b/block/validator.go new file mode 100644 index 000000000..3134b5f1c --- /dev/null +++ b/block/validator.go @@ -0,0 +1,135 @@ +package block + +import ( + "bytes" + "crypto/sha256" + "fmt" + + "github.com/dymensionxyz/dymint/da" + "github.com/dymensionxyz/dymint/settlement" + "github.com/dymensionxyz/dymint/types" +) + +type P2PBlockValidator interface { + ValidateP2PBlocks(daBlocks []*types.Block, p2pBlocks []*types.Block) error +} + +type DABlocksValidator interface { + ValidateStateUpdate(batch *settlement.ResultRetrieveBatch) (da.ResultRetrieveBatch, error) +} + +// StateUpdateValidator is a validator for messages gossiped in the p2p network. +type StateUpdateValidator struct { + logger types.Logger + retriever da.BatchRetriever +} + +var _ DABlocksValidator = &StateUpdateValidator{} +var _ P2PBlockValidator = &StateUpdateValidator{} + +// NewValidator creates a new Validator. +func NewStateUpdateValidator(logger types.Logger, daClient da.BatchRetriever) *StateUpdateValidator { + return &StateUpdateValidator{ + logger: logger, + retriever: daClient, + } +} + +func (v *StateUpdateValidator) ValidateStateUpdate(batch *settlement.ResultRetrieveBatch) (da.ResultRetrieveBatch, error) { + + //TODO(srene): check if pending validations because applied blocks from cache. + + err := v.validateDRS(batch.StartHeight, batch.EndHeight, batch.DRSVersion) + if err != nil { + return da.ResultRetrieveBatch{}, err + } + daBatch := v.retriever.RetrieveBatches(batch.MetaData.DA) + if daBatch.Code != da.StatusSuccess { + return da.ResultRetrieveBatch{}, daBatch.Error + } + + var daBlocks []*types.Block + for _, batch := range daBatch.Batches { + daBlocks = append(daBlocks, batch.Blocks...) + } + + err = v.validateDaBlocks(batch, daBlocks) + if err != nil { + return da.ResultRetrieveBatch{}, err + } + + return daBatch, nil +} + +func (v *StateUpdateValidator) ValidateP2PBlocks(daBlocks []*types.Block, p2pBlocks []*types.Block) error { + if len(daBlocks) != len(p2pBlocks) { + return fmt.Errorf("comparing different number of blocks between P2P blocks and DA blocks. Start height: %d End Height: %d", daBlocks[0].Header.Height, daBlocks[len(daBlocks)-1].Header.Height) + } + + for i, p2pBlock := range daBlocks { + + p2pBlockHash, err := blockHash(p2pBlock) + if err != nil { + return err + } + daBlockHash, err := blockHash(daBlocks[i]) + if err != nil { + return err + } + if !bytes.Equal(p2pBlockHash, daBlockHash) { + return fmt.Errorf("failed comparing blocks") + } + } + return nil +} + +// isHeightFinalized is required to skip old blocks validation +func (v *StateUpdateValidator) isHeightFinalized(height uint64) bool { + return false +} + +func (v *StateUpdateValidator) validateDaBlocks(slBatch *settlement.ResultRetrieveBatch, daBlocks []*types.Block) error { + + // check numblocks + numSlBlocks := len(slBatch.BlockDescriptors) + numDABlocks := len(daBlocks) + if numSlBlocks != numDABlocks { + return fmt.Errorf("num blocks mismatch between state update and DA batch. State index: %d State update blocks: %d DA batch blocks: %d", slBatch.StateIndex, numSlBlocks, numDABlocks) + } + + // check blocks + for i, bd := range slBatch.BlockDescriptors { + // height check + if bd.Height != daBlocks[i].Header.Height { + return fmt.Errorf("height mismatch between state update and DA batch. State index: %d SL height: %d DA height: %d", slBatch.StateIndex, bd.Height, daBlocks[i].Header.Height) + } + // we compare the state root between SL state info and DA block + if !bytes.Equal(bd.StateRoot, daBlocks[i].Header.AppHash[:]) { + return fmt.Errorf("state root mismatch between state update and DA batch. State index: %d: Height: %d State root SL: %d State root DA: %d", slBatch.StateIndex, bd.Height, bd.StateRoot, daBlocks[i].Header.AppHash[:]) + } + + // we compare the timestamp between SL state info and DA block + if bd.Timestamp != daBlocks[i].Header.GetTimestamp() { + return fmt.Errorf("timestamp mismatch between state update and DA batch. State index: %d: Height: %d Timestamp SL: %s Timestamp DA: %s", slBatch.StateIndex, bd.Height, bd.Timestamp, daBlocks[i].Header.GetTimestamp()) + } + } + + //TODO(srene): implement sequencer address validation + return nil +} + +func (v *StateUpdateValidator) validateDRS(startHeight, endHeight uint64, version string) error { + + //TODO(srene): implement DRS/height verification + return nil +} + +func blockHash(block *types.Block) ([]byte, error) { + blockBytes, err := block.MarshalBinary() + if err != nil { + return nil, fmt.Errorf("error marshal p2pblock. err: %w", err) + } + h := sha256.New() + h.Write(blockBytes) + return h.Sum(nil), nil +} From 3cd9f41b4d5eabc0739943945e1ce1bc50627dd1 Mon Sep 17 00:00:00 2001 From: Sergi Rene Date: Mon, 7 Oct 2024 12:08:56 +0200 Subject: [PATCH 09/29] remove validation from sync --- block/manager_test.go | 20 ++++---------------- block/retriever.go | 17 ++++++++++------- 2 files changed, 14 insertions(+), 23 deletions(-) diff --git a/block/manager_test.go b/block/manager_test.go index 6667e2406..4e2e7b7a9 100644 --- a/block/manager_test.go +++ b/block/manager_test.go @@ -404,14 +404,8 @@ func TestRetrieveDaBatchesFailed(t *testing.T) { Client: da.Mock, Height: 1, } - batch := &settlement.ResultRetrieveBatch{ - Batch: &settlement.Batch{ - MetaData: &settlement.BatchMetaData{ - DA: daMetaData, - }, - }, - } - err = manager.ProcessNextDABatch(batch) + + err = manager.ProcessNextDABatch(daMetaData) t.Log(err) assert.ErrorIs(t, err, da.ErrBlobNotFound) } @@ -753,14 +747,8 @@ func TestDAFetch(t *testing.T) { LastBlockHeight: int64(batch.EndHeight()), LastBlockAppHash: commitHash[:], }) - batch := &settlement.ResultRetrieveBatch{ - Batch: &settlement.Batch{ - MetaData: &settlement.BatchMetaData{ - DA: c.daMetaData, - }, - }, - } - err := manager.ProcessNextDABatch(batch) + + err := manager.ProcessNextDABatch(c.daMetaData) require.Equal(c.err, err) }) } diff --git a/block/retriever.go b/block/retriever.go index b7bc5ef64..810c027f2 100644 --- a/block/retriever.go +++ b/block/retriever.go @@ -9,7 +9,6 @@ import ( "github.com/dymensionxyz/dymint/da" "github.com/dymensionxyz/dymint/node/events" - "github.com/dymensionxyz/dymint/settlement" "github.com/dymensionxyz/dymint/types" uevent "github.com/dymensionxyz/dymint/utils/event" ) @@ -71,7 +70,7 @@ func (m *Manager) syncFromDABatch() error { } m.State.Sequencers.SetProposer(proposer) - err = m.ProcessNextDABatch(settlementBatch) + err = m.ProcessNextDABatch(settlementBatch.MetaData.DA) if err != nil { return fmt.Errorf("process next DA batch: %w", err) } @@ -100,12 +99,16 @@ func (m *Manager) applyLocalBlock(height uint64) error { return nil } -func (m *Manager) ProcessNextDABatch(slBatch *settlement.ResultRetrieveBatch) error { +func (m *Manager) ProcessNextDABatch(daMetaData *da.DASubmitMetaData) error { - batchResp, err := m.validator.ValidateStateUpdate(slBatch) - if err != nil { - //TODO (srene): handle fraud + m.logger.Debug("trying to retrieve batch from DA", "daHeight", daMetaData.Height) + batchResp := m.fetchBatch(daMetaData) + if batchResp.Code != da.StatusSuccess { + return batchResp.Error } + + m.logger.Debug("retrieved batches", "n", len(batchResp.Batches), "daHeight", daMetaData.Height) + m.retrieverMu.Lock() defer m.retrieverMu.Unlock() @@ -121,7 +124,7 @@ func (m *Manager) ProcessNextDABatch(slBatch *settlement.ResultRetrieveBatch) er } // We dont validate because validateBlockBeforeApply already checks if the block is already applied, and we don't need to fail there. - err := m.applyBlockWithFraudHandling(block, batch.Commits[i], types.BlockMetaData{Source: types.DA, DAHeight: slBatch.Batch.MetaData.DA.Height}, false) + err := m.applyBlockWithFraudHandling(block, batch.Commits[i], types.BlockMetaData{Source: types.DA, DAHeight: daMetaData.Height}, false) if err != nil { return fmt.Errorf("apply block: height: %d: %w", block.Header.Height, err) } From 3048f2e75d3ba3031e26ccf06f31c062184e101f Mon Sep 17 00:00:00 2001 From: Sergi Rene Date: Mon, 7 Oct 2024 13:57:19 +0200 Subject: [PATCH 10/29] validation loop refactor + storing block source --- block/block.go | 6 +-- block/manager.go | 2 +- block/sync.go | 31 +++++++--------- block/validator.go | 89 +++++++++++++++++++++++++++++++-------------- store/store.go | 36 ++++++------------ store/storeIface.go | 4 +- 6 files changed, 91 insertions(+), 77 deletions(-) diff --git a/block/block.go b/block/block.go index 321a5d3db..2b92d1fdf 100644 --- a/block/block.go +++ b/block/block.go @@ -92,11 +92,7 @@ func (m *Manager) applyBlock(block *types.Block, commit *types.Commit, blockMeta return fmt.Errorf("save block responses: %w", err) } - validated := false - if blockMetaData.Source == types.DA { - validated = true - } - _, err = m.Store.SaveBlockValidation(block.Header.Height, validated, nil) + _, err = m.Store.SaveBlockSource(block.Header.Height, blockMetaData.Source.String(), nil) if err != nil { return fmt.Errorf("save block responses: %w", err) } diff --git a/block/manager.go b/block/manager.go index 72b2d4bf2..3a1e3c949 100644 --- a/block/manager.go +++ b/block/manager.go @@ -155,7 +155,7 @@ func NewManager( return nil, err } - m.validator = NewStateUpdateValidator(m.logger, m.Retriever) + m.validator = NewStateUpdateValidator(m.logger, m) return m, nil } diff --git a/block/sync.go b/block/sync.go index c665dd10a..9965e6441 100644 --- a/block/sync.go +++ b/block/sync.go @@ -15,28 +15,25 @@ func (m *Manager) onNewStateUpdate(event pubsub.Message) { return } + // Update heights based on state update end height m.LastSubmittedHeight.Store(eventData.EndHeight) m.UpdateTargetHeight(eventData.EndHeight) - // If node is not synced trigger syncing from DA (includes validation). If it is already synced validate state update - if m.State.Height() < eventData.EndHeight { - select { - case m.syncingC <- eventData.EndHeight: - default: - m.logger.Debug("disregarding new state update, node is still syncing") - } - } else { - batch, err := m.SLClient.GetBatchAtIndex(eventData.StateIndex) - if err != nil { - m.logger.Error("state update retrieval", "error", err) - } - _, err = m.validator.ValidateStateUpdate(batch) - if err != nil { - m.logger.Error("state update validation", "error", err) - } + // If node is not synced, trigger syncing from DA. + select { + case m.syncingC <- eventData.EndHeight: + default: + m.logger.Debug("disregarding new state update, node is still syncing") + } + + // Validate new state update + err := m.validator.ValidateStateUpdate(eventData.StateIndex) + if err != nil { + m.logger.Error("state update validation", "error", err) } - err := m.UpdateSequencerSetFromSL() + // Update sequencers list from SL + err = m.UpdateSequencerSetFromSL() if err != nil { m.logger.Error("update bonded sequencer set", "error", err) } diff --git a/block/validator.go b/block/validator.go index 3134b5f1c..118c7a643 100644 --- a/block/validator.go +++ b/block/validator.go @@ -15,64 +15,99 @@ type P2PBlockValidator interface { } type DABlocksValidator interface { - ValidateStateUpdate(batch *settlement.ResultRetrieveBatch) (da.ResultRetrieveBatch, error) + ValidateDaBlocks(slBatch *settlement.ResultRetrieveBatch, daBlocks []*types.Block) error } // StateUpdateValidator is a validator for messages gossiped in the p2p network. type StateUpdateValidator struct { - logger types.Logger - retriever da.BatchRetriever + logger types.Logger + blockManager *Manager } var _ DABlocksValidator = &StateUpdateValidator{} var _ P2PBlockValidator = &StateUpdateValidator{} // NewValidator creates a new Validator. -func NewStateUpdateValidator(logger types.Logger, daClient da.BatchRetriever) *StateUpdateValidator { +func NewStateUpdateValidator(logger types.Logger, blockManager *Manager) *StateUpdateValidator { return &StateUpdateValidator{ - logger: logger, - retriever: daClient, + logger: logger, + blockManager: blockManager, } } -func (v *StateUpdateValidator) ValidateStateUpdate(batch *settlement.ResultRetrieveBatch) (da.ResultRetrieveBatch, error) { +func (v *StateUpdateValidator) ValidateStateUpdate(stateIndex uint64) error { - //TODO(srene): check if pending validations because applied blocks from cache. - - err := v.validateDRS(batch.StartHeight, batch.EndHeight, batch.DRSVersion) + batch, err := v.blockManager.SLClient.GetBatchAtIndex(stateIndex) if err != nil { - return da.ResultRetrieveBatch{}, err + return err } - daBatch := v.retriever.RetrieveBatches(batch.MetaData.DA) - if daBatch.Code != da.StatusSuccess { - return da.ResultRetrieveBatch{}, daBatch.Error + + err = v.validateDRS(batch.StartHeight, batch.EndHeight, batch.DRSVersion) + if err != nil { + return err } var daBlocks []*types.Block - for _, batch := range daBatch.Batches { - daBlocks = append(daBlocks, batch.Blocks...) + var p2pBlocks []*types.Block + + //load blocks for the batch height, either P2P or DA blocks + for height := batch.StartHeight; height <= batch.EndHeight; height++ { + source, err := v.blockManager.Store.LoadBlockSource(height) + if err != nil { + return err + } + block, err := v.blockManager.Store.LoadBlock(height) + if err != nil { + return err + } + if source == types.DA.String() { + daBlocks = append(daBlocks, block) + } else { + p2pBlocks = append(p2pBlocks, block) + } + } + + numBlocks := batch.EndHeight - batch.StartHeight + 1 + if uint64(len(daBlocks)) != numBlocks { + daBatch := v.blockManager.Retriever.RetrieveBatches(batch.MetaData.DA) + if daBatch.Code != da.StatusSuccess { + return daBatch.Error + } + + for _, batch := range daBatch.Batches { + daBlocks = append(daBlocks, batch.Blocks...) + } } - err = v.validateDaBlocks(batch, daBlocks) + err = v.ValidateDaBlocks(batch, daBlocks) if err != nil { - return da.ResultRetrieveBatch{}, err + return err + } + + if len(p2pBlocks) > 0 { + err = v.ValidateP2PBlocks(daBlocks, p2pBlocks) + if err != nil { + return err + } } - return daBatch, nil + return nil } func (v *StateUpdateValidator) ValidateP2PBlocks(daBlocks []*types.Block, p2pBlocks []*types.Block) error { - if len(daBlocks) != len(p2pBlocks) { - return fmt.Errorf("comparing different number of blocks between P2P blocks and DA blocks. Start height: %d End Height: %d", daBlocks[0].Header.Height, daBlocks[len(daBlocks)-1].Header.Height) - } - for i, p2pBlock := range daBlocks { + i := 0 + for _, daBlock := range daBlocks { - p2pBlockHash, err := blockHash(p2pBlock) + if p2pBlocks[i].Header.Height != daBlocks[i].Header.Height { + break + } + p2pBlockHash, err := blockHash(p2pBlocks[i]) if err != nil { return err } - daBlockHash, err := blockHash(daBlocks[i]) + i++ + daBlockHash, err := blockHash(daBlock) if err != nil { return err } @@ -88,7 +123,7 @@ func (v *StateUpdateValidator) isHeightFinalized(height uint64) bool { return false } -func (v *StateUpdateValidator) validateDaBlocks(slBatch *settlement.ResultRetrieveBatch, daBlocks []*types.Block) error { +func (v *StateUpdateValidator) ValidateDaBlocks(slBatch *settlement.ResultRetrieveBatch, daBlocks []*types.Block) error { // check numblocks numSlBlocks := len(slBatch.BlockDescriptors) @@ -127,7 +162,7 @@ func (v *StateUpdateValidator) validateDRS(startHeight, endHeight uint64, versio func blockHash(block *types.Block) ([]byte, error) { blockBytes, err := block.MarshalBinary() if err != nil { - return nil, fmt.Errorf("error marshal p2pblock. err: %w", err) + return nil, fmt.Errorf("error hashing block. err: %w", err) } h := sha256.New() h.Write(blockBytes) diff --git a/store/store.go b/store/store.go index 8f1597822..9a2160325 100644 --- a/store/store.go +++ b/store/store.go @@ -1,7 +1,6 @@ package store import ( - "bytes" "encoding/binary" "errors" "fmt" @@ -24,12 +23,7 @@ var ( responsesPrefix = [1]byte{5} sequencersPrefix = [1]byte{6} cidPrefix = [1]byte{7} - validationPrefix = [1]byte{8} -) - -var ( - blockNonValidated = [1]byte{0} - blockValidated = [1]byte{1} + sourcePrefix = [1]byte{8} ) // DefaultStore is a default store implementation. @@ -124,32 +118,24 @@ func (s *DefaultStore) LoadBlockByHash(hash [32]byte) (*types.Block, error) { } // SaveBlockValidation saves block validation in Store. -func (s *DefaultStore) SaveBlockValidation(height uint64, validation bool, batch KVBatch) (KVBatch, error) { +func (s *DefaultStore) SaveBlockSource(height uint64, source string, batch KVBatch) (KVBatch, error) { - var data []byte - if validation { - data = blockValidated[:] - } else { - data = blockNonValidated[:] - } if batch == nil { - return nil, s.db.Set(getValidationKey(height), data) + return nil, s.db.Set(getSourceKey(height), []byte(source)) } - err := batch.Set(getValidationKey(height), data) + err := batch.Set(getSourceKey(height), []byte(source)) return batch, err } // LoadBlockValidation returns block validation in Store. -func (s *DefaultStore) LoadBlockValidation(height uint64) (bool, error) { +func (s *DefaultStore) LoadBlockSource(height uint64) (string, error) { - data, err := s.db.Get(getValidationKey(height)) + source, err := s.db.Get(getSourceKey(height)) if err != nil { - return false, fmt.Errorf("retrieve block results from height %v: %w", height, err) - } - if bytes.Equal(data[:], blockValidated[:]) { - return true, nil + return "", fmt.Errorf("retrieve block results from height %v: %w", height, err) } - return false, nil + + return string(source[:]), nil } @@ -370,8 +356,8 @@ func getCidKey(height uint64) []byte { return append(cidPrefix[:], buf[:]...) } -func getValidationKey(height uint64) []byte { +func getSourceKey(height uint64) []byte { buf := make([]byte, 8) binary.BigEndian.PutUint64(buf, height) - return append(validationPrefix[:], buf[:]...) + return append(sourcePrefix[:], buf[:]...) } diff --git a/store/storeIface.go b/store/storeIface.go index f8e9e4780..5db492588 100644 --- a/store/storeIface.go +++ b/store/storeIface.go @@ -79,7 +79,7 @@ type Store interface { LoadBlockCid(height uint64) (cid.Cid, error) - SaveBlockValidation(height uint64, validation bool, batch KVBatch) (KVBatch, error) + SaveBlockSource(height uint64, source string, batch KVBatch) (KVBatch, error) - LoadBlockValidation(height uint64) (bool, error) + LoadBlockSource(height uint64) (string, error) } From b4ae8650f189b1f8542a31724beb4a517ddd0f50 Mon Sep 17 00:00:00 2001 From: Sergi Rene Date: Mon, 7 Oct 2024 16:13:28 +0200 Subject: [PATCH 11/29] fix intial seq update --- block/manager.go | 9 +++++++++ block/sync.go | 23 ++++++++++++----------- block/validator.go | 34 +++++++++++++++++++++++++++------- 3 files changed, 48 insertions(+), 18 deletions(-) diff --git a/block/manager.go b/block/manager.go index 3a1e3c949..374cf8649 100644 --- a/block/manager.go +++ b/block/manager.go @@ -89,6 +89,8 @@ type Manager struct { syncingC chan uint64 + validateC chan uint64 + synced *uchannel.Nudger validator *StateUpdateValidator @@ -136,6 +138,7 @@ func NewManager( FraudHandler: nil, // TODO: create a default handler pruningC: make(chan int64, 10), // use of buffered channel to avoid blocking applyBlock thread. In case channel is full, pruning will be skipped, but the retain height can be pruned in the next iteration. syncingC: make(chan uint64, 1), + validateC: make(chan uint64, 1), synced: uchannel.NewNudger(), } @@ -277,6 +280,12 @@ func (m *Manager) NextHeightToSubmit() uint64 { // syncFromSettlement enforces the node to be synced on initial run from SL and DA. func (m *Manager) syncFromSettlement() error { + // Update sequencers list from SL + err := m.UpdateSequencerSetFromSL() + if err != nil { + m.logger.Error("update bonded sequencer set", "error", err) + } + res, err := m.SLClient.GetLatestBatch() if errors.Is(err, gerrc.ErrNotFound) { // The SL hasn't got any batches for this chain yet. diff --git a/block/sync.go b/block/sync.go index 9965e6441..0cd5f24e9 100644 --- a/block/sync.go +++ b/block/sync.go @@ -19,23 +19,24 @@ func (m *Manager) onNewStateUpdate(event pubsub.Message) { m.LastSubmittedHeight.Store(eventData.EndHeight) m.UpdateTargetHeight(eventData.EndHeight) - // If node is not synced, trigger syncing from DA. + // Update sequencers list from SL + err := m.UpdateSequencerSetFromSL() + if err != nil { + m.logger.Error("update bonded sequencer set", "error", err) + } + + // Trigger syncing from DA. select { case m.syncingC <- eventData.EndHeight: default: m.logger.Debug("disregarding new state update, node is still syncing") } - // Validate new state update - err := m.validator.ValidateStateUpdate(eventData.StateIndex) - if err != nil { - m.logger.Error("state update validation", "error", err) - } - - // Update sequencers list from SL - err = m.UpdateSequencerSetFromSL() - if err != nil { - m.logger.Error("update bonded sequencer set", "error", err) + // Trigger state update validation. + select { + case m.validateC <- eventData.StateIndex: + default: + m.logger.Debug("disregarding new state update, node is still syncing") } } diff --git a/block/validator.go b/block/validator.go index 118c7a643..681304df7 100644 --- a/block/validator.go +++ b/block/validator.go @@ -2,6 +2,7 @@ package block import ( "bytes" + "context" "crypto/sha256" "fmt" @@ -10,6 +11,30 @@ import ( "github.com/dymensionxyz/dymint/types" ) +// SyncTargetLoop listens for syncing events (from new state update or from initial syncing) and syncs to the last submitted height. +// In case the node is already synced, it validate +func (m *Manager) ValidateLoop(ctx context.Context) error { + + for { + select { + case <-ctx.Done(): + return nil + case stateIndex := <-m.validateC: + + // Validate new state update + batch, err := m.SLClient.GetBatchAtIndex(stateIndex) + + if err != nil { + return err + } + err = m.validator.ValidateStateUpdate(batch) + if err != nil { + m.logger.Error("state update validation", "error", err) + } + } + } +} + type P2PBlockValidator interface { ValidateP2PBlocks(daBlocks []*types.Block, p2pBlocks []*types.Block) error } @@ -35,14 +60,9 @@ func NewStateUpdateValidator(logger types.Logger, blockManager *Manager) *StateU } } -func (v *StateUpdateValidator) ValidateStateUpdate(stateIndex uint64) error { - - batch, err := v.blockManager.SLClient.GetBatchAtIndex(stateIndex) - if err != nil { - return err - } +func (v *StateUpdateValidator) ValidateStateUpdate(batch *settlement.ResultRetrieveBatch) error { - err = v.validateDRS(batch.StartHeight, batch.EndHeight, batch.DRSVersion) + err := v.validateDRS(batch.StartHeight, batch.EndHeight, batch.DRSVersion) if err != nil { return err } From 4c890c7c27cf5126222389b23a8683524d2ed5bd Mon Sep 17 00:00:00 2001 From: Sergi Rene Date: Mon, 7 Oct 2024 22:34:49 +0200 Subject: [PATCH 12/29] adding finalized and validated height --- block/manager.go | 28 ++- block/retriever.go | 52 +---- block/sync.go | 44 +++- block/validator.go | 190 ------------------ .../dymint/da/avail/mock_SubstrateApiI.go | 2 +- .../celestia/types/mock_CelestiaRPCClient.go | 2 +- .../da/mock_DataAvailabilityLayerClient.go | 41 ++-- .../settlement/dymension/mock_CosmosClient.go | 2 +- .../dymint/settlement/mock_ClientI.go | 125 ++++++++---- .../dymensionxyz/dymint/store/mock_Store.go | 118 ++++++++++- .../sequencer/types/mock_QueryClient.go | 2 +- .../dymension/rollapp/mock_QueryClient.go | 2 +- .../tendermint/abci/types/mock_Application.go | 2 +- .../tendermint/proxy/mock_AppConnConsensus.go | 2 +- .../tendermint/proxy/mock_AppConns.go | 2 +- proto/types/dymint/state.proto | 2 + settlement/dymension/dymension.go | 32 +-- settlement/grpc/grpc.go | 7 +- settlement/local/local.go | 18 +- settlement/local/local_test.go | 12 +- settlement/settlement.go | 7 +- types/pb/dymint/state.pb.go | 129 +++++++----- types/serialization.go | 2 + types/state.go | 13 ++ 24 files changed, 438 insertions(+), 398 deletions(-) delete mode 100644 block/validator.go diff --git a/block/manager.go b/block/manager.go index 374cf8649..d9226dc7f 100644 --- a/block/manager.go +++ b/block/manager.go @@ -63,6 +63,9 @@ type Manager struct { // prune anything that might be submitted in the future. Therefore, it must be atomic. LastSubmittedHeight atomic.Uint64 + // The last height which was finalized after dispute period, obtained from the Hub + LastFinalizedHeight atomic.Uint64 + /* Retrieval */ @@ -87,9 +90,9 @@ type Manager struct { // indexer indexerService *txindex.IndexerService - syncingC chan uint64 + syncingC chan struct{} - validateC chan uint64 + validateC chan struct{} synced *uchannel.Nudger @@ -137,8 +140,8 @@ func NewManager( }, FraudHandler: nil, // TODO: create a default handler pruningC: make(chan int64, 10), // use of buffered channel to avoid blocking applyBlock thread. In case channel is full, pruning will be skipped, but the retain height can be pruned in the next iteration. - syncingC: make(chan uint64, 1), - validateC: make(chan uint64, 1), + syncingC: make(chan struct{}, 1), + validateC: make(chan struct{}, 1), synced: uchannel.NewNudger(), } @@ -200,6 +203,10 @@ func (m *Manager) Start(ctx context.Context) error { /* ----------------------------- full node mode ----------------------------- */ if !isProposer { + + uerrors.ErrGroupGoLog(eg, m.logger, func() error { + return m.ValidateLoop(ctx) + }) go uevent.MustSubscribe(ctx, m.Pubsub, "syncTargetLoop", settlement.EventQueryNewSettlementBatchAccepted, m.onNewStateUpdate, m.logger) // P2P Sync. Subscribe to P2P received blocks events go uevent.MustSubscribe(ctx, m.Pubsub, "applyGossipedBlocksLoop", p2p.EventQueryNewGossipedBlock, m.OnReceivedBlock, m.logger) @@ -301,7 +308,18 @@ func (m *Manager) syncFromSettlement() error { m.LastSubmittedHeight.Store(res.EndHeight) m.UpdateTargetHeight(res.EndHeight) - m.syncingC <- res.EndHeight + + m.syncingC <- struct{}{} + m.validateC <- struct{}{} + + res, err = m.SLClient.GetLatestFinalizedBatch() + if errors.Is(err, gerrc.ErrNotFound) { + // The SL hasn't got any batches for this chain yet. + m.logger.Info("No finalized batches for chain found in SL.") + m.LastFinalizedHeight.Store(uint64(m.Genesis.InitialHeight - 1)) + return nil + } + return nil } diff --git a/block/retriever.go b/block/retriever.go index 810c027f2..d7e0729ce 100644 --- a/block/retriever.go +++ b/block/retriever.go @@ -1,72 +1,26 @@ package block import ( - "context" - "errors" "fmt" "github.com/dymensionxyz/gerr-cosmos/gerrc" "github.com/dymensionxyz/dymint/da" - "github.com/dymensionxyz/dymint/node/events" "github.com/dymensionxyz/dymint/types" - uevent "github.com/dymensionxyz/dymint/utils/event" ) -// syncToTargetHeight syncs blocks until the target height is reached. -// It fetches the batches from the settlement, gets the DA height and gets -// the actual blocks from the DA. -func (m *Manager) syncToLastSubmittedHeight() error { - - for currH := m.State.NextHeight(); currH <= m.LastSubmittedHeight.Load(); currH = m.State.NextHeight() { - // if we have the block locally, we don't need to fetch it from the DA - err := m.applyLocalBlock(currH) - if err == nil { - m.logger.Info("Synced from local", "store height", currH, "target height", m.LastSubmittedHeight.Load()) - continue - } - if !errors.Is(err, gerrc.ErrNotFound) { - m.logger.Error("Apply local block", "err", err) - } - - err = m.syncFromDABatch() - if err != nil { - m.logger.Error("process next DA batch", "err", err) - } - - m.logger.Info("Synced from DA", "store height", m.State.Height(), "target height", m.LastSubmittedHeight.Load()) - - } - - err := m.attemptApplyCachedBlocks() - if err != nil { - uevent.MustPublish(context.TODO(), m.Pubsub, &events.DataHealthStatus{Error: err}, events.HealthStatusList) - m.logger.Error("Attempt apply cached blocks.", "err", err) - } - - return nil -} - func (m *Manager) syncFromDABatch() error { - // It's important that we query the state index before fetching the batch, rather - // than e.g. keep it and increment it, because we might be concurrently applying blocks - // and may require a higher index than expected. - res, err := m.SLClient.GetHeightState(m.State.NextHeight()) - if err != nil { - return fmt.Errorf("retrieve state: %w", err) - } - stateIndex := res.State.StateIndex - settlementBatch, err := m.SLClient.GetBatchAtIndex(stateIndex) + settlementBatch, err := m.SLClient.GetBatchAtHeight(m.State.NextHeight()) if err != nil { return fmt.Errorf("retrieve batch: %w", err) } - m.logger.Info("Retrieved batch.", "state_index", stateIndex) + m.logger.Info("Retrieved batch.", "state_index", settlementBatch.StateIndex) // update the proposer when syncing from the settlement layer proposer := m.State.Sequencers.GetByAddress(settlementBatch.Batch.Sequencer) if proposer == nil { - return fmt.Errorf("proposer not found: batch: %d: %s", stateIndex, settlementBatch.Batch.Sequencer) + return fmt.Errorf("proposer not found: batch: %d: %s", settlementBatch.StateIndex, settlementBatch.Batch.Sequencer) } m.State.Sequencers.SetProposer(proposer) diff --git a/block/sync.go b/block/sync.go index 0cd5f24e9..f1046d67c 100644 --- a/block/sync.go +++ b/block/sync.go @@ -2,8 +2,12 @@ package block import ( "context" + "errors" + "github.com/dymensionxyz/dymint/node/events" "github.com/dymensionxyz/dymint/settlement" + uevent "github.com/dymensionxyz/dymint/utils/event" + "github.com/dymensionxyz/gerr-cosmos/gerrc" "github.com/tendermint/tendermint/libs/pubsub" ) @@ -27,16 +31,16 @@ func (m *Manager) onNewStateUpdate(event pubsub.Message) { // Trigger syncing from DA. select { - case m.syncingC <- eventData.EndHeight: + case m.syncingC <- struct{}{}: default: m.logger.Debug("disregarding new state update, node is still syncing") } // Trigger state update validation. select { - case m.validateC <- eventData.StateIndex: + case m.validateC <- struct{}{}: default: - m.logger.Debug("disregarding new state update, node is still syncing") + m.logger.Debug("disregarding new state update, node is still validating") } } @@ -49,15 +53,39 @@ func (m *Manager) SyncLoop(ctx context.Context) error { select { case <-ctx.Done(): return nil - case targetHeight := <-m.syncingC: + case <-m.syncingC: + + m.logger.Info("syncing to target height", "targetHeight", m.LastSubmittedHeight.Load()) + + for currH := m.State.NextHeight(); currH <= m.LastSubmittedHeight.Load(); currH = m.State.NextHeight() { + // if we have the block locally, we don't need to fetch it from the DA + err := m.applyLocalBlock(currH) + if err == nil { + m.logger.Info("Synced from local", "store height", currH, "target height", m.LastSubmittedHeight.Load()) + continue + } + if !errors.Is(err, gerrc.ErrNotFound) { + m.logger.Error("Apply local block", "err", err) + } + + err = m.syncFromDABatch() + if err != nil { + m.logger.Error("process next DA batch", "err", err) + } + + m.logger.Info("Synced from DA", "store height", m.State.Height(), "target height", m.LastSubmittedHeight.Load()) - err := m.syncToLastSubmittedHeight() - if err != nil { - m.logger.Error("syncing to target height", "targetHeight", targetHeight, "error", err) + err = m.attemptApplyCachedBlocks() + if err != nil { + uevent.MustPublish(context.TODO(), m.Pubsub, &events.DataHealthStatus{Error: err}, events.HealthStatusList) + m.logger.Error("Attempt apply cached blocks.", "err", err) + } } - m.synced.Nudge() + m.logger.Info("Synced.", "current height", m.State.Height(), "last submitted height", m.LastSubmittedHeight.Load()) + m.synced.Nudge() + } } } diff --git a/block/validator.go b/block/validator.go deleted file mode 100644 index 681304df7..000000000 --- a/block/validator.go +++ /dev/null @@ -1,190 +0,0 @@ -package block - -import ( - "bytes" - "context" - "crypto/sha256" - "fmt" - - "github.com/dymensionxyz/dymint/da" - "github.com/dymensionxyz/dymint/settlement" - "github.com/dymensionxyz/dymint/types" -) - -// SyncTargetLoop listens for syncing events (from new state update or from initial syncing) and syncs to the last submitted height. -// In case the node is already synced, it validate -func (m *Manager) ValidateLoop(ctx context.Context) error { - - for { - select { - case <-ctx.Done(): - return nil - case stateIndex := <-m.validateC: - - // Validate new state update - batch, err := m.SLClient.GetBatchAtIndex(stateIndex) - - if err != nil { - return err - } - err = m.validator.ValidateStateUpdate(batch) - if err != nil { - m.logger.Error("state update validation", "error", err) - } - } - } -} - -type P2PBlockValidator interface { - ValidateP2PBlocks(daBlocks []*types.Block, p2pBlocks []*types.Block) error -} - -type DABlocksValidator interface { - ValidateDaBlocks(slBatch *settlement.ResultRetrieveBatch, daBlocks []*types.Block) error -} - -// StateUpdateValidator is a validator for messages gossiped in the p2p network. -type StateUpdateValidator struct { - logger types.Logger - blockManager *Manager -} - -var _ DABlocksValidator = &StateUpdateValidator{} -var _ P2PBlockValidator = &StateUpdateValidator{} - -// NewValidator creates a new Validator. -func NewStateUpdateValidator(logger types.Logger, blockManager *Manager) *StateUpdateValidator { - return &StateUpdateValidator{ - logger: logger, - blockManager: blockManager, - } -} - -func (v *StateUpdateValidator) ValidateStateUpdate(batch *settlement.ResultRetrieveBatch) error { - - err := v.validateDRS(batch.StartHeight, batch.EndHeight, batch.DRSVersion) - if err != nil { - return err - } - - var daBlocks []*types.Block - var p2pBlocks []*types.Block - - //load blocks for the batch height, either P2P or DA blocks - for height := batch.StartHeight; height <= batch.EndHeight; height++ { - source, err := v.blockManager.Store.LoadBlockSource(height) - if err != nil { - return err - } - block, err := v.blockManager.Store.LoadBlock(height) - if err != nil { - return err - } - if source == types.DA.String() { - daBlocks = append(daBlocks, block) - } else { - p2pBlocks = append(p2pBlocks, block) - } - } - - numBlocks := batch.EndHeight - batch.StartHeight + 1 - if uint64(len(daBlocks)) != numBlocks { - daBatch := v.blockManager.Retriever.RetrieveBatches(batch.MetaData.DA) - if daBatch.Code != da.StatusSuccess { - return daBatch.Error - } - - for _, batch := range daBatch.Batches { - daBlocks = append(daBlocks, batch.Blocks...) - } - } - - err = v.ValidateDaBlocks(batch, daBlocks) - if err != nil { - return err - } - - if len(p2pBlocks) > 0 { - err = v.ValidateP2PBlocks(daBlocks, p2pBlocks) - if err != nil { - return err - } - } - - return nil -} - -func (v *StateUpdateValidator) ValidateP2PBlocks(daBlocks []*types.Block, p2pBlocks []*types.Block) error { - - i := 0 - for _, daBlock := range daBlocks { - - if p2pBlocks[i].Header.Height != daBlocks[i].Header.Height { - break - } - p2pBlockHash, err := blockHash(p2pBlocks[i]) - if err != nil { - return err - } - i++ - daBlockHash, err := blockHash(daBlock) - if err != nil { - return err - } - if !bytes.Equal(p2pBlockHash, daBlockHash) { - return fmt.Errorf("failed comparing blocks") - } - } - return nil -} - -// isHeightFinalized is required to skip old blocks validation -func (v *StateUpdateValidator) isHeightFinalized(height uint64) bool { - return false -} - -func (v *StateUpdateValidator) ValidateDaBlocks(slBatch *settlement.ResultRetrieveBatch, daBlocks []*types.Block) error { - - // check numblocks - numSlBlocks := len(slBatch.BlockDescriptors) - numDABlocks := len(daBlocks) - if numSlBlocks != numDABlocks { - return fmt.Errorf("num blocks mismatch between state update and DA batch. State index: %d State update blocks: %d DA batch blocks: %d", slBatch.StateIndex, numSlBlocks, numDABlocks) - } - - // check blocks - for i, bd := range slBatch.BlockDescriptors { - // height check - if bd.Height != daBlocks[i].Header.Height { - return fmt.Errorf("height mismatch between state update and DA batch. State index: %d SL height: %d DA height: %d", slBatch.StateIndex, bd.Height, daBlocks[i].Header.Height) - } - // we compare the state root between SL state info and DA block - if !bytes.Equal(bd.StateRoot, daBlocks[i].Header.AppHash[:]) { - return fmt.Errorf("state root mismatch between state update and DA batch. State index: %d: Height: %d State root SL: %d State root DA: %d", slBatch.StateIndex, bd.Height, bd.StateRoot, daBlocks[i].Header.AppHash[:]) - } - - // we compare the timestamp between SL state info and DA block - if bd.Timestamp != daBlocks[i].Header.GetTimestamp() { - return fmt.Errorf("timestamp mismatch between state update and DA batch. State index: %d: Height: %d Timestamp SL: %s Timestamp DA: %s", slBatch.StateIndex, bd.Height, bd.Timestamp, daBlocks[i].Header.GetTimestamp()) - } - } - - //TODO(srene): implement sequencer address validation - return nil -} - -func (v *StateUpdateValidator) validateDRS(startHeight, endHeight uint64, version string) error { - - //TODO(srene): implement DRS/height verification - return nil -} - -func blockHash(block *types.Block) ([]byte, error) { - blockBytes, err := block.MarshalBinary() - if err != nil { - return nil, fmt.Errorf("error hashing block. err: %w", err) - } - h := sha256.New() - h.Write(blockBytes) - return h.Sum(nil), nil -} diff --git a/mocks/github.com/dymensionxyz/dymint/da/avail/mock_SubstrateApiI.go b/mocks/github.com/dymensionxyz/dymint/da/avail/mock_SubstrateApiI.go index bba31b087..6a52c1df8 100644 --- a/mocks/github.com/dymensionxyz/dymint/da/avail/mock_SubstrateApiI.go +++ b/mocks/github.com/dymensionxyz/dymint/da/avail/mock_SubstrateApiI.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.46.0. DO NOT EDIT. +// Code generated by mockery v2.42.3. DO NOT EDIT. package avail diff --git a/mocks/github.com/dymensionxyz/dymint/da/celestia/types/mock_CelestiaRPCClient.go b/mocks/github.com/dymensionxyz/dymint/da/celestia/types/mock_CelestiaRPCClient.go index 5994cf817..4935cc66a 100644 --- a/mocks/github.com/dymensionxyz/dymint/da/celestia/types/mock_CelestiaRPCClient.go +++ b/mocks/github.com/dymensionxyz/dymint/da/celestia/types/mock_CelestiaRPCClient.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.46.0. DO NOT EDIT. +// Code generated by mockery v2.42.3. DO NOT EDIT. package types diff --git a/mocks/github.com/dymensionxyz/dymint/da/mock_DataAvailabilityLayerClient.go b/mocks/github.com/dymensionxyz/dymint/da/mock_DataAvailabilityLayerClient.go index 1480d557c..0e79e172c 100644 --- a/mocks/github.com/dymensionxyz/dymint/da/mock_DataAvailabilityLayerClient.go +++ b/mocks/github.com/dymensionxyz/dymint/da/mock_DataAvailabilityLayerClient.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.46.0. DO NOT EDIT. +// Code generated by mockery v2.42.3. DO NOT EDIT. package da @@ -362,49 +362,34 @@ func (_c *MockDataAvailabilityLayerClient_SubmitBatch_Call) RunAndReturn(run fun return _c } -// Synced provides a mock function with given fields: -func (_m *MockDataAvailabilityLayerClient) Synced() <-chan struct{} { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for Synced") - } - - var r0 <-chan struct{} - if rf, ok := ret.Get(0).(func() <-chan struct{}); ok { - r0 = rf() - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(<-chan struct{}) - } - } - - return r0 +// WaitForSyncing provides a mock function with given fields: +func (_m *MockDataAvailabilityLayerClient) WaitForSyncing() { + _m.Called() } -// MockDataAvailabilityLayerClient_Synced_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Synced' -type MockDataAvailabilityLayerClient_Synced_Call struct { +// MockDataAvailabilityLayerClient_WaitForSyncing_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'WaitForSyncing' +type MockDataAvailabilityLayerClient_WaitForSyncing_Call struct { *mock.Call } -// Synced is a helper method to define mock.On call -func (_e *MockDataAvailabilityLayerClient_Expecter) Synced() *MockDataAvailabilityLayerClient_Synced_Call { - return &MockDataAvailabilityLayerClient_Synced_Call{Call: _e.mock.On("Synced")} +// WaitForSyncing is a helper method to define mock.On call +func (_e *MockDataAvailabilityLayerClient_Expecter) WaitForSyncing() *MockDataAvailabilityLayerClient_WaitForSyncing_Call { + return &MockDataAvailabilityLayerClient_WaitForSyncing_Call{Call: _e.mock.On("WaitForSyncing")} } -func (_c *MockDataAvailabilityLayerClient_Synced_Call) Run(run func()) *MockDataAvailabilityLayerClient_Synced_Call { +func (_c *MockDataAvailabilityLayerClient_WaitForSyncing_Call) Run(run func()) *MockDataAvailabilityLayerClient_WaitForSyncing_Call { _c.Call.Run(func(args mock.Arguments) { run() }) return _c } -func (_c *MockDataAvailabilityLayerClient_Synced_Call) Return(_a0 <-chan struct{}) *MockDataAvailabilityLayerClient_Synced_Call { - _c.Call.Return(_a0) +func (_c *MockDataAvailabilityLayerClient_WaitForSyncing_Call) Return() *MockDataAvailabilityLayerClient_WaitForSyncing_Call { + _c.Call.Return() return _c } -func (_c *MockDataAvailabilityLayerClient_Synced_Call) RunAndReturn(run func() <-chan struct{}) *MockDataAvailabilityLayerClient_Synced_Call { +func (_c *MockDataAvailabilityLayerClient_WaitForSyncing_Call) RunAndReturn(run func()) *MockDataAvailabilityLayerClient_WaitForSyncing_Call { _c.Call.Return(run) return _c } diff --git a/mocks/github.com/dymensionxyz/dymint/settlement/dymension/mock_CosmosClient.go b/mocks/github.com/dymensionxyz/dymint/settlement/dymension/mock_CosmosClient.go index bebd10ade..4b14a3eaf 100644 --- a/mocks/github.com/dymensionxyz/dymint/settlement/dymension/mock_CosmosClient.go +++ b/mocks/github.com/dymensionxyz/dymint/settlement/dymension/mock_CosmosClient.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.46.0. DO NOT EDIT. +// Code generated by mockery v2.42.3. DO NOT EDIT. package dymension diff --git a/mocks/github.com/dymensionxyz/dymint/settlement/mock_ClientI.go b/mocks/github.com/dymensionxyz/dymint/settlement/mock_ClientI.go index e71cc9d27..7072f55c2 100644 --- a/mocks/github.com/dymensionxyz/dymint/settlement/mock_ClientI.go +++ b/mocks/github.com/dymensionxyz/dymint/settlement/mock_ClientI.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.46.0. DO NOT EDIT. +// Code generated by mockery v2.42.3. DO NOT EDIT. package settlement @@ -140,6 +140,64 @@ func (_c *MockClientI_GetAllSequencers_Call) RunAndReturn(run func() ([]types.Se return _c } +// GetBatchAtHeight provides a mock function with given fields: index +func (_m *MockClientI) GetBatchAtHeight(index uint64) (*settlement.ResultRetrieveBatch, error) { + ret := _m.Called(index) + + if len(ret) == 0 { + panic("no return value specified for GetBatchAtHeight") + } + + var r0 *settlement.ResultRetrieveBatch + var r1 error + if rf, ok := ret.Get(0).(func(uint64) (*settlement.ResultRetrieveBatch, error)); ok { + return rf(index) + } + if rf, ok := ret.Get(0).(func(uint64) *settlement.ResultRetrieveBatch); ok { + r0 = rf(index) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*settlement.ResultRetrieveBatch) + } + } + + if rf, ok := ret.Get(1).(func(uint64) error); ok { + r1 = rf(index) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockClientI_GetBatchAtHeight_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetBatchAtHeight' +type MockClientI_GetBatchAtHeight_Call struct { + *mock.Call +} + +// GetBatchAtHeight is a helper method to define mock.On call +// - index uint64 +func (_e *MockClientI_Expecter) GetBatchAtHeight(index interface{}) *MockClientI_GetBatchAtHeight_Call { + return &MockClientI_GetBatchAtHeight_Call{Call: _e.mock.On("GetBatchAtHeight", index)} +} + +func (_c *MockClientI_GetBatchAtHeight_Call) Run(run func(index uint64)) *MockClientI_GetBatchAtHeight_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(uint64)) + }) + return _c +} + +func (_c *MockClientI_GetBatchAtHeight_Call) Return(_a0 *settlement.ResultRetrieveBatch, _a1 error) *MockClientI_GetBatchAtHeight_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockClientI_GetBatchAtHeight_Call) RunAndReturn(run func(uint64) (*settlement.ResultRetrieveBatch, error)) *MockClientI_GetBatchAtHeight_Call { + _c.Call.Return(run) + return _c +} + // GetBatchAtIndex provides a mock function with given fields: index func (_m *MockClientI) GetBatchAtIndex(index uint64) (*settlement.ResultRetrieveBatch, error) { ret := _m.Called(index) @@ -255,29 +313,29 @@ func (_c *MockClientI_GetBondedSequencers_Call) RunAndReturn(run func() ([]types return _c } -// GetHeightState provides a mock function with given fields: _a0 -func (_m *MockClientI) GetHeightState(_a0 uint64) (*settlement.ResultGetHeightState, error) { - ret := _m.Called(_a0) +// GetLatestBatch provides a mock function with given fields: +func (_m *MockClientI) GetLatestBatch() (*settlement.ResultRetrieveBatch, error) { + ret := _m.Called() if len(ret) == 0 { - panic("no return value specified for GetHeightState") + panic("no return value specified for GetLatestBatch") } - var r0 *settlement.ResultGetHeightState + var r0 *settlement.ResultRetrieveBatch var r1 error - if rf, ok := ret.Get(0).(func(uint64) (*settlement.ResultGetHeightState, error)); ok { - return rf(_a0) + if rf, ok := ret.Get(0).(func() (*settlement.ResultRetrieveBatch, error)); ok { + return rf() } - if rf, ok := ret.Get(0).(func(uint64) *settlement.ResultGetHeightState); ok { - r0 = rf(_a0) + if rf, ok := ret.Get(0).(func() *settlement.ResultRetrieveBatch); ok { + r0 = rf() } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(*settlement.ResultGetHeightState) + r0 = ret.Get(0).(*settlement.ResultRetrieveBatch) } } - if rf, ok := ret.Get(1).(func(uint64) error); ok { - r1 = rf(_a0) + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() } else { r1 = ret.Error(1) } @@ -285,40 +343,39 @@ func (_m *MockClientI) GetHeightState(_a0 uint64) (*settlement.ResultGetHeightSt return r0, r1 } -// MockClientI_GetHeightState_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetHeightState' -type MockClientI_GetHeightState_Call struct { +// MockClientI_GetLatestBatch_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetLatestBatch' +type MockClientI_GetLatestBatch_Call struct { *mock.Call } -// GetHeightState is a helper method to define mock.On call -// - _a0 uint64 -func (_e *MockClientI_Expecter) GetHeightState(_a0 interface{}) *MockClientI_GetHeightState_Call { - return &MockClientI_GetHeightState_Call{Call: _e.mock.On("GetHeightState", _a0)} +// GetLatestBatch is a helper method to define mock.On call +func (_e *MockClientI_Expecter) GetLatestBatch() *MockClientI_GetLatestBatch_Call { + return &MockClientI_GetLatestBatch_Call{Call: _e.mock.On("GetLatestBatch")} } -func (_c *MockClientI_GetHeightState_Call) Run(run func(_a0 uint64)) *MockClientI_GetHeightState_Call { +func (_c *MockClientI_GetLatestBatch_Call) Run(run func()) *MockClientI_GetLatestBatch_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(uint64)) + run() }) return _c } -func (_c *MockClientI_GetHeightState_Call) Return(_a0 *settlement.ResultGetHeightState, _a1 error) *MockClientI_GetHeightState_Call { +func (_c *MockClientI_GetLatestBatch_Call) Return(_a0 *settlement.ResultRetrieveBatch, _a1 error) *MockClientI_GetLatestBatch_Call { _c.Call.Return(_a0, _a1) return _c } -func (_c *MockClientI_GetHeightState_Call) RunAndReturn(run func(uint64) (*settlement.ResultGetHeightState, error)) *MockClientI_GetHeightState_Call { +func (_c *MockClientI_GetLatestBatch_Call) RunAndReturn(run func() (*settlement.ResultRetrieveBatch, error)) *MockClientI_GetLatestBatch_Call { _c.Call.Return(run) return _c } -// GetLatestBatch provides a mock function with given fields: -func (_m *MockClientI) GetLatestBatch() (*settlement.ResultRetrieveBatch, error) { +// GetLatestFinalizedBatch provides a mock function with given fields: +func (_m *MockClientI) GetLatestFinalizedBatch() (*settlement.ResultRetrieveBatch, error) { ret := _m.Called() if len(ret) == 0 { - panic("no return value specified for GetLatestBatch") + panic("no return value specified for GetLatestFinalizedBatch") } var r0 *settlement.ResultRetrieveBatch @@ -343,29 +400,29 @@ func (_m *MockClientI) GetLatestBatch() (*settlement.ResultRetrieveBatch, error) return r0, r1 } -// MockClientI_GetLatestBatch_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetLatestBatch' -type MockClientI_GetLatestBatch_Call struct { +// MockClientI_GetLatestFinalizedBatch_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetLatestFinalizedBatch' +type MockClientI_GetLatestFinalizedBatch_Call struct { *mock.Call } -// GetLatestBatch is a helper method to define mock.On call -func (_e *MockClientI_Expecter) GetLatestBatch() *MockClientI_GetLatestBatch_Call { - return &MockClientI_GetLatestBatch_Call{Call: _e.mock.On("GetLatestBatch")} +// GetLatestFinalizedBatch is a helper method to define mock.On call +func (_e *MockClientI_Expecter) GetLatestFinalizedBatch() *MockClientI_GetLatestFinalizedBatch_Call { + return &MockClientI_GetLatestFinalizedBatch_Call{Call: _e.mock.On("GetLatestFinalizedBatch")} } -func (_c *MockClientI_GetLatestBatch_Call) Run(run func()) *MockClientI_GetLatestBatch_Call { +func (_c *MockClientI_GetLatestFinalizedBatch_Call) Run(run func()) *MockClientI_GetLatestFinalizedBatch_Call { _c.Call.Run(func(args mock.Arguments) { run() }) return _c } -func (_c *MockClientI_GetLatestBatch_Call) Return(_a0 *settlement.ResultRetrieveBatch, _a1 error) *MockClientI_GetLatestBatch_Call { +func (_c *MockClientI_GetLatestFinalizedBatch_Call) Return(_a0 *settlement.ResultRetrieveBatch, _a1 error) *MockClientI_GetLatestFinalizedBatch_Call { _c.Call.Return(_a0, _a1) return _c } -func (_c *MockClientI_GetLatestBatch_Call) RunAndReturn(run func() (*settlement.ResultRetrieveBatch, error)) *MockClientI_GetLatestBatch_Call { +func (_c *MockClientI_GetLatestFinalizedBatch_Call) RunAndReturn(run func() (*settlement.ResultRetrieveBatch, error)) *MockClientI_GetLatestFinalizedBatch_Call { _c.Call.Return(run) return _c } diff --git a/mocks/github.com/dymensionxyz/dymint/store/mock_Store.go b/mocks/github.com/dymensionxyz/dymint/store/mock_Store.go index 66207d239..7d4ec56d6 100644 --- a/mocks/github.com/dymensionxyz/dymint/store/mock_Store.go +++ b/mocks/github.com/dymensionxyz/dymint/store/mock_Store.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.46.0. DO NOT EDIT. +// Code generated by mockery v2.42.3. DO NOT EDIT. package store @@ -301,6 +301,62 @@ func (_c *MockStore_LoadBlockResponses_Call) RunAndReturn(run func(uint64) (*sta return _c } +// LoadBlockSource provides a mock function with given fields: height +func (_m *MockStore) LoadBlockSource(height uint64) (string, error) { + ret := _m.Called(height) + + if len(ret) == 0 { + panic("no return value specified for LoadBlockSource") + } + + var r0 string + var r1 error + if rf, ok := ret.Get(0).(func(uint64) (string, error)); ok { + return rf(height) + } + if rf, ok := ret.Get(0).(func(uint64) string); ok { + r0 = rf(height) + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func(uint64) error); ok { + r1 = rf(height) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockStore_LoadBlockSource_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'LoadBlockSource' +type MockStore_LoadBlockSource_Call struct { + *mock.Call +} + +// LoadBlockSource is a helper method to define mock.On call +// - height uint64 +func (_e *MockStore_Expecter) LoadBlockSource(height interface{}) *MockStore_LoadBlockSource_Call { + return &MockStore_LoadBlockSource_Call{Call: _e.mock.On("LoadBlockSource", height)} +} + +func (_c *MockStore_LoadBlockSource_Call) Run(run func(height uint64)) *MockStore_LoadBlockSource_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(uint64)) + }) + return _c +} + +func (_c *MockStore_LoadBlockSource_Call) Return(_a0 string, _a1 error) *MockStore_LoadBlockSource_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockStore_LoadBlockSource_Call) RunAndReturn(run func(uint64) (string, error)) *MockStore_LoadBlockSource_Call { + _c.Call.Return(run) + return _c +} + // LoadCommit provides a mock function with given fields: height func (_m *MockStore) LoadCommit(height uint64) (*types.Commit, error) { ret := _m.Called(height) @@ -817,6 +873,66 @@ func (_c *MockStore_SaveBlockResponses_Call) RunAndReturn(run func(uint64, *stat return _c } +// SaveBlockSource provides a mock function with given fields: height, source, batch +func (_m *MockStore) SaveBlockSource(height uint64, source string, batch store.KVBatch) (store.KVBatch, error) { + ret := _m.Called(height, source, batch) + + if len(ret) == 0 { + panic("no return value specified for SaveBlockSource") + } + + var r0 store.KVBatch + var r1 error + if rf, ok := ret.Get(0).(func(uint64, string, store.KVBatch) (store.KVBatch, error)); ok { + return rf(height, source, batch) + } + if rf, ok := ret.Get(0).(func(uint64, string, store.KVBatch) store.KVBatch); ok { + r0 = rf(height, source, batch) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(store.KVBatch) + } + } + + if rf, ok := ret.Get(1).(func(uint64, string, store.KVBatch) error); ok { + r1 = rf(height, source, batch) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockStore_SaveBlockSource_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SaveBlockSource' +type MockStore_SaveBlockSource_Call struct { + *mock.Call +} + +// SaveBlockSource is a helper method to define mock.On call +// - height uint64 +// - source string +// - batch store.KVBatch +func (_e *MockStore_Expecter) SaveBlockSource(height interface{}, source interface{}, batch interface{}) *MockStore_SaveBlockSource_Call { + return &MockStore_SaveBlockSource_Call{Call: _e.mock.On("SaveBlockSource", height, source, batch)} +} + +func (_c *MockStore_SaveBlockSource_Call) Run(run func(height uint64, source string, batch store.KVBatch)) *MockStore_SaveBlockSource_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(uint64), args[1].(string), args[2].(store.KVBatch)) + }) + return _c +} + +func (_c *MockStore_SaveBlockSource_Call) Return(_a0 store.KVBatch, _a1 error) *MockStore_SaveBlockSource_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockStore_SaveBlockSource_Call) RunAndReturn(run func(uint64, string, store.KVBatch) (store.KVBatch, error)) *MockStore_SaveBlockSource_Call { + _c.Call.Return(run) + return _c +} + // SaveSequencers provides a mock function with given fields: height, seqSet, batch func (_m *MockStore) SaveSequencers(height uint64, seqSet *types.SequencerSet, batch store.KVBatch) (store.KVBatch, error) { ret := _m.Called(height, seqSet, batch) diff --git a/mocks/github.com/dymensionxyz/dymint/third_party/dymension/sequencer/types/mock_QueryClient.go b/mocks/github.com/dymensionxyz/dymint/third_party/dymension/sequencer/types/mock_QueryClient.go index 6b16285aa..fed52c6c5 100644 --- a/mocks/github.com/dymensionxyz/dymint/third_party/dymension/sequencer/types/mock_QueryClient.go +++ b/mocks/github.com/dymensionxyz/dymint/third_party/dymension/sequencer/types/mock_QueryClient.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.46.0. DO NOT EDIT. +// Code generated by mockery v2.42.3. DO NOT EDIT. package types diff --git a/mocks/github.com/dymensionxyz/dymint/types/pb/dymensionxyz/dymension/rollapp/mock_QueryClient.go b/mocks/github.com/dymensionxyz/dymint/types/pb/dymensionxyz/dymension/rollapp/mock_QueryClient.go index f182218a5..83c174f72 100644 --- a/mocks/github.com/dymensionxyz/dymint/types/pb/dymensionxyz/dymension/rollapp/mock_QueryClient.go +++ b/mocks/github.com/dymensionxyz/dymint/types/pb/dymensionxyz/dymension/rollapp/mock_QueryClient.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.46.0. DO NOT EDIT. +// Code generated by mockery v2.42.3. DO NOT EDIT. package rollapp diff --git a/mocks/github.com/tendermint/tendermint/abci/types/mock_Application.go b/mocks/github.com/tendermint/tendermint/abci/types/mock_Application.go index 45011bdc9..7393ef94e 100644 --- a/mocks/github.com/tendermint/tendermint/abci/types/mock_Application.go +++ b/mocks/github.com/tendermint/tendermint/abci/types/mock_Application.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.46.0. DO NOT EDIT. +// Code generated by mockery v2.42.3. DO NOT EDIT. package types diff --git a/mocks/github.com/tendermint/tendermint/proxy/mock_AppConnConsensus.go b/mocks/github.com/tendermint/tendermint/proxy/mock_AppConnConsensus.go index 9a28054e1..9ec6b2d18 100644 --- a/mocks/github.com/tendermint/tendermint/proxy/mock_AppConnConsensus.go +++ b/mocks/github.com/tendermint/tendermint/proxy/mock_AppConnConsensus.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.46.0. DO NOT EDIT. +// Code generated by mockery v2.42.3. DO NOT EDIT. package proxy diff --git a/mocks/github.com/tendermint/tendermint/proxy/mock_AppConns.go b/mocks/github.com/tendermint/tendermint/proxy/mock_AppConns.go index 120c2f698..affc90a4e 100644 --- a/mocks/github.com/tendermint/tendermint/proxy/mock_AppConns.go +++ b/mocks/github.com/tendermint/tendermint/proxy/mock_AppConns.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.46.0. DO NOT EDIT. +// Code generated by mockery v2.42.3. DO NOT EDIT. package proxy diff --git a/proto/types/dymint/state.proto b/proto/types/dymint/state.proto index 09abbb38b..b7241aebc 100755 --- a/proto/types/dymint/state.proto +++ b/proto/types/dymint/state.proto @@ -46,6 +46,8 @@ message State { RollappParams rollapp_params = 19 [(gogoproto.nullable) = false]; bytes last_header_hash = 20; + int64 last_validated_height = 21; + } //rollapp params defined in genesis and updated via gov proposal diff --git a/settlement/dymension/dymension.go b/settlement/dymension/dymension.go index f1987c9c0..e8588bfcc 100644 --- a/settlement/dymension/dymension.go +++ b/settlement/dymension/dymension.go @@ -217,8 +217,10 @@ func (c *Client) SubmitBatch(batch *types.Batch, daClient da.Client, daResult *d } } -func (c *Client) getStateInfo(index, height *uint64) (res *rollapptypes.QueryGetStateInfoResponse, err error) { - req := &rollapptypes.QueryGetStateInfoRequest{RollappId: c.rollappId} +func (c *Client) getStateInfo(index, height *uint64, finalized bool) (res *rollapptypes.QueryGetStateInfoResponse, err error) { + req := &rollapptypes.QueryGetStateInfoRequest{ + RollappId: c.rollappId, + Finalized: finalized} if index != nil { req.Index = *index } @@ -244,7 +246,16 @@ func (c *Client) getStateInfo(index, height *uint64) (res *rollapptypes.QueryGet // GetLatestBatch returns the latest batch from the Dymension Hub. func (c *Client) GetLatestBatch() (*settlement.ResultRetrieveBatch, error) { - res, err := c.getStateInfo(nil, nil) + res, err := c.getStateInfo(nil, nil, false) + if err != nil { + return nil, fmt.Errorf("get state info: %w", err) + } + return convertStateInfoToResultRetrieveBatch(&res.StateInfo) +} + +// GetLatestFinalizedBatch returns the latest finalized batch from the Dymension Hub. +func (c *Client) GetLatestFinalizedBatch() (*settlement.ResultRetrieveBatch, error) { + res, err := c.getStateInfo(nil, nil, true) if err != nil { return nil, fmt.Errorf("get state info: %w", err) } @@ -253,24 +264,21 @@ func (c *Client) GetLatestBatch() (*settlement.ResultRetrieveBatch, error) { // GetBatchAtIndex returns the batch at the given index from the Dymension Hub. func (c *Client) GetBatchAtIndex(index uint64) (*settlement.ResultRetrieveBatch, error) { - res, err := c.getStateInfo(&index, nil) + res, err := c.getStateInfo(&index, nil, false) if err != nil { return nil, fmt.Errorf("get state info: %w", err) } return convertStateInfoToResultRetrieveBatch(&res.StateInfo) } -func (c *Client) GetHeightState(h uint64) (*settlement.ResultGetHeightState, error) { - res, err := c.getStateInfo(nil, &h) +// GetBatchAtHeight returns the batch at the given height from the Dymension Hub. +func (c *Client) GetBatchAtHeight(height uint64) (*settlement.ResultRetrieveBatch, error) { + + res, err := c.getStateInfo(nil, &height, false) if err != nil { return nil, fmt.Errorf("get state info: %w", err) } - return &settlement.ResultGetHeightState{ - ResultBase: settlement.ResultBase{Code: settlement.StatusSuccess}, - State: settlement.State{ - StateIndex: res.GetStateInfo().StateInfoIndex.Index, - }, - }, nil + return convertStateInfoToResultRetrieveBatch(&res.StateInfo) } // GetProposer implements settlement.ClientI. diff --git a/settlement/grpc/grpc.go b/settlement/grpc/grpc.go index ff863212e..7db629da2 100644 --- a/settlement/grpc/grpc.go +++ b/settlement/grpc/grpc.go @@ -198,6 +198,11 @@ func (c *Client) GetLatestBatch() (*settlement.ResultRetrieveBatch, error) { return batchResult, nil } +// GetLatestFinalizedBatch returns the latest finalized batch from the kv store +func (c *Client) GetLatestFinalizedBatch() (*settlement.ResultRetrieveBatch, error) { + return nil, gerrc.ErrNotFound +} + // GetBatchAtIndex returns the batch at the given index func (c *Client) GetBatchAtIndex(index uint64) (*settlement.ResultRetrieveBatch, error) { batchResult, err := c.retrieveBatchAtStateIndex(index) @@ -209,7 +214,7 @@ func (c *Client) GetBatchAtIndex(index uint64) (*settlement.ResultRetrieveBatch, return batchResult, nil } -func (c *Client) GetHeightState(index uint64) (*settlement.ResultGetHeightState, error) { +func (c *Client) GetBatchAtHeight(height uint64) (*settlement.ResultRetrieveBatch, error) { panic("hub grpc client get height state is not implemented: implement me") // TODO: impl } diff --git a/settlement/local/local.go b/settlement/local/local.go index 78663a26f..e689ae1f7 100644 --- a/settlement/local/local.go +++ b/settlement/local/local.go @@ -159,6 +159,11 @@ func (c *Client) GetLatestBatch() (*settlement.ResultRetrieveBatch, error) { return batchResult, nil } +// GetLatestFinalizedBatch returns the latest finalized batch from the kv store +func (c *Client) GetLatestFinalizedBatch() (*settlement.ResultRetrieveBatch, error) { + return nil, gerrc.ErrNotFound // TODO: need to return a cosmos specific error? +} + // GetBatchAtIndex returns the batch at the given index func (c *Client) GetBatchAtIndex(index uint64) (*settlement.ResultRetrieveBatch, error) { batchResult, err := c.retrieveBatchAtStateIndex(index) @@ -170,22 +175,19 @@ func (c *Client) GetBatchAtIndex(index uint64) (*settlement.ResultRetrieveBatch, return batchResult, nil } -func (c *Client) GetHeightState(h uint64) (*settlement.ResultGetHeightState, error) { +func (c *Client) GetBatchAtHeight(h uint64) (*settlement.ResultRetrieveBatch, error) { c.mu.Lock() defer c.mu.Unlock() // TODO: optimize (binary search, or just make another index) for i := c.slStateIndex; i > 0; i-- { b, err := c.GetBatchAtIndex(i) if err != nil { - return nil, err + return &settlement.ResultRetrieveBatch{ + ResultBase: settlement.ResultBase{Code: settlement.StatusError, Message: err.Error()}, + }, err } if b.StartHeight <= h && b.EndHeight >= h { - return &settlement.ResultGetHeightState{ - ResultBase: settlement.ResultBase{Code: settlement.StatusSuccess}, - State: settlement.State{ - StateIndex: i, - }, - }, nil + return b, nil } } return nil, gerrc.ErrNotFound // TODO: need to return a cosmos specific error? diff --git a/settlement/local/local_test.go b/settlement/local/local_test.go index ce6bcd46a..ef71e123b 100644 --- a/settlement/local/local_test.go +++ b/settlement/local/local_test.go @@ -76,11 +76,11 @@ func TestSubmitBatch(t *testing.T) { require.NoError(err) assert.Equal(batch1.EndHeight(), queriedBatch.Batch.EndHeight) - state, err := sllayer.GetHeightState(1) + queriedBatchAtState, err := sllayer.GetBatchAtHeight(1) require.NoError(err) - assert.Equal(queriedBatch.StateIndex, state.State.StateIndex) + assert.Equal(queriedBatch.StateIndex, queriedBatchAtState.StateIndex) - queriedBatch, err = sllayer.GetBatchAtIndex(state.State.StateIndex) + queriedBatch, err = sllayer.GetBatchAtIndex(queriedBatchAtState.StateIndex) require.NoError(err) assert.Equal(batch1.EndHeight(), queriedBatch.Batch.EndHeight) @@ -94,11 +94,11 @@ func TestSubmitBatch(t *testing.T) { require.NoError(err) assert.Equal(batch2.EndHeight(), queriedBatch.Batch.EndHeight) - state, err = sllayer.GetHeightState(2) + queriedBatchAtState, err = sllayer.GetBatchAtHeight(2) require.NoError(err) - assert.Equal(queriedBatch.StateIndex, state.State.StateIndex) + assert.Equal(queriedBatch.StateIndex, queriedBatchAtState.StateIndex) - queriedBatch, err = sllayer.GetBatchAtIndex(state.State.StateIndex) + queriedBatch, err = sllayer.GetBatchAtIndex(queriedBatchAtState.StateIndex) require.NoError(err) assert.Equal(batch2.EndHeight(), queriedBatch.Batch.EndHeight) diff --git a/settlement/settlement.go b/settlement/settlement.go index b01647ac0..716601123 100644 --- a/settlement/settlement.go +++ b/settlement/settlement.go @@ -85,7 +85,10 @@ type ClientI interface { GetLatestBatch() (*ResultRetrieveBatch, error) // GetBatchAtIndex returns the batch at the given index. GetBatchAtIndex(index uint64) (*ResultRetrieveBatch, error) - + // GetBatchAtHeight returns the batch at the given height. + GetBatchAtHeight(index uint64) (*ResultRetrieveBatch, error) + // GetLatestFinalizedBatch returns the latest finalized batch from the settlement layer. + GetLatestFinalizedBatch() (*ResultRetrieveBatch, error) // GetAllSequencers returns all sequencers for this rollapp (bonded and not bonded). GetAllSequencers() ([]types.Sequencer, error) // GetBondedSequencers returns the list of the bonded sequencers for this rollapp. @@ -96,6 +99,4 @@ type ClientI interface { // CheckRotationInProgress returns the next proposer for this chain in case of a rotation. // If no rotation is in progress, it should return nil. CheckRotationInProgress() (*types.Sequencer, error) - - GetHeightState(uint64) (*ResultGetHeightState, error) } diff --git a/types/pb/dymint/state.pb.go b/types/pb/dymint/state.pb.go index 33c2a1de3..823a62c80 100644 --- a/types/pb/dymint/state.pb.go +++ b/types/pb/dymint/state.pb.go @@ -48,6 +48,7 @@ type State struct { SequencerSet SequencerSet `protobuf:"bytes,18,opt,name=sequencerSet,proto3" json:"sequencerSet"` RollappParams RollappParams `protobuf:"bytes,19,opt,name=rollapp_params,json=rollappParams,proto3" json:"rollapp_params"` LastHeaderHash []byte `protobuf:"bytes,20,opt,name=last_header_hash,json=lastHeaderHash,proto3" json:"last_header_hash,omitempty"` + LastValidatedHeight int64 `protobuf:"varint,21,opt,name=last_validated_height,json=lastValidatedHeight,proto3" json:"last_validated_height,omitempty"` } func (m *State) Reset() { *m = State{} } @@ -203,6 +204,13 @@ func (m *State) GetLastHeaderHash() []byte { return nil } +func (m *State) GetLastValidatedHeight() int64 { + if m != nil { + return m.LastValidatedHeight + } + return 0 +} + //rollapp params defined in genesis and updated via gov proposal type RollappParams struct { //data availability type (e.g. celestia) used in the rollapp @@ -266,51 +274,53 @@ func init() { func init() { proto.RegisterFile("types/dymint/state.proto", fileDescriptor_4b679420add07272) } var fileDescriptor_4b679420add07272 = []byte{ - // 701 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x54, 0xcd, 0x4e, 0xdb, 0x4a, - 0x18, 0x8d, 0x43, 0x20, 0xce, 0x84, 0xfc, 0x30, 0x70, 0x25, 0xc3, 0x95, 0x9c, 0xc0, 0xbd, 0xad, - 0xa2, 0x2e, 0x1c, 0xa9, 0xac, 0xba, 0x69, 0x25, 0xc3, 0x82, 0x20, 0x54, 0x55, 0x4e, 0xc5, 0xa2, - 0x1b, 0x6b, 0x6c, 0x4f, 0xed, 0x51, 0x1d, 0x8f, 0xeb, 0x99, 0xa0, 0xd2, 0xa7, 0xe0, 0xb1, 0x58, - 0xb2, 0xec, 0x8a, 0x56, 0xe1, 0x45, 0xaa, 0xf9, 0x71, 0xe2, 0x10, 0xb1, 0x4a, 0xe6, 0x7c, 0x67, - 0x8e, 0xcf, 0x9c, 0xef, 0x9b, 0x01, 0x16, 0xbf, 0xcd, 0x31, 0x1b, 0x47, 0xb7, 0x33, 0x92, 0xf1, - 0x31, 0xe3, 0x88, 0x63, 0x27, 0x2f, 0x28, 0xa7, 0x70, 0x47, 0x61, 0x47, 0x07, 0x31, 0x8d, 0xa9, - 0x84, 0xc6, 0xe2, 0x9f, 0xaa, 0x1e, 0x0d, 0x62, 0x4a, 0xe3, 0x14, 0x8f, 0xe5, 0x2a, 0x98, 0x7f, - 0x1d, 0x73, 0x32, 0xc3, 0x8c, 0xa3, 0x59, 0xae, 0x09, 0xc7, 0x4a, 0x98, 0xe3, 0x2c, 0xc2, 0x85, - 0x14, 0x47, 0x41, 0x48, 0xc6, 0x12, 0xd5, 0x94, 0x93, 0x0d, 0x8a, 0x06, 0x2a, 0x9c, 0xd7, 0x2f, - 0x70, 0x6e, 0x50, 0x4a, 0x22, 0xc4, 0x69, 0xa1, 0x79, 0xff, 0xbd, 0xc0, 0xcb, 0x51, 0x81, 0x66, - 0x2f, 0x7f, 0x50, 0x1e, 0x78, 0xed, 0x83, 0x87, 0x6b, 0x81, 0xa8, 0x1f, 0x55, 0x3a, 0x59, 0x34, - 0xc1, 0xf6, 0x54, 0x6c, 0x80, 0xa7, 0xa0, 0x79, 0x83, 0x0b, 0x46, 0x68, 0x66, 0x19, 0x43, 0x63, - 0xd4, 0x7e, 0x7b, 0xe8, 0xac, 0x44, 0x1d, 0x95, 0xe2, 0xb5, 0x22, 0x78, 0x25, 0x13, 0x1e, 0x02, - 0x33, 0x4c, 0x10, 0xc9, 0x7c, 0x12, 0x59, 0xf5, 0xa1, 0x31, 0x6a, 0x79, 0x4d, 0xb9, 0x9e, 0x44, - 0xf0, 0x15, 0xe8, 0x92, 0x8c, 0x70, 0x82, 0x52, 0x3f, 0xc1, 0x24, 0x4e, 0xb8, 0xb5, 0x35, 0x34, - 0x46, 0x5b, 0x5e, 0x47, 0xa3, 0x17, 0x12, 0x84, 0x6f, 0xc0, 0x5e, 0x8a, 0x18, 0xf7, 0x83, 0x94, - 0x86, 0xdf, 0x4a, 0x66, 0x43, 0x32, 0x7b, 0xa2, 0xe0, 0x0a, 0x5c, 0x73, 0x3d, 0xd0, 0xa9, 0x70, - 0x49, 0x64, 0x6d, 0x6f, 0x1a, 0x55, 0xe7, 0x96, 0xbb, 0x26, 0xe7, 0xee, 0xfe, 0xfd, 0xe3, 0xa0, - 0xb6, 0x78, 0x1c, 0xb4, 0xaf, 0x4a, 0xa9, 0xc9, 0xb9, 0xd7, 0x5e, 0xea, 0x4e, 0x22, 0x78, 0x05, - 0x7a, 0x15, 0x4d, 0xd1, 0x71, 0x6b, 0x47, 0xaa, 0x1e, 0x39, 0x6a, 0x1c, 0x9c, 0x72, 0x1c, 0x9c, - 0xcf, 0xe5, 0x38, 0xb8, 0xa6, 0x90, 0xbd, 0xfb, 0x3d, 0x30, 0xbc, 0xce, 0x52, 0x4b, 0x54, 0xa1, - 0x0b, 0xc0, 0xb2, 0x8b, 0xcc, 0x6a, 0x49, 0x21, 0x7b, 0xd3, 0xde, 0x75, 0xc9, 0x99, 0x62, 0xee, - 0xd6, 0x2d, 0xc3, 0xab, 0xec, 0x82, 0x67, 0xc0, 0x96, 0x8e, 0x54, 0x16, 0xfe, 0xaa, 0xe2, 0x87, - 0x09, 0xca, 0x62, 0x1c, 0x59, 0x6d, 0x19, 0xcf, 0xbf, 0x82, 0xa5, 0x92, 0x59, 0xea, 0xb1, 0x33, - 0x45, 0x81, 0x1e, 0xe8, 0x87, 0x34, 0x63, 0x38, 0x63, 0x73, 0xe6, 0xab, 0x81, 0xb1, 0x76, 0xa5, - 0x9d, 0xe3, 0x4d, 0x3b, 0x67, 0x25, 0xf3, 0x93, 0x24, 0xba, 0x0d, 0x71, 0x3c, 0xaf, 0x17, 0xae, - 0xc3, 0xf0, 0x23, 0xf8, 0xbf, 0x6a, 0xec, 0xb9, 0xfe, 0xd2, 0x5e, 0x47, 0xda, 0x1b, 0xae, 0xec, - 0x3d, 0xd3, 0x2f, 0x3d, 0x96, 0xad, 0x2f, 0x30, 0x9b, 0xa7, 0x9c, 0xf9, 0x09, 0x62, 0x89, 0xd5, - 0x1d, 0x1a, 0xa3, 0x5d, 0xd5, 0x7a, 0x4f, 0xe1, 0x17, 0x88, 0x25, 0x62, 0xd0, 0x50, 0x9e, 0x2b, - 0x4a, 0x4f, 0x52, 0x9a, 0x28, 0xcf, 0x65, 0xe9, 0x83, 0x96, 0x61, 0x9c, 0x16, 0xb8, 0x9c, 0xa0, - 0xfe, 0xd0, 0x18, 0x35, 0xdc, 0xfd, 0xc5, 0xe3, 0xa0, 0x27, 0x5a, 0x3f, 0x15, 0x35, 0x65, 0x46, - 0x69, 0x57, 0x00, 0x38, 0x00, 0xed, 0x00, 0xb1, 0xe5, 0xd6, 0x3d, 0xb1, 0xd5, 0x03, 0x02, 0xd2, - 0x84, 0xf7, 0x60, 0x97, 0xe1, 0xef, 0x73, 0x9c, 0x85, 0x58, 0x74, 0xcc, 0x82, 0x32, 0xc8, 0x03, - 0x47, 0xdf, 0xa4, 0x69, 0xa5, 0xa6, 0xb3, 0x5b, 0xe3, 0x43, 0x17, 0x74, 0x0b, 0x9a, 0xa6, 0xe2, - 0x00, 0xba, 0x15, 0xfb, 0x52, 0xe1, 0x9f, 0x52, 0xc1, 0x53, 0xd5, 0xb5, 0xf8, 0x3b, 0x45, 0x15, - 0x84, 0x23, 0xd0, 0xd7, 0xe1, 0xa3, 0x08, 0x17, 0x2a, 0x88, 0x03, 0x19, 0x44, 0x57, 0x05, 0x2d, - 0x60, 0x91, 0xc7, 0x65, 0xc3, 0x6c, 0xf6, 0xcd, 0xcb, 0x86, 0x69, 0xf6, 0x5b, 0x97, 0x0d, 0x13, - 0xf4, 0xdb, 0x27, 0xef, 0x40, 0x67, 0xed, 0x0b, 0xb0, 0x0b, 0xea, 0x11, 0x92, 0xd7, 0xbc, 0xe5, - 0xd5, 0x23, 0x04, 0xad, 0xd5, 0xdd, 0xd7, 0xb7, 0x58, 0x2f, 0xdd, 0x8b, 0xfb, 0x85, 0x6d, 0x3c, - 0x2c, 0x6c, 0xe3, 0xcf, 0xc2, 0x36, 0xee, 0x9e, 0xec, 0xda, 0xc3, 0x93, 0x5d, 0xfb, 0xf5, 0x64, - 0xd7, 0xbe, 0x38, 0x31, 0xe1, 0xc9, 0x3c, 0x70, 0x42, 0x3a, 0x13, 0x4f, 0x0a, 0xce, 0x04, 0xff, - 0xc7, 0xed, 0xcf, 0xf2, 0x99, 0xd1, 0x6f, 0x55, 0xa0, 0xd7, 0xc1, 0x8e, 0xbc, 0x47, 0xa7, 0x7f, - 0x03, 0x00, 0x00, 0xff, 0xff, 0x76, 0xee, 0xb0, 0xc3, 0x9e, 0x05, 0x00, 0x00, + // 721 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x54, 0xcd, 0x4e, 0xdb, 0x4c, + 0x14, 0x8d, 0x43, 0x20, 0xce, 0x84, 0xfc, 0x30, 0x01, 0xc9, 0xf0, 0x49, 0x4e, 0xe0, 0x6b, 0xab, + 0xa8, 0x0b, 0x47, 0x82, 0x55, 0x37, 0xad, 0x64, 0x58, 0x10, 0x84, 0xaa, 0xca, 0xa9, 0x58, 0x74, + 0x63, 0x8d, 0xed, 0xa9, 0x6d, 0xd5, 0xb1, 0x5d, 0xcf, 0x04, 0x95, 0x3e, 0x05, 0xab, 0x3e, 0x13, + 0x4b, 0x96, 0x5d, 0xd1, 0x2a, 0xbc, 0x48, 0x35, 0x7f, 0x89, 0x43, 0x94, 0x55, 0x32, 0xe7, 0x9e, + 0x39, 0x3e, 0x73, 0xee, 0x9d, 0x01, 0x06, 0xbd, 0xcb, 0x31, 0x19, 0x05, 0x77, 0xd3, 0x38, 0xa5, + 0x23, 0x42, 0x11, 0xc5, 0x56, 0x5e, 0x64, 0x34, 0x83, 0x3b, 0x02, 0x3b, 0xda, 0x0f, 0xb3, 0x30, + 0xe3, 0xd0, 0x88, 0xfd, 0x13, 0xd5, 0xa3, 0x7e, 0x98, 0x65, 0x61, 0x82, 0x47, 0x7c, 0xe5, 0xcd, + 0xbe, 0x8e, 0x68, 0x3c, 0xc5, 0x84, 0xa2, 0x69, 0x2e, 0x09, 0xc7, 0x42, 0x98, 0xe2, 0x34, 0xc0, + 0x05, 0x17, 0x47, 0x9e, 0x1f, 0x8f, 0x38, 0x2a, 0x29, 0x27, 0x6b, 0x14, 0x09, 0x94, 0x38, 0x6f, + 0x36, 0x70, 0x6e, 0x51, 0x12, 0x07, 0x88, 0x66, 0x85, 0xe4, 0xfd, 0xbf, 0x81, 0x97, 0xa3, 0x02, + 0x4d, 0x37, 0x7f, 0x90, 0x1f, 0x78, 0xe5, 0x83, 0x87, 0x2b, 0x81, 0x88, 0x1f, 0x51, 0x3a, 0xf9, + 0xa5, 0x83, 0xed, 0x09, 0xdb, 0x00, 0xcf, 0x40, 0xfd, 0x16, 0x17, 0x24, 0xce, 0x52, 0x43, 0x1b, + 0x68, 0xc3, 0xe6, 0xe9, 0xa1, 0xb5, 0x14, 0xb5, 0x44, 0x8a, 0x37, 0x82, 0xe0, 0x28, 0x26, 0x3c, + 0x04, 0xba, 0x1f, 0xa1, 0x38, 0x75, 0xe3, 0xc0, 0xa8, 0x0e, 0xb4, 0x61, 0xc3, 0xa9, 0xf3, 0xf5, + 0x38, 0x80, 0xaf, 0x41, 0x3b, 0x4e, 0x63, 0x1a, 0xa3, 0xc4, 0x8d, 0x70, 0x1c, 0x46, 0xd4, 0xd8, + 0x1a, 0x68, 0xc3, 0x2d, 0xa7, 0x25, 0xd1, 0x4b, 0x0e, 0xc2, 0xb7, 0x60, 0x2f, 0x41, 0x84, 0xba, + 0x5e, 0x92, 0xf9, 0xdf, 0x14, 0xb3, 0xc6, 0x99, 0x1d, 0x56, 0xb0, 0x19, 0x2e, 0xb9, 0x0e, 0x68, + 0x95, 0xb8, 0x71, 0x60, 0x6c, 0xaf, 0x1b, 0x15, 0xe7, 0xe6, 0xbb, 0xc6, 0x17, 0x76, 0xef, 0xe1, + 0xa9, 0x5f, 0x99, 0x3f, 0xf5, 0x9b, 0xd7, 0x4a, 0x6a, 0x7c, 0xe1, 0x34, 0x17, 0xba, 0xe3, 0x00, + 0x5e, 0x83, 0x4e, 0x49, 0x93, 0x75, 0xdc, 0xd8, 0xe1, 0xaa, 0x47, 0x96, 0x18, 0x07, 0x4b, 0x8d, + 0x83, 0xf5, 0x59, 0x8d, 0x83, 0xad, 0x33, 0xd9, 0xfb, 0x3f, 0x7d, 0xcd, 0x69, 0x2d, 0xb4, 0x58, + 0x15, 0xda, 0x00, 0x2c, 0xba, 0x48, 0x8c, 0x06, 0x17, 0x32, 0xd7, 0xed, 0xdd, 0x28, 0xce, 0x04, + 0x53, 0xbb, 0x6a, 0x68, 0x4e, 0x69, 0x17, 0x3c, 0x07, 0x26, 0x77, 0x24, 0xb2, 0x70, 0x97, 0x15, + 0xd7, 0x8f, 0x50, 0x1a, 0xe2, 0xc0, 0x68, 0xf2, 0x78, 0xfe, 0x63, 0x2c, 0x91, 0xcc, 0x42, 0x8f, + 0x9c, 0x0b, 0x0a, 0x74, 0x40, 0xd7, 0xcf, 0x52, 0x82, 0x53, 0x32, 0x23, 0xae, 0x18, 0x18, 0x63, + 0x97, 0xdb, 0x39, 0x5e, 0xb7, 0x73, 0xae, 0x98, 0x9f, 0x38, 0xd1, 0xae, 0xb1, 0xe3, 0x39, 0x1d, + 0x7f, 0x15, 0x86, 0x1f, 0xc1, 0xab, 0xb2, 0xb1, 0x97, 0xfa, 0x0b, 0x7b, 0x2d, 0x6e, 0x6f, 0xb0, + 0xb4, 0xf7, 0x42, 0x5f, 0x79, 0x54, 0xad, 0x2f, 0x30, 0x99, 0x25, 0x94, 0xb8, 0x11, 0x22, 0x91, + 0xd1, 0x1e, 0x68, 0xc3, 0x5d, 0xd1, 0x7a, 0x47, 0xe0, 0x97, 0x88, 0x44, 0x6c, 0xd0, 0x50, 0x9e, + 0x0b, 0x4a, 0x87, 0x53, 0xea, 0x28, 0xcf, 0x79, 0xe9, 0x83, 0x94, 0x21, 0x34, 0x2b, 0xb0, 0x9a, + 0xa0, 0xee, 0x40, 0x1b, 0xd6, 0xec, 0xde, 0xfc, 0xa9, 0xdf, 0x61, 0xad, 0x9f, 0xb0, 0x9a, 0x30, + 0x23, 0xb4, 0x4b, 0x00, 0xec, 0x83, 0xa6, 0x87, 0xc8, 0x62, 0xeb, 0x1e, 0xdb, 0xea, 0x00, 0x06, + 0x49, 0xc2, 0x7b, 0xb0, 0x4b, 0xf0, 0xf7, 0x19, 0x4e, 0x7d, 0xcc, 0x3a, 0x66, 0x40, 0x1e, 0xe4, + 0xbe, 0x25, 0x6f, 0xd2, 0xa4, 0x54, 0x93, 0xd9, 0xad, 0xf0, 0xa1, 0x0d, 0xda, 0x45, 0x96, 0x24, + 0xec, 0x00, 0xb2, 0x15, 0x3d, 0xae, 0x70, 0xa0, 0x14, 0x1c, 0x51, 0x5d, 0x89, 0xbf, 0x55, 0x94, + 0x41, 0x38, 0x04, 0x5d, 0x19, 0x3e, 0x0a, 0x70, 0x21, 0x82, 0xd8, 0xe7, 0x41, 0xb4, 0x45, 0xd0, + 0x0c, 0xe6, 0x79, 0x9c, 0x82, 0x03, 0xce, 0x94, 0x83, 0x83, 0x03, 0x75, 0xb0, 0x03, 0xde, 0x97, + 0x1e, 0x2b, 0xde, 0xa8, 0x9a, 0x38, 0xe1, 0x55, 0x4d, 0xaf, 0x77, 0xf5, 0xab, 0x9a, 0xae, 0x77, + 0x1b, 0x57, 0x35, 0x1d, 0x74, 0x9b, 0x27, 0xef, 0x40, 0x6b, 0xc5, 0x15, 0x6c, 0x83, 0x6a, 0x80, + 0xf8, 0xd3, 0xd0, 0x70, 0xaa, 0x01, 0x82, 0xc6, 0xf2, 0xbd, 0x90, 0x37, 0x5f, 0x2e, 0xed, 0xcb, + 0x87, 0xb9, 0xa9, 0x3d, 0xce, 0x4d, 0xed, 0xef, 0xdc, 0xd4, 0xee, 0x9f, 0xcd, 0xca, 0xe3, 0xb3, + 0x59, 0xf9, 0xfd, 0x6c, 0x56, 0xbe, 0x58, 0x61, 0x4c, 0xa3, 0x99, 0x67, 0xf9, 0xd9, 0x94, 0x3d, + 0x43, 0x38, 0x65, 0xfc, 0x1f, 0x77, 0x3f, 0xd5, 0xd3, 0x24, 0xdf, 0x37, 0x4f, 0xae, 0xbd, 0x1d, + 0x7e, 0xf7, 0xce, 0xfe, 0x05, 0x00, 0x00, 0xff, 0xff, 0xd2, 0x4a, 0x7a, 0xa7, 0xd2, 0x05, 0x00, + 0x00, } func (m *State) Marshal() (dAtA []byte, err error) { @@ -333,6 +343,13 @@ func (m *State) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.LastValidatedHeight != 0 { + i = encodeVarintState(dAtA, i, uint64(m.LastValidatedHeight)) + i-- + dAtA[i] = 0x1 + i-- + dAtA[i] = 0xa8 + } if len(m.LastHeaderHash) > 0 { i -= len(m.LastHeaderHash) copy(dAtA[i:], m.LastHeaderHash) @@ -582,6 +599,9 @@ func (m *State) Size() (n int) { if l > 0 { n += 2 + l + sovState(uint64(l)) } + if m.LastValidatedHeight != 0 { + n += 2 + sovState(uint64(m.LastValidatedHeight)) + } return n } @@ -1122,6 +1142,25 @@ func (m *State) Unmarshal(dAtA []byte) error { m.LastHeaderHash = []byte{} } iNdEx = postIndex + case 21: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field LastValidatedHeight", wireType) + } + m.LastValidatedHeight = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowState + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.LastValidatedHeight |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipState(dAtA[iNdEx:]) diff --git a/types/serialization.go b/types/serialization.go index 1ccabd1ff..5c5d520aa 100644 --- a/types/serialization.go +++ b/types/serialization.go @@ -267,6 +267,7 @@ func (s *State) ToProto() (*pb.State, error) { LastResultsHash: s.LastResultsHash[:], AppHash: s.AppHash[:], RollappParams: s.RollappParams, + LastValidatedHeight: int64(s.GetLastValidatedHeight()), }, nil } @@ -289,6 +290,7 @@ func (s *State) FromProto(other *pb.State) error { copy(s.LastResultsHash[:], other.LastResultsHash) copy(s.AppHash[:], other.AppHash) s.RollappParams = other.RollappParams + s.SetLastValidatedHeight(uint64(other.LastValidatedHeight)) return nil } diff --git a/types/state.go b/types/state.go index 1f4959a50..faeb8365a 100644 --- a/types/state.go +++ b/types/state.go @@ -45,6 +45,8 @@ type State struct { // LastHeaderHash is the hash of the last block header. LastHeaderHash [32]byte + // The last height which the node validated from available state updates. + LastValidatedHeight atomic.Uint64 } func (s *State) IsGenesis() bool { @@ -66,6 +68,17 @@ func (s *State) Height() uint64 { return s.LastBlockHeight.Load() } +// SetHeight sets the height saved in the Store if it is higher than the existing height +// returns OK if the value was updated successfully or did not need to be updated +func (s *State) SetLastValidatedHeight(height uint64) { + s.LastValidatedHeight.Store(height) +} + +// Height returns height of the highest block saved in the Store. +func (s *State) GetLastValidatedHeight() uint64 { + return s.LastValidatedHeight.Load() +} + // NextHeight returns the next height that expected to be stored in store. func (s *State) NextHeight() uint64 { if s.IsGenesis() { From ef378fc7df3ef1b2bbdb5bdc7278bef35a8ea741 Mon Sep 17 00:00:00 2001 From: Sergi Rene Date: Mon, 7 Oct 2024 22:35:07 +0200 Subject: [PATCH 13/29] missing files --- block/stateupdate_validator.go | 163 +++++++++++++++++++++++++++++++++ block/validate.go | 33 +++++++ 2 files changed, 196 insertions(+) create mode 100644 block/stateupdate_validator.go create mode 100644 block/validate.go diff --git a/block/stateupdate_validator.go b/block/stateupdate_validator.go new file mode 100644 index 000000000..c298c92fa --- /dev/null +++ b/block/stateupdate_validator.go @@ -0,0 +1,163 @@ +package block + +import ( + "bytes" + "crypto/sha256" + "fmt" + + "github.com/dymensionxyz/dymint/da" + "github.com/dymensionxyz/dymint/settlement" + "github.com/dymensionxyz/dymint/types" +) + +type P2PBlockValidator interface { + ValidateP2PBlocks(daBlocks []*types.Block, p2pBlocks []*types.Block) error +} + +type DABlocksValidator interface { + ValidateDaBlocks(slBatch *settlement.ResultRetrieveBatch, daBlocks []*types.Block) error +} + +// StateUpdateValidator is a validator for messages gossiped in the p2p network. +type StateUpdateValidator struct { + logger types.Logger + blockManager *Manager +} + +var _ DABlocksValidator = &StateUpdateValidator{} +var _ P2PBlockValidator = &StateUpdateValidator{} + +// NewValidator creates a new Validator. +func NewStateUpdateValidator(logger types.Logger, blockManager *Manager) *StateUpdateValidator { + return &StateUpdateValidator{ + logger: logger, + blockManager: blockManager, + } +} + +func (v *StateUpdateValidator) ValidateStateUpdate(batch *settlement.ResultRetrieveBatch) error { + + v.logger.Debug("validating state update", "start height", batch.StartHeight, "end height", batch.EndHeight) + + err := v.validateDRS(batch.StartHeight, batch.EndHeight, batch.DRSVersion) + if err != nil { + return err + } + + var daBlocks []*types.Block + var p2pBlocks []*types.Block + + //load blocks for the batch height, either P2P or DA blocks + for height := batch.StartHeight; height <= batch.EndHeight; height++ { + source, err := v.blockManager.Store.LoadBlockSource(height) + if err != nil { + return err + } + block, err := v.blockManager.Store.LoadBlock(height) + if err != nil { + return err + } + if source == types.DA.String() { + daBlocks = append(daBlocks, block) + } else { + p2pBlocks = append(p2pBlocks, block) + } + } + + numBlocks := batch.EndHeight - batch.StartHeight + 1 + if uint64(len(daBlocks)) != numBlocks { + daBatch := v.blockManager.Retriever.RetrieveBatches(batch.MetaData.DA) + if daBatch.Code != da.StatusSuccess { + return daBatch.Error + } + + for _, batch := range daBatch.Batches { + daBlocks = append(daBlocks, batch.Blocks...) + } + } + + err = v.ValidateDaBlocks(batch, daBlocks) + if err != nil { + return err + } + + if len(p2pBlocks) > 0 { + err = v.ValidateP2PBlocks(daBlocks, p2pBlocks) + if err != nil { + return err + } + } + + v.blockManager.State.SetLastValidatedHeight(batch.EndHeight) + return nil +} + +func (v *StateUpdateValidator) ValidateP2PBlocks(daBlocks []*types.Block, p2pBlocks []*types.Block) error { + + i := 0 + for _, daBlock := range daBlocks { + + if p2pBlocks[i].Header.Height != daBlocks[i].Header.Height { + break + } + p2pBlockHash, err := blockHash(p2pBlocks[i]) + if err != nil { + return err + } + i++ + daBlockHash, err := blockHash(daBlock) + if err != nil { + return err + } + if !bytes.Equal(p2pBlockHash, daBlockHash) { + return fmt.Errorf("failed comparing blocks") + } + } + return nil +} + +func (v *StateUpdateValidator) ValidateDaBlocks(slBatch *settlement.ResultRetrieveBatch, daBlocks []*types.Block) error { + + // check numblocks + numSlBlocks := len(slBatch.BlockDescriptors) + numDABlocks := len(daBlocks) + if numSlBlocks != numDABlocks { + return fmt.Errorf("num blocks mismatch between state update and DA batch. State index: %d State update blocks: %d DA batch blocks: %d", slBatch.StateIndex, numSlBlocks, numDABlocks) + } + + // check blocks + for i, bd := range slBatch.BlockDescriptors { + // height check + if bd.Height != daBlocks[i].Header.Height { + return fmt.Errorf("height mismatch between state update and DA batch. State index: %d SL height: %d DA height: %d", slBatch.StateIndex, bd.Height, daBlocks[i].Header.Height) + } + // we compare the state root between SL state info and DA block + if !bytes.Equal(bd.StateRoot, daBlocks[i].Header.AppHash[:]) { + return fmt.Errorf("state root mismatch between state update and DA batch. State index: %d: Height: %d State root SL: %d State root DA: %d", slBatch.StateIndex, bd.Height, bd.StateRoot, daBlocks[i].Header.AppHash[:]) + } + + // we compare the timestamp between SL state info and DA block + if bd.Timestamp != daBlocks[i].Header.GetTimestamp() { + return fmt.Errorf("timestamp mismatch between state update and DA batch. State index: %d: Height: %d Timestamp SL: %s Timestamp DA: %s", slBatch.StateIndex, bd.Height, bd.Timestamp, daBlocks[i].Header.GetTimestamp()) + } + } + + //TODO(srene): implement sequencer address validation + return nil +} + +func (v *StateUpdateValidator) validateDRS(startHeight, endHeight uint64, version string) error { + + //TODO(srene): implement DRS/height verification + return nil +} + +func blockHash(block *types.Block) ([]byte, error) { + blockBytes, err := block.MarshalBinary() + if err != nil { + return nil, fmt.Errorf("error hashing block. err: %w", err) + } + h := sha256.New() + h.Write(blockBytes) + return h.Sum(nil), nil +} diff --git a/block/validate.go b/block/validate.go new file mode 100644 index 000000000..19e9af4d1 --- /dev/null +++ b/block/validate.go @@ -0,0 +1,33 @@ +package block + +import "context" + +// SyncTargetLoop listens for syncing events (from new state update or from initial syncing) and syncs to the last submitted height. +// In case the node is already synced, it validate +func (m *Manager) ValidateLoop(ctx context.Context) error { + + for { + select { + case <-ctx.Done(): + return nil + case <-m.validateC: + + m.logger.Info("validating state updates to target height", "targetHeight", m.LastSubmittedHeight.Load()) + + validationStartHeight := max(m.LastSubmittedHeight.Load(), m.LastFinalizedHeight.Load()) + 1 + for currH := validationStartHeight; currH <= m.State.Height(); currH = m.LastSubmittedHeight.Load() { + + // Validate new state update + batch, err := m.SLClient.GetBatchAtHeight(m.LastSubmittedHeight.Load()) + + if err != nil { + return err + } + err = m.validator.ValidateStateUpdate(batch) + if err != nil { + m.logger.Error("state update validation", "error", err) + } + } + } + } +} From 3a034a465ba52a6cc6240451125492ce634b7d4e Mon Sep 17 00:00:00 2001 From: Sergi Rene Date: Mon, 7 Oct 2024 22:37:03 +0200 Subject: [PATCH 14/29] finalized height fix --- block/manager.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/block/manager.go b/block/manager.go index d9226dc7f..09279a4a6 100644 --- a/block/manager.go +++ b/block/manager.go @@ -316,9 +316,9 @@ func (m *Manager) syncFromSettlement() error { if errors.Is(err, gerrc.ErrNotFound) { // The SL hasn't got any batches for this chain yet. m.logger.Info("No finalized batches for chain found in SL.") - m.LastFinalizedHeight.Store(uint64(m.Genesis.InitialHeight - 1)) return nil } + m.LastFinalizedHeight.Store(res.EndHeight) return nil } From bab5019600161fe51ab23a601648d54f89350217 Mon Sep 17 00:00:00 2001 From: Sergi Rene Date: Tue, 8 Oct 2024 14:57:25 +0200 Subject: [PATCH 15/29] some fixes --- block/manager.go | 4 ++-- block/pruning.go | 2 +- block/stateupdate_validator.go | 15 +++++++++------ block/sync.go | 34 ++++++++++++++++++++++------------ block/validate.go | 20 +++++++++++++------- types/state.go | 5 +++++ 6 files changed, 52 insertions(+), 28 deletions(-) diff --git a/block/manager.go b/block/manager.go index 09279a4a6..c32ee5466 100644 --- a/block/manager.go +++ b/block/manager.go @@ -309,8 +309,8 @@ func (m *Manager) syncFromSettlement() error { m.LastSubmittedHeight.Store(res.EndHeight) m.UpdateTargetHeight(res.EndHeight) - m.syncingC <- struct{}{} - m.validateC <- struct{}{} + m.triggerStateUpdateSyncing() + m.triggerStateUpdateValidation() res, err = m.SLClient.GetLatestFinalizedBatch() if errors.Is(err, gerrc.ErrNotFound) { diff --git a/block/pruning.go b/block/pruning.go index d4ab18cc8..65aadf55b 100644 --- a/block/pruning.go +++ b/block/pruning.go @@ -9,7 +9,7 @@ import ( func (m *Manager) PruneBlocks(retainHeight uint64) (uint64, error) { nextSubmissionHeight := m.NextHeightToSubmit() if m.IsProposer() && nextSubmissionHeight < retainHeight { // do not delete anything that we might submit in future - m.logger.Debug("cannot prune blocks before they have been submitted. using height last submitted height for pruning", "retain_height", retainHeight, "height_to_submit", m.NextHeightToSubmit()) + m.logger.Debug("cannot prune blocks before they have been submitted. using last submitted height for pruning", "retain_height", retainHeight, "height_to_submit", m.NextHeightToSubmit()) retainHeight = nextSubmissionHeight } diff --git a/block/stateupdate_validator.go b/block/stateupdate_validator.go index c298c92fa..5b66ade60 100644 --- a/block/stateupdate_validator.go +++ b/block/stateupdate_validator.go @@ -51,11 +51,11 @@ func (v *StateUpdateValidator) ValidateStateUpdate(batch *settlement.ResultRetri for height := batch.StartHeight; height <= batch.EndHeight; height++ { source, err := v.blockManager.Store.LoadBlockSource(height) if err != nil { - return err + continue } block, err := v.blockManager.Store.LoadBlock(height) if err != nil { - return err + continue } if source == types.DA.String() { daBlocks = append(daBlocks, block) @@ -66,11 +66,11 @@ func (v *StateUpdateValidator) ValidateStateUpdate(batch *settlement.ResultRetri numBlocks := batch.EndHeight - batch.StartHeight + 1 if uint64(len(daBlocks)) != numBlocks { + daBlocks = []*types.Block{} daBatch := v.blockManager.Retriever.RetrieveBatches(batch.MetaData.DA) if daBatch.Code != da.StatusSuccess { return daBatch.Error } - for _, batch := range daBatch.Batches { daBlocks = append(daBlocks, batch.Blocks...) } @@ -104,7 +104,6 @@ func (v *StateUpdateValidator) ValidateP2PBlocks(daBlocks []*types.Block, p2pBlo if err != nil { return err } - i++ daBlockHash, err := blockHash(daBlock) if err != nil { return err @@ -112,6 +111,10 @@ func (v *StateUpdateValidator) ValidateP2PBlocks(daBlocks []*types.Block, p2pBlo if !bytes.Equal(p2pBlockHash, daBlockHash) { return fmt.Errorf("failed comparing blocks") } + i++ + if i == len(p2pBlocks) { + break + } } return nil } @@ -137,8 +140,8 @@ func (v *StateUpdateValidator) ValidateDaBlocks(slBatch *settlement.ResultRetrie } // we compare the timestamp between SL state info and DA block - if bd.Timestamp != daBlocks[i].Header.GetTimestamp() { - return fmt.Errorf("timestamp mismatch between state update and DA batch. State index: %d: Height: %d Timestamp SL: %s Timestamp DA: %s", slBatch.StateIndex, bd.Height, bd.Timestamp, daBlocks[i].Header.GetTimestamp()) + if !bd.Timestamp.Equal(daBlocks[i].Header.GetTimestamp()) { + return fmt.Errorf("timestamp mismatch between state update and DA batch. State index: %d: Height: %d Timestamp SL: %s Timestamp DA: %s", slBatch.StateIndex, bd.Height, bd.Timestamp.UTC(), daBlocks[i].Header.GetTimestamp().UTC()) } } diff --git a/block/sync.go b/block/sync.go index f1046d67c..b692565bb 100644 --- a/block/sync.go +++ b/block/sync.go @@ -30,18 +30,7 @@ func (m *Manager) onNewStateUpdate(event pubsub.Message) { } // Trigger syncing from DA. - select { - case m.syncingC <- struct{}{}: - default: - m.logger.Debug("disregarding new state update, node is still syncing") - } - - // Trigger state update validation. - select { - case m.validateC <- struct{}{}: - default: - m.logger.Debug("disregarding new state update, node is still validating") - } + m.triggerStateUpdateSyncing() } @@ -75,11 +64,14 @@ func (m *Manager) SyncLoop(ctx context.Context) error { m.logger.Info("Synced from DA", "store height", m.State.Height(), "target height", m.LastSubmittedHeight.Load()) + m.triggerStateUpdateValidation() + err = m.attemptApplyCachedBlocks() if err != nil { uevent.MustPublish(context.TODO(), m.Pubsub, &events.DataHealthStatus{Error: err}, events.HealthStatusList) m.logger.Error("Attempt apply cached blocks.", "err", err) } + } m.logger.Info("Synced.", "current height", m.State.Height(), "last submitted height", m.LastSubmittedHeight.Load()) @@ -96,3 +88,21 @@ func (m *Manager) waitForSyncing() { <-m.synced.C } } + +func (m *Manager) triggerStateUpdateValidation() { + // Trigger state update validation. + select { + case m.validateC <- struct{}{}: + default: + m.logger.Debug("disregarding new state update, node is still validating") + } +} + +func (m *Manager) triggerStateUpdateSyncing() { + // Trigger state update validation. + select { + case m.syncingC <- struct{}{}: + default: + m.logger.Debug("disregarding new state update, node is still syncing") + } +} diff --git a/block/validate.go b/block/validate.go index 19e9af4d1..cee727e5d 100644 --- a/block/validate.go +++ b/block/validate.go @@ -14,19 +14,25 @@ func (m *Manager) ValidateLoop(ctx context.Context) error { m.logger.Info("validating state updates to target height", "targetHeight", m.LastSubmittedHeight.Load()) - validationStartHeight := max(m.LastSubmittedHeight.Load(), m.LastFinalizedHeight.Load()) + 1 - for currH := validationStartHeight; currH <= m.State.Height(); currH = m.LastSubmittedHeight.Load() { - - // Validate new state update - batch, err := m.SLClient.GetBatchAtHeight(m.LastSubmittedHeight.Load()) + for currH := m.State.NextValidationHeight(); currH < m.NextHeightToSubmit(); currH = m.State.NextValidationHeight() { + // get next batch that needs to be validated from SL + batch, err := m.SLClient.GetBatchAtHeight(currH) if err != nil { - return err + m.logger.Error("failed batch retrieval", "error", err) + break } + // validate batch err = m.validator.ValidateStateUpdate(batch) if err != nil { - m.logger.Error("state update validation", "error", err) + panic(err) + } + + // this should not happen. if validation is successful m.State.NextValidationHeight() should advance. + if currH == m.State.NextValidationHeight() { + panic("validation not progressing") } + m.logger.Debug("state info validated", "batch end height", batch.EndHeight, "lastValidatedHeight", m.State.GetLastValidatedHeight()) } } } diff --git a/types/state.go b/types/state.go index faeb8365a..652ffa7eb 100644 --- a/types/state.go +++ b/types/state.go @@ -79,6 +79,11 @@ func (s *State) GetLastValidatedHeight() uint64 { return s.LastValidatedHeight.Load() } +// Height returns height of the highest block saved in the Store. +func (s *State) NextValidationHeight() uint64 { + return s.LastValidatedHeight.Load() + 1 +} + // NextHeight returns the next height that expected to be stored in store. func (s *State) NextHeight() uint64 { if s.IsGenesis() { From ed0d496339dc4fbfc42a48b41d8af5e3b070cfb0 Mon Sep 17 00:00:00 2001 From: Sergi Rene Date: Tue, 8 Oct 2024 15:44:19 +0200 Subject: [PATCH 16/29] minor edits --- block/manager.go | 14 +++++++------- block/sync.go | 14 ++++++++------ block/validate.go | 15 +++++++++++++-- types/state.go | 2 +- 4 files changed, 29 insertions(+), 16 deletions(-) diff --git a/block/manager.go b/block/manager.go index c32ee5466..06a8f64ee 100644 --- a/block/manager.go +++ b/block/manager.go @@ -63,9 +63,6 @@ type Manager struct { // prune anything that might be submitted in the future. Therefore, it must be atomic. LastSubmittedHeight atomic.Uint64 - // The last height which was finalized after dispute period, obtained from the Hub - LastFinalizedHeight atomic.Uint64 - /* Retrieval */ @@ -309,16 +306,19 @@ func (m *Manager) syncFromSettlement() error { m.LastSubmittedHeight.Store(res.EndHeight) m.UpdateTargetHeight(res.EndHeight) - m.triggerStateUpdateSyncing() - m.triggerStateUpdateValidation() - res, err = m.SLClient.GetLatestFinalizedBatch() if errors.Is(err, gerrc.ErrNotFound) { // The SL hasn't got any batches for this chain yet. m.logger.Info("No finalized batches for chain found in SL.") return nil } - m.LastFinalizedHeight.Store(res.EndHeight) + // update validation height with latest finalized height (it will be updated only of finalized height is higher) + m.State.SetLastValidatedHeight(res.EndHeight) + + // try to sync to last state update submitted on startup + m.triggerStateUpdateSyncing() + // try to validate all pending state updates on startup + m.triggerStateUpdateValidation() return nil } diff --git a/block/sync.go b/block/sync.go index b692565bb..577c5000e 100644 --- a/block/sync.go +++ b/block/sync.go @@ -74,6 +74,8 @@ func (m *Manager) SyncLoop(ctx context.Context) error { } + m.triggerStateUpdateValidation() + m.logger.Info("Synced.", "current height", m.State.Height(), "last submitted height", m.LastSubmittedHeight.Load()) m.synced.Nudge() @@ -89,20 +91,20 @@ func (m *Manager) waitForSyncing() { } } -func (m *Manager) triggerStateUpdateValidation() { +func (m *Manager) triggerStateUpdateSyncing() { // Trigger state update validation. select { - case m.validateC <- struct{}{}: + case m.syncingC <- struct{}{}: default: - m.logger.Debug("disregarding new state update, node is still validating") + m.logger.Debug("disregarding new state update, node is still syncing") } } -func (m *Manager) triggerStateUpdateSyncing() { +func (m *Manager) triggerStateUpdateValidation() { // Trigger state update validation. select { - case m.syncingC <- struct{}{}: + case m.validateC <- struct{}{}: default: - m.logger.Debug("disregarding new state update, node is still syncing") + m.logger.Debug("disregarding new state update, node is still validating") } } diff --git a/block/validate.go b/block/validate.go index cee727e5d..39f0421bb 100644 --- a/block/validate.go +++ b/block/validate.go @@ -1,6 +1,9 @@ package block -import "context" +import ( + "context" + "fmt" +) // SyncTargetLoop listens for syncing events (from new state update or from initial syncing) and syncs to the last submitted height. // In case the node is already synced, it validate @@ -14,7 +17,7 @@ func (m *Manager) ValidateLoop(ctx context.Context) error { m.logger.Info("validating state updates to target height", "targetHeight", m.LastSubmittedHeight.Load()) - for currH := m.State.NextValidationHeight(); currH < m.NextHeightToSubmit(); currH = m.State.NextValidationHeight() { + for currH := m.State.NextValidationHeight(); currH <= m.LastSubmittedHeight.Load(); currH = m.State.NextValidationHeight() { // get next batch that needs to be validated from SL batch, err := m.SLClient.GetBatchAtHeight(currH) @@ -32,8 +35,16 @@ func (m *Manager) ValidateLoop(ctx context.Context) error { if currH == m.State.NextValidationHeight() { panic("validation not progressing") } + + // update state with new validation height + _, err = m.Store.SaveState(m.State, nil) + if err != nil { + return fmt.Errorf("save state: %w", err) + } + m.logger.Debug("state info validated", "batch end height", batch.EndHeight, "lastValidatedHeight", m.State.GetLastValidatedHeight()) } + } } } diff --git a/types/state.go b/types/state.go index 652ffa7eb..e583ca21d 100644 --- a/types/state.go +++ b/types/state.go @@ -71,7 +71,7 @@ func (s *State) Height() uint64 { // SetHeight sets the height saved in the Store if it is higher than the existing height // returns OK if the value was updated successfully or did not need to be updated func (s *State) SetLastValidatedHeight(height uint64) { - s.LastValidatedHeight.Store(height) + s.LastValidatedHeight.Store(max(s.GetLastValidatedHeight(), height)) } // Height returns height of the highest block saved in the Store. From 35d2faa1919b1897dee1f998674afbcc109396d6 Mon Sep 17 00:00:00 2001 From: Sergi Rene Date: Tue, 8 Oct 2024 20:13:18 +0200 Subject: [PATCH 17/29] update finalized height fix --- block/fraud.go | 11 +++++++++++ block/manager.go | 30 +++++++++++++++++++++--------- block/sync.go | 22 ++++++++++++++-------- 3 files changed, 46 insertions(+), 17 deletions(-) diff --git a/block/fraud.go b/block/fraud.go index d1c724fd2..18910a746 100644 --- a/block/fraud.go +++ b/block/fraud.go @@ -29,3 +29,14 @@ func NewFreezeHandler(manager *Manager) *FreezeHandler { m: manager, } } + +// FreezeHandler is used to handle faults coming from executing and validating blocks. +type TestHandler struct{} + +func NewTestHandler() *TestHandler { + return &TestHandler{} +} + +func (f TestHandler) HandleFault(ctx context.Context, fault error) { + panic(fault) +} diff --git a/block/manager.go b/block/manager.go index 06a8f64ee..b92401bba 100644 --- a/block/manager.go +++ b/block/manager.go @@ -135,7 +135,7 @@ func NewManager( blockCache: &Cache{ cache: make(map[uint64]types.CachedBlock), }, - FraudHandler: nil, // TODO: create a default handler + FraudHandler: NewTestHandler(), // TODO: create a default handler pruningC: make(chan int64, 10), // use of buffered channel to avoid blocking applyBlock thread. In case channel is full, pruning will be skipped, but the retain height can be pruned in the next iteration. syncingC: make(chan struct{}, 1), validateC: make(chan struct{}, 1), @@ -306,14 +306,8 @@ func (m *Manager) syncFromSettlement() error { m.LastSubmittedHeight.Store(res.EndHeight) m.UpdateTargetHeight(res.EndHeight) - res, err = m.SLClient.GetLatestFinalizedBatch() - if errors.Is(err, gerrc.ErrNotFound) { - // The SL hasn't got any batches for this chain yet. - m.logger.Info("No finalized batches for chain found in SL.") - return nil - } - // update validation height with latest finalized height (it will be updated only of finalized height is higher) - m.State.SetLastValidatedHeight(res.EndHeight) + // get the latest finalized height to know from where to start validating + m.UpdateFinalizedHeight() // try to sync to last state update submitted on startup m.triggerStateUpdateSyncing() @@ -336,6 +330,24 @@ func (m *Manager) UpdateTargetHeight(h uint64) { } } +// UpdateFinalizedHeight retrieves the latest finalized batch and updates validation height with it +func (m *Manager) UpdateFinalizedHeight() error { + res, err := m.SLClient.GetLatestFinalizedBatch() + if err != nil && !errors.Is(err, gerrc.ErrNotFound) { + // The SL hasn't got any batches for this chain yet. + return fmt.Errorf("getting finalized height. err: %w", err) + } + if errors.Is(err, gerrc.ErrNotFound) { + // The SL hasn't got any batches for this chain yet. + m.logger.Info("No finalized batches for chain found in SL.") + m.State.SetLastValidatedHeight(0) + } else { + // update validation height with latest finalized height (it will be updated only of finalized height is higher) + m.State.SetLastValidatedHeight(res.EndHeight) + } + return nil +} + // ValidateConfigWithRollappParams checks the configuration params are consistent with the params in the dymint state (e.g. DA and version) func (m *Manager) ValidateConfigWithRollappParams() error { if version.Commit != m.State.RollappParams.Version { diff --git a/block/sync.go b/block/sync.go index 577c5000e..dd36ddddd 100644 --- a/block/sync.go +++ b/block/sync.go @@ -23,19 +23,26 @@ func (m *Manager) onNewStateUpdate(event pubsub.Message) { m.LastSubmittedHeight.Store(eventData.EndHeight) m.UpdateTargetHeight(eventData.EndHeight) + m.logger.Error("syncing") + // Update sequencers list from SL err := m.UpdateSequencerSetFromSL() if err != nil { m.logger.Error("update bonded sequencer set", "error", err) } - // Trigger syncing from DA. - m.triggerStateUpdateSyncing() + if eventData.EndHeight > m.State.Height() { + // Trigger syncing from DA. + m.triggerStateUpdateSyncing() + } else { + // trigger state update validation (in case no state update is applied) + m.triggerStateUpdateValidation() + } } -// SyncTargetLoop listens for syncing events (from new state update or from initial syncing) and syncs to the last submitted height. -// In case the node is already synced, it validate +// SyncLoop listens for syncing events (from new state update or from initial syncing) and syncs to the last submitted height. +// After syncing it sends signal to validation loop func (m *Manager) SyncLoop(ctx context.Context) error { for { @@ -64,6 +71,7 @@ func (m *Manager) SyncLoop(ctx context.Context) error { m.logger.Info("Synced from DA", "store height", m.State.Height(), "target height", m.LastSubmittedHeight.Load()) + // trigger state update validation, after each state update is applied m.triggerStateUpdateValidation() err = m.attemptApplyCachedBlocks() @@ -74,8 +82,6 @@ func (m *Manager) SyncLoop(ctx context.Context) error { } - m.triggerStateUpdateValidation() - m.logger.Info("Synced.", "current height", m.State.Height(), "last submitted height", m.LastSubmittedHeight.Load()) m.synced.Nudge() @@ -91,8 +97,8 @@ func (m *Manager) waitForSyncing() { } } +// triggerStateUpdateSyncing sends signal to channel used by syncing loop func (m *Manager) triggerStateUpdateSyncing() { - // Trigger state update validation. select { case m.syncingC <- struct{}{}: default: @@ -100,8 +106,8 @@ func (m *Manager) triggerStateUpdateSyncing() { } } +// triggerStateUpdateValidation sends signal to channel used by validation loop func (m *Manager) triggerStateUpdateValidation() { - // Trigger state update validation. select { case m.validateC <- struct{}{}: default: From 3013224e32cd3c9b5abf47e74b3dcccd9d9f15d7 Mon Sep 17 00:00:00 2001 From: Sergi Rene Date: Tue, 8 Oct 2024 20:22:19 +0200 Subject: [PATCH 18/29] remove test handler --- block/fraud.go | 11 ----------- block/manager.go | 2 +- block/validate.go | 2 +- 3 files changed, 2 insertions(+), 13 deletions(-) diff --git a/block/fraud.go b/block/fraud.go index 18910a746..d1c724fd2 100644 --- a/block/fraud.go +++ b/block/fraud.go @@ -29,14 +29,3 @@ func NewFreezeHandler(manager *Manager) *FreezeHandler { m: manager, } } - -// FreezeHandler is used to handle faults coming from executing and validating blocks. -type TestHandler struct{} - -func NewTestHandler() *TestHandler { - return &TestHandler{} -} - -func (f TestHandler) HandleFault(ctx context.Context, fault error) { - panic(fault) -} diff --git a/block/manager.go b/block/manager.go index b92401bba..72cca48b3 100644 --- a/block/manager.go +++ b/block/manager.go @@ -135,7 +135,7 @@ func NewManager( blockCache: &Cache{ cache: make(map[uint64]types.CachedBlock), }, - FraudHandler: NewTestHandler(), // TODO: create a default handler + FraudHandler: nil, // TODO: create a default handler pruningC: make(chan int64, 10), // use of buffered channel to avoid blocking applyBlock thread. In case channel is full, pruning will be skipped, but the retain height can be pruned in the next iteration. syncingC: make(chan struct{}, 1), validateC: make(chan struct{}, 1), diff --git a/block/validate.go b/block/validate.go index 39f0421bb..2cd9f4056 100644 --- a/block/validate.go +++ b/block/validate.go @@ -28,7 +28,7 @@ func (m *Manager) ValidateLoop(ctx context.Context) error { // validate batch err = m.validator.ValidateStateUpdate(batch) if err != nil { - panic(err) + m.FraudHandler.HandleFault(ctx, err) } // this should not happen. if validation is successful m.State.NextValidationHeight() should advance. From ff482a646f08c44906a49d003344b9c62d0da53a Mon Sep 17 00:00:00 2001 From: Sergi Rene Date: Tue, 8 Oct 2024 22:36:12 +0200 Subject: [PATCH 19/29] finalized event + tests --- block/manager.go | 4 +++- block/stateupdate_validator.go | 6 +++--- block/submit.go | 2 +- block/sync.go | 2 +- block/validate.go | 14 ++++++++++++++ settlement/dymension/dymension.go | 2 +- settlement/dymension/events.go | 21 +++++++++++++++------ settlement/events.go | 13 ++++++++----- settlement/grpc/grpc.go | 4 ++-- settlement/local/local.go | 2 +- testutil/types.go | 11 ++++++++++- 11 files changed, 59 insertions(+), 22 deletions(-) diff --git a/block/manager.go b/block/manager.go index 72cca48b3..5f55bba8b 100644 --- a/block/manager.go +++ b/block/manager.go @@ -204,7 +204,9 @@ func (m *Manager) Start(ctx context.Context) error { uerrors.ErrGroupGoLog(eg, m.logger, func() error { return m.ValidateLoop(ctx) }) - go uevent.MustSubscribe(ctx, m.Pubsub, "syncTargetLoop", settlement.EventQueryNewSettlementBatchAccepted, m.onNewStateUpdate, m.logger) + go uevent.MustSubscribe(ctx, m.Pubsub, "syncLoop", settlement.EventQueryNewSettlementBatchAccepted, m.onNewStateUpdate, m.logger) + go uevent.MustSubscribe(ctx, m.Pubsub, "validateLoop", settlement.EventQueryNewSettlementBatchFinalized, m.onNewStateUpdateFinalized, m.logger) + // P2P Sync. Subscribe to P2P received blocks events go uevent.MustSubscribe(ctx, m.Pubsub, "applyGossipedBlocksLoop", p2p.EventQueryNewGossipedBlock, m.OnReceivedBlock, m.logger) go uevent.MustSubscribe(ctx, m.Pubsub, "applyBlockSyncBlocksLoop", p2p.EventQueryNewBlockSyncBlock, m.OnReceivedBlock, m.logger) diff --git a/block/stateupdate_validator.go b/block/stateupdate_validator.go index 5b66ade60..8ffb7d551 100644 --- a/block/stateupdate_validator.go +++ b/block/stateupdate_validator.go @@ -97,7 +97,7 @@ func (v *StateUpdateValidator) ValidateP2PBlocks(daBlocks []*types.Block, p2pBlo i := 0 for _, daBlock := range daBlocks { - if p2pBlocks[i].Header.Height != daBlocks[i].Header.Height { + if p2pBlocks[i].Header.Height != daBlock.Header.Height { break } p2pBlockHash, err := blockHash(p2pBlocks[i]) @@ -109,7 +109,7 @@ func (v *StateUpdateValidator) ValidateP2PBlocks(daBlocks []*types.Block, p2pBlo return err } if !bytes.Equal(p2pBlockHash, daBlockHash) { - return fmt.Errorf("failed comparing blocks") + return fmt.Errorf("p2p block different from DA block. p2p height: %d, DA height: %d", p2pBlocks[i].Header.Height, daBlock.Header.Height) } i++ if i == len(p2pBlocks) { @@ -149,9 +149,9 @@ func (v *StateUpdateValidator) ValidateDaBlocks(slBatch *settlement.ResultRetrie return nil } +// TODO(srene): implement DRS/height verification func (v *StateUpdateValidator) validateDRS(startHeight, endHeight uint64, version string) error { - //TODO(srene): implement DRS/height verification return nil } diff --git a/block/submit.go b/block/submit.go index a330b2597..e6fb670f1 100644 --- a/block/submit.go +++ b/block/submit.go @@ -276,7 +276,7 @@ func (m *Manager) GetUnsubmittedBlocks() uint64 { // UpdateLastSubmittedHeight will update last height submitted height upon events. // This may be necessary in case we crashed/restarted before getting response for our submission to the settlement layer. func (m *Manager) UpdateLastSubmittedHeight(event pubsub.Message) { - eventData, ok := event.Data().(*settlement.EventDataNewBatchAccepted) + eventData, ok := event.Data().(*settlement.EventDataNewBatch) if !ok { m.logger.Error("onReceivedBatch", "err", "wrong event data received") return diff --git a/block/sync.go b/block/sync.go index dd36ddddd..fee3bdb7d 100644 --- a/block/sync.go +++ b/block/sync.go @@ -13,7 +13,7 @@ import ( // onNewStateUpdate will update the last submitted height and will update sequencers list from SL func (m *Manager) onNewStateUpdate(event pubsub.Message) { - eventData, ok := event.Data().(*settlement.EventDataNewBatchAccepted) + eventData, ok := event.Data().(*settlement.EventDataNewBatch) if !ok { m.logger.Error("onReceivedBatch", "err", "wrong event data received") return diff --git a/block/validate.go b/block/validate.go index 2cd9f4056..b9f5bc79d 100644 --- a/block/validate.go +++ b/block/validate.go @@ -3,8 +3,22 @@ package block import ( "context" "fmt" + + "github.com/dymensionxyz/dymint/settlement" + "github.com/tendermint/tendermint/libs/pubsub" ) +// onNewStateUpdate will update the last submitted height and will update sequencers list from SL +func (m *Manager) onNewStateUpdateFinalized(event pubsub.Message) { + eventData, ok := event.Data().(*settlement.EventDataNewBatch) + if !ok { + m.logger.Error("onReceivedBatch", "err", "wrong event data received") + return + } + m.State.SetLastValidatedHeight(eventData.EndHeight) + +} + // SyncTargetLoop listens for syncing events (from new state update or from initial syncing) and syncs to the last submitted height. // In case the node is already synced, it validate func (m *Manager) ValidateLoop(ctx context.Context) error { diff --git a/settlement/dymension/dymension.go b/settlement/dymension/dymension.go index e8588bfcc..2b2c38d07 100644 --- a/settlement/dymension/dymension.go +++ b/settlement/dymension/dymension.go @@ -169,7 +169,7 @@ func (c *Client) SubmitBatch(batch *types.Batch, daClient da.Client, daResult *d return fmt.Errorf("subscription cancelled") case event := <-subscription.Out(): - eventData, _ := event.Data().(*settlement.EventDataNewBatchAccepted) + eventData, _ := event.Data().(*settlement.EventDataNewBatch) if eventData.EndHeight != batch.EndHeight() { c.logger.Debug("Received event for a different batch, ignoring.", "event", eventData) continue // continue waiting for acceptance of the current batch diff --git a/settlement/dymension/events.go b/settlement/dymension/events.go index 05e31c06a..be541029f 100644 --- a/settlement/dymension/events.go +++ b/settlement/dymension/events.go @@ -15,6 +15,7 @@ import ( // TODO: use types and attributes from dymension proto const ( eventStateUpdateFmt = "state_update.rollapp_id='%s' AND state_update.status='PENDING'" + eventStateUpdateFinalizedFmt = "state_update.rollapp_id='%s' AND state_update.status='FINALIZED'" eventSequencersListUpdateFmt = "create_sequencer.rollapp_id='%s'" eventRotationStartedFmt = "proposer_rotation_started.rollapp_id='%s'" ) @@ -27,6 +28,8 @@ func (c *Client) getEventData(eventType string, rawEventData ctypes.ResultEvent) return convertToNewSequencerEvent(rawEventData) case settlement.EventRotationStarted: return convertToRotationStartedEvent(rawEventData) + case settlement.EventNewBatchFinalized: + return convertToNewBatchEvent(rawEventData) } return nil, fmt.Errorf("unrecognized event type: %s", eventType) } @@ -37,12 +40,14 @@ func (c *Client) eventHandler() { eventStateUpdateQ := fmt.Sprintf(eventStateUpdateFmt, c.rollappId) eventSequencersListQ := fmt.Sprintf(eventSequencersListUpdateFmt, c.rollappId) eventRotationStartedQ := fmt.Sprintf(eventRotationStartedFmt, c.rollappId) + eventStateUpdateFinalizedQ := fmt.Sprintf(eventStateUpdateFinalizedFmt, c.rollappId) // TODO: add validation callback for the event data eventMap := map[string]string{ - eventStateUpdateQ: settlement.EventNewBatchAccepted, - eventSequencersListQ: settlement.EventNewBondedSequencer, - eventRotationStartedQ: settlement.EventRotationStarted, + eventStateUpdateQ: settlement.EventNewBatchAccepted, + eventSequencersListQ: settlement.EventNewBondedSequencer, + eventRotationStartedQ: settlement.EventRotationStarted, + eventStateUpdateFinalizedQ: settlement.EventNewBatchFinalized, } stateUpdatesC, err := c.cosmosClient.SubscribeToEvents(c.ctx, subscriber, eventStateUpdateQ, 1000) @@ -57,7 +62,10 @@ func (c *Client) eventHandler() { if err != nil { panic(fmt.Errorf("subscribe to events (%s): %w", eventRotationStartedQ, err)) } - + stateUpdatesFinalizedC, err := c.cosmosClient.SubscribeToEvents(c.ctx, subscriber, eventStateUpdateFinalizedQ, 1000) + if err != nil { + panic(fmt.Errorf("subscribe to events (%s): %w", eventStateUpdateFinalizedQ, err)) + } defer c.cosmosClient.UnsubscribeAll(c.ctx, subscriber) //nolint:errcheck for { @@ -71,6 +79,7 @@ func (c *Client) eventHandler() { case e = <-stateUpdatesC: case e = <-sequencersListC: case e = <-rotationStartedC: + case e = <-stateUpdatesFinalizedC: } c.handleReceivedEvent(e, eventMap) } @@ -94,7 +103,7 @@ func (c *Client) handleReceivedEvent(event ctypes.ResultEvent, eventMap map[stri uevent.MustPublish(c.ctx, c.pubsub, eventData, map[string][]string{settlement.EventTypeKey: {internalType}}) } -func convertToNewBatchEvent(rawEventData ctypes.ResultEvent) (*settlement.EventDataNewBatchAccepted, error) { +func convertToNewBatchEvent(rawEventData ctypes.ResultEvent) (*settlement.EventDataNewBatch, error) { var errs []error // check all expected attributes exists events := rawEventData.Events @@ -118,7 +127,7 @@ func convertToNewBatchEvent(rawEventData ctypes.ResultEvent) (*settlement.EventD return nil, errors.Join(errs...) } endHeight := uint64(startHeight + numBlocks - 1) - NewBatchEvent := &settlement.EventDataNewBatchAccepted{ + NewBatchEvent := &settlement.EventDataNewBatch{ StartHeight: uint64(startHeight), EndHeight: endHeight, StateIndex: uint64(stateIndex), diff --git a/settlement/events.go b/settlement/events.go index d019c6022..2ff811410 100644 --- a/settlement/events.go +++ b/settlement/events.go @@ -14,6 +14,7 @@ const ( EventNewBatchAccepted = "NewBatchAccepted" EventNewBondedSequencer = "NewBondedSequencer" EventRotationStarted = "RotationStarted" + EventNewBatchFinalized = "NewBatchFinalized" ) // Convenience objects @@ -21,18 +22,20 @@ var ( EventNewBatchAcceptedList = map[string][]string{EventTypeKey: {EventNewBatchAccepted}} EventNewBondedSequencerList = map[string][]string{EventTypeKey: {EventNewBondedSequencer}} EventRotationStartedList = map[string][]string{EventTypeKey: {EventRotationStarted}} + EventNewBatchFinalizedList = map[string][]string{EventTypeKey: {EventNewBatchFinalized}} ) // Queries var ( - EventQueryNewSettlementBatchAccepted = uevent.QueryFor(EventTypeKey, EventNewBatchAccepted) - EventQueryNewBondedSequencer = uevent.QueryFor(EventTypeKey, EventNewBondedSequencer) - EventQueryRotationStarted = uevent.QueryFor(EventTypeKey, EventRotationStarted) + EventQueryNewSettlementBatchAccepted = uevent.QueryFor(EventTypeKey, EventNewBatchAccepted) + EventQueryNewSettlementBatchFinalized = uevent.QueryFor(EventTypeKey, EventNewBatchFinalized) + EventQueryNewBondedSequencer = uevent.QueryFor(EventTypeKey, EventNewBondedSequencer) + EventQueryRotationStarted = uevent.QueryFor(EventTypeKey, EventRotationStarted) ) // Data -type EventDataNewBatchAccepted struct { +type EventDataNewBatch struct { StartHeight uint64 // EndHeight is the height of the last accepted batch EndHeight uint64 @@ -40,7 +43,7 @@ type EventDataNewBatchAccepted struct { StateIndex uint64 } -func (e EventDataNewBatchAccepted) String() string { +func (e EventDataNewBatch) String() string { return fmt.Sprintf("EndHeight: %d, StateIndex: %d", e.EndHeight, e.StateIndex) } diff --git a/settlement/grpc/grpc.go b/settlement/grpc/grpc.go index 7db629da2..4567a0df2 100644 --- a/settlement/grpc/grpc.go +++ b/settlement/grpc/grpc.go @@ -152,7 +152,7 @@ func (c *Client) Start() error { if err != nil { panic(err) } - err = c.pubsub.PublishWithEvents(context.Background(), &settlement.EventDataNewBatchAccepted{EndHeight: b.EndHeight}, settlement.EventNewBatchAcceptedList) + err = c.pubsub.PublishWithEvents(context.Background(), &settlement.EventDataNewBatch{EndHeight: b.EndHeight}, settlement.EventNewBatchAcceptedList) if err != nil { panic(err) } @@ -181,7 +181,7 @@ func (c *Client) SubmitBatch(batch *types.Batch, daClient da.Client, daResult *d } time.Sleep(10 * time.Millisecond) // mimic a delay in batch acceptance - err = c.pubsub.PublishWithEvents(context.Background(), &settlement.EventDataNewBatchAccepted{EndHeight: settlementBatch.EndHeight}, settlement.EventNewBatchAcceptedList) + err = c.pubsub.PublishWithEvents(context.Background(), &settlement.EventDataNewBatch{EndHeight: settlementBatch.EndHeight}, settlement.EventNewBatchAcceptedList) if err != nil { return err } diff --git a/settlement/local/local.go b/settlement/local/local.go index e689ae1f7..c548d0bd1 100644 --- a/settlement/local/local.go +++ b/settlement/local/local.go @@ -142,7 +142,7 @@ func (c *Client) SubmitBatch(batch *types.Batch, daClient da.Client, daResult *d time.Sleep(100 * time.Millisecond) // mimic a delay in batch acceptance ctx := context.Background() - uevent.MustPublish(ctx, c.pubsub, settlement.EventDataNewBatchAccepted{EndHeight: settlementBatch.EndHeight}, settlement.EventNewBatchAcceptedList) + uevent.MustPublish(ctx, c.pubsub, settlement.EventDataNewBatch{EndHeight: settlementBatch.EndHeight}, settlement.EventNewBatchAcceptedList) return nil } diff --git a/testutil/types.go b/testutil/types.go index 812cd935a..cd1a92d14 100644 --- a/testutil/types.go +++ b/testutil/types.go @@ -38,6 +38,15 @@ func createRandomHashes() [][32]byte { return h } +func createRandomHash() [32]byte { + var h1 [32]byte + _, err := rand.Read(h1[:]) + if err != nil { + panic(err) + } + return h1 +} + func GetRandomTx() types.Tx { n, _ := rand.Int(rand.Reader, big.NewInt(100)) size := uint64(n.Int64()) + 100 @@ -66,7 +75,7 @@ func generateBlock(height uint64, lastHeaderHash [32]byte, proposerHash []byte, LastCommitHash: h[1], DataHash: h[2], ConsensusHash: h[3], - AppHash: [32]byte{}, + AppHash: createRandomHash(), LastResultsHash: GetEmptyLastResultsHash(), ProposerAddress: proposerAddress, SequencerHash: [32]byte(proposerHash), From f5b941cd2f14e830373f6718fc2e8b57ae8eb24b Mon Sep 17 00:00:00 2001 From: Sergi Rene Date: Tue, 8 Oct 2024 22:43:02 +0200 Subject: [PATCH 20/29] missing file --- block/stateupdate_validator_test.go | 187 ++++++++++++++++++++++++++++ 1 file changed, 187 insertions(+) create mode 100644 block/stateupdate_validator_test.go diff --git a/block/stateupdate_validator_test.go b/block/stateupdate_validator_test.go new file mode 100644 index 000000000..9d4897cdb --- /dev/null +++ b/block/stateupdate_validator_test.go @@ -0,0 +1,187 @@ +package block_test + +import ( + "crypto/rand" + "fmt" + "testing" + "time" + + "github.com/dymensionxyz/dymint/block" + "github.com/dymensionxyz/dymint/settlement" + "github.com/dymensionxyz/dymint/testutil" + "github.com/dymensionxyz/dymint/types" + "github.com/libp2p/go-libp2p/core/crypto" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestStateUpdateValidator_ValidateP2PBlocks(t *testing.T) { + validator := &block.StateUpdateValidator{} + + proposerKey, _, err := crypto.GenerateEd25519Key(rand.Reader) + require.NoError(t, err) + + batch, err := testutil.GenerateBatch(1, 10, proposerKey) + require.NoError(t, err) + + doubleSignedBatch, err := testutil.GenerateBatch(1, 10, proposerKey) + require.NoError(t, err) + + mixedBatch := make([]*types.Block, 10) + copy(mixedBatch, batch.Blocks) + mixedBatch[2] = doubleSignedBatch.Blocks[2] + + tests := []struct { + name string + daBlocks []*types.Block + p2pBlocks []*types.Block + wantErr bool + }{ + { + name: "Empty blocks", + daBlocks: []*types.Block{}, + p2pBlocks: []*types.Block{}, + wantErr: false, + }, + { + name: "Matching blocks", + daBlocks: batch.Blocks, + p2pBlocks: batch.Blocks, + wantErr: false, + }, + { + name: "double signing", + daBlocks: batch.Blocks, + p2pBlocks: doubleSignedBatch.Blocks, + wantErr: true, + }, + { + name: "mixed blocks", + daBlocks: batch.Blocks, + p2pBlocks: mixedBatch, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := validator.ValidateP2PBlocks(tt.daBlocks, tt.p2pBlocks) + if tt.wantErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestStateUpdateValidator_ValidateDaBlocks(t *testing.T) { + + validator := &block.StateUpdateValidator{} + + proposerKey, _, err := crypto.GenerateEd25519Key(rand.Reader) + require.NoError(t, err) + + batch, err := testutil.GenerateBatch(1, 2, proposerKey) + require.NoError(t, err) + + tests := []struct { + name string + slBatch *settlement.ResultRetrieveBatch + daBlocks []*types.Block + expectedError error + }{ + { + name: "Happy path - all validations pass", + slBatch: &settlement.ResultRetrieveBatch{ + Batch: &settlement.Batch{ + BlockDescriptors: []settlement.BlockDescriptor{ + {Height: 1, StateRoot: batch.Blocks[0].Header.AppHash[:], Timestamp: batch.Blocks[0].Header.GetTimestamp()}, + {Height: 2, StateRoot: batch.Blocks[1].Header.AppHash[:], Timestamp: batch.Blocks[0].Header.GetTimestamp()}, + }, + }, + ResultBase: settlement.ResultBase{ + StateIndex: 1, + }, + }, + daBlocks: batch.Blocks, + expectedError: nil, + }, + { + name: "Error - number of blocks mismatch", + slBatch: &settlement.ResultRetrieveBatch{ + Batch: &settlement.Batch{ + BlockDescriptors: []settlement.BlockDescriptor{ + {Height: 1, StateRoot: batch.Blocks[0].Header.AppHash[:], Timestamp: batch.Blocks[0].Header.GetTimestamp()}, + {Height: 2, StateRoot: batch.Blocks[1].Header.AppHash[:], Timestamp: batch.Blocks[1].Header.GetTimestamp()}, + }, + }, + ResultBase: settlement.ResultBase{ + StateIndex: 1, + }, + }, + daBlocks: []*types.Block{batch.Blocks[0]}, + expectedError: fmt.Errorf("num blocks mismatch between state update and DA batch. State index: 1 State update blocks: 2 DA batch blocks: 1"), + }, + { + name: "Error - height mismatch", + slBatch: &settlement.ResultRetrieveBatch{ + Batch: &settlement.Batch{ + BlockDescriptors: []settlement.BlockDescriptor{ + {Height: 101, StateRoot: batch.Blocks[0].Header.AppHash[:], Timestamp: batch.Blocks[0].Header.GetTimestamp()}, + {Height: 102, StateRoot: batch.Blocks[1].Header.AppHash[:], Timestamp: batch.Blocks[1].Header.GetTimestamp()}, + }, + }, + ResultBase: settlement.ResultBase{ + StateIndex: 1, + }, + }, + daBlocks: batch.Blocks, + expectedError: fmt.Errorf("height mismatch between state update and DA batch. State index: 1 SL height: 101 DA height: 1"), + }, + { + name: "Error - state root mismatch", + slBatch: &settlement.ResultRetrieveBatch{ + Batch: &settlement.Batch{ + BlockDescriptors: []settlement.BlockDescriptor{ + {Height: 1, StateRoot: batch.Blocks[1].Header.AppHash[:], Timestamp: batch.Blocks[0].Header.GetTimestamp()}, + {Height: 2, StateRoot: batch.Blocks[1].Header.AppHash[:], Timestamp: batch.Blocks[1].Header.GetTimestamp()}, + }, + }, + ResultBase: settlement.ResultBase{ + StateIndex: 1, + }, + }, + daBlocks: batch.Blocks, + expectedError: fmt.Errorf("state root mismatch between state update and DA batch. State index: 1: Height: 1 State root SL: %d State root DA: %d", batch.Blocks[1].Header.AppHash[:], batch.Blocks[0].Header.AppHash[:]), + }, + { + name: "Error - timestamp mismatch", + slBatch: &settlement.ResultRetrieveBatch{ + Batch: &settlement.Batch{ + BlockDescriptors: []settlement.BlockDescriptor{ + {Height: 1, StateRoot: batch.Blocks[0].Header.AppHash[:], Timestamp: batch.Blocks[0].Header.GetTimestamp()}, + {Height: 2, StateRoot: batch.Blocks[1].Header.AppHash[:], Timestamp: batch.Blocks[1].Header.GetTimestamp().Add(1 * time.Second)}, + }, + }, + ResultBase: settlement.ResultBase{ + StateIndex: 1, + }, + }, + daBlocks: batch.Blocks, + expectedError: fmt.Errorf("timestamp mismatch between state update and DA batch. State index: 1: Height: 2 Timestamp SL: %s Timestamp DA: %s", + batch.Blocks[1].Header.GetTimestamp().UTC().Add(1*time.Second), batch.Blocks[1].Header.GetTimestamp().UTC()), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := validator.ValidateDaBlocks(tt.slBatch, tt.daBlocks) + + if tt.expectedError != nil { + assert.EqualError(t, err, tt.expectedError.Error()) + } else { + assert.NoError(t, err) + } + }) + } +} From 9270799fb87a53323ab8730eae01fbeae8b2efdb Mon Sep 17 00:00:00 2001 From: Sergi Rene Date: Wed, 9 Oct 2024 09:21:52 +0200 Subject: [PATCH 21/29] checkin missed error --- block/manager.go | 6 ++++-- block/retriever.go | 3 --- block/stateupdate_validator.go | 14 ++++++-------- block/sync.go | 2 -- block/validate.go | 2 -- p2p/block.go | 1 - settlement/dymension/dymension.go | 4 ++-- settlement/grpc/grpc.go | 1 - store/store.go | 3 --- 9 files changed, 12 insertions(+), 24 deletions(-) diff --git a/block/manager.go b/block/manager.go index 5f55bba8b..ded5c4bdd 100644 --- a/block/manager.go +++ b/block/manager.go @@ -285,7 +285,6 @@ func (m *Manager) NextHeightToSubmit() uint64 { // syncFromSettlement enforces the node to be synced on initial run from SL and DA. func (m *Manager) syncFromSettlement() error { - // Update sequencers list from SL err := m.UpdateSequencerSetFromSL() if err != nil { @@ -309,7 +308,10 @@ func (m *Manager) syncFromSettlement() error { m.UpdateTargetHeight(res.EndHeight) // get the latest finalized height to know from where to start validating - m.UpdateFinalizedHeight() + err = m.UpdateFinalizedHeight() + if err != nil { + return err + } // try to sync to last state update submitted on startup m.triggerStateUpdateSyncing() diff --git a/block/retriever.go b/block/retriever.go index d7e0729ce..319a88140 100644 --- a/block/retriever.go +++ b/block/retriever.go @@ -10,7 +10,6 @@ import ( ) func (m *Manager) syncFromDABatch() error { - settlementBatch, err := m.SLClient.GetBatchAtHeight(m.State.NextHeight()) if err != nil { return fmt.Errorf("retrieve batch: %w", err) @@ -32,7 +31,6 @@ func (m *Manager) syncFromDABatch() error { } func (m *Manager) applyLocalBlock(height uint64) error { - defer m.retrieverMu.Unlock() m.retrieverMu.Lock() @@ -54,7 +52,6 @@ func (m *Manager) applyLocalBlock(height uint64) error { } func (m *Manager) ProcessNextDABatch(daMetaData *da.DASubmitMetaData) error { - m.logger.Debug("trying to retrieve batch from DA", "daHeight", daMetaData.Height) batchResp := m.fetchBatch(daMetaData) if batchResp.Code != da.StatusSuccess { diff --git a/block/stateupdate_validator.go b/block/stateupdate_validator.go index 8ffb7d551..6e56a649f 100644 --- a/block/stateupdate_validator.go +++ b/block/stateupdate_validator.go @@ -24,8 +24,10 @@ type StateUpdateValidator struct { blockManager *Manager } -var _ DABlocksValidator = &StateUpdateValidator{} -var _ P2PBlockValidator = &StateUpdateValidator{} +var ( + _ DABlocksValidator = &StateUpdateValidator{} + _ P2PBlockValidator = &StateUpdateValidator{} +) // NewValidator creates a new Validator. func NewStateUpdateValidator(logger types.Logger, blockManager *Manager) *StateUpdateValidator { @@ -36,7 +38,6 @@ func NewStateUpdateValidator(logger types.Logger, blockManager *Manager) *StateU } func (v *StateUpdateValidator) ValidateStateUpdate(batch *settlement.ResultRetrieveBatch) error { - v.logger.Debug("validating state update", "start height", batch.StartHeight, "end height", batch.EndHeight) err := v.validateDRS(batch.StartHeight, batch.EndHeight, batch.DRSVersion) @@ -47,7 +48,7 @@ func (v *StateUpdateValidator) ValidateStateUpdate(batch *settlement.ResultRetri var daBlocks []*types.Block var p2pBlocks []*types.Block - //load blocks for the batch height, either P2P or DA blocks + // load blocks for the batch height, either P2P or DA blocks for height := batch.StartHeight; height <= batch.EndHeight; height++ { source, err := v.blockManager.Store.LoadBlockSource(height) if err != nil { @@ -93,7 +94,6 @@ func (v *StateUpdateValidator) ValidateStateUpdate(batch *settlement.ResultRetri } func (v *StateUpdateValidator) ValidateP2PBlocks(daBlocks []*types.Block, p2pBlocks []*types.Block) error { - i := 0 for _, daBlock := range daBlocks { @@ -120,7 +120,6 @@ func (v *StateUpdateValidator) ValidateP2PBlocks(daBlocks []*types.Block, p2pBlo } func (v *StateUpdateValidator) ValidateDaBlocks(slBatch *settlement.ResultRetrieveBatch, daBlocks []*types.Block) error { - // check numblocks numSlBlocks := len(slBatch.BlockDescriptors) numDABlocks := len(daBlocks) @@ -145,13 +144,12 @@ func (v *StateUpdateValidator) ValidateDaBlocks(slBatch *settlement.ResultRetrie } } - //TODO(srene): implement sequencer address validation + // TODO(srene): implement sequencer address validation return nil } // TODO(srene): implement DRS/height verification func (v *StateUpdateValidator) validateDRS(startHeight, endHeight uint64, version string) error { - return nil } diff --git a/block/sync.go b/block/sync.go index fee3bdb7d..59647ad4e 100644 --- a/block/sync.go +++ b/block/sync.go @@ -38,13 +38,11 @@ func (m *Manager) onNewStateUpdate(event pubsub.Message) { // trigger state update validation (in case no state update is applied) m.triggerStateUpdateValidation() } - } // SyncLoop listens for syncing events (from new state update or from initial syncing) and syncs to the last submitted height. // After syncing it sends signal to validation loop func (m *Manager) SyncLoop(ctx context.Context) error { - for { select { case <-ctx.Done(): diff --git a/block/validate.go b/block/validate.go index b9f5bc79d..b6ee92b9a 100644 --- a/block/validate.go +++ b/block/validate.go @@ -16,13 +16,11 @@ func (m *Manager) onNewStateUpdateFinalized(event pubsub.Message) { return } m.State.SetLastValidatedHeight(eventData.EndHeight) - } // SyncTargetLoop listens for syncing events (from new state update or from initial syncing) and syncs to the last submitted height. // In case the node is already synced, it validate func (m *Manager) ValidateLoop(ctx context.Context) error { - for { select { case <-ctx.Done(): diff --git a/p2p/block.go b/p2p/block.go index 031827ba5..d6da3da96 100644 --- a/p2p/block.go +++ b/p2p/block.go @@ -55,7 +55,6 @@ func (b *BlockData) FromProto(other *pb.BlockData) error { // Validate run basic validation on the p2p block received func (b *BlockData) Validate(proposerPubKey tmcrypto.PubKey) error { - if err := b.Block.ValidateBasic(); err != nil { return err } diff --git a/settlement/dymension/dymension.go b/settlement/dymension/dymension.go index 2b2c38d07..f81d62abb 100644 --- a/settlement/dymension/dymension.go +++ b/settlement/dymension/dymension.go @@ -220,7 +220,8 @@ func (c *Client) SubmitBatch(batch *types.Batch, daClient da.Client, daResult *d func (c *Client) getStateInfo(index, height *uint64, finalized bool) (res *rollapptypes.QueryGetStateInfoResponse, err error) { req := &rollapptypes.QueryGetStateInfoRequest{ RollappId: c.rollappId, - Finalized: finalized} + Finalized: finalized, + } if index != nil { req.Index = *index } @@ -273,7 +274,6 @@ func (c *Client) GetBatchAtIndex(index uint64) (*settlement.ResultRetrieveBatch, // GetBatchAtHeight returns the batch at the given height from the Dymension Hub. func (c *Client) GetBatchAtHeight(height uint64) (*settlement.ResultRetrieveBatch, error) { - res, err := c.getStateInfo(nil, &height, false) if err != nil { return nil, fmt.Errorf("get state info: %w", err) diff --git a/settlement/grpc/grpc.go b/settlement/grpc/grpc.go index 4567a0df2..723aae1d0 100644 --- a/settlement/grpc/grpc.go +++ b/settlement/grpc/grpc.go @@ -278,7 +278,6 @@ func (c *Client) saveBatch(batch *settlement.Batch) error { } func (c *Client) convertBatchtoSettlementBatch(batch *types.Batch, daResult *da.ResultSubmitBatch) *settlement.Batch { - bds := []settlement.BlockDescriptor{} for _, block := range batch.Blocks { bd := settlement.BlockDescriptor{ diff --git a/store/store.go b/store/store.go index 9a2160325..a236faeb5 100644 --- a/store/store.go +++ b/store/store.go @@ -119,7 +119,6 @@ func (s *DefaultStore) LoadBlockByHash(hash [32]byte) (*types.Block, error) { // SaveBlockValidation saves block validation in Store. func (s *DefaultStore) SaveBlockSource(height uint64, source string, batch KVBatch) (KVBatch, error) { - if batch == nil { return nil, s.db.Set(getSourceKey(height), []byte(source)) } @@ -129,14 +128,12 @@ func (s *DefaultStore) SaveBlockSource(height uint64, source string, batch KVBat // LoadBlockValidation returns block validation in Store. func (s *DefaultStore) LoadBlockSource(height uint64) (string, error) { - source, err := s.db.Get(getSourceKey(height)) if err != nil { return "", fmt.Errorf("retrieve block results from height %v: %w", height, err) } return string(source[:]), nil - } // SaveBlockResponses saves block responses (events, tx responses, etc) in Store. From 54d729b2e6ab462617a745fae3346ef3715d43c7 Mon Sep 17 00:00:00 2001 From: Sergi Rene Date: Wed, 9 Oct 2024 15:20:46 +0200 Subject: [PATCH 22/29] tests fixed --- block/manager_test.go | 1 - block/stateupdate_validator_test.go | 12 ++++++------ testutil/types.go | 21 ++++++--------------- 3 files changed, 12 insertions(+), 22 deletions(-) diff --git a/block/manager_test.go b/block/manager_test.go index 4e2e7b7a9..ebcbf50ef 100644 --- a/block/manager_test.go +++ b/block/manager_test.go @@ -387,7 +387,6 @@ func TestApplyLocalBlock_WithFraudCheck(t *testing.T) { }() <-ctx.Done() assert.Equal(t, batchs[1].EndHeight(), manager.LastSubmittedHeight.Load()) - mockExecutor.AssertExpectations(t) mockFraudHandler.AssertExpectations(t) } diff --git a/block/stateupdate_validator_test.go b/block/stateupdate_validator_test.go index 9d4897cdb..29b8b5625 100644 --- a/block/stateupdate_validator_test.go +++ b/block/stateupdate_validator_test.go @@ -21,10 +21,10 @@ func TestStateUpdateValidator_ValidateP2PBlocks(t *testing.T) { proposerKey, _, err := crypto.GenerateEd25519Key(rand.Reader) require.NoError(t, err) - batch, err := testutil.GenerateBatch(1, 10, proposerKey) + batch, err := testutil.GenerateBatch(1, 10, proposerKey, "test", [32]byte{}) require.NoError(t, err) - doubleSignedBatch, err := testutil.GenerateBatch(1, 10, proposerKey) + doubleSignedBatch, err := testutil.GenerateBatch(1, 10, proposerKey, "test", [32]byte{}) require.NoError(t, err) mixedBatch := make([]*types.Block, 10) @@ -82,7 +82,7 @@ func TestStateUpdateValidator_ValidateDaBlocks(t *testing.T) { proposerKey, _, err := crypto.GenerateEd25519Key(rand.Reader) require.NoError(t, err) - batch, err := testutil.GenerateBatch(1, 2, proposerKey) + batch, err := testutil.GenerateBatch(1, 2, proposerKey, "test", [32]byte{}) require.NoError(t, err) tests := []struct { @@ -144,8 +144,8 @@ func TestStateUpdateValidator_ValidateDaBlocks(t *testing.T) { slBatch: &settlement.ResultRetrieveBatch{ Batch: &settlement.Batch{ BlockDescriptors: []settlement.BlockDescriptor{ - {Height: 1, StateRoot: batch.Blocks[1].Header.AppHash[:], Timestamp: batch.Blocks[0].Header.GetTimestamp()}, - {Height: 2, StateRoot: batch.Blocks[1].Header.AppHash[:], Timestamp: batch.Blocks[1].Header.GetTimestamp()}, + {Height: 1, StateRoot: batch.Blocks[0].Header.AppHash[:], Timestamp: batch.Blocks[0].Header.GetTimestamp()}, + {Height: 2, StateRoot: []byte{1, 2, 3, 4}, Timestamp: batch.Blocks[1].Header.GetTimestamp()}, }, }, ResultBase: settlement.ResultBase{ @@ -153,7 +153,7 @@ func TestStateUpdateValidator_ValidateDaBlocks(t *testing.T) { }, }, daBlocks: batch.Blocks, - expectedError: fmt.Errorf("state root mismatch between state update and DA batch. State index: 1: Height: 1 State root SL: %d State root DA: %d", batch.Blocks[1].Header.AppHash[:], batch.Blocks[0].Header.AppHash[:]), + expectedError: fmt.Errorf("state root mismatch between state update and DA batch. State index: 1: Height: 2 State root SL: %d State root DA: %d", []byte{1, 2, 3, 4}, batch.Blocks[0].Header.AppHash[:]), }, { name: "Error - timestamp mismatch", diff --git a/testutil/types.go b/testutil/types.go index cd1a92d14..365538e9f 100644 --- a/testutil/types.go +++ b/testutil/types.go @@ -27,7 +27,7 @@ const ( func createRandomHashes() [][32]byte { h := [][32]byte{} - for i := 0; i < 8; i++ { + for i := 0; i < 4; i++ { var h1 [32]byte _, err := rand.Read(h1[:]) if err != nil { @@ -38,15 +38,6 @@ func createRandomHashes() [][32]byte { return h } -func createRandomHash() [32]byte { - var h1 [32]byte - _, err := rand.Read(h1[:]) - if err != nil { - panic(err) - } - return h1 -} - func GetRandomTx() types.Tx { n, _ := rand.Int(rand.Reader, big.NewInt(100)) size := uint64(n.Int64()) + 100 @@ -72,10 +63,10 @@ func generateBlock(height uint64, lastHeaderHash [32]byte, proposerHash []byte, Height: height, Time: 4567, LastHeaderHash: lastHeaderHash, - LastCommitHash: h[1], - DataHash: h[2], - ConsensusHash: h[3], - AppHash: createRandomHash(), + LastCommitHash: h[0], + DataHash: h[1], + ConsensusHash: h[2], + AppHash: [32]byte{}, LastResultsHash: GetEmptyLastResultsHash(), ProposerAddress: proposerAddress, SequencerHash: [32]byte(proposerHash), @@ -89,7 +80,7 @@ func generateBlock(height uint64, lastHeaderHash [32]byte, proposerHash []byte, }, LastCommit: types.Commit{ Height: 8, - HeaderHash: h[7], + HeaderHash: h[3], Signatures: []types.Signature{}, }, } From e24d96fd1413d1595d7a86c07be1d2ef5e29021f Mon Sep 17 00:00:00 2001 From: Sergi Rene Date: Wed, 9 Oct 2024 15:33:54 +0200 Subject: [PATCH 23/29] retain height --- block/pruning.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/block/pruning.go b/block/pruning.go index 65aadf55b..833263fed 100644 --- a/block/pruning.go +++ b/block/pruning.go @@ -7,10 +7,10 @@ import ( // PruneBlocks prune all block related data from dymint store up to (but not including) retainHeight. It returns the number of blocks pruned, used for testing. func (m *Manager) PruneBlocks(retainHeight uint64) (uint64, error) { - nextSubmissionHeight := m.NextHeightToSubmit() - if m.IsProposer() && nextSubmissionHeight < retainHeight { // do not delete anything that we might submit in future - m.logger.Debug("cannot prune blocks before they have been submitted. using last submitted height for pruning", "retain_height", retainHeight, "height_to_submit", m.NextHeightToSubmit()) - retainHeight = nextSubmissionHeight + if m.IsProposer() { // do not delete anything that we might submit in future + retainHeight = max(m.NextHeightToSubmit(), retainHeight) + } else { // do not delete anything that is not validated yet + retainHeight = max(m.State.NextValidationHeight(), retainHeight) } // prune blocks from blocksync store From bb219143a64b708a8bb511da2177ce3461f635ff Mon Sep 17 00:00:00 2001 From: Sergi Rene Date: Wed, 9 Oct 2024 16:16:56 +0200 Subject: [PATCH 24/29] readd stuck check --- block/sync.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/block/sync.go b/block/sync.go index 59647ad4e..fe2674178 100644 --- a/block/sync.go +++ b/block/sync.go @@ -3,6 +3,7 @@ package block import ( "context" "errors" + "fmt" "github.com/dymensionxyz/dymint/node/events" "github.com/dymensionxyz/dymint/settlement" @@ -67,6 +68,11 @@ func (m *Manager) SyncLoop(ctx context.Context) error { m.logger.Error("process next DA batch", "err", err) } + // if height havent been updated, we are stuck + if m.State.NextHeight() == currH { + return fmt.Errorf("stuck at height %d", currH) + } + m.logger.Info("Synced from DA", "store height", m.State.Height(), "target height", m.LastSubmittedHeight.Load()) // trigger state update validation, after each state update is applied From 366c1ba2ed2ab6f54bd679c2cc5356ac4121d02e Mon Sep 17 00:00:00 2001 From: Sergi Rene Date: Wed, 9 Oct 2024 17:19:41 +0200 Subject: [PATCH 25/29] last header fix --- types/serialization.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/types/serialization.go b/types/serialization.go index 5c5d520aa..12cf30924 100644 --- a/types/serialization.go +++ b/types/serialization.go @@ -267,6 +267,7 @@ func (s *State) ToProto() (*pb.State, error) { LastResultsHash: s.LastResultsHash[:], AppHash: s.AppHash[:], RollappParams: s.RollappParams, + LastHeaderHash: s.LastHeaderHash[:], LastValidatedHeight: int64(s.GetLastValidatedHeight()), }, nil } @@ -290,6 +291,7 @@ func (s *State) FromProto(other *pb.State) error { copy(s.LastResultsHash[:], other.LastResultsHash) copy(s.AppHash[:], other.AppHash) s.RollappParams = other.RollappParams + copy(s.LastHeaderHash[:], other.LastHeaderHash) s.SetLastValidatedHeight(uint64(other.LastValidatedHeight)) return nil } From 94da675c3a4d8e15b00974a155153b46ce374172 Mon Sep 17 00:00:00 2001 From: Sergi Rene Date: Wed, 9 Oct 2024 22:58:19 +0200 Subject: [PATCH 26/29] new test --- block/manager_test.go | 1 - block/stateupdate_validator.go | 20 +++- block/stateupdate_validator_test.go | 173 ++++++++++++++++++++++++++++ 3 files changed, 188 insertions(+), 6 deletions(-) diff --git a/block/manager_test.go b/block/manager_test.go index ebcbf50ef..428c8d0e9 100644 --- a/block/manager_test.go +++ b/block/manager_test.go @@ -155,7 +155,6 @@ func TestProduceOnlyAfterSynced(t *testing.T) { require.NoError(t, err) manager, err := testutil.GetManagerWithProposerKey(chainId, testutil.GetManagerConfig(), proposerKey, nil, 1, 1, 0, proxyApp, nil) - //manager, err := testutil.GetManager(chainId, testutil.GetManagerConfig(), nil, 1, 1, 0, proxyApp, nil) require.NoError(t, err) require.NotNil(t, manager) diff --git a/block/stateupdate_validator.go b/block/stateupdate_validator.go index 6e56a649f..685f47bb0 100644 --- a/block/stateupdate_validator.go +++ b/block/stateupdate_validator.go @@ -65,6 +65,7 @@ func (v *StateUpdateValidator) ValidateStateUpdate(batch *settlement.ResultRetri } } + // if not all blocks are applied from DA, it is necessary to get all batch blocks from DA numBlocks := batch.EndHeight - batch.StartHeight + 1 if uint64(len(daBlocks)) != numBlocks { daBlocks = []*types.Block{} @@ -77,23 +78,31 @@ func (v *StateUpdateValidator) ValidateStateUpdate(batch *settlement.ResultRetri } } + // validate DA blocks against the state update err = v.ValidateDaBlocks(batch, daBlocks) if err != nil { return err } - if len(p2pBlocks) > 0 { - err = v.ValidateP2PBlocks(daBlocks, p2pBlocks) - if err != nil { - return err - } + // compare the batch blocks with the blocks applied from P2P + err = v.ValidateP2PBlocks(daBlocks, p2pBlocks) + if err != nil { + return err } + // update the last validated height to the batch last block height v.blockManager.State.SetLastValidatedHeight(batch.EndHeight) return nil } func (v *StateUpdateValidator) ValidateP2PBlocks(daBlocks []*types.Block, p2pBlocks []*types.Block) error { + + // nothing to compare + if len(p2pBlocks) == 0 { + return nil + } + + // iterate over daBlocks and compare hashes if there block is also in p2pBlocks i := 0 for _, daBlock := range daBlocks { @@ -153,6 +162,7 @@ func (v *StateUpdateValidator) validateDRS(startHeight, endHeight uint64, versio return nil } +// blockHash generates a hash from the block bytes to compare them func blockHash(block *types.Block) ([]byte, error) { blockBytes, err := block.MarshalBinary() if err != nil { diff --git a/block/stateupdate_validator_test.go b/block/stateupdate_validator_test.go index 29b8b5625..374cb4b76 100644 --- a/block/stateupdate_validator_test.go +++ b/block/stateupdate_validator_test.go @@ -7,12 +7,20 @@ import ( "time" "github.com/dymensionxyz/dymint/block" + "github.com/dymensionxyz/dymint/da" + "github.com/dymensionxyz/dymint/p2p" "github.com/dymensionxyz/dymint/settlement" "github.com/dymensionxyz/dymint/testutil" "github.com/dymensionxyz/dymint/types" + "github.com/dymensionxyz/dymint/version" "github.com/libp2p/go-libp2p/core/crypto" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/libs/log" + "github.com/tendermint/tendermint/libs/pubsub" + "github.com/tendermint/tendermint/proxy" ) func TestStateUpdateValidator_ValidateP2PBlocks(t *testing.T) { @@ -185,3 +193,168 @@ func TestStateUpdateValidator_ValidateDaBlocks(t *testing.T) { }) } } + +func TestStateUpdateValidator_ValidateStateUpdate(t *testing.T) { + + // Init app + app := testutil.GetAppMock(testutil.EndBlock) + app.On("EndBlock", mock.Anything).Return(abci.ResponseEndBlock{ + RollappParamUpdates: &abci.RollappParams{ + Da: "mock", + Version: version.Commit, + }, + ConsensusParamUpdates: &abci.ConsensusParams{ + Block: &abci.BlockParams{ + MaxGas: 40000000, + MaxBytes: 500000, + }, + }, + }) + // Create proxy app + clientCreator := proxy.NewLocalClientCreator(app) + proxyApp := proxy.NewAppConns(clientCreator) + err := proxyApp.Start() + require.NoError(t, err) + chainId := "test" + proposerKey, _, err := crypto.GenerateEd25519Key(rand.Reader) + require.NoError(t, err) + + doubleSigned, err := testutil.GenerateBlocks(1, 10, proposerKey, chainId, [32]byte{}) + require.NoError(t, err) + + // Test cases + testCases := []struct { + name string + p2pBlocks bool + doubleSignedBlocks []*types.Block + stateUpdateFraud string + expectedError error + }{ + { + name: "Successful validation applied from DA", + p2pBlocks: false, + doubleSignedBlocks: nil, + stateUpdateFraud: "", + expectedError: nil, + }, + { + name: "Successful validation applied from P2P", + p2pBlocks: true, + doubleSignedBlocks: nil, + stateUpdateFraud: "", + expectedError: nil, + }, + { + name: "Failed validation blocks not matching", + p2pBlocks: true, + stateUpdateFraud: "", + doubleSignedBlocks: doubleSigned, + expectedError: fmt.Errorf("p2p block different from DA block. p2p height: 1, DA height: 1"), + }, + { + name: "Failed validation wrong state roots", + p2pBlocks: true, + stateUpdateFraud: "stateroot", + doubleSignedBlocks: doubleSigned, + expectedError: fmt.Errorf("state root mismatch between state update and DA batch. State index: 1: Height: 1 State root SL: [] State root DA: [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]"), + }, + { + name: "Failed validation wrong timestamps", + p2pBlocks: true, + stateUpdateFraud: "timestamp", + doubleSignedBlocks: doubleSigned, + expectedError: fmt.Errorf("timestamp mismatch between state update and DA batch. State index: 1: Height: 1 Timestamp SL: 1970-01-01 00:00:01.000004567 +0000 UTC Timestamp DA: 1970-01-01 00:00:00.000004567 +0000 UTC"), + }, + { + name: "Failed validation wrong height", + p2pBlocks: true, + stateUpdateFraud: "height", + doubleSignedBlocks: doubleSigned, + expectedError: fmt.Errorf("height mismatch between state update and DA batch. State index: 1 SL height: 2 DA height: 1"), + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + + // Create manager + manager, err := testutil.GetManagerWithProposerKey(chainId, testutil.GetManagerConfig(), proposerKey, nil, 1, 1, 0, proxyApp, nil) + require.NoError(t, err) + require.NotNil(t, manager) + + // Create DA + manager.DAClient = testutil.GetMockDALC(log.TestingLogger()) + manager.Retriever = manager.DAClient.(da.BatchRetriever) + + // Generate batch + batch, err := testutil.GenerateBatch(1, 10, proposerKey, chainId, [32]byte{}) + assert.NoError(t, err) + + // Submit batch to DA + daResultSubmitBatch := manager.DAClient.SubmitBatch(batch) + assert.Equal(t, daResultSubmitBatch.Code, da.StatusSuccess) + + // Create block descriptors + var bds []settlement.BlockDescriptor + for _, block := range batch.Blocks { + bd := settlement.BlockDescriptor{ + Height: block.Header.Height, + StateRoot: block.Header.AppHash[:], + Timestamp: block.Header.GetTimestamp(), + } + bds = append(bds, bd) + } + + // create the batch in settlement + slBatch := &settlement.ResultRetrieveBatch{ + Batch: &settlement.Batch{ + BlockDescriptors: bds, + MetaData: &settlement.BatchMetaData{ + DA: daResultSubmitBatch.SubmitMetaData, + }, + StartHeight: 1, + EndHeight: 10, + }, + ResultBase: settlement.ResultBase{ + StateIndex: 1, + }, + } + switch tc.stateUpdateFraud { + case "stateroot": + slBatch.BlockDescriptors[0].StateRoot = []byte{} + case "timestamp": + slBatch.BlockDescriptors[0].Timestamp = slBatch.BlockDescriptors[0].Timestamp.Add(time.Second) + case "height": + slBatch.BlockDescriptors[0].Height = 2 + } + // Create the StateUpdateValidator + validator := block.NewStateUpdateValidator(testutil.NewLogger(t), manager) + + if tc.doubleSignedBlocks != nil { + batch.Blocks = tc.doubleSignedBlocks + batch.Commits, err = testutil.GenerateCommits(batch.Blocks, proposerKey) + require.NoError(t, err) + } + + if tc.p2pBlocks { + for i, block := range batch.Blocks { + blockData := p2p.BlockData{Block: *block, Commit: *batch.Commits[i]} + msg := pubsub.NewMessage(blockData, map[string][]string{p2p.EventTypeKey: {p2p.EventNewGossipedBlock}}) + manager.OnReceivedBlock(msg) + } + } else { + manager.ProcessNextDABatch(slBatch.MetaData.DA) + } + // Call the function + err = validator.ValidateStateUpdate(slBatch) + + // Check the result + if tc.expectedError == nil { + assert.NoError(t, err) + } else { + assert.EqualError(t, err, tc.expectedError.Error()) + } + + }) + } + +} From 9c25e73147f0c5e3e89df9272105c90fdb261459 Mon Sep 17 00:00:00 2001 From: Sergi Rene Date: Thu, 10 Oct 2024 11:00:00 +0200 Subject: [PATCH 27/29] missing file --- block/stateupdate_validator.go | 17 ++-- block/stateupdate_validator_test.go | 118 +++++++++++++++------------- block/validate.go | 2 +- types/errors.go | 89 +++++++++++++++++++++ 4 files changed, 164 insertions(+), 62 deletions(-) diff --git a/block/stateupdate_validator.go b/block/stateupdate_validator.go index 685f47bb0..57ff2e13e 100644 --- a/block/stateupdate_validator.go +++ b/block/stateupdate_validator.go @@ -69,9 +69,12 @@ func (v *StateUpdateValidator) ValidateStateUpdate(batch *settlement.ResultRetri numBlocks := batch.EndHeight - batch.StartHeight + 1 if uint64(len(daBlocks)) != numBlocks { daBlocks = []*types.Block{} - daBatch := v.blockManager.Retriever.RetrieveBatches(batch.MetaData.DA) - if daBatch.Code != da.StatusSuccess { - return daBatch.Error + var daBatch da.ResultRetrieveBatch + for { + daBatch = v.blockManager.Retriever.RetrieveBatches(batch.MetaData.DA) + if daBatch.Code == da.StatusSuccess { + break + } } for _, batch := range daBatch.Batches { daBlocks = append(daBlocks, batch.Blocks...) @@ -118,7 +121,7 @@ func (v *StateUpdateValidator) ValidateP2PBlocks(daBlocks []*types.Block, p2pBlo return err } if !bytes.Equal(p2pBlockHash, daBlockHash) { - return fmt.Errorf("p2p block different from DA block. p2p height: %d, DA height: %d", p2pBlocks[i].Header.Height, daBlock.Header.Height) + return types.NewErrStateUpdateDoubleSigningFraud(daBlock.Header.Height) } i++ if i == len(p2pBlocks) { @@ -140,16 +143,16 @@ func (v *StateUpdateValidator) ValidateDaBlocks(slBatch *settlement.ResultRetrie for i, bd := range slBatch.BlockDescriptors { // height check if bd.Height != daBlocks[i].Header.Height { - return fmt.Errorf("height mismatch between state update and DA batch. State index: %d SL height: %d DA height: %d", slBatch.StateIndex, bd.Height, daBlocks[i].Header.Height) + return types.NewErrStateUpdateHeightNotMatchingFraud(slBatch.StateIndex, bd.Height, daBlocks[i].Header.Height) } // we compare the state root between SL state info and DA block if !bytes.Equal(bd.StateRoot, daBlocks[i].Header.AppHash[:]) { - return fmt.Errorf("state root mismatch between state update and DA batch. State index: %d: Height: %d State root SL: %d State root DA: %d", slBatch.StateIndex, bd.Height, bd.StateRoot, daBlocks[i].Header.AppHash[:]) + return types.NewErrStateUpdateStateRootNotMatchingFraud(slBatch.StateIndex, bd.Height, bd.StateRoot, daBlocks[i].Header.AppHash[:]) } // we compare the timestamp between SL state info and DA block if !bd.Timestamp.Equal(daBlocks[i].Header.GetTimestamp()) { - return fmt.Errorf("timestamp mismatch between state update and DA batch. State index: %d: Height: %d Timestamp SL: %s Timestamp DA: %s", slBatch.StateIndex, bd.Height, bd.Timestamp.UTC(), daBlocks[i].Header.GetTimestamp().UTC()) + return types.NewErrStateUpdateTimestampNotMatchingFraud(slBatch.StateIndex, bd.Height, bd.Timestamp, daBlocks[i].Header.GetTimestamp()) } } diff --git a/block/stateupdate_validator_test.go b/block/stateupdate_validator_test.go index 374cb4b76..2623c2b0a 100644 --- a/block/stateupdate_validator_test.go +++ b/block/stateupdate_validator_test.go @@ -2,7 +2,7 @@ package block_test import ( "crypto/rand" - "fmt" + "errors" "testing" "time" @@ -40,44 +40,46 @@ func TestStateUpdateValidator_ValidateP2PBlocks(t *testing.T) { mixedBatch[2] = doubleSignedBatch.Blocks[2] tests := []struct { - name string - daBlocks []*types.Block - p2pBlocks []*types.Block - wantErr bool + name string + daBlocks []*types.Block + p2pBlocks []*types.Block + expectedErrType interface{} }{ { - name: "Empty blocks", - daBlocks: []*types.Block{}, - p2pBlocks: []*types.Block{}, - wantErr: false, + name: "Empty blocks", + daBlocks: []*types.Block{}, + p2pBlocks: []*types.Block{}, + expectedErrType: nil, }, { - name: "Matching blocks", - daBlocks: batch.Blocks, - p2pBlocks: batch.Blocks, - wantErr: false, + name: "Matching blocks", + daBlocks: batch.Blocks, + p2pBlocks: batch.Blocks, + expectedErrType: nil, }, { - name: "double signing", - daBlocks: batch.Blocks, - p2pBlocks: doubleSignedBatch.Blocks, - wantErr: true, + name: "double signing", + daBlocks: batch.Blocks, + p2pBlocks: doubleSignedBatch.Blocks, + expectedErrType: types.ErrStateUpdateDoubleSigningFraud{}, }, { - name: "mixed blocks", - daBlocks: batch.Blocks, - p2pBlocks: mixedBatch, - wantErr: true, + name: "mixed blocks", + daBlocks: batch.Blocks, + p2pBlocks: mixedBatch, + expectedErrType: types.ErrStateUpdateDoubleSigningFraud{}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := validator.ValidateP2PBlocks(tt.daBlocks, tt.p2pBlocks) - if tt.wantErr { - assert.Error(t, err) - } else { + // Check the result + if tt.expectedErrType == nil { assert.NoError(t, err) + } else { + assert.True(t, errors.As(err, &tt.expectedErrType), + "expected error of type %T, got %T", tt.expectedErrType, err) } }) } @@ -94,10 +96,10 @@ func TestStateUpdateValidator_ValidateDaBlocks(t *testing.T) { require.NoError(t, err) tests := []struct { - name string - slBatch *settlement.ResultRetrieveBatch - daBlocks []*types.Block - expectedError error + name string + slBatch *settlement.ResultRetrieveBatch + daBlocks []*types.Block + expectedErrType interface{} }{ { name: "Happy path - all validations pass", @@ -112,8 +114,8 @@ func TestStateUpdateValidator_ValidateDaBlocks(t *testing.T) { StateIndex: 1, }, }, - daBlocks: batch.Blocks, - expectedError: nil, + daBlocks: batch.Blocks, + expectedErrType: nil, }, { name: "Error - number of blocks mismatch", @@ -128,8 +130,8 @@ func TestStateUpdateValidator_ValidateDaBlocks(t *testing.T) { StateIndex: 1, }, }, - daBlocks: []*types.Block{batch.Blocks[0]}, - expectedError: fmt.Errorf("num blocks mismatch between state update and DA batch. State index: 1 State update blocks: 2 DA batch blocks: 1"), + daBlocks: []*types.Block{batch.Blocks[0]}, + expectedErrType: types.ErrStateUpdateHeightNotMatchingFraud{}, }, { name: "Error - height mismatch", @@ -144,8 +146,8 @@ func TestStateUpdateValidator_ValidateDaBlocks(t *testing.T) { StateIndex: 1, }, }, - daBlocks: batch.Blocks, - expectedError: fmt.Errorf("height mismatch between state update and DA batch. State index: 1 SL height: 101 DA height: 1"), + daBlocks: batch.Blocks, + expectedErrType: types.ErrStateUpdateHeightNotMatchingFraud{}, }, { name: "Error - state root mismatch", @@ -160,8 +162,8 @@ func TestStateUpdateValidator_ValidateDaBlocks(t *testing.T) { StateIndex: 1, }, }, - daBlocks: batch.Blocks, - expectedError: fmt.Errorf("state root mismatch between state update and DA batch. State index: 1: Height: 2 State root SL: %d State root DA: %d", []byte{1, 2, 3, 4}, batch.Blocks[0].Header.AppHash[:]), + daBlocks: batch.Blocks, + expectedErrType: types.ErrStateUpdateStateRootNotMatchingFraud{}, }, { name: "Error - timestamp mismatch", @@ -176,19 +178,22 @@ func TestStateUpdateValidator_ValidateDaBlocks(t *testing.T) { StateIndex: 1, }, }, - daBlocks: batch.Blocks, - expectedError: fmt.Errorf("timestamp mismatch between state update and DA batch. State index: 1: Height: 2 Timestamp SL: %s Timestamp DA: %s", - batch.Blocks[1].Header.GetTimestamp().UTC().Add(1*time.Second), batch.Blocks[1].Header.GetTimestamp().UTC()), + daBlocks: batch.Blocks, + expectedErrType: types.ErrStateUpdateTimestampNotMatchingFraud{}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + + // validate DA blocks err := validator.ValidateDaBlocks(tt.slBatch, tt.daBlocks) - if tt.expectedError != nil { - assert.EqualError(t, err, tt.expectedError.Error()) - } else { + // Check the result + if tt.expectedErrType == nil { assert.NoError(t, err) + } else { + assert.True(t, errors.As(err, &tt.expectedErrType), + "expected error of type %T, got %T", tt.expectedErrType, err) } }) } @@ -228,49 +233,49 @@ func TestStateUpdateValidator_ValidateStateUpdate(t *testing.T) { p2pBlocks bool doubleSignedBlocks []*types.Block stateUpdateFraud string - expectedError error + expectedErrType interface{} }{ { name: "Successful validation applied from DA", p2pBlocks: false, doubleSignedBlocks: nil, stateUpdateFraud: "", - expectedError: nil, + expectedErrType: nil, }, { name: "Successful validation applied from P2P", p2pBlocks: true, doubleSignedBlocks: nil, stateUpdateFraud: "", - expectedError: nil, + expectedErrType: nil, }, { name: "Failed validation blocks not matching", p2pBlocks: true, stateUpdateFraud: "", doubleSignedBlocks: doubleSigned, - expectedError: fmt.Errorf("p2p block different from DA block. p2p height: 1, DA height: 1"), + expectedErrType: types.ErrStateUpdateDoubleSigningFraud{}, }, { name: "Failed validation wrong state roots", p2pBlocks: true, stateUpdateFraud: "stateroot", doubleSignedBlocks: doubleSigned, - expectedError: fmt.Errorf("state root mismatch between state update and DA batch. State index: 1: Height: 1 State root SL: [] State root DA: [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]"), + expectedErrType: types.ErrStateUpdateStateRootNotMatchingFraud{}, }, { name: "Failed validation wrong timestamps", p2pBlocks: true, stateUpdateFraud: "timestamp", doubleSignedBlocks: doubleSigned, - expectedError: fmt.Errorf("timestamp mismatch between state update and DA batch. State index: 1: Height: 1 Timestamp SL: 1970-01-01 00:00:01.000004567 +0000 UTC Timestamp DA: 1970-01-01 00:00:00.000004567 +0000 UTC"), + expectedErrType: types.ErrStateUpdateTimestampNotMatchingFraud{}, }, { name: "Failed validation wrong height", p2pBlocks: true, stateUpdateFraud: "height", doubleSignedBlocks: doubleSigned, - expectedError: fmt.Errorf("height mismatch between state update and DA batch. State index: 1 SL height: 2 DA height: 1"), + expectedErrType: types.ErrStateUpdateHeightNotMatchingFraud{}, }, } for _, tc := range testCases { @@ -318,6 +323,11 @@ func TestStateUpdateValidator_ValidateStateUpdate(t *testing.T) { StateIndex: 1, }, } + + // Create the StateUpdateValidator + validator := block.NewStateUpdateValidator(testutil.NewLogger(t), manager) + + // set fraud data switch tc.stateUpdateFraud { case "stateroot": slBatch.BlockDescriptors[0].StateRoot = []byte{} @@ -326,8 +336,6 @@ func TestStateUpdateValidator_ValidateStateUpdate(t *testing.T) { case "height": slBatch.BlockDescriptors[0].Height = 2 } - // Create the StateUpdateValidator - validator := block.NewStateUpdateValidator(testutil.NewLogger(t), manager) if tc.doubleSignedBlocks != nil { batch.Blocks = tc.doubleSignedBlocks @@ -344,14 +352,16 @@ func TestStateUpdateValidator_ValidateStateUpdate(t *testing.T) { } else { manager.ProcessNextDABatch(slBatch.MetaData.DA) } - // Call the function + + // validate the state update err = validator.ValidateStateUpdate(slBatch) // Check the result - if tc.expectedError == nil { + if tc.expectedErrType == nil { assert.NoError(t, err) } else { - assert.EqualError(t, err, tc.expectedError.Error()) + assert.True(t, errors.As(err, &tc.expectedErrType), + "expected error of type %T, got %T", tc.expectedErrType, err) } }) diff --git a/block/validate.go b/block/validate.go index b6ee92b9a..d16620078 100644 --- a/block/validate.go +++ b/block/validate.go @@ -35,7 +35,7 @@ func (m *Manager) ValidateLoop(ctx context.Context) error { batch, err := m.SLClient.GetBatchAtHeight(currH) if err != nil { m.logger.Error("failed batch retrieval", "error", err) - break + continue } // validate batch err = m.validator.ValidateStateUpdate(batch) diff --git a/types/errors.go b/types/errors.go index f304b9d3b..96e8fd897 100644 --- a/types/errors.go +++ b/types/errors.go @@ -1,6 +1,7 @@ package types import ( + "encoding/hex" "errors" "fmt" "time" @@ -312,3 +313,91 @@ func (e ErrInvalidHeaderDataHashFraud) Error() string { func (e ErrInvalidHeaderDataHashFraud) Unwrap() error { return gerrc.ErrFault } + +type ErrStateUpdateHeightNotMatchingFraud struct { + StateIndex uint64 + SLHeight uint64 + DAHeight uint64 +} + +func NewErrStateUpdateHeightNotMatchingFraud(stateIndex uint64, slHeight uint64, daHeight uint64) error { + return &ErrStateUpdateHeightNotMatchingFraud{ + StateIndex: stateIndex, + SLHeight: slHeight, + DAHeight: daHeight, + } +} + +func (e ErrStateUpdateHeightNotMatchingFraud) Error() string { + return fmt.Sprintf("block height in DA batch not matching SL batch height. StateIndex: %d SLHeight: %d DAHeight: %d", e.StateIndex, e.SLHeight, e.DAHeight) +} + +func (e ErrStateUpdateHeightNotMatchingFraud) Unwrap() error { + return gerrc.ErrFault +} + +type ErrStateUpdateStateRootNotMatchingFraud struct { + StateIndex uint64 + Height uint64 + SLStateRoot []byte + DAStateRoot []byte +} + +func NewErrStateUpdateStateRootNotMatchingFraud(stateIndex uint64, height uint64, slStateRoot []byte, daStateRoot []byte) error { + return &ErrStateUpdateStateRootNotMatchingFraud{ + StateIndex: stateIndex, + Height: height, + SLStateRoot: slStateRoot, + DAStateRoot: daStateRoot, + } +} + +func (e ErrStateUpdateStateRootNotMatchingFraud) Error() string { + return fmt.Sprintf("state root in DA batch block not matching state root in SL. StateIndex: %d Height: %d SLTimestsamp: %s DA:Timestamp: %s", e.StateIndex, e.Height, hex.EncodeToString(e.SLStateRoot), hex.EncodeToString(e.DAStateRoot)) +} + +func (e ErrStateUpdateStateRootNotMatchingFraud) Unwrap() error { + return gerrc.ErrFault +} + +type ErrStateUpdateTimestampNotMatchingFraud struct { + StateIndex uint64 + Height uint64 + SLTimestamp time.Time + DATimestamp time.Time +} + +func NewErrStateUpdateTimestampNotMatchingFraud(stateIndex uint64, height uint64, slTimestamp time.Time, daTimestamp time.Time) error { + return &ErrStateUpdateTimestampNotMatchingFraud{ + StateIndex: stateIndex, + Height: height, + SLTimestamp: slTimestamp, + DATimestamp: daTimestamp, + } +} + +func (e ErrStateUpdateTimestampNotMatchingFraud) Error() string { + return fmt.Sprintf("timestamp in DA batch block not matching timestamp in SL. StateIndex: %d Height: %d SLTimestsamp: %s DA:Timestamp: %s", e.StateIndex, e.Height, e.SLTimestamp, e.DATimestamp) +} + +func (e ErrStateUpdateTimestampNotMatchingFraud) Unwrap() error { + return gerrc.ErrFault +} + +type ErrStateUpdateDoubleSigningFraud struct { + Height uint64 +} + +func NewErrStateUpdateDoubleSigningFraud(height uint64) error { + return &ErrStateUpdateDoubleSigningFraud{ + Height: height, + } +} + +func (e ErrStateUpdateDoubleSigningFraud) Error() string { + return fmt.Sprintf("block received from P2P not matching block found in DA. Height: %d", e.Height) +} + +func (e ErrStateUpdateDoubleSigningFraud) Unwrap() error { + return gerrc.ErrFault +} From ddf4015d726abb177068ff6243684e7e7dbbba17 Mon Sep 17 00:00:00 2001 From: Sergi Rene Date: Thu, 10 Oct 2024 11:21:42 +0200 Subject: [PATCH 28/29] removing iface --- block/stateupdate_validator.go | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/block/stateupdate_validator.go b/block/stateupdate_validator.go index 57ff2e13e..d176bff6a 100644 --- a/block/stateupdate_validator.go +++ b/block/stateupdate_validator.go @@ -10,25 +10,12 @@ import ( "github.com/dymensionxyz/dymint/types" ) -type P2PBlockValidator interface { - ValidateP2PBlocks(daBlocks []*types.Block, p2pBlocks []*types.Block) error -} - -type DABlocksValidator interface { - ValidateDaBlocks(slBatch *settlement.ResultRetrieveBatch, daBlocks []*types.Block) error -} - // StateUpdateValidator is a validator for messages gossiped in the p2p network. type StateUpdateValidator struct { logger types.Logger blockManager *Manager } -var ( - _ DABlocksValidator = &StateUpdateValidator{} - _ P2PBlockValidator = &StateUpdateValidator{} -) - // NewValidator creates a new Validator. func NewStateUpdateValidator(logger types.Logger, blockManager *Manager) *StateUpdateValidator { return &StateUpdateValidator{ @@ -99,7 +86,6 @@ func (v *StateUpdateValidator) ValidateStateUpdate(batch *settlement.ResultRetri } func (v *StateUpdateValidator) ValidateP2PBlocks(daBlocks []*types.Block, p2pBlocks []*types.Block) error { - // nothing to compare if len(p2pBlocks) == 0 { return nil From 9f9bcef261b02dd74bb4a133e13a8d16b7511a6e Mon Sep 17 00:00:00 2001 From: Sergi Rene Date: Thu, 10 Oct 2024 13:23:48 +0200 Subject: [PATCH 29/29] godoc fix --- block/manager.go | 2 +- block/sync.go | 4 ++-- block/validate.go | 5 ++--- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/block/manager.go b/block/manager.go index ded5c4bdd..4a89feef4 100644 --- a/block/manager.go +++ b/block/manager.go @@ -221,7 +221,7 @@ func (m *Manager) Start(ctx context.Context) error { // Sequencer must wait till DA is synced to start submitting blobs m.DAClient.WaitForSyncing() - // Sequencer must wait till node is synced till last submittedHeight, in case is not + // Sequencer must wait till node is synced till last submittedHeight, in case it is not m.waitForSyncing() // check if sequencer in the middle of rotation nextSeqAddr, missing, err := m.MissingLastBatch() diff --git a/block/sync.go b/block/sync.go index fe2674178..66efbe2a5 100644 --- a/block/sync.go +++ b/block/sync.go @@ -12,7 +12,7 @@ import ( "github.com/tendermint/tendermint/libs/pubsub" ) -// onNewStateUpdate will update the last submitted height and will update sequencers list from SL +// onNewStateUpdate will update the last submitted height and will update sequencers list from SL. After, it triggers syncing or validation, depending whether it needs to sync first or only validate. func (m *Manager) onNewStateUpdate(event pubsub.Message) { eventData, ok := event.Data().(*settlement.EventDataNewBatch) if !ok { @@ -42,7 +42,7 @@ func (m *Manager) onNewStateUpdate(event pubsub.Message) { } // SyncLoop listens for syncing events (from new state update or from initial syncing) and syncs to the last submitted height. -// After syncing it sends signal to validation loop +// It sends signal to validation loop for each synced state updated func (m *Manager) SyncLoop(ctx context.Context) error { for { select { diff --git a/block/validate.go b/block/validate.go index d16620078..fea13e533 100644 --- a/block/validate.go +++ b/block/validate.go @@ -8,7 +8,7 @@ import ( "github.com/tendermint/tendermint/libs/pubsub" ) -// onNewStateUpdate will update the last submitted height and will update sequencers list from SL +// onNewStateUpdateFinalized will update the last validated height with the last finalized height func (m *Manager) onNewStateUpdateFinalized(event pubsub.Message) { eventData, ok := event.Data().(*settlement.EventDataNewBatch) if !ok { @@ -18,8 +18,7 @@ func (m *Manager) onNewStateUpdateFinalized(event pubsub.Message) { m.State.SetLastValidatedHeight(eventData.EndHeight) } -// SyncTargetLoop listens for syncing events (from new state update or from initial syncing) and syncs to the last submitted height. -// In case the node is already synced, it validate +// ValidateLoop listens for syncing events (from new state update or from initial syncing) and validates state updates to the last submitted height. func (m *Manager) ValidateLoop(ctx context.Context) error { for { select {