marsxchain 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- package/README.md +28 -0
- package/account/index.js +43 -0
- package/account/index.test.js +29 -0
- package/api/index.js +96 -0
- package/api/pubsub.js +81 -0
- package/api-test.js +112 -0
- package/blockchain/block.js +144 -0
- package/blockchain/block.test.js +160 -0
- package/blockchain/index.js +53 -0
- package/config.js +27 -0
- package/course_logo_udemy.png +0 -0
- package/interpreter/index.js +251 -0
- package/interpreter/index.test.js +167 -0
- package/package.json +44 -0
- package/store/state.js +32 -0
- package/store/trie.js +60 -0
- package/store/trie.test.js +56 -0
- package/tmp.js +16 -0
- package/transaction/index.js +244 -0
- package/transaction/index.test.js +140 -0
- package/transaction/transaction-queue.js +21 -0
- package/util/index.js +22 -0
- package/util/index.test.js +22 -0
@@ -0,0 +1,53 @@
|
|
1
|
+
const Block = require('./block');
|
2
|
+
|
3
|
+
class Blockchain {
|
4
|
+
constructor({ state }) {
|
5
|
+
this.chain = [Block.genesis()];
|
6
|
+
this.state = state;
|
7
|
+
}
|
8
|
+
|
9
|
+
addBlock({ block, transactionQueue }) {
|
10
|
+
return new Promise((resolve, reject) => {
|
11
|
+
Block.validateBlock({
|
12
|
+
lastBlock: this.chain[this.chain.length-1],
|
13
|
+
block,
|
14
|
+
state: this.state
|
15
|
+
}).then(() => {
|
16
|
+
this.chain.push(block);
|
17
|
+
|
18
|
+
Block.runBlock({ block, state: this.state });
|
19
|
+
|
20
|
+
transactionQueue.clearBlockTransactions({
|
21
|
+
transactionSeries: block.transactionSeries
|
22
|
+
});
|
23
|
+
|
24
|
+
return resolve();
|
25
|
+
}).catch(reject);
|
26
|
+
});
|
27
|
+
}
|
28
|
+
|
29
|
+
replaceChain({ chain }) {
|
30
|
+
return new Promise(async (resolve, reject) => {
|
31
|
+
for (let i=0; i<chain.length; i++) {
|
32
|
+
const block = chain[i];
|
33
|
+
const lastBlockIndex = i-1;
|
34
|
+
const lastBlock = lastBlockIndex >= 0 ? chain[i-1] : null;
|
35
|
+
|
36
|
+
try {
|
37
|
+
await Block.validateBlock({ lastBlock, block, state: this.state });
|
38
|
+
Block.runBlock({ block, state: this.state });
|
39
|
+
} catch (error) {
|
40
|
+
return reject(error);
|
41
|
+
}
|
42
|
+
|
43
|
+
console.log(`*-- Validated block number: ${block.blockHeaders.number}`);
|
44
|
+
}
|
45
|
+
|
46
|
+
this.chain = chain;
|
47
|
+
|
48
|
+
return resolve();
|
49
|
+
});
|
50
|
+
}
|
51
|
+
}
|
52
|
+
|
53
|
+
module.exports = Blockchain;
|
package/config.js
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
const GENESIS_DATA = {
|
2
|
+
blockHeaders: {
|
3
|
+
parentHash: '--genesis-parent-hash--',
|
4
|
+
beneficiary: '--genesis-beneficiary--',
|
5
|
+
difficulty: 1,
|
6
|
+
number: 0,
|
7
|
+
timestamp: '--genesis-timestamp--',
|
8
|
+
nonce: 0,
|
9
|
+
transactionsRoot: '--genesis-transactions-root-',
|
10
|
+
stateRoot: '--genesis-state-root--'
|
11
|
+
},
|
12
|
+
transactionSeries: []
|
13
|
+
};
|
14
|
+
|
15
|
+
const MILLISECONDS = 1;
|
16
|
+
const SECONDS = 1000 * MILLISECONDS;
|
17
|
+
const MINE_RATE = 13 * SECONDS;
|
18
|
+
|
19
|
+
const STARTING_BALANCE = 1000;
|
20
|
+
const MINING_REWARD = 50;
|
21
|
+
|
22
|
+
module.exports = {
|
23
|
+
GENESIS_DATA,
|
24
|
+
MINE_RATE,
|
25
|
+
STARTING_BALANCE,
|
26
|
+
MINING_REWARD
|
27
|
+
};
|
Binary file
|
@@ -0,0 +1,251 @@
|
|
1
|
+
const STOP = 'STOP';
|
2
|
+
const ADD = 'ADD';
|
3
|
+
const SUB = 'SUB';
|
4
|
+
const MUL = 'MUL';
|
5
|
+
const DIV = 'DIV';
|
6
|
+
const PUSH = 'PUSH';
|
7
|
+
const LT = 'LT';
|
8
|
+
const GT = 'GT';
|
9
|
+
const EQ = 'EQ';
|
10
|
+
const AND = 'AND';
|
11
|
+
const OR = 'OR';
|
12
|
+
const JUMP = 'JUMP';
|
13
|
+
const JUMPI = 'JUMPI';
|
14
|
+
const STORE = 'STORE';
|
15
|
+
const LOAD = 'LOAD';
|
16
|
+
|
17
|
+
const OPCODE_MAP = {
|
18
|
+
STOP,
|
19
|
+
ADD,
|
20
|
+
SUB,
|
21
|
+
MUL,
|
22
|
+
DIV,
|
23
|
+
PUSH,
|
24
|
+
LT,
|
25
|
+
GT,
|
26
|
+
EQ,
|
27
|
+
AND,
|
28
|
+
OR,
|
29
|
+
JUMP,
|
30
|
+
JUMPI,
|
31
|
+
STORE,
|
32
|
+
LOAD
|
33
|
+
};
|
34
|
+
|
35
|
+
const OPCODE_GAS_MAP = {
|
36
|
+
STOP: 0,
|
37
|
+
ADD: 1,
|
38
|
+
SUB: 1,
|
39
|
+
MUL: 1,
|
40
|
+
DIV: 1,
|
41
|
+
PUSH: 0,
|
42
|
+
LT: 1,
|
43
|
+
GT: 1,
|
44
|
+
EQ: 1,
|
45
|
+
AND: 1,
|
46
|
+
OR: 1,
|
47
|
+
JUMP: 2,
|
48
|
+
JUMPI: 2,
|
49
|
+
STORE: 5,
|
50
|
+
LOAD: 5
|
51
|
+
};
|
52
|
+
|
53
|
+
const EXECUTION_COMPLETE = 'Execution complete';
|
54
|
+
const EXECUTION_LIMIT = 10000;
|
55
|
+
|
56
|
+
class Interpreter {
|
57
|
+
constructor({ storageTrie } = {}) {
|
58
|
+
this.state = {
|
59
|
+
programCounter: 0,
|
60
|
+
stack: [],
|
61
|
+
code: [],
|
62
|
+
executionCount: 0
|
63
|
+
};
|
64
|
+
this.storageTrie = storageTrie;
|
65
|
+
}
|
66
|
+
|
67
|
+
jump() {
|
68
|
+
const destination = this.state.stack.pop();
|
69
|
+
|
70
|
+
if (
|
71
|
+
destination < 0
|
72
|
+
|| destination > this.state.code.length
|
73
|
+
) {
|
74
|
+
throw new Error(`Invalid destination: ${destination}`);
|
75
|
+
}
|
76
|
+
|
77
|
+
this.state.programCounter = destination;
|
78
|
+
this.state.programCounter--;
|
79
|
+
}
|
80
|
+
|
81
|
+
runCode(code) {
|
82
|
+
this.state.code = code;
|
83
|
+
|
84
|
+
let gasUsed = 0;
|
85
|
+
|
86
|
+
while (this.state.programCounter < this.state.code.length) {
|
87
|
+
this.state.executionCount++;
|
88
|
+
|
89
|
+
if (this.state.executionCount > EXECUTION_LIMIT) {
|
90
|
+
throw new Error(
|
91
|
+
`Check for an infinite loop. Execution limit of ${EXECUTION_LIMIT} exceeded`
|
92
|
+
);
|
93
|
+
}
|
94
|
+
|
95
|
+
const opCode = this.state.code[this.state.programCounter];
|
96
|
+
|
97
|
+
gasUsed += OPCODE_GAS_MAP[opCode];
|
98
|
+
|
99
|
+
let value;
|
100
|
+
let key;
|
101
|
+
|
102
|
+
try {
|
103
|
+
switch (opCode) {
|
104
|
+
case STOP:
|
105
|
+
throw new Error(EXECUTION_COMPLETE);
|
106
|
+
case PUSH:
|
107
|
+
this.state.programCounter++;
|
108
|
+
|
109
|
+
if (this.state.programCounter === this.state.code.length) {
|
110
|
+
throw new Error(`The 'PUSH' instruction cannot be last.`);
|
111
|
+
}
|
112
|
+
|
113
|
+
value = this.state.code[this.state.programCounter];
|
114
|
+
this.state.stack.push(value);
|
115
|
+
break;
|
116
|
+
case ADD:
|
117
|
+
case SUB:
|
118
|
+
case MUL:
|
119
|
+
case DIV:
|
120
|
+
case LT:
|
121
|
+
case GT:
|
122
|
+
case EQ:
|
123
|
+
case AND:
|
124
|
+
case OR:
|
125
|
+
const a = this.state.stack.pop();
|
126
|
+
const b = this.state.stack.pop();
|
127
|
+
|
128
|
+
let result;
|
129
|
+
|
130
|
+
if (opCode === ADD) result = a + b;
|
131
|
+
if (opCode === SUB) result = a - b;
|
132
|
+
if (opCode === MUL) result = a * b;
|
133
|
+
if (opCode === DIV) result = a / b;
|
134
|
+
if (opCode === LT) result = a < b ? 1 : 0;
|
135
|
+
if (opCode === GT) result = a > b ? 1 : 0;
|
136
|
+
if (opCode === EQ) result = a === b ? 1 : 0;
|
137
|
+
if (opCode === AND) result = a && b;
|
138
|
+
if (opCode === OR) result = a || b;
|
139
|
+
|
140
|
+
this.state.stack.push(result);
|
141
|
+
break;
|
142
|
+
case JUMP:
|
143
|
+
this.jump();
|
144
|
+
break;
|
145
|
+
case JUMPI:
|
146
|
+
const condition = this.state.stack.pop();
|
147
|
+
|
148
|
+
if (condition === 1) {
|
149
|
+
this.jump();
|
150
|
+
}
|
151
|
+
break;
|
152
|
+
case STORE:
|
153
|
+
key = this.state.stack.pop();
|
154
|
+
value = this.state.stack.pop();
|
155
|
+
|
156
|
+
this.storageTrie.put({ key, value });
|
157
|
+
|
158
|
+
break;
|
159
|
+
case LOAD:
|
160
|
+
key = this.state.stack.pop();
|
161
|
+
value = this.storageTrie.get({ key });
|
162
|
+
|
163
|
+
this.state.stack.push(value);
|
164
|
+
|
165
|
+
break;
|
166
|
+
default:
|
167
|
+
break;
|
168
|
+
}
|
169
|
+
} catch (error) {
|
170
|
+
if (error.message === EXECUTION_COMPLETE) {
|
171
|
+
return {
|
172
|
+
result: this.state.stack[this.state.stack.length-1],
|
173
|
+
gasUsed
|
174
|
+
};
|
175
|
+
}
|
176
|
+
|
177
|
+
throw error;
|
178
|
+
}
|
179
|
+
|
180
|
+
this.state.programCounter++;
|
181
|
+
}
|
182
|
+
}
|
183
|
+
}
|
184
|
+
|
185
|
+
Interpreter.OPCODE_MAP = OPCODE_MAP;
|
186
|
+
module.exports = Interpreter;
|
187
|
+
|
188
|
+
// let code = [PUSH, 2, PUSH, 3, ADD, STOP];
|
189
|
+
// let result = new Interpreter().runCode(code);
|
190
|
+
// console.log('Result of 3 ADD 2:', result);
|
191
|
+
|
192
|
+
// code = [PUSH, 2, PUSH, 3, SUB, STOP];
|
193
|
+
// result = new Interpreter().runCode(code);
|
194
|
+
// console.log('Result of 3 SUB 2:', result);
|
195
|
+
|
196
|
+
// code = [PUSH, 2, PUSH, 3, MUL, STOP];
|
197
|
+
// result = new Interpreter().runCode(code);
|
198
|
+
// console.log('Result of 3 MUL 2:', result);
|
199
|
+
|
200
|
+
// code = [PUSH, 2, PUSH, 3, DIV, STOP];
|
201
|
+
// result = new Interpreter().runCode(code);
|
202
|
+
// console.log('Result of 3 DIV 2:', result);
|
203
|
+
|
204
|
+
// code = [PUSH, 2, PUSH, 3, LT, STOP];
|
205
|
+
// result = new Interpreter().runCode(code);
|
206
|
+
// console.log('Result of 3 LT 2:', result);
|
207
|
+
|
208
|
+
// code = [PUSH, 2, PUSH, 3, GT, STOP];
|
209
|
+
// result = new Interpreter().runCode(code);
|
210
|
+
// console.log('Result of 3 GT 2:', result);
|
211
|
+
|
212
|
+
// code = [PUSH, 2, PUSH, 2, EQ, STOP];
|
213
|
+
// result = new Interpreter().runCode(code);
|
214
|
+
// console.log('Result of 2 EQ 2:', result);
|
215
|
+
|
216
|
+
// code = [PUSH, 1, PUSH, 0, AND, STOP];
|
217
|
+
// result = new Interpreter().runCode(code);
|
218
|
+
// console.log('Result of 0 AND 1:', result);
|
219
|
+
|
220
|
+
// code = [PUSH, 1, PUSH, 0, OR, STOP];
|
221
|
+
// result = new Interpreter().runCode(code);
|
222
|
+
// console.log('Result of 0 OR 1:', result);
|
223
|
+
|
224
|
+
// code = [PUSH, 6, JUMP, PUSH, 0, JUMP, PUSH, 'jump successful', STOP];
|
225
|
+
// result = new Interpreter().runCode(code);
|
226
|
+
// console.log('Result of JUMP:', result);
|
227
|
+
|
228
|
+
// code = [PUSH, 8, PUSH, 1, JUMPI, PUSH, 0, JUMP, PUSH, 'jump successful', STOP];
|
229
|
+
// result = new Interpreter().runCode(code);
|
230
|
+
// console.log('Result of JUMPI:', result);
|
231
|
+
|
232
|
+
// code = [PUSH, 99, JUMP, PUSH, 0, JUMP, PUSH, 'jump successful', STOP];
|
233
|
+
// try {
|
234
|
+
// new Interpreter().runCode(code);
|
235
|
+
// } catch (error) {
|
236
|
+
// console.log('Invalid destination error:', error.message);
|
237
|
+
// }
|
238
|
+
|
239
|
+
// code = [PUSH, 0, PUSH];
|
240
|
+
// try {
|
241
|
+
// new Interpreter().runCode(code);
|
242
|
+
// } catch (error) {
|
243
|
+
// console.log('Expected invalid PUSH error:', error.message);
|
244
|
+
// }
|
245
|
+
|
246
|
+
// code = [PUSH, 0, JUMP, STOP];
|
247
|
+
// try {
|
248
|
+
// new Interpreter().runCode(code);
|
249
|
+
// } catch (error) {
|
250
|
+
// console.log('Expected invalid execution error:', error.message);
|
251
|
+
// }
|
@@ -0,0 +1,167 @@
|
|
1
|
+
const Interpreter = require('./index');
|
2
|
+
const Trie = require('../store/trie');
|
3
|
+
const {
|
4
|
+
STOP,
|
5
|
+
ADD,
|
6
|
+
SUB,
|
7
|
+
MUL,
|
8
|
+
DIV,
|
9
|
+
PUSH,
|
10
|
+
LT,
|
11
|
+
GT,
|
12
|
+
EQ,
|
13
|
+
AND,
|
14
|
+
OR,
|
15
|
+
JUMP,
|
16
|
+
JUMPI,
|
17
|
+
STORE,
|
18
|
+
LOAD
|
19
|
+
} = Interpreter.OPCODE_MAP;
|
20
|
+
|
21
|
+
describe('Interpreter', () => {
|
22
|
+
describe('runCode()', () => {
|
23
|
+
describe('and the code inludes ADD', () => {
|
24
|
+
it('adds two values', () => {
|
25
|
+
expect(
|
26
|
+
new Interpreter().runCode([PUSH, 2, PUSH, 3, ADD, STOP]).result
|
27
|
+
).toEqual(5);
|
28
|
+
});
|
29
|
+
});
|
30
|
+
|
31
|
+
describe('and the code inludes SUB', () => {
|
32
|
+
it('subtracts one value from another', () => {
|
33
|
+
expect(
|
34
|
+
new Interpreter().runCode([PUSH, 2, PUSH, 3, SUB, STOP]).result
|
35
|
+
).toEqual(1);
|
36
|
+
});
|
37
|
+
});
|
38
|
+
|
39
|
+
describe('and the code inludes MUL', () => {
|
40
|
+
it('products two values', () => {
|
41
|
+
expect(
|
42
|
+
new Interpreter().runCode([PUSH, 2, PUSH, 3, MUL, STOP]).result
|
43
|
+
).toEqual(6);
|
44
|
+
});
|
45
|
+
});
|
46
|
+
|
47
|
+
describe('and the code inludes DIV', () => {
|
48
|
+
it('divides one value from another', () => {
|
49
|
+
expect(
|
50
|
+
new Interpreter().runCode([PUSH, 2, PUSH, 3, DIV, STOP]).result
|
51
|
+
).toEqual(1.5);
|
52
|
+
});
|
53
|
+
});
|
54
|
+
|
55
|
+
describe('and the code inludes LT', () => {
|
56
|
+
it('checks if one value is less than another', () => {
|
57
|
+
expect(
|
58
|
+
new Interpreter().runCode([PUSH, 2, PUSH, 3, LT, STOP]).result
|
59
|
+
).toEqual(0);
|
60
|
+
});
|
61
|
+
});
|
62
|
+
|
63
|
+
describe('and the code inludes GT', () => {
|
64
|
+
it('checks if one value is greater than another', () => {
|
65
|
+
expect(
|
66
|
+
new Interpreter().runCode([PUSH, 2, PUSH, 3, GT, STOP]).result
|
67
|
+
).toEqual(1);
|
68
|
+
});
|
69
|
+
});
|
70
|
+
|
71
|
+
describe('and the code inludes EQ', () => {
|
72
|
+
it('checks if one value is equal to another', () => {
|
73
|
+
expect(
|
74
|
+
new Interpreter().runCode([PUSH, 2, PUSH, 3, EQ, STOP]).result
|
75
|
+
).toEqual(0);
|
76
|
+
});
|
77
|
+
});
|
78
|
+
|
79
|
+
describe('and the code inludes AND', () => {
|
80
|
+
it('ands two conditions', () => {
|
81
|
+
expect(
|
82
|
+
new Interpreter().runCode([PUSH, 1, PUSH, 0, AND, STOP]).result
|
83
|
+
).toEqual(0);
|
84
|
+
});
|
85
|
+
});
|
86
|
+
|
87
|
+
describe('and the code inludes OR', () => {
|
88
|
+
it('ors two conditions', () => {
|
89
|
+
expect(
|
90
|
+
new Interpreter().runCode([PUSH, 1, PUSH, 0, OR, STOP]).result
|
91
|
+
).toEqual(1);
|
92
|
+
});
|
93
|
+
});
|
94
|
+
|
95
|
+
describe('and the code inludes JUMP', () => {
|
96
|
+
it('jumps to a destination', () => {
|
97
|
+
expect(
|
98
|
+
new Interpreter().runCode(
|
99
|
+
[PUSH, 6, JUMP, PUSH, 0, JUMP, PUSH, 'jump successful', STOP]
|
100
|
+
).result
|
101
|
+
).toEqual('jump successful');
|
102
|
+
});
|
103
|
+
});
|
104
|
+
|
105
|
+
describe('and the code inludes JUMPI', () => {
|
106
|
+
it('jumps to a destination', () => {
|
107
|
+
expect(
|
108
|
+
new Interpreter().runCode(
|
109
|
+
[PUSH, 8, PUSH, 1, JUMPI, PUSH, 0, JUMP, PUSH, 'jump successful', STOP]
|
110
|
+
).result
|
111
|
+
).toEqual('jump successful');
|
112
|
+
});
|
113
|
+
});
|
114
|
+
|
115
|
+
describe('and the code includes STORE', () => {
|
116
|
+
const interpreter = new Interpreter({
|
117
|
+
storageTrie: new Trie()
|
118
|
+
});
|
119
|
+
const key = 'foo';
|
120
|
+
const value = 'bar';
|
121
|
+
|
122
|
+
interpreter.runCode([PUSH, value, PUSH, key, STORE, STOP]);
|
123
|
+
|
124
|
+
expect(interpreter.storageTrie.get({ key })).toEqual(value);
|
125
|
+
});
|
126
|
+
|
127
|
+
describe('and the code includes LOAD', () => {
|
128
|
+
const interpreter = new Interpreter({
|
129
|
+
storageTrie: new Trie()
|
130
|
+
});
|
131
|
+
const key = 'foo';
|
132
|
+
const value = 'bar';
|
133
|
+
|
134
|
+
expect(
|
135
|
+
interpreter.runCode(
|
136
|
+
[PUSH, value, PUSH, key, STORE, PUSH, key, LOAD, STOP]
|
137
|
+
).result
|
138
|
+
).toEqual(value);
|
139
|
+
});
|
140
|
+
|
141
|
+
describe('and the code includes an invalid JUMP destination', () => {
|
142
|
+
it('throws an error', () => {
|
143
|
+
expect(
|
144
|
+
() => new Interpreter().runCode(
|
145
|
+
[PUSH, 99, JUMP, PUSH, 0, JUMP, PUSH, 'jump successful', STOP]
|
146
|
+
)
|
147
|
+
).toThrow('Invalid destination: 99');
|
148
|
+
});
|
149
|
+
});
|
150
|
+
|
151
|
+
describe('and the code includes an invalid PUSH value', () => {
|
152
|
+
it('throws an error', () => {
|
153
|
+
expect(
|
154
|
+
() => new Interpreter().runCode([PUSH, 0, PUSH])
|
155
|
+
).toThrow("The 'PUSH' instruction cannot be last.");
|
156
|
+
});
|
157
|
+
});
|
158
|
+
|
159
|
+
describe('and the code includes an infinite loop', () => {
|
160
|
+
it('throws an error', () => {
|
161
|
+
expect(
|
162
|
+
() => new Interpreter().runCode([PUSH, 0, JUMP, STOP])
|
163
|
+
).toThrow('Check for an infinite loop. Execution limit of 10000 exceeded');
|
164
|
+
});
|
165
|
+
});
|
166
|
+
});
|
167
|
+
});
|
package/package.json
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
{
|
2
|
+
"name": "marsxchain",
|
3
|
+
"version": "1.0.0",
|
4
|
+
"description": "",
|
5
|
+
"main": "index.js",
|
6
|
+
"scripts": {
|
7
|
+
"api-test": "node ./api-test.js",
|
8
|
+
"dev": "nodemon ./api",
|
9
|
+
"dev-peer": "nodemon ./api --peer",
|
10
|
+
"start": "node ./api",
|
11
|
+
"test": "jest --watchAll"
|
12
|
+
},
|
13
|
+
"jest": {
|
14
|
+
"testEnvironment": "node"
|
15
|
+
},
|
16
|
+
"repository": {
|
17
|
+
"type": "git",
|
18
|
+
"url": "git+https://github.com/LifeCoachRay/SmartChain"
|
19
|
+
},
|
20
|
+
"keywords": [],
|
21
|
+
"author": "Life Coach Ray",
|
22
|
+
"license": "GNU",
|
23
|
+
"bugs": {
|
24
|
+
"url": "https://github.com/LifeCoachRay/SmartChain"
|
25
|
+
},
|
26
|
+
"homepage": "https://github.com/LifeCoachRay/SmartChain/blob/main/README.md",
|
27
|
+
"dependencies": {
|
28
|
+
"body-parser": "^1.20.0",
|
29
|
+
"elliptic": "^6.5.4",
|
30
|
+
"express": "^4.18.1",
|
31
|
+
"git": "^0.1.5",
|
32
|
+
"index.js": "^0.0.3",
|
33
|
+
"jest": "^24.9.0",
|
34
|
+
"js-sha3": "^0.8.0",
|
35
|
+
"lodash": "^4.17.21",
|
36
|
+
"node": "^18.7.0",
|
37
|
+
"pubnub": "^4.37.0",
|
38
|
+
"request": "^2.88.2",
|
39
|
+
"uuid": "^3.4.0"
|
40
|
+
},
|
41
|
+
"devDependencies": {
|
42
|
+
"nodemon": "^1.19.4"
|
43
|
+
}
|
44
|
+
}
|
package/store/state.js
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
const Trie = require('./trie');
|
2
|
+
|
3
|
+
class State {
|
4
|
+
constructor() {
|
5
|
+
this.stateTrie = new Trie();
|
6
|
+
this.storageTrieMap = {};
|
7
|
+
}
|
8
|
+
|
9
|
+
putAccount({ address, accountData }) {
|
10
|
+
if (!this.storageTrieMap[address]) {
|
11
|
+
this.storageTrieMap[address] = new Trie();
|
12
|
+
}
|
13
|
+
|
14
|
+
this.stateTrie.put({
|
15
|
+
key: address,
|
16
|
+
value: {
|
17
|
+
...accountData,
|
18
|
+
storageRoot: this.storageTrieMap[address].rootHash
|
19
|
+
}
|
20
|
+
});
|
21
|
+
}
|
22
|
+
|
23
|
+
getAccount({ address }) {
|
24
|
+
return this.stateTrie.get({ key: address });
|
25
|
+
}
|
26
|
+
|
27
|
+
getStateRoot() {
|
28
|
+
return this.stateTrie.rootHash;
|
29
|
+
}
|
30
|
+
}
|
31
|
+
|
32
|
+
module.exports = State;
|
package/store/trie.js
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
const _ = require('lodash');
|
2
|
+
const { keccakHash } = require('../util');
|
3
|
+
|
4
|
+
class Node {
|
5
|
+
constructor() {
|
6
|
+
this.value = null;
|
7
|
+
this.childMap = {};
|
8
|
+
}
|
9
|
+
}
|
10
|
+
|
11
|
+
class Trie {
|
12
|
+
constructor() {
|
13
|
+
this.head = new Node();
|
14
|
+
this.generateRootHash();
|
15
|
+
}
|
16
|
+
|
17
|
+
generateRootHash() {
|
18
|
+
this.rootHash = keccakHash(this.head);
|
19
|
+
}
|
20
|
+
|
21
|
+
get({ key }) {
|
22
|
+
let node = this.head;
|
23
|
+
|
24
|
+
for (let character of key) {
|
25
|
+
if (node.childMap[character]) {
|
26
|
+
node = node.childMap[character];
|
27
|
+
}
|
28
|
+
}
|
29
|
+
|
30
|
+
return _.cloneDeep(node.value);
|
31
|
+
}
|
32
|
+
|
33
|
+
put({ key, value }) {
|
34
|
+
let node = this.head;
|
35
|
+
|
36
|
+
for (let character of key) {
|
37
|
+
if (!node.childMap[character]) {
|
38
|
+
node.childMap[character] = new Node();
|
39
|
+
}
|
40
|
+
|
41
|
+
node = node.childMap[character];
|
42
|
+
}
|
43
|
+
|
44
|
+
node.value = value;
|
45
|
+
|
46
|
+
this.generateRootHash();
|
47
|
+
}
|
48
|
+
|
49
|
+
static buildTrie({ items }) {
|
50
|
+
const trie = new this();
|
51
|
+
|
52
|
+
for (let item of items.sort((a, b) => keccakHash(a) > keccakHash(b))) {
|
53
|
+
trie.put({ key: keccakHash(item), value: item });
|
54
|
+
}
|
55
|
+
|
56
|
+
return trie;
|
57
|
+
}
|
58
|
+
}
|
59
|
+
|
60
|
+
module.exports = Trie;
|
@@ -0,0 +1,56 @@
|
|
1
|
+
const Trie = require('./trie');
|
2
|
+
const { keccakHash } = require('../util');
|
3
|
+
|
4
|
+
describe('Trie', () => {
|
5
|
+
let trie;
|
6
|
+
|
7
|
+
beforeEach(() => {
|
8
|
+
trie = new Trie();
|
9
|
+
});
|
10
|
+
|
11
|
+
it('has a rootHash', () => {
|
12
|
+
expect(trie.rootHash).not.toBe(undefined);
|
13
|
+
});
|
14
|
+
|
15
|
+
describe('put()', () => {
|
16
|
+
it('stores a value under a key', () => {
|
17
|
+
const key = 'foo';
|
18
|
+
const value = 'bar';
|
19
|
+
trie.put({ key, value });
|
20
|
+
|
21
|
+
expect(trie.get({ key })).toEqual(value);
|
22
|
+
});
|
23
|
+
|
24
|
+
it('generates a new root hash after entering the value', () => {
|
25
|
+
const originalRootHash = trie.rootHash;
|
26
|
+
|
27
|
+
trie.put({ key: 'foo', value: 'bar' });
|
28
|
+
|
29
|
+
expect(trie.rootHash).not.toEqual(originalRootHash);
|
30
|
+
});
|
31
|
+
});
|
32
|
+
|
33
|
+
describe('get()', () => {
|
34
|
+
it('returns a copy of the stored value', () => {
|
35
|
+
const key = 'foo';
|
36
|
+
const value = { one: 1 };
|
37
|
+
trie.put({ key, value });
|
38
|
+
const gottenValue = trie.get({ key });
|
39
|
+
value.one = 2;
|
40
|
+
|
41
|
+
expect(gottenValue).toEqual({ one: 1 });
|
42
|
+
});
|
43
|
+
});
|
44
|
+
|
45
|
+
describe('buildTrie()', () => {
|
46
|
+
it('builds a trie where the items are accessible with their hashes', () => {
|
47
|
+
const item1 = { foo: 'bar' };
|
48
|
+
const item2 = { foo2: 'bar2' };
|
49
|
+
|
50
|
+
trie = Trie.buildTrie({ items: [item1, item2] });
|
51
|
+
|
52
|
+
expect(trie.get({ key: keccakHash(item1) })).toEqual(item1);
|
53
|
+
expect(trie.get({ key: keccakHash(item2) })).toEqual(item2);
|
54
|
+
});
|
55
|
+
});
|
56
|
+
});
|
package/tmp.js
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
const Trie = require('./store/trie');
|
2
|
+
const { keccakHash } = require('./util');
|
3
|
+
|
4
|
+
const trie = new Trie();
|
5
|
+
const accountData = { balance: 1000 };
|
6
|
+
const transaction = { data: accountData };
|
7
|
+
|
8
|
+
trie.put({ key: 'foo', value: transaction });
|
9
|
+
const retrievedTransaction = trie.get({ key: 'foo' });
|
10
|
+
const hash1 = keccakHash(retrievedTransaction);
|
11
|
+
console.log('hash1', hash1);
|
12
|
+
|
13
|
+
accountData.balance += 50;
|
14
|
+
|
15
|
+
const hash2 = keccakHash(retrievedTransaction);
|
16
|
+
console.log('hash2', hash2);
|