Skip to content

Commit

Permalink
Merge pull request #82 from nguyenphuminh/trie-fix
Browse files Browse the repository at this point in the history
Trie fix
  • Loading branch information
nguyenphuminh authored Dec 27, 2023
2 parents 23dd769 + a32f71e commit 065827e
Show file tree
Hide file tree
Showing 8 changed files with 110 additions and 100 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "jechain",
"version": "0.28.2",
"version": "0.29.0",
"description": "Node for JeChain - an experimental smart contract blockchain network",
"main": "./index.js",
"scripts": {
Expand Down
9 changes: 4 additions & 5 deletions src/consensus/consensus.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
const crypto = require("crypto"), SHA256 = message => crypto.createHash("sha256").update(message).digest("hex");
const Block = require("../core/block");
const { log16 } = require("../utils/utils");
const { buildMerkleTree } = require("../core/merkle");
const Merkle = require("../core/merkle");
const { BLOCK_TIME } = require("../config.json");
const { indexTxns } = require("../utils/utils");

async function verifyBlock(newBlock, chainInfo, stateDB, codeDB, enableLogging = false) {
// Check if the block is valid or not, if yes, we will push it to the chain, update the difficulty, chain state and the transaction pool.
Expand Down Expand Up @@ -32,11 +31,11 @@ async function verifyBlock(newBlock, chainInfo, stateDB, codeDB, enableLogging =
newBlock.hash.startsWith("00000" + Array(Math.floor(log16(chainInfo.difficulty)) + 1).join("0")) &&
newBlock.difficulty === chainInfo.difficulty &&

// Check transaction hash
buildMerkleTree(indexTxns(newBlock.transactions)).val === newBlock.txRoot &&

// Check transactions ordering
await Block.hasValidTxOrder(newBlock, stateDB) &&

// Check transaction trie root
Merkle.buildTxTrie(newBlock.transactions).root === newBlock.txRoot &&

// Check timestamp
newBlock.timestamp > chainInfo.latestBlock.timestamp &&
Expand Down
9 changes: 5 additions & 4 deletions src/core/block.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ const { Level } = require('level');
const crypto = require("crypto"), SHA256 = message => crypto.createHash("sha256").update(message).digest("hex");
const EC = require("elliptic").ec, ec = new EC("secp256k1");
const Transaction = require("./transaction");
const { buildMerkleTree } = require("./merkle");
const Merkle = require("./merkle");
const { BLOCK_REWARD, BLOCK_GAS_LIMIT, EMPTY_HASH } = require("../config.json");
const jelscript = require("./runtime");
const { indexTxns, serializeState, deserializeState } = require("../utils/utils");
const { serializeState, deserializeState } = require("../utils/utils");

class Block {
constructor(blockNumber = 1, timestamp = Date.now(), transactions = [], difficulty = 1, parentHash = "", coinbase = "") {
Expand All @@ -19,9 +19,10 @@ class Block {
this.difficulty = difficulty; // Difficulty to mine block
this.parentHash = parentHash; // Parent (previous) block's hash
this.nonce = 0; // Nonce
this.txRoot = buildMerkleTree(indexTxns(transactions)).val; // Merkle root of transactions
this.coinbase = coinbase; // Address to receive reward
this.hash = Block.getHash(this); // Hash of the block
// Merkle root of transactions
this.txRoot = Merkle.buildTxTrie(transactions.map(tx => Transaction.deserialize(tx))).root;
}

static serialize(block) {
Expand Down Expand Up @@ -226,7 +227,7 @@ class Block {
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;
states[address].storageRoot = Merkle.buildTxTrie(keys.map(key => key + " " + storage[address][key]), false).root;

for (const key of keys) {
await storageDB.put(key, storage[address][key]);
Expand Down
127 changes: 79 additions & 48 deletions src/core/merkle.js
Original file line number Diff line number Diff line change
@@ -1,73 +1,104 @@
"use strict";

const Transaction = require("./transaction");
const { EMPTY_HASH } = require("../config.json");

const crypto = require("crypto"), SHA256 = message => crypto.createHash("sha256").update(message).digest("hex");

function Node(val, left = null, right = null) {
return { val, left, right };
class Node {
constructor(leftHash = EMPTY_HASH, rightHash = EMPTY_HASH, parentHash = EMPTY_HASH) {
this.leftHash = leftHash;
this.rightHash = rightHash;
this.parentHash = parentHash;
}
}

function getMerklePath(node, target, path = []) {
if (node.val === target) return [...path, target];
if (node.left === null) return [];

const path1 = getMerklePath(node.left, target, [...path, node.right.val]);
const path2 = getMerklePath(node.right, target, [...path, node.left.val]);
class TxTrie {
constructor(root, trieMap) {
this.root = root;
this.trieMap = trieMap;
}
}

if (path1.length !== 0) return path1;
if (path2.length !== 0) return path2;
class Merkle {
static buildTxTrie(transactionList = [], indexed = true) {
let hashList = [];
const trieMap = [];

// Hash transactions
for (let index = 0; index < transactionList.length; index++) {
const tx = transactionList[index];

return [];
}
const hash = indexed ? SHA256(`${index} ` + Transaction.getHash(tx)) : SHA256(tx);

function verifyMerkleProof(leaves, root) {
let genHash = leaves[0];
hashList.push(hash);
trieMap[hash] = new Node();
}

for (let i = 1; i < leaves.length; i++) {
if (BigInt("0x" + genHash) < BigInt("0x" + leaves[i])) {
genHash = SHA256(genHash + leaves[i]);
} else {
genHash = SHA256(leaves[i] + genHash);
// If there are no transaction, supply an empty hash so there would not be an error
if (transactionList.length === 0) {
hashList.push(EMPTY_HASH);
trieMap[EMPTY_HASH] = new Node();
}
}

return genHash === root;
}
// Build the tree up continuously
while (true) {
// If the hash list only have one hash left, it is the root and we have finished building the tree
if (hashList.length === 1) return new TxTrie(hashList[0], trieMap);

function buildMerkleTree(items) {
if (items.length === 0) return Node(SHA256("0"));
// If hash amount is odd, then we duplicate the latest hash
if (hashList.length % 2 !== 0) {
hashList.push(hashList.at(-1));
}

let hashList = items.map(item => Node(SHA256(item)));

if (hashList.length % 2 !== 0 && hashList.length !== 1) {
hashList.push(hashList[hashList.length-1]);
}
const newHashList = [];

while (hashList.length !== 1) {
const newRow = [];
// Generate hashes at current depth
while (hashList.length !== 0) {

while (hashList.length !== 0) {
if (hashList.length % 2 !== 0 && hashList.length !== 1) {
hashList.push(hashList[hashList.length-1]);
}

const left = hashList.shift();
const right = hashList.shift();
const leftHash = hashList.shift();
const rightHash = hashList.shift();

if (BigInt("0x" + left.val) < BigInt("0x" + right.val)) {
const node = Node(SHA256(left.val + right.val), left, right);
let hash = EMPTY_HASH;

newRow.push(node);
} else {
const node = Node(SHA256(right.val + left.val), right, left);
if (BigInt("0x" + leftHash) > BigInt("0x" + rightHash)) {
hash = SHA256(leftHash + rightHash);
} else {
hash = SHA256(rightHash + leftHash);
}

newRow.push(node);
// Push hash to hash list
newHashList.push(hash);
// Update nodes in trie
trieMap[hash] = new Node(leftHash, rightHash);
trieMap[leftHash].parentHash = hash;
trieMap[rightHash].parentHash = hash;
}

hashList = newHashList;
}
}

static getTxTriePath(trieMap, rootHash, target) {
const path = [];

let currentHash = target;

hashList = newRow;
while (true) {
if (currentHash === rootHash) return path;

const currentNode = trieMap[currentHash];
const parentNode = trieMap[currentNode.parentHash];

if (parentNode.leftHash === currentHash) {
path.push(parentNode.rightHash);
} else {
path.push(parentNode.leftHash);
}

currentHash = currentNode.parentHash;
}
}

return hashList[0];
}

module.exports = { getMerklePath, verifyMerkleProof, buildMerkleTree };
module.exports = Merkle;
44 changes: 13 additions & 31 deletions src/core/runtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +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);

// Prepare code, memory, state, storage placeholder
const instructions = input.trim().replace(/\t/g, "").split("\n").map(ins => ins.trim()).filter(ins => ins !== "");

const memory = {}, state = { ...originalState }, storage = {};
Expand All @@ -20,6 +19,18 @@ async function jelscript(input, originalState = {}, gas, stateDB, block, txInfo,

let ptr = 0;


// Get contract state and storage
const storageDB = new Level(__dirname + "/../../log/accountStore/" + contractInfo.address);

for (const key of (await storageDB.keys().all())) {
storage[key] = await storageDB.get(key);
}

const contractState = deserializeState(await stateDB.get(contractInfo.address));
state[contractInfo.address] = contractState;


while (
ptr < instructions.length &&
instructions[ptr].trim() !== "stop" &&
Expand Down Expand Up @@ -217,11 +228,6 @@ async function jelscript(input, originalState = {}, gas, stateDB, block, txInfo,
break;

case "selfbalance": // Contract's balance
if (!state[contractInfo.address]) {
const contractState = deserializeState(await stateDB.get(contractInfo.address));
state[contractInfo.address] = contractState;
}

setMem(c, "0x" + BigInt(state[contractInfo.address].balance).toString(16));

break;
Expand Down Expand Up @@ -250,12 +256,6 @@ async function jelscript(input, originalState = {}, gas, stateDB, block, txInfo,
case "send": // Send tokens to address
const target = getValue(c).slice(2);
const amount = BigInt(getValue(args[1]));

if (!state[contractInfo.address]) {
const contractState = deserializeState(await stateDB.get(contractInfo.address));
state[contractInfo.address] = contractState;
}

const balance = state[contractInfo.address].balance;

if (BigInt(balance) >= amount) {
Expand Down Expand Up @@ -338,28 +338,10 @@ async function jelscript(input, originalState = {}, gas, stateDB, block, txInfo,
}

async function setStorage(key, value) {
if (!state[contractInfo.address]) {
const contractState = deserializeState(await stateDB.get(contractInfo.address));
state[contractInfo.address] = contractState;
}

for (const key of (await storageDB.keys().all())) {
storage[key] = await storageDB.get(key);
}

storage[key] = value;
}

async function getStorage(key) {
if (!state[contractInfo.address]) {
const contractState = deserializeState(await stateDB.get(contractInfo.address));
state[contractInfo.address] = contractState;
}

for (const key of (await storageDB.keys().all())) {
storage[key] = await storageDB.get(key);
}

return storage[key] ? storage[key] : "0x0";
}

Expand Down
3 changes: 2 additions & 1 deletion src/core/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const { Level } = require('level');
const crypto = require("crypto"), SHA256 = message => crypto.createHash("sha256").update(message).digest("hex");
const EC = require("elliptic").ec, ec = new EC("secp256k1");

const Merkle = require("./merkle");
const jelscript = require("./runtime");
const Transaction = require("./transaction");

Expand Down Expand Up @@ -69,7 +70,7 @@ async function changeState(newBlock, stateDB, codeDB, enableLogging = false) { /
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;
newState[tx.recipient].storageRoot = Merkle.buildTxTrie(keys.map(key => key + " " + newStorage[key]), false).root;

for (const key in newStorage) {
await storageDB.put(key, newStorage[key]);
Expand Down
10 changes: 5 additions & 5 deletions src/node/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ const { addTransaction, clearDepreciatedTxns }= require("../core/txPool");
const rpc = require("../rpc/rpc");
const TYPE = require("./message-types");
const { verifyBlock, updateDifficulty } = require("../consensus/consensus");
const { parseJSON, indexTxns, numToBuffer, serializeState, deserializeState } = require("../utils/utils");
const { parseJSON, numToBuffer, serializeState, deserializeState } = require("../utils/utils");
const jelscript = require("../core/runtime");
const { buildMerkleTree } = require("../core/merkle");
const Merkle = require("../core/merkle");
const { SyncQueue } = require("./queue");

const opened = []; // Addresses and sockets from connected nodes.
Expand Down Expand Up @@ -524,7 +524,7 @@ async function mine(publicKey, ENABLE_LOGGING) {

block.transactions = transactionsToMine.map(tx => Transaction.serialize(tx)); // Add transactions to block
block.hash = Block.getHash(block); // Re-hash with new transactions
block.txRoot = buildMerkleTree(indexTxns(block.transactions)).val; // Re-gen transaction root with new transactions
block.txRoot = Merkle.buildTxTrie(transactionsAsObj).root; // Re-gen transaction root with new transactions

// Mine the block.
mine(block, chainInfo.difficulty)
Expand Down Expand Up @@ -568,8 +568,8 @@ async function mine(publicKey, ENABLE_LOGGING) {
for (const address in storage) {
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;

states[address].storageRoot = Merkle.buildTxTrie(keys.map(key => key + " " + storage[address][key]), false).root;

for (const key of keys) {
await storageDB.put(key, storage[address][key]);
Expand Down
6 changes: 1 addition & 5 deletions src/utils/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,6 @@ function numToBuffer(value) {
return Buffer.from(hexValue.padStart(hexLength, "0"), "hex");
}

function indexTxns(transactions) {
return transactions.map((txn, index) => index.toString() + JSON.stringify(txn));
}

function serializeState(stateHeader) {
let hexState = "";

Expand Down Expand Up @@ -78,4 +74,4 @@ function deserializeState(stateInBytes) {
return stateHeader;
}

module.exports = { log16, isNumber, isHex, parseJSON, bigIntable, indexTxns, numToBuffer, serializeState, deserializeState };
module.exports = { log16, isNumber, isHex, parseJSON, bigIntable, numToBuffer, serializeState, deserializeState };

0 comments on commit 065827e

Please sign in to comment.