-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Re-rooting the state of a block #2832
Description
Substrate chains may occasionally need to do state rollbacks.
In Polkadot, that's the case when we have a fully-malicious validator committee whose misbehavior is discovered.
We introduce ext_reroot(ancestor: BlockNumber) -> Hash for this purpose.
This will set the current storage to be equal to the post-state of the ancestor of the current block with the given block number.
Some pseudocode for an implementation:
// substrate-executor
fn ext_reroot(ancestor: BlockNumber) -> Hash {
ext.reroot(ancestor);
}
// substrate-state-machine
struct Ext<'a, B: 'a + Backend> {
backend: Cow<'a, B>,
prospective: &'a mut OverlayedChanges,
// ...
}
impl Ext {
fn reroot(number: BlockNumber) {
// prospective is an overlay of all storage items changed so far within the runtime execution
// of the block.
prospective.clear();
if current_number <= ancestor { trap() }
self.backend = Cow::Owned(self.backend.reroot(number));
}
}
struct TrieBackend {
fn reroot(&self, number) -> TrieBackend {
let db_overlay = MemoryDB::new();
// reverse, inserting all deletions of ancestors and deleting all insertions.
for (num, hash) in ancestry_iter(parent_hash) {
if num == ancestor { break }
let pruning_record = db.pruning_record(hash);
for (inserted_key, _val) in pruning_record.insertions {
db_overlay.remove(&inserted_key); // undo an insertion.
}
// values might not actually be available in the current implementation of pruning.
// but the value should still be available within the DB because we are assuming this block hasn't been canonicalized yet (so deletions haven't been applied yet).
for (_deleted_key, val) in pruning_record.deletions {
db_overlay.insert(val); // undo a deletion.
}
};
// we used a memorydb for `db_overlay` because it's very likely that many of the storage operations cancel out.
// now, we have to make sure that the `update` produced by `Backend::root` and passed to `client::BlockImportOperation::update_storage` is based on top of the `db_overlay`, but still includes any further deltas.
}
}I would expect that most of this implementation is within state_machine::Ext and state_machine::Backend. Ext needs to clear the prospective overlay, and we would make the backend field on Ext into a Cow. Backend gets a new Backend::reroot(new_number) -> Self. For the trie implementation, this would do all of the pruning-overlay logic mentioned above, and create a new Backend with the new state root and a pre-update to apply the del
This won't play that nicely with the storage changes root or light clients for consensus. What we can do is introduce a special storage changes root digest which says "everything changed". To maintain good light client compatibility on the consensus layer, the caller of this function should force-issue a change digest to the same set as is currently enabled afterwards.