Merge pull request #86 from confio/search-non-existent
Improve implementation of CosmWasmConnection.getTx
This commit is contained in:
commit
843bed9e04
@ -5,11 +5,11 @@ import {
|
||||
ChainId,
|
||||
isBlockInfoPending,
|
||||
isConfirmedTransaction,
|
||||
isFailedTransaction,
|
||||
isSendTransaction,
|
||||
PubkeyBytes,
|
||||
SendTransaction,
|
||||
TokenTicker,
|
||||
TransactionId,
|
||||
TransactionState,
|
||||
} from "@iov/bcp";
|
||||
import { Random, Secp256k1 } from "@iov/crypto";
|
||||
@ -21,7 +21,7 @@ import { CosmWasmCodec } from "./cosmwasmcodec";
|
||||
import { CosmWasmConnection, TokenConfiguration } from "./cosmwasmconnection";
|
||||
import * as testdata from "./testdata.spec";
|
||||
|
||||
const { fromBase64, toHex } = Encoding;
|
||||
const { fromBase64 } = Encoding;
|
||||
|
||||
function pendingWithoutCosmos(): void {
|
||||
if (!process.env.COSMOS_ENABLED) {
|
||||
@ -264,6 +264,21 @@ describe("CosmWasmConnection", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("getTx", () => {
|
||||
it("throws for non-existent transaction", async () => {
|
||||
pendingWithoutCosmos();
|
||||
const connection = await CosmWasmConnection.establish(httpUrl, defaultPrefix, defaultConfig);
|
||||
|
||||
const nonExistentId = "0000000000000000000000000000000000000000000000000000000000000000" as TransactionId;
|
||||
await connection.getTx(nonExistentId).then(
|
||||
() => fail("this must not succeed"),
|
||||
error => expect(error).toMatch(/transaction does not exist/i),
|
||||
);
|
||||
|
||||
connection.disconnect();
|
||||
});
|
||||
});
|
||||
|
||||
describe("integration tests", () => {
|
||||
it("can post and get a transaction", async () => {
|
||||
pendingWithoutCosmos();
|
||||
@ -295,35 +310,25 @@ describe("CosmWasmConnection", () => {
|
||||
expect(blockInfo.state).toEqual(TransactionState.Succeeded);
|
||||
|
||||
const getResponse = await connection.getTx(transactionId);
|
||||
expect(getResponse).toBeTruthy();
|
||||
expect(getResponse.transactionId).toEqual(transactionId);
|
||||
if (isFailedTransaction(getResponse)) {
|
||||
throw new Error("Expected transaction to succeed");
|
||||
}
|
||||
assert(isConfirmedTransaction(getResponse), "Expected transaction to succeed");
|
||||
assert(getResponse.log, "Log must be available");
|
||||
// we get a json response in the log for each msg, multiple events is good (transfer succeeded)
|
||||
const [firstLog] = JSON.parse(getResponse.log);
|
||||
expect(firstLog.events.length).toEqual(2);
|
||||
const { transaction, signatures } = getResponse;
|
||||
if (!isSendTransaction(transaction)) {
|
||||
throw new Error("Expected send transaction");
|
||||
}
|
||||
expect(transaction.kind).toEqual(unsigned.kind);
|
||||
expect(transaction.sender).toEqual(unsigned.sender);
|
||||
expect(transaction.recipient).toEqual(unsigned.recipient);
|
||||
expect(transaction.memo).toEqual(unsigned.memo);
|
||||
expect(transaction.amount).toEqual(unsigned.amount);
|
||||
expect(transaction.chainId).toEqual(unsigned.chainId);
|
||||
|
||||
const { transaction, signatures } = getResponse;
|
||||
assert(isSendTransaction(transaction), "Expected send transaction");
|
||||
expect(transaction).toEqual(unsigned);
|
||||
expect(signatures.length).toEqual(1);
|
||||
expect(signatures[0].nonce).toEqual(signed.signatures[0].nonce);
|
||||
expect(signatures[0].pubkey.algo).toEqual(signed.signatures[0].pubkey.algo);
|
||||
expect(toHex(signatures[0].pubkey.data)).toEqual(
|
||||
toHex(Secp256k1.compressPubkey(signed.signatures[0].pubkey.data)),
|
||||
);
|
||||
expect(toHex(signatures[0].signature)).toEqual(
|
||||
toHex(Secp256k1.trimRecoveryByte(signed.signatures[0].signature)),
|
||||
);
|
||||
expect(signatures[0]).toEqual({
|
||||
nonce: -1, // Unfortunately this information is unavailable as previous implementation attempt is broken. See https://github.com/iov-one/iov-core/pull/1390
|
||||
pubkey: {
|
||||
algo: signed.signatures[0].pubkey.algo,
|
||||
data: Secp256k1.compressPubkey(signed.signatures[0].pubkey.data),
|
||||
},
|
||||
signature: Secp256k1.trimRecoveryByte(signed.signatures[0].signature),
|
||||
});
|
||||
|
||||
connection.disconnect();
|
||||
});
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/camelcase */
|
||||
import { CosmosAddressBech32Prefix, CosmWasmClient, RestClient, TxsResponse, types } from "@cosmwasm/sdk";
|
||||
import { CosmosAddressBech32Prefix, CosmWasmClient, RestClient, TxsResponse } from "@cosmwasm/sdk";
|
||||
import {
|
||||
Account,
|
||||
AccountQuery,
|
||||
@ -231,15 +231,14 @@ export class CosmWasmConnection implements BlockchainConnection {
|
||||
public async getTx(
|
||||
id: TransactionId,
|
||||
): Promise<ConfirmedAndSignedTransaction<UnsignedTransaction> | FailedTransaction> {
|
||||
try {
|
||||
// tslint:disable-next-line: deprecation
|
||||
const response = await this.restClient.txsById(id);
|
||||
return this.parseAndPopulateTxResponseSigned(response);
|
||||
} catch (error) {
|
||||
if (error.response.status === 404) {
|
||||
const results = await this.cosmWasmClient.searchTx({ id: id });
|
||||
switch (results.length) {
|
||||
case 0:
|
||||
throw new Error("Transaction does not exist");
|
||||
}
|
||||
throw error;
|
||||
case 1:
|
||||
return this.parseAndPopulateTxResponseSigned(results[0]);
|
||||
default:
|
||||
throw new Error("Got unexpected amount of search results");
|
||||
}
|
||||
}
|
||||
|
||||
@ -365,30 +364,10 @@ export class CosmWasmConnection implements BlockchainConnection {
|
||||
private async parseAndPopulateTxResponseSigned(
|
||||
response: TxsResponse,
|
||||
): Promise<ConfirmedAndSignedTransaction<UnsignedTransaction> | FailedTransaction> {
|
||||
const firstMsg = response.tx.value.msg.find(() => true);
|
||||
if (!firstMsg) throw new Error("Got transaction without a first message. What is going on here?");
|
||||
|
||||
// needed to get the (account_number, sequence) for the primary signature
|
||||
let primarySignerAddress: string;
|
||||
if (types.isMsgSend(firstMsg)) {
|
||||
primarySignerAddress = firstMsg.value.from_address;
|
||||
} else if (
|
||||
types.isMsgStoreCode(firstMsg) ||
|
||||
types.isMsgInstantiateContract(firstMsg) ||
|
||||
types.isMsgExecuteContract(firstMsg)
|
||||
) {
|
||||
primarySignerAddress = firstMsg.value.sender;
|
||||
} else {
|
||||
throw new Error(`Got unsupported type of message: ${firstMsg.type}`);
|
||||
}
|
||||
|
||||
// tslint:disable-next-line: deprecation
|
||||
const accountForHeight = await this.restClient.authAccounts(primarySignerAddress, response.height);
|
||||
const accountNumber = accountForHeight.result.value.account_number;
|
||||
// this is technically not the proper sequence. maybe this causes issues for sig validation?
|
||||
// leaving for now unless it causes issues
|
||||
const sequence = accountForHeight.result.value.sequence - 1;
|
||||
const nonce = accountToNonce(accountNumber, sequence);
|
||||
// There is no known way to get the nonce that was used for signing a transaction.
|
||||
// This information is nesessary for signature validation.
|
||||
// TODO: fix
|
||||
const nonce = -1 as Nonce;
|
||||
|
||||
const chainId = this.chainId();
|
||||
return parseTxsResponseSigned(chainId, parseInt(response.height, 10), nonce, response, this.bankTokens);
|
||||
|
||||
@ -202,6 +202,14 @@ describe("CosmWasmClient", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("can search by ID (non existent)", async () => {
|
||||
pendingWithoutCosmos();
|
||||
const client = CosmWasmClient.makeReadOnly(httpUrl);
|
||||
const nonExistentId = "0000000000000000000000000000000000000000000000000000000000000000";
|
||||
const result = await client.searchTx({ id: nonExistentId });
|
||||
expect(result.length).toEqual(0);
|
||||
});
|
||||
|
||||
it("can search by height", async () => {
|
||||
pendingWithoutCosmos();
|
||||
assert(posted, "value must be set in beforeAll()");
|
||||
|
||||
@ -164,7 +164,7 @@ export class CosmWasmClient {
|
||||
}
|
||||
|
||||
if (isSearchByIdQuery(query)) {
|
||||
return [await this.restClient.txsById(query.id)];
|
||||
return (await this.restClient.txs(`tx.hash=${query.id}`)).txs;
|
||||
} else if (isSearchByHeightQuery(query)) {
|
||||
return (await this.restClient.txs(`tx.height=${query.height}`)).txs;
|
||||
} else if (isSearchBySentFromOrToQuery(query)) {
|
||||
|
||||
@ -228,9 +228,8 @@ export class RestClient {
|
||||
return Encoding.fromBase64((responseData as EncodeTxResponse).tx);
|
||||
}
|
||||
|
||||
public async authAccounts(address: string, height?: string): Promise<AuthAccountsResponse> {
|
||||
const path =
|
||||
height === undefined ? `/auth/accounts/${address}` : `/auth/accounts/${address}?tx.height=${height}`;
|
||||
public async authAccounts(address: string): Promise<AuthAccountsResponse> {
|
||||
const path = `/auth/accounts/${address}`;
|
||||
const responseData = await this.get(path);
|
||||
if ((responseData as any).result.type !== "cosmos-sdk/Account") {
|
||||
throw new Error("Unexpected response data format");
|
||||
|
||||
2
packages/sdk/types/restclient.d.ts
vendored
2
packages/sdk/types/restclient.d.ts
vendored
@ -93,7 +93,7 @@ export declare class RestClient {
|
||||
blocks(height: number): Promise<BlocksResponse>;
|
||||
/** returns the amino-encoding of the transaction performed by the server */
|
||||
encodeTx(tx: CosmosSdkTx): Promise<Uint8Array>;
|
||||
authAccounts(address: string, height?: string): Promise<AuthAccountsResponse>;
|
||||
authAccounts(address: string): Promise<AuthAccountsResponse>;
|
||||
txs(query: string): Promise<SearchTxsResponse>;
|
||||
txsById(id: string): Promise<TxsResponse>;
|
||||
postTx(tx: Uint8Array): Promise<PostTxsResponse>;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user