hivesmartcontracts/libs/Block.js

190 lines
7.1 KiB
JavaScript

const SHA256 = require('crypto-js/sha256');
const enchex = require('crypto-js/enc-hex');
const { SmartContracts } = require('./SmartContracts');
const { Transaction } = require('../libs/Transaction');
const DB_PLUGIN_NAME = require('../plugins/Database.constants').PLUGIN_NAME;
const DB_PLUGIN_ACTIONS = require('../plugins/Database.constants').PLUGIN_ACTIONS;
class Block {
constructor(timestamp, refSteemBlockNumber, refSteemBlockId, prevRefSteemBlockId, transactions, previousBlockNumber, previousHash = '', previousDatabaseHash = '') {
this.blockNumber = previousBlockNumber + 1;
this.refSteemBlockNumber = refSteemBlockNumber;
this.refSteemBlockId = refSteemBlockId;
this.prevRefSteemBlockId = prevRefSteemBlockId;
this.previousHash = previousHash;
this.previousDatabaseHash = previousDatabaseHash;
this.timestamp = timestamp;
this.transactions = transactions;
this.virtualTransactions = [];
this.hash = this.calculateHash();
this.databaseHash = '';
this.merkleRoot = '';
this.signature = '';
}
// calculate the hash of the block
calculateHash() {
return SHA256(
this.previousHash
+ this.previousDatabaseHash
+ this.blockNumber.toString()
+ this.refSteemBlockNumber.toString()
+ this.refSteemBlockId
+ this.prevRefSteemBlockId
+ this.timestamp
+ JSON.stringify(this.transactions) // eslint-disable-line
)
.toString(enchex);
}
// calculate the Merkle root of the block ((#TA + #TB) + (#TC + #TD) )
calculateMerkleRoot(transactions) {
if (transactions.length <= 0) return '';
const tmpTransactions = transactions.slice(0, transactions.length);
const newTransactions = [];
const nbTransactions = tmpTransactions.length;
for (let index = 0; index < nbTransactions; index += 2) {
const left = tmpTransactions[index].hash;
const right = index + 1 < nbTransactions ? tmpTransactions[index + 1].hash : left;
const leftDbHash = tmpTransactions[index].databaseHash;
const rightDbHash = index + 1 < nbTransactions
? tmpTransactions[index + 1].databaseHash
: leftDbHash;
newTransactions.push({
hash: SHA256(left + right).toString(enchex),
databaseHash: SHA256(leftDbHash + rightDbHash).toString(enchex),
});
}
if (newTransactions.length === 1) {
return {
hash: newTransactions[0].hash,
databaseHash: newTransactions[0].databaseHash,
};
}
return this.calculateMerkleRoot(newTransactions);
}
// produce the block (deploy a smart contract or execute a smart contract)
async produceBlock(ipc, jsVMTimeout, activeSigningKey) {
const nbTransactions = this.transactions.length;
let currentDatabaseHash = this.previousDatabaseHash;
for (let i = 0; i < nbTransactions; i += 1) {
const transaction = this.transactions[i];
await this.processTransaction(ipc, jsVMTimeout, transaction, currentDatabaseHash); // eslint-disable-line
currentDatabaseHash = transaction.databaseHash;
}
// remove comment, comment_options and votes if not relevant
this.transactions = this.transactions.filter(value => value.contract !== 'comments' || value.logs === '{}');
// handle virtual transactions
const virtualTransactions = [];
// check the pending unstakings and undelegation
virtualTransactions.push(new Transaction(0, '', 'null', 'tokens', 'checkPendingUnstakes', ''));
virtualTransactions.push(new Transaction(0, '', 'null', 'tokens', 'checkPendingUndelegations', ''));
const nbVirtualTransactions = virtualTransactions.length;
for (let i = 0; i < nbVirtualTransactions; i += 1) {
const transaction = virtualTransactions[i];
transaction.refSteemBlockNumber = this.refSteemBlockNumber;
transaction.transactionId = `${this.refSteemBlockNumber}-${i}`;
await this.processTransaction(ipc, jsVMTimeout, transaction, currentDatabaseHash); // eslint-disable-line
currentDatabaseHash = transaction.databaseHash;
// if there are outputs in the virtual transaction we save the transaction into the block
// the "unknown error" errors are removed as they are related to a non existing action
if (transaction.logs !== '{}' && transaction.logs !== '{"errors":["unknown error"]}') {
this.virtualTransactions.push(transaction);
}
}
if (this.transactions.length > 0 || this.virtualTransactions.length > 0) {
this.hash = this.calculateHash();
// calculate the merkle root of the transactions' hashes and the transactions' database hashes
const finalTransactions = this.transactions.concat(this.virtualTransactions);
const merkleRoots = this.calculateMerkleRoot(finalTransactions);
this.merkleRoot = merkleRoots.hash;
this.databaseHash = merkleRoots.databaseHash;
const buffMR = Buffer.from(this.merkleRoot, 'hex');
this.signature = activeSigningKey.sign(buffMR).toString();
}
}
async processTransaction(ipc, jsVMTimeout, transaction, currentDatabaseHash) {
const {
sender,
contract,
action,
payload,
} = transaction;
let results = null;
let newCurrentDatabaseHash = currentDatabaseHash;
// init the database hash for that transactions
await ipc.send({ // eslint-disable-line
to: DB_PLUGIN_NAME,
action: DB_PLUGIN_ACTIONS.INIT_DATABASE_HASH,
payload: newCurrentDatabaseHash,
});
if (sender && contract && action) {
if (contract === 'contract' && (action === 'deploy' || action === 'update') && payload) {
const authorizedAccountContractDeployment = ['null', 'steemsc', 'steem-peg'];
if (authorizedAccountContractDeployment.includes(sender)) {
results = await SmartContracts.deploySmartContract( // eslint-disable-line
ipc, transaction, this.blockNumber, this.timestamp,
this.refSteemBlockId, this.prevRefSteemBlockId, jsVMTimeout,
);
} else {
results = { logs: { errors: ['the contract deployment is currently unavailable'] } };
}
} else if (contract === 'blockProduction' && payload) {
// results = await bp.processTransaction(transaction); // eslint-disable-line
results = { logs: { errors: ['blockProduction contract not available'] } };
} else {
results = await SmartContracts.executeSmartContract(// eslint-disable-line
ipc, transaction, this.blockNumber, this.timestamp,
this.refSteemBlockId, this.prevRefSteemBlockId, jsVMTimeout,
);
}
} else {
results = { logs: { errors: ['the parameters sender, contract and action are required'] } };
}
// get the database hash
const res = await ipc.send({ // eslint-disable-line
to: DB_PLUGIN_NAME,
action: DB_PLUGIN_ACTIONS.GET_DATABASE_HASH,
payload: {
},
});
newCurrentDatabaseHash = res.payload;
// console.log('transac logs', results.logs);
transaction.addLogs(results.logs);
transaction.executedCodeHash = results.executedCodeHash || ''; // eslint-disable-line
transaction.databaseHash = newCurrentDatabaseHash; // eslint-disable-line
transaction.calculateHash();
}
}
module.exports.Block = Block;