Skip to content

Commit

Permalink
Feature/extend ethereumflow (#1447)
Browse files Browse the repository at this point in the history
* init progress

Signed-off-by: Petar Tonev <petar.tonev@limechain.tech>

* Extend ethereum flow to accept maxChunks

Signed-off-by: ochikov <ognyan@limechain.tech>

---------

Signed-off-by: Petar Tonev <petar.tonev@limechain.tech>
Signed-off-by: ochikov <ognyan@limechain.tech>
Co-authored-by: Petar Tonev <petar.tonev@limechain.tech>
  • Loading branch information
ochikov and petreze authored Feb 6, 2023
1 parent 70145a0 commit 5448744
Show file tree
Hide file tree
Showing 2 changed files with 333 additions and 12 deletions.
43 changes: 31 additions & 12 deletions src/EthereumFlow.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

/**
Expand Down Expand Up @@ -177,14 +195,15 @@ export default class EthereumFlow {
}

ethereumTransaction
.setEthereumData(this._ethereumData.toBytes())
.setEthereumData(ethereumTransactionDataBytes)
.setCallDataFileId(this._callDataFileId);
} else if (ethereumTransactionDataBytes.length <= 5120) {
ethereumTransaction.setEthereumData(ethereumTransactionDataBytes);
} else {
const fileId = await createFile(
this._ethereumData.callData,
client
client,
this._maxChunks
);

this._ethereumData.callData = new Uint8Array();
Expand All @@ -203,9 +222,10 @@ export default class EthereumFlow {
* @template MirrorChannelT
* @param {Uint8Array} callData
* @param {import("./client/Client.js").default<ChannelT, MirrorChannelT>} client
* @param {?number} maxChunks
* @returns {Promise<FileId>}
*/
async function createFile(callData, client) {
async function createFile(callData, client, maxChunks) {
const hexedCallData = hex.encode(callData);

const fileId = /** @type {FileId} */ (
Expand All @@ -224,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;
Expand Down
302 changes: 302 additions & 0 deletions test/unit/EthereumFlowMocking.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});

0 comments on commit 5448744

Please sign in to comment.