diff --git a/index/manager.go b/index/manager.go index 2e87cb3f..6b294c0f 100644 --- a/index/manager.go +++ b/index/manager.go @@ -2,6 +2,7 @@ package index import ( "context" + "errors" "fmt" "sync" @@ -17,6 +18,10 @@ import ( type ( // A Store is a persistent store for the index manager. Store interface { + // ResetChainState resets the consensus state of the store. This + // should only occur if the user has reset their consensus database to + // sync from scratch. + ResetChainState() error UpdateChainState(func(UpdateTx) error) error Tip() (types.ChainIndex, error) } @@ -122,7 +127,7 @@ func NewManager(store Store, chain *chain.Manager, contracts ContractManager, wa } reorgCh := make(chan struct{}, 1) - reorgCh <- struct{}{} // trigger initial check + reorgCh <- struct{}{} // trigger initial sync stop := m.chain.OnReorg(func(index types.ChainIndex) { select { case reorgCh <- struct{}{}: @@ -145,7 +150,7 @@ func NewManager(store Store, chain *chain.Manager, contracts ContractManager, wa case <-ctx.Done(): return case <-reorgCh: - if err := m.syncDB(ctx); err != nil { + if err := m.syncDB(ctx); err != nil && !errors.Is(err, context.Canceled) { m.log.Error("failed to sync database", zap.Error(err)) } } diff --git a/index/update.go b/index/update.go index b71ee904..32560142 100644 --- a/index/update.go +++ b/index/update.go @@ -3,6 +3,7 @@ package index import ( "context" "fmt" + "strings" "go.sia.tech/core/types" "go.sia.tech/coreutils/wallet" @@ -32,7 +33,19 @@ func (m *Manager) syncDB(ctx context.Context) error { } reverted, applied, err := m.chain.UpdatesSince(index, m.updateBatchSize) - if err != nil { + if err != nil && strings.Contains(err.Error(), "missing block at index") { + log.Warn("missing block at index, resetting chain state") + // reset the consensus state. Should delete all chain related + // state from the store + if err := m.store.ResetChainState(); err != nil { + return fmt.Errorf("failed to reset consensus state: %w", err) + } + // zero out the index to force a full resync + m.mu.Lock() + m.index = types.ChainIndex{} + m.mu.Unlock() + return nil + } else if err != nil { return fmt.Errorf("failed to get updates since %v: %w", index, err) } else if len(reverted) == 0 && len(applied) == 0 { return nil diff --git a/persist/sqlite/consensus.go b/persist/sqlite/consensus.go index 631c3664..4d428dad 100644 --- a/persist/sqlite/consensus.go +++ b/persist/sqlite/consensus.go @@ -39,6 +39,25 @@ type ( var _ index.UpdateTx = (*updateTx)(nil) +// ResetChainState resets the consensus state of the store. This +// should only occur if the user has reset their consensus database to +// sync from scratch. +func (s *Store) ResetChainState() error { + return s.transaction(func(tx *txn) error { + _, err := tx.Exec(` +-- v2 contracts +DELETE FROM contracts_v2_chain_index_elements; +DELETE FROM contract_v2_state_elements; +-- wallet +DELETE FROM wallet_siacoin_elements; +DELETE FROM wallet_events; +DELETE FROM host_stats WHERE stat=?; -- reset wallet stats since they are derived from the chain +-- settings +UPDATE global_settings SET last_scanned_index=NULL, last_announce_index=NULL, last_announce_address=NULL`, metricWalletBalance) + return err + }) +} + // WalletStateElements returns all state elements related to the wallet. It is used // to update the proofs of all state elements affected by the update. func (ux *updateTx) WalletStateElements() (elements []types.StateElement, err error) {