marsxchain 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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);