Skip to content

Commit

Permalink
debug: increase logs of simulateBundle endpoint (#105)
Browse files Browse the repository at this point in the history
* debug: increase logs of simulateBundle endpoint

* debug: add logs on the repository level
  • Loading branch information
yvesfracari authored Jan 22, 2025
1 parent 6582512 commit 48a5255
Show file tree
Hide file tree
Showing 3 changed files with 168 additions and 55 deletions.
29 changes: 23 additions & 6 deletions apps/api/src/app/inversify.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,6 @@ import {
viemClients,
} from '@cowprotocol/repositories';

const DEFAULT_CACHE_VALUE_SECONDS = ms('2min') / 1000; // 2min cache time by default for values
const DEFAULT_CACHE_NULL_SECONDS = ms('30min') / 1000; // 30min cache time by default for NULL values (when the repository isn't known)

const CACHE_TOKEN_INFO_SECONDS = ms('24h') / 1000; // 24h

import { Container } from 'inversify';
import {
SimulationService,
Expand All @@ -47,6 +42,20 @@ import {
usdServiceSymbol,
} from '@cowprotocol/services';
import ms from 'ms';
import pino from 'pino';

const DEFAULT_CACHE_VALUE_SECONDS = ms('2min') / 1000; // 2min cache time by default for values
const DEFAULT_CACHE_NULL_SECONDS = ms('30min') / 1000; // 30min cache time by default for NULL values (when the repository isn't known)

const CACHE_TOKEN_INFO_SECONDS = ms('24h') / 1000; // 24h

// Configure the logger
const logger = pino({
level: process.env.LOG_LEVEL || 'info',
formatters: {
level: (label) => ({ level: label }),
},
});

function getErc20Repository(cacheRepository: CacheRepository): Erc20Repository {
return new Erc20RepositoryCache(
Expand Down Expand Up @@ -133,12 +142,20 @@ function getTokenHolderRepository(
]);
}

function getSimulationRepository(): SimulationRepository {
return new SimulationRepositoryTenderly(logger.child({ module: 'tenderly' }));
}

function getApiContainer(): Container {
const apiContainer = new Container();

// Bind logger
apiContainer.bind<pino.Logger>('Logger').toConstantValue(logger);

// Repositories
const cacheRepository = getCacheRepository(apiContainer);
const erc20Repository = getErc20Repository(cacheRepository);
const simulationRepository = new SimulationRepositoryTenderly();
const simulationRepository = getSimulationRepository();
const tokenHolderRepository = getTokenHolderRepository(cacheRepository);

apiContainer
Expand Down
35 changes: 22 additions & 13 deletions apps/api/src/app/routes/__chainId/simulation/simulateBundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,23 +130,32 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
},
},
async function (request, reply) {
const { chainId } = request.params;
try {
const { chainId } = request.params;

const simulationResult =
await tenderlyService.postTenderlyBundleSimulation(
chainId,
request.body
fastify.log.info(
`Starting simulation of ${request.body.length} transactions on chain ${chainId}`
);

if (simulationResult === null) {
reply.code(400).send({ message: 'Build simulation error' });
return;
}
fastify.log.info(
`Post Tenderly bundle of ${request.body.length} simulation on chain ${chainId}`
);
const simulationResult =
await tenderlyService.postTenderlyBundleSimulation(
chainId,
request.body
);

if (simulationResult === null) {
reply.code(400).send({ message: 'Build simulation error' });
return;
}
fastify.log.info(
`Post bundle of ${request.body.length} simulation on chain ${chainId}`
);

reply.send(simulationResult);
reply.send(simulationResult);
} catch (e) {
fastify.log.error('Error in /simulateBundle', e);
reply.code(500).send({ message: 'Error in /simulateBundle' });
}
}
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,94 @@ import {
TENDERLY_API_BASE_ENDPOINT,
TENDERLY_API_KEY,
} from '../datasources/tenderlyApi';
import { injectable } from 'inversify';
import { injectable, inject } from 'inversify';
import {
SimulationData,
SimulationInput,
SimulationRepository,
} from './SimulationRepository';
import { BigNumber } from 'ethers';
import { Logger } from 'pino';

interface TenderlyRequestLog {
timestamp: string;
chainId: SupportedChainId;
endpoint: string;
method: string;
simulationsCount: number;
simulations: TenderlySimulatePayload[];
}

interface TenderlyResponseLog {
timestamp: string;
duration: number;
status: 'success' | 'error';
error?: string;
simulationResults?: {
id: string;
status: boolean;
gasUsed?: string;
}[];
}

export const tenderlyRepositorySymbol = Symbol.for('TenderlyRepository');

@injectable()
export class SimulationRepositoryTenderly implements SimulationRepository {
constructor(@inject('Logger') private readonly logger: Logger) {}

private logRequest(
chainId: SupportedChainId,
simulations: TenderlySimulatePayload[]
): TenderlyRequestLog {
const requestLog: TenderlyRequestLog = {
timestamp: new Date().toISOString(),
chainId,
endpoint: `${TENDERLY_API_BASE_ENDPOINT}/simulate-bundle`,
method: 'POST',
simulationsCount: simulations.length,
simulations,
};

this.logger.info({
msg: 'Tenderly simulation request',
...requestLog,
});

return requestLog;
}

private logResponse(
startTime: number,
response: TenderlyBundleSimulationResponse | SimulationError
): TenderlyResponseLog {
const duration = Date.now() - startTime;
const responseLog: TenderlyResponseLog = {
timestamp: new Date().toISOString(),
duration,
status: this.checkBundleSimulationError(response) ? 'error' : 'success',
};

if (this.checkBundleSimulationError(response)) {
responseLog.error = response.error.message;
} else {
responseLog.simulationResults = response.simulation_results.map(
(result) => ({
id: result.simulation.id,
status: result.simulation.status,
gasUsed: result.transaction?.gas_used.toString(),
})
);
}

this.logger.info({
msg: 'Tenderly simulation response',
...responseLog,
});

return responseLog;
}

async postBundleSimulation(
chainId: SupportedChainId,
simulationsInput: SimulationInput[]
Expand All @@ -33,38 +109,55 @@ export class SimulationRepositoryTenderly implements SimulationRepository {
save: true,
save_if_fails: true,
})) as TenderlySimulatePayload[];
const response = (await fetch(
`${TENDERLY_API_BASE_ENDPOINT}/simulate-bundle`,
{
method: 'POST',
body: JSON.stringify({ simulations }),
headers: {
'X-Access-Key': TENDERLY_API_KEY,
},

const startTime = Date.now();
this.logRequest(chainId, simulations);

try {
const response = (await fetch(
`${TENDERLY_API_BASE_ENDPOINT}/simulate-bundle`,
{
method: 'POST',
body: JSON.stringify({ simulations }),
headers: {
'X-Access-Key': TENDERLY_API_KEY,
},
}
).then((res) => res.json())) as
| TenderlyBundleSimulationResponse
| SimulationError;

this.logResponse(startTime, response);

if (this.checkBundleSimulationError(response)) {
return null;
}
).then((res) => res.json())) as
| TenderlyBundleSimulationResponse
| SimulationError;

if (this.checkBundleSimulationError(response)) {
return null;
}
const balancesDiff = this.buildBalancesDiff(
response.simulation_results.map(
(result) => result.transaction?.transaction_info.asset_changes || []
)
);

const balancesDiff = this.buildBalancesDiff(
response.simulation_results.map(
(result) => result.transaction?.transaction_info.asset_changes || []
)
);

return response.simulation_results.map((simulation_result, i) => {
return {
status: simulation_result.simulation.status,
id: simulation_result.simulation.id,
link: getTenderlySimulationLink(simulation_result.simulation.id),
cumulativeBalancesDiff: balancesDiff[i],
gasUsed: simulation_result.transaction?.gas_used.toString(),
};
});
return response.simulation_results.map((simulation_result, i) => {
return {
status: simulation_result.simulation.status,
id: simulation_result.simulation.id,
link: getTenderlySimulationLink(simulation_result.simulation.id),
cumulativeBalancesDiff: balancesDiff[i],
gasUsed: simulation_result.transaction?.gas_used.toString(),
};
});
} catch (error) {
this.logger.error({
msg: 'Tenderly simulation unexpected error',
error: error instanceof Error ? error.message : 'Unknown error',
chainId,
simulationsCount: simulations.length,
duration: Date.now() - startTime,
});
throw error;
}
}

checkBundleSimulationError(
Expand All @@ -77,13 +170,11 @@ export class SimulationRepositoryTenderly implements SimulationRepository {
assetChangesList: AssetChange[][]
): Record<string, Record<string, string>>[] {
const cumulativeBalancesDiff: Record<string, Record<string, string>> = {};

return assetChangesList.map((assetChanges) => {
assetChanges.forEach((change) => {
const { token_info, from, to, raw_amount } = change;
const { contract_address } = token_info;

// Helper function to update balance
const updateBalance = (
address: string,
tokenSymbol: string,
Expand All @@ -95,25 +186,21 @@ export class SimulationRepositoryTenderly implements SimulationRepository {
if (!cumulativeBalancesDiff[address][tokenSymbol]) {
cumulativeBalancesDiff[address][tokenSymbol] = '0';
}

const currentBalance = BigNumber.from(
cumulativeBalancesDiff[address][tokenSymbol]
);
const changeValue = BigNumber.from(changeAmount);
const newBalance = currentBalance.add(changeValue);

cumulativeBalancesDiff[address][tokenSymbol] = newBalance.toString();
};

if (from) {
updateBalance(from, contract_address, `-${raw_amount}`);
}

if (to) {
updateBalance(to, contract_address, raw_amount);
}
});

return JSON.parse(JSON.stringify(cumulativeBalancesDiff));
});
}
Expand Down

0 comments on commit 48a5255

Please sign in to comment.