Bootstrap Bootstrap
            {
   0xb20a534816befbdf9867f03817473eded9884fef772f2fdaf7af3444266729cd: {
      data: {
         blockHash: "0x005498770261f4434741d0cf1dfa974209ba73a8612d9137eb0491ecc252ff63",
         blockNumber: "1",
         transactionIndex: 0,
         hash: "0xb20a534816befbdf9867f03817473eded9884fef772f2fdaf7af3444266729cd",
         from: "0x0000000000000000000000000000000000000001",
         gasLimit: "1000000000000000000",
         gasPrice: "0",
         data: "0x",
         nonce: "0",
         to: "0xccf8ba457dcad7ee6a0361c96846a0f79744b113",
         value: "250000000000000000000",
         chainId: "3615",
         origin: "1651572503570",
         v: "",
         r: "",
         s: ""
      },
      hash: "0xb20a534816befbdf9867f03817473eded9884fef772f2fdaf7af3444266729cd",
      load: "() => {
const transactionFile = config.node.dataDir + '/transactions/' + this.hash + '.json';

if (! fs.existsSync(transactionFile)) {
console.warn(utils.getDate(), "[BLOCKCHAIN]", 'IMPORT Transaction not found', this.hash);
throw new Error('IMPORT_TRANSACTION_NOT_FOUND_DATABASE_CORRUPTED');
}

const transactionJson = fs.readFileSync(transactionFile);
transactionData = JSON.parse(transactionJson, utils.bnJsonParser);

this.data = (typeof transactionData == 'TransactionData') ? transactionData : new TransactionData(transactionData);

return true;
}"
, save: "() => {
const transactionFile = config.node.dataDir + '/transactions/' + this.hash + '.json';
fs.writeFileSync(transactionFile, JSON.stringify(this.data));
return true;
}"
, getReceipt: "() => {
return blockchain.getTransactionReceiptByHash(this.hash);
}"
, getBlock: "() => {
return blockchain.getBlockByHash(this.data.blockHash);
}"
, getBlockTransationPrev: "() => {
const block = this.getBlock();
const txPrevIdx = (this.data.transactionIndex > 0) ? (this.data.transactionIndex-1) : null;
const tx = (block && txPrevIdx in block.transactions) ? block.transactions[txPrevIdx] : null;
return tx;
}"
, getBlockTransationNext: "() => {
const block = this.getBlock();
const txNextIdx = (this.data.transactionIndex < block.transactions.length - 1) ? (this.data.transactionIndex+1) : null;
const tx = (block && txNextIdx in block.transactions) ? block.transactions[txNextIdx] : null;
return tx;
}"
, getAccountTransactionIndex: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = account.data.transactions.findIndex(transactionHash => blockchain.getTransactionByHash(transactionHash).hash == this.hash);
if (txIdx === -1) {
return null;
}
return txIdx;
}"
, getAccountTransationPrev: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = this.getAccountTransactionIndex();
if (txIdx === null) {
return null;
}
const txPrevIdx = (txIdx > 0) ? (txIdx-1) : null;
const transactionHash = (txPrevIdx in account.data.transactions) ? account.data.transactions[txPrevIdx] : null;
if (! transactionHash) {
return null;
}
return blockchain.getTransactionByHash(transactionHash);
}"
, getAccountTransationNext: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = this.getAccountTransactionIndex();
if (txIdx === null) {
return null;
}
const txNextIdx = (txIdx < account.data.transactions.length - 1) ? (txIdx+1) : null;
const transactionHash = (txNextIdx in account.data.transactions) ? account.data.transactions[txNextIdx] : null;
if (! transactionHash) {
return null;
}
return blockchain.getTransactionByHash(transactionHash);

}"
, formatRpc: "() => {
// https://github.com/ethers-io/ethers.js/blob/master/packages/abstract-provider/lib/index.d.ts

const to = (this.data.to && this.data.to.length == 42) ? this.data.to : null;

const _txData = {
blockHash: this.data.blockHash,
blockNumber: this.data.blockNumber.toNumber(),
transactionIndex: this.data.transactionIndex,
hash: this.data.hash,
from: this.data.from,
gasLimit: this.data.gasLimit.toHexString(),
gasPrice: this.data.gasPrice.toHexString(),
data: this.data.data,
nonce: this.data.nonce.toHexString(),
to,
value: this.data.value.toHexString(),
chainId: this.data.chainId.toNumber(),
//origin: this.data.origin.toHexString(),
};

return _txData;
}"
, getHashOLD: "() => {
const _txData = {
gasPrice: this.data.gasPrice,
gasLimit: this.data.gasLimit,
from: this.data.from,
to: this.data.to,
value: this.data.value,
data: this.data.data,
nonce: this.data.nonce,
};

const strvalue = JSON.stringify(utils.orderedObject(_txData));
const transactionHash = ethers.utils.id(strvalue);

return transactionHash;
}"
, getHash: "() => {
const _txData = {
gasPrice: this.data.gasPrice.toHexString(),
gasLimit: this.data.gasLimit.toHexString(),
from: this.data.from,
to: this.data.to,
value: this.data.value.toHexString(),
data: this.data.data,
nonce: this.data.nonce.toHexString(),
v: this.data.v ? ('0x' + this.data.v) : '',
r: this.data.r ? ('0x' + this.data.r) : '',
s: this.data.s ? ('0x' + this.data.s) : '',
};

const tx = new EthereumJS.Transaction(_txData);
const hashRaw = tx.hash();

const transactionHash = ethers.utils.defaultAbiCoder.decode(["uint256"], hashRaw)[0].toHexString();

return transactionHash;
}"
, execute: "(block, transactionIndex, logIndexOffset) => {
const _tx = this;

function _rejectTransaction(reason) {
// https://openethereum.github.io/Transactions-Queue
reason = reason || 'unknown';
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] REJECTED PROCESSING TRANSACTION', _tx.data.hash, ' (Reason:', reason + ')');
const success = blockchain.removeTransactionsFromMempool([_tx.hash], true, true);
if (success) {
saveMempool();
}
throw new Error(reason); // on rejete definitivement la tx (elle ne sera pas minée ... et donc aucun frais de gas ne sera compté)
// et du coup on rejete le block
}

function _checkGas(step) {
execDuration = performance.now()/1000 - execTsStart; //parseFloat(process.hrtime(execTsStart).join('.'));
gasUsed = toBn(Math.round(execDuration * cpuPricePerSecond)); // TODO: prendre en compte le storage utilisé dans le calcul du gasUsed
txCost = gasUsed.mul(_tx.data.gasPrice);

if (step == 'afterTransfer') {
//debugger;
}

if (status) {
gasUsedEffective = gasUsed;
txCostEffective = gasUsedEffective.mul(_tx.data.gasPrice);

if (gasUsed.gt(_tx.data.gasLimit)) {
status = 0;
error = new Error('INSUFFICIENT_GAS');

gasUsedEffective = _tx.data.gasLimit;
txCostEffective = txCostLimit;

// emit event
const debugError = {
message: 'INSUFFICIENT_GAS',
step,
gasPrice: _tx.data.gasPrice,
gasLimit: _tx.data.gasLimit,
gasUsed,
gasUsedEffective,
txCostLimit,
txCost,
txCostEffective,
execDuration,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, _tx.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

}
}


//const block = this;
const txLogs = [];
let vmStorage = {};

let gasUsed = toBn(0);
let gasUsedEffective = toBn(0);
let status = 1;
let error = null;
let createdContractAddress = null;
let amountTransfered = toBn(0);

const contractAddress = this.data.to;
let storageBackup;
let balanceBackup;
let contractExecuted = false;
let contractDeployed = false;

const execTsStart = performance.now()/1000; //process.hrtime(); //Date.now();
let execDuration = 0;

const txCostLimit = this.data.gasLimit.mul(this.data.gasPrice);
let txCost = toBn(0);
let txCostEffective = toBn(0);


const accountFrom = blockchain.getAccount(this.data.from);
if (! accountFrom) {
_rejectTransaction('TRANSACTION_REJECTED_EMPTY_ACCOUNT');
}
const accountFromBalance = accountFrom.data.balance;


if (accountFrom.address != mintAddress) {

// verif nonce
const expectedNonce = toBn(accountFrom.data.transactions.length); // le nonce attendu est egal au nombre de transactions passées de l'account
if (! this.data.nonce.eq(expectedNonce)) {
_rejectTransaction('TRANSACTION_REJECTED_INCORRECT_NONCE');
}

// verif balance pour gas + transfer
if (this.data.value && this.data.value.gt(0)) {
if (accountFromBalance.lt(this.data.value.add(txCostLimit))) {
// l'adresse n'a pas suffisamment de fond pour executer la transaction (selon gasLimit)
// TODO: faut-il rejeter la tx ? ou la laisser passer (en status=0) et lui vider sa balance (si 100% du gas est utilisé) ?
//return _rejectTransaction('TRANSACTION_FAILED_INSUFFICIENT_ETH_BALANCE');

_rejectTransaction('TRANSACTION_REJECTED_INSUFFICIENT_ETH_BALANCE');
}
}
}



_checkGas('init');


if (status) {
if (this.data.value && this.data.value.gt(0) && contractAddress && contractAddress.length > 2) {
// Transfer la valeur demandée (simple ETH transaction)
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(this.data, block);
txLogs.push( ...transferLogs );

// si recipient == contract => on transfer quand meme dès maintenant => TODO: a revoir (le transferEth declenche un appel à receive ... avant meme de savoir si unne methode payable doit etre appelée. Probleme si receive provoque un revert => la fonction payable ne sera pas appelée)
// 1) si methode payable appellée, on l'execute
// 2) si methode non payable => on appelle receive juste avant
// 3) si methode inconnue => revert


if (transferResult && transferReverted) {
// reverted (after transfer)
amountTransfered = this.data.value;

status = 0;
error = new Error('TRANSFER_REVERTED');

} else if (transferResult) {
// success
amountTransfered = this.data.value;

} else {
// transfer failed
status = 0;
error = new Error('TRANSFER_FAILED');
}

}

_checkGas('afterTransfer');
}

const gasUsedBeforeContractExecution = gasUsed;

if (status && this.data.data && this.data.data != '0x') {
// Traitement du champ data de la transaction (smart contract call)

if (!contractAddress || contractAddress == '0x') {
// contract creation

contractDeployed = true;

let contractExecResult;
try {
contractExecResult = this.transactionDeployContract(block);
const creationLogs = contractExecResult.logs;
vmStorage = contractExecResult.storage;

if (creationLogs.length) {
txLogs.push( ...creationLogs );
}

if (! contractExecResult.result) {
// Deploy Failed
status = 0;
error = new Error('CONTRACT_CREATION_FAILED');

// emit event
const debugError = {
message: 'CONTRACT_CREATION_FAILED',
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);

} else {
// Deploy Success
createdContractAddress = contractExecResult.result.toLowerCase();

if (this.data.value.gt(0)) {
// Transfer tx value to contract
const _txData = {
...this.data,
to: createdContractAddress,
};
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(_txData, block);
txLogs.push( ...transferLogs );

if (transferResult) {
amountTransfered = this.data.value;

} else {
status = 0;
error = new Error('CONTRACT_CREATION_TRANSFER_FAILED');
}
}

// TODO: emit event CONTRACT_CREATION_SUCCESS ?
}



} catch (e) {
//console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] Cannot execute transaction');
//throw e;
status = 0;
error = new Error('CONTRACT_CREATION_ERROR');

console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] CONTRACT_CREATION_ERROR with message', e.message)

// emit event
const debugError = {
message: 'CONTRACT_CREATION_ERROR',
error: e.message,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

_checkGas('afterContractCreation');

} else {
// contract execution

const contractAccount = blockchain.getAccount(contractAddress);

if (! contractAccount) {
status = 0;
error = new Error('CONTRACT_ACCOUNT_NOT_FOUND');

// emit event
const debugError = {
message: 'CONTRACT_ACCOUNT_NOT_FOUND',
address: contractAddress,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

if (status) {
storageBackup = JSON.stringify(contractAccount.data.storage);
balanceBackup = contractAccount.data.balance;
contractExecuted = true;

try {
const contractExecResult = this.executeTransactionContract(block);
const internalLogs = contractExecResult.logs;
vmStorage = contractExecResult.storage;

if (internalLogs.length) {
txLogs.push( ...internalLogs );
}

if (! contractExecResult.result) {
status = 0;
error = new Error('CONTRACT_EXEC_FAILED');

// emit event
const debugError = {
message: 'CONTRACT_EXEC_FAILED',
address: contractAddress,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

} catch (e) {
status = 0;
error = new Error('CONTRACT_EXEC_ERROR');

// emit event
const debugError = {
message: 'CONTRACT_EXEC_ERROR',
address: contractAddress,
error: e.message,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}
}

_checkGas('afterContractCall');
}

}

const gasUsedAfterContractExecution = gasUsed;
const gasUsedInContractExecution = gasUsedAfterContractExecution.sub(gasUsedBeforeContractExecution);
const contractExecutionCost = gasUsedInContractExecution.mul(_tx.data.gasPrice);


console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] execDuration =', (Math.round(execDuration * 1000000) / 1000000), ' seconds');



if (txCostEffective.gt(0)) {
// Gas payment
//const minerAccount = blockchain.getAccount(block.data.miner, true);
//const gasPaymentResult = minerAccount.transferFrom(accountFrom.address, txCostEffective);

const txDataGasPayment = {
from: accountFrom.address,
to: block.data.miner,
value: txCostEffective,
};
const [gasPaymentResult, gasPaymentLogs, transferReverted] = blockchain.transferEth(txDataGasPayment, block, true);
txLogs.push(...gasPaymentLogs);

if (gasPaymentResult) {
// gas payment success

} else {
// gas payment failed => reject transaction (and block)
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] Gas payment failed');
_rejectTransaction('GAS_PAYMENT_FAILED');
}

if (false) {
// DEBUG
const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();
const gasEffectivePercent = gasUsedEffective.mul(100).div(this.data.gasLimit).toNumber();
const costPercent = txCost.mul(100).div(txCostLimit).toNumber();
const costEffectivePercent = txCostEffective.mul(100).div(txCostLimit).toNumber();
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] txCostLimit =', ethers.utils.formatEther(txCostLimit));
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] txCostEffective =', ethers.utils.formatEther(txCostEffective));
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] gas % =', Math.round(gasPercent), '% =', Math.round(gasEffectivePercent), '%');
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] cost % =', Math.round(costPercent), '%', Math.round(costEffectivePercent), '%');
}


}


if (accountFrom.address != mintAddress) {
const accountFromBalanceAtEnd = blockchain.getAccountBalance(accountFrom.address);

if (accountFromBalanceAtEnd.lt(0)){
_rejectTransaction('TRANSACTION_FAILED_INSUFFICIENT_ETH_BALANCE_AT_END');
}
}


if (! status) {
// Transaction Failed

// Revert transfer
if (amountTransfered.gt(0)) {
const txDataRevert = {
...this.data,
from: this.data.to,
to: this.data.from,
value: amountTransfered,
};
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(txDataRevert, block);

// TODO: gerer le revert des internal transfers
}

txLogs.forEach(log => {
log.removed = true;
});

// Revert contract storage
if (contractExecuted) {
const contractAccount = blockchain.getAccount(contractAddress);
contractAccount.data.storage = JSON.parse(storageBackup, utils.bnJsonParser);
contractAccount.data.balance = balanceBackup;

} else if (amountTransfered) {
// on check aussi amountTransfered car le contrat peut avoir été exécuté via receive => TODO: process a revoir
const contractAccount = blockchain.getAccount(contractAddress);
if (contractAccount && contractAccount.isContract()) {
//contractAccount.data.storage = JSON.parse(storageBackup, utils.bnJsonParser);
//contractAccount.data.balance = balanceBackup;
// TODO
}

}

// Revert deployed contract
if (contractDeployed) {
// nothing to do ?
}

const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] FAILED TRANSACTION ', this.data.hash, '| gas usage percent =', gasPercent, '%', ' | Error: ', error.message);

const realExecDuration = performance.now()/1000 - execTsStart; //parseFloat(process.hrtime(execTsStart).join('.'));
const realGasUsed = toBn(Math.round(realExecDuration * cpuPricePerSecond)); // TODO: prendre en compte le storage utilisé dans le calcul du gasUsed
const realGasPercent = realGasUsed.mul(100).div(this.data.gasLimit).toNumber();
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] FAILED TRANSACTION ', this.data.hash, '| real gas usage percent =', realGasPercent, '%', ' | Error: ', error.message);

} else {
// Transaction Success

// save contract storage
Object.entries(vmStorage).forEach((entry, idx) => {
const [contractAddress, contractStorage] = entry;

const contractAccount = blockchain.getAccount(contractAddress, true);
contractAccount.data.storage = contractStorage;
contractAccount.save();
});

// show result (debug)
const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();;
console.log(utils.getDate(), "[BLOCKCHAIN]", 'SUCCECSS TRANSACTION ', this.data.hash, '| gas usage percent =', gasPercent, '%');
}


// total gas utilisé dans le block
block.data.gasUsed = block.data.gasUsed.add(gasUsed);


// assign block data on logs
txLogs.forEach((txLog, idx) => {
txLog.data.transactionIndex = transactionIndex;
txLog.data.logIndex = logIndexOffset + idx;
txLog.data.transactionHash = this.data.hash;
txLog.data.blockHash = block.hash;
txLog.data.blockNumber = block.data.number;
});


const receiptData = {
transactionHash: this.data.hash,
transactionIndex: transactionIndex,
blockNumber: block.data.number,
blockHash: block.hash,
//logsBloom: '0x0000',
contractAddress: createdContractAddress,
gasUsed,
cumulativeGasUsed: block.data.gasUsed,
effectiveGasPrice: this.data.gasPrice,
effectiveCost: this.data.gasPrice.mul(gasUsed),
logs: txLogs,
status,
}

const receipt = new Receipt(blockchain, receiptData);

return [receipt, txLogs];
}"
, executeTransactionContract: "(block) => {
let contractAddress = this.data.to;

const isNullRecipient = (! contractAddress || contractAddress.length <= 2);
if (isNullRecipient) {
throw new Error('CONTRACT_EXEC_ADDRESS_INVALID_BUG_1');
}

const contractAccount = blockchain.getAccount(contractAddress);

if (! contractAccount) {
throw new Error('CONTRACT_EXEC_ACCOUNT_NOT_FOUND');
}

// Execute existing contract method call
return blockchain.callContractMethodInVM(this.data, block);

}"
, transactionDeployContract: "(block) => {
let contractAddress = this.data.to;

let contractAccount;

if (contractAddress && contractAddress.length > 2) {
throw new Error('CONTRACT_DEPLOY_ADDRESS_INVALID_BUG_2');
}


// CONTRACT CREATION

if (! this.data.data || this.data.data.length <= 2) {
throw new Error('CONTRACT_DEPLOY_INVALID_DATA_EMPTY_CODE');
}


// build contract address
contractAddress = ethers.utils.getContractAddress(this.data).toLocaleLowerCase();

// parse contract source code & constructor parameters
const codes = ethers.utils.defaultAbiCoder.decode(["string", "string"], this.data.data);
if (! codes) {
throw new Error('CONTRACT_DEPLOY_CODE_PARSING_FAILED');
}

const sourceCode = codes[0];
const constructorParams = codes[1];
const constructorRawParams = ethers.utils.defaultAbiCoder.encode(["string"], [constructorParams]);
const codeHash = ethers.utils.id(sourceCode);
const codeAbi = null; // l'abi sera généré une fois que le contrat aura été instancié
const storageHash = ethers.utils.id('{}');

// create new account
const accountData = {
address: contractAddress,
code: sourceCode,
codeHash,
codeAbi,
storage: {},
storageHash,
};
contractAccount = blockchain.createAccount(contractAddress, accountData);


// instanciate contract + call constructor + recuperer storage
const txData = {
from: this.data.from,
to: contractAddress,
data: constructorRawParams,
value: this.data.value.toHexString(),
};
const callResult = blockchain.deployContractInVM(txData, block);
const creationLogs = callResult.logs;
const vmStorage = callResult.storage;


// emit event
const eventParams = [
contractAddress,
];
const _txData = {
...this.data,
to: blockchain.getNullAddress(),
}
const deployLog = new TxLog(blockchain, null, _txData, 'ContractCreated', eventParams, deployAbi);
creationLogs.push(deployLog);


return {
result: contractAddress,
logs: creationLogs,
storage: vmStorage,
};
}"
, timestamp: "1651572503759", status: 1, gasUsed: "28281" }, 0x1964b5ae0f32659b2cc50f7aa8e2f2d7cb213197adb64008206f1b32ba49aa2f: { data: { blockHash: "0x005498770261f4434741d0cf1dfa974209ba73a8612d9137eb0491ecc252ff63", blockNumber: "1", transactionIndex: 1, hash: "0x1964b5ae0f32659b2cc50f7aa8e2f2d7cb213197adb64008206f1b32ba49aa2f", from: "0x131a190fa22cb867abf58193bf9e7640e9fce682", gasLimit: "1073150", gasPrice: "1000000000", data: "0x000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000028c000000000000000000000000000000000000000000000000000000000000028532f2a0a4e616d653a205772617070656420424e42203d3e2057726170706564204b41524d410a56657273696f6e3a20302e302e330a536f757263653a0a202020202d2057424e423a2068747470733a2f2f6769746875622e636f6d2f70616e63616b65737761702f70616e63616b652d6661726d2f626c6f622f6d61737465722f636f6e7472616374732f6c6962732f57424e422e736f6c0a4465706c6f793a2068747470733a2f2f6273637363616e2e636f6d2f616464726573732f3078626234436442394342643336423031624431634261454246324465303864393137336263303935630a2a2f0a0a0a636c617373204f776e61626c65207b0a202020206f776e6572203d2027307830303030303030303030303030303030303030303030303030303030303030303030303030303030273b0a0a202020204f776e6572736869705472616e73666572726564286f6c644f776e65722c206e65774f776e657229207b7d202f2f206576656e740a202020200a0a20202020636f6e7374727563746f722829207b0a2020202020202020746869732e6f776e6572203d206d73672e73656e6465723b0a202020207d0a0a202020206765744f776e65722829207b0a202020202020202072657475726e205b746869732e6f776e65725d3b0a202020207d0a202020200a202020207472616e736665724f776e657273686970286e65774f776e657229207b0a202020202020202072657175697265286e65774f776e657220213d20616464726573732830292c20224f776e61626c653a206e6577206f776e657220697320746865207a65726f206164647265737322293b0a2020202020202020746869732e5f7472616e736665724f776e657273686970286e65774f776e6572293b0a202020207d0a0a202020205f7472616e736665724f776e657273686970286e65774f776e657229207b0a20202020202020206e65774f776e6572203d206e65774f776e65722e746f4c6f7765724361736528293b0a2020202020202020636f6e7374206f6c644f776e6572203d20746869732e6f776e65723b0a2020202020202020746869732e6f776e6572203d206e65774f776e65723b0a2020202020202020656d697428274f776e6572736869705472616e73666572726564272c205b6f6c644f776e65722c206e65774f776e65725d293b0a202020207d0a0a202020205f6f6e6c794f776e6572286e65787429207b0a202020202020202072657175697265286d73672e73656e6465722e746f4c6f776572436173652829203d3d20746869732e6f776e65722c20276f6e6c79206f776e65722063616e2065786563757465207468697327293b0a20202020202020206e65787428293b0a202020207d0a7d0a0a2f2f204d6574686f64730a4f776e61626c652e70726f746f747970652e4f776e6572736869705472616e736665727265642e6576656e74203d20747275653b0a4f776e61626c652e70726f746f747970652e4f776e6572736869705472616e736665727265642e696e70757473203d205b276164647265737320696e6465786564272c20276164647265737320696e6465786564275d3b0a0a2f2f204d6574686f64730a4f776e61626c652e70726f746f747970652e6765744f776e65722e76696577203d20747275653b0a4f776e61626c652e70726f746f747970652e6765744f776e65722e6f757470757473203d205b2761646472657373275d3b0a0a4f776e61626c652e70726f746f747970652e7472616e736665724f776e6572736869702e696e70757473203d205b2761646472657373275d3b0a4f776e61626c652e70726f746f747970652e7472616e736665724f776e6572736869702e6d6f64696669657273203d205b275f6f6e6c794f776e6572275d3b0a0a4f776e61626c652e70726f746f747970652e5f7472616e736665724f776e6572736869702e696e7465726e616c203d20747275653b0a0a4f776e61626c652e70726f746f747970652e5f6f6e6c794f776e65722e696e7465726e616c203d20747275653b0a0a0a0a0a636c617373204552433230546f6b656e20657874656e6473204f776e61626c65207b0a202020206e616d65203d2027455243323020546f6b656e273b0a2020202073796d626f6c203d20274552433230273b0a20202020646563696d616c73203d2031383b0a20202020746f74616c537570706c79203d206e756d62657228273027293b0a0a20202020616c6c6f776564203d207b7d3b0a2020202062616c616e636573203d207b7d3b0a0a20202020737570706f72746564496e7465726661636573203d207b0a20202020202020202730783336333732623037273a20747275652c202f2f2045524332300a20202020202020202730783036666464653033273a20747275652c202f2f204552433230206e616d650a20202020202020202730783935643839623431273a20747275652c202f2f2045524332302073796d626f6c0a20202020202020202730783331336365353637273a20747275652c202f2f20455243323020646563696d616c730a202020207d3b0a0a0a202020205472616e736665722873656e6465722c20726563697069656e742c20616d6f756e7429207b7d202f2f206576656e740a20202020417070726f76616c286f776e65722c207370656e6465722c20616d6f756e7429207b7d202f2f206576656e740a202020204d696e74286d696e7465722c20616d6f756e7429207b7d202f2f206576656e740a202020204275726e286275726e65722c20616d6f756e7429207b7d202f2f206576656e740a0a0a2020202062616c616e63654f6628686f6c6465724164647265737329207b0a2020202020202020686f6c64657241646472657373203d20686f6c646572416464726573732e746f4c6f7765724361736528293b0a202020202020202076617220746f6b656e42616c616e636573203d20746869732e62616c616e636573207c7c207b7d3b0a0a202020202020202069662028686f6c6465724164647265737320696e20746f6b656e42616c616e63657329207b0a202020202020202020202020636f6e73742062616c616e6365203d20746f6b656e42616c616e6365735b686f6c646572416464726573735d3b0a20202020202020202020202072657475726e205b62616c616e63655d3b0a20202020202020207d0a202020202020202072657475726e205b6e756d6265722830295d3b0a202020207d0a0a202020207472616e7366657228726563697069656e742c20616d6f756e7429207b0a2020202020202020726563697069656e74203d20726563697069656e742e746f4c6f7765724361736528293b0a2020202020202020636f6e73742073656e646572203d206d73672e73656e6465723b0a202020202020202076617220746f6b656e42616c616e636573203d20746869732e62616c616e636573207c7c207b7d3b0a0a20202020202020206966202821202873656e64657220696e20746f6b656e42616c616e6365732929207b0a2020202020202020202020205f7468726f772827494e53554646494349454e545f544f4b454e5f42414c414e434527293b0a20202020202020207d0a202020202020202069662028746f6b656e42616c616e6365735b73656e6465725d2e6c7428616d6f756e742929207b0a2020202020202020202020205f7468726f772827494e53554646494349454e545f544f4b454e5f42414c414e434527293b0a20202020202020207d0a0a202020202020202069662028746869732e5f64656372656d656e745573657242616c616e63652e62696e642874686973292873656e6465722c20616d6f756e742929207b0a202020202020202020202020746869732e5f696e6372656d656e745573657242616c616e63652e62696e6428746869732928726563697069656e742c20616d6f756e74290a20202020202020207d0a0a2020202020202020656d697428275472616e73666572272c205b73656e6465722c20726563697069656e742c20616d6f756e745d293b0a202020202020202072657475726e205b747275655d3b0a202020207d0a0a0a202020207472616e7366657246726f6d2873656e6465722c20726563697069656e742c20616d6f756e7429207b0a202020202020202073656e646572203d2073656e6465722e746f4c6f7765724361736528293b0a2020202020202020726563697069656e74203d20726563697069656e742e746f4c6f7765724361736528293b0a20202020202020200a202020202020202076617220746f6b656e42616c616e636573203d20746869732e62616c616e636573207c7c207b7d3b0a202020202020202076617220746f6b656e416c6c6f77616e636573203d20746869732e616c6c6f776564207c7c207b7d3b0a0a2020202020202020726571756972652873656e64657220696e20746f6b656e42616c616e6365732c20274d495353494e475f544f4b454e5f42414c414e434527293b0a202020202020202069662028746f6b656e42616c616e6365735b73656e6465725d2e6c7428616d6f756e742929207b0a2020202020202020202020205f7468726f772827494e53554646494349454e545f544f4b454e5f42414c414e434527293b0a20202020202020207d0a20202020202020200a2020202020202020726571756972652873656e646572203d3d206d73672e73656e646572207c7c202873656e64657220696e20746f6b656e416c6c6f77616e636573202626206d73672e73656e64657220696e20746f6b656e416c6c6f77616e6365735b73656e6465725d292c20224d495353494e475f414c4c4f57414e434522293b0a20202020202020206966202873656e64657220213d206d73672e73656e64657220262620746f6b656e416c6c6f77616e6365735b73656e6465725d5b6d73672e73656e6465725d2e6c7428616d6f756e742929207b0a2020202020202020202020205f7468726f772827494e53554646494349454e545f544f4b454e5f414c4c4f57414e434527293b0a20202020202020207d0a0a2020202020202020746f6b656e416c6c6f77616e6365735b73656e6465725d5b6d73672e73656e6465725d203d20746f6b656e416c6c6f77616e6365735b73656e6465725d5b6d73672e73656e6465725d2e73756228616d6f756e74293b0a0a202020202020202069662028746869732e5f64656372656d656e745573657242616c616e63652e62696e642874686973292873656e6465722c20616d6f756e742929207b0a202020202020202020202020746869732e5f696e6372656d656e745573657242616c616e63652e62696e6428746869732928726563697069656e742c20616d6f756e74290a20202020202020207d0a20202020202020200a0a2020202020202020656d697428275472616e73666572272c205b73656e6465722c20726563697069656e742c20616d6f756e745d293b0a202020202020202072657475726e205b747275655d3b0a202020207d0a0a0a20202020616c6c6f77616e6365286f776e65722c207370656e64657229207b0a20202020202020206f776e6572203d206f776e65722e746f4c6f7765724361736528293b0a20202020202020207370656e646572203d207370656e6465722e746f4c6f7765724361736528293b0a202020202020202076617220746f6b656e416c6c6f77616e636573203d20746869732e616c6c6f776564207c7c207b7d3b0a2020202020202020696620286f776e657220696e20746f6b656e416c6c6f77616e636573202626207370656e64657220696e20746f6b656e416c6c6f77616e6365735b6f776e65725d29207b0a20202020202020202020202072657475726e205b746f6b656e416c6c6f77616e6365735b6f776e65725d5b7370656e6465725d5d3b0a20202020202020207d0a202020202020202072657475726e205b6e756d6265722830295d3b0a202020207d0a202020200a20202020617070726f7665287370656e6465722c2076616c756529207b0a20202020202020207370656e646572203d207370656e6465722e746f4c6f7765724361736528293b0a202020202020202076617220746f6b656e416c6c6f77616e636573203d20746869732e616c6c6f776564207c7c207b7d3b0a2020202020202020696620282120286d73672e73656e64657220696e20746f6b656e416c6c6f77616e6365732929207b0a202020202020202020202020746f6b656e416c6c6f77616e6365735b6d73672e73656e6465725d203d207b7d3b0a20202020202020207d0a2020202020202020746f6b656e416c6c6f77616e6365735b6d73672e73656e6465725d5b7370656e6465725d203d2076616c75653b0a2020202020202020656d69742827417070726f76616c272c205b6d73672e73656e6465722c207370656e6465722c2076616c75655d293b0a202020202020202072657475726e205b747275655d3b0a202020207d0a0a202020200a202020206d696e742875736572416464726573732c20616d6f756e7429207b0a2020202020202020746869732e5f6d696e742875736572416464726573732c20616d6f756e74293b0a202020207d0a0a202020205f6d696e742875736572416464726573732c20616d6f756e7429207b0a20202020202020207573657241646472657373203d2075736572416464726573732e746f4c6f7765724361736528293b0a2020202020202020696620287573657241646472657373203d3d20616464726573732830292e6164647265737329207b0a2020202020202020202020205f7468726f77282745524332303a206d696e7420746f20746865207a65726f206164647265737327293b0a20202020202020207d0a20202020202020200a202020202020202069662028746869732e5f696e6372656d656e745573657242616c616e63652e62696e642874686973292875736572416464726573732c20616d6f756e742929207b0a202020202020202020202020746869732e746f74616c537570706c79203d20746869732e746f74616c537570706c792e61646428616d6f756e74293b0a20202020202020207d0a0a2020202020202020656d697428274d696e74272c205b75736572416464726573732c20616d6f756e745d293b0a202020207d0a0a202020200a202020206275726e2875736572416464726573732c20616d6f756e7429207b0a2020202020202020746869732e5f6275726e2875736572416464726573732c20616d6f756e74293b0a202020207d0a0a202020205f6275726e2875736572416464726573732c20616d6f756e7429207b0a20202020202020207573657241646472657373203d2075736572416464726573732e746f4c6f7765724361736528293b0a2020202020202020696620287573657241646472657373203d3d20616464726573732830292e6164647265737329207b0a2020202020202020202020205f7468726f77282745524332303a206275726e2066726f6d20746865207a65726f206164647265737327293b0a20202020202020207d0a20202020202020200a202020202020202069662028746869732e5f64656372656d656e745573657242616c616e63652e62696e642874686973292875736572416464726573732c20616d6f756e742929207b0a202020202020202020202020746869732e746f74616c537570706c79203d20746869732e746f74616c537570706c792e73756228616d6f756e74293b0a20202020202020207d0a0a2020202020202020656d697428274275726e272c205b75736572416464726573732c20616d6f756e745d293b0a202020207d0a0a0a20202020737570706f727473496e7465726661636528696e74657266616365494429207b0a2020202020202020636f6e7374205f696e7465726661636573203d20746869732e737570706f72746564496e7465726661636573207c7c207b7d3b0a202020202020202072657475726e205b0a2020202020202020202020202828696e74657266616365494420696e205f696e7465726661636573290a202020202020202020202020202020203f205f696e74657266616365735b696e7465726661636549445d0a202020202020202020202020202020203a2066616c7365290a20202020202020205d3b0a202020207d0a0a0a202020205f64656372656d656e745573657242616c616e636528686f6c646572416464726573732c20616d6f756e7429207b0a202020202020202076617220746f6b656e42616c616e636573203d20746869732e62616c616e636573207c7c207b7d3b0a0a202020202020202069662028212028686f6c6465724164647265737320696e20746f6b656e42616c616e6365732929207b0a2020202020202020202020205f7468726f772827494e53554646494349454e545f544f4b454e5f42414c414e434527293b0a20202020202020207d0a202020202020202069662028616d6f756e742e677428746f6b656e42616c616e6365735b686f6c646572416464726573735d2929207b0a2020202020202020202020205f7468726f772827494e53554646494349454e545f544f4b454e5f42414c414e434527293b0a20202020202020207d0a2020202020202020746f6b656e42616c616e6365735b686f6c646572416464726573735d203d20746f6b656e42616c616e6365735b686f6c646572416464726573735d2e73756228616d6f756e74293b0a202020202020202072657475726e20747275653b0a202020207d0a0a0a202020205f696e6372656d656e745573657242616c616e636528686f6c646572416464726573732c20616d6f756e7429207b0a202020202020202076617220746f6b656e42616c616e636573203d20746869732e62616c616e636573207c7c207b7d3b0a0a202020202020202069662028212028686f6c6465724164647265737320696e20746f6b656e42616c616e6365732929207b0a202020202020202020202020746f6b656e42616c616e6365735b686f6c646572416464726573735d203d206e756d6265722830293b0a20202020202020207d0a2020202020202020746f6b656e42616c616e6365735b686f6c646572416464726573735d203d20746f6b656e42616c616e6365735b686f6c646572416464726573735d2e61646428616d6f756e74293b0a202020202020202072657475726e20747275653b0a202020207d0a0a7d0a0a2f2f204576656e74730a4552433230546f6b656e2e70726f746f747970652e5472616e736665722e6576656e74203d20747275653b0a4552433230546f6b656e2e70726f746f747970652e5472616e736665722e696e70757473203d205b276164647265737320696e6465786564272c20276164647265737320696e6465786564272c202775696e74323536275d3b0a0a4552433230546f6b656e2e70726f746f747970652e417070726f76616c2e6576656e74203d20747275653b0a4552433230546f6b656e2e70726f746f747970652e417070726f76616c2e696e70757473203d205b276164647265737320696e6465786564272c20276164647265737320696e6465786564272c202775696e74323536275d3b0a0a4552433230546f6b656e2e70726f746f747970652e4d696e742e6576656e74203d20747275653b0a4552433230546f6b656e2e70726f746f747970652e4d696e742e696e70757473203d205b276164647265737320696e6465786564272c202775696e74323536275d3b0a0a4552433230546f6b656e2e70726f746f747970652e4275726e2e6576656e74203d20747275653b0a4552433230546f6b656e2e70726f746f747970652e4275726e2e696e70757473203d205b276164647265737320696e6465786564272c202775696e74323536275d3b0a0a2f2f204d6574686f64730a4552433230546f6b656e2e70726f746f747970652e62616c616e63654f662e76696577203d20747275653b0a4552433230546f6b656e2e70726f746f747970652e62616c616e63654f662e696e70757473203d205b2761646472657373275d3b0a4552433230546f6b656e2e70726f746f747970652e62616c616e63654f662e6f757470757473203d205b2775696e74323536275d3b0a0a4552433230546f6b656e2e70726f746f747970652e7472616e736665722e696e70757473203d205b2761646472657373272c202775696e74323536275d3b0a4552433230546f6b656e2e70726f746f747970652e7472616e736665722e6f757470757473203d205b27626f6f6c275d3b0a0a4552433230546f6b656e2e70726f746f747970652e7472616e7366657246726f6d2e696e70757473203d205b2761646472657373272c202761646472657373272c202775696e74323536275d3b0a4552433230546f6b656e2e70726f746f747970652e7472616e7366657246726f6d2e6f757470757473203d205b27626f6f6c275d3b0a0a4552433230546f6b656e2e70726f746f747970652e616c6c6f77616e63652e76696577203d20747275653b0a4552433230546f6b656e2e70726f746f747970652e616c6c6f77616e63652e696e70757473203d205b2761646472657373272c202761646472657373275d3b0a4552433230546f6b656e2e70726f746f747970652e616c6c6f77616e63652e6f757470757473203d205b2775696e74323536275d3b0a0a4552433230546f6b656e2e70726f746f747970652e617070726f76652e696e70757473203d205b2761646472657373272c202775696e74323536275d3b0a4552433230546f6b656e2e70726f746f747970652e617070726f76652e6f757470757473203d205b27626f6f6c275d3b0a0a4552433230546f6b656e2e70726f746f747970652e6d696e742e696e70757473203d205b2761646472657373272c202775696e74323536275d3b0a4552433230546f6b656e2e70726f746f747970652e6d696e742e6d6f64696669657273203d205b275f6f6e6c794f776e6572275d3b0a0a4552433230546f6b656e2e70726f746f747970652e5f6d696e742e696e7465726e616c203d20747275653b0a0a4552433230546f6b656e2e70726f746f747970652e6275726e2e696e70757473203d205b2761646472657373272c202775696e74323536275d3b0a4552433230546f6b656e2e70726f746f747970652e6275726e2e6d6f64696669657273203d205b275f6f6e6c794f776e6572275d3b0a0a4552433230546f6b656e2e70726f746f747970652e5f6275726e2e696e7465726e616c203d20747275653b0a0a4552433230546f6b656e2e70726f746f747970652e737570706f727473496e746572666163652e76696577203d20747275653b0a4552433230546f6b656e2e70726f746f747970652e737570706f727473496e746572666163652e696e70757473203d205b27627974657334275d3b0a4552433230546f6b656e2e70726f746f747970652e737570706f727473496e746572666163652e6f757470757473203d205b27626f6f6c275d3b0a0a4552433230546f6b656e2e70726f746f747970652e5f64656372656d656e745573657242616c616e63652e696e7465726e616c203d20747275653b0a0a4552433230546f6b656e2e70726f746f747970652e5f696e6372656d656e745573657242616c616e63652e696e7465726e616c203d20747275653b0a0a0a0a0a0a0a636c61737320574b41524d4120657874656e6473204552433230546f6b656e207b0a202020206e616d65203d202757726170706564204b41524d41273b0a2020202073796d626f6c203d2027574b41524d41273b0a202020200a202020200a202020206465706f7369742829207b0a2020202020202020696620282120286d73672e73656e64657220696e20746869732e62616c616e6365732929207b0a202020202020202020202020746869732e62616c616e6365735b6d73672e73656e6465725d203d206e756d6265722830293b0a20202020202020207d0a0a2020202020202020746869732e62616c616e6365735b6d73672e73656e6465725d203d20746869732e62616c616e6365735b6d73672e73656e6465725d2e616464286d73672e76616c7565293b0a2020202020202020746869732e746f74616c537570706c79203d20746869732e746f74616c537570706c792e616464286d73672e76616c7565293b0a0a20202020202020207265717569726528746869732e746f74616c537570706c792e657128616464726573732874686973292e62616c616e6365292c2027544f54414c5f535550504c595f4d49534d4154434827293b0a2020202020202020746869732e647261696e28293b0a2020202020202020656d697428274465706f736974272c205b6d73672e73656e6465722c206d73672e76616c75655d293b0a202020207d0a0a0a202020204465706f7369742873656e6465722c2076616c756529207b7d202f2f206576656e740a0a0a2020202077697468647261772877616429207b0a202020202020202072657175697265286d73672e73656e64657220696e20746869732e62616c616e636573202626202120746869732e62616c616e6365735b6d73672e73656e6465725d2e6c742877616429293b0a2020202020202020746869732e647261696e28293b0a0a2020202020202020746869732e62616c616e6365735b6d73672e73656e6465725d203d20746869732e62616c616e6365735b6d73672e73656e6465725d2e73756228776164293b0a2020202020202020746869732e746f74616c537570706c79203d20746869732e746f74616c537570706c792e73756228776164293b0a0a202020202020202061646472657373286d73672e73656e646572292e7472616e7366657228776164293b0a0a20202020202020207265717569726528746869732e746f74616c537570706c792e657128616464726573732874686973292e62616c616e6365292c2027544f54414c5f535550504c595f4d49534d4154434827293b0a2020202020202020656d697428275769746864726177616c272c205b6d73672e73656e6465722c207761645d293b0a202020207d0a0a0a202020205769746864726177616c2873656e6465722c2076616c756529207b7d202f2f206576656e740a202020200a0a20202020647261696e2829207b0a202020202020202069662028616464726573732874686973292e62616c616e63652e677428746869732e746f74616c537570706c792929207b0a202020202020202020202020636f6e7374206f76657242616c616e6365203d20616464726573732874686973292e62616c616e63652e73756228746869732e746f74616c537570706c79293b0a2020202020202020202020206164647265737328746869732e6f776e6572292e7472616e73666572286f76657242616c616e6365293b0a202020202020202020202020656d69742827447261696e272c205b6d73672e73656e6465722c206f76657242616c616e63655d293b0a20202020202020207d0a202020207d0a0a0a20202020447261696e2873656e6465722c2076616c756529207b7d202f2f206576656e740a0a7d0a0a0a2f2f204576656e74730a574b41524d412e70726f746f747970652e4465706f7369742e6576656e74203d20747275653b0a574b41524d412e70726f746f747970652e4465706f7369742e696e70757473203d205b276164647265737320696e6465786564272c202775696e74323536275d3b0a0a574b41524d412e70726f746f747970652e5769746864726177616c2e6576656e74203d20747275653b0a574b41524d412e70726f746f747970652e5769746864726177616c2e696e70757473203d205b276164647265737320696e6465786564272c202775696e74323536275d3b0a0a574b41524d412e70726f746f747970652e447261696e2e6576656e74203d20747275653b0a574b41524d412e70726f746f747970652e447261696e2e696e70757473203d205b276164647265737320696e6465786564272c202775696e74323536275d3b0a0a2f2f204d6574686f64730a574b41524d412e70726f746f747970652e6465706f7369742e70617961626c65203d20747275653b0a0a574b41524d412e70726f746f747970652e77697468647261772e696e70757473203d205b2775696e74323536275d3b0a0a0a72657475726e20574b41524d413b0a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000025b5d000000000000000000000000000000000000000000000000000000000000", nonce: "0", to: "0x", value: "0", chainId: "3615", origin: "1651572502964", v: "1c62", r: "e3db003d21373351623e8b936590ad7a0bc1da1ce0bf6902d27d57ad16faecc7", s: "40d44b6306b2e3c59f2b18cd11143707bac54b876218af8defda8cdff57ea39d" }, hash: "0x1964b5ae0f32659b2cc50f7aa8e2f2d7cb213197adb64008206f1b32ba49aa2f", load: "() => {
const transactionFile = config.node.dataDir + '/transactions/' + this.hash + '.json';

if (! fs.existsSync(transactionFile)) {
console.warn(utils.getDate(), "[BLOCKCHAIN]", 'IMPORT Transaction not found', this.hash);
throw new Error('IMPORT_TRANSACTION_NOT_FOUND_DATABASE_CORRUPTED');
}

const transactionJson = fs.readFileSync(transactionFile);
transactionData = JSON.parse(transactionJson, utils.bnJsonParser);

this.data = (typeof transactionData == 'TransactionData') ? transactionData : new TransactionData(transactionData);

return true;
}"
, save: "() => {
const transactionFile = config.node.dataDir + '/transactions/' + this.hash + '.json';
fs.writeFileSync(transactionFile, JSON.stringify(this.data));
return true;
}"
, getReceipt: "() => {
return blockchain.getTransactionReceiptByHash(this.hash);
}"
, getBlock: "() => {
return blockchain.getBlockByHash(this.data.blockHash);
}"
, getBlockTransationPrev: "() => {
const block = this.getBlock();
const txPrevIdx = (this.data.transactionIndex > 0) ? (this.data.transactionIndex-1) : null;
const tx = (block && txPrevIdx in block.transactions) ? block.transactions[txPrevIdx] : null;
return tx;
}"
, getBlockTransationNext: "() => {
const block = this.getBlock();
const txNextIdx = (this.data.transactionIndex < block.transactions.length - 1) ? (this.data.transactionIndex+1) : null;
const tx = (block && txNextIdx in block.transactions) ? block.transactions[txNextIdx] : null;
return tx;
}"
, getAccountTransactionIndex: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = account.data.transactions.findIndex(transactionHash => blockchain.getTransactionByHash(transactionHash).hash == this.hash);
if (txIdx === -1) {
return null;
}
return txIdx;
}"
, getAccountTransationPrev: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = this.getAccountTransactionIndex();
if (txIdx === null) {
return null;
}
const txPrevIdx = (txIdx > 0) ? (txIdx-1) : null;
const transactionHash = (txPrevIdx in account.data.transactions) ? account.data.transactions[txPrevIdx] : null;
if (! transactionHash) {
return null;
}
return blockchain.getTransactionByHash(transactionHash);
}"
, getAccountTransationNext: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = this.getAccountTransactionIndex();
if (txIdx === null) {
return null;
}
const txNextIdx = (txIdx < account.data.transactions.length - 1) ? (txIdx+1) : null;
const transactionHash = (txNextIdx in account.data.transactions) ? account.data.transactions[txNextIdx] : null;
if (! transactionHash) {
return null;
}
return blockchain.getTransactionByHash(transactionHash);

}"
, formatRpc: "() => {
// https://github.com/ethers-io/ethers.js/blob/master/packages/abstract-provider/lib/index.d.ts

const to = (this.data.to && this.data.to.length == 42) ? this.data.to : null;

const _txData = {
blockHash: this.data.blockHash,
blockNumber: this.data.blockNumber.toNumber(),
transactionIndex: this.data.transactionIndex,
hash: this.data.hash,
from: this.data.from,
gasLimit: this.data.gasLimit.toHexString(),
gasPrice: this.data.gasPrice.toHexString(),
data: this.data.data,
nonce: this.data.nonce.toHexString(),
to,
value: this.data.value.toHexString(),
chainId: this.data.chainId.toNumber(),
//origin: this.data.origin.toHexString(),
};

return _txData;
}"
, getHashOLD: "() => {
const _txData = {
gasPrice: this.data.gasPrice,
gasLimit: this.data.gasLimit,
from: this.data.from,
to: this.data.to,
value: this.data.value,
data: this.data.data,
nonce: this.data.nonce,
};

const strvalue = JSON.stringify(utils.orderedObject(_txData));
const transactionHash = ethers.utils.id(strvalue);

return transactionHash;
}"
, getHash: "() => {
const _txData = {
gasPrice: this.data.gasPrice.toHexString(),
gasLimit: this.data.gasLimit.toHexString(),
from: this.data.from,
to: this.data.to,
value: this.data.value.toHexString(),
data: this.data.data,
nonce: this.data.nonce.toHexString(),
v: this.data.v ? ('0x' + this.data.v) : '',
r: this.data.r ? ('0x' + this.data.r) : '',
s: this.data.s ? ('0x' + this.data.s) : '',
};

const tx = new EthereumJS.Transaction(_txData);
const hashRaw = tx.hash();

const transactionHash = ethers.utils.defaultAbiCoder.decode(["uint256"], hashRaw)[0].toHexString();

return transactionHash;
}"
, execute: "(block, transactionIndex, logIndexOffset) => {
const _tx = this;

function _rejectTransaction(reason) {
// https://openethereum.github.io/Transactions-Queue
reason = reason || 'unknown';
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] REJECTED PROCESSING TRANSACTION', _tx.data.hash, ' (Reason:', reason + ')');
const success = blockchain.removeTransactionsFromMempool([_tx.hash], true, true);
if (success) {
saveMempool();
}
throw new Error(reason); // on rejete definitivement la tx (elle ne sera pas minée ... et donc aucun frais de gas ne sera compté)
// et du coup on rejete le block
}

function _checkGas(step) {
execDuration = performance.now()/1000 - execTsStart; //parseFloat(process.hrtime(execTsStart).join('.'));
gasUsed = toBn(Math.round(execDuration * cpuPricePerSecond)); // TODO: prendre en compte le storage utilisé dans le calcul du gasUsed
txCost = gasUsed.mul(_tx.data.gasPrice);

if (step == 'afterTransfer') {
//debugger;
}

if (status) {
gasUsedEffective = gasUsed;
txCostEffective = gasUsedEffective.mul(_tx.data.gasPrice);

if (gasUsed.gt(_tx.data.gasLimit)) {
status = 0;
error = new Error('INSUFFICIENT_GAS');

gasUsedEffective = _tx.data.gasLimit;
txCostEffective = txCostLimit;

// emit event
const debugError = {
message: 'INSUFFICIENT_GAS',
step,
gasPrice: _tx.data.gasPrice,
gasLimit: _tx.data.gasLimit,
gasUsed,
gasUsedEffective,
txCostLimit,
txCost,
txCostEffective,
execDuration,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, _tx.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

}
}


//const block = this;
const txLogs = [];
let vmStorage = {};

let gasUsed = toBn(0);
let gasUsedEffective = toBn(0);
let status = 1;
let error = null;
let createdContractAddress = null;
let amountTransfered = toBn(0);

const contractAddress = this.data.to;
let storageBackup;
let balanceBackup;
let contractExecuted = false;
let contractDeployed = false;

const execTsStart = performance.now()/1000; //process.hrtime(); //Date.now();
let execDuration = 0;

const txCostLimit = this.data.gasLimit.mul(this.data.gasPrice);
let txCost = toBn(0);
let txCostEffective = toBn(0);


const accountFrom = blockchain.getAccount(this.data.from);
if (! accountFrom) {
_rejectTransaction('TRANSACTION_REJECTED_EMPTY_ACCOUNT');
}
const accountFromBalance = accountFrom.data.balance;


if (accountFrom.address != mintAddress) {

// verif nonce
const expectedNonce = toBn(accountFrom.data.transactions.length); // le nonce attendu est egal au nombre de transactions passées de l'account
if (! this.data.nonce.eq(expectedNonce)) {
_rejectTransaction('TRANSACTION_REJECTED_INCORRECT_NONCE');
}

// verif balance pour gas + transfer
if (this.data.value && this.data.value.gt(0)) {
if (accountFromBalance.lt(this.data.value.add(txCostLimit))) {
// l'adresse n'a pas suffisamment de fond pour executer la transaction (selon gasLimit)
// TODO: faut-il rejeter la tx ? ou la laisser passer (en status=0) et lui vider sa balance (si 100% du gas est utilisé) ?
//return _rejectTransaction('TRANSACTION_FAILED_INSUFFICIENT_ETH_BALANCE');

_rejectTransaction('TRANSACTION_REJECTED_INSUFFICIENT_ETH_BALANCE');
}
}
}



_checkGas('init');


if (status) {
if (this.data.value && this.data.value.gt(0) && contractAddress && contractAddress.length > 2) {
// Transfer la valeur demandée (simple ETH transaction)
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(this.data, block);
txLogs.push( ...transferLogs );

// si recipient == contract => on transfer quand meme dès maintenant => TODO: a revoir (le transferEth declenche un appel à receive ... avant meme de savoir si unne methode payable doit etre appelée. Probleme si receive provoque un revert => la fonction payable ne sera pas appelée)
// 1) si methode payable appellée, on l'execute
// 2) si methode non payable => on appelle receive juste avant
// 3) si methode inconnue => revert


if (transferResult && transferReverted) {
// reverted (after transfer)
amountTransfered = this.data.value;

status = 0;
error = new Error('TRANSFER_REVERTED');

} else if (transferResult) {
// success
amountTransfered = this.data.value;

} else {
// transfer failed
status = 0;
error = new Error('TRANSFER_FAILED');
}

}

_checkGas('afterTransfer');
}

const gasUsedBeforeContractExecution = gasUsed;

if (status && this.data.data && this.data.data != '0x') {
// Traitement du champ data de la transaction (smart contract call)

if (!contractAddress || contractAddress == '0x') {
// contract creation

contractDeployed = true;

let contractExecResult;
try {
contractExecResult = this.transactionDeployContract(block);
const creationLogs = contractExecResult.logs;
vmStorage = contractExecResult.storage;

if (creationLogs.length) {
txLogs.push( ...creationLogs );
}

if (! contractExecResult.result) {
// Deploy Failed
status = 0;
error = new Error('CONTRACT_CREATION_FAILED');

// emit event
const debugError = {
message: 'CONTRACT_CREATION_FAILED',
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);

} else {
// Deploy Success
createdContractAddress = contractExecResult.result.toLowerCase();

if (this.data.value.gt(0)) {
// Transfer tx value to contract
const _txData = {
...this.data,
to: createdContractAddress,
};
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(_txData, block);
txLogs.push( ...transferLogs );

if (transferResult) {
amountTransfered = this.data.value;

} else {
status = 0;
error = new Error('CONTRACT_CREATION_TRANSFER_FAILED');
}
}

// TODO: emit event CONTRACT_CREATION_SUCCESS ?
}



} catch (e) {
//console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] Cannot execute transaction');
//throw e;
status = 0;
error = new Error('CONTRACT_CREATION_ERROR');

console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] CONTRACT_CREATION_ERROR with message', e.message)

// emit event
const debugError = {
message: 'CONTRACT_CREATION_ERROR',
error: e.message,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

_checkGas('afterContractCreation');

} else {
// contract execution

const contractAccount = blockchain.getAccount(contractAddress);

if (! contractAccount) {
status = 0;
error = new Error('CONTRACT_ACCOUNT_NOT_FOUND');

// emit event
const debugError = {
message: 'CONTRACT_ACCOUNT_NOT_FOUND',
address: contractAddress,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

if (status) {
storageBackup = JSON.stringify(contractAccount.data.storage);
balanceBackup = contractAccount.data.balance;
contractExecuted = true;

try {
const contractExecResult = this.executeTransactionContract(block);
const internalLogs = contractExecResult.logs;
vmStorage = contractExecResult.storage;

if (internalLogs.length) {
txLogs.push( ...internalLogs );
}

if (! contractExecResult.result) {
status = 0;
error = new Error('CONTRACT_EXEC_FAILED');

// emit event
const debugError = {
message: 'CONTRACT_EXEC_FAILED',
address: contractAddress,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

} catch (e) {
status = 0;
error = new Error('CONTRACT_EXEC_ERROR');

// emit event
const debugError = {
message: 'CONTRACT_EXEC_ERROR',
address: contractAddress,
error: e.message,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}
}

_checkGas('afterContractCall');
}

}

const gasUsedAfterContractExecution = gasUsed;
const gasUsedInContractExecution = gasUsedAfterContractExecution.sub(gasUsedBeforeContractExecution);
const contractExecutionCost = gasUsedInContractExecution.mul(_tx.data.gasPrice);


console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] execDuration =', (Math.round(execDuration * 1000000) / 1000000), ' seconds');



if (txCostEffective.gt(0)) {
// Gas payment
//const minerAccount = blockchain.getAccount(block.data.miner, true);
//const gasPaymentResult = minerAccount.transferFrom(accountFrom.address, txCostEffective);

const txDataGasPayment = {
from: accountFrom.address,
to: block.data.miner,
value: txCostEffective,
};
const [gasPaymentResult, gasPaymentLogs, transferReverted] = blockchain.transferEth(txDataGasPayment, block, true);
txLogs.push(...gasPaymentLogs);

if (gasPaymentResult) {
// gas payment success

} else {
// gas payment failed => reject transaction (and block)
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] Gas payment failed');
_rejectTransaction('GAS_PAYMENT_FAILED');
}

if (false) {
// DEBUG
const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();
const gasEffectivePercent = gasUsedEffective.mul(100).div(this.data.gasLimit).toNumber();
const costPercent = txCost.mul(100).div(txCostLimit).toNumber();
const costEffectivePercent = txCostEffective.mul(100).div(txCostLimit).toNumber();
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] txCostLimit =', ethers.utils.formatEther(txCostLimit));
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] txCostEffective =', ethers.utils.formatEther(txCostEffective));
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] gas % =', Math.round(gasPercent), '% =', Math.round(gasEffectivePercent), '%');
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] cost % =', Math.round(costPercent), '%', Math.round(costEffectivePercent), '%');
}


}


if (accountFrom.address != mintAddress) {
const accountFromBalanceAtEnd = blockchain.getAccountBalance(accountFrom.address);

if (accountFromBalanceAtEnd.lt(0)){
_rejectTransaction('TRANSACTION_FAILED_INSUFFICIENT_ETH_BALANCE_AT_END');
}
}


if (! status) {
// Transaction Failed

// Revert transfer
if (amountTransfered.gt(0)) {
const txDataRevert = {
...this.data,
from: this.data.to,
to: this.data.from,
value: amountTransfered,
};
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(txDataRevert, block);

// TODO: gerer le revert des internal transfers
}

txLogs.forEach(log => {
log.removed = true;
});

// Revert contract storage
if (contractExecuted) {
const contractAccount = blockchain.getAccount(contractAddress);
contractAccount.data.storage = JSON.parse(storageBackup, utils.bnJsonParser);
contractAccount.data.balance = balanceBackup;

} else if (amountTransfered) {
// on check aussi amountTransfered car le contrat peut avoir été exécuté via receive => TODO: process a revoir
const contractAccount = blockchain.getAccount(contractAddress);
if (contractAccount && contractAccount.isContract()) {
//contractAccount.data.storage = JSON.parse(storageBackup, utils.bnJsonParser);
//contractAccount.data.balance = balanceBackup;
// TODO
}

}

// Revert deployed contract
if (contractDeployed) {
// nothing to do ?
}

const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] FAILED TRANSACTION ', this.data.hash, '| gas usage percent =', gasPercent, '%', ' | Error: ', error.message);

const realExecDuration = performance.now()/1000 - execTsStart; //parseFloat(process.hrtime(execTsStart).join('.'));
const realGasUsed = toBn(Math.round(realExecDuration * cpuPricePerSecond)); // TODO: prendre en compte le storage utilisé dans le calcul du gasUsed
const realGasPercent = realGasUsed.mul(100).div(this.data.gasLimit).toNumber();
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] FAILED TRANSACTION ', this.data.hash, '| real gas usage percent =', realGasPercent, '%', ' | Error: ', error.message);

} else {
// Transaction Success

// save contract storage
Object.entries(vmStorage).forEach((entry, idx) => {
const [contractAddress, contractStorage] = entry;

const contractAccount = blockchain.getAccount(contractAddress, true);
contractAccount.data.storage = contractStorage;
contractAccount.save();
});

// show result (debug)
const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();;
console.log(utils.getDate(), "[BLOCKCHAIN]", 'SUCCECSS TRANSACTION ', this.data.hash, '| gas usage percent =', gasPercent, '%');
}


// total gas utilisé dans le block
block.data.gasUsed = block.data.gasUsed.add(gasUsed);


// assign block data on logs
txLogs.forEach((txLog, idx) => {
txLog.data.transactionIndex = transactionIndex;
txLog.data.logIndex = logIndexOffset + idx;
txLog.data.transactionHash = this.data.hash;
txLog.data.blockHash = block.hash;
txLog.data.blockNumber = block.data.number;
});


const receiptData = {
transactionHash: this.data.hash,
transactionIndex: transactionIndex,
blockNumber: block.data.number,
blockHash: block.hash,
//logsBloom: '0x0000',
contractAddress: createdContractAddress,
gasUsed,
cumulativeGasUsed: block.data.gasUsed,
effectiveGasPrice: this.data.gasPrice,
effectiveCost: this.data.gasPrice.mul(gasUsed),
logs: txLogs,
status,
}

const receipt = new Receipt(blockchain, receiptData);

return [receipt, txLogs];
}"
, executeTransactionContract: "(block) => {
let contractAddress = this.data.to;

const isNullRecipient = (! contractAddress || contractAddress.length <= 2);
if (isNullRecipient) {
throw new Error('CONTRACT_EXEC_ADDRESS_INVALID_BUG_1');
}

const contractAccount = blockchain.getAccount(contractAddress);

if (! contractAccount) {
throw new Error('CONTRACT_EXEC_ACCOUNT_NOT_FOUND');
}

// Execute existing contract method call
return blockchain.callContractMethodInVM(this.data, block);

}"
, transactionDeployContract: "(block) => {
let contractAddress = this.data.to;

let contractAccount;

if (contractAddress && contractAddress.length > 2) {
throw new Error('CONTRACT_DEPLOY_ADDRESS_INVALID_BUG_2');
}


// CONTRACT CREATION

if (! this.data.data || this.data.data.length <= 2) {
throw new Error('CONTRACT_DEPLOY_INVALID_DATA_EMPTY_CODE');
}


// build contract address
contractAddress = ethers.utils.getContractAddress(this.data).toLocaleLowerCase();

// parse contract source code & constructor parameters
const codes = ethers.utils.defaultAbiCoder.decode(["string", "string"], this.data.data);
if (! codes) {
throw new Error('CONTRACT_DEPLOY_CODE_PARSING_FAILED');
}

const sourceCode = codes[0];
const constructorParams = codes[1];
const constructorRawParams = ethers.utils.defaultAbiCoder.encode(["string"], [constructorParams]);
const codeHash = ethers.utils.id(sourceCode);
const codeAbi = null; // l'abi sera généré une fois que le contrat aura été instancié
const storageHash = ethers.utils.id('{}');

// create new account
const accountData = {
address: contractAddress,
code: sourceCode,
codeHash,
codeAbi,
storage: {},
storageHash,
};
contractAccount = blockchain.createAccount(contractAddress, accountData);


// instanciate contract + call constructor + recuperer storage
const txData = {
from: this.data.from,
to: contractAddress,
data: constructorRawParams,
value: this.data.value.toHexString(),
};
const callResult = blockchain.deployContractInVM(txData, block);
const creationLogs = callResult.logs;
const vmStorage = callResult.storage;


// emit event
const eventParams = [
contractAddress,
];
const _txData = {
...this.data,
to: blockchain.getNullAddress(),
}
const deployLog = new TxLog(blockchain, null, _txData, 'ContractCreated', eventParams, deployAbi);
creationLogs.push(deployLog);


return {
result: contractAddress,
logs: creationLogs,
storage: vmStorage,
};
}"
, timestamp: "1651572503759", status: 1, gasUsed: "43025" }, 0x04abb86e2b5e444838e6b3bd8ba622183c57a135c08f96fff703520d4d5a886b: { data: { blockHash: "0x00a4160dc73d5ae90b2375fe405b56005fa5693009c9c23d62b129f878aec822", blockNumber: "2", transactionIndex: 0, hash: "0x04abb86e2b5e444838e6b3bd8ba622183c57a135c08f96fff703520d4d5a886b", from: "0x0000000000000000000000000000000000000001", gasLimit: "1000000000000000000", gasPrice: "0", data: "0x", nonce: "1", to: "0xccf8ba457dcad7ee6a0361c96846a0f79744b113", value: "250000000000000000000", chainId: "3615", origin: "1651572508924", v: "", r: "", s: "" }, hash: "0x04abb86e2b5e444838e6b3bd8ba622183c57a135c08f96fff703520d4d5a886b", load: "() => {
const transactionFile = config.node.dataDir + '/transactions/' + this.hash + '.json';

if (! fs.existsSync(transactionFile)) {
console.warn(utils.getDate(), "[BLOCKCHAIN]", 'IMPORT Transaction not found', this.hash);
throw new Error('IMPORT_TRANSACTION_NOT_FOUND_DATABASE_CORRUPTED');
}

const transactionJson = fs.readFileSync(transactionFile);
transactionData = JSON.parse(transactionJson, utils.bnJsonParser);

this.data = (typeof transactionData == 'TransactionData') ? transactionData : new TransactionData(transactionData);

return true;
}"
, save: "() => {
const transactionFile = config.node.dataDir + '/transactions/' + this.hash + '.json';
fs.writeFileSync(transactionFile, JSON.stringify(this.data));
return true;
}"
, getReceipt: "() => {
return blockchain.getTransactionReceiptByHash(this.hash);
}"
, getBlock: "() => {
return blockchain.getBlockByHash(this.data.blockHash);
}"
, getBlockTransationPrev: "() => {
const block = this.getBlock();
const txPrevIdx = (this.data.transactionIndex > 0) ? (this.data.transactionIndex-1) : null;
const tx = (block && txPrevIdx in block.transactions) ? block.transactions[txPrevIdx] : null;
return tx;
}"
, getBlockTransationNext: "() => {
const block = this.getBlock();
const txNextIdx = (this.data.transactionIndex < block.transactions.length - 1) ? (this.data.transactionIndex+1) : null;
const tx = (block && txNextIdx in block.transactions) ? block.transactions[txNextIdx] : null;
return tx;
}"
, getAccountTransactionIndex: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = account.data.transactions.findIndex(transactionHash => blockchain.getTransactionByHash(transactionHash).hash == this.hash);
if (txIdx === -1) {
return null;
}
return txIdx;
}"
, getAccountTransationPrev: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = this.getAccountTransactionIndex();
if (txIdx === null) {
return null;
}
const txPrevIdx = (txIdx > 0) ? (txIdx-1) : null;
const transactionHash = (txPrevIdx in account.data.transactions) ? account.data.transactions[txPrevIdx] : null;
if (! transactionHash) {
return null;
}
return blockchain.getTransactionByHash(transactionHash);
}"
, getAccountTransationNext: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = this.getAccountTransactionIndex();
if (txIdx === null) {
return null;
}
const txNextIdx = (txIdx < account.data.transactions.length - 1) ? (txIdx+1) : null;
const transactionHash = (txNextIdx in account.data.transactions) ? account.data.transactions[txNextIdx] : null;
if (! transactionHash) {
return null;
}
return blockchain.getTransactionByHash(transactionHash);

}"
, formatRpc: "() => {
// https://github.com/ethers-io/ethers.js/blob/master/packages/abstract-provider/lib/index.d.ts

const to = (this.data.to && this.data.to.length == 42) ? this.data.to : null;

const _txData = {
blockHash: this.data.blockHash,
blockNumber: this.data.blockNumber.toNumber(),
transactionIndex: this.data.transactionIndex,
hash: this.data.hash,
from: this.data.from,
gasLimit: this.data.gasLimit.toHexString(),
gasPrice: this.data.gasPrice.toHexString(),
data: this.data.data,
nonce: this.data.nonce.toHexString(),
to,
value: this.data.value.toHexString(),
chainId: this.data.chainId.toNumber(),
//origin: this.data.origin.toHexString(),
};

return _txData;
}"
, getHashOLD: "() => {
const _txData = {
gasPrice: this.data.gasPrice,
gasLimit: this.data.gasLimit,
from: this.data.from,
to: this.data.to,
value: this.data.value,
data: this.data.data,
nonce: this.data.nonce,
};

const strvalue = JSON.stringify(utils.orderedObject(_txData));
const transactionHash = ethers.utils.id(strvalue);

return transactionHash;
}"
, getHash: "() => {
const _txData = {
gasPrice: this.data.gasPrice.toHexString(),
gasLimit: this.data.gasLimit.toHexString(),
from: this.data.from,
to: this.data.to,
value: this.data.value.toHexString(),
data: this.data.data,
nonce: this.data.nonce.toHexString(),
v: this.data.v ? ('0x' + this.data.v) : '',
r: this.data.r ? ('0x' + this.data.r) : '',
s: this.data.s ? ('0x' + this.data.s) : '',
};

const tx = new EthereumJS.Transaction(_txData);
const hashRaw = tx.hash();

const transactionHash = ethers.utils.defaultAbiCoder.decode(["uint256"], hashRaw)[0].toHexString();

return transactionHash;
}"
, execute: "(block, transactionIndex, logIndexOffset) => {
const _tx = this;

function _rejectTransaction(reason) {
// https://openethereum.github.io/Transactions-Queue
reason = reason || 'unknown';
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] REJECTED PROCESSING TRANSACTION', _tx.data.hash, ' (Reason:', reason + ')');
const success = blockchain.removeTransactionsFromMempool([_tx.hash], true, true);
if (success) {
saveMempool();
}
throw new Error(reason); // on rejete definitivement la tx (elle ne sera pas minée ... et donc aucun frais de gas ne sera compté)
// et du coup on rejete le block
}

function _checkGas(step) {
execDuration = performance.now()/1000 - execTsStart; //parseFloat(process.hrtime(execTsStart).join('.'));
gasUsed = toBn(Math.round(execDuration * cpuPricePerSecond)); // TODO: prendre en compte le storage utilisé dans le calcul du gasUsed
txCost = gasUsed.mul(_tx.data.gasPrice);

if (step == 'afterTransfer') {
//debugger;
}

if (status) {
gasUsedEffective = gasUsed;
txCostEffective = gasUsedEffective.mul(_tx.data.gasPrice);

if (gasUsed.gt(_tx.data.gasLimit)) {
status = 0;
error = new Error('INSUFFICIENT_GAS');

gasUsedEffective = _tx.data.gasLimit;
txCostEffective = txCostLimit;

// emit event
const debugError = {
message: 'INSUFFICIENT_GAS',
step,
gasPrice: _tx.data.gasPrice,
gasLimit: _tx.data.gasLimit,
gasUsed,
gasUsedEffective,
txCostLimit,
txCost,
txCostEffective,
execDuration,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, _tx.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

}
}


//const block = this;
const txLogs = [];
let vmStorage = {};

let gasUsed = toBn(0);
let gasUsedEffective = toBn(0);
let status = 1;
let error = null;
let createdContractAddress = null;
let amountTransfered = toBn(0);

const contractAddress = this.data.to;
let storageBackup;
let balanceBackup;
let contractExecuted = false;
let contractDeployed = false;

const execTsStart = performance.now()/1000; //process.hrtime(); //Date.now();
let execDuration = 0;

const txCostLimit = this.data.gasLimit.mul(this.data.gasPrice);
let txCost = toBn(0);
let txCostEffective = toBn(0);


const accountFrom = blockchain.getAccount(this.data.from);
if (! accountFrom) {
_rejectTransaction('TRANSACTION_REJECTED_EMPTY_ACCOUNT');
}
const accountFromBalance = accountFrom.data.balance;


if (accountFrom.address != mintAddress) {

// verif nonce
const expectedNonce = toBn(accountFrom.data.transactions.length); // le nonce attendu est egal au nombre de transactions passées de l'account
if (! this.data.nonce.eq(expectedNonce)) {
_rejectTransaction('TRANSACTION_REJECTED_INCORRECT_NONCE');
}

// verif balance pour gas + transfer
if (this.data.value && this.data.value.gt(0)) {
if (accountFromBalance.lt(this.data.value.add(txCostLimit))) {
// l'adresse n'a pas suffisamment de fond pour executer la transaction (selon gasLimit)
// TODO: faut-il rejeter la tx ? ou la laisser passer (en status=0) et lui vider sa balance (si 100% du gas est utilisé) ?
//return _rejectTransaction('TRANSACTION_FAILED_INSUFFICIENT_ETH_BALANCE');

_rejectTransaction('TRANSACTION_REJECTED_INSUFFICIENT_ETH_BALANCE');
}
}
}



_checkGas('init');


if (status) {
if (this.data.value && this.data.value.gt(0) && contractAddress && contractAddress.length > 2) {
// Transfer la valeur demandée (simple ETH transaction)
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(this.data, block);
txLogs.push( ...transferLogs );

// si recipient == contract => on transfer quand meme dès maintenant => TODO: a revoir (le transferEth declenche un appel à receive ... avant meme de savoir si unne methode payable doit etre appelée. Probleme si receive provoque un revert => la fonction payable ne sera pas appelée)
// 1) si methode payable appellée, on l'execute
// 2) si methode non payable => on appelle receive juste avant
// 3) si methode inconnue => revert


if (transferResult && transferReverted) {
// reverted (after transfer)
amountTransfered = this.data.value;

status = 0;
error = new Error('TRANSFER_REVERTED');

} else if (transferResult) {
// success
amountTransfered = this.data.value;

} else {
// transfer failed
status = 0;
error = new Error('TRANSFER_FAILED');
}

}

_checkGas('afterTransfer');
}

const gasUsedBeforeContractExecution = gasUsed;

if (status && this.data.data && this.data.data != '0x') {
// Traitement du champ data de la transaction (smart contract call)

if (!contractAddress || contractAddress == '0x') {
// contract creation

contractDeployed = true;

let contractExecResult;
try {
contractExecResult = this.transactionDeployContract(block);
const creationLogs = contractExecResult.logs;
vmStorage = contractExecResult.storage;

if (creationLogs.length) {
txLogs.push( ...creationLogs );
}

if (! contractExecResult.result) {
// Deploy Failed
status = 0;
error = new Error('CONTRACT_CREATION_FAILED');

// emit event
const debugError = {
message: 'CONTRACT_CREATION_FAILED',
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);

} else {
// Deploy Success
createdContractAddress = contractExecResult.result.toLowerCase();

if (this.data.value.gt(0)) {
// Transfer tx value to contract
const _txData = {
...this.data,
to: createdContractAddress,
};
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(_txData, block);
txLogs.push( ...transferLogs );

if (transferResult) {
amountTransfered = this.data.value;

} else {
status = 0;
error = new Error('CONTRACT_CREATION_TRANSFER_FAILED');
}
}

// TODO: emit event CONTRACT_CREATION_SUCCESS ?
}



} catch (e) {
//console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] Cannot execute transaction');
//throw e;
status = 0;
error = new Error('CONTRACT_CREATION_ERROR');

console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] CONTRACT_CREATION_ERROR with message', e.message)

// emit event
const debugError = {
message: 'CONTRACT_CREATION_ERROR',
error: e.message,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

_checkGas('afterContractCreation');

} else {
// contract execution

const contractAccount = blockchain.getAccount(contractAddress);

if (! contractAccount) {
status = 0;
error = new Error('CONTRACT_ACCOUNT_NOT_FOUND');

// emit event
const debugError = {
message: 'CONTRACT_ACCOUNT_NOT_FOUND',
address: contractAddress,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

if (status) {
storageBackup = JSON.stringify(contractAccount.data.storage);
balanceBackup = contractAccount.data.balance;
contractExecuted = true;

try {
const contractExecResult = this.executeTransactionContract(block);
const internalLogs = contractExecResult.logs;
vmStorage = contractExecResult.storage;

if (internalLogs.length) {
txLogs.push( ...internalLogs );
}

if (! contractExecResult.result) {
status = 0;
error = new Error('CONTRACT_EXEC_FAILED');

// emit event
const debugError = {
message: 'CONTRACT_EXEC_FAILED',
address: contractAddress,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

} catch (e) {
status = 0;
error = new Error('CONTRACT_EXEC_ERROR');

// emit event
const debugError = {
message: 'CONTRACT_EXEC_ERROR',
address: contractAddress,
error: e.message,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}
}

_checkGas('afterContractCall');
}

}

const gasUsedAfterContractExecution = gasUsed;
const gasUsedInContractExecution = gasUsedAfterContractExecution.sub(gasUsedBeforeContractExecution);
const contractExecutionCost = gasUsedInContractExecution.mul(_tx.data.gasPrice);


console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] execDuration =', (Math.round(execDuration * 1000000) / 1000000), ' seconds');



if (txCostEffective.gt(0)) {
// Gas payment
//const minerAccount = blockchain.getAccount(block.data.miner, true);
//const gasPaymentResult = minerAccount.transferFrom(accountFrom.address, txCostEffective);

const txDataGasPayment = {
from: accountFrom.address,
to: block.data.miner,
value: txCostEffective,
};
const [gasPaymentResult, gasPaymentLogs, transferReverted] = blockchain.transferEth(txDataGasPayment, block, true);
txLogs.push(...gasPaymentLogs);

if (gasPaymentResult) {
// gas payment success

} else {
// gas payment failed => reject transaction (and block)
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] Gas payment failed');
_rejectTransaction('GAS_PAYMENT_FAILED');
}

if (false) {
// DEBUG
const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();
const gasEffectivePercent = gasUsedEffective.mul(100).div(this.data.gasLimit).toNumber();
const costPercent = txCost.mul(100).div(txCostLimit).toNumber();
const costEffectivePercent = txCostEffective.mul(100).div(txCostLimit).toNumber();
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] txCostLimit =', ethers.utils.formatEther(txCostLimit));
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] txCostEffective =', ethers.utils.formatEther(txCostEffective));
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] gas % =', Math.round(gasPercent), '% =', Math.round(gasEffectivePercent), '%');
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] cost % =', Math.round(costPercent), '%', Math.round(costEffectivePercent), '%');
}


}


if (accountFrom.address != mintAddress) {
const accountFromBalanceAtEnd = blockchain.getAccountBalance(accountFrom.address);

if (accountFromBalanceAtEnd.lt(0)){
_rejectTransaction('TRANSACTION_FAILED_INSUFFICIENT_ETH_BALANCE_AT_END');
}
}


if (! status) {
// Transaction Failed

// Revert transfer
if (amountTransfered.gt(0)) {
const txDataRevert = {
...this.data,
from: this.data.to,
to: this.data.from,
value: amountTransfered,
};
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(txDataRevert, block);

// TODO: gerer le revert des internal transfers
}

txLogs.forEach(log => {
log.removed = true;
});

// Revert contract storage
if (contractExecuted) {
const contractAccount = blockchain.getAccount(contractAddress);
contractAccount.data.storage = JSON.parse(storageBackup, utils.bnJsonParser);
contractAccount.data.balance = balanceBackup;

} else if (amountTransfered) {
// on check aussi amountTransfered car le contrat peut avoir été exécuté via receive => TODO: process a revoir
const contractAccount = blockchain.getAccount(contractAddress);
if (contractAccount && contractAccount.isContract()) {
//contractAccount.data.storage = JSON.parse(storageBackup, utils.bnJsonParser);
//contractAccount.data.balance = balanceBackup;
// TODO
}

}

// Revert deployed contract
if (contractDeployed) {
// nothing to do ?
}

const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] FAILED TRANSACTION ', this.data.hash, '| gas usage percent =', gasPercent, '%', ' | Error: ', error.message);

const realExecDuration = performance.now()/1000 - execTsStart; //parseFloat(process.hrtime(execTsStart).join('.'));
const realGasUsed = toBn(Math.round(realExecDuration * cpuPricePerSecond)); // TODO: prendre en compte le storage utilisé dans le calcul du gasUsed
const realGasPercent = realGasUsed.mul(100).div(this.data.gasLimit).toNumber();
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] FAILED TRANSACTION ', this.data.hash, '| real gas usage percent =', realGasPercent, '%', ' | Error: ', error.message);

} else {
// Transaction Success

// save contract storage
Object.entries(vmStorage).forEach((entry, idx) => {
const [contractAddress, contractStorage] = entry;

const contractAccount = blockchain.getAccount(contractAddress, true);
contractAccount.data.storage = contractStorage;
contractAccount.save();
});

// show result (debug)
const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();;
console.log(utils.getDate(), "[BLOCKCHAIN]", 'SUCCECSS TRANSACTION ', this.data.hash, '| gas usage percent =', gasPercent, '%');
}


// total gas utilisé dans le block
block.data.gasUsed = block.data.gasUsed.add(gasUsed);


// assign block data on logs
txLogs.forEach((txLog, idx) => {
txLog.data.transactionIndex = transactionIndex;
txLog.data.logIndex = logIndexOffset + idx;
txLog.data.transactionHash = this.data.hash;
txLog.data.blockHash = block.hash;
txLog.data.blockNumber = block.data.number;
});


const receiptData = {
transactionHash: this.data.hash,
transactionIndex: transactionIndex,
blockNumber: block.data.number,
blockHash: block.hash,
//logsBloom: '0x0000',
contractAddress: createdContractAddress,
gasUsed,
cumulativeGasUsed: block.data.gasUsed,
effectiveGasPrice: this.data.gasPrice,
effectiveCost: this.data.gasPrice.mul(gasUsed),
logs: txLogs,
status,
}

const receipt = new Receipt(blockchain, receiptData);

return [receipt, txLogs];
}"
, executeTransactionContract: "(block) => {
let contractAddress = this.data.to;

const isNullRecipient = (! contractAddress || contractAddress.length <= 2);
if (isNullRecipient) {
throw new Error('CONTRACT_EXEC_ADDRESS_INVALID_BUG_1');
}

const contractAccount = blockchain.getAccount(contractAddress);

if (! contractAccount) {
throw new Error('CONTRACT_EXEC_ACCOUNT_NOT_FOUND');
}

// Execute existing contract method call
return blockchain.callContractMethodInVM(this.data, block);

}"
, transactionDeployContract: "(block) => {
let contractAddress = this.data.to;

let contractAccount;

if (contractAddress && contractAddress.length > 2) {
throw new Error('CONTRACT_DEPLOY_ADDRESS_INVALID_BUG_2');
}


// CONTRACT CREATION

if (! this.data.data || this.data.data.length <= 2) {
throw new Error('CONTRACT_DEPLOY_INVALID_DATA_EMPTY_CODE');
}


// build contract address
contractAddress = ethers.utils.getContractAddress(this.data).toLocaleLowerCase();

// parse contract source code & constructor parameters
const codes = ethers.utils.defaultAbiCoder.decode(["string", "string"], this.data.data);
if (! codes) {
throw new Error('CONTRACT_DEPLOY_CODE_PARSING_FAILED');
}

const sourceCode = codes[0];
const constructorParams = codes[1];
const constructorRawParams = ethers.utils.defaultAbiCoder.encode(["string"], [constructorParams]);
const codeHash = ethers.utils.id(sourceCode);
const codeAbi = null; // l'abi sera généré une fois que le contrat aura été instancié
const storageHash = ethers.utils.id('{}');

// create new account
const accountData = {
address: contractAddress,
code: sourceCode,
codeHash,
codeAbi,
storage: {},
storageHash,
};
contractAccount = blockchain.createAccount(contractAddress, accountData);


// instanciate contract + call constructor + recuperer storage
const txData = {
from: this.data.from,
to: contractAddress,
data: constructorRawParams,
value: this.data.value.toHexString(),
};
const callResult = blockchain.deployContractInVM(txData, block);
const creationLogs = callResult.logs;
const vmStorage = callResult.storage;


// emit event
const eventParams = [
contractAddress,
];
const _txData = {
...this.data,
to: blockchain.getNullAddress(),
}
const deployLog = new TxLog(blockchain, null, _txData, 'ContractCreated', eventParams, deployAbi);
creationLogs.push(deployLog);


return {
result: contractAddress,
logs: creationLogs,
storage: vmStorage,
};
}"
, timestamp: "1651572509409", status: 1, gasUsed: "882" }, 0x5f688782a2de8e88327e6930d4133fd833252e1255a7595d619964a2f58bba67: { data: { blockHash: "0x00a4160dc73d5ae90b2375fe405b56005fa5693009c9c23d62b129f878aec822", blockNumber: "2", transactionIndex: 1, hash: "0x5f688782a2de8e88327e6930d4133fd833252e1255a7595d619964a2f58bba67", from: "0x131a190fa22cb867abf58193bf9e7640e9fce682", gasLimit: "679950", gasPrice: "1000000000", data: "0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000099c0a2f2a0a4e616d653a2046617563657420436f6e74726163740a56657273696f6e3a20302e302e330a2a2f0a0a0a636c61737320466175636574207b0a20202020616c6c6f636174696f6e73203d207b7d3b202f2f206c6973746520646520416c6c6f636174696f6e203a207b746f74616c416d6f756e742c206c617374416d6f756e742c206c61737454696d657374616d707d20696e646578c3a9652070617220616464726573730a202020206d696e416d6f756e74506572446179203d206e756d6265722827313027202b202730303030303030303030303030303030303027293b202f2f2031300a202020206d6178416d6f756e74506572446179203d206e756d6265722827353027202b202730303030303030303030303030303030303027293b202f2f2035300a20202020746f74616c416c6c6f6361746564203d206e756d6265722830293b0a20202020616c6c6f636174696f6e7344656c6179203d2038363430303b0a0a0a2020202046617563657428726563697069656e742c20616d6f756e7429207b7d202f2f206576656e740a0a0a20202020676574467265654d6f6e657928726563697069656e7429207b0a2020202020202020636f6e737420746f6b656e416c6c6f636174696f6e73203d20746869732e616c6c6f636174696f6e73207c7c207b7d3b0a0a20202020202020207265717569726528616464726573732874686973292e62616c616e63652e67742830292c2022494e53554646494349454e545f4641554345545f42414c414e434522293b0a20202020202020200a2020202020202020696620282120726563697069656e7429207b0a2020202020202020202020205f7468726f7728274d495353494e475f53454e4445525f4144445245535327293b0a20202020202020207d0a0a202020202020202069662028726563697069656e7420696e20746f6b656e416c6c6f636174696f6e7320262620746f6b656e416c6c6f636174696f6e735b726563697069656e745d2e6c61737454696d657374616d70203e20446174652e6e6f772829202d20746869732e616c6c6f636174696f6e7344656c6179202a203130303029207b0a2020202020202020202020205f7468726f772827544f4f5f534f4f4e27293b0a20202020202020207d0a0a2020202020202020636f6e737420646966664d696e4d6178203d20746869732e6d6178416d6f756e745065724461792e73756228746869732e6d696e416d6f756e74506572446179293b0a2020202020202020636f6e737420726e64203d204d6174682e726f756e64284d6174682e72616e646f6d2829202a2031303030293b0a20202020202020206c657420616d6f756e74203d20746869732e6d696e416d6f756e745065724461792e6164642820646966664d696e4d61782e6d756c28726e64292e64697628313030302920293b0a20202020202020202f2f636f6e737420616d6f756e74203d206e756d62657228273230303030303030303030303030303030303027293b0a0a202020202020202069662028616d6f756e742e6c7428746869732e6d696e416d6f756e745065724461792929207b0a202020202020202020202020616d6f756e74203d20746869732e6d696e416d6f756e745065724461793b0a20202020202020207d0a0a202020202020202069662028616d6f756e742e677428616464726573732874686973292e62616c616e63652929207b0a202020202020202020202020616d6f756e74203d20616464726573732874686973292e62616c616e63653b0a20202020202020207d0a0a202020202020202069662028616d6f756e742e677428746869732e6d6178416d6f756e745065724461792929207b0a202020202020202020202020616d6f756e74203d20746869732e6d6178416d6f756e745065724461793b0a20202020202020207d0a20202020202020200a202020202020202069662028212028726563697069656e7420696e20746f6b656e416c6c6f636174696f6e732929207b0a202020202020202020202020746f6b656e416c6c6f636174696f6e735b726563697069656e745d203d207b0a20202020202020202020202020202020746f74616c416d6f756e743a206e756d6265722830292c0a202020202020202020202020202020206c617374416d6f756e743a206e756d6265722830292c0a202020202020202020202020202020206c61737454696d657374616d703a206e756d6265722830292c0a2020202020202020202020207d3b0a20202020202020207d0a0a0a2020202020202020746f6b656e416c6c6f636174696f6e735b726563697069656e745d2e746f74616c416d6f756e74203d20746f6b656e416c6c6f636174696f6e735b726563697069656e745d2e746f74616c416d6f756e742e61646428616d6f756e74293b0a2020202020202020746f6b656e416c6c6f636174696f6e735b726563697069656e745d2e6c617374416d6f756e74203d20616d6f756e743b0a2020202020202020746f6b656e416c6c6f636174696f6e735b726563697069656e745d2e6c61737454696d657374616d70203d20446174652e6e6f7728293b0a0a20202020202020206164647265737328726563697069656e74292e7472616e7366657228616d6f756e74293b202f2f2073656e642066726565206d6f6e657920746f20726563697069656e740a0a202020202020202061646472657373286d73672e73656e646572292e7472616e7366657228206e756d62657228316531382e746f537472696e6728292920293b202f2f2073656e64206d6f6e657920666f7220676173207061796d656e74207468616e6b730a0a2020202020202020746869732e746f74616c416c6c6f6361746564203d20746869732e746f74616c416c6c6f63617465642e61646428616d6f756e74293b0a0a2020202020202020656d69742827466175636574272c205b726563697069656e742c20616d6f756e745d293b0a0a202020202020202072657475726e205b616d6f756e745d3b0a202020207d0a7d0a0a2f2f204576656e74730a4661756365742e70726f746f747970652e4661756365742e6576656e74203d20747275653b0a4661756365742e70726f746f747970652e4661756365742e696e70757473203d205b276164647265737320696e6465786564272c202775696e74323536275d3b0a0a2f2f204d6574686f64730a4661756365742e70726f746f747970652e676574467265654d6f6e65792e696e70757473203d205b2761646472657373275d3b0a4661756365742e70726f746f747970652e676574467265654d6f6e65792e6f757470757473203d205b2775696e74323536275d3b0a0a0a72657475726e204661756365743b0a0000000000000000000000000000000000000000000000000000000000000000000000025b5d000000000000000000000000000000000000000000000000000000000000", nonce: "1", to: "0x", value: "0", chainId: "3615", origin: "1651572507298", v: "1c61", r: "52d7b13a7aa39476956574523caae17c0a7b9a6db8ce5d2d6f93cd892a9712fa", s: "16f7aa69823edaf5a24fb214149fd1a93223244a52397f767a9babb696ced4c1" }, hash: "0x5f688782a2de8e88327e6930d4133fd833252e1255a7595d619964a2f58bba67", load: "() => {
const transactionFile = config.node.dataDir + '/transactions/' + this.hash + '.json';

if (! fs.existsSync(transactionFile)) {
console.warn(utils.getDate(), "[BLOCKCHAIN]", 'IMPORT Transaction not found', this.hash);
throw new Error('IMPORT_TRANSACTION_NOT_FOUND_DATABASE_CORRUPTED');
}

const transactionJson = fs.readFileSync(transactionFile);
transactionData = JSON.parse(transactionJson, utils.bnJsonParser);

this.data = (typeof transactionData == 'TransactionData') ? transactionData : new TransactionData(transactionData);

return true;
}"
, save: "() => {
const transactionFile = config.node.dataDir + '/transactions/' + this.hash + '.json';
fs.writeFileSync(transactionFile, JSON.stringify(this.data));
return true;
}"
, getReceipt: "() => {
return blockchain.getTransactionReceiptByHash(this.hash);
}"
, getBlock: "() => {
return blockchain.getBlockByHash(this.data.blockHash);
}"
, getBlockTransationPrev: "() => {
const block = this.getBlock();
const txPrevIdx = (this.data.transactionIndex > 0) ? (this.data.transactionIndex-1) : null;
const tx = (block && txPrevIdx in block.transactions) ? block.transactions[txPrevIdx] : null;
return tx;
}"
, getBlockTransationNext: "() => {
const block = this.getBlock();
const txNextIdx = (this.data.transactionIndex < block.transactions.length - 1) ? (this.data.transactionIndex+1) : null;
const tx = (block && txNextIdx in block.transactions) ? block.transactions[txNextIdx] : null;
return tx;
}"
, getAccountTransactionIndex: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = account.data.transactions.findIndex(transactionHash => blockchain.getTransactionByHash(transactionHash).hash == this.hash);
if (txIdx === -1) {
return null;
}
return txIdx;
}"
, getAccountTransationPrev: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = this.getAccountTransactionIndex();
if (txIdx === null) {
return null;
}
const txPrevIdx = (txIdx > 0) ? (txIdx-1) : null;
const transactionHash = (txPrevIdx in account.data.transactions) ? account.data.transactions[txPrevIdx] : null;
if (! transactionHash) {
return null;
}
return blockchain.getTransactionByHash(transactionHash);
}"
, getAccountTransationNext: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = this.getAccountTransactionIndex();
if (txIdx === null) {
return null;
}
const txNextIdx = (txIdx < account.data.transactions.length - 1) ? (txIdx+1) : null;
const transactionHash = (txNextIdx in account.data.transactions) ? account.data.transactions[txNextIdx] : null;
if (! transactionHash) {
return null;
}
return blockchain.getTransactionByHash(transactionHash);

}"
, formatRpc: "() => {
// https://github.com/ethers-io/ethers.js/blob/master/packages/abstract-provider/lib/index.d.ts

const to = (this.data.to && this.data.to.length == 42) ? this.data.to : null;

const _txData = {
blockHash: this.data.blockHash,
blockNumber: this.data.blockNumber.toNumber(),
transactionIndex: this.data.transactionIndex,
hash: this.data.hash,
from: this.data.from,
gasLimit: this.data.gasLimit.toHexString(),
gasPrice: this.data.gasPrice.toHexString(),
data: this.data.data,
nonce: this.data.nonce.toHexString(),
to,
value: this.data.value.toHexString(),
chainId: this.data.chainId.toNumber(),
//origin: this.data.origin.toHexString(),
};

return _txData;
}"
, getHashOLD: "() => {
const _txData = {
gasPrice: this.data.gasPrice,
gasLimit: this.data.gasLimit,
from: this.data.from,
to: this.data.to,
value: this.data.value,
data: this.data.data,
nonce: this.data.nonce,
};

const strvalue = JSON.stringify(utils.orderedObject(_txData));
const transactionHash = ethers.utils.id(strvalue);

return transactionHash;
}"
, getHash: "() => {
const _txData = {
gasPrice: this.data.gasPrice.toHexString(),
gasLimit: this.data.gasLimit.toHexString(),
from: this.data.from,
to: this.data.to,
value: this.data.value.toHexString(),
data: this.data.data,
nonce: this.data.nonce.toHexString(),
v: this.data.v ? ('0x' + this.data.v) : '',
r: this.data.r ? ('0x' + this.data.r) : '',
s: this.data.s ? ('0x' + this.data.s) : '',
};

const tx = new EthereumJS.Transaction(_txData);
const hashRaw = tx.hash();

const transactionHash = ethers.utils.defaultAbiCoder.decode(["uint256"], hashRaw)[0].toHexString();

return transactionHash;
}"
, execute: "(block, transactionIndex, logIndexOffset) => {
const _tx = this;

function _rejectTransaction(reason) {
// https://openethereum.github.io/Transactions-Queue
reason = reason || 'unknown';
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] REJECTED PROCESSING TRANSACTION', _tx.data.hash, ' (Reason:', reason + ')');
const success = blockchain.removeTransactionsFromMempool([_tx.hash], true, true);
if (success) {
saveMempool();
}
throw new Error(reason); // on rejete definitivement la tx (elle ne sera pas minée ... et donc aucun frais de gas ne sera compté)
// et du coup on rejete le block
}

function _checkGas(step) {
execDuration = performance.now()/1000 - execTsStart; //parseFloat(process.hrtime(execTsStart).join('.'));
gasUsed = toBn(Math.round(execDuration * cpuPricePerSecond)); // TODO: prendre en compte le storage utilisé dans le calcul du gasUsed
txCost = gasUsed.mul(_tx.data.gasPrice);

if (step == 'afterTransfer') {
//debugger;
}

if (status) {
gasUsedEffective = gasUsed;
txCostEffective = gasUsedEffective.mul(_tx.data.gasPrice);

if (gasUsed.gt(_tx.data.gasLimit)) {
status = 0;
error = new Error('INSUFFICIENT_GAS');

gasUsedEffective = _tx.data.gasLimit;
txCostEffective = txCostLimit;

// emit event
const debugError = {
message: 'INSUFFICIENT_GAS',
step,
gasPrice: _tx.data.gasPrice,
gasLimit: _tx.data.gasLimit,
gasUsed,
gasUsedEffective,
txCostLimit,
txCost,
txCostEffective,
execDuration,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, _tx.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

}
}


//const block = this;
const txLogs = [];
let vmStorage = {};

let gasUsed = toBn(0);
let gasUsedEffective = toBn(0);
let status = 1;
let error = null;
let createdContractAddress = null;
let amountTransfered = toBn(0);

const contractAddress = this.data.to;
let storageBackup;
let balanceBackup;
let contractExecuted = false;
let contractDeployed = false;

const execTsStart = performance.now()/1000; //process.hrtime(); //Date.now();
let execDuration = 0;

const txCostLimit = this.data.gasLimit.mul(this.data.gasPrice);
let txCost = toBn(0);
let txCostEffective = toBn(0);


const accountFrom = blockchain.getAccount(this.data.from);
if (! accountFrom) {
_rejectTransaction('TRANSACTION_REJECTED_EMPTY_ACCOUNT');
}
const accountFromBalance = accountFrom.data.balance;


if (accountFrom.address != mintAddress) {

// verif nonce
const expectedNonce = toBn(accountFrom.data.transactions.length); // le nonce attendu est egal au nombre de transactions passées de l'account
if (! this.data.nonce.eq(expectedNonce)) {
_rejectTransaction('TRANSACTION_REJECTED_INCORRECT_NONCE');
}

// verif balance pour gas + transfer
if (this.data.value && this.data.value.gt(0)) {
if (accountFromBalance.lt(this.data.value.add(txCostLimit))) {
// l'adresse n'a pas suffisamment de fond pour executer la transaction (selon gasLimit)
// TODO: faut-il rejeter la tx ? ou la laisser passer (en status=0) et lui vider sa balance (si 100% du gas est utilisé) ?
//return _rejectTransaction('TRANSACTION_FAILED_INSUFFICIENT_ETH_BALANCE');

_rejectTransaction('TRANSACTION_REJECTED_INSUFFICIENT_ETH_BALANCE');
}
}
}



_checkGas('init');


if (status) {
if (this.data.value && this.data.value.gt(0) && contractAddress && contractAddress.length > 2) {
// Transfer la valeur demandée (simple ETH transaction)
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(this.data, block);
txLogs.push( ...transferLogs );

// si recipient == contract => on transfer quand meme dès maintenant => TODO: a revoir (le transferEth declenche un appel à receive ... avant meme de savoir si unne methode payable doit etre appelée. Probleme si receive provoque un revert => la fonction payable ne sera pas appelée)
// 1) si methode payable appellée, on l'execute
// 2) si methode non payable => on appelle receive juste avant
// 3) si methode inconnue => revert


if (transferResult && transferReverted) {
// reverted (after transfer)
amountTransfered = this.data.value;

status = 0;
error = new Error('TRANSFER_REVERTED');

} else if (transferResult) {
// success
amountTransfered = this.data.value;

} else {
// transfer failed
status = 0;
error = new Error('TRANSFER_FAILED');
}

}

_checkGas('afterTransfer');
}

const gasUsedBeforeContractExecution = gasUsed;

if (status && this.data.data && this.data.data != '0x') {
// Traitement du champ data de la transaction (smart contract call)

if (!contractAddress || contractAddress == '0x') {
// contract creation

contractDeployed = true;

let contractExecResult;
try {
contractExecResult = this.transactionDeployContract(block);
const creationLogs = contractExecResult.logs;
vmStorage = contractExecResult.storage;

if (creationLogs.length) {
txLogs.push( ...creationLogs );
}

if (! contractExecResult.result) {
// Deploy Failed
status = 0;
error = new Error('CONTRACT_CREATION_FAILED');

// emit event
const debugError = {
message: 'CONTRACT_CREATION_FAILED',
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);

} else {
// Deploy Success
createdContractAddress = contractExecResult.result.toLowerCase();

if (this.data.value.gt(0)) {
// Transfer tx value to contract
const _txData = {
...this.data,
to: createdContractAddress,
};
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(_txData, block);
txLogs.push( ...transferLogs );

if (transferResult) {
amountTransfered = this.data.value;

} else {
status = 0;
error = new Error('CONTRACT_CREATION_TRANSFER_FAILED');
}
}

// TODO: emit event CONTRACT_CREATION_SUCCESS ?
}



} catch (e) {
//console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] Cannot execute transaction');
//throw e;
status = 0;
error = new Error('CONTRACT_CREATION_ERROR');

console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] CONTRACT_CREATION_ERROR with message', e.message)

// emit event
const debugError = {
message: 'CONTRACT_CREATION_ERROR',
error: e.message,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

_checkGas('afterContractCreation');

} else {
// contract execution

const contractAccount = blockchain.getAccount(contractAddress);

if (! contractAccount) {
status = 0;
error = new Error('CONTRACT_ACCOUNT_NOT_FOUND');

// emit event
const debugError = {
message: 'CONTRACT_ACCOUNT_NOT_FOUND',
address: contractAddress,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

if (status) {
storageBackup = JSON.stringify(contractAccount.data.storage);
balanceBackup = contractAccount.data.balance;
contractExecuted = true;

try {
const contractExecResult = this.executeTransactionContract(block);
const internalLogs = contractExecResult.logs;
vmStorage = contractExecResult.storage;

if (internalLogs.length) {
txLogs.push( ...internalLogs );
}

if (! contractExecResult.result) {
status = 0;
error = new Error('CONTRACT_EXEC_FAILED');

// emit event
const debugError = {
message: 'CONTRACT_EXEC_FAILED',
address: contractAddress,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

} catch (e) {
status = 0;
error = new Error('CONTRACT_EXEC_ERROR');

// emit event
const debugError = {
message: 'CONTRACT_EXEC_ERROR',
address: contractAddress,
error: e.message,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}
}

_checkGas('afterContractCall');
}

}

const gasUsedAfterContractExecution = gasUsed;
const gasUsedInContractExecution = gasUsedAfterContractExecution.sub(gasUsedBeforeContractExecution);
const contractExecutionCost = gasUsedInContractExecution.mul(_tx.data.gasPrice);


console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] execDuration =', (Math.round(execDuration * 1000000) / 1000000), ' seconds');



if (txCostEffective.gt(0)) {
// Gas payment
//const minerAccount = blockchain.getAccount(block.data.miner, true);
//const gasPaymentResult = minerAccount.transferFrom(accountFrom.address, txCostEffective);

const txDataGasPayment = {
from: accountFrom.address,
to: block.data.miner,
value: txCostEffective,
};
const [gasPaymentResult, gasPaymentLogs, transferReverted] = blockchain.transferEth(txDataGasPayment, block, true);
txLogs.push(...gasPaymentLogs);

if (gasPaymentResult) {
// gas payment success

} else {
// gas payment failed => reject transaction (and block)
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] Gas payment failed');
_rejectTransaction('GAS_PAYMENT_FAILED');
}

if (false) {
// DEBUG
const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();
const gasEffectivePercent = gasUsedEffective.mul(100).div(this.data.gasLimit).toNumber();
const costPercent = txCost.mul(100).div(txCostLimit).toNumber();
const costEffectivePercent = txCostEffective.mul(100).div(txCostLimit).toNumber();
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] txCostLimit =', ethers.utils.formatEther(txCostLimit));
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] txCostEffective =', ethers.utils.formatEther(txCostEffective));
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] gas % =', Math.round(gasPercent), '% =', Math.round(gasEffectivePercent), '%');
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] cost % =', Math.round(costPercent), '%', Math.round(costEffectivePercent), '%');
}


}


if (accountFrom.address != mintAddress) {
const accountFromBalanceAtEnd = blockchain.getAccountBalance(accountFrom.address);

if (accountFromBalanceAtEnd.lt(0)){
_rejectTransaction('TRANSACTION_FAILED_INSUFFICIENT_ETH_BALANCE_AT_END');
}
}


if (! status) {
// Transaction Failed

// Revert transfer
if (amountTransfered.gt(0)) {
const txDataRevert = {
...this.data,
from: this.data.to,
to: this.data.from,
value: amountTransfered,
};
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(txDataRevert, block);

// TODO: gerer le revert des internal transfers
}

txLogs.forEach(log => {
log.removed = true;
});

// Revert contract storage
if (contractExecuted) {
const contractAccount = blockchain.getAccount(contractAddress);
contractAccount.data.storage = JSON.parse(storageBackup, utils.bnJsonParser);
contractAccount.data.balance = balanceBackup;

} else if (amountTransfered) {
// on check aussi amountTransfered car le contrat peut avoir été exécuté via receive => TODO: process a revoir
const contractAccount = blockchain.getAccount(contractAddress);
if (contractAccount && contractAccount.isContract()) {
//contractAccount.data.storage = JSON.parse(storageBackup, utils.bnJsonParser);
//contractAccount.data.balance = balanceBackup;
// TODO
}

}

// Revert deployed contract
if (contractDeployed) {
// nothing to do ?
}

const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] FAILED TRANSACTION ', this.data.hash, '| gas usage percent =', gasPercent, '%', ' | Error: ', error.message);

const realExecDuration = performance.now()/1000 - execTsStart; //parseFloat(process.hrtime(execTsStart).join('.'));
const realGasUsed = toBn(Math.round(realExecDuration * cpuPricePerSecond)); // TODO: prendre en compte le storage utilisé dans le calcul du gasUsed
const realGasPercent = realGasUsed.mul(100).div(this.data.gasLimit).toNumber();
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] FAILED TRANSACTION ', this.data.hash, '| real gas usage percent =', realGasPercent, '%', ' | Error: ', error.message);

} else {
// Transaction Success

// save contract storage
Object.entries(vmStorage).forEach((entry, idx) => {
const [contractAddress, contractStorage] = entry;

const contractAccount = blockchain.getAccount(contractAddress, true);
contractAccount.data.storage = contractStorage;
contractAccount.save();
});

// show result (debug)
const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();;
console.log(utils.getDate(), "[BLOCKCHAIN]", 'SUCCECSS TRANSACTION ', this.data.hash, '| gas usage percent =', gasPercent, '%');
}


// total gas utilisé dans le block
block.data.gasUsed = block.data.gasUsed.add(gasUsed);


// assign block data on logs
txLogs.forEach((txLog, idx) => {
txLog.data.transactionIndex = transactionIndex;
txLog.data.logIndex = logIndexOffset + idx;
txLog.data.transactionHash = this.data.hash;
txLog.data.blockHash = block.hash;
txLog.data.blockNumber = block.data.number;
});


const receiptData = {
transactionHash: this.data.hash,
transactionIndex: transactionIndex,
blockNumber: block.data.number,
blockHash: block.hash,
//logsBloom: '0x0000',
contractAddress: createdContractAddress,
gasUsed,
cumulativeGasUsed: block.data.gasUsed,
effectiveGasPrice: this.data.gasPrice,
effectiveCost: this.data.gasPrice.mul(gasUsed),
logs: txLogs,
status,
}

const receipt = new Receipt(blockchain, receiptData);

return [receipt, txLogs];
}"
, executeTransactionContract: "(block) => {
let contractAddress = this.data.to;

const isNullRecipient = (! contractAddress || contractAddress.length <= 2);
if (isNullRecipient) {
throw new Error('CONTRACT_EXEC_ADDRESS_INVALID_BUG_1');
}

const contractAccount = blockchain.getAccount(contractAddress);

if (! contractAccount) {
throw new Error('CONTRACT_EXEC_ACCOUNT_NOT_FOUND');
}

// Execute existing contract method call
return blockchain.callContractMethodInVM(this.data, block);

}"
, transactionDeployContract: "(block) => {
let contractAddress = this.data.to;

let contractAccount;

if (contractAddress && contractAddress.length > 2) {
throw new Error('CONTRACT_DEPLOY_ADDRESS_INVALID_BUG_2');
}


// CONTRACT CREATION

if (! this.data.data || this.data.data.length <= 2) {
throw new Error('CONTRACT_DEPLOY_INVALID_DATA_EMPTY_CODE');
}


// build contract address
contractAddress = ethers.utils.getContractAddress(this.data).toLocaleLowerCase();

// parse contract source code & constructor parameters
const codes = ethers.utils.defaultAbiCoder.decode(["string", "string"], this.data.data);
if (! codes) {
throw new Error('CONTRACT_DEPLOY_CODE_PARSING_FAILED');
}

const sourceCode = codes[0];
const constructorParams = codes[1];
const constructorRawParams = ethers.utils.defaultAbiCoder.encode(["string"], [constructorParams]);
const codeHash = ethers.utils.id(sourceCode);
const codeAbi = null; // l'abi sera généré une fois que le contrat aura été instancié
const storageHash = ethers.utils.id('{}');

// create new account
const accountData = {
address: contractAddress,
code: sourceCode,
codeHash,
codeAbi,
storage: {},
storageHash,
};
contractAccount = blockchain.createAccount(contractAddress, accountData);


// instanciate contract + call constructor + recuperer storage
const txData = {
from: this.data.from,
to: contractAddress,
data: constructorRawParams,
value: this.data.value.toHexString(),
};
const callResult = blockchain.deployContractInVM(txData, block);
const creationLogs = callResult.logs;
const vmStorage = callResult.storage;


// emit event
const eventParams = [
contractAddress,
];
const _txData = {
...this.data,
to: blockchain.getNullAddress(),
}
const deployLog = new TxLog(blockchain, null, _txData, 'ContractCreated', eventParams, deployAbi);
creationLogs.push(deployLog);


return {
result: contractAddress,
logs: creationLogs,
storage: vmStorage,
};
}"
, timestamp: "1651572509409", status: 1, gasUsed: "7139" }, 0x591adc77dc95f7fdcbf421db667bf86454d0dffcd9c9ecd7b29a726a7edb75ec: { data: { blockHash: "0x00159c1cfcf8717f506966ffcaa50f6f1ee46bf48ac7f60b1ee60979600d6436", blockNumber: "3", transactionIndex: 0, hash: "0x591adc77dc95f7fdcbf421db667bf86454d0dffcd9c9ecd7b29a726a7edb75ec", from: "0x0000000000000000000000000000000000000001", gasLimit: "1000000000000000000", gasPrice: "0", data: "0x", nonce: "2", to: "0xccf8ba457dcad7ee6a0361c96846a0f79744b113", value: "250000000000000000000", chainId: "3615", origin: "1651572514573", v: "", r: "", s: "" }, hash: "0x591adc77dc95f7fdcbf421db667bf86454d0dffcd9c9ecd7b29a726a7edb75ec", load: "() => {
const transactionFile = config.node.dataDir + '/transactions/' + this.hash + '.json';

if (! fs.existsSync(transactionFile)) {
console.warn(utils.getDate(), "[BLOCKCHAIN]", 'IMPORT Transaction not found', this.hash);
throw new Error('IMPORT_TRANSACTION_NOT_FOUND_DATABASE_CORRUPTED');
}

const transactionJson = fs.readFileSync(transactionFile);
transactionData = JSON.parse(transactionJson, utils.bnJsonParser);

this.data = (typeof transactionData == 'TransactionData') ? transactionData : new TransactionData(transactionData);

return true;
}"
, save: "() => {
const transactionFile = config.node.dataDir + '/transactions/' + this.hash + '.json';
fs.writeFileSync(transactionFile, JSON.stringify(this.data));
return true;
}"
, getReceipt: "() => {
return blockchain.getTransactionReceiptByHash(this.hash);
}"
, getBlock: "() => {
return blockchain.getBlockByHash(this.data.blockHash);
}"
, getBlockTransationPrev: "() => {
const block = this.getBlock();
const txPrevIdx = (this.data.transactionIndex > 0) ? (this.data.transactionIndex-1) : null;
const tx = (block && txPrevIdx in block.transactions) ? block.transactions[txPrevIdx] : null;
return tx;
}"
, getBlockTransationNext: "() => {
const block = this.getBlock();
const txNextIdx = (this.data.transactionIndex < block.transactions.length - 1) ? (this.data.transactionIndex+1) : null;
const tx = (block && txNextIdx in block.transactions) ? block.transactions[txNextIdx] : null;
return tx;
}"
, getAccountTransactionIndex: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = account.data.transactions.findIndex(transactionHash => blockchain.getTransactionByHash(transactionHash).hash == this.hash);
if (txIdx === -1) {
return null;
}
return txIdx;
}"
, getAccountTransationPrev: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = this.getAccountTransactionIndex();
if (txIdx === null) {
return null;
}
const txPrevIdx = (txIdx > 0) ? (txIdx-1) : null;
const transactionHash = (txPrevIdx in account.data.transactions) ? account.data.transactions[txPrevIdx] : null;
if (! transactionHash) {
return null;
}
return blockchain.getTransactionByHash(transactionHash);
}"
, getAccountTransationNext: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = this.getAccountTransactionIndex();
if (txIdx === null) {
return null;
}
const txNextIdx = (txIdx < account.data.transactions.length - 1) ? (txIdx+1) : null;
const transactionHash = (txNextIdx in account.data.transactions) ? account.data.transactions[txNextIdx] : null;
if (! transactionHash) {
return null;
}
return blockchain.getTransactionByHash(transactionHash);

}"
, formatRpc: "() => {
// https://github.com/ethers-io/ethers.js/blob/master/packages/abstract-provider/lib/index.d.ts

const to = (this.data.to && this.data.to.length == 42) ? this.data.to : null;

const _txData = {
blockHash: this.data.blockHash,
blockNumber: this.data.blockNumber.toNumber(),
transactionIndex: this.data.transactionIndex,
hash: this.data.hash,
from: this.data.from,
gasLimit: this.data.gasLimit.toHexString(),
gasPrice: this.data.gasPrice.toHexString(),
data: this.data.data,
nonce: this.data.nonce.toHexString(),
to,
value: this.data.value.toHexString(),
chainId: this.data.chainId.toNumber(),
//origin: this.data.origin.toHexString(),
};

return _txData;
}"
, getHashOLD: "() => {
const _txData = {
gasPrice: this.data.gasPrice,
gasLimit: this.data.gasLimit,
from: this.data.from,
to: this.data.to,
value: this.data.value,
data: this.data.data,
nonce: this.data.nonce,
};

const strvalue = JSON.stringify(utils.orderedObject(_txData));
const transactionHash = ethers.utils.id(strvalue);

return transactionHash;
}"
, getHash: "() => {
const _txData = {
gasPrice: this.data.gasPrice.toHexString(),
gasLimit: this.data.gasLimit.toHexString(),
from: this.data.from,
to: this.data.to,
value: this.data.value.toHexString(),
data: this.data.data,
nonce: this.data.nonce.toHexString(),
v: this.data.v ? ('0x' + this.data.v) : '',
r: this.data.r ? ('0x' + this.data.r) : '',
s: this.data.s ? ('0x' + this.data.s) : '',
};

const tx = new EthereumJS.Transaction(_txData);
const hashRaw = tx.hash();

const transactionHash = ethers.utils.defaultAbiCoder.decode(["uint256"], hashRaw)[0].toHexString();

return transactionHash;
}"
, execute: "(block, transactionIndex, logIndexOffset) => {
const _tx = this;

function _rejectTransaction(reason) {
// https://openethereum.github.io/Transactions-Queue
reason = reason || 'unknown';
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] REJECTED PROCESSING TRANSACTION', _tx.data.hash, ' (Reason:', reason + ')');
const success = blockchain.removeTransactionsFromMempool([_tx.hash], true, true);
if (success) {
saveMempool();
}
throw new Error(reason); // on rejete definitivement la tx (elle ne sera pas minée ... et donc aucun frais de gas ne sera compté)
// et du coup on rejete le block
}

function _checkGas(step) {
execDuration = performance.now()/1000 - execTsStart; //parseFloat(process.hrtime(execTsStart).join('.'));
gasUsed = toBn(Math.round(execDuration * cpuPricePerSecond)); // TODO: prendre en compte le storage utilisé dans le calcul du gasUsed
txCost = gasUsed.mul(_tx.data.gasPrice);

if (step == 'afterTransfer') {
//debugger;
}

if (status) {
gasUsedEffective = gasUsed;
txCostEffective = gasUsedEffective.mul(_tx.data.gasPrice);

if (gasUsed.gt(_tx.data.gasLimit)) {
status = 0;
error = new Error('INSUFFICIENT_GAS');

gasUsedEffective = _tx.data.gasLimit;
txCostEffective = txCostLimit;

// emit event
const debugError = {
message: 'INSUFFICIENT_GAS',
step,
gasPrice: _tx.data.gasPrice,
gasLimit: _tx.data.gasLimit,
gasUsed,
gasUsedEffective,
txCostLimit,
txCost,
txCostEffective,
execDuration,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, _tx.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

}
}


//const block = this;
const txLogs = [];
let vmStorage = {};

let gasUsed = toBn(0);
let gasUsedEffective = toBn(0);
let status = 1;
let error = null;
let createdContractAddress = null;
let amountTransfered = toBn(0);

const contractAddress = this.data.to;
let storageBackup;
let balanceBackup;
let contractExecuted = false;
let contractDeployed = false;

const execTsStart = performance.now()/1000; //process.hrtime(); //Date.now();
let execDuration = 0;

const txCostLimit = this.data.gasLimit.mul(this.data.gasPrice);
let txCost = toBn(0);
let txCostEffective = toBn(0);


const accountFrom = blockchain.getAccount(this.data.from);
if (! accountFrom) {
_rejectTransaction('TRANSACTION_REJECTED_EMPTY_ACCOUNT');
}
const accountFromBalance = accountFrom.data.balance;


if (accountFrom.address != mintAddress) {

// verif nonce
const expectedNonce = toBn(accountFrom.data.transactions.length); // le nonce attendu est egal au nombre de transactions passées de l'account
if (! this.data.nonce.eq(expectedNonce)) {
_rejectTransaction('TRANSACTION_REJECTED_INCORRECT_NONCE');
}

// verif balance pour gas + transfer
if (this.data.value && this.data.value.gt(0)) {
if (accountFromBalance.lt(this.data.value.add(txCostLimit))) {
// l'adresse n'a pas suffisamment de fond pour executer la transaction (selon gasLimit)
// TODO: faut-il rejeter la tx ? ou la laisser passer (en status=0) et lui vider sa balance (si 100% du gas est utilisé) ?
//return _rejectTransaction('TRANSACTION_FAILED_INSUFFICIENT_ETH_BALANCE');

_rejectTransaction('TRANSACTION_REJECTED_INSUFFICIENT_ETH_BALANCE');
}
}
}



_checkGas('init');


if (status) {
if (this.data.value && this.data.value.gt(0) && contractAddress && contractAddress.length > 2) {
// Transfer la valeur demandée (simple ETH transaction)
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(this.data, block);
txLogs.push( ...transferLogs );

// si recipient == contract => on transfer quand meme dès maintenant => TODO: a revoir (le transferEth declenche un appel à receive ... avant meme de savoir si unne methode payable doit etre appelée. Probleme si receive provoque un revert => la fonction payable ne sera pas appelée)
// 1) si methode payable appellée, on l'execute
// 2) si methode non payable => on appelle receive juste avant
// 3) si methode inconnue => revert


if (transferResult && transferReverted) {
// reverted (after transfer)
amountTransfered = this.data.value;

status = 0;
error = new Error('TRANSFER_REVERTED');

} else if (transferResult) {
// success
amountTransfered = this.data.value;

} else {
// transfer failed
status = 0;
error = new Error('TRANSFER_FAILED');
}

}

_checkGas('afterTransfer');
}

const gasUsedBeforeContractExecution = gasUsed;

if (status && this.data.data && this.data.data != '0x') {
// Traitement du champ data de la transaction (smart contract call)

if (!contractAddress || contractAddress == '0x') {
// contract creation

contractDeployed = true;

let contractExecResult;
try {
contractExecResult = this.transactionDeployContract(block);
const creationLogs = contractExecResult.logs;
vmStorage = contractExecResult.storage;

if (creationLogs.length) {
txLogs.push( ...creationLogs );
}

if (! contractExecResult.result) {
// Deploy Failed
status = 0;
error = new Error('CONTRACT_CREATION_FAILED');

// emit event
const debugError = {
message: 'CONTRACT_CREATION_FAILED',
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);

} else {
// Deploy Success
createdContractAddress = contractExecResult.result.toLowerCase();

if (this.data.value.gt(0)) {
// Transfer tx value to contract
const _txData = {
...this.data,
to: createdContractAddress,
};
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(_txData, block);
txLogs.push( ...transferLogs );

if (transferResult) {
amountTransfered = this.data.value;

} else {
status = 0;
error = new Error('CONTRACT_CREATION_TRANSFER_FAILED');
}
}

// TODO: emit event CONTRACT_CREATION_SUCCESS ?
}



} catch (e) {
//console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] Cannot execute transaction');
//throw e;
status = 0;
error = new Error('CONTRACT_CREATION_ERROR');

console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] CONTRACT_CREATION_ERROR with message', e.message)

// emit event
const debugError = {
message: 'CONTRACT_CREATION_ERROR',
error: e.message,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

_checkGas('afterContractCreation');

} else {
// contract execution

const contractAccount = blockchain.getAccount(contractAddress);

if (! contractAccount) {
status = 0;
error = new Error('CONTRACT_ACCOUNT_NOT_FOUND');

// emit event
const debugError = {
message: 'CONTRACT_ACCOUNT_NOT_FOUND',
address: contractAddress,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

if (status) {
storageBackup = JSON.stringify(contractAccount.data.storage);
balanceBackup = contractAccount.data.balance;
contractExecuted = true;

try {
const contractExecResult = this.executeTransactionContract(block);
const internalLogs = contractExecResult.logs;
vmStorage = contractExecResult.storage;

if (internalLogs.length) {
txLogs.push( ...internalLogs );
}

if (! contractExecResult.result) {
status = 0;
error = new Error('CONTRACT_EXEC_FAILED');

// emit event
const debugError = {
message: 'CONTRACT_EXEC_FAILED',
address: contractAddress,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

} catch (e) {
status = 0;
error = new Error('CONTRACT_EXEC_ERROR');

// emit event
const debugError = {
message: 'CONTRACT_EXEC_ERROR',
address: contractAddress,
error: e.message,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}
}

_checkGas('afterContractCall');
}

}

const gasUsedAfterContractExecution = gasUsed;
const gasUsedInContractExecution = gasUsedAfterContractExecution.sub(gasUsedBeforeContractExecution);
const contractExecutionCost = gasUsedInContractExecution.mul(_tx.data.gasPrice);


console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] execDuration =', (Math.round(execDuration * 1000000) / 1000000), ' seconds');



if (txCostEffective.gt(0)) {
// Gas payment
//const minerAccount = blockchain.getAccount(block.data.miner, true);
//const gasPaymentResult = minerAccount.transferFrom(accountFrom.address, txCostEffective);

const txDataGasPayment = {
from: accountFrom.address,
to: block.data.miner,
value: txCostEffective,
};
const [gasPaymentResult, gasPaymentLogs, transferReverted] = blockchain.transferEth(txDataGasPayment, block, true);
txLogs.push(...gasPaymentLogs);

if (gasPaymentResult) {
// gas payment success

} else {
// gas payment failed => reject transaction (and block)
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] Gas payment failed');
_rejectTransaction('GAS_PAYMENT_FAILED');
}

if (false) {
// DEBUG
const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();
const gasEffectivePercent = gasUsedEffective.mul(100).div(this.data.gasLimit).toNumber();
const costPercent = txCost.mul(100).div(txCostLimit).toNumber();
const costEffectivePercent = txCostEffective.mul(100).div(txCostLimit).toNumber();
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] txCostLimit =', ethers.utils.formatEther(txCostLimit));
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] txCostEffective =', ethers.utils.formatEther(txCostEffective));
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] gas % =', Math.round(gasPercent), '% =', Math.round(gasEffectivePercent), '%');
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] cost % =', Math.round(costPercent), '%', Math.round(costEffectivePercent), '%');
}


}


if (accountFrom.address != mintAddress) {
const accountFromBalanceAtEnd = blockchain.getAccountBalance(accountFrom.address);

if (accountFromBalanceAtEnd.lt(0)){
_rejectTransaction('TRANSACTION_FAILED_INSUFFICIENT_ETH_BALANCE_AT_END');
}
}


if (! status) {
// Transaction Failed

// Revert transfer
if (amountTransfered.gt(0)) {
const txDataRevert = {
...this.data,
from: this.data.to,
to: this.data.from,
value: amountTransfered,
};
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(txDataRevert, block);

// TODO: gerer le revert des internal transfers
}

txLogs.forEach(log => {
log.removed = true;
});

// Revert contract storage
if (contractExecuted) {
const contractAccount = blockchain.getAccount(contractAddress);
contractAccount.data.storage = JSON.parse(storageBackup, utils.bnJsonParser);
contractAccount.data.balance = balanceBackup;

} else if (amountTransfered) {
// on check aussi amountTransfered car le contrat peut avoir été exécuté via receive => TODO: process a revoir
const contractAccount = blockchain.getAccount(contractAddress);
if (contractAccount && contractAccount.isContract()) {
//contractAccount.data.storage = JSON.parse(storageBackup, utils.bnJsonParser);
//contractAccount.data.balance = balanceBackup;
// TODO
}

}

// Revert deployed contract
if (contractDeployed) {
// nothing to do ?
}

const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] FAILED TRANSACTION ', this.data.hash, '| gas usage percent =', gasPercent, '%', ' | Error: ', error.message);

const realExecDuration = performance.now()/1000 - execTsStart; //parseFloat(process.hrtime(execTsStart).join('.'));
const realGasUsed = toBn(Math.round(realExecDuration * cpuPricePerSecond)); // TODO: prendre en compte le storage utilisé dans le calcul du gasUsed
const realGasPercent = realGasUsed.mul(100).div(this.data.gasLimit).toNumber();
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] FAILED TRANSACTION ', this.data.hash, '| real gas usage percent =', realGasPercent, '%', ' | Error: ', error.message);

} else {
// Transaction Success

// save contract storage
Object.entries(vmStorage).forEach((entry, idx) => {
const [contractAddress, contractStorage] = entry;

const contractAccount = blockchain.getAccount(contractAddress, true);
contractAccount.data.storage = contractStorage;
contractAccount.save();
});

// show result (debug)
const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();;
console.log(utils.getDate(), "[BLOCKCHAIN]", 'SUCCECSS TRANSACTION ', this.data.hash, '| gas usage percent =', gasPercent, '%');
}


// total gas utilisé dans le block
block.data.gasUsed = block.data.gasUsed.add(gasUsed);


// assign block data on logs
txLogs.forEach((txLog, idx) => {
txLog.data.transactionIndex = transactionIndex;
txLog.data.logIndex = logIndexOffset + idx;
txLog.data.transactionHash = this.data.hash;
txLog.data.blockHash = block.hash;
txLog.data.blockNumber = block.data.number;
});


const receiptData = {
transactionHash: this.data.hash,
transactionIndex: transactionIndex,
blockNumber: block.data.number,
blockHash: block.hash,
//logsBloom: '0x0000',
contractAddress: createdContractAddress,
gasUsed,
cumulativeGasUsed: block.data.gasUsed,
effectiveGasPrice: this.data.gasPrice,
effectiveCost: this.data.gasPrice.mul(gasUsed),
logs: txLogs,
status,
}

const receipt = new Receipt(blockchain, receiptData);

return [receipt, txLogs];
}"
, executeTransactionContract: "(block) => {
let contractAddress = this.data.to;

const isNullRecipient = (! contractAddress || contractAddress.length <= 2);
if (isNullRecipient) {
throw new Error('CONTRACT_EXEC_ADDRESS_INVALID_BUG_1');
}

const contractAccount = blockchain.getAccount(contractAddress);

if (! contractAccount) {
throw new Error('CONTRACT_EXEC_ACCOUNT_NOT_FOUND');
}

// Execute existing contract method call
return blockchain.callContractMethodInVM(this.data, block);

}"
, transactionDeployContract: "(block) => {
let contractAddress = this.data.to;

let contractAccount;

if (contractAddress && contractAddress.length > 2) {
throw new Error('CONTRACT_DEPLOY_ADDRESS_INVALID_BUG_2');
}


// CONTRACT CREATION

if (! this.data.data || this.data.data.length <= 2) {
throw new Error('CONTRACT_DEPLOY_INVALID_DATA_EMPTY_CODE');
}


// build contract address
contractAddress = ethers.utils.getContractAddress(this.data).toLocaleLowerCase();

// parse contract source code & constructor parameters
const codes = ethers.utils.defaultAbiCoder.decode(["string", "string"], this.data.data);
if (! codes) {
throw new Error('CONTRACT_DEPLOY_CODE_PARSING_FAILED');
}

const sourceCode = codes[0];
const constructorParams = codes[1];
const constructorRawParams = ethers.utils.defaultAbiCoder.encode(["string"], [constructorParams]);
const codeHash = ethers.utils.id(sourceCode);
const codeAbi = null; // l'abi sera généré une fois que le contrat aura été instancié
const storageHash = ethers.utils.id('{}');

// create new account
const accountData = {
address: contractAddress,
code: sourceCode,
codeHash,
codeAbi,
storage: {},
storageHash,
};
contractAccount = blockchain.createAccount(contractAddress, accountData);


// instanciate contract + call constructor + recuperer storage
const txData = {
from: this.data.from,
to: contractAddress,
data: constructorRawParams,
value: this.data.value.toHexString(),
};
const callResult = blockchain.deployContractInVM(txData, block);
const creationLogs = callResult.logs;
const vmStorage = callResult.storage;


// emit event
const eventParams = [
contractAddress,
];
const _txData = {
...this.data,
to: blockchain.getNullAddress(),
}
const deployLog = new TxLog(blockchain, null, _txData, 'ContractCreated', eventParams, deployAbi);
creationLogs.push(deployLog);


return {
result: contractAddress,
logs: creationLogs,
storage: vmStorage,
};
}"
, timestamp: "1651572515672", status: 1, gasUsed: "17662" }, 0x242f750a46ec75c386606f20ed7d4c2a1486cf0150919aef761010e89c5b43eb: { data: { blockHash: "0x00159c1cfcf8717f506966ffcaa50f6f1ee46bf48ac7f60b1ee60979600d6436", blockNumber: "3", transactionIndex: 1, hash: "0x242f750a46ec75c386606f20ed7d4c2a1486cf0150919aef761010e89c5b43eb", from: "0x131a190fa22cb867abf58193bf9e7640e9fce682", gasLimit: "1026150", gasPrice: "1000000000", data: "0x0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000252000000000000000000000000000000000000000000000000000000000000024a72f2a0a4e616d653a20594f20546f6b656e0a56657273696f6e3a20302e302e330a2a2f0a0a0a636c617373204f776e61626c65207b0a202020206f776e6572203d2027307830303030303030303030303030303030303030303030303030303030303030303030303030303030273b0a0a202020204f776e6572736869705472616e73666572726564286f6c644f776e65722c206e65774f776e657229207b7d202f2f206576656e740a202020200a0a20202020636f6e7374727563746f722829207b0a2020202020202020746869732e6f776e6572203d206d73672e73656e6465723b0a202020207d0a0a202020206765744f776e65722829207b0a202020202020202072657475726e205b746869732e6f776e65725d3b0a202020207d0a202020200a202020207472616e736665724f776e657273686970286e65774f776e657229207b0a202020202020202072657175697265286e65774f776e657220213d20616464726573732830292c20224f776e61626c653a206e6577206f776e657220697320746865207a65726f206164647265737322293b0a2020202020202020746869732e5f7472616e736665724f776e657273686970286e65774f776e6572293b0a202020207d0a0a202020205f7472616e736665724f776e657273686970286e65774f776e657229207b0a20202020202020206e65774f776e6572203d206e65774f776e65722e746f4c6f7765724361736528293b0a2020202020202020636f6e7374206f6c644f776e6572203d20746869732e6f776e65723b0a2020202020202020746869732e6f776e6572203d206e65774f776e65723b0a2020202020202020656d697428274f776e6572736869705472616e73666572726564272c205b6f6c644f776e65722c206e65774f776e65725d293b0a202020207d0a0a202020205f6f6e6c794f776e6572286e65787429207b0a202020202020202072657175697265286d73672e73656e6465722e746f4c6f776572436173652829203d3d20746869732e6f776e65722c20276f6e6c79206f776e65722063616e2065786563757465207468697327293b0a20202020202020206e65787428293b0a202020207d0a7d0a0a2f2f204d6574686f64730a4f776e61626c652e70726f746f747970652e4f776e6572736869705472616e736665727265642e6576656e74203d20747275653b0a4f776e61626c652e70726f746f747970652e4f776e6572736869705472616e736665727265642e696e70757473203d205b276164647265737320696e6465786564272c20276164647265737320696e6465786564275d3b0a0a2f2f204d6574686f64730a4f776e61626c652e70726f746f747970652e6765744f776e65722e76696577203d20747275653b0a4f776e61626c652e70726f746f747970652e6765744f776e65722e6f757470757473203d205b2761646472657373275d3b0a0a4f776e61626c652e70726f746f747970652e7472616e736665724f776e6572736869702e696e70757473203d205b2761646472657373275d3b0a4f776e61626c652e70726f746f747970652e7472616e736665724f776e6572736869702e6d6f64696669657273203d205b275f6f6e6c794f776e6572275d3b0a0a4f776e61626c652e70726f746f747970652e5f7472616e736665724f776e6572736869702e696e7465726e616c203d20747275653b0a0a4f776e61626c652e70726f746f747970652e5f6f6e6c794f776e65722e696e7465726e616c203d20747275653b0a0a0a0a0a636c617373204552433230546f6b656e20657874656e6473204f776e61626c65207b0a202020206e616d65203d2027455243323020546f6b656e273b0a2020202073796d626f6c203d20274552433230273b0a20202020646563696d616c73203d2031383b0a20202020746f74616c537570706c79203d206e756d62657228273027293b0a0a20202020616c6c6f776564203d207b7d3b0a2020202062616c616e636573203d207b7d3b0a0a20202020737570706f72746564496e7465726661636573203d207b0a20202020202020202730783336333732623037273a20747275652c202f2f2045524332300a20202020202020202730783036666464653033273a20747275652c202f2f204552433230206e616d650a20202020202020202730783935643839623431273a20747275652c202f2f2045524332302073796d626f6c0a20202020202020202730783331336365353637273a20747275652c202f2f20455243323020646563696d616c730a202020207d3b0a0a0a202020205472616e736665722873656e6465722c20726563697069656e742c20616d6f756e7429207b7d202f2f206576656e740a20202020417070726f76616c286f776e65722c207370656e6465722c20616d6f756e7429207b7d202f2f206576656e740a202020204d696e74286d696e7465722c20616d6f756e7429207b7d202f2f206576656e740a202020204275726e286275726e65722c20616d6f756e7429207b7d202f2f206576656e740a0a0a2020202062616c616e63654f6628686f6c6465724164647265737329207b0a2020202020202020686f6c64657241646472657373203d20686f6c646572416464726573732e746f4c6f7765724361736528293b0a202020202020202076617220746f6b656e42616c616e636573203d20746869732e62616c616e636573207c7c207b7d3b0a0a202020202020202069662028686f6c6465724164647265737320696e20746f6b656e42616c616e63657329207b0a202020202020202020202020636f6e73742062616c616e6365203d20746f6b656e42616c616e6365735b686f6c646572416464726573735d3b0a20202020202020202020202072657475726e205b62616c616e63655d3b0a20202020202020207d0a202020202020202072657475726e205b6e756d6265722830295d3b0a202020207d0a0a202020207472616e7366657228726563697069656e742c20616d6f756e7429207b0a2020202020202020726563697069656e74203d20726563697069656e742e746f4c6f7765724361736528293b0a2020202020202020636f6e73742073656e646572203d206d73672e73656e6465723b0a202020202020202076617220746f6b656e42616c616e636573203d20746869732e62616c616e636573207c7c207b7d3b0a0a20202020202020206966202821202873656e64657220696e20746f6b656e42616c616e6365732929207b0a2020202020202020202020205f7468726f772827494e53554646494349454e545f544f4b454e5f42414c414e434527293b0a20202020202020207d0a202020202020202069662028746f6b656e42616c616e6365735b73656e6465725d2e6c7428616d6f756e742929207b0a2020202020202020202020205f7468726f772827494e53554646494349454e545f544f4b454e5f42414c414e434527293b0a20202020202020207d0a0a202020202020202069662028746869732e5f64656372656d656e745573657242616c616e63652e62696e642874686973292873656e6465722c20616d6f756e742929207b0a202020202020202020202020746869732e5f696e6372656d656e745573657242616c616e63652e62696e6428746869732928726563697069656e742c20616d6f756e74290a20202020202020207d0a0a2020202020202020656d697428275472616e73666572272c205b73656e6465722c20726563697069656e742c20616d6f756e745d293b0a202020202020202072657475726e205b747275655d3b0a202020207d0a0a0a202020207472616e7366657246726f6d2873656e6465722c20726563697069656e742c20616d6f756e7429207b0a202020202020202073656e646572203d2073656e6465722e746f4c6f7765724361736528293b0a2020202020202020726563697069656e74203d20726563697069656e742e746f4c6f7765724361736528293b0a20202020202020200a202020202020202076617220746f6b656e42616c616e636573203d20746869732e62616c616e636573207c7c207b7d3b0a202020202020202076617220746f6b656e416c6c6f77616e636573203d20746869732e616c6c6f776564207c7c207b7d3b0a0a2020202020202020726571756972652873656e64657220696e20746f6b656e42616c616e6365732c20274d495353494e475f544f4b454e5f42414c414e434527293b0a202020202020202069662028746f6b656e42616c616e6365735b73656e6465725d2e6c7428616d6f756e742929207b0a2020202020202020202020205f7468726f772827494e53554646494349454e545f544f4b454e5f42414c414e434527293b0a20202020202020207d0a20202020202020200a2020202020202020726571756972652873656e646572203d3d206d73672e73656e646572207c7c202873656e64657220696e20746f6b656e416c6c6f77616e636573202626206d73672e73656e64657220696e20746f6b656e416c6c6f77616e6365735b73656e6465725d292c20224d495353494e475f414c4c4f57414e434522293b0a20202020202020206966202873656e64657220213d206d73672e73656e64657220262620746f6b656e416c6c6f77616e6365735b73656e6465725d5b6d73672e73656e6465725d2e6c7428616d6f756e742929207b0a2020202020202020202020205f7468726f772827494e53554646494349454e545f544f4b454e5f414c4c4f57414e434527293b0a20202020202020207d0a0a2020202020202020746f6b656e416c6c6f77616e6365735b73656e6465725d5b6d73672e73656e6465725d203d20746f6b656e416c6c6f77616e6365735b73656e6465725d5b6d73672e73656e6465725d2e73756228616d6f756e74293b0a0a202020202020202069662028746869732e5f64656372656d656e745573657242616c616e63652e62696e642874686973292873656e6465722c20616d6f756e742929207b0a202020202020202020202020746869732e5f696e6372656d656e745573657242616c616e63652e62696e6428746869732928726563697069656e742c20616d6f756e74290a20202020202020207d0a20202020202020200a0a2020202020202020656d697428275472616e73666572272c205b73656e6465722c20726563697069656e742c20616d6f756e745d293b0a202020202020202072657475726e205b747275655d3b0a202020207d0a0a0a20202020616c6c6f77616e6365286f776e65722c207370656e64657229207b0a20202020202020206f776e6572203d206f776e65722e746f4c6f7765724361736528293b0a20202020202020207370656e646572203d207370656e6465722e746f4c6f7765724361736528293b0a202020202020202076617220746f6b656e416c6c6f77616e636573203d20746869732e616c6c6f776564207c7c207b7d3b0a2020202020202020696620286f776e657220696e20746f6b656e416c6c6f77616e636573202626207370656e64657220696e20746f6b656e416c6c6f77616e6365735b6f776e65725d29207b0a20202020202020202020202072657475726e205b746f6b656e416c6c6f77616e6365735b6f776e65725d5b7370656e6465725d5d3b0a20202020202020207d0a202020202020202072657475726e205b6e756d6265722830295d3b0a202020207d0a202020200a20202020617070726f7665287370656e6465722c2076616c756529207b0a20202020202020207370656e646572203d207370656e6465722e746f4c6f7765724361736528293b0a202020202020202076617220746f6b656e416c6c6f77616e636573203d20746869732e616c6c6f776564207c7c207b7d3b0a2020202020202020696620282120286d73672e73656e64657220696e20746f6b656e416c6c6f77616e6365732929207b0a202020202020202020202020746f6b656e416c6c6f77616e6365735b6d73672e73656e6465725d203d207b7d3b0a20202020202020207d0a2020202020202020746f6b656e416c6c6f77616e6365735b6d73672e73656e6465725d5b7370656e6465725d203d2076616c75653b0a2020202020202020656d69742827417070726f76616c272c205b6d73672e73656e6465722c207370656e6465722c2076616c75655d293b0a202020202020202072657475726e205b747275655d3b0a202020207d0a0a202020200a202020206d696e742875736572416464726573732c20616d6f756e7429207b0a2020202020202020746869732e5f6d696e742875736572416464726573732c20616d6f756e74293b0a202020207d0a0a202020205f6d696e742875736572416464726573732c20616d6f756e7429207b0a20202020202020207573657241646472657373203d2075736572416464726573732e746f4c6f7765724361736528293b0a2020202020202020696620287573657241646472657373203d3d20616464726573732830292e6164647265737329207b0a2020202020202020202020205f7468726f77282745524332303a206d696e7420746f20746865207a65726f206164647265737327293b0a20202020202020207d0a20202020202020200a202020202020202069662028746869732e5f696e6372656d656e745573657242616c616e63652e62696e642874686973292875736572416464726573732c20616d6f756e742929207b0a202020202020202020202020746869732e746f74616c537570706c79203d20746869732e746f74616c537570706c792e61646428616d6f756e74293b0a20202020202020207d0a0a2020202020202020656d697428274d696e74272c205b75736572416464726573732c20616d6f756e745d293b0a202020207d0a0a202020200a202020206275726e2875736572416464726573732c20616d6f756e7429207b0a2020202020202020746869732e5f6275726e2875736572416464726573732c20616d6f756e74293b0a202020207d0a0a202020205f6275726e2875736572416464726573732c20616d6f756e7429207b0a20202020202020207573657241646472657373203d2075736572416464726573732e746f4c6f7765724361736528293b0a2020202020202020696620287573657241646472657373203d3d20616464726573732830292e6164647265737329207b0a2020202020202020202020205f7468726f77282745524332303a206275726e2066726f6d20746865207a65726f206164647265737327293b0a20202020202020207d0a20202020202020200a202020202020202069662028746869732e5f64656372656d656e745573657242616c616e63652e62696e642874686973292875736572416464726573732c20616d6f756e742929207b0a202020202020202020202020746869732e746f74616c537570706c79203d20746869732e746f74616c537570706c792e73756228616d6f756e74293b0a20202020202020207d0a0a2020202020202020656d697428274275726e272c205b75736572416464726573732c20616d6f756e745d293b0a202020207d0a0a0a20202020737570706f727473496e7465726661636528696e74657266616365494429207b0a2020202020202020636f6e7374205f696e7465726661636573203d20746869732e737570706f72746564496e7465726661636573207c7c207b7d3b0a202020202020202072657475726e205b0a2020202020202020202020202828696e74657266616365494420696e205f696e7465726661636573290a202020202020202020202020202020203f205f696e74657266616365735b696e7465726661636549445d0a202020202020202020202020202020203a2066616c7365290a20202020202020205d3b0a202020207d0a0a0a202020205f64656372656d656e745573657242616c616e636528686f6c646572416464726573732c20616d6f756e7429207b0a202020202020202076617220746f6b656e42616c616e636573203d20746869732e62616c616e636573207c7c207b7d3b0a0a202020202020202069662028212028686f6c6465724164647265737320696e20746f6b656e42616c616e6365732929207b0a2020202020202020202020205f7468726f772827494e53554646494349454e545f544f4b454e5f42414c414e434527293b0a20202020202020207d0a202020202020202069662028616d6f756e742e677428746f6b656e42616c616e6365735b686f6c646572416464726573735d2929207b0a2020202020202020202020205f7468726f772827494e53554646494349454e545f544f4b454e5f42414c414e434527293b0a20202020202020207d0a2020202020202020746f6b656e42616c616e6365735b686f6c646572416464726573735d203d20746f6b656e42616c616e6365735b686f6c646572416464726573735d2e73756228616d6f756e74293b0a202020202020202072657475726e20747275653b0a202020207d0a0a0a202020205f696e6372656d656e745573657242616c616e636528686f6c646572416464726573732c20616d6f756e7429207b0a202020202020202076617220746f6b656e42616c616e636573203d20746869732e62616c616e636573207c7c207b7d3b0a0a202020202020202069662028212028686f6c6465724164647265737320696e20746f6b656e42616c616e6365732929207b0a202020202020202020202020746f6b656e42616c616e6365735b686f6c646572416464726573735d203d206e756d6265722830293b0a20202020202020207d0a2020202020202020746f6b656e42616c616e6365735b686f6c646572416464726573735d203d20746f6b656e42616c616e6365735b686f6c646572416464726573735d2e61646428616d6f756e74293b0a202020202020202072657475726e20747275653b0a202020207d0a0a7d0a0a2f2f204576656e74730a4552433230546f6b656e2e70726f746f747970652e5472616e736665722e6576656e74203d20747275653b0a4552433230546f6b656e2e70726f746f747970652e5472616e736665722e696e70757473203d205b276164647265737320696e6465786564272c20276164647265737320696e6465786564272c202775696e74323536275d3b0a0a4552433230546f6b656e2e70726f746f747970652e417070726f76616c2e6576656e74203d20747275653b0a4552433230546f6b656e2e70726f746f747970652e417070726f76616c2e696e70757473203d205b276164647265737320696e6465786564272c20276164647265737320696e6465786564272c202775696e74323536275d3b0a0a4552433230546f6b656e2e70726f746f747970652e4d696e742e6576656e74203d20747275653b0a4552433230546f6b656e2e70726f746f747970652e4d696e742e696e70757473203d205b276164647265737320696e6465786564272c202775696e74323536275d3b0a0a4552433230546f6b656e2e70726f746f747970652e4275726e2e6576656e74203d20747275653b0a4552433230546f6b656e2e70726f746f747970652e4275726e2e696e70757473203d205b276164647265737320696e6465786564272c202775696e74323536275d3b0a0a2f2f204d6574686f64730a4552433230546f6b656e2e70726f746f747970652e62616c616e63654f662e76696577203d20747275653b0a4552433230546f6b656e2e70726f746f747970652e62616c616e63654f662e696e70757473203d205b2761646472657373275d3b0a4552433230546f6b656e2e70726f746f747970652e62616c616e63654f662e6f757470757473203d205b2775696e74323536275d3b0a0a4552433230546f6b656e2e70726f746f747970652e7472616e736665722e696e70757473203d205b2761646472657373272c202775696e74323536275d3b0a4552433230546f6b656e2e70726f746f747970652e7472616e736665722e6f757470757473203d205b27626f6f6c275d3b0a0a4552433230546f6b656e2e70726f746f747970652e7472616e7366657246726f6d2e696e70757473203d205b2761646472657373272c202761646472657373272c202775696e74323536275d3b0a4552433230546f6b656e2e70726f746f747970652e7472616e7366657246726f6d2e6f757470757473203d205b27626f6f6c275d3b0a0a4552433230546f6b656e2e70726f746f747970652e616c6c6f77616e63652e76696577203d20747275653b0a4552433230546f6b656e2e70726f746f747970652e616c6c6f77616e63652e696e70757473203d205b2761646472657373272c202761646472657373275d3b0a4552433230546f6b656e2e70726f746f747970652e616c6c6f77616e63652e6f757470757473203d205b2775696e74323536275d3b0a0a4552433230546f6b656e2e70726f746f747970652e617070726f76652e696e70757473203d205b2761646472657373272c202775696e74323536275d3b0a4552433230546f6b656e2e70726f746f747970652e617070726f76652e6f757470757473203d205b27626f6f6c275d3b0a0a4552433230546f6b656e2e70726f746f747970652e6d696e742e696e70757473203d205b2761646472657373272c202775696e74323536275d3b0a4552433230546f6b656e2e70726f746f747970652e6d696e742e6d6f64696669657273203d205b275f6f6e6c794f776e6572275d3b0a0a4552433230546f6b656e2e70726f746f747970652e5f6d696e742e696e7465726e616c203d20747275653b0a0a4552433230546f6b656e2e70726f746f747970652e6275726e2e696e70757473203d205b2761646472657373272c202775696e74323536275d3b0a4552433230546f6b656e2e70726f746f747970652e6275726e2e6d6f64696669657273203d205b275f6f6e6c794f776e6572275d3b0a0a4552433230546f6b656e2e70726f746f747970652e5f6275726e2e696e7465726e616c203d20747275653b0a0a4552433230546f6b656e2e70726f746f747970652e737570706f727473496e746572666163652e76696577203d20747275653b0a4552433230546f6b656e2e70726f746f747970652e737570706f727473496e746572666163652e696e70757473203d205b27627974657334275d3b0a4552433230546f6b656e2e70726f746f747970652e737570706f727473496e746572666163652e6f757470757473203d205b27626f6f6c275d3b0a0a4552433230546f6b656e2e70726f746f747970652e5f64656372656d656e745573657242616c616e63652e696e7465726e616c203d20747275653b0a0a4552433230546f6b656e2e70726f746f747970652e5f696e6372656d656e745573657242616c616e63652e696e7465726e616c203d20747275653b0a0a0a0a0a636c61737320596f546f6b656e20657874656e6473204552433230546f6b656e207b2020200a202020206e616d65203d2027594f20546f6b656e273b0a2020202073796d626f6c203d2027594f273b0a0a20202020737461636b696e6753657276696365203d2027273b0a0a0a20202020696e697469616c697a6528737461636b696e67536572766963654164647265737329207b0a20202020202020202f2f7265717569726528746869732e737461636b696e6753657276696365203d3d2027272c2022414c52454144595f494e495449414c495a454422293b0a2020202020202020746869732e737461636b696e6753657276696365203d20737461636b696e6753657276696365416464726573732e746f4c6f7765724361736528293b0a0a2020202020202020636f6e737420696e6974416d6f756e74203d206e756d6265722827343230303030303027202b202730303030303030303030303030303030303027293b202f2f203432203030302030303020594f0a2020202020202020746869732e5f6d696e74286d73672e73656e6465722c20696e6974416d6f756e74293b0a0a2020202020202020656d69742827496e697469616c697a65272c205b737461636b696e6753657276696365416464726573735d293b0a202020207d0a0a0a20202020496e697469616c697a6528737461636b696e67536572766963654164647265737329207b7d202f2f206576656e740a0a0a202020206d696e7428726563697069656e742c20616d6f756e74546f4d696e7429207b0a20202020202020207265717569726528746869732e737461636b696e675365727669636520213d2027272c20224e4f545f494e495449414c495a454422293b0a202020202020202072657175697265286d73672e73656e646572203d3d20746869732e737461636b696e67536572766963652c20224f4e4c595f535441434b494e475f5345525649434522293b0a2020202020202020746869732e5f6d696e7428726563697069656e742c20616d6f756e74546f4d696e74293b0a202020207d0a0a7d0a0a0a2f2f204576656e74730a596f546f6b656e2e70726f746f747970652e496e697469616c697a652e6576656e74203d20747275653b0a596f546f6b656e2e70726f746f747970652e496e697469616c697a652e696e70757473203d205b276164647265737320696e6465786564275d3b0a0a2f2f204d6574686f64730a596f546f6b656e2e70726f746f747970652e696e697469616c697a652e696e70757473203d205b2761646472657373275d3b0a2f2f596f546f6b656e2e70726f746f747970652e696e697469616c697a652e6d6f64696669657273203d205b275f6f6e6c794f776e6572275d3b0a0a596f546f6b656e2e70726f746f747970652e6d696e742e696e70757473203d205b2761646472657373272c202775696e74323536275d3b0a0a0a0a72657475726e20596f546f6b656e3b0a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000025b5d000000000000000000000000000000000000000000000000000000000000", nonce: "2", to: "0x", value: "0", chainId: "3615", origin: "1651572512663", v: "1c61", r: "ba56678227bb332eb4d1c3f12cc364089430e2cb540e18d039f5f052c6d5bb4b", s: "38b0c26a130a04692fc548a89f8e69704dc4cf4133c357cd511ecd0cbb3717d5" }, hash: "0x242f750a46ec75c386606f20ed7d4c2a1486cf0150919aef761010e89c5b43eb", load: "() => {
const transactionFile = config.node.dataDir + '/transactions/' + this.hash + '.json';

if (! fs.existsSync(transactionFile)) {
console.warn(utils.getDate(), "[BLOCKCHAIN]", 'IMPORT Transaction not found', this.hash);
throw new Error('IMPORT_TRANSACTION_NOT_FOUND_DATABASE_CORRUPTED');
}

const transactionJson = fs.readFileSync(transactionFile);
transactionData = JSON.parse(transactionJson, utils.bnJsonParser);

this.data = (typeof transactionData == 'TransactionData') ? transactionData : new TransactionData(transactionData);

return true;
}"
, save: "() => {
const transactionFile = config.node.dataDir + '/transactions/' + this.hash + '.json';
fs.writeFileSync(transactionFile, JSON.stringify(this.data));
return true;
}"
, getReceipt: "() => {
return blockchain.getTransactionReceiptByHash(this.hash);
}"
, getBlock: "() => {
return blockchain.getBlockByHash(this.data.blockHash);
}"
, getBlockTransationPrev: "() => {
const block = this.getBlock();
const txPrevIdx = (this.data.transactionIndex > 0) ? (this.data.transactionIndex-1) : null;
const tx = (block && txPrevIdx in block.transactions) ? block.transactions[txPrevIdx] : null;
return tx;
}"
, getBlockTransationNext: "() => {
const block = this.getBlock();
const txNextIdx = (this.data.transactionIndex < block.transactions.length - 1) ? (this.data.transactionIndex+1) : null;
const tx = (block && txNextIdx in block.transactions) ? block.transactions[txNextIdx] : null;
return tx;
}"
, getAccountTransactionIndex: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = account.data.transactions.findIndex(transactionHash => blockchain.getTransactionByHash(transactionHash).hash == this.hash);
if (txIdx === -1) {
return null;
}
return txIdx;
}"
, getAccountTransationPrev: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = this.getAccountTransactionIndex();
if (txIdx === null) {
return null;
}
const txPrevIdx = (txIdx > 0) ? (txIdx-1) : null;
const transactionHash = (txPrevIdx in account.data.transactions) ? account.data.transactions[txPrevIdx] : null;
if (! transactionHash) {
return null;
}
return blockchain.getTransactionByHash(transactionHash);
}"
, getAccountTransationNext: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = this.getAccountTransactionIndex();
if (txIdx === null) {
return null;
}
const txNextIdx = (txIdx < account.data.transactions.length - 1) ? (txIdx+1) : null;
const transactionHash = (txNextIdx in account.data.transactions) ? account.data.transactions[txNextIdx] : null;
if (! transactionHash) {
return null;
}
return blockchain.getTransactionByHash(transactionHash);

}"
, formatRpc: "() => {
// https://github.com/ethers-io/ethers.js/blob/master/packages/abstract-provider/lib/index.d.ts

const to = (this.data.to && this.data.to.length == 42) ? this.data.to : null;

const _txData = {
blockHash: this.data.blockHash,
blockNumber: this.data.blockNumber.toNumber(),
transactionIndex: this.data.transactionIndex,
hash: this.data.hash,
from: this.data.from,
gasLimit: this.data.gasLimit.toHexString(),
gasPrice: this.data.gasPrice.toHexString(),
data: this.data.data,
nonce: this.data.nonce.toHexString(),
to,
value: this.data.value.toHexString(),
chainId: this.data.chainId.toNumber(),
//origin: this.data.origin.toHexString(),
};

return _txData;
}"
, getHashOLD: "() => {
const _txData = {
gasPrice: this.data.gasPrice,
gasLimit: this.data.gasLimit,
from: this.data.from,
to: this.data.to,
value: this.data.value,
data: this.data.data,
nonce: this.data.nonce,
};

const strvalue = JSON.stringify(utils.orderedObject(_txData));
const transactionHash = ethers.utils.id(strvalue);

return transactionHash;
}"
, getHash: "() => {
const _txData = {
gasPrice: this.data.gasPrice.toHexString(),
gasLimit: this.data.gasLimit.toHexString(),
from: this.data.from,
to: this.data.to,
value: this.data.value.toHexString(),
data: this.data.data,
nonce: this.data.nonce.toHexString(),
v: this.data.v ? ('0x' + this.data.v) : '',
r: this.data.r ? ('0x' + this.data.r) : '',
s: this.data.s ? ('0x' + this.data.s) : '',
};

const tx = new EthereumJS.Transaction(_txData);
const hashRaw = tx.hash();

const transactionHash = ethers.utils.defaultAbiCoder.decode(["uint256"], hashRaw)[0].toHexString();

return transactionHash;
}"
, execute: "(block, transactionIndex, logIndexOffset) => {
const _tx = this;

function _rejectTransaction(reason) {
// https://openethereum.github.io/Transactions-Queue
reason = reason || 'unknown';
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] REJECTED PROCESSING TRANSACTION', _tx.data.hash, ' (Reason:', reason + ')');
const success = blockchain.removeTransactionsFromMempool([_tx.hash], true, true);
if (success) {
saveMempool();
}
throw new Error(reason); // on rejete definitivement la tx (elle ne sera pas minée ... et donc aucun frais de gas ne sera compté)
// et du coup on rejete le block
}

function _checkGas(step) {
execDuration = performance.now()/1000 - execTsStart; //parseFloat(process.hrtime(execTsStart).join('.'));
gasUsed = toBn(Math.round(execDuration * cpuPricePerSecond)); // TODO: prendre en compte le storage utilisé dans le calcul du gasUsed
txCost = gasUsed.mul(_tx.data.gasPrice);

if (step == 'afterTransfer') {
//debugger;
}

if (status) {
gasUsedEffective = gasUsed;
txCostEffective = gasUsedEffective.mul(_tx.data.gasPrice);

if (gasUsed.gt(_tx.data.gasLimit)) {
status = 0;
error = new Error('INSUFFICIENT_GAS');

gasUsedEffective = _tx.data.gasLimit;
txCostEffective = txCostLimit;

// emit event
const debugError = {
message: 'INSUFFICIENT_GAS',
step,
gasPrice: _tx.data.gasPrice,
gasLimit: _tx.data.gasLimit,
gasUsed,
gasUsedEffective,
txCostLimit,
txCost,
txCostEffective,
execDuration,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, _tx.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

}
}


//const block = this;
const txLogs = [];
let vmStorage = {};

let gasUsed = toBn(0);
let gasUsedEffective = toBn(0);
let status = 1;
let error = null;
let createdContractAddress = null;
let amountTransfered = toBn(0);

const contractAddress = this.data.to;
let storageBackup;
let balanceBackup;
let contractExecuted = false;
let contractDeployed = false;

const execTsStart = performance.now()/1000; //process.hrtime(); //Date.now();
let execDuration = 0;

const txCostLimit = this.data.gasLimit.mul(this.data.gasPrice);
let txCost = toBn(0);
let txCostEffective = toBn(0);


const accountFrom = blockchain.getAccount(this.data.from);
if (! accountFrom) {
_rejectTransaction('TRANSACTION_REJECTED_EMPTY_ACCOUNT');
}
const accountFromBalance = accountFrom.data.balance;


if (accountFrom.address != mintAddress) {

// verif nonce
const expectedNonce = toBn(accountFrom.data.transactions.length); // le nonce attendu est egal au nombre de transactions passées de l'account
if (! this.data.nonce.eq(expectedNonce)) {
_rejectTransaction('TRANSACTION_REJECTED_INCORRECT_NONCE');
}

// verif balance pour gas + transfer
if (this.data.value && this.data.value.gt(0)) {
if (accountFromBalance.lt(this.data.value.add(txCostLimit))) {
// l'adresse n'a pas suffisamment de fond pour executer la transaction (selon gasLimit)
// TODO: faut-il rejeter la tx ? ou la laisser passer (en status=0) et lui vider sa balance (si 100% du gas est utilisé) ?
//return _rejectTransaction('TRANSACTION_FAILED_INSUFFICIENT_ETH_BALANCE');

_rejectTransaction('TRANSACTION_REJECTED_INSUFFICIENT_ETH_BALANCE');
}
}
}



_checkGas('init');


if (status) {
if (this.data.value && this.data.value.gt(0) && contractAddress && contractAddress.length > 2) {
// Transfer la valeur demandée (simple ETH transaction)
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(this.data, block);
txLogs.push( ...transferLogs );

// si recipient == contract => on transfer quand meme dès maintenant => TODO: a revoir (le transferEth declenche un appel à receive ... avant meme de savoir si unne methode payable doit etre appelée. Probleme si receive provoque un revert => la fonction payable ne sera pas appelée)
// 1) si methode payable appellée, on l'execute
// 2) si methode non payable => on appelle receive juste avant
// 3) si methode inconnue => revert


if (transferResult && transferReverted) {
// reverted (after transfer)
amountTransfered = this.data.value;

status = 0;
error = new Error('TRANSFER_REVERTED');

} else if (transferResult) {
// success
amountTransfered = this.data.value;

} else {
// transfer failed
status = 0;
error = new Error('TRANSFER_FAILED');
}

}

_checkGas('afterTransfer');
}

const gasUsedBeforeContractExecution = gasUsed;

if (status && this.data.data && this.data.data != '0x') {
// Traitement du champ data de la transaction (smart contract call)

if (!contractAddress || contractAddress == '0x') {
// contract creation

contractDeployed = true;

let contractExecResult;
try {
contractExecResult = this.transactionDeployContract(block);
const creationLogs = contractExecResult.logs;
vmStorage = contractExecResult.storage;

if (creationLogs.length) {
txLogs.push( ...creationLogs );
}

if (! contractExecResult.result) {
// Deploy Failed
status = 0;
error = new Error('CONTRACT_CREATION_FAILED');

// emit event
const debugError = {
message: 'CONTRACT_CREATION_FAILED',
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);

} else {
// Deploy Success
createdContractAddress = contractExecResult.result.toLowerCase();

if (this.data.value.gt(0)) {
// Transfer tx value to contract
const _txData = {
...this.data,
to: createdContractAddress,
};
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(_txData, block);
txLogs.push( ...transferLogs );

if (transferResult) {
amountTransfered = this.data.value;

} else {
status = 0;
error = new Error('CONTRACT_CREATION_TRANSFER_FAILED');
}
}

// TODO: emit event CONTRACT_CREATION_SUCCESS ?
}



} catch (e) {
//console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] Cannot execute transaction');
//throw e;
status = 0;
error = new Error('CONTRACT_CREATION_ERROR');

console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] CONTRACT_CREATION_ERROR with message', e.message)

// emit event
const debugError = {
message: 'CONTRACT_CREATION_ERROR',
error: e.message,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

_checkGas('afterContractCreation');

} else {
// contract execution

const contractAccount = blockchain.getAccount(contractAddress);

if (! contractAccount) {
status = 0;
error = new Error('CONTRACT_ACCOUNT_NOT_FOUND');

// emit event
const debugError = {
message: 'CONTRACT_ACCOUNT_NOT_FOUND',
address: contractAddress,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

if (status) {
storageBackup = JSON.stringify(contractAccount.data.storage);
balanceBackup = contractAccount.data.balance;
contractExecuted = true;

try {
const contractExecResult = this.executeTransactionContract(block);
const internalLogs = contractExecResult.logs;
vmStorage = contractExecResult.storage;

if (internalLogs.length) {
txLogs.push( ...internalLogs );
}

if (! contractExecResult.result) {
status = 0;
error = new Error('CONTRACT_EXEC_FAILED');

// emit event
const debugError = {
message: 'CONTRACT_EXEC_FAILED',
address: contractAddress,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

} catch (e) {
status = 0;
error = new Error('CONTRACT_EXEC_ERROR');

// emit event
const debugError = {
message: 'CONTRACT_EXEC_ERROR',
address: contractAddress,
error: e.message,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}
}

_checkGas('afterContractCall');
}

}

const gasUsedAfterContractExecution = gasUsed;
const gasUsedInContractExecution = gasUsedAfterContractExecution.sub(gasUsedBeforeContractExecution);
const contractExecutionCost = gasUsedInContractExecution.mul(_tx.data.gasPrice);


console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] execDuration =', (Math.round(execDuration * 1000000) / 1000000), ' seconds');



if (txCostEffective.gt(0)) {
// Gas payment
//const minerAccount = blockchain.getAccount(block.data.miner, true);
//const gasPaymentResult = minerAccount.transferFrom(accountFrom.address, txCostEffective);

const txDataGasPayment = {
from: accountFrom.address,
to: block.data.miner,
value: txCostEffective,
};
const [gasPaymentResult, gasPaymentLogs, transferReverted] = blockchain.transferEth(txDataGasPayment, block, true);
txLogs.push(...gasPaymentLogs);

if (gasPaymentResult) {
// gas payment success

} else {
// gas payment failed => reject transaction (and block)
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] Gas payment failed');
_rejectTransaction('GAS_PAYMENT_FAILED');
}

if (false) {
// DEBUG
const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();
const gasEffectivePercent = gasUsedEffective.mul(100).div(this.data.gasLimit).toNumber();
const costPercent = txCost.mul(100).div(txCostLimit).toNumber();
const costEffectivePercent = txCostEffective.mul(100).div(txCostLimit).toNumber();
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] txCostLimit =', ethers.utils.formatEther(txCostLimit));
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] txCostEffective =', ethers.utils.formatEther(txCostEffective));
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] gas % =', Math.round(gasPercent), '% =', Math.round(gasEffectivePercent), '%');
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] cost % =', Math.round(costPercent), '%', Math.round(costEffectivePercent), '%');
}


}


if (accountFrom.address != mintAddress) {
const accountFromBalanceAtEnd = blockchain.getAccountBalance(accountFrom.address);

if (accountFromBalanceAtEnd.lt(0)){
_rejectTransaction('TRANSACTION_FAILED_INSUFFICIENT_ETH_BALANCE_AT_END');
}
}


if (! status) {
// Transaction Failed

// Revert transfer
if (amountTransfered.gt(0)) {
const txDataRevert = {
...this.data,
from: this.data.to,
to: this.data.from,
value: amountTransfered,
};
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(txDataRevert, block);

// TODO: gerer le revert des internal transfers
}

txLogs.forEach(log => {
log.removed = true;
});

// Revert contract storage
if (contractExecuted) {
const contractAccount = blockchain.getAccount(contractAddress);
contractAccount.data.storage = JSON.parse(storageBackup, utils.bnJsonParser);
contractAccount.data.balance = balanceBackup;

} else if (amountTransfered) {
// on check aussi amountTransfered car le contrat peut avoir été exécuté via receive => TODO: process a revoir
const contractAccount = blockchain.getAccount(contractAddress);
if (contractAccount && contractAccount.isContract()) {
//contractAccount.data.storage = JSON.parse(storageBackup, utils.bnJsonParser);
//contractAccount.data.balance = balanceBackup;
// TODO
}

}

// Revert deployed contract
if (contractDeployed) {
// nothing to do ?
}

const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] FAILED TRANSACTION ', this.data.hash, '| gas usage percent =', gasPercent, '%', ' | Error: ', error.message);

const realExecDuration = performance.now()/1000 - execTsStart; //parseFloat(process.hrtime(execTsStart).join('.'));
const realGasUsed = toBn(Math.round(realExecDuration * cpuPricePerSecond)); // TODO: prendre en compte le storage utilisé dans le calcul du gasUsed
const realGasPercent = realGasUsed.mul(100).div(this.data.gasLimit).toNumber();
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] FAILED TRANSACTION ', this.data.hash, '| real gas usage percent =', realGasPercent, '%', ' | Error: ', error.message);

} else {
// Transaction Success

// save contract storage
Object.entries(vmStorage).forEach((entry, idx) => {
const [contractAddress, contractStorage] = entry;

const contractAccount = blockchain.getAccount(contractAddress, true);
contractAccount.data.storage = contractStorage;
contractAccount.save();
});

// show result (debug)
const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();;
console.log(utils.getDate(), "[BLOCKCHAIN]", 'SUCCECSS TRANSACTION ', this.data.hash, '| gas usage percent =', gasPercent, '%');
}


// total gas utilisé dans le block
block.data.gasUsed = block.data.gasUsed.add(gasUsed);


// assign block data on logs
txLogs.forEach((txLog, idx) => {
txLog.data.transactionIndex = transactionIndex;
txLog.data.logIndex = logIndexOffset + idx;
txLog.data.transactionHash = this.data.hash;
txLog.data.blockHash = block.hash;
txLog.data.blockNumber = block.data.number;
});


const receiptData = {
transactionHash: this.data.hash,
transactionIndex: transactionIndex,
blockNumber: block.data.number,
blockHash: block.hash,
//logsBloom: '0x0000',
contractAddress: createdContractAddress,
gasUsed,
cumulativeGasUsed: block.data.gasUsed,
effectiveGasPrice: this.data.gasPrice,
effectiveCost: this.data.gasPrice.mul(gasUsed),
logs: txLogs,
status,
}

const receipt = new Receipt(blockchain, receiptData);

return [receipt, txLogs];
}"
, executeTransactionContract: "(block) => {
let contractAddress = this.data.to;

const isNullRecipient = (! contractAddress || contractAddress.length <= 2);
if (isNullRecipient) {
throw new Error('CONTRACT_EXEC_ADDRESS_INVALID_BUG_1');
}

const contractAccount = blockchain.getAccount(contractAddress);

if (! contractAccount) {
throw new Error('CONTRACT_EXEC_ACCOUNT_NOT_FOUND');
}

// Execute existing contract method call
return blockchain.callContractMethodInVM(this.data, block);

}"
, transactionDeployContract: "(block) => {
let contractAddress = this.data.to;

let contractAccount;

if (contractAddress && contractAddress.length > 2) {
throw new Error('CONTRACT_DEPLOY_ADDRESS_INVALID_BUG_2');
}


// CONTRACT CREATION

if (! this.data.data || this.data.data.length <= 2) {
throw new Error('CONTRACT_DEPLOY_INVALID_DATA_EMPTY_CODE');
}


// build contract address
contractAddress = ethers.utils.getContractAddress(this.data).toLocaleLowerCase();

// parse contract source code & constructor parameters
const codes = ethers.utils.defaultAbiCoder.decode(["string", "string"], this.data.data);
if (! codes) {
throw new Error('CONTRACT_DEPLOY_CODE_PARSING_FAILED');
}

const sourceCode = codes[0];
const constructorParams = codes[1];
const constructorRawParams = ethers.utils.defaultAbiCoder.encode(["string"], [constructorParams]);
const codeHash = ethers.utils.id(sourceCode);
const codeAbi = null; // l'abi sera généré une fois que le contrat aura été instancié
const storageHash = ethers.utils.id('{}');

// create new account
const accountData = {
address: contractAddress,
code: sourceCode,
codeHash,
codeAbi,
storage: {},
storageHash,
};
contractAccount = blockchain.createAccount(contractAddress, accountData);


// instanciate contract + call constructor + recuperer storage
const txData = {
from: this.data.from,
to: contractAddress,
data: constructorRawParams,
value: this.data.value.toHexString(),
};
const callResult = blockchain.deployContractInVM(txData, block);
const creationLogs = callResult.logs;
const vmStorage = callResult.storage;


// emit event
const eventParams = [
contractAddress,
];
const _txData = {
...this.data,
to: blockchain.getNullAddress(),
}
const deployLog = new TxLog(blockchain, null, _txData, 'ContractCreated', eventParams, deployAbi);
creationLogs.push(deployLog);


return {
result: contractAddress,
logs: creationLogs,
storage: vmStorage,
};
}"
, timestamp: "1651572515672", status: 1, gasUsed: "8192" }, 0x27e73c42af396d9235a8853bbcefb4616a038980141c7a5b4c38ee527ff91477: { data: { blockHash: "0x00984edd40de5df064bb772c113d288d9d12599ba750074e6150184f409f87c5", blockNumber: "4", transactionIndex: 0, hash: "0x27e73c42af396d9235a8853bbcefb4616a038980141c7a5b4c38ee527ff91477", from: "0x0000000000000000000000000000000000000001", gasLimit: "1000000000000000000", gasPrice: "0", data: "0x", nonce: "3", to: "0xccf8ba457dcad7ee6a0361c96846a0f79744b113", value: "250000000000000000000", chainId: "3615", origin: "1651572520847", v: "", r: "", s: "" }, hash: "0x27e73c42af396d9235a8853bbcefb4616a038980141c7a5b4c38ee527ff91477", load: "() => {
const transactionFile = config.node.dataDir + '/transactions/' + this.hash + '.json';

if (! fs.existsSync(transactionFile)) {
console.warn(utils.getDate(), "[BLOCKCHAIN]", 'IMPORT Transaction not found', this.hash);
throw new Error('IMPORT_TRANSACTION_NOT_FOUND_DATABASE_CORRUPTED');
}

const transactionJson = fs.readFileSync(transactionFile);
transactionData = JSON.parse(transactionJson, utils.bnJsonParser);

this.data = (typeof transactionData == 'TransactionData') ? transactionData : new TransactionData(transactionData);

return true;
}"
, save: "() => {
const transactionFile = config.node.dataDir + '/transactions/' + this.hash + '.json';
fs.writeFileSync(transactionFile, JSON.stringify(this.data));
return true;
}"
, getReceipt: "() => {
return blockchain.getTransactionReceiptByHash(this.hash);
}"
, getBlock: "() => {
return blockchain.getBlockByHash(this.data.blockHash);
}"
, getBlockTransationPrev: "() => {
const block = this.getBlock();
const txPrevIdx = (this.data.transactionIndex > 0) ? (this.data.transactionIndex-1) : null;
const tx = (block && txPrevIdx in block.transactions) ? block.transactions[txPrevIdx] : null;
return tx;
}"
, getBlockTransationNext: "() => {
const block = this.getBlock();
const txNextIdx = (this.data.transactionIndex < block.transactions.length - 1) ? (this.data.transactionIndex+1) : null;
const tx = (block && txNextIdx in block.transactions) ? block.transactions[txNextIdx] : null;
return tx;
}"
, getAccountTransactionIndex: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = account.data.transactions.findIndex(transactionHash => blockchain.getTransactionByHash(transactionHash).hash == this.hash);
if (txIdx === -1) {
return null;
}
return txIdx;
}"
, getAccountTransationPrev: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = this.getAccountTransactionIndex();
if (txIdx === null) {
return null;
}
const txPrevIdx = (txIdx > 0) ? (txIdx-1) : null;
const transactionHash = (txPrevIdx in account.data.transactions) ? account.data.transactions[txPrevIdx] : null;
if (! transactionHash) {
return null;
}
return blockchain.getTransactionByHash(transactionHash);
}"
, getAccountTransationNext: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = this.getAccountTransactionIndex();
if (txIdx === null) {
return null;
}
const txNextIdx = (txIdx < account.data.transactions.length - 1) ? (txIdx+1) : null;
const transactionHash = (txNextIdx in account.data.transactions) ? account.data.transactions[txNextIdx] : null;
if (! transactionHash) {
return null;
}
return blockchain.getTransactionByHash(transactionHash);

}"
, formatRpc: "() => {
// https://github.com/ethers-io/ethers.js/blob/master/packages/abstract-provider/lib/index.d.ts

const to = (this.data.to && this.data.to.length == 42) ? this.data.to : null;

const _txData = {
blockHash: this.data.blockHash,
blockNumber: this.data.blockNumber.toNumber(),
transactionIndex: this.data.transactionIndex,
hash: this.data.hash,
from: this.data.from,
gasLimit: this.data.gasLimit.toHexString(),
gasPrice: this.data.gasPrice.toHexString(),
data: this.data.data,
nonce: this.data.nonce.toHexString(),
to,
value: this.data.value.toHexString(),
chainId: this.data.chainId.toNumber(),
//origin: this.data.origin.toHexString(),
};

return _txData;
}"
, getHashOLD: "() => {
const _txData = {
gasPrice: this.data.gasPrice,
gasLimit: this.data.gasLimit,
from: this.data.from,
to: this.data.to,
value: this.data.value,
data: this.data.data,
nonce: this.data.nonce,
};

const strvalue = JSON.stringify(utils.orderedObject(_txData));
const transactionHash = ethers.utils.id(strvalue);

return transactionHash;
}"
, getHash: "() => {
const _txData = {
gasPrice: this.data.gasPrice.toHexString(),
gasLimit: this.data.gasLimit.toHexString(),
from: this.data.from,
to: this.data.to,
value: this.data.value.toHexString(),
data: this.data.data,
nonce: this.data.nonce.toHexString(),
v: this.data.v ? ('0x' + this.data.v) : '',
r: this.data.r ? ('0x' + this.data.r) : '',
s: this.data.s ? ('0x' + this.data.s) : '',
};

const tx = new EthereumJS.Transaction(_txData);
const hashRaw = tx.hash();

const transactionHash = ethers.utils.defaultAbiCoder.decode(["uint256"], hashRaw)[0].toHexString();

return transactionHash;
}"
, execute: "(block, transactionIndex, logIndexOffset) => {
const _tx = this;

function _rejectTransaction(reason) {
// https://openethereum.github.io/Transactions-Queue
reason = reason || 'unknown';
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] REJECTED PROCESSING TRANSACTION', _tx.data.hash, ' (Reason:', reason + ')');
const success = blockchain.removeTransactionsFromMempool([_tx.hash], true, true);
if (success) {
saveMempool();
}
throw new Error(reason); // on rejete definitivement la tx (elle ne sera pas minée ... et donc aucun frais de gas ne sera compté)
// et du coup on rejete le block
}

function _checkGas(step) {
execDuration = performance.now()/1000 - execTsStart; //parseFloat(process.hrtime(execTsStart).join('.'));
gasUsed = toBn(Math.round(execDuration * cpuPricePerSecond)); // TODO: prendre en compte le storage utilisé dans le calcul du gasUsed
txCost = gasUsed.mul(_tx.data.gasPrice);

if (step == 'afterTransfer') {
//debugger;
}

if (status) {
gasUsedEffective = gasUsed;
txCostEffective = gasUsedEffective.mul(_tx.data.gasPrice);

if (gasUsed.gt(_tx.data.gasLimit)) {
status = 0;
error = new Error('INSUFFICIENT_GAS');

gasUsedEffective = _tx.data.gasLimit;
txCostEffective = txCostLimit;

// emit event
const debugError = {
message: 'INSUFFICIENT_GAS',
step,
gasPrice: _tx.data.gasPrice,
gasLimit: _tx.data.gasLimit,
gasUsed,
gasUsedEffective,
txCostLimit,
txCost,
txCostEffective,
execDuration,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, _tx.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

}
}


//const block = this;
const txLogs = [];
let vmStorage = {};

let gasUsed = toBn(0);
let gasUsedEffective = toBn(0);
let status = 1;
let error = null;
let createdContractAddress = null;
let amountTransfered = toBn(0);

const contractAddress = this.data.to;
let storageBackup;
let balanceBackup;
let contractExecuted = false;
let contractDeployed = false;

const execTsStart = performance.now()/1000; //process.hrtime(); //Date.now();
let execDuration = 0;

const txCostLimit = this.data.gasLimit.mul(this.data.gasPrice);
let txCost = toBn(0);
let txCostEffective = toBn(0);


const accountFrom = blockchain.getAccount(this.data.from);
if (! accountFrom) {
_rejectTransaction('TRANSACTION_REJECTED_EMPTY_ACCOUNT');
}
const accountFromBalance = accountFrom.data.balance;


if (accountFrom.address != mintAddress) {

// verif nonce
const expectedNonce = toBn(accountFrom.data.transactions.length); // le nonce attendu est egal au nombre de transactions passées de l'account
if (! this.data.nonce.eq(expectedNonce)) {
_rejectTransaction('TRANSACTION_REJECTED_INCORRECT_NONCE');
}

// verif balance pour gas + transfer
if (this.data.value && this.data.value.gt(0)) {
if (accountFromBalance.lt(this.data.value.add(txCostLimit))) {
// l'adresse n'a pas suffisamment de fond pour executer la transaction (selon gasLimit)
// TODO: faut-il rejeter la tx ? ou la laisser passer (en status=0) et lui vider sa balance (si 100% du gas est utilisé) ?
//return _rejectTransaction('TRANSACTION_FAILED_INSUFFICIENT_ETH_BALANCE');

_rejectTransaction('TRANSACTION_REJECTED_INSUFFICIENT_ETH_BALANCE');
}
}
}



_checkGas('init');


if (status) {
if (this.data.value && this.data.value.gt(0) && contractAddress && contractAddress.length > 2) {
// Transfer la valeur demandée (simple ETH transaction)
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(this.data, block);
txLogs.push( ...transferLogs );

// si recipient == contract => on transfer quand meme dès maintenant => TODO: a revoir (le transferEth declenche un appel à receive ... avant meme de savoir si unne methode payable doit etre appelée. Probleme si receive provoque un revert => la fonction payable ne sera pas appelée)
// 1) si methode payable appellée, on l'execute
// 2) si methode non payable => on appelle receive juste avant
// 3) si methode inconnue => revert


if (transferResult && transferReverted) {
// reverted (after transfer)
amountTransfered = this.data.value;

status = 0;
error = new Error('TRANSFER_REVERTED');

} else if (transferResult) {
// success
amountTransfered = this.data.value;

} else {
// transfer failed
status = 0;
error = new Error('TRANSFER_FAILED');
}

}

_checkGas('afterTransfer');
}

const gasUsedBeforeContractExecution = gasUsed;

if (status && this.data.data && this.data.data != '0x') {
// Traitement du champ data de la transaction (smart contract call)

if (!contractAddress || contractAddress == '0x') {
// contract creation

contractDeployed = true;

let contractExecResult;
try {
contractExecResult = this.transactionDeployContract(block);
const creationLogs = contractExecResult.logs;
vmStorage = contractExecResult.storage;

if (creationLogs.length) {
txLogs.push( ...creationLogs );
}

if (! contractExecResult.result) {
// Deploy Failed
status = 0;
error = new Error('CONTRACT_CREATION_FAILED');

// emit event
const debugError = {
message: 'CONTRACT_CREATION_FAILED',
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);

} else {
// Deploy Success
createdContractAddress = contractExecResult.result.toLowerCase();

if (this.data.value.gt(0)) {
// Transfer tx value to contract
const _txData = {
...this.data,
to: createdContractAddress,
};
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(_txData, block);
txLogs.push( ...transferLogs );

if (transferResult) {
amountTransfered = this.data.value;

} else {
status = 0;
error = new Error('CONTRACT_CREATION_TRANSFER_FAILED');
}
}

// TODO: emit event CONTRACT_CREATION_SUCCESS ?
}



} catch (e) {
//console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] Cannot execute transaction');
//throw e;
status = 0;
error = new Error('CONTRACT_CREATION_ERROR');

console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] CONTRACT_CREATION_ERROR with message', e.message)

// emit event
const debugError = {
message: 'CONTRACT_CREATION_ERROR',
error: e.message,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

_checkGas('afterContractCreation');

} else {
// contract execution

const contractAccount = blockchain.getAccount(contractAddress);

if (! contractAccount) {
status = 0;
error = new Error('CONTRACT_ACCOUNT_NOT_FOUND');

// emit event
const debugError = {
message: 'CONTRACT_ACCOUNT_NOT_FOUND',
address: contractAddress,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

if (status) {
storageBackup = JSON.stringify(contractAccount.data.storage);
balanceBackup = contractAccount.data.balance;
contractExecuted = true;

try {
const contractExecResult = this.executeTransactionContract(block);
const internalLogs = contractExecResult.logs;
vmStorage = contractExecResult.storage;

if (internalLogs.length) {
txLogs.push( ...internalLogs );
}

if (! contractExecResult.result) {
status = 0;
error = new Error('CONTRACT_EXEC_FAILED');

// emit event
const debugError = {
message: 'CONTRACT_EXEC_FAILED',
address: contractAddress,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

} catch (e) {
status = 0;
error = new Error('CONTRACT_EXEC_ERROR');

// emit event
const debugError = {
message: 'CONTRACT_EXEC_ERROR',
address: contractAddress,
error: e.message,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}
}

_checkGas('afterContractCall');
}

}

const gasUsedAfterContractExecution = gasUsed;
const gasUsedInContractExecution = gasUsedAfterContractExecution.sub(gasUsedBeforeContractExecution);
const contractExecutionCost = gasUsedInContractExecution.mul(_tx.data.gasPrice);


console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] execDuration =', (Math.round(execDuration * 1000000) / 1000000), ' seconds');



if (txCostEffective.gt(0)) {
// Gas payment
//const minerAccount = blockchain.getAccount(block.data.miner, true);
//const gasPaymentResult = minerAccount.transferFrom(accountFrom.address, txCostEffective);

const txDataGasPayment = {
from: accountFrom.address,
to: block.data.miner,
value: txCostEffective,
};
const [gasPaymentResult, gasPaymentLogs, transferReverted] = blockchain.transferEth(txDataGasPayment, block, true);
txLogs.push(...gasPaymentLogs);

if (gasPaymentResult) {
// gas payment success

} else {
// gas payment failed => reject transaction (and block)
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] Gas payment failed');
_rejectTransaction('GAS_PAYMENT_FAILED');
}

if (false) {
// DEBUG
const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();
const gasEffectivePercent = gasUsedEffective.mul(100).div(this.data.gasLimit).toNumber();
const costPercent = txCost.mul(100).div(txCostLimit).toNumber();
const costEffectivePercent = txCostEffective.mul(100).div(txCostLimit).toNumber();
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] txCostLimit =', ethers.utils.formatEther(txCostLimit));
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] txCostEffective =', ethers.utils.formatEther(txCostEffective));
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] gas % =', Math.round(gasPercent), '% =', Math.round(gasEffectivePercent), '%');
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] cost % =', Math.round(costPercent), '%', Math.round(costEffectivePercent), '%');
}


}


if (accountFrom.address != mintAddress) {
const accountFromBalanceAtEnd = blockchain.getAccountBalance(accountFrom.address);

if (accountFromBalanceAtEnd.lt(0)){
_rejectTransaction('TRANSACTION_FAILED_INSUFFICIENT_ETH_BALANCE_AT_END');
}
}


if (! status) {
// Transaction Failed

// Revert transfer
if (amountTransfered.gt(0)) {
const txDataRevert = {
...this.data,
from: this.data.to,
to: this.data.from,
value: amountTransfered,
};
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(txDataRevert, block);

// TODO: gerer le revert des internal transfers
}

txLogs.forEach(log => {
log.removed = true;
});

// Revert contract storage
if (contractExecuted) {
const contractAccount = blockchain.getAccount(contractAddress);
contractAccount.data.storage = JSON.parse(storageBackup, utils.bnJsonParser);
contractAccount.data.balance = balanceBackup;

} else if (amountTransfered) {
// on check aussi amountTransfered car le contrat peut avoir été exécuté via receive => TODO: process a revoir
const contractAccount = blockchain.getAccount(contractAddress);
if (contractAccount && contractAccount.isContract()) {
//contractAccount.data.storage = JSON.parse(storageBackup, utils.bnJsonParser);
//contractAccount.data.balance = balanceBackup;
// TODO
}

}

// Revert deployed contract
if (contractDeployed) {
// nothing to do ?
}

const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] FAILED TRANSACTION ', this.data.hash, '| gas usage percent =', gasPercent, '%', ' | Error: ', error.message);

const realExecDuration = performance.now()/1000 - execTsStart; //parseFloat(process.hrtime(execTsStart).join('.'));
const realGasUsed = toBn(Math.round(realExecDuration * cpuPricePerSecond)); // TODO: prendre en compte le storage utilisé dans le calcul du gasUsed
const realGasPercent = realGasUsed.mul(100).div(this.data.gasLimit).toNumber();
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] FAILED TRANSACTION ', this.data.hash, '| real gas usage percent =', realGasPercent, '%', ' | Error: ', error.message);

} else {
// Transaction Success

// save contract storage
Object.entries(vmStorage).forEach((entry, idx) => {
const [contractAddress, contractStorage] = entry;

const contractAccount = blockchain.getAccount(contractAddress, true);
contractAccount.data.storage = contractStorage;
contractAccount.save();
});

// show result (debug)
const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();;
console.log(utils.getDate(), "[BLOCKCHAIN]", 'SUCCECSS TRANSACTION ', this.data.hash, '| gas usage percent =', gasPercent, '%');
}


// total gas utilisé dans le block
block.data.gasUsed = block.data.gasUsed.add(gasUsed);


// assign block data on logs
txLogs.forEach((txLog, idx) => {
txLog.data.transactionIndex = transactionIndex;
txLog.data.logIndex = logIndexOffset + idx;
txLog.data.transactionHash = this.data.hash;
txLog.data.blockHash = block.hash;
txLog.data.blockNumber = block.data.number;
});


const receiptData = {
transactionHash: this.data.hash,
transactionIndex: transactionIndex,
blockNumber: block.data.number,
blockHash: block.hash,
//logsBloom: '0x0000',
contractAddress: createdContractAddress,
gasUsed,
cumulativeGasUsed: block.data.gasUsed,
effectiveGasPrice: this.data.gasPrice,
effectiveCost: this.data.gasPrice.mul(gasUsed),
logs: txLogs,
status,
}

const receipt = new Receipt(blockchain, receiptData);

return [receipt, txLogs];
}"
, executeTransactionContract: "(block) => {
let contractAddress = this.data.to;

const isNullRecipient = (! contractAddress || contractAddress.length <= 2);
if (isNullRecipient) {
throw new Error('CONTRACT_EXEC_ADDRESS_INVALID_BUG_1');
}

const contractAccount = blockchain.getAccount(contractAddress);

if (! contractAccount) {
throw new Error('CONTRACT_EXEC_ACCOUNT_NOT_FOUND');
}

// Execute existing contract method call
return blockchain.callContractMethodInVM(this.data, block);

}"
, transactionDeployContract: "(block) => {
let contractAddress = this.data.to;

let contractAccount;

if (contractAddress && contractAddress.length > 2) {
throw new Error('CONTRACT_DEPLOY_ADDRESS_INVALID_BUG_2');
}


// CONTRACT CREATION

if (! this.data.data || this.data.data.length <= 2) {
throw new Error('CONTRACT_DEPLOY_INVALID_DATA_EMPTY_CODE');
}


// build contract address
contractAddress = ethers.utils.getContractAddress(this.data).toLocaleLowerCase();

// parse contract source code & constructor parameters
const codes = ethers.utils.defaultAbiCoder.decode(["string", "string"], this.data.data);
if (! codes) {
throw new Error('CONTRACT_DEPLOY_CODE_PARSING_FAILED');
}

const sourceCode = codes[0];
const constructorParams = codes[1];
const constructorRawParams = ethers.utils.defaultAbiCoder.encode(["string"], [constructorParams]);
const codeHash = ethers.utils.id(sourceCode);
const codeAbi = null; // l'abi sera généré une fois que le contrat aura été instancié
const storageHash = ethers.utils.id('{}');

// create new account
const accountData = {
address: contractAddress,
code: sourceCode,
codeHash,
codeAbi,
storage: {},
storageHash,
};
contractAccount = blockchain.createAccount(contractAddress, accountData);


// instanciate contract + call constructor + recuperer storage
const txData = {
from: this.data.from,
to: contractAddress,
data: constructorRawParams,
value: this.data.value.toHexString(),
};
const callResult = blockchain.deployContractInVM(txData, block);
const creationLogs = callResult.logs;
const vmStorage = callResult.storage;


// emit event
const eventParams = [
contractAddress,
];
const _txData = {
...this.data,
to: blockchain.getNullAddress(),
}
const deployLog = new TxLog(blockchain, null, _txData, 'ContractCreated', eventParams, deployAbi);
creationLogs.push(deployLog);


return {
result: contractAddress,
logs: creationLogs,
storage: vmStorage,
};
}"
, timestamp: "1651572522237", status: 1, gasUsed: "2870" }, 0x484d9c454cc34fc752903e492ce5c573118ba68824594a6fe0eabedd938361f6: { data: { blockHash: "0x00984edd40de5df064bb772c113d288d9d12599ba750074e6150184f409f87c5", blockNumber: "4", transactionIndex: 1, hash: "0x484d9c454cc34fc752903e492ce5c573118ba68824594a6fe0eabedd938361f6", from: "0x131a190fa22cb867abf58193bf9e7640e9fce682", gasLimit: "1034900", gasPrice: "1000000000", data: "0x000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000025c0000000000000000000000000000000000000000000000000000000000000255d2f2a0a4e616d653a20537461636b696e67536572766963650a56657273696f6e3a20302e302e330a536f757263653a0a202d2043616b655661756c74203a2068747470733a2f2f6769746875622e636f6d2f70616e63616b65737761702f70616e63616b652d736d6172742d636f6e7472616374732f626c6f622f6d61737465722f70726f6a656374732f63616b652d7661756c742f636f6e7472616374732f43616b655661756c742e736f6c0a202d20536d61727443686566496e697469616c697a61626c65203a2068747470733a2f2f6769746875622e636f6d2f70616e63616b65737761702f70616e63616b652d736d6172742d636f6e7472616374732f626c6f622f6d61737465722f70726f6a656374732f736d617274636865662f76322f636f6e7472616374732f536d61727443686566496e697469616c697a61626c652e736f6c0a202d204d617374657243686566203a2068747470733a2f2f6769746875622e636f6d2f70616e63616b65737761702f70616e63616b652d736d6172742d636f6e7472616374732f626c6f622f6d61737465722f70726f6a656374732f6661726d732d706f6f6c732f636f6e7472616374732f4d6173746572436865662e736f6c0a2a2f0a0a0a66756e6374696f6e2049455243323028746f6b656e4164647265737329207b0a2020202072657475726e206164647265737328746f6b656e41646472657373292e676574436f6e747261637428293b0a7d0a0a66756e6374696f6e20537461636b6572496e666f28737461636b6564416d6f756e742c207265776172644465627429207b0a20202020746869732e737461636b6564416d6f756e74203d20737461636b6564416d6f756e74207c7c206e756d6265722830293b0a20202020746869732e72657761726444656274203d2072657761726444656274207c7c206e756d6265722830293b0a7d0a0a0a636c61737320537461636b696e6753657276696365207b0a202020206f776e6572203d2027270a20202020737461636b6564546f6b656e203d2027273b0a20202020726577617264546f6b656e203d2027273b0a20202020746f74616c537461636b6564203d206e756d6265722830293b0a20202020616363526577617264506572537461636b546f6b656e203d206e756d6265722830293b0a20202020737461636b657273203d207b7d3b202f2f206d617070696e672861646472657373203d3e20537461636b6572496e666f290a202020206c6173744d696e74203d206e756d6265722830293b0a202020200a202020206d696e74506572696f64203d206e756d626572283630202a2031303030293b202f2f203630207365636f6e6473202831206d696e757465290a2020202072657761726473506572506572696f64203d206e756d626572282731303027202b202730303030303030303030303030303030303027293b202f2f2031303020546f6b656e20656d69747465642070657220706572696f64202873706c6974746564206f6e20616c6c20737461636b657273290a0a0a0a20202020496e697469616c697a6528737461636b6564546f6b656e2c20726577617264546f6b656e29207b7d202f2f206576656e740a2020202052657365742829207b7d202f2f206576656e740a202020204465706f7369742873656e6465722c20616d6f756e7429207b7d202f2f206576656e740a202020205769746864726177616c2873656e6465722c20616d6f756e742c20726577617264416d6f756e7429207b7d202f2f206576656e740a20202020557064617465506f6f6c2870656e64696e67526577617264732c206578747261526577617264506572537461636b546f6b656e29207b7d202f2f206576656e740a20202020557064617465537461636b657228737461636b6572416464726573732c20737461636b6572526577617264416d6f756e7429207b7d202f2f206576656e740a0a0a20202020696e697469616c697a6528737461636b6564546f6b656e2c20726577617264546f6b656e29207b0a20202020202020207265717569726528746869732e737461636b6564546f6b656e203d3d2027272c2022414c52454144595f494e495449414c495a454422293b0a2020202020202020746869732e737461636b6564546f6b656e203d20737461636b6564546f6b656e2e746f4c6f7765724361736528293b0a2020202020202020746869732e726577617264546f6b656e203d20726577617264546f6b656e2e746f4c6f7765724361736528293b0a2020202020202020746869732e6f776e6572203d206d73672e73656e6465723b0a0a2020202020202020656d69742827496e697469616c697a65272c205b737461636b6564546f6b656e2c20726577617264546f6b656e5d293b0a202020207d0a0a0a2020202072657365742829207b0a202020202020202072657175697265286d73672e73656e646572203d3d20746869732e6f776e65722c20274f4e4c595f4f574e455227293b0a20202020202020207265717569726528746869732e737461636b6564546f6b656e20213d2027272c20274e4f545f494e495449414c495a454427293b0a2020202020202020746869732e737461636b657273203d207b7d3b0a2020202020202020746869732e746f74616c537461636b6564203d206e756d6265722830293b0a2020202020202020746869732e6c6173744d696e74203d206e756d6265722830293b0a2020202020202020746869732e616363526577617264506572537461636b546f6b656e203d206e756d6265722830293b0a0a2020202020202020636f6e7374205b62616c616e6365312c5d203d2049455243323028746869732e737461636b6564546f6b656e292e62616c616e63654f6628616464726573732874686973292e61646472657373293b0a20202020202020206966202862616c616e6365312e677428302929207b0a20202020202020202020202049455243323028746869732e737461636b6564546f6b656e292e7472616e7366657246726f6d28616464726573732874686973292e616464726573732c206d73672e73656e6465722c2062616c616e636531293b0a20202020202020207d0a0a2020202020202020636f6e7374205b62616c616e6365322c5d203d2049455243323028746869732e726577617264546f6b656e292e62616c616e63654f6628616464726573732874686973292e61646472657373293b0a20202020202020206966202862616c616e6365322e677428302929207b0a20202020202020202020202049455243323028746869732e726577617264546f6b656e292e7472616e7366657246726f6d28616464726573732874686973292e616464726573732c206d73672e73656e6465722c2062616c616e636532293b0a20202020202020207d0a0a2020202020202020656d697428275265736574272c205b5d293b0a202020207d0a0a202020200a2020202062616c616e63654f6628737461636b657229207b0a2020202020202020737461636b6572203d20737461636b65722e746f4c6f7765724361736528293b0a20202020202020206c657420737461636b6564416d6f756e74203d206e756d6265722830293b0a202020202020202069662028737461636b657220696e20746869732e737461636b65727329207b0a202020202020202020202020737461636b6564416d6f756e74203d20746869732e737461636b6572735b737461636b65725d2e737461636b6564416d6f756e743b0a20202020202020207d0a202020202020202072657475726e205b737461636b6564416d6f756e745d3b0a202020207d0a0a0a202020206465706f736974285f616d6f756e7429207b0a20202020202020202f2f20737461636b696e670a20202020202020207265717569726528746869732e737461636b6564546f6b656e20213d2027272c20274e4f545f494e495449414c495a454427293b0a202020202020202072657175697265285f616d6f756e742e67742830292c2027494e56414c49445f414d4f554e5427293b0a0a202020202020202069662028746869732e746f74616c537461636b65642e657128302929207b0a202020202020202020202020746869732e6c6173744d696e74203d20626c6f636b2e74696d657374616d703b0a20202020202020207d0a0a2020202020202020636f6e7374205f726577617264416d6f756e74203d20746869732e706179537461636b657252657761726473286d73672e73656e646572293b202f2f206d6973652061206a6f7572206465206c6120706f6f6c202b206f6e20656e636169737365206c6573207265776172647320706f74656e7469656c732c20706f7572207265706172746972207375722064657320636f6d7074657320c3a0207a65726f20c3a020706172746972206465206365206465706f7369740a0a202020202020202049455243323028746869732e737461636b6564546f6b656e292e7472616e7366657246726f6d286d73672e73656e6465722c20616464726573732874686973292e616464726573732c205f616d6f756e74293b0a0a2020202020202020696620282120286d73672e73656e64657220696e20746869732e737461636b6572732929207b0a202020202020202020202020746869732e737461636b6572735b6d73672e73656e6465725d203d206e657720537461636b6572496e666f3b0a20202020202020207d0a0a2020202020202020636f6e737420737461636b6572203d20746869732e737461636b6572735b6d73672e73656e6465725d3b0a2020202020202020737461636b65722e737461636b6564416d6f756e74203d20737461636b65722e737461636b6564416d6f756e742e616464285f616d6f756e74293b0a0a2020202020202020746869732e746f74616c537461636b6564203d20746869732e746f74616c537461636b65642e616464285f616d6f756e74293b0a0a2020202020202020737461636b65722e72657761726444656274203d20737461636b65722e737461636b6564416d6f756e742e6d756c28746869732e616363526577617264506572537461636b546f6b656e292e6469762831653132293b0a0a20202020202020202f2f746869732e5f6561726e28293b0a0a2020202020202020656d697428274465706f736974272c205b6d73672e73656e6465722c205f616d6f756e745d293b0a202020207d0a0a202020200a202020207769746864726177285f616d6f756e7429207b0a20202020202020202f2f20756e737461636b696e67203d3e205769746864726177207374616b656420746f6b656e7320616e6420636f6c6c6563742072657761726420746f6b656e730a20202020202020207265717569726528746869732e737461636b6564546f6b656e20213d2027272c20274e4f545f494e495449414c495a454427293b0a202020202020202072657175697265285f616d6f756e742e67742830292c20274e6f7468696e6720746f20776974686472617727293b0a2020202020202020726571756972652821205f616d6f756e742e677428746869732e746f74616c537461636b6564292c2027494e53554646494349454e545f544f54414c27293b0a202020202020202072657175697265286d73672e73656e64657220696e20746869732e737461636b6572732c2027494e56414c49445f4f574e455227293b0a2020202020202020726571756972652821205f616d6f756e742e677428746869732e737461636b6572735b6d73672e73656e6465725d2e737461636b6564416d6f756e74292c2027576974686472617720616d6f756e7420657863656564732062616c616e636527293b0a0a2020202020202020636f6e7374205f726577617264416d6f756e74203d20746869732e706179537461636b657252657761726473286d73672e73656e646572293b202f2f206d6973652061206a6f7572206465206c6120706f6f6c202b206f6e20656e636169737365206c6573207265776172647320706f74656e7469656c732c20706f7572207265706172746972207375722064657320636f6d7074657320c3a0207a65726f20c3a0207061727469722064652063652077697468647261770a20202020202020200a2020202020202020636f6e737420737461636b6572203d20746869732e737461636b6572735b6d73672e73656e6465725d3b0a2020202020202020737461636b65722e737461636b6564416d6f756e74203d20737461636b65722e737461636b6564416d6f756e742e737562285f616d6f756e74293b0a2020202020202020746869732e746f74616c537461636b6564203d20746869732e746f74616c537461636b65642e737562285f616d6f756e74293b0a0a202020202020202049455243323028746869732e737461636b6564546f6b656e292e7472616e73666572286d73672e73656e6465722c205f616d6f756e74293b0a0a2020202020202020737461636b65722e72657761726444656274203d20737461636b65722e737461636b6564416d6f756e742e6d756c28746869732e616363526577617264506572537461636b546f6b656e292e6469762831653132293b0a20202020202020200a2020202020202020656d697428275769746864726177616c272c205b6d73672e73656e6465722c205f616d6f756e742c205f726577617264416d6f756e745d293b0a202020207d0a0a200a2020202067657450656e64696e67526577617264732829207b0a20202020202020202f2f207265776172647320c3a0206d696e7465720a20202020202020206c65742070656e64696e6752657761726473203d206e756d6265722830293b0a0a202020202020202069662028746869732e746f74616c537461636b65642e677428302929207b0a202020202020202020202020636f6e7374206475726174696f6e203d20626c6f636b2e74696d657374616d702e73756228746869732e6c6173744d696e74293b202f2f206d696c6c697365636f6e647320646570756973206c65206465726e696572206d696e740a0a202020202020202020202020696620286475726174696f6e2e677428302929207b0a20202020202020202020202020202020636f6e737420706572696f6473436f756e74203d206475726174696f6e2e64697628746869732e6d696e74506572696f64293b202f2f206e62506572696f647320646570756973206c65206465726e696572206d696e740a202020202020202020202020202020200a2020202020202020202020202020202069662028706572696f6473436f756e742e677428302929207b0a202020202020202020202020202020202020202070656e64696e6752657761726473203d20706572696f6473436f756e742e6d756c28746869732e72657761726473506572506572696f64293b0a202020202020202020202020202020207d0a2020202020202020202020207d0a20202020202020207d0a0a202020202020202072657475726e205b70656e64696e67526577617264735d3b202f2f207265746f75726e65206c65206e62206465207265776172647320504f5556414e5420c38a545245206d696e74c3a9730a202020207d0a0a0a20202020757064617465506f6f6c2829207b0a2020202020202020636f6e7374205b70656e64696e67526577617264732c5d203d20746869732e67657450656e64696e675265776172647328293b0a20202020202020206966202870656e64696e67526577617264732e677428302929207b0a202020202020202020202020746869732e6c6173744d696e74203d20626c6f636b2e74696d657374616d703b0a20202020202020202020202049455243323028746869732e726577617264546f6b656e292e6d696e7428616464726573732874686973292e616464726573732c2070656e64696e6752657761726473293b202f2f206f6e206175676d656e7465206c612062616c616e636520726577617264546f6b656e2028706f7572206c652070726573656e7420636f6e7472616374290a2020202020202020202020200a202020202020202020202020636f6e7374206578747261526577617264506572537461636b546f6b656e203d2070656e64696e67526577617264732e6d756c2831653132292e64697628746869732e746f74616c537461636b6564293b0a202020202020202020202020746869732e616363526577617264506572537461636b546f6b656e203d20746869732e616363526577617264506572537461636b546f6b656e2e616464286578747261526577617264506572537461636b546f6b656e293b0a0a202020202020202020202020656d69742827557064617465506f6f6c272c205b70656e64696e67526577617264732c206578747261526577617264506572537461636b546f6b656e5d293b0a20202020202020207d0a202020207d0a0a0a20202020676574537461636b6572417661696c61626c655265776172647328737461636b65724164647265737329207b0a2020202020202020737461636b657241646472657373203d20737461636b6572416464726573732e746f4c6f7765724361736528293b0a20202020202020206c657420737461636b6572526577617264416d6f756e74203d206e756d6265722830293b0a0a2020202020202020636f6e7374205b70656e64696e67526577617264732c5d203d20746869732e67657450656e64696e675265776172647328293b0a20202020202020200a202020202020202069662028737461636b65724164647265737320696e20746869732e737461636b65727329207b0a202020202020202020202020636f6e7374206578747261526577617264506572537461636b546f6b656e203d2070656e64696e67526577617264732e6d756c2831653132292e64697628746869732e746f74616c537461636b6564293b0a202020202020202020202020636f6e737420616363526577617264506572537461636b546f6b656e203d20746869732e616363526577617264506572537461636b546f6b656e2e616464286578747261526577617264506572537461636b546f6b656e293b0a0a202020202020202020202020636f6e737420737461636b6572203d20746869732e737461636b6572735b737461636b6572416464726573735d3b0a202020202020202020202020737461636b6572526577617264416d6f756e74203d20737461636b65722e737461636b6564416d6f756e742e6d756c28616363526577617264506572537461636b546f6b656e292e6469762831653132292e73756228737461636b65722e72657761726444656274293b0a20202020202020207d0a0a202020202020202072657475726e205b737461636b6572526577617264416d6f756e745d3b0a202020207d0a0a0a20202020706179537461636b65725265776172647328737461636b65724164647265737329207b0a20202020202020202f2f20686172766573740a2020202020202020737461636b657241646472657373203d20737461636b6572416464726573732e746f4c6f7765724361736528293b0a0a2020202020202020746869732e757064617465506f6f6c28293b0a2020202020202020636f6e7374205b737461636b6572526577617264416d6f756e742c5d203d20746869732e5f757064617465537461636b657228737461636b657241646472657373293b0a0a202020202020202072657475726e205b737461636b6572526577617264416d6f756e745d3b0a202020207d0a0a0a202020205f757064617465537461636b657228737461636b65724164647265737329207b0a20202020202020206c657420737461636b6572526577617264416d6f756e74203d206e756d6265722830293b0a20202020202020200a202020202020202069662028737461636b65724164647265737320696e20746869732e737461636b65727329207b0a202020202020202020202020636f6e737420737461636b6572203d20746869732e737461636b6572735b737461636b6572416464726573735d3b0a2020202020202020202020205b737461636b6572526577617264416d6f756e742c205d203d20746869732e676574537461636b6572417661696c61626c655265776172647328737461636b657241646472657373293b0a202020200a202020202020202020202020737461636b65722e72657761726444656274203d20737461636b65722e737461636b6564416d6f756e742e6d756c28746869732e616363526577617264506572537461636b546f6b656e292e6469762831653132293b0a202020200a20202020202020202020202069662028737461636b6572526577617264416d6f756e742e677428302929207b0a2020202020202020202020202020202049455243323028746869732e726577617264546f6b656e292e7472616e7366657228737461636b6572416464726573732c20737461636b6572526577617264416d6f756e74293b0a0a20202020202020202020202020202020656d69742827557064617465537461636b6572272c205b737461636b6572416464726573732c20737461636b6572526577617264416d6f756e745d293b0a2020202020202020202020207d0a20202020202020207d0a0a202020202020202072657475726e205b737461636b6572526577617264416d6f756e745d3b0a202020207d0a0a7d0a0a2f2f204576656e74730a537461636b696e67536572766963652e70726f746f747970652e496e697469616c697a652e6576656e74203d20747275653b0a537461636b696e67536572766963652e70726f746f747970652e496e697469616c697a652e696e70757473203d205b276164647265737320696e6465786564272c20276164647265737320696e6465786564275d3b0a0a537461636b696e67536572766963652e70726f746f747970652e557064617465537461636b65722e6576656e74203d20747275653b0a537461636b696e67536572766963652e70726f746f747970652e557064617465537461636b65722e696e70757473203d205b276164647265737320696e6465786564272c202775696e74323536275d3b0a0a537461636b696e67536572766963652e70726f746f747970652e52657365742e6576656e74203d20747275653b0a0a537461636b696e67536572766963652e70726f746f747970652e4465706f7369742e6576656e74203d20747275653b0a537461636b696e67536572766963652e70726f746f747970652e4465706f7369742e696e70757473203d205b276164647265737320696e6465786564272c202775696e74323536275d3b0a0a537461636b696e67536572766963652e70726f746f747970652e5769746864726177616c2e6576656e74203d20747275653b0a537461636b696e67536572766963652e70726f746f747970652e5769746864726177616c2e696e70757473203d205b276164647265737320696e6465786564272c202775696e74323536272c202775696e74323536275d3b0a0a537461636b696e67536572766963652e70726f746f747970652e557064617465506f6f6c2e6576656e74203d20747275653b0a537461636b696e67536572766963652e70726f746f747970652e557064617465506f6f6c2e696e70757473203d205b2775696e74323536272c202775696e74323536275d3b0a0a537461636b696e67536572766963652e70726f746f747970652e557064617465537461636b65722e6576656e74203d20747275653b0a537461636b696e67536572766963652e70726f746f747970652e557064617465537461636b65722e696e70757473203d205b276164647265737320696e6465786564272c202775696e74323536275d3b0a0a2f2f204d6574686f64730a537461636b696e67536572766963652e70726f746f747970652e696e697469616c697a652e696e70757473203d205b2761646472657373272c202761646472657373275d3b0a0a537461636b696e67536572766963652e70726f746f747970652e62616c616e63654f662e76696577203d20747275653b0a537461636b696e67536572766963652e70726f746f747970652e62616c616e63654f662e696e70757473203d205b2761646472657373275d3b0a537461636b696e67536572766963652e70726f746f747970652e62616c616e63654f662e6f757470757473203d205b2775696e74323536275d3b0a0a537461636b696e67536572766963652e70726f746f747970652e6465706f7369742e696e70757473203d205b2775696e74323536275d3b0a0a537461636b696e67536572766963652e70726f746f747970652e77697468647261772e696e70757473203d205b2775696e74323536275d3b0a0a537461636b696e67536572766963652e70726f746f747970652e67657450656e64696e67526577617264732e76696577203d20747275653b0a537461636b696e67536572766963652e70726f746f747970652e67657450656e64696e67526577617264732e6f757470757473203d205b2775696e74323536275d3b0a0a537461636b696e67536572766963652e70726f746f747970652e676574537461636b6572417661696c61626c65526577617264732e76696577203d20747275653b0a537461636b696e67536572766963652e70726f746f747970652e676574537461636b6572417661696c61626c65526577617264732e696e70757473203d205b2761646472657373275d3b0a537461636b696e67536572766963652e70726f746f747970652e676574537461636b6572417661696c61626c65526577617264732e6f757470757473203d205b2775696e74323536275d3b0a0a537461636b696e67536572766963652e70726f746f747970652e706179537461636b6572526577617264732e696e70757473203d205b2761646472657373275d3b0a537461636b696e67536572766963652e70726f746f747970652e706179537461636b6572526577617264732e6f757470757473203d205b2775696e74323536275d3b0a0a537461636b696e67536572766963652e70726f746f747970652e5f757064617465537461636b65722e696e7465726e616c203d20747275653b0a0a0a72657475726e20537461636b696e67536572766963653b0a0a00000000000000000000000000000000000000000000000000000000000000000000025b5d000000000000000000000000000000000000000000000000000000000000", nonce: "3", to: "0x", value: "0", chainId: "3615", origin: "1651572518038", v: "1c61", r: "ab608e48519c177551b5c95bfa8957457ce09228fa08b7b0b9f969c50ca1cf99", s: "998d2eebee6018906c1a9e12337283ee402ad71d6be3d65c6ec8efc5de0d335" }, hash: "0x484d9c454cc34fc752903e492ce5c573118ba68824594a6fe0eabedd938361f6", load: "() => {
const transactionFile = config.node.dataDir + '/transactions/' + this.hash + '.json';

if (! fs.existsSync(transactionFile)) {
console.warn(utils.getDate(), "[BLOCKCHAIN]", 'IMPORT Transaction not found', this.hash);
throw new Error('IMPORT_TRANSACTION_NOT_FOUND_DATABASE_CORRUPTED');
}

const transactionJson = fs.readFileSync(transactionFile);
transactionData = JSON.parse(transactionJson, utils.bnJsonParser);

this.data = (typeof transactionData == 'TransactionData') ? transactionData : new TransactionData(transactionData);

return true;
}"
, save: "() => {
const transactionFile = config.node.dataDir + '/transactions/' + this.hash + '.json';
fs.writeFileSync(transactionFile, JSON.stringify(this.data));
return true;
}"
, getReceipt: "() => {
return blockchain.getTransactionReceiptByHash(this.hash);
}"
, getBlock: "() => {
return blockchain.getBlockByHash(this.data.blockHash);
}"
, getBlockTransationPrev: "() => {
const block = this.getBlock();
const txPrevIdx = (this.data.transactionIndex > 0) ? (this.data.transactionIndex-1) : null;
const tx = (block && txPrevIdx in block.transactions) ? block.transactions[txPrevIdx] : null;
return tx;
}"
, getBlockTransationNext: "() => {
const block = this.getBlock();
const txNextIdx = (this.data.transactionIndex < block.transactions.length - 1) ? (this.data.transactionIndex+1) : null;
const tx = (block && txNextIdx in block.transactions) ? block.transactions[txNextIdx] : null;
return tx;
}"
, getAccountTransactionIndex: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = account.data.transactions.findIndex(transactionHash => blockchain.getTransactionByHash(transactionHash).hash == this.hash);
if (txIdx === -1) {
return null;
}
return txIdx;
}"
, getAccountTransationPrev: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = this.getAccountTransactionIndex();
if (txIdx === null) {
return null;
}
const txPrevIdx = (txIdx > 0) ? (txIdx-1) : null;
const transactionHash = (txPrevIdx in account.data.transactions) ? account.data.transactions[txPrevIdx] : null;
if (! transactionHash) {
return null;
}
return blockchain.getTransactionByHash(transactionHash);
}"
, getAccountTransationNext: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = this.getAccountTransactionIndex();
if (txIdx === null) {
return null;
}
const txNextIdx = (txIdx < account.data.transactions.length - 1) ? (txIdx+1) : null;
const transactionHash = (txNextIdx in account.data.transactions) ? account.data.transactions[txNextIdx] : null;
if (! transactionHash) {
return null;
}
return blockchain.getTransactionByHash(transactionHash);

}"
, formatRpc: "() => {
// https://github.com/ethers-io/ethers.js/blob/master/packages/abstract-provider/lib/index.d.ts

const to = (this.data.to && this.data.to.length == 42) ? this.data.to : null;

const _txData = {
blockHash: this.data.blockHash,
blockNumber: this.data.blockNumber.toNumber(),
transactionIndex: this.data.transactionIndex,
hash: this.data.hash,
from: this.data.from,
gasLimit: this.data.gasLimit.toHexString(),
gasPrice: this.data.gasPrice.toHexString(),
data: this.data.data,
nonce: this.data.nonce.toHexString(),
to,
value: this.data.value.toHexString(),
chainId: this.data.chainId.toNumber(),
//origin: this.data.origin.toHexString(),
};

return _txData;
}"
, getHashOLD: "() => {
const _txData = {
gasPrice: this.data.gasPrice,
gasLimit: this.data.gasLimit,
from: this.data.from,
to: this.data.to,
value: this.data.value,
data: this.data.data,
nonce: this.data.nonce,
};

const strvalue = JSON.stringify(utils.orderedObject(_txData));
const transactionHash = ethers.utils.id(strvalue);

return transactionHash;
}"
, getHash: "() => {
const _txData = {
gasPrice: this.data.gasPrice.toHexString(),
gasLimit: this.data.gasLimit.toHexString(),
from: this.data.from,
to: this.data.to,
value: this.data.value.toHexString(),
data: this.data.data,
nonce: this.data.nonce.toHexString(),
v: this.data.v ? ('0x' + this.data.v) : '',
r: this.data.r ? ('0x' + this.data.r) : '',
s: this.data.s ? ('0x' + this.data.s) : '',
};

const tx = new EthereumJS.Transaction(_txData);
const hashRaw = tx.hash();

const transactionHash = ethers.utils.defaultAbiCoder.decode(["uint256"], hashRaw)[0].toHexString();

return transactionHash;
}"
, execute: "(block, transactionIndex, logIndexOffset) => {
const _tx = this;

function _rejectTransaction(reason) {
// https://openethereum.github.io/Transactions-Queue
reason = reason || 'unknown';
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] REJECTED PROCESSING TRANSACTION', _tx.data.hash, ' (Reason:', reason + ')');
const success = blockchain.removeTransactionsFromMempool([_tx.hash], true, true);
if (success) {
saveMempool();
}
throw new Error(reason); // on rejete definitivement la tx (elle ne sera pas minée ... et donc aucun frais de gas ne sera compté)
// et du coup on rejete le block
}

function _checkGas(step) {
execDuration = performance.now()/1000 - execTsStart; //parseFloat(process.hrtime(execTsStart).join('.'));
gasUsed = toBn(Math.round(execDuration * cpuPricePerSecond)); // TODO: prendre en compte le storage utilisé dans le calcul du gasUsed
txCost = gasUsed.mul(_tx.data.gasPrice);

if (step == 'afterTransfer') {
//debugger;
}

if (status) {
gasUsedEffective = gasUsed;
txCostEffective = gasUsedEffective.mul(_tx.data.gasPrice);

if (gasUsed.gt(_tx.data.gasLimit)) {
status = 0;
error = new Error('INSUFFICIENT_GAS');

gasUsedEffective = _tx.data.gasLimit;
txCostEffective = txCostLimit;

// emit event
const debugError = {
message: 'INSUFFICIENT_GAS',
step,
gasPrice: _tx.data.gasPrice,
gasLimit: _tx.data.gasLimit,
gasUsed,
gasUsedEffective,
txCostLimit,
txCost,
txCostEffective,
execDuration,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, _tx.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

}
}


//const block = this;
const txLogs = [];
let vmStorage = {};

let gasUsed = toBn(0);
let gasUsedEffective = toBn(0);
let status = 1;
let error = null;
let createdContractAddress = null;
let amountTransfered = toBn(0);

const contractAddress = this.data.to;
let storageBackup;
let balanceBackup;
let contractExecuted = false;
let contractDeployed = false;

const execTsStart = performance.now()/1000; //process.hrtime(); //Date.now();
let execDuration = 0;

const txCostLimit = this.data.gasLimit.mul(this.data.gasPrice);
let txCost = toBn(0);
let txCostEffective = toBn(0);


const accountFrom = blockchain.getAccount(this.data.from);
if (! accountFrom) {
_rejectTransaction('TRANSACTION_REJECTED_EMPTY_ACCOUNT');
}
const accountFromBalance = accountFrom.data.balance;


if (accountFrom.address != mintAddress) {

// verif nonce
const expectedNonce = toBn(accountFrom.data.transactions.length); // le nonce attendu est egal au nombre de transactions passées de l'account
if (! this.data.nonce.eq(expectedNonce)) {
_rejectTransaction('TRANSACTION_REJECTED_INCORRECT_NONCE');
}

// verif balance pour gas + transfer
if (this.data.value && this.data.value.gt(0)) {
if (accountFromBalance.lt(this.data.value.add(txCostLimit))) {
// l'adresse n'a pas suffisamment de fond pour executer la transaction (selon gasLimit)
// TODO: faut-il rejeter la tx ? ou la laisser passer (en status=0) et lui vider sa balance (si 100% du gas est utilisé) ?
//return _rejectTransaction('TRANSACTION_FAILED_INSUFFICIENT_ETH_BALANCE');

_rejectTransaction('TRANSACTION_REJECTED_INSUFFICIENT_ETH_BALANCE');
}
}
}



_checkGas('init');


if (status) {
if (this.data.value && this.data.value.gt(0) && contractAddress && contractAddress.length > 2) {
// Transfer la valeur demandée (simple ETH transaction)
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(this.data, block);
txLogs.push( ...transferLogs );

// si recipient == contract => on transfer quand meme dès maintenant => TODO: a revoir (le transferEth declenche un appel à receive ... avant meme de savoir si unne methode payable doit etre appelée. Probleme si receive provoque un revert => la fonction payable ne sera pas appelée)
// 1) si methode payable appellée, on l'execute
// 2) si methode non payable => on appelle receive juste avant
// 3) si methode inconnue => revert


if (transferResult && transferReverted) {
// reverted (after transfer)
amountTransfered = this.data.value;

status = 0;
error = new Error('TRANSFER_REVERTED');

} else if (transferResult) {
// success
amountTransfered = this.data.value;

} else {
// transfer failed
status = 0;
error = new Error('TRANSFER_FAILED');
}

}

_checkGas('afterTransfer');
}

const gasUsedBeforeContractExecution = gasUsed;

if (status && this.data.data && this.data.data != '0x') {
// Traitement du champ data de la transaction (smart contract call)

if (!contractAddress || contractAddress == '0x') {
// contract creation

contractDeployed = true;

let contractExecResult;
try {
contractExecResult = this.transactionDeployContract(block);
const creationLogs = contractExecResult.logs;
vmStorage = contractExecResult.storage;

if (creationLogs.length) {
txLogs.push( ...creationLogs );
}

if (! contractExecResult.result) {
// Deploy Failed
status = 0;
error = new Error('CONTRACT_CREATION_FAILED');

// emit event
const debugError = {
message: 'CONTRACT_CREATION_FAILED',
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);

} else {
// Deploy Success
createdContractAddress = contractExecResult.result.toLowerCase();

if (this.data.value.gt(0)) {
// Transfer tx value to contract
const _txData = {
...this.data,
to: createdContractAddress,
};
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(_txData, block);
txLogs.push( ...transferLogs );

if (transferResult) {
amountTransfered = this.data.value;

} else {
status = 0;
error = new Error('CONTRACT_CREATION_TRANSFER_FAILED');
}
}

// TODO: emit event CONTRACT_CREATION_SUCCESS ?
}



} catch (e) {
//console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] Cannot execute transaction');
//throw e;
status = 0;
error = new Error('CONTRACT_CREATION_ERROR');

console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] CONTRACT_CREATION_ERROR with message', e.message)

// emit event
const debugError = {
message: 'CONTRACT_CREATION_ERROR',
error: e.message,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

_checkGas('afterContractCreation');

} else {
// contract execution

const contractAccount = blockchain.getAccount(contractAddress);

if (! contractAccount) {
status = 0;
error = new Error('CONTRACT_ACCOUNT_NOT_FOUND');

// emit event
const debugError = {
message: 'CONTRACT_ACCOUNT_NOT_FOUND',
address: contractAddress,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

if (status) {
storageBackup = JSON.stringify(contractAccount.data.storage);
balanceBackup = contractAccount.data.balance;
contractExecuted = true;

try {
const contractExecResult = this.executeTransactionContract(block);
const internalLogs = contractExecResult.logs;
vmStorage = contractExecResult.storage;

if (internalLogs.length) {
txLogs.push( ...internalLogs );
}

if (! contractExecResult.result) {
status = 0;
error = new Error('CONTRACT_EXEC_FAILED');

// emit event
const debugError = {
message: 'CONTRACT_EXEC_FAILED',
address: contractAddress,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

} catch (e) {
status = 0;
error = new Error('CONTRACT_EXEC_ERROR');

// emit event
const debugError = {
message: 'CONTRACT_EXEC_ERROR',
address: contractAddress,
error: e.message,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}
}

_checkGas('afterContractCall');
}

}

const gasUsedAfterContractExecution = gasUsed;
const gasUsedInContractExecution = gasUsedAfterContractExecution.sub(gasUsedBeforeContractExecution);
const contractExecutionCost = gasUsedInContractExecution.mul(_tx.data.gasPrice);


console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] execDuration =', (Math.round(execDuration * 1000000) / 1000000), ' seconds');



if (txCostEffective.gt(0)) {
// Gas payment
//const minerAccount = blockchain.getAccount(block.data.miner, true);
//const gasPaymentResult = minerAccount.transferFrom(accountFrom.address, txCostEffective);

const txDataGasPayment = {
from: accountFrom.address,
to: block.data.miner,
value: txCostEffective,
};
const [gasPaymentResult, gasPaymentLogs, transferReverted] = blockchain.transferEth(txDataGasPayment, block, true);
txLogs.push(...gasPaymentLogs);

if (gasPaymentResult) {
// gas payment success

} else {
// gas payment failed => reject transaction (and block)
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] Gas payment failed');
_rejectTransaction('GAS_PAYMENT_FAILED');
}

if (false) {
// DEBUG
const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();
const gasEffectivePercent = gasUsedEffective.mul(100).div(this.data.gasLimit).toNumber();
const costPercent = txCost.mul(100).div(txCostLimit).toNumber();
const costEffectivePercent = txCostEffective.mul(100).div(txCostLimit).toNumber();
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] txCostLimit =', ethers.utils.formatEther(txCostLimit));
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] txCostEffective =', ethers.utils.formatEther(txCostEffective));
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] gas % =', Math.round(gasPercent), '% =', Math.round(gasEffectivePercent), '%');
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] cost % =', Math.round(costPercent), '%', Math.round(costEffectivePercent), '%');
}


}


if (accountFrom.address != mintAddress) {
const accountFromBalanceAtEnd = blockchain.getAccountBalance(accountFrom.address);

if (accountFromBalanceAtEnd.lt(0)){
_rejectTransaction('TRANSACTION_FAILED_INSUFFICIENT_ETH_BALANCE_AT_END');
}
}


if (! status) {
// Transaction Failed

// Revert transfer
if (amountTransfered.gt(0)) {
const txDataRevert = {
...this.data,
from: this.data.to,
to: this.data.from,
value: amountTransfered,
};
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(txDataRevert, block);

// TODO: gerer le revert des internal transfers
}

txLogs.forEach(log => {
log.removed = true;
});

// Revert contract storage
if (contractExecuted) {
const contractAccount = blockchain.getAccount(contractAddress);
contractAccount.data.storage = JSON.parse(storageBackup, utils.bnJsonParser);
contractAccount.data.balance = balanceBackup;

} else if (amountTransfered) {
// on check aussi amountTransfered car le contrat peut avoir été exécuté via receive => TODO: process a revoir
const contractAccount = blockchain.getAccount(contractAddress);
if (contractAccount && contractAccount.isContract()) {
//contractAccount.data.storage = JSON.parse(storageBackup, utils.bnJsonParser);
//contractAccount.data.balance = balanceBackup;
// TODO
}

}

// Revert deployed contract
if (contractDeployed) {
// nothing to do ?
}

const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] FAILED TRANSACTION ', this.data.hash, '| gas usage percent =', gasPercent, '%', ' | Error: ', error.message);

const realExecDuration = performance.now()/1000 - execTsStart; //parseFloat(process.hrtime(execTsStart).join('.'));
const realGasUsed = toBn(Math.round(realExecDuration * cpuPricePerSecond)); // TODO: prendre en compte le storage utilisé dans le calcul du gasUsed
const realGasPercent = realGasUsed.mul(100).div(this.data.gasLimit).toNumber();
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] FAILED TRANSACTION ', this.data.hash, '| real gas usage percent =', realGasPercent, '%', ' | Error: ', error.message);

} else {
// Transaction Success

// save contract storage
Object.entries(vmStorage).forEach((entry, idx) => {
const [contractAddress, contractStorage] = entry;

const contractAccount = blockchain.getAccount(contractAddress, true);
contractAccount.data.storage = contractStorage;
contractAccount.save();
});

// show result (debug)
const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();;
console.log(utils.getDate(), "[BLOCKCHAIN]", 'SUCCECSS TRANSACTION ', this.data.hash, '| gas usage percent =', gasPercent, '%');
}


// total gas utilisé dans le block
block.data.gasUsed = block.data.gasUsed.add(gasUsed);


// assign block data on logs
txLogs.forEach((txLog, idx) => {
txLog.data.transactionIndex = transactionIndex;
txLog.data.logIndex = logIndexOffset + idx;
txLog.data.transactionHash = this.data.hash;
txLog.data.blockHash = block.hash;
txLog.data.blockNumber = block.data.number;
});


const receiptData = {
transactionHash: this.data.hash,
transactionIndex: transactionIndex,
blockNumber: block.data.number,
blockHash: block.hash,
//logsBloom: '0x0000',
contractAddress: createdContractAddress,
gasUsed,
cumulativeGasUsed: block.data.gasUsed,
effectiveGasPrice: this.data.gasPrice,
effectiveCost: this.data.gasPrice.mul(gasUsed),
logs: txLogs,
status,
}

const receipt = new Receipt(blockchain, receiptData);

return [receipt, txLogs];
}"
, executeTransactionContract: "(block) => {
let contractAddress = this.data.to;

const isNullRecipient = (! contractAddress || contractAddress.length <= 2);
if (isNullRecipient) {
throw new Error('CONTRACT_EXEC_ADDRESS_INVALID_BUG_1');
}

const contractAccount = blockchain.getAccount(contractAddress);

if (! contractAccount) {
throw new Error('CONTRACT_EXEC_ACCOUNT_NOT_FOUND');
}

// Execute existing contract method call
return blockchain.callContractMethodInVM(this.data, block);

}"
, transactionDeployContract: "(block) => {
let contractAddress = this.data.to;

let contractAccount;

if (contractAddress && contractAddress.length > 2) {
throw new Error('CONTRACT_DEPLOY_ADDRESS_INVALID_BUG_2');
}


// CONTRACT CREATION

if (! this.data.data || this.data.data.length <= 2) {
throw new Error('CONTRACT_DEPLOY_INVALID_DATA_EMPTY_CODE');
}


// build contract address
contractAddress = ethers.utils.getContractAddress(this.data).toLocaleLowerCase();

// parse contract source code & constructor parameters
const codes = ethers.utils.defaultAbiCoder.decode(["string", "string"], this.data.data);
if (! codes) {
throw new Error('CONTRACT_DEPLOY_CODE_PARSING_FAILED');
}

const sourceCode = codes[0];
const constructorParams = codes[1];
const constructorRawParams = ethers.utils.defaultAbiCoder.encode(["string"], [constructorParams]);
const codeHash = ethers.utils.id(sourceCode);
const codeAbi = null; // l'abi sera généré une fois que le contrat aura été instancié
const storageHash = ethers.utils.id('{}');

// create new account
const accountData = {
address: contractAddress,
code: sourceCode,
codeHash,
codeAbi,
storage: {},
storageHash,
};
contractAccount = blockchain.createAccount(contractAddress, accountData);


// instanciate contract + call constructor + recuperer storage
const txData = {
from: this.data.from,
to: contractAddress,
data: constructorRawParams,
value: this.data.value.toHexString(),
};
const callResult = blockchain.deployContractInVM(txData, block);
const creationLogs = callResult.logs;
const vmStorage = callResult.storage;


// emit event
const eventParams = [
contractAddress,
];
const _txData = {
...this.data,
to: blockchain.getNullAddress(),
}
const deployLog = new TxLog(blockchain, null, _txData, 'ContractCreated', eventParams, deployAbi);
creationLogs.push(deployLog);


return {
result: contractAddress,
logs: creationLogs,
storage: vmStorage,
};
}"
, timestamp: "1651572522237", status: 1, gasUsed: "24473" }, 0x064db09f8ba8a213241f621442f62b9e1a69154aed2455cd68bd217f9a1e907b: { data: { blockHash: "0x00b78e2d71f5430ea94beb2f6001c4ed6a97e6916a8a000fe57accad16de9ee9", blockNumber: "5", transactionIndex: 0, hash: "0x064db09f8ba8a213241f621442f62b9e1a69154aed2455cd68bd217f9a1e907b", from: "0x0000000000000000000000000000000000000001", gasLimit: "1000000000000000000", gasPrice: "0", data: "0x", nonce: "4", to: "0xccf8ba457dcad7ee6a0361c96846a0f79744b113", value: "250000000000000000000", chainId: "3615", origin: "1651572528074", v: "", r: "", s: "" }, hash: "0x064db09f8ba8a213241f621442f62b9e1a69154aed2455cd68bd217f9a1e907b", load: "() => {
const transactionFile = config.node.dataDir + '/transactions/' + this.hash + '.json';

if (! fs.existsSync(transactionFile)) {
console.warn(utils.getDate(), "[BLOCKCHAIN]", 'IMPORT Transaction not found', this.hash);
throw new Error('IMPORT_TRANSACTION_NOT_FOUND_DATABASE_CORRUPTED');
}

const transactionJson = fs.readFileSync(transactionFile);
transactionData = JSON.parse(transactionJson, utils.bnJsonParser);

this.data = (typeof transactionData == 'TransactionData') ? transactionData : new TransactionData(transactionData);

return true;
}"
, save: "() => {
const transactionFile = config.node.dataDir + '/transactions/' + this.hash + '.json';
fs.writeFileSync(transactionFile, JSON.stringify(this.data));
return true;
}"
, getReceipt: "() => {
return blockchain.getTransactionReceiptByHash(this.hash);
}"
, getBlock: "() => {
return blockchain.getBlockByHash(this.data.blockHash);
}"
, getBlockTransationPrev: "() => {
const block = this.getBlock();
const txPrevIdx = (this.data.transactionIndex > 0) ? (this.data.transactionIndex-1) : null;
const tx = (block && txPrevIdx in block.transactions) ? block.transactions[txPrevIdx] : null;
return tx;
}"
, getBlockTransationNext: "() => {
const block = this.getBlock();
const txNextIdx = (this.data.transactionIndex < block.transactions.length - 1) ? (this.data.transactionIndex+1) : null;
const tx = (block && txNextIdx in block.transactions) ? block.transactions[txNextIdx] : null;
return tx;
}"
, getAccountTransactionIndex: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = account.data.transactions.findIndex(transactionHash => blockchain.getTransactionByHash(transactionHash).hash == this.hash);
if (txIdx === -1) {
return null;
}
return txIdx;
}"
, getAccountTransationPrev: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = this.getAccountTransactionIndex();
if (txIdx === null) {
return null;
}
const txPrevIdx = (txIdx > 0) ? (txIdx-1) : null;
const transactionHash = (txPrevIdx in account.data.transactions) ? account.data.transactions[txPrevIdx] : null;
if (! transactionHash) {
return null;
}
return blockchain.getTransactionByHash(transactionHash);
}"
, getAccountTransationNext: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = this.getAccountTransactionIndex();
if (txIdx === null) {
return null;
}
const txNextIdx = (txIdx < account.data.transactions.length - 1) ? (txIdx+1) : null;
const transactionHash = (txNextIdx in account.data.transactions) ? account.data.transactions[txNextIdx] : null;
if (! transactionHash) {
return null;
}
return blockchain.getTransactionByHash(transactionHash);

}"
, formatRpc: "() => {
// https://github.com/ethers-io/ethers.js/blob/master/packages/abstract-provider/lib/index.d.ts

const to = (this.data.to && this.data.to.length == 42) ? this.data.to : null;

const _txData = {
blockHash: this.data.blockHash,
blockNumber: this.data.blockNumber.toNumber(),
transactionIndex: this.data.transactionIndex,
hash: this.data.hash,
from: this.data.from,
gasLimit: this.data.gasLimit.toHexString(),
gasPrice: this.data.gasPrice.toHexString(),
data: this.data.data,
nonce: this.data.nonce.toHexString(),
to,
value: this.data.value.toHexString(),
chainId: this.data.chainId.toNumber(),
//origin: this.data.origin.toHexString(),
};

return _txData;
}"
, getHashOLD: "() => {
const _txData = {
gasPrice: this.data.gasPrice,
gasLimit: this.data.gasLimit,
from: this.data.from,
to: this.data.to,
value: this.data.value,
data: this.data.data,
nonce: this.data.nonce,
};

const strvalue = JSON.stringify(utils.orderedObject(_txData));
const transactionHash = ethers.utils.id(strvalue);

return transactionHash;
}"
, getHash: "() => {
const _txData = {
gasPrice: this.data.gasPrice.toHexString(),
gasLimit: this.data.gasLimit.toHexString(),
from: this.data.from,
to: this.data.to,
value: this.data.value.toHexString(),
data: this.data.data,
nonce: this.data.nonce.toHexString(),
v: this.data.v ? ('0x' + this.data.v) : '',
r: this.data.r ? ('0x' + this.data.r) : '',
s: this.data.s ? ('0x' + this.data.s) : '',
};

const tx = new EthereumJS.Transaction(_txData);
const hashRaw = tx.hash();

const transactionHash = ethers.utils.defaultAbiCoder.decode(["uint256"], hashRaw)[0].toHexString();

return transactionHash;
}"
, execute: "(block, transactionIndex, logIndexOffset) => {
const _tx = this;

function _rejectTransaction(reason) {
// https://openethereum.github.io/Transactions-Queue
reason = reason || 'unknown';
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] REJECTED PROCESSING TRANSACTION', _tx.data.hash, ' (Reason:', reason + ')');
const success = blockchain.removeTransactionsFromMempool([_tx.hash], true, true);
if (success) {
saveMempool();
}
throw new Error(reason); // on rejete definitivement la tx (elle ne sera pas minée ... et donc aucun frais de gas ne sera compté)
// et du coup on rejete le block
}

function _checkGas(step) {
execDuration = performance.now()/1000 - execTsStart; //parseFloat(process.hrtime(execTsStart).join('.'));
gasUsed = toBn(Math.round(execDuration * cpuPricePerSecond)); // TODO: prendre en compte le storage utilisé dans le calcul du gasUsed
txCost = gasUsed.mul(_tx.data.gasPrice);

if (step == 'afterTransfer') {
//debugger;
}

if (status) {
gasUsedEffective = gasUsed;
txCostEffective = gasUsedEffective.mul(_tx.data.gasPrice);

if (gasUsed.gt(_tx.data.gasLimit)) {
status = 0;
error = new Error('INSUFFICIENT_GAS');

gasUsedEffective = _tx.data.gasLimit;
txCostEffective = txCostLimit;

// emit event
const debugError = {
message: 'INSUFFICIENT_GAS',
step,
gasPrice: _tx.data.gasPrice,
gasLimit: _tx.data.gasLimit,
gasUsed,
gasUsedEffective,
txCostLimit,
txCost,
txCostEffective,
execDuration,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, _tx.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

}
}


//const block = this;
const txLogs = [];
let vmStorage = {};

let gasUsed = toBn(0);
let gasUsedEffective = toBn(0);
let status = 1;
let error = null;
let createdContractAddress = null;
let amountTransfered = toBn(0);

const contractAddress = this.data.to;
let storageBackup;
let balanceBackup;
let contractExecuted = false;
let contractDeployed = false;

const execTsStart = performance.now()/1000; //process.hrtime(); //Date.now();
let execDuration = 0;

const txCostLimit = this.data.gasLimit.mul(this.data.gasPrice);
let txCost = toBn(0);
let txCostEffective = toBn(0);


const accountFrom = blockchain.getAccount(this.data.from);
if (! accountFrom) {
_rejectTransaction('TRANSACTION_REJECTED_EMPTY_ACCOUNT');
}
const accountFromBalance = accountFrom.data.balance;


if (accountFrom.address != mintAddress) {

// verif nonce
const expectedNonce = toBn(accountFrom.data.transactions.length); // le nonce attendu est egal au nombre de transactions passées de l'account
if (! this.data.nonce.eq(expectedNonce)) {
_rejectTransaction('TRANSACTION_REJECTED_INCORRECT_NONCE');
}

// verif balance pour gas + transfer
if (this.data.value && this.data.value.gt(0)) {
if (accountFromBalance.lt(this.data.value.add(txCostLimit))) {
// l'adresse n'a pas suffisamment de fond pour executer la transaction (selon gasLimit)
// TODO: faut-il rejeter la tx ? ou la laisser passer (en status=0) et lui vider sa balance (si 100% du gas est utilisé) ?
//return _rejectTransaction('TRANSACTION_FAILED_INSUFFICIENT_ETH_BALANCE');

_rejectTransaction('TRANSACTION_REJECTED_INSUFFICIENT_ETH_BALANCE');
}
}
}



_checkGas('init');


if (status) {
if (this.data.value && this.data.value.gt(0) && contractAddress && contractAddress.length > 2) {
// Transfer la valeur demandée (simple ETH transaction)
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(this.data, block);
txLogs.push( ...transferLogs );

// si recipient == contract => on transfer quand meme dès maintenant => TODO: a revoir (le transferEth declenche un appel à receive ... avant meme de savoir si unne methode payable doit etre appelée. Probleme si receive provoque un revert => la fonction payable ne sera pas appelée)
// 1) si methode payable appellée, on l'execute
// 2) si methode non payable => on appelle receive juste avant
// 3) si methode inconnue => revert


if (transferResult && transferReverted) {
// reverted (after transfer)
amountTransfered = this.data.value;

status = 0;
error = new Error('TRANSFER_REVERTED');

} else if (transferResult) {
// success
amountTransfered = this.data.value;

} else {
// transfer failed
status = 0;
error = new Error('TRANSFER_FAILED');
}

}

_checkGas('afterTransfer');
}

const gasUsedBeforeContractExecution = gasUsed;

if (status && this.data.data && this.data.data != '0x') {
// Traitement du champ data de la transaction (smart contract call)

if (!contractAddress || contractAddress == '0x') {
// contract creation

contractDeployed = true;

let contractExecResult;
try {
contractExecResult = this.transactionDeployContract(block);
const creationLogs = contractExecResult.logs;
vmStorage = contractExecResult.storage;

if (creationLogs.length) {
txLogs.push( ...creationLogs );
}

if (! contractExecResult.result) {
// Deploy Failed
status = 0;
error = new Error('CONTRACT_CREATION_FAILED');

// emit event
const debugError = {
message: 'CONTRACT_CREATION_FAILED',
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);

} else {
// Deploy Success
createdContractAddress = contractExecResult.result.toLowerCase();

if (this.data.value.gt(0)) {
// Transfer tx value to contract
const _txData = {
...this.data,
to: createdContractAddress,
};
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(_txData, block);
txLogs.push( ...transferLogs );

if (transferResult) {
amountTransfered = this.data.value;

} else {
status = 0;
error = new Error('CONTRACT_CREATION_TRANSFER_FAILED');
}
}

// TODO: emit event CONTRACT_CREATION_SUCCESS ?
}



} catch (e) {
//console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] Cannot execute transaction');
//throw e;
status = 0;
error = new Error('CONTRACT_CREATION_ERROR');

console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] CONTRACT_CREATION_ERROR with message', e.message)

// emit event
const debugError = {
message: 'CONTRACT_CREATION_ERROR',
error: e.message,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

_checkGas('afterContractCreation');

} else {
// contract execution

const contractAccount = blockchain.getAccount(contractAddress);

if (! contractAccount) {
status = 0;
error = new Error('CONTRACT_ACCOUNT_NOT_FOUND');

// emit event
const debugError = {
message: 'CONTRACT_ACCOUNT_NOT_FOUND',
address: contractAddress,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

if (status) {
storageBackup = JSON.stringify(contractAccount.data.storage);
balanceBackup = contractAccount.data.balance;
contractExecuted = true;

try {
const contractExecResult = this.executeTransactionContract(block);
const internalLogs = contractExecResult.logs;
vmStorage = contractExecResult.storage;

if (internalLogs.length) {
txLogs.push( ...internalLogs );
}

if (! contractExecResult.result) {
status = 0;
error = new Error('CONTRACT_EXEC_FAILED');

// emit event
const debugError = {
message: 'CONTRACT_EXEC_FAILED',
address: contractAddress,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

} catch (e) {
status = 0;
error = new Error('CONTRACT_EXEC_ERROR');

// emit event
const debugError = {
message: 'CONTRACT_EXEC_ERROR',
address: contractAddress,
error: e.message,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}
}

_checkGas('afterContractCall');
}

}

const gasUsedAfterContractExecution = gasUsed;
const gasUsedInContractExecution = gasUsedAfterContractExecution.sub(gasUsedBeforeContractExecution);
const contractExecutionCost = gasUsedInContractExecution.mul(_tx.data.gasPrice);


console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] execDuration =', (Math.round(execDuration * 1000000) / 1000000), ' seconds');



if (txCostEffective.gt(0)) {
// Gas payment
//const minerAccount = blockchain.getAccount(block.data.miner, true);
//const gasPaymentResult = minerAccount.transferFrom(accountFrom.address, txCostEffective);

const txDataGasPayment = {
from: accountFrom.address,
to: block.data.miner,
value: txCostEffective,
};
const [gasPaymentResult, gasPaymentLogs, transferReverted] = blockchain.transferEth(txDataGasPayment, block, true);
txLogs.push(...gasPaymentLogs);

if (gasPaymentResult) {
// gas payment success

} else {
// gas payment failed => reject transaction (and block)
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] Gas payment failed');
_rejectTransaction('GAS_PAYMENT_FAILED');
}

if (false) {
// DEBUG
const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();
const gasEffectivePercent = gasUsedEffective.mul(100).div(this.data.gasLimit).toNumber();
const costPercent = txCost.mul(100).div(txCostLimit).toNumber();
const costEffectivePercent = txCostEffective.mul(100).div(txCostLimit).toNumber();
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] txCostLimit =', ethers.utils.formatEther(txCostLimit));
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] txCostEffective =', ethers.utils.formatEther(txCostEffective));
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] gas % =', Math.round(gasPercent), '% =', Math.round(gasEffectivePercent), '%');
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] cost % =', Math.round(costPercent), '%', Math.round(costEffectivePercent), '%');
}


}


if (accountFrom.address != mintAddress) {
const accountFromBalanceAtEnd = blockchain.getAccountBalance(accountFrom.address);

if (accountFromBalanceAtEnd.lt(0)){
_rejectTransaction('TRANSACTION_FAILED_INSUFFICIENT_ETH_BALANCE_AT_END');
}
}


if (! status) {
// Transaction Failed

// Revert transfer
if (amountTransfered.gt(0)) {
const txDataRevert = {
...this.data,
from: this.data.to,
to: this.data.from,
value: amountTransfered,
};
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(txDataRevert, block);

// TODO: gerer le revert des internal transfers
}

txLogs.forEach(log => {
log.removed = true;
});

// Revert contract storage
if (contractExecuted) {
const contractAccount = blockchain.getAccount(contractAddress);
contractAccount.data.storage = JSON.parse(storageBackup, utils.bnJsonParser);
contractAccount.data.balance = balanceBackup;

} else if (amountTransfered) {
// on check aussi amountTransfered car le contrat peut avoir été exécuté via receive => TODO: process a revoir
const contractAccount = blockchain.getAccount(contractAddress);
if (contractAccount && contractAccount.isContract()) {
//contractAccount.data.storage = JSON.parse(storageBackup, utils.bnJsonParser);
//contractAccount.data.balance = balanceBackup;
// TODO
}

}

// Revert deployed contract
if (contractDeployed) {
// nothing to do ?
}

const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] FAILED TRANSACTION ', this.data.hash, '| gas usage percent =', gasPercent, '%', ' | Error: ', error.message);

const realExecDuration = performance.now()/1000 - execTsStart; //parseFloat(process.hrtime(execTsStart).join('.'));
const realGasUsed = toBn(Math.round(realExecDuration * cpuPricePerSecond)); // TODO: prendre en compte le storage utilisé dans le calcul du gasUsed
const realGasPercent = realGasUsed.mul(100).div(this.data.gasLimit).toNumber();
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] FAILED TRANSACTION ', this.data.hash, '| real gas usage percent =', realGasPercent, '%', ' | Error: ', error.message);

} else {
// Transaction Success

// save contract storage
Object.entries(vmStorage).forEach((entry, idx) => {
const [contractAddress, contractStorage] = entry;

const contractAccount = blockchain.getAccount(contractAddress, true);
contractAccount.data.storage = contractStorage;
contractAccount.save();
});

// show result (debug)
const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();;
console.log(utils.getDate(), "[BLOCKCHAIN]", 'SUCCECSS TRANSACTION ', this.data.hash, '| gas usage percent =', gasPercent, '%');
}


// total gas utilisé dans le block
block.data.gasUsed = block.data.gasUsed.add(gasUsed);


// assign block data on logs
txLogs.forEach((txLog, idx) => {
txLog.data.transactionIndex = transactionIndex;
txLog.data.logIndex = logIndexOffset + idx;
txLog.data.transactionHash = this.data.hash;
txLog.data.blockHash = block.hash;
txLog.data.blockNumber = block.data.number;
});


const receiptData = {
transactionHash: this.data.hash,
transactionIndex: transactionIndex,
blockNumber: block.data.number,
blockHash: block.hash,
//logsBloom: '0x0000',
contractAddress: createdContractAddress,
gasUsed,
cumulativeGasUsed: block.data.gasUsed,
effectiveGasPrice: this.data.gasPrice,
effectiveCost: this.data.gasPrice.mul(gasUsed),
logs: txLogs,
status,
}

const receipt = new Receipt(blockchain, receiptData);

return [receipt, txLogs];
}"
, executeTransactionContract: "(block) => {
let contractAddress = this.data.to;

const isNullRecipient = (! contractAddress || contractAddress.length <= 2);
if (isNullRecipient) {
throw new Error('CONTRACT_EXEC_ADDRESS_INVALID_BUG_1');
}

const contractAccount = blockchain.getAccount(contractAddress);

if (! contractAccount) {
throw new Error('CONTRACT_EXEC_ACCOUNT_NOT_FOUND');
}

// Execute existing contract method call
return blockchain.callContractMethodInVM(this.data, block);

}"
, transactionDeployContract: "(block) => {
let contractAddress = this.data.to;

let contractAccount;

if (contractAddress && contractAddress.length > 2) {
throw new Error('CONTRACT_DEPLOY_ADDRESS_INVALID_BUG_2');
}


// CONTRACT CREATION

if (! this.data.data || this.data.data.length <= 2) {
throw new Error('CONTRACT_DEPLOY_INVALID_DATA_EMPTY_CODE');
}


// build contract address
contractAddress = ethers.utils.getContractAddress(this.data).toLocaleLowerCase();

// parse contract source code & constructor parameters
const codes = ethers.utils.defaultAbiCoder.decode(["string", "string"], this.data.data);
if (! codes) {
throw new Error('CONTRACT_DEPLOY_CODE_PARSING_FAILED');
}

const sourceCode = codes[0];
const constructorParams = codes[1];
const constructorRawParams = ethers.utils.defaultAbiCoder.encode(["string"], [constructorParams]);
const codeHash = ethers.utils.id(sourceCode);
const codeAbi = null; // l'abi sera généré une fois que le contrat aura été instancié
const storageHash = ethers.utils.id('{}');

// create new account
const accountData = {
address: contractAddress,
code: sourceCode,
codeHash,
codeAbi,
storage: {},
storageHash,
};
contractAccount = blockchain.createAccount(contractAddress, accountData);


// instanciate contract + call constructor + recuperer storage
const txData = {
from: this.data.from,
to: contractAddress,
data: constructorRawParams,
value: this.data.value.toHexString(),
};
const callResult = blockchain.deployContractInVM(txData, block);
const creationLogs = callResult.logs;
const vmStorage = callResult.storage;


// emit event
const eventParams = [
contractAddress,
];
const _txData = {
...this.data,
to: blockchain.getNullAddress(),
}
const deployLog = new TxLog(blockchain, null, _txData, 'ContractCreated', eventParams, deployAbi);
creationLogs.push(deployLog);


return {
result: contractAddress,
logs: creationLogs,
storage: vmStorage,
};
}"
, timestamp: "1651572534826", status: 1, gasUsed: "3043" }, 0x86764232071da39aafa015bef38b5a40f390b01afa54744d0e3965cca608b472: { data: { blockHash: "0x00b78e2d71f5430ea94beb2f6001c4ed6a97e6916a8a000fe57accad16de9ee9", blockNumber: "5", transactionIndex: 1, hash: "0x86764232071da39aafa015bef38b5a40f390b01afa54744d0e3965cca608b472", from: "0x131a190fa22cb867abf58193bf9e7640e9fce682", gasLimit: "1154700", gasPrice: "1000000000", data: "0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000002f200000000000000000000000000000000000000000000000000000000000002eb20a2f2a0a4e616d653a20594f204e46540a56657273696f6e3a20302e302e330a2a2f0a0a0a66756e6374696f6e2049455243373231526563656976657228746f6b656e4164647265737329207b0a2020202072657475726e206164647265737328746f6b656e41646472657373292e676574436f6e747261637428293b0a7d0a0a0a636c617373204552433732315265636569766572207b0a202020206f6e4552433732315265636569766564286f70657261746f722c2066726f6d2c20746f6b656e49642c206461746129207b0a2020202020202020636f6e73742073656c6563746f72203d202730786138666363343137273b202f2f206f6e45524337323152656365697665640a202020202020202072657475726e205b73656c6563746f725d3b0a202020207d3b0a7d0a0a45524337323152656365697665722e70726f746f747970652e6f6e45524337323152656365697665642e696e70757473203d205b2761646472657373272c202761646472657373272c202775696e74323536272c20276279746573275d3b0a45524337323152656365697665722e70726f746f747970652e6f6e45524337323152656365697665642e6f757470757473203d205b27627974657334275d3b0a0a0a0a636c6173732045524337323120657874656e6473204552433732315265636569766572207b2020200a202020206e616d65203d2027273b0a2020202073796d626f6c203d2027273b0a20202020646563696d616c73203d206e756d6265722830293b0a0a202020206f776e657273203d207b7d3b202f2f206d617070696e672875696e74323536203d3e2061646472657373290a2020202062616c616e636573203d207b7d3b202f2f206d617070696e672861646472657373203d3e2075696e74323536290a20202020746f6b656e417070726f76616c73203d207b7d3b202f2f206d617070696e672875696e74323536203d3e2061646472657373290a202020206f70657261746f72417070726f76616c73203d207b7d3b202f2f206d617070696e672861646472657373203d3e206d617070696e672861646472657373203d3e20626f6f6c29290a0a0a202020204d696e74286d696e7465722c20746f2c20746f6b656e496429207b7d202f2f206576656e740a202020204275726e2829207b6f776e65722c206275726e65722c20746f6b656e49647d202f2f206576656e740a202020205472616e736665722866726f6d2c20746f2c20746f6b656e496429207b7d202f2f206576656e740a20202020417070726f76616c286f776e65722c20617070726f7665642c20746f6b656e496429207b7d202f2f206576656e740a20202020417070726f76616c466f72416c6c286f776e65722c206f70657261746f722c20617070726f76656429207b7d202f2f206576656e740a202020200a0a20202020636f6e7374727563746f72286e616d652c2073796d626f6c29207b0a2020202020202020737570657228293b0a2020202020202020746869732e6e616d65203d206e616d653b0a2020202020202020746869732e73796d626f6c203d2073796d626f6c3b0a202020207d0a0a202020200a20202020737570706f727473496e7465726661636528696e74657266616365496429207b0a20202020202020202f2f636f6e737420726573756c74203d20696e746572666163654964203d3d20273078383061633538636427207c7c20696e746572666163654964203d3d20273078356235653133396627207c7c2073757065722e737570706f727473496e7465726661636528696e746572666163654964293b0a2020202020202020636f6e737420726573756c74203d2028696e746572666163654964203d3d20273078383061633538636427207c7c20696e746572666163654964203d3d20273078356235653133396627293b0a202020202020202072657475726e205b726573756c745d3b0a202020207d0a0a0a2020202062616c616e63654f66286f776e65722920207b0a20202020202020206f776e6572203d206f776e65722e746f4c6f7765724361736528293b0a202020202020202072657175697265286f776e657220213d20616464726573732830292e616464726573732c20224552433732313a2061646472657373207a65726f206973206e6f7420612076616c6964206f776e657222293b0a2020202020202020696620286f776e657220696e20746869732e62616c616e63657329207b0a20202020202020202020202072657475726e205b746869732e62616c616e6365735b6f776e65725d5d3b0a20202020202020207d0a202020202020202072657475726e205b4e756d6265722830295d3b0a202020207d0a0a0a202020206f776e65724f6628746f6b656e496429207b0a20202020202020207265717569726528746f6b656e496420696e20746869732e6f776e6572732c20224552433732313a206f776e657220717565727920666f72206e6f6e6578697374656e7420746f6b656e22293b0a2020202020202020636f6e7374206f776e6572203d20746869732e6f776e6572735b746f6b656e49645d3b0a202020202020202072657475726e205b6f776e65725d3b0a202020207d0a0a0a20202020746f6b656e55524928746f6b656e496429207b0a20202020202020207265717569726528746869732e5f65786973747328746f6b656e4964295b305d2c20224552433732314d657461646174613a2055524920717565727920666f72206e6f6e6578697374656e7420746f6b656e22293b0a0a2020202020202020636f6e73742062617365555249203d20746869732e6261736555524928295b305d3b0a2020202020202020636f6e7374205f746f6b656e557269203d20626173655552492e6c656e677468203e2030203f202862617365555249202b20746f6b656e49642e746f537472696e67282929203a2022223b0a202020202020202072657475726e205b5f746f6b656e5572695d3b0a202020207d0a0a0a202020205f626173655552492829207b0a202020202020202072657475726e205b22225d3b0a202020207d0a0a0a20202020617070726f766528746f2c20746f6b656e496429207b0a2020202020202020746f203d20746f2e746f4c6f7765724361736528293b0a20202020202020207265717569726528746869732e5f65786973747328746f6b656e4964295b305d2c20224552433732313a20617070726f76616c206e6f6e6578697374656e7420746f6b656e22293b0a2020202020202020636f6e7374206f776e6572203d20746869732e6f776e65724f6628746f6b656e4964295b305d3b0a20202020202020207265717569726528746f20213d206f776e65722c20224552433732313a20617070726f76616c20746f2063757272656e74206f776e657222293b0a0a202020202020202072657175697265280a2020202020202020202020206d73672e73656e646572203d3d206f776e6572207c7c20746869732e6973417070726f766564466f72416c6c286f776e65722c206d73672e73656e646572295b305d2c0a202020202020202020202020224552433732313a20617070726f76652063616c6c6572206973206e6f74206f776e6572206e6f7220617070726f76656420666f7220616c6c220a2020202020202020293b0a0a2020202020202020746869732e5f617070726f766528746f2c20746f6b656e4964293b0a202020207d0a0a0a20202020676574417070726f76656428746f6b656e496429207b0a20202020202020207265717569726528746869732e5f65786973747328746f6b656e4964295b305d2c20224552433732313a20617070726f76656420717565727920666f72206e6f6e6578697374656e7420746f6b656e22293b0a0a202020202020202069662028746f6b656e496420696e20746869732e746f6b656e417070726f76616c7329207b0a20202020202020202020202072657475726e205b20746869732e746f6b656e417070726f76616c735b746f6b656e49645d205d3b0a20202020202020207d0a0a202020202020202072657475726e205b206e756d626572283029205d3b0a202020207d0a0a0a20202020736574417070726f76616c466f72416c6c286f70657261746f722c20617070726f76656429207b0a20202020202020206f70657261746f72203d206f70657261746f722e746f4c6f7765724361736528293b0a2020202020202020746869732e5f736574417070726f76616c466f72416c6c286d73672e73656e6465722c206f70657261746f722c20617070726f766564293b0a202020207d0a0a0a202020206973417070726f766564466f72416c6c286f776e65722c206f70657261746f7229207b0a20202020202020206f776e6572203d206f776e65722e746f4c6f7765724361736528293b0a20202020202020206f70657261746f72203d206f70657261746f722e746f4c6f7765724361736528293b0a2020202020202020696620286f776e657220696e20746869732e6f70657261746f72417070726f76616c73202626206f70657261746f7220696e20746869732e6f70657261746f72417070726f76616c735b6f776e65725d29207b0a20202020202020202020202072657475726e205b20746869732e6f70657261746f72417070726f76616c735b6f776e65725d5b6f70657261746f725d205d3b0a20202020202020207d0a202020202020202072657475726e205b66616c73655d3b0a202020207d0a0a0a202020207472616e7366657246726f6d2866726f6d2c20746f2c20746f6b656e496429207b0a202020202020202066726f6d203d2066726f6d2e746f4c6f7765724361736528293b0a2020202020202020746f203d20746f2e746f4c6f7765724361736528293b0a20202020202020207265717569726528746869732e5f6973417070726f7665644f724f776e6572286d73672e73656e6465722c20746f6b656e4964295b305d2c20224552433732313a207472616e736665722063616c6c6572206973206e6f74206f776e6572206e6f7220617070726f76656422293b0a2020202020202020746869732e5f7472616e736665722866726f6d2c20746f2c20746f6b656e4964293b0a202020207d0a0a0a20202020736166655472616e7366657246726f6d2866726f6d2c20746f2c20746f6b656e49642c205f6461746129207b0a202020202020202066726f6d203d2066726f6d2e746f4c6f7765724361736528293b0a2020202020202020746f203d20746f2e746f4c6f7765724361736528293b0a20202020202020207265717569726528746869732e5f6973417070726f7665644f724f776e6572286d73672e73656e6465722c20746f6b656e4964295b305d2c20224552433732313a207472616e736665722063616c6c6572206973206e6f74206f776e6572206e6f7220617070726f76656422293b0a2020202020202020746869732e5f736166655472616e736665722866726f6d2c20746f2c20746f6b656e49642c205f64617461293b0a202020207d0a0a0a202020205f736166655472616e736665722866726f6d2c20746f2c20746f6b656e49642c205f6461746129207b0a202020202020202066726f6d203d2066726f6d2e746f4c6f7765724361736528293b0a2020202020202020746f203d20746f2e746f4c6f7765724361736528293b0a2020202020202020746869732e5f7472616e736665722866726f6d2c20746f2c20746f6b656e4964293b0a20202020202020207265717569726528746869732e5f636865636b4f6e45524337323152656365697665642866726f6d2c20746f2c20746f6b656e49642c205f64617461295b305d2c20224552433732313a207472616e7366657220746f206e6f6e20455243373231526563656976657220696d706c656d656e74657222293b0a202020207d0a0a0a202020205f65786973747328746f6b656e496429207b0a202020202020202072657475726e205b20746f6b656e496420696e20746869732e6f776e657273205d3b0a202020207d0a0a0a202020205f6973417070726f7665644f724f776e6572287370656e6465722c20746f6b656e49642920207b0a20202020202020207370656e646572203d207370656e6465722e746f4c6f7765724361736528293b0a202020202020202072657175697265285f65786973747328746f6b656e4964292c20224552433732313a206f70657261746f7220717565727920666f72206e6f6e6578697374656e7420746f6b656e22293b0a2020202020202020636f6e7374206f776e6572203d20746869732e6f776e65724f6628746f6b656e4964295b305d3b0a2020202020202020636f6e737420726573756c74203d20287370656e646572203d3d206f776e6572207c7c20746869732e6973417070726f766564466f72416c6c286f776e65722c207370656e646572295b305d207c7c20746869732e676574417070726f76656428746f6b656e4964295b305d203d3d207370656e646572293b0a202020202020202072657475726e205b726573756c745d3b0a202020207d0a0a0a202020205f736166654d696e7428746f2c20746f6b656e49642c205f6461746129207b0a2020202020202020746f203d20746f2e746f4c6f7765724361736528293b0a2020202020202020746869732e5f6d696e7428746f2c20746f6b656e4964293b0a202020202020202072657175697265280a202020202020202020202020746869732e5f636865636b4f6e455243373231526563656976656428616464726573732830292e616464726573732c20746f2c20746f6b656e49642c205f64617461295b305d2c0a202020202020202020202020224552433732313a207472616e7366657220746f206e6f6e20455243373231526563656976657220696d706c656d656e746572220a2020202020202020293b0a202020207d0a0a0a202020205f6d696e7428746f2c20746f6b656e496429207b0a2020202020202020746f203d20746f2e746f4c6f7765724361736528293b0a20202020202020207265717569726528746f20213d20616464726573732830292e616464726573732c20224552433732313a206d696e7420746f20746865207a65726f206164647265737322293b0a2020202020202020726571756972652821746869732e5f65786973747328746f6b656e4964295b305d2c20224552433732313a20746f6b656e20616c7265616479206d696e74656422293b0a0a2020202020202020746869732e5f6265666f7265546f6b656e5472616e7366657228616464726573732830292e616464726573732c20746f2c20746f6b656e4964293b0a0a2020202020202020746869732e62616c616e6365735b746f5d203d20746869732e62616c616e6365735b746f5d207c7c206e756d6265722830293b0a2020202020202020746869732e62616c616e6365735b746f5d203d20746869732e62616c616e6365735b746f5d2e6164642831293b0a2020202020202020746869732e6f776e6572735b746f6b656e49645d203d20746f3b0a0a2020202020202020656d697428274d696e74272c205b616464726573732830292e616464726573732c20746f2c20746f6b656e49645d293b0a0a2020202020202020746869732e5f6166746572546f6b656e5472616e7366657228616464726573732830292e616464726573732c20746f2c20746f6b656e4964293b0a202020207d0a0a0a202020205f6275726e28746f6b656e496429207b0a2020202020202020636f6e7374206f776e6572203d20746869732e6f776e65724f6628746f6b656e4964295b305d3b0a0a2020202020202020746869732e5f6265666f7265546f6b656e5472616e73666572286f776e65722c20616464726573732830292e616464726573732c20746f6b656e4964293b0a0a20202020202020202f2f20436c65617220617070726f76616c730a2020202020202020746869732e5f617070726f766528616464726573732830292e616464726573732c20746f6b656e4964293b0a0a2020202020202020746869732e62616c616e6365735b6f776e65725d203d20746869732e62616c616e6365735b6f776e65725d2e7375622831293b0a202020202020202064656c65746520746869732e6f776e6572735b746f6b656e49645d3b0a0a2020202020202020656d697428274275726e272c205b6f776e65722c20616464726573732830292e616464726573732c20746f6b656e49645d293b0a0a20202020202020205f6166746572546f6b656e5472616e73666572286f776e65722c20616464726573732830292e616464726573732c20746f6b656e4964293b0a202020207d0a0a0a202020205f7472616e736665722866726f6d2c20746f2c20746f6b656e496429207b0a202020202020202066726f6d203d2066726f6d2e746f4c6f7765724361736528293b0a2020202020202020746f203d20746f2e746f4c6f7765724361736528293b0a20202020202020207265717569726528746869732e6f776e65724f6628746f6b656e4964295b305d203d3d2066726f6d2c20224552433732313a207472616e736665722066726f6d20696e636f7272656374206f776e657222293b0a20202020202020207265717569726528746f20213d20616464726573732830292e616464726573732c20224552433732313a207472616e7366657220746f20746865207a65726f206164647265737322293b0a0a2020202020202020746869732e5f6265666f7265546f6b656e5472616e736665722866726f6d2c20746f2c20746f6b656e4964293b0a0a20202020202020202f2f20436c65617220617070726f76616c732066726f6d207468652070726576696f7573206f776e65720a2020202020202020746869732e5f617070726f766528616464726573732830292e616464726573732c20746f6b656e4964293b0a0a2020202020202020746869732e62616c616e6365735b66726f6d5d203d20746869732e62616c616e6365735b66726f6d5d2e7375622831293b0a2020202020202020746869732e62616c616e6365735b746f5d203d20746869732e62616c616e6365735b746f5d207c7c206e756d6265722830293b0a2020202020202020746869732e62616c616e6365735b746f5d203d20746869732e62616c616e6365735b746f5d2e6164642831293b0a2020202020202020746869732e6f776e6572735b746f6b656e49645d203d20746f3b0a0a2020202020202020656d697428275472616e73666572272c205b66726f6d2c20746f2c20746f6b656e49645d293b0a0a2020202020202020746869732e5f6166746572546f6b656e5472616e736665722866726f6d2c20746f2c20746f6b656e4964293b0a202020207d0a0a0a202020205f617070726f766528617070726f7665642c20746f6b656e496429207b0a2020202020202020617070726f766564203d20617070726f7665642e746f4c6f7765724361736528293b0a2020202020202020746869732e746f6b656e417070726f76616c735b746f6b656e49645d203d20617070726f7665643b0a2020202020202020656d69742827417070726f76616c272c205b746869732e6f776e65724f6628746f6b656e4964295b305d2c20617070726f7665642c20746f6b656e49645d293b0a202020207d0a0a0a202020205f736574417070726f76616c466f72416c6c286f776e65722c206f70657261746f722c20617070726f76656429207b0a20202020202020206f776e6572203d206f776e65722e746f4c6f7765724361736528293b0a20202020202020206f70657261746f72203d206f70657261746f722e746f4c6f7765724361736528293b0a202020202020202072657175697265286f776e657220213d206f70657261746f722c20224552433732313a20617070726f766520746f2063616c6c657222293b0a2020202020202020696620282120286f776e657220696e20746869732e6f70657261746f72417070726f76616c732929207b0a202020202020202020202020746869732e6f70657261746f72417070726f76616c735b6f776e65725d203d207b7d3b0a20202020202020207d0a2020202020202020746869732e6f70657261746f72417070726f76616c735b6f776e65725d5b6f70657261746f725d203d20617070726f7665643b0a2020202020202020656d69742827417070726f76616c466f72416c6c272c205b6f776e65722c206f70657261746f722c20617070726f7665645d293b0a202020207d0a0a0a202020205f636865636b4f6e45524337323152656365697665642866726f6d2c20746f2c20746f6b656e49642c205f6461746129207b0a202020202020202066726f6d203d2066726f6d2e746f4c6f7765724361736528293b0a2020202020202020746f203d20746f2e746f4c6f7765724361736528293b0a2020202020202020696620286164647265737328746f292e6973436f6e7472616374282929207b0a202020202020202020202020747279207b0a20202020202020202020202020202020636f6e73742072657476616c203d2049455243373231526563656976657228746f292e6f6e4552433732315265636569766564286d73672e73656e6465722c2066726f6d2c20746f6b656e49642c205f64617461295b305d3b0a20202020202020202020202020202020636f6e73742073656c6563746f72203d202730786138666363343137273b202f2f206f6e45524337323152656365697665640a2020202020202020202020202020202072657475726e205b72657476616c203d3d2073656c6563746f725d3b0a0a2020202020202020202020207d20636174636820286529207b0a20202020202020202020202020202020636f6e737420726561736f6e203d20652e6d6573736167653b0a2020202020202020202020202020202069662028726561736f6e2e6c656e677468203d3d203029207b0a202020202020202020202020202020202020202072657665727428224552433732313a207472616e7366657220746f206e6f6e20455243373231526563656976657220696d706c656d656e74657222293b0a0a202020202020202020202020202020207d20656c7365207b0a202020202020202020202020202020202020202072657665727428726561736f6e293b0a202020202020202020202020202020207d0a2020202020202020202020207d0a0a20202020202020207d20656c7365207b0a20202020202020202020202072657475726e205b747275655d3b0a20202020202020207d0a202020207d0a0a0a202020205f6265666f7265546f6b656e5472616e736665722866726f6d2c20746f2c20746f6b656e496429207b0a202020207d0a0a202020205f6166746572546f6b656e5472616e736665722866726f6d2c20746f2c20746f6b656e496429207b0a202020207d0a202020200a7d0a0a2f2f204576656e74730a4552433732312e70726f746f747970652e4d696e742e6576656e74203d20747275653b0a4552433732312e70726f746f747970652e4d696e742e696e70757473203d205b276164647265737320696e6465786564272c20276164647265737320696e6465786564272c202775696e74323536275d3b0a0a4552433732312e70726f746f747970652e4275726e2e6576656e74203d20747275653b0a4552433732312e70726f746f747970652e4275726e2e696e70757473203d205b276164647265737320696e6465786564272c20276164647265737320696e6465786564272c202775696e74323536275d3b0a0a4552433732312e70726f746f747970652e5472616e736665722e6576656e74203d20747275653b0a4552433732312e70726f746f747970652e5472616e736665722e696e70757473203d205b276164647265737320696e6465786564272c20276164647265737320696e6465786564272c202775696e74323536275d3b0a0a4552433732312e70726f746f747970652e417070726f76616c2e6576656e74203d20747275653b0a4552433732312e70726f746f747970652e417070726f76616c2e696e70757473203d205b276164647265737320696e6465786564272c20276164647265737320696e6465786564272c202775696e74323536275d3b0a0a4552433732312e70726f746f747970652e417070726f76616c466f72416c6c2e6576656e74203d20747275653b0a4552433732312e70726f746f747970652e417070726f76616c466f72416c6c2e696e70757473203d205b276164647265737320696e6465786564272c20276164647265737320696e6465786564272c202775696e74323536275d3b0a0a2f2f204d6574686f64730a4552433732312e70726f746f747970652e737570706f727473496e746572666163652e76696577203d20747275653b0a4552433732312e70726f746f747970652e737570706f727473496e746572666163652e696e70757473203d205b27627974657334275d3b0a4552433732312e70726f746f747970652e737570706f727473496e746572666163652e6f757470757473203d205b27626f6f6c275d3b0a0a4552433732312e70726f746f747970652e62616c616e63654f662e76696577203d20747275653b0a4552433732312e70726f746f747970652e62616c616e63654f662e696e70757473203d205b2761646472657373275d3b0a4552433732312e70726f746f747970652e62616c616e63654f662e6f757470757473203d205b2775696e74323536275d3b0a0a4552433732312e70726f746f747970652e6f776e65724f662e76696577203d20747275653b0a4552433732312e70726f746f747970652e6f776e65724f662e696e70757473203d205b2775696e74323536275d3b0a4552433732312e70726f746f747970652e6f776e65724f662e6f757470757473203d205b2761646472657373275d3b0a0a4552433732312e70726f746f747970652e746f6b656e5552492e76696577203d20747275653b0a4552433732312e70726f746f747970652e746f6b656e5552492e696e70757473203d205b2775696e74323536275d3b0a4552433732312e70726f746f747970652e746f6b656e5552492e6f757470757473203d205b27737472696e67275d3b0a0a4552433732312e70726f746f747970652e5f626173655552492e696e7465726e616c203d20747275653b0a0a4552433732312e70726f746f747970652e617070726f76652e696e70757473203d205b2761646472657373272c202775696e74323536275d3b0a0a4552433732312e70726f746f747970652e676574417070726f7665642e76696577203d20747275653b0a4552433732312e70726f746f747970652e676574417070726f7665642e6f757470757473203d205b2761646472657373275d3b0a0a4552433732312e70726f746f747970652e736574417070726f76616c466f72416c6c2e696e70757473203d205b2761646472657373272c2027626f6f6c275d3b0a0a4552433732312e70726f746f747970652e6973417070726f766564466f72416c6c2e76696577203d20747275653b0a4552433732312e70726f746f747970652e6973417070726f766564466f72416c6c2e696e70757473203d205b2761646472657373272c202761646472657373275d3b0a0a4552433732312e70726f746f747970652e7472616e7366657246726f6d2e696e70757473203d205b2761646472657373272c202761646472657373272c202775696e74323536275d3b0a0a4552433732312e70726f746f747970652e736166655472616e7366657246726f6d2e696e70757473203d205b2761646472657373272c202761646472657373272c202775696e74323536272c20276279746573275d3b0a0a4552433732312e70726f746f747970652e5f736166655472616e736665722e696e7465726e616c203d20747275653b0a0a4552433732312e70726f746f747970652e5f6578697374732e696e7465726e616c203d20747275653b0a0a4552433732312e70726f746f747970652e5f6973417070726f7665644f724f776e65722e696e7465726e616c203d20747275653b0a0a4552433732312e70726f746f747970652e5f736166654d696e742e696e7465726e616c203d20747275653b0a0a4552433732312e70726f746f747970652e5f6d696e742e696e7465726e616c203d20747275653b0a0a4552433732312e70726f746f747970652e5f6275726e2e696e7465726e616c203d20747275653b0a0a4552433732312e70726f746f747970652e5f7472616e736665722e696e7465726e616c203d20747275653b0a0a4552433732312e70726f746f747970652e5f617070726f76652e696e7465726e616c203d20747275653b0a0a4552433732312e70726f746f747970652e5f736574417070726f76616c466f72416c6c2e696e7465726e616c203d20747275653b0a0a4552433732312e70726f746f747970652e5f636865636b4f6e45524337323152656365697665642e70726976617465203d20747275653b0a0a4552433732312e70726f746f747970652e5f6265666f7265546f6b656e5472616e736665722e696e7465726e616c203d20747275653b0a0a4552433732312e70726f746f747970652e5f6166746572546f6b656e5472616e736665722e696e7465726e616c203d20747275653b0a0a0a0a0a0a636c61737320596f4e465420657874656e647320455243373231207b0a202020206e616d65203d2027594f204e4654273b0a2020202073796d626f6c203d2027596f4e4654273b0a0a202020205f746f6b656e496473203d20303b0a202020205f4d41585f535550504c59203d2031303b0a0a202020206d696e747072696365203d206e756d62657228273127202b202730303030303030303030303030303030303027293b202f2f20312065746865720a0a0a202020205f626173655552492829207b0a2020202020202020636f6e73742062617365555249203d2027687474703a2f2f6c6f63616c686f73743a383534352f636f6c6c656374696f6e732f27202b20616464726573732874686973292e61646472657373202b20272f273b0a202020202020202072657475726e205b2062617365555249205d3b0a202020207d0a0a20202020746f6b656e55524928746f6b656e496429207b0a20202020202020207265717569726528746869732e5f65786973747328746f6b656e4964295b305d2c20224552433732314d657461646174613a2055524920717565727920666f72206e6f6e6578697374656e7420746f6b656e22293b0a0a2020202020202020636f6e73742062617365555249203d20746869732e6261736555524928295b305d3b0a2020202020202020636f6e7374205f746f6b656e557269203d20626173655552492e6c656e677468203e2030203f202862617365555249202b20746f6b656e49642e746f537472696e672829202b20272e6a736f6e2729203a2022223b0a202020202020202072657475726e205b5f746f6b656e5572695d3b0a202020207d0a0a202020206d696e7428726563656976657229207b0a20202020202020207265717569726528746869732e5f746f6b656e496473203c20746869732e5f4d41585f535550504c592c20274d617820737570706c79207265616368656427293b0a202020202020202072657175697265286d73672e76616c75652e657128746869732e6d696e747072696365292c2027496e76616c696420707269636527293b0a2020202020202020746869732e5f746f6b656e4964732b2b3b0a0a2020202020202020636f6e7374206e65774e6674546f6b656e4964203d20746869732e5f746f6b656e4964733b0a2020202020202020746869732e5f736166654d696e742872656365697665722c206e65774e6674546f6b656e49642c202727293b0a0a202020202020202072657475726e205b6e65774e6674546f6b656e49645d3b0a202020207d0a7d0a0a2f2f204d6574686f64730a596f4e46542e70726f746f747970652e746f6b656e5552492e696e70757473203d205b2775696e74323536275d3b0a596f4e46542e70726f746f747970652e746f6b656e5552492e6f757470757473203d205b27737472696e67275d3b0a596f4e46542e70726f746f747970652e746f6b656e5552492e76696577203d20747275653b0a0a596f4e46542e70726f746f747970652e6d696e742e696e70757473203d205b2761646472657373275d3b0a596f4e46542e70726f746f747970652e6d696e742e6f757470757473203d205b2775696e74323536275d3b0a596f4e46542e70726f746f747970652e6d696e742e6d6f64696669657273203d205b276f6e6c794f776e6572275d3b0a596f4e46542e70726f746f747970652e6d696e742e70617961626c65203d20747275653b0a0a0a72657475726e20596f4e46543b0a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000025b5d000000000000000000000000000000000000000000000000000000000000", nonce: "4", to: "0x", value: "0", chainId: "3615", origin: "1651572527414", v: "1c61", r: "2fc3087e36c40e64e6c573d59067566a81b8901ae977aefb08004b58e0f65c6a", s: "8b960c42292215ee3c0bea851b0a5c797b3653c5960c3522d68d4a788c80abf" }, hash: "0x86764232071da39aafa015bef38b5a40f390b01afa54744d0e3965cca608b472", load: "() => {
const transactionFile = config.node.dataDir + '/transactions/' + this.hash + '.json';

if (! fs.existsSync(transactionFile)) {
console.warn(utils.getDate(), "[BLOCKCHAIN]", 'IMPORT Transaction not found', this.hash);
throw new Error('IMPORT_TRANSACTION_NOT_FOUND_DATABASE_CORRUPTED');
}

const transactionJson = fs.readFileSync(transactionFile);
transactionData = JSON.parse(transactionJson, utils.bnJsonParser);

this.data = (typeof transactionData == 'TransactionData') ? transactionData : new TransactionData(transactionData);

return true;
}"
, save: "() => {
const transactionFile = config.node.dataDir + '/transactions/' + this.hash + '.json';
fs.writeFileSync(transactionFile, JSON.stringify(this.data));
return true;
}"
, getReceipt: "() => {
return blockchain.getTransactionReceiptByHash(this.hash);
}"
, getBlock: "() => {
return blockchain.getBlockByHash(this.data.blockHash);
}"
, getBlockTransationPrev: "() => {
const block = this.getBlock();
const txPrevIdx = (this.data.transactionIndex > 0) ? (this.data.transactionIndex-1) : null;
const tx = (block && txPrevIdx in block.transactions) ? block.transactions[txPrevIdx] : null;
return tx;
}"
, getBlockTransationNext: "() => {
const block = this.getBlock();
const txNextIdx = (this.data.transactionIndex < block.transactions.length - 1) ? (this.data.transactionIndex+1) : null;
const tx = (block && txNextIdx in block.transactions) ? block.transactions[txNextIdx] : null;
return tx;
}"
, getAccountTransactionIndex: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = account.data.transactions.findIndex(transactionHash => blockchain.getTransactionByHash(transactionHash).hash == this.hash);
if (txIdx === -1) {
return null;
}
return txIdx;
}"
, getAccountTransationPrev: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = this.getAccountTransactionIndex();
if (txIdx === null) {
return null;
}
const txPrevIdx = (txIdx > 0) ? (txIdx-1) : null;
const transactionHash = (txPrevIdx in account.data.transactions) ? account.data.transactions[txPrevIdx] : null;
if (! transactionHash) {
return null;
}
return blockchain.getTransactionByHash(transactionHash);
}"
, getAccountTransationNext: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = this.getAccountTransactionIndex();
if (txIdx === null) {
return null;
}
const txNextIdx = (txIdx < account.data.transactions.length - 1) ? (txIdx+1) : null;
const transactionHash = (txNextIdx in account.data.transactions) ? account.data.transactions[txNextIdx] : null;
if (! transactionHash) {
return null;
}
return blockchain.getTransactionByHash(transactionHash);

}"
, formatRpc: "() => {
// https://github.com/ethers-io/ethers.js/blob/master/packages/abstract-provider/lib/index.d.ts

const to = (this.data.to && this.data.to.length == 42) ? this.data.to : null;

const _txData = {
blockHash: this.data.blockHash,
blockNumber: this.data.blockNumber.toNumber(),
transactionIndex: this.data.transactionIndex,
hash: this.data.hash,
from: this.data.from,
gasLimit: this.data.gasLimit.toHexString(),
gasPrice: this.data.gasPrice.toHexString(),
data: this.data.data,
nonce: this.data.nonce.toHexString(),
to,
value: this.data.value.toHexString(),
chainId: this.data.chainId.toNumber(),
//origin: this.data.origin.toHexString(),
};

return _txData;
}"
, getHashOLD: "() => {
const _txData = {
gasPrice: this.data.gasPrice,
gasLimit: this.data.gasLimit,
from: this.data.from,
to: this.data.to,
value: this.data.value,
data: this.data.data,
nonce: this.data.nonce,
};

const strvalue = JSON.stringify(utils.orderedObject(_txData));
const transactionHash = ethers.utils.id(strvalue);

return transactionHash;
}"
, getHash: "() => {
const _txData = {
gasPrice: this.data.gasPrice.toHexString(),
gasLimit: this.data.gasLimit.toHexString(),
from: this.data.from,
to: this.data.to,
value: this.data.value.toHexString(),
data: this.data.data,
nonce: this.data.nonce.toHexString(),
v: this.data.v ? ('0x' + this.data.v) : '',
r: this.data.r ? ('0x' + this.data.r) : '',
s: this.data.s ? ('0x' + this.data.s) : '',
};

const tx = new EthereumJS.Transaction(_txData);
const hashRaw = tx.hash();

const transactionHash = ethers.utils.defaultAbiCoder.decode(["uint256"], hashRaw)[0].toHexString();

return transactionHash;
}"
, execute: "(block, transactionIndex, logIndexOffset) => {
const _tx = this;

function _rejectTransaction(reason) {
// https://openethereum.github.io/Transactions-Queue
reason = reason || 'unknown';
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] REJECTED PROCESSING TRANSACTION', _tx.data.hash, ' (Reason:', reason + ')');
const success = blockchain.removeTransactionsFromMempool([_tx.hash], true, true);
if (success) {
saveMempool();
}
throw new Error(reason); // on rejete definitivement la tx (elle ne sera pas minée ... et donc aucun frais de gas ne sera compté)
// et du coup on rejete le block
}

function _checkGas(step) {
execDuration = performance.now()/1000 - execTsStart; //parseFloat(process.hrtime(execTsStart).join('.'));
gasUsed = toBn(Math.round(execDuration * cpuPricePerSecond)); // TODO: prendre en compte le storage utilisé dans le calcul du gasUsed
txCost = gasUsed.mul(_tx.data.gasPrice);

if (step == 'afterTransfer') {
//debugger;
}

if (status) {
gasUsedEffective = gasUsed;
txCostEffective = gasUsedEffective.mul(_tx.data.gasPrice);

if (gasUsed.gt(_tx.data.gasLimit)) {
status = 0;
error = new Error('INSUFFICIENT_GAS');

gasUsedEffective = _tx.data.gasLimit;
txCostEffective = txCostLimit;

// emit event
const debugError = {
message: 'INSUFFICIENT_GAS',
step,
gasPrice: _tx.data.gasPrice,
gasLimit: _tx.data.gasLimit,
gasUsed,
gasUsedEffective,
txCostLimit,
txCost,
txCostEffective,
execDuration,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, _tx.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

}
}


//const block = this;
const txLogs = [];
let vmStorage = {};

let gasUsed = toBn(0);
let gasUsedEffective = toBn(0);
let status = 1;
let error = null;
let createdContractAddress = null;
let amountTransfered = toBn(0);

const contractAddress = this.data.to;
let storageBackup;
let balanceBackup;
let contractExecuted = false;
let contractDeployed = false;

const execTsStart = performance.now()/1000; //process.hrtime(); //Date.now();
let execDuration = 0;

const txCostLimit = this.data.gasLimit.mul(this.data.gasPrice);
let txCost = toBn(0);
let txCostEffective = toBn(0);


const accountFrom = blockchain.getAccount(this.data.from);
if (! accountFrom) {
_rejectTransaction('TRANSACTION_REJECTED_EMPTY_ACCOUNT');
}
const accountFromBalance = accountFrom.data.balance;


if (accountFrom.address != mintAddress) {

// verif nonce
const expectedNonce = toBn(accountFrom.data.transactions.length); // le nonce attendu est egal au nombre de transactions passées de l'account
if (! this.data.nonce.eq(expectedNonce)) {
_rejectTransaction('TRANSACTION_REJECTED_INCORRECT_NONCE');
}

// verif balance pour gas + transfer
if (this.data.value && this.data.value.gt(0)) {
if (accountFromBalance.lt(this.data.value.add(txCostLimit))) {
// l'adresse n'a pas suffisamment de fond pour executer la transaction (selon gasLimit)
// TODO: faut-il rejeter la tx ? ou la laisser passer (en status=0) et lui vider sa balance (si 100% du gas est utilisé) ?
//return _rejectTransaction('TRANSACTION_FAILED_INSUFFICIENT_ETH_BALANCE');

_rejectTransaction('TRANSACTION_REJECTED_INSUFFICIENT_ETH_BALANCE');
}
}
}



_checkGas('init');


if (status) {
if (this.data.value && this.data.value.gt(0) && contractAddress && contractAddress.length > 2) {
// Transfer la valeur demandée (simple ETH transaction)
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(this.data, block);
txLogs.push( ...transferLogs );

// si recipient == contract => on transfer quand meme dès maintenant => TODO: a revoir (le transferEth declenche un appel à receive ... avant meme de savoir si unne methode payable doit etre appelée. Probleme si receive provoque un revert => la fonction payable ne sera pas appelée)
// 1) si methode payable appellée, on l'execute
// 2) si methode non payable => on appelle receive juste avant
// 3) si methode inconnue => revert


if (transferResult && transferReverted) {
// reverted (after transfer)
amountTransfered = this.data.value;

status = 0;
error = new Error('TRANSFER_REVERTED');

} else if (transferResult) {
// success
amountTransfered = this.data.value;

} else {
// transfer failed
status = 0;
error = new Error('TRANSFER_FAILED');
}

}

_checkGas('afterTransfer');
}

const gasUsedBeforeContractExecution = gasUsed;

if (status && this.data.data && this.data.data != '0x') {
// Traitement du champ data de la transaction (smart contract call)

if (!contractAddress || contractAddress == '0x') {
// contract creation

contractDeployed = true;

let contractExecResult;
try {
contractExecResult = this.transactionDeployContract(block);
const creationLogs = contractExecResult.logs;
vmStorage = contractExecResult.storage;

if (creationLogs.length) {
txLogs.push( ...creationLogs );
}

if (! contractExecResult.result) {
// Deploy Failed
status = 0;
error = new Error('CONTRACT_CREATION_FAILED');

// emit event
const debugError = {
message: 'CONTRACT_CREATION_FAILED',
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);

} else {
// Deploy Success
createdContractAddress = contractExecResult.result.toLowerCase();

if (this.data.value.gt(0)) {
// Transfer tx value to contract
const _txData = {
...this.data,
to: createdContractAddress,
};
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(_txData, block);
txLogs.push( ...transferLogs );

if (transferResult) {
amountTransfered = this.data.value;

} else {
status = 0;
error = new Error('CONTRACT_CREATION_TRANSFER_FAILED');
}
}

// TODO: emit event CONTRACT_CREATION_SUCCESS ?
}



} catch (e) {
//console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] Cannot execute transaction');
//throw e;
status = 0;
error = new Error('CONTRACT_CREATION_ERROR');

console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] CONTRACT_CREATION_ERROR with message', e.message)

// emit event
const debugError = {
message: 'CONTRACT_CREATION_ERROR',
error: e.message,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

_checkGas('afterContractCreation');

} else {
// contract execution

const contractAccount = blockchain.getAccount(contractAddress);

if (! contractAccount) {
status = 0;
error = new Error('CONTRACT_ACCOUNT_NOT_FOUND');

// emit event
const debugError = {
message: 'CONTRACT_ACCOUNT_NOT_FOUND',
address: contractAddress,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

if (status) {
storageBackup = JSON.stringify(contractAccount.data.storage);
balanceBackup = contractAccount.data.balance;
contractExecuted = true;

try {
const contractExecResult = this.executeTransactionContract(block);
const internalLogs = contractExecResult.logs;
vmStorage = contractExecResult.storage;

if (internalLogs.length) {
txLogs.push( ...internalLogs );
}

if (! contractExecResult.result) {
status = 0;
error = new Error('CONTRACT_EXEC_FAILED');

// emit event
const debugError = {
message: 'CONTRACT_EXEC_FAILED',
address: contractAddress,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

} catch (e) {
status = 0;
error = new Error('CONTRACT_EXEC_ERROR');

// emit event
const debugError = {
message: 'CONTRACT_EXEC_ERROR',
address: contractAddress,
error: e.message,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}
}

_checkGas('afterContractCall');
}

}

const gasUsedAfterContractExecution = gasUsed;
const gasUsedInContractExecution = gasUsedAfterContractExecution.sub(gasUsedBeforeContractExecution);
const contractExecutionCost = gasUsedInContractExecution.mul(_tx.data.gasPrice);


console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] execDuration =', (Math.round(execDuration * 1000000) / 1000000), ' seconds');



if (txCostEffective.gt(0)) {
// Gas payment
//const minerAccount = blockchain.getAccount(block.data.miner, true);
//const gasPaymentResult = minerAccount.transferFrom(accountFrom.address, txCostEffective);

const txDataGasPayment = {
from: accountFrom.address,
to: block.data.miner,
value: txCostEffective,
};
const [gasPaymentResult, gasPaymentLogs, transferReverted] = blockchain.transferEth(txDataGasPayment, block, true);
txLogs.push(...gasPaymentLogs);

if (gasPaymentResult) {
// gas payment success

} else {
// gas payment failed => reject transaction (and block)
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] Gas payment failed');
_rejectTransaction('GAS_PAYMENT_FAILED');
}

if (false) {
// DEBUG
const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();
const gasEffectivePercent = gasUsedEffective.mul(100).div(this.data.gasLimit).toNumber();
const costPercent = txCost.mul(100).div(txCostLimit).toNumber();
const costEffectivePercent = txCostEffective.mul(100).div(txCostLimit).toNumber();
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] txCostLimit =', ethers.utils.formatEther(txCostLimit));
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] txCostEffective =', ethers.utils.formatEther(txCostEffective));
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] gas % =', Math.round(gasPercent), '% =', Math.round(gasEffectivePercent), '%');
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] cost % =', Math.round(costPercent), '%', Math.round(costEffectivePercent), '%');
}


}


if (accountFrom.address != mintAddress) {
const accountFromBalanceAtEnd = blockchain.getAccountBalance(accountFrom.address);

if (accountFromBalanceAtEnd.lt(0)){
_rejectTransaction('TRANSACTION_FAILED_INSUFFICIENT_ETH_BALANCE_AT_END');
}
}


if (! status) {
// Transaction Failed

// Revert transfer
if (amountTransfered.gt(0)) {
const txDataRevert = {
...this.data,
from: this.data.to,
to: this.data.from,
value: amountTransfered,
};
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(txDataRevert, block);

// TODO: gerer le revert des internal transfers
}

txLogs.forEach(log => {
log.removed = true;
});

// Revert contract storage
if (contractExecuted) {
const contractAccount = blockchain.getAccount(contractAddress);
contractAccount.data.storage = JSON.parse(storageBackup, utils.bnJsonParser);
contractAccount.data.balance = balanceBackup;

} else if (amountTransfered) {
// on check aussi amountTransfered car le contrat peut avoir été exécuté via receive => TODO: process a revoir
const contractAccount = blockchain.getAccount(contractAddress);
if (contractAccount && contractAccount.isContract()) {
//contractAccount.data.storage = JSON.parse(storageBackup, utils.bnJsonParser);
//contractAccount.data.balance = balanceBackup;
// TODO
}

}

// Revert deployed contract
if (contractDeployed) {
// nothing to do ?
}

const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] FAILED TRANSACTION ', this.data.hash, '| gas usage percent =', gasPercent, '%', ' | Error: ', error.message);

const realExecDuration = performance.now()/1000 - execTsStart; //parseFloat(process.hrtime(execTsStart).join('.'));
const realGasUsed = toBn(Math.round(realExecDuration * cpuPricePerSecond)); // TODO: prendre en compte le storage utilisé dans le calcul du gasUsed
const realGasPercent = realGasUsed.mul(100).div(this.data.gasLimit).toNumber();
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] FAILED TRANSACTION ', this.data.hash, '| real gas usage percent =', realGasPercent, '%', ' | Error: ', error.message);

} else {
// Transaction Success

// save contract storage
Object.entries(vmStorage).forEach((entry, idx) => {
const [contractAddress, contractStorage] = entry;

const contractAccount = blockchain.getAccount(contractAddress, true);
contractAccount.data.storage = contractStorage;
contractAccount.save();
});

// show result (debug)
const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();;
console.log(utils.getDate(), "[BLOCKCHAIN]", 'SUCCECSS TRANSACTION ', this.data.hash, '| gas usage percent =', gasPercent, '%');
}


// total gas utilisé dans le block
block.data.gasUsed = block.data.gasUsed.add(gasUsed);


// assign block data on logs
txLogs.forEach((txLog, idx) => {
txLog.data.transactionIndex = transactionIndex;
txLog.data.logIndex = logIndexOffset + idx;
txLog.data.transactionHash = this.data.hash;
txLog.data.blockHash = block.hash;
txLog.data.blockNumber = block.data.number;
});


const receiptData = {
transactionHash: this.data.hash,
transactionIndex: transactionIndex,
blockNumber: block.data.number,
blockHash: block.hash,
//logsBloom: '0x0000',
contractAddress: createdContractAddress,
gasUsed,
cumulativeGasUsed: block.data.gasUsed,
effectiveGasPrice: this.data.gasPrice,
effectiveCost: this.data.gasPrice.mul(gasUsed),
logs: txLogs,
status,
}

const receipt = new Receipt(blockchain, receiptData);

return [receipt, txLogs];
}"
, executeTransactionContract: "(block) => {
let contractAddress = this.data.to;

const isNullRecipient = (! contractAddress || contractAddress.length <= 2);
if (isNullRecipient) {
throw new Error('CONTRACT_EXEC_ADDRESS_INVALID_BUG_1');
}

const contractAccount = blockchain.getAccount(contractAddress);

if (! contractAccount) {
throw new Error('CONTRACT_EXEC_ACCOUNT_NOT_FOUND');
}

// Execute existing contract method call
return blockchain.callContractMethodInVM(this.data, block);

}"
, transactionDeployContract: "(block) => {
let contractAddress = this.data.to;

let contractAccount;

if (contractAddress && contractAddress.length > 2) {
throw new Error('CONTRACT_DEPLOY_ADDRESS_INVALID_BUG_2');
}


// CONTRACT CREATION

if (! this.data.data || this.data.data.length <= 2) {
throw new Error('CONTRACT_DEPLOY_INVALID_DATA_EMPTY_CODE');
}


// build contract address
contractAddress = ethers.utils.getContractAddress(this.data).toLocaleLowerCase();

// parse contract source code & constructor parameters
const codes = ethers.utils.defaultAbiCoder.decode(["string", "string"], this.data.data);
if (! codes) {
throw new Error('CONTRACT_DEPLOY_CODE_PARSING_FAILED');
}

const sourceCode = codes[0];
const constructorParams = codes[1];
const constructorRawParams = ethers.utils.defaultAbiCoder.encode(["string"], [constructorParams]);
const codeHash = ethers.utils.id(sourceCode);
const codeAbi = null; // l'abi sera généré une fois que le contrat aura été instancié
const storageHash = ethers.utils.id('{}');

// create new account
const accountData = {
address: contractAddress,
code: sourceCode,
codeHash,
codeAbi,
storage: {},
storageHash,
};
contractAccount = blockchain.createAccount(contractAddress, accountData);


// instanciate contract + call constructor + recuperer storage
const txData = {
from: this.data.from,
to: contractAddress,
data: constructorRawParams,
value: this.data.value.toHexString(),
};
const callResult = blockchain.deployContractInVM(txData, block);
const creationLogs = callResult.logs;
const vmStorage = callResult.storage;


// emit event
const eventParams = [
contractAddress,
];
const _txData = {
...this.data,
to: blockchain.getNullAddress(),
}
const deployLog = new TxLog(blockchain, null, _txData, 'ContractCreated', eventParams, deployAbi);
creationLogs.push(deployLog);


return {
result: contractAddress,
logs: creationLogs,
storage: vmStorage,
};
}"
, timestamp: "1651572534826", status: 1, gasUsed: "32626" }, 0xdd7fdeab583b5a983e7327d948a4aed7c53e5bb868bdb2d2db2dd9218bd39b90: { data: { blockHash: "0x0097f4ce4ac7dd73246b361656d4f11539bfe8a6ae68479b06a1aefe52729dea", blockNumber: "6", transactionIndex: 0, hash: "0xdd7fdeab583b5a983e7327d948a4aed7c53e5bb868bdb2d2db2dd9218bd39b90", from: "0x0000000000000000000000000000000000000001", gasLimit: "1000000000000000000", gasPrice: "0", data: "0x", nonce: "5", to: "0xccf8ba457dcad7ee6a0361c96846a0f79744b113", value: "250000000000000000000", chainId: "3615", origin: "1651572540008", v: "", r: "", s: "" }, hash: "0xdd7fdeab583b5a983e7327d948a4aed7c53e5bb868bdb2d2db2dd9218bd39b90", load: "() => {
const transactionFile = config.node.dataDir + '/transactions/' + this.hash + '.json';

if (! fs.existsSync(transactionFile)) {
console.warn(utils.getDate(), "[BLOCKCHAIN]", 'IMPORT Transaction not found', this.hash);
throw new Error('IMPORT_TRANSACTION_NOT_FOUND_DATABASE_CORRUPTED');
}

const transactionJson = fs.readFileSync(transactionFile);
transactionData = JSON.parse(transactionJson, utils.bnJsonParser);

this.data = (typeof transactionData == 'TransactionData') ? transactionData : new TransactionData(transactionData);

return true;
}"
, save: "() => {
const transactionFile = config.node.dataDir + '/transactions/' + this.hash + '.json';
fs.writeFileSync(transactionFile, JSON.stringify(this.data));
return true;
}"
, getReceipt: "() => {
return blockchain.getTransactionReceiptByHash(this.hash);
}"
, getBlock: "() => {
return blockchain.getBlockByHash(this.data.blockHash);
}"
, getBlockTransationPrev: "() => {
const block = this.getBlock();
const txPrevIdx = (this.data.transactionIndex > 0) ? (this.data.transactionIndex-1) : null;
const tx = (block && txPrevIdx in block.transactions) ? block.transactions[txPrevIdx] : null;
return tx;
}"
, getBlockTransationNext: "() => {
const block = this.getBlock();
const txNextIdx = (this.data.transactionIndex < block.transactions.length - 1) ? (this.data.transactionIndex+1) : null;
const tx = (block && txNextIdx in block.transactions) ? block.transactions[txNextIdx] : null;
return tx;
}"
, getAccountTransactionIndex: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = account.data.transactions.findIndex(transactionHash => blockchain.getTransactionByHash(transactionHash).hash == this.hash);
if (txIdx === -1) {
return null;
}
return txIdx;
}"
, getAccountTransationPrev: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = this.getAccountTransactionIndex();
if (txIdx === null) {
return null;
}
const txPrevIdx = (txIdx > 0) ? (txIdx-1) : null;
const transactionHash = (txPrevIdx in account.data.transactions) ? account.data.transactions[txPrevIdx] : null;
if (! transactionHash) {
return null;
}
return blockchain.getTransactionByHash(transactionHash);
}"
, getAccountTransationNext: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = this.getAccountTransactionIndex();
if (txIdx === null) {
return null;
}
const txNextIdx = (txIdx < account.data.transactions.length - 1) ? (txIdx+1) : null;
const transactionHash = (txNextIdx in account.data.transactions) ? account.data.transactions[txNextIdx] : null;
if (! transactionHash) {
return null;
}
return blockchain.getTransactionByHash(transactionHash);

}"
, formatRpc: "() => {
// https://github.com/ethers-io/ethers.js/blob/master/packages/abstract-provider/lib/index.d.ts

const to = (this.data.to && this.data.to.length == 42) ? this.data.to : null;

const _txData = {
blockHash: this.data.blockHash,
blockNumber: this.data.blockNumber.toNumber(),
transactionIndex: this.data.transactionIndex,
hash: this.data.hash,
from: this.data.from,
gasLimit: this.data.gasLimit.toHexString(),
gasPrice: this.data.gasPrice.toHexString(),
data: this.data.data,
nonce: this.data.nonce.toHexString(),
to,
value: this.data.value.toHexString(),
chainId: this.data.chainId.toNumber(),
//origin: this.data.origin.toHexString(),
};

return _txData;
}"
, getHashOLD: "() => {
const _txData = {
gasPrice: this.data.gasPrice,
gasLimit: this.data.gasLimit,
from: this.data.from,
to: this.data.to,
value: this.data.value,
data: this.data.data,
nonce: this.data.nonce,
};

const strvalue = JSON.stringify(utils.orderedObject(_txData));
const transactionHash = ethers.utils.id(strvalue);

return transactionHash;
}"
, getHash: "() => {
const _txData = {
gasPrice: this.data.gasPrice.toHexString(),
gasLimit: this.data.gasLimit.toHexString(),
from: this.data.from,
to: this.data.to,
value: this.data.value.toHexString(),
data: this.data.data,
nonce: this.data.nonce.toHexString(),
v: this.data.v ? ('0x' + this.data.v) : '',
r: this.data.r ? ('0x' + this.data.r) : '',
s: this.data.s ? ('0x' + this.data.s) : '',
};

const tx = new EthereumJS.Transaction(_txData);
const hashRaw = tx.hash();

const transactionHash = ethers.utils.defaultAbiCoder.decode(["uint256"], hashRaw)[0].toHexString();

return transactionHash;
}"
, execute: "(block, transactionIndex, logIndexOffset) => {
const _tx = this;

function _rejectTransaction(reason) {
// https://openethereum.github.io/Transactions-Queue
reason = reason || 'unknown';
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] REJECTED PROCESSING TRANSACTION', _tx.data.hash, ' (Reason:', reason + ')');
const success = blockchain.removeTransactionsFromMempool([_tx.hash], true, true);
if (success) {
saveMempool();
}
throw new Error(reason); // on rejete definitivement la tx (elle ne sera pas minée ... et donc aucun frais de gas ne sera compté)
// et du coup on rejete le block
}

function _checkGas(step) {
execDuration = performance.now()/1000 - execTsStart; //parseFloat(process.hrtime(execTsStart).join('.'));
gasUsed = toBn(Math.round(execDuration * cpuPricePerSecond)); // TODO: prendre en compte le storage utilisé dans le calcul du gasUsed
txCost = gasUsed.mul(_tx.data.gasPrice);

if (step == 'afterTransfer') {
//debugger;
}

if (status) {
gasUsedEffective = gasUsed;
txCostEffective = gasUsedEffective.mul(_tx.data.gasPrice);

if (gasUsed.gt(_tx.data.gasLimit)) {
status = 0;
error = new Error('INSUFFICIENT_GAS');

gasUsedEffective = _tx.data.gasLimit;
txCostEffective = txCostLimit;

// emit event
const debugError = {
message: 'INSUFFICIENT_GAS',
step,
gasPrice: _tx.data.gasPrice,
gasLimit: _tx.data.gasLimit,
gasUsed,
gasUsedEffective,
txCostLimit,
txCost,
txCostEffective,
execDuration,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, _tx.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

}
}


//const block = this;
const txLogs = [];
let vmStorage = {};

let gasUsed = toBn(0);
let gasUsedEffective = toBn(0);
let status = 1;
let error = null;
let createdContractAddress = null;
let amountTransfered = toBn(0);

const contractAddress = this.data.to;
let storageBackup;
let balanceBackup;
let contractExecuted = false;
let contractDeployed = false;

const execTsStart = performance.now()/1000; //process.hrtime(); //Date.now();
let execDuration = 0;

const txCostLimit = this.data.gasLimit.mul(this.data.gasPrice);
let txCost = toBn(0);
let txCostEffective = toBn(0);


const accountFrom = blockchain.getAccount(this.data.from);
if (! accountFrom) {
_rejectTransaction('TRANSACTION_REJECTED_EMPTY_ACCOUNT');
}
const accountFromBalance = accountFrom.data.balance;


if (accountFrom.address != mintAddress) {

// verif nonce
const expectedNonce = toBn(accountFrom.data.transactions.length); // le nonce attendu est egal au nombre de transactions passées de l'account
if (! this.data.nonce.eq(expectedNonce)) {
_rejectTransaction('TRANSACTION_REJECTED_INCORRECT_NONCE');
}

// verif balance pour gas + transfer
if (this.data.value && this.data.value.gt(0)) {
if (accountFromBalance.lt(this.data.value.add(txCostLimit))) {
// l'adresse n'a pas suffisamment de fond pour executer la transaction (selon gasLimit)
// TODO: faut-il rejeter la tx ? ou la laisser passer (en status=0) et lui vider sa balance (si 100% du gas est utilisé) ?
//return _rejectTransaction('TRANSACTION_FAILED_INSUFFICIENT_ETH_BALANCE');

_rejectTransaction('TRANSACTION_REJECTED_INSUFFICIENT_ETH_BALANCE');
}
}
}



_checkGas('init');


if (status) {
if (this.data.value && this.data.value.gt(0) && contractAddress && contractAddress.length > 2) {
// Transfer la valeur demandée (simple ETH transaction)
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(this.data, block);
txLogs.push( ...transferLogs );

// si recipient == contract => on transfer quand meme dès maintenant => TODO: a revoir (le transferEth declenche un appel à receive ... avant meme de savoir si unne methode payable doit etre appelée. Probleme si receive provoque un revert => la fonction payable ne sera pas appelée)
// 1) si methode payable appellée, on l'execute
// 2) si methode non payable => on appelle receive juste avant
// 3) si methode inconnue => revert


if (transferResult && transferReverted) {
// reverted (after transfer)
amountTransfered = this.data.value;

status = 0;
error = new Error('TRANSFER_REVERTED');

} else if (transferResult) {
// success
amountTransfered = this.data.value;

} else {
// transfer failed
status = 0;
error = new Error('TRANSFER_FAILED');
}

}

_checkGas('afterTransfer');
}

const gasUsedBeforeContractExecution = gasUsed;

if (status && this.data.data && this.data.data != '0x') {
// Traitement du champ data de la transaction (smart contract call)

if (!contractAddress || contractAddress == '0x') {
// contract creation

contractDeployed = true;

let contractExecResult;
try {
contractExecResult = this.transactionDeployContract(block);
const creationLogs = contractExecResult.logs;
vmStorage = contractExecResult.storage;

if (creationLogs.length) {
txLogs.push( ...creationLogs );
}

if (! contractExecResult.result) {
// Deploy Failed
status = 0;
error = new Error('CONTRACT_CREATION_FAILED');

// emit event
const debugError = {
message: 'CONTRACT_CREATION_FAILED',
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);

} else {
// Deploy Success
createdContractAddress = contractExecResult.result.toLowerCase();

if (this.data.value.gt(0)) {
// Transfer tx value to contract
const _txData = {
...this.data,
to: createdContractAddress,
};
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(_txData, block);
txLogs.push( ...transferLogs );

if (transferResult) {
amountTransfered = this.data.value;

} else {
status = 0;
error = new Error('CONTRACT_CREATION_TRANSFER_FAILED');
}
}

// TODO: emit event CONTRACT_CREATION_SUCCESS ?
}



} catch (e) {
//console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] Cannot execute transaction');
//throw e;
status = 0;
error = new Error('CONTRACT_CREATION_ERROR');

console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] CONTRACT_CREATION_ERROR with message', e.message)

// emit event
const debugError = {
message: 'CONTRACT_CREATION_ERROR',
error: e.message,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

_checkGas('afterContractCreation');

} else {
// contract execution

const contractAccount = blockchain.getAccount(contractAddress);

if (! contractAccount) {
status = 0;
error = new Error('CONTRACT_ACCOUNT_NOT_FOUND');

// emit event
const debugError = {
message: 'CONTRACT_ACCOUNT_NOT_FOUND',
address: contractAddress,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

if (status) {
storageBackup = JSON.stringify(contractAccount.data.storage);
balanceBackup = contractAccount.data.balance;
contractExecuted = true;

try {
const contractExecResult = this.executeTransactionContract(block);
const internalLogs = contractExecResult.logs;
vmStorage = contractExecResult.storage;

if (internalLogs.length) {
txLogs.push( ...internalLogs );
}

if (! contractExecResult.result) {
status = 0;
error = new Error('CONTRACT_EXEC_FAILED');

// emit event
const debugError = {
message: 'CONTRACT_EXEC_FAILED',
address: contractAddress,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

} catch (e) {
status = 0;
error = new Error('CONTRACT_EXEC_ERROR');

// emit event
const debugError = {
message: 'CONTRACT_EXEC_ERROR',
address: contractAddress,
error: e.message,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}
}

_checkGas('afterContractCall');
}

}

const gasUsedAfterContractExecution = gasUsed;
const gasUsedInContractExecution = gasUsedAfterContractExecution.sub(gasUsedBeforeContractExecution);
const contractExecutionCost = gasUsedInContractExecution.mul(_tx.data.gasPrice);


console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] execDuration =', (Math.round(execDuration * 1000000) / 1000000), ' seconds');



if (txCostEffective.gt(0)) {
// Gas payment
//const minerAccount = blockchain.getAccount(block.data.miner, true);
//const gasPaymentResult = minerAccount.transferFrom(accountFrom.address, txCostEffective);

const txDataGasPayment = {
from: accountFrom.address,
to: block.data.miner,
value: txCostEffective,
};
const [gasPaymentResult, gasPaymentLogs, transferReverted] = blockchain.transferEth(txDataGasPayment, block, true);
txLogs.push(...gasPaymentLogs);

if (gasPaymentResult) {
// gas payment success

} else {
// gas payment failed => reject transaction (and block)
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] Gas payment failed');
_rejectTransaction('GAS_PAYMENT_FAILED');
}

if (false) {
// DEBUG
const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();
const gasEffectivePercent = gasUsedEffective.mul(100).div(this.data.gasLimit).toNumber();
const costPercent = txCost.mul(100).div(txCostLimit).toNumber();
const costEffectivePercent = txCostEffective.mul(100).div(txCostLimit).toNumber();
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] txCostLimit =', ethers.utils.formatEther(txCostLimit));
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] txCostEffective =', ethers.utils.formatEther(txCostEffective));
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] gas % =', Math.round(gasPercent), '% =', Math.round(gasEffectivePercent), '%');
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] cost % =', Math.round(costPercent), '%', Math.round(costEffectivePercent), '%');
}


}


if (accountFrom.address != mintAddress) {
const accountFromBalanceAtEnd = blockchain.getAccountBalance(accountFrom.address);

if (accountFromBalanceAtEnd.lt(0)){
_rejectTransaction('TRANSACTION_FAILED_INSUFFICIENT_ETH_BALANCE_AT_END');
}
}


if (! status) {
// Transaction Failed

// Revert transfer
if (amountTransfered.gt(0)) {
const txDataRevert = {
...this.data,
from: this.data.to,
to: this.data.from,
value: amountTransfered,
};
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(txDataRevert, block);

// TODO: gerer le revert des internal transfers
}

txLogs.forEach(log => {
log.removed = true;
});

// Revert contract storage
if (contractExecuted) {
const contractAccount = blockchain.getAccount(contractAddress);
contractAccount.data.storage = JSON.parse(storageBackup, utils.bnJsonParser);
contractAccount.data.balance = balanceBackup;

} else if (amountTransfered) {
// on check aussi amountTransfered car le contrat peut avoir été exécuté via receive => TODO: process a revoir
const contractAccount = blockchain.getAccount(contractAddress);
if (contractAccount && contractAccount.isContract()) {
//contractAccount.data.storage = JSON.parse(storageBackup, utils.bnJsonParser);
//contractAccount.data.balance = balanceBackup;
// TODO
}

}

// Revert deployed contract
if (contractDeployed) {
// nothing to do ?
}

const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] FAILED TRANSACTION ', this.data.hash, '| gas usage percent =', gasPercent, '%', ' | Error: ', error.message);

const realExecDuration = performance.now()/1000 - execTsStart; //parseFloat(process.hrtime(execTsStart).join('.'));
const realGasUsed = toBn(Math.round(realExecDuration * cpuPricePerSecond)); // TODO: prendre en compte le storage utilisé dans le calcul du gasUsed
const realGasPercent = realGasUsed.mul(100).div(this.data.gasLimit).toNumber();
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] FAILED TRANSACTION ', this.data.hash, '| real gas usage percent =', realGasPercent, '%', ' | Error: ', error.message);

} else {
// Transaction Success

// save contract storage
Object.entries(vmStorage).forEach((entry, idx) => {
const [contractAddress, contractStorage] = entry;

const contractAccount = blockchain.getAccount(contractAddress, true);
contractAccount.data.storage = contractStorage;
contractAccount.save();
});

// show result (debug)
const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();;
console.log(utils.getDate(), "[BLOCKCHAIN]", 'SUCCECSS TRANSACTION ', this.data.hash, '| gas usage percent =', gasPercent, '%');
}


// total gas utilisé dans le block
block.data.gasUsed = block.data.gasUsed.add(gasUsed);


// assign block data on logs
txLogs.forEach((txLog, idx) => {
txLog.data.transactionIndex = transactionIndex;
txLog.data.logIndex = logIndexOffset + idx;
txLog.data.transactionHash = this.data.hash;
txLog.data.blockHash = block.hash;
txLog.data.blockNumber = block.data.number;
});


const receiptData = {
transactionHash: this.data.hash,
transactionIndex: transactionIndex,
blockNumber: block.data.number,
blockHash: block.hash,
//logsBloom: '0x0000',
contractAddress: createdContractAddress,
gasUsed,
cumulativeGasUsed: block.data.gasUsed,
effectiveGasPrice: this.data.gasPrice,
effectiveCost: this.data.gasPrice.mul(gasUsed),
logs: txLogs,
status,
}

const receipt = new Receipt(blockchain, receiptData);

return [receipt, txLogs];
}"
, executeTransactionContract: "(block) => {
let contractAddress = this.data.to;

const isNullRecipient = (! contractAddress || contractAddress.length <= 2);
if (isNullRecipient) {
throw new Error('CONTRACT_EXEC_ADDRESS_INVALID_BUG_1');
}

const contractAccount = blockchain.getAccount(contractAddress);

if (! contractAccount) {
throw new Error('CONTRACT_EXEC_ACCOUNT_NOT_FOUND');
}

// Execute existing contract method call
return blockchain.callContractMethodInVM(this.data, block);

}"
, transactionDeployContract: "(block) => {
let contractAddress = this.data.to;

let contractAccount;

if (contractAddress && contractAddress.length > 2) {
throw new Error('CONTRACT_DEPLOY_ADDRESS_INVALID_BUG_2');
}


// CONTRACT CREATION

if (! this.data.data || this.data.data.length <= 2) {
throw new Error('CONTRACT_DEPLOY_INVALID_DATA_EMPTY_CODE');
}


// build contract address
contractAddress = ethers.utils.getContractAddress(this.data).toLocaleLowerCase();

// parse contract source code & constructor parameters
const codes = ethers.utils.defaultAbiCoder.decode(["string", "string"], this.data.data);
if (! codes) {
throw new Error('CONTRACT_DEPLOY_CODE_PARSING_FAILED');
}

const sourceCode = codes[0];
const constructorParams = codes[1];
const constructorRawParams = ethers.utils.defaultAbiCoder.encode(["string"], [constructorParams]);
const codeHash = ethers.utils.id(sourceCode);
const codeAbi = null; // l'abi sera généré une fois que le contrat aura été instancié
const storageHash = ethers.utils.id('{}');

// create new account
const accountData = {
address: contractAddress,
code: sourceCode,
codeHash,
codeAbi,
storage: {},
storageHash,
};
contractAccount = blockchain.createAccount(contractAddress, accountData);


// instanciate contract + call constructor + recuperer storage
const txData = {
from: this.data.from,
to: contractAddress,
data: constructorRawParams,
value: this.data.value.toHexString(),
};
const callResult = blockchain.deployContractInVM(txData, block);
const creationLogs = callResult.logs;
const vmStorage = callResult.storage;


// emit event
const eventParams = [
contractAddress,
];
const _txData = {
...this.data,
to: blockchain.getNullAddress(),
}
const deployLog = new TxLog(blockchain, null, _txData, 'ContractCreated', eventParams, deployAbi);
creationLogs.push(deployLog);


return {
result: contractAddress,
logs: creationLogs,
storage: vmStorage,
};
}"
, timestamp: "1651572545092", status: 1, gasUsed: "2385" }, 0x2d9987bffd02b12a95167d157f4d345b1954b62f043e308b892e916e0b217308: { data: { blockHash: "0x0097f4ce4ac7dd73246b361656d4f11539bfe8a6ae68479b06a1aefe52729dea", blockNumber: "6", transactionIndex: 1, hash: "0x2d9987bffd02b12a95167d157f4d345b1954b62f043e308b892e916e0b217308", from: "0x131a190fa22cb867abf58193bf9e7640e9fce682", gasLimit: "989450", gasPrice: "1000000000", data: "0x0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000224000000000000000000000000000000000000000000000000000000000000021c92f2a0a4e616d653a20486f7420506f7461746f650a56657273696f6e3a20302e302e330a2a2f0a0a0a636c61737320486f74506f7461746f65207b0a202020206e616d65203d2027484f5420506f7461746f65273b0a2020202073796d626f6c203d2027504f5441544f45273b0a0a20202020646563696d616c73203d20303b0a20202020746f74616c537570706c79203d206e756d62657228273027293b0a0a20202020696e69745072696365203d206e756d62657228273127202b202730303030303030303030303030303030303027293b202f2f20312065746865720a2020202061646d696e203d2027273b0a202020200a20202020506f7461746f6554797065203d207b0a20202020202020206964783a206e756d6265722830292c0a20202020202020206d696e7465723a2027272c0a20202020202020206f776e65723a2027272c0a2020202020202020646562746f723a2027272c0a202020202020202070726963653a206e756d6265722830292c0a2020202020202020706172656e744964783a206e756d626572282d31292c0a2020202020202020696e6974446174653a206e756d6265722830292c0a20202020202020207472616e73666572733a206e756d6265722830292c0a202020202020202063756d756c617469766550726963653a206e756d6265722830292c0a202020207d3b0a0a20202020706f7461746f6573203d205b5d3b202f2f206172726179206f6620506f7461746f650a202020207061727469636970616e7473203d207b7d3b202f2f206d617070696e67202861646472657373207061727469636970616e74203d3e206d617070696e672875696e7432353620706f7461746f65496478203d3e20737472696e6720726f6c6529290a0a0a20202020496e697469616c697a65286d696e7465724164647265737329207b7d202f2f206576656e740a202020204d696e7428706f7461746f654964782c206d696e74657229207b7d202f2f206576656e740a202020205472616e7366657228706f7461746f654964782c2073656e6465722c20746f29207b7d202f2f206576656e740a20202020537465616c28706f7461746f654964782c20737465616c657229207b7d202f2f206576656e740a202020205769746864726177616c28616d6f756e7429207b7d202f2f206576656e740a0a0a20202020636f6e7374727563746f722829207b0a2020202020202020746869732e61646d696e203d206d73672e73656e6465723b0a202020207d0a0a20202020696e697469616c697a652829207b0a20202020202020202f2f72657175697265286d73672e73656e646572203d3d20746869732e61646d696e2c20224f4e4c595f41444d494e22293b0a20202020202020207265717569726528746869732e706f7461746f65732e6c656e677468203d3d20302c20224f4e4c595f4f4e434522293b0a2020202020202020746869732e5f6d696e74286d73672e73656e6465722c202d31293b0a0a2020202020202020656d69742827496e697469616c697a65272c205b6d73672e73656e6465725d293b0a202020207d0a0a0a20202020706f7461746f6573436f756e742829207b0a202020202020202072657475726e205b746869732e706f7461746f65732e6c656e6774685d3b0a202020207d0a0a202020205f6d696e74286d696e7465722c20706172656e7449647829207b0a20202020202020206d696e746572203d206d696e7465722e746f4c6f7765724361736528293b0a2020202020202020636f6e737420706f7461746f65496478203d20746869732e706f7461746f65732e6c656e6774683b0a0a2020202020202020636f6e737420706f7461746f65203d207b0a2020202020202020202020206964783a20706f7461746f654964782c0a2020202020202020202020206d696e7465722c0a2020202020202020202020206f776e65723a20616464726573732874686973292e616464726573732c0a202020202020202020202020646562746f723a206d696e7465722c0a20202020202020202020202070726963653a20746869732e696e697450726963652c0a202020202020202020202020706172656e744964782c0a202020202020202020202020696e6974446174653a206e756d62657228626c6f636b2e74696d657374616d70292c0a2020202020202020202020207472616e73666572733a206e756d6265722830292c0a20202020202020202020202063756d756c617469766550726963653a206e756d6265722830292c0a20202020202020207d3b0a2020202020202020746869732e706f7461746f65732e7075736828706f7461746f65293b0a2020202020202020746869732e5f736574486f6c64696e67286d696e7465722c20706f7461746f654964782c20276d696e7465722b646562746f7227293b0a2020202020202020746869732e746f74616c537570706c79203d20746869732e746f74616c537570706c792e6164642831293b0a0a2020202020202020656d697428274d696e74272c205b706f7461746f654964782c206d696e7465725d293b0a202020207d0a0a202020205f6e65787450726963652863757272656e74507269636529207b0a2020202020202020636f6e73742072657761726450657263656e74203d206e756d62657228313530293b202f2f20313530250a2020202020202020636f6e7374207072696365203d2063757272656e7450726963652e6d756c2872657761726450657263656e74292e64697628313030293b0a202020202020202072657475726e205b70726963655d3b0a202020207d0a0a20202020707269636528706f7461746f6549647829207b0a20202020202020207265717569726528706f7461746f6549647820696e20746869732e706f7461746f65732c2022494e56414c49445f504f5441544f455f494422293b0a2020202020202020636f6e737420706f7461746f65203d20746869732e706f7461746f65735b706f7461746f654964785d3b0a2020202020202020636f6e7374207072696365203d20706f7461746f652e70726963653b0a202020202020202072657475726e205b70726963655d3b0a202020207d0a0a202020206e657874507269636528706f7461746f6549647829207b0a20202020202020207265717569726528706f7461746f6549647820696e20746869732e706f7461746f65732c2022494e56414c49445f504f5441544f455f494422293b0a2020202020202020636f6e737420706f7461746f65203d20746869732e706f7461746f65735b706f7461746f654964785d3b0a2020202020202020636f6e7374207072696365203d20746869732e5f6e657874507269636528706f7461746f652e7072696365295b305d3b0a202020202020202072657475726e205b70726963655d3b0a202020207d0a0a202020206f776e657228706f7461746f6549647829207b0a20202020202020207265717569726528706f7461746f6549647820696e20746869732e706f7461746f65732c2022494e56414c49445f504f5441544f455f494422293b0a2020202020202020636f6e737420706f7461746f65203d20746869732e706f7461746f65735b706f7461746f654964785d3b0a2020202020202020636f6e7374206f776e6572203d20706f7461746f652e6f776e65723b0a202020202020202072657475726e205b6f776e65725d3b0a202020207d0a0a20202020646562746f7228706f7461746f6549647829207b0a20202020202020207265717569726528706f7461746f6549647820696e20746869732e706f7461746f65732c2022494e56414c49445f504f5441544f455f494422293b0a2020202020202020636f6e737420706f7461746f65203d20746869732e706f7461746f65735b706f7461746f654964785d3b0a2020202020202020636f6e737420646562746f72203d20706f7461746f652e646562746f723b0a202020202020202072657475726e205b646562746f725d3b0a202020207d0a0a202020207472616e7366657228706f7461746f654964782c20746f29207b0a20202020202020207265717569726528706f7461746f6549647820696e20746869732e706f7461746f65732c2022494e56414c49445f504f5441544f455f494422293b0a2020202020202020636f6e737420706f7461746f65203d20746869732e706f7461746f65735b706f7461746f654964785d3b0a0a2020202020202020636f6e73742073656e646572203d206d73672e73656e6465722e746f4c6f7765724361736528293b0a20202020202020207265717569726528706f7461746f652e646562746f72203d3d2073656e6465722c20224f4e4c595f444542544f5222293b0a0a202020202020202072657175697265286164647265737328746869732920213d20746f2c2022494e56414c49445f54415247455422293b0a202020202020202072657175697265286164647265737328302920213d20746f2c2022494e56414c49445f54415247455422293b0a0a2020202020202020746f203d20746f2e746f4c6f7765724361736528293b0a20202020202020207265717569726528706f7461746f652e6f776e657220213d20746f2c20224e4f5f4241434b545241434b5f544f5f4f574e455222293b0a20202020202020207265717569726528706f7461746f652e6d696e74657220213d20746f2c20224e4f5f4241434b545241434b5f544f5f4d494e54455222293b0a20202020202020200a2020202020202020636f6e7374207072696365203d20706f7461746f652e70726963653b0a2020202020202020726571756972652870726963652e6571286d73672e76616c7565292c2022494e56414c49445f504f5441544f455f505249434522293b0a0a2020202020202020636f6e737420747265617375726546656573203d206d73672e76616c75652e6d756c2831292e6469762831303030293b202f2f20302e3125206665657320666f72207468652074726561737572650a2020202020202020636f6e7374206d696e74657246656573203d206d73672e76616c75652e6d756c2839292e6469762831303030293b2020202f2f20302e3925206665657320666f7220746865206d696e7465720a20202020202020206164647265737328706f7461746f652e6d696e746572292e7472616e73666572286d696e74657246656573293b0a20202020202020206164647265737328706f7461746f652e6f776e6572292e7472616e73666572286d73672e76616c75652e73756228747265617375726546656573292e737562286d696e7465724665657329293b202f2f20726570617920282b626f6e75732d6665657329207468652070726576696f7573206f776e65720a0a202020202020202069662028706f7461746f652e6f776e6572203d3d20706f7461746f652e6d696e74657229207b0a202020202020202020202020746869732e5f736574486f6c64696e6728706f7461746f652e6f776e65722c20706f7461746f654964782c20276d696e7465722b7265746972656427293b0a20202020202020207d20656c73652069662028706f7461746f652e6f776e657220213d20616464726573732874686973292e6164647265737329207b0a202020202020202020202020746869732e5f736574486f6c64696e6728706f7461746f652e6f776e65722c20706f7461746f654964782c20277265746972656427293b0a20202020202020207d0a0a20202020202020206966202873656e646572203d3d20706f7461746f652e6d696e74657229207b0a202020202020202020202020746869732e5f736574486f6c64696e672873656e6465722c20706f7461746f654964782c20276d696e7465722b6f776e657227293b0a20202020202020207d20656c7365207b0a202020202020202020202020746869732e5f736574486f6c64696e672873656e6465722c20706f7461746f654964782c20276f776e657227293b0a20202020202020207d0a0a202020202020202069662028746f203d3d20706f7461746f652e6d696e74657229207b0a202020202020202020202020746869732e5f736574486f6c64696e6728746f2c20706f7461746f654964782c20276d696e7465722b646562746f7227293b0a20202020202020207d20656c7365207b0a202020202020202020202020746869732e5f736574486f6c64696e6728746f2c20706f7461746f654964782c2027646562746f7227293b0a20202020202020207d0a0a2020202020202020706f7461746f652e6f776e6572203d2073656e6465723b202f2f206e6577206f776e657220286f6c6420646562746f72290a2020202020202020706f7461746f652e646562746f72203d20746f3b202f2f206e657720646562746f720a2020202020202020706f7461746f652e7472616e7366657273203d20706f7461746f652e7472616e73666572732e6164642831293b0a0a20202020202020206966202873656e64657220213d20746f29207b0a202020202020202020202020706f7461746f652e63756d756c61746976655072696365203d20706f7461746f652e63756d756c617469766550726963652e616464286d73672e76616c7565293b0a20202020202020207d0a0a2020202020202020636f6e7374206e65775072696365203d20746869732e5f6e6578745072696365287072696365295b305d3b0a2020202020202020706f7461746f652e7072696365203d206e657750726963653b202f2f206e65772070726963650a0a2020202020202020656d697428275472616e73666572272c205b706f7461746f654964782c2073656e6465722c20746f5d293b0a20202020202020200a2020202020202020746869732e5f6d696e74286d73672e73656e6465722c20706f7461746f652e696478293b0a202020207d0a0a20202020686f6c64696e6773287061727469636970616e744164647265737329207b0a20202020202020207061727469636970616e7441646472657373203d207061727469636970616e74416464726573732e746f4c6f7765724361736528293b0a202020202020202072657175697265287061727469636970616e744164647265737320696e20746869732e7061727469636970616e74732c2022494e56414c49445f5041525449434950414e5422293b0a0a2020202020202020636f6e737420726573756c74203d204f626a6563742e656e747269657328746869732e7061727469636970616e74735b7061727469636970616e74416464726573735d292e6d61702828656e747279203d3e207b0a202020202020202020202020636f6e7374205b706f7461746f654964782c20726f6c655d203d20656e7472793b0a20202020202020202020202072657475726e20706f7461746f65496478202b20273a27202b20726f6c653b0a20202020202020207d29292e6a6f696e28272027293b0a202020202020202072657475726e205b726573756c745d3b0a202020207d0a0a202020205f736574486f6c64696e67287061727469636970616e74416464726573732c20706f7461746f654964782c20726f6c6529207b0a20202020202020207061727469636970616e7441646472657373203d207061727469636970616e74416464726573732e746f4c6f7765724361736528293b0a2020202020202020696620282120287061727469636970616e744164647265737320696e20746869732e7061727469636970616e74732929207b0a202020202020202020202020746869732e7061727469636970616e74735b7061727469636970616e74416464726573735d203d207b7d3b0a20202020202020207d0a2020202020202020746869732e7061727469636970616e74735b7061727469636970616e74416464726573735d5b706f7461746f654964785d203d20726f6c653b0a202020207d0a0a20202020737465616c28706f7461746f6549647829207b0a20202020202020207265717569726528706f7461746f6549647820696e20746869732e706f7461746f65732c2022494e56414c49445f504f5441544f455f494422293b0a2020202020202020636f6e737420706f7461746f65203d20746869732e706f7461746f65735b706f7461746f654964785d3b0a0a2020202020202020636f6e737420737465616c6572203d206d73672e73656e6465722e746f4c6f7765724361736528293b0a20202020202020207265717569726528706f7461746f652e6f776e657220213d20737465616c65722c2022494e56414c49445f535445414c45525f4f574e455222293b0a20202020202020207265717569726528706f7461746f652e646562746f7220213d20737465616c65722c2022494e56414c49445f535445414c45525f444542544f5222293b0a0a2020202020202020636f6e7374205f737465616c5072696365203d20746869732e6e657874507269636528706f7461746f65496478295b305d3b0a202020202020202072657175697265285f737465616c50726963652e6571286d73672e76616c7565292c2022494e56414c49445f504f5441544f455f535445414c5f505249434522293b0a0a2020202020202020706f7461746f652e6f776e6572203d20737465616c65723b202f2f20737465616c6572206973206e6f7720746865206f776e6572203d3e2068652077696c6c206e6f742070617920746865207472616e7366657220287472616e7366657220746f2068696d73656c66290a2020202020202020706f7461746f652e646562746f72203d20737465616c65723b0a2020202020202020706f7461746f652e7072696365203d205f737465616c50726963653b0a0a2020202020202020706f7461746f652e63756d756c61746976655072696365203d20706f7461746f652e63756d756c617469766550726963652e616464286d73672e76616c7565293b0a0a2020202020202020746869732e5f736574486f6c64696e6728737465616c65722c20706f7461746f654964782c2027737465616c657227293b0a0a2020202020202020656d69742827537465616c272c205b706f7461746f654964782c20737465616c65725d293b0a202020207d0a0a20202020776974686472617728616d6f756e7429207b0a202020202020202072657175697265286d73672e73656e646572203d3d20746869732e61646d696e2c20224f4e4c595f41444d494e22293b0a20202020202020206164647265737328746869732e61646d696e292e7472616e7366657228616d6f756e74293b0a2020202020202020656d697428275769746864726177616c272c205b616d6f756e745d293b0a202020207d0a0a7d0a0a2f2f204576656e74730a486f74506f7461746f652e70726f746f747970652e496e697469616c697a652e6576656e74203d20747275653b0a486f74506f7461746f652e70726f746f747970652e496e697469616c697a652e696e70757473203d205b276164647265737320696e6465786564275d3b0a0a486f74506f7461746f652e70726f746f747970652e4d696e742e6576656e74203d20747275653b0a486f74506f7461746f652e70726f746f747970652e4d696e742e696e70757473203d205b2775696e7432353620696e6465786564272c20276164647265737320696e6465786564275d3b0a0a486f74506f7461746f652e70726f746f747970652e5472616e736665722e6576656e74203d20747275653b0a486f74506f7461746f652e70726f746f747970652e5472616e736665722e696e70757473203d205b2775696e7432353620696e6465786564272c20276164647265737320696e6465786564272c20276164647265737320696e6465786564275d3b0a0a486f74506f7461746f652e70726f746f747970652e537465616c2e6576656e74203d20747275653b0a486f74506f7461746f652e70726f746f747970652e537465616c2e696e70757473203d205b2775696e7432353620696e6465786564272c20276164647265737320696e6465786564275d3b0a0a486f74506f7461746f652e70726f746f747970652e5769746864726177616c2e6576656e74203d20747275653b0a486f74506f7461746f652e70726f746f747970652e5769746864726177616c2e696e70757473203d205b2775696e74323536275d3b0a0a0a2f2f204d6574686f64730a486f74506f7461746f652e70726f746f747970652e5f6d696e742e696e7465726e616c203d20747275653b0a0a486f74506f7461746f652e70726f746f747970652e5f6e65787450726963652e696e7465726e616c203d20747275653b0a0a486f74506f7461746f652e70726f746f747970652e7472616e736665722e70617961626c65203d20747275653b0a486f74506f7461746f652e70726f746f747970652e7472616e736665722e696e70757473203d205b2775696e74323536272c202761646472657373275d3b0a0a486f74506f7461746f652e70726f746f747970652e70726963652e76696577203d20747275653b0a486f74506f7461746f652e70726f746f747970652e70726963652e696e70757473203d205b2775696e74323536275d3b0a486f74506f7461746f652e70726f746f747970652e70726963652e6f757470757473203d205b2775696e74323536275d3b0a0a486f74506f7461746f652e70726f746f747970652e706f7461746f6573436f756e742e76696577203d20747275653b0a486f74506f7461746f652e70726f746f747970652e706f7461746f6573436f756e742e6f757470757473203d205b2775696e74323536275d3b0a0a486f74506f7461746f652e70726f746f747970652e6e65787450726963652e76696577203d20747275653b0a486f74506f7461746f652e70726f746f747970652e6e65787450726963652e696e70757473203d205b2775696e74323536275d3b0a486f74506f7461746f652e70726f746f747970652e6e65787450726963652e6f757470757473203d205b2775696e74323536275d3b0a0a486f74506f7461746f652e70726f746f747970652e6f776e65722e76696577203d20747275653b0a486f74506f7461746f652e70726f746f747970652e6f776e65722e696e70757473203d205b2775696e74323536275d3b0a486f74506f7461746f652e70726f746f747970652e6f776e65722e6f757470757473203d205b2761646472657373275d3b0a0a486f74506f7461746f652e70726f746f747970652e646562746f722e76696577203d20747275653b0a486f74506f7461746f652e70726f746f747970652e646562746f722e696e70757473203d205b2775696e74323536275d3b0a486f74506f7461746f652e70726f746f747970652e646562746f722e6f757470757473203d205b2761646472657373275d3b0a0a486f74506f7461746f652e70726f746f747970652e686f6c64696e67732e76696577203d20747275653b0a486f74506f7461746f652e70726f746f747970652e686f6c64696e67732e696e70757473203d205b2761646472657373275d3b0a486f74506f7461746f652e70726f746f747970652e686f6c64696e67732e6f757470757473203d205b27737472696e67275d3b0a0a486f74506f7461746f652e70726f746f747970652e5f736574486f6c64696e672e696e7465726e616c203d20747275653b0a0a486f74506f7461746f652e70726f746f747970652e737465616c2e70617961626c65203d20747275653b0a486f74506f7461746f652e70726f746f747970652e737465616c2e696e70757473203d205b2775696e74323536275d3b0a0a486f74506f7461746f652e70726f746f747970652e77697468647261772e696e70757473203d205b2775696e74323536275d3b0a0a72657475726e20486f74506f7461746f653b0a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000025b5d000000000000000000000000000000000000000000000000000000000000", nonce: "5", to: "0x", value: "0", chainId: "3615", origin: "1651572536772", v: "1c62", r: "bd32c1efb50f46056411c4565ff213b9662d532f7f29409aa616ed9410647639", s: "221598b4931dfb612a38c235dcaddc2e1c8167d69db03932bcdbdb96b6bc1ae3" }, hash: "0x2d9987bffd02b12a95167d157f4d345b1954b62f043e308b892e916e0b217308", load: "() => {
const transactionFile = config.node.dataDir + '/transactions/' + this.hash + '.json';

if (! fs.existsSync(transactionFile)) {
console.warn(utils.getDate(), "[BLOCKCHAIN]", 'IMPORT Transaction not found', this.hash);
throw new Error('IMPORT_TRANSACTION_NOT_FOUND_DATABASE_CORRUPTED');
}

const transactionJson = fs.readFileSync(transactionFile);
transactionData = JSON.parse(transactionJson, utils.bnJsonParser);

this.data = (typeof transactionData == 'TransactionData') ? transactionData : new TransactionData(transactionData);

return true;
}"
, save: "() => {
const transactionFile = config.node.dataDir + '/transactions/' + this.hash + '.json';
fs.writeFileSync(transactionFile, JSON.stringify(this.data));
return true;
}"
, getReceipt: "() => {
return blockchain.getTransactionReceiptByHash(this.hash);
}"
, getBlock: "() => {
return blockchain.getBlockByHash(this.data.blockHash);
}"
, getBlockTransationPrev: "() => {
const block = this.getBlock();
const txPrevIdx = (this.data.transactionIndex > 0) ? (this.data.transactionIndex-1) : null;
const tx = (block && txPrevIdx in block.transactions) ? block.transactions[txPrevIdx] : null;
return tx;
}"
, getBlockTransationNext: "() => {
const block = this.getBlock();
const txNextIdx = (this.data.transactionIndex < block.transactions.length - 1) ? (this.data.transactionIndex+1) : null;
const tx = (block && txNextIdx in block.transactions) ? block.transactions[txNextIdx] : null;
return tx;
}"
, getAccountTransactionIndex: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = account.data.transactions.findIndex(transactionHash => blockchain.getTransactionByHash(transactionHash).hash == this.hash);
if (txIdx === -1) {
return null;
}
return txIdx;
}"
, getAccountTransationPrev: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = this.getAccountTransactionIndex();
if (txIdx === null) {
return null;
}
const txPrevIdx = (txIdx > 0) ? (txIdx-1) : null;
const transactionHash = (txPrevIdx in account.data.transactions) ? account.data.transactions[txPrevIdx] : null;
if (! transactionHash) {
return null;
}
return blockchain.getTransactionByHash(transactionHash);
}"
, getAccountTransationNext: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = this.getAccountTransactionIndex();
if (txIdx === null) {
return null;
}
const txNextIdx = (txIdx < account.data.transactions.length - 1) ? (txIdx+1) : null;
const transactionHash = (txNextIdx in account.data.transactions) ? account.data.transactions[txNextIdx] : null;
if (! transactionHash) {
return null;
}
return blockchain.getTransactionByHash(transactionHash);

}"
, formatRpc: "() => {
// https://github.com/ethers-io/ethers.js/blob/master/packages/abstract-provider/lib/index.d.ts

const to = (this.data.to && this.data.to.length == 42) ? this.data.to : null;

const _txData = {
blockHash: this.data.blockHash,
blockNumber: this.data.blockNumber.toNumber(),
transactionIndex: this.data.transactionIndex,
hash: this.data.hash,
from: this.data.from,
gasLimit: this.data.gasLimit.toHexString(),
gasPrice: this.data.gasPrice.toHexString(),
data: this.data.data,
nonce: this.data.nonce.toHexString(),
to,
value: this.data.value.toHexString(),
chainId: this.data.chainId.toNumber(),
//origin: this.data.origin.toHexString(),
};

return _txData;
}"
, getHashOLD: "() => {
const _txData = {
gasPrice: this.data.gasPrice,
gasLimit: this.data.gasLimit,
from: this.data.from,
to: this.data.to,
value: this.data.value,
data: this.data.data,
nonce: this.data.nonce,
};

const strvalue = JSON.stringify(utils.orderedObject(_txData));
const transactionHash = ethers.utils.id(strvalue);

return transactionHash;
}"
, getHash: "() => {
const _txData = {
gasPrice: this.data.gasPrice.toHexString(),
gasLimit: this.data.gasLimit.toHexString(),
from: this.data.from,
to: this.data.to,
value: this.data.value.toHexString(),
data: this.data.data,
nonce: this.data.nonce.toHexString(),
v: this.data.v ? ('0x' + this.data.v) : '',
r: this.data.r ? ('0x' + this.data.r) : '',
s: this.data.s ? ('0x' + this.data.s) : '',
};

const tx = new EthereumJS.Transaction(_txData);
const hashRaw = tx.hash();

const transactionHash = ethers.utils.defaultAbiCoder.decode(["uint256"], hashRaw)[0].toHexString();

return transactionHash;
}"
, execute: "(block, transactionIndex, logIndexOffset) => {
const _tx = this;

function _rejectTransaction(reason) {
// https://openethereum.github.io/Transactions-Queue
reason = reason || 'unknown';
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] REJECTED PROCESSING TRANSACTION', _tx.data.hash, ' (Reason:', reason + ')');
const success = blockchain.removeTransactionsFromMempool([_tx.hash], true, true);
if (success) {
saveMempool();
}
throw new Error(reason); // on rejete definitivement la tx (elle ne sera pas minée ... et donc aucun frais de gas ne sera compté)
// et du coup on rejete le block
}

function _checkGas(step) {
execDuration = performance.now()/1000 - execTsStart; //parseFloat(process.hrtime(execTsStart).join('.'));
gasUsed = toBn(Math.round(execDuration * cpuPricePerSecond)); // TODO: prendre en compte le storage utilisé dans le calcul du gasUsed
txCost = gasUsed.mul(_tx.data.gasPrice);

if (step == 'afterTransfer') {
//debugger;
}

if (status) {
gasUsedEffective = gasUsed;
txCostEffective = gasUsedEffective.mul(_tx.data.gasPrice);

if (gasUsed.gt(_tx.data.gasLimit)) {
status = 0;
error = new Error('INSUFFICIENT_GAS');

gasUsedEffective = _tx.data.gasLimit;
txCostEffective = txCostLimit;

// emit event
const debugError = {
message: 'INSUFFICIENT_GAS',
step,
gasPrice: _tx.data.gasPrice,
gasLimit: _tx.data.gasLimit,
gasUsed,
gasUsedEffective,
txCostLimit,
txCost,
txCostEffective,
execDuration,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, _tx.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

}
}


//const block = this;
const txLogs = [];
let vmStorage = {};

let gasUsed = toBn(0);
let gasUsedEffective = toBn(0);
let status = 1;
let error = null;
let createdContractAddress = null;
let amountTransfered = toBn(0);

const contractAddress = this.data.to;
let storageBackup;
let balanceBackup;
let contractExecuted = false;
let contractDeployed = false;

const execTsStart = performance.now()/1000; //process.hrtime(); //Date.now();
let execDuration = 0;

const txCostLimit = this.data.gasLimit.mul(this.data.gasPrice);
let txCost = toBn(0);
let txCostEffective = toBn(0);


const accountFrom = blockchain.getAccount(this.data.from);
if (! accountFrom) {
_rejectTransaction('TRANSACTION_REJECTED_EMPTY_ACCOUNT');
}
const accountFromBalance = accountFrom.data.balance;


if (accountFrom.address != mintAddress) {

// verif nonce
const expectedNonce = toBn(accountFrom.data.transactions.length); // le nonce attendu est egal au nombre de transactions passées de l'account
if (! this.data.nonce.eq(expectedNonce)) {
_rejectTransaction('TRANSACTION_REJECTED_INCORRECT_NONCE');
}

// verif balance pour gas + transfer
if (this.data.value && this.data.value.gt(0)) {
if (accountFromBalance.lt(this.data.value.add(txCostLimit))) {
// l'adresse n'a pas suffisamment de fond pour executer la transaction (selon gasLimit)
// TODO: faut-il rejeter la tx ? ou la laisser passer (en status=0) et lui vider sa balance (si 100% du gas est utilisé) ?
//return _rejectTransaction('TRANSACTION_FAILED_INSUFFICIENT_ETH_BALANCE');

_rejectTransaction('TRANSACTION_REJECTED_INSUFFICIENT_ETH_BALANCE');
}
}
}



_checkGas('init');


if (status) {
if (this.data.value && this.data.value.gt(0) && contractAddress && contractAddress.length > 2) {
// Transfer la valeur demandée (simple ETH transaction)
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(this.data, block);
txLogs.push( ...transferLogs );

// si recipient == contract => on transfer quand meme dès maintenant => TODO: a revoir (le transferEth declenche un appel à receive ... avant meme de savoir si unne methode payable doit etre appelée. Probleme si receive provoque un revert => la fonction payable ne sera pas appelée)
// 1) si methode payable appellée, on l'execute
// 2) si methode non payable => on appelle receive juste avant
// 3) si methode inconnue => revert


if (transferResult && transferReverted) {
// reverted (after transfer)
amountTransfered = this.data.value;

status = 0;
error = new Error('TRANSFER_REVERTED');

} else if (transferResult) {
// success
amountTransfered = this.data.value;

} else {
// transfer failed
status = 0;
error = new Error('TRANSFER_FAILED');
}

}

_checkGas('afterTransfer');
}

const gasUsedBeforeContractExecution = gasUsed;

if (status && this.data.data && this.data.data != '0x') {
// Traitement du champ data de la transaction (smart contract call)

if (!contractAddress || contractAddress == '0x') {
// contract creation

contractDeployed = true;

let contractExecResult;
try {
contractExecResult = this.transactionDeployContract(block);
const creationLogs = contractExecResult.logs;
vmStorage = contractExecResult.storage;

if (creationLogs.length) {
txLogs.push( ...creationLogs );
}

if (! contractExecResult.result) {
// Deploy Failed
status = 0;
error = new Error('CONTRACT_CREATION_FAILED');

// emit event
const debugError = {
message: 'CONTRACT_CREATION_FAILED',
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);

} else {
// Deploy Success
createdContractAddress = contractExecResult.result.toLowerCase();

if (this.data.value.gt(0)) {
// Transfer tx value to contract
const _txData = {
...this.data,
to: createdContractAddress,
};
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(_txData, block);
txLogs.push( ...transferLogs );

if (transferResult) {
amountTransfered = this.data.value;

} else {
status = 0;
error = new Error('CONTRACT_CREATION_TRANSFER_FAILED');
}
}

// TODO: emit event CONTRACT_CREATION_SUCCESS ?
}



} catch (e) {
//console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] Cannot execute transaction');
//throw e;
status = 0;
error = new Error('CONTRACT_CREATION_ERROR');

console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] CONTRACT_CREATION_ERROR with message', e.message)

// emit event
const debugError = {
message: 'CONTRACT_CREATION_ERROR',
error: e.message,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

_checkGas('afterContractCreation');

} else {
// contract execution

const contractAccount = blockchain.getAccount(contractAddress);

if (! contractAccount) {
status = 0;
error = new Error('CONTRACT_ACCOUNT_NOT_FOUND');

// emit event
const debugError = {
message: 'CONTRACT_ACCOUNT_NOT_FOUND',
address: contractAddress,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

if (status) {
storageBackup = JSON.stringify(contractAccount.data.storage);
balanceBackup = contractAccount.data.balance;
contractExecuted = true;

try {
const contractExecResult = this.executeTransactionContract(block);
const internalLogs = contractExecResult.logs;
vmStorage = contractExecResult.storage;

if (internalLogs.length) {
txLogs.push( ...internalLogs );
}

if (! contractExecResult.result) {
status = 0;
error = new Error('CONTRACT_EXEC_FAILED');

// emit event
const debugError = {
message: 'CONTRACT_EXEC_FAILED',
address: contractAddress,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

} catch (e) {
status = 0;
error = new Error('CONTRACT_EXEC_ERROR');

// emit event
const debugError = {
message: 'CONTRACT_EXEC_ERROR',
address: contractAddress,
error: e.message,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}
}

_checkGas('afterContractCall');
}

}

const gasUsedAfterContractExecution = gasUsed;
const gasUsedInContractExecution = gasUsedAfterContractExecution.sub(gasUsedBeforeContractExecution);
const contractExecutionCost = gasUsedInContractExecution.mul(_tx.data.gasPrice);


console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] execDuration =', (Math.round(execDuration * 1000000) / 1000000), ' seconds');



if (txCostEffective.gt(0)) {
// Gas payment
//const minerAccount = blockchain.getAccount(block.data.miner, true);
//const gasPaymentResult = minerAccount.transferFrom(accountFrom.address, txCostEffective);

const txDataGasPayment = {
from: accountFrom.address,
to: block.data.miner,
value: txCostEffective,
};
const [gasPaymentResult, gasPaymentLogs, transferReverted] = blockchain.transferEth(txDataGasPayment, block, true);
txLogs.push(...gasPaymentLogs);

if (gasPaymentResult) {
// gas payment success

} else {
// gas payment failed => reject transaction (and block)
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] Gas payment failed');
_rejectTransaction('GAS_PAYMENT_FAILED');
}

if (false) {
// DEBUG
const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();
const gasEffectivePercent = gasUsedEffective.mul(100).div(this.data.gasLimit).toNumber();
const costPercent = txCost.mul(100).div(txCostLimit).toNumber();
const costEffectivePercent = txCostEffective.mul(100).div(txCostLimit).toNumber();
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] txCostLimit =', ethers.utils.formatEther(txCostLimit));
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] txCostEffective =', ethers.utils.formatEther(txCostEffective));
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] gas % =', Math.round(gasPercent), '% =', Math.round(gasEffectivePercent), '%');
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] cost % =', Math.round(costPercent), '%', Math.round(costEffectivePercent), '%');
}


}


if (accountFrom.address != mintAddress) {
const accountFromBalanceAtEnd = blockchain.getAccountBalance(accountFrom.address);

if (accountFromBalanceAtEnd.lt(0)){
_rejectTransaction('TRANSACTION_FAILED_INSUFFICIENT_ETH_BALANCE_AT_END');
}
}


if (! status) {
// Transaction Failed

// Revert transfer
if (amountTransfered.gt(0)) {
const txDataRevert = {
...this.data,
from: this.data.to,
to: this.data.from,
value: amountTransfered,
};
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(txDataRevert, block);

// TODO: gerer le revert des internal transfers
}

txLogs.forEach(log => {
log.removed = true;
});

// Revert contract storage
if (contractExecuted) {
const contractAccount = blockchain.getAccount(contractAddress);
contractAccount.data.storage = JSON.parse(storageBackup, utils.bnJsonParser);
contractAccount.data.balance = balanceBackup;

} else if (amountTransfered) {
// on check aussi amountTransfered car le contrat peut avoir été exécuté via receive => TODO: process a revoir
const contractAccount = blockchain.getAccount(contractAddress);
if (contractAccount && contractAccount.isContract()) {
//contractAccount.data.storage = JSON.parse(storageBackup, utils.bnJsonParser);
//contractAccount.data.balance = balanceBackup;
// TODO
}

}

// Revert deployed contract
if (contractDeployed) {
// nothing to do ?
}

const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] FAILED TRANSACTION ', this.data.hash, '| gas usage percent =', gasPercent, '%', ' | Error: ', error.message);

const realExecDuration = performance.now()/1000 - execTsStart; //parseFloat(process.hrtime(execTsStart).join('.'));
const realGasUsed = toBn(Math.round(realExecDuration * cpuPricePerSecond)); // TODO: prendre en compte le storage utilisé dans le calcul du gasUsed
const realGasPercent = realGasUsed.mul(100).div(this.data.gasLimit).toNumber();
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] FAILED TRANSACTION ', this.data.hash, '| real gas usage percent =', realGasPercent, '%', ' | Error: ', error.message);

} else {
// Transaction Success

// save contract storage
Object.entries(vmStorage).forEach((entry, idx) => {
const [contractAddress, contractStorage] = entry;

const contractAccount = blockchain.getAccount(contractAddress, true);
contractAccount.data.storage = contractStorage;
contractAccount.save();
});

// show result (debug)
const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();;
console.log(utils.getDate(), "[BLOCKCHAIN]", 'SUCCECSS TRANSACTION ', this.data.hash, '| gas usage percent =', gasPercent, '%');
}


// total gas utilisé dans le block
block.data.gasUsed = block.data.gasUsed.add(gasUsed);


// assign block data on logs
txLogs.forEach((txLog, idx) => {
txLog.data.transactionIndex = transactionIndex;
txLog.data.logIndex = logIndexOffset + idx;
txLog.data.transactionHash = this.data.hash;
txLog.data.blockHash = block.hash;
txLog.data.blockNumber = block.data.number;
});


const receiptData = {
transactionHash: this.data.hash,
transactionIndex: transactionIndex,
blockNumber: block.data.number,
blockHash: block.hash,
//logsBloom: '0x0000',
contractAddress: createdContractAddress,
gasUsed,
cumulativeGasUsed: block.data.gasUsed,
effectiveGasPrice: this.data.gasPrice,
effectiveCost: this.data.gasPrice.mul(gasUsed),
logs: txLogs,
status,
}

const receipt = new Receipt(blockchain, receiptData);

return [receipt, txLogs];
}"
, executeTransactionContract: "(block) => {
let contractAddress = this.data.to;

const isNullRecipient = (! contractAddress || contractAddress.length <= 2);
if (isNullRecipient) {
throw new Error('CONTRACT_EXEC_ADDRESS_INVALID_BUG_1');
}

const contractAccount = blockchain.getAccount(contractAddress);

if (! contractAccount) {
throw new Error('CONTRACT_EXEC_ACCOUNT_NOT_FOUND');
}

// Execute existing contract method call
return blockchain.callContractMethodInVM(this.data, block);

}"
, transactionDeployContract: "(block) => {
let contractAddress = this.data.to;

let contractAccount;

if (contractAddress && contractAddress.length > 2) {
throw new Error('CONTRACT_DEPLOY_ADDRESS_INVALID_BUG_2');
}


// CONTRACT CREATION

if (! this.data.data || this.data.data.length <= 2) {
throw new Error('CONTRACT_DEPLOY_INVALID_DATA_EMPTY_CODE');
}


// build contract address
contractAddress = ethers.utils.getContractAddress(this.data).toLocaleLowerCase();

// parse contract source code & constructor parameters
const codes = ethers.utils.defaultAbiCoder.decode(["string", "string"], this.data.data);
if (! codes) {
throw new Error('CONTRACT_DEPLOY_CODE_PARSING_FAILED');
}

const sourceCode = codes[0];
const constructorParams = codes[1];
const constructorRawParams = ethers.utils.defaultAbiCoder.encode(["string"], [constructorParams]);
const codeHash = ethers.utils.id(sourceCode);
const codeAbi = null; // l'abi sera généré une fois que le contrat aura été instancié
const storageHash = ethers.utils.id('{}');

// create new account
const accountData = {
address: contractAddress,
code: sourceCode,
codeHash,
codeAbi,
storage: {},
storageHash,
};
contractAccount = blockchain.createAccount(contractAddress, accountData);


// instanciate contract + call constructor + recuperer storage
const txData = {
from: this.data.from,
to: contractAddress,
data: constructorRawParams,
value: this.data.value.toHexString(),
};
const callResult = blockchain.deployContractInVM(txData, block);
const creationLogs = callResult.logs;
const vmStorage = callResult.storage;


// emit event
const eventParams = [
contractAddress,
];
const _txData = {
...this.data,
to: blockchain.getNullAddress(),
}
const deployLog = new TxLog(blockchain, null, _txData, 'ContractCreated', eventParams, deployAbi);
creationLogs.push(deployLog);


return {
result: contractAddress,
logs: creationLogs,
storage: vmStorage,
};
}"
, timestamp: "1651572545092", status: 1, gasUsed: "21116" }, 0x8242a23917239a9fdc16f71f3272d08a0e6e6ebd4ec9696f13aa8f2a3bb42aeb: { data: { blockHash: "0x0002e11e37101c4d7386f6c36294514c2308fe8434e6c413dd9c843b2a0bd52e", blockNumber: "7", transactionIndex: 0, hash: "0x8242a23917239a9fdc16f71f3272d08a0e6e6ebd4ec9696f13aa8f2a3bb42aeb", from: "0x0000000000000000000000000000000000000001", gasLimit: "1000000000000000000", gasPrice: "0", data: "0x", nonce: "6", to: "0xccf8ba457dcad7ee6a0361c96846a0f79744b113", value: "250000000000000000000", chainId: "3615", origin: "1651572550882", v: "", r: "", s: "" }, hash: "0x8242a23917239a9fdc16f71f3272d08a0e6e6ebd4ec9696f13aa8f2a3bb42aeb", load: "() => {
const transactionFile = config.node.dataDir + '/transactions/' + this.hash + '.json';

if (! fs.existsSync(transactionFile)) {
console.warn(utils.getDate(), "[BLOCKCHAIN]", 'IMPORT Transaction not found', this.hash);
throw new Error('IMPORT_TRANSACTION_NOT_FOUND_DATABASE_CORRUPTED');
}

const transactionJson = fs.readFileSync(transactionFile);
transactionData = JSON.parse(transactionJson, utils.bnJsonParser);

this.data = (typeof transactionData == 'TransactionData') ? transactionData : new TransactionData(transactionData);

return true;
}"
, save: "() => {
const transactionFile = config.node.dataDir + '/transactions/' + this.hash + '.json';
fs.writeFileSync(transactionFile, JSON.stringify(this.data));
return true;
}"
, getReceipt: "() => {
return blockchain.getTransactionReceiptByHash(this.hash);
}"
, getBlock: "() => {
return blockchain.getBlockByHash(this.data.blockHash);
}"
, getBlockTransationPrev: "() => {
const block = this.getBlock();
const txPrevIdx = (this.data.transactionIndex > 0) ? (this.data.transactionIndex-1) : null;
const tx = (block && txPrevIdx in block.transactions) ? block.transactions[txPrevIdx] : null;
return tx;
}"
, getBlockTransationNext: "() => {
const block = this.getBlock();
const txNextIdx = (this.data.transactionIndex < block.transactions.length - 1) ? (this.data.transactionIndex+1) : null;
const tx = (block && txNextIdx in block.transactions) ? block.transactions[txNextIdx] : null;
return tx;
}"
, getAccountTransactionIndex: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = account.data.transactions.findIndex(transactionHash => blockchain.getTransactionByHash(transactionHash).hash == this.hash);
if (txIdx === -1) {
return null;
}
return txIdx;
}"
, getAccountTransationPrev: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = this.getAccountTransactionIndex();
if (txIdx === null) {
return null;
}
const txPrevIdx = (txIdx > 0) ? (txIdx-1) : null;
const transactionHash = (txPrevIdx in account.data.transactions) ? account.data.transactions[txPrevIdx] : null;
if (! transactionHash) {
return null;
}
return blockchain.getTransactionByHash(transactionHash);
}"
, getAccountTransationNext: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = this.getAccountTransactionIndex();
if (txIdx === null) {
return null;
}
const txNextIdx = (txIdx < account.data.transactions.length - 1) ? (txIdx+1) : null;
const transactionHash = (txNextIdx in account.data.transactions) ? account.data.transactions[txNextIdx] : null;
if (! transactionHash) {
return null;
}
return blockchain.getTransactionByHash(transactionHash);

}"
, formatRpc: "() => {
// https://github.com/ethers-io/ethers.js/blob/master/packages/abstract-provider/lib/index.d.ts

const to = (this.data.to && this.data.to.length == 42) ? this.data.to : null;

const _txData = {
blockHash: this.data.blockHash,
blockNumber: this.data.blockNumber.toNumber(),
transactionIndex: this.data.transactionIndex,
hash: this.data.hash,
from: this.data.from,
gasLimit: this.data.gasLimit.toHexString(),
gasPrice: this.data.gasPrice.toHexString(),
data: this.data.data,
nonce: this.data.nonce.toHexString(),
to,
value: this.data.value.toHexString(),
chainId: this.data.chainId.toNumber(),
//origin: this.data.origin.toHexString(),
};

return _txData;
}"
, getHashOLD: "() => {
const _txData = {
gasPrice: this.data.gasPrice,
gasLimit: this.data.gasLimit,
from: this.data.from,
to: this.data.to,
value: this.data.value,
data: this.data.data,
nonce: this.data.nonce,
};

const strvalue = JSON.stringify(utils.orderedObject(_txData));
const transactionHash = ethers.utils.id(strvalue);

return transactionHash;
}"
, getHash: "() => {
const _txData = {
gasPrice: this.data.gasPrice.toHexString(),
gasLimit: this.data.gasLimit.toHexString(),
from: this.data.from,
to: this.data.to,
value: this.data.value.toHexString(),
data: this.data.data,
nonce: this.data.nonce.toHexString(),
v: this.data.v ? ('0x' + this.data.v) : '',
r: this.data.r ? ('0x' + this.data.r) : '',
s: this.data.s ? ('0x' + this.data.s) : '',
};

const tx = new EthereumJS.Transaction(_txData);
const hashRaw = tx.hash();

const transactionHash = ethers.utils.defaultAbiCoder.decode(["uint256"], hashRaw)[0].toHexString();

return transactionHash;
}"
, execute: "(block, transactionIndex, logIndexOffset) => {
const _tx = this;

function _rejectTransaction(reason) {
// https://openethereum.github.io/Transactions-Queue
reason = reason || 'unknown';
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] REJECTED PROCESSING TRANSACTION', _tx.data.hash, ' (Reason:', reason + ')');
const success = blockchain.removeTransactionsFromMempool([_tx.hash], true, true);
if (success) {
saveMempool();
}
throw new Error(reason); // on rejete definitivement la tx (elle ne sera pas minée ... et donc aucun frais de gas ne sera compté)
// et du coup on rejete le block
}

function _checkGas(step) {
execDuration = performance.now()/1000 - execTsStart; //parseFloat(process.hrtime(execTsStart).join('.'));
gasUsed = toBn(Math.round(execDuration * cpuPricePerSecond)); // TODO: prendre en compte le storage utilisé dans le calcul du gasUsed
txCost = gasUsed.mul(_tx.data.gasPrice);

if (step == 'afterTransfer') {
//debugger;
}

if (status) {
gasUsedEffective = gasUsed;
txCostEffective = gasUsedEffective.mul(_tx.data.gasPrice);

if (gasUsed.gt(_tx.data.gasLimit)) {
status = 0;
error = new Error('INSUFFICIENT_GAS');

gasUsedEffective = _tx.data.gasLimit;
txCostEffective = txCostLimit;

// emit event
const debugError = {
message: 'INSUFFICIENT_GAS',
step,
gasPrice: _tx.data.gasPrice,
gasLimit: _tx.data.gasLimit,
gasUsed,
gasUsedEffective,
txCostLimit,
txCost,
txCostEffective,
execDuration,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, _tx.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

}
}


//const block = this;
const txLogs = [];
let vmStorage = {};

let gasUsed = toBn(0);
let gasUsedEffective = toBn(0);
let status = 1;
let error = null;
let createdContractAddress = null;
let amountTransfered = toBn(0);

const contractAddress = this.data.to;
let storageBackup;
let balanceBackup;
let contractExecuted = false;
let contractDeployed = false;

const execTsStart = performance.now()/1000; //process.hrtime(); //Date.now();
let execDuration = 0;

const txCostLimit = this.data.gasLimit.mul(this.data.gasPrice);
let txCost = toBn(0);
let txCostEffective = toBn(0);


const accountFrom = blockchain.getAccount(this.data.from);
if (! accountFrom) {
_rejectTransaction('TRANSACTION_REJECTED_EMPTY_ACCOUNT');
}
const accountFromBalance = accountFrom.data.balance;


if (accountFrom.address != mintAddress) {

// verif nonce
const expectedNonce = toBn(accountFrom.data.transactions.length); // le nonce attendu est egal au nombre de transactions passées de l'account
if (! this.data.nonce.eq(expectedNonce)) {
_rejectTransaction('TRANSACTION_REJECTED_INCORRECT_NONCE');
}

// verif balance pour gas + transfer
if (this.data.value && this.data.value.gt(0)) {
if (accountFromBalance.lt(this.data.value.add(txCostLimit))) {
// l'adresse n'a pas suffisamment de fond pour executer la transaction (selon gasLimit)
// TODO: faut-il rejeter la tx ? ou la laisser passer (en status=0) et lui vider sa balance (si 100% du gas est utilisé) ?
//return _rejectTransaction('TRANSACTION_FAILED_INSUFFICIENT_ETH_BALANCE');

_rejectTransaction('TRANSACTION_REJECTED_INSUFFICIENT_ETH_BALANCE');
}
}
}



_checkGas('init');


if (status) {
if (this.data.value && this.data.value.gt(0) && contractAddress && contractAddress.length > 2) {
// Transfer la valeur demandée (simple ETH transaction)
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(this.data, block);
txLogs.push( ...transferLogs );

// si recipient == contract => on transfer quand meme dès maintenant => TODO: a revoir (le transferEth declenche un appel à receive ... avant meme de savoir si unne methode payable doit etre appelée. Probleme si receive provoque un revert => la fonction payable ne sera pas appelée)
// 1) si methode payable appellée, on l'execute
// 2) si methode non payable => on appelle receive juste avant
// 3) si methode inconnue => revert


if (transferResult && transferReverted) {
// reverted (after transfer)
amountTransfered = this.data.value;

status = 0;
error = new Error('TRANSFER_REVERTED');

} else if (transferResult) {
// success
amountTransfered = this.data.value;

} else {
// transfer failed
status = 0;
error = new Error('TRANSFER_FAILED');
}

}

_checkGas('afterTransfer');
}

const gasUsedBeforeContractExecution = gasUsed;

if (status && this.data.data && this.data.data != '0x') {
// Traitement du champ data de la transaction (smart contract call)

if (!contractAddress || contractAddress == '0x') {
// contract creation

contractDeployed = true;

let contractExecResult;
try {
contractExecResult = this.transactionDeployContract(block);
const creationLogs = contractExecResult.logs;
vmStorage = contractExecResult.storage;

if (creationLogs.length) {
txLogs.push( ...creationLogs );
}

if (! contractExecResult.result) {
// Deploy Failed
status = 0;
error = new Error('CONTRACT_CREATION_FAILED');

// emit event
const debugError = {
message: 'CONTRACT_CREATION_FAILED',
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);

} else {
// Deploy Success
createdContractAddress = contractExecResult.result.toLowerCase();

if (this.data.value.gt(0)) {
// Transfer tx value to contract
const _txData = {
...this.data,
to: createdContractAddress,
};
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(_txData, block);
txLogs.push( ...transferLogs );

if (transferResult) {
amountTransfered = this.data.value;

} else {
status = 0;
error = new Error('CONTRACT_CREATION_TRANSFER_FAILED');
}
}

// TODO: emit event CONTRACT_CREATION_SUCCESS ?
}



} catch (e) {
//console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] Cannot execute transaction');
//throw e;
status = 0;
error = new Error('CONTRACT_CREATION_ERROR');

console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] CONTRACT_CREATION_ERROR with message', e.message)

// emit event
const debugError = {
message: 'CONTRACT_CREATION_ERROR',
error: e.message,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

_checkGas('afterContractCreation');

} else {
// contract execution

const contractAccount = blockchain.getAccount(contractAddress);

if (! contractAccount) {
status = 0;
error = new Error('CONTRACT_ACCOUNT_NOT_FOUND');

// emit event
const debugError = {
message: 'CONTRACT_ACCOUNT_NOT_FOUND',
address: contractAddress,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

if (status) {
storageBackup = JSON.stringify(contractAccount.data.storage);
balanceBackup = contractAccount.data.balance;
contractExecuted = true;

try {
const contractExecResult = this.executeTransactionContract(block);
const internalLogs = contractExecResult.logs;
vmStorage = contractExecResult.storage;

if (internalLogs.length) {
txLogs.push( ...internalLogs );
}

if (! contractExecResult.result) {
status = 0;
error = new Error('CONTRACT_EXEC_FAILED');

// emit event
const debugError = {
message: 'CONTRACT_EXEC_FAILED',
address: contractAddress,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

} catch (e) {
status = 0;
error = new Error('CONTRACT_EXEC_ERROR');

// emit event
const debugError = {
message: 'CONTRACT_EXEC_ERROR',
address: contractAddress,
error: e.message,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}
}

_checkGas('afterContractCall');
}

}

const gasUsedAfterContractExecution = gasUsed;
const gasUsedInContractExecution = gasUsedAfterContractExecution.sub(gasUsedBeforeContractExecution);
const contractExecutionCost = gasUsedInContractExecution.mul(_tx.data.gasPrice);


console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] execDuration =', (Math.round(execDuration * 1000000) / 1000000), ' seconds');



if (txCostEffective.gt(0)) {
// Gas payment
//const minerAccount = blockchain.getAccount(block.data.miner, true);
//const gasPaymentResult = minerAccount.transferFrom(accountFrom.address, txCostEffective);

const txDataGasPayment = {
from: accountFrom.address,
to: block.data.miner,
value: txCostEffective,
};
const [gasPaymentResult, gasPaymentLogs, transferReverted] = blockchain.transferEth(txDataGasPayment, block, true);
txLogs.push(...gasPaymentLogs);

if (gasPaymentResult) {
// gas payment success

} else {
// gas payment failed => reject transaction (and block)
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] Gas payment failed');
_rejectTransaction('GAS_PAYMENT_FAILED');
}

if (false) {
// DEBUG
const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();
const gasEffectivePercent = gasUsedEffective.mul(100).div(this.data.gasLimit).toNumber();
const costPercent = txCost.mul(100).div(txCostLimit).toNumber();
const costEffectivePercent = txCostEffective.mul(100).div(txCostLimit).toNumber();
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] txCostLimit =', ethers.utils.formatEther(txCostLimit));
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] txCostEffective =', ethers.utils.formatEther(txCostEffective));
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] gas % =', Math.round(gasPercent), '% =', Math.round(gasEffectivePercent), '%');
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] cost % =', Math.round(costPercent), '%', Math.round(costEffectivePercent), '%');
}


}


if (accountFrom.address != mintAddress) {
const accountFromBalanceAtEnd = blockchain.getAccountBalance(accountFrom.address);

if (accountFromBalanceAtEnd.lt(0)){
_rejectTransaction('TRANSACTION_FAILED_INSUFFICIENT_ETH_BALANCE_AT_END');
}
}


if (! status) {
// Transaction Failed

// Revert transfer
if (amountTransfered.gt(0)) {
const txDataRevert = {
...this.data,
from: this.data.to,
to: this.data.from,
value: amountTransfered,
};
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(txDataRevert, block);

// TODO: gerer le revert des internal transfers
}

txLogs.forEach(log => {
log.removed = true;
});

// Revert contract storage
if (contractExecuted) {
const contractAccount = blockchain.getAccount(contractAddress);
contractAccount.data.storage = JSON.parse(storageBackup, utils.bnJsonParser);
contractAccount.data.balance = balanceBackup;

} else if (amountTransfered) {
// on check aussi amountTransfered car le contrat peut avoir été exécuté via receive => TODO: process a revoir
const contractAccount = blockchain.getAccount(contractAddress);
if (contractAccount && contractAccount.isContract()) {
//contractAccount.data.storage = JSON.parse(storageBackup, utils.bnJsonParser);
//contractAccount.data.balance = balanceBackup;
// TODO
}

}

// Revert deployed contract
if (contractDeployed) {
// nothing to do ?
}

const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] FAILED TRANSACTION ', this.data.hash, '| gas usage percent =', gasPercent, '%', ' | Error: ', error.message);

const realExecDuration = performance.now()/1000 - execTsStart; //parseFloat(process.hrtime(execTsStart).join('.'));
const realGasUsed = toBn(Math.round(realExecDuration * cpuPricePerSecond)); // TODO: prendre en compte le storage utilisé dans le calcul du gasUsed
const realGasPercent = realGasUsed.mul(100).div(this.data.gasLimit).toNumber();
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] FAILED TRANSACTION ', this.data.hash, '| real gas usage percent =', realGasPercent, '%', ' | Error: ', error.message);

} else {
// Transaction Success

// save contract storage
Object.entries(vmStorage).forEach((entry, idx) => {
const [contractAddress, contractStorage] = entry;

const contractAccount = blockchain.getAccount(contractAddress, true);
contractAccount.data.storage = contractStorage;
contractAccount.save();
});

// show result (debug)
const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();;
console.log(utils.getDate(), "[BLOCKCHAIN]", 'SUCCECSS TRANSACTION ', this.data.hash, '| gas usage percent =', gasPercent, '%');
}


// total gas utilisé dans le block
block.data.gasUsed = block.data.gasUsed.add(gasUsed);


// assign block data on logs
txLogs.forEach((txLog, idx) => {
txLog.data.transactionIndex = transactionIndex;
txLog.data.logIndex = logIndexOffset + idx;
txLog.data.transactionHash = this.data.hash;
txLog.data.blockHash = block.hash;
txLog.data.blockNumber = block.data.number;
});


const receiptData = {
transactionHash: this.data.hash,
transactionIndex: transactionIndex,
blockNumber: block.data.number,
blockHash: block.hash,
//logsBloom: '0x0000',
contractAddress: createdContractAddress,
gasUsed,
cumulativeGasUsed: block.data.gasUsed,
effectiveGasPrice: this.data.gasPrice,
effectiveCost: this.data.gasPrice.mul(gasUsed),
logs: txLogs,
status,
}

const receipt = new Receipt(blockchain, receiptData);

return [receipt, txLogs];
}"
, executeTransactionContract: "(block) => {
let contractAddress = this.data.to;

const isNullRecipient = (! contractAddress || contractAddress.length <= 2);
if (isNullRecipient) {
throw new Error('CONTRACT_EXEC_ADDRESS_INVALID_BUG_1');
}

const contractAccount = blockchain.getAccount(contractAddress);

if (! contractAccount) {
throw new Error('CONTRACT_EXEC_ACCOUNT_NOT_FOUND');
}

// Execute existing contract method call
return blockchain.callContractMethodInVM(this.data, block);

}"
, transactionDeployContract: "(block) => {
let contractAddress = this.data.to;

let contractAccount;

if (contractAddress && contractAddress.length > 2) {
throw new Error('CONTRACT_DEPLOY_ADDRESS_INVALID_BUG_2');
}


// CONTRACT CREATION

if (! this.data.data || this.data.data.length <= 2) {
throw new Error('CONTRACT_DEPLOY_INVALID_DATA_EMPTY_CODE');
}


// build contract address
contractAddress = ethers.utils.getContractAddress(this.data).toLocaleLowerCase();

// parse contract source code & constructor parameters
const codes = ethers.utils.defaultAbiCoder.decode(["string", "string"], this.data.data);
if (! codes) {
throw new Error('CONTRACT_DEPLOY_CODE_PARSING_FAILED');
}

const sourceCode = codes[0];
const constructorParams = codes[1];
const constructorRawParams = ethers.utils.defaultAbiCoder.encode(["string"], [constructorParams]);
const codeHash = ethers.utils.id(sourceCode);
const codeAbi = null; // l'abi sera généré une fois que le contrat aura été instancié
const storageHash = ethers.utils.id('{}');

// create new account
const accountData = {
address: contractAddress,
code: sourceCode,
codeHash,
codeAbi,
storage: {},
storageHash,
};
contractAccount = blockchain.createAccount(contractAddress, accountData);


// instanciate contract + call constructor + recuperer storage
const txData = {
from: this.data.from,
to: contractAddress,
data: constructorRawParams,
value: this.data.value.toHexString(),
};
const callResult = blockchain.deployContractInVM(txData, block);
const creationLogs = callResult.logs;
const vmStorage = callResult.storage;


// emit event
const eventParams = [
contractAddress,
];
const _txData = {
...this.data,
to: blockchain.getNullAddress(),
}
const deployLog = new TxLog(blockchain, null, _txData, 'ContractCreated', eventParams, deployAbi);
creationLogs.push(deployLog);


return {
result: contractAddress,
logs: creationLogs,
storage: vmStorage,
};
}"
, timestamp: "1651572551733", status: 1, gasUsed: "3108" }, 0xcef76138cfb3883fae36032061551ff0dcff8785795478380912e44318fe40bd: { data: { blockHash: "0x0002e11e37101c4d7386f6c36294514c2308fe8434e6c413dd9c843b2a0bd52e", blockNumber: "7", transactionIndex: 1, hash: "0xcef76138cfb3883fae36032061551ff0dcff8785795478380912e44318fe40bd", from: "0x131a190fa22cb867abf58193bf9e7640e9fce682", gasLimit: "1135950", gasPrice: "1000000000", data: "0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000002da00000000000000000000000000000000000000000000000000000000000002d3d2f2a0a4e616d653a20446f75626c654f72517569747320546f6b656e0a56657273696f6e3a20302e302e330a2a2f0a0a0a636c617373204f776e61626c65207b0a202020206f776e6572203d2027307830303030303030303030303030303030303030303030303030303030303030303030303030303030273b0a0a202020204f776e6572736869705472616e73666572726564286f6c644f776e65722c206e65774f776e657229207b7d202f2f206576656e740a202020200a0a20202020636f6e7374727563746f722829207b0a2020202020202020746869732e6f776e6572203d206d73672e73656e6465723b0a202020207d0a0a202020206765744f776e65722829207b0a202020202020202072657475726e205b746869732e6f776e65725d3b0a202020207d0a202020200a202020207472616e736665724f776e657273686970286e65774f776e657229207b0a202020202020202072657175697265286e65774f776e657220213d20616464726573732830292c20224f776e61626c653a206e6577206f776e657220697320746865207a65726f206164647265737322293b0a2020202020202020746869732e5f7472616e736665724f776e657273686970286e65774f776e6572293b0a202020207d0a0a202020205f7472616e736665724f776e657273686970286e65774f776e657229207b0a20202020202020206e65774f776e6572203d206e65774f776e65722e746f4c6f7765724361736528293b0a2020202020202020636f6e7374206f6c644f776e6572203d20746869732e6f776e65723b0a2020202020202020746869732e6f776e6572203d206e65774f776e65723b0a2020202020202020656d697428274f776e6572736869705472616e73666572726564272c205b6f6c644f776e65722c206e65774f776e65725d293b0a202020207d0a0a202020205f6f6e6c794f776e6572286e65787429207b0a202020202020202072657175697265286d73672e73656e6465722e746f4c6f776572436173652829203d3d20746869732e6f776e65722c20276f6e6c79206f776e65722063616e2065786563757465207468697327293b0a20202020202020206e65787428293b0a202020207d0a7d0a0a2f2f204d6574686f64730a4f776e61626c652e70726f746f747970652e4f776e6572736869705472616e736665727265642e6576656e74203d20747275653b0a4f776e61626c652e70726f746f747970652e4f776e6572736869705472616e736665727265642e696e70757473203d205b276164647265737320696e6465786564272c20276164647265737320696e6465786564275d3b0a0a2f2f204d6574686f64730a4f776e61626c652e70726f746f747970652e6765744f776e65722e76696577203d20747275653b0a4f776e61626c652e70726f746f747970652e6765744f776e65722e6f757470757473203d205b2761646472657373275d3b0a0a4f776e61626c652e70726f746f747970652e7472616e736665724f776e6572736869702e696e70757473203d205b2761646472657373275d3b0a4f776e61626c652e70726f746f747970652e7472616e736665724f776e6572736869702e6d6f64696669657273203d205b275f6f6e6c794f776e6572275d3b0a0a4f776e61626c652e70726f746f747970652e5f7472616e736665724f776e6572736869702e696e7465726e616c203d20747275653b0a0a4f776e61626c652e70726f746f747970652e5f6f6e6c794f776e65722e696e7465726e616c203d20747275653b0a0a0a0a0a636c617373204552433230546f6b656e20657874656e6473204f776e61626c65207b0a202020206e616d65203d2027455243323020546f6b656e273b0a2020202073796d626f6c203d20274552433230273b0a20202020646563696d616c73203d2031383b0a20202020746f74616c537570706c79203d206e756d62657228273027293b0a0a20202020616c6c6f776564203d207b7d3b0a2020202062616c616e636573203d207b7d3b0a0a20202020737570706f72746564496e7465726661636573203d207b0a20202020202020202730783336333732623037273a20747275652c202f2f2045524332300a20202020202020202730783036666464653033273a20747275652c202f2f204552433230206e616d650a20202020202020202730783935643839623431273a20747275652c202f2f2045524332302073796d626f6c0a20202020202020202730783331336365353637273a20747275652c202f2f20455243323020646563696d616c730a202020207d3b0a0a0a202020205472616e736665722873656e6465722c20726563697069656e742c20616d6f756e7429207b7d202f2f206576656e740a20202020417070726f76616c286f776e65722c207370656e6465722c20616d6f756e7429207b7d202f2f206576656e740a202020204d696e74286d696e7465722c20616d6f756e7429207b7d202f2f206576656e740a202020204275726e286275726e65722c20616d6f756e7429207b7d202f2f206576656e740a0a0a2020202062616c616e63654f6628686f6c6465724164647265737329207b0a2020202020202020686f6c64657241646472657373203d20686f6c646572416464726573732e746f4c6f7765724361736528293b0a202020202020202076617220746f6b656e42616c616e636573203d20746869732e62616c616e636573207c7c207b7d3b0a0a202020202020202069662028686f6c6465724164647265737320696e20746f6b656e42616c616e63657329207b0a202020202020202020202020636f6e73742062616c616e6365203d20746f6b656e42616c616e6365735b686f6c646572416464726573735d3b0a20202020202020202020202072657475726e205b62616c616e63655d3b0a20202020202020207d0a202020202020202072657475726e205b6e756d6265722830295d3b0a202020207d0a0a202020207472616e7366657228726563697069656e742c20616d6f756e7429207b0a2020202020202020726563697069656e74203d20726563697069656e742e746f4c6f7765724361736528293b0a2020202020202020636f6e73742073656e646572203d206d73672e73656e6465723b0a202020202020202076617220746f6b656e42616c616e636573203d20746869732e62616c616e636573207c7c207b7d3b0a0a20202020202020206966202821202873656e64657220696e20746f6b656e42616c616e6365732929207b0a2020202020202020202020205f7468726f772827494e53554646494349454e545f544f4b454e5f42414c414e434527293b0a20202020202020207d0a202020202020202069662028746f6b656e42616c616e6365735b73656e6465725d2e6c7428616d6f756e742929207b0a2020202020202020202020205f7468726f772827494e53554646494349454e545f544f4b454e5f42414c414e434527293b0a20202020202020207d0a0a202020202020202069662028746869732e5f64656372656d656e745573657242616c616e63652e62696e642874686973292873656e6465722c20616d6f756e742929207b0a202020202020202020202020746869732e5f696e6372656d656e745573657242616c616e63652e62696e6428746869732928726563697069656e742c20616d6f756e74290a20202020202020207d0a0a2020202020202020656d697428275472616e73666572272c205b73656e6465722c20726563697069656e742c20616d6f756e745d293b0a202020202020202072657475726e205b747275655d3b0a202020207d0a0a0a202020207472616e7366657246726f6d2873656e6465722c20726563697069656e742c20616d6f756e7429207b0a202020202020202073656e646572203d2073656e6465722e746f4c6f7765724361736528293b0a2020202020202020726563697069656e74203d20726563697069656e742e746f4c6f7765724361736528293b0a20202020202020200a202020202020202076617220746f6b656e42616c616e636573203d20746869732e62616c616e636573207c7c207b7d3b0a202020202020202076617220746f6b656e416c6c6f77616e636573203d20746869732e616c6c6f776564207c7c207b7d3b0a0a2020202020202020726571756972652873656e64657220696e20746f6b656e42616c616e6365732c20274d495353494e475f544f4b454e5f42414c414e434527293b0a202020202020202069662028746f6b656e42616c616e6365735b73656e6465725d2e6c7428616d6f756e742929207b0a2020202020202020202020205f7468726f772827494e53554646494349454e545f544f4b454e5f42414c414e434527293b0a20202020202020207d0a20202020202020200a2020202020202020726571756972652873656e646572203d3d206d73672e73656e646572207c7c202873656e64657220696e20746f6b656e416c6c6f77616e636573202626206d73672e73656e64657220696e20746f6b656e416c6c6f77616e6365735b73656e6465725d292c20224d495353494e475f414c4c4f57414e434522293b0a20202020202020206966202873656e64657220213d206d73672e73656e64657220262620746f6b656e416c6c6f77616e6365735b73656e6465725d5b6d73672e73656e6465725d2e6c7428616d6f756e742929207b0a2020202020202020202020205f7468726f772827494e53554646494349454e545f544f4b454e5f414c4c4f57414e434527293b0a20202020202020207d0a0a2020202020202020746f6b656e416c6c6f77616e6365735b73656e6465725d5b6d73672e73656e6465725d203d20746f6b656e416c6c6f77616e6365735b73656e6465725d5b6d73672e73656e6465725d2e73756228616d6f756e74293b0a0a202020202020202069662028746869732e5f64656372656d656e745573657242616c616e63652e62696e642874686973292873656e6465722c20616d6f756e742929207b0a202020202020202020202020746869732e5f696e6372656d656e745573657242616c616e63652e62696e6428746869732928726563697069656e742c20616d6f756e74290a20202020202020207d0a20202020202020200a0a2020202020202020656d697428275472616e73666572272c205b73656e6465722c20726563697069656e742c20616d6f756e745d293b0a202020202020202072657475726e205b747275655d3b0a202020207d0a0a0a20202020616c6c6f77616e6365286f776e65722c207370656e64657229207b0a20202020202020206f776e6572203d206f776e65722e746f4c6f7765724361736528293b0a20202020202020207370656e646572203d207370656e6465722e746f4c6f7765724361736528293b0a202020202020202076617220746f6b656e416c6c6f77616e636573203d20746869732e616c6c6f776564207c7c207b7d3b0a2020202020202020696620286f776e657220696e20746f6b656e416c6c6f77616e636573202626207370656e64657220696e20746f6b656e416c6c6f77616e6365735b6f776e65725d29207b0a20202020202020202020202072657475726e205b746f6b656e416c6c6f77616e6365735b6f776e65725d5b7370656e6465725d5d3b0a20202020202020207d0a202020202020202072657475726e205b6e756d6265722830295d3b0a202020207d0a202020200a20202020617070726f7665287370656e6465722c2076616c756529207b0a20202020202020207370656e646572203d207370656e6465722e746f4c6f7765724361736528293b0a202020202020202076617220746f6b656e416c6c6f77616e636573203d20746869732e616c6c6f776564207c7c207b7d3b0a2020202020202020696620282120286d73672e73656e64657220696e20746f6b656e416c6c6f77616e6365732929207b0a202020202020202020202020746f6b656e416c6c6f77616e6365735b6d73672e73656e6465725d203d207b7d3b0a20202020202020207d0a2020202020202020746f6b656e416c6c6f77616e6365735b6d73672e73656e6465725d5b7370656e6465725d203d2076616c75653b0a2020202020202020656d69742827417070726f76616c272c205b6d73672e73656e6465722c207370656e6465722c2076616c75655d293b0a202020202020202072657475726e205b747275655d3b0a202020207d0a0a202020200a202020206d696e742875736572416464726573732c20616d6f756e7429207b0a2020202020202020746869732e5f6d696e742875736572416464726573732c20616d6f756e74293b0a202020207d0a0a202020205f6d696e742875736572416464726573732c20616d6f756e7429207b0a20202020202020207573657241646472657373203d2075736572416464726573732e746f4c6f7765724361736528293b0a2020202020202020696620287573657241646472657373203d3d20616464726573732830292e6164647265737329207b0a2020202020202020202020205f7468726f77282745524332303a206d696e7420746f20746865207a65726f206164647265737327293b0a20202020202020207d0a20202020202020200a202020202020202069662028746869732e5f696e6372656d656e745573657242616c616e63652e62696e642874686973292875736572416464726573732c20616d6f756e742929207b0a202020202020202020202020746869732e746f74616c537570706c79203d20746869732e746f74616c537570706c792e61646428616d6f756e74293b0a20202020202020207d0a0a2020202020202020656d697428274d696e74272c205b75736572416464726573732c20616d6f756e745d293b0a202020207d0a0a202020200a202020206275726e2875736572416464726573732c20616d6f756e7429207b0a2020202020202020746869732e5f6275726e2875736572416464726573732c20616d6f756e74293b0a202020207d0a0a202020205f6275726e2875736572416464726573732c20616d6f756e7429207b0a20202020202020207573657241646472657373203d2075736572416464726573732e746f4c6f7765724361736528293b0a2020202020202020696620287573657241646472657373203d3d20616464726573732830292e6164647265737329207b0a2020202020202020202020205f7468726f77282745524332303a206275726e2066726f6d20746865207a65726f206164647265737327293b0a20202020202020207d0a20202020202020200a202020202020202069662028746869732e5f64656372656d656e745573657242616c616e63652e62696e642874686973292875736572416464726573732c20616d6f756e742929207b0a202020202020202020202020746869732e746f74616c537570706c79203d20746869732e746f74616c537570706c792e73756228616d6f756e74293b0a20202020202020207d0a0a2020202020202020656d697428274275726e272c205b75736572416464726573732c20616d6f756e745d293b0a202020207d0a0a0a20202020737570706f727473496e7465726661636528696e74657266616365494429207b0a2020202020202020636f6e7374205f696e7465726661636573203d20746869732e737570706f72746564496e7465726661636573207c7c207b7d3b0a202020202020202072657475726e205b0a2020202020202020202020202828696e74657266616365494420696e205f696e7465726661636573290a202020202020202020202020202020203f205f696e74657266616365735b696e7465726661636549445d0a202020202020202020202020202020203a2066616c7365290a20202020202020205d3b0a202020207d0a0a0a202020205f64656372656d656e745573657242616c616e636528686f6c646572416464726573732c20616d6f756e7429207b0a202020202020202076617220746f6b656e42616c616e636573203d20746869732e62616c616e636573207c7c207b7d3b0a0a202020202020202069662028212028686f6c6465724164647265737320696e20746f6b656e42616c616e6365732929207b0a2020202020202020202020205f7468726f772827494e53554646494349454e545f544f4b454e5f42414c414e434527293b0a20202020202020207d0a202020202020202069662028616d6f756e742e677428746f6b656e42616c616e6365735b686f6c646572416464726573735d2929207b0a2020202020202020202020205f7468726f772827494e53554646494349454e545f544f4b454e5f42414c414e434527293b0a20202020202020207d0a2020202020202020746f6b656e42616c616e6365735b686f6c646572416464726573735d203d20746f6b656e42616c616e6365735b686f6c646572416464726573735d2e73756228616d6f756e74293b0a202020202020202072657475726e20747275653b0a202020207d0a0a0a202020205f696e6372656d656e745573657242616c616e636528686f6c646572416464726573732c20616d6f756e7429207b0a202020202020202076617220746f6b656e42616c616e636573203d20746869732e62616c616e636573207c7c207b7d3b0a0a202020202020202069662028212028686f6c6465724164647265737320696e20746f6b656e42616c616e6365732929207b0a202020202020202020202020746f6b656e42616c616e6365735b686f6c646572416464726573735d203d206e756d6265722830293b0a20202020202020207d0a2020202020202020746f6b656e42616c616e6365735b686f6c646572416464726573735d203d20746f6b656e42616c616e6365735b686f6c646572416464726573735d2e61646428616d6f756e74293b0a202020202020202072657475726e20747275653b0a202020207d0a0a7d0a0a2f2f204576656e74730a4552433230546f6b656e2e70726f746f747970652e5472616e736665722e6576656e74203d20747275653b0a4552433230546f6b656e2e70726f746f747970652e5472616e736665722e696e70757473203d205b276164647265737320696e6465786564272c20276164647265737320696e6465786564272c202775696e74323536275d3b0a0a4552433230546f6b656e2e70726f746f747970652e417070726f76616c2e6576656e74203d20747275653b0a4552433230546f6b656e2e70726f746f747970652e417070726f76616c2e696e70757473203d205b276164647265737320696e6465786564272c20276164647265737320696e6465786564272c202775696e74323536275d3b0a0a4552433230546f6b656e2e70726f746f747970652e4d696e742e6576656e74203d20747275653b0a4552433230546f6b656e2e70726f746f747970652e4d696e742e696e70757473203d205b276164647265737320696e6465786564272c202775696e74323536275d3b0a0a4552433230546f6b656e2e70726f746f747970652e4275726e2e6576656e74203d20747275653b0a4552433230546f6b656e2e70726f746f747970652e4275726e2e696e70757473203d205b276164647265737320696e6465786564272c202775696e74323536275d3b0a0a2f2f204d6574686f64730a4552433230546f6b656e2e70726f746f747970652e62616c616e63654f662e76696577203d20747275653b0a4552433230546f6b656e2e70726f746f747970652e62616c616e63654f662e696e70757473203d205b2761646472657373275d3b0a4552433230546f6b656e2e70726f746f747970652e62616c616e63654f662e6f757470757473203d205b2775696e74323536275d3b0a0a4552433230546f6b656e2e70726f746f747970652e7472616e736665722e696e70757473203d205b2761646472657373272c202775696e74323536275d3b0a4552433230546f6b656e2e70726f746f747970652e7472616e736665722e6f757470757473203d205b27626f6f6c275d3b0a0a4552433230546f6b656e2e70726f746f747970652e7472616e7366657246726f6d2e696e70757473203d205b2761646472657373272c202761646472657373272c202775696e74323536275d3b0a4552433230546f6b656e2e70726f746f747970652e7472616e7366657246726f6d2e6f757470757473203d205b27626f6f6c275d3b0a0a4552433230546f6b656e2e70726f746f747970652e616c6c6f77616e63652e76696577203d20747275653b0a4552433230546f6b656e2e70726f746f747970652e616c6c6f77616e63652e696e70757473203d205b2761646472657373272c202761646472657373275d3b0a4552433230546f6b656e2e70726f746f747970652e616c6c6f77616e63652e6f757470757473203d205b2775696e74323536275d3b0a0a4552433230546f6b656e2e70726f746f747970652e617070726f76652e696e70757473203d205b2761646472657373272c202775696e74323536275d3b0a4552433230546f6b656e2e70726f746f747970652e617070726f76652e6f757470757473203d205b27626f6f6c275d3b0a0a4552433230546f6b656e2e70726f746f747970652e6d696e742e696e70757473203d205b2761646472657373272c202775696e74323536275d3b0a4552433230546f6b656e2e70726f746f747970652e6d696e742e6d6f64696669657273203d205b275f6f6e6c794f776e6572275d3b0a0a4552433230546f6b656e2e70726f746f747970652e5f6d696e742e696e7465726e616c203d20747275653b0a0a4552433230546f6b656e2e70726f746f747970652e6275726e2e696e70757473203d205b2761646472657373272c202775696e74323536275d3b0a4552433230546f6b656e2e70726f746f747970652e6275726e2e6d6f64696669657273203d205b275f6f6e6c794f776e6572275d3b0a0a4552433230546f6b656e2e70726f746f747970652e5f6275726e2e696e7465726e616c203d20747275653b0a0a4552433230546f6b656e2e70726f746f747970652e737570706f727473496e746572666163652e76696577203d20747275653b0a4552433230546f6b656e2e70726f746f747970652e737570706f727473496e746572666163652e696e70757473203d205b27627974657334275d3b0a4552433230546f6b656e2e70726f746f747970652e737570706f727473496e746572666163652e6f757470757473203d205b27626f6f6c275d3b0a0a4552433230546f6b656e2e70726f746f747970652e5f64656372656d656e745573657242616c616e63652e696e7465726e616c203d20747275653b0a0a4552433230546f6b656e2e70726f746f747970652e5f696e6372656d656e745573657242616c616e63652e696e7465726e616c203d20747275653b0a0a0a0a0a0a636c61737320446f75626c654f725175697473546f6b656e20657874656e6473204552433230546f6b656e207b0a202020206e616d65203d2027446f75626c65204f72205175697473273b0a2020202073796d626f6c203d2027444f51273b0a0a20202020646f75626c654f7251756974732829207b0a2020202020202020636f6e7374207573657241646472657373203d206d73672e73656e6465722e746f4c6f7765724361736528293b0a202020202020202076617220746f6b656e42616c616e636573203d20746869732e62616c616e636573207c7c207b7d3b0a20202020202020206c65742077696e203d2066616c73653b0a0a202020202020202069662028757365724164647265737320696e20746f6b656e42616c616e63657320262620746f6b656e42616c616e6365735b75736572416464726573735d2e677428302929207b0a202020202020202020202020636f6e73742062616c616e6365203d20746f6b656e42616c616e6365735b75736572416464726573735d3b0a202020202020202020202020636f6e73742072616e64203d204d6174682e72616e646f6d2829202a203130303b0a20202020202020202020202077696e203d20284d6174682e726f756e642872616e642920252032203d3d2030293b0a0a2020202020202020202020206966202877696e29207b0a20202020202020202020202020202020746869732e6d696e742875736572416464726573732c2062616c616e6365293b0a20202020202020202020202020202020656d6974282757696e272c205b75736572416464726573732c2062616c616e63655d293b0a0a2020202020202020202020207d20656c7365207b0a20202020202020202020202020202020746869732e6275726e2875736572416464726573732c2062616c616e6365293b0a20202020202020202020202020202020656d697428274c6f6f7365272c205b75736572416464726573732c2062616c616e63655d293b0a2020202020202020202020207d0a0a20202020202020207d20656c7365207b0a202020202020202020202020636f6e7374206d696e74416d6f756e74203d206e756d62657228273130303030303030303030303030303030303027293b202f2f203120444f510a202020202020202020202020746869732e6d696e742875736572416464726573732c206d696e74416d6f756e74293b0a20202020202020202020202077696e203d20747275653b0a202020202020202020202020656d6974282746726565546f6b656e272c205b75736572416464726573732c206d696e74416d6f756e745d293b0a20202020202020207d0a0a202020202020202072657475726e205b77696e5d3b0a202020207d0a0a202020207472616e7366657228726563697069656e742c20616d6f756e7429207b0a2020202020202020726563697069656e74203d20726563697069656e742e746f4c6f7765724361736528293b0a2020202020202020636f6e73742073656e646572203d206d73672e73656e6465723b0a202020202020202076617220746f6b656e42616c616e636573203d20746869732e62616c616e636573207c7c207b7d3b0a0a20202020202020206966202821202873656e64657220696e20746f6b656e42616c616e6365732929207b0a2020202020202020202020207468726f77206e6577204572726f722827494e53554646494349454e545f544f4b454e5f42414c414e434527293b0a20202020202020207d0a202020202020202069662028746f6b656e42616c616e6365735b73656e6465725d2e6c7428616d6f756e742929207b0a2020202020202020202020207468726f77206e6577204572726f722827494e53554646494349454e545f544f4b454e5f42414c414e434527293b0a20202020202020207d0a0a2020202020202020636f6e7374206275726e416d6f756e74203d20616d6f756e742e6469762832293b0a2020202020202020746869732e6275726e2873656e6465722c206275726e416d6f756e74293b0a0a2020202020202020636f6e737420726563697069656e74416d6f756e74203d20616d6f756e742e737562286275726e416d6f756e74293b0a2020202020202020746869732e5f64656372656d656e745573657242616c616e63652e62696e642874686973292873656e6465722c20726563697069656e74416d6f756e74293b0a2020202020202020746869732e5f696e6372656d656e745573657242616c616e63652e62696e6428746869732928726563697069656e742c20726563697069656e74416d6f756e74290a20202020202020200a2020202020202020656d697428275472616e73666572272c205b73656e6465722c20726563697069656e742c20726563697069656e74416d6f756e745d293b0a0a202020202020202072657475726e205b747275655d3b0a202020207d0a0a0a202020207472616e7366657246726f6d2873656e6465722c20726563697069656e742c20616d6f756e7429202f2a2065787465726e616c2072657475726e732028626f6f6c29202a2f207b0a202020202020202073656e646572203d2073656e6465722e746f4c6f7765724361736528293b202f2f2073656e646572206573742063656c756920646f6e74206c657320746f6b656e7320766f6e7420657472652064c3a970656e73c3a97320706172206d73672e73656e6465720a2020202020202020726563697069656e74203d20726563697069656e742e746f4c6f7765724361736528293b0a20202020202020200a202020202020202076617220746f6b656e42616c616e636573203d20746869732e62616c616e636573207c7c207b7d3b0a202020202020202076617220746f6b656e416c6c6f77616e636573203d20746869732e616c6c6f776564207c7c207b7d3b0a0a20202020202020206966202821202873656e64657220696e20746f6b656e42616c616e6365732929207b0a2020202020202020202020207468726f77206e6577204572726f722827494e53554646494349454e545f544f4b454e5f42414c414e434527293b0a20202020202020207d0a202020202020202069662028746f6b656e42616c616e6365735b73656e6465725d2e6c7428616d6f756e742929207b0a2020202020202020202020207468726f77206e6577204572726f722827494e53554646494349454e545f544f4b454e5f42414c414e434527293b0a20202020202020207d0a202020202020202069662028746f6b656e416c6c6f77616e6365735b73656e6465725d5b6d73672e73656e6465725d2e6c7428616d6f756e742929207b0a2020202020202020202020207468726f77206e6577204572726f722827494e53554646494349454e545f544f4b454e5f414c4c4f57414e434527293b0a20202020202020207d0a0a2020202020202020636f6e7374206275726e416d6f756e74203d206e756d6265722830293b202f2f616d6f756e742e6469762832293b0a20202020202020202f2f746869732e6275726e2873656e6465722c206275726e416d6f756e74293b0a0a2020202020202020636f6e737420726563697069656e74416d6f756e74203d20616d6f756e742e737562286275726e416d6f756e74293b0a2020202020202020746869732e5f64656372656d656e745573657242616c616e63652e62696e642874686973292873656e6465722c20726563697069656e74416d6f756e74293b0a2020202020202020746869732e5f696e6372656d656e745573657242616c616e63652e62696e6428746869732928726563697069656e742c20726563697069656e74416d6f756e74290a0a2020202020202020656d697428275472616e73666572272c205b73656e6465722c20726563697069656e742c20726563697069656e74416d6f756e745d293b0a0a202020202020202072657475726e205b747275655d3b0a202020207d0a7d0a0a2f2f204d6574686f64730a446f75626c654f725175697473546f6b656e2e70726f746f747970652e646f75626c654f7251756974732e6f757470757473203d205b27626f6f6c275d3b0a0a446f75626c654f725175697473546f6b656e2e70726f746f747970652e7472616e736665722e696e70757473203d205b2761646472657373272c202775696e74323536275d3b0a446f75626c654f725175697473546f6b656e2e70726f746f747970652e7472616e736665722e6f757470757473203d205b27626f6f6c275d3b0a0a446f75626c654f725175697473546f6b656e2e70726f746f747970652e7472616e7366657246726f6d2e696e70757473203d205b2761646472657373272c202761646472657373272c202775696e74323536275d3b0a446f75626c654f725175697473546f6b656e2e70726f746f747970652e7472616e7366657246726f6d2e6f757470757473203d205b27626f6f6c275d3b0a0a0a72657475726e20446f75626c654f725175697473546f6b656e3b00000000000000000000000000000000000000000000000000000000000000000000025b5d000000000000000000000000000000000000000000000000000000000000", nonce: "6", to: "0x", value: "0", chainId: "3615", origin: "1651572550141", v: "1c61", r: "a980b72389ae3d2144af00f47acbe772ae53fb51f1acbcd068760d57b663cf41", s: "775a54a26bf9f47097959d46d772a5bddbbc9310ef6c8cd4ea160e026de5ec9e" }, hash: "0xcef76138cfb3883fae36032061551ff0dcff8785795478380912e44318fe40bd", load: "() => {
const transactionFile = config.node.dataDir + '/transactions/' + this.hash + '.json';

if (! fs.existsSync(transactionFile)) {
console.warn(utils.getDate(), "[BLOCKCHAIN]", 'IMPORT Transaction not found', this.hash);
throw new Error('IMPORT_TRANSACTION_NOT_FOUND_DATABASE_CORRUPTED');
}

const transactionJson = fs.readFileSync(transactionFile);
transactionData = JSON.parse(transactionJson, utils.bnJsonParser);

this.data = (typeof transactionData == 'TransactionData') ? transactionData : new TransactionData(transactionData);

return true;
}"
, save: "() => {
const transactionFile = config.node.dataDir + '/transactions/' + this.hash + '.json';
fs.writeFileSync(transactionFile, JSON.stringify(this.data));
return true;
}"
, getReceipt: "() => {
return blockchain.getTransactionReceiptByHash(this.hash);
}"
, getBlock: "() => {
return blockchain.getBlockByHash(this.data.blockHash);
}"
, getBlockTransationPrev: "() => {
const block = this.getBlock();
const txPrevIdx = (this.data.transactionIndex > 0) ? (this.data.transactionIndex-1) : null;
const tx = (block && txPrevIdx in block.transactions) ? block.transactions[txPrevIdx] : null;
return tx;
}"
, getBlockTransationNext: "() => {
const block = this.getBlock();
const txNextIdx = (this.data.transactionIndex < block.transactions.length - 1) ? (this.data.transactionIndex+1) : null;
const tx = (block && txNextIdx in block.transactions) ? block.transactions[txNextIdx] : null;
return tx;
}"
, getAccountTransactionIndex: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = account.data.transactions.findIndex(transactionHash => blockchain.getTransactionByHash(transactionHash).hash == this.hash);
if (txIdx === -1) {
return null;
}
return txIdx;
}"
, getAccountTransationPrev: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = this.getAccountTransactionIndex();
if (txIdx === null) {
return null;
}
const txPrevIdx = (txIdx > 0) ? (txIdx-1) : null;
const transactionHash = (txPrevIdx in account.data.transactions) ? account.data.transactions[txPrevIdx] : null;
if (! transactionHash) {
return null;
}
return blockchain.getTransactionByHash(transactionHash);
}"
, getAccountTransationNext: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = this.getAccountTransactionIndex();
if (txIdx === null) {
return null;
}
const txNextIdx = (txIdx < account.data.transactions.length - 1) ? (txIdx+1) : null;
const transactionHash = (txNextIdx in account.data.transactions) ? account.data.transactions[txNextIdx] : null;
if (! transactionHash) {
return null;
}
return blockchain.getTransactionByHash(transactionHash);

}"
, formatRpc: "() => {
// https://github.com/ethers-io/ethers.js/blob/master/packages/abstract-provider/lib/index.d.ts

const to = (this.data.to && this.data.to.length == 42) ? this.data.to : null;

const _txData = {
blockHash: this.data.blockHash,
blockNumber: this.data.blockNumber.toNumber(),
transactionIndex: this.data.transactionIndex,
hash: this.data.hash,
from: this.data.from,
gasLimit: this.data.gasLimit.toHexString(),
gasPrice: this.data.gasPrice.toHexString(),
data: this.data.data,
nonce: this.data.nonce.toHexString(),
to,
value: this.data.value.toHexString(),
chainId: this.data.chainId.toNumber(),
//origin: this.data.origin.toHexString(),
};

return _txData;
}"
, getHashOLD: "() => {
const _txData = {
gasPrice: this.data.gasPrice,
gasLimit: this.data.gasLimit,
from: this.data.from,
to: this.data.to,
value: this.data.value,
data: this.data.data,
nonce: this.data.nonce,
};

const strvalue = JSON.stringify(utils.orderedObject(_txData));
const transactionHash = ethers.utils.id(strvalue);

return transactionHash;
}"
, getHash: "() => {
const _txData = {
gasPrice: this.data.gasPrice.toHexString(),
gasLimit: this.data.gasLimit.toHexString(),
from: this.data.from,
to: this.data.to,
value: this.data.value.toHexString(),
data: this.data.data,
nonce: this.data.nonce.toHexString(),
v: this.data.v ? ('0x' + this.data.v) : '',
r: this.data.r ? ('0x' + this.data.r) : '',
s: this.data.s ? ('0x' + this.data.s) : '',
};

const tx = new EthereumJS.Transaction(_txData);
const hashRaw = tx.hash();

const transactionHash = ethers.utils.defaultAbiCoder.decode(["uint256"], hashRaw)[0].toHexString();

return transactionHash;
}"
, execute: "(block, transactionIndex, logIndexOffset) => {
const _tx = this;

function _rejectTransaction(reason) {
// https://openethereum.github.io/Transactions-Queue
reason = reason || 'unknown';
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] REJECTED PROCESSING TRANSACTION', _tx.data.hash, ' (Reason:', reason + ')');
const success = blockchain.removeTransactionsFromMempool([_tx.hash], true, true);
if (success) {
saveMempool();
}
throw new Error(reason); // on rejete definitivement la tx (elle ne sera pas minée ... et donc aucun frais de gas ne sera compté)
// et du coup on rejete le block
}

function _checkGas(step) {
execDuration = performance.now()/1000 - execTsStart; //parseFloat(process.hrtime(execTsStart).join('.'));
gasUsed = toBn(Math.round(execDuration * cpuPricePerSecond)); // TODO: prendre en compte le storage utilisé dans le calcul du gasUsed
txCost = gasUsed.mul(_tx.data.gasPrice);

if (step == 'afterTransfer') {
//debugger;
}

if (status) {
gasUsedEffective = gasUsed;
txCostEffective = gasUsedEffective.mul(_tx.data.gasPrice);

if (gasUsed.gt(_tx.data.gasLimit)) {
status = 0;
error = new Error('INSUFFICIENT_GAS');

gasUsedEffective = _tx.data.gasLimit;
txCostEffective = txCostLimit;

// emit event
const debugError = {
message: 'INSUFFICIENT_GAS',
step,
gasPrice: _tx.data.gasPrice,
gasLimit: _tx.data.gasLimit,
gasUsed,
gasUsedEffective,
txCostLimit,
txCost,
txCostEffective,
execDuration,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, _tx.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

}
}


//const block = this;
const txLogs = [];
let vmStorage = {};

let gasUsed = toBn(0);
let gasUsedEffective = toBn(0);
let status = 1;
let error = null;
let createdContractAddress = null;
let amountTransfered = toBn(0);

const contractAddress = this.data.to;
let storageBackup;
let balanceBackup;
let contractExecuted = false;
let contractDeployed = false;

const execTsStart = performance.now()/1000; //process.hrtime(); //Date.now();
let execDuration = 0;

const txCostLimit = this.data.gasLimit.mul(this.data.gasPrice);
let txCost = toBn(0);
let txCostEffective = toBn(0);


const accountFrom = blockchain.getAccount(this.data.from);
if (! accountFrom) {
_rejectTransaction('TRANSACTION_REJECTED_EMPTY_ACCOUNT');
}
const accountFromBalance = accountFrom.data.balance;


if (accountFrom.address != mintAddress) {

// verif nonce
const expectedNonce = toBn(accountFrom.data.transactions.length); // le nonce attendu est egal au nombre de transactions passées de l'account
if (! this.data.nonce.eq(expectedNonce)) {
_rejectTransaction('TRANSACTION_REJECTED_INCORRECT_NONCE');
}

// verif balance pour gas + transfer
if (this.data.value && this.data.value.gt(0)) {
if (accountFromBalance.lt(this.data.value.add(txCostLimit))) {
// l'adresse n'a pas suffisamment de fond pour executer la transaction (selon gasLimit)
// TODO: faut-il rejeter la tx ? ou la laisser passer (en status=0) et lui vider sa balance (si 100% du gas est utilisé) ?
//return _rejectTransaction('TRANSACTION_FAILED_INSUFFICIENT_ETH_BALANCE');

_rejectTransaction('TRANSACTION_REJECTED_INSUFFICIENT_ETH_BALANCE');
}
}
}



_checkGas('init');


if (status) {
if (this.data.value && this.data.value.gt(0) && contractAddress && contractAddress.length > 2) {
// Transfer la valeur demandée (simple ETH transaction)
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(this.data, block);
txLogs.push( ...transferLogs );

// si recipient == contract => on transfer quand meme dès maintenant => TODO: a revoir (le transferEth declenche un appel à receive ... avant meme de savoir si unne methode payable doit etre appelée. Probleme si receive provoque un revert => la fonction payable ne sera pas appelée)
// 1) si methode payable appellée, on l'execute
// 2) si methode non payable => on appelle receive juste avant
// 3) si methode inconnue => revert


if (transferResult && transferReverted) {
// reverted (after transfer)
amountTransfered = this.data.value;

status = 0;
error = new Error('TRANSFER_REVERTED');

} else if (transferResult) {
// success
amountTransfered = this.data.value;

} else {
// transfer failed
status = 0;
error = new Error('TRANSFER_FAILED');
}

}

_checkGas('afterTransfer');
}

const gasUsedBeforeContractExecution = gasUsed;

if (status && this.data.data && this.data.data != '0x') {
// Traitement du champ data de la transaction (smart contract call)

if (!contractAddress || contractAddress == '0x') {
// contract creation

contractDeployed = true;

let contractExecResult;
try {
contractExecResult = this.transactionDeployContract(block);
const creationLogs = contractExecResult.logs;
vmStorage = contractExecResult.storage;

if (creationLogs.length) {
txLogs.push( ...creationLogs );
}

if (! contractExecResult.result) {
// Deploy Failed
status = 0;
error = new Error('CONTRACT_CREATION_FAILED');

// emit event
const debugError = {
message: 'CONTRACT_CREATION_FAILED',
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);

} else {
// Deploy Success
createdContractAddress = contractExecResult.result.toLowerCase();

if (this.data.value.gt(0)) {
// Transfer tx value to contract
const _txData = {
...this.data,
to: createdContractAddress,
};
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(_txData, block);
txLogs.push( ...transferLogs );

if (transferResult) {
amountTransfered = this.data.value;

} else {
status = 0;
error = new Error('CONTRACT_CREATION_TRANSFER_FAILED');
}
}

// TODO: emit event CONTRACT_CREATION_SUCCESS ?
}



} catch (e) {
//console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] Cannot execute transaction');
//throw e;
status = 0;
error = new Error('CONTRACT_CREATION_ERROR');

console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] CONTRACT_CREATION_ERROR with message', e.message)

// emit event
const debugError = {
message: 'CONTRACT_CREATION_ERROR',
error: e.message,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

_checkGas('afterContractCreation');

} else {
// contract execution

const contractAccount = blockchain.getAccount(contractAddress);

if (! contractAccount) {
status = 0;
error = new Error('CONTRACT_ACCOUNT_NOT_FOUND');

// emit event
const debugError = {
message: 'CONTRACT_ACCOUNT_NOT_FOUND',
address: contractAddress,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

if (status) {
storageBackup = JSON.stringify(contractAccount.data.storage);
balanceBackup = contractAccount.data.balance;
contractExecuted = true;

try {
const contractExecResult = this.executeTransactionContract(block);
const internalLogs = contractExecResult.logs;
vmStorage = contractExecResult.storage;

if (internalLogs.length) {
txLogs.push( ...internalLogs );
}

if (! contractExecResult.result) {
status = 0;
error = new Error('CONTRACT_EXEC_FAILED');

// emit event
const debugError = {
message: 'CONTRACT_EXEC_FAILED',
address: contractAddress,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

} catch (e) {
status = 0;
error = new Error('CONTRACT_EXEC_ERROR');

// emit event
const debugError = {
message: 'CONTRACT_EXEC_ERROR',
address: contractAddress,
error: e.message,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}
}

_checkGas('afterContractCall');
}

}

const gasUsedAfterContractExecution = gasUsed;
const gasUsedInContractExecution = gasUsedAfterContractExecution.sub(gasUsedBeforeContractExecution);
const contractExecutionCost = gasUsedInContractExecution.mul(_tx.data.gasPrice);


console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] execDuration =', (Math.round(execDuration * 1000000) / 1000000), ' seconds');



if (txCostEffective.gt(0)) {
// Gas payment
//const minerAccount = blockchain.getAccount(block.data.miner, true);
//const gasPaymentResult = minerAccount.transferFrom(accountFrom.address, txCostEffective);

const txDataGasPayment = {
from: accountFrom.address,
to: block.data.miner,
value: txCostEffective,
};
const [gasPaymentResult, gasPaymentLogs, transferReverted] = blockchain.transferEth(txDataGasPayment, block, true);
txLogs.push(...gasPaymentLogs);

if (gasPaymentResult) {
// gas payment success

} else {
// gas payment failed => reject transaction (and block)
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] Gas payment failed');
_rejectTransaction('GAS_PAYMENT_FAILED');
}

if (false) {
// DEBUG
const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();
const gasEffectivePercent = gasUsedEffective.mul(100).div(this.data.gasLimit).toNumber();
const costPercent = txCost.mul(100).div(txCostLimit).toNumber();
const costEffectivePercent = txCostEffective.mul(100).div(txCostLimit).toNumber();
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] txCostLimit =', ethers.utils.formatEther(txCostLimit));
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] txCostEffective =', ethers.utils.formatEther(txCostEffective));
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] gas % =', Math.round(gasPercent), '% =', Math.round(gasEffectivePercent), '%');
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] cost % =', Math.round(costPercent), '%', Math.round(costEffectivePercent), '%');
}


}


if (accountFrom.address != mintAddress) {
const accountFromBalanceAtEnd = blockchain.getAccountBalance(accountFrom.address);

if (accountFromBalanceAtEnd.lt(0)){
_rejectTransaction('TRANSACTION_FAILED_INSUFFICIENT_ETH_BALANCE_AT_END');
}
}


if (! status) {
// Transaction Failed

// Revert transfer
if (amountTransfered.gt(0)) {
const txDataRevert = {
...this.data,
from: this.data.to,
to: this.data.from,
value: amountTransfered,
};
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(txDataRevert, block);

// TODO: gerer le revert des internal transfers
}

txLogs.forEach(log => {
log.removed = true;
});

// Revert contract storage
if (contractExecuted) {
const contractAccount = blockchain.getAccount(contractAddress);
contractAccount.data.storage = JSON.parse(storageBackup, utils.bnJsonParser);
contractAccount.data.balance = balanceBackup;

} else if (amountTransfered) {
// on check aussi amountTransfered car le contrat peut avoir été exécuté via receive => TODO: process a revoir
const contractAccount = blockchain.getAccount(contractAddress);
if (contractAccount && contractAccount.isContract()) {
//contractAccount.data.storage = JSON.parse(storageBackup, utils.bnJsonParser);
//contractAccount.data.balance = balanceBackup;
// TODO
}

}

// Revert deployed contract
if (contractDeployed) {
// nothing to do ?
}

const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] FAILED TRANSACTION ', this.data.hash, '| gas usage percent =', gasPercent, '%', ' | Error: ', error.message);

const realExecDuration = performance.now()/1000 - execTsStart; //parseFloat(process.hrtime(execTsStart).join('.'));
const realGasUsed = toBn(Math.round(realExecDuration * cpuPricePerSecond)); // TODO: prendre en compte le storage utilisé dans le calcul du gasUsed
const realGasPercent = realGasUsed.mul(100).div(this.data.gasLimit).toNumber();
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] FAILED TRANSACTION ', this.data.hash, '| real gas usage percent =', realGasPercent, '%', ' | Error: ', error.message);

} else {
// Transaction Success

// save contract storage
Object.entries(vmStorage).forEach((entry, idx) => {
const [contractAddress, contractStorage] = entry;

const contractAccount = blockchain.getAccount(contractAddress, true);
contractAccount.data.storage = contractStorage;
contractAccount.save();
});

// show result (debug)
const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();;
console.log(utils.getDate(), "[BLOCKCHAIN]", 'SUCCECSS TRANSACTION ', this.data.hash, '| gas usage percent =', gasPercent, '%');
}


// total gas utilisé dans le block
block.data.gasUsed = block.data.gasUsed.add(gasUsed);


// assign block data on logs
txLogs.forEach((txLog, idx) => {
txLog.data.transactionIndex = transactionIndex;
txLog.data.logIndex = logIndexOffset + idx;
txLog.data.transactionHash = this.data.hash;
txLog.data.blockHash = block.hash;
txLog.data.blockNumber = block.data.number;
});


const receiptData = {
transactionHash: this.data.hash,
transactionIndex: transactionIndex,
blockNumber: block.data.number,
blockHash: block.hash,
//logsBloom: '0x0000',
contractAddress: createdContractAddress,
gasUsed,
cumulativeGasUsed: block.data.gasUsed,
effectiveGasPrice: this.data.gasPrice,
effectiveCost: this.data.gasPrice.mul(gasUsed),
logs: txLogs,
status,
}

const receipt = new Receipt(blockchain, receiptData);

return [receipt, txLogs];
}"
, executeTransactionContract: "(block) => {
let contractAddress = this.data.to;

const isNullRecipient = (! contractAddress || contractAddress.length <= 2);
if (isNullRecipient) {
throw new Error('CONTRACT_EXEC_ADDRESS_INVALID_BUG_1');
}

const contractAccount = blockchain.getAccount(contractAddress);

if (! contractAccount) {
throw new Error('CONTRACT_EXEC_ACCOUNT_NOT_FOUND');
}

// Execute existing contract method call
return blockchain.callContractMethodInVM(this.data, block);

}"
, transactionDeployContract: "(block) => {
let contractAddress = this.data.to;

let contractAccount;

if (contractAddress && contractAddress.length > 2) {
throw new Error('CONTRACT_DEPLOY_ADDRESS_INVALID_BUG_2');
}


// CONTRACT CREATION

if (! this.data.data || this.data.data.length <= 2) {
throw new Error('CONTRACT_DEPLOY_INVALID_DATA_EMPTY_CODE');
}


// build contract address
contractAddress = ethers.utils.getContractAddress(this.data).toLocaleLowerCase();

// parse contract source code & constructor parameters
const codes = ethers.utils.defaultAbiCoder.decode(["string", "string"], this.data.data);
if (! codes) {
throw new Error('CONTRACT_DEPLOY_CODE_PARSING_FAILED');
}

const sourceCode = codes[0];
const constructorParams = codes[1];
const constructorRawParams = ethers.utils.defaultAbiCoder.encode(["string"], [constructorParams]);
const codeHash = ethers.utils.id(sourceCode);
const codeAbi = null; // l'abi sera généré une fois que le contrat aura été instancié
const storageHash = ethers.utils.id('{}');

// create new account
const accountData = {
address: contractAddress,
code: sourceCode,
codeHash,
codeAbi,
storage: {},
storageHash,
};
contractAccount = blockchain.createAccount(contractAddress, accountData);


// instanciate contract + call constructor + recuperer storage
const txData = {
from: this.data.from,
to: contractAddress,
data: constructorRawParams,
value: this.data.value.toHexString(),
};
const callResult = blockchain.deployContractInVM(txData, block);
const creationLogs = callResult.logs;
const vmStorage = callResult.storage;


// emit event
const eventParams = [
contractAddress,
];
const _txData = {
...this.data,
to: blockchain.getNullAddress(),
}
const deployLog = new TxLog(blockchain, null, _txData, 'ContractCreated', eventParams, deployAbi);
creationLogs.push(deployLog);


return {
result: contractAddress,
logs: creationLogs,
storage: vmStorage,
};
}"
, timestamp: "1651572551733", status: 1, gasUsed: "30925" }, 0xfc4ea62204e6014c299da06a9f495eb1188f09b5c0e0832ee9bcb2b279573445: { data: { blockHash: "0x00abd0559e99a60cc3895a255124003fd783f2a7f5859b15aad137437ea7c79a", blockNumber: "8", transactionIndex: 0, hash: "0xfc4ea62204e6014c299da06a9f495eb1188f09b5c0e0832ee9bcb2b279573445", from: "0x0000000000000000000000000000000000000001", gasLimit: "1000000000000000000", gasPrice: "0", data: "0x", nonce: "7", to: "0xccf8ba457dcad7ee6a0361c96846a0f79744b113", value: "250000000000000000000", chainId: "3615", origin: "1651572556926", v: "", r: "", s: "" }, hash: "0xfc4ea62204e6014c299da06a9f495eb1188f09b5c0e0832ee9bcb2b279573445", load: "() => {
const transactionFile = config.node.dataDir + '/transactions/' + this.hash + '.json';

if (! fs.existsSync(transactionFile)) {
console.warn(utils.getDate(), "[BLOCKCHAIN]", 'IMPORT Transaction not found', this.hash);
throw new Error('IMPORT_TRANSACTION_NOT_FOUND_DATABASE_CORRUPTED');
}

const transactionJson = fs.readFileSync(transactionFile);
transactionData = JSON.parse(transactionJson, utils.bnJsonParser);

this.data = (typeof transactionData == 'TransactionData') ? transactionData : new TransactionData(transactionData);

return true;
}"
, save: "() => {
const transactionFile = config.node.dataDir + '/transactions/' + this.hash + '.json';
fs.writeFileSync(transactionFile, JSON.stringify(this.data));
return true;
}"
, getReceipt: "() => {
return blockchain.getTransactionReceiptByHash(this.hash);
}"
, getBlock: "() => {
return blockchain.getBlockByHash(this.data.blockHash);
}"
, getBlockTransationPrev: "() => {
const block = this.getBlock();
const txPrevIdx = (this.data.transactionIndex > 0) ? (this.data.transactionIndex-1) : null;
const tx = (block && txPrevIdx in block.transactions) ? block.transactions[txPrevIdx] : null;
return tx;
}"
, getBlockTransationNext: "() => {
const block = this.getBlock();
const txNextIdx = (this.data.transactionIndex < block.transactions.length - 1) ? (this.data.transactionIndex+1) : null;
const tx = (block && txNextIdx in block.transactions) ? block.transactions[txNextIdx] : null;
return tx;
}"
, getAccountTransactionIndex: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = account.data.transactions.findIndex(transactionHash => blockchain.getTransactionByHash(transactionHash).hash == this.hash);
if (txIdx === -1) {
return null;
}
return txIdx;
}"
, getAccountTransationPrev: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = this.getAccountTransactionIndex();
if (txIdx === null) {
return null;
}
const txPrevIdx = (txIdx > 0) ? (txIdx-1) : null;
const transactionHash = (txPrevIdx in account.data.transactions) ? account.data.transactions[txPrevIdx] : null;
if (! transactionHash) {
return null;
}
return blockchain.getTransactionByHash(transactionHash);
}"
, getAccountTransationNext: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = this.getAccountTransactionIndex();
if (txIdx === null) {
return null;
}
const txNextIdx = (txIdx < account.data.transactions.length - 1) ? (txIdx+1) : null;
const transactionHash = (txNextIdx in account.data.transactions) ? account.data.transactions[txNextIdx] : null;
if (! transactionHash) {
return null;
}
return blockchain.getTransactionByHash(transactionHash);

}"
, formatRpc: "() => {
// https://github.com/ethers-io/ethers.js/blob/master/packages/abstract-provider/lib/index.d.ts

const to = (this.data.to && this.data.to.length == 42) ? this.data.to : null;

const _txData = {
blockHash: this.data.blockHash,
blockNumber: this.data.blockNumber.toNumber(),
transactionIndex: this.data.transactionIndex,
hash: this.data.hash,
from: this.data.from,
gasLimit: this.data.gasLimit.toHexString(),
gasPrice: this.data.gasPrice.toHexString(),
data: this.data.data,
nonce: this.data.nonce.toHexString(),
to,
value: this.data.value.toHexString(),
chainId: this.data.chainId.toNumber(),
//origin: this.data.origin.toHexString(),
};

return _txData;
}"
, getHashOLD: "() => {
const _txData = {
gasPrice: this.data.gasPrice,
gasLimit: this.data.gasLimit,
from: this.data.from,
to: this.data.to,
value: this.data.value,
data: this.data.data,
nonce: this.data.nonce,
};

const strvalue = JSON.stringify(utils.orderedObject(_txData));
const transactionHash = ethers.utils.id(strvalue);

return transactionHash;
}"
, getHash: "() => {
const _txData = {
gasPrice: this.data.gasPrice.toHexString(),
gasLimit: this.data.gasLimit.toHexString(),
from: this.data.from,
to: this.data.to,
value: this.data.value.toHexString(),
data: this.data.data,
nonce: this.data.nonce.toHexString(),
v: this.data.v ? ('0x' + this.data.v) : '',
r: this.data.r ? ('0x' + this.data.r) : '',
s: this.data.s ? ('0x' + this.data.s) : '',
};

const tx = new EthereumJS.Transaction(_txData);
const hashRaw = tx.hash();

const transactionHash = ethers.utils.defaultAbiCoder.decode(["uint256"], hashRaw)[0].toHexString();

return transactionHash;
}"
, execute: "(block, transactionIndex, logIndexOffset) => {
const _tx = this;

function _rejectTransaction(reason) {
// https://openethereum.github.io/Transactions-Queue
reason = reason || 'unknown';
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] REJECTED PROCESSING TRANSACTION', _tx.data.hash, ' (Reason:', reason + ')');
const success = blockchain.removeTransactionsFromMempool([_tx.hash], true, true);
if (success) {
saveMempool();
}
throw new Error(reason); // on rejete definitivement la tx (elle ne sera pas minée ... et donc aucun frais de gas ne sera compté)
// et du coup on rejete le block
}

function _checkGas(step) {
execDuration = performance.now()/1000 - execTsStart; //parseFloat(process.hrtime(execTsStart).join('.'));
gasUsed = toBn(Math.round(execDuration * cpuPricePerSecond)); // TODO: prendre en compte le storage utilisé dans le calcul du gasUsed
txCost = gasUsed.mul(_tx.data.gasPrice);

if (step == 'afterTransfer') {
//debugger;
}

if (status) {
gasUsedEffective = gasUsed;
txCostEffective = gasUsedEffective.mul(_tx.data.gasPrice);

if (gasUsed.gt(_tx.data.gasLimit)) {
status = 0;
error = new Error('INSUFFICIENT_GAS');

gasUsedEffective = _tx.data.gasLimit;
txCostEffective = txCostLimit;

// emit event
const debugError = {
message: 'INSUFFICIENT_GAS',
step,
gasPrice: _tx.data.gasPrice,
gasLimit: _tx.data.gasLimit,
gasUsed,
gasUsedEffective,
txCostLimit,
txCost,
txCostEffective,
execDuration,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, _tx.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

}
}


//const block = this;
const txLogs = [];
let vmStorage = {};

let gasUsed = toBn(0);
let gasUsedEffective = toBn(0);
let status = 1;
let error = null;
let createdContractAddress = null;
let amountTransfered = toBn(0);

const contractAddress = this.data.to;
let storageBackup;
let balanceBackup;
let contractExecuted = false;
let contractDeployed = false;

const execTsStart = performance.now()/1000; //process.hrtime(); //Date.now();
let execDuration = 0;

const txCostLimit = this.data.gasLimit.mul(this.data.gasPrice);
let txCost = toBn(0);
let txCostEffective = toBn(0);


const accountFrom = blockchain.getAccount(this.data.from);
if (! accountFrom) {
_rejectTransaction('TRANSACTION_REJECTED_EMPTY_ACCOUNT');
}
const accountFromBalance = accountFrom.data.balance;


if (accountFrom.address != mintAddress) {

// verif nonce
const expectedNonce = toBn(accountFrom.data.transactions.length); // le nonce attendu est egal au nombre de transactions passées de l'account
if (! this.data.nonce.eq(expectedNonce)) {
_rejectTransaction('TRANSACTION_REJECTED_INCORRECT_NONCE');
}

// verif balance pour gas + transfer
if (this.data.value && this.data.value.gt(0)) {
if (accountFromBalance.lt(this.data.value.add(txCostLimit))) {
// l'adresse n'a pas suffisamment de fond pour executer la transaction (selon gasLimit)
// TODO: faut-il rejeter la tx ? ou la laisser passer (en status=0) et lui vider sa balance (si 100% du gas est utilisé) ?
//return _rejectTransaction('TRANSACTION_FAILED_INSUFFICIENT_ETH_BALANCE');

_rejectTransaction('TRANSACTION_REJECTED_INSUFFICIENT_ETH_BALANCE');
}
}
}



_checkGas('init');


if (status) {
if (this.data.value && this.data.value.gt(0) && contractAddress && contractAddress.length > 2) {
// Transfer la valeur demandée (simple ETH transaction)
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(this.data, block);
txLogs.push( ...transferLogs );

// si recipient == contract => on transfer quand meme dès maintenant => TODO: a revoir (le transferEth declenche un appel à receive ... avant meme de savoir si unne methode payable doit etre appelée. Probleme si receive provoque un revert => la fonction payable ne sera pas appelée)
// 1) si methode payable appellée, on l'execute
// 2) si methode non payable => on appelle receive juste avant
// 3) si methode inconnue => revert


if (transferResult && transferReverted) {
// reverted (after transfer)
amountTransfered = this.data.value;

status = 0;
error = new Error('TRANSFER_REVERTED');

} else if (transferResult) {
// success
amountTransfered = this.data.value;

} else {
// transfer failed
status = 0;
error = new Error('TRANSFER_FAILED');
}

}

_checkGas('afterTransfer');
}

const gasUsedBeforeContractExecution = gasUsed;

if (status && this.data.data && this.data.data != '0x') {
// Traitement du champ data de la transaction (smart contract call)

if (!contractAddress || contractAddress == '0x') {
// contract creation

contractDeployed = true;

let contractExecResult;
try {
contractExecResult = this.transactionDeployContract(block);
const creationLogs = contractExecResult.logs;
vmStorage = contractExecResult.storage;

if (creationLogs.length) {
txLogs.push( ...creationLogs );
}

if (! contractExecResult.result) {
// Deploy Failed
status = 0;
error = new Error('CONTRACT_CREATION_FAILED');

// emit event
const debugError = {
message: 'CONTRACT_CREATION_FAILED',
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);

} else {
// Deploy Success
createdContractAddress = contractExecResult.result.toLowerCase();

if (this.data.value.gt(0)) {
// Transfer tx value to contract
const _txData = {
...this.data,
to: createdContractAddress,
};
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(_txData, block);
txLogs.push( ...transferLogs );

if (transferResult) {
amountTransfered = this.data.value;

} else {
status = 0;
error = new Error('CONTRACT_CREATION_TRANSFER_FAILED');
}
}

// TODO: emit event CONTRACT_CREATION_SUCCESS ?
}



} catch (e) {
//console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] Cannot execute transaction');
//throw e;
status = 0;
error = new Error('CONTRACT_CREATION_ERROR');

console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] CONTRACT_CREATION_ERROR with message', e.message)

// emit event
const debugError = {
message: 'CONTRACT_CREATION_ERROR',
error: e.message,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

_checkGas('afterContractCreation');

} else {
// contract execution

const contractAccount = blockchain.getAccount(contractAddress);

if (! contractAccount) {
status = 0;
error = new Error('CONTRACT_ACCOUNT_NOT_FOUND');

// emit event
const debugError = {
message: 'CONTRACT_ACCOUNT_NOT_FOUND',
address: contractAddress,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

if (status) {
storageBackup = JSON.stringify(contractAccount.data.storage);
balanceBackup = contractAccount.data.balance;
contractExecuted = true;

try {
const contractExecResult = this.executeTransactionContract(block);
const internalLogs = contractExecResult.logs;
vmStorage = contractExecResult.storage;

if (internalLogs.length) {
txLogs.push( ...internalLogs );
}

if (! contractExecResult.result) {
status = 0;
error = new Error('CONTRACT_EXEC_FAILED');

// emit event
const debugError = {
message: 'CONTRACT_EXEC_FAILED',
address: contractAddress,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

} catch (e) {
status = 0;
error = new Error('CONTRACT_EXEC_ERROR');

// emit event
const debugError = {
message: 'CONTRACT_EXEC_ERROR',
address: contractAddress,
error: e.message,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}
}

_checkGas('afterContractCall');
}

}

const gasUsedAfterContractExecution = gasUsed;
const gasUsedInContractExecution = gasUsedAfterContractExecution.sub(gasUsedBeforeContractExecution);
const contractExecutionCost = gasUsedInContractExecution.mul(_tx.data.gasPrice);


console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] execDuration =', (Math.round(execDuration * 1000000) / 1000000), ' seconds');



if (txCostEffective.gt(0)) {
// Gas payment
//const minerAccount = blockchain.getAccount(block.data.miner, true);
//const gasPaymentResult = minerAccount.transferFrom(accountFrom.address, txCostEffective);

const txDataGasPayment = {
from: accountFrom.address,
to: block.data.miner,
value: txCostEffective,
};
const [gasPaymentResult, gasPaymentLogs, transferReverted] = blockchain.transferEth(txDataGasPayment, block, true);
txLogs.push(...gasPaymentLogs);

if (gasPaymentResult) {
// gas payment success

} else {
// gas payment failed => reject transaction (and block)
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] Gas payment failed');
_rejectTransaction('GAS_PAYMENT_FAILED');
}

if (false) {
// DEBUG
const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();
const gasEffectivePercent = gasUsedEffective.mul(100).div(this.data.gasLimit).toNumber();
const costPercent = txCost.mul(100).div(txCostLimit).toNumber();
const costEffectivePercent = txCostEffective.mul(100).div(txCostLimit).toNumber();
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] txCostLimit =', ethers.utils.formatEther(txCostLimit));
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] txCostEffective =', ethers.utils.formatEther(txCostEffective));
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] gas % =', Math.round(gasPercent), '% =', Math.round(gasEffectivePercent), '%');
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] cost % =', Math.round(costPercent), '%', Math.round(costEffectivePercent), '%');
}


}


if (accountFrom.address != mintAddress) {
const accountFromBalanceAtEnd = blockchain.getAccountBalance(accountFrom.address);

if (accountFromBalanceAtEnd.lt(0)){
_rejectTransaction('TRANSACTION_FAILED_INSUFFICIENT_ETH_BALANCE_AT_END');
}
}


if (! status) {
// Transaction Failed

// Revert transfer
if (amountTransfered.gt(0)) {
const txDataRevert = {
...this.data,
from: this.data.to,
to: this.data.from,
value: amountTransfered,
};
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(txDataRevert, block);

// TODO: gerer le revert des internal transfers
}

txLogs.forEach(log => {
log.removed = true;
});

// Revert contract storage
if (contractExecuted) {
const contractAccount = blockchain.getAccount(contractAddress);
contractAccount.data.storage = JSON.parse(storageBackup, utils.bnJsonParser);
contractAccount.data.balance = balanceBackup;

} else if (amountTransfered) {
// on check aussi amountTransfered car le contrat peut avoir été exécuté via receive => TODO: process a revoir
const contractAccount = blockchain.getAccount(contractAddress);
if (contractAccount && contractAccount.isContract()) {
//contractAccount.data.storage = JSON.parse(storageBackup, utils.bnJsonParser);
//contractAccount.data.balance = balanceBackup;
// TODO
}

}

// Revert deployed contract
if (contractDeployed) {
// nothing to do ?
}

const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] FAILED TRANSACTION ', this.data.hash, '| gas usage percent =', gasPercent, '%', ' | Error: ', error.message);

const realExecDuration = performance.now()/1000 - execTsStart; //parseFloat(process.hrtime(execTsStart).join('.'));
const realGasUsed = toBn(Math.round(realExecDuration * cpuPricePerSecond)); // TODO: prendre en compte le storage utilisé dans le calcul du gasUsed
const realGasPercent = realGasUsed.mul(100).div(this.data.gasLimit).toNumber();
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] FAILED TRANSACTION ', this.data.hash, '| real gas usage percent =', realGasPercent, '%', ' | Error: ', error.message);

} else {
// Transaction Success

// save contract storage
Object.entries(vmStorage).forEach((entry, idx) => {
const [contractAddress, contractStorage] = entry;

const contractAccount = blockchain.getAccount(contractAddress, true);
contractAccount.data.storage = contractStorage;
contractAccount.save();
});

// show result (debug)
const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();;
console.log(utils.getDate(), "[BLOCKCHAIN]", 'SUCCECSS TRANSACTION ', this.data.hash, '| gas usage percent =', gasPercent, '%');
}


// total gas utilisé dans le block
block.data.gasUsed = block.data.gasUsed.add(gasUsed);


// assign block data on logs
txLogs.forEach((txLog, idx) => {
txLog.data.transactionIndex = transactionIndex;
txLog.data.logIndex = logIndexOffset + idx;
txLog.data.transactionHash = this.data.hash;
txLog.data.blockHash = block.hash;
txLog.data.blockNumber = block.data.number;
});


const receiptData = {
transactionHash: this.data.hash,
transactionIndex: transactionIndex,
blockNumber: block.data.number,
blockHash: block.hash,
//logsBloom: '0x0000',
contractAddress: createdContractAddress,
gasUsed,
cumulativeGasUsed: block.data.gasUsed,
effectiveGasPrice: this.data.gasPrice,
effectiveCost: this.data.gasPrice.mul(gasUsed),
logs: txLogs,
status,
}

const receipt = new Receipt(blockchain, receiptData);

return [receipt, txLogs];
}"
, executeTransactionContract: "(block) => {
let contractAddress = this.data.to;

const isNullRecipient = (! contractAddress || contractAddress.length <= 2);
if (isNullRecipient) {
throw new Error('CONTRACT_EXEC_ADDRESS_INVALID_BUG_1');
}

const contractAccount = blockchain.getAccount(contractAddress);

if (! contractAccount) {
throw new Error('CONTRACT_EXEC_ACCOUNT_NOT_FOUND');
}

// Execute existing contract method call
return blockchain.callContractMethodInVM(this.data, block);

}"
, transactionDeployContract: "(block) => {
let contractAddress = this.data.to;

let contractAccount;

if (contractAddress && contractAddress.length > 2) {
throw new Error('CONTRACT_DEPLOY_ADDRESS_INVALID_BUG_2');
}


// CONTRACT CREATION

if (! this.data.data || this.data.data.length <= 2) {
throw new Error('CONTRACT_DEPLOY_INVALID_DATA_EMPTY_CODE');
}


// build contract address
contractAddress = ethers.utils.getContractAddress(this.data).toLocaleLowerCase();

// parse contract source code & constructor parameters
const codes = ethers.utils.defaultAbiCoder.decode(["string", "string"], this.data.data);
if (! codes) {
throw new Error('CONTRACT_DEPLOY_CODE_PARSING_FAILED');
}

const sourceCode = codes[0];
const constructorParams = codes[1];
const constructorRawParams = ethers.utils.defaultAbiCoder.encode(["string"], [constructorParams]);
const codeHash = ethers.utils.id(sourceCode);
const codeAbi = null; // l'abi sera généré une fois que le contrat aura été instancié
const storageHash = ethers.utils.id('{}');

// create new account
const accountData = {
address: contractAddress,
code: sourceCode,
codeHash,
codeAbi,
storage: {},
storageHash,
};
contractAccount = blockchain.createAccount(contractAddress, accountData);


// instanciate contract + call constructor + recuperer storage
const txData = {
from: this.data.from,
to: contractAddress,
data: constructorRawParams,
value: this.data.value.toHexString(),
};
const callResult = blockchain.deployContractInVM(txData, block);
const creationLogs = callResult.logs;
const vmStorage = callResult.storage;


// emit event
const eventParams = [
contractAddress,
];
const _txData = {
...this.data,
to: blockchain.getNullAddress(),
}
const deployLog = new TxLog(blockchain, null, _txData, 'ContractCreated', eventParams, deployAbi);
creationLogs.push(deployLog);


return {
result: contractAddress,
logs: creationLogs,
storage: vmStorage,
};
}"
, timestamp: "1651572557849", status: 1, gasUsed: "5720" }, 0xfe576f241ed53b32a1748eb53e93838c2e60c2ffc866a993482590a854d9152e: { data: { blockHash: "0x00abd0559e99a60cc3895a255124003fd783f2a7f5859b15aad137437ea7c79a", blockNumber: "8", transactionIndex: 1, hash: "0xfe576f241ed53b32a1748eb53e93838c2e60c2ffc866a993482590a854d9152e", from: "0x131a190fa22cb867abf58193bf9e7640e9fce682", gasLimit: "1750900", gasPrice: "1000000000", data: "0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000005dc00000000000000000000000000000000000000000000000000000000000005d462f2a0a4e616d653a204c69717569646974795061697220286578616d706c653a20574b41524d412d594f290a56657273696f6e3a20302e302e310a536f757263653a200a202d2050616e63616b65506169723a2068747470733a2f2f6769746875622e636f6d2f70616e63616b65737761702f70616e63616b652d737761702d636f72652f626c6f622f6d61737465722f636f6e7472616374732f50616e63616b65506169722e736f6c0a202d2050616e63616b6545524332303a2068747470733a2f2f6769746875622e636f6d2f70616e63616b65737761702f70616e63616b652d737761702d636f72652f626c6f622f6d61737465722f636f6e7472616374732f50616e63616b6545524332302e736f6c0a202d2050616e63616b65526f757465723a2068747470733a2f2f6769746875622e636f6d2f70616e63616b65737761702f70616e63616b652d737761702d7065726970686572792f626c6f622f6d61737465722f636f6e7472616374732f50616e63616b65526f757465722e736f6c0a202d2050616e63616b65466163746f72793a2068747470733a2f2f6769746875622e636f6d2f70616e63616b65737761702f70616e63616b652d737761702d636f72652f626c6f622f6d61737465722f636f6e7472616374732f50616e63616b65466163746f72792e736f6c0a2a2f0a0a0a636c617373204f776e61626c65207b0a202020206f776e6572203d2027307830303030303030303030303030303030303030303030303030303030303030303030303030303030273b0a0a202020204f776e6572736869705472616e73666572726564286f6c644f776e65722c206e65774f776e657229207b7d202f2f206576656e740a202020200a0a20202020636f6e7374727563746f722829207b0a2020202020202020746869732e6f776e6572203d206d73672e73656e6465723b0a202020207d0a0a202020206765744f776e65722829207b0a202020202020202072657475726e205b746869732e6f776e65725d3b0a202020207d0a202020200a202020207472616e736665724f776e657273686970286e65774f776e657229207b0a202020202020202072657175697265286e65774f776e657220213d20616464726573732830292c20224f776e61626c653a206e6577206f776e657220697320746865207a65726f206164647265737322293b0a2020202020202020746869732e5f7472616e736665724f776e657273686970286e65774f776e6572293b0a202020207d0a0a202020205f7472616e736665724f776e657273686970286e65774f776e657229207b0a20202020202020206e65774f776e6572203d206e65774f776e65722e746f4c6f7765724361736528293b0a2020202020202020636f6e7374206f6c644f776e6572203d20746869732e6f776e65723b0a2020202020202020746869732e6f776e6572203d206e65774f776e65723b0a2020202020202020656d697428274f776e6572736869705472616e73666572726564272c205b6f6c644f776e65722c206e65774f776e65725d293b0a202020207d0a0a202020205f6f6e6c794f776e6572286e65787429207b0a202020202020202072657175697265286d73672e73656e6465722e746f4c6f776572436173652829203d3d20746869732e6f776e65722c20276f6e6c79206f776e65722063616e2065786563757465207468697327293b0a20202020202020206e65787428293b0a202020207d0a7d0a0a2f2f204d6574686f64730a4f776e61626c652e70726f746f747970652e4f776e6572736869705472616e736665727265642e6576656e74203d20747275653b0a4f776e61626c652e70726f746f747970652e4f776e6572736869705472616e736665727265642e696e70757473203d205b276164647265737320696e6465786564272c20276164647265737320696e6465786564275d3b0a0a2f2f204d6574686f64730a4f776e61626c652e70726f746f747970652e6765744f776e65722e76696577203d20747275653b0a4f776e61626c652e70726f746f747970652e6765744f776e65722e6f757470757473203d205b2761646472657373275d3b0a0a4f776e61626c652e70726f746f747970652e7472616e736665724f776e6572736869702e696e70757473203d205b2761646472657373275d3b0a4f776e61626c652e70726f746f747970652e7472616e736665724f776e6572736869702e6d6f64696669657273203d205b275f6f6e6c794f776e6572275d3b0a0a4f776e61626c652e70726f746f747970652e5f7472616e736665724f776e6572736869702e696e7465726e616c203d20747275653b0a0a4f776e61626c652e70726f746f747970652e5f6f6e6c794f776e65722e696e7465726e616c203d20747275653b0a0a0a0a0a636c617373204552433230546f6b656e20657874656e6473204f776e61626c65207b0a202020206e616d65203d2027455243323020546f6b656e273b0a2020202073796d626f6c203d20274552433230273b0a20202020646563696d616c73203d2031383b0a20202020746f74616c537570706c79203d206e756d62657228273027293b0a0a20202020616c6c6f776564203d207b7d3b0a2020202062616c616e636573203d207b7d3b0a0a20202020737570706f72746564496e7465726661636573203d207b0a20202020202020202730783336333732623037273a20747275652c202f2f2045524332300a20202020202020202730783036666464653033273a20747275652c202f2f204552433230206e616d650a20202020202020202730783935643839623431273a20747275652c202f2f2045524332302073796d626f6c0a20202020202020202730783331336365353637273a20747275652c202f2f20455243323020646563696d616c730a202020207d3b0a0a0a202020205472616e736665722873656e6465722c20726563697069656e742c20616d6f756e7429207b7d202f2f206576656e740a20202020417070726f76616c286f776e65722c207370656e6465722c20616d6f756e7429207b7d202f2f206576656e740a202020204d696e74286d696e7465722c20616d6f756e7429207b7d202f2f206576656e740a202020204275726e286275726e65722c20616d6f756e7429207b7d202f2f206576656e740a0a0a2020202062616c616e63654f6628686f6c6465724164647265737329207b0a2020202020202020686f6c64657241646472657373203d20686f6c646572416464726573732e746f4c6f7765724361736528293b0a202020202020202076617220746f6b656e42616c616e636573203d20746869732e62616c616e636573207c7c207b7d3b0a0a202020202020202069662028686f6c6465724164647265737320696e20746f6b656e42616c616e63657329207b0a202020202020202020202020636f6e73742062616c616e6365203d20746f6b656e42616c616e6365735b686f6c646572416464726573735d3b0a20202020202020202020202072657475726e205b62616c616e63655d3b0a20202020202020207d0a202020202020202072657475726e205b6e756d6265722830295d3b0a202020207d0a0a202020207472616e7366657228726563697069656e742c20616d6f756e7429207b0a2020202020202020726563697069656e74203d20726563697069656e742e746f4c6f7765724361736528293b0a2020202020202020636f6e73742073656e646572203d206d73672e73656e6465723b0a202020202020202076617220746f6b656e42616c616e636573203d20746869732e62616c616e636573207c7c207b7d3b0a0a20202020202020206966202821202873656e64657220696e20746f6b656e42616c616e6365732929207b0a2020202020202020202020205f7468726f772827494e53554646494349454e545f544f4b454e5f42414c414e434527293b0a20202020202020207d0a202020202020202069662028746f6b656e42616c616e6365735b73656e6465725d2e6c7428616d6f756e742929207b0a2020202020202020202020205f7468726f772827494e53554646494349454e545f544f4b454e5f42414c414e434527293b0a20202020202020207d0a0a202020202020202069662028746869732e5f64656372656d656e745573657242616c616e63652e62696e642874686973292873656e6465722c20616d6f756e742929207b0a202020202020202020202020746869732e5f696e6372656d656e745573657242616c616e63652e62696e6428746869732928726563697069656e742c20616d6f756e74290a20202020202020207d0a0a2020202020202020656d697428275472616e73666572272c205b73656e6465722c20726563697069656e742c20616d6f756e745d293b0a202020202020202072657475726e205b747275655d3b0a202020207d0a0a0a202020207472616e7366657246726f6d2873656e6465722c20726563697069656e742c20616d6f756e7429207b0a202020202020202073656e646572203d2073656e6465722e746f4c6f7765724361736528293b0a2020202020202020726563697069656e74203d20726563697069656e742e746f4c6f7765724361736528293b0a20202020202020200a202020202020202076617220746f6b656e42616c616e636573203d20746869732e62616c616e636573207c7c207b7d3b0a202020202020202076617220746f6b656e416c6c6f77616e636573203d20746869732e616c6c6f776564207c7c207b7d3b0a0a2020202020202020726571756972652873656e64657220696e20746f6b656e42616c616e6365732c20274d495353494e475f544f4b454e5f42414c414e434527293b0a202020202020202069662028746f6b656e42616c616e6365735b73656e6465725d2e6c7428616d6f756e742929207b0a2020202020202020202020205f7468726f772827494e53554646494349454e545f544f4b454e5f42414c414e434527293b0a20202020202020207d0a20202020202020200a2020202020202020726571756972652873656e646572203d3d206d73672e73656e646572207c7c202873656e64657220696e20746f6b656e416c6c6f77616e636573202626206d73672e73656e64657220696e20746f6b656e416c6c6f77616e6365735b73656e6465725d292c20224d495353494e475f414c4c4f57414e434522293b0a20202020202020206966202873656e64657220213d206d73672e73656e64657220262620746f6b656e416c6c6f77616e6365735b73656e6465725d5b6d73672e73656e6465725d2e6c7428616d6f756e742929207b0a2020202020202020202020205f7468726f772827494e53554646494349454e545f544f4b454e5f414c4c4f57414e434527293b0a20202020202020207d0a0a2020202020202020746f6b656e416c6c6f77616e6365735b73656e6465725d5b6d73672e73656e6465725d203d20746f6b656e416c6c6f77616e6365735b73656e6465725d5b6d73672e73656e6465725d2e73756228616d6f756e74293b0a0a202020202020202069662028746869732e5f64656372656d656e745573657242616c616e63652e62696e642874686973292873656e6465722c20616d6f756e742929207b0a202020202020202020202020746869732e5f696e6372656d656e745573657242616c616e63652e62696e6428746869732928726563697069656e742c20616d6f756e74290a20202020202020207d0a20202020202020200a0a2020202020202020656d697428275472616e73666572272c205b73656e6465722c20726563697069656e742c20616d6f756e745d293b0a202020202020202072657475726e205b747275655d3b0a202020207d0a0a0a20202020616c6c6f77616e6365286f776e65722c207370656e64657229207b0a20202020202020206f776e6572203d206f776e65722e746f4c6f7765724361736528293b0a20202020202020207370656e646572203d207370656e6465722e746f4c6f7765724361736528293b0a202020202020202076617220746f6b656e416c6c6f77616e636573203d20746869732e616c6c6f776564207c7c207b7d3b0a2020202020202020696620286f776e657220696e20746f6b656e416c6c6f77616e636573202626207370656e64657220696e20746f6b656e416c6c6f77616e6365735b6f776e65725d29207b0a20202020202020202020202072657475726e205b746f6b656e416c6c6f77616e6365735b6f776e65725d5b7370656e6465725d5d3b0a20202020202020207d0a202020202020202072657475726e205b6e756d6265722830295d3b0a202020207d0a202020200a20202020617070726f7665287370656e6465722c2076616c756529207b0a20202020202020207370656e646572203d207370656e6465722e746f4c6f7765724361736528293b0a202020202020202076617220746f6b656e416c6c6f77616e636573203d20746869732e616c6c6f776564207c7c207b7d3b0a2020202020202020696620282120286d73672e73656e64657220696e20746f6b656e416c6c6f77616e6365732929207b0a202020202020202020202020746f6b656e416c6c6f77616e6365735b6d73672e73656e6465725d203d207b7d3b0a20202020202020207d0a2020202020202020746f6b656e416c6c6f77616e6365735b6d73672e73656e6465725d5b7370656e6465725d203d2076616c75653b0a2020202020202020656d69742827417070726f76616c272c205b6d73672e73656e6465722c207370656e6465722c2076616c75655d293b0a202020202020202072657475726e205b747275655d3b0a202020207d0a0a202020200a202020206d696e742875736572416464726573732c20616d6f756e7429207b0a2020202020202020746869732e5f6d696e742875736572416464726573732c20616d6f756e74293b0a202020207d0a0a202020205f6d696e742875736572416464726573732c20616d6f756e7429207b0a20202020202020207573657241646472657373203d2075736572416464726573732e746f4c6f7765724361736528293b0a2020202020202020696620287573657241646472657373203d3d20616464726573732830292e6164647265737329207b0a2020202020202020202020205f7468726f77282745524332303a206d696e7420746f20746865207a65726f206164647265737327293b0a20202020202020207d0a20202020202020200a202020202020202069662028746869732e5f696e6372656d656e745573657242616c616e63652e62696e642874686973292875736572416464726573732c20616d6f756e742929207b0a202020202020202020202020746869732e746f74616c537570706c79203d20746869732e746f74616c537570706c792e61646428616d6f756e74293b0a20202020202020207d0a0a2020202020202020656d697428274d696e74272c205b75736572416464726573732c20616d6f756e745d293b0a202020207d0a0a202020200a202020206275726e2875736572416464726573732c20616d6f756e7429207b0a2020202020202020746869732e5f6275726e2875736572416464726573732c20616d6f756e74293b0a202020207d0a0a202020205f6275726e2875736572416464726573732c20616d6f756e7429207b0a20202020202020207573657241646472657373203d2075736572416464726573732e746f4c6f7765724361736528293b0a2020202020202020696620287573657241646472657373203d3d20616464726573732830292e6164647265737329207b0a2020202020202020202020205f7468726f77282745524332303a206275726e2066726f6d20746865207a65726f206164647265737327293b0a20202020202020207d0a20202020202020200a202020202020202069662028746869732e5f64656372656d656e745573657242616c616e63652e62696e642874686973292875736572416464726573732c20616d6f756e742929207b0a202020202020202020202020746869732e746f74616c537570706c79203d20746869732e746f74616c537570706c792e73756228616d6f756e74293b0a20202020202020207d0a0a2020202020202020656d697428274275726e272c205b75736572416464726573732c20616d6f756e745d293b0a202020207d0a0a0a20202020737570706f727473496e7465726661636528696e74657266616365494429207b0a2020202020202020636f6e7374205f696e7465726661636573203d20746869732e737570706f72746564496e7465726661636573207c7c207b7d3b0a202020202020202072657475726e205b0a2020202020202020202020202828696e74657266616365494420696e205f696e7465726661636573290a202020202020202020202020202020203f205f696e74657266616365735b696e7465726661636549445d0a202020202020202020202020202020203a2066616c7365290a20202020202020205d3b0a202020207d0a0a0a202020205f64656372656d656e745573657242616c616e636528686f6c646572416464726573732c20616d6f756e7429207b0a202020202020202076617220746f6b656e42616c616e636573203d20746869732e62616c616e636573207c7c207b7d3b0a0a202020202020202069662028212028686f6c6465724164647265737320696e20746f6b656e42616c616e6365732929207b0a2020202020202020202020205f7468726f772827494e53554646494349454e545f544f4b454e5f42414c414e434527293b0a20202020202020207d0a202020202020202069662028616d6f756e742e677428746f6b656e42616c616e6365735b686f6c646572416464726573735d2929207b0a2020202020202020202020205f7468726f772827494e53554646494349454e545f544f4b454e5f42414c414e434527293b0a20202020202020207d0a2020202020202020746f6b656e42616c616e6365735b686f6c646572416464726573735d203d20746f6b656e42616c616e6365735b686f6c646572416464726573735d2e73756228616d6f756e74293b0a202020202020202072657475726e20747275653b0a202020207d0a0a0a202020205f696e6372656d656e745573657242616c616e636528686f6c646572416464726573732c20616d6f756e7429207b0a202020202020202076617220746f6b656e42616c616e636573203d20746869732e62616c616e636573207c7c207b7d3b0a0a202020202020202069662028212028686f6c6465724164647265737320696e20746f6b656e42616c616e6365732929207b0a202020202020202020202020746f6b656e42616c616e6365735b686f6c646572416464726573735d203d206e756d6265722830293b0a20202020202020207d0a2020202020202020746f6b656e42616c616e6365735b686f6c646572416464726573735d203d20746f6b656e42616c616e6365735b686f6c646572416464726573735d2e61646428616d6f756e74293b0a202020202020202072657475726e20747275653b0a202020207d0a0a7d0a0a2f2f204576656e74730a4552433230546f6b656e2e70726f746f747970652e5472616e736665722e6576656e74203d20747275653b0a4552433230546f6b656e2e70726f746f747970652e5472616e736665722e696e70757473203d205b276164647265737320696e6465786564272c20276164647265737320696e6465786564272c202775696e74323536275d3b0a0a4552433230546f6b656e2e70726f746f747970652e417070726f76616c2e6576656e74203d20747275653b0a4552433230546f6b656e2e70726f746f747970652e417070726f76616c2e696e70757473203d205b276164647265737320696e6465786564272c20276164647265737320696e6465786564272c202775696e74323536275d3b0a0a4552433230546f6b656e2e70726f746f747970652e4d696e742e6576656e74203d20747275653b0a4552433230546f6b656e2e70726f746f747970652e4d696e742e696e70757473203d205b276164647265737320696e6465786564272c202775696e74323536275d3b0a0a4552433230546f6b656e2e70726f746f747970652e4275726e2e6576656e74203d20747275653b0a4552433230546f6b656e2e70726f746f747970652e4275726e2e696e70757473203d205b276164647265737320696e6465786564272c202775696e74323536275d3b0a0a2f2f204d6574686f64730a4552433230546f6b656e2e70726f746f747970652e62616c616e63654f662e76696577203d20747275653b0a4552433230546f6b656e2e70726f746f747970652e62616c616e63654f662e696e70757473203d205b2761646472657373275d3b0a4552433230546f6b656e2e70726f746f747970652e62616c616e63654f662e6f757470757473203d205b2775696e74323536275d3b0a0a4552433230546f6b656e2e70726f746f747970652e7472616e736665722e696e70757473203d205b2761646472657373272c202775696e74323536275d3b0a4552433230546f6b656e2e70726f746f747970652e7472616e736665722e6f757470757473203d205b27626f6f6c275d3b0a0a4552433230546f6b656e2e70726f746f747970652e7472616e7366657246726f6d2e696e70757473203d205b2761646472657373272c202761646472657373272c202775696e74323536275d3b0a4552433230546f6b656e2e70726f746f747970652e7472616e7366657246726f6d2e6f757470757473203d205b27626f6f6c275d3b0a0a4552433230546f6b656e2e70726f746f747970652e616c6c6f77616e63652e76696577203d20747275653b0a4552433230546f6b656e2e70726f746f747970652e616c6c6f77616e63652e696e70757473203d205b2761646472657373272c202761646472657373275d3b0a4552433230546f6b656e2e70726f746f747970652e616c6c6f77616e63652e6f757470757473203d205b2775696e74323536275d3b0a0a4552433230546f6b656e2e70726f746f747970652e617070726f76652e696e70757473203d205b2761646472657373272c202775696e74323536275d3b0a4552433230546f6b656e2e70726f746f747970652e617070726f76652e6f757470757473203d205b27626f6f6c275d3b0a0a4552433230546f6b656e2e70726f746f747970652e6d696e742e696e70757473203d205b2761646472657373272c202775696e74323536275d3b0a4552433230546f6b656e2e70726f746f747970652e6d696e742e6d6f64696669657273203d205b275f6f6e6c794f776e6572275d3b0a0a4552433230546f6b656e2e70726f746f747970652e5f6d696e742e696e7465726e616c203d20747275653b0a0a4552433230546f6b656e2e70726f746f747970652e6275726e2e696e70757473203d205b2761646472657373272c202775696e74323536275d3b0a4552433230546f6b656e2e70726f746f747970652e6275726e2e6d6f64696669657273203d205b275f6f6e6c794f776e6572275d3b0a0a4552433230546f6b656e2e70726f746f747970652e5f6275726e2e696e7465726e616c203d20747275653b0a0a4552433230546f6b656e2e70726f746f747970652e737570706f727473496e746572666163652e76696577203d20747275653b0a4552433230546f6b656e2e70726f746f747970652e737570706f727473496e746572666163652e696e70757473203d205b27627974657334275d3b0a4552433230546f6b656e2e70726f746f747970652e737570706f727473496e746572666163652e6f757470757473203d205b27626f6f6c275d3b0a0a4552433230546f6b656e2e70726f746f747970652e5f64656372656d656e745573657242616c616e63652e696e7465726e616c203d20747275653b0a0a4552433230546f6b656e2e70726f746f747970652e5f696e6372656d656e745573657242616c616e63652e696e7465726e616c203d20747275653b0a0a0a0a0a0a0a66756e6374696f6e2049455243323028746f6b656e4164647265737329207b0a2020202072657475726e206164647265737328746f6b656e41646472657373292e676574436f6e747261637428293b0a7d0a0a0a636c617373204c69717569646974795061697220657874656e6473204552433230546f6b656e207b0a202020206e616d65203d20274c697175696469747950616972204c5073273b0a2020202073796d626f6c203d20274c6971756964697479506169722d4c50273b0a20202020646563696d616c73203d2031383b0a20202020746f74616c537570706c79203d206e756d6265722830293b0a202020207265736572766530203d206e756d6265722830293b0a202020207265736572766531203d206e756d6265722830293b0a20202020746f6b656e30203d2027307830303030303030303030303030303030303030303030303030303030303030303030303030303030273b0a20202020746f6b656e31203d2027307830303030303030303030303030303030303030303030303030303030303030303030303030303030273b0a0a202020200a20202020496e697469616c697a6528746f6b656e302c20746f6b656e3129207b7d202f2f206576656e740a202020204164644c697175696469747928616d6f756e74302c20616d6f756e743129207b7d202f2f206576656e740a2020202052656d6f76654c697175696469747928616d6f756e74302c20616d6f756e743129207b7d202f2f206576656e740a202020205472616e736665722866726f6d2c20746f2c2076616c756529207b7d202f2f206576656e740a202020204d696e74286d696e7465722c20616d6f756e74302c20616d6f756e743129207b7d202f2f206576656e740a202020204275726e286275726e65722c20616d6f756e74302c20616d6f756e74312c20746f29207b7d202f2f206576656e740a2020202053796e632872657365727665302c20726573657276653129207b7d202f2f206576656e740a20202020537761702873656e6465722c20616d6f756e7430496e2c20616d6f756e7431496e2c20616d6f756e74304f75742c20616d6f756e74314f75742c20746f29207b7d202f2f206576656e740a0a202020200a20202020636f6e7374727563746f722829207b0a2020202020202020737570657228293b0a2020202020202020746869732e666163746f7279203d206d73672e73656e6465723b0a202020207d0a0a0a20202020696e697469616c697a6528746f6b656e302c20746f6b656e3129207b0a202020202020202072657175697265286d73672e73656e646572203d3d20746869732e666163746f72792c202750616e63616b653a20464f5242494444454e27293b0a2020202020202020746869732e746f6b656e30203d20746f6b656e302e746f4c6f7765724361736528293b0a2020202020202020746869732e746f6b656e31203d20746f6b656e312e746f4c6f7765724361736528293b0a0a2020202020202020656d69742827496e697469616c697a65272c205b746f6b656e302c20746f6b656e315d293b0a202020207d0a0a2020202067657452657365727665732829207b0a202020202020202072657475726e205b0a202020202020202020202020746869732e72657365727665302c0a202020202020202020202020746869732e72657365727665312c0a20202020202020205d3b0a202020207d0a0a202020200a202020205f6164644c697175696469747928616d6f756e7441446573697265642c20616d6f756e74424465736972656429207b0a2020202020202020636f6e7374205b72657365727665412c2072657365727665425d203d20746869732e676574526573657276657328293b0a20202020202020206c657420616d6f756e74412c20616d6f756e74423b0a202020200a20202020202020206966202872657365727665412e65712830292026262072657365727665422e657128302929207b0a2020202020202020202020205b616d6f756e74412c20616d6f756e74425d203d205b616d6f756e7441446573697265642c20616d6f756e7442446573697265645d3b0a0a20202020202020207d20656c7365207b0a202020202020202020202020636f6e737420616d6f756e74424f7074696d616c203d20746869732e5f71756f746528616d6f756e7441446573697265642c2072657365727665412c207265736572766542295b305d3b0a2020202020202020202020202f2f20544f444f3a20612072657465737465722f7265766f69720a2020202020202020202020206966202874727565207c7c202120616d6f756e74424f7074696d616c2e677428616d6f756e7442446573697265642929207b0a202020202020202020202020202020202f2f726571756972652821616d6f756e74424f7074696d616c2e6c7428616d6f756e74424d696e292c202750616e63616b65526f757465723a20494e53554646494349454e545f425f414d4f554e5427293b0a202020202020202020202020202020205b616d6f756e74412c20616d6f756e74425d203d205b616d6f756e7441446573697265642c20616d6f756e74424f7074696d616c5d3b0a0a2020202020202020202020207d20656c7365207b0a20202020202020202020202020202020636f6e737420616d6f756e74414f7074696d616c203d20746869732e5f71756f746528616d6f756e7442446573697265642c2072657365727665422c207265736572766541295b305d3b0a202020202020202020202020202020206173736572742821616d6f756e74414f7074696d616c2e677428616d6f756e74414465736972656429293b0a202020202020202020202020202020202f2f726571756972652821616d6f756e74414f7074696d616c2e6c7428616d6f756e74414d696e292c202750616e63616b65526f757465723a20494e53554646494349454e545f415f414d4f554e5427293b0a202020202020202020202020202020205b616d6f756e74412c20616d6f756e74425d203d205b616d6f756e74414f7074696d616c2c20616d6f756e7442446573697265645d3b0a2020202020202020202020207d0a20202020202020207d0a202020202020202072657475726e205b616d6f756e74412c20616d6f756e74425d3b0a202020207d0a0a0a202020205f71756f746528616d6f756e74412c2072657365727665422c20726573657276654129207b0a20202020202020207265717569726528616d6f756e74412e67742830292c202750616e63616b654c6962726172793a20494e53554646494349454e545f414d4f554e5427293b0a2020202020202020726571756972652872657365727665412e67742830292026262072657365727665422e67742830292c202750616e63616b654c6962726172793a20494e53554646494349454e545f4c495155494449545927293b0a2020202020202020636f6e737420616d6f756e7442203d20616d6f756e74412e6d756c287265736572766542292e646976287265736572766541293b0a202020202020202072657475726e205b616d6f756e74425d3b0a202020207d0a0a0a202020206164644c697175696469747928616d6f756e7441446573697265642c20616d6f756e74424465736972656429207b0a20202020202020207265717569726528746869732e746f6b656e3120213d2027307830303030303030303030303030303030303030303030303030303030303030303030303030303030272c20224e4f545f494e495449414c495a454422293b0a2020202020202020636f6e7374205b616d6f756e74412c20616d6f756e74425d203d20746869732e5f6164644c697175696469747928616d6f756e7441446573697265642c20616d6f756e744244657369726564293b0a0a2020202020202020636f6e737420746f6b656e41203d20746869732e746f6b656e303b0a2020202020202020636f6e737420746f6b656e42203d20746869732e746f6b656e313b0a202020202020202049455243323028746f6b656e41292e7472616e7366657246726f6d286d73672e73656e6465722c20616464726573732874686973292e616464726573732c20616d6f756e7441293b0a202020202020202049455243323028746f6b656e42292e7472616e7366657246726f6d286d73672e73656e6465722c20616464726573732874686973292e616464726573732c20616d6f756e7442293b0a20202020202020200a2020202020202020636f6e7374206c6971756964697479203d20746869732e6d696e74286d73672e73656e646572295b305d3b0a20202020202020200a2020202020202020656d697428274164644c6971756964697479272c205b616d6f756e74412c20616d6f756e74425d293b0a0a202020202020202072657475726e205b616d6f756e74412c20616d6f756e74422c206c69717569646974795d3b0a202020207d0a0a0a202020206d696e7428746f29207b0a20202020202020207265717569726528746869732e746f6b656e3120213d2027307830303030303030303030303030303030303030303030303030303030303030303030303030303030272c20224e4f545f494e495449414c495a454422293b0a2020202020202020636f6e7374205b5f72657365727665302c205f72657365727665315d203d20746869732e676574526573657276657328293b0a2020202020202020636f6e73742062616c616e636530203d2049455243323028746869732e746f6b656e30292e62616c616e63654f6628616464726573732874686973292e61646472657373295b305d3b0a2020202020202020636f6e73742062616c616e636531203d2049455243323028746869732e746f6b656e31292e62616c616e63654f6628616464726573732874686973292e61646472657373295b305d3b0a2020202020202020636f6e737420616d6f756e7430203d2062616c616e6365302e737562285f7265736572766530293b0a2020202020202020636f6e737420616d6f756e7431203d2062616c616e6365312e737562285f7265736572766531293b0a2020202020202020636f6e7374204d494e494d554d5f4c4951554944495459203d2031302a2a333b0a0a202020202020202066756e6374696f6e205f6d696e28612c206229207b0a20202020202020202020202072657475726e20612e6774286229203f2062203a20613b0a20202020202020207d0a0a202020202020202066756e6374696f6e2073717274426967496e742876616c756529207b0a2020202020202020202020206966202876616c7565203c20306e29207b0a202020202020202020202020202020207468726f77202773717561726520726f6f74206f66206e65676174697665206e756d62657273206973206e6f7420737570706f72746564270a2020202020202020202020207d0a20202020202020200a2020202020202020202020206966202876616c7565203c20326e29207b0a2020202020202020202020202020202072657475726e2076616c75653b0a2020202020202020202020207d0a20202020202020200a20202020202020202020202066756e6374696f6e205f69746572286e2c20783029207b0a20202020202020202020202020202020636f6e7374207831203d2028286e202f20783029202b20783029203e3e20316e3b0a20202020202020202020202020202020696620287830203d3d3d207831207c7c207830203d3d3d20287831202d20316e2929207b0a202020202020202020202020202020202020202072657475726e2078303b0a202020202020202020202020202020207d0a2020202020202020202020202020202072657475726e205f69746572286e2c207831293b0a2020202020202020202020207d0a20202020202020200a20202020202020202020202072657475726e205f697465722876616c75652c20316e293b0a20202020202020207d0a20202020202020200a202020202020202066756e6374696f6e205f73717274286129207b0a20202020202020202020202072657475726e206e756d6265722873717274426967496e7428612e746f426967496e742829292e746f537472696e672829293b0a20202020202020207d0a0a20202020202020206c6574206c69717569646974793b0a20202020202020202f2f636f6e7374206665654f6e203d205f6d696e74466565285f72657365727665302c205f7265736572766531293b0a0a2020202020202020636f6e7374205f746f74616c537570706c79203d20746869732e746f74616c537570706c793b0a2020202020202020696620285f746f74616c537570706c79203d3d203029207b0a2020202020202020202020206c6971756964697479203d205f7371727428616d6f756e74302e6d756c28616d6f756e743129292e737562284d494e494d554d5f4c4951554944495459293b0a202020202020202020202020746869732e5f6d696e7428616464726573732830292e616464726573732c204d494e494d554d5f4c4951554944495459293b202f2f207065726d616e656e746c79206c6f636b20746865206669727374204d494e494d554d5f4c495155494449545920746f6b656e730a0a20202020202020207d20656c7365207b0a2020202020202020202020206c6971756964697479203d205f6d696e28616d6f756e74302e6d756c285f746f74616c537570706c79292e646976285f7265736572766530292c20616d6f756e74312e6d756c285f746f74616c537570706c79292e646976285f726573657276653129293b0a20202020202020207d0a202020202020202072657175697265286c69717569646974792e67742830292c202750616e63616b653a20494e53554646494349454e545f4c49515549444954595f4d494e54454427293b0a2020202020202020746869732e5f6d696e7428746f2c206c6971756964697479293b0a0a2020202020202020746869732e5f7570646174652862616c616e6365302c2062616c616e6365312c205f72657365727665302c205f7265736572766531293b0a20202020202020200a20202020202020202f2f696620286665654f6e29206b4c617374203d2075696e74287265736572766530292e6d756c287265736572766531293b202f2f20726573657276653020616e64207265736572766531206172652075702d746f2d646174650a20202020202020202f2f656d697428274d696e74272c205b6d73672e73656e6465722c20616d6f756e74302c20616d6f756e74315d293b0a0a202020202020202072657475726e205b0a202020202020202020202020616d6f756e74302c0a20202020202020205d3b0a202020207d0a0a0a2020202072656d6f76654c6971756964697479286c697175696469747929207b0a20202020202020207265717569726528746869732e746f6b656e3120213d2027307830303030303030303030303030303030303030303030303030303030303030303030303030303030272c20224e4f545f494e495449414c495a454422293b0a2020202020202020746869732e7472616e7366657246726f6d286d73672e73656e6465722c20616464726573732874686973292e616464726573732c206c6971756964697479293b202f2f2073656e64206c697175696469747920746f20706169720a0a0a2020202020202020636f6e737420746f6b656e30203d20746869732e746f6b656e303b0a0a2020202020202020636f6e7374205b616d6f756e74412c20616d6f756e74425d203d20746869732e6275726e28746f293b0a0a20202020202020202f2f72657175697265282120616d6f756e74412e6c7428616d6f756e74414d696e292c202750616e63616b65526f757465723a20494e53554646494349454e545f415f414d4f554e5427293b0a20202020202020202f2f72657175697265282120616d6f756e74422e6c7428616d6f756e74424d696e292c202750616e63616b65526f757465723a20494e53554646494349454e545f425f414d4f554e5427293b0a20202020202020207265717569726528616d6f756e74412e67742830292c202750616e63616b65526f757465723a20494e53554646494349454e545f415f414d4f554e5427293b0a20202020202020207265717569726528616d6f756e74422e67742830292c202750616e63616b65526f757465723a20494e53554646494349454e545f425f414d4f554e5427293b0a20202020202020200a2020202020202020656d6974282752656d6f76654c6971756964697479272c205b616d6f756e74412c20616d6f756e74425d293b0a0a202020202020202072657475726e205b616d6f756e74412c20616d6f756e74425d3b0a202020207d0a0a0a202020206275726e28746f29207b0a20202020202020207265717569726528746869732e746f6b656e3120213d2027307830303030303030303030303030303030303030303030303030303030303030303030303030303030272c20224e4f545f494e495449414c495a454422293b0a2020202020202020636f6e7374205b5f72657365727665302c205f72657365727665315d203d20746869732e676574526573657276657328293b0a2020202020202020636f6e7374205f746f6b656e30203d20746869732e746f6b656e303b0a2020202020202020636f6e7374205f746f6b656e31203d20746869732e746f6b656e313b0a2020202020202020636f6e73742062616c616e636530203d20494552433230285f746f6b656e30292e62616c616e63654f6628616464726573732874686973292e61646472657373295b305d3b0a2020202020202020636f6e73742062616c616e636531203d20494552433230285f746f6b656e31292e62616c616e63654f6628616464726573732874686973292e61646472657373295b305d3b0a2020202020202020636f6e7374206c6971756964697479203d20746869732e62616c616e63654f665b616464726573732874686973292e616464726573735d3b0a0a20202020202020202f2f636f6e7374206665654f6e203d20746869732e5f6d696e74466565285f72657365727665302c205f7265736572766531293b0a2020202020202020636f6e7374205f746f74616c537570706c79203d20746869732e746f74616c537570706c793b0a20202020202020200a2020202020202020636f6e737420616d6f756e7430203d206c69717569646974792e6d756c2862616c616e636530292e646976285f746f74616c537570706c79293b202f2f207573696e672062616c616e63657320656e73757265732070726f2d7261746120646973747269627574696f6e0a2020202020202020636f6e737420616d6f756e7431203d206c69717569646974792e6d756c2862616c616e636531292e646976285f746f74616c537570706c79293b202f2f207573696e672062616c616e63657320656e73757265732070726f2d7261746120646973747269627574696f6e0a20202020202020207265717569726528616d6f756e74302e677428302920262620616d6f756e74312e67742830292c202750616e63616b653a20494e53554646494349454e545f4c49515549444954595f4255524e454427293b0a0a2020202020202020746869732e5f6275726e28616464726573732874686973292e616464726573732c206c6971756964697479293b0a2020202020202020494552433230285f746f6b656e30292e7472616e7366657228746f2c20616d6f756e7430293b0a2020202020202020494552433230285f746f6b656e31292e7472616e7366657228746f2c20616d6f756e7431293b0a202020202020202062616c616e636530203d20494552433230285f746f6b656e30292e62616c616e63654f6628616464726573732874686973292e61646472657373293b0a202020202020202062616c616e636531203d20494552433230285f746f6b656e31292e62616c616e63654f6628616464726573732874686973292e61646472657373293b0a0a2020202020202020746869732e5f7570646174652862616c616e6365302c2062616c616e6365312c205f72657365727665302c205f7265736572766531293b0a0a20202020202020202f2f696620286665654f6e29206b4c617374203d2075696e74287265736572766530292e6d756c287265736572766531293b202f2f20726573657276653020616e64207265736572766531206172652075702d746f2d646174650a20202020202020202f2f656d697428274275726e272c205b6d73672e73656e6465722c20616d6f756e74302c20616d6f756e74312c20746f5d293b0a0a202020202020202072657475726e205b0a202020202020202020202020616d6f756e74302c0a202020202020202020202020616d6f756e74312c0a20202020202020205d0a202020207d0a0a0a202020205f6d696e7428746f2c2076616c756529207b0a2020202020202020746869732e746f74616c537570706c79203d20746869732e746f74616c537570706c792e6164642876616c7565293b0a202020202020202069662028212028746f20696e20746869732e62616c616e6365732929207b0a202020202020202020202020746869732e62616c616e6365735b746f5d203d206e756d6265722830293b0a20202020202020207d0a2020202020202020746869732e62616c616e6365735b746f5d203d20746869732e62616c616e6365735b746f5d2e6164642876616c7565293b0a2020202020202020656d697428275472616e73666572272c205b616464726573732830292e616464726573732c20746f2c2076616c75655d293b0a202020207d0a0a202020205f6275726e2866726f6d2c2076616c756529207b0a2020202020202020726571756972652866726f6d20696e20746869732e62616c616e636573202626202120746869732e62616c616e6365735b66726f6d5d2e6c7428746869732e62616c616e6365735b66726f6d5d29293b0a2020202020202020746869732e62616c616e6365735b66726f6d5d203d20746869732e62616c616e6365735b66726f6d5d2e7375622876616c7565293b0a2020202020202020746869732e746f74616c537570706c79203d20746869732e746f74616c537570706c792e7375622876616c7565293b0a2020202020202020656d697428275472616e73666572272c205b66726f6d2c20616464726573732830292e616464726573732c2076616c75655d293b0a202020207d0a202020200a202020205f7570646174652862616c616e6365302c2062616c616e63653129207b0a2020202020202020746869732e7265736572766530203d2062616c616e6365303b0a2020202020202020746869732e7265736572766531203d2062616c616e6365313b0a2020202020202020656d6974282753796e63272c205b746869732e72657365727665302c20746869732e72657365727665315d293b0a202020207d0a0a0a20202020737761704578616374546f6b656e73466f72546f6b656e7328736f75726365546f6b656e2c20616d6f756e74496e2c20616d6f756e744f75744d696e29207b0a20202020202020207265717569726528746869732e746f6b656e3120213d2027307830303030303030303030303030303030303030303030303030303030303030303030303030303030272c20224e4f545f494e495449414c495a454422293b0a2020202020202020736f75726365546f6b656e203d20736f75726365546f6b656e2e746f4c6f7765724361736528293b0a2020202020202020636f6e737420746f203d206d73672e73656e6465723b0a2020202020202020636f6e737420746172676574546f6b656e203d2028736f75726365546f6b656e203d3d20746869732e746f6b656e3029203f20746869732e746f6b656e31203a20746869732e746f6b656e303b0a20202020202020200a2020202020202020636f6e737420616d6f756e7473203d20746869732e5f676574416d6f756e74734f757428616d6f756e74496e2c20736f75726365546f6b656e295b305d3b0a202020202020202072657175697265282120616d6f756e74735b616d6f756e74732e6c656e677468202d20315d2e6c7428616d6f756e744f75744d696e292c202750616e63616b65526f757465723a20494e53554646494349454e545f4f55545055545f414d4f554e5427293b0a0a202020202020202049455243323028736f75726365546f6b656e292e7472616e7366657246726f6d286d73672e73656e6465722c20616464726573732874686973292e616464726573732c20616d6f756e74735b305d293b0a0a2020202020202020746869732e5f7377617028616d6f756e74732c20736f75726365546f6b656e2c20746f293b0a0a202020202020202072657475726e205b616d6f756e74735d3b0a202020207d0a0a202020205f676574416d6f756e74734f757428616d6f756e74496e2c20736f75726365546f6b656e29207b0a2020202020202020636f6e7374205b5f72657365727665302c205f72657365727665315d203d20746869732e676574526573657276657328293b0a2020202020202020636f6e7374205b72657365727665496e2c20726573657276654f75745d203d2028736f75726365546f6b656e203d3d20746869732e746f6b656e3029203f205b5f72657365727665302c205f72657365727665315d203a205b5f72657365727665312c205f72657365727665305d3b0a0a2020202020202020636f6e737420616d6f756e7473203d205b5d3b0a2020202020202020616d6f756e74732e7075736828616d6f756e74496e293b0a20202020202020200a2020202020202020636f6e7374205f616d6f756e74203d20746869732e5f676574416d6f756e744f757428616d6f756e74735b305d2c2072657365727665496e2c20726573657276654f7574295b305d3b0a2020202020202020616d6f756e74732e70757368285f616d6f756e74293b0a202020202020202072657475726e205b616d6f756e74735d3b0a202020207d0a0a202020205f676574416d6f756e744f757428616d6f756e74496e2c2072657365727665496e2c20726573657276654f757429207b0a20202020202020207265717569726528616d6f756e74496e2e67742830292c202750616e63616b654c6962726172793a20494e53554646494349454e545f494e5055545f414d4f554e5427293b0a2020202020202020726571756972652872657365727665496e2e677428302920262620726573657276654f75742e67742830292c202750616e63616b654c6962726172793a20494e53554646494349454e545f4c495155494449545927293b0a0a2020202020202020636f6e737420616d6f756e74496e57697468466565203d20616d6f756e74496e2e6d756c28393938293b0a2020202020202020636f6e7374206e756d657261746f72203d20616d6f756e74496e576974684665652e6d756c28726573657276654f7574293b0a2020202020202020636f6e73742064656e6f6d696e61746f72203d2072657365727665496e2e6d756c2831303030292e61646428616d6f756e74496e57697468466565293b0a2020202020202020636f6e737420616d6f756e744f7574203d206e756d657261746f722e6469762864656e6f6d696e61746f72293b0a202020202020202072657475726e205b616d6f756e744f75745d3b0a202020207d0a0a0a202020207377617028616d6f756e74304f75742c20616d6f756e74314f75742c20746f2c20646174612920207b0a20202020202020207265717569726528746869732e746f6b656e3120213d2027307830303030303030303030303030303030303030303030303030303030303030303030303030303030272c20224e4f545f494e495449414c495a454422293b0a20202020202020207265717569726528616d6f756e74304f75742e6774283029207c7c20616d6f756e74314f75742e67742830292c202750616e63616b653a20494e53554646494349454e545f4f55545055545f414d4f554e5427293b0a2020202020202020636f6e7374205b5f72657365727665302c205f72657365727665312c5d203d20746869732e676574526573657276657328293b0a20202020202020207265717569726528616d6f756e74304f75742e6c74285f72657365727665302920262620616d6f756e74314f75742e6c74285f7265736572766531292c202750616e63616b653a20494e53554646494349454e545f4c495155494449545927293b0a0a20202020202020206c65742062616c616e6365303b0a20202020202020206c65742062616c616e6365313b0a20202020202020207b202f2f2073636f706520666f72205f746f6b656e7b302c317d2c2061766f69647320737461636b20746f6f2064656570206572726f72730a202020202020202020202020636f6e7374205f746f6b656e30203d20746869732e746f6b656e303b0a202020202020202020202020636f6e7374205f746f6b656e31203d20746869732e746f6b656e313b0a2020202020202020202020207265717569726528746f20213d205f746f6b656e3020262620746f20213d205f746f6b656e312c202750616e63616b653a20494e56414c49445f544f27293b0a0a20202020202020202020202069662028616d6f756e74304f75742e67742830292920494552433230285f746f6b656e30292e7472616e7366657228746f2c20616d6f756e74304f7574293b0a20202020202020202020202069662028616d6f756e74314f75742e67742830292920494552433230285f746f6b656e31292e7472616e7366657228746f2c20616d6f756e74314f7574293b0a0a202020202020202020202020696620286461746120262620646174612e6c656e677468203e2030292049455243323028746f292e70616e63616b6543616c6c286d73672e73656e6465722c20616d6f756e74304f75742c20616d6f756e74314f75742c2064617461293b0a0a20202020202020202020202062616c616e636530203d20494552433230285f746f6b656e30292e62616c616e63654f6628616464726573732874686973292e61646472657373295b305d3b0a20202020202020202020202062616c616e636531203d20494552433230285f746f6b656e31292e62616c616e63654f6628616464726573732874686973292e61646472657373295b305d3b0a20202020202020207d0a0a2020202020202020636f6e737420616d6f756e7430496e203d202862616c616e6365302e6774285f72657365727665302e73756228616d6f756e74304f7574292929203f2062616c616e6365302e73756228285f72657365727665302e73756228616d6f756e74304f7574292929203a206e756d6265722830293b0a2020202020202020636f6e737420616d6f756e7431496e203d202862616c616e6365312e6774285f72657365727665312e73756228616d6f756e74314f7574292929203f2062616c616e6365312e73756228285f72657365727665312e73756228616d6f756e74314f7574292929203a206e756d6265722830293b0a20202020202020207265717569726528616d6f756e7430496e2e6774283029207c7c20616d6f756e7431496e2e67742830292c202750616e63616b653a20494e53554646494349454e545f494e5055545f414d4f554e5427293b0a0a20202020202020207b202f2f2073636f706520666f7220726573657276657b302c317d41646a75737465642c2061766f69647320737461636b20746f6f2064656570206572726f72730a202020202020202020202020636f6e73742062616c616e63653041646a7573746564203d2062616c616e6365302e6d756c2831303030292e73756228616d6f756e7430496e2e6d756c283229293b0a202020202020202020202020636f6e73742062616c616e63653141646a7573746564203d2062616c616e6365312e6d756c2831303030292e73756228616d6f756e7431496e2e6d756c283229293b0a20202020202020202020202072657175697265282162616c616e63653041646a75737465642e6d756c2862616c616e63653141646a7573746564292e6c74285f72657365727665302e6d756c285f7265736572766531292e6d756c2828313030302a2a32292e746f537472696e67282929292c202750616e63616b653a204b27293b0a20202020202020207d0a0a2020202020202020746869732e5f7570646174652862616c616e6365302c2062616c616e6365312c205f72657365727665302c205f7265736572766531293b0a20202020202020200a2020202020202020656d6974282753776170272c205b6d73672e73656e6465722c20616d6f756e7430496e2c20616d6f756e7431496e2c20616d6f756e74304f75742c20616d6f756e74314f75742c20746f5d293b0a202020207d0a0a0a202020205f7377617028616d6f756e74732c20736f75726365546f6b656e2c205f746f29207b0a2020202020202020636f6e737420746172676574546f6b656e203d2028736f75726365546f6b656e203d3d20746869732e746f6b656e3029203f20746869732e746f6b656e31203a20746869732e746f6b656e303b0a2020202020202020636f6e73742070617468203d205b736f75726365546f6b656e2c20746172676574546f6b656e5d3b0a2020202020202020666f7220286c657420693d303b2069203c20706174682e6c656e677468202d20313b20692b2b29207b0a202020202020202020202020636f6e7374205b696e7075742c206f75747075745d203d205b20706174685b695d2c20706174685b69202b20315d205d3b0a2020202020202020202020202f2f636f6e7374205b746f6b656e302c5d203d2050616e63616b654c6962726172792e736f7274546f6b656e7328696e7075742c206f7574707574293b0a202020202020202020202020636f6e737420746f6b656e30203d2028696e707574203e206f757470757429203f206f7574707574203a20696e7075743b0a2020202020202020202020200a202020202020202020202020636f6e737420616d6f756e744f7574203d20616d6f756e74735b69202b20315d3b0a0a202020202020202020202020636f6e7374205b616d6f756e74304f75742c20616d6f756e74314f75745d203d2028696e707574203d3d20746f6b656e3029203f205b6e756d6265722830292c20616d6f756e744f75745d203a205b616d6f756e744f75742c206e756d6265722830295d3b0a0a202020202020202020202020636f6e737420746f203d202869203c20706174682e6c656e677468202d203229203f20616464726573732874686973292e61646472657373203a205f746f3b0a0a202020202020202020202020746869732e7377617028616d6f756e74304f75742c20616d6f756e74314f75742c20746f2c202727293b0a20202020202020207d0a202020207d0a0a7d0a0a2f2f204576656e74730a4c6971756964697479506169722e70726f746f747970652e496e697469616c697a652e6576656e74203d20747275653b0a4c6971756964697479506169722e70726f746f747970652e496e697469616c697a652e696e70757473203d205b276164647265737320696e6465786564272c20276164647265737320696e6465786564275d3b0a0a4c6971756964697479506169722e70726f746f747970652e4164644c69717569646974792e6576656e74203d20747275653b0a4c6971756964697479506169722e70726f746f747970652e4164644c69717569646974792e696e70757473203d205b2775696e74323536272c202775696e74323536275d3b0a0a4c6971756964697479506169722e70726f746f747970652e52656d6f76654c69717569646974792e6576656e74203d20747275653b0a4c6971756964697479506169722e70726f746f747970652e52656d6f76654c69717569646974792e696e70757473203d205b2775696e74323536272c202775696e74323536275d3b0a0a4c6971756964697479506169722e70726f746f747970652e5472616e736665722e6576656e74203d20747275653b0a4c6971756964697479506169722e70726f746f747970652e5472616e736665722e696e70757473203d205b276164647265737320696e6465786564272c202775696e74323536272c202775696e74323536275d3b0a0a4c6971756964697479506169722e70726f746f747970652e4d696e742e6576656e74203d20747275653b0a4c6971756964697479506169722e70726f746f747970652e4d696e742e696e70757473203d205b276164647265737320696e6465786564272c202775696e74323536272c202775696e74323536275d3b0a0a4c6971756964697479506169722e70726f746f747970652e4275726e2e6576656e74203d20747275653b0a4c6971756964697479506169722e70726f746f747970652e4275726e2e696e70757473203d205b276164647265737320696e6465786564272c202775696e74323536272c202775696e74323536272c20276164647265737320696e6465786564275d3b0a0a4c6971756964697479506169722e70726f746f747970652e53796e632e6576656e74203d20747275653b0a4c6971756964697479506169722e70726f746f747970652e53796e632e696e70757473203d205b2775696e74323536272c202775696e74323536275d3b0a0a4c6971756964697479506169722e70726f746f747970652e537761702e6576656e74203d20747275653b0a4c6971756964697479506169722e70726f746f747970652e537761702e696e70757473203d205b276164647265737320696e6465786564272c202775696e74323536272c202775696e74323536272c202775696e74323536272c202775696e74323536272c20276164647265737320696e6465786564275d3b0a0a2f2f204d6574686f64730a4c6971756964697479506169722e70726f746f747970652e696e697469616c697a652e696e70757473203d205b2761646472657373272c202761646472657373275d3b0a0a4c6971756964697479506169722e70726f746f747970652e67657452657365727665732e76696577203d20747275653b0a4c6971756964697479506169722e70726f746f747970652e67657452657365727665732e6f757470757473203d205b2775696e74323536272c202775696e74323536275d3b0a0a4c6971756964697479506169722e70726f746f747970652e5f6164644c69717569646974792e696e7465726e616c203d20747275653b0a0a4c6971756964697479506169722e70726f746f747970652e5f71756f74652e696e7465726e616c203d20747275653b0a0a4c6971756964697479506169722e70726f746f747970652e5f6d696e742e696e7465726e616c203d20747275653b0a0a4c6971756964697479506169722e70726f746f747970652e5f6275726e2e696e7465726e616c203d20747275653b0a0a4c6971756964697479506169722e70726f746f747970652e6164644c69717569646974792e696e70757473203d205b2775696e74323536272c202775696e74323536275d3b0a4c6971756964697479506169722e70726f746f747970652e6164644c69717569646974792e6f757470757473203d205b2775696e74323536272c202775696e74323536272c202775696e74323536275d3b0a0a4c6971756964697479506169722e70726f746f747970652e6d696e742e696e70757473203d205b2761646472657373275d3b0a4c6971756964697479506169722e70726f746f747970652e6d696e742e6f757470757473203d205b2775696e74323536275d3b0a0a4c6971756964697479506169722e70726f746f747970652e72656d6f76654c69717569646974792e696e70757473203d205b2775696e74323536275d3b0a4c6971756964697479506169722e70726f746f747970652e72656d6f76654c69717569646974792e6f757470757473203d205b2775696e74323536272c202775696e74323536275d3b0a0a4c6971756964697479506169722e70726f746f747970652e6275726e2e696e70757473203d205b2761646472657373275d3b0a4c6971756964697479506169722e70726f746f747970652e6275726e2e6f757470757473203d205b2775696e74323536272c202775696e74323536275d3b0a0a4c6971756964697479506169722e70726f746f747970652e737761704578616374546f6b656e73466f72546f6b656e732e696e70757473203d205b2761646472657373272c202775696e74323536272c202775696e74323536275d3b0a4c6971756964697479506169722e70726f746f747970652e737761704578616374546f6b656e73466f72546f6b656e732e6f757470757473203d205b2775696e745b5d275d3b0a0a4c6971756964697479506169722e70726f746f747970652e5f676574416d6f756e74734f75742e696e7465726e616c203d20747275653b0a0a4c6971756964697479506169722e70726f746f747970652e5f676574416d6f756e744f75742e696e7465726e616c203d20747275653b0a0a4c6971756964697479506169722e70726f746f747970652e737761702e696e70757473203d205b2775696e74323536272c202775696e74323536272c202761646472657373272c20276279746573275d3b0a0a4c6971756964697479506169722e70726f746f747970652e5f737761702e696e7465726e616c203d20747275653b0a0a0a0a72657475726e204c6971756964697479506169723b0a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000025b5d000000000000000000000000000000000000000000000000000000000000", nonce: "7", to: "0x", value: "0", chainId: "3615", origin: "1651572555562", v: "1c62", r: "4799de081734b7104ce37c499c427ead6a2e9133cc370ed5e2600e31f665acd1", s: "188d92578b709d60685699c65685d49a2758c0ca042bdb5b8ab5bdcc05cc594e" }, hash: "0xfe576f241ed53b32a1748eb53e93838c2e60c2ffc866a993482590a854d9152e", load: "() => {
const transactionFile = config.node.dataDir + '/transactions/' + this.hash + '.json';

if (! fs.existsSync(transactionFile)) {
console.warn(utils.getDate(), "[BLOCKCHAIN]", 'IMPORT Transaction not found', this.hash);
throw new Error('IMPORT_TRANSACTION_NOT_FOUND_DATABASE_CORRUPTED');
}

const transactionJson = fs.readFileSync(transactionFile);
transactionData = JSON.parse(transactionJson, utils.bnJsonParser);

this.data = (typeof transactionData == 'TransactionData') ? transactionData : new TransactionData(transactionData);

return true;
}"
, save: "() => {
const transactionFile = config.node.dataDir + '/transactions/' + this.hash + '.json';
fs.writeFileSync(transactionFile, JSON.stringify(this.data));
return true;
}"
, getReceipt: "() => {
return blockchain.getTransactionReceiptByHash(this.hash);
}"
, getBlock: "() => {
return blockchain.getBlockByHash(this.data.blockHash);
}"
, getBlockTransationPrev: "() => {
const block = this.getBlock();
const txPrevIdx = (this.data.transactionIndex > 0) ? (this.data.transactionIndex-1) : null;
const tx = (block && txPrevIdx in block.transactions) ? block.transactions[txPrevIdx] : null;
return tx;
}"
, getBlockTransationNext: "() => {
const block = this.getBlock();
const txNextIdx = (this.data.transactionIndex < block.transactions.length - 1) ? (this.data.transactionIndex+1) : null;
const tx = (block && txNextIdx in block.transactions) ? block.transactions[txNextIdx] : null;
return tx;
}"
, getAccountTransactionIndex: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = account.data.transactions.findIndex(transactionHash => blockchain.getTransactionByHash(transactionHash).hash == this.hash);
if (txIdx === -1) {
return null;
}
return txIdx;
}"
, getAccountTransationPrev: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = this.getAccountTransactionIndex();
if (txIdx === null) {
return null;
}
const txPrevIdx = (txIdx > 0) ? (txIdx-1) : null;
const transactionHash = (txPrevIdx in account.data.transactions) ? account.data.transactions[txPrevIdx] : null;
if (! transactionHash) {
return null;
}
return blockchain.getTransactionByHash(transactionHash);
}"
, getAccountTransationNext: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = this.getAccountTransactionIndex();
if (txIdx === null) {
return null;
}
const txNextIdx = (txIdx < account.data.transactions.length - 1) ? (txIdx+1) : null;
const transactionHash = (txNextIdx in account.data.transactions) ? account.data.transactions[txNextIdx] : null;
if (! transactionHash) {
return null;
}
return blockchain.getTransactionByHash(transactionHash);

}"
, formatRpc: "() => {
// https://github.com/ethers-io/ethers.js/blob/master/packages/abstract-provider/lib/index.d.ts

const to = (this.data.to && this.data.to.length == 42) ? this.data.to : null;

const _txData = {
blockHash: this.data.blockHash,
blockNumber: this.data.blockNumber.toNumber(),
transactionIndex: this.data.transactionIndex,
hash: this.data.hash,
from: this.data.from,
gasLimit: this.data.gasLimit.toHexString(),
gasPrice: this.data.gasPrice.toHexString(),
data: this.data.data,
nonce: this.data.nonce.toHexString(),
to,
value: this.data.value.toHexString(),
chainId: this.data.chainId.toNumber(),
//origin: this.data.origin.toHexString(),
};

return _txData;
}"
, getHashOLD: "() => {
const _txData = {
gasPrice: this.data.gasPrice,
gasLimit: this.data.gasLimit,
from: this.data.from,
to: this.data.to,
value: this.data.value,
data: this.data.data,
nonce: this.data.nonce,
};

const strvalue = JSON.stringify(utils.orderedObject(_txData));
const transactionHash = ethers.utils.id(strvalue);

return transactionHash;
}"
, getHash: "() => {
const _txData = {
gasPrice: this.data.gasPrice.toHexString(),
gasLimit: this.data.gasLimit.toHexString(),
from: this.data.from,
to: this.data.to,
value: this.data.value.toHexString(),
data: this.data.data,
nonce: this.data.nonce.toHexString(),
v: this.data.v ? ('0x' + this.data.v) : '',
r: this.data.r ? ('0x' + this.data.r) : '',
s: this.data.s ? ('0x' + this.data.s) : '',
};

const tx = new EthereumJS.Transaction(_txData);
const hashRaw = tx.hash();

const transactionHash = ethers.utils.defaultAbiCoder.decode(["uint256"], hashRaw)[0].toHexString();

return transactionHash;
}"
, execute: "(block, transactionIndex, logIndexOffset) => {
const _tx = this;

function _rejectTransaction(reason) {
// https://openethereum.github.io/Transactions-Queue
reason = reason || 'unknown';
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] REJECTED PROCESSING TRANSACTION', _tx.data.hash, ' (Reason:', reason + ')');
const success = blockchain.removeTransactionsFromMempool([_tx.hash], true, true);
if (success) {
saveMempool();
}
throw new Error(reason); // on rejete definitivement la tx (elle ne sera pas minée ... et donc aucun frais de gas ne sera compté)
// et du coup on rejete le block
}

function _checkGas(step) {
execDuration = performance.now()/1000 - execTsStart; //parseFloat(process.hrtime(execTsStart).join('.'));
gasUsed = toBn(Math.round(execDuration * cpuPricePerSecond)); // TODO: prendre en compte le storage utilisé dans le calcul du gasUsed
txCost = gasUsed.mul(_tx.data.gasPrice);

if (step == 'afterTransfer') {
//debugger;
}

if (status) {
gasUsedEffective = gasUsed;
txCostEffective = gasUsedEffective.mul(_tx.data.gasPrice);

if (gasUsed.gt(_tx.data.gasLimit)) {
status = 0;
error = new Error('INSUFFICIENT_GAS');

gasUsedEffective = _tx.data.gasLimit;
txCostEffective = txCostLimit;

// emit event
const debugError = {
message: 'INSUFFICIENT_GAS',
step,
gasPrice: _tx.data.gasPrice,
gasLimit: _tx.data.gasLimit,
gasUsed,
gasUsedEffective,
txCostLimit,
txCost,
txCostEffective,
execDuration,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, _tx.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

}
}


//const block = this;
const txLogs = [];
let vmStorage = {};

let gasUsed = toBn(0);
let gasUsedEffective = toBn(0);
let status = 1;
let error = null;
let createdContractAddress = null;
let amountTransfered = toBn(0);

const contractAddress = this.data.to;
let storageBackup;
let balanceBackup;
let contractExecuted = false;
let contractDeployed = false;

const execTsStart = performance.now()/1000; //process.hrtime(); //Date.now();
let execDuration = 0;

const txCostLimit = this.data.gasLimit.mul(this.data.gasPrice);
let txCost = toBn(0);
let txCostEffective = toBn(0);


const accountFrom = blockchain.getAccount(this.data.from);
if (! accountFrom) {
_rejectTransaction('TRANSACTION_REJECTED_EMPTY_ACCOUNT');
}
const accountFromBalance = accountFrom.data.balance;


if (accountFrom.address != mintAddress) {

// verif nonce
const expectedNonce = toBn(accountFrom.data.transactions.length); // le nonce attendu est egal au nombre de transactions passées de l'account
if (! this.data.nonce.eq(expectedNonce)) {
_rejectTransaction('TRANSACTION_REJECTED_INCORRECT_NONCE');
}

// verif balance pour gas + transfer
if (this.data.value && this.data.value.gt(0)) {
if (accountFromBalance.lt(this.data.value.add(txCostLimit))) {
// l'adresse n'a pas suffisamment de fond pour executer la transaction (selon gasLimit)
// TODO: faut-il rejeter la tx ? ou la laisser passer (en status=0) et lui vider sa balance (si 100% du gas est utilisé) ?
//return _rejectTransaction('TRANSACTION_FAILED_INSUFFICIENT_ETH_BALANCE');

_rejectTransaction('TRANSACTION_REJECTED_INSUFFICIENT_ETH_BALANCE');
}
}
}



_checkGas('init');


if (status) {
if (this.data.value && this.data.value.gt(0) && contractAddress && contractAddress.length > 2) {
// Transfer la valeur demandée (simple ETH transaction)
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(this.data, block);
txLogs.push( ...transferLogs );

// si recipient == contract => on transfer quand meme dès maintenant => TODO: a revoir (le transferEth declenche un appel à receive ... avant meme de savoir si unne methode payable doit etre appelée. Probleme si receive provoque un revert => la fonction payable ne sera pas appelée)
// 1) si methode payable appellée, on l'execute
// 2) si methode non payable => on appelle receive juste avant
// 3) si methode inconnue => revert


if (transferResult && transferReverted) {
// reverted (after transfer)
amountTransfered = this.data.value;

status = 0;
error = new Error('TRANSFER_REVERTED');

} else if (transferResult) {
// success
amountTransfered = this.data.value;

} else {
// transfer failed
status = 0;
error = new Error('TRANSFER_FAILED');
}

}

_checkGas('afterTransfer');
}

const gasUsedBeforeContractExecution = gasUsed;

if (status && this.data.data && this.data.data != '0x') {
// Traitement du champ data de la transaction (smart contract call)

if (!contractAddress || contractAddress == '0x') {
// contract creation

contractDeployed = true;

let contractExecResult;
try {
contractExecResult = this.transactionDeployContract(block);
const creationLogs = contractExecResult.logs;
vmStorage = contractExecResult.storage;

if (creationLogs.length) {
txLogs.push( ...creationLogs );
}

if (! contractExecResult.result) {
// Deploy Failed
status = 0;
error = new Error('CONTRACT_CREATION_FAILED');

// emit event
const debugError = {
message: 'CONTRACT_CREATION_FAILED',
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);

} else {
// Deploy Success
createdContractAddress = contractExecResult.result.toLowerCase();

if (this.data.value.gt(0)) {
// Transfer tx value to contract
const _txData = {
...this.data,
to: createdContractAddress,
};
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(_txData, block);
txLogs.push( ...transferLogs );

if (transferResult) {
amountTransfered = this.data.value;

} else {
status = 0;
error = new Error('CONTRACT_CREATION_TRANSFER_FAILED');
}
}

// TODO: emit event CONTRACT_CREATION_SUCCESS ?
}



} catch (e) {
//console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] Cannot execute transaction');
//throw e;
status = 0;
error = new Error('CONTRACT_CREATION_ERROR');

console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] CONTRACT_CREATION_ERROR with message', e.message)

// emit event
const debugError = {
message: 'CONTRACT_CREATION_ERROR',
error: e.message,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

_checkGas('afterContractCreation');

} else {
// contract execution

const contractAccount = blockchain.getAccount(contractAddress);

if (! contractAccount) {
status = 0;
error = new Error('CONTRACT_ACCOUNT_NOT_FOUND');

// emit event
const debugError = {
message: 'CONTRACT_ACCOUNT_NOT_FOUND',
address: contractAddress,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

if (status) {
storageBackup = JSON.stringify(contractAccount.data.storage);
balanceBackup = contractAccount.data.balance;
contractExecuted = true;

try {
const contractExecResult = this.executeTransactionContract(block);
const internalLogs = contractExecResult.logs;
vmStorage = contractExecResult.storage;

if (internalLogs.length) {
txLogs.push( ...internalLogs );
}

if (! contractExecResult.result) {
status = 0;
error = new Error('CONTRACT_EXEC_FAILED');

// emit event
const debugError = {
message: 'CONTRACT_EXEC_FAILED',
address: contractAddress,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

} catch (e) {
status = 0;
error = new Error('CONTRACT_EXEC_ERROR');

// emit event
const debugError = {
message: 'CONTRACT_EXEC_ERROR',
address: contractAddress,
error: e.message,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}
}

_checkGas('afterContractCall');
}

}

const gasUsedAfterContractExecution = gasUsed;
const gasUsedInContractExecution = gasUsedAfterContractExecution.sub(gasUsedBeforeContractExecution);
const contractExecutionCost = gasUsedInContractExecution.mul(_tx.data.gasPrice);


console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] execDuration =', (Math.round(execDuration * 1000000) / 1000000), ' seconds');



if (txCostEffective.gt(0)) {
// Gas payment
//const minerAccount = blockchain.getAccount(block.data.miner, true);
//const gasPaymentResult = minerAccount.transferFrom(accountFrom.address, txCostEffective);

const txDataGasPayment = {
from: accountFrom.address,
to: block.data.miner,
value: txCostEffective,
};
const [gasPaymentResult, gasPaymentLogs, transferReverted] = blockchain.transferEth(txDataGasPayment, block, true);
txLogs.push(...gasPaymentLogs);

if (gasPaymentResult) {
// gas payment success

} else {
// gas payment failed => reject transaction (and block)
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] Gas payment failed');
_rejectTransaction('GAS_PAYMENT_FAILED');
}

if (false) {
// DEBUG
const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();
const gasEffectivePercent = gasUsedEffective.mul(100).div(this.data.gasLimit).toNumber();
const costPercent = txCost.mul(100).div(txCostLimit).toNumber();
const costEffectivePercent = txCostEffective.mul(100).div(txCostLimit).toNumber();
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] txCostLimit =', ethers.utils.formatEther(txCostLimit));
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] txCostEffective =', ethers.utils.formatEther(txCostEffective));
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] gas % =', Math.round(gasPercent), '% =', Math.round(gasEffectivePercent), '%');
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] cost % =', Math.round(costPercent), '%', Math.round(costEffectivePercent), '%');
}


}


if (accountFrom.address != mintAddress) {
const accountFromBalanceAtEnd = blockchain.getAccountBalance(accountFrom.address);

if (accountFromBalanceAtEnd.lt(0)){
_rejectTransaction('TRANSACTION_FAILED_INSUFFICIENT_ETH_BALANCE_AT_END');
}
}


if (! status) {
// Transaction Failed

// Revert transfer
if (amountTransfered.gt(0)) {
const txDataRevert = {
...this.data,
from: this.data.to,
to: this.data.from,
value: amountTransfered,
};
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(txDataRevert, block);

// TODO: gerer le revert des internal transfers
}

txLogs.forEach(log => {
log.removed = true;
});

// Revert contract storage
if (contractExecuted) {
const contractAccount = blockchain.getAccount(contractAddress);
contractAccount.data.storage = JSON.parse(storageBackup, utils.bnJsonParser);
contractAccount.data.balance = balanceBackup;

} else if (amountTransfered) {
// on check aussi amountTransfered car le contrat peut avoir été exécuté via receive => TODO: process a revoir
const contractAccount = blockchain.getAccount(contractAddress);
if (contractAccount && contractAccount.isContract()) {
//contractAccount.data.storage = JSON.parse(storageBackup, utils.bnJsonParser);
//contractAccount.data.balance = balanceBackup;
// TODO
}

}

// Revert deployed contract
if (contractDeployed) {
// nothing to do ?
}

const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] FAILED TRANSACTION ', this.data.hash, '| gas usage percent =', gasPercent, '%', ' | Error: ', error.message);

const realExecDuration = performance.now()/1000 - execTsStart; //parseFloat(process.hrtime(execTsStart).join('.'));
const realGasUsed = toBn(Math.round(realExecDuration * cpuPricePerSecond)); // TODO: prendre en compte le storage utilisé dans le calcul du gasUsed
const realGasPercent = realGasUsed.mul(100).div(this.data.gasLimit).toNumber();
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] FAILED TRANSACTION ', this.data.hash, '| real gas usage percent =', realGasPercent, '%', ' | Error: ', error.message);

} else {
// Transaction Success

// save contract storage
Object.entries(vmStorage).forEach((entry, idx) => {
const [contractAddress, contractStorage] = entry;

const contractAccount = blockchain.getAccount(contractAddress, true);
contractAccount.data.storage = contractStorage;
contractAccount.save();
});

// show result (debug)
const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();;
console.log(utils.getDate(), "[BLOCKCHAIN]", 'SUCCECSS TRANSACTION ', this.data.hash, '| gas usage percent =', gasPercent, '%');
}


// total gas utilisé dans le block
block.data.gasUsed = block.data.gasUsed.add(gasUsed);


// assign block data on logs
txLogs.forEach((txLog, idx) => {
txLog.data.transactionIndex = transactionIndex;
txLog.data.logIndex = logIndexOffset + idx;
txLog.data.transactionHash = this.data.hash;
txLog.data.blockHash = block.hash;
txLog.data.blockNumber = block.data.number;
});


const receiptData = {
transactionHash: this.data.hash,
transactionIndex: transactionIndex,
blockNumber: block.data.number,
blockHash: block.hash,
//logsBloom: '0x0000',
contractAddress: createdContractAddress,
gasUsed,
cumulativeGasUsed: block.data.gasUsed,
effectiveGasPrice: this.data.gasPrice,
effectiveCost: this.data.gasPrice.mul(gasUsed),
logs: txLogs,
status,
}

const receipt = new Receipt(blockchain, receiptData);

return [receipt, txLogs];
}"
, executeTransactionContract: "(block) => {
let contractAddress = this.data.to;

const isNullRecipient = (! contractAddress || contractAddress.length <= 2);
if (isNullRecipient) {
throw new Error('CONTRACT_EXEC_ADDRESS_INVALID_BUG_1');
}

const contractAccount = blockchain.getAccount(contractAddress);

if (! contractAccount) {
throw new Error('CONTRACT_EXEC_ACCOUNT_NOT_FOUND');
}

// Execute existing contract method call
return blockchain.callContractMethodInVM(this.data, block);

}"
, transactionDeployContract: "(block) => {
let contractAddress = this.data.to;

let contractAccount;

if (contractAddress && contractAddress.length > 2) {
throw new Error('CONTRACT_DEPLOY_ADDRESS_INVALID_BUG_2');
}


// CONTRACT CREATION

if (! this.data.data || this.data.data.length <= 2) {
throw new Error('CONTRACT_DEPLOY_INVALID_DATA_EMPTY_CODE');
}


// build contract address
contractAddress = ethers.utils.getContractAddress(this.data).toLocaleLowerCase();

// parse contract source code & constructor parameters
const codes = ethers.utils.defaultAbiCoder.decode(["string", "string"], this.data.data);
if (! codes) {
throw new Error('CONTRACT_DEPLOY_CODE_PARSING_FAILED');
}

const sourceCode = codes[0];
const constructorParams = codes[1];
const constructorRawParams = ethers.utils.defaultAbiCoder.encode(["string"], [constructorParams]);
const codeHash = ethers.utils.id(sourceCode);
const codeAbi = null; // l'abi sera généré une fois que le contrat aura été instancié
const storageHash = ethers.utils.id('{}');

// create new account
const accountData = {
address: contractAddress,
code: sourceCode,
codeHash,
codeAbi,
storage: {},
storageHash,
};
contractAccount = blockchain.createAccount(contractAddress, accountData);


// instanciate contract + call constructor + recuperer storage
const txData = {
from: this.data.from,
to: contractAddress,
data: constructorRawParams,
value: this.data.value.toHexString(),
};
const callResult = blockchain.deployContractInVM(txData, block);
const creationLogs = callResult.logs;
const vmStorage = callResult.storage;


// emit event
const eventParams = [
contractAddress,
];
const _txData = {
...this.data,
to: blockchain.getNullAddress(),
}
const deployLog = new TxLog(blockchain, null, _txData, 'ContractCreated', eventParams, deployAbi);
creationLogs.push(deployLog);


return {
result: contractAddress,
logs: creationLogs,
storage: vmStorage,
};
}"
, timestamp: "1651572557849", status: 1, gasUsed: "130808" }, 0xd27067832f83e7888f4fa5193f443f880650af6aa95d1f67ba0f48d22619d302: { data: { blockHash: "0x007d12ef737a792e65de5c88ad295881851a53ffb193f3157f157bb13747118d", blockNumber: "9", transactionIndex: 0, hash: "0xd27067832f83e7888f4fa5193f443f880650af6aa95d1f67ba0f48d22619d302", from: "0x0000000000000000000000000000000000000001", gasLimit: "1000000000000000000", gasPrice: "0", data: "0x", nonce: "8", to: "0xccf8ba457dcad7ee6a0361c96846a0f79744b113", value: "250000000000000000000", chainId: "3615", origin: "1651572563042", v: "", r: "", s: "" }, hash: "0xd27067832f83e7888f4fa5193f443f880650af6aa95d1f67ba0f48d22619d302", load: "() => {
const transactionFile = config.node.dataDir + '/transactions/' + this.hash + '.json';

if (! fs.existsSync(transactionFile)) {
console.warn(utils.getDate(), "[BLOCKCHAIN]", 'IMPORT Transaction not found', this.hash);
throw new Error('IMPORT_TRANSACTION_NOT_FOUND_DATABASE_CORRUPTED');
}

const transactionJson = fs.readFileSync(transactionFile);
transactionData = JSON.parse(transactionJson, utils.bnJsonParser);

this.data = (typeof transactionData == 'TransactionData') ? transactionData : new TransactionData(transactionData);

return true;
}"
, save: "() => {
const transactionFile = config.node.dataDir + '/transactions/' + this.hash + '.json';
fs.writeFileSync(transactionFile, JSON.stringify(this.data));
return true;
}"
, getReceipt: "() => {
return blockchain.getTransactionReceiptByHash(this.hash);
}"
, getBlock: "() => {
return blockchain.getBlockByHash(this.data.blockHash);
}"
, getBlockTransationPrev: "() => {
const block = this.getBlock();
const txPrevIdx = (this.data.transactionIndex > 0) ? (this.data.transactionIndex-1) : null;
const tx = (block && txPrevIdx in block.transactions) ? block.transactions[txPrevIdx] : null;
return tx;
}"
, getBlockTransationNext: "() => {
const block = this.getBlock();
const txNextIdx = (this.data.transactionIndex < block.transactions.length - 1) ? (this.data.transactionIndex+1) : null;
const tx = (block && txNextIdx in block.transactions) ? block.transactions[txNextIdx] : null;
return tx;
}"
, getAccountTransactionIndex: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = account.data.transactions.findIndex(transactionHash => blockchain.getTransactionByHash(transactionHash).hash == this.hash);
if (txIdx === -1) {
return null;
}
return txIdx;
}"
, getAccountTransationPrev: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = this.getAccountTransactionIndex();
if (txIdx === null) {
return null;
}
const txPrevIdx = (txIdx > 0) ? (txIdx-1) : null;
const transactionHash = (txPrevIdx in account.data.transactions) ? account.data.transactions[txPrevIdx] : null;
if (! transactionHash) {
return null;
}
return blockchain.getTransactionByHash(transactionHash);
}"
, getAccountTransationNext: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = this.getAccountTransactionIndex();
if (txIdx === null) {
return null;
}
const txNextIdx = (txIdx < account.data.transactions.length - 1) ? (txIdx+1) : null;
const transactionHash = (txNextIdx in account.data.transactions) ? account.data.transactions[txNextIdx] : null;
if (! transactionHash) {
return null;
}
return blockchain.getTransactionByHash(transactionHash);

}"
, formatRpc: "() => {
// https://github.com/ethers-io/ethers.js/blob/master/packages/abstract-provider/lib/index.d.ts

const to = (this.data.to && this.data.to.length == 42) ? this.data.to : null;

const _txData = {
blockHash: this.data.blockHash,
blockNumber: this.data.blockNumber.toNumber(),
transactionIndex: this.data.transactionIndex,
hash: this.data.hash,
from: this.data.from,
gasLimit: this.data.gasLimit.toHexString(),
gasPrice: this.data.gasPrice.toHexString(),
data: this.data.data,
nonce: this.data.nonce.toHexString(),
to,
value: this.data.value.toHexString(),
chainId: this.data.chainId.toNumber(),
//origin: this.data.origin.toHexString(),
};

return _txData;
}"
, getHashOLD: "() => {
const _txData = {
gasPrice: this.data.gasPrice,
gasLimit: this.data.gasLimit,
from: this.data.from,
to: this.data.to,
value: this.data.value,
data: this.data.data,
nonce: this.data.nonce,
};

const strvalue = JSON.stringify(utils.orderedObject(_txData));
const transactionHash = ethers.utils.id(strvalue);

return transactionHash;
}"
, getHash: "() => {
const _txData = {
gasPrice: this.data.gasPrice.toHexString(),
gasLimit: this.data.gasLimit.toHexString(),
from: this.data.from,
to: this.data.to,
value: this.data.value.toHexString(),
data: this.data.data,
nonce: this.data.nonce.toHexString(),
v: this.data.v ? ('0x' + this.data.v) : '',
r: this.data.r ? ('0x' + this.data.r) : '',
s: this.data.s ? ('0x' + this.data.s) : '',
};

const tx = new EthereumJS.Transaction(_txData);
const hashRaw = tx.hash();

const transactionHash = ethers.utils.defaultAbiCoder.decode(["uint256"], hashRaw)[0].toHexString();

return transactionHash;
}"
, execute: "(block, transactionIndex, logIndexOffset) => {
const _tx = this;

function _rejectTransaction(reason) {
// https://openethereum.github.io/Transactions-Queue
reason = reason || 'unknown';
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] REJECTED PROCESSING TRANSACTION', _tx.data.hash, ' (Reason:', reason + ')');
const success = blockchain.removeTransactionsFromMempool([_tx.hash], true, true);
if (success) {
saveMempool();
}
throw new Error(reason); // on rejete definitivement la tx (elle ne sera pas minée ... et donc aucun frais de gas ne sera compté)
// et du coup on rejete le block
}

function _checkGas(step) {
execDuration = performance.now()/1000 - execTsStart; //parseFloat(process.hrtime(execTsStart).join('.'));
gasUsed = toBn(Math.round(execDuration * cpuPricePerSecond)); // TODO: prendre en compte le storage utilisé dans le calcul du gasUsed
txCost = gasUsed.mul(_tx.data.gasPrice);

if (step == 'afterTransfer') {
//debugger;
}

if (status) {
gasUsedEffective = gasUsed;
txCostEffective = gasUsedEffective.mul(_tx.data.gasPrice);

if (gasUsed.gt(_tx.data.gasLimit)) {
status = 0;
error = new Error('INSUFFICIENT_GAS');

gasUsedEffective = _tx.data.gasLimit;
txCostEffective = txCostLimit;

// emit event
const debugError = {
message: 'INSUFFICIENT_GAS',
step,
gasPrice: _tx.data.gasPrice,
gasLimit: _tx.data.gasLimit,
gasUsed,
gasUsedEffective,
txCostLimit,
txCost,
txCostEffective,
execDuration,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, _tx.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

}
}


//const block = this;
const txLogs = [];
let vmStorage = {};

let gasUsed = toBn(0);
let gasUsedEffective = toBn(0);
let status = 1;
let error = null;
let createdContractAddress = null;
let amountTransfered = toBn(0);

const contractAddress = this.data.to;
let storageBackup;
let balanceBackup;
let contractExecuted = false;
let contractDeployed = false;

const execTsStart = performance.now()/1000; //process.hrtime(); //Date.now();
let execDuration = 0;

const txCostLimit = this.data.gasLimit.mul(this.data.gasPrice);
let txCost = toBn(0);
let txCostEffective = toBn(0);


const accountFrom = blockchain.getAccount(this.data.from);
if (! accountFrom) {
_rejectTransaction('TRANSACTION_REJECTED_EMPTY_ACCOUNT');
}
const accountFromBalance = accountFrom.data.balance;


if (accountFrom.address != mintAddress) {

// verif nonce
const expectedNonce = toBn(accountFrom.data.transactions.length); // le nonce attendu est egal au nombre de transactions passées de l'account
if (! this.data.nonce.eq(expectedNonce)) {
_rejectTransaction('TRANSACTION_REJECTED_INCORRECT_NONCE');
}

// verif balance pour gas + transfer
if (this.data.value && this.data.value.gt(0)) {
if (accountFromBalance.lt(this.data.value.add(txCostLimit))) {
// l'adresse n'a pas suffisamment de fond pour executer la transaction (selon gasLimit)
// TODO: faut-il rejeter la tx ? ou la laisser passer (en status=0) et lui vider sa balance (si 100% du gas est utilisé) ?
//return _rejectTransaction('TRANSACTION_FAILED_INSUFFICIENT_ETH_BALANCE');

_rejectTransaction('TRANSACTION_REJECTED_INSUFFICIENT_ETH_BALANCE');
}
}
}



_checkGas('init');


if (status) {
if (this.data.value && this.data.value.gt(0) && contractAddress && contractAddress.length > 2) {
// Transfer la valeur demandée (simple ETH transaction)
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(this.data, block);
txLogs.push( ...transferLogs );

// si recipient == contract => on transfer quand meme dès maintenant => TODO: a revoir (le transferEth declenche un appel à receive ... avant meme de savoir si unne methode payable doit etre appelée. Probleme si receive provoque un revert => la fonction payable ne sera pas appelée)
// 1) si methode payable appellée, on l'execute
// 2) si methode non payable => on appelle receive juste avant
// 3) si methode inconnue => revert


if (transferResult && transferReverted) {
// reverted (after transfer)
amountTransfered = this.data.value;

status = 0;
error = new Error('TRANSFER_REVERTED');

} else if (transferResult) {
// success
amountTransfered = this.data.value;

} else {
// transfer failed
status = 0;
error = new Error('TRANSFER_FAILED');
}

}

_checkGas('afterTransfer');
}

const gasUsedBeforeContractExecution = gasUsed;

if (status && this.data.data && this.data.data != '0x') {
// Traitement du champ data de la transaction (smart contract call)

if (!contractAddress || contractAddress == '0x') {
// contract creation

contractDeployed = true;

let contractExecResult;
try {
contractExecResult = this.transactionDeployContract(block);
const creationLogs = contractExecResult.logs;
vmStorage = contractExecResult.storage;

if (creationLogs.length) {
txLogs.push( ...creationLogs );
}

if (! contractExecResult.result) {
// Deploy Failed
status = 0;
error = new Error('CONTRACT_CREATION_FAILED');

// emit event
const debugError = {
message: 'CONTRACT_CREATION_FAILED',
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);

} else {
// Deploy Success
createdContractAddress = contractExecResult.result.toLowerCase();

if (this.data.value.gt(0)) {
// Transfer tx value to contract
const _txData = {
...this.data,
to: createdContractAddress,
};
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(_txData, block);
txLogs.push( ...transferLogs );

if (transferResult) {
amountTransfered = this.data.value;

} else {
status = 0;
error = new Error('CONTRACT_CREATION_TRANSFER_FAILED');
}
}

// TODO: emit event CONTRACT_CREATION_SUCCESS ?
}



} catch (e) {
//console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] Cannot execute transaction');
//throw e;
status = 0;
error = new Error('CONTRACT_CREATION_ERROR');

console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] CONTRACT_CREATION_ERROR with message', e.message)

// emit event
const debugError = {
message: 'CONTRACT_CREATION_ERROR',
error: e.message,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

_checkGas('afterContractCreation');

} else {
// contract execution

const contractAccount = blockchain.getAccount(contractAddress);

if (! contractAccount) {
status = 0;
error = new Error('CONTRACT_ACCOUNT_NOT_FOUND');

// emit event
const debugError = {
message: 'CONTRACT_ACCOUNT_NOT_FOUND',
address: contractAddress,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

if (status) {
storageBackup = JSON.stringify(contractAccount.data.storage);
balanceBackup = contractAccount.data.balance;
contractExecuted = true;

try {
const contractExecResult = this.executeTransactionContract(block);
const internalLogs = contractExecResult.logs;
vmStorage = contractExecResult.storage;

if (internalLogs.length) {
txLogs.push( ...internalLogs );
}

if (! contractExecResult.result) {
status = 0;
error = new Error('CONTRACT_EXEC_FAILED');

// emit event
const debugError = {
message: 'CONTRACT_EXEC_FAILED',
address: contractAddress,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

} catch (e) {
status = 0;
error = new Error('CONTRACT_EXEC_ERROR');

// emit event
const debugError = {
message: 'CONTRACT_EXEC_ERROR',
address: contractAddress,
error: e.message,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}
}

_checkGas('afterContractCall');
}

}

const gasUsedAfterContractExecution = gasUsed;
const gasUsedInContractExecution = gasUsedAfterContractExecution.sub(gasUsedBeforeContractExecution);
const contractExecutionCost = gasUsedInContractExecution.mul(_tx.data.gasPrice);


console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] execDuration =', (Math.round(execDuration * 1000000) / 1000000), ' seconds');



if (txCostEffective.gt(0)) {
// Gas payment
//const minerAccount = blockchain.getAccount(block.data.miner, true);
//const gasPaymentResult = minerAccount.transferFrom(accountFrom.address, txCostEffective);

const txDataGasPayment = {
from: accountFrom.address,
to: block.data.miner,
value: txCostEffective,
};
const [gasPaymentResult, gasPaymentLogs, transferReverted] = blockchain.transferEth(txDataGasPayment, block, true);
txLogs.push(...gasPaymentLogs);

if (gasPaymentResult) {
// gas payment success

} else {
// gas payment failed => reject transaction (and block)
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] Gas payment failed');
_rejectTransaction('GAS_PAYMENT_FAILED');
}

if (false) {
// DEBUG
const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();
const gasEffectivePercent = gasUsedEffective.mul(100).div(this.data.gasLimit).toNumber();
const costPercent = txCost.mul(100).div(txCostLimit).toNumber();
const costEffectivePercent = txCostEffective.mul(100).div(txCostLimit).toNumber();
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] txCostLimit =', ethers.utils.formatEther(txCostLimit));
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] txCostEffective =', ethers.utils.formatEther(txCostEffective));
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] gas % =', Math.round(gasPercent), '% =', Math.round(gasEffectivePercent), '%');
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] cost % =', Math.round(costPercent), '%', Math.round(costEffectivePercent), '%');
}


}


if (accountFrom.address != mintAddress) {
const accountFromBalanceAtEnd = blockchain.getAccountBalance(accountFrom.address);

if (accountFromBalanceAtEnd.lt(0)){
_rejectTransaction('TRANSACTION_FAILED_INSUFFICIENT_ETH_BALANCE_AT_END');
}
}


if (! status) {
// Transaction Failed

// Revert transfer
if (amountTransfered.gt(0)) {
const txDataRevert = {
...this.data,
from: this.data.to,
to: this.data.from,
value: amountTransfered,
};
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(txDataRevert, block);

// TODO: gerer le revert des internal transfers
}

txLogs.forEach(log => {
log.removed = true;
});

// Revert contract storage
if (contractExecuted) {
const contractAccount = blockchain.getAccount(contractAddress);
contractAccount.data.storage = JSON.parse(storageBackup, utils.bnJsonParser);
contractAccount.data.balance = balanceBackup;

} else if (amountTransfered) {
// on check aussi amountTransfered car le contrat peut avoir été exécuté via receive => TODO: process a revoir
const contractAccount = blockchain.getAccount(contractAddress);
if (contractAccount && contractAccount.isContract()) {
//contractAccount.data.storage = JSON.parse(storageBackup, utils.bnJsonParser);
//contractAccount.data.balance = balanceBackup;
// TODO
}

}

// Revert deployed contract
if (contractDeployed) {
// nothing to do ?
}

const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] FAILED TRANSACTION ', this.data.hash, '| gas usage percent =', gasPercent, '%', ' | Error: ', error.message);

const realExecDuration = performance.now()/1000 - execTsStart; //parseFloat(process.hrtime(execTsStart).join('.'));
const realGasUsed = toBn(Math.round(realExecDuration * cpuPricePerSecond)); // TODO: prendre en compte le storage utilisé dans le calcul du gasUsed
const realGasPercent = realGasUsed.mul(100).div(this.data.gasLimit).toNumber();
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] FAILED TRANSACTION ', this.data.hash, '| real gas usage percent =', realGasPercent, '%', ' | Error: ', error.message);

} else {
// Transaction Success

// save contract storage
Object.entries(vmStorage).forEach((entry, idx) => {
const [contractAddress, contractStorage] = entry;

const contractAccount = blockchain.getAccount(contractAddress, true);
contractAccount.data.storage = contractStorage;
contractAccount.save();
});

// show result (debug)
const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();;
console.log(utils.getDate(), "[BLOCKCHAIN]", 'SUCCECSS TRANSACTION ', this.data.hash, '| gas usage percent =', gasPercent, '%');
}


// total gas utilisé dans le block
block.data.gasUsed = block.data.gasUsed.add(gasUsed);


// assign block data on logs
txLogs.forEach((txLog, idx) => {
txLog.data.transactionIndex = transactionIndex;
txLog.data.logIndex = logIndexOffset + idx;
txLog.data.transactionHash = this.data.hash;
txLog.data.blockHash = block.hash;
txLog.data.blockNumber = block.data.number;
});


const receiptData = {
transactionHash: this.data.hash,
transactionIndex: transactionIndex,
blockNumber: block.data.number,
blockHash: block.hash,
//logsBloom: '0x0000',
contractAddress: createdContractAddress,
gasUsed,
cumulativeGasUsed: block.data.gasUsed,
effectiveGasPrice: this.data.gasPrice,
effectiveCost: this.data.gasPrice.mul(gasUsed),
logs: txLogs,
status,
}

const receipt = new Receipt(blockchain, receiptData);

return [receipt, txLogs];
}"
, executeTransactionContract: "(block) => {
let contractAddress = this.data.to;

const isNullRecipient = (! contractAddress || contractAddress.length <= 2);
if (isNullRecipient) {
throw new Error('CONTRACT_EXEC_ADDRESS_INVALID_BUG_1');
}

const contractAccount = blockchain.getAccount(contractAddress);

if (! contractAccount) {
throw new Error('CONTRACT_EXEC_ACCOUNT_NOT_FOUND');
}

// Execute existing contract method call
return blockchain.callContractMethodInVM(this.data, block);

}"
, transactionDeployContract: "(block) => {
let contractAddress = this.data.to;

let contractAccount;

if (contractAddress && contractAddress.length > 2) {
throw new Error('CONTRACT_DEPLOY_ADDRESS_INVALID_BUG_2');
}


// CONTRACT CREATION

if (! this.data.data || this.data.data.length <= 2) {
throw new Error('CONTRACT_DEPLOY_INVALID_DATA_EMPTY_CODE');
}


// build contract address
contractAddress = ethers.utils.getContractAddress(this.data).toLocaleLowerCase();

// parse contract source code & constructor parameters
const codes = ethers.utils.defaultAbiCoder.decode(["string", "string"], this.data.data);
if (! codes) {
throw new Error('CONTRACT_DEPLOY_CODE_PARSING_FAILED');
}

const sourceCode = codes[0];
const constructorParams = codes[1];
const constructorRawParams = ethers.utils.defaultAbiCoder.encode(["string"], [constructorParams]);
const codeHash = ethers.utils.id(sourceCode);
const codeAbi = null; // l'abi sera généré une fois que le contrat aura été instancié
const storageHash = ethers.utils.id('{}');

// create new account
const accountData = {
address: contractAddress,
code: sourceCode,
codeHash,
codeAbi,
storage: {},
storageHash,
};
contractAccount = blockchain.createAccount(contractAddress, accountData);


// instanciate contract + call constructor + recuperer storage
const txData = {
from: this.data.from,
to: contractAddress,
data: constructorRawParams,
value: this.data.value.toHexString(),
};
const callResult = blockchain.deployContractInVM(txData, block);
const creationLogs = callResult.logs;
const vmStorage = callResult.storage;


// emit event
const eventParams = [
contractAddress,
];
const _txData = {
...this.data,
to: blockchain.getNullAddress(),
}
const deployLog = new TxLog(blockchain, null, _txData, 'ContractCreated', eventParams, deployAbi);
creationLogs.push(deployLog);


return {
result: contractAddress,
logs: creationLogs,
storage: vmStorage,
};
}"
, timestamp: "1651572564252", status: 1, gasUsed: "2419" }, 0xd242c8ffbe99682086ac93dfbdb2f83ce794ef8af244cc33bf59368aaf85b603: { data: { blockHash: "0x007d12ef737a792e65de5c88ad295881851a53ffb193f3157f157bb13747118d", blockNumber: "9", transactionIndex: 1, hash: "0xd242c8ffbe99682086ac93dfbdb2f83ce794ef8af244cc33bf59368aaf85b603", from: "0x131a190fa22cb867abf58193bf9e7640e9fce682", gasLimit: "1015000", gasPrice: "1000000000", data: "0x0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000244000000000000000000000000000000000000000000000000000000000000023c82f2a0a4e616d653a2050726573616c6520546f6b656e0a56657273696f6e3a20302e302e330a2a2f0a0a0a636c617373204f776e61626c65207b0a202020206f776e6572203d2027307830303030303030303030303030303030303030303030303030303030303030303030303030303030273b0a0a202020204f776e6572736869705472616e73666572726564286f6c644f776e65722c206e65774f776e657229207b7d202f2f206576656e740a202020200a0a20202020636f6e7374727563746f722829207b0a2020202020202020746869732e6f776e6572203d206d73672e73656e6465723b0a202020207d0a0a202020206765744f776e65722829207b0a202020202020202072657475726e205b746869732e6f776e65725d3b0a202020207d0a202020200a202020207472616e736665724f776e657273686970286e65774f776e657229207b0a202020202020202072657175697265286e65774f776e657220213d20616464726573732830292c20224f776e61626c653a206e6577206f776e657220697320746865207a65726f206164647265737322293b0a2020202020202020746869732e5f7472616e736665724f776e657273686970286e65774f776e6572293b0a202020207d0a0a202020205f7472616e736665724f776e657273686970286e65774f776e657229207b0a20202020202020206e65774f776e6572203d206e65774f776e65722e746f4c6f7765724361736528293b0a2020202020202020636f6e7374206f6c644f776e6572203d20746869732e6f776e65723b0a2020202020202020746869732e6f776e6572203d206e65774f776e65723b0a2020202020202020656d697428274f776e6572736869705472616e73666572726564272c205b6f6c644f776e65722c206e65774f776e65725d293b0a202020207d0a0a202020205f6f6e6c794f776e6572286e65787429207b0a202020202020202072657175697265286d73672e73656e6465722e746f4c6f776572436173652829203d3d20746869732e6f776e65722c20276f6e6c79206f776e65722063616e2065786563757465207468697327293b0a20202020202020206e65787428293b0a202020207d0a7d0a0a2f2f204d6574686f64730a4f776e61626c652e70726f746f747970652e4f776e6572736869705472616e736665727265642e6576656e74203d20747275653b0a4f776e61626c652e70726f746f747970652e4f776e6572736869705472616e736665727265642e696e70757473203d205b276164647265737320696e6465786564272c20276164647265737320696e6465786564275d3b0a0a2f2f204d6574686f64730a4f776e61626c652e70726f746f747970652e6765744f776e65722e76696577203d20747275653b0a4f776e61626c652e70726f746f747970652e6765744f776e65722e6f757470757473203d205b2761646472657373275d3b0a0a4f776e61626c652e70726f746f747970652e7472616e736665724f776e6572736869702e696e70757473203d205b2761646472657373275d3b0a4f776e61626c652e70726f746f747970652e7472616e736665724f776e6572736869702e6d6f64696669657273203d205b275f6f6e6c794f776e6572275d3b0a0a4f776e61626c652e70726f746f747970652e5f7472616e736665724f776e6572736869702e696e7465726e616c203d20747275653b0a0a4f776e61626c652e70726f746f747970652e5f6f6e6c794f776e65722e696e7465726e616c203d20747275653b0a0a0a0a0a636c617373204552433230546f6b656e20657874656e6473204f776e61626c65207b0a202020206e616d65203d2027455243323020546f6b656e273b0a2020202073796d626f6c203d20274552433230273b0a20202020646563696d616c73203d2031383b0a20202020746f74616c537570706c79203d206e756d62657228273027293b0a0a20202020616c6c6f776564203d207b7d3b0a2020202062616c616e636573203d207b7d3b0a0a20202020737570706f72746564496e7465726661636573203d207b0a20202020202020202730783336333732623037273a20747275652c202f2f2045524332300a20202020202020202730783036666464653033273a20747275652c202f2f204552433230206e616d650a20202020202020202730783935643839623431273a20747275652c202f2f2045524332302073796d626f6c0a20202020202020202730783331336365353637273a20747275652c202f2f20455243323020646563696d616c730a202020207d3b0a0a0a202020205472616e736665722873656e6465722c20726563697069656e742c20616d6f756e7429207b7d202f2f206576656e740a20202020417070726f76616c286f776e65722c207370656e6465722c20616d6f756e7429207b7d202f2f206576656e740a202020204d696e74286d696e7465722c20616d6f756e7429207b7d202f2f206576656e740a202020204275726e286275726e65722c20616d6f756e7429207b7d202f2f206576656e740a0a0a2020202062616c616e63654f6628686f6c6465724164647265737329207b0a2020202020202020686f6c64657241646472657373203d20686f6c646572416464726573732e746f4c6f7765724361736528293b0a202020202020202076617220746f6b656e42616c616e636573203d20746869732e62616c616e636573207c7c207b7d3b0a0a202020202020202069662028686f6c6465724164647265737320696e20746f6b656e42616c616e63657329207b0a202020202020202020202020636f6e73742062616c616e6365203d20746f6b656e42616c616e6365735b686f6c646572416464726573735d3b0a20202020202020202020202072657475726e205b62616c616e63655d3b0a20202020202020207d0a202020202020202072657475726e205b6e756d6265722830295d3b0a202020207d0a0a202020207472616e7366657228726563697069656e742c20616d6f756e7429207b0a2020202020202020726563697069656e74203d20726563697069656e742e746f4c6f7765724361736528293b0a2020202020202020636f6e73742073656e646572203d206d73672e73656e6465723b0a202020202020202076617220746f6b656e42616c616e636573203d20746869732e62616c616e636573207c7c207b7d3b0a0a20202020202020206966202821202873656e64657220696e20746f6b656e42616c616e6365732929207b0a2020202020202020202020205f7468726f772827494e53554646494349454e545f544f4b454e5f42414c414e434527293b0a20202020202020207d0a202020202020202069662028746f6b656e42616c616e6365735b73656e6465725d2e6c7428616d6f756e742929207b0a2020202020202020202020205f7468726f772827494e53554646494349454e545f544f4b454e5f42414c414e434527293b0a20202020202020207d0a0a202020202020202069662028746869732e5f64656372656d656e745573657242616c616e63652e62696e642874686973292873656e6465722c20616d6f756e742929207b0a202020202020202020202020746869732e5f696e6372656d656e745573657242616c616e63652e62696e6428746869732928726563697069656e742c20616d6f756e74290a20202020202020207d0a0a2020202020202020656d697428275472616e73666572272c205b73656e6465722c20726563697069656e742c20616d6f756e745d293b0a202020202020202072657475726e205b747275655d3b0a202020207d0a0a0a202020207472616e7366657246726f6d2873656e6465722c20726563697069656e742c20616d6f756e7429207b0a202020202020202073656e646572203d2073656e6465722e746f4c6f7765724361736528293b0a2020202020202020726563697069656e74203d20726563697069656e742e746f4c6f7765724361736528293b0a20202020202020200a202020202020202076617220746f6b656e42616c616e636573203d20746869732e62616c616e636573207c7c207b7d3b0a202020202020202076617220746f6b656e416c6c6f77616e636573203d20746869732e616c6c6f776564207c7c207b7d3b0a0a2020202020202020726571756972652873656e64657220696e20746f6b656e42616c616e6365732c20274d495353494e475f544f4b454e5f42414c414e434527293b0a202020202020202069662028746f6b656e42616c616e6365735b73656e6465725d2e6c7428616d6f756e742929207b0a2020202020202020202020205f7468726f772827494e53554646494349454e545f544f4b454e5f42414c414e434527293b0a20202020202020207d0a20202020202020200a2020202020202020726571756972652873656e646572203d3d206d73672e73656e646572207c7c202873656e64657220696e20746f6b656e416c6c6f77616e636573202626206d73672e73656e64657220696e20746f6b656e416c6c6f77616e6365735b73656e6465725d292c20224d495353494e475f414c4c4f57414e434522293b0a20202020202020206966202873656e64657220213d206d73672e73656e64657220262620746f6b656e416c6c6f77616e6365735b73656e6465725d5b6d73672e73656e6465725d2e6c7428616d6f756e742929207b0a2020202020202020202020205f7468726f772827494e53554646494349454e545f544f4b454e5f414c4c4f57414e434527293b0a20202020202020207d0a0a2020202020202020746f6b656e416c6c6f77616e6365735b73656e6465725d5b6d73672e73656e6465725d203d20746f6b656e416c6c6f77616e6365735b73656e6465725d5b6d73672e73656e6465725d2e73756228616d6f756e74293b0a0a202020202020202069662028746869732e5f64656372656d656e745573657242616c616e63652e62696e642874686973292873656e6465722c20616d6f756e742929207b0a202020202020202020202020746869732e5f696e6372656d656e745573657242616c616e63652e62696e6428746869732928726563697069656e742c20616d6f756e74290a20202020202020207d0a20202020202020200a0a2020202020202020656d697428275472616e73666572272c205b73656e6465722c20726563697069656e742c20616d6f756e745d293b0a202020202020202072657475726e205b747275655d3b0a202020207d0a0a0a20202020616c6c6f77616e6365286f776e65722c207370656e64657229207b0a20202020202020206f776e6572203d206f776e65722e746f4c6f7765724361736528293b0a20202020202020207370656e646572203d207370656e6465722e746f4c6f7765724361736528293b0a202020202020202076617220746f6b656e416c6c6f77616e636573203d20746869732e616c6c6f776564207c7c207b7d3b0a2020202020202020696620286f776e657220696e20746f6b656e416c6c6f77616e636573202626207370656e64657220696e20746f6b656e416c6c6f77616e6365735b6f776e65725d29207b0a20202020202020202020202072657475726e205b746f6b656e416c6c6f77616e6365735b6f776e65725d5b7370656e6465725d5d3b0a20202020202020207d0a202020202020202072657475726e205b6e756d6265722830295d3b0a202020207d0a202020200a20202020617070726f7665287370656e6465722c2076616c756529207b0a20202020202020207370656e646572203d207370656e6465722e746f4c6f7765724361736528293b0a202020202020202076617220746f6b656e416c6c6f77616e636573203d20746869732e616c6c6f776564207c7c207b7d3b0a2020202020202020696620282120286d73672e73656e64657220696e20746f6b656e416c6c6f77616e6365732929207b0a202020202020202020202020746f6b656e416c6c6f77616e6365735b6d73672e73656e6465725d203d207b7d3b0a20202020202020207d0a2020202020202020746f6b656e416c6c6f77616e6365735b6d73672e73656e6465725d5b7370656e6465725d203d2076616c75653b0a2020202020202020656d69742827417070726f76616c272c205b6d73672e73656e6465722c207370656e6465722c2076616c75655d293b0a202020202020202072657475726e205b747275655d3b0a202020207d0a0a202020200a202020206d696e742875736572416464726573732c20616d6f756e7429207b0a2020202020202020746869732e5f6d696e742875736572416464726573732c20616d6f756e74293b0a202020207d0a0a202020205f6d696e742875736572416464726573732c20616d6f756e7429207b0a20202020202020207573657241646472657373203d2075736572416464726573732e746f4c6f7765724361736528293b0a2020202020202020696620287573657241646472657373203d3d20616464726573732830292e6164647265737329207b0a2020202020202020202020205f7468726f77282745524332303a206d696e7420746f20746865207a65726f206164647265737327293b0a20202020202020207d0a20202020202020200a202020202020202069662028746869732e5f696e6372656d656e745573657242616c616e63652e62696e642874686973292875736572416464726573732c20616d6f756e742929207b0a202020202020202020202020746869732e746f74616c537570706c79203d20746869732e746f74616c537570706c792e61646428616d6f756e74293b0a20202020202020207d0a0a2020202020202020656d697428274d696e74272c205b75736572416464726573732c20616d6f756e745d293b0a202020207d0a0a202020200a202020206275726e2875736572416464726573732c20616d6f756e7429207b0a2020202020202020746869732e5f6275726e2875736572416464726573732c20616d6f756e74293b0a202020207d0a0a202020205f6275726e2875736572416464726573732c20616d6f756e7429207b0a20202020202020207573657241646472657373203d2075736572416464726573732e746f4c6f7765724361736528293b0a2020202020202020696620287573657241646472657373203d3d20616464726573732830292e6164647265737329207b0a2020202020202020202020205f7468726f77282745524332303a206275726e2066726f6d20746865207a65726f206164647265737327293b0a20202020202020207d0a20202020202020200a202020202020202069662028746869732e5f64656372656d656e745573657242616c616e63652e62696e642874686973292875736572416464726573732c20616d6f756e742929207b0a202020202020202020202020746869732e746f74616c537570706c79203d20746869732e746f74616c537570706c792e73756228616d6f756e74293b0a20202020202020207d0a0a2020202020202020656d697428274275726e272c205b75736572416464726573732c20616d6f756e745d293b0a202020207d0a0a0a20202020737570706f727473496e7465726661636528696e74657266616365494429207b0a2020202020202020636f6e7374205f696e7465726661636573203d20746869732e737570706f72746564496e7465726661636573207c7c207b7d3b0a202020202020202072657475726e205b0a2020202020202020202020202828696e74657266616365494420696e205f696e7465726661636573290a202020202020202020202020202020203f205f696e74657266616365735b696e7465726661636549445d0a202020202020202020202020202020203a2066616c7365290a20202020202020205d3b0a202020207d0a0a0a202020205f64656372656d656e745573657242616c616e636528686f6c646572416464726573732c20616d6f756e7429207b0a202020202020202076617220746f6b656e42616c616e636573203d20746869732e62616c616e636573207c7c207b7d3b0a0a202020202020202069662028212028686f6c6465724164647265737320696e20746f6b656e42616c616e6365732929207b0a2020202020202020202020205f7468726f772827494e53554646494349454e545f544f4b454e5f42414c414e434527293b0a20202020202020207d0a202020202020202069662028616d6f756e742e677428746f6b656e42616c616e6365735b686f6c646572416464726573735d2929207b0a2020202020202020202020205f7468726f772827494e53554646494349454e545f544f4b454e5f42414c414e434527293b0a20202020202020207d0a2020202020202020746f6b656e42616c616e6365735b686f6c646572416464726573735d203d20746f6b656e42616c616e6365735b686f6c646572416464726573735d2e73756228616d6f756e74293b0a202020202020202072657475726e20747275653b0a202020207d0a0a0a202020205f696e6372656d656e745573657242616c616e636528686f6c646572416464726573732c20616d6f756e7429207b0a202020202020202076617220746f6b656e42616c616e636573203d20746869732e62616c616e636573207c7c207b7d3b0a0a202020202020202069662028212028686f6c6465724164647265737320696e20746f6b656e42616c616e6365732929207b0a202020202020202020202020746f6b656e42616c616e6365735b686f6c646572416464726573735d203d206e756d6265722830293b0a20202020202020207d0a2020202020202020746f6b656e42616c616e6365735b686f6c646572416464726573735d203d20746f6b656e42616c616e6365735b686f6c646572416464726573735d2e61646428616d6f756e74293b0a202020202020202072657475726e20747275653b0a202020207d0a0a7d0a0a2f2f204576656e74730a4552433230546f6b656e2e70726f746f747970652e5472616e736665722e6576656e74203d20747275653b0a4552433230546f6b656e2e70726f746f747970652e5472616e736665722e696e70757473203d205b276164647265737320696e6465786564272c20276164647265737320696e6465786564272c202775696e74323536275d3b0a0a4552433230546f6b656e2e70726f746f747970652e417070726f76616c2e6576656e74203d20747275653b0a4552433230546f6b656e2e70726f746f747970652e417070726f76616c2e696e70757473203d205b276164647265737320696e6465786564272c20276164647265737320696e6465786564272c202775696e74323536275d3b0a0a4552433230546f6b656e2e70726f746f747970652e4d696e742e6576656e74203d20747275653b0a4552433230546f6b656e2e70726f746f747970652e4d696e742e696e70757473203d205b276164647265737320696e6465786564272c202775696e74323536275d3b0a0a4552433230546f6b656e2e70726f746f747970652e4275726e2e6576656e74203d20747275653b0a4552433230546f6b656e2e70726f746f747970652e4275726e2e696e70757473203d205b276164647265737320696e6465786564272c202775696e74323536275d3b0a0a2f2f204d6574686f64730a4552433230546f6b656e2e70726f746f747970652e62616c616e63654f662e76696577203d20747275653b0a4552433230546f6b656e2e70726f746f747970652e62616c616e63654f662e696e70757473203d205b2761646472657373275d3b0a4552433230546f6b656e2e70726f746f747970652e62616c616e63654f662e6f757470757473203d205b2775696e74323536275d3b0a0a4552433230546f6b656e2e70726f746f747970652e7472616e736665722e696e70757473203d205b2761646472657373272c202775696e74323536275d3b0a4552433230546f6b656e2e70726f746f747970652e7472616e736665722e6f757470757473203d205b27626f6f6c275d3b0a0a4552433230546f6b656e2e70726f746f747970652e7472616e7366657246726f6d2e696e70757473203d205b2761646472657373272c202761646472657373272c202775696e74323536275d3b0a4552433230546f6b656e2e70726f746f747970652e7472616e7366657246726f6d2e6f757470757473203d205b27626f6f6c275d3b0a0a4552433230546f6b656e2e70726f746f747970652e616c6c6f77616e63652e76696577203d20747275653b0a4552433230546f6b656e2e70726f746f747970652e616c6c6f77616e63652e696e70757473203d205b2761646472657373272c202761646472657373275d3b0a4552433230546f6b656e2e70726f746f747970652e616c6c6f77616e63652e6f757470757473203d205b2775696e74323536275d3b0a0a4552433230546f6b656e2e70726f746f747970652e617070726f76652e696e70757473203d205b2761646472657373272c202775696e74323536275d3b0a4552433230546f6b656e2e70726f746f747970652e617070726f76652e6f757470757473203d205b27626f6f6c275d3b0a0a4552433230546f6b656e2e70726f746f747970652e6d696e742e696e70757473203d205b2761646472657373272c202775696e74323536275d3b0a4552433230546f6b656e2e70726f746f747970652e6d696e742e6d6f64696669657273203d205b275f6f6e6c794f776e6572275d3b0a0a4552433230546f6b656e2e70726f746f747970652e5f6d696e742e696e7465726e616c203d20747275653b0a0a4552433230546f6b656e2e70726f746f747970652e6275726e2e696e70757473203d205b2761646472657373272c202775696e74323536275d3b0a4552433230546f6b656e2e70726f746f747970652e6275726e2e6d6f64696669657273203d205b275f6f6e6c794f776e6572275d3b0a0a4552433230546f6b656e2e70726f746f747970652e5f6275726e2e696e7465726e616c203d20747275653b0a0a4552433230546f6b656e2e70726f746f747970652e737570706f727473496e746572666163652e76696577203d20747275653b0a4552433230546f6b656e2e70726f746f747970652e737570706f727473496e746572666163652e696e70757473203d205b27627974657334275d3b0a4552433230546f6b656e2e70726f746f747970652e737570706f727473496e746572666163652e6f757470757473203d205b27626f6f6c275d3b0a0a4552433230546f6b656e2e70726f746f747970652e5f64656372656d656e745573657242616c616e63652e696e7465726e616c203d20747275653b0a0a4552433230546f6b656e2e70726f746f747970652e5f696e6372656d656e745573657242616c616e63652e696e7465726e616c203d20747275653b0a0a0a0a0a0a636c6173732050726573616c65546f6b656e20657874656e6473204552433230546f6b656e207b0a202020206e616d65203d202750726573616c6520546f6b656e273b0a2020202073796d626f6c203d2027505245273b0a0a2020202073616c655365727669636541646472657373203d2027273b0a202020200a0a20202020496e697469616c697a652873616c65536572766963654164647265737329207b7d202f2f206576656e740a0a0a20202020696e697469616c697a652873616c65536572766963654164647265737329207b0a2020202020202020746869732e73616c655365727669636541646472657373203d2073616c6553657276696365416464726573732e746f4c6f7765724361736528293b0a0a2020202020202020656d69742827496e697469616c697a65272c205b73616c6553657276696365416464726573735d293b0a202020207d0a0a0a202020206d696e7428726563697069656e742c20616d6f756e74546f4d696e7429207b0a20202020202020207265717569726528746869732e73616c65536572766963654164647265737320213d2027272c20224e4f545f494e495449414c495a454422293b0a202020202020202072657175697265286d73672e73656e646572203d3d20746869732e73616c6553657276696365416464726573732c20224f4e4c595f535441434b494e475f5345525649434522293b0a2020202020202020746869732e5f6d696e7428726563697069656e742c20616d6f756e74546f4d696e74293b0a202020207d0a0a7d0a0a2f2f204576656e74730a50726573616c65546f6b656e2e70726f746f747970652e496e697469616c697a652e6576656e74203d20747275653b0a50726573616c65546f6b656e2e70726f746f747970652e496e697469616c697a652e696e70757473203d205b276164647265737320696e6465786564275d3b0a0a2f2f204d6574686f64730a50726573616c65546f6b656e2e70726f746f747970652e696e697469616c697a652e696e70757473203d205b2761646472657373275d3b0a0a50726573616c65546f6b656e2e70726f746f747970652e6d696e742e696e70757473203d205b2761646472657373272c202775696e74323536275d3b0a0a0a0a72657475726e2050726573616c65546f6b656e3b0a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000025b5d000000000000000000000000000000000000000000000000000000000000", nonce: "8", to: "0x", value: "0", chainId: "3615", origin: "1651572560908", v: "1c62", r: "d14cfec2be03194c3f5a22a8c52034fca6a038ec7274d758214020b2d3e079d0", s: "4eeed5c1be8fe784e7601029f7300369eb5458317718434504e1d115c704d533" }, hash: "0xd242c8ffbe99682086ac93dfbdb2f83ce794ef8af244cc33bf59368aaf85b603", load: "() => {
const transactionFile = config.node.dataDir + '/transactions/' + this.hash + '.json';

if (! fs.existsSync(transactionFile)) {
console.warn(utils.getDate(), "[BLOCKCHAIN]", 'IMPORT Transaction not found', this.hash);
throw new Error('IMPORT_TRANSACTION_NOT_FOUND_DATABASE_CORRUPTED');
}

const transactionJson = fs.readFileSync(transactionFile);
transactionData = JSON.parse(transactionJson, utils.bnJsonParser);

this.data = (typeof transactionData == 'TransactionData') ? transactionData : new TransactionData(transactionData);

return true;
}"
, save: "() => {
const transactionFile = config.node.dataDir + '/transactions/' + this.hash + '.json';
fs.writeFileSync(transactionFile, JSON.stringify(this.data));
return true;
}"
, getReceipt: "() => {
return blockchain.getTransactionReceiptByHash(this.hash);
}"
, getBlock: "() => {
return blockchain.getBlockByHash(this.data.blockHash);
}"
, getBlockTransationPrev: "() => {
const block = this.getBlock();
const txPrevIdx = (this.data.transactionIndex > 0) ? (this.data.transactionIndex-1) : null;
const tx = (block && txPrevIdx in block.transactions) ? block.transactions[txPrevIdx] : null;
return tx;
}"
, getBlockTransationNext: "() => {
const block = this.getBlock();
const txNextIdx = (this.data.transactionIndex < block.transactions.length - 1) ? (this.data.transactionIndex+1) : null;
const tx = (block && txNextIdx in block.transactions) ? block.transactions[txNextIdx] : null;
return tx;
}"
, getAccountTransactionIndex: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = account.data.transactions.findIndex(transactionHash => blockchain.getTransactionByHash(transactionHash).hash == this.hash);
if (txIdx === -1) {
return null;
}
return txIdx;
}"
, getAccountTransationPrev: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = this.getAccountTransactionIndex();
if (txIdx === null) {
return null;
}
const txPrevIdx = (txIdx > 0) ? (txIdx-1) : null;
const transactionHash = (txPrevIdx in account.data.transactions) ? account.data.transactions[txPrevIdx] : null;
if (! transactionHash) {
return null;
}
return blockchain.getTransactionByHash(transactionHash);
}"
, getAccountTransationNext: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = this.getAccountTransactionIndex();
if (txIdx === null) {
return null;
}
const txNextIdx = (txIdx < account.data.transactions.length - 1) ? (txIdx+1) : null;
const transactionHash = (txNextIdx in account.data.transactions) ? account.data.transactions[txNextIdx] : null;
if (! transactionHash) {
return null;
}
return blockchain.getTransactionByHash(transactionHash);

}"
, formatRpc: "() => {
// https://github.com/ethers-io/ethers.js/blob/master/packages/abstract-provider/lib/index.d.ts

const to = (this.data.to && this.data.to.length == 42) ? this.data.to : null;

const _txData = {
blockHash: this.data.blockHash,
blockNumber: this.data.blockNumber.toNumber(),
transactionIndex: this.data.transactionIndex,
hash: this.data.hash,
from: this.data.from,
gasLimit: this.data.gasLimit.toHexString(),
gasPrice: this.data.gasPrice.toHexString(),
data: this.data.data,
nonce: this.data.nonce.toHexString(),
to,
value: this.data.value.toHexString(),
chainId: this.data.chainId.toNumber(),
//origin: this.data.origin.toHexString(),
};

return _txData;
}"
, getHashOLD: "() => {
const _txData = {
gasPrice: this.data.gasPrice,
gasLimit: this.data.gasLimit,
from: this.data.from,
to: this.data.to,
value: this.data.value,
data: this.data.data,
nonce: this.data.nonce,
};

const strvalue = JSON.stringify(utils.orderedObject(_txData));
const transactionHash = ethers.utils.id(strvalue);

return transactionHash;
}"
, getHash: "() => {
const _txData = {
gasPrice: this.data.gasPrice.toHexString(),
gasLimit: this.data.gasLimit.toHexString(),
from: this.data.from,
to: this.data.to,
value: this.data.value.toHexString(),
data: this.data.data,
nonce: this.data.nonce.toHexString(),
v: this.data.v ? ('0x' + this.data.v) : '',
r: this.data.r ? ('0x' + this.data.r) : '',
s: this.data.s ? ('0x' + this.data.s) : '',
};

const tx = new EthereumJS.Transaction(_txData);
const hashRaw = tx.hash();

const transactionHash = ethers.utils.defaultAbiCoder.decode(["uint256"], hashRaw)[0].toHexString();

return transactionHash;
}"
, execute: "(block, transactionIndex, logIndexOffset) => {
const _tx = this;

function _rejectTransaction(reason) {
// https://openethereum.github.io/Transactions-Queue
reason = reason || 'unknown';
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] REJECTED PROCESSING TRANSACTION', _tx.data.hash, ' (Reason:', reason + ')');
const success = blockchain.removeTransactionsFromMempool([_tx.hash], true, true);
if (success) {
saveMempool();
}
throw new Error(reason); // on rejete definitivement la tx (elle ne sera pas minée ... et donc aucun frais de gas ne sera compté)
// et du coup on rejete le block
}

function _checkGas(step) {
execDuration = performance.now()/1000 - execTsStart; //parseFloat(process.hrtime(execTsStart).join('.'));
gasUsed = toBn(Math.round(execDuration * cpuPricePerSecond)); // TODO: prendre en compte le storage utilisé dans le calcul du gasUsed
txCost = gasUsed.mul(_tx.data.gasPrice);

if (step == 'afterTransfer') {
//debugger;
}

if (status) {
gasUsedEffective = gasUsed;
txCostEffective = gasUsedEffective.mul(_tx.data.gasPrice);

if (gasUsed.gt(_tx.data.gasLimit)) {
status = 0;
error = new Error('INSUFFICIENT_GAS');

gasUsedEffective = _tx.data.gasLimit;
txCostEffective = txCostLimit;

// emit event
const debugError = {
message: 'INSUFFICIENT_GAS',
step,
gasPrice: _tx.data.gasPrice,
gasLimit: _tx.data.gasLimit,
gasUsed,
gasUsedEffective,
txCostLimit,
txCost,
txCostEffective,
execDuration,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, _tx.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

}
}


//const block = this;
const txLogs = [];
let vmStorage = {};

let gasUsed = toBn(0);
let gasUsedEffective = toBn(0);
let status = 1;
let error = null;
let createdContractAddress = null;
let amountTransfered = toBn(0);

const contractAddress = this.data.to;
let storageBackup;
let balanceBackup;
let contractExecuted = false;
let contractDeployed = false;

const execTsStart = performance.now()/1000; //process.hrtime(); //Date.now();
let execDuration = 0;

const txCostLimit = this.data.gasLimit.mul(this.data.gasPrice);
let txCost = toBn(0);
let txCostEffective = toBn(0);


const accountFrom = blockchain.getAccount(this.data.from);
if (! accountFrom) {
_rejectTransaction('TRANSACTION_REJECTED_EMPTY_ACCOUNT');
}
const accountFromBalance = accountFrom.data.balance;


if (accountFrom.address != mintAddress) {

// verif nonce
const expectedNonce = toBn(accountFrom.data.transactions.length); // le nonce attendu est egal au nombre de transactions passées de l'account
if (! this.data.nonce.eq(expectedNonce)) {
_rejectTransaction('TRANSACTION_REJECTED_INCORRECT_NONCE');
}

// verif balance pour gas + transfer
if (this.data.value && this.data.value.gt(0)) {
if (accountFromBalance.lt(this.data.value.add(txCostLimit))) {
// l'adresse n'a pas suffisamment de fond pour executer la transaction (selon gasLimit)
// TODO: faut-il rejeter la tx ? ou la laisser passer (en status=0) et lui vider sa balance (si 100% du gas est utilisé) ?
//return _rejectTransaction('TRANSACTION_FAILED_INSUFFICIENT_ETH_BALANCE');

_rejectTransaction('TRANSACTION_REJECTED_INSUFFICIENT_ETH_BALANCE');
}
}
}



_checkGas('init');


if (status) {
if (this.data.value && this.data.value.gt(0) && contractAddress && contractAddress.length > 2) {
// Transfer la valeur demandée (simple ETH transaction)
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(this.data, block);
txLogs.push( ...transferLogs );

// si recipient == contract => on transfer quand meme dès maintenant => TODO: a revoir (le transferEth declenche un appel à receive ... avant meme de savoir si unne methode payable doit etre appelée. Probleme si receive provoque un revert => la fonction payable ne sera pas appelée)
// 1) si methode payable appellée, on l'execute
// 2) si methode non payable => on appelle receive juste avant
// 3) si methode inconnue => revert


if (transferResult && transferReverted) {
// reverted (after transfer)
amountTransfered = this.data.value;

status = 0;
error = new Error('TRANSFER_REVERTED');

} else if (transferResult) {
// success
amountTransfered = this.data.value;

} else {
// transfer failed
status = 0;
error = new Error('TRANSFER_FAILED');
}

}

_checkGas('afterTransfer');
}

const gasUsedBeforeContractExecution = gasUsed;

if (status && this.data.data && this.data.data != '0x') {
// Traitement du champ data de la transaction (smart contract call)

if (!contractAddress || contractAddress == '0x') {
// contract creation

contractDeployed = true;

let contractExecResult;
try {
contractExecResult = this.transactionDeployContract(block);
const creationLogs = contractExecResult.logs;
vmStorage = contractExecResult.storage;

if (creationLogs.length) {
txLogs.push( ...creationLogs );
}

if (! contractExecResult.result) {
// Deploy Failed
status = 0;
error = new Error('CONTRACT_CREATION_FAILED');

// emit event
const debugError = {
message: 'CONTRACT_CREATION_FAILED',
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);

} else {
// Deploy Success
createdContractAddress = contractExecResult.result.toLowerCase();

if (this.data.value.gt(0)) {
// Transfer tx value to contract
const _txData = {
...this.data,
to: createdContractAddress,
};
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(_txData, block);
txLogs.push( ...transferLogs );

if (transferResult) {
amountTransfered = this.data.value;

} else {
status = 0;
error = new Error('CONTRACT_CREATION_TRANSFER_FAILED');
}
}

// TODO: emit event CONTRACT_CREATION_SUCCESS ?
}



} catch (e) {
//console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] Cannot execute transaction');
//throw e;
status = 0;
error = new Error('CONTRACT_CREATION_ERROR');

console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] CONTRACT_CREATION_ERROR with message', e.message)

// emit event
const debugError = {
message: 'CONTRACT_CREATION_ERROR',
error: e.message,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

_checkGas('afterContractCreation');

} else {
// contract execution

const contractAccount = blockchain.getAccount(contractAddress);

if (! contractAccount) {
status = 0;
error = new Error('CONTRACT_ACCOUNT_NOT_FOUND');

// emit event
const debugError = {
message: 'CONTRACT_ACCOUNT_NOT_FOUND',
address: contractAddress,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

if (status) {
storageBackup = JSON.stringify(contractAccount.data.storage);
balanceBackup = contractAccount.data.balance;
contractExecuted = true;

try {
const contractExecResult = this.executeTransactionContract(block);
const internalLogs = contractExecResult.logs;
vmStorage = contractExecResult.storage;

if (internalLogs.length) {
txLogs.push( ...internalLogs );
}

if (! contractExecResult.result) {
status = 0;
error = new Error('CONTRACT_EXEC_FAILED');

// emit event
const debugError = {
message: 'CONTRACT_EXEC_FAILED',
address: contractAddress,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

} catch (e) {
status = 0;
error = new Error('CONTRACT_EXEC_ERROR');

// emit event
const debugError = {
message: 'CONTRACT_EXEC_ERROR',
address: contractAddress,
error: e.message,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}
}

_checkGas('afterContractCall');
}

}

const gasUsedAfterContractExecution = gasUsed;
const gasUsedInContractExecution = gasUsedAfterContractExecution.sub(gasUsedBeforeContractExecution);
const contractExecutionCost = gasUsedInContractExecution.mul(_tx.data.gasPrice);


console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] execDuration =', (Math.round(execDuration * 1000000) / 1000000), ' seconds');



if (txCostEffective.gt(0)) {
// Gas payment
//const minerAccount = blockchain.getAccount(block.data.miner, true);
//const gasPaymentResult = minerAccount.transferFrom(accountFrom.address, txCostEffective);

const txDataGasPayment = {
from: accountFrom.address,
to: block.data.miner,
value: txCostEffective,
};
const [gasPaymentResult, gasPaymentLogs, transferReverted] = blockchain.transferEth(txDataGasPayment, block, true);
txLogs.push(...gasPaymentLogs);

if (gasPaymentResult) {
// gas payment success

} else {
// gas payment failed => reject transaction (and block)
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] Gas payment failed');
_rejectTransaction('GAS_PAYMENT_FAILED');
}

if (false) {
// DEBUG
const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();
const gasEffectivePercent = gasUsedEffective.mul(100).div(this.data.gasLimit).toNumber();
const costPercent = txCost.mul(100).div(txCostLimit).toNumber();
const costEffectivePercent = txCostEffective.mul(100).div(txCostLimit).toNumber();
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] txCostLimit =', ethers.utils.formatEther(txCostLimit));
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] txCostEffective =', ethers.utils.formatEther(txCostEffective));
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] gas % =', Math.round(gasPercent), '% =', Math.round(gasEffectivePercent), '%');
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] cost % =', Math.round(costPercent), '%', Math.round(costEffectivePercent), '%');
}


}


if (accountFrom.address != mintAddress) {
const accountFromBalanceAtEnd = blockchain.getAccountBalance(accountFrom.address);

if (accountFromBalanceAtEnd.lt(0)){
_rejectTransaction('TRANSACTION_FAILED_INSUFFICIENT_ETH_BALANCE_AT_END');
}
}


if (! status) {
// Transaction Failed

// Revert transfer
if (amountTransfered.gt(0)) {
const txDataRevert = {
...this.data,
from: this.data.to,
to: this.data.from,
value: amountTransfered,
};
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(txDataRevert, block);

// TODO: gerer le revert des internal transfers
}

txLogs.forEach(log => {
log.removed = true;
});

// Revert contract storage
if (contractExecuted) {
const contractAccount = blockchain.getAccount(contractAddress);
contractAccount.data.storage = JSON.parse(storageBackup, utils.bnJsonParser);
contractAccount.data.balance = balanceBackup;

} else if (amountTransfered) {
// on check aussi amountTransfered car le contrat peut avoir été exécuté via receive => TODO: process a revoir
const contractAccount = blockchain.getAccount(contractAddress);
if (contractAccount && contractAccount.isContract()) {
//contractAccount.data.storage = JSON.parse(storageBackup, utils.bnJsonParser);
//contractAccount.data.balance = balanceBackup;
// TODO
}

}

// Revert deployed contract
if (contractDeployed) {
// nothing to do ?
}

const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] FAILED TRANSACTION ', this.data.hash, '| gas usage percent =', gasPercent, '%', ' | Error: ', error.message);

const realExecDuration = performance.now()/1000 - execTsStart; //parseFloat(process.hrtime(execTsStart).join('.'));
const realGasUsed = toBn(Math.round(realExecDuration * cpuPricePerSecond)); // TODO: prendre en compte le storage utilisé dans le calcul du gasUsed
const realGasPercent = realGasUsed.mul(100).div(this.data.gasLimit).toNumber();
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] FAILED TRANSACTION ', this.data.hash, '| real gas usage percent =', realGasPercent, '%', ' | Error: ', error.message);

} else {
// Transaction Success

// save contract storage
Object.entries(vmStorage).forEach((entry, idx) => {
const [contractAddress, contractStorage] = entry;

const contractAccount = blockchain.getAccount(contractAddress, true);
contractAccount.data.storage = contractStorage;
contractAccount.save();
});

// show result (debug)
const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();;
console.log(utils.getDate(), "[BLOCKCHAIN]", 'SUCCECSS TRANSACTION ', this.data.hash, '| gas usage percent =', gasPercent, '%');
}


// total gas utilisé dans le block
block.data.gasUsed = block.data.gasUsed.add(gasUsed);


// assign block data on logs
txLogs.forEach((txLog, idx) => {
txLog.data.transactionIndex = transactionIndex;
txLog.data.logIndex = logIndexOffset + idx;
txLog.data.transactionHash = this.data.hash;
txLog.data.blockHash = block.hash;
txLog.data.blockNumber = block.data.number;
});


const receiptData = {
transactionHash: this.data.hash,
transactionIndex: transactionIndex,
blockNumber: block.data.number,
blockHash: block.hash,
//logsBloom: '0x0000',
contractAddress: createdContractAddress,
gasUsed,
cumulativeGasUsed: block.data.gasUsed,
effectiveGasPrice: this.data.gasPrice,
effectiveCost: this.data.gasPrice.mul(gasUsed),
logs: txLogs,
status,
}

const receipt = new Receipt(blockchain, receiptData);

return [receipt, txLogs];
}"
, executeTransactionContract: "(block) => {
let contractAddress = this.data.to;

const isNullRecipient = (! contractAddress || contractAddress.length <= 2);
if (isNullRecipient) {
throw new Error('CONTRACT_EXEC_ADDRESS_INVALID_BUG_1');
}

const contractAccount = blockchain.getAccount(contractAddress);

if (! contractAccount) {
throw new Error('CONTRACT_EXEC_ACCOUNT_NOT_FOUND');
}

// Execute existing contract method call
return blockchain.callContractMethodInVM(this.data, block);

}"
, transactionDeployContract: "(block) => {
let contractAddress = this.data.to;

let contractAccount;

if (contractAddress && contractAddress.length > 2) {
throw new Error('CONTRACT_DEPLOY_ADDRESS_INVALID_BUG_2');
}


// CONTRACT CREATION

if (! this.data.data || this.data.data.length <= 2) {
throw new Error('CONTRACT_DEPLOY_INVALID_DATA_EMPTY_CODE');
}


// build contract address
contractAddress = ethers.utils.getContractAddress(this.data).toLocaleLowerCase();

// parse contract source code & constructor parameters
const codes = ethers.utils.defaultAbiCoder.decode(["string", "string"], this.data.data);
if (! codes) {
throw new Error('CONTRACT_DEPLOY_CODE_PARSING_FAILED');
}

const sourceCode = codes[0];
const constructorParams = codes[1];
const constructorRawParams = ethers.utils.defaultAbiCoder.encode(["string"], [constructorParams]);
const codeHash = ethers.utils.id(sourceCode);
const codeAbi = null; // l'abi sera généré une fois que le contrat aura été instancié
const storageHash = ethers.utils.id('{}');

// create new account
const accountData = {
address: contractAddress,
code: sourceCode,
codeHash,
codeAbi,
storage: {},
storageHash,
};
contractAccount = blockchain.createAccount(contractAddress, accountData);


// instanciate contract + call constructor + recuperer storage
const txData = {
from: this.data.from,
to: contractAddress,
data: constructorRawParams,
value: this.data.value.toHexString(),
};
const callResult = blockchain.deployContractInVM(txData, block);
const creationLogs = callResult.logs;
const vmStorage = callResult.storage;


// emit event
const eventParams = [
contractAddress,
];
const _txData = {
...this.data,
to: blockchain.getNullAddress(),
}
const deployLog = new TxLog(blockchain, null, _txData, 'ContractCreated', eventParams, deployAbi);
creationLogs.push(deployLog);


return {
result: contractAddress,
logs: creationLogs,
storage: vmStorage,
};
}"
, timestamp: "1651572564252", status: 1, gasUsed: "22850" }, 0xdb8d72297b2c4ec5299b905c528f01396a1b01a40e07a1096ed6490d4f09f341: { data: { blockHash: "0x00995e427a45c5fef2068e47722cab6cfedf79fa867f67a4183b56a24d67a629", blockNumber: "10", transactionIndex: 0, hash: "0xdb8d72297b2c4ec5299b905c528f01396a1b01a40e07a1096ed6490d4f09f341", from: "0x0000000000000000000000000000000000000001", gasLimit: "1000000000000000000", gasPrice: "0", data: "0x", nonce: "9", to: "0xccf8ba457dcad7ee6a0361c96846a0f79744b113", value: "250000000000000000000", chainId: "3615", origin: "1651572569412", v: "", r: "", s: "" }, hash: "0xdb8d72297b2c4ec5299b905c528f01396a1b01a40e07a1096ed6490d4f09f341", load: "() => {
const transactionFile = config.node.dataDir + '/transactions/' + this.hash + '.json';

if (! fs.existsSync(transactionFile)) {
console.warn(utils.getDate(), "[BLOCKCHAIN]", 'IMPORT Transaction not found', this.hash);
throw new Error('IMPORT_TRANSACTION_NOT_FOUND_DATABASE_CORRUPTED');
}

const transactionJson = fs.readFileSync(transactionFile);
transactionData = JSON.parse(transactionJson, utils.bnJsonParser);

this.data = (typeof transactionData == 'TransactionData') ? transactionData : new TransactionData(transactionData);

return true;
}"
, save: "() => {
const transactionFile = config.node.dataDir + '/transactions/' + this.hash + '.json';
fs.writeFileSync(transactionFile, JSON.stringify(this.data));
return true;
}"
, getReceipt: "() => {
return blockchain.getTransactionReceiptByHash(this.hash);
}"
, getBlock: "() => {
return blockchain.getBlockByHash(this.data.blockHash);
}"
, getBlockTransationPrev: "() => {
const block = this.getBlock();
const txPrevIdx = (this.data.transactionIndex > 0) ? (this.data.transactionIndex-1) : null;
const tx = (block && txPrevIdx in block.transactions) ? block.transactions[txPrevIdx] : null;
return tx;
}"
, getBlockTransationNext: "() => {
const block = this.getBlock();
const txNextIdx = (this.data.transactionIndex < block.transactions.length - 1) ? (this.data.transactionIndex+1) : null;
const tx = (block && txNextIdx in block.transactions) ? block.transactions[txNextIdx] : null;
return tx;
}"
, getAccountTransactionIndex: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = account.data.transactions.findIndex(transactionHash => blockchain.getTransactionByHash(transactionHash).hash == this.hash);
if (txIdx === -1) {
return null;
}
return txIdx;
}"
, getAccountTransationPrev: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = this.getAccountTransactionIndex();
if (txIdx === null) {
return null;
}
const txPrevIdx = (txIdx > 0) ? (txIdx-1) : null;
const transactionHash = (txPrevIdx in account.data.transactions) ? account.data.transactions[txPrevIdx] : null;
if (! transactionHash) {
return null;
}
return blockchain.getTransactionByHash(transactionHash);
}"
, getAccountTransationNext: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = this.getAccountTransactionIndex();
if (txIdx === null) {
return null;
}
const txNextIdx = (txIdx < account.data.transactions.length - 1) ? (txIdx+1) : null;
const transactionHash = (txNextIdx in account.data.transactions) ? account.data.transactions[txNextIdx] : null;
if (! transactionHash) {
return null;
}
return blockchain.getTransactionByHash(transactionHash);

}"
, formatRpc: "() => {
// https://github.com/ethers-io/ethers.js/blob/master/packages/abstract-provider/lib/index.d.ts

const to = (this.data.to && this.data.to.length == 42) ? this.data.to : null;

const _txData = {
blockHash: this.data.blockHash,
blockNumber: this.data.blockNumber.toNumber(),
transactionIndex: this.data.transactionIndex,
hash: this.data.hash,
from: this.data.from,
gasLimit: this.data.gasLimit.toHexString(),
gasPrice: this.data.gasPrice.toHexString(),
data: this.data.data,
nonce: this.data.nonce.toHexString(),
to,
value: this.data.value.toHexString(),
chainId: this.data.chainId.toNumber(),
//origin: this.data.origin.toHexString(),
};

return _txData;
}"
, getHashOLD: "() => {
const _txData = {
gasPrice: this.data.gasPrice,
gasLimit: this.data.gasLimit,
from: this.data.from,
to: this.data.to,
value: this.data.value,
data: this.data.data,
nonce: this.data.nonce,
};

const strvalue = JSON.stringify(utils.orderedObject(_txData));
const transactionHash = ethers.utils.id(strvalue);

return transactionHash;
}"
, getHash: "() => {
const _txData = {
gasPrice: this.data.gasPrice.toHexString(),
gasLimit: this.data.gasLimit.toHexString(),
from: this.data.from,
to: this.data.to,
value: this.data.value.toHexString(),
data: this.data.data,
nonce: this.data.nonce.toHexString(),
v: this.data.v ? ('0x' + this.data.v) : '',
r: this.data.r ? ('0x' + this.data.r) : '',
s: this.data.s ? ('0x' + this.data.s) : '',
};

const tx = new EthereumJS.Transaction(_txData);
const hashRaw = tx.hash();

const transactionHash = ethers.utils.defaultAbiCoder.decode(["uint256"], hashRaw)[0].toHexString();

return transactionHash;
}"
, execute: "(block, transactionIndex, logIndexOffset) => {
const _tx = this;

function _rejectTransaction(reason) {
// https://openethereum.github.io/Transactions-Queue
reason = reason || 'unknown';
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] REJECTED PROCESSING TRANSACTION', _tx.data.hash, ' (Reason:', reason + ')');
const success = blockchain.removeTransactionsFromMempool([_tx.hash], true, true);
if (success) {
saveMempool();
}
throw new Error(reason); // on rejete definitivement la tx (elle ne sera pas minée ... et donc aucun frais de gas ne sera compté)
// et du coup on rejete le block
}

function _checkGas(step) {
execDuration = performance.now()/1000 - execTsStart; //parseFloat(process.hrtime(execTsStart).join('.'));
gasUsed = toBn(Math.round(execDuration * cpuPricePerSecond)); // TODO: prendre en compte le storage utilisé dans le calcul du gasUsed
txCost = gasUsed.mul(_tx.data.gasPrice);

if (step == 'afterTransfer') {
//debugger;
}

if (status) {
gasUsedEffective = gasUsed;
txCostEffective = gasUsedEffective.mul(_tx.data.gasPrice);

if (gasUsed.gt(_tx.data.gasLimit)) {
status = 0;
error = new Error('INSUFFICIENT_GAS');

gasUsedEffective = _tx.data.gasLimit;
txCostEffective = txCostLimit;

// emit event
const debugError = {
message: 'INSUFFICIENT_GAS',
step,
gasPrice: _tx.data.gasPrice,
gasLimit: _tx.data.gasLimit,
gasUsed,
gasUsedEffective,
txCostLimit,
txCost,
txCostEffective,
execDuration,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, _tx.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

}
}


//const block = this;
const txLogs = [];
let vmStorage = {};

let gasUsed = toBn(0);
let gasUsedEffective = toBn(0);
let status = 1;
let error = null;
let createdContractAddress = null;
let amountTransfered = toBn(0);

const contractAddress = this.data.to;
let storageBackup;
let balanceBackup;
let contractExecuted = false;
let contractDeployed = false;

const execTsStart = performance.now()/1000; //process.hrtime(); //Date.now();
let execDuration = 0;

const txCostLimit = this.data.gasLimit.mul(this.data.gasPrice);
let txCost = toBn(0);
let txCostEffective = toBn(0);


const accountFrom = blockchain.getAccount(this.data.from);
if (! accountFrom) {
_rejectTransaction('TRANSACTION_REJECTED_EMPTY_ACCOUNT');
}
const accountFromBalance = accountFrom.data.balance;


if (accountFrom.address != mintAddress) {

// verif nonce
const expectedNonce = toBn(accountFrom.data.transactions.length); // le nonce attendu est egal au nombre de transactions passées de l'account
if (! this.data.nonce.eq(expectedNonce)) {
_rejectTransaction('TRANSACTION_REJECTED_INCORRECT_NONCE');
}

// verif balance pour gas + transfer
if (this.data.value && this.data.value.gt(0)) {
if (accountFromBalance.lt(this.data.value.add(txCostLimit))) {
// l'adresse n'a pas suffisamment de fond pour executer la transaction (selon gasLimit)
// TODO: faut-il rejeter la tx ? ou la laisser passer (en status=0) et lui vider sa balance (si 100% du gas est utilisé) ?
//return _rejectTransaction('TRANSACTION_FAILED_INSUFFICIENT_ETH_BALANCE');

_rejectTransaction('TRANSACTION_REJECTED_INSUFFICIENT_ETH_BALANCE');
}
}
}



_checkGas('init');


if (status) {
if (this.data.value && this.data.value.gt(0) && contractAddress && contractAddress.length > 2) {
// Transfer la valeur demandée (simple ETH transaction)
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(this.data, block);
txLogs.push( ...transferLogs );

// si recipient == contract => on transfer quand meme dès maintenant => TODO: a revoir (le transferEth declenche un appel à receive ... avant meme de savoir si unne methode payable doit etre appelée. Probleme si receive provoque un revert => la fonction payable ne sera pas appelée)
// 1) si methode payable appellée, on l'execute
// 2) si methode non payable => on appelle receive juste avant
// 3) si methode inconnue => revert


if (transferResult && transferReverted) {
// reverted (after transfer)
amountTransfered = this.data.value;

status = 0;
error = new Error('TRANSFER_REVERTED');

} else if (transferResult) {
// success
amountTransfered = this.data.value;

} else {
// transfer failed
status = 0;
error = new Error('TRANSFER_FAILED');
}

}

_checkGas('afterTransfer');
}

const gasUsedBeforeContractExecution = gasUsed;

if (status && this.data.data && this.data.data != '0x') {
// Traitement du champ data de la transaction (smart contract call)

if (!contractAddress || contractAddress == '0x') {
// contract creation

contractDeployed = true;

let contractExecResult;
try {
contractExecResult = this.transactionDeployContract(block);
const creationLogs = contractExecResult.logs;
vmStorage = contractExecResult.storage;

if (creationLogs.length) {
txLogs.push( ...creationLogs );
}

if (! contractExecResult.result) {
// Deploy Failed
status = 0;
error = new Error('CONTRACT_CREATION_FAILED');

// emit event
const debugError = {
message: 'CONTRACT_CREATION_FAILED',
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);

} else {
// Deploy Success
createdContractAddress = contractExecResult.result.toLowerCase();

if (this.data.value.gt(0)) {
// Transfer tx value to contract
const _txData = {
...this.data,
to: createdContractAddress,
};
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(_txData, block);
txLogs.push( ...transferLogs );

if (transferResult) {
amountTransfered = this.data.value;

} else {
status = 0;
error = new Error('CONTRACT_CREATION_TRANSFER_FAILED');
}
}

// TODO: emit event CONTRACT_CREATION_SUCCESS ?
}



} catch (e) {
//console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] Cannot execute transaction');
//throw e;
status = 0;
error = new Error('CONTRACT_CREATION_ERROR');

console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] CONTRACT_CREATION_ERROR with message', e.message)

// emit event
const debugError = {
message: 'CONTRACT_CREATION_ERROR',
error: e.message,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

_checkGas('afterContractCreation');

} else {
// contract execution

const contractAccount = blockchain.getAccount(contractAddress);

if (! contractAccount) {
status = 0;
error = new Error('CONTRACT_ACCOUNT_NOT_FOUND');

// emit event
const debugError = {
message: 'CONTRACT_ACCOUNT_NOT_FOUND',
address: contractAddress,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

if (status) {
storageBackup = JSON.stringify(contractAccount.data.storage);
balanceBackup = contractAccount.data.balance;
contractExecuted = true;

try {
const contractExecResult = this.executeTransactionContract(block);
const internalLogs = contractExecResult.logs;
vmStorage = contractExecResult.storage;

if (internalLogs.length) {
txLogs.push( ...internalLogs );
}

if (! contractExecResult.result) {
status = 0;
error = new Error('CONTRACT_EXEC_FAILED');

// emit event
const debugError = {
message: 'CONTRACT_EXEC_FAILED',
address: contractAddress,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

} catch (e) {
status = 0;
error = new Error('CONTRACT_EXEC_ERROR');

// emit event
const debugError = {
message: 'CONTRACT_EXEC_ERROR',
address: contractAddress,
error: e.message,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}
}

_checkGas('afterContractCall');
}

}

const gasUsedAfterContractExecution = gasUsed;
const gasUsedInContractExecution = gasUsedAfterContractExecution.sub(gasUsedBeforeContractExecution);
const contractExecutionCost = gasUsedInContractExecution.mul(_tx.data.gasPrice);


console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] execDuration =', (Math.round(execDuration * 1000000) / 1000000), ' seconds');



if (txCostEffective.gt(0)) {
// Gas payment
//const minerAccount = blockchain.getAccount(block.data.miner, true);
//const gasPaymentResult = minerAccount.transferFrom(accountFrom.address, txCostEffective);

const txDataGasPayment = {
from: accountFrom.address,
to: block.data.miner,
value: txCostEffective,
};
const [gasPaymentResult, gasPaymentLogs, transferReverted] = blockchain.transferEth(txDataGasPayment, block, true);
txLogs.push(...gasPaymentLogs);

if (gasPaymentResult) {
// gas payment success

} else {
// gas payment failed => reject transaction (and block)
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] Gas payment failed');
_rejectTransaction('GAS_PAYMENT_FAILED');
}

if (false) {
// DEBUG
const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();
const gasEffectivePercent = gasUsedEffective.mul(100).div(this.data.gasLimit).toNumber();
const costPercent = txCost.mul(100).div(txCostLimit).toNumber();
const costEffectivePercent = txCostEffective.mul(100).div(txCostLimit).toNumber();
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] txCostLimit =', ethers.utils.formatEther(txCostLimit));
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] txCostEffective =', ethers.utils.formatEther(txCostEffective));
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] gas % =', Math.round(gasPercent), '% =', Math.round(gasEffectivePercent), '%');
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] cost % =', Math.round(costPercent), '%', Math.round(costEffectivePercent), '%');
}


}


if (accountFrom.address != mintAddress) {
const accountFromBalanceAtEnd = blockchain.getAccountBalance(accountFrom.address);

if (accountFromBalanceAtEnd.lt(0)){
_rejectTransaction('TRANSACTION_FAILED_INSUFFICIENT_ETH_BALANCE_AT_END');
}
}


if (! status) {
// Transaction Failed

// Revert transfer
if (amountTransfered.gt(0)) {
const txDataRevert = {
...this.data,
from: this.data.to,
to: this.data.from,
value: amountTransfered,
};
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(txDataRevert, block);

// TODO: gerer le revert des internal transfers
}

txLogs.forEach(log => {
log.removed = true;
});

// Revert contract storage
if (contractExecuted) {
const contractAccount = blockchain.getAccount(contractAddress);
contractAccount.data.storage = JSON.parse(storageBackup, utils.bnJsonParser);
contractAccount.data.balance = balanceBackup;

} else if (amountTransfered) {
// on check aussi amountTransfered car le contrat peut avoir été exécuté via receive => TODO: process a revoir
const contractAccount = blockchain.getAccount(contractAddress);
if (contractAccount && contractAccount.isContract()) {
//contractAccount.data.storage = JSON.parse(storageBackup, utils.bnJsonParser);
//contractAccount.data.balance = balanceBackup;
// TODO
}

}

// Revert deployed contract
if (contractDeployed) {
// nothing to do ?
}

const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] FAILED TRANSACTION ', this.data.hash, '| gas usage percent =', gasPercent, '%', ' | Error: ', error.message);

const realExecDuration = performance.now()/1000 - execTsStart; //parseFloat(process.hrtime(execTsStart).join('.'));
const realGasUsed = toBn(Math.round(realExecDuration * cpuPricePerSecond)); // TODO: prendre en compte le storage utilisé dans le calcul du gasUsed
const realGasPercent = realGasUsed.mul(100).div(this.data.gasLimit).toNumber();
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] FAILED TRANSACTION ', this.data.hash, '| real gas usage percent =', realGasPercent, '%', ' | Error: ', error.message);

} else {
// Transaction Success

// save contract storage
Object.entries(vmStorage).forEach((entry, idx) => {
const [contractAddress, contractStorage] = entry;

const contractAccount = blockchain.getAccount(contractAddress, true);
contractAccount.data.storage = contractStorage;
contractAccount.save();
});

// show result (debug)
const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();;
console.log(utils.getDate(), "[BLOCKCHAIN]", 'SUCCECSS TRANSACTION ', this.data.hash, '| gas usage percent =', gasPercent, '%');
}


// total gas utilisé dans le block
block.data.gasUsed = block.data.gasUsed.add(gasUsed);


// assign block data on logs
txLogs.forEach((txLog, idx) => {
txLog.data.transactionIndex = transactionIndex;
txLog.data.logIndex = logIndexOffset + idx;
txLog.data.transactionHash = this.data.hash;
txLog.data.blockHash = block.hash;
txLog.data.blockNumber = block.data.number;
});


const receiptData = {
transactionHash: this.data.hash,
transactionIndex: transactionIndex,
blockNumber: block.data.number,
blockHash: block.hash,
//logsBloom: '0x0000',
contractAddress: createdContractAddress,
gasUsed,
cumulativeGasUsed: block.data.gasUsed,
effectiveGasPrice: this.data.gasPrice,
effectiveCost: this.data.gasPrice.mul(gasUsed),
logs: txLogs,
status,
}

const receipt = new Receipt(blockchain, receiptData);

return [receipt, txLogs];
}"
, executeTransactionContract: "(block) => {
let contractAddress = this.data.to;

const isNullRecipient = (! contractAddress || contractAddress.length <= 2);
if (isNullRecipient) {
throw new Error('CONTRACT_EXEC_ADDRESS_INVALID_BUG_1');
}

const contractAccount = blockchain.getAccount(contractAddress);

if (! contractAccount) {
throw new Error('CONTRACT_EXEC_ACCOUNT_NOT_FOUND');
}

// Execute existing contract method call
return blockchain.callContractMethodInVM(this.data, block);

}"
, transactionDeployContract: "(block) => {
let contractAddress = this.data.to;

let contractAccount;

if (contractAddress && contractAddress.length > 2) {
throw new Error('CONTRACT_DEPLOY_ADDRESS_INVALID_BUG_2');
}


// CONTRACT CREATION

if (! this.data.data || this.data.data.length <= 2) {
throw new Error('CONTRACT_DEPLOY_INVALID_DATA_EMPTY_CODE');
}


// build contract address
contractAddress = ethers.utils.getContractAddress(this.data).toLocaleLowerCase();

// parse contract source code & constructor parameters
const codes = ethers.utils.defaultAbiCoder.decode(["string", "string"], this.data.data);
if (! codes) {
throw new Error('CONTRACT_DEPLOY_CODE_PARSING_FAILED');
}

const sourceCode = codes[0];
const constructorParams = codes[1];
const constructorRawParams = ethers.utils.defaultAbiCoder.encode(["string"], [constructorParams]);
const codeHash = ethers.utils.id(sourceCode);
const codeAbi = null; // l'abi sera généré une fois que le contrat aura été instancié
const storageHash = ethers.utils.id('{}');

// create new account
const accountData = {
address: contractAddress,
code: sourceCode,
codeHash,
codeAbi,
storage: {},
storageHash,
};
contractAccount = blockchain.createAccount(contractAddress, accountData);


// instanciate contract + call constructor + recuperer storage
const txData = {
from: this.data.from,
to: contractAddress,
data: constructorRawParams,
value: this.data.value.toHexString(),
};
const callResult = blockchain.deployContractInVM(txData, block);
const creationLogs = callResult.logs;
const vmStorage = callResult.storage;


// emit event
const eventParams = [
contractAddress,
];
const _txData = {
...this.data,
to: blockchain.getNullAddress(),
}
const deployLog = new TxLog(blockchain, null, _txData, 'ContractCreated', eventParams, deployAbi);
creationLogs.push(deployLog);


return {
result: contractAddress,
logs: creationLogs,
storage: vmStorage,
};
}"
, timestamp: "1651572572819", status: 1, gasUsed: "2371" }, 0x15a7d8bf0c3ef7b0d877bd57e0410f1d73fe4bc4e3425a606f3dab8b44370584: { data: { blockHash: "0x00995e427a45c5fef2068e47722cab6cfedf79fa867f67a4183b56a24d67a629", blockNumber: "10", transactionIndex: 1, hash: "0x15a7d8bf0c3ef7b0d877bd57e0410f1d73fe4bc4e3425a606f3dab8b44370584", from: "0x131a190fa22cb867abf58193bf9e7640e9fce682", gasLimit: "773200", gasPrice: "1000000000", data: "0x0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000116000000000000000000000000000000000000000000000000000000000000010e42f2a0a4e616d653a2053616c65536572766963650a56657273696f6e3a20302e302e330a2a2f0a0a0a66756e6374696f6e2049455243323028746f6b656e4164647265737329207b0a2020202072657475726e206164647265737328746f6b656e41646472657373292e676574436f6e747261637428293b0a7d0a0a0a636c6173732053616c6553657276696365207b2020200a202020206e616d65203d202750726573616c652053657276696365273b0a202020200a20202020746f6b656e41646472657373203d2027307830303030303030303030303030303030303030303030303030303030303030303030303030303030273b0a202020206d6178537570706c79203d206e756d62657228273027293b0a2020202070726573616c65446174655374617274203d206e756d6265722830293b0a2020202070726573616c654461746553746f70203d206e756d6265722830293b0a2020202070726573616c655072696365203d206e756d6265722830293b0a2020202070726573616c654d6178537570706c79203d206e756d6265722830293b0a2020202070726573616c655768696c74654c6973744d65726b6c65526f6f74203d206e756d62657228273027293b0a202020207075626c69635072696365203d206e756d62657228273027293b0a0a0a20202020496e697469616c697a6528746f6b656e416464726573732c206d6178537570706c792c2070726573616c654461746553746172742c2070726573616c654461746553746f702c2070726573616c6550726963652c2070726573616c654d6178537570706c792c2070726573616c655768696c74654c6973744d65726b6c65526f6f742c207075626c6963507269636529207b7d202f2f206576656e740a2020202042757950726573616c652873656e6465722c2076616c75652c20616d6f756e744f757429207b7d202f2f206576656e740a202020204275792873656e6465722c2076616c75652c20616d6f756e744f757429207b7d202f2f206576656e740a20202020576974686472617753616c65732873656e6465722c20616d6f756e7429207b7d202f2f206576656e740a0a0a20202020696e697469616c697a6528746f6b656e416464726573732c206d6178537570706c792c2070726573616c654461746553746172742c2070726573616c654461746553746f702c2070726573616c6550726963652c2070726573616c654d6178537570706c792c2070726573616c655768696c74654c6973744d65726b6c65526f6f742c207075626c6963507269636529207b0a2020202020202020726571756972652821206d6178537570706c792e6c742870726573616c654d6178537570706c79292c2220494e56414c49445f535550504c5922293b0a2020202020202020746869732e746f6b656e41646472657373203d20746f6b656e416464726573733b0a2020202020202020746869732e6d6178537570706c79203d206d6178537570706c793b0a2020202020202020746869732e70726573616c65446174655374617274203d2070726573616c654461746553746172743b0a2020202020202020746869732e70726573616c654461746553746f70203d2070726573616c654461746553746f703b0a2020202020202020746869732e70726573616c655072696365203d2070726573616c6550726963653b0a2020202020202020746869732e70726573616c654d6178537570706c79203d2070726573616c654d6178537570706c793b0a2020202020202020746869732e70726573616c655768696c74654c6973744d65726b6c65526f6f74203d2070726573616c655768696c74654c6973744d65726b6c65526f6f743b0a2020202020202020746869732e7075626c69635072696365203d207075626c696350726963653b0a0a2020202020202020656d69742827496e697469616c697a65272c205b746f6b656e416464726573732c206d6178537570706c792c2070726573616c654461746553746172742c2070726573616c654461746553746f702c2070726573616c6550726963652c2070726573616c654d6178537570706c792c2070726573616c655768696c74654c6973744d65726b6c65526f6f742c207075626c696350726963655d293b0a202020207d0a0a0a2020202062757950726573616c652829207b0a20202020202020207265717569726528746869732e746f6b656e4164647265737320213d2027272c20224e4f545f494e495449414c495a454422293b0a202020202020202072657175697265282120626c6f636b2e74696d657374616d702e6c7428746869732e70726573616c65446174655374617274292c202250524553414c455f4e4f545f5354415254454422293b0a202020202020202072657175697265282120626c6f636b2e74696d657374616d702e677428746869732e70726573616c654461746553746f70292c202250524553414c455f46494e495348454422293b0a20202020202020200a2020202020202020636f6e7374205b616d6f756e744f75742c5d203d20746869732e5f676574416d6f756e744f7574286d73672e76616c75652c20746869732e70726573616c655072696365293b0a20202020202020207265717569726528616d6f756e744f75742e67742830292c2022494e56414c49445f414d4f554e545f4f555422293b0a0a2020202020202020636f6e737420746f74616c537570706c79203d20746f6b656e4f75742e746f74616c537570706c793b0a202020202020202072657175697265282120746f74616c537570706c792e61646428616d6f756e744f7574292e677428746869732e70726573616c654d6178537570706c79292c202750524553414c455f535550504c595f455843454544454427293b0a202020202020202072657175697265282120746f74616c537570706c792e61646428616d6f756e744f7574292e677428746869732e6d6178537570706c79292c2027544f54414c5f535550504c595f455843454544454427293b0a0a2020202020202020746869732e70726573616c654d6178537570706c79203d20746869732e70726573616c654d6178537570706c792e61646428616d6f756e744f7574293b0a202020202020202049455243323028746869732e746f6b656e41646472657373292e6d696e74286d73672e73656e6465722c20616d6f756e744f7574293b0a0a2020202020202020656d6974282742757950726573616c65272c205b6d73672e73656e6465722c206d73672e76616c75652c20616d6f756e744f75745d293b0a202020207d0a0a0a202020206275792829207b0a20202020202020207265717569726528746869732e746f6b656e4164647265737320213d2027272c20224e4f545f494e495449414c495a454422293b0a20202020202020207265717569726528626c6f636b2e74696d657374616d702e677428746869732e70726573616c654461746553746f70292c202250524553414c455f4e4f545f46494e495348454422293b0a20202020202020200a2020202020202020636f6e7374205b616d6f756e744f75742c5d203d20746869732e5f676574416d6f756e744f7574286d73672e76616c75652c20746869732e7075626c69635072696365293b0a20202020202020207265717569726528616d6f756e744f75742e67742830292c2022494e56414c49445f414d4f554e545f4f555422293b0a0a2020202020202020636f6e737420746f74616c537570706c79203d20746f6b656e4f75742e746f74616c537570706c793b0a202020202020202072657175697265282120746f74616c537570706c792e61646428616d6f756e744f7574292e677428746869732e6d6178537570706c79292c2027544f54414c5f535550504c595f455843454544454427293b0a0a202020202020202049455243323028746869732e746f6b656e41646472657373292e6d696e74286d73672e73656e6465722c20616d6f756e744f7574293b0a0a2020202020202020656d69742827427579272c205b6d73672e73656e6465722c206d73672e76616c75652c20616d6f756e744f75745d293b0a202020207d0a0a0a202020205f676574416d6f756e744f757428616d6f756e74496e2c20707269636529207b0a2020202020202020636f6e737420746f6b656e4f7574203d2049455243323028746869732e746f6b656e41646472657373293b0a2020202020202020636f6e737420646563696d616c734f7574203d20746f6b656e4f75742e646563696d616c733b0a2020202020202020636f6e737420616d6f756e744f7574203d20616d6f756e74496e2e646976287072696365292e6d756c283130202a2a20646563696d616c734f7574293b0a202020202020202072657475726e205b616d6f756e744f75745d3b0a202020207d0a0a0a20202020776974686472617753616c657328616d6f756e7429207b0a202020202020202072657175697265286d73672e73656e646572203d3d20746869732e6f776e65722c20274f4e4c595f4f574e455227293b0a202020202020202072657175697265282120616d6f756e742e677428616464726573732874686973292e62616c616e636529293b0a2020202020202020616464726573732874686973292e7472616e73666572286d73672e73656e6465722c20616d6f756e74293b0a0a2020202020202020656d69742827576974686472617753616c6573272c205b6d73672e73656e6465722c20616d6f756e745d293b0a202020207d0a0a7d0a0a2f2f204576656e74730a53616c65536572766963652e70726f746f747970652e496e697469616c697a652e6576656e74203d20747275653b0a53616c65536572766963652e70726f746f747970652e496e697469616c697a652e696e70757473203d205b276164647265737320696e6465786564272c202775696e74323536272c202775696e74323536272c202775696e74323536272c202775696e74323536272c202775696e74323536272c202761646472657373272c202775696e74323536275d3b0a0a53616c65536572766963652e70726f746f747970652e42757950726573616c652e6576656e74203d20747275653b0a53616c65536572766963652e70726f746f747970652e42757950726573616c652e696e70757473203d205b276164647265737320696e6465786564272c202775696e74323536272c202775696e74323536275d0a0a53616c65536572766963652e70726f746f747970652e4275792e6576656e74203d20747275653b0a53616c65536572766963652e70726f746f747970652e4275792e696e70757473203d205b276164647265737320696e6465786564272c202775696e74323536272c202775696e74323536275d0a0a53616c65536572766963652e70726f746f747970652e576974686472617753616c65732e6576656e74203d20747275653b0a53616c65536572766963652e70726f746f747970652e576974686472617753616c65732e696e70757473203d205b276164647265737320696e6465786564272c202775696e74323536275d0a0a2f2f204d6574686f64730a53616c65536572766963652e70726f746f747970652e696e697469616c697a652e696e70757473203d205b2761646472657373272c202775696e74323536272c202775696e74323536272c202775696e74323536272c202775696e74323536272c202775696e74323536272c202761646472657373272c202775696e74323536275d3b0a0a53616c65536572766963652e70726f746f747970652e62757950726573616c652e70617961626c65203d20747275653b0a0a53616c65536572766963652e70726f746f747970652e776974686472617753616c65732e696e70757473203d205b2775696e74323536275d3b0a0a0a72657475726e2053616c65536572766963653b0a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000025b5d000000000000000000000000000000000000000000000000000000000000", nonce: "9", to: "0x", value: "0", chainId: "3615", origin: "1651572566256", v: "1c61", r: "59929cee33cb5db34421e6445095e373f112098a28fa78e51411c6d9c5e0877a", s: "676b05cfcdcbcdc8f5ac9f2da3eb8834e505bd338f505afc9246ed44e9c09a93" }, hash: "0x15a7d8bf0c3ef7b0d877bd57e0410f1d73fe4bc4e3425a606f3dab8b44370584", load: "() => {
const transactionFile = config.node.dataDir + '/transactions/' + this.hash + '.json';

if (! fs.existsSync(transactionFile)) {
console.warn(utils.getDate(), "[BLOCKCHAIN]", 'IMPORT Transaction not found', this.hash);
throw new Error('IMPORT_TRANSACTION_NOT_FOUND_DATABASE_CORRUPTED');
}

const transactionJson = fs.readFileSync(transactionFile);
transactionData = JSON.parse(transactionJson, utils.bnJsonParser);

this.data = (typeof transactionData == 'TransactionData') ? transactionData : new TransactionData(transactionData);

return true;
}"
, save: "() => {
const transactionFile = config.node.dataDir + '/transactions/' + this.hash + '.json';
fs.writeFileSync(transactionFile, JSON.stringify(this.data));
return true;
}"
, getReceipt: "() => {
return blockchain.getTransactionReceiptByHash(this.hash);
}"
, getBlock: "() => {
return blockchain.getBlockByHash(this.data.blockHash);
}"
, getBlockTransationPrev: "() => {
const block = this.getBlock();
const txPrevIdx = (this.data.transactionIndex > 0) ? (this.data.transactionIndex-1) : null;
const tx = (block && txPrevIdx in block.transactions) ? block.transactions[txPrevIdx] : null;
return tx;
}"
, getBlockTransationNext: "() => {
const block = this.getBlock();
const txNextIdx = (this.data.transactionIndex < block.transactions.length - 1) ? (this.data.transactionIndex+1) : null;
const tx = (block && txNextIdx in block.transactions) ? block.transactions[txNextIdx] : null;
return tx;
}"
, getAccountTransactionIndex: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = account.data.transactions.findIndex(transactionHash => blockchain.getTransactionByHash(transactionHash).hash == this.hash);
if (txIdx === -1) {
return null;
}
return txIdx;
}"
, getAccountTransationPrev: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = this.getAccountTransactionIndex();
if (txIdx === null) {
return null;
}
const txPrevIdx = (txIdx > 0) ? (txIdx-1) : null;
const transactionHash = (txPrevIdx in account.data.transactions) ? account.data.transactions[txPrevIdx] : null;
if (! transactionHash) {
return null;
}
return blockchain.getTransactionByHash(transactionHash);
}"
, getAccountTransationNext: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = this.getAccountTransactionIndex();
if (txIdx === null) {
return null;
}
const txNextIdx = (txIdx < account.data.transactions.length - 1) ? (txIdx+1) : null;
const transactionHash = (txNextIdx in account.data.transactions) ? account.data.transactions[txNextIdx] : null;
if (! transactionHash) {
return null;
}
return blockchain.getTransactionByHash(transactionHash);

}"
, formatRpc: "() => {
// https://github.com/ethers-io/ethers.js/blob/master/packages/abstract-provider/lib/index.d.ts

const to = (this.data.to && this.data.to.length == 42) ? this.data.to : null;

const _txData = {
blockHash: this.data.blockHash,
blockNumber: this.data.blockNumber.toNumber(),
transactionIndex: this.data.transactionIndex,
hash: this.data.hash,
from: this.data.from,
gasLimit: this.data.gasLimit.toHexString(),
gasPrice: this.data.gasPrice.toHexString(),
data: this.data.data,
nonce: this.data.nonce.toHexString(),
to,
value: this.data.value.toHexString(),
chainId: this.data.chainId.toNumber(),
//origin: this.data.origin.toHexString(),
};

return _txData;
}"
, getHashOLD: "() => {
const _txData = {
gasPrice: this.data.gasPrice,
gasLimit: this.data.gasLimit,
from: this.data.from,
to: this.data.to,
value: this.data.value,
data: this.data.data,
nonce: this.data.nonce,
};

const strvalue = JSON.stringify(utils.orderedObject(_txData));
const transactionHash = ethers.utils.id(strvalue);

return transactionHash;
}"
, getHash: "() => {
const _txData = {
gasPrice: this.data.gasPrice.toHexString(),
gasLimit: this.data.gasLimit.toHexString(),
from: this.data.from,
to: this.data.to,
value: this.data.value.toHexString(),
data: this.data.data,
nonce: this.data.nonce.toHexString(),
v: this.data.v ? ('0x' + this.data.v) : '',
r: this.data.r ? ('0x' + this.data.r) : '',
s: this.data.s ? ('0x' + this.data.s) : '',
};

const tx = new EthereumJS.Transaction(_txData);
const hashRaw = tx.hash();

const transactionHash = ethers.utils.defaultAbiCoder.decode(["uint256"], hashRaw)[0].toHexString();

return transactionHash;
}"
, execute: "(block, transactionIndex, logIndexOffset) => {
const _tx = this;

function _rejectTransaction(reason) {
// https://openethereum.github.io/Transactions-Queue
reason = reason || 'unknown';
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] REJECTED PROCESSING TRANSACTION', _tx.data.hash, ' (Reason:', reason + ')');
const success = blockchain.removeTransactionsFromMempool([_tx.hash], true, true);
if (success) {
saveMempool();
}
throw new Error(reason); // on rejete definitivement la tx (elle ne sera pas minée ... et donc aucun frais de gas ne sera compté)
// et du coup on rejete le block
}

function _checkGas(step) {
execDuration = performance.now()/1000 - execTsStart; //parseFloat(process.hrtime(execTsStart).join('.'));
gasUsed = toBn(Math.round(execDuration * cpuPricePerSecond)); // TODO: prendre en compte le storage utilisé dans le calcul du gasUsed
txCost = gasUsed.mul(_tx.data.gasPrice);

if (step == 'afterTransfer') {
//debugger;
}

if (status) {
gasUsedEffective = gasUsed;
txCostEffective = gasUsedEffective.mul(_tx.data.gasPrice);

if (gasUsed.gt(_tx.data.gasLimit)) {
status = 0;
error = new Error('INSUFFICIENT_GAS');

gasUsedEffective = _tx.data.gasLimit;
txCostEffective = txCostLimit;

// emit event
const debugError = {
message: 'INSUFFICIENT_GAS',
step,
gasPrice: _tx.data.gasPrice,
gasLimit: _tx.data.gasLimit,
gasUsed,
gasUsedEffective,
txCostLimit,
txCost,
txCostEffective,
execDuration,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, _tx.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

}
}


//const block = this;
const txLogs = [];
let vmStorage = {};

let gasUsed = toBn(0);
let gasUsedEffective = toBn(0);
let status = 1;
let error = null;
let createdContractAddress = null;
let amountTransfered = toBn(0);

const contractAddress = this.data.to;
let storageBackup;
let balanceBackup;
let contractExecuted = false;
let contractDeployed = false;

const execTsStart = performance.now()/1000; //process.hrtime(); //Date.now();
let execDuration = 0;

const txCostLimit = this.data.gasLimit.mul(this.data.gasPrice);
let txCost = toBn(0);
let txCostEffective = toBn(0);


const accountFrom = blockchain.getAccount(this.data.from);
if (! accountFrom) {
_rejectTransaction('TRANSACTION_REJECTED_EMPTY_ACCOUNT');
}
const accountFromBalance = accountFrom.data.balance;


if (accountFrom.address != mintAddress) {

// verif nonce
const expectedNonce = toBn(accountFrom.data.transactions.length); // le nonce attendu est egal au nombre de transactions passées de l'account
if (! this.data.nonce.eq(expectedNonce)) {
_rejectTransaction('TRANSACTION_REJECTED_INCORRECT_NONCE');
}

// verif balance pour gas + transfer
if (this.data.value && this.data.value.gt(0)) {
if (accountFromBalance.lt(this.data.value.add(txCostLimit))) {
// l'adresse n'a pas suffisamment de fond pour executer la transaction (selon gasLimit)
// TODO: faut-il rejeter la tx ? ou la laisser passer (en status=0) et lui vider sa balance (si 100% du gas est utilisé) ?
//return _rejectTransaction('TRANSACTION_FAILED_INSUFFICIENT_ETH_BALANCE');

_rejectTransaction('TRANSACTION_REJECTED_INSUFFICIENT_ETH_BALANCE');
}
}
}



_checkGas('init');


if (status) {
if (this.data.value && this.data.value.gt(0) && contractAddress && contractAddress.length > 2) {
// Transfer la valeur demandée (simple ETH transaction)
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(this.data, block);
txLogs.push( ...transferLogs );

// si recipient == contract => on transfer quand meme dès maintenant => TODO: a revoir (le transferEth declenche un appel à receive ... avant meme de savoir si unne methode payable doit etre appelée. Probleme si receive provoque un revert => la fonction payable ne sera pas appelée)
// 1) si methode payable appellée, on l'execute
// 2) si methode non payable => on appelle receive juste avant
// 3) si methode inconnue => revert


if (transferResult && transferReverted) {
// reverted (after transfer)
amountTransfered = this.data.value;

status = 0;
error = new Error('TRANSFER_REVERTED');

} else if (transferResult) {
// success
amountTransfered = this.data.value;

} else {
// transfer failed
status = 0;
error = new Error('TRANSFER_FAILED');
}

}

_checkGas('afterTransfer');
}

const gasUsedBeforeContractExecution = gasUsed;

if (status && this.data.data && this.data.data != '0x') {
// Traitement du champ data de la transaction (smart contract call)

if (!contractAddress || contractAddress == '0x') {
// contract creation

contractDeployed = true;

let contractExecResult;
try {
contractExecResult = this.transactionDeployContract(block);
const creationLogs = contractExecResult.logs;
vmStorage = contractExecResult.storage;

if (creationLogs.length) {
txLogs.push( ...creationLogs );
}

if (! contractExecResult.result) {
// Deploy Failed
status = 0;
error = new Error('CONTRACT_CREATION_FAILED');

// emit event
const debugError = {
message: 'CONTRACT_CREATION_FAILED',
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);

} else {
// Deploy Success
createdContractAddress = contractExecResult.result.toLowerCase();

if (this.data.value.gt(0)) {
// Transfer tx value to contract
const _txData = {
...this.data,
to: createdContractAddress,
};
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(_txData, block);
txLogs.push( ...transferLogs );

if (transferResult) {
amountTransfered = this.data.value;

} else {
status = 0;
error = new Error('CONTRACT_CREATION_TRANSFER_FAILED');
}
}

// TODO: emit event CONTRACT_CREATION_SUCCESS ?
}



} catch (e) {
//console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] Cannot execute transaction');
//throw e;
status = 0;
error = new Error('CONTRACT_CREATION_ERROR');

console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] CONTRACT_CREATION_ERROR with message', e.message)

// emit event
const debugError = {
message: 'CONTRACT_CREATION_ERROR',
error: e.message,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

_checkGas('afterContractCreation');

} else {
// contract execution

const contractAccount = blockchain.getAccount(contractAddress);

if (! contractAccount) {
status = 0;
error = new Error('CONTRACT_ACCOUNT_NOT_FOUND');

// emit event
const debugError = {
message: 'CONTRACT_ACCOUNT_NOT_FOUND',
address: contractAddress,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

if (status) {
storageBackup = JSON.stringify(contractAccount.data.storage);
balanceBackup = contractAccount.data.balance;
contractExecuted = true;

try {
const contractExecResult = this.executeTransactionContract(block);
const internalLogs = contractExecResult.logs;
vmStorage = contractExecResult.storage;

if (internalLogs.length) {
txLogs.push( ...internalLogs );
}

if (! contractExecResult.result) {
status = 0;
error = new Error('CONTRACT_EXEC_FAILED');

// emit event
const debugError = {
message: 'CONTRACT_EXEC_FAILED',
address: contractAddress,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

} catch (e) {
status = 0;
error = new Error('CONTRACT_EXEC_ERROR');

// emit event
const debugError = {
message: 'CONTRACT_EXEC_ERROR',
address: contractAddress,
error: e.message,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}
}

_checkGas('afterContractCall');
}

}

const gasUsedAfterContractExecution = gasUsed;
const gasUsedInContractExecution = gasUsedAfterContractExecution.sub(gasUsedBeforeContractExecution);
const contractExecutionCost = gasUsedInContractExecution.mul(_tx.data.gasPrice);


console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] execDuration =', (Math.round(execDuration * 1000000) / 1000000), ' seconds');



if (txCostEffective.gt(0)) {
// Gas payment
//const minerAccount = blockchain.getAccount(block.data.miner, true);
//const gasPaymentResult = minerAccount.transferFrom(accountFrom.address, txCostEffective);

const txDataGasPayment = {
from: accountFrom.address,
to: block.data.miner,
value: txCostEffective,
};
const [gasPaymentResult, gasPaymentLogs, transferReverted] = blockchain.transferEth(txDataGasPayment, block, true);
txLogs.push(...gasPaymentLogs);

if (gasPaymentResult) {
// gas payment success

} else {
// gas payment failed => reject transaction (and block)
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] Gas payment failed');
_rejectTransaction('GAS_PAYMENT_FAILED');
}

if (false) {
// DEBUG
const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();
const gasEffectivePercent = gasUsedEffective.mul(100).div(this.data.gasLimit).toNumber();
const costPercent = txCost.mul(100).div(txCostLimit).toNumber();
const costEffectivePercent = txCostEffective.mul(100).div(txCostLimit).toNumber();
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] txCostLimit =', ethers.utils.formatEther(txCostLimit));
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] txCostEffective =', ethers.utils.formatEther(txCostEffective));
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] gas % =', Math.round(gasPercent), '% =', Math.round(gasEffectivePercent), '%');
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] cost % =', Math.round(costPercent), '%', Math.round(costEffectivePercent), '%');
}


}


if (accountFrom.address != mintAddress) {
const accountFromBalanceAtEnd = blockchain.getAccountBalance(accountFrom.address);

if (accountFromBalanceAtEnd.lt(0)){
_rejectTransaction('TRANSACTION_FAILED_INSUFFICIENT_ETH_BALANCE_AT_END');
}
}


if (! status) {
// Transaction Failed

// Revert transfer
if (amountTransfered.gt(0)) {
const txDataRevert = {
...this.data,
from: this.data.to,
to: this.data.from,
value: amountTransfered,
};
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(txDataRevert, block);

// TODO: gerer le revert des internal transfers
}

txLogs.forEach(log => {
log.removed = true;
});

// Revert contract storage
if (contractExecuted) {
const contractAccount = blockchain.getAccount(contractAddress);
contractAccount.data.storage = JSON.parse(storageBackup, utils.bnJsonParser);
contractAccount.data.balance = balanceBackup;

} else if (amountTransfered) {
// on check aussi amountTransfered car le contrat peut avoir été exécuté via receive => TODO: process a revoir
const contractAccount = blockchain.getAccount(contractAddress);
if (contractAccount && contractAccount.isContract()) {
//contractAccount.data.storage = JSON.parse(storageBackup, utils.bnJsonParser);
//contractAccount.data.balance = balanceBackup;
// TODO
}

}

// Revert deployed contract
if (contractDeployed) {
// nothing to do ?
}

const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] FAILED TRANSACTION ', this.data.hash, '| gas usage percent =', gasPercent, '%', ' | Error: ', error.message);

const realExecDuration = performance.now()/1000 - execTsStart; //parseFloat(process.hrtime(execTsStart).join('.'));
const realGasUsed = toBn(Math.round(realExecDuration * cpuPricePerSecond)); // TODO: prendre en compte le storage utilisé dans le calcul du gasUsed
const realGasPercent = realGasUsed.mul(100).div(this.data.gasLimit).toNumber();
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] FAILED TRANSACTION ', this.data.hash, '| real gas usage percent =', realGasPercent, '%', ' | Error: ', error.message);

} else {
// Transaction Success

// save contract storage
Object.entries(vmStorage).forEach((entry, idx) => {
const [contractAddress, contractStorage] = entry;

const contractAccount = blockchain.getAccount(contractAddress, true);
contractAccount.data.storage = contractStorage;
contractAccount.save();
});

// show result (debug)
const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();;
console.log(utils.getDate(), "[BLOCKCHAIN]", 'SUCCECSS TRANSACTION ', this.data.hash, '| gas usage percent =', gasPercent, '%');
}


// total gas utilisé dans le block
block.data.gasUsed = block.data.gasUsed.add(gasUsed);


// assign block data on logs
txLogs.forEach((txLog, idx) => {
txLog.data.transactionIndex = transactionIndex;
txLog.data.logIndex = logIndexOffset + idx;
txLog.data.transactionHash = this.data.hash;
txLog.data.blockHash = block.hash;
txLog.data.blockNumber = block.data.number;
});


const receiptData = {
transactionHash: this.data.hash,
transactionIndex: transactionIndex,
blockNumber: block.data.number,
blockHash: block.hash,
//logsBloom: '0x0000',
contractAddress: createdContractAddress,
gasUsed,
cumulativeGasUsed: block.data.gasUsed,
effectiveGasPrice: this.data.gasPrice,
effectiveCost: this.data.gasPrice.mul(gasUsed),
logs: txLogs,
status,
}

const receipt = new Receipt(blockchain, receiptData);

return [receipt, txLogs];
}"
, executeTransactionContract: "(block) => {
let contractAddress = this.data.to;

const isNullRecipient = (! contractAddress || contractAddress.length <= 2);
if (isNullRecipient) {
throw new Error('CONTRACT_EXEC_ADDRESS_INVALID_BUG_1');
}

const contractAccount = blockchain.getAccount(contractAddress);

if (! contractAccount) {
throw new Error('CONTRACT_EXEC_ACCOUNT_NOT_FOUND');
}

// Execute existing contract method call
return blockchain.callContractMethodInVM(this.data, block);

}"
, transactionDeployContract: "(block) => {
let contractAddress = this.data.to;

let contractAccount;

if (contractAddress && contractAddress.length > 2) {
throw new Error('CONTRACT_DEPLOY_ADDRESS_INVALID_BUG_2');
}


// CONTRACT CREATION

if (! this.data.data || this.data.data.length <= 2) {
throw new Error('CONTRACT_DEPLOY_INVALID_DATA_EMPTY_CODE');
}


// build contract address
contractAddress = ethers.utils.getContractAddress(this.data).toLocaleLowerCase();

// parse contract source code & constructor parameters
const codes = ethers.utils.defaultAbiCoder.decode(["string", "string"], this.data.data);
if (! codes) {
throw new Error('CONTRACT_DEPLOY_CODE_PARSING_FAILED');
}

const sourceCode = codes[0];
const constructorParams = codes[1];
const constructorRawParams = ethers.utils.defaultAbiCoder.encode(["string"], [constructorParams]);
const codeHash = ethers.utils.id(sourceCode);
const codeAbi = null; // l'abi sera généré une fois que le contrat aura été instancié
const storageHash = ethers.utils.id('{}');

// create new account
const accountData = {
address: contractAddress,
code: sourceCode,
codeHash,
codeAbi,
storage: {},
storageHash,
};
contractAccount = blockchain.createAccount(contractAddress, accountData);


// instanciate contract + call constructor + recuperer storage
const txData = {
from: this.data.from,
to: contractAddress,
data: constructorRawParams,
value: this.data.value.toHexString(),
};
const callResult = blockchain.deployContractInVM(txData, block);
const creationLogs = callResult.logs;
const vmStorage = callResult.storage;


// emit event
const eventParams = [
contractAddress,
];
const _txData = {
...this.data,
to: blockchain.getNullAddress(),
}
const deployLog = new TxLog(blockchain, null, _txData, 'ContractCreated', eventParams, deployAbi);
creationLogs.push(deployLog);


return {
result: contractAddress,
logs: creationLogs,
storage: vmStorage,
};
}"
, timestamp: "1651572572819", status: 1, gasUsed: "15563" }, 0x41b01d5cf0ac57ead682903fef63b69ac9dbfb9cddf90ad94423ecac16fd6a44: { data: { blockHash: "0x00a5f0a942a24b2965f4c5c46e76099952b74c10e6ec7160cecad9d9290f80cb", blockNumber: "11", transactionIndex: 0, hash: "0x41b01d5cf0ac57ead682903fef63b69ac9dbfb9cddf90ad94423ecac16fd6a44", from: "0x0000000000000000000000000000000000000001", gasLimit: "1000000000000000000", gasPrice: "0", data: "0x", nonce: "10", to: "0xccf8ba457dcad7ee6a0361c96846a0f79744b113", value: "250000000000000000000", chainId: "3615", origin: "1651572577821", v: "", r: "", s: "" }, hash: "0x41b01d5cf0ac57ead682903fef63b69ac9dbfb9cddf90ad94423ecac16fd6a44", load: "() => {
const transactionFile = config.node.dataDir + '/transactions/' + this.hash + '.json';

if (! fs.existsSync(transactionFile)) {
console.warn(utils.getDate(), "[BLOCKCHAIN]", 'IMPORT Transaction not found', this.hash);
throw new Error('IMPORT_TRANSACTION_NOT_FOUND_DATABASE_CORRUPTED');
}

const transactionJson = fs.readFileSync(transactionFile);
transactionData = JSON.parse(transactionJson, utils.bnJsonParser);

this.data = (typeof transactionData == 'TransactionData') ? transactionData : new TransactionData(transactionData);

return true;
}"
, save: "() => {
const transactionFile = config.node.dataDir + '/transactions/' + this.hash + '.json';
fs.writeFileSync(transactionFile, JSON.stringify(this.data));
return true;
}"
, getReceipt: "() => {
return blockchain.getTransactionReceiptByHash(this.hash);
}"
, getBlock: "() => {
return blockchain.getBlockByHash(this.data.blockHash);
}"
, getBlockTransationPrev: "() => {
const block = this.getBlock();
const txPrevIdx = (this.data.transactionIndex > 0) ? (this.data.transactionIndex-1) : null;
const tx = (block && txPrevIdx in block.transactions) ? block.transactions[txPrevIdx] : null;
return tx;
}"
, getBlockTransationNext: "() => {
const block = this.getBlock();
const txNextIdx = (this.data.transactionIndex < block.transactions.length - 1) ? (this.data.transactionIndex+1) : null;
const tx = (block && txNextIdx in block.transactions) ? block.transactions[txNextIdx] : null;
return tx;
}"
, getAccountTransactionIndex: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = account.data.transactions.findIndex(transactionHash => blockchain.getTransactionByHash(transactionHash).hash == this.hash);
if (txIdx === -1) {
return null;
}
return txIdx;
}"
, getAccountTransationPrev: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = this.getAccountTransactionIndex();
if (txIdx === null) {
return null;
}
const txPrevIdx = (txIdx > 0) ? (txIdx-1) : null;
const transactionHash = (txPrevIdx in account.data.transactions) ? account.data.transactions[txPrevIdx] : null;
if (! transactionHash) {
return null;
}
return blockchain.getTransactionByHash(transactionHash);
}"
, getAccountTransationNext: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = this.getAccountTransactionIndex();
if (txIdx === null) {
return null;
}
const txNextIdx = (txIdx < account.data.transactions.length - 1) ? (txIdx+1) : null;
const transactionHash = (txNextIdx in account.data.transactions) ? account.data.transactions[txNextIdx] : null;
if (! transactionHash) {
return null;
}
return blockchain.getTransactionByHash(transactionHash);

}"
, formatRpc: "() => {
// https://github.com/ethers-io/ethers.js/blob/master/packages/abstract-provider/lib/index.d.ts

const to = (this.data.to && this.data.to.length == 42) ? this.data.to : null;

const _txData = {
blockHash: this.data.blockHash,
blockNumber: this.data.blockNumber.toNumber(),
transactionIndex: this.data.transactionIndex,
hash: this.data.hash,
from: this.data.from,
gasLimit: this.data.gasLimit.toHexString(),
gasPrice: this.data.gasPrice.toHexString(),
data: this.data.data,
nonce: this.data.nonce.toHexString(),
to,
value: this.data.value.toHexString(),
chainId: this.data.chainId.toNumber(),
//origin: this.data.origin.toHexString(),
};

return _txData;
}"
, getHashOLD: "() => {
const _txData = {
gasPrice: this.data.gasPrice,
gasLimit: this.data.gasLimit,
from: this.data.from,
to: this.data.to,
value: this.data.value,
data: this.data.data,
nonce: this.data.nonce,
};

const strvalue = JSON.stringify(utils.orderedObject(_txData));
const transactionHash = ethers.utils.id(strvalue);

return transactionHash;
}"
, getHash: "() => {
const _txData = {
gasPrice: this.data.gasPrice.toHexString(),
gasLimit: this.data.gasLimit.toHexString(),
from: this.data.from,
to: this.data.to,
value: this.data.value.toHexString(),
data: this.data.data,
nonce: this.data.nonce.toHexString(),
v: this.data.v ? ('0x' + this.data.v) : '',
r: this.data.r ? ('0x' + this.data.r) : '',
s: this.data.s ? ('0x' + this.data.s) : '',
};

const tx = new EthereumJS.Transaction(_txData);
const hashRaw = tx.hash();

const transactionHash = ethers.utils.defaultAbiCoder.decode(["uint256"], hashRaw)[0].toHexString();

return transactionHash;
}"
, execute: "(block, transactionIndex, logIndexOffset) => {
const _tx = this;

function _rejectTransaction(reason) {
// https://openethereum.github.io/Transactions-Queue
reason = reason || 'unknown';
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] REJECTED PROCESSING TRANSACTION', _tx.data.hash, ' (Reason:', reason + ')');
const success = blockchain.removeTransactionsFromMempool([_tx.hash], true, true);
if (success) {
saveMempool();
}
throw new Error(reason); // on rejete definitivement la tx (elle ne sera pas minée ... et donc aucun frais de gas ne sera compté)
// et du coup on rejete le block
}

function _checkGas(step) {
execDuration = performance.now()/1000 - execTsStart; //parseFloat(process.hrtime(execTsStart).join('.'));
gasUsed = toBn(Math.round(execDuration * cpuPricePerSecond)); // TODO: prendre en compte le storage utilisé dans le calcul du gasUsed
txCost = gasUsed.mul(_tx.data.gasPrice);

if (step == 'afterTransfer') {
//debugger;
}

if (status) {
gasUsedEffective = gasUsed;
txCostEffective = gasUsedEffective.mul(_tx.data.gasPrice);

if (gasUsed.gt(_tx.data.gasLimit)) {
status = 0;
error = new Error('INSUFFICIENT_GAS');

gasUsedEffective = _tx.data.gasLimit;
txCostEffective = txCostLimit;

// emit event
const debugError = {
message: 'INSUFFICIENT_GAS',
step,
gasPrice: _tx.data.gasPrice,
gasLimit: _tx.data.gasLimit,
gasUsed,
gasUsedEffective,
txCostLimit,
txCost,
txCostEffective,
execDuration,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, _tx.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

}
}


//const block = this;
const txLogs = [];
let vmStorage = {};

let gasUsed = toBn(0);
let gasUsedEffective = toBn(0);
let status = 1;
let error = null;
let createdContractAddress = null;
let amountTransfered = toBn(0);

const contractAddress = this.data.to;
let storageBackup;
let balanceBackup;
let contractExecuted = false;
let contractDeployed = false;

const execTsStart = performance.now()/1000; //process.hrtime(); //Date.now();
let execDuration = 0;

const txCostLimit = this.data.gasLimit.mul(this.data.gasPrice);
let txCost = toBn(0);
let txCostEffective = toBn(0);


const accountFrom = blockchain.getAccount(this.data.from);
if (! accountFrom) {
_rejectTransaction('TRANSACTION_REJECTED_EMPTY_ACCOUNT');
}
const accountFromBalance = accountFrom.data.balance;


if (accountFrom.address != mintAddress) {

// verif nonce
const expectedNonce = toBn(accountFrom.data.transactions.length); // le nonce attendu est egal au nombre de transactions passées de l'account
if (! this.data.nonce.eq(expectedNonce)) {
_rejectTransaction('TRANSACTION_REJECTED_INCORRECT_NONCE');
}

// verif balance pour gas + transfer
if (this.data.value && this.data.value.gt(0)) {
if (accountFromBalance.lt(this.data.value.add(txCostLimit))) {
// l'adresse n'a pas suffisamment de fond pour executer la transaction (selon gasLimit)
// TODO: faut-il rejeter la tx ? ou la laisser passer (en status=0) et lui vider sa balance (si 100% du gas est utilisé) ?
//return _rejectTransaction('TRANSACTION_FAILED_INSUFFICIENT_ETH_BALANCE');

_rejectTransaction('TRANSACTION_REJECTED_INSUFFICIENT_ETH_BALANCE');
}
}
}



_checkGas('init');


if (status) {
if (this.data.value && this.data.value.gt(0) && contractAddress && contractAddress.length > 2) {
// Transfer la valeur demandée (simple ETH transaction)
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(this.data, block);
txLogs.push( ...transferLogs );

// si recipient == contract => on transfer quand meme dès maintenant => TODO: a revoir (le transferEth declenche un appel à receive ... avant meme de savoir si unne methode payable doit etre appelée. Probleme si receive provoque un revert => la fonction payable ne sera pas appelée)
// 1) si methode payable appellée, on l'execute
// 2) si methode non payable => on appelle receive juste avant
// 3) si methode inconnue => revert


if (transferResult && transferReverted) {
// reverted (after transfer)
amountTransfered = this.data.value;

status = 0;
error = new Error('TRANSFER_REVERTED');

} else if (transferResult) {
// success
amountTransfered = this.data.value;

} else {
// transfer failed
status = 0;
error = new Error('TRANSFER_FAILED');
}

}

_checkGas('afterTransfer');
}

const gasUsedBeforeContractExecution = gasUsed;

if (status && this.data.data && this.data.data != '0x') {
// Traitement du champ data de la transaction (smart contract call)

if (!contractAddress || contractAddress == '0x') {
// contract creation

contractDeployed = true;

let contractExecResult;
try {
contractExecResult = this.transactionDeployContract(block);
const creationLogs = contractExecResult.logs;
vmStorage = contractExecResult.storage;

if (creationLogs.length) {
txLogs.push( ...creationLogs );
}

if (! contractExecResult.result) {
// Deploy Failed
status = 0;
error = new Error('CONTRACT_CREATION_FAILED');

// emit event
const debugError = {
message: 'CONTRACT_CREATION_FAILED',
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);

} else {
// Deploy Success
createdContractAddress = contractExecResult.result.toLowerCase();

if (this.data.value.gt(0)) {
// Transfer tx value to contract
const _txData = {
...this.data,
to: createdContractAddress,
};
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(_txData, block);
txLogs.push( ...transferLogs );

if (transferResult) {
amountTransfered = this.data.value;

} else {
status = 0;
error = new Error('CONTRACT_CREATION_TRANSFER_FAILED');
}
}

// TODO: emit event CONTRACT_CREATION_SUCCESS ?
}



} catch (e) {
//console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] Cannot execute transaction');
//throw e;
status = 0;
error = new Error('CONTRACT_CREATION_ERROR');

console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] CONTRACT_CREATION_ERROR with message', e.message)

// emit event
const debugError = {
message: 'CONTRACT_CREATION_ERROR',
error: e.message,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

_checkGas('afterContractCreation');

} else {
// contract execution

const contractAccount = blockchain.getAccount(contractAddress);

if (! contractAccount) {
status = 0;
error = new Error('CONTRACT_ACCOUNT_NOT_FOUND');

// emit event
const debugError = {
message: 'CONTRACT_ACCOUNT_NOT_FOUND',
address: contractAddress,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

if (status) {
storageBackup = JSON.stringify(contractAccount.data.storage);
balanceBackup = contractAccount.data.balance;
contractExecuted = true;

try {
const contractExecResult = this.executeTransactionContract(block);
const internalLogs = contractExecResult.logs;
vmStorage = contractExecResult.storage;

if (internalLogs.length) {
txLogs.push( ...internalLogs );
}

if (! contractExecResult.result) {
status = 0;
error = new Error('CONTRACT_EXEC_FAILED');

// emit event
const debugError = {
message: 'CONTRACT_EXEC_FAILED',
address: contractAddress,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

} catch (e) {
status = 0;
error = new Error('CONTRACT_EXEC_ERROR');

// emit event
const debugError = {
message: 'CONTRACT_EXEC_ERROR',
address: contractAddress,
error: e.message,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}
}

_checkGas('afterContractCall');
}

}

const gasUsedAfterContractExecution = gasUsed;
const gasUsedInContractExecution = gasUsedAfterContractExecution.sub(gasUsedBeforeContractExecution);
const contractExecutionCost = gasUsedInContractExecution.mul(_tx.data.gasPrice);


console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] execDuration =', (Math.round(execDuration * 1000000) / 1000000), ' seconds');



if (txCostEffective.gt(0)) {
// Gas payment
//const minerAccount = blockchain.getAccount(block.data.miner, true);
//const gasPaymentResult = minerAccount.transferFrom(accountFrom.address, txCostEffective);

const txDataGasPayment = {
from: accountFrom.address,
to: block.data.miner,
value: txCostEffective,
};
const [gasPaymentResult, gasPaymentLogs, transferReverted] = blockchain.transferEth(txDataGasPayment, block, true);
txLogs.push(...gasPaymentLogs);

if (gasPaymentResult) {
// gas payment success

} else {
// gas payment failed => reject transaction (and block)
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] Gas payment failed');
_rejectTransaction('GAS_PAYMENT_FAILED');
}

if (false) {
// DEBUG
const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();
const gasEffectivePercent = gasUsedEffective.mul(100).div(this.data.gasLimit).toNumber();
const costPercent = txCost.mul(100).div(txCostLimit).toNumber();
const costEffectivePercent = txCostEffective.mul(100).div(txCostLimit).toNumber();
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] txCostLimit =', ethers.utils.formatEther(txCostLimit));
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] txCostEffective =', ethers.utils.formatEther(txCostEffective));
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] gas % =', Math.round(gasPercent), '% =', Math.round(gasEffectivePercent), '%');
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] cost % =', Math.round(costPercent), '%', Math.round(costEffectivePercent), '%');
}


}


if (accountFrom.address != mintAddress) {
const accountFromBalanceAtEnd = blockchain.getAccountBalance(accountFrom.address);

if (accountFromBalanceAtEnd.lt(0)){
_rejectTransaction('TRANSACTION_FAILED_INSUFFICIENT_ETH_BALANCE_AT_END');
}
}


if (! status) {
// Transaction Failed

// Revert transfer
if (amountTransfered.gt(0)) {
const txDataRevert = {
...this.data,
from: this.data.to,
to: this.data.from,
value: amountTransfered,
};
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(txDataRevert, block);

// TODO: gerer le revert des internal transfers
}

txLogs.forEach(log => {
log.removed = true;
});

// Revert contract storage
if (contractExecuted) {
const contractAccount = blockchain.getAccount(contractAddress);
contractAccount.data.storage = JSON.parse(storageBackup, utils.bnJsonParser);
contractAccount.data.balance = balanceBackup;

} else if (amountTransfered) {
// on check aussi amountTransfered car le contrat peut avoir été exécuté via receive => TODO: process a revoir
const contractAccount = blockchain.getAccount(contractAddress);
if (contractAccount && contractAccount.isContract()) {
//contractAccount.data.storage = JSON.parse(storageBackup, utils.bnJsonParser);
//contractAccount.data.balance = balanceBackup;
// TODO
}

}

// Revert deployed contract
if (contractDeployed) {
// nothing to do ?
}

const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] FAILED TRANSACTION ', this.data.hash, '| gas usage percent =', gasPercent, '%', ' | Error: ', error.message);

const realExecDuration = performance.now()/1000 - execTsStart; //parseFloat(process.hrtime(execTsStart).join('.'));
const realGasUsed = toBn(Math.round(realExecDuration * cpuPricePerSecond)); // TODO: prendre en compte le storage utilisé dans le calcul du gasUsed
const realGasPercent = realGasUsed.mul(100).div(this.data.gasLimit).toNumber();
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] FAILED TRANSACTION ', this.data.hash, '| real gas usage percent =', realGasPercent, '%', ' | Error: ', error.message);

} else {
// Transaction Success

// save contract storage
Object.entries(vmStorage).forEach((entry, idx) => {
const [contractAddress, contractStorage] = entry;

const contractAccount = blockchain.getAccount(contractAddress, true);
contractAccount.data.storage = contractStorage;
contractAccount.save();
});

// show result (debug)
const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();;
console.log(utils.getDate(), "[BLOCKCHAIN]", 'SUCCECSS TRANSACTION ', this.data.hash, '| gas usage percent =', gasPercent, '%');
}


// total gas utilisé dans le block
block.data.gasUsed = block.data.gasUsed.add(gasUsed);


// assign block data on logs
txLogs.forEach((txLog, idx) => {
txLog.data.transactionIndex = transactionIndex;
txLog.data.logIndex = logIndexOffset + idx;
txLog.data.transactionHash = this.data.hash;
txLog.data.blockHash = block.hash;
txLog.data.blockNumber = block.data.number;
});


const receiptData = {
transactionHash: this.data.hash,
transactionIndex: transactionIndex,
blockNumber: block.data.number,
blockHash: block.hash,
//logsBloom: '0x0000',
contractAddress: createdContractAddress,
gasUsed,
cumulativeGasUsed: block.data.gasUsed,
effectiveGasPrice: this.data.gasPrice,
effectiveCost: this.data.gasPrice.mul(gasUsed),
logs: txLogs,
status,
}

const receipt = new Receipt(blockchain, receiptData);

return [receipt, txLogs];
}"
, executeTransactionContract: "(block) => {
let contractAddress = this.data.to;

const isNullRecipient = (! contractAddress || contractAddress.length <= 2);
if (isNullRecipient) {
throw new Error('CONTRACT_EXEC_ADDRESS_INVALID_BUG_1');
}

const contractAccount = blockchain.getAccount(contractAddress);

if (! contractAccount) {
throw new Error('CONTRACT_EXEC_ACCOUNT_NOT_FOUND');
}

// Execute existing contract method call
return blockchain.callContractMethodInVM(this.data, block);

}"
, transactionDeployContract: "(block) => {
let contractAddress = this.data.to;

let contractAccount;

if (contractAddress && contractAddress.length > 2) {
throw new Error('CONTRACT_DEPLOY_ADDRESS_INVALID_BUG_2');
}


// CONTRACT CREATION

if (! this.data.data || this.data.data.length <= 2) {
throw new Error('CONTRACT_DEPLOY_INVALID_DATA_EMPTY_CODE');
}


// build contract address
contractAddress = ethers.utils.getContractAddress(this.data).toLocaleLowerCase();

// parse contract source code & constructor parameters
const codes = ethers.utils.defaultAbiCoder.decode(["string", "string"], this.data.data);
if (! codes) {
throw new Error('CONTRACT_DEPLOY_CODE_PARSING_FAILED');
}

const sourceCode = codes[0];
const constructorParams = codes[1];
const constructorRawParams = ethers.utils.defaultAbiCoder.encode(["string"], [constructorParams]);
const codeHash = ethers.utils.id(sourceCode);
const codeAbi = null; // l'abi sera généré une fois que le contrat aura été instancié
const storageHash = ethers.utils.id('{}');

// create new account
const accountData = {
address: contractAddress,
code: sourceCode,
codeHash,
codeAbi,
storage: {},
storageHash,
};
contractAccount = blockchain.createAccount(contractAddress, accountData);


// instanciate contract + call constructor + recuperer storage
const txData = {
from: this.data.from,
to: contractAddress,
data: constructorRawParams,
value: this.data.value.toHexString(),
};
const callResult = blockchain.deployContractInVM(txData, block);
const creationLogs = callResult.logs;
const vmStorage = callResult.storage;


// emit event
const eventParams = [
contractAddress,
];
const _txData = {
...this.data,
to: blockchain.getNullAddress(),
}
const deployLog = new TxLog(blockchain, null, _txData, 'ContractCreated', eventParams, deployAbi);
creationLogs.push(deployLog);


return {
result: contractAddress,
logs: creationLogs,
storage: vmStorage,
};
}"
, timestamp: "1651572579441", status: 1, gasUsed: "2427" }, 0xd35ada50d67fe104087551ffcc9b05db08902ced472e9d26982fce6f59b66cf1: { data: { blockHash: "0x00a5f0a942a24b2965f4c5c46e76099952b74c10e6ec7160cecad9d9290f80cb", blockNumber: "11", transactionIndex: 1, hash: "0xd35ada50d67fe104087551ffcc9b05db08902ced472e9d26982fce6f59b66cf1", from: "0x131a190fa22cb867abf58193bf9e7640e9fce682", gasLimit: "987250", gasPrice: "1000000000", data: "0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000219d2f2a0a4e616d653a20424153494320546f6b656e0a56657273696f6e3a20302e302e330a2a2f0a0a0a636c617373204f776e61626c65207b0a202020206f776e6572203d2027307830303030303030303030303030303030303030303030303030303030303030303030303030303030273b0a0a202020204f776e6572736869705472616e73666572726564286f6c644f776e65722c206e65774f776e657229207b7d202f2f206576656e740a202020200a0a20202020636f6e7374727563746f722829207b0a2020202020202020746869732e6f776e6572203d206d73672e73656e6465723b0a202020207d0a0a202020206765744f776e65722829207b0a202020202020202072657475726e205b746869732e6f776e65725d3b0a202020207d0a202020200a202020207472616e736665724f776e657273686970286e65774f776e657229207b0a202020202020202072657175697265286e65774f776e657220213d20616464726573732830292c20224f776e61626c653a206e6577206f776e657220697320746865207a65726f206164647265737322293b0a2020202020202020746869732e5f7472616e736665724f776e657273686970286e65774f776e6572293b0a202020207d0a0a202020205f7472616e736665724f776e657273686970286e65774f776e657229207b0a20202020202020206e65774f776e6572203d206e65774f776e65722e746f4c6f7765724361736528293b0a2020202020202020636f6e7374206f6c644f776e6572203d20746869732e6f776e65723b0a2020202020202020746869732e6f776e6572203d206e65774f776e65723b0a2020202020202020656d697428274f776e6572736869705472616e73666572726564272c205b6f6c644f776e65722c206e65774f776e65725d293b0a202020207d0a0a202020205f6f6e6c794f776e6572286e65787429207b0a202020202020202072657175697265286d73672e73656e6465722e746f4c6f776572436173652829203d3d20746869732e6f776e65722c20276f6e6c79206f776e65722063616e2065786563757465207468697327293b0a20202020202020206e65787428293b0a202020207d0a7d0a0a2f2f204d6574686f64730a4f776e61626c652e70726f746f747970652e4f776e6572736869705472616e736665727265642e6576656e74203d20747275653b0a4f776e61626c652e70726f746f747970652e4f776e6572736869705472616e736665727265642e696e70757473203d205b276164647265737320696e6465786564272c20276164647265737320696e6465786564275d3b0a0a2f2f204d6574686f64730a4f776e61626c652e70726f746f747970652e6765744f776e65722e76696577203d20747275653b0a4f776e61626c652e70726f746f747970652e6765744f776e65722e6f757470757473203d205b2761646472657373275d3b0a0a4f776e61626c652e70726f746f747970652e7472616e736665724f776e6572736869702e696e70757473203d205b2761646472657373275d3b0a4f776e61626c652e70726f746f747970652e7472616e736665724f776e6572736869702e6d6f64696669657273203d205b275f6f6e6c794f776e6572275d3b0a0a4f776e61626c652e70726f746f747970652e5f7472616e736665724f776e6572736869702e696e7465726e616c203d20747275653b0a0a4f776e61626c652e70726f746f747970652e5f6f6e6c794f776e65722e696e7465726e616c203d20747275653b0a0a0a0a0a636c617373204552433230546f6b656e20657874656e6473204f776e61626c65207b0a202020206e616d65203d2027455243323020546f6b656e273b0a2020202073796d626f6c203d20274552433230273b0a20202020646563696d616c73203d2031383b0a20202020746f74616c537570706c79203d206e756d62657228273027293b0a0a20202020616c6c6f776564203d207b7d3b0a2020202062616c616e636573203d207b7d3b0a0a20202020737570706f72746564496e7465726661636573203d207b0a20202020202020202730783336333732623037273a20747275652c202f2f2045524332300a20202020202020202730783036666464653033273a20747275652c202f2f204552433230206e616d650a20202020202020202730783935643839623431273a20747275652c202f2f2045524332302073796d626f6c0a20202020202020202730783331336365353637273a20747275652c202f2f20455243323020646563696d616c730a202020207d3b0a0a0a202020205472616e736665722873656e6465722c20726563697069656e742c20616d6f756e7429207b7d202f2f206576656e740a20202020417070726f76616c286f776e65722c207370656e6465722c20616d6f756e7429207b7d202f2f206576656e740a202020204d696e74286d696e7465722c20616d6f756e7429207b7d202f2f206576656e740a202020204275726e286275726e65722c20616d6f756e7429207b7d202f2f206576656e740a0a0a2020202062616c616e63654f6628686f6c6465724164647265737329207b0a2020202020202020686f6c64657241646472657373203d20686f6c646572416464726573732e746f4c6f7765724361736528293b0a202020202020202076617220746f6b656e42616c616e636573203d20746869732e62616c616e636573207c7c207b7d3b0a0a202020202020202069662028686f6c6465724164647265737320696e20746f6b656e42616c616e63657329207b0a202020202020202020202020636f6e73742062616c616e6365203d20746f6b656e42616c616e6365735b686f6c646572416464726573735d3b0a20202020202020202020202072657475726e205b62616c616e63655d3b0a20202020202020207d0a202020202020202072657475726e205b6e756d6265722830295d3b0a202020207d0a0a202020207472616e7366657228726563697069656e742c20616d6f756e7429207b0a2020202020202020726563697069656e74203d20726563697069656e742e746f4c6f7765724361736528293b0a2020202020202020636f6e73742073656e646572203d206d73672e73656e6465723b0a202020202020202076617220746f6b656e42616c616e636573203d20746869732e62616c616e636573207c7c207b7d3b0a0a20202020202020206966202821202873656e64657220696e20746f6b656e42616c616e6365732929207b0a2020202020202020202020205f7468726f772827494e53554646494349454e545f544f4b454e5f42414c414e434527293b0a20202020202020207d0a202020202020202069662028746f6b656e42616c616e6365735b73656e6465725d2e6c7428616d6f756e742929207b0a2020202020202020202020205f7468726f772827494e53554646494349454e545f544f4b454e5f42414c414e434527293b0a20202020202020207d0a0a202020202020202069662028746869732e5f64656372656d656e745573657242616c616e63652e62696e642874686973292873656e6465722c20616d6f756e742929207b0a202020202020202020202020746869732e5f696e6372656d656e745573657242616c616e63652e62696e6428746869732928726563697069656e742c20616d6f756e74290a20202020202020207d0a0a2020202020202020656d697428275472616e73666572272c205b73656e6465722c20726563697069656e742c20616d6f756e745d293b0a202020202020202072657475726e205b747275655d3b0a202020207d0a0a0a202020207472616e7366657246726f6d2873656e6465722c20726563697069656e742c20616d6f756e7429207b0a202020202020202073656e646572203d2073656e6465722e746f4c6f7765724361736528293b0a2020202020202020726563697069656e74203d20726563697069656e742e746f4c6f7765724361736528293b0a20202020202020200a202020202020202076617220746f6b656e42616c616e636573203d20746869732e62616c616e636573207c7c207b7d3b0a202020202020202076617220746f6b656e416c6c6f77616e636573203d20746869732e616c6c6f776564207c7c207b7d3b0a0a2020202020202020726571756972652873656e64657220696e20746f6b656e42616c616e6365732c20274d495353494e475f544f4b454e5f42414c414e434527293b0a202020202020202069662028746f6b656e42616c616e6365735b73656e6465725d2e6c7428616d6f756e742929207b0a2020202020202020202020205f7468726f772827494e53554646494349454e545f544f4b454e5f42414c414e434527293b0a20202020202020207d0a20202020202020200a2020202020202020726571756972652873656e646572203d3d206d73672e73656e646572207c7c202873656e64657220696e20746f6b656e416c6c6f77616e636573202626206d73672e73656e64657220696e20746f6b656e416c6c6f77616e6365735b73656e6465725d292c20224d495353494e475f414c4c4f57414e434522293b0a20202020202020206966202873656e64657220213d206d73672e73656e64657220262620746f6b656e416c6c6f77616e6365735b73656e6465725d5b6d73672e73656e6465725d2e6c7428616d6f756e742929207b0a2020202020202020202020205f7468726f772827494e53554646494349454e545f544f4b454e5f414c4c4f57414e434527293b0a20202020202020207d0a0a2020202020202020746f6b656e416c6c6f77616e6365735b73656e6465725d5b6d73672e73656e6465725d203d20746f6b656e416c6c6f77616e6365735b73656e6465725d5b6d73672e73656e6465725d2e73756228616d6f756e74293b0a0a202020202020202069662028746869732e5f64656372656d656e745573657242616c616e63652e62696e642874686973292873656e6465722c20616d6f756e742929207b0a202020202020202020202020746869732e5f696e6372656d656e745573657242616c616e63652e62696e6428746869732928726563697069656e742c20616d6f756e74290a20202020202020207d0a20202020202020200a0a2020202020202020656d697428275472616e73666572272c205b73656e6465722c20726563697069656e742c20616d6f756e745d293b0a202020202020202072657475726e205b747275655d3b0a202020207d0a0a0a20202020616c6c6f77616e6365286f776e65722c207370656e64657229207b0a20202020202020206f776e6572203d206f776e65722e746f4c6f7765724361736528293b0a20202020202020207370656e646572203d207370656e6465722e746f4c6f7765724361736528293b0a202020202020202076617220746f6b656e416c6c6f77616e636573203d20746869732e616c6c6f776564207c7c207b7d3b0a2020202020202020696620286f776e657220696e20746f6b656e416c6c6f77616e636573202626207370656e64657220696e20746f6b656e416c6c6f77616e6365735b6f776e65725d29207b0a20202020202020202020202072657475726e205b746f6b656e416c6c6f77616e6365735b6f776e65725d5b7370656e6465725d5d3b0a20202020202020207d0a202020202020202072657475726e205b6e756d6265722830295d3b0a202020207d0a202020200a20202020617070726f7665287370656e6465722c2076616c756529207b0a20202020202020207370656e646572203d207370656e6465722e746f4c6f7765724361736528293b0a202020202020202076617220746f6b656e416c6c6f77616e636573203d20746869732e616c6c6f776564207c7c207b7d3b0a2020202020202020696620282120286d73672e73656e64657220696e20746f6b656e416c6c6f77616e6365732929207b0a202020202020202020202020746f6b656e416c6c6f77616e6365735b6d73672e73656e6465725d203d207b7d3b0a20202020202020207d0a2020202020202020746f6b656e416c6c6f77616e6365735b6d73672e73656e6465725d5b7370656e6465725d203d2076616c75653b0a2020202020202020656d69742827417070726f76616c272c205b6d73672e73656e6465722c207370656e6465722c2076616c75655d293b0a202020202020202072657475726e205b747275655d3b0a202020207d0a0a202020200a202020206d696e742875736572416464726573732c20616d6f756e7429207b0a2020202020202020746869732e5f6d696e742875736572416464726573732c20616d6f756e74293b0a202020207d0a0a202020205f6d696e742875736572416464726573732c20616d6f756e7429207b0a20202020202020207573657241646472657373203d2075736572416464726573732e746f4c6f7765724361736528293b0a2020202020202020696620287573657241646472657373203d3d20616464726573732830292e6164647265737329207b0a2020202020202020202020205f7468726f77282745524332303a206d696e7420746f20746865207a65726f206164647265737327293b0a20202020202020207d0a20202020202020200a202020202020202069662028746869732e5f696e6372656d656e745573657242616c616e63652e62696e642874686973292875736572416464726573732c20616d6f756e742929207b0a202020202020202020202020746869732e746f74616c537570706c79203d20746869732e746f74616c537570706c792e61646428616d6f756e74293b0a20202020202020207d0a0a2020202020202020656d697428274d696e74272c205b75736572416464726573732c20616d6f756e745d293b0a202020207d0a0a202020200a202020206275726e2875736572416464726573732c20616d6f756e7429207b0a2020202020202020746869732e5f6275726e2875736572416464726573732c20616d6f756e74293b0a202020207d0a0a202020205f6275726e2875736572416464726573732c20616d6f756e7429207b0a20202020202020207573657241646472657373203d2075736572416464726573732e746f4c6f7765724361736528293b0a2020202020202020696620287573657241646472657373203d3d20616464726573732830292e6164647265737329207b0a2020202020202020202020205f7468726f77282745524332303a206275726e2066726f6d20746865207a65726f206164647265737327293b0a20202020202020207d0a20202020202020200a202020202020202069662028746869732e5f64656372656d656e745573657242616c616e63652e62696e642874686973292875736572416464726573732c20616d6f756e742929207b0a202020202020202020202020746869732e746f74616c537570706c79203d20746869732e746f74616c537570706c792e73756228616d6f756e74293b0a20202020202020207d0a0a2020202020202020656d697428274275726e272c205b75736572416464726573732c20616d6f756e745d293b0a202020207d0a0a0a20202020737570706f727473496e7465726661636528696e74657266616365494429207b0a2020202020202020636f6e7374205f696e7465726661636573203d20746869732e737570706f72746564496e7465726661636573207c7c207b7d3b0a202020202020202072657475726e205b0a2020202020202020202020202828696e74657266616365494420696e205f696e7465726661636573290a202020202020202020202020202020203f205f696e74657266616365735b696e7465726661636549445d0a202020202020202020202020202020203a2066616c7365290a20202020202020205d3b0a202020207d0a0a0a202020205f64656372656d656e745573657242616c616e636528686f6c646572416464726573732c20616d6f756e7429207b0a202020202020202076617220746f6b656e42616c616e636573203d20746869732e62616c616e636573207c7c207b7d3b0a0a202020202020202069662028212028686f6c6465724164647265737320696e20746f6b656e42616c616e6365732929207b0a2020202020202020202020205f7468726f772827494e53554646494349454e545f544f4b454e5f42414c414e434527293b0a20202020202020207d0a202020202020202069662028616d6f756e742e677428746f6b656e42616c616e6365735b686f6c646572416464726573735d2929207b0a2020202020202020202020205f7468726f772827494e53554646494349454e545f544f4b454e5f42414c414e434527293b0a20202020202020207d0a2020202020202020746f6b656e42616c616e6365735b686f6c646572416464726573735d203d20746f6b656e42616c616e6365735b686f6c646572416464726573735d2e73756228616d6f756e74293b0a202020202020202072657475726e20747275653b0a202020207d0a0a0a202020205f696e6372656d656e745573657242616c616e636528686f6c646572416464726573732c20616d6f756e7429207b0a202020202020202076617220746f6b656e42616c616e636573203d20746869732e62616c616e636573207c7c207b7d3b0a0a202020202020202069662028212028686f6c6465724164647265737320696e20746f6b656e42616c616e6365732929207b0a202020202020202020202020746f6b656e42616c616e6365735b686f6c646572416464726573735d203d206e756d6265722830293b0a20202020202020207d0a2020202020202020746f6b656e42616c616e6365735b686f6c646572416464726573735d203d20746f6b656e42616c616e6365735b686f6c646572416464726573735d2e61646428616d6f756e74293b0a202020202020202072657475726e20747275653b0a202020207d0a0a7d0a0a2f2f204576656e74730a4552433230546f6b656e2e70726f746f747970652e5472616e736665722e6576656e74203d20747275653b0a4552433230546f6b656e2e70726f746f747970652e5472616e736665722e696e70757473203d205b276164647265737320696e6465786564272c20276164647265737320696e6465786564272c202775696e74323536275d3b0a0a4552433230546f6b656e2e70726f746f747970652e417070726f76616c2e6576656e74203d20747275653b0a4552433230546f6b656e2e70726f746f747970652e417070726f76616c2e696e70757473203d205b276164647265737320696e6465786564272c20276164647265737320696e6465786564272c202775696e74323536275d3b0a0a4552433230546f6b656e2e70726f746f747970652e4d696e742e6576656e74203d20747275653b0a4552433230546f6b656e2e70726f746f747970652e4d696e742e696e70757473203d205b276164647265737320696e6465786564272c202775696e74323536275d3b0a0a4552433230546f6b656e2e70726f746f747970652e4275726e2e6576656e74203d20747275653b0a4552433230546f6b656e2e70726f746f747970652e4275726e2e696e70757473203d205b276164647265737320696e6465786564272c202775696e74323536275d3b0a0a2f2f204d6574686f64730a4552433230546f6b656e2e70726f746f747970652e62616c616e63654f662e76696577203d20747275653b0a4552433230546f6b656e2e70726f746f747970652e62616c616e63654f662e696e70757473203d205b2761646472657373275d3b0a4552433230546f6b656e2e70726f746f747970652e62616c616e63654f662e6f757470757473203d205b2775696e74323536275d3b0a0a4552433230546f6b656e2e70726f746f747970652e7472616e736665722e696e70757473203d205b2761646472657373272c202775696e74323536275d3b0a4552433230546f6b656e2e70726f746f747970652e7472616e736665722e6f757470757473203d205b27626f6f6c275d3b0a0a4552433230546f6b656e2e70726f746f747970652e7472616e7366657246726f6d2e696e70757473203d205b2761646472657373272c202761646472657373272c202775696e74323536275d3b0a4552433230546f6b656e2e70726f746f747970652e7472616e7366657246726f6d2e6f757470757473203d205b27626f6f6c275d3b0a0a4552433230546f6b656e2e70726f746f747970652e616c6c6f77616e63652e76696577203d20747275653b0a4552433230546f6b656e2e70726f746f747970652e616c6c6f77616e63652e696e70757473203d205b2761646472657373272c202761646472657373275d3b0a4552433230546f6b656e2e70726f746f747970652e616c6c6f77616e63652e6f757470757473203d205b2775696e74323536275d3b0a0a4552433230546f6b656e2e70726f746f747970652e617070726f76652e696e70757473203d205b2761646472657373272c202775696e74323536275d3b0a4552433230546f6b656e2e70726f746f747970652e617070726f76652e6f757470757473203d205b27626f6f6c275d3b0a0a4552433230546f6b656e2e70726f746f747970652e6d696e742e696e70757473203d205b2761646472657373272c202775696e74323536275d3b0a4552433230546f6b656e2e70726f746f747970652e6d696e742e6d6f64696669657273203d205b275f6f6e6c794f776e6572275d3b0a0a4552433230546f6b656e2e70726f746f747970652e5f6d696e742e696e7465726e616c203d20747275653b0a0a4552433230546f6b656e2e70726f746f747970652e6275726e2e696e70757473203d205b2761646472657373272c202775696e74323536275d3b0a4552433230546f6b656e2e70726f746f747970652e6275726e2e6d6f64696669657273203d205b275f6f6e6c794f776e6572275d3b0a0a4552433230546f6b656e2e70726f746f747970652e5f6275726e2e696e7465726e616c203d20747275653b0a0a4552433230546f6b656e2e70726f746f747970652e737570706f727473496e746572666163652e76696577203d20747275653b0a4552433230546f6b656e2e70726f746f747970652e737570706f727473496e746572666163652e696e70757473203d205b27627974657334275d3b0a4552433230546f6b656e2e70726f746f747970652e737570706f727473496e746572666163652e6f757470757473203d205b27626f6f6c275d3b0a0a4552433230546f6b656e2e70726f746f747970652e5f64656372656d656e745573657242616c616e63652e696e7465726e616c203d20747275653b0a0a4552433230546f6b656e2e70726f746f747970652e5f696e6372656d656e745573657242616c616e63652e696e7465726e616c203d20747275653b0a0a0a0a636c617373204261736963546f6b656e20657874656e6473204552433230546f6b656e207b2020200a202020206e616d65203d2027424153494320546f6b656e273b0a2020202073796d626f6c203d20274241534943273b0a0a20202020636f6e7374727563746f722829207b0a2020202020202020737570657228293b0a2020202020202020746869732e746f74616c537570706c79203d206e756d6265722827343230303030303027202b202730303030303030303030303030303030303027293b202f2f20343220303030203030302042415349430a2020202020202020746869732e62616c616e6365735b6d73672e73656e6465725d203d20746869732e746f74616c537570706c793b0a202020207d0a0a7d0a0a0a72657475726e204261736963546f6b656e3b0a00000000000000000000000000000000000000000000000000000000000000000000025b5d000000000000000000000000000000000000000000000000000000000000", nonce: "10", to: "0x", value: "0", chainId: "3615", origin: "1651572575597", v: "1c62", r: "9f401c8da69afce6b0386fe070ad3f5590f4a9145f392e4fc9157a1efdda51fd", s: "7893ea5244c46eef14522140de92b977b08999aea6e49de661916914afcd55b7" }, hash: "0xd35ada50d67fe104087551ffcc9b05db08902ced472e9d26982fce6f59b66cf1", load: "() => {
const transactionFile = config.node.dataDir + '/transactions/' + this.hash + '.json';

if (! fs.existsSync(transactionFile)) {
console.warn(utils.getDate(), "[BLOCKCHAIN]", 'IMPORT Transaction not found', this.hash);
throw new Error('IMPORT_TRANSACTION_NOT_FOUND_DATABASE_CORRUPTED');
}

const transactionJson = fs.readFileSync(transactionFile);
transactionData = JSON.parse(transactionJson, utils.bnJsonParser);

this.data = (typeof transactionData == 'TransactionData') ? transactionData : new TransactionData(transactionData);

return true;
}"
, save: "() => {
const transactionFile = config.node.dataDir + '/transactions/' + this.hash + '.json';
fs.writeFileSync(transactionFile, JSON.stringify(this.data));
return true;
}"
, getReceipt: "() => {
return blockchain.getTransactionReceiptByHash(this.hash);
}"
, getBlock: "() => {
return blockchain.getBlockByHash(this.data.blockHash);
}"
, getBlockTransationPrev: "() => {
const block = this.getBlock();
const txPrevIdx = (this.data.transactionIndex > 0) ? (this.data.transactionIndex-1) : null;
const tx = (block && txPrevIdx in block.transactions) ? block.transactions[txPrevIdx] : null;
return tx;
}"
, getBlockTransationNext: "() => {
const block = this.getBlock();
const txNextIdx = (this.data.transactionIndex < block.transactions.length - 1) ? (this.data.transactionIndex+1) : null;
const tx = (block && txNextIdx in block.transactions) ? block.transactions[txNextIdx] : null;
return tx;
}"
, getAccountTransactionIndex: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = account.data.transactions.findIndex(transactionHash => blockchain.getTransactionByHash(transactionHash).hash == this.hash);
if (txIdx === -1) {
return null;
}
return txIdx;
}"
, getAccountTransationPrev: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = this.getAccountTransactionIndex();
if (txIdx === null) {
return null;
}
const txPrevIdx = (txIdx > 0) ? (txIdx-1) : null;
const transactionHash = (txPrevIdx in account.data.transactions) ? account.data.transactions[txPrevIdx] : null;
if (! transactionHash) {
return null;
}
return blockchain.getTransactionByHash(transactionHash);
}"
, getAccountTransationNext: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = this.getAccountTransactionIndex();
if (txIdx === null) {
return null;
}
const txNextIdx = (txIdx < account.data.transactions.length - 1) ? (txIdx+1) : null;
const transactionHash = (txNextIdx in account.data.transactions) ? account.data.transactions[txNextIdx] : null;
if (! transactionHash) {
return null;
}
return blockchain.getTransactionByHash(transactionHash);

}"
, formatRpc: "() => {
// https://github.com/ethers-io/ethers.js/blob/master/packages/abstract-provider/lib/index.d.ts

const to = (this.data.to && this.data.to.length == 42) ? this.data.to : null;

const _txData = {
blockHash: this.data.blockHash,
blockNumber: this.data.blockNumber.toNumber(),
transactionIndex: this.data.transactionIndex,
hash: this.data.hash,
from: this.data.from,
gasLimit: this.data.gasLimit.toHexString(),
gasPrice: this.data.gasPrice.toHexString(),
data: this.data.data,
nonce: this.data.nonce.toHexString(),
to,
value: this.data.value.toHexString(),
chainId: this.data.chainId.toNumber(),
//origin: this.data.origin.toHexString(),
};

return _txData;
}"
, getHashOLD: "() => {
const _txData = {
gasPrice: this.data.gasPrice,
gasLimit: this.data.gasLimit,
from: this.data.from,
to: this.data.to,
value: this.data.value,
data: this.data.data,
nonce: this.data.nonce,
};

const strvalue = JSON.stringify(utils.orderedObject(_txData));
const transactionHash = ethers.utils.id(strvalue);

return transactionHash;
}"
, getHash: "() => {
const _txData = {
gasPrice: this.data.gasPrice.toHexString(),
gasLimit: this.data.gasLimit.toHexString(),
from: this.data.from,
to: this.data.to,
value: this.data.value.toHexString(),
data: this.data.data,
nonce: this.data.nonce.toHexString(),
v: this.data.v ? ('0x' + this.data.v) : '',
r: this.data.r ? ('0x' + this.data.r) : '',
s: this.data.s ? ('0x' + this.data.s) : '',
};

const tx = new EthereumJS.Transaction(_txData);
const hashRaw = tx.hash();

const transactionHash = ethers.utils.defaultAbiCoder.decode(["uint256"], hashRaw)[0].toHexString();

return transactionHash;
}"
, execute: "(block, transactionIndex, logIndexOffset) => {
const _tx = this;

function _rejectTransaction(reason) {
// https://openethereum.github.io/Transactions-Queue
reason = reason || 'unknown';
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] REJECTED PROCESSING TRANSACTION', _tx.data.hash, ' (Reason:', reason + ')');
const success = blockchain.removeTransactionsFromMempool([_tx.hash], true, true);
if (success) {
saveMempool();
}
throw new Error(reason); // on rejete definitivement la tx (elle ne sera pas minée ... et donc aucun frais de gas ne sera compté)
// et du coup on rejete le block
}

function _checkGas(step) {
execDuration = performance.now()/1000 - execTsStart; //parseFloat(process.hrtime(execTsStart).join('.'));
gasUsed = toBn(Math.round(execDuration * cpuPricePerSecond)); // TODO: prendre en compte le storage utilisé dans le calcul du gasUsed
txCost = gasUsed.mul(_tx.data.gasPrice);

if (step == 'afterTransfer') {
//debugger;
}

if (status) {
gasUsedEffective = gasUsed;
txCostEffective = gasUsedEffective.mul(_tx.data.gasPrice);

if (gasUsed.gt(_tx.data.gasLimit)) {
status = 0;
error = new Error('INSUFFICIENT_GAS');

gasUsedEffective = _tx.data.gasLimit;
txCostEffective = txCostLimit;

// emit event
const debugError = {
message: 'INSUFFICIENT_GAS',
step,
gasPrice: _tx.data.gasPrice,
gasLimit: _tx.data.gasLimit,
gasUsed,
gasUsedEffective,
txCostLimit,
txCost,
txCostEffective,
execDuration,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, _tx.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

}
}


//const block = this;
const txLogs = [];
let vmStorage = {};

let gasUsed = toBn(0);
let gasUsedEffective = toBn(0);
let status = 1;
let error = null;
let createdContractAddress = null;
let amountTransfered = toBn(0);

const contractAddress = this.data.to;
let storageBackup;
let balanceBackup;
let contractExecuted = false;
let contractDeployed = false;

const execTsStart = performance.now()/1000; //process.hrtime(); //Date.now();
let execDuration = 0;

const txCostLimit = this.data.gasLimit.mul(this.data.gasPrice);
let txCost = toBn(0);
let txCostEffective = toBn(0);


const accountFrom = blockchain.getAccount(this.data.from);
if (! accountFrom) {
_rejectTransaction('TRANSACTION_REJECTED_EMPTY_ACCOUNT');
}
const accountFromBalance = accountFrom.data.balance;


if (accountFrom.address != mintAddress) {

// verif nonce
const expectedNonce = toBn(accountFrom.data.transactions.length); // le nonce attendu est egal au nombre de transactions passées de l'account
if (! this.data.nonce.eq(expectedNonce)) {
_rejectTransaction('TRANSACTION_REJECTED_INCORRECT_NONCE');
}

// verif balance pour gas + transfer
if (this.data.value && this.data.value.gt(0)) {
if (accountFromBalance.lt(this.data.value.add(txCostLimit))) {
// l'adresse n'a pas suffisamment de fond pour executer la transaction (selon gasLimit)
// TODO: faut-il rejeter la tx ? ou la laisser passer (en status=0) et lui vider sa balance (si 100% du gas est utilisé) ?
//return _rejectTransaction('TRANSACTION_FAILED_INSUFFICIENT_ETH_BALANCE');

_rejectTransaction('TRANSACTION_REJECTED_INSUFFICIENT_ETH_BALANCE');
}
}
}



_checkGas('init');


if (status) {
if (this.data.value && this.data.value.gt(0) && contractAddress && contractAddress.length > 2) {
// Transfer la valeur demandée (simple ETH transaction)
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(this.data, block);
txLogs.push( ...transferLogs );

// si recipient == contract => on transfer quand meme dès maintenant => TODO: a revoir (le transferEth declenche un appel à receive ... avant meme de savoir si unne methode payable doit etre appelée. Probleme si receive provoque un revert => la fonction payable ne sera pas appelée)
// 1) si methode payable appellée, on l'execute
// 2) si methode non payable => on appelle receive juste avant
// 3) si methode inconnue => revert


if (transferResult && transferReverted) {
// reverted (after transfer)
amountTransfered = this.data.value;

status = 0;
error = new Error('TRANSFER_REVERTED');

} else if (transferResult) {
// success
amountTransfered = this.data.value;

} else {
// transfer failed
status = 0;
error = new Error('TRANSFER_FAILED');
}

}

_checkGas('afterTransfer');
}

const gasUsedBeforeContractExecution = gasUsed;

if (status && this.data.data && this.data.data != '0x') {
// Traitement du champ data de la transaction (smart contract call)

if (!contractAddress || contractAddress == '0x') {
// contract creation

contractDeployed = true;

let contractExecResult;
try {
contractExecResult = this.transactionDeployContract(block);
const creationLogs = contractExecResult.logs;
vmStorage = contractExecResult.storage;

if (creationLogs.length) {
txLogs.push( ...creationLogs );
}

if (! contractExecResult.result) {
// Deploy Failed
status = 0;
error = new Error('CONTRACT_CREATION_FAILED');

// emit event
const debugError = {
message: 'CONTRACT_CREATION_FAILED',
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);

} else {
// Deploy Success
createdContractAddress = contractExecResult.result.toLowerCase();

if (this.data.value.gt(0)) {
// Transfer tx value to contract
const _txData = {
...this.data,
to: createdContractAddress,
};
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(_txData, block);
txLogs.push( ...transferLogs );

if (transferResult) {
amountTransfered = this.data.value;

} else {
status = 0;
error = new Error('CONTRACT_CREATION_TRANSFER_FAILED');
}
}

// TODO: emit event CONTRACT_CREATION_SUCCESS ?
}



} catch (e) {
//console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] Cannot execute transaction');
//throw e;
status = 0;
error = new Error('CONTRACT_CREATION_ERROR');

console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] CONTRACT_CREATION_ERROR with message', e.message)

// emit event
const debugError = {
message: 'CONTRACT_CREATION_ERROR',
error: e.message,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

_checkGas('afterContractCreation');

} else {
// contract execution

const contractAccount = blockchain.getAccount(contractAddress);

if (! contractAccount) {
status = 0;
error = new Error('CONTRACT_ACCOUNT_NOT_FOUND');

// emit event
const debugError = {
message: 'CONTRACT_ACCOUNT_NOT_FOUND',
address: contractAddress,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

if (status) {
storageBackup = JSON.stringify(contractAccount.data.storage);
balanceBackup = contractAccount.data.balance;
contractExecuted = true;

try {
const contractExecResult = this.executeTransactionContract(block);
const internalLogs = contractExecResult.logs;
vmStorage = contractExecResult.storage;

if (internalLogs.length) {
txLogs.push( ...internalLogs );
}

if (! contractExecResult.result) {
status = 0;
error = new Error('CONTRACT_EXEC_FAILED');

// emit event
const debugError = {
message: 'CONTRACT_EXEC_FAILED',
address: contractAddress,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

} catch (e) {
status = 0;
error = new Error('CONTRACT_EXEC_ERROR');

// emit event
const debugError = {
message: 'CONTRACT_EXEC_ERROR',
address: contractAddress,
error: e.message,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}
}

_checkGas('afterContractCall');
}

}

const gasUsedAfterContractExecution = gasUsed;
const gasUsedInContractExecution = gasUsedAfterContractExecution.sub(gasUsedBeforeContractExecution);
const contractExecutionCost = gasUsedInContractExecution.mul(_tx.data.gasPrice);


console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] execDuration =', (Math.round(execDuration * 1000000) / 1000000), ' seconds');



if (txCostEffective.gt(0)) {
// Gas payment
//const minerAccount = blockchain.getAccount(block.data.miner, true);
//const gasPaymentResult = minerAccount.transferFrom(accountFrom.address, txCostEffective);

const txDataGasPayment = {
from: accountFrom.address,
to: block.data.miner,
value: txCostEffective,
};
const [gasPaymentResult, gasPaymentLogs, transferReverted] = blockchain.transferEth(txDataGasPayment, block, true);
txLogs.push(...gasPaymentLogs);

if (gasPaymentResult) {
// gas payment success

} else {
// gas payment failed => reject transaction (and block)
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] Gas payment failed');
_rejectTransaction('GAS_PAYMENT_FAILED');
}

if (false) {
// DEBUG
const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();
const gasEffectivePercent = gasUsedEffective.mul(100).div(this.data.gasLimit).toNumber();
const costPercent = txCost.mul(100).div(txCostLimit).toNumber();
const costEffectivePercent = txCostEffective.mul(100).div(txCostLimit).toNumber();
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] txCostLimit =', ethers.utils.formatEther(txCostLimit));
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] txCostEffective =', ethers.utils.formatEther(txCostEffective));
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] gas % =', Math.round(gasPercent), '% =', Math.round(gasEffectivePercent), '%');
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] cost % =', Math.round(costPercent), '%', Math.round(costEffectivePercent), '%');
}


}


if (accountFrom.address != mintAddress) {
const accountFromBalanceAtEnd = blockchain.getAccountBalance(accountFrom.address);

if (accountFromBalanceAtEnd.lt(0)){
_rejectTransaction('TRANSACTION_FAILED_INSUFFICIENT_ETH_BALANCE_AT_END');
}
}


if (! status) {
// Transaction Failed

// Revert transfer
if (amountTransfered.gt(0)) {
const txDataRevert = {
...this.data,
from: this.data.to,
to: this.data.from,
value: amountTransfered,
};
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(txDataRevert, block);

// TODO: gerer le revert des internal transfers
}

txLogs.forEach(log => {
log.removed = true;
});

// Revert contract storage
if (contractExecuted) {
const contractAccount = blockchain.getAccount(contractAddress);
contractAccount.data.storage = JSON.parse(storageBackup, utils.bnJsonParser);
contractAccount.data.balance = balanceBackup;

} else if (amountTransfered) {
// on check aussi amountTransfered car le contrat peut avoir été exécuté via receive => TODO: process a revoir
const contractAccount = blockchain.getAccount(contractAddress);
if (contractAccount && contractAccount.isContract()) {
//contractAccount.data.storage = JSON.parse(storageBackup, utils.bnJsonParser);
//contractAccount.data.balance = balanceBackup;
// TODO
}

}

// Revert deployed contract
if (contractDeployed) {
// nothing to do ?
}

const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] FAILED TRANSACTION ', this.data.hash, '| gas usage percent =', gasPercent, '%', ' | Error: ', error.message);

const realExecDuration = performance.now()/1000 - execTsStart; //parseFloat(process.hrtime(execTsStart).join('.'));
const realGasUsed = toBn(Math.round(realExecDuration * cpuPricePerSecond)); // TODO: prendre en compte le storage utilisé dans le calcul du gasUsed
const realGasPercent = realGasUsed.mul(100).div(this.data.gasLimit).toNumber();
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] FAILED TRANSACTION ', this.data.hash, '| real gas usage percent =', realGasPercent, '%', ' | Error: ', error.message);

} else {
// Transaction Success

// save contract storage
Object.entries(vmStorage).forEach((entry, idx) => {
const [contractAddress, contractStorage] = entry;

const contractAccount = blockchain.getAccount(contractAddress, true);
contractAccount.data.storage = contractStorage;
contractAccount.save();
});

// show result (debug)
const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();;
console.log(utils.getDate(), "[BLOCKCHAIN]", 'SUCCECSS TRANSACTION ', this.data.hash, '| gas usage percent =', gasPercent, '%');
}


// total gas utilisé dans le block
block.data.gasUsed = block.data.gasUsed.add(gasUsed);


// assign block data on logs
txLogs.forEach((txLog, idx) => {
txLog.data.transactionIndex = transactionIndex;
txLog.data.logIndex = logIndexOffset + idx;
txLog.data.transactionHash = this.data.hash;
txLog.data.blockHash = block.hash;
txLog.data.blockNumber = block.data.number;
});


const receiptData = {
transactionHash: this.data.hash,
transactionIndex: transactionIndex,
blockNumber: block.data.number,
blockHash: block.hash,
//logsBloom: '0x0000',
contractAddress: createdContractAddress,
gasUsed,
cumulativeGasUsed: block.data.gasUsed,
effectiveGasPrice: this.data.gasPrice,
effectiveCost: this.data.gasPrice.mul(gasUsed),
logs: txLogs,
status,
}

const receipt = new Receipt(blockchain, receiptData);

return [receipt, txLogs];
}"
, executeTransactionContract: "(block) => {
let contractAddress = this.data.to;

const isNullRecipient = (! contractAddress || contractAddress.length <= 2);
if (isNullRecipient) {
throw new Error('CONTRACT_EXEC_ADDRESS_INVALID_BUG_1');
}

const contractAccount = blockchain.getAccount(contractAddress);

if (! contractAccount) {
throw new Error('CONTRACT_EXEC_ACCOUNT_NOT_FOUND');
}

// Execute existing contract method call
return blockchain.callContractMethodInVM(this.data, block);

}"
, transactionDeployContract: "(block) => {
let contractAddress = this.data.to;

let contractAccount;

if (contractAddress && contractAddress.length > 2) {
throw new Error('CONTRACT_DEPLOY_ADDRESS_INVALID_BUG_2');
}


// CONTRACT CREATION

if (! this.data.data || this.data.data.length <= 2) {
throw new Error('CONTRACT_DEPLOY_INVALID_DATA_EMPTY_CODE');
}


// build contract address
contractAddress = ethers.utils.getContractAddress(this.data).toLocaleLowerCase();

// parse contract source code & constructor parameters
const codes = ethers.utils.defaultAbiCoder.decode(["string", "string"], this.data.data);
if (! codes) {
throw new Error('CONTRACT_DEPLOY_CODE_PARSING_FAILED');
}

const sourceCode = codes[0];
const constructorParams = codes[1];
const constructorRawParams = ethers.utils.defaultAbiCoder.encode(["string"], [constructorParams]);
const codeHash = ethers.utils.id(sourceCode);
const codeAbi = null; // l'abi sera généré une fois que le contrat aura été instancié
const storageHash = ethers.utils.id('{}');

// create new account
const accountData = {
address: contractAddress,
code: sourceCode,
codeHash,
codeAbi,
storage: {},
storageHash,
};
contractAccount = blockchain.createAccount(contractAddress, accountData);


// instanciate contract + call constructor + recuperer storage
const txData = {
from: this.data.from,
to: contractAddress,
data: constructorRawParams,
value: this.data.value.toHexString(),
};
const callResult = blockchain.deployContractInVM(txData, block);
const creationLogs = callResult.logs;
const vmStorage = callResult.storage;


// emit event
const eventParams = [
contractAddress,
];
const _txData = {
...this.data,
to: blockchain.getNullAddress(),
}
const deployLog = new TxLog(blockchain, null, _txData, 'ContractCreated', eventParams, deployAbi);
creationLogs.push(deployLog);


return {
result: contractAddress,
logs: creationLogs,
storage: vmStorage,
};
}"
, timestamp: "1651572579441", status: 1, gasUsed: "118396" }, 0x1ccfa75127b63b28546df83229b654b3b3e9e6b1f19a39b66b8d4360cec9075a: { data: { blockHash: "0x0057534b6cfdb8717c383af981bbb658d7b6ddd2dc8ad354d8d1707bc2f387a8", blockNumber: "12", transactionIndex: 0, hash: "0x1ccfa75127b63b28546df83229b654b3b3e9e6b1f19a39b66b8d4360cec9075a", from: "0x0000000000000000000000000000000000000001", gasLimit: "1000000000000000000", gasPrice: "0", data: "0x", nonce: "11", to: "0xccf8ba457dcad7ee6a0361c96846a0f79744b113", value: "250000000000000000000", chainId: "3615", origin: "1651573244565", v: "", r: "", s: "" }, hash: "0x1ccfa75127b63b28546df83229b654b3b3e9e6b1f19a39b66b8d4360cec9075a", load: "() => {
const transactionFile = config.node.dataDir + '/transactions/' + this.hash + '.json';

if (! fs.existsSync(transactionFile)) {
console.warn(utils.getDate(), "[BLOCKCHAIN]", 'IMPORT Transaction not found', this.hash);
throw new Error('IMPORT_TRANSACTION_NOT_FOUND_DATABASE_CORRUPTED');
}

const transactionJson = fs.readFileSync(transactionFile);
transactionData = JSON.parse(transactionJson, utils.bnJsonParser);

this.data = (typeof transactionData == 'TransactionData') ? transactionData : new TransactionData(transactionData);

return true;
}"
, save: "() => {
const transactionFile = config.node.dataDir + '/transactions/' + this.hash + '.json';
fs.writeFileSync(transactionFile, JSON.stringify(this.data));
return true;
}"
, getReceipt: "() => {
return blockchain.getTransactionReceiptByHash(this.hash);
}"
, getBlock: "() => {
return blockchain.getBlockByHash(this.data.blockHash);
}"
, getBlockTransationPrev: "() => {
const block = this.getBlock();
const txPrevIdx = (this.data.transactionIndex > 0) ? (this.data.transactionIndex-1) : null;
const tx = (block && txPrevIdx in block.transactions) ? block.transactions[txPrevIdx] : null;
return tx;
}"
, getBlockTransationNext: "() => {
const block = this.getBlock();
const txNextIdx = (this.data.transactionIndex < block.transactions.length - 1) ? (this.data.transactionIndex+1) : null;
const tx = (block && txNextIdx in block.transactions) ? block.transactions[txNextIdx] : null;
return tx;
}"
, getAccountTransactionIndex: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = account.data.transactions.findIndex(transactionHash => blockchain.getTransactionByHash(transactionHash).hash == this.hash);
if (txIdx === -1) {
return null;
}
return txIdx;
}"
, getAccountTransationPrev: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = this.getAccountTransactionIndex();
if (txIdx === null) {
return null;
}
const txPrevIdx = (txIdx > 0) ? (txIdx-1) : null;
const transactionHash = (txPrevIdx in account.data.transactions) ? account.data.transactions[txPrevIdx] : null;
if (! transactionHash) {
return null;
}
return blockchain.getTransactionByHash(transactionHash);
}"
, getAccountTransationNext: "() => {
const account = blockchain.getAccount(this.data.from);
if (! account) {
return null;
}
const txIdx = this.getAccountTransactionIndex();
if (txIdx === null) {
return null;
}
const txNextIdx = (txIdx < account.data.transactions.length - 1) ? (txIdx+1) : null;
const transactionHash = (txNextIdx in account.data.transactions) ? account.data.transactions[txNextIdx] : null;
if (! transactionHash) {
return null;
}
return blockchain.getTransactionByHash(transactionHash);

}"
, formatRpc: "() => {
// https://github.com/ethers-io/ethers.js/blob/master/packages/abstract-provider/lib/index.d.ts

const to = (this.data.to && this.data.to.length == 42) ? this.data.to : null;

const _txData = {
blockHash: this.data.blockHash,
blockNumber: this.data.blockNumber.toNumber(),
transactionIndex: this.data.transactionIndex,
hash: this.data.hash,
from: this.data.from,
gasLimit: this.data.gasLimit.toHexString(),
gasPrice: this.data.gasPrice.toHexString(),
data: this.data.data,
nonce: this.data.nonce.toHexString(),
to,
value: this.data.value.toHexString(),
chainId: this.data.chainId.toNumber(),
//origin: this.data.origin.toHexString(),
};

return _txData;
}"
, getHashOLD: "() => {
const _txData = {
gasPrice: this.data.gasPrice,
gasLimit: this.data.gasLimit,
from: this.data.from,
to: this.data.to,
value: this.data.value,
data: this.data.data,
nonce: this.data.nonce,
};

const strvalue = JSON.stringify(utils.orderedObject(_txData));
const transactionHash = ethers.utils.id(strvalue);

return transactionHash;
}"
, getHash: "() => {
const _txData = {
gasPrice: this.data.gasPrice.toHexString(),
gasLimit: this.data.gasLimit.toHexString(),
from: this.data.from,
to: this.data.to,
value: this.data.value.toHexString(),
data: this.data.data,
nonce: this.data.nonce.toHexString(),
v: this.data.v ? ('0x' + this.data.v) : '',
r: this.data.r ? ('0x' + this.data.r) : '',
s: this.data.s ? ('0x' + this.data.s) : '',
};

const tx = new EthereumJS.Transaction(_txData);
const hashRaw = tx.hash();

const transactionHash = ethers.utils.defaultAbiCoder.decode(["uint256"], hashRaw)[0].toHexString();

return transactionHash;
}"
, execute: "(block, transactionIndex, logIndexOffset) => {
const _tx = this;

function _rejectTransaction(reason) {
// https://openethereum.github.io/Transactions-Queue
reason = reason || 'unknown';
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] REJECTED PROCESSING TRANSACTION', _tx.data.hash, ' (Reason:', reason + ')');
const success = blockchain.removeTransactionsFromMempool([_tx.hash], true, true);
if (success) {
saveMempool();
}
throw new Error(reason); // on rejete definitivement la tx (elle ne sera pas minée ... et donc aucun frais de gas ne sera compté)
// et du coup on rejete le block
}

function _checkGas(step) {
execDuration = performance.now()/1000 - execTsStart; //parseFloat(process.hrtime(execTsStart).join('.'));
gasUsed = toBn(Math.round(execDuration * cpuPricePerSecond)); // TODO: prendre en compte le storage utilisé dans le calcul du gasUsed
txCost = gasUsed.mul(_tx.data.gasPrice);

if (step == 'afterTransfer') {
//debugger;
}

if (status) {
gasUsedEffective = gasUsed;
txCostEffective = gasUsedEffective.mul(_tx.data.gasPrice);

if (gasUsed.gt(_tx.data.gasLimit)) {
status = 0;
error = new Error('INSUFFICIENT_GAS');

gasUsedEffective = _tx.data.gasLimit;
txCostEffective = txCostLimit;

// emit event
const debugError = {
message: 'INSUFFICIENT_GAS',
step,
gasPrice: _tx.data.gasPrice,
gasLimit: _tx.data.gasLimit,
gasUsed,
gasUsedEffective,
txCostLimit,
txCost,
txCostEffective,
execDuration,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, _tx.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

}
}


//const block = this;
const txLogs = [];
let vmStorage = {};

let gasUsed = toBn(0);
let gasUsedEffective = toBn(0);
let status = 1;
let error = null;
let createdContractAddress = null;
let amountTransfered = toBn(0);

const contractAddress = this.data.to;
let storageBackup;
let balanceBackup;
let contractExecuted = false;
let contractDeployed = false;

const execTsStart = performance.now()/1000; //process.hrtime(); //Date.now();
let execDuration = 0;

const txCostLimit = this.data.gasLimit.mul(this.data.gasPrice);
let txCost = toBn(0);
let txCostEffective = toBn(0);


const accountFrom = blockchain.getAccount(this.data.from);
if (! accountFrom) {
_rejectTransaction('TRANSACTION_REJECTED_EMPTY_ACCOUNT');
}
const accountFromBalance = accountFrom.data.balance;


if (accountFrom.address != mintAddress) {

// verif nonce
const expectedNonce = toBn(accountFrom.data.transactions.length); // le nonce attendu est egal au nombre de transactions passées de l'account
if (! this.data.nonce.eq(expectedNonce)) {
_rejectTransaction('TRANSACTION_REJECTED_INCORRECT_NONCE');
}

// verif balance pour gas + transfer
if (this.data.value && this.data.value.gt(0)) {
if (accountFromBalance.lt(this.data.value.add(txCostLimit))) {
// l'adresse n'a pas suffisamment de fond pour executer la transaction (selon gasLimit)
// TODO: faut-il rejeter la tx ? ou la laisser passer (en status=0) et lui vider sa balance (si 100% du gas est utilisé) ?
//return _rejectTransaction('TRANSACTION_FAILED_INSUFFICIENT_ETH_BALANCE');

_rejectTransaction('TRANSACTION_REJECTED_INSUFFICIENT_ETH_BALANCE');
}
}
}



_checkGas('init');


if (status) {
if (this.data.value && this.data.value.gt(0) && contractAddress && contractAddress.length > 2) {
// Transfer la valeur demandée (simple ETH transaction)
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(this.data, block);
txLogs.push( ...transferLogs );

// si recipient == contract => on transfer quand meme dès maintenant => TODO: a revoir (le transferEth declenche un appel à receive ... avant meme de savoir si unne methode payable doit etre appelée. Probleme si receive provoque un revert => la fonction payable ne sera pas appelée)
// 1) si methode payable appellée, on l'execute
// 2) si methode non payable => on appelle receive juste avant
// 3) si methode inconnue => revert


if (transferResult && transferReverted) {
// reverted (after transfer)
amountTransfered = this.data.value;

status = 0;
error = new Error('TRANSFER_REVERTED');

} else if (transferResult) {
// success
amountTransfered = this.data.value;

} else {
// transfer failed
status = 0;
error = new Error('TRANSFER_FAILED');
}

}

_checkGas('afterTransfer');
}

const gasUsedBeforeContractExecution = gasUsed;

if (status && this.data.data && this.data.data != '0x') {
// Traitement du champ data de la transaction (smart contract call)

if (!contractAddress || contractAddress == '0x') {
// contract creation

contractDeployed = true;

let contractExecResult;
try {
contractExecResult = this.transactionDeployContract(block);
const creationLogs = contractExecResult.logs;
vmStorage = contractExecResult.storage;

if (creationLogs.length) {
txLogs.push( ...creationLogs );
}

if (! contractExecResult.result) {
// Deploy Failed
status = 0;
error = new Error('CONTRACT_CREATION_FAILED');

// emit event
const debugError = {
message: 'CONTRACT_CREATION_FAILED',
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);

} else {
// Deploy Success
createdContractAddress = contractExecResult.result.toLowerCase();

if (this.data.value.gt(0)) {
// Transfer tx value to contract
const _txData = {
...this.data,
to: createdContractAddress,
};
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(_txData, block);
txLogs.push( ...transferLogs );

if (transferResult) {
amountTransfered = this.data.value;

} else {
status = 0;
error = new Error('CONTRACT_CREATION_TRANSFER_FAILED');
}
}

// TODO: emit event CONTRACT_CREATION_SUCCESS ?
}



} catch (e) {
//console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] Cannot execute transaction');
//throw e;
status = 0;
error = new Error('CONTRACT_CREATION_ERROR');

console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] CONTRACT_CREATION_ERROR with message', e.message)

// emit event
const debugError = {
message: 'CONTRACT_CREATION_ERROR',
error: e.message,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

_checkGas('afterContractCreation');

} else {
// contract execution

const contractAccount = blockchain.getAccount(contractAddress);

if (! contractAccount) {
status = 0;
error = new Error('CONTRACT_ACCOUNT_NOT_FOUND');

// emit event
const debugError = {
message: 'CONTRACT_ACCOUNT_NOT_FOUND',
address: contractAddress,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

if (status) {
storageBackup = JSON.stringify(contractAccount.data.storage);
balanceBackup = contractAccount.data.balance;
contractExecuted = true;

try {
const contractExecResult = this.executeTransactionContract(block);
const internalLogs = contractExecResult.logs;
vmStorage = contractExecResult.storage;

if (internalLogs.length) {
txLogs.push( ...internalLogs );
}

if (! contractExecResult.result) {
status = 0;
error = new Error('CONTRACT_EXEC_FAILED');

// emit event
const debugError = {
message: 'CONTRACT_EXEC_FAILED',
address: contractAddress,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}

} catch (e) {
status = 0;
error = new Error('CONTRACT_EXEC_ERROR');

// emit event
const debugError = {
message: 'CONTRACT_EXEC_ERROR',
address: contractAddress,
error: e.message,
};
const eventParams = [
JSON.stringify(debugError),
];
const errorLog = new TxLog(blockchain, null, this.data, 'Error', eventParams, errorAbi);
txLogs.push(errorLog);
}
}

_checkGas('afterContractCall');
}

}

const gasUsedAfterContractExecution = gasUsed;
const gasUsedInContractExecution = gasUsedAfterContractExecution.sub(gasUsedBeforeContractExecution);
const contractExecutionCost = gasUsedInContractExecution.mul(_tx.data.gasPrice);


console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] execDuration =', (Math.round(execDuration * 1000000) / 1000000), ' seconds');



if (txCostEffective.gt(0)) {
// Gas payment
//const minerAccount = blockchain.getAccount(block.data.miner, true);
//const gasPaymentResult = minerAccount.transferFrom(accountFrom.address, txCostEffective);

const txDataGasPayment = {
from: accountFrom.address,
to: block.data.miner,
value: txCostEffective,
};
const [gasPaymentResult, gasPaymentLogs, transferReverted] = blockchain.transferEth(txDataGasPayment, block, true);
txLogs.push(...gasPaymentLogs);

if (gasPaymentResult) {
// gas payment success

} else {
// gas payment failed => reject transaction (and block)
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] Gas payment failed');
_rejectTransaction('GAS_PAYMENT_FAILED');
}

if (false) {
// DEBUG
const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();
const gasEffectivePercent = gasUsedEffective.mul(100).div(this.data.gasLimit).toNumber();
const costPercent = txCost.mul(100).div(txCostLimit).toNumber();
const costEffectivePercent = txCostEffective.mul(100).div(txCostLimit).toNumber();
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] txCostLimit =', ethers.utils.formatEther(txCostLimit));
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] txCostEffective =', ethers.utils.formatEther(txCostEffective));
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] gas % =', Math.round(gasPercent), '% =', Math.round(gasEffectivePercent), '%');
console.log(utils.getDate(), "[BLOCKCHAIN]", '[DEBUG] cost % =', Math.round(costPercent), '%', Math.round(costEffectivePercent), '%');
}


}


if (accountFrom.address != mintAddress) {
const accountFromBalanceAtEnd = blockchain.getAccountBalance(accountFrom.address);

if (accountFromBalanceAtEnd.lt(0)){
_rejectTransaction('TRANSACTION_FAILED_INSUFFICIENT_ETH_BALANCE_AT_END');
}
}


if (! status) {
// Transaction Failed

// Revert transfer
if (amountTransfered.gt(0)) {
const txDataRevert = {
...this.data,
from: this.data.to,
to: this.data.from,
value: amountTransfered,
};
const [transferResult, transferLogs, transferReverted] = blockchain.transferEth(txDataRevert, block);

// TODO: gerer le revert des internal transfers
}

txLogs.forEach(log => {
log.removed = true;
});

// Revert contract storage
if (contractExecuted) {
const contractAccount = blockchain.getAccount(contractAddress);
contractAccount.data.storage = JSON.parse(storageBackup, utils.bnJsonParser);
contractAccount.data.balance = balanceBackup;

} else if (amountTransfered) {
// on check aussi amountTransfered car le contrat peut avoir été exécuté via receive => TODO: process a revoir
const contractAccount = blockchain.getAccount(contractAddress);
if (contractAccount && contractAccount.isContract()) {
//contractAccount.data.storage = JSON.parse(storageBackup, utils.bnJsonParser);
//contractAccount.data.balance = balanceBackup;
// TODO
}

}

// Revert deployed contract
if (contractDeployed) {
// nothing to do ?
}

const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] FAILED TRANSACTION ', this.data.hash, '| gas usage percent =', gasPercent, '%', ' | Error: ', error.message);

const realExecDuration = performance.now()/1000 - execTsStart; //parseFloat(process.hrtime(execTsStart).join('.'));
const realGasUsed = toBn(Math.round(realExecDuration * cpuPricePerSecond)); // TODO: prendre en compte le storage utilisé dans le calcul du gasUsed
const realGasPercent = realGasUsed.mul(100).div(this.data.gasLimit).toNumber();
console.warn(utils.getDate(), "[BLOCKCHAIN]", '[WARNING] FAILED TRANSACTION ', this.data.hash, '| real gas usage percent =', realGasPercent, '%', ' | Error: ', error.message);

} else {
// Transaction Success

// save contract storage
Object.entries(vmStorage).forEach((entry, idx) => {
const [contractAddress, contractStorage] = entry;

const contractAccount = blockchain.getAccount(contractAddress, true);
contractAccount.data.storage = contractStorage;
contractAccount.save();
});

// show result (debug)
const gasPercent = gasUsed.mul(100).div(this.data.gasLimit).toNumber();;
console.log(utils.getDate(), "[BLOCKCHAIN]", 'SUCCECSS TRANSACTION ', this.data.hash, '| gas usage percent =', gasPercent, '%');
}


// total gas utilisé dans le block
block.data.gasUsed = block.data.gasUsed.add(gasUsed);


// assign block data on logs
txLogs.forEach((txLog, idx) => {
txLog.data.transactionIndex = transactionIndex;
txLog.data.logIndex = logIndexOffset + idx;
txLog.data.transactionHash = this.data.hash;
txLog.data.blockHash = block.hash;
txLog.data.blockNumber = block.data.number;
});


const receiptData = {
transactionHash: this.data.hash,
transactionIndex: transactionIndex,
blockNumber: block.data.number,
blockHash: block.hash,
//logsBloom: '0x0000',
contractAddress: createdContractAddress,
gasUsed,
cumulativeGasUsed: block.data.gasUsed,
effectiveGasPrice: this.data.gasPrice,
effectiveCost: this.data.gasPrice.mul(gasUsed),
logs: txLogs,
status,
}

const receipt = new Receipt(blockchain, receiptData);

return [receipt, txLogs];
}"
, executeTransactionContract: "(block) => {
let contractAddress = this.data.to;

const isNullRecipient = (! contractAddress || contractAddress.length <= 2);
if (isNullRecipient) {
throw new Error('CONTRACT_EXEC_ADDRESS_INVALID_BUG_1');
}

const contractAccount = blockchain.getAccount(contractAddress);

if (! contractAccount) {
throw new Error('CONTRACT_EXEC_ACCOUNT_NOT_FOUND');
}

// Execute existing contract method call
return blockchain.callContractMethodInVM(this.data, block);

}"
, transactionDeployContract: "(block) => {
let contractAddress = this.data.to;

let contractAccount;

if (contractAddress && contractAddress.length > 2) {
throw new Error('CONTRACT_DEPLOY_ADDRESS_INVALID_BUG_2');
}


// CONTRACT CREATION

if (! this.data.data || this.data.data.length <= 2) {
throw new Error('CONTRACT_DEPLOY_INVALID_DATA_EMPTY_CODE');
}


// build contract address
contractAddress = ethers.utils.getContractAddress(this.data).toLocaleLowerCase();

// parse contract source code & constructor parameters
const codes = ethers.utils.defaultAbiCoder.decode(["string", "string"], this.data.data);
if (! codes) {
throw new Error('CONTRACT_DEPLOY_CODE_PARSING_FAILED');
}

const sourceCode = codes[0];
const constructorParams = codes[1];
const constructorRawParams = ethers.utils.defaultAbiCoder.encode(["string"], [constructorParams]);
const codeHash = ethers.utils.id(sourceCode);
const codeAbi = null; // l'abi sera généré une fois que le contrat aura été instancié
const storageHash = ethers.utils.id('{}');

// create new account
const accountData = {
address: contractAddress,
code: sourceCode,
codeHash,
codeAbi,
storage: {},
storageHash,
};
contractAccount = blockchain.createAccount(contractAddress, accountData);


// instanciate contract + call constructor + recuperer storage
const txData = {
from: this.data.from,
to: contractAddress,
data: constructorRawParams,
value: this.data.value.toHexString(),
};
const callResult = blockchain.deployContractInVM(txData, block);
const creationLogs = callResult.logs;
const vmStorage = callResult.storage;


// emit event
const eventParams = [
contractAddress,
];
const _txData = {
...this.data,
to: blockchain.getNullAddress(),
}
const deployLog = new TxLog(blockchain, null, _txData, 'ContractCreated', eventParams, deployAbi);
creationLogs.push(deployLog);


return {
result: contractAddress,
logs: creationLogs,
storage: vmStorage,
};
}"
, timestamp: "1651573244923", status: 1, gasUsed: "10220" }, 0x769cdbd9cf5e7f437df3a7f1b8020b66933d562fcec2b04982a6db364b7e4368: { data: { blockHash: