1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
/* External Imports */
import * as rlp from 'rlp'
import { default as seedbytes } from 'random-bytes-seed'
import { SecureTrie, BaseTrie } from 'merkle-patricia-tree'
import { fromHexString, toHexString } from '@eth-optimism/core-utils'
import { ethers } from 'ethers'
export interface TrieNode {
key: string
val: string
}
export interface InclusionProofTest {
key: string
val: string
proof: string
root: string
}
export interface NodeUpdateTest extends InclusionProofTest {
newRoot: string
}
export interface EthereumAccount {
address?: string
nonce: number
balance: number
codeHash: string
storageRoot?: string
storage?: TrieNode[]
}
export interface AccountProofTest {
address: string
account: EthereumAccount
accountTrieWitness: string
accountTrieRoot: string
}
export interface AccountUpdateTest extends AccountProofTest {
newAccountTrieRoot: string
}
const rlpEncodeAccount = (account: EthereumAccount): string => {
return toHexString(
rlp.encode([
account.nonce,
account.balance,
account.storageRoot || ethers.constants.HashZero,
account.codeHash || ethers.constants.HashZero,
])
)
}
const rlpDecodeAccount = (encoded: string): EthereumAccount => {
const decoded = rlp.decode(fromHexString(encoded)) as any
return {
nonce: decoded[0].length ? parseInt(decoded[0], 16) : 0,
balance: decoded[1].length ? parseInt(decoded[1], 16) : 0,
storageRoot: decoded[2].length
? toHexString(decoded[2])
: ethers.constants.HashZero,
codeHash: decoded[3].length
? toHexString(decoded[3])
: ethers.constants.HashZero,
}
}
const makeTrie = async (
nodes: TrieNode[],
secure?: boolean
): Promise<{
trie: SecureTrie | BaseTrie
TrieClass: any
}> => {
const TrieClass = secure ? SecureTrie : BaseTrie
const trie = new TrieClass()
for (const node of nodes) {
await trie.put(fromHexString(node.key), fromHexString(node.val))
}
return {
trie,
TrieClass,
}
}
export class TrieTestGenerator {
constructor(
public _TrieClass: any,
public _trie: SecureTrie | BaseTrie,
public _nodes: TrieNode[],
public _subGenerators?: TrieTestGenerator[]
) {}
static async fromNodes(opts: {
nodes: TrieNode[]
secure?: boolean
}): Promise<TrieTestGenerator> {
const { trie, TrieClass } = await makeTrie(opts.nodes, opts.secure)
return new TrieTestGenerator(TrieClass, trie, opts.nodes)
}
static async fromRandom(opts: {
seed: string
nodeCount: number
secure?: boolean
keySize?: number
valSize?: number
}): Promise<TrieTestGenerator> {
const getRandomBytes = seedbytes(opts.seed)
const nodes: TrieNode[] = [...Array(opts.nodeCount)].map(() => {
return {
key: toHexString(getRandomBytes(opts.keySize || 32)),
val: toHexString(getRandomBytes(opts.valSize || 32)),
}
})
return TrieTestGenerator.fromNodes({
nodes,
secure: opts.secure,
})
}
static async fromAccounts(opts: {
accounts: EthereumAccount[]
secure?: boolean
}): Promise<TrieTestGenerator> {
const subGenerators: TrieTestGenerator[] = []
for (const account of opts.accounts) {
if (account.storage) {
const subGenerator = await TrieTestGenerator.fromNodes({
nodes: account.storage,
secure: opts.secure,
})
account.storageRoot = toHexString(subGenerator._trie.root)
subGenerators.push(subGenerator)
}
}
const nodes = opts.accounts.map((account) => {
return {
key: account.address,
val: rlpEncodeAccount(account),
}
})
const { trie, TrieClass } = await makeTrie(nodes, opts.secure)
return new TrieTestGenerator(TrieClass, trie, nodes, subGenerators)
}
public async makeInclusionProofTest(
key: string | number
): Promise<InclusionProofTest> {
if (typeof key === 'number') {
key = this._nodes[key].key
}
const trie = this._trie.copy()
const proof = await this.prove(key)
const val = await trie.get(fromHexString(key))
return {
proof: toHexString(rlp.encode(proof)),
key: toHexString(key),
val: toHexString(val),
root: toHexString(trie.root),
}
}
public async makeAllInclusionProofTests(): Promise<InclusionProofTest[]> {
return Promise.all(
this._nodes.map(async (node) => {
return this.makeInclusionProofTest(node.key)
})
)
}
public async makeNodeUpdateTest(
key: string | number,
val: string
): Promise<NodeUpdateTest> {
if (typeof key === 'number') {
key = this._nodes[key].key
}
const trie = this._trie.copy()
const proof = await this.prove(key)
const oldRoot = trie.root
await trie.put(fromHexString(key), fromHexString(val))
const newRoot = trie.root
return {
proof: toHexString(rlp.encode(proof)),
key: toHexString(key),
val: toHexString(val),
root: toHexString(oldRoot),
newRoot: toHexString(newRoot),
}
}
public async makeAccountProofTest(
address: string | number
): Promise<AccountProofTest> {
if (typeof address === 'number') {
address = this._nodes[address].key
}
const trie = this._trie.copy()
const proof = await this.prove(address)
const account = await trie.get(fromHexString(address))
return {
address,
account: rlpDecodeAccount(toHexString(account)),
accountTrieWitness: toHexString(rlp.encode(proof)),
accountTrieRoot: toHexString(trie.root),
}
}
public async makeAccountUpdateTest(
address: string | number,
account: EthereumAccount
): Promise<AccountUpdateTest> {
if (typeof address === 'number') {
address = this._nodes[address].key
}
const trie = this._trie.copy()
const proof = await this.prove(address)
const oldRoot = trie.root
await trie.put(
fromHexString(address),
fromHexString(rlpEncodeAccount(account))
)
const newRoot = trie.root
return {
address,
account,
accountTrieWitness: toHexString(rlp.encode(proof)),
accountTrieRoot: toHexString(oldRoot),
newAccountTrieRoot: toHexString(newRoot),
}
}
private async prove(key: string): Promise<any> {
return this._TrieClass.prove(this._trie, fromHexString(key))
}
}