Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added record for failed transaction #1643

Merged
merged 1 commit into from
Jun 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions src/RecordStatusError.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*-
* ‌
* Hedera JavaScript SDK
* ​
* Copyright (C) 2020 - 2022 Hedera Hashgraph, LLC
* ​
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ‍
*/

import StatusError from "./StatusError.js";

/**
* @typedef {import("./Status.js").default} Status
* @typedef {import("./transaction/TransactionId.js").default} TransactionId
* @typedef {import("./transaction/TransactionRecord").default} TransactionRecord
*/

export default class RecordStatusError extends StatusError {
/**
* @param {object} props
* @param {TransactionRecord} props.transactionRecord
* @param {Status} props.status
* @param {TransactionId} props.transactionId
*/
constructor(props) {
super(
props,
`Record for transaction ${props.transactionId.toString()} contained error status ${props.status.toString()}`
);

/**
* @type {TransactionRecord}
* @readonly
*/
this.transactionRecord = props.transactionRecord;
}
}
37 changes: 31 additions & 6 deletions src/transaction/TransactionRecordQuery.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import TransactionId from "./TransactionId.js";
import Status from "../Status.js";
import PrecheckStatusError from "../PrecheckStatusError.js";
import ReceiptStatusError from "../ReceiptStatusError.js";
import RecordStatusError from "../RecordStatusError.js";
import { ExecutionState } from "../Executable.js";
import * as HashgraphProto from "@hashgraph/proto";

Expand Down Expand Up @@ -288,12 +289,21 @@ export default class TransactionRecordQuery extends Query {
? nodeTransactionPrecheckCode
: proto.ResponseCodeEnum.OK
);

switch (status) {
case Status.Ok:
// Do nothing
break;

case Status.ContractRevertExecuted:
return new RecordStatusError({
status,
transactionId: this._getTransactionId(),
transactionRecord: TransactionRecord._fromProtobuf({
transactionRecord:
response.transactionGetRecord?.transactionRecord,
}),
});

default:
return new PrecheckStatusError({
status,
Expand All @@ -320,11 +330,26 @@ export default class TransactionRecordQuery extends Query {

status = Status._fromCode(receiptStatusError);

return new ReceiptStatusError({
status,
transactionId: this._getTransactionId(),
transactionReceipt: TransactionReceipt._fromProtobuf({ receipt }),
});
switch (status) {
case Status.ContractRevertExecuted:
return new RecordStatusError({
status,
transactionId: this._getTransactionId(),
transactionRecord: TransactionRecord._fromProtobuf({
transactionRecord:
response.transactionGetRecord?.transactionRecord,
}),
});

default:
return new ReceiptStatusError({
status,
transactionId: this._getTransactionId(),
transactionReceipt: TransactionReceipt._fromProtobuf({
receipt,
}),
});
}
}

/**
Expand Down
18 changes: 18 additions & 0 deletions src/transaction/TransactionResponse.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ export default class TransactionResponse {
}

/**
* getRecord is calling getReceipt and in case the receipt status code is not OK, only the receipt is returned.
*
* @param {Client} client
* @returns {Promise<TransactionRecord>}
*/
Expand All @@ -101,6 +103,22 @@ export default class TransactionResponse {
return this.getRecordQuery().execute(client);
}

/**
* getVerboseRecord is calling getReceipt and in case the receipt status code is not OK, the record is returned.
*
* @param {Client} client
* @returns {Promise<TransactionRecord>}
*/
async getVerboseRecord(client) {
try {
// The receipt needs to be called in order to wait for transaction to be included in the consensus. Otherwise we are going to get "DUPLICATE_TRANSACTION".
await this.getReceiptQuery().execute(client);
return this.getRecordQuery().execute(client);
} catch (e) {
return this.getRecordQuery().execute(client);
}
}

/**
* @param {Signer} signer
* @returns {Promise<TransactionReceipt>}
Expand Down
89 changes: 89 additions & 0 deletions test/integration/TransactionRecordIntegrationTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import {
FileCreateTransaction,
ContractCreateTransaction,
FileAppendTransaction,
ContractExecuteTransaction,
ContractFunctionParameters,
} from "../../src/exports.js";
import IntegrationTestEnv from "./client/NodeIntegrationTestEnv.js";

describe("TransactionRecord", function () {
let env;
const contractByteCode =
"0x608060405234801561001057600080fd5b50610594806100206000396000f3fe608060405234801561001057600080fd5b50600436106100625760003560e01c8063368b87721461006757806353f2a8a31461009057806380acb9e6146100b1578063ce6d41de146100c4578063e21f37ce146100cc578063f7753390146100d4575b600080fd5b61007a610075366004610406565b6100dc565b60405161008791906104ba565b60405180910390f35b6100a361009e3660046104a2565b610112565b604051908152602001610087565b61007a6100bf366004610441565b610177565b61007a6101c2565b61007a610254565b61007a6102e2565b80516060906100f29060009060208501906102ef565b50506040805180820190915260028152614f4b60f01b6020820152919050565b6000606482116101735760405162461bcd60e51b815260206004820152602260248201527f546865206e756d62657220697320736d616c6c6572207468616e2068756e6472604482015261195960f21b606482015260840160405180910390fd5b5090565b815160609061018d9060009060208601906102ef565b5081516101a19060019060208501906102ef565b50506040805180820190915260028152614f4b60f01b602082015292915050565b6060600080546101d19061050d565b80601f01602080910402602001604051908101604052809291908181526020018280546101fd9061050d565b801561024a5780601f1061021f5761010080835404028352916020019161024a565b820191906000526020600020905b81548152906001019060200180831161022d57829003601f168201915b5050505050905090565b600080546102619061050d565b80601f016020809104026020016040519081016040528092919081815260200182805461028d9061050d565b80156102da5780601f106102af576101008083540402835291602001916102da565b820191906000526020600020905b8154815290600101906020018083116102bd57829003601f168201915b505050505081565b600180546102619061050d565b8280546102fb9061050d565b90600052602060002090601f01602090048101928261031d5760008555610363565b82601f1061033657805160ff1916838001178555610363565b82800160010185558215610363579182015b82811115610363578251825591602001919060010190610348565b506101739291505b80821115610173576000815560010161036b565b600082601f83011261038f578081fd5b813567ffffffffffffffff808211156103aa576103aa610548565b604051601f8301601f19908116603f011681019082821181831017156103d2576103d2610548565b816040528381528660208588010111156103ea578485fd5b8360208701602083013792830160200193909352509392505050565b600060208284031215610417578081fd5b813567ffffffffffffffff81111561042d578182fd5b6104398482850161037f565b949350505050565b60008060408385031215610453578081fd5b823567ffffffffffffffff8082111561046a578283fd5b6104768683870161037f565b9350602085013591508082111561048b578283fd5b506104988582860161037f565b9150509250929050565b6000602082840312156104b3578081fd5b5035919050565b6000602080835283518082850152825b818110156104e6578581018301518582016040015282016104ca565b818111156104f75783604083870101525b50601f01601f1916929092016040019392505050565b600181811c9082168061052157607f821691505b6020821081141561054257634e487b7160e01b600052602260045260246000fd5b50919050565b634e487b7160e01b600052604160045260246000fdfea26469706673582212206369861146f0c706a48e951145ffca721f1fe7e98b0fb535d02d2e0b4195be0664736f6c63430008040033";

before(async function () {
env = await IntegrationTestEnv.new({ throwaway: true });
});

it("should return the verbose record log when a transaction failed ", async function () {
this.timeout(120000);

const privateKey = env.operatorKey;

// Create a file on Hedera and store the bytecode
const fileCreateTx = new FileCreateTransaction()
.setKeys([privateKey])
.freezeWith(env.client);
const fileCreateSign = await fileCreateTx.sign(privateKey);
const fileCreateSubmit = await fileCreateSign.execute(env.client);
const fileCreateRx = await fileCreateSubmit.getReceipt(env.client);
const bytecodeFileId = fileCreateRx.fileId;
console.log(`- The bytecode file ID is: ${bytecodeFileId} \n`);

//Append contents to the file
const fileAppendTx = new FileAppendTransaction()
.setFileId(bytecodeFileId)
.setContents(contractByteCode)
.setMaxChunks(10)
.freezeWith(env.client);
const fileAppendSign = await fileAppendTx.sign(privateKey);
const fileAppendSubmit = await fileAppendSign.execute(env.client);
const fileAppendRx = await fileAppendSubmit.getReceipt(env.client);
console.log("Status of file append is", fileAppendRx.status.toString());

// Instantiate the contract instance
const contractTx = await new ContractCreateTransaction()
//Set the file ID of the Hedera file storing the bytecode
.setBytecodeFileId(bytecodeFileId)
//Set the gas to instantiate the contract
.setGas(100000)
//Provide the constructor parameters for the contract
.setConstructorParameters();

//Submit the transaction to the Hedera test network
const contractResponse = await contractTx.execute(env.client);

//Get the receipt of the file create transaction
const contractReceipt = await contractResponse.getReceipt(env.client);

//Get the smart contract ID
const newContractId = contractReceipt.contractId;

//Log the smart contract ID
console.log("The smart contract ID is " + newContractId);

const contractExecuteTx = new ContractExecuteTransaction()
.setContractId(newContractId)
.setGas(750000)
.setFunction(
"setDataRequire",
new ContractFunctionParameters().addUint256(10)
)
.freezeWith(env.client);

try {
const signedTx = await contractExecuteTx.sign(privateKey);
const contractExecuteSubmit = await signedTx.execute(env.client);

// Get transaction record information
await contractExecuteSubmit.getVerboseRecord(env.client);
} catch (err) {
expect(err.transactionRecord.transactionFee).not.to.be.null;
}
});

after(async function () {
await env.close();
});
});