mirror of
https://github.com/cosmos/cosmjs.git
synced 2025-03-11 14:09:15 +00:00
Merge pull request #38 from confio/test-rest-client
Test RestClient and fix nonce type in REST response
This commit is contained in:
commit
ee2c621fba
@ -56,12 +56,12 @@ export class CosmWasmCodec implements TxCodec {
|
||||
const built = buildUnsignedTx(unsigned, this.tokens);
|
||||
|
||||
const signMsg = sortJson({
|
||||
account_number: nonceToAccountNumber(nonce),
|
||||
account_number: nonceToAccountNumber(nonce).toString(),
|
||||
chain_id: Caip5.decode(unsigned.chainId),
|
||||
fee: (built.value as any).fee,
|
||||
memo: memo,
|
||||
msgs: (built.value as any).msg,
|
||||
sequence: nonceToSequence(nonce),
|
||||
sequence: nonceToSequence(nonce).toString(),
|
||||
});
|
||||
const signBytes = toUtf8(JSON.stringify(signMsg));
|
||||
|
||||
|
@ -242,7 +242,7 @@ describe("CosmWasmConnection", () => {
|
||||
|
||||
expect(signatures.length).toEqual(1);
|
||||
// TODO: the nonce we recover in response doesn't have accountNumber, only sequence
|
||||
const signedSequence = parseInt(nonceToSequence(signed.signatures[0].nonce), 10);
|
||||
const signedSequence = nonceToSequence(signed.signatures[0].nonce);
|
||||
expect(signatures[0].nonce).toEqual(signedSequence);
|
||||
expect(signatures[0].pubkey.algo).toEqual(signed.signatures[0].pubkey.algo);
|
||||
expect(toHex(signatures[0].pubkey.data)).toEqual(
|
||||
|
@ -309,7 +309,7 @@ export class CosmWasmConnection implements BlockchainConnection {
|
||||
const accountForHeight = await this.restClient.authAccounts(sender, response.height);
|
||||
// this is technically not the proper nonce. maybe this causes issues for sig validation?
|
||||
// leaving for now unless it causes issues
|
||||
const sequence = (parseInt(accountForHeight.result.value.sequence, 10) - 1) as Nonce;
|
||||
const sequence = (accountForHeight.result.value.sequence - 1) as Nonce;
|
||||
return parseTxsResponse(chainId, parseInt(response.height, 10), sequence, response, this.tokenInfo);
|
||||
}
|
||||
}
|
||||
|
@ -4,24 +4,24 @@ import { accountToNonce, nonceToAccountNumber, nonceToSequence } from "./types";
|
||||
describe("nonceEncoding", () => {
|
||||
it("works for input in range", () => {
|
||||
const nonce = accountToNonce({
|
||||
account_number: "1234",
|
||||
sequence: "7890",
|
||||
account_number: 1234,
|
||||
sequence: 7890,
|
||||
});
|
||||
expect(nonceToAccountNumber(nonce)).toEqual("1234");
|
||||
expect(nonceToSequence(nonce)).toEqual("7890");
|
||||
expect(nonceToAccountNumber(nonce)).toEqual(1234);
|
||||
expect(nonceToSequence(nonce)).toEqual(7890);
|
||||
});
|
||||
|
||||
it("errors on input too large", () => {
|
||||
expect(() =>
|
||||
accountToNonce({
|
||||
account_number: "1234567890",
|
||||
sequence: "7890",
|
||||
account_number: 1234567890,
|
||||
sequence: 7890,
|
||||
}),
|
||||
).toThrow();
|
||||
expect(() =>
|
||||
accountToNonce({
|
||||
account_number: "178",
|
||||
sequence: "97320247923",
|
||||
account_number: 178,
|
||||
sequence: 97320247923,
|
||||
}),
|
||||
).toThrow();
|
||||
});
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { types } from "@cosmwasm/sdk";
|
||||
import { Nonce } from "@iov/bcp";
|
||||
|
||||
export interface TokenInfo {
|
||||
@ -24,42 +25,35 @@ const maxSeq = 1 << 20;
|
||||
|
||||
// NonceInfo is the data we need from account to create a nonce
|
||||
// Use this so no confusion about order of arguments
|
||||
export interface NonceInfo {
|
||||
readonly account_number: string;
|
||||
readonly sequence: string;
|
||||
}
|
||||
export type NonceInfo = Pick<types.BaseAccount, "account_number" | "sequence">;
|
||||
|
||||
// this (lossily) encodes the two pieces of info (uint64) needed to sign into
|
||||
// one (53-bit) number. Cross your fingers.
|
||||
/* eslint-disable-next-line @typescript-eslint/camelcase */
|
||||
export function accountToNonce({ account_number, sequence }: NonceInfo): Nonce {
|
||||
const acct = parseInt(account_number, 10);
|
||||
const seq = parseInt(sequence, 10);
|
||||
|
||||
export function accountToNonce({ account_number: account, sequence }: NonceInfo): Nonce {
|
||||
// we allow 23 bits (8 million) for accounts, and 20 bits (1 million) for tx/account
|
||||
// let's fix this soon
|
||||
if (acct > maxAcct) {
|
||||
if (account > maxAcct) {
|
||||
throw new Error("Account number is greater than 2^23, must update Nonce handler");
|
||||
}
|
||||
if (seq > maxSeq) {
|
||||
if (sequence > maxSeq) {
|
||||
throw new Error("Sequence is greater than 2^20, must update Nonce handler");
|
||||
}
|
||||
|
||||
const val = acct * maxSeq + seq;
|
||||
const val = account * maxSeq + sequence;
|
||||
return val as Nonce;
|
||||
}
|
||||
|
||||
// this extracts info from nonce for signing
|
||||
export function nonceToAccountNumber(nonce: Nonce): string {
|
||||
export function nonceToAccountNumber(nonce: Nonce): number {
|
||||
const acct = nonce / maxSeq;
|
||||
if (acct > maxAcct) {
|
||||
throw new Error("Invalid Nonce, account number is higher than can safely be encoded in Nonce");
|
||||
}
|
||||
return Math.round(acct).toString();
|
||||
return Math.round(acct);
|
||||
}
|
||||
|
||||
// this extracts info from nonce for signing
|
||||
export function nonceToSequence(nonce: Nonce): string {
|
||||
export function nonceToSequence(nonce: Nonce): number {
|
||||
const seq = nonce % maxSeq;
|
||||
return Math.round(seq).toString();
|
||||
return Math.round(seq);
|
||||
}
|
||||
|
12
packages/bcp/types/types.d.ts
vendored
12
packages/bcp/types/types.d.ts
vendored
@ -1,3 +1,4 @@
|
||||
import { types } from "@cosmwasm/sdk";
|
||||
import { Nonce } from "@iov/bcp";
|
||||
export interface TokenInfo {
|
||||
readonly denom: string;
|
||||
@ -14,10 +15,7 @@ export interface TokenInfo {
|
||||
readonly fractionalDigits: number;
|
||||
}
|
||||
export declare type TokenInfos = ReadonlyArray<TokenInfo>;
|
||||
export interface NonceInfo {
|
||||
readonly account_number: string;
|
||||
readonly sequence: string;
|
||||
}
|
||||
export declare function accountToNonce({ account_number, sequence }: NonceInfo): Nonce;
|
||||
export declare function nonceToAccountNumber(nonce: Nonce): string;
|
||||
export declare function nonceToSequence(nonce: Nonce): string;
|
||||
export declare type NonceInfo = Pick<types.BaseAccount, "account_number" | "sequence">;
|
||||
export declare function accountToNonce({ account_number: account, sequence }: NonceInfo): Nonce;
|
||||
export declare function nonceToAccountNumber(nonce: Nonce): number;
|
||||
export declare function nonceToSequence(nonce: Nonce): number;
|
||||
|
@ -1,10 +1,54 @@
|
||||
/* eslint-disable @typescript-eslint/camelcase */
|
||||
import { Encoding } from "@iov/encoding";
|
||||
|
||||
import { RestClient } from "./restclient";
|
||||
import data from "./testdata/cosmoshub.json";
|
||||
import { StdTx } from "./types";
|
||||
|
||||
const { fromBase64 } = Encoding;
|
||||
|
||||
const httpUrl = "http://localhost:1317";
|
||||
const defaultNetworkId = "testing";
|
||||
const faucetAddress = "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6";
|
||||
|
||||
function pendingWithoutCosmos(): void {
|
||||
if (!process.env.COSMOS_ENABLED) {
|
||||
return pending("Set COSMOS_ENABLED to enable Cosmos node-based tests");
|
||||
}
|
||||
}
|
||||
|
||||
describe("RestClient", () => {
|
||||
it("can be constructed", () => {
|
||||
const client = new RestClient(httpUrl);
|
||||
expect(client).toBeTruthy();
|
||||
});
|
||||
|
||||
describe("nodeInfo", () => {
|
||||
it("works", async () => {
|
||||
pendingWithoutCosmos();
|
||||
const client = new RestClient(httpUrl);
|
||||
const info = await client.nodeInfo();
|
||||
expect(info.node_info.network).toEqual(defaultNetworkId);
|
||||
});
|
||||
});
|
||||
|
||||
describe("authAccounts", () => {
|
||||
it("works", async () => {
|
||||
pendingWithoutCosmos();
|
||||
const client = new RestClient(httpUrl);
|
||||
const { result } = await client.authAccounts(faucetAddress);
|
||||
const account = result.value;
|
||||
expect(account.account_number).toEqual(4);
|
||||
expect(account.sequence).toBeGreaterThanOrEqual(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("encodeTx", () => {
|
||||
it("works for cosmoshub example", async () => {
|
||||
pendingWithoutCosmos();
|
||||
const tx: StdTx = data.tx.value;
|
||||
const client = new RestClient(httpUrl);
|
||||
expect(await client.encodeTx(tx)).toEqual(fromBase64(data.tx_data));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -139,7 +139,7 @@ export class RestClient {
|
||||
return responseData as BlocksResponse;
|
||||
}
|
||||
|
||||
// encodeTx returns the amino-encoding of the transaction
|
||||
/** returns the amino-encoding of the transaction performed by the server */
|
||||
public async encodeTx(stdTx: StdTx): Promise<Uint8Array> {
|
||||
const tx = { type: "cosmos-sdk/StdTx", value: stdTx };
|
||||
const responseData = await this.post("/txs/encode", tx);
|
||||
|
44
packages/sdk/src/testdata/cosmoshub.json
vendored
Normal file
44
packages/sdk/src/testdata/cosmoshub.json
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
{
|
||||
"//source": "https://hubble.figment.network/cosmos/chains/cosmoshub-3/blocks/415777/transactions/2BD600EA6090FC75FD844CA73542CC90A828770F4C01C5B483C3C1C43CCB65F4?format=json",
|
||||
"tx": {
|
||||
"type": "cosmos-sdk/StdTx",
|
||||
"value": {
|
||||
"msg": [
|
||||
{
|
||||
"type": "cosmos-sdk/MsgSend",
|
||||
"value": {
|
||||
"from_address": "cosmos1txqfn5jmcts0x0q7krdxj8tgf98tj0965vqlmq",
|
||||
"to_address": "cosmos1nynns8ex9fq6sjjfj8k79ymkdz4sqth06xexae",
|
||||
"amount": [
|
||||
{
|
||||
"denom": "uatom",
|
||||
"amount": "35997500"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"fee": {
|
||||
"amount": [
|
||||
{
|
||||
"denom": "uatom",
|
||||
"amount": "2500"
|
||||
}
|
||||
],
|
||||
"gas": "100000"
|
||||
},
|
||||
"signatures": [
|
||||
{
|
||||
"pub_key": {
|
||||
"type": "tendermint/PubKeySecp256k1",
|
||||
"value": "A5qFcJBJvEK/fOmEAY0DHNWwSRZ9TEfNZyH8VoVvDtAq"
|
||||
},
|
||||
"signature": "NK1Oy4EUGAsoC03c1wi9GG03JC/39LEdautC5Jk643oIbEPqeXHMwaqbdvO/Jws0X/NAXaN8SAy2KNY5Qml+5Q=="
|
||||
}
|
||||
],
|
||||
"memo": ""
|
||||
}
|
||||
},
|
||||
"tx_data": "ygEoKBapCkOoo2GaChRZgJnSW8Lg8zwesNppHWhJTrk8uhIUmSc4HyYqQahKSZHt4pN2aKsALu8aEQoFdWF0b20SCDM1OTk3NTAwEhMKDQoFdWF0b20SBDI1MDAQoI0GGmoKJuta6YchA5qFcJBJvEK/fOmEAY0DHNWwSRZ9TEfNZyH8VoVvDtAqEkA0rU7LgRQYCygLTdzXCL0YbTckL/f0sR1q60LkmTrjeghsQ+p5cczBqpt2878nCzRf80Bdo3xIDLYo1jlCaX7l",
|
||||
"id": "2BD600EA6090FC75FD844CA73542CC90A828770F4C01C5B483C3C1C43CCB65F4"
|
||||
}
|
@ -64,6 +64,6 @@ export interface BaseAccount {
|
||||
readonly address: string;
|
||||
readonly coins: ReadonlyArray<Coin>;
|
||||
readonly public_key: AccountPubKey;
|
||||
readonly account_number: string;
|
||||
readonly sequence: string;
|
||||
readonly account_number: number;
|
||||
readonly sequence: number;
|
||||
}
|
||||
|
1
packages/sdk/types/restclient.d.ts
vendored
1
packages/sdk/types/restclient.d.ts
vendored
@ -71,6 +71,7 @@ export declare class RestClient {
|
||||
nodeInfo(): Promise<NodeInfoResponse>;
|
||||
blocksLatest(): Promise<BlocksResponse>;
|
||||
blocks(height: number): Promise<BlocksResponse>;
|
||||
/** returns the amino-encoding of the transaction performed by the server */
|
||||
encodeTx(stdTx: StdTx): Promise<Uint8Array>;
|
||||
authAccounts(address: string, height?: string): Promise<AuthAccountsResponse>;
|
||||
txs(query: string): Promise<SearchTxsResponse>;
|
||||
|
4
packages/sdk/types/types.d.ts
vendored
4
packages/sdk/types/types.d.ts
vendored
@ -45,6 +45,6 @@ export interface BaseAccount {
|
||||
readonly address: string;
|
||||
readonly coins: ReadonlyArray<Coin>;
|
||||
readonly public_key: AccountPubKey;
|
||||
readonly account_number: string;
|
||||
readonly sequence: string;
|
||||
readonly account_number: number;
|
||||
readonly sequence: number;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user