Create higher level IndexedTx
This commit is contained in:
parent
01758aadb2
commit
5bf3a191b3
@ -1,5 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/camelcase */
|
||||
import { CosmWasmClient, findSequenceForSignedTx, SearchTxFilter, TxsResponse, types } from "@cosmwasm/sdk";
|
||||
import { CosmWasmClient, findSequenceForSignedTx, IndexedTx, SearchTxFilter, types } from "@cosmwasm/sdk";
|
||||
import {
|
||||
Account,
|
||||
AccountQuery,
|
||||
@ -71,11 +70,11 @@ function deduplicate<T>(input: ReadonlyArray<T>, comparator: (a: T, b: T) => num
|
||||
}
|
||||
|
||||
/** Compares transaxtion by height. If the height is equal, compare by hash to ensure deterministic order */
|
||||
function compareByHeightAndHash(a: TxsResponse, b: TxsResponse): number {
|
||||
function compareByHeightAndHash(a: IndexedTx, b: IndexedTx): number {
|
||||
if (a.height === b.height) {
|
||||
return a.txhash.localeCompare(b.txhash);
|
||||
return a.hash.localeCompare(b.hash);
|
||||
} else {
|
||||
return parseInt(a.height, 10) - parseInt(b.height, 10);
|
||||
return a.height - b.height;
|
||||
}
|
||||
}
|
||||
|
||||
@ -248,9 +247,10 @@ export class CosmWasmConnection implements BlockchainConnection {
|
||||
}
|
||||
|
||||
public async getBlockHeader(height: number): Promise<BlockHeader> {
|
||||
const { block_id, block } = await this.cosmWasmClient.getBlock(height);
|
||||
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||
const { block_id: blockId, block } = await this.cosmWasmClient.getBlock(height);
|
||||
return {
|
||||
id: block_id.hash as BlockId,
|
||||
id: blockId.hash as BlockId,
|
||||
height: parseInt(block.header.height, 10),
|
||||
time: new ReadonlyDate(block.header.time),
|
||||
transactionCount: block.data.txs?.length || 0,
|
||||
@ -337,13 +337,13 @@ export class CosmWasmConnection implements BlockchainConnection {
|
||||
|
||||
const filter: SearchTxFilter = { minHeight: minHeight, maxHeight: maxHeight };
|
||||
|
||||
let txs: readonly TxsResponse[];
|
||||
let txs: readonly IndexedTx[];
|
||||
if (id) {
|
||||
txs = await this.cosmWasmClient.searchTx({ id: id }, filter);
|
||||
} else if (height) {
|
||||
txs = await this.cosmWasmClient.searchTx({ height: height }, filter);
|
||||
} else if (sentFromOrTo) {
|
||||
const pendingRequests = new Array<Promise<readonly TxsResponse[]>>();
|
||||
const pendingRequests = new Array<Promise<readonly IndexedTx[]>>();
|
||||
pendingRequests.push(this.cosmWasmClient.searchTx({ sentFromOrTo: sentFromOrTo }, filter));
|
||||
for (const contract of this.erc20Tokens.map(token => token.contractAddress)) {
|
||||
const searchBySender = [
|
||||
@ -371,7 +371,7 @@ export class CosmWasmConnection implements BlockchainConnection {
|
||||
}
|
||||
const responses = await Promise.all(pendingRequests);
|
||||
const allResults = responses.reduce((accumulator, results) => accumulator.concat(results), []);
|
||||
txs = deduplicate(allResults, (a, b) => a.txhash.localeCompare(b.txhash)).sort(compareByHeightAndHash);
|
||||
txs = deduplicate(allResults, (a, b) => a.hash.localeCompare(b.hash)).sort(compareByHeightAndHash);
|
||||
} else {
|
||||
throw new Error("Unsupported query");
|
||||
}
|
||||
@ -460,11 +460,11 @@ export class CosmWasmConnection implements BlockchainConnection {
|
||||
}
|
||||
|
||||
private parseAndPopulateTxResponseUnsigned(
|
||||
response: TxsResponse,
|
||||
response: IndexedTx,
|
||||
): ConfirmedTransaction<UnsignedTransaction> | FailedTransaction {
|
||||
return parseTxsResponseUnsigned(
|
||||
this.chainId,
|
||||
parseInt(response.height, 10),
|
||||
response.height,
|
||||
response,
|
||||
this.bankTokens,
|
||||
this.erc20Tokens,
|
||||
@ -472,7 +472,7 @@ export class CosmWasmConnection implements BlockchainConnection {
|
||||
}
|
||||
|
||||
private async parseAndPopulateTxResponseSigned(
|
||||
response: TxsResponse,
|
||||
response: IndexedTx,
|
||||
): 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?");
|
||||
@ -503,7 +503,7 @@ export class CosmWasmConnection implements BlockchainConnection {
|
||||
|
||||
return parseTxsResponseSigned(
|
||||
this.chainId,
|
||||
parseInt(response.height, 10),
|
||||
response.height,
|
||||
nonce,
|
||||
response,
|
||||
this.bankTokens,
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/camelcase */
|
||||
import { types } from "@cosmwasm/sdk";
|
||||
import { IndexedTx, types } from "@cosmwasm/sdk";
|
||||
import { Address, Algorithm, isSendTransaction, SendTransaction, TokenTicker } from "@iov/bcp";
|
||||
import { Encoding } from "@iov/encoding";
|
||||
import { assert } from "@iov/utils";
|
||||
@ -281,10 +281,11 @@ describe("decode", () => {
|
||||
describe("parseTxsResponseUnsigned", () => {
|
||||
it("works", () => {
|
||||
const currentHeight = 2923;
|
||||
const txsResponse = {
|
||||
height: "2823",
|
||||
txhash: testdata.txId,
|
||||
raw_log: '[{"msg_index":0,"success":true,"log":""}]',
|
||||
const txsResponse: IndexedTx = {
|
||||
height: 2823,
|
||||
hash: testdata.txId,
|
||||
rawLog: '[{"msg_index":0,"success":true,"log":""}]',
|
||||
logs: [],
|
||||
tx: cosmoshub.tx,
|
||||
timestamp: "2020-02-14T11:35:41Z",
|
||||
};
|
||||
@ -310,10 +311,11 @@ describe("decode", () => {
|
||||
describe("parseTxsResponseSigned", () => {
|
||||
it("works", () => {
|
||||
const currentHeight = 2923;
|
||||
const txsResponse = {
|
||||
height: "2823",
|
||||
txhash: testdata.txId,
|
||||
raw_log: '[{"msg_index":0,"success":true,"log":""}]',
|
||||
const txsResponse: IndexedTx = {
|
||||
height: 2823,
|
||||
hash: testdata.txId,
|
||||
rawLog: '[{"msg_index":0,"success":true,"log":""}]',
|
||||
logs: [],
|
||||
tx: cosmoshub.tx,
|
||||
timestamp: "2020-02-14T11:35:41Z",
|
||||
};
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { TxsResponse, types } from "@cosmwasm/sdk";
|
||||
import { IndexedTx, types } from "@cosmwasm/sdk";
|
||||
import {
|
||||
Address,
|
||||
Algorithm,
|
||||
@ -180,17 +180,16 @@ export function parseSignedTx(
|
||||
export function parseTxsResponseUnsigned(
|
||||
chainId: ChainId,
|
||||
currentHeight: number,
|
||||
response: TxsResponse,
|
||||
response: IndexedTx,
|
||||
tokens: BankTokens,
|
||||
erc20Tokens: readonly Erc20Token[],
|
||||
): ConfirmedTransaction<UnsignedTransaction> {
|
||||
const height = parseInt(response.height, 10);
|
||||
return {
|
||||
transaction: parseUnsignedTx(response.tx.value, chainId, tokens, erc20Tokens),
|
||||
height: height,
|
||||
confirmations: currentHeight - height + 1,
|
||||
transactionId: response.txhash as TransactionId,
|
||||
log: response.raw_log,
|
||||
height: response.height,
|
||||
confirmations: currentHeight - response.height + 1,
|
||||
transactionId: response.hash as TransactionId,
|
||||
log: response.rawLog,
|
||||
};
|
||||
}
|
||||
|
||||
@ -198,16 +197,15 @@ export function parseTxsResponseSigned(
|
||||
chainId: ChainId,
|
||||
currentHeight: number,
|
||||
nonce: Nonce,
|
||||
response: TxsResponse,
|
||||
response: IndexedTx,
|
||||
tokens: BankTokens,
|
||||
erc20Tokens: readonly Erc20Token[],
|
||||
): ConfirmedAndSignedTransaction<UnsignedTransaction> {
|
||||
const height = parseInt(response.height, 10);
|
||||
return {
|
||||
...parseSignedTx(response.tx.value, chainId, nonce, tokens, erc20Tokens),
|
||||
height: height,
|
||||
confirmations: currentHeight - height + 1,
|
||||
transactionId: response.txhash as TransactionId,
|
||||
log: response.raw_log,
|
||||
height: response.height,
|
||||
confirmations: currentHeight - response.height + 1,
|
||||
transactionId: response.hash as TransactionId,
|
||||
log: response.rawLog,
|
||||
};
|
||||
}
|
||||
|
||||
6
packages/bcp/types/decode.d.ts
vendored
6
packages/bcp/types/decode.d.ts
vendored
@ -1,4 +1,4 @@
|
||||
import { TxsResponse, types } from "@cosmwasm/sdk";
|
||||
import { IndexedTx, types } from "@cosmwasm/sdk";
|
||||
import {
|
||||
Amount,
|
||||
ChainId,
|
||||
@ -43,7 +43,7 @@ export declare function parseSignedTx(
|
||||
export declare function parseTxsResponseUnsigned(
|
||||
chainId: ChainId,
|
||||
currentHeight: number,
|
||||
response: TxsResponse,
|
||||
response: IndexedTx,
|
||||
tokens: BankTokens,
|
||||
erc20Tokens: readonly Erc20Token[],
|
||||
): ConfirmedTransaction<UnsignedTransaction>;
|
||||
@ -51,7 +51,7 @@ export declare function parseTxsResponseSigned(
|
||||
chainId: ChainId,
|
||||
currentHeight: number,
|
||||
nonce: Nonce,
|
||||
response: TxsResponse,
|
||||
response: IndexedTx,
|
||||
tokens: BankTokens,
|
||||
erc20Tokens: readonly Erc20Token[],
|
||||
): ConfirmedAndSignedTransaction<UnsignedTransaction>;
|
||||
|
||||
@ -92,8 +92,8 @@ describe("CosmWasmClient.searchTx", () => {
|
||||
expect(result.length).toEqual(1);
|
||||
expect(result[0]).toEqual(
|
||||
jasmine.objectContaining({
|
||||
height: postedSend.height.toString(),
|
||||
txhash: postedSend.hash,
|
||||
height: postedSend.height,
|
||||
hash: postedSend.hash,
|
||||
tx: postedSend.tx,
|
||||
}),
|
||||
);
|
||||
@ -144,8 +144,8 @@ describe("CosmWasmClient.searchTx", () => {
|
||||
expect(result.length).toEqual(1);
|
||||
expect(result[0]).toEqual(
|
||||
jasmine.objectContaining({
|
||||
height: postedSend.height.toString(),
|
||||
txhash: postedSend.hash,
|
||||
height: postedSend.height,
|
||||
hash: postedSend.hash,
|
||||
tx: postedSend.tx,
|
||||
}),
|
||||
);
|
||||
@ -163,7 +163,7 @@ describe("CosmWasmClient.searchTx", () => {
|
||||
// Check basic structure of all results
|
||||
for (const result of results) {
|
||||
const msg = fromOneElementArray(result.tx.value.msg);
|
||||
assert(isMsgSend(msg), `${result.txhash} (height ${result.height}) is not a bank send transaction`);
|
||||
assert(isMsgSend(msg), `${result.hash} (height ${result.height}) is not a bank send transaction`);
|
||||
expect(
|
||||
msg.value.to_address === postedSend.sender || msg.value.from_address == postedSend.sender,
|
||||
).toEqual(true);
|
||||
@ -172,8 +172,8 @@ describe("CosmWasmClient.searchTx", () => {
|
||||
// Check details of most recent result
|
||||
expect(results[results.length - 1]).toEqual(
|
||||
jasmine.objectContaining({
|
||||
height: postedSend.height.toString(),
|
||||
txhash: postedSend.hash,
|
||||
height: postedSend.height,
|
||||
hash: postedSend.hash,
|
||||
tx: postedSend.tx,
|
||||
}),
|
||||
);
|
||||
@ -189,7 +189,7 @@ describe("CosmWasmClient.searchTx", () => {
|
||||
// Check basic structure of all results
|
||||
for (const result of results) {
|
||||
const msg = fromOneElementArray(result.tx.value.msg);
|
||||
assert(isMsgSend(msg), `${result.txhash} (height ${result.height}) is not a bank send transaction`);
|
||||
assert(isMsgSend(msg), `${result.hash} (height ${result.height}) is not a bank send transaction`);
|
||||
expect(
|
||||
msg.value.to_address === postedSend.recipient || msg.value.from_address == postedSend.recipient,
|
||||
).toEqual(true);
|
||||
@ -198,8 +198,8 @@ describe("CosmWasmClient.searchTx", () => {
|
||||
// Check details of most recent result
|
||||
expect(results[results.length - 1]).toEqual(
|
||||
jasmine.objectContaining({
|
||||
height: postedSend.height.toString(),
|
||||
txhash: postedSend.hash,
|
||||
height: postedSend.height,
|
||||
hash: postedSend.hash,
|
||||
tx: postedSend.tx,
|
||||
}),
|
||||
);
|
||||
@ -273,15 +273,15 @@ describe("CosmWasmClient.searchTx", () => {
|
||||
// Check basic structure of all results
|
||||
for (const result of results) {
|
||||
const msg = fromOneElementArray(result.tx.value.msg);
|
||||
assert(isMsgSend(msg), `${result.txhash} (height ${result.height}) is not a bank send transaction`);
|
||||
assert(isMsgSend(msg), `${result.hash} (height ${result.height}) is not a bank send transaction`);
|
||||
expect(msg.value.to_address).toEqual(postedSend.recipient);
|
||||
}
|
||||
|
||||
// Check details of most recent result
|
||||
expect(results[results.length - 1]).toEqual(
|
||||
jasmine.objectContaining({
|
||||
height: postedSend.height.toString(),
|
||||
txhash: postedSend.hash,
|
||||
height: postedSend.height,
|
||||
hash: postedSend.hash,
|
||||
tx: postedSend.tx,
|
||||
}),
|
||||
);
|
||||
@ -301,7 +301,7 @@ describe("CosmWasmClient.searchTx", () => {
|
||||
const msg = fromOneElementArray(result.tx.value.msg);
|
||||
assert(
|
||||
isMsgExecuteContract(msg) || isMsgInstantiateContract(msg),
|
||||
`${result.txhash} (at ${result.height}) not an execute or instantiate msg`,
|
||||
`${result.hash} (at ${result.height}) not an execute or instantiate msg`,
|
||||
);
|
||||
}
|
||||
|
||||
@ -322,8 +322,8 @@ describe("CosmWasmClient.searchTx", () => {
|
||||
// Check details of most recent result
|
||||
expect(results[results.length - 1]).toEqual(
|
||||
jasmine.objectContaining({
|
||||
height: postedExecute.height.toString(),
|
||||
txhash: postedExecute.hash,
|
||||
height: postedExecute.height,
|
||||
hash: postedExecute.hash,
|
||||
tx: postedExecute.tx,
|
||||
}),
|
||||
);
|
||||
@ -344,15 +344,15 @@ describe("CosmWasmClient.searchTx", () => {
|
||||
// Check basic structure of all results
|
||||
for (const result of results) {
|
||||
const msg = fromOneElementArray(result.tx.value.msg);
|
||||
assert(isMsgExecuteContract(msg), `${result.txhash} (at ${result.height}) not an execute msg`);
|
||||
assert(isMsgExecuteContract(msg), `${result.hash} (at ${result.height}) not an execute msg`);
|
||||
expect(msg.value.contract).toEqual(postedExecute.contract);
|
||||
}
|
||||
|
||||
// Check details of most recent result
|
||||
expect(results[results.length - 1]).toEqual(
|
||||
jasmine.objectContaining({
|
||||
height: postedExecute.height.toString(),
|
||||
txhash: postedExecute.hash,
|
||||
height: postedExecute.height,
|
||||
hash: postedExecute.hash,
|
||||
tx: postedExecute.tx,
|
||||
}),
|
||||
);
|
||||
|
||||
@ -2,7 +2,7 @@ import { Sha256 } from "@iov/crypto";
|
||||
import { Encoding } from "@iov/encoding";
|
||||
|
||||
import { Log, parseLogs } from "./logs";
|
||||
import { BlockResponse, BroadcastMode, RestClient, TxsResponse } from "./restclient";
|
||||
import { BlockResponse, BroadcastMode, RestClient } from "./restclient";
|
||||
import { CosmosSdkAccount, CosmosSdkTx, StdTx } from "./types";
|
||||
|
||||
export interface GetNonceResult {
|
||||
@ -92,6 +92,20 @@ export interface ContractDetails extends Contract {
|
||||
readonly initMsg: object;
|
||||
}
|
||||
|
||||
/** A transaction that is indexed as part of the transaction history */
|
||||
export interface IndexedTx {
|
||||
readonly height: number;
|
||||
readonly hash: string;
|
||||
readonly rawLog: string;
|
||||
readonly logs: readonly Log[];
|
||||
readonly tx: CosmosSdkTx;
|
||||
/** The gas limit as set by the user */
|
||||
readonly gasWanted?: number;
|
||||
/** The gas used by the execution */
|
||||
readonly gasUsed?: number;
|
||||
readonly timestamp: string;
|
||||
}
|
||||
|
||||
export class CosmWasmClient {
|
||||
protected readonly restClient: RestClient;
|
||||
|
||||
@ -153,7 +167,7 @@ export class CosmWasmClient {
|
||||
}
|
||||
}
|
||||
|
||||
public async searchTx(query: SearchTxQuery, filter: SearchTxFilter = {}): Promise<readonly TxsResponse[]> {
|
||||
public async searchTx(query: SearchTxQuery, filter: SearchTxFilter = {}): Promise<readonly IndexedTx[]> {
|
||||
const minHeight = filter.minHeight || 0;
|
||||
const maxHeight = filter.maxHeight || Number.MAX_SAFE_INTEGER;
|
||||
|
||||
@ -163,7 +177,7 @@ export class CosmWasmClient {
|
||||
return `${originalQuery}&tx.minheight=${minHeight}&tx.maxheight=${maxHeight}`;
|
||||
}
|
||||
|
||||
let txs: readonly TxsResponse[];
|
||||
let txs: readonly IndexedTx[];
|
||||
if (isSearchByIdQuery(query)) {
|
||||
txs = await this.txsQuery(`tx.hash=${query.id}`);
|
||||
} else if (isSearchByHeightQuery(query)) {
|
||||
@ -180,8 +194,8 @@ export class CosmWasmClient {
|
||||
const sent = await this.txsQuery(sentQuery);
|
||||
const received = await this.txsQuery(receivedQuery);
|
||||
|
||||
const sentHashes = sent.map(t => t.txhash);
|
||||
txs = [...sent, ...received.filter(t => !sentHashes.includes(t.txhash))];
|
||||
const sentHashes = sent.map(t => t.hash);
|
||||
txs = [...sent, ...received.filter(t => !sentHashes.includes(t.hash))];
|
||||
} else if (isSearchByTagsQuery(query)) {
|
||||
const rawQuery = withFilters(query.tags.map(t => `${t.key}=${t.value}`).join("&"));
|
||||
txs = await this.txsQuery(rawQuery);
|
||||
@ -190,10 +204,7 @@ export class CosmWasmClient {
|
||||
}
|
||||
|
||||
// backend sometimes messes up with min/max height filtering
|
||||
const filtered = txs.filter(tx => {
|
||||
const txHeight = parseInt(tx.height, 10);
|
||||
return txHeight >= minHeight && txHeight <= maxHeight;
|
||||
});
|
||||
const filtered = txs.filter(tx => tx.height >= minHeight && tx.height <= maxHeight);
|
||||
|
||||
return filtered;
|
||||
}
|
||||
@ -303,7 +314,7 @@ export class CosmWasmClient {
|
||||
}
|
||||
}
|
||||
|
||||
private async txsQuery(query: string): Promise<readonly TxsResponse[]> {
|
||||
private async txsQuery(query: string): Promise<readonly IndexedTx[]> {
|
||||
// TODO: we need proper pagination support
|
||||
const limit = 100;
|
||||
const result = await this.restClient.txsQuery(`${query}&limit=${limit}`);
|
||||
@ -313,6 +324,15 @@ export class CosmWasmClient {
|
||||
`Found more results on the backend than we can process currently. Results: ${result.total_count}, supported: ${limit}`,
|
||||
);
|
||||
}
|
||||
return result.txs;
|
||||
return result.txs.map(
|
||||
(restItem): IndexedTx => ({
|
||||
height: parseInt(restItem.height, 10),
|
||||
hash: restItem.txhash,
|
||||
rawLog: restItem.raw_log,
|
||||
logs: parseLogs(restItem.logs || []),
|
||||
tx: restItem.tx,
|
||||
timestamp: restItem.timestamp,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,6 +12,7 @@ export {
|
||||
Contract,
|
||||
CosmWasmClient,
|
||||
GetNonceResult,
|
||||
IndexedTx,
|
||||
PostTxResult,
|
||||
SearchByHeightQuery,
|
||||
SearchByIdQuery,
|
||||
|
||||
17
packages/sdk/types/cosmwasmclient.d.ts
vendored
17
packages/sdk/types/cosmwasmclient.d.ts
vendored
@ -1,5 +1,5 @@
|
||||
import { Log } from "./logs";
|
||||
import { BlockResponse, BroadcastMode, RestClient, TxsResponse } from "./restclient";
|
||||
import { BlockResponse, BroadcastMode, RestClient } from "./restclient";
|
||||
import { CosmosSdkAccount, CosmosSdkTx, StdTx } from "./types";
|
||||
export interface GetNonceResult {
|
||||
readonly accountNumber: number;
|
||||
@ -63,6 +63,19 @@ export interface ContractDetails extends Contract {
|
||||
/** Argument passed on initialization of the contract */
|
||||
readonly initMsg: object;
|
||||
}
|
||||
/** A transaction that is indexed as part of the transaction history */
|
||||
export interface IndexedTx {
|
||||
readonly height: number;
|
||||
readonly hash: string;
|
||||
readonly rawLog: string;
|
||||
readonly logs: readonly Log[];
|
||||
readonly tx: CosmosSdkTx;
|
||||
/** The gas limit as set by the user */
|
||||
readonly gasWanted?: number;
|
||||
/** The gas used by the execution */
|
||||
readonly gasUsed?: number;
|
||||
readonly timestamp: string;
|
||||
}
|
||||
export declare class CosmWasmClient {
|
||||
protected readonly restClient: RestClient;
|
||||
constructor(url: string, broadcastMode?: BroadcastMode);
|
||||
@ -86,7 +99,7 @@ export declare class CosmWasmClient {
|
||||
* @param height The height of the block. If undefined, the latest height is used.
|
||||
*/
|
||||
getBlock(height?: number): Promise<BlockResponse>;
|
||||
searchTx(query: SearchTxQuery, filter?: SearchTxFilter): Promise<readonly TxsResponse[]>;
|
||||
searchTx(query: SearchTxQuery, filter?: SearchTxFilter): Promise<readonly IndexedTx[]>;
|
||||
postTx(tx: StdTx): Promise<PostTxResult>;
|
||||
getCodes(): Promise<readonly Code[]>;
|
||||
getCodeDetails(codeId: number): Promise<CodeDetails>;
|
||||
|
||||
1
packages/sdk/types/index.d.ts
vendored
1
packages/sdk/types/index.d.ts
vendored
@ -11,6 +11,7 @@ export {
|
||||
Contract,
|
||||
CosmWasmClient,
|
||||
GetNonceResult,
|
||||
IndexedTx,
|
||||
PostTxResult,
|
||||
SearchByHeightQuery,
|
||||
SearchByIdQuery,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user