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

View File

@ -14,7 +14,7 @@ import { createTxMsgCancelBond, createTxMsgCreateBond, createTxMsgRefillBond, cr
import { RegistryClient } from "./registry-client";
import { Account } from "./account";
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';
const DEFAULT_WRITE_ERROR = 'Unable to write to chiba-clonk.';
@ -82,6 +82,20 @@ export class Registry {
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.
* @param transactionPrivateKey - private key in HEX to sign transaction.
@ -245,7 +259,7 @@ export class Registry {
/**
* Cancel bond.
*/
async cancelBond(params: MessageMsgCancelBond, senderAddress: string, privateKey: string, fee: Fee) {
async cancelBond(params: MessageMsgCancelBond, senderAddress: string, privateKey: string, fee: Fee) {
let result;
try {
@ -338,7 +352,7 @@ export class Registry {
* @param {string} privateKey
* @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;
try {
@ -361,6 +375,39 @@ export class Registry {
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.
* @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 {
name: string
owner: string
@ -80,6 +87,10 @@ export interface MessageMsgSetAuthorityBond {
bondId: string
}
export interface MessageMsgDeleteName {
wrn: string
}
export function createTxMsgReserveAuthority(
chain: Chain,
sender: Sender,
@ -176,6 +187,28 @@ export function createTxMsgSetAuthorityBond(
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(
name: string,
signer: string,
@ -316,3 +349,31 @@ const protoCreateMsgSetAuthorityBond = (
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 path from 'path';
import semver from 'semver';
import { Account } from './account';
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');
jest.setTimeout(120 * 1000);
@ -14,10 +16,15 @@ const { chainId, restEndpoint, gqlEndpoint, privateKey, accountAddress, fee } =
const namingTests = () => {
let registry: Registry;
let bondId: string;
let watcher: any;
let watcherId: string;
let authorityName: string;
let otherAuthorityName: string;
let otherPrivateKey: string;
let wrn: string;
beforeAll(async () => {
@ -28,7 +35,8 @@ const namingTests = () => {
await registry.createBond({ denom: 'aphoton', amount: '1000000000' }, accountAddress, privateKey, fee);
// Create bot.
watcher = await ensureUpdatedConfig(WATCHER_YML_PATH);
// TODO: Use ensureUpdatedConfig from helper.
watcher = await getBaseConfig(WATCHER_YML_PATH);
const result = await registry.setRecord(
{
privateKey,
@ -42,7 +50,7 @@ const namingTests = () => {
// TODO: Get id from setRecord response.
// watcherId = result.data;
watcherId = WATCHER_ID;
watcherId = WATCHER_1_ID;
});
test('Reserve authority.', async () => {
@ -116,6 +124,142 @@ const namingTests = () => {
test('Set authority bond', async () => {
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) {

View File

@ -1,10 +1,41 @@
import assert from 'assert';
import axios from 'axios';
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';
const attributeField = `
attributes {
key
value {
null
int
float
string
boolean
json
reference {
id
}
}
}
`;
const refsField = `
references {
id
}
`;
const historyFields = `
history {
id
height
}
`;
const auctionFields = `
id
status
@ -64,7 +95,7 @@ export class RegistryClient {
/**
* 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;
if (result && result[key] && result[key].length && result[key][0] !== null) {
if (modifier) {
@ -75,6 +106,19 @@ export class RegistryClient {
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.
*/
@ -100,6 +144,38 @@ export class RegistryClient {
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.
*/
@ -127,6 +203,60 @@ export class RegistryClient {
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.
*/

View File

@ -10,6 +10,13 @@ export const ensureUpdatedConfig = async (path: string) => {
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 = () => {
assert(process.env.PRIVATE_KEY);
assert(process.env.ACCOUNT_ADDRESS);

View File

@ -724,6 +724,11 @@
jest-matcher-utils "^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":
version "17.0.23"
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"
integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=
lodash@^4.7.0:
lodash@^4.17.21, lodash@^4.7.0:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==