From 2b8864911a33cc80df67b34bed722c461226e497 Mon Sep 17 00:00:00 2001 From: Petar Tonev Date: Mon, 30 Jan 2023 12:35:43 +0200 Subject: [PATCH 1/2] init progress Signed-off-by: Petar Tonev --- src/EthereumFlow.js | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/EthereumFlow.js b/src/EthereumFlow.js index 2e389199d..400ffee92 100644 --- a/src/EthereumFlow.js +++ b/src/EthereumFlow.js @@ -84,6 +84,24 @@ export default class EthereumFlow { if (props.maxGasAllowance != null) { this.setMaxGasAllowanceHbar(props.maxGasAllowance); } + + this._maxChunks = null; + } + + /** + * @returns {number | null} + */ + get maxChunks() { + return this._maxChunks; + } + + /** + * @param {number} maxChunks + * @returns {this} + */ + setMaxChunks(maxChunks) { + this._maxChunks = maxChunks; + return this; } /** @@ -177,7 +195,7 @@ export default class EthereumFlow { } ethereumTransaction - .setEthereumData(this._ethereumData.toBytes()) + .setEthereumData(ethereumTransactionDataBytes) .setCallDataFileId(this._callDataFileId); } else if (ethereumTransactionDataBytes.length <= 5120) { ethereumTransaction.setEthereumData(ethereumTransactionDataBytes); From a9d761e10ce931bc07d90735db93db98f0292fee Mon Sep 17 00:00:00 2001 From: ochikov Date: Sun, 5 Feb 2023 18:00:31 +0200 Subject: [PATCH 2/2] Extend ethereum flow to accept maxChunks Signed-off-by: ochikov --- src/EthereumFlow.js | 23 +-- test/unit/EthereumFlowMocking.js | 302 +++++++++++++++++++++++++++++++ 2 files changed, 314 insertions(+), 11 deletions(-) diff --git a/src/EthereumFlow.js b/src/EthereumFlow.js index 400ffee92..3f89562cd 100644 --- a/src/EthereumFlow.js +++ b/src/EthereumFlow.js @@ -202,7 +202,8 @@ export default class EthereumFlow { } else { const fileId = await createFile( this._ethereumData.callData, - client + client, + this._maxChunks ); this._ethereumData.callData = new Uint8Array(); @@ -221,9 +222,10 @@ export default class EthereumFlow { * @template MirrorChannelT * @param {Uint8Array} callData * @param {import("./client/Client.js").default} client + * @param {?number} maxChunks * @returns {Promise} */ -async function createFile(callData, client) { +async function createFile(callData, client, maxChunks) { const hexedCallData = hex.encode(callData); const fileId = /** @type {FileId} */ ( @@ -242,15 +244,14 @@ async function createFile(callData, client) { ); if (callData.length > 4096) { - await ( - await new FileAppendTransaction() - .setFileId(fileId) - .setContents( - hexedCallData.substring(4096, hexedCallData.length) - ) - .setChunkSize(4096) - .execute(client) - ).getReceipt(client); + let fileAppendTransaction = new FileAppendTransaction() + .setFileId(fileId) + .setContents(hexedCallData.substring(4096, hexedCallData.length)); + if (maxChunks != null) { + fileAppendTransaction.setMaxChunks(maxChunks); + } + + await (await fileAppendTransaction.execute(client)).getReceipt(client); } return fileId; diff --git a/test/unit/EthereumFlowMocking.js b/test/unit/EthereumFlowMocking.js index 62bfddaa6..895f920ca 100644 --- a/test/unit/EthereumFlowMocking.js +++ b/test/unit/EthereumFlowMocking.js @@ -200,4 +200,306 @@ describe("EthereumFlowMocking", function () { await new EthereumFlow().setEthereumData(encoded).execute(client) ).getReceipt(client); }); + + it("extracts the calldata if it's too large and try to deploy it by chunks, but thrown", async function () { + this.timeout(10000); + + const decoded = rlp.decode(bytes); + const longCallData = "0x" + "00".repeat(7000); + decoded[5] = longCallData; + const encoded = hex.decode(rlp.encode(decoded)); + decoded[5] = "0x"; + const encodedWithoutCallData = hex.decode(rlp.encode(decoded)); + + ({ client, servers } = await Mocker.withResponses([ + [ + { + call: (request) => { + const transactionBody = proto.TransactionBody.decode( + proto.SignedTransaction.decode( + request.signedTransactionBytes + ).bodyBytes + ); + + const fileCreate = transactionBody.fileCreate; + expect( + `0x${fileCreate.contents.toString()}` + ).to.deep.equal( + // includes 0x prefix + longCallData.substring(0, 4098) + ); + + return { response: TRANSACTION_RESPONSE_SUCCESS }; + }, + }, + { + response: { + transactionGetReceipt: { + header: { + nodeTransactionPrecheckCode: + proto.ResponseCodeEnum.OK, + }, + receipt: { + status: proto.ResponseCodeEnum.SUCCESS, + fileID: callDataFileId._toProtobuf(), + }, + }, + }, + }, + { + call: (request) => { + const transactionBody = proto.TransactionBody.decode( + proto.SignedTransaction.decode( + request.signedTransactionBytes + ).bodyBytes + ); + + const fileAppend = transactionBody.fileAppend; + expect(fileAppend.contents.toString()).to.deep.equal( + longCallData.substring(4098, 8194) + ); + + return { response: TRANSACTION_RESPONSE_SUCCESS }; + }, + }, + { + response: { + transactionGetReceipt: { + header: { + nodeTransactionPrecheckCode: + proto.ResponseCodeEnum.OK, + }, + receipt: { + status: proto.ResponseCodeEnum.SUCCESS, + fileID: callDataFileId._toProtobuf(), + }, + }, + }, + }, + { + call: (request) => { + const transactionBody = proto.TransactionBody.decode( + proto.SignedTransaction.decode( + request.signedTransactionBytes + ).bodyBytes + ); + + const fileAppend = transactionBody.fileAppend; + expect(fileAppend.contents.toString()).to.deep.equal( + longCallData.substring(8194, 12290) + ); + + return { response: TRANSACTION_RESPONSE_SUCCESS }; + }, + }, + { + response: { + transactionGetReceipt: { + header: { + nodeTransactionPrecheckCode: + proto.ResponseCodeEnum.OK, + }, + receipt: { + status: proto.ResponseCodeEnum.SUCCESS, + fileID: callDataFileId._toProtobuf(), + }, + }, + }, + }, + // Yes, you need 4 receipt responses here. One happens in + // `FileAppendTransaction.executeAll()` in a loop, and the next + // is for `TransactionResponse.getReceipt()` + { response: TRANSACTION_RECEIPT_SUCCESS_RESPONSE }, + { response: TRANSACTION_RECEIPT_SUCCESS_RESPONSE }, + { response: TRANSACTION_RECEIPT_SUCCESS_RESPONSE }, + { response: TRANSACTION_RECEIPT_SUCCESS_RESPONSE }, + { response: TRANSACTION_RECEIPT_SUCCESS_RESPONSE }, + { response: TRANSACTION_RECEIPT_SUCCESS_RESPONSE }, + { response: TRANSACTION_RECEIPT_SUCCESS_RESPONSE }, + { + call: (request) => { + const transactionBody = proto.TransactionBody.decode( + proto.SignedTransaction.decode( + request.signedTransactionBytes + ).bodyBytes + ); + + const ethereumTransaction = + transactionBody.ethereumTransaction; + expect(ethereumTransaction.ethereumData).to.deep.equal( + encodedWithoutCallData + ); + expect( + FileId._fromProtobuf( + ethereumTransaction.callData + ).toString() + ).to.equal(callDataFileId.toString()); + + return { response: TRANSACTION_RESPONSE_SUCCESS }; + }, + }, + { response: TRANSACTION_RECEIPT_SUCCESS_RESPONSE }, + ], + ])); + + let error = null; + try { + await new EthereumFlow() + .setEthereumData(encoded) + .setMaxChunks(2) + .execute(client); + } catch (err) { + error = err; + } + expect(error).to.be.an("Error"); + }); + + it("extracts the calldata if it's too large and deploy it by the right amount of chunks", async function () { + this.timeout(10000); + + const decoded = rlp.decode(bytes); + const longCallData = "0x" + "00".repeat(7000); + decoded[5] = longCallData; + const encoded = hex.decode(rlp.encode(decoded)); + decoded[5] = "0x"; + const encodedWithoutCallData = hex.decode(rlp.encode(decoded)); + + ({ client, servers } = await Mocker.withResponses([ + [ + { + call: (request) => { + const transactionBody = proto.TransactionBody.decode( + proto.SignedTransaction.decode( + request.signedTransactionBytes + ).bodyBytes + ); + + const fileCreate = transactionBody.fileCreate; + expect( + `0x${fileCreate.contents.toString()}` + ).to.deep.equal( + // includes 0x prefix + longCallData.substring(0, 4098) + ); + + return { response: TRANSACTION_RESPONSE_SUCCESS }; + }, + }, + { + response: { + transactionGetReceipt: { + header: { + nodeTransactionPrecheckCode: + proto.ResponseCodeEnum.OK, + }, + receipt: { + status: proto.ResponseCodeEnum.SUCCESS, + fileID: callDataFileId._toProtobuf(), + }, + }, + }, + }, + { + call: (request) => { + const transactionBody = proto.TransactionBody.decode( + proto.SignedTransaction.decode( + request.signedTransactionBytes + ).bodyBytes + ); + + const fileAppend = transactionBody.fileAppend; + expect(fileAppend.contents.toString()).to.deep.equal( + longCallData.substring(4098, 8194) + ); + + return { response: TRANSACTION_RESPONSE_SUCCESS }; + }, + }, + { + response: { + transactionGetReceipt: { + header: { + nodeTransactionPrecheckCode: + proto.ResponseCodeEnum.OK, + }, + receipt: { + status: proto.ResponseCodeEnum.SUCCESS, + fileID: callDataFileId._toProtobuf(), + }, + }, + }, + }, + { + call: (request) => { + const transactionBody = proto.TransactionBody.decode( + proto.SignedTransaction.decode( + request.signedTransactionBytes + ).bodyBytes + ); + + const fileAppend = transactionBody.fileAppend; + expect(fileAppend.contents.toString()).to.deep.equal( + longCallData.substring(8194, 12290) + ); + + return { response: TRANSACTION_RESPONSE_SUCCESS }; + }, + }, + { + response: { + transactionGetReceipt: { + header: { + nodeTransactionPrecheckCode: + proto.ResponseCodeEnum.OK, + }, + receipt: { + status: proto.ResponseCodeEnum.SUCCESS, + fileID: callDataFileId._toProtobuf(), + }, + }, + }, + }, + // Yes, you need 4 receipt responses here. One happens in + // `FileAppendTransaction.executeAll()` in a loop, and the next + // is for `TransactionResponse.getReceipt()` + { response: TRANSACTION_RECEIPT_SUCCESS_RESPONSE }, + { response: TRANSACTION_RECEIPT_SUCCESS_RESPONSE }, + { response: TRANSACTION_RECEIPT_SUCCESS_RESPONSE }, + { response: TRANSACTION_RECEIPT_SUCCESS_RESPONSE }, + { response: TRANSACTION_RECEIPT_SUCCESS_RESPONSE }, + { response: TRANSACTION_RECEIPT_SUCCESS_RESPONSE }, + { response: TRANSACTION_RECEIPT_SUCCESS_RESPONSE }, + { + call: (request) => { + const transactionBody = proto.TransactionBody.decode( + proto.SignedTransaction.decode( + request.signedTransactionBytes + ).bodyBytes + ); + + const ethereumTransaction = + transactionBody.ethereumTransaction; + expect(ethereumTransaction.ethereumData).to.deep.equal( + encodedWithoutCallData + ); + expect( + FileId._fromProtobuf( + ethereumTransaction.callData + ).toString() + ).to.equal(callDataFileId.toString()); + + return { response: TRANSACTION_RESPONSE_SUCCESS }; + }, + }, + { response: TRANSACTION_RECEIPT_SUCCESS_RESPONSE }, + ], + ])); + + await ( + await new EthereumFlow() + .setEthereumData(encoded) + .setMaxChunks(3) + .execute(client) + ).getReceipt(client); + }); });