From 997f1b2fb389b6ce23d2356da6dbeceda28293e9 Mon Sep 17 00:00:00 2001 From: the-masthead <116779271+the-masthead@users.noreply.github.com> Date: Tue, 1 Nov 2022 21:58:17 -0700 Subject: [PATCH 1/4] Implement proper paging of results --- src/commands/node/list.ts | 15 +++++++++++++-- src/commands/project/list.ts | 26 +++++++++++++++++++++----- 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/src/commands/node/list.ts b/src/commands/node/list.ts index b366bff..f0affc4 100644 --- a/src/commands/node/list.ts +++ b/src/commands/node/list.ts @@ -1,4 +1,5 @@ import { Flags } from "@oclif/core"; +import { Result } from "ethers/lib/utils"; import { BlockchainCommand } from "../../base"; import { getContract, getProvider, normalizeHex, normalizeRecords } from "../../helpers"; @@ -11,6 +12,7 @@ export default class NodeList extends BlockchainCommand { operator: Flags.string({ description: "Filter by operator ID.", helpValue: "ID" }), skip: Flags.integer({ description: "The number of results to skip.", helpValue: "N", default: 0 }), size: Flags.integer({ description: "The number of results to list.", helpValue: "N", default: 100 }), + page: Flags.integer({ description: "The contract call paging size.", helpValue: "N", default: 100 }), }; public async run(): Promise { @@ -18,7 +20,16 @@ export default class NodeList extends BlockchainCommand { const provider = await getProvider(flags.network); const nodes = await getContract(flags.network, "nodes", provider); const operatorId = normalizeHex(flags.operator); - const records = await nodes.getNodes(operatorId, flags.topology, flags.skip, flags.size); - console.log(normalizeRecords(records)); + const blockTag = await provider.getBlockNumber(); + const results: Result[] = []; + for (let i = flags.skip; results.length < flags.size; ) { + const records: Result[] = await nodes.getNodes(operatorId, flags.topology, i, flags.page, { blockTag }); + i += records.length; + results.push(...records.slice(0, flags.size - results.length)); + if (records.length !== flags.page) { + break; + } + } + console.log(normalizeRecords(results)); } } diff --git a/src/commands/project/list.ts b/src/commands/project/list.ts index 5e80cd9..eb97e5e 100644 --- a/src/commands/project/list.ts +++ b/src/commands/project/list.ts @@ -1,4 +1,5 @@ import { Flags } from "@oclif/core"; +import { Result } from "ethers/lib/utils"; import { BlockchainCommand } from "../../base"; import { getContract, getProvider, normalizeHex, normalizeRecords } from "../../helpers"; @@ -10,17 +11,32 @@ export default class ProjectList extends BlockchainCommand { owner: Flags.string({ description: "Filter by owner address.", helpValue: "ADDR" }), skip: Flags.integer({ description: "The number of results to skip.", helpValue: "N", default: 0 }), size: Flags.integer({ description: "The number of results to list.", helpValue: "N", default: 100 }), + page: Flags.integer({ description: "The contract call paging size.", helpValue: "N", default: 100 }), }; public async run(): Promise { const { flags } = await this.parse(ProjectList); const provider = await getProvider(flags.network); const projects = await getContract(flags.network, "projects", provider); - let records = await projects.getProjects(flags.skip, flags.size); - if (flags.owner) { - const owner = normalizeHex(flags.owner); - records = records.filter((value: { owner: string }) => value.owner.toLowerCase() === owner.toLowerCase()); + const owner = normalizeHex(flags.owner); + const blockTag = await provider.getBlockNumber(); + const results: Result[] = []; + for (let i = flags.skip; results.length < flags.size; ) { + const records = await projects.getProjects(i, flags.page, { blockTag }); + i += records.length; + if (flags.owner) { + results.push( + ...records + .filter((value: { owner: string }) => value.owner.toLowerCase() === owner.toLowerCase()) + .slice(0, flags.size - results.length) + ); + } else { + results.push(...records.slice(0, flags.size - results.length)); + } + if (records.length !== flags.page) { + break; + } } - console.log(normalizeRecords(records)); + console.log(normalizeRecords(results)); } } From 5cec4cf591831ef7dc80aa42305ac47ac87d609d Mon Sep 17 00:00:00 2001 From: the-masthead <116779271+the-masthead@users.noreply.github.com> Date: Tue, 1 Nov 2022 23:11:04 -0700 Subject: [PATCH 2/4] Simplify code and add comments --- src/commands/node/list.ts | 9 ++++----- src/commands/project/list.ts | 21 ++++++++------------- 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/src/commands/node/list.ts b/src/commands/node/list.ts index f0affc4..f714a7c 100644 --- a/src/commands/node/list.ts +++ b/src/commands/node/list.ts @@ -22,13 +22,12 @@ export default class NodeList extends BlockchainCommand { const operatorId = normalizeHex(flags.operator); const blockTag = await provider.getBlockNumber(); const results: Result[] = []; - for (let i = flags.skip; results.length < flags.size; ) { + for (let i = flags.skip; i != Number.MAX_VALUE && results.length < flags.size; ) { const records: Result[] = await nodes.getNodes(operatorId, flags.topology, i, flags.page, { blockTag }); - i += records.length; + // Advance to the next page or signal early exit if exhausted all records + i = records.length !== flags.page ? Number.MAX_VALUE : i + records.length; + // Append as many results as requested, but no more than that results.push(...records.slice(0, flags.size - results.length)); - if (records.length !== flags.page) { - break; - } } console.log(normalizeRecords(results)); } diff --git a/src/commands/project/list.ts b/src/commands/project/list.ts index eb97e5e..86a54d7 100644 --- a/src/commands/project/list.ts +++ b/src/commands/project/list.ts @@ -21,21 +21,16 @@ export default class ProjectList extends BlockchainCommand { const owner = normalizeHex(flags.owner); const blockTag = await provider.getBlockNumber(); const results: Result[] = []; - for (let i = flags.skip; results.length < flags.size; ) { - const records = await projects.getProjects(i, flags.page, { blockTag }); - i += records.length; + for (let i = flags.skip; i != Number.MAX_VALUE && results.length < flags.size; ) { + let records: Result[] = await projects.getProjects(i, flags.page, { blockTag }); + // Advance to the next page or signal early exit if exhausted all records + i = records.length !== flags.page ? Number.MAX_VALUE : i + records.length; + // Apply filters if (flags.owner) { - results.push( - ...records - .filter((value: { owner: string }) => value.owner.toLowerCase() === owner.toLowerCase()) - .slice(0, flags.size - results.length) - ); - } else { - results.push(...records.slice(0, flags.size - results.length)); - } - if (records.length !== flags.page) { - break; + records = records.filter((value) => value.owner.toLowerCase() === owner.toLowerCase()); } + // Append as many results as requested, but no more than that + results.push(...records.slice(0, flags.size - results.length)); } console.log(normalizeRecords(results)); } From 412e7cc2ff452c5d35b79ae91d74dfef2a32ae8f Mon Sep 17 00:00:00 2001 From: the-masthead <116779271+the-masthead@users.noreply.github.com> Date: Wed, 2 Nov 2022 00:27:10 -0700 Subject: [PATCH 3/4] Add flag for signer method This prepares for adding more signers such as multisig and hex raw. --- src/base.ts | 11 ++++++----- src/commands/project/content.ts | 2 +- src/commands/project/create.ts | 2 +- src/commands/project/delete.ts | 2 +- src/helpers.ts | 23 +++++++++++++++-------- 5 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/base.ts b/src/base.ts index b4f526d..00ab9e5 100644 --- a/src/base.ts +++ b/src/base.ts @@ -1,5 +1,6 @@ import { Command, Flags } from "@oclif/core"; import { CommandError } from "@oclif/core/lib/interfaces"; +import { SignerType, SignerTypes } from "./helpers"; import { NetworkName, NetworkNames } from "./networks"; type TxError = { error: { reason: string } }; @@ -7,7 +8,7 @@ type TxError = { error: { reason: string } }; export abstract class BlockchainCommand extends Command { static globalFlags = { network: Flags.enum({ - description: "The network to use", + description: "The network to use.", options: NetworkNames, default: "testnet", }), @@ -19,11 +20,11 @@ export abstract class TransactionCommand extends BlockchainCommand { ...super.globalFlags, address: Flags.string({ description: "The account address to use.", - exclusive: ["ledger"], }), - ledger: Flags.boolean({ - description: "Use Ledger wallet to sign transactions", - exclusive: ["address"], + signer: Flags.enum({ + description: "The method for signing transactions.", + options: SignerTypes, + default: "keystore", }), }; diff --git a/src/commands/project/content.ts b/src/commands/project/content.ts index 73f41a3..ada0dbf 100644 --- a/src/commands/project/content.ts +++ b/src/commands/project/content.ts @@ -23,7 +23,7 @@ export default class ProjectContent extends TransactionCommand { this.error("URL and SHA must be specified"); } - const signer = await getSigner(flags.network, flags.address, flags.ledger); + const signer = await getSigner(flags.network, flags.address, flags.signer); const projects = await getContract(flags.network, "projects", signer); const projectId = normalizeHex(args.ID); const bundleSha = normalizeHex(args.SHA); diff --git a/src/commands/project/create.ts b/src/commands/project/create.ts index b631fdd..edc483b 100644 --- a/src/commands/project/create.ts +++ b/src/commands/project/create.ts @@ -20,7 +20,7 @@ export default class ProjectCreate extends TransactionCommand { this.error("URL and SHA must be specified together"); } - const signer = await getSigner(flags.network, flags.address, flags.ledger); + const signer = await getSigner(flags.network, flags.address, flags.signer); const projects = await getContract(flags.network, "projects", signer); const address = await signer.getAddress(); const bundleSha = normalizeHex(args.SHA); diff --git a/src/commands/project/delete.ts b/src/commands/project/delete.ts index 503c96d..087eb03 100644 --- a/src/commands/project/delete.ts +++ b/src/commands/project/delete.ts @@ -11,7 +11,7 @@ export default class ProjectDelete extends TransactionCommand { public async run(): Promise { const { args, flags } = await this.parse(ProjectDelete); - const signer = await getSigner(flags.network, flags.address, flags.ledger); + const signer = await getSigner(flags.network, flags.address, flags.signer); const projects = await getContract(flags.network, "projects", signer); const projectId = normalizeHex(args.ID); CliUx.ux.action.start("- Submitting transaction"); diff --git a/src/helpers.ts b/src/helpers.ts index b77917a..33132f2 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -7,6 +7,9 @@ import { listWallets, loadWallet } from "./keystore"; import { LedgerSigner } from "./ledger"; import { ContractName, Contracts, NetworkName, Networks } from "./networks"; +export type SignerType = "keystore" | "ledger"; +export const SignerTypes: SignerType[] = ["keystore", "ledger"]; + const Chains: Record = { 0: "mainnet", 5: "goerli", @@ -46,15 +49,19 @@ export async function getProvider(network: NetworkName): Promise { return provider; } -export async function getSigner(network: NetworkName, address: string | undefined, ledger: boolean): Promise { +export async function getSigner( + network: NetworkName, + address: string | undefined, + signer: SignerType +): Promise { const url = Networks[network].url; const provider = new ethers.providers.JsonRpcProvider(url); - let signer: Signer; - if (ledger) { + let wallet: Signer; + if (signer === "ledger") { console.log("Make sure the Ledger wallet is unlocked and the Ethereum application is open"); - signer = new LedgerSigner(provider); - const address = await signer.getAddress(); + wallet = new LedgerSigner(provider); + const address = await wallet.getAddress(); console.log("Using Ledger wallet. Wallet address: ", address); } else { if (!address) { @@ -88,11 +95,11 @@ export async function getSigner(network: NetworkName, address: string | undefine password = res.password as string; } - signer = await loadWallet(address, password); - signer = signer.connect(provider); + wallet = await loadWallet(address, password); + wallet = wallet.connect(provider); } - return signer; + return wallet; } export async function getContract( From 61d2720f6d08c58e0e5e32c187fee501bd02ab96 Mon Sep 17 00:00:00 2001 From: the-masthead <116779271+the-masthead@users.noreply.github.com> Date: Wed, 2 Nov 2022 13:38:03 -0700 Subject: [PATCH 4/4] Filter all records in memory --- src/commands/node/list.ts | 15 +++++---------- src/commands/project/list.ts | 20 +++++++------------- src/helpers.ts | 13 +++++++++++++ 3 files changed, 25 insertions(+), 23 deletions(-) diff --git a/src/commands/node/list.ts b/src/commands/node/list.ts index f714a7c..a090667 100644 --- a/src/commands/node/list.ts +++ b/src/commands/node/list.ts @@ -1,7 +1,7 @@ import { Flags } from "@oclif/core"; import { Result } from "ethers/lib/utils"; import { BlockchainCommand } from "../../base"; -import { getContract, getProvider, normalizeHex, normalizeRecords } from "../../helpers"; +import { getAll, getContract, getProvider, normalizeHex, normalizeRecords } from "../../helpers"; export default class NodeList extends BlockchainCommand { static description = "Lists content nodes on the Armada Network."; @@ -21,14 +21,9 @@ export default class NodeList extends BlockchainCommand { const nodes = await getContract(flags.network, "nodes", provider); const operatorId = normalizeHex(flags.operator); const blockTag = await provider.getBlockNumber(); - const results: Result[] = []; - for (let i = flags.skip; i != Number.MAX_VALUE && results.length < flags.size; ) { - const records: Result[] = await nodes.getNodes(operatorId, flags.topology, i, flags.page, { blockTag }); - // Advance to the next page or signal early exit if exhausted all records - i = records.length !== flags.page ? Number.MAX_VALUE : i + records.length; - // Append as many results as requested, but no more than that - results.push(...records.slice(0, flags.size - results.length)); - } - console.log(normalizeRecords(results)); + const results: Result[] = await getAll(flags.page, async (i, n) => { + return await nodes.getNodes(operatorId, flags.topology, i, n, { blockTag }); + }); + console.log(normalizeRecords(results.slice(flags.skip, flags.skip + flags.size))); } } diff --git a/src/commands/project/list.ts b/src/commands/project/list.ts index 86a54d7..18bc9d5 100644 --- a/src/commands/project/list.ts +++ b/src/commands/project/list.ts @@ -1,7 +1,7 @@ import { Flags } from "@oclif/core"; import { Result } from "ethers/lib/utils"; import { BlockchainCommand } from "../../base"; -import { getContract, getProvider, normalizeHex, normalizeRecords } from "../../helpers"; +import { getAll, getContract, getProvider, normalizeHex, normalizeRecords } from "../../helpers"; export default class ProjectList extends BlockchainCommand { static description = "Lists projects on the Armada Network."; @@ -20,18 +20,12 @@ export default class ProjectList extends BlockchainCommand { const projects = await getContract(flags.network, "projects", provider); const owner = normalizeHex(flags.owner); const blockTag = await provider.getBlockNumber(); - const results: Result[] = []; - for (let i = flags.skip; i != Number.MAX_VALUE && results.length < flags.size; ) { - let records: Result[] = await projects.getProjects(i, flags.page, { blockTag }); - // Advance to the next page or signal early exit if exhausted all records - i = records.length !== flags.page ? Number.MAX_VALUE : i + records.length; - // Apply filters - if (flags.owner) { - records = records.filter((value) => value.owner.toLowerCase() === owner.toLowerCase()); - } - // Append as many results as requested, but no more than that - results.push(...records.slice(0, flags.size - results.length)); + let results: Result[] = await getAll(flags.page, async (i, n) => { + return await projects.getProjects(i, n, { blockTag }); + }); + if (flags.owner) { + results = results.filter((v) => v.owner.toLowerCase() === owner.toLowerCase()); } - console.log(normalizeRecords(results)); + console.log(normalizeRecords(results.slice(flags.skip, flags.skip + flags.size))); } } diff --git a/src/helpers.ts b/src/helpers.ts index 33132f2..ad4237f 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -139,3 +139,16 @@ export async function decodeEvents(receipt: TransactionReceipt, contract: Contra export async function decodeEvent(receipt: TransactionReceipt, contract: Contract, event: string): Promise { return (await decodeEvents(receipt, contract, event))[0]; } + +// Returns all results of a paged function call (a function that accepts skip and size parameters). +export async function getAll(page: number, call: (skip: number, size: number) => Promise): Promise { + const results: Result[] = []; + while (page > 0) { + const records = await call(results.length, page); + results.push(...records); + if (records.length !== page) { + break; + } + } + return results; +}