diff --git a/packages/trie/test/trie/checkpoint.spec.ts b/packages/trie/test/trie/checkpoint.spec.ts index 85ee293b10..4b8c9f3dd2 100644 --- a/packages/trie/test/trie/checkpoint.spec.ts +++ b/packages/trie/test/trie/checkpoint.spec.ts @@ -12,8 +12,6 @@ import { assert, describe, it } from 'vitest' import { ROOT_DB_KEY, Trie, createTrie } from '../../src/index.js' -import type { BatchDBOp } from '@ethereumjs/util' - describe('testing checkpoints', () => { let trie: Trie let trieCopy: Trie @@ -51,16 +49,6 @@ describe('testing checkpoints', () => { postRoot = bytesToHex(trie.root()) }) - it('should get values from before checkpoint', async () => { - const res = await trie.get(utf8ToBytes('doge')) - assert.ok(equalsBytes(utf8ToBytes('coin'), res!)) - }) - - it('should get values from cache', async () => { - const res = await trie.get(utf8ToBytes('love')) - assert.ok(equalsBytes(utf8ToBytes('emotion'), res!)) - }) - it('should copy trie and get upstream and cache values after checkpoint', async () => { trieCopy = trie.shallowCopy() assert.equal(bytesToHex(trieCopy.root()), postRoot) @@ -87,110 +75,6 @@ describe('testing checkpoints', () => { assert.equal(bytesToUtf8(value!), 'value1') }) - it('should revert to the original root', async () => { - assert.ok(trie.hasCheckpoints()) - await trie.revert() - assert.equal(bytesToHex(trie.root()), preRoot) - assert.notOk(trie.hasCheckpoints()) - }) - - it('should not get values from cache after revert', async () => { - const res = await trie.get(utf8ToBytes('love')) - assert.notOk(res) - }) - - it('should commit a checkpoint', async () => { - trie.checkpoint() - await trie.put(utf8ToBytes('test'), utf8ToBytes('something')) - await trie.put(utf8ToBytes('love'), utf8ToBytes('emotion')) - await trie.commit() - assert.equal(trie.hasCheckpoints(), false) - assert.equal(bytesToHex(trie.root()), postRoot) - }) - - it('should get new values after commit', async () => { - const res = await trie.get(utf8ToBytes('love')) - assert.ok(equalsBytes(utf8ToBytes('emotion'), res!)) - }) - - it('should commit a nested checkpoint', async () => { - trie.checkpoint() - await trie.put(utf8ToBytes('test'), utf8ToBytes('something else')) - const root = trie.root() - trie.checkpoint() - await trie.put(utf8ToBytes('the feels'), utf8ToBytes('emotion')) - await trie.revert() - await trie.commit() - assert.equal(trie.hasCheckpoints(), false) - assert.equal(trie.root(), root) - }) - - const k1 = utf8ToBytes('k1') - const v1 = utf8ToBytes('v1') - const v12 = utf8ToBytes('v12') - const v123 = utf8ToBytes('v123') - - it('revert -> put', async () => { - trie = new Trie() - - trie.checkpoint() - await trie.put(k1, v1) - assert.deepEqual(await trie.get(k1), v1, 'before revert: v1 in trie') - await trie.revert() - assert.deepEqual(await trie.get(k1), null, 'after revert: v1 removed') - }) - - it('revert -> put (update)', async () => { - trie = new Trie() - - await trie.put(k1, v1) - assert.deepEqual(await trie.get(k1), v1, 'before CP: v1') - trie.checkpoint() - await trie.put(k1, v12) - await trie.put(k1, v123) - await trie.revert() - assert.deepEqual(await trie.get(k1), v1, 'after revert: v1') - }) - - it('revert -> put (update) batched', async () => { - const trie = new Trie() - await trie.put(k1, v1) - assert.deepEqual(await trie.get(k1), v1, 'before CP: v1') - trie.checkpoint() - const ops = [ - { type: 'put', key: k1, value: v12 }, - { type: 'put', key: k1, value: v123 }, - ] as BatchDBOp[] - await trie.batch(ops) - await trie.revert() - assert.deepEqual(await trie.get(k1), v1, 'after revert: v1') - }) - - it('Checkpointing: revert -> del', async () => { - const trie = new Trie() - await trie.put(k1, v1) - assert.deepEqual(await trie.get(k1), v1, 'before CP: v1') - trie.checkpoint() - await trie.del(k1) - assert.deepEqual(await trie.get(k1), null, 'before revert: null') - await trie.revert() - assert.deepEqual(await trie.get(k1), v1, 'after revert: v1') - }) - - it('Checkpointing: nested checkpoints -> commit -> revert', async () => { - const trie = new Trie() - await trie.put(k1, v1) - assert.deepEqual(await trie.get(k1), v1, 'before CP: v1') - trie.checkpoint() - await trie.put(k1, v12) - trie.checkpoint() - await trie.put(k1, v123) - await trie.commit() - assert.deepEqual(await trie.get(k1), v123, 'after commit (second CP): v123') - await trie.revert() - assert.deepEqual(await trie.get(k1), v1, 'after revert (first CP): v1') - }) - /* In this educational example, it is shown how operations on a clone of a trie can be copied into the original trie. This also includes pruning. diff --git a/packages/trie/test/trie/checkpointing.spec.ts b/packages/trie/test/trie/checkpointing.spec.ts new file mode 100644 index 0000000000..5d7184ccc5 --- /dev/null +++ b/packages/trie/test/trie/checkpointing.spec.ts @@ -0,0 +1,284 @@ +import { equalsBytes, hexToBytes } from '@ethereumjs/util' +import { assert, describe, it } from 'vitest' + +import { Trie } from '../../src/index.js' + +// exhaustive testing of checkpoint, revert, flush, and commit functionality of trie, inspired by +// the statemanager checkpointing.*.spec.ts tests +describe('trie: checkpointing', () => { + // Sample key-value pairs using Uint8Array + const key = hexToBytes('0x11') + const value1 = hexToBytes('0xaa') + const value2 = hexToBytes('0xbb') + const value3 = hexToBytes('0xcc') + const value4 = hexToBytes('0xdd') + const value5 = hexToBytes('0xee') + const valueUndefined = null + + const kvs = [ + { + v1: value1, + v2: value2, + v3: value3, + v4: value4, + v5: value5, + }, + { + v1: valueUndefined, + v2: value2, + v3: value3, + v4: value4, + v5: value5, + }, + { + v1: valueUndefined, + v2: value2, + v3: valueUndefined, + v4: value4, + v5: value5, + }, + { + v1: valueUndefined, + v2: value2, + v3: value3, + v4: valueUndefined, + v5: value5, + }, + { + v1: value1, + v2: valueUndefined, + v3: value3, + v4: valueUndefined, + v5: value5, + }, + { + v1: value1, + v2: valueUndefined, + v3: value3, + v4: value4, + v5: valueUndefined, + }, + { + v1: value1, + v2: value2, + v3: valueUndefined, + v4: value4, + v5: valueUndefined, + }, + ] + + const trieEval = async (trie: Trie, value: any) => { + const actualValue = await trie.get(key) + const pass = actualValue === null ? value === null : equalsBytes(actualValue, value) + return pass + } + + for (const kv of kvs) { + it('No CP -> V1 -> Flush() (-> V1)', async () => { + const trie = new Trie() + + await trie.put(key, kv.v1) + trie.flushCheckpoints() + + assert.ok(await trieEval(trie, kv.v1)) + }) + it('CP -> V1 -> Commit -> Flush() (-> V1)', async () => { + const trie = new Trie() + + trie.checkpoint() + await trie.put(key, kv.v1) + await trie.commit() + trie.flushCheckpoints() + + assert.ok(await trieEval(trie, kv.v1)) + }) + + it('CP -> V1 -> Revert -> Flush() (-> Undefined)', async () => { + const trie = new Trie() + + trie.checkpoint() + await trie.put(key, kv.v1) + await trie.revert() + trie.flushCheckpoints() + + assert.ok(await trieEval(trie, null)) + }) + + it('V1 -> CP -> Commit -> Flush() (-> V1)', async () => { + const trie = new Trie() + + await trie.put(key, kv.v1) + trie.checkpoint() + await trie.commit() + trie.flushCheckpoints() + + assert.ok(await trieEval(trie, kv.v1)) + }) + + it('V1 -> CP -> Revert -> Flush() (-> V1)', async () => { + const trie = new Trie() + + await trie.put(key, kv.v1) + trie.checkpoint() + await trie.revert() + trie.flushCheckpoints() + + assert.ok(await trieEval(trie, kv.v1)) + }) + + it('V1 -> CP -> V2 -> Commit -> Flush() (-> V2)', async () => { + const trie = new Trie() + + await trie.put(key, kv.v1) + trie.checkpoint() + await trie.put(key, kv.v2) + await trie.commit() + trie.flushCheckpoints() + + assert.ok(await trieEval(trie, kv.v2)) + }) + + it('V1 -> CP -> V2 -> Commit -> V3 -> Flush() (-> V3)', async () => { + const trie = new Trie() + + await trie.put(key, kv.v1) + trie.checkpoint() + await trie.put(key, kv.v2) + await trie.commit() + await trie.put(key, kv.v3) + trie.flushCheckpoints() + + assert.ok(await trieEval(trie, kv.v3)) + }) + + it('V1 -> CP -> V2 -> V3 -> Commit -> Flush() (-> V3)', async () => { + const trie = new Trie() + + await trie.put(key, kv.v1) + trie.checkpoint() + await trie.put(key, kv.v2) + await trie.put(key, kv.v3) + await trie.commit() + trie.flushCheckpoints() + + assert.ok(await trieEval(trie, kv.v3)) + }) + + it('CP -> V1 -> V2 -> Commit -> Flush() (-> V2)', async () => { + const trie = new Trie() + + trie.checkpoint() + await trie.put(key, kv.v1) + await trie.put(key, kv.v2) + await trie.commit() + trie.flushCheckpoints() + + assert.ok(await trieEval(trie, kv.v2)) + }) + + it('CP -> V1 -> V2 -> Revert -> Flush() (-> Undefined)', async () => { + const trie = new Trie() + + trie.checkpoint() + await trie.put(key, kv.v1) + + await trie.put(key, kv.v2) + await trie.revert() + trie.flushCheckpoints() + + assert.ok(await trieEval(trie, null)) + }) + + it('V1 -> CP -> V2 -> Revert -> Flush() (-> V1)', async () => { + const trie = new Trie() + + await trie.put(key, kv.v1) + trie.checkpoint() + await trie.put(key, kv.v2) + await trie.revert() + trie.flushCheckpoints() + + assert.ok(await trieEval(trie, kv.v1)) + }) + + it('V1 -> CP -> V2 -> CP -> V3 -> Commit -> Commit -> Flush() (-> V3)', async () => { + const trie = new Trie() + + await trie.put(key, kv.v1) + trie.checkpoint() + await trie.put(key, kv.v2) + trie.checkpoint() + await trie.put(key, kv.v3) + await trie.commit() + await trie.commit() + trie.flushCheckpoints() + + assert.ok(await trieEval(trie, kv.v3)) + }) + + it('V1 -> CP -> V2 -> CP -> V3 -> Commit -> Revert -> Flush() (-> V1)', async () => { + const trie = new Trie() + + await trie.put(key, kv.v1) + trie.checkpoint() + await trie.put(key, kv.v2) + trie.checkpoint() + await trie.put(key, kv.v3) + await trie.commit() + await trie.revert() + trie.flushCheckpoints() + + assert.ok(await trieEval(trie, kv.v1)) + }) + + it('V1 -> CP -> V2 -> CP -> V3 -> Revert -> Commit -> Flush() (-> V2)', async () => { + const trie = new Trie() + + await trie.put(key, kv.v1) + trie.checkpoint() + await trie.put(key, kv.v2) + trie.checkpoint() + await trie.put(key, kv.v3) + await trie.revert() + await trie.commit() + trie.flushCheckpoints() + + assert.ok(await trieEval(trie, kv.v2)) + }) + + it('V1 -> CP -> V2 -> CP -> V3 -> Revert -> V4 -> Commit -> Flush() (-> V4)', async () => { + const trie = new Trie() + + await trie.put(key, kv.v1) + trie.checkpoint() + await trie.put(key, kv.v2) + trie.checkpoint() + await trie.put(key, kv.v3) + await trie.revert() + await trie.put(key, kv.v4) + await trie.commit() + trie.flushCheckpoints() + + assert.ok(await trieEval(trie, kv.v4)) + }) + + it('V1 -> CP -> V2 -> CP -> V3 -> Revert -> V4 -> CP -> V5 -> Commit -> Commit -> Flush() (-> V5)', async () => { + const trie = new Trie() + + await trie.put(key, kv.v1) + trie.checkpoint() + await trie.put(key, kv.v2) + trie.checkpoint() + await trie.put(key, kv.v3) + await trie.revert() + await trie.put(key, kv.v4) + trie.checkpoint() + await trie.put(key, kv.v5) + await trie.commit() + await trie.commit() + trie.flushCheckpoints() + + assert.ok(await trieEval(trie, kv.v5)) + }) + } +})