Skip to content

Commit 66effa9

Browse files
committed
trie -> new CP mechanism: added DB revert operations for put, del and batch, added DB checkpointing tests
1 parent 3b51e45 commit 66effa9

File tree

5 files changed

+170
-16
lines changed

5 files changed

+170
-16
lines changed

packages/trie/src/checkpointTrie.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export class CheckpointTrie extends BaseTrie {
5050

5151
await this.lock.wait()
5252
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
53-
this.root = this.db.revert()
53+
this.root = await this.db.revert()
5454
this.lock.signal()
5555
}
5656

packages/trie/src/db.ts

Lines changed: 79 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ export const ENCODING_OPTS = { keyEncoding: 'binary', valueEncoding: 'binary' }
55

66
export type Checkpoint = {
77
root: Buffer
8-
operations: any[]
8+
revertOps: BatchDBOp[]
99
}
1010

1111
export type BatchDBOp = PutBatch | DelBatch
@@ -40,30 +40,47 @@ export class DB {
4040
this._leveldb = leveldb || level()
4141
}
4242

43+
/**
44+
* Is the DB during a checkpoint phase?
45+
*/
46+
get isCheckpoint() {
47+
return this.checkpoints.length > 0
48+
}
49+
4350
/**
4451
* Adds a new checkpoint to the stack
4552
* @param root
4653
*/
4754
checkpoint(root: Buffer) {
48-
this.checkpoints.push({ root, operations: [] })
55+
this.checkpoints.push({ root, revertOps: [] })
4956
}
5057

5158
/**
5259
* Commits the latest checkpoint
5360
*/
5461
commit() {
55-
const { root } = this.checkpoints.pop()!
62+
const { root, revertOps } = this.checkpoints.pop()!
63+
// On nested checkpoints put the revertOps on the parent
64+
// stack in case there is a revert
65+
if (this.isCheckpoint) {
66+
this.checkpoints[this.checkpoints.length - 1].revertOps.concat(revertOps)
67+
}
5668
return root
5769
}
5870

5971
/**
6072
* Reverts the latest checkpoint
6173
*/
62-
revert() {
63-
const { root } = this.checkpoints.pop()!
74+
async revert() {
75+
const { root, revertOps } = this.checkpoints.pop()!
76+
await this.batch(revertOps.reverse())
6477
return root
6578
}
6679

80+
private addCPRevertOperation(op: BatchDBOp) {
81+
this.checkpoints[this.checkpoints.length - 1].revertOps.push(op)
82+
}
83+
6784
/**
6885
* Retrieves a raw value from leveldb.
6986
* @param key
@@ -89,23 +106,79 @@ export class DB {
89106
* @param value The value to be stored
90107
*/
91108
async put(key: Buffer, val: Buffer): Promise<void> {
109+
const revertOps: BatchDBOp[] = []
110+
// In CP mode check for an old value to be put
111+
// on the revert ops stack
112+
if (this.isCheckpoint) {
113+
const oldValue = await this.get(key)
114+
if (oldValue !== null) {
115+
revertOps.push({
116+
type: 'put',
117+
key: key,
118+
value: oldValue,
119+
})
120+
}
121+
}
92122
await this._leveldb.put(key, val, ENCODING_OPTS)
123+
// In CP mode add del to the revert ops stack
124+
if (this.isCheckpoint) {
125+
revertOps.push({
126+
type: 'del',
127+
key: key,
128+
})
129+
for (const revertOp of revertOps) {
130+
this.addCPRevertOperation(revertOp)
131+
}
132+
}
93133
}
94134

95135
/**
96136
* Removes a raw value in the underlying leveldb.
97137
* @param keys
98138
*/
99139
async del(key: Buffer): Promise<void> {
140+
const revertOps: BatchDBOp[] = []
141+
// In CP mode check for an old value to be put
142+
// on the revert ops stack
143+
if (this.isCheckpoint) {
144+
const oldValue = await this.get(key)
145+
if (oldValue !== null) {
146+
revertOps.push({
147+
type: 'put',
148+
key: key,
149+
value: oldValue,
150+
})
151+
}
152+
}
100153
await this._leveldb.del(key, ENCODING_OPTS)
154+
if (this.isCheckpoint) {
155+
for (const revertOp of revertOps) {
156+
this.addCPRevertOperation(revertOp)
157+
}
158+
}
101159
}
102160

103161
/**
104162
* Performs a batch operation on db.
105163
* @param opStack A stack of levelup operations
106164
*/
107165
async batch(opStack: BatchDBOp[]): Promise<void> {
108-
await this._leveldb.batch(opStack, ENCODING_OPTS)
166+
if (this.isCheckpoint) {
167+
for (const op of opStack) {
168+
if (op.type === 'put') {
169+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
170+
if (!op.value) {
171+
continue
172+
}
173+
await this.put(op.key, op.value)
174+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
175+
} else if (op.type === 'del') {
176+
await this.del(op.key)
177+
}
178+
}
179+
} else {
180+
await this._leveldb.batch(opStack, ENCODING_OPTS)
181+
}
109182
}
110183

111184
/**

packages/trie/test/checkpoint.spec.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,4 +104,18 @@ tape('testing checkpoints', function (tester) {
104104
t.equal(trie.root.toString('hex'), root.toString('hex'))
105105
t.end()
106106
})
107+
108+
it('revert -> put', async function (t) {
109+
trie = new CheckpointTrie()
110+
111+
trie.checkpoint()
112+
const k1 = Buffer.from('k1')
113+
const v1 = Buffer.from('v1')
114+
await trie.put(k1, v1)
115+
t.deepEqual(await trie.get(k1), v1, 'before revert: v1 in trie')
116+
await trie.revert()
117+
t.deepEqual(await trie.get(k1), null, 'after revert: v1 removed')
118+
119+
t.end()
120+
})
107121
})

packages/trie/test/db.spec.ts

Lines changed: 74 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,31 @@
11
import tape from 'tape'
22
import { DB, BatchDBOp } from '../src/db'
33

4-
tape('DB basic functionality', (t) => {
4+
tape('DB tests', (t) => {
55
const db = new DB()
66

7-
const k = Buffer.from('foo')
8-
const v = Buffer.from('bar')
7+
const k = Buffer.from('k1')
8+
const v = Buffer.from('v1')
9+
const k2 = Buffer.from('k2')
10+
const v2 = Buffer.from('v2')
11+
//const k3 = Buffer.from('k3')
12+
const v3 = Buffer.from('v3')
913

10-
t.test('puts and gets value', async (st) => {
14+
t.test('Operations: puts and gets value', async (st) => {
1115
await db.put(k, v)
1216
const res = await db.get(k)
1317
st.ok(v.equals(res!))
1418
st.end()
1519
})
1620

17-
t.test('deletes value', async (st) => {
21+
t.test('Operations: deletes value', async (st) => {
1822
await db.del(k)
1923
const res = await db.get(k)
2024
st.notOk(res)
2125
st.end()
2226
})
2327

24-
t.test('batch ops', async (st) => {
25-
const k2 = Buffer.from('bar')
26-
const v2 = Buffer.from('baz')
28+
t.test('Operations: batch ops', async (st) => {
2729
const ops = [
2830
{ type: 'put', key: k, value: v },
2931
{ type: 'put', key: k2, value: v2 },
@@ -33,4 +35,68 @@ tape('DB basic functionality', (t) => {
3335
st.ok(v2.equals(res!))
3436
st.end()
3537
})
38+
39+
t.test('Checkpointing: revert -> put (add)', async (st) => {
40+
const db = new DB()
41+
db.checkpoint(Buffer.from('1', 'hex'))
42+
await db.put(k, v)
43+
st.deepEqual(await db.get(k), v, 'before revert: v1')
44+
await db.revert()
45+
st.deepEqual(await db.get(k), null, 'after revert: null')
46+
st.end()
47+
})
48+
49+
t.test('Checkpointing: revert -> put (update)', async (st) => {
50+
const db = new DB()
51+
await db.put(k, v)
52+
st.deepEqual(await db.get(k), v, 'before CP: v1')
53+
db.checkpoint(Buffer.from('1', 'hex'))
54+
await db.put(k, v2)
55+
await db.put(k, v3)
56+
await db.revert()
57+
st.deepEqual(await db.get(k), v, 'after revert: v1')
58+
st.end()
59+
})
60+
61+
t.test('Checkpointing: revert -> put (update) batched', async (st) => {
62+
const db = new DB()
63+
await db.put(k, v)
64+
st.deepEqual(await db.get(k), v, 'before CP: v1')
65+
db.checkpoint(Buffer.from('1', 'hex'))
66+
const ops = [
67+
{ type: 'put', key: k, value: v2 },
68+
{ type: 'put', key: k, value: v3 },
69+
] as BatchDBOp[]
70+
await db.batch(ops)
71+
await db.revert()
72+
st.deepEqual(await db.get(k), v, 'after revert: v1')
73+
st.end()
74+
})
75+
76+
t.test('Checkpointing: revert -> del', async (st) => {
77+
const db = new DB()
78+
await db.put(k, v)
79+
st.deepEqual(await db.get(k), v, 'before CP: v1')
80+
db.checkpoint(Buffer.from('1', 'hex'))
81+
await db.del(k)
82+
st.deepEqual(await db.get(k), null, 'before revert: null')
83+
await db.revert()
84+
st.deepEqual(await db.get(k), v, 'after revert: v1')
85+
st.end()
86+
})
87+
88+
t.test('Checkpointing: nested checkpoints -> commit -> revert', async (st) => {
89+
const db = new DB()
90+
await db.put(k, v)
91+
st.deepEqual(await db.get(k), v, 'before CP: v1')
92+
db.checkpoint(Buffer.from('1', 'hex'))
93+
await db.put(k, v2)
94+
db.checkpoint(Buffer.from('2', 'hex'))
95+
await db.put(k, v3)
96+
db.commit()
97+
st.deepEqual(await db.get(k), v3, 'after commit (second CP): v3')
98+
await db.revert()
99+
st.deepEqual(await db.get(k), v, 'after revert (first CP): v1')
100+
st.end()
101+
})
36102
})

packages/vm/lib/state/stateManager.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,8 @@ export default class DefaultStateManager implements StateManager {
161161
const account = await this.getAccount(address)
162162
const storageTrie = this._trie.copy(false)
163163
storageTrie.root = account.stateRoot
164-
storageTrie._checkpoints = []
164+
//@ts-ignore
165+
storageTrie.db.checkpoints = []
165166
return storageTrie
166167
}
167168

0 commit comments

Comments
 (0)