Skip to content

Commit

Permalink
chore: add precheck for receivers account in sendRawTransactionCheck (#…
Browse files Browse the repository at this point in the history
…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]>
  • Loading branch information
nadezhdapopovaa authored Dec 17, 2024
1 parent 50316b5 commit 1ee437d
Show file tree
Hide file tree
Showing 6 changed files with 170 additions and 2 deletions.
4 changes: 4 additions & 0 deletions packages/relay/src/lib/errors/JsonRpcError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,10 @@ export const predefined = {
code: -39013,
message: 'Invalid block range',
}),
RECEIVER_SIGNATURE_ENABLED: new JsonRpcError({
code: -32000,
message: "Operation is not supported when receiver's signature is enabled.",
}),
FILTER_NOT_FOUND: new JsonRpcError({
code: -32001,
message: 'Filter not found',
Expand Down
17 changes: 17 additions & 0 deletions packages/relay/src/lib/precheck.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ export class Precheck {
this.value(parsedTx);
this.gasPrice(parsedTx, networkGasPriceInWeiBars, requestDetails);
this.balance(parsedTx, mirrorAccountInfo, requestDetails);
await this.receiverAccount(parsedTx, requestDetails);
}

/**
Expand Down Expand Up @@ -376,4 +377,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 && verifyAccount.receiver_sig_required) {
throw predefined.RECEIVER_SIGNATURE_ENABLED;
}
}
}
}
9 changes: 9 additions & 0 deletions packages/relay/tests/lib/eth/eth_sendRawTransaction.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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(1, HbarUnit.Hbar).to(HbarUnit.Tinybar),
},
receiver_sig_required: false,
};
const useAsyncTxProcessing = ConfigService.get('USE_ASYNC_TX_PROCESSING') as boolean;

beforeEach(() => {
Expand All @@ -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);
});

Expand Down
3 changes: 2 additions & 1 deletion packages/relay/tests/lib/openrpc.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ import {
overrideEnvsInMochaDescribe,
signedTransactionHash,
} from '../helpers';
import { NOT_FOUND_RES } from './eth/eth-config';
import { CONTRACT_RESULT_MOCK, NOT_FOUND_RES } from './eth/eth-config';

const logger = pino();
const registry = new Registry();
Expand Down Expand Up @@ -229,6 +229,7 @@ describe('Open RPC Specification', function () {
mock.onGet(`accounts/${defaultContractResults.results[1].from}?transactions=false`).reply(200);
mock.onGet(`accounts/${defaultContractResults.results[0].to}?transactions=false`).reply(200);
mock.onGet(`accounts/${defaultContractResults.results[1].to}?transactions=false`).reply(200);
mock.onGet(`accounts/${CONTRACT_RESULT_MOCK.from}?transactions=false`).reply(200, CONTRACT_RESULT_MOCK);
mock.onGet(`contracts/${defaultContractResults.results[0].from}`).reply(404, NOT_FOUND_RES);
mock.onGet(`contracts/${defaultContractResults.results[1].from}`).reply(404, NOT_FOUND_RES);
mock.onGet(`contracts/${defaultContractResults.results[0].to}`).reply(200);
Expand Down
46 changes: 46 additions & 0 deletions packages/relay/tests/lib/precheck.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -654,4 +654,50 @@ describe('Precheck', async function () {
expect(error.code).to.equal(predefined.UNSUPPORTED_TRANSACTION_TYPE.code);
});
});

describe('receiverAccount', async function () {
let parsedTx: Transaction;
let mirrorAccountTo: any;
const defaultNonce: number = 4;
const toAddress = ethers.Wallet.createRandom().address;

before(async () => {
const wallet = ethers.Wallet.createRandom();
const signed = await wallet.signTransaction({
...defaultTx,
from: wallet.address,
to: toAddress,
nonce: defaultNonce,
});

parsedTx = ethers.Transaction.from(signed);
});

it('should fail with signature required error', async function () {
mirrorAccountTo = {
receiver_sig_required: true,
};

mock.onGet(`accounts/${parsedTx.to}${transactionsPostFix}`).reply(200, mirrorAccountTo);

try {
await precheck.receiverAccount(parsedTx, requestDetails);
expectedError();
} catch (e: any) {
expect(e).to.exist;
expect(e.code).to.eq(-32000);
expect(e).to.eql(predefined.RECEIVER_SIGNATURE_ENABLED);
}
});

it('should accept check if signature required is set to false', async function () {
mirrorAccountTo = {
receiver_sig_required: false,
};

mock.onGet(`accounts/${parsedTx.to}${transactionsPostFix}`).reply(200, mirrorAccountTo);

expect(async () => await precheck.receiverAccount(parsedTx, requestDetails)).not.to.throw;
});
});
});
93 changes: 92 additions & 1 deletion packages/server/tests/acceptance/rpc_batch1.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -1586,6 +1593,90 @@ 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 verifyAccount = await mirrorNode.get(`/accounts/${toAddress}`, requestId);

if (verifyAccount && !verifyAccount.account) {
verifyAccount == (await mirrorNode.get(`/accounts/${toAddress}`, requestId));
}

expect(verifyAccount.receiver_sig_required).to.be.true;

const tx = {
...defaultLegacyTransactionData,
chainId: Number(CHAIN_ID),
nonce: await accounts[0].wallet.getNonce(),
to: toAddress,
from: accounts[0].address,
};

const signedTx = await accounts[0].wallet.signTransaction(tx);

const error = predefined.RECEIVER_SIGNATURE_ENABLED;

await Assertions.assertPredefinedRpcError(error, sendRawTransaction, false, relay, [
signedTx,
requestDetails,
]);
});

it('should execute "eth_sendRawTransaction" if receiver\'s account has receiver_sig_required disabled', async function () {
const newPrivateKey = PrivateKey.generateED25519();
const newAccount = await new AccountCreateTransaction()
.setKey(newPrivateKey.publicKey)
.setInitialBalance(100)
.setReceiverSignatureRequired(false)
.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 verifyAccount = await mirrorNode.get(`/accounts/${toAddress}`, requestId);

if (verifyAccount && !verifyAccount.account) {
verifyAccount == (await mirrorNode.get(`/accounts/${toAddress}`, requestId));
}

expect(verifyAccount.receiver_sig_required).to.be.false;

const tx = {
...defaultLegacyTransactionData,
chainId: Number(CHAIN_ID),
nonce: await accounts[0].wallet.getNonce(),
to: toAddress,
from: accounts[0].address,
};

const signedTx = await accounts[0].wallet.signTransaction(tx);
const transactionHash = await relay.sendRawTransaction(signedTx, requestId);
const info = await mirrorNode.get(`/contracts/results/${transactionHash}`, requestId);

expect(info).to.exist;
expect(info.result).to.equal('SUCCESS');
});
});

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

0 comments on commit 1ee437d

Please sign in to comment.