canopy-network/canopy · error · ErrCommitDB
root is not supported for nested transactions
Error message
root is not supported for nested transactions
What it means
Store.Root returns the Sparse Merkle Tree root hash of the StateCommitStore. A nested transaction has no independently committed SMT root — its root is only meaningful after flushing to the parent — so calling Root() on a txn store (isTxn) returns this error. Commit() internally calls Root(), so committing a txn store hits the same guard.
Source
Thrown at store/store.go:543
Indexer: &Indexer{NewTxn(s.Indexer.db, s.Indexer.db, nil, false, true, false, nextVersion), s.config},
metrics: s.metrics,
mu: s.mu,
isTxn: true,
}
}
// DB() returns the underlying PebbleDB instance associated with the Store, providing access
// to the database for direct operations and management.
func (s *Store) DB() *pebble.DB { return s.db }
// IsRootCached() reports whether the SMT root is already cached on this store instance.
func (s *Store) IsRootCached() bool { return s.sc != nil }
// Root() retrieves the root hash of the StateCommitStore, representing the current root of the
// Sparse Merkle Tree. This hash is used for verifying the integrity and consistency of the state.
func (s *Store) Root() (root []byte, err lib.ErrorI) {
if s.isTxn {
return nil, ErrCommitDB(fmt.Errorf("root is not supported for nested transactions"))
}
// if smt not cached
if s.sc == nil {
startTime := time.Now()
defer s.metrics.UpdateStoreRootTime(startTime)
nextVersion := s.version + 1
// set up the state commit store
s.sc = NewDefaultSMT(NewTxn(s.ss.reader, s.ss.writer, stateCommitIDPrefix, false, false, true, nextVersion))
// commit the SMT directly using the txn ops
//
// NOTE: the SMT node cache MUST NOT be persisted across blocks. `node.copy()` is a
// no-op alias, so the parallel commit mutates cached `*node` objects in place. Reusing
// them later can serve stale nodes (e.g. from a speculative, uncommitted `Root()` call),
// diverging from the on-disk snapshot. A fresh per-block cache still caches within the commit.
if err = s.sc.CommitParallel(s.ss.txn.ops); err != nil {
return nil, err
}
s.metrics.UpdateStoreRootStats(View on GitHub (pinned to ee8197d91d)
Solutions
- Call Root() on the root/parent Store instead of the transaction-scoped handle
- Compute the in-transaction root via the transaction's own SMT/cache APIs (e.g. working-tree view) if available, or flush the txn to the parent first
- Restructure so root queries happen after the transaction is committed
Example fix
// before txn, _ := store.NewTxn(parent, ...) root, _ := txn.Root() // error // after root, _ := parent.Root() // root of committed state // or flush txn changes to parent, then query parent
Defensive patterns
Strategy: type-guard
Validate before calling
if s.IsTxn() { return errors.New("root is only available on the committed store") } Type guard
func canReadRoot(s *store.Store) bool { return !s.IsTxn() } Try / catch
root, err := st.Root()
if err != nil && strings.Contains(err.Error(), "nested transactions") {
return parent.Root() // query the committed state instead
} Prevention
- Expose root queries only through the root store abstraction
- For in-transaction root needs, use the txn's working SMT/cache APIs, not Root()
- Add code-review checks that Root()/Commit() call sites hold the root store
When it happens
Trigger: Calling Root() directly on a transaction-scoped Store, or calling Commit() on a txn store (which invokes Root()) — e.g. trying to read the state root mid-transaction.
Common situations: Application code reading the state root inside a transaction to anchor it externally; tests inspecting roots via a txn handle; code paths that commit or query root through a transaction-scoped store after a refactor.
Related errors
- nested transactions are not supported
- rollback 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/c2ed5870a54c43b9.
Report an issue: GitHub.