From 96e6c8af65f7ada96d63e0060b33ca25626a8908 Mon Sep 17 00:00:00 2001 From: Logan Nguyen Date: Tue, 23 Apr 2024 11:34:10 -0400 Subject: [PATCH] alpha-1 Signed-off-by: Logan Nguyen --- .github/workflows/acceptance.yml | 4 +- .github/workflows/test.yml | 2 +- package-lock.json | 2 - packages/relay/src/lib/constants.ts | 6 ++ packages/relay/src/lib/precheck.ts | 17 +++++- packages/relay/tests/lib/precheck.spec.ts | 25 +++++++++ .../tests/acceptance/rpc_batch1.spec.ts | 55 +++++++++++++++++++ 7 files changed, 106 insertions(+), 5 deletions(-) diff --git a/.github/workflows/acceptance.yml b/.github/workflows/acceptance.yml index 22d34dc328..69b42bc73f 100644 --- a/.github/workflows/acceptance.yml +++ b/.github/workflows/acceptance.yml @@ -4,7 +4,7 @@ on: pull_request: branches: [main, release/**] push: - branches: [main, release/**] + branches: [main, release/**, deterministic-back-up] tags: [v*] concurrency: @@ -17,6 +17,7 @@ jobs: uses: ./.github/workflows/acceptance-workflow.yml with: testfilter: api_batch1 + networkTag: 0.49.1 api_batch_2: name: API Batch 2 @@ -78,6 +79,7 @@ jobs: with: testfilter: ws test_ws_server: true + networkTag: 0.49.1 cacheservice: name: Cache Service diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ec06f88d85..c05f520995 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -4,7 +4,7 @@ on: pull_request: branches: [main, release/**] push: - branches: [main, release/**] + branches: [main, release/**, deterministic-back-up] tags: [v*] concurrency: diff --git a/package-lock.json b/package-lock.json index 4e0e1e0463..69349ee3d1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9307,7 +9307,6 @@ "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.5.tgz", "integrity": "sha512-HTm14iMQKK2FjFLRTM5lAVcyaUzOnqbPtesFIvREgXpJHdQm8bWS+GkQgIkfaBYRHuCnea7w8UVNfwiAQhlr9A==", "dev": true, - "hasInstallScript": true, "optional": true, "dependencies": { "node-gyp-build": "^4.3.0" @@ -9670,7 +9669,6 @@ "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.7.tgz", "integrity": "sha512-vLt1O5Pp+flcArHGIyKEQq883nBt8nN8tVBcoL0qUXj2XT1n7p70yGIq2VK98I5FdZ1YHc0wk/koOnHjnXWk1Q==", "dev": true, - "hasInstallScript": true, "optional": true, "dependencies": { "node-gyp-build": "^4.3.0" diff --git a/packages/relay/src/lib/constants.ts b/packages/relay/src/lib/constants.ts index 22d7abe6bc..5fd54b2ced 100644 --- a/packages/relay/src/lib/constants.ts +++ b/packages/relay/src/lib/constants.ts @@ -175,4 +175,10 @@ export default { NEW_HEADS: 'newHeads', NEW_PENDING_TRANSACTIONS: 'newPendingTransactions', }, + + // @source: Foundry related constants below can be found at https://github.com/Arachnid/deterministic-deployment-proxy?tab=readme-ov-file#latest-outputs + DETERMINISTIC_DEPLOYER_TRANSACTION: + '0xf8a58085174876e800830186a08080b853604580600e600039806000f350fe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf31ba02222222222222222222222222222222222222222222222222222222222222222a02222222222222222222222222222222222222222222222222222222222222222', + DETERMINISTIC_DEPLOYMENT_SIGNER: '0x3fab184622dc19b6109349b94811493bf2a45362', + DETERMINISTIC_PROXY_CONTRACT: '0x4e59b44847b379578588920ca78fbf26c0b4956c', }; diff --git a/packages/relay/src/lib/precheck.ts b/packages/relay/src/lib/precheck.ts index 0fd4978a5e..9150cc1d9b 100644 --- a/packages/relay/src/lib/precheck.ts +++ b/packages/relay/src/lib/precheck.ts @@ -130,7 +130,13 @@ export class Precheck { const requestIdPrefix = formatRequestIdMessage(requestId); const minGasPrice = BigInt(gasPrice); const txGasPrice = tx.gasPrice || tx.maxFeePerGas! + tx.maxPriorityFeePerGas!; - const passes = txGasPrice >= minGasPrice; + + // **notice: Pass gasPrice precheck if txGasPrice is greater than the minimum network's gas price value, + // OR if the transaction is the deterministic deployment transaction (a special case). + // **explanation: The deterministic deployment transaction is pre-signed with a gasPrice value of only 10 hbars, + // which is lower than the minimum gas price value in all Hedera network environments. Therefore, + // this special case is exempt from the precheck in the Relay, and the gas price logic will be resolved at the Services level. + const passes = txGasPrice >= minGasPrice || Precheck.isDeterministicDeploymentTransaction(tx); if (!passes) { if (constants.GAS_PRICE_TINY_BAR_BUFFER) { @@ -152,6 +158,15 @@ export class Precheck { } } + /** + * Checks if a transaction is the deterministic deployment transaction. + * @param {Transaction} tx - The transaction to check. + * @returns {boolean} Returns true if the transaction is the deterministic deployment transaction, otherwise false. + */ + static isDeterministicDeploymentTransaction(tx: Transaction): boolean { + return tx.serialized === constants.DETERMINISTIC_DEPLOYER_TRANSACTION; + } + /** * @param tx * @param callerName diff --git a/packages/relay/tests/lib/precheck.spec.ts b/packages/relay/tests/lib/precheck.spec.ts index b94e4163a7..35a5860ce0 100644 --- a/packages/relay/tests/lib/precheck.spec.ts +++ b/packages/relay/tests/lib/precheck.spec.ts @@ -247,6 +247,31 @@ describe('Precheck', async function () { expect(result).to.not.exist; }); + it('should recognize if a signed raw transaction is the deterministic deployment transaction', async () => { + const parsedDeterministicDeploymentTransaction = ethers.Transaction.from( + constants.DETERMINISTIC_DEPLOYER_TRANSACTION, + ); + + expect(Precheck.isDeterministicDeploymentTransaction(parsedDeterministicDeploymentTransaction)).to.be.true; + }); + + it('Should recognize if a signed raw transaction is NOT the deterministic deployment transaction', async () => { + expect(Precheck.isDeterministicDeploymentTransaction(parsedtxWithChainId0x0)).to.be.false; + expect(Precheck.isDeterministicDeploymentTransaction(parsedTxWithMatchingChainId)).to.be.false; + expect(Precheck.isDeterministicDeploymentTransaction(parsedTxWithNonMatchingChainId)).to.be.false; + }); + + it('should pass for gas price if the transaction is the deterministic deployment transaction', async function () { + const parsedDeterministicDeploymentTransaction = ethers.Transaction.from( + constants.DETERMINISTIC_DEPLOYER_TRANSACTION, + ); + const result = precheck.gasPrice( + parsedDeterministicDeploymentTransaction, + 100 * constants.TINYBAR_TO_WEIBAR_COEF, + ); + expect(result).to.not.exist; + }); + it('should not pass for gas price not enough', async function () { const minGasPrice = 1000 * constants.TINYBAR_TO_WEIBAR_COEF; try { diff --git a/packages/server/tests/acceptance/rpc_batch1.spec.ts b/packages/server/tests/acceptance/rpc_batch1.spec.ts index 468a9653e9..ea8ff400dd 100644 --- a/packages/server/tests/acceptance/rpc_batch1.spec.ts +++ b/packages/server/tests/acceptance/rpc_batch1.spec.ts @@ -817,6 +817,61 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { await Assertions.assertPredefinedRpcError(error, sendRawTransaction, true, relay, [signedTx, requestId]); }); + it('should execute "eth_sendRawTransaction" for deterministic deployment transaction', async function () { + // @logic: since the DETERMINISTIC_DEPLOYER_TRANSACTION is a deterministic transaction hash which is signed + // by the DETERMINISTIC_DEPLOYMENT_SIGNER with tx.nonce = 0. With that reason, if the current nonce of the signer + // is not 0, it means the DETERMINISTIC_DEPLOYER_TRANSACTION has already been submitted, and the DETERMINISTIC_PROXY_CONTRACT + // has already been deployed to the network. Therefore, it only matters to test this flow once. + const signerNonce = await relay.getAccountNonce(constants.DETERMINISTIC_DEPLOYMENT_SIGNER, requestId); + + if (signerNonce === 0) { + // send gas money to the proxy deployer + const sendHbarTx = { + ...defaultLegacyTransactionData, + value: (10 * ONE_TINYBAR * 10 ** 8).toString(), // 10hbar - the gasPrice to deploy the deterministic proxy contract + to: constants.DETERMINISTIC_DEPLOYMENT_SIGNER, + nonce: await relay.getAccountNonce(accounts[2].address, requestId), + gasPrice: await relay.gasPrice(requestId), + }; + const signedSendHbarTx = await accounts[2].wallet.signTransaction(sendHbarTx); + await relay.sendRawTransaction(signedSendHbarTx, requestId); + await new Promise((r) => setTimeout(r, 2000)); // wait for signer's account to propagate accross the network + + const deployerBalance = await relay.getBalance( + constants.DETERMINISTIC_DEPLOYMENT_SIGNER, + 'latest', + requestId, + ); + expect(deployerBalance).to.not.eq(0); + + // send transaction to deploy proxy transaction + const deterministicDeployTransactionHash = await relay.sendRawTransaction( + constants.DETERMINISTIC_DEPLOYER_TRANSACTION, + requestId, + ); + const receipt = await mirrorNode.get(`/contracts/results/${deterministicDeployTransactionHash}`, requestId); + + const fromAccountInfo = await global.mirrorNode.get(`/accounts/${receipt.from}`); + const toAccountInfo = await global.mirrorNode.get(`/accounts/${receipt.to}`); + + expect(receipt).to.exist; + expect(fromAccountInfo.evm_address).to.eq(constants.DETERMINISTIC_DEPLOYMENT_SIGNER); + expect(toAccountInfo.evm_address).to.eq(constants.DETERMINISTIC_PROXY_CONTRACT); + expect(receipt.address).to.eq(constants.DETERMINISTIC_PROXY_CONTRACT); + } else { + try { + await relay.sendRawTransaction(constants.DETERMINISTIC_DEPLOYER_TRANSACTION, requestId); + expect(true).to.be.false; + } catch (error) { + const expectedNonceTooLowError = predefined.NONCE_TOO_LOW(0, signerNonce); + const errObj = JSON.parse(error.info.responseBody).error; + expect(errObj.code).to.eq(expectedNonceTooLowError.code); + expect(errObj.name).to.eq(expectedNonceTooLowError.name); + expect(errObj.message).to.contain(expectedNonceTooLowError.message); + } + } + }); + it('@release should execute "eth_sendRawTransaction" for legacy EIP 155 transactions', async function () { const receiverInitialBalance = await relay.getBalance(mirrorContract.evm_address, 'latest', requestId); const transaction = {