Skip to content
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

Return evm_address in eth_getTransactionByHash and eth_getTransactionReceipt #1726

Merged
merged 7 commits into from
Sep 13, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 48 additions & 48 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 1 addition & 3 deletions packages/relay/src/lib/clients/cache/redisCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,7 @@ export class RedisCache implements ICacheClient {
const resolvedTtl = (ttl ?? this.options.ttl) / 1000; // convert to seconds

await this.client.setEx(key, resolvedTtl, serializedValue);
this.logger.trace(
`${requestIdPrefix} caching ${key}: ${serializedValue} on ${callingMethod} for ${resolvedTtl} s`
);
this.logger.trace(`${requestIdPrefix} caching ${key}: ${serializedValue} on ${callingMethod} for ${resolvedTtl} s`);
// TODO: add metrics
}

Expand Down
16 changes: 10 additions & 6 deletions packages/relay/src/lib/clients/mirrorNodeClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -428,11 +428,12 @@ export class MirrorNodeClient {
}
}

public async getAccount(idOrAliasOrEvmAddress: string, requestIdPrefix?: string) {
public async getAccount(idOrAliasOrEvmAddress: string, requestIdPrefix?: string, retries?: number) {
return this.get(
`${MirrorNodeClient.GET_ACCOUNTS_BY_ID_ENDPOINT}${idOrAliasOrEvmAddress}?transactions=false`,
MirrorNodeClient.GET_ACCOUNTS_BY_ID_ENDPOINT,
requestIdPrefix,
retries,
);
}

Expand Down Expand Up @@ -579,11 +580,12 @@ export class MirrorNodeClient {
);
}

public async getContract(contractIdOrAddress: string, requestIdPrefix?: string) {
public async getContract(contractIdOrAddress: string, requestIdPrefix?: string, retries?: number) {
return this.get(
`${MirrorNodeClient.GET_CONTRACT_ENDPOINT}${contractIdOrAddress}`,
MirrorNodeClient.GET_CONTRACT_ENDPOINT,
requestIdPrefix,
retries,
);
}

Expand Down Expand Up @@ -894,11 +896,12 @@ export class MirrorNodeClient {
).replace(MirrorNodeClient.TIMESTAMP_PLACEHOLDER, timestamp);
}

public async getTokenById(tokenId: string, requestIdPrefix?: string) {
public async getTokenById(tokenId: string, requestIdPrefix?: string, retries?: number) {
return this.get(
`${MirrorNodeClient.GET_TOKENS_ENDPOINT}/${tokenId}`,
MirrorNodeClient.GET_TOKENS_ENDPOINT,
requestIdPrefix,
retries,
);
}

Expand Down Expand Up @@ -1058,6 +1061,7 @@ export class MirrorNodeClient {
searchableTypes: any[] = [constants.TYPE_CONTRACT, constants.TYPE_ACCOUNT, constants.TYPE_TOKEN],
callerName: string,
requestIdPrefix?: string,
retries?: number,
) {
const cachedLabel = `${constants.CACHE_KEY.RESOLVE_ENTITY_TYPE}_${entityIdentifier}`;
const cachedResponse: { type: string; entity: any } | undefined = this.cacheService.get(
Expand All @@ -1078,7 +1082,7 @@ export class MirrorNodeClient {
);

if (searchableTypes.find((t) => t === constants.TYPE_CONTRACT)) {
const contract = await this.getContract(entityIdentifier, requestIdPrefix).catch(() => {
const contract = await this.getContract(entityIdentifier, requestIdPrefix, retries).catch(() => {
return null;
});
if (contract) {
Expand All @@ -1096,7 +1100,7 @@ export class MirrorNodeClient {
const promises = [
searchableTypes.find((t) => t === constants.TYPE_ACCOUNT)
? buildPromise(
this.getAccount(entityIdentifier, requestIdPrefix).catch(() => {
this.getAccount(entityIdentifier, requestIdPrefix, retries).catch(() => {
return null;
}),
)
Expand All @@ -1108,7 +1112,7 @@ export class MirrorNodeClient {
promises.push(
searchableTypes.find((t) => t === constants.TYPE_TOKEN)
? buildPromise(
this.getTokenById(`0.0.${parseInt(entityIdentifier, 16)}`, requestIdPrefix).catch(() => {
this.getTokenById(`0.0.${parseInt(entityIdentifier, 16)}`, requestIdPrefix, retries).catch(() => {
return null;
}),
)
Expand Down
61 changes: 36 additions & 25 deletions packages/relay/src/lib/eth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1662,6 +1662,30 @@ export class EthImpl implements Eth {
}
}

async resolveEvmAddress(
address: string,
requestIdPrefix?: string,
searchableTypes = [constants.TYPE_CONTRACT, constants.TYPE_TOKEN, constants.TYPE_ACCOUNT],
) {
const entity = await this.mirrorNodeClient.resolveEntityType(
address,
searchableTypes,
EthImpl.ethGetCode,
requestIdPrefix,
0,
);
let resolvedAddress = address;
if (
entity &&
(entity.type === constants.TYPE_CONTRACT || entity.type === constants.TYPE_ACCOUNT) &&
entity.entity?.evm_address
) {
resolvedAddress = entity.entity.evm_address;
}

return resolvedAddress;
}

/**
* Gets a transaction by the provided hash
*
Expand All @@ -1680,29 +1704,13 @@ export class EthImpl implements Eth {
);
}

let fromAddress;
if (contractResult.from) {
fromAddress = contractResult.from.substring(0, 42);

const accountCacheKey = `${constants.CACHE_KEY.ACCOUNT}_${fromAddress}`;
let accountResult: any | null = this.cacheService.get(accountCacheKey, EthImpl.ethGetTransactionByHash);
if (!accountResult) {
accountResult = await this.mirrorNodeClient.getAccount(fromAddress, requestIdPrefix);
if (accountResult) {
this.cacheService.set(
accountCacheKey,
accountResult,
EthImpl.ethGetTransactionByHash,
undefined,
requestIdPrefix,
);
}
}
const fromAddress = contractResult.from
? await this.resolveEvmAddress(contractResult.from, requestIdPrefix, [constants.TYPE_ACCOUNT])
: contractResult.to;

if (accountResult?.evm_address?.length > 0) {
fromAddress = accountResult.evm_address.substring(0, 42);
}
}
const toAddress = contractResult.to
? await this.resolveEvmAddress(contractResult.to, requestIdPrefix)
: contractResult.to;

if (
process.env.DEV_MODE &&
Expand All @@ -1716,6 +1724,7 @@ export class EthImpl implements Eth {
return formatContractResult({
...contractResult,
from: fromAddress,
to: toAddress,
});
}

Expand Down Expand Up @@ -1754,7 +1763,7 @@ export class EthImpl implements Eth {
to: cachedLog.address,
transactionHash: cachedLog.transactionHash,
transactionIndex: cachedLog.transactionIndex,
type: null, //null fro HAPI transactions
type: null, // null from HAPI transactions
};

this.logger.debug(
Expand Down Expand Up @@ -1802,8 +1811,10 @@ export class EthImpl implements Eth {
const receipt: any = {
blockHash: toHash32(receiptResponse.block_hash),
blockNumber: numberTo0x(receiptResponse.block_number),
from: receiptResponse.from,
to: receiptResponse.to,
from: receiptResponse.from
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could move the undefined/null check into the resolveEvmAddress()

? await this.resolveEvmAddress(receiptResponse.from, requestIdPrefix)
: receiptResponse.from,
to: receiptResponse.to ? await this.resolveEvmAddress(receiptResponse.to, requestIdPrefix) : receiptResponse.to,
cumulativeGasUsed: numberTo0x(receiptResponse.block_gas_used),
gasUsed: nanOrNumberTo0x(receiptResponse.gas_used),
contractAddress: receiptResponse.address,
Expand Down
9 changes: 9 additions & 0 deletions packages/server/tests/acceptance/rpc_batch1.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -599,6 +599,8 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () {
// Since the transactionId is not available in this context
// Wait for the transaction to be processed and imported in the mirror node with axios-retry
const mirrorResult = await mirrorNode.get(`/contracts/results/${legacyTxHash}`, requestId);
mirrorResult.from = accounts[2].wallet.address;
mirrorResult.to = mirrorContract.evm_address;

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

Expand All @@ -620,6 +622,8 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () {
// Since the transactionId is not available in this context
// Wait for the transaction to be processed and imported in the mirror node with axios-retry
const mirrorResult = await mirrorNode.get(`/contracts/results/${transactionHash}`, requestId);
mirrorResult.from = accounts[2].wallet.address;
mirrorResult.to = mirrorContract.evm_address;

const res = await relay.call(
RelayCalls.ETH_ENDPOINTS.ETH_GET_TRANSACTION_RECEIPT,
Expand All @@ -643,6 +647,8 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () {
// Since the transactionId is not available in this context
// Wait for the transaction to be processed and imported in the mirror node with axios-retry
const mirrorResult = await mirrorNode.get(`/contracts/results/${transactionHash}`, requestId);
mirrorResult.from = accounts[2].wallet.address;
mirrorResult.to = mirrorContract.evm_address;

const res = await relay.call(
RelayCalls.ETH_ENDPOINTS.ETH_GET_TRANSACTION_RECEIPT,
Expand Down Expand Up @@ -1019,6 +1025,9 @@ describe('@api-batch-1 RPC Server Acceptance Tests', function () {
const signedTx = await accounts[2].wallet.signTransaction(transaction);
const txHash1 = await relay.sendRawTransaction(signedTx, requestId);
const mirrorResult = await mirrorNode.get(`/contracts/results/${txHash1}`, requestId);
mirrorResult.from = accounts[2].wallet.address;
mirrorResult.to = mirrorContract.evm_address;

const res = await relay.call(RelayCalls.ETH_ENDPOINTS.ETH_GET_TRANSACTION_RECEIPT, [txHash1], requestId);
Assertions.transactionReceipt(res, mirrorResult);
const error = predefined.NONCE_TOO_LOW(nonce, nonce + 1);
Expand Down
105 changes: 105 additions & 0 deletions packages/server/tests/acceptance/rpc_batch2.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import RelayCalls from '../../tests/helpers/constants';
import Helper from '../../tests/helpers/constants';
import Address from '../../tests/helpers/constants';
import { numberTo0x } from '../../../../packages/relay/src/formatters';
import ERC20MockJson from '../contracts/ERC20Mock.json';

describe('@api-batch-2 RPC Server Acceptance Tests', function () {
this.timeout(240 * 1000); // 240 seconds
Expand All @@ -55,6 +56,7 @@ describe('@api-batch-2 RPC Server Acceptance Tests', function () {
let mirrorContractDetails;
let mirrorSecondaryAccount;
let requestId;
let htsAddress;

const CHAIN_ID = process.env.CHAIN_ID || 0;
const ONE_TINYBAR = Utils.add0xPrefix(Utils.toHex(ethers.parseUnits('1', 10)));
Expand Down Expand Up @@ -95,6 +97,8 @@ describe('@api-batch-2 RPC Server Acceptance Tests', function () {
await accounts[0].client.executeContractCall(contractId, 'createChild', params, 75000, requestId)
).contractExecuteTimestamp;
tokenId = await servicesNode.createToken(1000, requestId);
htsAddress = Utils.idToEvmAddress(tokenId.toString());

logger.info('Associate and transfer tokens');
await accounts[0].client.associateToken(tokenId, requestId);
await accounts[1].client.associateToken(tokenId, requestId);
Expand Down Expand Up @@ -989,4 +993,105 @@ describe('@api-batch-2 RPC Server Acceptance Tests', function () {
expect(Number(res.oldestBlock)).to.be.gt(0);
});
});

describe('Formats of addresses in Transaction and Receipt results', () => {
const getTxData = async (hash) => {
const txByHash = await relay.call(RelayCalls.ETH_ENDPOINTS.ETH_GET_TRANSACTION_BY_HASH, [hash], requestId);
const receipt = await relay.call(RelayCalls.ETH_ENDPOINTS.ETH_GET_TRANSACTION_RECEIPT, [hash], requestId);
let mirrorResult = await mirrorNode.get(`/contracts/results/${hash}`, requestId);

return { txByHash, receipt, mirrorResult };
};

it('from/to Addresses in transaction between accounts are in evm format', async function () {
const tx = await accounts[0].wallet.sendTransaction({
to: accounts[1].wallet,
value: ethers.parseEther('1'),
});

await tx.wait();

let { txByHash, receipt, mirrorResult } = await getTxData(tx.hash);

mirrorResult.from = accounts[0].wallet.address;
mirrorResult.to = accounts[1].wallet.address;

Assertions.transaction(txByHash, mirrorResult);
Assertions.transactionReceipt(receipt, mirrorResult);

Assertions.evmAddress(txByHash.from);
Assertions.evmAddress(txByHash.to);
Assertions.evmAddress(receipt.from);
Assertions.evmAddress(receipt.to);
});

it('from/to Addresses in transaction to a contract (deployed through the relay) are in evm and long-zero format', async function () {
const relayContract = await Utils.deployContractWithEthers([], basicContractJson, accounts[0].wallet, relay);

const tx = await accounts[0].wallet.sendTransaction({
to: relayContract.target,
value: ethers.parseEther('1'),
});

await tx.wait();

let { txByHash, receipt, mirrorResult } = await getTxData(tx.hash);

mirrorResult.from = accounts[0].wallet.address;
mirrorResult.to = relayContract.target;

Assertions.transaction(txByHash, mirrorResult);
Assertions.transactionReceipt(receipt, mirrorResult);

Assertions.evmAddress(txByHash.from);
Assertions.evmAddress(txByHash.to);
Assertions.evmAddress(receipt.from);
Assertions.evmAddress(receipt.to);
});

it('from/to Addresses in transaction to a contract (deployed through HAPI tx) are in evm and long-zero format', async function () {
const tx = await accounts[0].wallet.sendTransaction({
to: mirrorContract.evm_address,
value: ethers.parseEther('1'),
});

await tx.wait();

let { txByHash, receipt, mirrorResult } = await getTxData(tx.hash);

mirrorResult.from = accounts[0].wallet.address;
mirrorResult.to = mirrorContract.evm_address;

Assertions.transaction(txByHash, mirrorResult);
Assertions.transactionReceipt(receipt, mirrorResult);

Assertions.evmAddress(txByHash.from);
Assertions.longZeroAddress(txByHash.to);
Assertions.evmAddress(receipt.from);
Assertions.longZeroAddress(receipt.to);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Q: post HIP 729 shouldn't all new contract have a valid evm addresses?
I thought we were trying to return evm address values all the time when applicable?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From what I've noticed, contracts that have been deployed via the SDK (using fileCreateTransaction still show a long-zero address in the mirror node instead of a proper evm address. Here are a couple of the latest contracts on testnet:

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very strange.
Can you open an issue on the services side so we can track this nuance.

});

it('from/to Addresses when transferring HTS tokens to the tokenAddress are in evm and long-zero format', async function () {
const tokenAsERC20 = new ethers.Contract(htsAddress, ERC20MockJson.abi, accounts[0].wallet);
const tx = await tokenAsERC20.transfer(accounts[1].wallet.address, 1, await Utils.gasOptions(requestId));

await tx.wait();

let { txByHash, receipt, mirrorResult } = await getTxData(tx.hash);

mirrorResult.from = accounts[0].wallet.address;

// ignore assertion of logs to keep the test simple
receipt.logs = [];
mirrorResult.logs = [];

Assertions.transaction(txByHash, mirrorResult);
Assertions.transactionReceipt(receipt, mirrorResult);

Assertions.evmAddress(txByHash.from);
Assertions.longZeroAddress(txByHash.to);
Assertions.evmAddress(receipt.from);
Assertions.longZeroAddress(receipt.to);
});
});
});
4 changes: 3 additions & 1 deletion packages/server/tests/clients/servicesClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ export default class ServicesClient {

async transferToken(tokenId, recipient: AccountId, amount = 10, requestId?: string) {
const requestIdPrefix = Utils.formatRequestIdMessage(requestId);
await this.executeAndGetTransactionReceipt(
const receipt = await this.executeAndGetTransactionReceipt(
new TransferTransaction()
.addTokenTransfer(tokenId, this._thisAccountId(), -amount)
.addTokenTransfer(tokenId, recipient, amount)
Expand All @@ -183,6 +183,8 @@ export default class ServicesClient {
this.logger.debug(
`${requestIdPrefix} Token balances for ${recipient.toString()} are ${balances.tokens.toString().toString()}`,
);

return receipt;
}

async createParentContract(contractJson, requestId?: string) {
Expand Down
Loading