Skip to content

Fix TrieDBMut panic when removing a strict-prefix key#232

Open
dimartiro wants to merge 1 commit into
paritytech:masterfrom
dimartiro:fix/issue-217-remove-prefix-panic
Open

Fix TrieDBMut panic when removing a strict-prefix key#232
dimartiro wants to merge 1 commit into
paritytech:masterfrom
dimartiro:fix/issue-217-remove-prefix-panic

Conversation

@dimartiro

Copy link
Copy Markdown
Contributor

Summary

Removing a key that is only a strict prefix of existing keys panics in debug builds and silently corrupts the trie in release builds, under any no-extension layout (including the substrate-like hashed-value codec used in production).

Closes #217.

Reproduction

// NoExtensionLayout
let mut memdb = MemoryDB::<_, HashKey<_>, _>::default();
let mut root = Default::default();
let mut t = TrieDBMutBuilder::<NoExtensionLayout>::new(&mut memdb, &mut root).build();
t.insert(&[0xff, 0xff], &[0x0a]).unwrap();
t.insert(&[0xfe, 0xff, 0xe9], &[0x0b]).unwrap();
t.insert(&[0xff, 0xff, 0xff], &[0x0c]).unwrap();
t.remove(&[0xff]).unwrap(); // `ff` is absent — panics: assertion failed: self.len() >= i

Root cause

ff is not present but is a strict prefix of ffff / ffffff. While descending, the root NibbledBranch consumes the whole search key via key.advance(common + 1), so the recursion reaches the inner NibbledBranch (whose own partial the key never traversed) with an empty remaining key.

The (Node::NibbledBranch(n, children, val), true) arm in remove_inspector treated "empty remaining key" as "the value lives here". It removed a different (longer) key's value and called fix, whose fix_inner advances the nibble offset by the branch's own partial length on an already-exhausted key — tripping debug_assert!(self.len() >= i) in NibbleSlice::advance (panic in debug; in release the offset overflows and a valid value is dropped).

Fix

Guard the value removal on the branch's own partial being empty. A NibbledBranch's value is reachable only by a key that still contains the whole of its partial, so an empty remaining key with a non-empty partial means the key terminated early and is absent — removal must be a no-op (Action::Restore).

Testing

Two regression tests, run across all reference layouts via test_layouts! (including HashedValueNoExtThreshold, the substrate-like hashed-value codec):

  • remove_prefix_key_is_noop_issue_217 — removing the absent prefix key leaves the trie root byte-identical and all existing values intact.
  • remove_exact_branch_value_issue_217 — a value genuinely sitting on a partial-bearing branch is still removable (the guard must not over-skip legitimate removals).

Full trie-db-test suite passes (126/126).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Panic in trie operation logic – Assertion Failure in nibbleslice.rs:120:9

1 participant