canopy-network/canopy · error · ErrCommitDB
rollback is not supported for nested transactions
Error message
rollback is not supported for nested transactions
What it means
Store.Rollback is an offline maintenance operation that rewinds the database to a target version. It cannot run while the store is a nested transaction, because a txn view lacks authoritative ownership of the underlying DB and rollback would corrupt the versioned state. The library rejects it eagerly with this error.
Source
Thrown at store/store.go:328
// the journal only needs keys
func (s *Store) recordStateChangeKeys(version uint64) lib.ErrorI {
s.ss.txn.l.Lock()
keys := make([][]byte, 0, len(s.ss.txn.ops))
for _, op := range s.ss.txn.ops {
keys = append(keys, bytes.Clone(op.key))
}
s.ss.txn.l.Unlock()
return s.Indexer.indexStateChangeKeys(version, keys)
}
// Rollback rewinds the store to a previous version (height).
//
// It removes all versioned entries above targetVersion, rebuilds the latest state
// view from historical state at targetVersion, and resets the latest commit pointer.
// NOTE: Rollback is an offline maintenance operation and must only run while the node is stopped.
func (s *Store) Rollback(targetVersion uint64) lib.ErrorI {
if s.isTxn {
return ErrCommitDB(fmt.Errorf("rollback is not supported for nested transactions"))
}
if targetVersion == 0 {
return ErrCommitDB(fmt.Errorf("rollback target height must be >= 1"))
}
s.mu.Lock()
defer s.mu.Unlock()
currentVersion := s.version
if targetVersion > currentVersion {
return ErrCommitDB(fmt.Errorf("rollback target height %d exceeds current height %d", targetVersion, currentVersion))
}
if targetVersion == currentVersion {
return nil
}
snapshot := s.db.NewSnapshot()
defer snapshot.Close()
View on GitHub (pinned to ee8197d91d)
Solutions
- Open the root Store (not a transaction) for the rollback operation
- Ensure the node is fully stopped before running rollback, as required for this offline operation
- Refactor maintenance tooling to accept only the root store instance
Example fix
// before txnStore, _ := store.NewTxn(parent, ...) txnStore.Rollback(100) // error // after rootStore, _ := store.NewStore(db, ...) rootStore.Rollback(100)
Defensive patterns
Strategy: validation
Validate before calling
func canRollback(s *store.Store) error {
if s.IsTxn() { return errors.New("rollback requires the root store, not a transaction") }
return nil
} Type guard
func isRollbackTarget(s *store.Store) bool { return !s.IsTxn() } Try / catch
if err := st.Rollback(h); err != nil {
if strings.Contains(err.Error(), "nested transactions") {
return rootStore.Rollback(h)
}
return err
} Prevention
- Run rollback only from offline maintenance tooling that opens the root store directly
- Assert node is stopped before any rollback operation
- Never pass txn-scoped stores into maintenance utilities
When it happens
Trigger: Calling Rollback(targetVersion) on a Store whose isTxn flag is set (a transaction-scoped store), typically during offline maintenance scripts that accidentally obtained a txn handle instead of the root store.
Common situations: Running a rollback/rewind maintenance command against a store instance opened in transaction mode; code refactors that pass a txn store into the rollback utility.
Related errors
- nested transactions are not supported
- rollback target height must be >= 1
- rollback target height %d exceeds current height %d
- missing commit id at height %d
- root is not supported for nested transactions
AI-assisted analysis of canopy-network/canopy@ee8197d91d (2026-09-06).
Data as JSON: /api/errors/33a2b9c146faa596.
Report an issue: GitHub.