Skip to content

Commit

Permalink
Merge pull request #37 from nguyenphuminh/change-tx-model
Browse files Browse the repository at this point in the history
Change tx model
  • Loading branch information
nguyenphuminh authored Jul 26, 2022
2 parents 231820b + 152da64 commit 4d63a8d
Show file tree
Hide file tree
Showing 8 changed files with 95 additions and 45 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.14.5",
"version": "0.15.0",
"description": "Node for JeChain - an experimental smart contract blockchain network",
"main": "./index.js",
"scripts": {
Expand Down
18 changes: 10 additions & 8 deletions src/core/block.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"use strict";

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");
Expand Down Expand Up @@ -46,7 +45,7 @@ class Block {
// Their balance are changed based on "amount" and "gas" props in each transactions.

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

// If senders' address doesn't exist, return false
Expand All @@ -55,14 +54,17 @@ class Block {
let gas = 0, reward = 0, balances = {};

for (const transaction of block.transactions) {
if (transaction.sender !== MINT_PUBLIC_ADDRESS) {
if (!balances[transaction.sender]) {
const dataFromSender = await stateDB.get(transaction.sender);
const txSenderPubkey = Transaction.getPubKey(transaction);
const txSenderAddress = SHA256(txSenderPubkey);

if (txSenderPubkey !== MINT_PUBLIC_ADDRESS) {
if (!balances[txSenderAddress]) {
const dataFromSender = await stateDB.get(txSenderAddress);
const senderBalance = dataFromSender.balance;

balances[transaction.sender] = senderBalance - transaction.amount - transaction.gas - (transaction.additionalData.contractGas || 0);
balances[txSenderAddress] = senderBalance - transaction.amount - transaction.gas - (transaction.additionalData.contractGas || 0);
} else {
balances[transaction.sender] -= transaction.amount + transaction.gas + (transaction.additionalData.contractGas || 0);
balances[txSenderAddress] -= transaction.amount + transaction.gas + (transaction.additionalData.contractGas || 0);
}
gas += transaction.gas + (transaction.additionalData.contractGas || 0);
} else {
Expand All @@ -88,7 +90,7 @@ class Block {
return (
reward - gas === BLOCK_REWARD &&
everyTransactionIsValid &&
block.transactions.filter(transaction => transaction.sender === MINT_PUBLIC_ADDRESS).length === 1 &&
block.transactions.filter(transaction => Transaction.getPubKey(transaction) === MINT_PUBLIC_ADDRESS).length === 1 &&
Object.values(balances).every(balance => balance >= 0)
);
}
Expand Down
5 changes: 3 additions & 2 deletions src/core/genesis.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
const EC = require("elliptic").ec, ec = new EC("secp256k1");
const crypto = require("crypto"), SHA256 = message => crypto.createHash("sha256").update(message).digest("hex");

const Block = require("./block");
const Transaction = require("./transaction");
const { INITIAL_SUPPLY } = require("../config.json");
Expand All @@ -9,8 +11,7 @@ const MINT_PUBLIC_ADDRESS = MINT_KEY_PAIR.getPublic("hex");

function generateGenesisBlock() {
const firstMint = new Transaction(
MINT_PUBLIC_ADDRESS,
"04f91a1954d96068c26c860e5935c568c1a4ca757804e26716b27c95d152722c054e7a459bfd0b3ab22ef65a820cc93a9f316a9dd213d31fdf7a28621b43119b73",
SHA256("04f91a1954d96068c26c860e5935c568c1a4ca757804e26716b27c95d152722c054e7a459bfd0b3ab22ef65a820cc93a9f316a9dd213d31fdf7a28621b43119b73"),
INITIAL_SUPPLY,
0,
{},
Expand Down
9 changes: 5 additions & 4 deletions src/core/runtime.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
const { send } = require("process");
const Transaction = require("./transaction");

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

async function jelscript(input, gas, stateDB, block, txInfo, contractInfo, enableLogging) {
console.log("wtf");

const instructions = input.trim().replace(/\t/g, "").split("\n").map(ins => ins.trim()).filter(ins => ins !== "");

const memory = {};
Expand Down Expand Up @@ -225,7 +223,10 @@ async function jelscript(input, gas, stateDB, block, txInfo, contractInfo, enabl
break;

case "txsender": // Sender of transaction
setMem(args[0], txInfo.sender);
const txSenderPubkey = Transaction.getPubKey(txInfo);
const txSenderAddress = SHA256(txSenderPubkey);

setMem(args[0], txSenderAddress);

break;

Expand Down
22 changes: 14 additions & 8 deletions src/core/state.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
"use strict";

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 jelscript = require("./runtime");
const Transaction = require("./transaction");

const MINT_PRIVATE_ADDRESS = "0700a1ad28a20e5b2a517c00242d3e25a88d84bf54dce9e1733e6096e6d6495e";
const MINT_KEY_PAIR = ec.keyFromPrivate(MINT_PRIVATE_ADDRESS, "hex");
Expand All @@ -23,28 +24,33 @@ async function changeState(newBlock, stateDB, enableLogging = false) {
});
}


// Get sender's public key and address
const txSenderPubkey = Transaction.getPubKey(tx);
const txSenderAddress = SHA256(txSenderPubkey);

// If the address doesn't already exist in the chain state, we will create a new empty one.
if (!existedAddresses.includes(tx.sender)) {
await stateDB.put(tx.sender, {
if (!existedAddresses.includes(txSenderAddress)) {
await stateDB.put(txSenderAddress, {
balance: 0,
body: "",
timestamps: [],
storage: {}
});
} else if (typeof tx.additionalData.scBody === "string") {
const dataFromSender = await stateDB.get(tx.sender);
const dataFromSender = await stateDB.get(txSenderAddress);

if (dataFromSender.body === "") {
dataFromSender.body = tx.additionalData.scBody;

await stateDB.put(tx.sender, dataFromSender);
await stateDB.put(txSenderAddress, dataFromSender);
}
}

const dataFromSender = await stateDB.get(tx.sender);
const dataFromSender = await stateDB.get(txSenderAddress);
const dataFromRecipient = await stateDB.get(tx.recipient);

await stateDB.put(tx.sender, {
await stateDB.put(txSenderAddress, {
balance: dataFromSender.balance - tx.amount - tx.gas - (tx.additionalData.contractGas || 0),
body: dataFromSender.body,
timestamps: [...dataFromSender.timestamps, tx.timestamp],
Expand All @@ -59,7 +65,7 @@ async function changeState(newBlock, stateDB, enableLogging = false) {
});

if (
tx.sender !== MINT_PUBLIC_ADDRESS &&
txSenderPubkey !== MINT_PUBLIC_ADDRESS &&
typeof dataFromRecipient.body === "string" &&
dataFromRecipient.body !== ""
) {
Expand Down
46 changes: 36 additions & 10 deletions src/core/transaction.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use strict";

const { Level } = require('level');
const BN = require("bn.js");
const crypto = require("crypto"), SHA256 = message => crypto.createHash("sha256").update(message).digest("hex");
const EC = require("elliptic").ec, ec = new EC("secp256k1");

Expand All @@ -9,19 +9,17 @@ const MINT_KEY_PAIR = ec.keyFromPrivate(MINT_PRIVATE_ADDRESS, "hex");
const MINT_PUBLIC_ADDRESS = MINT_KEY_PAIR.getPublic("hex");

class Transaction {
constructor(sender = "", recipient = "", amount = 0, gas = 1, additionalData = {}, timestamp = Date.now()) {
this.sender = sender; // Sender's address (public key)
constructor(recipient = "", amount = 0, gas = 1, additionalData = {}, timestamp = Date.now()) {
this.recipient = recipient; // Recipient's address (public key)
this.amount = amount; // Amount to be sent
this.gas = gas; // Gas that transaction consumed + tip for miner
this.additionalData = additionalData; // Additional data that goes into the transaction
this.timestamp = timestamp; // Creation timestamp (doesn't matter if true or not, just for randomness)
this.signature = ""; // Transaction's signature, will be generated later
this.signature = {}; // Transaction's signature, will be generated later
}

static getHash(tx) {
return SHA256(
tx.sender +
tx.recipient +
tx.amount.toString() +
tx.gas.toString() +
Expand All @@ -31,15 +29,44 @@ class Transaction {
}

static sign(transaction, keyPair) {
transaction.signature = keyPair.sign(Transaction.getHash(transaction), "base64").toDER("hex");
const sigObj = keyPair.sign(Transaction.getHash(transaction));

transaction.signature = {
v: sigObj.recoveryParam.toString(16),
r: sigObj.r.toString(16),
s: sigObj.s.toString(16)
};
}

static getPubKey(tx) {
// Get transaction's body's hash and recover original signature object
const msgHash = Transaction.getHash(tx);

const sigObj = {
r: new BN(tx.signature.r, 16),
s: new BN(tx.signature.s, 16),
recoveryParam: parseInt(tx.signature.v, 16)
};

// Recover public key and get real address.
const txSenderPubkey = ec.recoverPubKey(
new BN(msgHash, 16).toString(10),
sigObj,
ec.getKeyRecoveryParam(msgHash, sigObj, ec.genKeyPair().getPublic())
);

return ec.keyFromPublic(txSenderPubkey).getPublic("hex");
}

static async isValid(tx, stateDB) {
const txSenderPubkey = Transaction.getPubKey(tx);
const txSenderAddress = SHA256(txSenderPubkey);

// If state of sender does not exist, then the transaction is 100% false
if (!(await stateDB.keys().all()).includes(tx.sender)) return false;
if (!(await stateDB.keys().all()).includes(txSenderAddress)) return false;

// Fetch sender's state object
const dataFromSender = await stateDB.get(tx.sender);
const dataFromSender = await stateDB.get(txSenderAddress);
// Get sender's balance and used timestamps
const senderBalance = dataFromSender.balance;
const usedTimestamps = dataFromSender.timestamps;
Expand All @@ -53,8 +80,7 @@ class Transaction {

return (
tx.amount >= 0 &&
((senderBalance >= tx.amount + tx.gas + (tx.additionalData.contractGas || 0) && tx.gas >= 1) || tx.sender === MINT_PUBLIC_ADDRESS) &&
ec.keyFromPublic(tx.sender, "hex").verify(Transaction.getHash(tx), tx.signature) &&
((senderBalance >= tx.amount + tx.gas + (tx.additionalData.contractGas || 0) && tx.gas >= 1) || txSenderPubkey === MINT_PUBLIC_ADDRESS) &&
!usedTimestamps.includes(tx.timestamp) &&
tx.timestamp <= Date.now()
)
Expand Down
17 changes: 12 additions & 5 deletions src/core/txPool.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,38 @@
const { Level } = require('level');
const crypto = require("crypto"), SHA256 = message => crypto.createHash("sha256").update(message).digest("hex");

const Transaction = require("./transaction");

async function addTransaction(transaction, txPool, stateDB) {
// Get public key and address from sender
const txSenderPubkey = Transaction.getPubKey(transaction);
const txSenderAddress = SHA256(txSenderPubkey);

// Transactions are added into "this.transactions", which is the transaction pool.
// To be added, transactions must be valid, and they are valid under these criterias:
// - They are valid based on Transaction.isValid
// - The balance of the sender is enough to make the transaction (based his transactions the pool).
// - Its timestamp are not already used.

if (!(await stateDB.keys().all()).includes(transaction.sender)) return;
if (!(await stateDB.keys().all()).includes(txSenderAddress)) return;

// Fetch sender's state object
const dataFromSender = await stateDB.get(transaction.sender);
const dataFromSender = await stateDB.get(txSenderAddress);
// Get sender's balance
let balance = dataFromSender.balance - transaction.amount - transaction.gas - (transaction.additionalData.contractGas || 0);

txPool.forEach(tx => {
if (tx.sender === transaction.sender) {
const _txSenderPubkey = Transaction.getPubKey(tx);
const _txSenderAddress = SHA256(_txSenderPubkey);

if (_txSenderAddress === txSenderAddress) {
balance -= tx.amount + tx.gas + (tx.additionalData.contractGas || 0);
}
});

if (
await Transaction.isValid(transaction, stateDB) &&
balance >= 0 &&
!txPool.filter(_tx => _tx.sender === transaction.sender).some(_tx => _tx.timestamp === transaction.timestamp)
!txPool.filter(_tx => SHA256(Transaction.getPubKey(_tx)) === txSenderAddress).some(_tx => _tx.timestamp === transaction.timestamp)
) {
txPool.push(transaction);
}
Expand Down
21 changes: 14 additions & 7 deletions src/node/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,9 @@ async function startServer(options) {

chainInfo.transactionPool = newTransactionPool;

sendMessage(produceMessage("TYPE_NEW_BLOCK", newBlock), opened);

console.log(`LOG :: Block #${newBlock.blockNumber} synced, state transisted.`);

sendMessage(produceMessage("TYPE_NEW_BLOCK", newBlock), opened);
}
}

Expand All @@ -159,6 +159,10 @@ async function startServer(options) {

const transaction = _message.data;

// Get public key and address from sender
const txSenderPubkey = Transaction.getPubKey(transaction);
const txSenderAddress = SHA256(txSenderPubkey);

// Transactions are added into "chainInfo.transactions", which is the transaction pool.
// To be added, transactions must be valid, and they are valid under these criterias:
// - They are valid based on Transaction.isValid
Expand All @@ -169,23 +173,26 @@ async function startServer(options) {

// This is pretty much the same as addTransaction, but we will send the transaction to other connected nodes if it's valid.

if (!(await stateDB.keys().all()).includes(transaction.sender)) break;
if (!(await stateDB.keys().all()).includes(txSenderAddress)) break;

const dataFromSender = await stateDB.get(transaction.sender); // Fetch sender's state object
const dataFromSender = await stateDB.get(txSenderAddress); // Fetch sender's state object
const senderBalance = dataFromSender.balance; // Get sender's balance

let balance = senderBalance - transaction.amount - transaction.gas - (transaction.additionalData.contractGas || 0);

chainInfo.transactionPool.forEach(tx => {
if (tx.sender === transaction.sender) {
const _txSenderPubkey = Transaction.getPubKey(tx);
const _txSenderAddress = SHA256(_txSenderPubkey);

if (_txSenderAddress === txSenderAddress) {
balance -= tx.amount + tx.gas + (transaction.additionalData.contractGas || 0);
}
});

if (
await Transaction.isValid(transaction, stateDB) &&
balance >= 0 &&
!chainInfo.transactionPool.filter(_tx => _tx.sender === transaction.sender).some(_tx => _tx.timestamp === transaction.timestamp)
!chainInfo.transactionPool.filter(_tx => SHA256(Transaction.getPubKey(_tx)) === txSenderAddress).some(_tx => _tx.timestamp === transaction.timestamp)
) {
console.log("LOG :: New transaction received.");

Expand Down Expand Up @@ -437,7 +444,7 @@ function mine(publicKey, ENABLE_LOGGING) {
chainInfo.transactionPool.forEach(transaction => { gas += transaction.gas + (transaction.additionalData.contractGas || 0) });

// Mint transaction for miner's reward.
const rewardTransaction = new Transaction(MINT_PUBLIC_ADDRESS, publicKey, BLOCK_REWARD + gas);
const rewardTransaction = new Transaction(SHA256(publicKey), BLOCK_REWARD + gas);
Transaction.sign(rewardTransaction, MINT_KEY_PAIR);

// Create a new block.
Expand Down

0 comments on commit 4d63a8d

Please sign in to comment.