Skip to content

Commit

Permalink
fix: Enables getStorageAt to accept blockHashes (#2123)
Browse files Browse the repository at this point in the history
* Enables getStorageAt to accept block hashes

Signed-off-by: Konstantina Blazhukova <[email protected]>

* Adds tests for the new functionality

Signed-off-by: Konstantina Blazhukova <[email protected]>

* Adds to openrpc

Signed-off-by: Konstantina Blazhukova <[email protected]>

---------

Signed-off-by: Konstantina Blazhukova <[email protected]>
  • Loading branch information
konstantinabl authored Feb 26, 2024
1 parent cd5ab67 commit 292b26b
Show file tree
Hide file tree
Showing 7 changed files with 120 additions and 19 deletions.
19 changes: 18 additions & 1 deletion docs/openrpc.json
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,7 @@
"name": "Block",
"required": false,
"schema": {
"$ref": "#/components/schemas/BlockNumberOrTag"
"$ref": "#/components/schemas/BlockNumberOrTagOrHash"
}
}
],
Expand Down Expand Up @@ -1444,6 +1444,23 @@
}
]
},
"BlockNumberOrTagOrHash": {
"title": "Block number or tag",
"oneOf": [
{
"title": "Block number",
"$ref": "#/components/schemas/uint"
},
{
"title": "Block tag",
"$ref": "#/components/schemas/BlockTag"
},
{
"title": "Block hash",
"$ref": "#/components/schemas/hash32"
}
]
},
"TransactionWithSender": {
"title": "Transaction object with sender",
"type": "object",
Expand Down
10 changes: 5 additions & 5 deletions packages/relay/src/lib/eth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -793,20 +793,20 @@ export class EthImpl implements Eth {
async getStorageAt(
address: string,
slot: string,
blockNumberOrTag?: string | null,
blockNumberOrTagOrHash?: string | null,
requestIdPrefix?: string,
): Promise<string> {
this.logger.trace(
`${requestIdPrefix} getStorageAt(address=${address}, slot=${slot}, blockNumberOrTag=${blockNumberOrTag})`,
`${requestIdPrefix} getStorageAt(address=${address}, slot=${slot}, blockNumberOrOrHashTag=${blockNumberOrTagOrHash})`,
);

let result = EthImpl.zeroHex32Byte; // if contract or slot not found then return 32 byte 0

const blockResponse = await this.common.getHistoricalBlockResponse(blockNumberOrTag, false, requestIdPrefix);
const blockResponse = await this.common.getHistoricalBlockResponse(blockNumberOrTagOrHash, false, requestIdPrefix);
// To save a request to the mirror node for `latest` and `pending` blocks, we directly return null from `getHistoricalBlockResponse`
// But if a block number or `earliest` tag is passed and the mirror node returns `null`, we should throw an error.
if (!this.common.blockTagIsLatestOrPending(blockNumberOrTag) && blockResponse == null) {
throw predefined.RESOURCE_NOT_FOUND(`block '${blockNumberOrTag}'.`);
if (!this.common.blockTagIsLatestOrPending(blockNumberOrTagOrHash) && blockResponse == null) {
throw predefined.RESOURCE_NOT_FOUND(`block '${blockNumberOrTagOrHash}'.`);
}

const blockEndTimestamp = blockResponse?.timestamp?.to;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,37 +149,37 @@ export class CommonService implements ICommonService {
* @param returnLatest
*/
public async getHistoricalBlockResponse(
blockNumberOrTag?: string | null,
blockNumberOrTagOrHash?: string | null,
returnLatest?: boolean,
requestIdPrefix?: string | undefined,
): Promise<any> {
if (!returnLatest && this.blockTagIsLatestOrPending(blockNumberOrTag)) {
if (!returnLatest && this.blockTagIsLatestOrPending(blockNumberOrTagOrHash)) {
return null;
}

const blockNumber = Number(blockNumberOrTag);
if (blockNumberOrTag != null && blockNumberOrTag.length < 32 && !isNaN(blockNumber)) {
const blockNumber = Number(blockNumberOrTagOrHash);
if (blockNumberOrTagOrHash != null && blockNumberOrTagOrHash.length < 32 && !isNaN(blockNumber)) {
const latestBlockResponse = await this.mirrorNodeClient.getLatestBlock(requestIdPrefix);
const latestBlock = latestBlockResponse.blocks[0];
if (blockNumber > latestBlock.number + this.maxBlockRange) {
return null;
}
}

if (blockNumberOrTag == null || this.blockTagIsLatestOrPending(blockNumberOrTag)) {
if (blockNumberOrTagOrHash == null || this.blockTagIsLatestOrPending(blockNumberOrTagOrHash)) {
const latestBlockResponse = await this.mirrorNodeClient.getLatestBlock(requestIdPrefix);
return latestBlockResponse.blocks[0];
}

if (blockNumberOrTag == CommonService.blockEarliest) {
if (blockNumberOrTagOrHash == CommonService.blockEarliest) {
return await this.mirrorNodeClient.getBlock(0, requestIdPrefix);
}

if (blockNumberOrTag.length < 32) {
return await this.mirrorNodeClient.getBlock(Number(blockNumberOrTag), requestIdPrefix);
if (blockNumberOrTagOrHash.length < 32) {
return await this.mirrorNodeClient.getBlock(Number(blockNumberOrTagOrHash), requestIdPrefix);
}

return await this.mirrorNodeClient.getBlock(blockNumberOrTag, requestIdPrefix);
return await this.mirrorNodeClient.getBlock(blockNumberOrTagOrHash, requestIdPrefix);
}

/**
Expand Down
21 changes: 21 additions & 0 deletions packages/relay/tests/lib/eth/eth_getStorageAt.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {
DETAILD_CONTRACT_RESULT_NOT_FOUND,
MOST_RECENT_BLOCK,
OLDER_BLOCK,
BLOCK_HASH,
} from './eth-config';
import { predefined } from '../../../src/lib/errors/JsonRpcError';
import RelayAssertions from '../../assertions';
Expand Down Expand Up @@ -72,6 +73,7 @@ describe('@ethGetStorageAt eth_getStorageAt spec', async function () {
currentMaxBlockRange = Number(process.env.ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE);
process.env.ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE = '1';
restMock.onGet(`blocks/${BLOCK_NUMBER}`).reply(200, DEFAULT_BLOCK);
restMock.onGet(`blocks/${BLOCK_HASH}`).reply(200, DEFAULT_BLOCK);
restMock.onGet(BLOCKS_LIMIT_ORDER_URL).reply(200, MOST_RECENT_BLOCK);
});

Expand Down Expand Up @@ -131,6 +133,25 @@ describe('@ethGetStorageAt eth_getStorageAt spec', async function () {
expect(result).equal(defaultDetailedContractResults.state_changes[0].value_written);
});

it('eth_getStorageAt with match with block hash', async function () {
// mirror node request mocks
restMock
.onGet(
`contracts/${CONTRACT_ADDRESS_1}/state?timestamp=${DEFAULT_BLOCK.timestamp.to}&slot=0x0000000000000000000000000000000000000000000000000000000000000101&limit=100&order=desc`,
)
.reply(200, DEFAULT_CURRENT_CONTRACT_STATE);

const result = await ethImpl.getStorageAt(
CONTRACT_ADDRESS_1,
defaultDetailedContractResults.state_changes[0].slot,
BLOCK_HASH,
);
confirmResult(result);

// verify slot value
expect(result).equal(defaultDetailedContractResults.state_changes[0].value_written);
});

it('eth_getStorageAt with match with latest block', async function () {
// mirror node request mocks
restMock
Expand Down
2 changes: 1 addition & 1 deletion packages/server/src/validator/methods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ export const METHODS = {
type: 'hex',
},
2: {
type: 'blockNumber',
type: 'blockNumber|blockHash',
},
},
eth_getTransactionByBlockHashAndIndex: {
Expand Down
45 changes: 45 additions & 0 deletions packages/server/tests/acceptance/rpc_batch2.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -920,6 +920,51 @@ describe('@api-batch-2 RPC Server Acceptance Tests', function () {
);
expect(storageVal).to.eq(EXPECTED_STORAGE_VAL);
});

it('should execute "eth_getStorageAt" request to get current state changes with passing specific block hash', async function () {
const EXPECTED_STORAGE_VAL = '0x0000000000000000000000000000000000000000000000000000000000000008';

const gasPrice = await relay.gasPrice();
const transaction = {
value: 0,
gasLimit: 50000,
chainId: Number(CHAIN_ID),
to: evmAddress,
nonce: await relay.getAccountNonce(accounts[1].address),
gasPrice: gasPrice,
data: STORAGE_CONTRACT_UPDATE,
maxPriorityFeePerGas: gasPrice,
maxFeePerGas: gasPrice,
type: 2,
};

const signedTx = await accounts[1].wallet.signTransaction(transaction);
const transactionHash = await relay.sendRawTransaction(signedTx, requestId);

const blockHash = await relay.call(
RelayCalls.ETH_ENDPOINTS.ETH_GET_TRANSACTION_RECEIPT,
[transactionHash],
requestId,
).blockHash;

const transaction1 = {
...transaction,
nonce: await relay.getAccountNonce(accounts[1].address),
data: NEXT_STORAGE_CONTRACT_UPDATE,
};

const signedTx1 = await accounts[1].wallet.signTransaction(transaction1);
await relay.sendRawTransaction(signedTx1, requestId);
await new Promise((r) => setTimeout(r, 2000));

//Get previous state change with specific block number
const storageVal = await relay.call(
RelayCalls.ETH_ENDPOINTS.ETH_GET_STORAGE_AT,
[evmAddress, '0x0000000000000000000000000000000000000000000000000000000000000000', blockHash],
requestId,
);
expect(storageVal).to.eq(EXPECTED_STORAGE_VAL);
});
});

// Only run the following tests against a local node since they only work with the genesis account
Expand Down
24 changes: 21 additions & 3 deletions packages/server/tests/integration/server.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1830,7 +1830,7 @@ describe('RPC Server', async function () {
BaseTest.invalidParamError(
error.response,
Validator.ERROR_CODE,
`Invalid parameter 2: ${Validator.BLOCK_NUMBER_ERROR}, value: 123`,
`Invalid parameter 2: ${Validator.INVALID_BLOCK_HASH_TAG_NUMBER} 123`,
);
}
});
Expand All @@ -1849,7 +1849,26 @@ describe('RPC Server', async function () {
BaseTest.invalidParamError(
error.response,
Validator.ERROR_CODE,
`Invalid parameter 2: ${Validator.BLOCK_NUMBER_ERROR}, value: newest`,
`Invalid parameter 2: ${Validator.INVALID_BLOCK_HASH_TAG_NUMBER} newest`,
);
}
});

it('validates parameter 2 is valid block tag', async function () {
try {
await this.testClient.post('/', {
id: '2',
jsonrpc: '2.0',
method: RelayCalls.ETH_ENDPOINTS.ETH_GET_STORAGE_AT,
params: ['0x0000000000000000000000000000000000000001', '0x1', 'newest'],
});

Assertions.expectedError();
} catch (error) {
BaseTest.invalidParamError(
error.response,
Validator.ERROR_CODE,
`Invalid parameter 2: ${Validator.INVALID_BLOCK_HASH_TAG_NUMBER} newest`,
);
}
});
Expand Down Expand Up @@ -2045,7 +2064,6 @@ describe('RPC Server', async function () {

Assertions.expectedError();
} catch (error) {
console.log(error.response.data);
BaseTest.invalidParamError(
error.response,
Validator.ERROR_CODE,
Expand Down

0 comments on commit 292b26b

Please sign in to comment.