Skip to content

Commit

Permalink
Merge pull request #91 from nguyenphuminh/big-fix-6
Browse files Browse the repository at this point in the history
Big fix 6
  • Loading branch information
nguyenphuminh authored Jul 15, 2024
2 parents 5bb4828 + 2b49ecb commit 7848ebe
Show file tree
Hide file tree
Showing 9 changed files with 159 additions and 164 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.30.3",
"version": "0.30.4",
"description": "Node for JeChain - an experimental smart contract blockchain network",
"main": "./index.js",
"scripts": {
Expand Down
3 changes: 0 additions & 3 deletions src/consensus/consensus.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,6 @@ async function verifyBlock(newBlock, chainInfo, stateDB, codeDB, enableLogging =
// Check block number
newBlock.blockNumber - 1 === chainInfo.latestBlock.blockNumber &&

// Check gas limit
Block.hasValidGasLimit(newBlock) &&

// Check transactions and transit state right after
await Block.verifyTxAndTransit(newBlock, stateDB, codeDB, enableLogging)
)
Expand Down
106 changes: 52 additions & 54 deletions src/core/block.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

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 Merkle = require("./merkle");
const { BLOCK_REWARD, BLOCK_GAS_LIMIT, EMPTY_HASH } = require("../config.json");
Expand Down Expand Up @@ -130,39 +129,52 @@ class Block {
for (const tx of block.transactions) {
if (!(await Transaction.isValid(tx, stateDB))) return false;
}

// Get all existing addresses
const addressesInBlock = block.transactions.map(tx => SHA256(Transaction.getPubKey(tx)));
const existedAddresses = await stateDB.keys().all();

// If senders' address doesn't exist, return false
if (!addressesInBlock.every(address => existedAddresses.includes(address))) return false;

// Start state replay to check if transactions are legit
let states = {}, code = {}, storage = {};
const states = {}, code = {}, storage = {};

let totalTxGas = 0n;

// Execute transactions and add them to the block sequentially
for (const tx of block.transactions) {
// If packed transactions exceed block gas limit, stop
if (totalTxGas + BigInt(tx.additionalData.contractGas || 0) >= BigInt(BLOCK_GAS_LIMIT)) return false;

const txSenderPubkey = Transaction.getPubKey(tx);
const txSenderAddress = SHA256(txSenderPubkey);

const totalAmountToPay = BigInt(tx.amount) + BigInt(tx.gas) + BigInt(tx.additionalData.contractGas || 0);


// Cache the state of sender
if (!states[txSenderAddress]) {
const senderState = deserializeState(await stateDB.get(txSenderAddress));

states[txSenderAddress] = senderState;

code[senderState.codeHash] = await codeDB.get(senderState.codeHash);
}

// If sender does not have enough money or is now a contract, skip
if (states[txSenderAddress].codeHash !== EMPTY_HASH || BigInt(states[txSenderAddress].balance) < totalAmountToPay) return false;

if (senderState.codeHash !== EMPTY_HASH || BigInt(senderState.balance) < totalAmountToPay) return false;

states[txSenderAddress].balance = (BigInt(senderState.balance) - totalAmountToPay).toString();
} else {
if (states[txSenderAddress].codeHash !== EMPTY_HASH || BigInt(states[txSenderAddress].balance) < totalAmountToPay) return false;
// Update balance of sender
states[txSenderAddress].balance = (BigInt(states[txSenderAddress].balance) - BigInt(tx.amount) - BigInt(tx.gas) - BigInt(tx.additionalData.contractGas || 0)).toString();

states[txSenderAddress].balance = (BigInt(states[txSenderAddress].balance) - totalAmountToPay).toString();
// Cache the state of recipient
if (!states[tx.recipient]) {
try { // If account exists but is not cached
states[tx.recipient] = deserializeState(await stateDB.get(tx.recipient));
code[states[tx.recipient].codeHash] = await codeDB.get(states[tx.recipient].codeHash);
} catch (e) { // If account does not exist and is not cached
states[tx.recipient] = { balance: "0", codeHash: EMPTY_HASH, nonce: 0, storageRoot: EMPTY_HASH }
code[EMPTY_HASH] = "";
}
}

// Update balance of recipient
states[tx.recipient].balance = (BigInt(states[tx.recipient].balance) + BigInt(tx.amount)).toString();

// console.log(tx.recipient, states[tx.recipient].balance, block);

// Contract deployment
if (
states[txSenderAddress].codeHash === EMPTY_HASH &&
Expand All @@ -175,44 +187,41 @@ class Block {
// Update nonce
states[txSenderAddress].nonce += 1;

if (BigInt(states[txSenderAddress].balance) < 0n) return false;

if (!existedAddresses.includes(tx.recipient) && !states[tx.recipient]) {
states[tx.recipient] = { balance: "0", codeHash: EMPTY_HASH, nonce: 0, storageRoot: EMPTY_HASH }
code[EMPTY_HASH] = "";
}

if (existedAddresses.includes(tx.recipient) && !states[tx.recipient]) {
states[tx.recipient] = deserializeState(await stateDB.get(tx.recipient));
code[states[tx.recipient].codeHash] = await codeDB.get(states[tx.recipient].codeHash);
}

states[tx.recipient].balance = (BigInt(states[tx.recipient].balance) + BigInt(tx.amount)).toString();

// Contract execution
if (states[tx.recipient].codeHash !== EMPTY_HASH) {
const contractInfo = { address: tx.recipient };

const [ newState, newStorage ] = await jelscript(code[states[tx.recipient].codeHash], states, BigInt(tx.additionalData.contractGas || 0), stateDB, block, tx, contractInfo, enableLogging);

const [ newState, newStorage ] = await jelscript(
code[states[tx.recipient].codeHash],
states,
storage[tx.recipient] || {},
BigInt(tx.additionalData.contractGas || 0),
stateDB,
block,
tx,
contractInfo,
enableLogging
);

for (const account of Object.keys(newState)) {
states[account] = newState[account];
}

storage[tx.recipient] = newStorage;
}
}

// Reward

if (!existedAddresses.includes(block.coinbase) && !states[block.coinbase]) {
states[block.coinbase] = { balance: "0", codeHash: EMPTY_HASH, nonce: 0, storageRoot: EMPTY_HASH }
code[EMPTY_HASH] = "";
// console.log(tx.recipient, states[tx.recipient].balance, block);
}

if (existedAddresses.includes(block.coinbase) && !states[block.coinbase]) {
states[block.coinbase] = deserializeState(await stateDB.get(block.coinbase));
code[states[block.coinbase].codeHash] = await codeDB.get(states[block.coinbase].codeHash);

// Send reward to coinbase's address
if (!states[block.coinbase]) {
try { // If account exists but is not cached
states[block.coinbase] = deserializeState(await stateDB.get(block.coinbase));
code[states[block.coinbase].codeHash] = await codeDB.get(states[block.coinbase].codeHash);
} catch (e) { // If account does not exist and is not cached
states[block.coinbase] = { balance: "0", codeHash: EMPTY_HASH, nonce: 0, storageRoot: EMPTY_HASH }
code[EMPTY_HASH] = "";
}
}

let gas = 0n;
Expand All @@ -222,7 +231,6 @@ class Block {
states[block.coinbase].balance = (BigInt(states[block.coinbase].balance) + BigInt(BLOCK_REWARD) + gas).toString();

// Finalize state and contract storage into DB

for (const address in storage) {
const storageDB = new Level("./log/accountStore/" + address);
const keys = Object.keys(storage[address]);
Expand Down Expand Up @@ -275,16 +283,6 @@ class Block {

return true;
}

static hasValidGasLimit(block) {
let totalGas = 0n;

for (const tx of block.transactions) {
totalGas += BigInt(tx.additionalData.contractGas || 0);
}

return totalGas <= BigInt(BLOCK_GAS_LIMIT);
}
}

module.exports = Block;
63 changes: 33 additions & 30 deletions src/core/runtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,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) {
async function jelscript(input, originalState = {}, originalStorage = {}, gas, stateDB, block, txInfo, contractInfo, enableLogging = false) {
// Prepare code, memory, state, storage placeholder
const instructions = input.trim().replace(/\t/g, "").split("\n").map(ins => ins.trim()).filter(ins => ins !== "");

Expand All @@ -20,15 +20,22 @@ async function jelscript(input, originalState = {}, gas, stateDB, block, txInfo,
let ptr = 0;


// Get contract state and storage
const storageDB = new Level("./log/accountStore/" + contractInfo.address);
// Get contract storage
if (Object.keys(originalStorage).length === 0) {
const storageDB = new Level("./log/accountStore/" + contractInfo.address);

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

await storageDB.close();
}

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


while (
Expand Down Expand Up @@ -241,18 +248,14 @@ async function jelscript(input, originalState = {}, gas, stateDB, block, txInfo,
case "balance": // Get balance from address
const address = getValue(args[1]).slice(2); // Get address

const existedAddresses = await stateDB.keys().all();

if (!existedAddresses.includes(address) && !state[address]) {
setMem(c, "0x0");
}

if (existedAddresses.includes(address) && !state[address]) {
setMem(c, "0x" + BigInt(deserializeState((await stateDB.get(address))).balance).toString(16));
}

if (!existedAddresses.includes(address) && state[address]) {
if (state[address]) {
setMem(c, "0x" + BigInt(state[address].balance).toString(16));
} else {
try {
setMem(c, "0x" + BigInt(deserializeState((await stateDB.get(address))).balance).toString(16));
} catch (e) {
setMem(c, "0x0");
}
}

break;
Expand All @@ -263,18 +266,20 @@ async function jelscript(input, originalState = {}, gas, stateDB, block, txInfo,
const balance = state[contractInfo.address].balance;

if (BigInt(balance) >= amount) {
if (!(await stateDB.keys().all()).includes(target) && !state[target]) {
state[target] = {
balance: amount.toString(),
codeHash: EMPTY_HASH,
nonce: 0,
storageRoot: EMPTY_HASH
}
} else {
if (!state[target]) {
if (!state[target]) {
try {
state[target] = deserializeState(await stateDB.get(target));
}

state[target].balance = BigInt(state[target].balance) + amount;
} catch (e) {
state[target] = {
balance: amount.toString(),
codeHash: EMPTY_HASH,
nonce: 0,
storageRoot: EMPTY_HASH
}
}
} else {
state[target].balance = BigInt(state[target].balance) + amount;
}

Expand Down Expand Up @@ -349,8 +354,6 @@ async function jelscript(input, originalState = {}, gas, stateDB, block, txInfo,
return storage[key] ? storage[key] : "0x0";
}

await storageDB.close();

return [state, storage];
}

Expand Down
3 changes: 1 addition & 2 deletions src/core/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ async function changeState(newBlock, stateDB, codeDB, enableLogging = false) { /
if (dataFromRecipient.codeHash !== EMPTY_HASH) {
const contractInfo = { address: tx.recipient };

const [ newState, newStorage ] = await jelscript(await codeDB.get(dataFromRecipient.codeHash), {}, BigInt(tx.additionalData.contractGas || 0), stateDB, newBlock, tx, contractInfo, enableLogging);
const [ newState, newStorage ] = await jelscript(await codeDB.get(dataFromRecipient.codeHash), {}, {}, BigInt(tx.additionalData.contractGas || 0), stateDB, newBlock, tx, contractInfo, enableLogging);

const storageDB = new Level("./log/accountStore/" + tx.recipient);
const keys = Object.keys(newStorage);
Expand All @@ -87,7 +87,6 @@ async function changeState(newBlock, stateDB, codeDB, enableLogging = false) { /
}

// Reward

let gas = 0n;

for (const tx of newBlock.transactions) { gas += BigInt(tx.gas) + BigInt(tx.additionalData.contractGas || 0) }
Expand Down
14 changes: 8 additions & 6 deletions src/core/transaction.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,13 +182,15 @@ class Transaction {
const txSenderAddress = SHA256(txSenderPubkey);

// If sender does not exist return false
if (!(await stateDB.keys().all()).includes(txSenderAddress)) return false;

// Fetch sender's state object
const dataFromSender = deserializeState(await stateDB.get(txSenderAddress));
try {
// Fetch sender's state object
const dataFromSender = deserializeState(await stateDB.get(txSenderAddress));

// If sender is a contract address, then it's not supposed to be used to send money, so return false if it is.
if (dataFromSender.codeHash !== EMPTY_HASH) return false;
// If sender is a contract address, then it's not supposed to be used to send money, so return false if it is.
if (dataFromSender.codeHash !== EMPTY_HASH) return false;
} catch (e) {
return false;
}

return true;

Expand Down
6 changes: 0 additions & 6 deletions src/core/txPool.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,6 @@ async function addTransaction(transaction, chainInfo, stateDB) {
const txSenderPubkey = Transaction.getPubKey(transaction);
const txSenderAddress = SHA256(txSenderPubkey);

if (!(await stateDB.keys().all()).includes(txSenderAddress)) {
console.log(`\x1b[31mERROR\x1b[0m [${(new Date()).toISOString()}] Failed to add one transaction to pool: Sender does not exist.`);

return;
}

// Check nonce
let maxNonce = deserializeState(await stateDB.get(txSenderAddress)).nonce;

Expand Down
Loading

0 comments on commit 7848ebe

Please sign in to comment.