-
Notifications
You must be signed in to change notification settings - Fork 74
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
chore: add precheck for receivers account in sendRawTransactionCheck #3310
base: main
Are you sure you want to change the base?
Changes from all commits
a8fdd86
a892247
4604f1f
3b85b1a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,7 +20,6 @@ | |
|
||
import { ethers, Transaction } from 'ethers'; | ||
import { Logger } from 'pino'; | ||
|
||
import { prepend0x } from '../formatters'; | ||
import { MirrorNodeClient } from './clients'; | ||
import constants from './constants'; | ||
|
@@ -85,6 +84,7 @@ export class Precheck { | |
this.value(parsedTx); | ||
this.gasPrice(parsedTx, networkGasPriceInWeiBars, requestDetails); | ||
this.balance(parsedTx, mirrorAccountInfo, requestDetails); | ||
await this.receiverAccount(parsedTx, requestDetails); | ||
} | ||
|
||
/** | ||
|
@@ -376,4 +376,20 @@ export class Precheck { | |
throw predefined.UNSUPPORTED_TRANSACTION_TYPE; | ||
} | ||
} | ||
|
||
/** | ||
* Checks if the receiver account exists and has receiver_sig_required set to true. | ||
* @param {Transaction} tx - The transaction. | ||
* @param {RequestDetails} requestDetails - The request details for logging and tracking. | ||
*/ | ||
async receiverAccount(tx: Transaction, requestDetails: RequestDetails) { | ||
if (tx.to) { | ||
const verifyAccount = await this.mirrorNodeClient.getAccount(tx.to!, requestDetails); | ||
|
||
// When `receiver_sig_required` is set to true, the receiver's account must sign all incoming transactions. | ||
if (verifyAccount !== null && verifyAccount.receiver_sig_required === true) { | ||
throw predefined.RECEIVER_SIGNATURE_REQUIRED; | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's add another check to see if verifyAccount is null then throw a
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Careful on this one since there are valid cases for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ahh yeah you're right tx.to is nullable if the transaction is a contract create transaction. Then maybe in the test make the tx.to a non-null address but mock not found. @nadezhdapopovaa please let me know if this is clear enough There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh wait this block only executes when |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -99,6 +99,7 @@ describe('@ethSendRawTransaction eth_sendRawTransaction spec', async function () | |
let clock: any; | ||
const accountAddress = '0x9eaee9E66efdb91bfDcF516b034e001cc535EB57'; | ||
const accountEndpoint = `accounts/${accountAddress}${NO_TRANSACTIONS}`; | ||
const receiverAccountEndpoint = `accounts/${ACCOUNT_ADDRESS_1}${NO_TRANSACTIONS}`; | ||
const gasPrice = '0xad78ebc5ac620000'; | ||
const transactionIdServicesFormat = '[email protected]'; | ||
const transactionId = '0.0.902-1684375868-230217103'; | ||
|
@@ -127,6 +128,13 @@ describe('@ethSendRawTransaction eth_sendRawTransaction spec', async function () | |
balance: Hbar.from(100_000_000_000, HbarUnit.Hbar).to(HbarUnit.Tinybar), | ||
}, | ||
}; | ||
const RECEIVER_ACCOUNT_RES = { | ||
account: ACCOUNT_ADDRESS_1, | ||
balance: { | ||
balance: Hbar.from(100_000_000_000, HbarUnit.Hbar).to(HbarUnit.Tinybar), | ||
}, | ||
receiver_sig_required: false, | ||
}; | ||
const useAsyncTxProcessing = ConfigService.get('USE_ASYNC_TX_PROCESSING') as boolean; | ||
|
||
beforeEach(() => { | ||
|
@@ -135,6 +143,7 @@ describe('@ethSendRawTransaction eth_sendRawTransaction spec', async function () | |
sdkClientStub = sinon.createStubInstance(SDKClient); | ||
sinon.stub(hapiServiceInstance, 'getSDKClient').returns(sdkClientStub); | ||
restMock.onGet(accountEndpoint).reply(200, ACCOUNT_RES); | ||
restMock.onGet(receiverAccountEndpoint).reply(200, RECEIVER_ACCOUNT_RES); | ||
restMock.onGet(networkExchangeRateEndpoint).reply(200, mockedExchangeRate); | ||
}); | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,7 +26,14 @@ import Constants from '@hashgraph/json-rpc-relay/dist/lib/constants'; | |
// Errors and constants from local resources | ||
import { predefined } from '@hashgraph/json-rpc-relay/dist/lib/errors/JsonRpcError'; | ||
import { RequestDetails } from '@hashgraph/json-rpc-relay/dist/lib/types'; | ||
import { FileInfo, FileInfoQuery, Hbar, TransferTransaction } from '@hashgraph/sdk'; | ||
import { | ||
AccountCreateTransaction, | ||
FileInfo, | ||
FileInfoQuery, | ||
Hbar, | ||
PrivateKey, | ||
TransferTransaction, | ||
} from '@hashgraph/sdk'; | ||
import { expect } from 'chai'; | ||
import { ethers } from 'ethers'; | ||
|
||
|
@@ -1586,6 +1593,46 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () { | |
const error = predefined.NONCE_TOO_LOW(nonce, nonce + 1); | ||
await Assertions.assertPredefinedRpcError(error, sendRawTransaction, true, relay, [signedTx, requestDetails]); | ||
}); | ||
|
||
it('should fail "eth_sendRawTransaction" if receiver\'s account has receiver_sig_required enabled', async function () { | ||
const newPrivateKey = PrivateKey.generateED25519(); | ||
const newAccount = await new AccountCreateTransaction() | ||
.setKey(newPrivateKey.publicKey) | ||
.setInitialBalance(100) | ||
.setReceiverSignatureRequired(true) | ||
.freezeWith(servicesNode.client) | ||
.sign(newPrivateKey); | ||
|
||
const transaction = await newAccount.execute(servicesNode.client); | ||
const receipt = await transaction.getReceipt(servicesNode.client); | ||
|
||
if (!receipt.accountId) { | ||
throw new Error('Failed to create new account - accountId is null'); | ||
} | ||
|
||
const toAddress = Utils.idToEvmAddress(receipt.accountId.toString()); | ||
const tx = { | ||
nonce: await accounts[0].wallet.getNonce(), | ||
chainId: CHAIN_ID, | ||
to: toAddress, | ||
from: accounts[0].address, | ||
value: '0x2E90EDD000', | ||
gasLimit: defaultGasLimit, | ||
accessList: [], | ||
maxPriorityFeePerGas: defaultGasPrice, | ||
maxFeePerGas: defaultGasPrice, | ||
}; | ||
|
||
const signedTx = await accounts[0].wallet.signTransaction(tx); | ||
await new Promise((r) => setTimeout(r, 3000)); | ||
|
||
const error = predefined.RECEIVER_SIGNATURE_REQUIRED; | ||
|
||
await Assertions.assertPredefinedRpcError(error, sendRawTransaction, false, relay, [ | ||
signedTx, | ||
requestDetails, | ||
]); | ||
}); | ||
Comment on lines
+1597
to
+1635
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Great test! Let's add another test to ensure it works fine when Additionally, if we decide to throw a |
||
}); | ||
|
||
it('@release should execute "eth_getTransactionByHash" for existing transaction', async function () { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add test coverage for this method in the precheck.spec.ts