diff --git a/.gitea/workflows/on-pr.yaml b/.gitea/workflows/on-pr.yaml new file mode 100644 index 0000000..b20b248 --- /dev/null +++ b/.gitea/workflows/on-pr.yaml @@ -0,0 +1,27 @@ +name: Lint & Build + +on: + pull_request: + +jobs: + lint_and_build: + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [20.x] + steps: + - uses: actions/checkout@v3 + - name: Download yarn + run: | + curl -fsSL -o /usr/local/bin/yarn https://github.com/yarnpkg/yarn/releases/download/v1.22.21/yarn-1.22.21.js + chmod +x /usr/local/bin/yarn + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + registry-url: 'https://npm.pkg.github.com' + - run: yarn + - name: Run lint + run: yarn lint + - name: Run build + run: yarn build diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml index e39c7ca..561430e 100644 --- a/.gitea/workflows/test.yml +++ b/.gitea/workflows/test.yml @@ -61,3 +61,12 @@ jobs: - name: Run nameservice expiry tests working-directory: laconicd/tests/sdk_tests run: ./run-tests.sh test:nameservice-expiry + + - name: Start containers (onboarding enabled) + working-directory: laconicd/tests/sdk_tests + env: + ONBOARDING_ENABLED: true + run: docker compose up -d + - name: Run participants onboarding tests + working-directory: laconicd/tests/sdk_tests + run: ./run-tests.sh test:onboarding diff --git a/.github/workflows/on-pr.yaml b/.github/workflows/on-pr.yaml new file mode 100644 index 0000000..d9f3736 --- /dev/null +++ b/.github/workflows/on-pr.yaml @@ -0,0 +1,23 @@ +name: Lint & Build + +on: + pull_request: + +jobs: + lint_and_build: + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [20.x] + steps: + - uses: actions/checkout@v3 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + registry-url: 'https://npm.pkg.github.com' + - run: yarn + - name: Run lint + run: yarn lint + - name: Run build + run: yarn build diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2b77c8e..a45b8e6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,7 +15,7 @@ jobs: uses: actions/checkout@v3 with: path: "./laconicd/" - repository: cerc-io/laconicd + repository: cerc-io/laconic2d fetch-depth: 0 ref: main - name: Environment @@ -51,3 +51,12 @@ jobs: - name: Run nameservice expiry tests working-directory: laconicd/tests/sdk_tests run: ./run-tests.sh test:nameservice-expiry + + - name: Start containers (onboarding enabled) + working-directory: laconicd/tests/sdk_tests + env: + ONBOARDING_ENABLED: true + run: docker compose up -d + - name: Run participants onboarding tests + working-directory: laconicd/tests/sdk_tests + run: ./run-tests.sh test:onboarding diff --git a/package.json b/package.json index 17d7150..199abd6 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ "test": "jest --runInBand --verbose --testPathPattern=src", "test:auctions": "TEST_AUCTIONS_ENABLED=1 jest --runInBand --verbose src/auction.test.ts", "test:nameservice-expiry": "TEST_NAMESERVICE_EXPIRY=1 jest --runInBand --verbose src/nameservice-expiry.test.ts", + "test:onboarding": "ONBOARDING_ENABLED=1 jest --runInBand --verbose src/onboarding.test.ts", "build": "tsc", "lint": "eslint .", "prepare": "husky" diff --git a/proto/cerc/onboarding/module/v1/module.proto b/proto/cerc/onboarding/module/v1/module.proto new file mode 100644 index 0000000..e921a6c --- /dev/null +++ b/proto/cerc/onboarding/module/v1/module.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; + +package cerc.onboarding.module.v1; + +import "cosmos/app/v1alpha1/module.proto"; + +// Module is the app config object of the module. +// Learn more: https://docs.cosmos.network/main/building-modules/depinject +message Module { + option (cosmos.app.v1alpha1.module) = { + go_import : "git.vdb.to/cerc-io/laconicd/x/onboarding" + }; + + string authority = 1; +} diff --git a/proto/cerc/onboarding/v1/genesis.proto b/proto/cerc/onboarding/v1/genesis.proto new file mode 100644 index 0000000..5bfac13 --- /dev/null +++ b/proto/cerc/onboarding/v1/genesis.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; + +package cerc.onboarding.v1; + +import "gogoproto/gogo.proto"; +import "cerc/onboarding/v1/onboarding.proto"; + +option go_package = "git.vdb.to/cerc-io/laconicd/x/onboarding"; + +// GenesisState defines the onboarding module's genesis state. +message GenesisState { + // params defines all the parameters of the module. + Params params = 1 [ (gogoproto.nullable) = false ]; + // participants defines all the participants + repeated Participant participants = 2 [ (gogoproto.nullable) = false ]; +} diff --git a/proto/cerc/onboarding/v1/onboarding.proto b/proto/cerc/onboarding/v1/onboarding.proto new file mode 100644 index 0000000..cda954a --- /dev/null +++ b/proto/cerc/onboarding/v1/onboarding.proto @@ -0,0 +1,35 @@ +syntax = "proto3"; + +package cerc.onboarding.v1; + +import "gogoproto/gogo.proto"; + +option go_package = "git.vdb.to/cerc-io/laconicd/x/onboarding"; + +// Params defines the parameters of the onboarding module. +message Params { + bool onboarding_enabled = 1 [ + (gogoproto.moretags) = "json:\"onboarding_enabled\" yaml:\"onboarding_enabled\"" + ]; +} + +// Participant defines the data that will be stored for each enrolled participant +message Participant { + string cosmos_address = 1 [ + (gogoproto.moretags) = "json:\"cosmos_address\" yaml:\"cosmos_address\"" + ]; + + string nitro_address = 2 [ + (gogoproto.moretags) = "json:\"nitro_address\" yaml:\"nitro_address\"" + ]; +} +// EthPayload defines the payload that is signed by the ethereum private key +message EthPayload { + string address = 1 [ + (gogoproto.moretags) = "json:\"address\" yaml:\"address\"" + ]; + + string msg = 2 [ + (gogoproto.moretags) = "json:\"msg\" yaml:\"msg\"" + ]; +} diff --git a/proto/cerc/onboarding/v1/query.proto b/proto/cerc/onboarding/v1/query.proto new file mode 100644 index 0000000..1345bb8 --- /dev/null +++ b/proto/cerc/onboarding/v1/query.proto @@ -0,0 +1,33 @@ +syntax = "proto3"; + +package cerc.onboarding.v1; + +import "gogoproto/gogo.proto"; +import "cosmos/base/query/v1beta1/pagination.proto"; +import "cerc/onboarding/v1/onboarding.proto"; +import "google/api/annotations.proto"; + +option go_package = "git.vdb.to/cerc-io/laconicd/x/onboarding"; + +// Query defines the gRPC querier service for onboarding module +service Query { + // Participants queries Participants list + rpc Participants(QueryParticipantsRequest) returns (QueryParticipantsResponse) { + option (google.api.http).get = "/cerc/onboarding/v1/participants"; + } +} + +// QueryParticipantsRequest queries participants +message QueryParticipantsRequest { + // pagination defines an optional pagination for the request. + cosmos.base.query.v1beta1.PageRequest pagination = 1; +} + +// QueryParticipantsResponse is response type for get the participants +message QueryParticipantsResponse { + repeated Participant participants = 1 + [ (gogoproto.moretags) = "json:\"participants\" yaml:\"participants\"" ]; + + // pagination defines the pagination in the response. + cosmos.base.query.v1beta1.PageResponse pagination = 2; +} diff --git a/proto/cerc/onboarding/v1/tx.proto b/proto/cerc/onboarding/v1/tx.proto new file mode 100644 index 0000000..bf07de5 --- /dev/null +++ b/proto/cerc/onboarding/v1/tx.proto @@ -0,0 +1,34 @@ +syntax = "proto3"; + +package cerc.onboarding.v1; + +import "cosmos/msg/v1/msg.proto"; +import "google/api/annotations.proto"; +import "gogoproto/gogo.proto"; +import "cerc/onboarding/v1/onboarding.proto"; + +option go_package = "git.vdb.to/cerc-io/laconicd/x/onboarding"; + +// Msg defines the onboarding Msg service. +service Msg { + option (cosmos.msg.v1.service) = true; + + // OnboardParticipant defines a method for enrolling a new validator. + rpc OnboardParticipant(MsgOnboardParticipant) + returns (MsgOnboardParticipantResponse) { + option (google.api.http).post = "/cerc/onboarding/v1/onboard_participant"; + }; +} + +// MsgOnboardParticipant defines a SDK message for enrolling a new validator. +message MsgOnboardParticipant { + option (cosmos.msg.v1.signer) = "participant"; + + // Participant is the msg sender + string participant = 1; + EthPayload eth_payload = 2 [ (gogoproto.nullable) = false ]; + string eth_signature = 3; +} + +// MsgOnboardParticipantResponse defines the Msg/OnboardParticipant response type. +message MsgOnboardParticipantResponse {} diff --git a/src/index.ts b/src/index.ts index 1bb4239..b3b128e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -24,11 +24,13 @@ import { MessageMsgCommitBid, MessageMsgRevealBid } from './types/cerc/auction/message'; -import { LaconicClient } from './laconic-client'; -import { MsgCancelBondResponse, MsgCreateBondResponse, MsgRefillBondResponse, MsgWithdrawBondResponse } from './proto/cerc/bond/v1/tx'; -import { Coin } from './proto/cosmos/base/v1beta1/coin'; -import { MsgSendResponse } from './proto/cosmos/bank/v1beta1/tx'; import { MessageMsgSendCoins } from './types/cosmos/bank/message'; +import { MessageMsgOnboardParticipant } from './types/cerc/onboarding/message'; +import { LaconicClient } from './laconic-client'; +import { Coin } from './proto/cosmos/base/v1beta1/coin'; +import { MsgCancelBondResponse, MsgCreateBondResponse, MsgRefillBondResponse, MsgWithdrawBondResponse } from './proto/cerc/bond/v1/tx'; +import { MsgOnboardParticipantResponse } from './proto/cerc/onboarding/v1/tx'; +import { MsgSendResponse } from './proto/cosmos/bank/v1beta1/tx'; export const DEFAULT_CHAIN_ID = 'laconic_9000-1'; @@ -59,7 +61,7 @@ export const createBid = async (chainId: string, auctionId: string, bidderAddres }; export class Registry { - _endpoints: {[key: string]: string}; + _endpoints: { [key: string]: string }; _chainID: string; _client: RegistryClient; @@ -105,7 +107,7 @@ export class Registry { /** * Get records by attributes. */ - async queryRecords (attributes: {[key: string]: any}, all = false, refs = false) { + async queryRecords (attributes: { [key: string]: any }, all = false, refs = false) { return this._client.queryRecords(attributes, all, refs); } @@ -434,6 +436,32 @@ export class Registry { async getLaconicClient (account: Account) { return LaconicClient.connectWithSigner(this._endpoints.rpc, account.wallet); } + + /** + * Onboard participant. + */ + async onboardParticipant ({ ethPayload, ethSignature }: MessageMsgOnboardParticipant, privateKey: string, fee: StdFee): Promise { + const account = new Account(Buffer.from(privateKey, 'hex')); + await account.init(); + const laconicClient = await this.getLaconicClient(account); + + return laconicClient.onboardParticipant( + account.address, + ethPayload, + ethSignature, + fee + ); + } + + /** + * Query participants. + */ + async getParticipants () { + return this._client.getParticipants(); + } } export { Account }; +export { LaconicClient }; +export * from './types/cerc/bond/message'; +export * from './types/cerc/onboarding/message'; diff --git a/src/laconic-client.ts b/src/laconic-client.ts index 1434f26..ec57184 100644 --- a/src/laconic-client.ts +++ b/src/laconic-client.ts @@ -13,13 +13,16 @@ import { MsgCancelBondEncodeObject, MsgCreateBondEncodeObject, MsgRefillBondEnco import { Coin } from './proto/cosmos/base/v1beta1/coin'; import { MsgAssociateBondEncodeObject, MsgDeleteNameEncodeObject, MsgDissociateBondEncodeObject, MsgDissociateRecordsEncodeObject, MsgReassociateRecordsEncodeObject, MsgReserveAuthorityEncodeObject, MsgSetAuthorityBondEncodeObject, MsgSetNameEncodeObject, MsgSetRecordEncodeObject, registryTypes, typeUrlMsgAssociateBond, typeUrlMsgDeleteName, typeUrlMsgDissociateBond, typeUrlMsgDissociateRecords, typeUrlMsgReassociateRecords, typeUrlMsgReserveAuthority, typeUrlMsgSetAuthorityBond, typeUrlMsgSetName, typeUrlMsgSetRecord, NAMESERVICE_ERRORS } from './types/cerc/registry/message'; import { MsgCommitBidEncodeObject, MsgRevealBidEncodeObject, auctionTypes, typeUrlMsgCommitBid, typeUrlMsgRevealBid } from './types/cerc/auction/message'; +import { MsgOnboardParticipantEncodeObject, onboardingTypes, typeUrlMsgOnboardParticipant } from './types/cerc/onboarding/message'; import { MsgAssociateBondResponse, MsgDeleteNameResponse, MsgDissociateBondResponse, MsgDissociateRecordsResponse, MsgReassociateRecordsResponse, MsgReserveAuthorityResponse, MsgSetAuthorityBondResponse, MsgSetNameResponse, MsgSetRecordResponse, Payload } from './proto/cerc/registry/v1/tx'; import { Record, Signature } from './proto/cerc/registry/v1/registry'; import { Account } from './account'; import { Util } from './util'; import { MsgCommitBidResponse, MsgRevealBidResponse } from './proto/cerc/auction/v1/tx'; import { MsgCancelBondResponse, MsgCreateBondResponse, MsgRefillBondResponse, MsgWithdrawBondResponse } from './proto/cerc/bond/v1/tx'; +import { MsgOnboardParticipantResponse } from './proto/cerc/onboarding/v1/tx'; import { bankTypes } from './types/cosmos/bank/message'; +import { EthPayload } from './proto/cerc/onboarding/v1/onboarding'; const DEFAULT_WRITE_ERROR = 'Unable to write to laconic2d.'; @@ -28,7 +31,8 @@ export const laconicDefaultRegistryTypes: ReadonlyArray<[string, GeneratedType]> ...bondTypes, ...registryTypes, ...auctionTypes, - ...bankTypes + ...bankTypes, + ...onboardingTypes ]; function createDefaultRegistry (): Registry { @@ -385,4 +389,24 @@ export class LaconicClient extends SigningStargateClient { return `${errorMessage || DEFAULT_WRITE_ERROR}: ${error}`; } + + public async onboardParticipant ( + signer: string, + ethPayload: EthPayload, + ethSignature: string, + fee: StdFee | 'auto' | number, + memo = '' + ) { + const onboardParticipantMsg: MsgOnboardParticipantEncodeObject = { + typeUrl: typeUrlMsgOnboardParticipant, + value: { + participant: signer, + ethPayload, + ethSignature + } + }; + + const response = await this.signAndBroadcast(signer, [onboardParticipantMsg], fee, memo); + return this.parseResponse(response); + } } diff --git a/src/onboarding.test.ts b/src/onboarding.test.ts new file mode 100644 index 0000000..81144e5 --- /dev/null +++ b/src/onboarding.test.ts @@ -0,0 +1,106 @@ +import { Wallet } from 'ethers'; + +import { DirectSecp256k1Wallet } from '@cosmjs/proto-signing'; + +import { Registry, Account } from './index'; +import { getConfig } from './testing/helper'; +import { Participant } from './proto/cerc/onboarding/v1/onboarding'; + +const { chainId, rpcEndpoint, gqlEndpoint, privateKey, fee } = getConfig(); + +jest.setTimeout(90 * 1000); + +const onboardingEnabledTests = () => { + let registry: Registry; + let ethWallet: Wallet; + + beforeAll(async () => { + registry = new Registry(gqlEndpoint, rpcEndpoint, chainId); + }); + + test('Onboard participant.', async () => { + const mnemonic = Account.generateMnemonic(); + ethWallet = Wallet.fromMnemonic(mnemonic); + + const ethPayload = { + address: ethWallet.address, + msg: 'Message signed by ethereum private key' + }; + + const message = JSON.stringify(ethPayload); + const ethSignature = await ethWallet.signMessage(message); + + await registry.onboardParticipant({ + ethPayload, + ethSignature + }, privateKey, fee); + }); + + test('Query participants.', async () => { + const account = new Account(Buffer.from(privateKey, 'hex')); + const cosmosAccount = await DirectSecp256k1Wallet.fromKey(account._privateKey, 'laconic'); + const [cosmosWallet] = await cosmosAccount.getAccounts(); + + const expectedParticipants = [ + { + cosmosAddress: cosmosWallet.address, + nitroAddress: ethWallet.address + } + ]; + const participants = await registry.getParticipants(); + expect(participants).toEqual(expectedParticipants); + }); +}; + +const onboardingDisabledTests = () => { + let registry: Registry; + let ethWallet: Wallet; + + beforeAll(async () => { + registry = new Registry(gqlEndpoint, rpcEndpoint, chainId); + }); + + test('Error on onboarding attempt.', async () => { + const errorMsg = 'Validator onboarding is disabled: invalid request'; + const mnemonic = Account.generateMnemonic(); + ethWallet = Wallet.fromMnemonic(mnemonic); + + const ethPayload = { + address: ethWallet.address, + msg: 'Message signed by ethereum private key' + }; + + const message = JSON.stringify(ethPayload); + const ethSignature = await ethWallet.signMessage(message); + + try { + await registry.onboardParticipant({ + ethPayload, + ethSignature + }, privateKey, fee); + } catch (error: any) { + expect(error.toString()).toContain(errorMsg); + } + }); + + test('No participants onboarded.', async () => { + const expectedParticipants: Participant[] = []; + const participants = await registry.getParticipants(); + expect(participants).toMatchObject(expectedParticipants); + }); +}; + +if (process.env.ONBOARDING_ENABLED !== '1') { + describe('Onboarding disabled', onboardingDisabledTests); +} else { + /** + Running this test requires participants onboarding enabled. In laconic2d repo run: + + ONBOARDING_ENABLED=true ./init.sh + + Run test: + + yarn test:onboarding + */ + describe('Onboarding enabled', onboardingEnabledTests); +} diff --git a/src/proto/cerc/onboarding/module/v1/module.ts b/src/proto/cerc/onboarding/module/v1/module.ts new file mode 100644 index 0000000..e76c41b --- /dev/null +++ b/src/proto/cerc/onboarding/module/v1/module.ts @@ -0,0 +1,102 @@ +/* eslint-disable */ +import Long from "long"; +import _m0 from "protobufjs/minimal"; + +export const protobufPackage = "cerc.onboarding.module.v1"; + +/** + * Module is the app config object of the module. + * Learn more: https://docs.cosmos.network/main/building-modules/depinject + */ +export interface Module { + authority: string; +} + +function createBaseModule(): Module { + return { authority: "" }; +} + +export const Module = { + encode( + message: Module, + writer: _m0.Writer = _m0.Writer.create() + ): _m0.Writer { + if (message.authority !== "") { + writer.uint32(10).string(message.authority); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): Module { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseModule(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.authority = reader.string(); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(object: any): Module { + return { + authority: isSet(object.authority) ? String(object.authority) : "", + }; + }, + + toJSON(message: Module): unknown { + const obj: any = {}; + message.authority !== undefined && (obj.authority = message.authority); + return obj; + }, + + fromPartial, I>>(object: I): Module { + const message = createBaseModule(); + message.authority = object.authority ?? ""; + return message; + }, +}; + +type Builtin = + | Date + | Function + | Uint8Array + | string + | number + | boolean + | undefined; + +export type DeepPartial = T extends Builtin + ? T + : T extends Long + ? string | number | Long + : T extends Array + ? Array> + : T extends ReadonlyArray + ? ReadonlyArray> + : T extends {} + ? { [K in keyof T]?: DeepPartial } + : Partial; + +type KeysOfUnion = T extends T ? keyof T : never; +export type Exact = P extends Builtin + ? P + : P & { [K in keyof P]: Exact } & { + [K in Exclude>]: never; + }; + +if (_m0.util.Long !== Long) { + _m0.util.Long = Long as any; + _m0.configure(); +} + +function isSet(value: any): boolean { + return value !== null && value !== undefined; +} diff --git a/src/proto/cerc/onboarding/v1/genesis.ts b/src/proto/cerc/onboarding/v1/genesis.ts new file mode 100644 index 0000000..10a4057 --- /dev/null +++ b/src/proto/cerc/onboarding/v1/genesis.ts @@ -0,0 +1,129 @@ +/* eslint-disable */ +import { Params, Participant } from "./onboarding"; +import Long from "long"; +import _m0 from "protobufjs/minimal"; + +export const protobufPackage = "cerc.onboarding.v1"; + +/** GenesisState defines the onboarding module's genesis state. */ +export interface GenesisState { + /** params defines all the parameters of the module. */ + params?: Params; + /** participants defines all the participants */ + participants: Participant[]; +} + +function createBaseGenesisState(): GenesisState { + return { params: undefined, participants: [] }; +} + +export const GenesisState = { + encode( + message: GenesisState, + writer: _m0.Writer = _m0.Writer.create() + ): _m0.Writer { + if (message.params !== undefined) { + Params.encode(message.params, writer.uint32(10).fork()).ldelim(); + } + for (const v of message.participants) { + Participant.encode(v!, writer.uint32(18).fork()).ldelim(); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): GenesisState { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseGenesisState(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.params = Params.decode(reader, reader.uint32()); + break; + case 2: + message.participants.push( + Participant.decode(reader, reader.uint32()) + ); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(object: any): GenesisState { + return { + params: isSet(object.params) ? Params.fromJSON(object.params) : undefined, + participants: Array.isArray(object?.participants) + ? object.participants.map((e: any) => Participant.fromJSON(e)) + : [], + }; + }, + + toJSON(message: GenesisState): unknown { + const obj: any = {}; + message.params !== undefined && + (obj.params = message.params ? Params.toJSON(message.params) : undefined); + if (message.participants) { + obj.participants = message.participants.map((e) => + e ? Participant.toJSON(e) : undefined + ); + } else { + obj.participants = []; + } + return obj; + }, + + fromPartial, I>>( + object: I + ): GenesisState { + const message = createBaseGenesisState(); + message.params = + object.params !== undefined && object.params !== null + ? Params.fromPartial(object.params) + : undefined; + message.participants = + object.participants?.map((e) => Participant.fromPartial(e)) || []; + return message; + }, +}; + +type Builtin = + | Date + | Function + | Uint8Array + | string + | number + | boolean + | undefined; + +export type DeepPartial = T extends Builtin + ? T + : T extends Long + ? string | number | Long + : T extends Array + ? Array> + : T extends ReadonlyArray + ? ReadonlyArray> + : T extends {} + ? { [K in keyof T]?: DeepPartial } + : Partial; + +type KeysOfUnion = T extends T ? keyof T : never; +export type Exact = P extends Builtin + ? P + : P & { [K in keyof P]: Exact } & { + [K in Exclude>]: never; + }; + +if (_m0.util.Long !== Long) { + _m0.util.Long = Long as any; + _m0.configure(); +} + +function isSet(value: any): boolean { + return value !== null && value !== undefined; +} diff --git a/src/proto/cerc/onboarding/v1/onboarding.ts b/src/proto/cerc/onboarding/v1/onboarding.ts new file mode 100644 index 0000000..445b4cf --- /dev/null +++ b/src/proto/cerc/onboarding/v1/onboarding.ts @@ -0,0 +1,246 @@ +/* eslint-disable */ +import Long from "long"; +import _m0 from "protobufjs/minimal"; + +export const protobufPackage = "cerc.onboarding.v1"; + +/** Params defines the parameters of the onboarding module. */ +export interface Params { + onboardingEnabled: boolean; +} + +/** Participant defines the data that will be stored for each enrolled participant */ +export interface Participant { + cosmosAddress: string; + nitroAddress: string; +} + +/** EthPayload defines the payload that is signed by the ethereum private key */ +export interface EthPayload { + address: string; + msg: string; +} + +function createBaseParams(): Params { + return { onboardingEnabled: false }; +} + +export const Params = { + encode( + message: Params, + writer: _m0.Writer = _m0.Writer.create() + ): _m0.Writer { + if (message.onboardingEnabled === true) { + writer.uint32(8).bool(message.onboardingEnabled); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): Params { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseParams(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.onboardingEnabled = reader.bool(); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(object: any): Params { + return { + onboardingEnabled: isSet(object.onboardingEnabled) + ? Boolean(object.onboardingEnabled) + : false, + }; + }, + + toJSON(message: Params): unknown { + const obj: any = {}; + message.onboardingEnabled !== undefined && + (obj.onboardingEnabled = message.onboardingEnabled); + return obj; + }, + + fromPartial, I>>(object: I): Params { + const message = createBaseParams(); + message.onboardingEnabled = object.onboardingEnabled ?? false; + return message; + }, +}; + +function createBaseParticipant(): Participant { + return { cosmosAddress: "", nitroAddress: "" }; +} + +export const Participant = { + encode( + message: Participant, + writer: _m0.Writer = _m0.Writer.create() + ): _m0.Writer { + if (message.cosmosAddress !== "") { + writer.uint32(10).string(message.cosmosAddress); + } + if (message.nitroAddress !== "") { + writer.uint32(18).string(message.nitroAddress); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): Participant { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseParticipant(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.cosmosAddress = reader.string(); + break; + case 2: + message.nitroAddress = reader.string(); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(object: any): Participant { + return { + cosmosAddress: isSet(object.cosmosAddress) + ? String(object.cosmosAddress) + : "", + nitroAddress: isSet(object.nitroAddress) + ? String(object.nitroAddress) + : "", + }; + }, + + toJSON(message: Participant): unknown { + const obj: any = {}; + message.cosmosAddress !== undefined && + (obj.cosmosAddress = message.cosmosAddress); + message.nitroAddress !== undefined && + (obj.nitroAddress = message.nitroAddress); + return obj; + }, + + fromPartial, I>>( + object: I + ): Participant { + const message = createBaseParticipant(); + message.cosmosAddress = object.cosmosAddress ?? ""; + message.nitroAddress = object.nitroAddress ?? ""; + return message; + }, +}; + +function createBaseEthPayload(): EthPayload { + return { address: "", msg: "" }; +} + +export const EthPayload = { + encode( + message: EthPayload, + writer: _m0.Writer = _m0.Writer.create() + ): _m0.Writer { + if (message.address !== "") { + writer.uint32(10).string(message.address); + } + if (message.msg !== "") { + writer.uint32(18).string(message.msg); + } + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): EthPayload { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseEthPayload(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.address = reader.string(); + break; + case 2: + message.msg = reader.string(); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(object: any): EthPayload { + return { + address: isSet(object.address) ? String(object.address) : "", + msg: isSet(object.msg) ? String(object.msg) : "", + }; + }, + + toJSON(message: EthPayload): unknown { + const obj: any = {}; + message.address !== undefined && (obj.address = message.address); + message.msg !== undefined && (obj.msg = message.msg); + return obj; + }, + + fromPartial, I>>( + object: I + ): EthPayload { + const message = createBaseEthPayload(); + message.address = object.address ?? ""; + message.msg = object.msg ?? ""; + return message; + }, +}; + +type Builtin = + | Date + | Function + | Uint8Array + | string + | number + | boolean + | undefined; + +export type DeepPartial = T extends Builtin + ? T + : T extends Long + ? string | number | Long + : T extends Array + ? Array> + : T extends ReadonlyArray + ? ReadonlyArray> + : T extends {} + ? { [K in keyof T]?: DeepPartial } + : Partial; + +type KeysOfUnion = T extends T ? keyof T : never; +export type Exact = P extends Builtin + ? P + : P & { [K in keyof P]: Exact } & { + [K in Exclude>]: never; + }; + +if (_m0.util.Long !== Long) { + _m0.util.Long = Long as any; + _m0.configure(); +} + +function isSet(value: any): boolean { + return value !== null && value !== undefined; +} diff --git a/src/proto/cerc/onboarding/v1/query.ts b/src/proto/cerc/onboarding/v1/query.ts new file mode 100644 index 0000000..7c0d0e0 --- /dev/null +++ b/src/proto/cerc/onboarding/v1/query.ts @@ -0,0 +1,250 @@ +/* eslint-disable */ +import { + PageRequest, + PageResponse, +} from "../../../cosmos/base/query/v1beta1/pagination"; +import Long from "long"; +import { Participant } from "./onboarding"; +import _m0 from "protobufjs/minimal"; + +export const protobufPackage = "cerc.onboarding.v1"; + +/** QueryParticipantsRequest queries participants */ +export interface QueryParticipantsRequest { + /** pagination defines an optional pagination for the request. */ + pagination?: PageRequest; +} + +/** QueryParticipantsResponse is response type for get the participants */ +export interface QueryParticipantsResponse { + participants: Participant[]; + /** pagination defines the pagination in the response. */ + pagination?: PageResponse; +} + +function createBaseQueryParticipantsRequest(): QueryParticipantsRequest { + return { pagination: undefined }; +} + +export const QueryParticipantsRequest = { + encode( + message: QueryParticipantsRequest, + writer: _m0.Writer = _m0.Writer.create() + ): _m0.Writer { + if (message.pagination !== undefined) { + PageRequest.encode(message.pagination, writer.uint32(10).fork()).ldelim(); + } + return writer; + }, + + decode( + input: _m0.Reader | Uint8Array, + length?: number + ): QueryParticipantsRequest { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseQueryParticipantsRequest(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.pagination = PageRequest.decode(reader, reader.uint32()); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(object: any): QueryParticipantsRequest { + return { + pagination: isSet(object.pagination) + ? PageRequest.fromJSON(object.pagination) + : undefined, + }; + }, + + toJSON(message: QueryParticipantsRequest): unknown { + const obj: any = {}; + message.pagination !== undefined && + (obj.pagination = message.pagination + ? PageRequest.toJSON(message.pagination) + : undefined); + return obj; + }, + + fromPartial, I>>( + object: I + ): QueryParticipantsRequest { + const message = createBaseQueryParticipantsRequest(); + message.pagination = + object.pagination !== undefined && object.pagination !== null + ? PageRequest.fromPartial(object.pagination) + : undefined; + return message; + }, +}; + +function createBaseQueryParticipantsResponse(): QueryParticipantsResponse { + return { participants: [], pagination: undefined }; +} + +export const QueryParticipantsResponse = { + encode( + message: QueryParticipantsResponse, + writer: _m0.Writer = _m0.Writer.create() + ): _m0.Writer { + for (const v of message.participants) { + Participant.encode(v!, writer.uint32(10).fork()).ldelim(); + } + if (message.pagination !== undefined) { + PageResponse.encode( + message.pagination, + writer.uint32(18).fork() + ).ldelim(); + } + return writer; + }, + + decode( + input: _m0.Reader | Uint8Array, + length?: number + ): QueryParticipantsResponse { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseQueryParticipantsResponse(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.participants.push( + Participant.decode(reader, reader.uint32()) + ); + break; + case 2: + message.pagination = PageResponse.decode(reader, reader.uint32()); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(object: any): QueryParticipantsResponse { + return { + participants: Array.isArray(object?.participants) + ? object.participants.map((e: any) => Participant.fromJSON(e)) + : [], + pagination: isSet(object.pagination) + ? PageResponse.fromJSON(object.pagination) + : undefined, + }; + }, + + toJSON(message: QueryParticipantsResponse): unknown { + const obj: any = {}; + if (message.participants) { + obj.participants = message.participants.map((e) => + e ? Participant.toJSON(e) : undefined + ); + } else { + obj.participants = []; + } + message.pagination !== undefined && + (obj.pagination = message.pagination + ? PageResponse.toJSON(message.pagination) + : undefined); + return obj; + }, + + fromPartial, I>>( + object: I + ): QueryParticipantsResponse { + const message = createBaseQueryParticipantsResponse(); + message.participants = + object.participants?.map((e) => Participant.fromPartial(e)) || []; + message.pagination = + object.pagination !== undefined && object.pagination !== null + ? PageResponse.fromPartial(object.pagination) + : undefined; + return message; + }, +}; + +/** Query defines the gRPC querier service for onboarding module */ +export interface Query { + /** Participants queries Participants list */ + Participants( + request: QueryParticipantsRequest + ): Promise; +} + +export class QueryClientImpl implements Query { + private readonly rpc: Rpc; + constructor(rpc: Rpc) { + this.rpc = rpc; + this.Participants = this.Participants.bind(this); + } + Participants( + request: QueryParticipantsRequest + ): Promise { + const data = QueryParticipantsRequest.encode(request).finish(); + const promise = this.rpc.request( + "cerc.onboarding.v1.Query", + "Participants", + data + ); + return promise.then((data) => + QueryParticipantsResponse.decode(new _m0.Reader(data)) + ); + } +} + +interface Rpc { + request( + service: string, + method: string, + data: Uint8Array + ): Promise; +} + +type Builtin = + | Date + | Function + | Uint8Array + | string + | number + | boolean + | undefined; + +export type DeepPartial = T extends Builtin + ? T + : T extends Long + ? string | number | Long + : T extends Array + ? Array> + : T extends ReadonlyArray + ? ReadonlyArray> + : T extends {} + ? { [K in keyof T]?: DeepPartial } + : Partial; + +type KeysOfUnion = T extends T ? keyof T : never; +export type Exact = P extends Builtin + ? P + : P & { [K in keyof P]: Exact } & { + [K in Exclude>]: never; + }; + +if (_m0.util.Long !== Long) { + _m0.util.Long = Long as any; + _m0.configure(); +} + +function isSet(value: any): boolean { + return value !== null && value !== undefined; +} diff --git a/src/proto/cerc/onboarding/v1/tx.ts b/src/proto/cerc/onboarding/v1/tx.ts new file mode 100644 index 0000000..3897ffd --- /dev/null +++ b/src/proto/cerc/onboarding/v1/tx.ts @@ -0,0 +1,225 @@ +/* eslint-disable */ +import { EthPayload } from "./onboarding"; +import Long from "long"; +import _m0 from "protobufjs/minimal"; + +export const protobufPackage = "cerc.onboarding.v1"; + +/** MsgOnboardParticipant defines a SDK message for enrolling a new validator. */ +export interface MsgOnboardParticipant { + /** Participant is the msg sender */ + participant: string; + ethPayload?: EthPayload; + ethSignature: string; +} + +/** MsgOnboardParticipantResponse defines the Msg/OnboardParticipant response type. */ +export interface MsgOnboardParticipantResponse {} + +function createBaseMsgOnboardParticipant(): MsgOnboardParticipant { + return { participant: "", ethPayload: undefined, ethSignature: "" }; +} + +export const MsgOnboardParticipant = { + encode( + message: MsgOnboardParticipant, + writer: _m0.Writer = _m0.Writer.create() + ): _m0.Writer { + if (message.participant !== "") { + writer.uint32(10).string(message.participant); + } + if (message.ethPayload !== undefined) { + EthPayload.encode(message.ethPayload, writer.uint32(18).fork()).ldelim(); + } + if (message.ethSignature !== "") { + writer.uint32(26).string(message.ethSignature); + } + return writer; + }, + + decode( + input: _m0.Reader | Uint8Array, + length?: number + ): MsgOnboardParticipant { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseMsgOnboardParticipant(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + case 1: + message.participant = reader.string(); + break; + case 2: + message.ethPayload = EthPayload.decode(reader, reader.uint32()); + break; + case 3: + message.ethSignature = reader.string(); + break; + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(object: any): MsgOnboardParticipant { + return { + participant: isSet(object.participant) ? String(object.participant) : "", + ethPayload: isSet(object.ethPayload) + ? EthPayload.fromJSON(object.ethPayload) + : undefined, + ethSignature: isSet(object.ethSignature) + ? String(object.ethSignature) + : "", + }; + }, + + toJSON(message: MsgOnboardParticipant): unknown { + const obj: any = {}; + message.participant !== undefined && + (obj.participant = message.participant); + message.ethPayload !== undefined && + (obj.ethPayload = message.ethPayload + ? EthPayload.toJSON(message.ethPayload) + : undefined); + message.ethSignature !== undefined && + (obj.ethSignature = message.ethSignature); + return obj; + }, + + fromPartial, I>>( + object: I + ): MsgOnboardParticipant { + const message = createBaseMsgOnboardParticipant(); + message.participant = object.participant ?? ""; + message.ethPayload = + object.ethPayload !== undefined && object.ethPayload !== null + ? EthPayload.fromPartial(object.ethPayload) + : undefined; + message.ethSignature = object.ethSignature ?? ""; + return message; + }, +}; + +function createBaseMsgOnboardParticipantResponse(): MsgOnboardParticipantResponse { + return {}; +} + +export const MsgOnboardParticipantResponse = { + encode( + _: MsgOnboardParticipantResponse, + writer: _m0.Writer = _m0.Writer.create() + ): _m0.Writer { + return writer; + }, + + decode( + input: _m0.Reader | Uint8Array, + length?: number + ): MsgOnboardParticipantResponse { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseMsgOnboardParticipantResponse(); + while (reader.pos < end) { + const tag = reader.uint32(); + switch (tag >>> 3) { + default: + reader.skipType(tag & 7); + break; + } + } + return message; + }, + + fromJSON(_: any): MsgOnboardParticipantResponse { + return {}; + }, + + toJSON(_: MsgOnboardParticipantResponse): unknown { + const obj: any = {}; + return obj; + }, + + fromPartial, I>>( + _: I + ): MsgOnboardParticipantResponse { + const message = createBaseMsgOnboardParticipantResponse(); + return message; + }, +}; + +/** Msg defines the onboarding Msg service. */ +export interface Msg { + /** OnboardParticipant defines a method for enrolling a new validator. */ + OnboardParticipant( + request: MsgOnboardParticipant + ): Promise; +} + +export class MsgClientImpl implements Msg { + private readonly rpc: Rpc; + constructor(rpc: Rpc) { + this.rpc = rpc; + this.OnboardParticipant = this.OnboardParticipant.bind(this); + } + OnboardParticipant( + request: MsgOnboardParticipant + ): Promise { + const data = MsgOnboardParticipant.encode(request).finish(); + const promise = this.rpc.request( + "cerc.onboarding.v1.Msg", + "OnboardParticipant", + data + ); + return promise.then((data) => + MsgOnboardParticipantResponse.decode(new _m0.Reader(data)) + ); + } +} + +interface Rpc { + request( + service: string, + method: string, + data: Uint8Array + ): Promise; +} + +type Builtin = + | Date + | Function + | Uint8Array + | string + | number + | boolean + | undefined; + +export type DeepPartial = T extends Builtin + ? T + : T extends Long + ? string | number | Long + : T extends Array + ? Array> + : T extends ReadonlyArray + ? ReadonlyArray> + : T extends {} + ? { [K in keyof T]?: DeepPartial } + : Partial; + +type KeysOfUnion = T extends T ? keyof T : never; +export type Exact = P extends Builtin + ? P + : P & { [K in keyof P]: Exact } & { + [K in Exclude>]: never; + }; + +if (_m0.util.Long !== Long) { + _m0.util.Long = Long as any; + _m0.configure(); +} + +function isSet(value: any): boolean { + return value !== null && value !== undefined; +} diff --git a/src/registry-client.ts b/src/registry-client.ts index 69d9ab5..bf28a75 100644 --- a/src/registry-client.ts +++ b/src/registry-client.ts @@ -151,31 +151,31 @@ export class RegistryClient { moniker } sync { - latest_block_hash - latest_block_height - latest_block_time - catching_up + latestBlockHash + latestBlockHeight + latestBlockTime + catchingUp } validator { address - voting_power + votingPower } validators { address - voting_power - proposer_priority + votingPower + proposerPriority } - num_peers + numPeers peers { node { id network moniker } - is_outbound - remote_ip + isOutbound + remoteIp } - disk_usage + diskUsage } }`; @@ -241,7 +241,7 @@ export class RegistryClient { /** * Get records by attributes. */ - async queryRecords (attributes: {[key: string]: any}, all = false, refs = false) { + async queryRecords (attributes: { [key: string]: any }, all = false, refs = false) { if (!attributes) { attributes = {}; } @@ -397,7 +397,7 @@ export class RegistryClient { } /** - * Get records by attributes. + * Get bonds by attributes. */ async queryBonds (attributes = {}) { const query = `query ($attributes: [KeyValueInput!]) { @@ -417,4 +417,20 @@ export class RegistryClient { return RegistryClient.getResult(this._graph(query)(variables), 'queryBonds'); } + + /** + * Get participants. + */ + async getParticipants () { + const query = `query { + getParticipants { + cosmosAddress + nitroAddress + } + }`; + + const variables = {}; + + return RegistryClient.getResult(this._graph(query)(variables), 'getParticipants'); + } } diff --git a/src/testing/data/watcher.yml b/src/testing/data/watcher.yml index d9deb11..23356e5 100644 --- a/src/testing/data/watcher.yml +++ b/src/testing/data/watcher.yml @@ -7,4 +7,4 @@ record: /: QmP8jTG1m9GSDJLCbeWhVSVgEzCPPwXRdCRuJtQ5Tz9Kc9 tls_cert_cid: /: QmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR - version: 1.0.23 + version: 1.0.30 diff --git a/src/types/cerc/onboarding/message.ts b/src/types/cerc/onboarding/message.ts new file mode 100644 index 0000000..edbeccf --- /dev/null +++ b/src/types/cerc/onboarding/message.ts @@ -0,0 +1,26 @@ +import { EncodeObject, GeneratedType } from '@cosmjs/proto-signing'; + +import { MsgOnboardParticipantResponse, MsgOnboardParticipant } from '../../../proto/cerc/onboarding/v1/tx'; + +export const typeUrlMsgOnboardParticipant = '/cerc.onboarding.v1.MsgOnboardParticipant'; +export const typeUrlMsgOnboardParticipantResponse = '/cerc.onboarding.v1.MsgOnboardParticipantResponse'; + +export const onboardingTypes: ReadonlyArray<[string, GeneratedType]> = [ + [typeUrlMsgOnboardParticipant, MsgOnboardParticipant], + [typeUrlMsgOnboardParticipantResponse, MsgOnboardParticipantResponse] +]; + +export interface MsgOnboardParticipantEncodeObject extends EncodeObject { + readonly typeUrl: '/cerc.onboarding.v1.MsgOnboardParticipant'; + readonly value: Partial; +} + +interface ethPayload { + address: string + msg: string +} + +export interface MessageMsgOnboardParticipant { + ethPayload: ethPayload + ethSignature: string +}