Skip to content

Commit

Permalink
Merge pull request #171 from ensdomains/mdt/batch-query
Browse files Browse the repository at this point in the history
add subgraph batch query support
  • Loading branch information
mdtanrikulu authored Jun 6, 2024
2 parents 9153b26 + 3b8d725 commit 6ceba97
Show file tree
Hide file tree
Showing 9 changed files with 190 additions and 103 deletions.
92 changes: 41 additions & 51 deletions mock/entry.mock.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,24 @@
import { namehash } from '@ensdomains/ensjs/utils';
import {
keccak256,
toUtf8Bytes
} from 'ethers';
import nock from 'nock';
import { Version } from '../src/base';
import { ADDRESS_NAME_WRAPPER } from '../src/config';
import { Metadata } from '../src/service/metadata';
import getNetwork from '../src/service/network';
import { decodeFuses, getWrapperState } from '../src/utils/fuse';
import { namehash } from '@ensdomains/ensjs/utils';
import { keccak256, toUtf8Bytes } from 'ethers';
import nock from 'nock';
import { Version } from '../src/base';
import { ADDRESS_NAME_WRAPPER } from '../src/config';
import { Metadata } from '../src/service/metadata';
import getNetwork from '../src/service/network';
import { createBatchQuery } from '../src/utils/batchQuery';
import { decodeFuses, getWrapperState } from '../src/utils/fuse';

import {
GET_DOMAINS,
GET_REGISTRATIONS,
GET_WRAPPED_DOMAIN,
} from '../src/service/subgraph';
} from '../src/service/subgraph';
import {
DomainResponse,
MockEntryBody,
RegistrationResponse,
WrappedDomainResponse,
} from './interface';

} from './interface';

const { SUBGRAPH_URL: subgraph_url } = getNetwork('goerli');
const SUBGRAPH_URL = new URL(subgraph_url);
Expand Down Expand Up @@ -53,13 +51,17 @@ export class MockEntry {

if (!registered) {
this.expect = 'No results found.';
const newBatchQuery = createBatchQuery('getDomainInfo')
.add(GET_DOMAINS)
.add(GET_REGISTRATIONS)
.add(GET_WRAPPED_DOMAIN);
nock(SUBGRAPH_URL.origin)
.post(SUBGRAPH_PATH, {
query: GET_DOMAINS,
query: newBatchQuery.query(),
variables: {
tokenId: this.namehash,
},
operationName: 'getDomains',
operationName: 'getDomainInfo',
})
.reply(statusCode, {
data: null,
Expand All @@ -77,16 +79,20 @@ export class MockEntry {
version: Version.v1,
});
this.expect = JSON.parse(JSON.stringify(unknownMetadata));
const newBatchQuery = createBatchQuery('getDomainInfo')
.add(GET_DOMAINS)
.add(GET_REGISTRATIONS)
.add(GET_WRAPPED_DOMAIN);
nock(SUBGRAPH_URL.origin)
.post(SUBGRAPH_PATH, {
query: GET_DOMAINS,
query: newBatchQuery.query(),
variables: {
tokenId: this.namehash,
},
operationName: 'getDomains',
operationName: 'getDomainInfo',
})
.reply(statusCode, {
data: { domain: {} },
data: { domain: {}, registrations: {}, wrappedDomain: {} },
})
.persist(persist);
return;
Expand Down Expand Up @@ -147,19 +153,6 @@ export class MockEntry {
display_type: 'date',
value: expiryDate * 1000,
});

nock(SUBGRAPH_URL.origin)
.post(SUBGRAPH_PATH, {
query: GET_REGISTRATIONS,
variables: {
labelhash,
},
operationName: 'getRegistration',
})
.reply(statusCode, {
data: this.registrationResponse,
})
.persist(persist);
}

if (version === Version.v2) {
Expand All @@ -183,43 +176,40 @@ export class MockEntry {
display_type: 'date',
value: expiryDate * 1000,
});

_metadata.addAttribute({
trait_type: 'Namewrapper State',
display_type: 'string',
value: getWrapperState(decodedFuses),
});

_metadata.description += _metadata.generateRuggableWarning(
_metadata.name, version, getWrapperState(decodedFuses)
)

nock(SUBGRAPH_URL.origin)
.post(SUBGRAPH_PATH, {
query: GET_WRAPPED_DOMAIN,
variables: {
tokenId: this.namehash,
},
operationName: 'getWrappedDomain',
})
.reply(statusCode, {
data: this.wrappedDomainResponse,
})
.persist(persist);
_metadata.name,
version,
getWrapperState(decodedFuses)
);
}

this.expect = JSON.parse(JSON.stringify(_metadata)); //todo: find better serialization option

const newBatchQuery = createBatchQuery('getDomainInfo')
.add(GET_DOMAINS)
.add(GET_REGISTRATIONS)
.add(GET_WRAPPED_DOMAIN);

nock(SUBGRAPH_URL.origin)
.post(SUBGRAPH_PATH, {
query: GET_DOMAINS,
query: newBatchQuery.query(),
variables: {
tokenId: this.namehash,
},
operationName: 'getDomains',
operationName: 'getDomainInfo',
})
.reply(statusCode, {
data: this.domainResponse,
data: {
...this.domainResponse,
...this.registrationResponse,
...this.wrappedDomainResponse,
},
})
.persist(persist);
}
Expand Down
1 change: 0 additions & 1 deletion src/controller/ensMetadata.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { strict as assert } from 'assert';
import { Contract } from 'ethers';
import { Request, Response } from 'express';
import { FetchError } from 'node-fetch';
import {
ContractMismatchError,
ExpiredNameError,
Expand Down
18 changes: 16 additions & 2 deletions src/service/contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,22 @@ async function checkV1Contract(
);
assert(isInterfaceSupported);
return { tokenId: _tokenId, version: Version.v1w };
} catch (error) {
console.warn(`checkV1Contract: nft ownership check fails for ${_tokenId}`);
} catch (error: any) {
if (
// ethers error: given address is not contract, or does not have the supportsInterface method available
error?.info?.method === 'supportsInterface' ||
// assert error: given address is a contract but given INAMEWRAPPER interface is not available
(typeof error?.actual === 'boolean' && !error?.actual)
) {
// fail is expected for regular owners since the owner is not a contract and do not have supportsInterface method
console.warn(
`checkV1Contract: supportsInterface check fails for ${_tokenId}`
);
} else {
console.warn(
`checkV1Contract: nft ownership check fails for ${_tokenId}`
);
}
}
return { tokenId: _tokenId, version: Version.v1 };
}
Expand Down
37 changes: 16 additions & 21 deletions src/service/domain.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import { nockProvider } from '../../mock/helper';
import { TestContext } from '../../mock/interface';
import { NamehashMismatchError, Version } from '../base';
import { ADDRESS_ETH_REGISTRAR, ADDRESS_ETH_REGISTRY } from '../config';
import { createBatchQuery } from '../utils/batchQuery';
import { getDomain } from './domain';
import getNetwork from './network';
import { GET_DOMAINS_BY_LABELHASH, GET_REGISTRATIONS } from './subgraph';
import { GET_DOMAINS_BY_LABELHASH, GET_REGISTRATIONS, GET_WRAPPED_DOMAIN } from './subgraph';

const test = avaTest as TestFn<TestContext>;
const NETWORK = 'mainnet';
Expand Down Expand Up @@ -61,15 +62,20 @@ test.before(async (t: ExecutionContext<TestContext>) => {
}
);

const newBatchQuery = createBatchQuery('getDomainInfo')
.add(GET_DOMAINS_BY_LABELHASH)
.add(GET_REGISTRATIONS)
.add(GET_WRAPPED_DOMAIN);

// fake vitalik.eth with nullifier
nock(SUBGRAPH_URL.origin)
.post(SUBGRAPH_PATH, {
query: GET_DOMAINS_BY_LABELHASH,
.post(SUBGRAPH_URL.pathname + SUBGRAPH_URL.search, {
query: newBatchQuery.query(),
variables: {
tokenId:
'0x3581397a478dcebdc1ee778deed625697f624c6f7dbed8bb7f780a6ac094b772',
},
operationName: 'getDomains',
operationName: 'getDomainInfo',
})
.reply(200, {
data: {
Expand All @@ -86,18 +92,20 @@ test.before(async (t: ExecutionContext<TestContext>) => {
resolver: null,
},
],
registrations: [],
wrappedDomain: null,
},
});

// original vitalik.eth
nock(SUBGRAPH_URL.origin)
.post(SUBGRAPH_PATH, {
query: GET_DOMAINS_BY_LABELHASH,
.post(SUBGRAPH_URL.pathname + SUBGRAPH_URL.search, {
query: newBatchQuery.query(),
variables: {
tokenId:
'0xaf2caa1c2ca1d027f1ac823b529d0a67cd144264b2789fa2ea4d63a67c7103cc',
},
operationName: 'getDomains',
operationName: 'getDomainInfo',
})
.reply(200, {
data: {
Expand All @@ -117,27 +125,14 @@ test.before(async (t: ExecutionContext<TestContext>) => {
},
},
],
},
});

nock(SUBGRAPH_URL.origin)
.post(SUBGRAPH_PATH, {
query: GET_REGISTRATIONS,
variables: {
labelhash:
'0xaf2caa1c2ca1d027f1ac823b529d0a67cd144264b2789fa2ea4d63a67c7103cc',
},
operationName: 'getRegistration',
})
.reply(200, {
data: {
registrations: [
{
labelName: 'vitalik',
registrationDate: '1581013420',
expiryDate: '2032977474',
},
],
wrappedDomain: null,
},
});
});
Expand Down
32 changes: 17 additions & 15 deletions src/service/domain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import {
GET_DOMAINS,
GET_DOMAINS_BY_LABELHASH,
GET_WRAPPED_DOMAIN,
} from './subgraph';
import { Metadata } from './metadata';
import { getAvatarImage } from './avatar';
} from './subgraph';
import { Metadata } from './metadata';
import { getAvatarImage } from './avatar';
import {
ExpiredNameError,
NamehashMismatchError,
Expand All @@ -23,6 +23,7 @@ import {
decodeFuses,
getWrapperState
} from '../utils/fuse';
import { createBatchQuery } from '../utils/batchQuery';
import { getNamehash } from '../utils/namehash';
import { bigIntToUint8Array } from '../utils/bigIntToUint8Array';

Expand All @@ -49,11 +50,16 @@ export async function getDomain(
}
const queryDocument: string =
version !== Version.v2 ? GET_DOMAINS_BY_LABELHASH : GET_DOMAINS;
const result = await request(SUBGRAPH_URL, queryDocument, { tokenId: hexId });
const domain = version !== Version.v2 ? result.domains[0] : result.domain;

const newBatch = createBatchQuery('getDomainInfo');
newBatch.add(queryDocument).add(GET_REGISTRATIONS).add(GET_WRAPPED_DOMAIN);

const domainQueryResult = await request(SUBGRAPH_URL, newBatch.query(), { tokenId: hexId });

const domain = version !== Version.v2 ? domainQueryResult.domains[0] : domainQueryResult.domain;
if (!(domain && Object.keys(domain).length))
throw new SubgraphRecordNotFound(`No record for ${hexId}`);
const { name, labelhash, createdAt, parent, resolver, id: namehash } = domain;
const { name, createdAt, parent, resolver, id: namehash } = domain;

/**
* IMPORTANT
Expand Down Expand Up @@ -109,11 +115,8 @@ export async function getDomain(
}

async function requestAttributes() {
if (parent.id === eth) {
const { registrations } = await request(SUBGRAPH_URL, GET_REGISTRATIONS, {
labelhash,
});
const registration = registrations[0];
if (parent.id === eth && domainQueryResult.registrations?.length) {
const registration = domainQueryResult.registrations[0];
const registered_date = registration.registrationDate * 1000;
const expiration_date = registration.expiryDate * 1000;
if (expiration_date + GRACE_PERIOD_MS < +new Date()) {
Expand All @@ -138,13 +141,12 @@ export async function getDomain(
}
}

if (version === Version.v2) {
if (version === Version.v2 && domainQueryResult.wrappedDomain) {
const {
wrappedDomain: { fuses, expiryDate },
} = await request(SUBGRAPH_URL, GET_WRAPPED_DOMAIN, {
tokenId: namehash,
});
} = domainQueryResult;
const decodedFuses = decodeFuses(fuses);

metadata.addAttribute({
trait_type: 'Namewrapper Fuse States',
display_type: 'object',
Expand Down
2 changes: 1 addition & 1 deletion src/service/metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ export class Metadata {
try {
this.setImage('data:image/svg+xml;base64,' + base64EncodeUnicode(svg));
} catch (e) {
console.log(processedDomain, e);
console.log("generateImage", processedDomain, e);
this.setImage('');
}
}
Expand Down
Loading

0 comments on commit 6ceba97

Please sign in to comment.