Skip to content

Commit

Permalink
Merge pull request #1203 from ainblockchain/feature/platfowner/feature
Browse files Browse the repository at this point in the history
Make ain_getTransactionByHash() return tx execution result
  • Loading branch information
platfowner authored Aug 18, 2023
2 parents 6e1a1f3 + 6e561e2 commit bd700d2
Show file tree
Hide file tree
Showing 7 changed files with 121 additions and 46 deletions.
8 changes: 5 additions & 3 deletions common/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -574,15 +574,17 @@ const WriteDbOperations = {
*/
const TransactionStates = {
FINALIZED: 'FINALIZED',
REVERTED: 'REVERTED', // Reverted means it's failed but included in a block
REVERTED: 'REVERTED', // Failed but included in a block
EXECUTED: 'EXECUTED',
FAILED: 'FAILED', // Failed means it's failed and is NOT included in a block
FAILED: 'FAILED', // Failed and is NOT included in a block
IN_BLOCK: 'IN_BLOCK', // Included in a block, NOT reverted nor finalized.
PENDING: 'PENDING',
TIMED_OUT: 'TIMED_OUT',
};

function isTxInBlock(state) {
return state === TransactionStates.FINALIZED || state === TransactionStates.REVERTED;
return state === TransactionStates.FINALIZED || state === TransactionStates.IN_BLOCK
|| state === TransactionStates.REVERTED;
}

function isEndState(state) {
Expand Down
78 changes: 68 additions & 10 deletions consensus/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const {
PredefinedDbPaths,
StateVersions,
ValueChangedEventSources,
TransactionStates,
} = require('../common/constants');
const { ConsensusErrorCode } = require('../common/result-code');
const {
Expand Down Expand Up @@ -600,7 +601,8 @@ class Consensus {

static validateAndExecuteLastVotes(lastVotes, lastHash, number, blockTime, db, blockPool, eventSource) {
if (number === 0) return;
if (!db.executeTransactionList(lastVotes, true, false, number, blockTime, eventSource)) {
const txsRes = db.executeTransactionList(lastVotes, true, false, number, blockTime, eventSource);
if (!txsRes) {
throw new ConsensusError({
code: ConsensusErrorCode.EXECUTING_LAST_VOTES_FAILURE,
message: `Failed to execute last votes`,
Expand Down Expand Up @@ -628,6 +630,7 @@ class Consensus {
level: 'error'
});
}
return txsRes;
}

// Cross-check the offenses in proposalTx & the evidence in proposalBlock
Expand Down Expand Up @@ -726,6 +729,7 @@ class Consensus {
level: 'error'
});
}
return txsRes;
}

static validateStateProofHash(expectedStateProofHash, block, db, node, takeSnapshot) {
Expand Down Expand Up @@ -774,16 +778,17 @@ class Consensus {
});
}
// Try executing the proposal tx on the proposal block's db state
const proposalTxExecRes = tempDb.executeTransaction(executableTx, true, false, number, blockTime);
const txRes = tempDb.executeTransaction(executableTx, true, false, number, blockTime);
tempDb.destroyDb();
if (CommonUtil.isFailedTx(proposalTxExecRes)) {
if (CommonUtil.isFailedTx(txRes)) {
throw new ConsensusError({
code: ConsensusErrorCode.EXECUTING_PROPOSAL_FAILURE,
message: `Failed to execute the proposal tx: ${JSON.stringify(proposalTxExecRes)}`,
message: `Failed to execute the proposal tx: ${JSON.stringify(txRes)}`,
level: 'error'
});
}
node.tp.addTransaction(executableTx);
return txRes;
}

static addBlockToBlockPool(block, proposalTx, db, blockPool) {
Expand All @@ -797,7 +802,54 @@ class Consensus {
blockPool.addToHashToDbMap(block.hash, db);
}

static validateAndExecuteBlockOnDb(rawBlock, node, stateVersionPrefix, proposalTx = null, takeSnapshot = false) {
static updateTransactionInfoForBlock(node, voteTxs, proposalTx, transactions, voteTxsRes, proposalTxRes, txsRes) {
const trackedAt = Date.now();
for (let i = 0; i < voteTxs.length; i++) {
const voteTx = voteTxs[i];
const voteResult = voteTxsRes[i];
const tracked = node.tp.transactionTracker.get(voteTx.hash) || {};
const beforeState = _.get(tracked, 'state', null);
node.tp.updateTransactionInfo(voteTx.hash, {
state: TransactionStates.IN_BLOCK,
exec_result: voteResult,
tracked_at: trackedAt,
});
if (node.eh) {
node.eh.emitTxStateChanged(voteTx, beforeState, TransactionStates.IN_BLOCK);
}
}
// NOTE(platfowner): Genesis block has no porposal transaction.
if (proposalTx) {
const trackedProposalTx = node.tp.transactionTracker.get(proposalTx.hash) || {};
const beforeState = _.get(trackedProposalTx, 'state', null);
node.tp.updateTransactionInfo(proposalTx.hash, {
state: TransactionStates.IN_BLOCK,
exec_result: proposalTxRes,
tracked_at: trackedAt,
});
if (node.eh) {
node.eh.emitTxStateChanged(proposalTx, beforeState, TransactionStates.IN_BLOCK);
}
}
for (let i = 0; i < transactions.length; i++) {
const tx = transactions[i];
const txResult = txsRes[i];
const tracked = node.tp.transactionTracker.get(tx.hash) || {};
const beforeState = _.get(tracked, 'state', null);
const txState =
CommonUtil.isFailedTx(txResult) ? TransactionStates.REVERTED : TransactionStates.IN_BLOCK;
node.tp.updateTransactionInfo(tx.hash, {
state: txState,
exec_result: txResult,
tracked_at: trackedAt,
});
if (node.eh) {
node.eh.emitTxStateChanged(tx, beforeState, txState);
}
}
}

static validateAndExecuteBlockOnDb(rawBlock, node, stateVersionPrefix, proposalTx, updateTxInfo, takeSnapshot = false) {
const block = Block.parse(rawBlock);
if (!block) {
throw new ConsensusError({
Expand All @@ -822,23 +874,29 @@ class Consensus {
node.bc.lastBlockNumber(), node.stateManager.getFinalVersion(), node.bp);
const db = Consensus.getNewDbForProposal(number, baseVersion, stateVersionPrefix, node);

let voteTxsRes = null;
let proposalTxRes = null;
let txsRes = null;
try {
Consensus.validateBlockNumberAndHashes(block, prevBlock, node.bc.genesisBlockHash);
Consensus.validateValidators(validators, number, baseVersion, node);
Consensus.validateProposer(number, prevBlockLastVotesHash, epoch, validators, proposer);
Consensus.validateAndExecuteLastVotes(last_votes, last_hash, number, timestamp, db, node.bp, ValueChangedEventSources.BLOCK);
voteTxsRes = Consensus.validateAndExecuteLastVotes(last_votes, last_hash, number, timestamp, db, node.bp, ValueChangedEventSources.BLOCK);
Consensus.validateAndExecuteOffensesAndEvidence(
evidence, validators, prevBlockMajority, number, timestamp, proposalTx, db, ValueChangedEventSources.BLOCK);
Consensus.validateAndExecuteTransactions(
txsRes = Consensus.validateAndExecuteTransactions(
transactions, receipts, number, timestamp, gas_amount_total, gas_cost_total, db, node, ValueChangedEventSources.BLOCK);
db.applyBandagesForBlockNumber(number);
Consensus.validateStateProofHash(state_proof_hash, block, db, node, takeSnapshot);
Consensus.executeProposalTx(proposalTx, number, timestamp, db, node);
proposalTxRes = Consensus.executeProposalTx(proposalTx, number, timestamp, db, node);
Consensus.addBlockToBlockPool(block, proposalTx, db, node.bp);
} catch (e) {
db.destroyDb();
throw e;
}
if (updateTxInfo) {
Consensus.updateTransactionInfoForBlock(node, last_votes, proposalTx, transactions, voteTxsRes, proposalTxRes, txsRes);
}
}

/**
Expand All @@ -854,7 +912,7 @@ class Consensus {
`${proposalBlock.number} / ${proposalBlock.epoch} / ${proposalBlock.hash} / ${proposalBlock.proposer}\n` +
`${proposalTx.hash} / ${proposalTx.address}`);

Consensus.validateAndExecuteBlockOnDb(proposalBlock, this.node, StateVersions.POOL, proposalTx);
Consensus.validateAndExecuteBlockOnDb(proposalBlock, this.node, StateVersions.POOL, proposalTx, true);

if (proposalBlock.number > 1 && !this.node.bp.longestNotarizedChainTips.includes(proposalBlock.last_hash)) {
throw new ConsensusError({
Expand Down Expand Up @@ -1144,7 +1202,7 @@ class Consensus {
proposalTx = i < chain.length - 1 ? ConsensusUtil.filterProposalFromVotes(chain[i + 1].last_votes) : null;
logger.debug(`[${LOG_HEADER}] applying block ${JSON.stringify(block)}`);
try {
Consensus.validateAndExecuteBlockOnDb(block, this.node, StateVersions.SNAP, proposalTx);
Consensus.validateAndExecuteBlockOnDb(block, this.node, StateVersions.SNAP, proposalTx, false);
} catch (e) {
logger.error(`[${LOG_HEADER}] Failed to validate and execute block ${block.number}: ${e}`);
return null;
Expand Down
10 changes: 5 additions & 5 deletions node/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -806,11 +806,11 @@ class BlockchainNode {
// will not be included in the tx pool, as they can't be included in a block
// anyway, and may be used for attacks on blockchain nodes.
if (!isDryrun && !CommonUtil.txPrecheckFailed(result)) {
this.tp.addTransaction(executableTx);
this.tp.addTransaction(executableTx, result);
}
} else {
if (!isDryrun) {
this.tp.addTransaction(executableTx, true);
this.tp.addTransaction(executableTx, result, true);
}
}

Expand Down Expand Up @@ -851,7 +851,7 @@ class BlockchainNode {
const latestDb = this.createTempDb(latestSnapshotStateVersion, `${StateVersions.LOAD}:${number}`, number);
this.bp.addToHashToDbMap(block.hash, latestDb);
} else {
Consensus.validateAndExecuteBlockOnDb(block, this, StateVersions.LOAD, proposalTx, true);
Consensus.validateAndExecuteBlockOnDb(block, this, StateVersions.LOAD, proposalTx, true, true);
if (number === 0) {
this.bc.addBlockToChainAndWriteToDisk(block, false);
this.cloneAndFinalizeVersion(this.bp.hashToDb.get(block.hash).stateVersion, 0);
Expand Down Expand Up @@ -905,7 +905,7 @@ class BlockchainNode {
const proposalTx = i < validBlocks.length - 1 ?
ConsensusUtil.filterProposalFromVotes(validBlocks[i + 1].last_votes) : null;
try {
Consensus.validateAndExecuteBlockOnDb(block, this, StateVersions.SEGMENT, proposalTx, true);
Consensus.validateAndExecuteBlockOnDb(block, this, StateVersions.SEGMENT, proposalTx, true, true);
this.tryFinalizeChain();
} catch (e) {
logger.info(`[${LOG_HEADER}] Failed to add new block (${block.number} / ${block.hash}) to chain: ${e.stack}`);
Expand Down Expand Up @@ -1044,7 +1044,7 @@ class BlockchainNode {
lastFinalizedBlock = blockToFinalize;
logger.debug(`[${LOG_HEADER}] Finalized a block of number ${blockToFinalize.number} and ` +
`hash ${blockToFinalize.hash}`);
this.tp.cleanUpForNewBlock(blockToFinalize);
this.tp.cleanUpForFinalizedBlock(blockToFinalize);
if (!CommonUtil.isEmpty(blockToFinalize.evidence)) {
Object.values(blockToFinalize.evidence).forEach((evidenceList) => {
evidenceList.forEach((val) => {
Expand Down
21 changes: 13 additions & 8 deletions test/integration/event_handler.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -371,12 +371,15 @@ describe('Event Handler Test', function() {
if (eventTriggeredCnt === 0) {
expect(txState.before).to.equal(null);
expect(txState.after).to.equal(TransactionStates.EXECUTED);
eventTriggeredCnt++;
} else {
} else if (eventTriggeredCnt === 1) {
expect(txState.before).to.equal(TransactionStates.EXECUTED);
expect(txState.after).to.equal(TransactionStates.IN_BLOCK);
} else if (eventTriggeredCnt === 2) {
expect(txState.before).to.equal(TransactionStates.IN_BLOCK);
expect(txState.after).to.equal(TransactionStates.FINALIZED);
done();
}
eventTriggeredCnt++;
}
});
const txResult = setValue(serverList[EVENT_HANDLER_NODE_INDEX], targetPath, 'change')
Expand All @@ -402,15 +405,17 @@ describe('Event Handler Test', function() {
if (eventTriggeredCnt < 2) {
expect(txState.before).to.equal(null);
expect(txState.after).to.equal(TransactionStates.EXECUTED);
eventTriggeredCnt++;
} else {
} else if (eventTriggeredCnt < 4) {
expect(txState.before).to.equal(TransactionStates.EXECUTED);
expect(txState.after).to.equal(TransactionStates.IN_BLOCK);
} else if (eventTriggeredCnt < 6) {
expect(txState.before).to.equal(TransactionStates.IN_BLOCK);
expect(txState.after).to.equal(TransactionStates.FINALIZED);
eventTriggeredCnt++;
if (eventTriggeredCnt === 4) {
if (eventTriggeredCnt === 5) {
done();
}
}
eventTriggeredCnt++;
}
});
const txResult = setValue(serverList[EVENT_HANDLER_NODE_INDEX], targetPath, 'change')
Expand Down Expand Up @@ -443,12 +448,12 @@ describe('Event Handler Test', function() {
if (eventTriggeredCnt === 0) {
expect(txState.before).to.equal(null);
expect(txState.after).to.equal(TransactionStates.PENDING);
eventTriggeredCnt++;
} else {
} else if (eventTriggeredCnt === 1) {
expect(txState.before).to.equal(TransactionStates.PENDING);
expect(txState.after).to.equal(TransactionStates.REVERTED);
done();
}
eventTriggeredCnt++;
}
});
registerFilter(wsClient, filterId, BlockchainEventTypes.TX_STATE_CHANGED, config);
Expand Down
8 changes: 5 additions & 3 deletions test/unit/tx-pool.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,13 @@ describe('TransactionPool', () => {
});

it('add an executed transaction', () => {
node.tp.addTransaction(txToAdd, true);
const execResult = { code: 0 };
node.tp.addTransaction(txToAdd, execResult, true);
const addedTx = node.tp.transactions.get(node.account.address).find((t) => t.hash === txToAdd.hash);
assert.deepEqual(eraseTxCreatedAt(addedTx), eraseTxCreatedAt(txToAdd));
const txInfo = node.getTransactionByHash(txToAdd.hash);
expect(txInfo.state).to.equal(TransactionStates.EXECUTED);
assert.deepEqual(txInfo.exec_result, execResult);
});
});

Expand Down Expand Up @@ -764,7 +766,7 @@ describe('TransactionPool', () => {
nodes = [node2, node3, node4];
});

it('cleanUpForNewBlock()', () => {
it('cleanUpForFinalizedBlock()', () => {
const number = 1;
const lastBlock = node.bc.genesisBlock;
const transactions = node.tp.getValidTransactions();
Expand All @@ -787,7 +789,7 @@ describe('TransactionPool', () => {
}));
node.tp.addTransaction(newTransactions[node.account.address][i]);
}
node.tp.cleanUpForNewBlock(block);
node.tp.cleanUpForFinalizedBlock(block);
assert.deepEqual(newTransactions, Object.fromEntries(node.tp.transactions));
});

Expand Down
2 changes: 1 addition & 1 deletion tools/proof-hash/verifyBlock.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ async function verifyBlock(snapshotFile, blockFileList) {
// TODO(platfowner): Make the block execution code work.
console.log(`\n* Executing block on db...`);
try {
Consensus.validateAndExecuteBlockOnDb(block, node, 'verifyBlock', proposalTx);
Consensus.validateAndExecuteBlockOnDb(block, node, 'verifyBlock', proposalTx, false);
} catch (e) {
console.log(`Failed to validate and excute block ${block.number}: ${e}`);
process.exit(0);
Expand Down
Loading

0 comments on commit bd700d2

Please sign in to comment.