marsxchain 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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);