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

Add support for repeated topics in contract logs REST API #3673

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -10,8 +10,6 @@ exports[`checkTimestampsForTopics topic2 param one timestamp lt 1`] = `"Cannot s

exports[`extractContractLogsByIdQuery error - multiple index 1`] = `"Multiple params not allowed for index"`;

exports[`extractContractLogsByIdQuery error - multiple topic0 1`] = `"Multiple params not allowed for topic0"`;

exports[`extractContractLogsByIdQuery error - timestamp not equal operator 1`] = `"Not equals operator not supported for timestamp param"`;

exports[`validateContractIdAndConsensusTimestampParam invalid case - {"contractId":"1","consensusTimestamp":"y"} 1`] = `
Expand Down
Expand Up @@ -684,9 +684,8 @@ describe('extractContractLogsByIdQuery', () => {
operator: utils.opsMap.eq,
value: '0x0011',
},

{
key: constants.filterKeys.TOPIC1,
key: constants.filterKeys.TOPIC0,
operator: utils.opsMap.eq,
value: '0x000013',
},
Expand All @@ -700,24 +699,24 @@ describe('extractContractLogsByIdQuery', () => {
operator: utils.opsMap.eq,
value: '0000150',
},
{
key: constants.filterKeys.TOPIC3,
operator: utils.opsMap.eq,
value: '0000150',
Copy link
Member

Choose a reason for hiding this comment

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

nit: Redundant to say topic3=150 OR topic3=150

},
],
contractId: defaultContractId,
},
expected: {
...defaultExpected,
conditions: [
defaultContractLogCondition,
'cl.topic0 = $2',
'cl.topic1 = $3',
'cl.topic2 = $4',
'cl.topic3 = $5',
],
conditions: [defaultContractLogCondition, 'cl.topic0 in ($2,$3)', 'cl.topic2 in ($4)', 'cl.topic3 in ($5,$6)'],
params: [
defaultContractId,
Buffer.from('11', 'hex'),
Buffer.from('13', 'hex'),
Buffer.from('0140', 'hex'),
Buffer.from('0150', 'hex'),
Buffer.from('0150', 'hex'),
],
},
},
Expand Down Expand Up @@ -790,25 +789,6 @@ describe('extractContractLogsByIdQuery', () => {
},
errorMessage: 'Not equals operator not supported for timestamp param',
},
{
name: 'multiple topic0',
input: {
filter: [
{
key: constants.filterKeys.TOPIC0,
operator: utils.opsMap.eq,
value: '0xaaaa',
},
{
key: constants.filterKeys.TOPIC0,
operator: utils.opsMap.eq,
value: '0xbbbb',
},
],
contractId: defaultContractId,
},
errorMessage: 'Multiple params not allowed for topic0',
},
{
name: 'multiple index',
input: {
Expand Down
@@ -0,0 +1,69 @@
{
ar-conmit marked this conversation as resolved.
Show resolved Hide resolved
"description": "Contract logs api calls with multiple topic1 and timestamp param",
"setup": {
"contractlogs": [
{
"consensus_timestamp": 1639010141000000000,
"contract_id": 1000,
"index": 0,
"topic0": [10],
"topic1": [11]
},
{
"consensus_timestamp": 1639010141000000000,
"contract_id": 1000,
"index": 1,
"topic0": [10],
"topic1": [12],
"topic2": [13]
},
{
"consensus_timestamp": 1639010141000000000,
"contract_id": 1000,
"index": 3,
"topic0": null,
"topic1": null,
"topic2": null
}
]
},
"urls": [
"/api/v1/contracts/0.0.1000/results/logs?topic0=A&timestamp=1639010141.000000000",
"/api/v1/contracts/0.0.1000/results/logs?topic1=B&topic1=C&timestamp=1639010141.000000000"
],
"responseStatus": 200,
"responseJson": {
"logs": [
{
"address": "0x00000000000000000000000000000000000003e8",
"bloom": "0x0123",
"contract_id": "0.0.1000",
"data": "0x0123",
"index": 1,
"root_contract_id": null,
"timestamp": "1639010141.000000000",
"topics": [
"0x000000000000000000000000000000000000000000000000000000000000000a",
"0x000000000000000000000000000000000000000000000000000000000000000c",
"0x000000000000000000000000000000000000000000000000000000000000000d",
"0xe8d47b56e8cdfa95f871b19d4f50a857217c44a95502b0811a350fec1500dd67"
]
},
{
"address": "0x00000000000000000000000000000000000003e8",
"bloom": "0x0123",
"contract_id": "0.0.1000",
"data": "0x0123",
"index": 0,
"root_contract_id": null,
"timestamp": "1639010141.000000000",
"topics": [
"0x000000000000000000000000000000000000000000000000000000000000000a",
"0x000000000000000000000000000000000000000000000000000000000000000b",
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"0xe8d47b56e8cdfa95f871b19d4f50a857217c44a95502b0811a350fec1500dd67"
]
}
]
}
}

This file was deleted.

@@ -0,0 +1,41 @@
{
"description": "Contract logs api calls with mismatching topics",
"setup": {
"contractlogs": [
{
"consensus_timestamp": 1639010141000000000,
"contract_id": 1000,
"index": 0,
"topic0": [10],
"topic1": [11]
},
{
"consensus_timestamp": 1639010141000000000,
"contract_id": 1000,
"index": 1,
"topic0": [10],
"topic1": [12],
"topic2": [13]
},
{
"consensus_timestamp": 1639010141000000000,
"contract_id": 1000,
"index": 3,
"topic0": null,
"topic1": null,
"topic2": null
},
{
"consensus_timestamp": 1639010141000000000,
"contract_id": 1000,
"index": 4,
"topic0": [14]
}
]
},
"urls": ["/api/v1/contracts/0.0.1000/results/logs?topic0=E&topic2=D&timestamp=1639010141.000000000"],
"responseStatus": 200,
"responseJson": {
"logs": []
}
}
11 changes: 5 additions & 6 deletions hedera-mirror-rest/api/v1/openapi.yml
Expand Up @@ -1943,8 +1943,10 @@ components:
custom_fees:
$ref: '#/components/schemas/CustomFees'
LogTopicQueryParam:
type: string
pattern: ^(0x)?[0-9A-Fa-f]{1,64}$
type: array
items:
type: string
pattern: ^(0x)?[0-9A-Fa-f]{1,64}$
TransactionTypes:
type: string
enum:
Expand Down Expand Up @@ -2869,29 +2871,26 @@ components:
logTopic0QueryParam:
name: topic0
in: query
explode: true
description: The first topic associated with a contract log. Requires a timestamp range also be populated.
example: 0x5908baf1fe6ba0149e9bf0af1346476940e92be2436cfccbe428b71009767a10
schema:
$ref: '#/components/schemas/LogTopicQueryParam'
logTopic1QueryParam:
name: topic1
in: query
description: The second topic associated with a contract log. Requires a timestamp range also be populated.
example: 0x5908baf1fe6ba0149e9bf0af1346476940e92be2436cfccbe428b71009767a10
schema:
$ref: '#/components/schemas/LogTopicQueryParam'
logTopic2QueryParam:
name: topic2
in: query
description: The third topic associated with a contract log. Requires a timestamp range also be populated.
example: 0x5908baf1fe6ba0149e9bf0af1346476940e92be2436cfccbe428b71009767a10
schema:
$ref: '#/components/schemas/LogTopicQueryParam'
logTopic3QueryParam:
name: topic3
in: query
description: The fourth topic associated with a contract log. Requires a timestamp range also be populated.
example: 0x5908baf1fe6ba0149e9bf0af1346476940e92be2436cfccbe428b71009767a10
schema:
$ref: '#/components/schemas/LogTopicQueryParam'
spenderIdQueryParam:
Expand Down
45 changes: 27 additions & 18 deletions hedera-mirror-rest/controllers/contractController.js
Expand Up @@ -711,12 +711,23 @@ const extractContractLogsByIdQuery = (filters, contractId) => {
const conditions = [`${ContractLog.getFullName(ContractLog.CONTRACT_ID)} = $1`];
const params = [contractId];

const inValues = {};
const keyFullNames = {};
const oneOperatorValues = {};

keyFullNames[constants.filterKeys.TIMESTAMP] = ContractLog.getFullName(ContractLog.CONSENSUS_TIMESTAMP);
inValues[constants.filterKeys.TIMESTAMP] = [];
const keyFullNames = {
[constants.filterKeys.TIMESTAMP]: ContractLog.getFullName(ContractLog.CONSENSUS_TIMESTAMP),
[constants.filterKeys.TOPIC0]: ContractLog.getFullName(ContractLog.TOPIC0),
[constants.filterKeys.TOPIC1]: ContractLog.getFullName(ContractLog.TOPIC1),
[constants.filterKeys.TOPIC2]: ContractLog.getFullName(ContractLog.TOPIC2),
[constants.filterKeys.TOPIC3]: ContractLog.getFullName(ContractLog.TOPIC3),
};

const inValues = {
[constants.filterKeys.TIMESTAMP]: [],
[constants.filterKeys.TOPIC0]: [],
[constants.filterKeys.TOPIC1]: [],
[constants.filterKeys.TOPIC2]: [],
[constants.filterKeys.TOPIC3]: [],
};

for (const filter of filters) {
switch (filter.key) {
Expand Down Expand Up @@ -751,30 +762,28 @@ const extractContractLogsByIdQuery = (filters, contractId) => {
case constants.filterKeys.TOPIC1:
case constants.filterKeys.TOPIC2:
case constants.filterKeys.TOPIC3:
if (oneOperatorValues[filter.key]) {
throw new InvalidArgumentError(`Multiple params not allowed for ${filter.key}`);
}
let topic = filter.value.replace(/^(0x)?0*/, '');
if (topic.length % 2 !== 0) {
topic = `0${topic}`; //Left pad so that Buffer.from parses correctly
topic = `0${topic}`; // Left pad so that Buffer.from parses correctly
}
topic = Buffer.from(topic, 'hex');
params.push(topic);
conditions.push(`${ContractLog.getFullName(filter.key)}${filter.operator}$${params.length}`);
oneOperatorValues[filter.key] = true;
filter.value = Buffer.from(topic, 'hex');
updateConditionsAndParamsWithInValues(
filter,
inValues[filter.key],
params,
conditions,
keyFullNames[filter.key]
);
break;
default:
break;
}
}

// update query with repeated values
updateQueryFiltersWithInValues(
params,
conditions,
inValues[constants.filterKeys.TIMESTAMP],
keyFullNames[constants.filterKeys.TIMESTAMP]
);
Object.keys(keyFullNames).forEach((filterKey) => {
updateQueryFiltersWithInValues(params, conditions, inValues[filterKey], keyFullNames[filterKey]);
});

return {
conditions,
Expand Down