Page MenuHomePhabricator

D15501.id45483.diff
No OneTemporary

D15501.id45483.diff

diff --git a/modules/chronik-client/.mocharc.js b/modules/chronik-client/.mocharc.js
--- a/modules/chronik-client/.mocharc.js
+++ b/modules/chronik-client/.mocharc.js
@@ -1,4 +1,4 @@
'use strict';
module.exports = {
- timeout: '20s',
+ timeout: '60s',
};
diff --git a/modules/chronik-client/README.md b/modules/chronik-client/README.md
--- a/modules/chronik-client/README.md
+++ b/modules/chronik-client/README.md
@@ -96,3 +96,4 @@
0.18.0 - Add support for websocket connections to `ChronikClientNode`
0.19.0 - Add support for token data in tx inputs and outputs to `ChronikClientNode`
0.20.0 - Add support for calling script endpoints by address to `ChronikClientNode`
+0.21.0 - Add support for `tokenId` endpoints and token data in utxos to `ChronikClientNode`
diff --git a/modules/chronik-client/package-lock.json b/modules/chronik-client/package-lock.json
--- a/modules/chronik-client/package-lock.json
+++ b/modules/chronik-client/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "chronik-client",
- "version": "0.20.0",
+ "version": "0.21.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "chronik-client",
- "version": "0.20.0",
+ "version": "0.21.0",
"license": "MIT",
"dependencies": {
"@types/ws": "^8.2.1",
diff --git a/modules/chronik-client/package.json b/modules/chronik-client/package.json
--- a/modules/chronik-client/package.json
+++ b/modules/chronik-client/package.json
@@ -1,6 +1,6 @@
{
"name": "chronik-client",
- "version": "0.20.0",
+ "version": "0.21.0",
"description": "A client for accessing the Chronik Indexer API",
"main": "dist/index.js",
"types": "dist/index.d.ts",
diff --git a/modules/chronik-client/src/ChronikClientNode.ts b/modules/chronik-client/src/ChronikClientNode.ts
--- a/modules/chronik-client/src/ChronikClientNode.ts
+++ b/modules/chronik-client/src/ChronikClientNode.ts
@@ -143,6 +143,11 @@
return convertToRawTx(rawTx);
}
+ /** Create object that allows fetching info about a given token */
+ public tokenId(tokenId: string): TokenIdEndpoint {
+ return new TokenIdEndpoint(this._proxyInterface, tokenId);
+ }
+
/** Create object that allows fetching script history or UTXOs. */
public script(
scriptType: ScriptType_InNode,
@@ -228,6 +233,91 @@
}
}
+/** Allows fetching tokenId confirmedTxs, unconfirmedTxs, history, and UTXOs. */
+export class TokenIdEndpoint {
+ private _proxyInterface: FailoverProxy;
+ private _tokenId: string;
+
+ constructor(proxyInterface: FailoverProxy, tokenId: string) {
+ this._proxyInterface = proxyInterface;
+ this._tokenId = tokenId;
+ }
+
+ /**
+ * Fetches the tx history of this tokenId, in anti-chronological order.
+ * @param page Page index of the tx history.
+ * @param pageSize Number of txs per page.
+ */
+ public async history(
+ page = 0, // Get the first page if unspecified
+ pageSize = 25, // Must be less than 200, let server handle error as server setting could change
+ ): Promise<TxHistoryPage_InNode> {
+ const data = await this._proxyInterface.get(
+ `/token-id/${this._tokenId}/history?page=${page}&page_size=${pageSize}`,
+ );
+ const historyPage = proto.TxHistoryPage.decode(data);
+ return {
+ txs: historyPage.txs.map(convertToTx),
+ numPages: historyPage.numPages,
+ numTxs: historyPage.numTxs,
+ };
+ }
+
+ /**
+ * Fetches the confirmed tx history of this tokenId, in anti-chronological order.
+ * @param page Page index of the tx history.
+ * @param pageSize Number of txs per page.
+ */
+ public async confirmedTxs(
+ page = 0, // Get the first page if unspecified
+ pageSize = 25, // Must be less than 200, let server handle error as server setting could change
+ ): Promise<TxHistoryPage_InNode> {
+ const data = await this._proxyInterface.get(
+ `/token-id/${this._tokenId}/confirmed-txs?page=${page}&page_size=${pageSize}`,
+ );
+ const historyPage = proto.TxHistoryPage.decode(data);
+ return {
+ txs: historyPage.txs.map(convertToTx),
+ numPages: historyPage.numPages,
+ numTxs: historyPage.numTxs,
+ };
+ }
+
+ /**
+ * Fetches the unconfirmed tx history of this tokenId, in anti-chronological order.
+ * @param page Page index of the tx history.
+ * @param pageSize Number of txs per page.
+ */
+ public async unconfirmedTxs(
+ page = 0, // Get the first page if unspecified
+ pageSize = 25, // Must be less than 200, let server handle error as server setting could change
+ ): Promise<TxHistoryPage_InNode> {
+ const data = await this._proxyInterface.get(
+ `/token-id/${this._tokenId}/unconfirmed-txs?page=${page}&page_size=${pageSize}`,
+ );
+ const historyPage = proto.TxHistoryPage.decode(data);
+ return {
+ txs: historyPage.txs.map(convertToTx),
+ numPages: historyPage.numPages,
+ numTxs: historyPage.numTxs,
+ };
+ }
+
+ /**
+ * Fetches the current UTXO set for this tokenId.
+ */
+ public async utxos(): Promise<TokenIdUtxos> {
+ const data = await this._proxyInterface.get(
+ `/token-id/${this._tokenId}/utxos`,
+ );
+ const utxos = proto.Utxos.decode(data);
+ return {
+ tokenId: this._tokenId,
+ utxos: utxos.utxos.map(convertToUtxo),
+ };
+ }
+}
+
/** Config for a WebSocket connection to Chronik. */
export interface WsConfig_InNode {
/** Fired when a message is sent from the WebSocket. */
@@ -587,7 +677,7 @@
if (utxo.outpoint === undefined) {
throw new Error('UTXO outpoint is undefined');
}
- return {
+ const utxoInNode: Utxo_InNode = {
outpoint: {
txid: toHexRev(utxo.outpoint.txid),
outIdx: utxo.outpoint.outIdx,
@@ -597,6 +687,11 @@
value: parseInt(utxo.value),
isFinal: utxo.isFinal,
};
+ if (typeof utxo.token !== 'undefined') {
+ // We only return a token key if we have token data for this input
+ utxoInNode.token = convertToTokenInNode(utxo.token);
+ }
+ return utxoInNode;
}
function convertToTokenEntry(tokenEntry: proto.TokenEntry): TokenEntry {
@@ -711,13 +806,19 @@
);
}
- return {
+ const tokenInNode: Token_InNode = {
tokenId: token.tokenId,
tokenType: convertToTokenType(token.tokenType),
- entryIdx: token.entryIdx,
amount: token.amount,
isMintBaton: token.isMintBaton,
};
+
+ // We do not bother including entryIdx for utxos, where it is always -1
+ if (token.entryIdx !== -1) {
+ tokenInNode.entryIdx = token.entryIdx;
+ }
+
+ return tokenInNode;
}
function convertToBlockMsgType(msgType: proto.BlockMsgType): BlockMsgType {
@@ -1063,6 +1164,8 @@
value: number;
/** Is this utxo avalanche finalized */
isFinal: boolean;
+ /** Token value attached to this utxo */
+ token?: Token_InNode;
}
/** Token coloring an input or output */
@@ -1071,8 +1174,12 @@
tokenId: string;
/** Token type of the token */
tokenType: TokenType;
- /** Index into `token_entries` for `Tx`. -1 for UTXOs */
- entryIdx: number;
+ /**
+ * Index into `token_entries` for `Tx`
+ * chronik returns -1 for UTXOs, chronik-client
+ * passes no entryIdx key for UTXOS
+ */
+ entryIdx?: number;
/** Base token amount of the input/output */
amount: string;
/** Whether the token is a mint baton */
@@ -1163,3 +1270,11 @@
type: 'Error';
msg: string;
}
+
+/** List of UTXOs */
+export interface TokenIdUtxos {
+ /** TokenId used to fetch these utxos */
+ tokenId: string;
+ /** UTXOs */
+ utxos: Utxo_InNode[];
+}
diff --git a/modules/chronik-client/test/integration/token_alp.ts b/modules/chronik-client/test/integration/token_alp.ts
--- a/modules/chronik-client/test/integration/token_alp.ts
+++ b/modules/chronik-client/test/integration/token_alp.ts
@@ -6,7 +6,12 @@
import chaiAsPromised from 'chai-as-promised';
import { ChildProcess } from 'node:child_process';
import { EventEmitter, once } from 'node:events';
-import { ChronikClientNode, Tx_InNode } from '../../index';
+import {
+ ChronikClientNode,
+ Token_InNode,
+ TxHistoryPage_InNode,
+ Tx_InNode,
+} from '../../index';
import initializeTestRunner from '../setup/testRunner';
const expect = chai.expect;
@@ -21,6 +26,8 @@
let get_alp_genesis2_txid: Promise<string>;
let get_alp_multi_txid: Promise<string>;
let get_alp_mega_txid: Promise<string>;
+ let get_alp_mint_two_txid: Promise<string>;
+ let get_alp_send_two_txid: Promise<string>;
const statusEvent = new EventEmitter();
before(async () => {
@@ -69,6 +76,18 @@
});
}
+ if (message && message.alp_mint_two_txid) {
+ get_alp_mint_two_txid = new Promise(resolve => {
+ resolve(message.alp_mint_two_txid);
+ });
+ }
+
+ if (message && message.alp_send_two_txid) {
+ get_alp_send_two_txid = new Promise(resolve => {
+ resolve(message.alp_send_two_txid);
+ });
+ }
+
if (message && message.status) {
statusEvent.emit(message.status);
}
@@ -126,6 +145,8 @@
let alpNextGenesisTxid = '';
let alpMultiTxid = '';
let alpMegaTxid = '';
+ let alpMintTwoTxid = '';
+ let alpSendTwoTxid = '';
let alpGenesis: Tx_InNode;
let alpMint: Tx_InNode;
@@ -133,6 +154,10 @@
let alpNextGenesis: Tx_InNode;
let alpMulti: Tx_InNode;
let alpMega: Tx_InNode;
+ let alpMintTwo: Tx_InNode;
+ let alpSendTwo: Tx_InNode;
+
+ let confirmedTxsForAlpGenesisTxid: TxHistoryPage_InNode;
it('Gets an ALP genesis tx from the mempool', async () => {
const chronikUrl = await chronik_url;
@@ -157,7 +182,7 @@
]);
// We get expected outputs including expected Token data
- expect(alpGenesis.outputs).to.deep.equal([
+ const expectedOutputs = [
{
...BASE_TX_OUTPUT,
value: 0,
@@ -210,7 +235,8 @@
isMintBaton: true,
},
},
- ]);
+ ];
+ expect(alpGenesis.outputs).to.deep.equal(expectedOutputs);
// We get a Entries of expected shape, with tokenId the txid for a genesis tx
expect(alpGenesis.tokenEntries).to.deep.equal([
@@ -231,6 +257,61 @@
// Normal status
expect(alpGenesis.tokenStatus).to.eql('TOKEN_STATUS_NORMAL');
+
+ // We can get the same token info from calling chronik.tokenId.utxos() on this genesis txid
+ const utxosByTokenId = await chronik.tokenId(alpGenesisTxid).utxos();
+
+ // We get the calling tokenId returned
+ expect(utxosByTokenId.tokenId).to.eql(alpGenesisTxid);
+
+ // Utxos returned by token id include Token object matching the outputs, except they have no entryIdx
+
+ // Get only the outputs that are token utxos for alpGenesisTxid
+ const outputsWithTokenKey = expectedOutputs.filter(
+ output => 'token' in output,
+ );
+
+ const utxoTokenKeysFromOutputs: Token_InNode[] = [];
+ for (const output of outputsWithTokenKey) {
+ if ('token' in output) {
+ const { token } = output;
+ // Remove the entryIdx key from these outputs, as we do not expect to see it in tokenId.utxos() output
+ delete (token as Token_InNode).entryIdx;
+ utxoTokenKeysFromOutputs.push(output.token as Token_InNode);
+ }
+ }
+
+ // We have as many utxosByTokenId as we do outputs with token key
+ expect(utxosByTokenId.utxos.length).to.eql(
+ utxoTokenKeysFromOutputs.length,
+ );
+
+ // They match and are in the same order
+ for (let i = 0; i < utxosByTokenId.utxos.length; i += 1) {
+ expect(utxosByTokenId.utxos[i].token).to.deep.equal(
+ utxoTokenKeysFromOutputs[i],
+ );
+ }
+
+ // We get the same tx info for this tx from calling chronik.tokenId().unconfirmedTxs()
+ const unconfirmedTxsForThisTokenId = await chronik
+ .tokenId(alpGenesisTxid)
+ .unconfirmedTxs();
+ expect(unconfirmedTxsForThisTokenId.txs.length).to.eql(1);
+ expect(unconfirmedTxsForThisTokenId.txs[0]).to.deep.equal(alpGenesis);
+
+ // We get nothing from confirmedTxs() as none are confirmed
+ const confirmedTxsForThisTokenId = await chronik
+ .tokenId(alpGenesisTxid)
+ .confirmedTxs();
+ expect(confirmedTxsForThisTokenId.txs.length).to.eql(0);
+
+ // History returns the output of confirmed + unconfirmed (in this case, just unconfirmed)
+ const historyForThisTokenId = await chronik
+ .tokenId(alpGenesisTxid)
+ .history();
+ expect(historyForThisTokenId.txs.length).to.eql(1);
+ expect(historyForThisTokenId.txs[0]).to.deep.equal(alpGenesis);
});
it('Gets an ALP mint tx from the mempool', async () => {
const chronikUrl = await chronik_url;
@@ -607,6 +688,45 @@
// Normal status
expect(alpMulti.tokenStatus).to.eql('TOKEN_STATUS_NORMAL');
+
+ // Test order of tokenId.history()
+ const unconfirmedTxs = await chronik
+ .tokenId(alpGenesisTxid)
+ .unconfirmedTxs();
+
+ const alphabeticalBroadcastAlpGenesisMempoolTxs = [
+ alpGenesisTxid,
+ alpMintTxid,
+ alpSendTxid,
+ alpMultiTxid,
+ ].sort();
+
+ // Unconfirmed txs come in alphabetical order by txid
+ // Note that the genesis tx was broadcast first but is alphabetically 2nd
+ for (let i = 0; i < unconfirmedTxs.txs.length; i += 1) {
+ expect(unconfirmedTxs.txs[i].txid).to.eql(
+ alphabeticalBroadcastAlpGenesisMempoolTxs[i],
+ );
+ }
+
+ // Test order of tokenId.history()
+ const historyTxs = await chronik.tokenId(alpGenesisTxid).history();
+
+ // History txs are sorted by blockheight, then timeFirstSeen, then reverse alphabetical by txid
+ // These txs all have the same blockheight and timeFirstSeen, so we see them in reverse alphabetical order
+ const reverseAlphabeticalBroadcastAlpGenesisMempoolTxs = [
+ alpGenesisTxid,
+ alpMintTxid,
+ alpSendTxid,
+ alpMultiTxid,
+ ]
+ .sort()
+ .reverse();
+ for (let i = 0; i < historyTxs.txs.length; i += 1) {
+ expect(historyTxs.txs[i].txid).to.eql(
+ reverseAlphabeticalBroadcastAlpGenesisMempoolTxs[i],
+ );
+ }
});
it('Can get all of the above txs, and a wild mega-tx, from the blockTxs endpoint after they are mined in a block', async () => {
const chronikUrl = await chronik_url;
@@ -904,7 +1024,7 @@
alpMega,
].sort((a, b) => a.txid.localeCompare(b.txid));
- // The token fields of Tx_InNode(s) from blockTxs match the Tx_InNode(s) from tx]
+ // The token fields of Tx_InNode(s) from blockTxs match the Tx_InNode(s) from tx
// Note the txs are not expected to fully match bc now we have block key and spentBy,
// expected after confirmation
// This type of functionality is tested in blocktxs_and_tx_and_rawtx.ts
@@ -939,5 +1059,260 @@
// Same tx count as blockTxs
expect(history.numTxs).to.eql(7);
+
+ // Now we have no unconfirmed txs for the alpGenesisTxid
+ const unconfirmedTxsForThisTokenId = await chronik
+ .tokenId(alpGenesisTxid)
+ .unconfirmedTxs();
+ expect(unconfirmedTxsForThisTokenId.txs.length).to.eql(0);
+
+ // We can get all the confirmedTxs for alpGenesisTxid
+ // Note: they are in alphabetical order by txid (at least, in this block)
+ const broadcastAlpTxsOfAlpGenesisTokenId = [
+ alpGenesis,
+ alpMint,
+ alpSend,
+ alpMulti,
+ alpMega,
+ ].sort((a, b) => a.txid.localeCompare(b.txid));
+
+ confirmedTxsForAlpGenesisTxid = await chronik
+ .tokenId(alpGenesisTxid)
+ .confirmedTxs();
+
+ expect(confirmedTxsForAlpGenesisTxid.txs.length).to.eql(
+ broadcastAlpTxsOfAlpGenesisTokenId.length,
+ );
+
+ // They are sorted by blockheight, then alphabetical by txid
+ // In this case, they all have the same blockheight -- so we see alphabetical by txid
+ // Note that timeFirstSeen is not considered from this endpoint, as alpMega has timeFirstSeen of 0
+ // But it appears second here
+ for (let i = 0; i < confirmedTxsForAlpGenesisTxid.txs.length; i += 1) {
+ // in practice, everything matches except for the 'block' and 'output.spentBy' keys
+ // these are expected to have changed since we stored the txs when they were in the mempool
+ // now we are comparing result to confirmed txs
+ expect(confirmedTxsForAlpGenesisTxid.txs[i].txid).to.eql(
+ broadcastAlpTxsOfAlpGenesisTokenId[i].txid,
+ );
+ expect(confirmedTxsForAlpGenesisTxid.txs[i].inputs).to.deep.equal(
+ broadcastAlpTxsOfAlpGenesisTokenId[i].inputs,
+ );
+ expect(
+ confirmedTxsForAlpGenesisTxid.txs[i].tokenEntries,
+ ).to.deep.equal(broadcastAlpTxsOfAlpGenesisTokenId[i].tokenEntries);
+ }
+ });
+ it('Can get confirmed and unconfirmed txs from tokenId.history()', async () => {
+ const chronikUrl = await chronik_url;
+ const chronik = new ChronikClientNode(chronikUrl);
+
+ alpMintTwoTxid = await get_alp_mint_two_txid;
+ alpSendTwoTxid = await get_alp_send_two_txid;
+
+ // Can these from the tx endpoint
+ alpMintTwo = await chronik.tx(alpMintTwoTxid);
+ alpSendTwo = await chronik.tx(alpSendTwoTxid);
+
+ // alpSendTwoTxid eb227bec6fd4d270262e14efa128a970f0ea4e529c599b1eb28af5fc8eaf3452 (broadcast 1st, alphabetically 2nd)
+ // alpMintTwoTxid 665c70b6c01b8e9b4865136ee650bae344fb8b45def49eb7ec303b04d2520b4b (broadcast 2nd, alphabetically 1st)
+ const alphabeticalUnconfirmedAlpGenesisTxs = [
+ alpMintTwo,
+ alpSendTwo,
+ ].sort((a, b) => a.txid.localeCompare(b.txid));
+
+ // Can get these from unconfirmed txs
+ const unconfirmedTxs = await chronik
+ .tokenId(alpGenesisTxid)
+ .unconfirmedTxs();
+
+ // unconfirmedTxs returns them in alphabetical order
+ for (let i = 0; i < unconfirmedTxs.txs.length; i += 1) {
+ expect(unconfirmedTxs.txs[i]).to.deep.equal(
+ alphabeticalUnconfirmedAlpGenesisTxs[i],
+ );
+ }
+
+ // They are the only unconfirmed txs
+ expect(unconfirmedTxs.txs.length).to.eql(2);
+
+ // Calling chronik.tokenId.history() returns all confirmed and unconfirmed txs
+
+ const allTokenTxsForAlpGenesisTxid = await chronik
+ .tokenId(alpGenesisTxid)
+ .history();
+
+ // We get all expected txs, confirmed and unconfirmed
+ expect(allTokenTxsForAlpGenesisTxid.txs.length).to.eql(
+ confirmedTxsForAlpGenesisTxid.txs.length +
+ unconfirmedTxs.txs.length,
+ );
+
+ // Txs from history are sorted by blockheight, then timeFirstSeen, then txid
+ // In this case, timeFirstSeen is a mock time, so it is the same for each
+ // So we expect reverse alphabetical order by txid
+ const unconfirmedInHistory = allTokenTxsForAlpGenesisTxid.txs.splice(
+ 0,
+ 2,
+ );
+
+ const reverseAlphabeticalUnconfirmedAlpGenesisTxs = [
+ alpMintTwo,
+ alpSendTwo,
+ ].sort((b, a) => a.txid.localeCompare(b.txid));
+ expect(unconfirmedInHistory).to.deep.equal(
+ reverseAlphabeticalUnconfirmedAlpGenesisTxs,
+ );
+
+ // We get the rest of the txs in expected order
+ // Sorted by blockheight, then timeFirstSeen, then txid
+ // In this case, all txs have the same blockheight
+ // alpMegaTxid has timeFirstSeen of 0 since it was manually mined
+ // All other txs have the same timeFirstSeen so are sorted in reverse alphabetical order
+
+ // Confirm the order of txs from .history()
+ const confirmedAlpTxs = [
+ alpMegaTxid, // timeFirstSeen 0 | 72101f535470e0a6de7db9ba0ba115845566f738cc5124255b472347b5927565
+ alpSendTxid, // timeFirstSeen 1300000000 | e623ab8971c93fa1a831a4310da65554c8dfd811c16cd5d41c6612268cb5dd5f
+ alpMultiTxid, // timeFirstSeen 1300000000 | e3c47f14d7ba3ab9a6f32a0fa8fcac41d06d3af595ebb5bab77ad03633a52eba
+ alpGenesisTxid, // timeFirstSeen 1300000000 | bb4d71aa6c0a92144f854402f2677975ad86d3a72cb7b0fb48d02473a88fc6e2
+ alpMintTxid, // timeFirstSeen 1300000000 | 0dab1008db30343a4f771983e9fd96cbc15f0c6efc73f5249c9bae311ef1e92f
+ ];
+
+ for (let i = 0; i < allTokenTxsForAlpGenesisTxid.txs.length; i += 1) {
+ expect(confirmedAlpTxs[i]).to.eql(
+ allTokenTxsForAlpGenesisTxid.txs[i].txid,
+ );
+ }
+
+ // Make sure other parts of returned tx objects are as expected
+ // Since one of the utxos from confirmedTxsForAlpGenesisTxid was spent to create
+ // the second mint tx, this spentBy key has changed
+
+ // We spent the mint baton
+ const newConfirmedMintTxIndex =
+ allTokenTxsForAlpGenesisTxid.txs.findIndex(
+ tx => tx.txid === alpMintTxid,
+ );
+
+ const newMintTx = allTokenTxsForAlpGenesisTxid.txs.splice(
+ newConfirmedMintTxIndex,
+ 1,
+ )[0];
+ const confirmedMintTxIndex =
+ confirmedTxsForAlpGenesisTxid.txs.findIndex(
+ tx => tx.txid === alpMintTxid,
+ );
+ const oldMintTx = confirmedTxsForAlpGenesisTxid.txs.splice(
+ confirmedMintTxIndex,
+ 1,
+ )[0];
+
+ // We have removed this tx from both histories
+ expect(allTokenTxsForAlpGenesisTxid.txs.length).to.eql(
+ confirmedTxsForAlpGenesisTxid.txs.length,
+ );
+
+ // They are the same except for outputs, as expected
+ expect(oldMintTx.inputs).to.deep.equal(newMintTx.inputs);
+ expect(oldMintTx.tokenEntries).to.deep.equal(newMintTx.tokenEntries);
+
+ // Since one of the utxos from confirmedTxsForAlpGenesisTxid was spent to create
+ // the second send tx, this spentBy key has changed
+ const newConfirmedSendTxIndex =
+ allTokenTxsForAlpGenesisTxid.txs.findIndex(
+ tx => tx.txid === alpSendTxid,
+ );
+
+ const newSendTx = allTokenTxsForAlpGenesisTxid.txs.splice(
+ newConfirmedSendTxIndex,
+ 1,
+ )[0];
+ const confirmedSendTxIndex =
+ confirmedTxsForAlpGenesisTxid.txs.findIndex(
+ tx => tx.txid === alpSendTxid,
+ );
+ const oldSendTx = confirmedTxsForAlpGenesisTxid.txs.splice(
+ confirmedSendTxIndex,
+ 1,
+ )[0];
+
+ // We have removed this tx from both histories
+ expect(allTokenTxsForAlpGenesisTxid.txs.length).to.eql(
+ confirmedTxsForAlpGenesisTxid.txs.length,
+ );
+
+ // They are the same except for outputs, as expected
+ expect(oldSendTx.inputs).to.deep.equal(newSendTx.inputs);
+ expect(oldSendTx.tokenEntries).to.deep.equal(newSendTx.tokenEntries);
+
+ // The other txs are the same, though the order is not the same
+ // allTokenTxsForAlpGenesisTxid is sorted by blockheight, then timeFirstSeen, then reverse alphabetical by txid
+ // confirmedTxsForAlpGenesisTxid is sorted by blockheight, then alphabetical by txid
+
+ // These respective orderings are already tested above, here we test they have the same content
+ expect(
+ allTokenTxsForAlpGenesisTxid.txs.sort((a, b) =>
+ a.txid.localeCompare(b.txid),
+ ),
+ ).to.deep.equal(
+ confirmedTxsForAlpGenesisTxid.txs.sort((a, b) =>
+ a.txid.localeCompare(b.txid),
+ ),
+ );
+ });
+ it('We get tx history in expected order from both tokenId().history() and tokenId.confirmedTxs()', async () => {
+ const chronikUrl = await chronik_url;
+ const chronik = new ChronikClientNode(chronikUrl);
+
+ // Can get all confirmed token txs for alpGenesisTxid
+ const confirmedTxs = await chronik
+ .tokenId(alpGenesisTxid)
+ .confirmedTxs();
+
+ // Confirmed txs are sorted by blockheight, then alphabetically by txid
+ // timeFirstSeen is not considered, demonstrated by alpMegaTxid not being first
+ const confirmedAlpTxids = [
+ alpMintTxid, // blockheight 102, timeFirstSeen 1300000000 | 0dab1008db30343a4f771983e9fd96cbc15f0c6efc73f5249c9bae311ef1e92f
+ alpMegaTxid, // blockheight 102, timeFirstSeen 0 | 72101f535470e0a6de7db9ba0ba115845566f738cc5124255b472347b5927565
+ alpGenesisTxid, // blockheight 102, timeFirstSeen 1300000000 | bb4d71aa6c0a92144f854402f2677975ad86d3a72cb7b0fb48d02473a88fc6e2
+ alpMultiTxid, // blockheight 102, timeFirstSeen 1300000000 | e3c47f14d7ba3ab9a6f32a0fa8fcac41d06d3af595ebb5bab77ad03633a52eba
+ alpSendTxid, //blockheight 102, timeFirstSeen 1300000000 | e623ab8971c93fa1a831a4310da65554c8dfd811c16cd5d41c6612268cb5dd5f
+ alpMintTwoTxid, // blockheight 103, timeFirstSeen 1300000000 | 665c70b6c01b8e9b4865136ee650bae344fb8b45def49eb7ec303b04d2520b4b
+ alpSendTwoTxid, // blockheight 103, timeFirstSeen 1300000000 | eb227bec6fd4d270262e14efa128a970f0ea4e529c599b1eb28af5fc8eaf3452
+ ];
+
+ // Same amount of txs in each
+ expect(confirmedTxs.txs.length).to.eql(confirmedAlpTxids.length);
+
+ // Txs are in expected order
+ for (let i = 0; i < confirmedTxs.txs.length; i += 1) {
+ expect(confirmedTxs.txs[i].txid).to.eql(confirmedAlpTxids[i]);
+ }
+
+ // Can get all confirmed token txs for alpGenesisTxid
+ const history = await chronik.tokenId(alpGenesisTxid).history();
+
+ // Txs from history are ordered by blockheight, then timeFirstSeen, then reverse alphabetical by txid
+ // In this case, alpSendTwoTxid and alpMintTwoTxid are from the highest blockheight.
+ // alpMegaTxid has timeFirstSeen of 0 because it had to be manually mined in block. So, it comes first in the next block
+ // The other txids in the alpMegaTxid block all have the same timeFirstSeen, so they are sorted reverse alphabetically
+ const historyAlpTxids = [
+ alpSendTwoTxid, // timeFirstSeen 1300000000, blockheight 103 | eb227bec6fd4d270262e14efa128a970f0ea4e529c599b1eb28af5fc8eaf3452
+ alpMintTwoTxid, // timeFirstSeen 1300000000, blockheight 103 | 665c70b6c01b8e9b4865136ee650bae344fb8b45def49eb7ec303b04d2520b4b
+ alpMegaTxid, // timeFirstSeen 0, blockheight 102 | 72101f535470e0a6de7db9ba0ba115845566f738cc5124255b472347b5927565
+ alpSendTxid, // timeFirstSeen 1300000000, blockheight 102 | e623ab8971c93fa1a831a4310da65554c8dfd811c16cd5d41c6612268cb5dd5f
+ alpMultiTxid, // timeFirstSeen 1300000000, blockheight 102 | e3c47f14d7ba3ab9a6f32a0fa8fcac41d06d3af595ebb5bab77ad03633a52eba
+ alpGenesisTxid, // timeFirstSeen 1300000000, blockheight 102 | bb4d71aa6c0a92144f854402f2677975ad86d3a72cb7b0fb48d02473a88fc6e2
+ alpMintTxid, // timeFirstSeen 1300000000, blockheight 102 | 0dab1008db30343a4f771983e9fd96cbc15f0c6efc73f5249c9bae311ef1e92f
+ ];
+
+ // Same amount of txs in each
+ expect(history.txs.length).to.eql(historyAlpTxids.length);
+
+ // Txs in expected order
+ for (let i = 0; i < confirmedTxs.txs.length; i += 1) {
+ expect(history.txs[i].txid).to.eql(historyAlpTxids[i]);
+ }
});
});
diff --git a/test/functional/setup_scripts/chronik-client_token_alp.py b/test/functional/setup_scripts/chronik-client_token_alp.py
--- a/test/functional/setup_scripts/chronik-client_token_alp.py
+++ b/test/functional/setup_scripts/chronik-client_token_alp.py
@@ -308,8 +308,70 @@
assert_equal(node.getblockcount(), 102)
yield True
- self.log.info("Step 7: Send wild oversized ALP tx")
- assert_equal(node.getblockcount(), 102)
+ self.log.info("Step 7: Send another ALP tx in the mempool")
+ # Another ALP Send tx
+ alp_send_two_tx = CTransaction()
+ alp_send_two_tx.vin = [
+ CTxIn(
+ COutPoint(int(another_alp_genesis_tx_txid, 16), 4),
+ SCRIPTSIG_OP_TRUE,
+ ),
+ CTxIn(
+ COutPoint(int(alp_send_tx_txid, 16), 2),
+ SCRIPTSIG_OP_TRUE,
+ ),
+ ]
+ alp_send_two_tx.vout = [
+ alp_opreturn(
+ alp_send(
+ token_id=alp_genesis_tx_txid,
+ output_amounts=[10, 1],
+ ),
+ ),
+ CTxOut(coinvalue - 300000, P2SH_OP_TRUE),
+ CTxOut(546, P2SH_OP_TRUE),
+ ]
+ alp_send_two_tx_txid = node.sendrawtransaction(
+ alp_send_two_tx.serialize().hex()
+ )
+ self.log.info(f"alp_send_two_tx_txid: {alp_send_two_tx_txid}")
+ send_ipc_message({"alp_send_two_txid": alp_send_two_tx_txid})
+
+ # Another ALP Mint tx
+ alp_mint_two_tx = CTransaction()
+ alp_mint_two_tx.vin = [
+ CTxIn(
+ # COutPoint(int(another_alp_genesis_tx_txid, 16), 4),
+ COutPoint(int(alp_send_two_tx_txid, 16), 1),
+ SCRIPTSIG_OP_TRUE,
+ ),
+ CTxIn(
+ COutPoint(int(alp_mint_tx_txid, 16), 3),
+ SCRIPTSIG_OP_TRUE,
+ ),
+ ]
+ alp_mint_two_tx.vout = [
+ alp_opreturn(
+ alp_mint(
+ token_id=alp_genesis_tx_txid,
+ mint_amounts=[5, 0],
+ num_batons=1,
+ ),
+ ),
+ CTxOut(546, P2SH_OP_TRUE),
+ CTxOut(546, P2SH_OP_TRUE),
+ CTxOut(546, P2SH_OP_TRUE),
+ CTxOut(coinvalue - 400000, P2SH_OP_TRUE),
+ ]
+ alp_mint_two_tx_txid = node.sendrawtransaction(
+ alp_mint_two_tx.serialize().hex()
+ )
+ send_ipc_message({"alp_mint_two_txid": alp_mint_two_tx_txid})
+ yield True
+
+ self.log.info("Step 8: Confirm your second ALP tx")
+ self.generate(node, 1)
+ assert_equal(node.getblockcount(), 103)
yield True

File Metadata

Mime Type
text/plain
Expires
Wed, May 21, 00:14 (30 m, 14 s)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5866195
Default Alt Text
D15501.id45483.diff (30 KB)

Event Timeline