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

  1. Call Commit() on the root/parent Store, not on the transaction-scoped store
  2. If working within a transaction, use the transaction's commit/flush-to-parent mechanism instead of the DB-level Commit
  3. 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

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


AI-assisted analysis of canopy-network/canopy@ee8197d91d (2026-09-06). Data as JSON: /api/errors/15b1a14fbd2bc9cb. Report an issue: GitHub.