Skip to content

Commit 68f658f

Browse files
committed
fix: skipping calling resolveEntityType if action is CREATE
Signed-off-by: Simeon Nakov <[email protected]>
1 parent a7d183a commit 68f658f

File tree

3 files changed

+119
-9
lines changed

3 files changed

+119
-9
lines changed

packages/relay/src/lib/debug.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -257,16 +257,18 @@ export class DebugImpl implements Debug {
257257
async formatActionsResult(result: any, requestDetails: RequestDetails): Promise<[] | any> {
258258
return await Promise.all(
259259
result.map(async (action, index) => {
260-
const { resolvedFrom, resolvedTo } = await this.resolveMultipleAddresses(
261-
action.from,
262-
action.to,
263-
requestDetails,
264-
);
260+
// Skip resolving entity type for CREATE operations with to=null (contract creation)
261+
// MN responds with 400 for contracts/null; avoid querying in this case
262+
const shouldSkipToResolution = action.call_operation_type === CallType.CREATE && !action.to;
263+
264+
// Resolve 'from' normally; resolve 'to' only if present to avoid MN lookups for CREATE with to=null
265+
const resolvedFrom = await this.resolveAddress(action.from, requestDetails);
266+
const resolvedTo = shouldSkipToResolution ? action.to : await this.resolveAddress(action.to, requestDetails);
265267

266268
// The actions endpoint does not return input and output for the calls so we get them from another endpoint
267269
// The first one is excluded because we take its input and output from the contracts/results/{transactionIdOrHash} endpoint
268270
const contract =
269-
index !== 0 && action.call_operation_type === CallType.CREATE
271+
index !== 0 && action.call_operation_type === CallType.CREATE && action.to
270272
? await this.mirrorNodeClient.getContract(action.to, requestDetails)
271273
: undefined;
272274

packages/relay/tests/lib/debug.spec.ts

Lines changed: 78 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ describe('Debug API Test Suite', async function () {
284284
});
285285

286286
this.beforeEach(() => {
287-
cacheService.clear(requestDetails);
287+
cacheService.clear();
288288
});
289289

290290
describe('debug_traceTransaction', async function () {
@@ -640,6 +640,81 @@ describe('Debug API Test Suite', async function () {
640640
expect(address).to.eq(accountAddress);
641641
});
642642
});
643+
644+
describe('formatActionsResult with CREATE actions', async function () {
645+
it('should handle CREATE action with to=null without making MN lookup', async function () {
646+
const createActionWithNullTo = {
647+
actions: [
648+
{
649+
call_depth: 0,
650+
call_operation_type: 'CREATE',
651+
call_type: 'CREATE',
652+
caller: '0.0.1016',
653+
caller_type: 'ACCOUNT',
654+
from: '0x00000000000000000000000000000000000003f8',
655+
gas: 247000,
656+
gas_used: 77324,
657+
index: 0,
658+
input: '0x608060405234801561001057600080fd5b50',
659+
recipient: '0.0.1033',
660+
recipient_type: 'CONTRACT',
661+
result_data: '0x',
662+
result_data_type: 'OUTPUT',
663+
timestamp: '1696438011.462526383',
664+
to: null, // This should not trigger MN lookup
665+
value: 0,
666+
},
667+
],
668+
};
669+
670+
restMock.onGet(SENDER_BY_ADDRESS).reply(200, JSON.stringify(accountsResult));
671+
672+
const result = await debugService.formatActionsResult(createActionWithNullTo.actions, requestDetails);
673+
674+
expect(result).to.be.an('array').with.lengthOf(1);
675+
expect(result[0]).to.have.property('type', 'CREATE');
676+
expect(result[0]).to.have.property('from', '0xc37f417fa09933335240fca72dd257bfbde9c275');
677+
expect(result[0]).to.have.property('to', null);
678+
expect(result[0]).to.have.property('input', '0x608060405234801561001057600080fd5b50');
679+
});
680+
681+
it('should handle CREATE action with to=null and skip getContract call', async function () {
682+
const createActionWithNullTo = {
683+
actions: [
684+
{
685+
call_depth: 0,
686+
call_operation_type: 'CREATE',
687+
call_type: 'CREATE',
688+
caller: '0.0.1016',
689+
caller_type: 'ACCOUNT',
690+
from: '0x00000000000000000000000000000000000003f8',
691+
gas: 247000,
692+
gas_used: 77324,
693+
index: 1, // Non-zero index to test getContract path
694+
input: '0x608060405234801561001057600080fd5b50',
695+
recipient: '0.0.1033',
696+
recipient_type: 'CONTRACT',
697+
result_data: '0x',
698+
result_data_type: 'OUTPUT',
699+
timestamp: '1696438011.462526383',
700+
to: null, // Should skip getContract call
701+
value: 0,
702+
},
703+
],
704+
};
705+
706+
restMock.onGet(SENDER_BY_ADDRESS).reply(200, JSON.stringify(accountsResult));
707+
// No mock for getContract call - should not be called
708+
709+
const result = await debugService.formatActionsResult(createActionWithNullTo.actions, requestDetails);
710+
711+
expect(result).to.be.an('array').with.lengthOf(1);
712+
expect(result[0]).to.have.property('type', 'CREATE');
713+
expect(result[0]).to.have.property('to', null);
714+
expect(result[0]).to.have.property('input', '0x608060405234801561001057600080fd5b50');
715+
expect(result[0]).to.have.property('output', '0x');
716+
});
717+
});
643718
});
644719
});
645720
});
@@ -710,7 +785,7 @@ describe('Debug API Test Suite', async function () {
710785
sinon.restore();
711786
restMock.reset();
712787
web3Mock.reset();
713-
cacheService.clear(requestDetails);
788+
cacheService.clear();
714789
});
715790

716791
withOverriddenEnvsInMochaTest({ DEBUG_API_ENABLED: undefined }, () => {
@@ -999,7 +1074,7 @@ describe('Debug API Test Suite', async function () {
9991074
sinon.restore();
10001075
restMock.reset();
10011076
web3Mock.reset();
1002-
cacheService.clear(requestDetails);
1077+
cacheService.clear();
10031078
});
10041079

10051080
withOverriddenEnvsInMochaTest({ DEBUG_API_ENABLED: true }, () => {

packages/server/tests/acceptance/debug.spec.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,39 @@ describe('@debug API Acceptance Tests', function () {
319319
predefined.INVALID_PARAMETER("'tracer' for TracerConfigWrapper", 'Expected TracerType, value: InvalidTracer'),
320320
);
321321
});
322+
323+
it('@release should handle CREATE transactions with to=null in trace results', async function () {
324+
// Just use the existing basic contract deployment to test CREATE transactions
325+
// The deployment block should contain a CREATE transaction
326+
const deploymentBlockHex = numberTo0x(deploymentBlockNumber);
327+
328+
// Call debug_traceBlockByNumber for the deployment block
329+
const result = await relay.call(DEBUG_TRACE_BLOCK_BY_NUMBER, [
330+
deploymentBlockHex,
331+
TRACER_CONFIGS.CALL_TRACER_TOP_ONLY_FALSE,
332+
]);
333+
334+
expect(result).to.be.an('array');
335+
expect(result.length).to.be.at.least(1);
336+
337+
// Find a CREATE transaction in the result (from contract deployment)
338+
const createTxTrace = result.find((trace) => trace.result && trace.result.type === 'CREATE');
339+
expect(createTxTrace).to.exist;
340+
expect(createTxTrace.result).to.exist;
341+
342+
// Verify it's a CREATE transaction with proper structure
343+
expect(createTxTrace.result.type).to.equal('CREATE');
344+
expect(createTxTrace.result.from).to.exist;
345+
expect(createTxTrace.result.to).to.exist;
346+
expect(createTxTrace.result.gas).to.exist;
347+
expect(createTxTrace.result.gasUsed).to.exist;
348+
expect(createTxTrace.result.input).to.exist;
349+
expect(createTxTrace.result.output).to.exist;
350+
351+
// This test verifies that CREATE actions with to=null don't cause Mirror Node lookup errors
352+
// The main goal is that the debug_traceBlockByNumber call succeeds without throwing
353+
// "Invalid parameter: contractid" errors when processing CREATE transactions
354+
});
322355
});
323356

324357
describe('debug_traceTransaction', () => {

0 commit comments

Comments
 (0)