canopy-network/canopy · error · ErrCommitDB
nested transactions are not supported
Error message
nested transactions are not supported
What it means
Store.Commit performs a single atomic write of the current state to all backing stores. When the Store is a nested transaction (created via NewTxn with isTxn), Commit is not allowed to flush to the database — nested transactions must only flush changes to their parent — so this error is returned instead.
Source
Thrown at store/store.go:253
return &Store{
version: s.version,
log: s.log,
db: s.db,
writer: writer,
ss: s.ss.Copy(lssReader, lssReader),
Indexer: &Indexer{s.Indexer.db.Copy(reader, reader), s.config},
metrics: s.metrics,
mu: &sync.Mutex{},
compaction: atomic.Bool{},
backup: atomic.Bool{},
}, nil
}
// Commit() performs a single atomic write of the current state to all stores.
func (s *Store) Commit() (root []byte, err lib.ErrorI) {
// nested transactions should only flush changes to the parent transaction, not the database
if s.isTxn {
return nil, ErrCommitDB(fmt.Errorf("nested transactions are not supported"))
}
s.mu.Lock() // lock commit op
defer s.mu.Unlock() // unlock commit op
startTime := time.Now()
// get the root from the sparse merkle tree at the current state
root, err = s.Root()
if err != nil {
return nil, err
}
nextVersion := s.version + 1
// set the new CommitID (to the Transaction not the actual DB)
if err = s.setCommitID(nextVersion, root); err != nil {
s.Reset()
return nil, err
}
// collect LSS tombstones before Flush() clears the txn operations
lssDeleteKeys := s.collectLssDeleteKeys()
// Persist the keys touched by this commit outside consensus state.View on GitHub (pinned to ee8197d91d)
Solutions
- Call Commit() on the root/parent Store, not on the transaction-scoped store
- If working within a transaction, use the transaction's commit/flush-to-parent mechanism instead of the DB-level Commit
- Restructure code so only one store instance with ownsDB/isTxn=false performs commits
Example fix
// before txn, _ := store.NewTxn(...) root, err := txn.Commit() // nested -> error // after root, err := parentStore.Commit() // commit at the root store // txn changes are flushed to parent via the txn commit path
Defensive patterns
Strategy: type-guard
Validate before calling
if s.IsTxn() { // expose or check txn flag before committing
return errors.New("cannot commit a nested transaction; commit via parent store")
} Type guard
func isRootStore(s *store.Store) bool { return !s.IsTxn() } Try / catch
root, err := st.Commit()
if err != nil && strings.Contains(err.Error(), "nested transactions") {
// wrong handle: retry with the parent store reference
return parent.Commit()
} Prevention
- Keep a single canonical reference to the root store for commits
- Type-wrap transaction handles so the compiler distinguishes txn vs root stores
- Review code paths introduced during refactors that pass store handles into commit sites
When it happens
Trigger: Calling Commit() on a Store instance obtained through a transaction/nested store (isTxn=true), e.g. calling the top-level Commit on a txn-scoped store handle inside tests or application code that holds a transaction view.
Common situations: Application code mistakenly holds a reference to a transactional store and calls Commit directly instead of committing via the parent/root store; refactoring that moved a Commit call inside a transaction scope.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- rollback is not supported for nested transactions
- root is not supported for nested transactions
- event not found
- quorum certificate not found
- ErrStoreGet
AI-assisted analysis of canopy-network/canopy@ee8197d91d (2026-09-06).
Data as JSON: /api/errors/15b1a14fbd2bc9cb.
Report an issue: GitHub.