Add tests for querying names and deleteName

This commit is contained in:
nabarun 2022-04-08 16:02:24 +05:30 committed by Ashwin Phatak
parent 9de3c59c19
commit f207be4838
7 changed files with 406 additions and 10 deletions

View File

@ -9,6 +9,7 @@
"devDependencies": { "devDependencies": {
"@types/is-url": "^1.2.30", "@types/is-url": "^1.2.30",
"@types/jest": "^27.4.1", "@types/jest": "^27.4.1",
"@types/lodash": "^4.14.181",
"@types/semver": "^7.3.9", "@types/semver": "^7.3.9",
"dotenv": "^16.0.0", "dotenv": "^16.0.0",
"jest": "^27.5.1", "jest": "^27.5.1",
@ -32,6 +33,7 @@
"js-sha256": "^0.9.0", "js-sha256": "^0.9.0",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"jsonschema": "^1.4.0", "jsonschema": "^1.4.0",
"lodash": "^4.17.21",
"node-yaml": "^4.0.1", "node-yaml": "^4.0.1",
"semver": "^7.3.5", "semver": "^7.3.5",
"tiny-secp256k1": "^2.2.1" "tiny-secp256k1": "^2.2.1"

View File

@ -14,7 +14,7 @@ import { createTxMsgCancelBond, createTxMsgCreateBond, createTxMsgRefillBond, cr
import { RegistryClient } from "./registry-client"; import { RegistryClient } from "./registry-client";
import { Account } from "./account"; import { Account } from "./account";
import { createTransaction } from "./txbuilder"; import { createTransaction } from "./txbuilder";
import { createTxMsgReserveAuthority, createTxMsgSetAuthorityBond, createTxMsgSetName, createTxMsgSetRecord, MessageMsgReserveAuthority, MessageMsgSetAuthorityBond, MessageMsgSetName, MessageMsgSetRecord } from './messages/nameservice'; import { createTxMsgDeleteName, createTxMsgReserveAuthority, createTxMsgSetAuthorityBond, createTxMsgSetName, createTxMsgSetRecord, MessageMsgDeleteName, MessageMsgReserveAuthority, MessageMsgSetAuthorityBond, MessageMsgSetName, MessageMsgSetRecord } from './messages/nameservice';
import { Payload, Record } from './types'; import { Payload, Record } from './types';
const DEFAULT_WRITE_ERROR = 'Unable to write to chiba-clonk.'; const DEFAULT_WRITE_ERROR = 'Unable to write to chiba-clonk.';
@ -82,6 +82,20 @@ export class Registry {
return this._client.getAccount(address); return this._client.getAccount(address);
} }
/**
* Get records by attributes.
*/
async queryRecords(attributes: {[key: string]: any}, all = false, refs = false) {
return this._client.queryRecords(attributes, all, refs);
}
/**
* Resolve names to records.
*/
async resolveNames(names: string[], refs = false) {
return this._client.resolveNames(names, refs);
}
/** /**
* Publish record. * Publish record.
* @param transactionPrivateKey - private key in HEX to sign transaction. * @param transactionPrivateKey - private key in HEX to sign transaction.
@ -245,7 +259,7 @@ export class Registry {
/** /**
* Cancel bond. * Cancel bond.
*/ */
async cancelBond(params: MessageMsgCancelBond, senderAddress: string, privateKey: string, fee: Fee) { async cancelBond(params: MessageMsgCancelBond, senderAddress: string, privateKey: string, fee: Fee) {
let result; let result;
try { try {
@ -338,7 +352,7 @@ export class Registry {
* @param {string} privateKey * @param {string} privateKey
* @param {object} fee * @param {object} fee
*/ */
async setName(params: MessageMsgSetName, senderAddress: string, privateKey: string, fee: Fee) { async setName(params: MessageMsgSetName, senderAddress: string, privateKey: string, fee: Fee) {
let result; let result;
try { try {
@ -361,6 +375,39 @@ export class Registry {
return parseTxResponse(result); return parseTxResponse(result);
} }
/**
* Lookup naming information.
*/
async lookupNames(names: string[], history = false) {
return this._client.lookupNames(names, history);
}
/**
* Delete name (WRN) mapping.
*/
async deleteName(params: MessageMsgDeleteName, senderAddress: string, privateKey: string, fee: Fee) {
let result;
try {
const { account: { base_account: accountInfo } } = await this.getAccount(senderAddress);
const sender = {
accountAddress: accountInfo.address,
sequence: accountInfo.sequence,
accountNumber: accountInfo.account_number,
pubkey: accountInfo.pub_key.key,
}
const msg = createTxMsgDeleteName(this._chain, sender, fee, '', params)
result = await this._submitTx(msg, privateKey, sender);
} catch (err: any) {
const error = err[0] || err;
throw new Error(Registry.processWriteError(error));
}
return parseTxResponse(result);
}
/** /**
* Submit record transaction. * Submit record transaction.
* @param privateKey - private key in HEX to sign message. * @param privateKey - private key in HEX to sign message.

View File

@ -60,6 +60,13 @@ const MSG_SET_AUTHORITY_BOND_TYPES = {
], ],
} }
const MSG_DELETE_NAME_TYPES = {
MsgValue: [
{ name: 'wrn', type: 'string' },
{ name: 'signer', type: 'string' },
],
}
export interface MessageMsgReserveAuthority { export interface MessageMsgReserveAuthority {
name: string name: string
owner: string owner: string
@ -80,6 +87,10 @@ export interface MessageMsgSetAuthorityBond {
bondId: string bondId: string
} }
export interface MessageMsgDeleteName {
wrn: string
}
export function createTxMsgReserveAuthority( export function createTxMsgReserveAuthority(
chain: Chain, chain: Chain,
sender: Sender, sender: Sender,
@ -176,6 +187,28 @@ export function createTxMsgSetAuthorityBond(
return createTx(chain, sender, fee, memo, types, msg, msgCosmos) return createTx(chain, sender, fee, memo, types, msg, msgCosmos)
} }
export function createTxMsgDeleteName(
chain: Chain,
sender: Sender,
fee: Fee,
memo: string,
params: MessageMsgDeleteName,
) {
const types = generateTypes(MSG_DELETE_NAME_TYPES)
const msg = createMsgDeleteName(
params.wrn,
sender.accountAddress
)
const msgCosmos = protoCreateMsgDeleteName(
params.wrn,
sender.accountAddress
)
return createTx(chain, sender, fee, memo, types, msg, msgCosmos)
}
function createMsgReserveAuthority( function createMsgReserveAuthority(
name: string, name: string,
signer: string, signer: string,
@ -316,3 +349,31 @@ const protoCreateMsgSetAuthorityBond = (
path: 'vulcanize.nameservice.v1beta1.MsgSetAuthorityBond', path: 'vulcanize.nameservice.v1beta1.MsgSetAuthorityBond',
} }
} }
function createMsgDeleteName(
wrn: string,
signer: string
) {
return {
type: 'nameservice/DeleteAuthority',
value: {
wrn,
signer
},
}
}
const protoCreateMsgDeleteName = (
wrn: string,
signer: string
) => {
const deleteNameAutorityMessage = new nameserviceTx.vulcanize.nameservice.v1beta1.MsgDeleteNameAuthority({
wrn,
signer,
})
return {
message: deleteNameAutorityMessage,
path: 'vulcanize.nameservice.v1beta1.MsgDeleteNameAuthority',
}
}

View File

@ -1,11 +1,13 @@
import assert from 'assert'; import assert from 'assert';
import path from 'path'; import path from 'path';
import semver from 'semver';
import { Account } from './account'; import { Account } from './account';
import { Registry } from './index'; import { Registry } from './index';
import { ensureUpdatedConfig, getConfig } from './testing/helper'; import { getBaseConfig, getConfig } from './testing/helper';
const WATCHER_ID = 'bafyreibmr47ksukoadck2wigevb2jp5j5oubfadeyzb6zi57ydjsvjmmby' const WATCHER_1_ID = 'bafyreif7z4lbftuxkj7nu4bl7xihitqvhus3wmoqdkjo7lwv24j6fkfskm'
const WATCHER_2_ID = 'bafyreiaplz7n2bddg3xhkeuiavwnku7xta2s2cfyaza37ytv4vw367exo4'
const WATCHER_YML_PATH = path.join(__dirname, './testing/data/watcher.yml'); const WATCHER_YML_PATH = path.join(__dirname, './testing/data/watcher.yml');
jest.setTimeout(120 * 1000); jest.setTimeout(120 * 1000);
@ -14,10 +16,15 @@ const { chainId, restEndpoint, gqlEndpoint, privateKey, accountAddress, fee } =
const namingTests = () => { const namingTests = () => {
let registry: Registry; let registry: Registry;
let bondId: string; let bondId: string;
let watcher: any; let watcher: any;
let watcherId: string; let watcherId: string;
let authorityName: string; let authorityName: string;
let otherAuthorityName: string;
let otherPrivateKey: string;
let wrn: string; let wrn: string;
beforeAll(async () => { beforeAll(async () => {
@ -28,7 +35,8 @@ const namingTests = () => {
await registry.createBond({ denom: 'aphoton', amount: '1000000000' }, accountAddress, privateKey, fee); await registry.createBond({ denom: 'aphoton', amount: '1000000000' }, accountAddress, privateKey, fee);
// Create bot. // Create bot.
watcher = await ensureUpdatedConfig(WATCHER_YML_PATH); // TODO: Use ensureUpdatedConfig from helper.
watcher = await getBaseConfig(WATCHER_YML_PATH);
const result = await registry.setRecord( const result = await registry.setRecord(
{ {
privateKey, privateKey,
@ -42,7 +50,7 @@ const namingTests = () => {
// TODO: Get id from setRecord response. // TODO: Get id from setRecord response.
// watcherId = result.data; // watcherId = result.data;
watcherId = WATCHER_ID; watcherId = WATCHER_1_ID;
}); });
test('Reserve authority.', async () => { test('Reserve authority.', async () => {
@ -116,6 +124,142 @@ const namingTests = () => {
test('Set authority bond', async () => { test('Set authority bond', async () => {
await registry.setAuthorityBond({ name: authorityName, bondId }, accountAddress, privateKey, fee); await registry.setAuthorityBond({ name: authorityName, bondId }, accountAddress, privateKey, fee);
}); });
test('Set name', async () => {
wrn = `wrn://${authorityName}/app/test`;
await registry.setName({ wrn, cid: watcherId }, accountAddress, privateKey, fee);
// Query records should return it (some WRN points to it).
const records = await registry.queryRecords({ type: 'watcher', version: watcher.record.version });
expect(records).toBeDefined();
// TODO: Fix queryRecords to return filtered results.
// expect(records).toHaveLength(1);
});
test('Lookup name', async () => {
const records = await registry.lookupNames([wrn]);
expect(records).toBeDefined();
expect(records).toHaveLength(1);
const [{ latest, history }] = records;
expect(latest).toBeDefined();
expect(latest.id).toBeDefined();
expect(latest.id).toBe(watcherId);
expect(latest.height).toBeDefined();
expect(history).toBeUndefined();
});
test('Resolve name', async () => {
const records = await registry.resolveNames([wrn]);
expect(records).toBeDefined();
expect(records).toHaveLength(1);
const [{ attributes }] = records;
// TODO: Set name with new record.
// expect(attributes).toEqual(watcher.record);
});
test('Lookup name with history', async () => {
// TODO: Use ensureUpdatedConfig from helper.
const updatedWatcher = await getBaseConfig(WATCHER_YML_PATH);
updatedWatcher.record.version = semver.inc(updatedWatcher.record.version, 'patch');
const result = await registry.setRecord(
{
privateKey,
bondId,
record: updatedWatcher.record
},
accountAddress,
privateKey,
fee
)
// TODO: Get id from setRecord response.
// const updatedWatcherId = result.data;
const updatedWatcherId = WATCHER_2_ID;
await registry.setName({ wrn, cid: updatedWatcherId }, accountAddress, privateKey, fee);
const records = await registry.lookupNames([wrn], true);
expect(records).toHaveLength(1);
const [{ latest, history }] = records;
expect(latest).toBeDefined();
expect(latest.id).toBeDefined();
expect(latest.id).toBe(updatedWatcherId);
expect(latest.height).toBeDefined();
expect(history).toBeDefined();
expect(history).toHaveLength(1);
const [oldRecord] = history;
expect(oldRecord).toBeDefined();
expect(oldRecord.id).toBeDefined();
expect(oldRecord.id).toBe(watcherId);
expect(oldRecord.height).toBeDefined();
});
xtest('Set name without reserving authority', async () => {
await expect(registry.setName({ wrn: 'wrn://not-reserved/app/test', cid: watcherId }, accountAddress, privateKey, fee))
.rejects.toThrow('Name authority not found.');
});
xtest('Set name for non-owned authority', async () => {
// Create another account.
const mnenonic = Account.generateMnemonic();
const otherAccount = await Account.generateFromMnemonic(mnenonic);
await otherAccount.init()
assert(otherAccount.formattedCosmosAddress)
// TODO: Get correct account address from private key.
await registry.sendCoins({ denom: 'aphoton', amount: '1000000000', destinationAddress: otherAccount.formattedCosmosAddress }, accountAddress, privateKey, fee);
// Other account reserves an authority.
otherAuthorityName = `other-${Date.now()}`;
otherPrivateKey = otherAccount.privateKey.toString('hex');
await registry.reserveAuthority({ name: otherAuthorityName, owner: otherAccount.formattedCosmosAddress }, otherAccount.formattedCosmosAddress, otherPrivateKey, fee);
// Try setting name under other authority.
await expect(registry.setName({ wrn: `wrn://${otherAuthorityName}/app/test`, cid: watcherId }, accountAddress, privateKey, fee)).rejects.toThrow('Access denied.');
});
test('Lookup non existing name', async () => {
const records = await registry.lookupNames(['wrn://not-reserved/app/test']);
expect(records).toBeDefined();
expect(records).toHaveLength(1);
const [record] = records;
expect(record).toBeNull();
});
test('Resolve non existing name', async () => {
const records = await registry.resolveNames(['wrn://not-reserved/app/test']);
expect(records).toBeDefined();
expect(records).toHaveLength(1);
const [record] = records;
expect(record).toBeNull();
});
test('Delete name', async () => {
await registry.deleteName({ wrn }, accountAddress, privateKey, fee);
let records = await registry.lookupNames([wrn], true);
expect(records).toBeDefined();
expect(records).toHaveLength(1);
const [{ latest }] = records;
expect(latest).toBeDefined();
expect(latest.id).toBeDefined();
expect(latest.id).toBe('');
expect(latest.height).toBeDefined();
// Query records should NOT return it (no WRN points to it).
records = await registry.queryRecords({ type: 'watcher', version: watcher.record.version });
expect(records).toBeDefined();
// TODO: Fix queryRecords to return filtered results.
// expect(records).toHaveLength(0);
// Query all records should return it (all: true).
records = await registry.queryRecords({ type: 'watcher', version: watcher.record.version }, true);
expect(records).toBeDefined();
// expect(records).toHaveLength(1);
});
}; };
if (process.env.AUCTIONS_ENABLED) { if (process.env.AUCTIONS_ENABLED) {

View File

@ -1,10 +1,41 @@
import assert from 'assert'; import assert from 'assert';
import axios from 'axios'; import axios from 'axios';
import graphqlClient from 'graphql.js' import graphqlClient from 'graphql.js'
import { generateEndpointAccount, generateEndpointBroadcast, generatePostBodyBroadcast } from '@tharsis/provider'; import { get, set } from 'lodash'
import { generateEndpointAccount, generateEndpointBroadcast } from '@tharsis/provider';
import { Util } from './util'; import { Util } from './util';
const attributeField = `
attributes {
key
value {
null
int
float
string
boolean
json
reference {
id
}
}
}
`;
const refsField = `
references {
id
}
`;
const historyFields = `
history {
id
height
}
`;
const auctionFields = ` const auctionFields = `
id id
status status
@ -64,7 +95,7 @@ export class RegistryClient {
/** /**
* Get query result. * Get query result.
*/ */
static async getResult(query: any, key: string, modifier?: (rows: any[]) => {}) { static async getResult(query: any, key: string, modifier?: (rows: any[]) => {}) {
const result = await query; const result = await query;
if (result && result[key] && result[key].length && result[key][0] !== null) { if (result && result[key] && result[key].length && result[key][0] !== null) {
if (modifier) { if (modifier) {
@ -75,6 +106,19 @@ export class RegistryClient {
return []; return [];
} }
/**
* Prepare response attributes.
*/
static prepareAttributes(path: string) {
return (rows: any[]) => {
const result = rows.map(r => {
set(r, path, Util.fromGQLAttributes(get(r, path)));
return r;
});
return result;
};
}
/** /**
* New Client. * New Client.
*/ */
@ -100,6 +144,38 @@ export class RegistryClient {
return data return data
} }
/**
* Get records by attributes.
*/
async queryRecords(attributes: {[key: string]: any}, all = false, refs = false) {
if (!attributes) {
attributes = {};
}
const query = `query ($attributes: [KeyValueInput!], $all: Boolean) {
queryRecords(attributes: $attributes, all: $all) {
id
names
owners
bondId
createTime
expiryTime
${attributeField}
${refs ? refsField : ''}
}
}`;
const variables = {
attributes: Util.toGQLAttributes(attributes),
all
};
let result = (await this._graph(query)(variables))['queryRecords'];
result = RegistryClient.prepareAttributes('attributes')(result);
return result;
}
/** /**
* Lookup authorities by names. * Lookup authorities by names.
*/ */
@ -127,6 +203,60 @@ export class RegistryClient {
return result['lookupAuthorities']; return result['lookupAuthorities'];
} }
/**
* Lookup names.
*/
async lookupNames(names: string[], history = false) {
assert(names.length);
const query = `query ($names: [String!]) {
lookupNames(names: $names) {
latest {
id
height
}
${history ? historyFields : ''}
}
}`;
const variables = {
names
};
const result = await this._graph(query)(variables);
return result['lookupNames'];
}
/**
* Resolve names to records.
*/
async resolveNames(names: string[], refs = false) {
assert(names.length);
const query = `query ($names: [String!]) {
resolveNames(names: $names) {
id
names
owners
bondId
createTime
expiryTime
${attributeField}
${refs ? refsField : ''}
}
}`;
const variables = {
names
};
const result = (await this._graph(query)(variables))['resolveNames'];
result.records = RegistryClient.prepareAttributes('attributes')(result);
return result;
}
/** /**
* Get bonds by ids. * Get bonds by ids.
*/ */

View File

@ -10,6 +10,13 @@ export const ensureUpdatedConfig = async (path: string) => {
return conf; return conf;
}; };
export const getBaseConfig = async (path: string) => {
const conf = await yaml.read(path);
conf.record.version = '0.0.1';
return conf;
};
export const getConfig = () => { export const getConfig = () => {
assert(process.env.PRIVATE_KEY); assert(process.env.PRIVATE_KEY);
assert(process.env.ACCOUNT_ADDRESS); assert(process.env.ACCOUNT_ADDRESS);

View File

@ -724,6 +724,11 @@
jest-matcher-utils "^27.0.0" jest-matcher-utils "^27.0.0"
pretty-format "^27.0.0" pretty-format "^27.0.0"
"@types/lodash@^4.14.181":
version "4.14.181"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.181.tgz#d1d3740c379fda17ab175165ba04e2d03389385d"
integrity sha512-n3tyKthHJbkiWhDZs3DkhkCzt2MexYHXlX0td5iMplyfwketaOeKboEVBqzceH7juqvEg3q5oUoBFxSLu7zFag==
"@types/node@*", "@types/node@^17.0.21": "@types/node@*", "@types/node@^17.0.21":
version "17.0.23" version "17.0.23"
resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.23.tgz#3b41a6e643589ac6442bdbd7a4a3ded62f33f7da" resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.23.tgz#3b41a6e643589ac6442bdbd7a4a3ded62f33f7da"
@ -2500,7 +2505,7 @@ lodash.memoize@4.x:
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=
lodash@^4.7.0: lodash@^4.17.21, lodash@^4.7.0:
version "4.17.21" version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==