Merge pull request #38 from confio/test-rest-client

Test RestClient and fix nonce type in REST response
This commit is contained in:
Simon Warta 2020-02-04 12:29:35 +01:00 committed by GitHub
commit ee2c621fba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 121 additions and 40 deletions

View File

@ -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));

View File

@ -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(

View File

@ -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);
}
}

View File

@ -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();
});

View File

@ -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);
}

View File

@ -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;

View File

@ -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));
});
});
});

View File

@ -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);

View 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"
}

View File

@ -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;
}

View File

@ -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>;

View File

@ -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;
}