diff --git a/package.json b/package.json index bd73366..2bd5376 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jechain", - "version": "0.27.0", + "version": "0.28.0", "description": "Node for JeChain - an experimental smart contract blockchain network", "main": "./index.js", "scripts": { diff --git a/src/core/block.js b/src/core/block.js index cebf155..bafb093 100644 --- a/src/core/block.js +++ b/src/core/block.js @@ -223,7 +223,7 @@ class Block { // Finalize state and contract storage into DB for (const address in storage) { - const storageDB = new Level(__dirname + "/../log/accountStore/" + address); + const storageDB = new Level(__dirname + "/../../log/accountStore/" + address); const keys = Object.keys(storage[address]); states[address].storageRoot = buildMerkleTree(keys.map(key => key + " " + storage[address][key])).val; diff --git a/src/core/genesis.js b/src/core/genesis.js index c009e5f..7adb5f2 100644 --- a/src/core/genesis.js +++ b/src/core/genesis.js @@ -1,3 +1,5 @@ +"use strict"; + const EC = require("elliptic").ec, ec = new EC("secp256k1"); const crypto = require("crypto"), SHA256 = message => crypto.createHash("sha256").update(message).digest("hex"); diff --git a/src/core/merkle.js b/src/core/merkle.js index 403aed2..f8750bd 100644 --- a/src/core/merkle.js +++ b/src/core/merkle.js @@ -1,3 +1,5 @@ +"use strict"; + const crypto = require("crypto"), SHA256 = message => crypto.createHash("sha256").update(message).digest("hex"); function Node(val, left = null, right = null) { diff --git a/src/core/runtime.js b/src/core/runtime.js index 00ab255..17a0b9c 100644 --- a/src/core/runtime.js +++ b/src/core/runtime.js @@ -1,3 +1,5 @@ +"use strict"; + const { Level } = require('level'); const { bigIntable, isHex, deserializeState, serializeState } = require("../utils/utils"); @@ -8,7 +10,7 @@ const { EMPTY_HASH } = require("../config.json"); const crypto = require("crypto"), SHA256 = message => crypto.createHash("sha256").update(message).digest("hex"); async function jelscript(input, originalState = {}, gas, stateDB, block, txInfo, contractInfo, enableLogging = false) { - const storageDB = new Level(__dirname + "/../log/accountStore/" + contractInfo.address); + const storageDB = new Level(__dirname + "/../../log/accountStore/" + contractInfo.address); const instructions = input.trim().replace(/\t/g, "").split("\n").map(ins => ins.trim()).filter(ins => ins !== ""); diff --git a/src/core/state.js b/src/core/state.js index 84a3d0d..5c37484 100644 --- a/src/core/state.js +++ b/src/core/state.js @@ -66,7 +66,7 @@ async function changeState(newBlock, stateDB, codeDB, enableLogging = false) { / const [ newState, newStorage ] = await jelscript(await codeDB.get(dataFromRecipient.codeHash), {}, BigInt(tx.additionalData.contractGas || 0), stateDB, newBlock, tx, contractInfo, enableLogging); - const storageDB = new Level(__dirname + "/../log/accountStore/" + tx.recipient); + const storageDB = new Level(__dirname + "/../../log/accountStore/" + tx.recipient); const keys = Object.keys(newStorage); newState[tx.recipient].storageRoot = buildMerkleTree(keys.map(key => key + " " + newStorage[key])).val; diff --git a/src/core/txPool.js b/src/core/txPool.js index fa60653..d03417d 100644 --- a/src/core/txPool.js +++ b/src/core/txPool.js @@ -1,3 +1,5 @@ +"use strict"; + const crypto = require("crypto"), SHA256 = message => crypto.createHash("sha256").update(message).digest("hex"); const EC = require("elliptic").ec, ec = new EC("secp256k1"); @@ -11,6 +13,8 @@ async function addTransaction(transaction, chainInfo, stateDB) { try { transaction = Transaction.deserialize(transaction); } catch (e) { + console.log(`\x1b[31mERROR\x1b[0m [${(new Date()).toISOString()}] Failed to add one transaction to pool.`); + // If transaction can not be deserialized, it's faulty return; } @@ -21,6 +25,7 @@ async function addTransaction(transaction, chainInfo, stateDB) { BigInt(transaction.additionalData.contractGas || 0) > BigInt(BLOCK_GAS_LIMIT) ) { console.log(`\x1b[31mERROR\x1b[0m [${(new Date()).toISOString()}] Failed to add one transaction to pool.`); + return; } @@ -32,6 +37,7 @@ async function addTransaction(transaction, chainInfo, stateDB) { if (!(await stateDB.keys().all()).includes(txSenderAddress)) { console.log(`\x1b[31mERROR\x1b[0m [${(new Date()).toISOString()}] Failed to add one transaction to pool.`); + return; } @@ -49,6 +55,7 @@ async function addTransaction(transaction, chainInfo, stateDB) { if (maxNonce + 1 !== transaction.nonce) { console.log(`\x1b[31mERROR\x1b[0m [${(new Date()).toISOString()}] Failed to add one transaction to pool.`); + return; } diff --git a/src/node/queue.js b/src/node/queue.js new file mode 100644 index 0000000..38695e9 --- /dev/null +++ b/src/node/queue.js @@ -0,0 +1,33 @@ +"use strict"; + +class SyncQueue { + constructor (chainInfo) { + this.queue = []; + this.chainInfo = chainInfo; + } + + async add(block, verificationHandler) { + this.queue.push(block); + + if (!this.chainInfo.syncing) { + this.chainInfo.syncing = true; + await this.sync(verificationHandler); + } + } + + async sync(verificationHandler) { + while (this.queue.length !== 0) { + const block = this.queue.shift(); + + if (await verificationHandler(block)) break; + } + + this.chainInfo.syncing = false; + } + + wipe() { + this.queue = []; + } +} + +module.exports = { SyncQueue }; diff --git a/src/node/server.js b/src/node/server.js index a3b14a6..c3cbe97 100644 --- a/src/node/server.js +++ b/src/node/server.js @@ -19,6 +19,7 @@ const { verifyBlock, updateDifficulty } = require("../consensus/consensus"); const { parseJSON, indexTxns, numToBuffer, serializeState, deserializeState } = require("../utils/utils"); const jelscript = require("../core/runtime"); const { buildMerkleTree } = require("../core/merkle"); +const { SyncQueue } = require("./queue"); const opened = []; // Addresses and sockets from connected nodes. const connected = []; // Addresses from connected nodes. @@ -33,15 +34,16 @@ const chainInfo = { transactionPool: [], latestBlock: generateGenesisBlock(), latestSyncBlock: null, + syncQueue: new SyncQueue(this), + syncing: false, checkedBlock: {}, - tempStates: {}, difficulty: 1 }; -const stateDB = new Level(__dirname + "/../log/stateStore", { valueEncoding: "buffer" }); -const blockDB = new Level(__dirname + "/../log/blockStore", { valueEncoding: "buffer" }); -const bhashDB = new Level(__dirname + "/../log/bhashStore", { valueEncoding: "buffer" }); -const codeDB = new Level(__dirname + "/../log/codeStore"); +const stateDB = new Level(__dirname + "/../../log/stateStore", { valueEncoding: "buffer" }); +const blockDB = new Level(__dirname + "/../../log/blockStore", { valueEncoding: "buffer" }); +const bhashDB = new Level(__dirname + "/../../log/bhashStore", { valueEncoding: "buffer" }); +const codeDB = new Level(__dirname + "/../../log/codeStore"); async function startServer(options) { const PORT = options.PORT || 3000; // Node's PORT @@ -189,22 +191,23 @@ async function startServer(options) { break; case TYPE.REQUEST_BLOCK: - if (!ENABLE_CHAIN_REQUEST) { // Unsynced nodes should not be able to send blocks - const { blockNumber, requestAddress } = _message.data; + const { blockNumber, requestAddress } = _message.data; - const socket = opened.find(node => node.address === requestAddress).socket; // Get socket from address + let requestedBlock; + + try { + requestedBlock = [ ...await blockDB.get( blockNumber.toString() ) ]; // Get block + } catch (e) { + // If block does not exist, break + break; + } - const currentBlockNumber = Math.max(...(await blockDB.keys().all()).map(key => parseInt(key))); // Get latest block number + const socket = opened.find(node => node.address === requestAddress).socket; // Get socket from address - if (blockNumber > 0 && blockNumber <= currentBlockNumber) { // Check if block number is valid - const block = [ ...await blockDB.get( blockNumber.toString() ) ]; // Get block + socket.send(produceMessage(TYPE.SEND_BLOCK, requestedBlock)); // Send block + + console.log(`\x1b[32mLOG\x1b[0m [${(new Date()).toISOString()}] Sent block at position ${blockNumber} to ${requestAddress}.`); - socket.send(produceMessage(TYPE.SEND_BLOCK, block)); // Send block - - console.log(`\x1b[32mLOG\x1b[0m [${(new Date()).toISOString()}] Sent block at position ${blockNumber} to ${requestAddress}.`); - } - } - break; case TYPE.SEND_BLOCK: @@ -214,45 +217,54 @@ async function startServer(options) { block = Block.deserialize(_message.data); } catch (e) { // If block fails to be deserialized, it's faulty - return; } - if (ENABLE_CHAIN_REQUEST && currentSyncBlock === block.blockNumber) { - if ( - chainInfo.latestSyncBlock === null // If latest synced block is null then we immediately add the block into the chain without verification. - || // This happens due to the fact that the genesis block can discard every possible set rule ¯\_(ツ)_/¯ - await verifyBlock(block, chainInfo, stateDB, codeDB, ENABLE_LOGGING) - ) { - currentSyncBlock += 1; - - await blockDB.put(block.blockNumber.toString(), Buffer.from(_message.data)); // Add block to chain. - await bhashDB.put(block.hash, numToBuffer(block.blockNumber)); // Assign block number to the matching block hash - - if (!chainInfo.latestSyncBlock) { - chainInfo.latestSyncBlock = block; // Update latest synced block. - - await changeState(block, stateDB, codeDB, ENABLE_LOGGING); // Transit state - } - - chainInfo.latestBlock = block; // Update latest block cache - - await updateDifficulty(block, chainInfo, blockDB); // Update difficulty. - - console.log(`\x1b[32mLOG\x1b[0m [${(new Date()).toISOString()}] Synced block at position ${block.blockNumber}.`); - - // Continue requesting the next block - for (const node of opened) { - node.socket.send( - produceMessage( - TYPE.REQUEST_BLOCK, - { blockNumber: currentSyncBlock, requestAddress: MY_ADDRESS } - ) - ); - - await new Promise(r => setTimeout(r, 5000)); // Delay for block verification + if (ENABLE_CHAIN_REQUEST && block.blockNumber === currentSyncBlock) { + const verificationHandler = async function(block) { + if ( + chainInfo.latestSyncBlock === null // If latest synced block is null, we immediately add the block into the chain without verification. + || // This happens due to the fact that the genesis block can discard every possible set rule ¯\_(ツ)_/¯ + await verifyBlock(block, chainInfo, stateDB, codeDB, ENABLE_LOGGING) + ) { + await blockDB.put(block.blockNumber.toString(), Buffer.from(_message.data)); // Add block to chain + await bhashDB.put(block.hash, numToBuffer(block.blockNumber)); // Assign block number to the matching block hash + + if (!chainInfo.latestSyncBlock) { + chainInfo.latestSyncBlock = block; // Update latest synced block. + + await changeState(block, stateDB, codeDB, ENABLE_LOGGING); // Force transit state + } + + chainInfo.latestBlock = block; // Update latest block cache + + await updateDifficulty(block, chainInfo, blockDB); // Update difficulty + + console.log(`\x1b[32mLOG\x1b[0m [${(new Date()).toISOString()}] Synced block at position ${block.blockNumber}.`); + + chainInfo.syncing = false; + // Wipe sync queue + chainInfo.syncQueue.wipe(); + + currentSyncBlock++; + + // Continue requesting the next block + for (const node of opened) { + node.socket.send( + produceMessage( + TYPE.REQUEST_BLOCK, + { blockNumber: currentSyncBlock, requestAddress: MY_ADDRESS } + ) + ); + } + + return true; } + + return false; } + + chainInfo.syncQueue.add(block, verificationHandler); } break; @@ -315,8 +327,6 @@ async function startServer(options) { { blockNumber: currentSyncBlock, requestAddress: MY_ADDRESS } ) ); - - await new Promise(r => setTimeout(r, 5000)); // Delay for block verification } }, 5000); } @@ -514,7 +524,7 @@ async function mine(publicKey, ENABLE_LOGGING) { // Transit state for (const address in storage) { - const storageDB = new Level(__dirname + "/../log/accountStore/" + address); + const storageDB = new Level(__dirname + "/../../log/accountStore/" + address); const keys = Object.keys(storage[address]); states[address].storageRoot = buildMerkleTree(keys.map(key => key + " " + storage[address][key])).val; diff --git a/src/rpc/rpc.js b/src/rpc/rpc.js index dee4856..575390e 100644 --- a/src/rpc/rpc.js +++ b/src/rpc/rpc.js @@ -224,7 +224,7 @@ function rpc(PORT, client, transactionHandler, keyPair, stateDB, blockDB, bhashD ) { throwError("Invalid request.", 400); } else { - const storageDB = new Level(__dirname + "/../log/accountStore/" + contractInfo.address); + const storageDB = new Level(__dirname + "/../../log/accountStore/" + contractInfo.address); respond({ storage: await storageDB.get(req.body.params.key) }); @@ -240,7 +240,7 @@ function rpc(PORT, client, transactionHandler, keyPair, stateDB, blockDB, bhashD ) { throwError("Invalid request.", 400); } else { - const storageDB = new Level(__dirname + "/../log/accountStore/" + contractInfo.address); + const storageDB = new Level(__dirname + "/../../log/accountStore/" + contractInfo.address); respond({ storage: await storageDB.keys().all() }); }