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

Revert "feat: deprecate ETH_POPULATE_SYNTHETIC_CONTRACT_RESULTS env… #2783

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ Unless you need to set a non-default value, it is recommended to only populate o
| `ETH_GET_TRANSACTION_COUNT_MAX_BLOCK_RANGE` | "1000" | The maximum number of transactions to return when running eth_getBlockByHash or eth_getBlockByNumber with transaction objects set to true call. |
| `FEE_HISTORY_MAX_RESULTS` | "10" | The maximum number of results to returns as part of `eth_feeHistory`. |
| `ETH_FEE_HISTORY_FIXED` | "true" | Flag to set if eth_feeHistory should return a fixed fee for the set of results. |
| `ETH_POPULATE_SYNTHETIC_CONTRACT_RESULTS` | "true" | Flag to set if the relay should populate the contract results for synthetic contracts. |
| `GAS_PRICE_PERCENTAGE_BUFFER` | "0" | The additional buffer that adds a percentage on top of the calculated network gasPrice. This may be used by operators to reduce the chances of `INSUFFICIENT_TX_FEE` errors experienced by users caused by minor fluctuations in the exchange rate. |
| `GAS_PRICE_TINY_BAR_BUFFER` | "10000000000" | The additional buffer range to allow during a relay precheck of gas price. This supports slight fluctuations in network gasprice calculations. |
| `HAPI_CLIENT_DURATION_RESET` | "3600000" | Time until client reinitialization. (ms) |
Expand Down
96 changes: 83 additions & 13 deletions packages/relay/src/lib/eth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,13 @@ export class EthImpl implements Eth {
private readonly estimateGasThrows = process.env.ESTIMATE_GAS_THROWS
? process.env.ESTIMATE_GAS_THROWS === 'true'
: true;
private readonly syntheticLogCacheTtl = parseNumericEnvVar(
'SYNTHETIC_LOG_CACHE_TTL',
'DEFAULT_SYNTHETIC_LOG_CACHE_TTL',
);
private readonly shouldPopulateSyntheticContractResults = process.env.ETH_POPULATE_SYNTHETIC_CONTRACT_RESULTS
? process.env.ETH_POPULATE_SYNTHETIC_CONTRACT_RESULTS === 'true'
: true;

private readonly ethGasPRiceCacheTtlMs = parseNumericEnvVar(
'ETH_GET_GAS_PRICE_CACHE_TTL_MS',
Expand Down Expand Up @@ -1950,24 +1957,24 @@ export class EthImpl implements Eth {
async getTransactionByHash(hash: string, requestIdPrefix?: string): Promise<Transaction | null> {
this.logger.trace(`${requestIdPrefix} getTransactionByHash(hash=${hash})`, hash);

const contractResult = await this.mirrorNodeClient.getContractResultWithRetry(hash, requestIdPrefix);
if (contractResult === null || contractResult.hash === undefined) {
// handle synthetic transactions
const syntheticLogs = await this.common.getLogsWithParams(
null,
{
'transaction.hash': hash,
},
if (this.shouldPopulateSyntheticContractResults) {
// check if tx is synthetic and exists in cache
const cacheKeySyntheticLog = `${constants.CACHE_KEY.SYNTHETIC_LOG_TRANSACTION_HASH}${hash}`;
const cachedLog = await this.cacheService.getSharedWithFallback(
cacheKeySyntheticLog,
EthImpl.ethGetTransactionReceipt,
requestIdPrefix,
);

// no tx found
if (!syntheticLogs.length) {
this.logger.trace(`${requestIdPrefix} no tx for ${hash}`);
return null;
if (cachedLog) {
const tx: Transaction1559 = this.createTransactionFromLog(cachedLog);
return tx;
}
}

return this.createTransactionFromLog(syntheticLogs[0]);
const contractResult = await this.mirrorNodeClient.getContractResultWithRetry(hash, requestIdPrefix);
if (contractResult === null || contractResult.hash === undefined) {
return null;
}

if (!contractResult.block_number || (!contractResult.transaction_index && contractResult.transaction_index !== 0)) {
Expand Down Expand Up @@ -2244,6 +2251,10 @@ export class EthImpl implements Eth {
}

const blockHash = toHash32(blockResponse.hash);
// Gating feature in case of unexpected behavior with other apps.
if (this.shouldPopulateSyntheticContractResults) {
this.filterAndPopulateSyntheticContractResults(showDetails, logs, transactionArray, requestIdPrefix);
}
return new Block({
baseFeePerGas: await this.gasPrice(requestIdPrefix),
difficulty: EthImpl.zeroHex,
Expand All @@ -2269,6 +2280,65 @@ export class EthImpl implements Eth {
});
}

/**
* Filter contract logs to remove the duplicate ones with the contract results to get only the synthetic ones.
* If showDetails is set to false filter the contract logs and add missing transaction hashes
* If showDetails is set to true filter the contract logs and add construct missing transaction objects
* @param showDetails
* @param logs
* @param transactionArray
* @param requestIdPrefix
*/
filterAndPopulateSyntheticContractResults(
showDetails: boolean,
logs: Log[],
transactionArray: Array<any>,
requestIdPrefix?: string,
): void {
let filteredLogs: Log[];
const keyValuePairs: Record<string, any> = {}; // Object to accumulate cache entries

if (showDetails) {
filteredLogs = logs.filter(
(log) => !transactionArray.some((transaction) => transaction.hash === log.transactionHash),
);
filteredLogs.forEach((log) => {
const transaction: Transaction1559 = this.createTransactionFromLog(log);
transactionArray.push(transaction);

const cacheKey = `${constants.CACHE_KEY.SYNTHETIC_LOG_TRANSACTION_HASH}${log.transactionHash}`;
keyValuePairs[cacheKey] = log;
});
} else {
filteredLogs = logs.filter((log) => !transactionArray.includes(log.transactionHash));
filteredLogs.forEach((log) => {
transactionArray.push(log.transactionHash);

const cacheKey = `${constants.CACHE_KEY.SYNTHETIC_LOG_TRANSACTION_HASH}${log.transactionHash}`;
keyValuePairs[cacheKey] = log;
});

this.logger.trace(
`${requestIdPrefix} ${filteredLogs.length} Synthetic transaction hashes will be added in the block response`,
);
}
// cache the whole array using mSet
if (Object.keys(keyValuePairs).length > 0) {
this.cacheService.multiSet(
keyValuePairs,
EthImpl.ethGetBlockByHash,
this.syntheticLogCacheTtl,
requestIdPrefix,
true,
);
}
}

/**
* Creates a new instance of transaction object using the information in the log, all unavailable information will be null
* @param log
* @returns Transaction Object
*/
private createTransactionFromLog(log: Log): Transaction1559 {
return new Transaction1559({
accessList: undefined, // we don't support access lists for now
Expand Down
33 changes: 33 additions & 0 deletions packages/relay/tests/lib/eth/eth_getTransactionByHash.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -283,4 +283,37 @@ describe('@ethGetTransactionByHash eth_getTransactionByHash tests', async functi
maxPriorityFeePerGas: '0x43',
});
});

it('returns synthetic transaction when it matches cache', async function () {
// prepare cache with synthetic log
const cacheKeySyntheticLog = `${constants.CACHE_KEY.SYNTHETIC_LOG_TRANSACTION_HASH}${defaultDetailedContractResultByHash.hash}`;
const cachedLog = new Log({
address: defaultLogs1[0].address,
blockHash: toHash32(defaultLogs1[0].block_hash),
blockNumber: numberTo0x(defaultLogs1[0].block_number),
data: defaultLogs1[0].data,
logIndex: numberTo0x(defaultLogs1[0].index),
removed: false,
topics: defaultLogs1[0].topics,
transactionHash: toHash32(defaultLogs1[0].transaction_hash),
transactionIndex: nullableNumberTo0x(defaultLogs1[0].transaction_index),
});

cacheService.set(cacheKeySyntheticLog, cachedLog, EthImpl.ethGetTransactionReceipt);

const transaction = await ethImpl.getTransactionByHash(DEFAULT_TX_HASH);

if (transaction) {
// Assert the respnse tx
expect(transaction.blockHash).to.eq(cachedLog.blockHash);
expect(transaction.blockNumber).to.eq(cachedLog.blockNumber);
expect(transaction.from).to.eq(cachedLog.address);
expect(transaction.gas).to.eq(EthImpl.defaultTxGas);
expect(transaction.gasPrice).to.eq(EthImpl.invalidEVMInstruction);
expect(transaction.value).to.eq(EthImpl.oneTwoThreeFourHex);
expect(transaction.to).to.eq(cachedLog.address);
expect(transaction.hash).to.eq(cachedLog.transactionHash);
expect(transaction.transactionIndex).to.eq(cachedLog.transactionIndex);
}
});
});
Loading
Loading