Skip to content

Commit 1ee437d

Browse files
chore: add precheck for receivers account in sendRawTransactionCheck (#3310)
* chore: add precheck for receivers account in sendRawTransactionCheck Signed-off-by: Nadezhda Popova <[email protected]> * fixup! chore: add precheck for receivers account in sendRawTransactionCheck Signed-off-by: Nadezhda Popova <[email protected]> * fixup! fixup! chore: add precheck for receivers account in sendRawTransactionCheck Signed-off-by: Nadezhda Popova <[email protected]> * test: add test for receivers account precheck Signed-off-by: Nadezhda Popova <[email protected]> * fixup! test: add test for receivers account precheck Signed-off-by: Nadezhda Popova <[email protected]> * fixup! fixup! test: add test for receivers account precheck Signed-off-by: Nadezhda Popova <[email protected]> * fixup! fixup! fixup! test: add test for receivers account precheck Signed-off-by: Nadezhda Popova <[email protected]> * fixup! fixup! fixup! fixup! test: add test for receivers account precheck Signed-off-by: Nadezhda Popova <[email protected]> * fixup! fixup! fixup! fixup! fixup! test: add test for receivers account precheck Signed-off-by: Nadezhda Popova <[email protected]> * fixup! fixup! fixup! fixup! fixup! fixup! test: add test for receivers account precheck Signed-off-by: Nadezhda Popova <[email protected]> * fixup! fixup! fixup! fixup! fixup! fixup! fixup! test: add test for receivers account precheck Signed-off-by: Nadezhda Popova <[email protected]> --------- Signed-off-by: Nadezhda Popova <[email protected]>
1 parent 50316b5 commit 1ee437d

File tree

6 files changed

+170
-2
lines changed

6 files changed

+170
-2
lines changed

packages/relay/src/lib/errors/JsonRpcError.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,10 @@ export const predefined = {
238238
code: -39013,
239239
message: 'Invalid block range',
240240
}),
241+
RECEIVER_SIGNATURE_ENABLED: new JsonRpcError({
242+
code: -32000,
243+
message: "Operation is not supported when receiver's signature is enabled.",
244+
}),
241245
FILTER_NOT_FOUND: new JsonRpcError({
242246
code: -32001,
243247
message: 'Filter not found',

packages/relay/src/lib/precheck.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ export class Precheck {
8585
this.value(parsedTx);
8686
this.gasPrice(parsedTx, networkGasPriceInWeiBars, requestDetails);
8787
this.balance(parsedTx, mirrorAccountInfo, requestDetails);
88+
await this.receiverAccount(parsedTx, requestDetails);
8889
}
8990

9091
/**
@@ -376,4 +377,20 @@ export class Precheck {
376377
throw predefined.UNSUPPORTED_TRANSACTION_TYPE;
377378
}
378379
}
380+
381+
/**
382+
* Checks if the receiver account exists and has receiver_sig_required set to true.
383+
* @param {Transaction} tx - The transaction.
384+
* @param {RequestDetails} requestDetails - The request details for logging and tracking.
385+
*/
386+
async receiverAccount(tx: Transaction, requestDetails: RequestDetails) {
387+
if (tx.to) {
388+
const verifyAccount = await this.mirrorNodeClient.getAccount(tx.to, requestDetails);
389+
390+
// When `receiver_sig_required` is set to true, the receiver's account must sign all incoming transactions.
391+
if (verifyAccount && verifyAccount.receiver_sig_required) {
392+
throw predefined.RECEIVER_SIGNATURE_ENABLED;
393+
}
394+
}
395+
}
379396
}

packages/relay/tests/lib/eth/eth_sendRawTransaction.spec.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ describe('@ethSendRawTransaction eth_sendRawTransaction spec', async function ()
9999
let clock: any;
100100
const accountAddress = '0x9eaee9E66efdb91bfDcF516b034e001cc535EB57';
101101
const accountEndpoint = `accounts/${accountAddress}${NO_TRANSACTIONS}`;
102+
const receiverAccountEndpoint = `accounts/${ACCOUNT_ADDRESS_1}${NO_TRANSACTIONS}`;
102103
const gasPrice = '0xad78ebc5ac620000';
103104
const transactionIdServicesFormat = '[email protected]';
104105
const transactionId = '0.0.902-1684375868-230217103';
@@ -127,6 +128,13 @@ describe('@ethSendRawTransaction eth_sendRawTransaction spec', async function ()
127128
balance: Hbar.from(100_000_000_000, HbarUnit.Hbar).to(HbarUnit.Tinybar),
128129
},
129130
};
131+
const RECEIVER_ACCOUNT_RES = {
132+
account: ACCOUNT_ADDRESS_1,
133+
balance: {
134+
balance: Hbar.from(1, HbarUnit.Hbar).to(HbarUnit.Tinybar),
135+
},
136+
receiver_sig_required: false,
137+
};
130138
const useAsyncTxProcessing = ConfigService.get('USE_ASYNC_TX_PROCESSING') as boolean;
131139

132140
beforeEach(() => {
@@ -135,6 +143,7 @@ describe('@ethSendRawTransaction eth_sendRawTransaction spec', async function ()
135143
sdkClientStub = sinon.createStubInstance(SDKClient);
136144
sinon.stub(hapiServiceInstance, 'getSDKClient').returns(sdkClientStub);
137145
restMock.onGet(accountEndpoint).reply(200, ACCOUNT_RES);
146+
restMock.onGet(receiverAccountEndpoint).reply(200, RECEIVER_ACCOUNT_RES);
138147
restMock.onGet(networkExchangeRateEndpoint).reply(200, mockedExchangeRate);
139148
});
140149

packages/relay/tests/lib/openrpc.spec.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ import {
7575
overrideEnvsInMochaDescribe,
7676
signedTransactionHash,
7777
} from '../helpers';
78-
import { NOT_FOUND_RES } from './eth/eth-config';
78+
import { CONTRACT_RESULT_MOCK, NOT_FOUND_RES } from './eth/eth-config';
7979

8080
const logger = pino();
8181
const registry = new Registry();
@@ -229,6 +229,7 @@ describe('Open RPC Specification', function () {
229229
mock.onGet(`accounts/${defaultContractResults.results[1].from}?transactions=false`).reply(200);
230230
mock.onGet(`accounts/${defaultContractResults.results[0].to}?transactions=false`).reply(200);
231231
mock.onGet(`accounts/${defaultContractResults.results[1].to}?transactions=false`).reply(200);
232+
mock.onGet(`accounts/${CONTRACT_RESULT_MOCK.from}?transactions=false`).reply(200, CONTRACT_RESULT_MOCK);
232233
mock.onGet(`contracts/${defaultContractResults.results[0].from}`).reply(404, NOT_FOUND_RES);
233234
mock.onGet(`contracts/${defaultContractResults.results[1].from}`).reply(404, NOT_FOUND_RES);
234235
mock.onGet(`contracts/${defaultContractResults.results[0].to}`).reply(200);

packages/relay/tests/lib/precheck.spec.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -654,4 +654,50 @@ describe('Precheck', async function () {
654654
expect(error.code).to.equal(predefined.UNSUPPORTED_TRANSACTION_TYPE.code);
655655
});
656656
});
657+
658+
describe('receiverAccount', async function () {
659+
let parsedTx: Transaction;
660+
let mirrorAccountTo: any;
661+
const defaultNonce: number = 4;
662+
const toAddress = ethers.Wallet.createRandom().address;
663+
664+
before(async () => {
665+
const wallet = ethers.Wallet.createRandom();
666+
const signed = await wallet.signTransaction({
667+
...defaultTx,
668+
from: wallet.address,
669+
to: toAddress,
670+
nonce: defaultNonce,
671+
});
672+
673+
parsedTx = ethers.Transaction.from(signed);
674+
});
675+
676+
it('should fail with signature required error', async function () {
677+
mirrorAccountTo = {
678+
receiver_sig_required: true,
679+
};
680+
681+
mock.onGet(`accounts/${parsedTx.to}${transactionsPostFix}`).reply(200, mirrorAccountTo);
682+
683+
try {
684+
await precheck.receiverAccount(parsedTx, requestDetails);
685+
expectedError();
686+
} catch (e: any) {
687+
expect(e).to.exist;
688+
expect(e.code).to.eq(-32000);
689+
expect(e).to.eql(predefined.RECEIVER_SIGNATURE_ENABLED);
690+
}
691+
});
692+
693+
it('should accept check if signature required is set to false', async function () {
694+
mirrorAccountTo = {
695+
receiver_sig_required: false,
696+
};
697+
698+
mock.onGet(`accounts/${parsedTx.to}${transactionsPostFix}`).reply(200, mirrorAccountTo);
699+
700+
expect(async () => await precheck.receiverAccount(parsedTx, requestDetails)).not.to.throw;
701+
});
702+
});
657703
});

packages/server/tests/acceptance/rpc_batch1.spec.ts

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,14 @@ import Constants from '@hashgraph/json-rpc-relay/dist/lib/constants';
2626
// Errors and constants from local resources
2727
import { predefined } from '@hashgraph/json-rpc-relay/dist/lib/errors/JsonRpcError';
2828
import { RequestDetails } from '@hashgraph/json-rpc-relay/dist/lib/types';
29-
import { FileInfo, FileInfoQuery, Hbar, TransferTransaction } from '@hashgraph/sdk';
29+
import {
30+
AccountCreateTransaction,
31+
FileInfo,
32+
FileInfoQuery,
33+
Hbar,
34+
PrivateKey,
35+
TransferTransaction,
36+
} from '@hashgraph/sdk';
3037
import { expect } from 'chai';
3138
import { ethers } from 'ethers';
3239

@@ -1586,6 +1593,90 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () {
15861593
const error = predefined.NONCE_TOO_LOW(nonce, nonce + 1);
15871594
await Assertions.assertPredefinedRpcError(error, sendRawTransaction, true, relay, [signedTx, requestDetails]);
15881595
});
1596+
1597+
it('should fail "eth_sendRawTransaction" if receiver\'s account has receiver_sig_required enabled', async function () {
1598+
const newPrivateKey = PrivateKey.generateED25519();
1599+
const newAccount = await new AccountCreateTransaction()
1600+
.setKey(newPrivateKey.publicKey)
1601+
.setInitialBalance(100)
1602+
.setReceiverSignatureRequired(true)
1603+
.freezeWith(servicesNode.client)
1604+
.sign(newPrivateKey);
1605+
1606+
const transaction = await newAccount.execute(servicesNode.client);
1607+
const receipt = await transaction.getReceipt(servicesNode.client);
1608+
1609+
if (!receipt.accountId) {
1610+
throw new Error('Failed to create new account - accountId is null');
1611+
}
1612+
1613+
const toAddress = Utils.idToEvmAddress(receipt.accountId.toString());
1614+
const verifyAccount = await mirrorNode.get(`/accounts/${toAddress}`, requestId);
1615+
1616+
if (verifyAccount && !verifyAccount.account) {
1617+
verifyAccount == (await mirrorNode.get(`/accounts/${toAddress}`, requestId));
1618+
}
1619+
1620+
expect(verifyAccount.receiver_sig_required).to.be.true;
1621+
1622+
const tx = {
1623+
...defaultLegacyTransactionData,
1624+
chainId: Number(CHAIN_ID),
1625+
nonce: await accounts[0].wallet.getNonce(),
1626+
to: toAddress,
1627+
from: accounts[0].address,
1628+
};
1629+
1630+
const signedTx = await accounts[0].wallet.signTransaction(tx);
1631+
1632+
const error = predefined.RECEIVER_SIGNATURE_ENABLED;
1633+
1634+
await Assertions.assertPredefinedRpcError(error, sendRawTransaction, false, relay, [
1635+
signedTx,
1636+
requestDetails,
1637+
]);
1638+
});
1639+
1640+
it('should execute "eth_sendRawTransaction" if receiver\'s account has receiver_sig_required disabled', async function () {
1641+
const newPrivateKey = PrivateKey.generateED25519();
1642+
const newAccount = await new AccountCreateTransaction()
1643+
.setKey(newPrivateKey.publicKey)
1644+
.setInitialBalance(100)
1645+
.setReceiverSignatureRequired(false)
1646+
.freezeWith(servicesNode.client)
1647+
.sign(newPrivateKey);
1648+
1649+
const transaction = await newAccount.execute(servicesNode.client);
1650+
const receipt = await transaction.getReceipt(servicesNode.client);
1651+
1652+
if (!receipt.accountId) {
1653+
throw new Error('Failed to create new account - accountId is null');
1654+
}
1655+
1656+
const toAddress = Utils.idToEvmAddress(receipt.accountId.toString());
1657+
const verifyAccount = await mirrorNode.get(`/accounts/${toAddress}`, requestId);
1658+
1659+
if (verifyAccount && !verifyAccount.account) {
1660+
verifyAccount == (await mirrorNode.get(`/accounts/${toAddress}`, requestId));
1661+
}
1662+
1663+
expect(verifyAccount.receiver_sig_required).to.be.false;
1664+
1665+
const tx = {
1666+
...defaultLegacyTransactionData,
1667+
chainId: Number(CHAIN_ID),
1668+
nonce: await accounts[0].wallet.getNonce(),
1669+
to: toAddress,
1670+
from: accounts[0].address,
1671+
};
1672+
1673+
const signedTx = await accounts[0].wallet.signTransaction(tx);
1674+
const transactionHash = await relay.sendRawTransaction(signedTx, requestId);
1675+
const info = await mirrorNode.get(`/contracts/results/${transactionHash}`, requestId);
1676+
1677+
expect(info).to.exist;
1678+
expect(info.result).to.equal('SUCCESS');
1679+
});
15891680
});
15901681

15911682
it('@release should execute "eth_getTransactionByHash" for existing transaction', async function () {

0 commit comments

Comments
 (0)