diff --git a/typescript/src/bitcoin.ts b/typescript/src/bitcoin.ts index 237079ea6..67cf1e0ba 100644 --- a/typescript/src/bitcoin.ts +++ b/typescript/src/bitcoin.ts @@ -66,7 +66,7 @@ export type TransactionInput = TransactionOutpoint & { /** * The scriptSig that unlocks the specified outpoint for spending. */ - scriptSig: any + scriptSig: Hex } /** @@ -86,7 +86,7 @@ export interface TransactionOutput { /** * The receiving scriptPubKey. */ - scriptPubKey: any + scriptPubKey: Hex } /** diff --git a/typescript/src/electrum.ts b/typescript/src/electrum.ts index b056b8716..7342d1835 100644 --- a/typescript/src/electrum.ts +++ b/typescript/src/electrum.ts @@ -13,6 +13,7 @@ import Electrum from "electrum-client-js" import sha256 from "bcrypto/lib/sha256-browser.js" import { BigNumber } from "ethers" import { URL } from "url" +import { Hex } from "./hex" /** * Represents a set of credentials required to establish an Electrum connection. @@ -149,30 +150,40 @@ export class Client implements BitcoinClient { */ getTransaction(transactionHash: TransactionHash): Promise { return this.withElectrum(async (electrum: any) => { - const transaction = await electrum.blockchain_transaction_get( + // We cannot use `blockchain_transaction_get` with `verbose = true` argument + // to get the the transaction details as Esplora/Electrs doesn't support verbose + // transactions. + // See: https://github.com/Blockstream/electrs/pull/36 + const rawTransaction = await electrum.blockchain_transaction_get( transactionHash.toString(), - true + false ) - const inputs = transaction.vin.map( + if (!rawTransaction) { + throw new Error(`Transaction not found`) + } + + // Decode the raw transaction. + const transaction = bcoin.TX.fromRaw(rawTransaction, "hex") + + const inputs = transaction.inputs.map( (input: any): TransactionInput => ({ - transactionHash: TransactionHash.from(input.txid), - outputIndex: input.vout, - scriptSig: input.scriptSig, + transactionHash: TransactionHash.from(input.prevout.hash).reverse(), + outputIndex: input.prevout.index, + scriptSig: Hex.from(input.script.toRaw()), }) ) - const outputs = transaction.vout.map( - (output: any): TransactionOutput => ({ - outputIndex: output.n, - // The `output.value` is in BTC so it must be converted to satoshis. - value: BigNumber.from((parseFloat(output.value) * 1e8).toFixed(0)), - scriptPubKey: output.scriptPubKey, + const outputs = transaction.outputs.map( + (output: any, i: number): TransactionOutput => ({ + outputIndex: i, + value: BigNumber.from(output.value), + scriptPubKey: Hex.from(output.script.toRaw()), }) ) return { - transactionHash: TransactionHash.from(transaction.txid), + transactionHash: TransactionHash.from(transaction.hash()).reverse(), inputs: inputs, outputs: outputs, } @@ -203,15 +214,81 @@ export class Client implements BitcoinClient { getTransactionConfirmations( transactionHash: TransactionHash ): Promise { + // We cannot use `blockchain_transaction_get` with `verbose = true` argument + // to get the the transaction details as Esplora/Electrs doesn't support verbose + // transactions. + // See: https://github.com/Blockstream/electrs/pull/36 + return this.withElectrum(async (electrum: any) => { - const transaction = await electrum.blockchain_transaction_get( + const rawTransaction: string = await electrum.blockchain_transaction_get( transactionHash.toString(), - true + false ) - // For unconfirmed transactions `confirmations` property may be undefined, so - // we will return 0 instead. - return transaction.confirmations ?? 0 + // Decode the raw transaction. + const transaction = bcoin.TX.fromRaw(rawTransaction, "hex") + + // As a workaround for the problem described in https://github.com/Blockstream/electrs/pull/36 + // we need to calculate the number of confirmations based on the latest + // block height and block height of the transaction. + // Electrum protocol doesn't expose a function to get the transaction's block + // height (other that the `GetTransaction` that is unsupported by Esplora/Electrs). + // To get the block height of the transaction we query the history of transactions + // for the output script hash, as the history contains the transaction's block + // height. + + // Initialize txBlockHeigh with minimum int32 value to identify a problem when + // a block height was not found in a history of any of the script hashes. + // + // The history is expected to return a block height for confirmed transaction. + // If a transaction is unconfirmed (is still in the mempool) the height will + // have a value of `0` or `-1`. + let txBlockHeight: number = Math.min() + for (const output of transaction.outputs) { + const scriptHash: Buffer = output.script.sha256() + + type HistoryEntry = { + // eslint-disable-next-line camelcase + tx_hash: string + height: number + } + + const scriptHashHistory: HistoryEntry[] = + await electrum.blockchain_scripthash_getHistory( + scriptHash.reverse().toString("hex") + ) + + const tx = scriptHashHistory.find( + (t) => t.tx_hash === transactionHash.toString() + ) + + if (tx) { + txBlockHeight = tx.height + break + } + } + + // History querying didn't come up with the transaction's block height. Return + // an error. + if (txBlockHeight === Math.min()) { + throw new Error( + "failed to find the transaction block height in script hashes' histories" + ) + } + + // If the block height is greater than `0` the transaction is confirmed. + if (txBlockHeight > 0) { + const latestBlockHeight: number = await this.latestBlockHeight() + + if (latestBlockHeight >= txBlockHeight) { + // Add `1` to the calculated difference as if the transaction block + // height equals the latest block height the transaction is already + // confirmed, so it has one confirmation. + return latestBlockHeight - txBlockHeight + 1 + } + } + + return 0 }) } diff --git a/typescript/test/data/deposit-sweep.ts b/typescript/test/data/deposit-sweep.ts index b2ae7ceec..59e3f5770 100644 --- a/typescript/test/data/deposit-sweep.ts +++ b/typescript/test/data/deposit-sweep.ts @@ -10,6 +10,7 @@ import { import { calculateDepositRefundLocktime, Deposit } from "../../src/deposit" import { BigNumber } from "ethers" import { Address } from "../../src/ethereum" +import { Hex } from "../../src" export const NO_MAIN_UTXO = { transactionHash: TransactionHash.from(""), @@ -406,41 +407,37 @@ export const depositSweepProof: DepositSweepProofTestData = { "ea4d9e45f8c1b8a187c007f36ba1e9b201e8511182c7083c4edcaf9325b2998f" ), outputIndex: 0, - scriptSig: { asm: "", hex: "" }, + scriptSig: Hex.from(""), }, { transactionHash: TransactionHash.from( "c844ff4c1781c884bb5e80392398b81b984d7106367ae16675f132bd1a7f33fd" ), outputIndex: 0, - scriptSig: { asm: "", hex: "" }, + scriptSig: Hex.from(""), }, { transactionHash: TransactionHash.from( "44c568bc0eac07a2a9c2b46829be5b5d46e7d00e17bfb613f506a75ccf86a473" ), outputIndex: 0, - scriptSig: { asm: "", hex: "" }, + scriptSig: Hex.from(""), }, { transactionHash: TransactionHash.from( "f548c00e464764e112826450a00cf005ca771a6108a629b559b6c60a519e4378" ), outputIndex: 0, - scriptSig: { asm: "", hex: "" }, + scriptSig: Hex.from(""), }, ], outputs: [ { outputIndex: 0, value: BigNumber.from(39800), - scriptPubKey: { - asm: "OP_0 8db50eb52063ea9d98b3eac91489a90f738986f6", - hex: "00148db50eb52063ea9d98b3eac91489a90f738986f6", - type: "WITNESSPUBKEYHASH", - reqSigs: 1, - addresses: ["tb1q3k6sadfqv04fmx9naty3fzdfpaecnphkfm3cf3"], - }, + scriptPubKey: Hex.from( + "00148db50eb52063ea9d98b3eac91489a90f738986f6" + ), }, ], }, diff --git a/typescript/test/data/electrum.ts b/typescript/test/data/electrum.ts index 7ec46b647..f17708bf4 100644 --- a/typescript/test/data/electrum.ts +++ b/typescript/test/data/electrum.ts @@ -6,6 +6,7 @@ import { TransactionHash, } from "../../src/bitcoin" import { BigNumber } from "ethers" +import { Hex } from "../../src" /** * Bitcoin testnet address used for Electrum client tests. @@ -27,10 +28,7 @@ export const testnetTransaction: Transaction = { "c6ffe9e0f8cca057acad211023ff6b9d46604fbbcb76c6dd669c20b22985f802" ), outputIndex: 1, - scriptSig: { - asm: "", - hex: "", - }, + scriptSig: Hex.from(""), }, ], @@ -38,24 +36,12 @@ export const testnetTransaction: Transaction = { { outputIndex: 0, value: BigNumber.from(101), - scriptPubKey: { - addresses: ["tb1qfdru0xx39mw30ha5a2vw23reymmxgucujfnc7l"], - asm: "OP_0 4b47c798d12edd17dfb4ea98e5447926f664731c", - hex: "00144b47c798d12edd17dfb4ea98e5447926f664731c", - reqSigs: 1, - type: "WITNESSPUBKEYHASH", - }, + scriptPubKey: Hex.from("00144b47c798d12edd17dfb4ea98e5447926f664731c"), }, { outputIndex: 1, value: BigNumber.from(9125), - scriptPubKey: { - addresses: ["tb1q78ezl08lyhuazzfz592sstenmegdns7durc4cl"], - asm: "OP_0 f1f22fbcff25f9d10922a155082f33de50d9c3cd", - hex: "0014f1f22fbcff25f9d10922a155082f33de50d9c3cd", - reqSigs: 1, - type: "WITNESSPUBKEYHASH", - }, + scriptPubKey: Hex.from("0014f1f22fbcff25f9d10922a155082f33de50d9c3cd"), }, ], } diff --git a/typescript/test/data/proof.ts b/typescript/test/data/proof.ts index 8c57afe0e..6540d709d 100644 --- a/typescript/test/data/proof.ts +++ b/typescript/test/data/proof.ts @@ -6,6 +6,7 @@ import { TransactionMerkleBranch, } from "../../src/bitcoin" import { BigNumber } from "ethers" +import { Hex } from "../../src" /** * Represents a set of data used for given proof scenario. @@ -40,23 +41,16 @@ export const singleInputProofTestData: ProofTestData = { "8ee67b585eeb682bf6907ea311282540ee53edf605e0f09757226a4dc3e72a67" ), outputIndex: 0, - scriptSig: { - asm: "", - hex: "", - }, + scriptSig: Hex.from(""), }, ], outputs: [ { outputIndex: 0, value: BigNumber.from(8400), - scriptPubKey: { - asm: "OP_0 8db50eb52063ea9d98b3eac91489a90f738986f6", - hex: "00148db50eb52063ea9d98b3eac91489a90f738986f6", - type: "WITNESSPUBKEYHASH", - reqSigs: 1, - addresses: ["tb1q3k6sadfqv04fmx9naty3fzdfpaecnphkfm3cf3"], - }, + scriptPubKey: Hex.from( + "00148db50eb52063ea9d98b3eac91489a90f738986f6" + ), }, ], }, @@ -117,23 +111,14 @@ export const singleInputProofTestData: ProofTestData = { "8ee67b585eeb682bf6907ea311282540ee53edf605e0f09757226a4dc3e72a67" ), outputIndex: 0, - scriptSig: { - asm: "", - hex: "", - }, + scriptSig: Hex.from(""), }, ], outputs: [ { outputIndex: 0, value: BigNumber.from(8400), - scriptPubKey: { - asm: "OP_0 8db50eb52063ea9d98b3eac91489a90f738986f6", - hex: "00148db50eb52063ea9d98b3eac91489a90f738986f6", - type: "WITNESSPUBKEYHASH", - reqSigs: 1, - addresses: ["tb1q3k6sadfqv04fmx9naty3fzdfpaecnphkfm3cf3"], - }, + scriptPubKey: Hex.from("00148db50eb52063ea9d98b3eac91489a90f738986f6"), }, ], merkleProof: @@ -184,41 +169,37 @@ export const multipleInputsProofTestData: ProofTestData = { "ea4d9e45f8c1b8a187c007f36ba1e9b201e8511182c7083c4edcaf9325b2998f" ), outputIndex: 0, - scriptSig: { asm: "", hex: "" }, + scriptSig: Hex.from(""), }, { transactionHash: TransactionHash.from( "c844ff4c1781c884bb5e80392398b81b984d7106367ae16675f132bd1a7f33fd" ), outputIndex: 0, - scriptSig: { asm: "", hex: "" }, + scriptSig: Hex.from(""), }, { transactionHash: TransactionHash.from( "44c568bc0eac07a2a9c2b46829be5b5d46e7d00e17bfb613f506a75ccf86a473" ), outputIndex: 0, - scriptSig: { asm: "", hex: "" }, + scriptSig: Hex.from(""), }, { transactionHash: TransactionHash.from( "f548c00e464764e112826450a00cf005ca771a6108a629b559b6c60a519e4378" ), outputIndex: 0, - scriptSig: { asm: "", hex: "" }, + scriptSig: Hex.from(""), }, ], outputs: [ { outputIndex: 0, value: BigNumber.from(39800), - scriptPubKey: { - asm: "OP_0 8db50eb52063ea9d98b3eac91489a90f738986f6", - hex: "00148db50eb52063ea9d98b3eac91489a90f738986f6", - type: "WITNESSPUBKEYHASH", - reqSigs: 1, - addresses: ["tb1q3k6sadfqv04fmx9naty3fzdfpaecnphkfm3cf3"], - }, + scriptPubKey: Hex.from( + "00148db50eb52063ea9d98b3eac91489a90f738986f6" + ), }, ], }, @@ -297,41 +278,35 @@ export const multipleInputsProofTestData: ProofTestData = { "ea4d9e45f8c1b8a187c007f36ba1e9b201e8511182c7083c4edcaf9325b2998f" ), outputIndex: 0, - scriptSig: { asm: "", hex: "" }, + scriptSig: Hex.from(""), }, { transactionHash: TransactionHash.from( "c844ff4c1781c884bb5e80392398b81b984d7106367ae16675f132bd1a7f33fd" ), outputIndex: 0, - scriptSig: { asm: "", hex: "" }, + scriptSig: Hex.from(""), }, { transactionHash: TransactionHash.from( "44c568bc0eac07a2a9c2b46829be5b5d46e7d00e17bfb613f506a75ccf86a473" ), outputIndex: 0, - scriptSig: { asm: "", hex: "" }, + scriptSig: Hex.from(""), }, { transactionHash: TransactionHash.from( "f548c00e464764e112826450a00cf005ca771a6108a629b559b6c60a519e4378" ), outputIndex: 0, - scriptSig: { asm: "", hex: "" }, + scriptSig: Hex.from(""), }, ], outputs: [ { outputIndex: 0, value: BigNumber.from(39800), - scriptPubKey: { - asm: "OP_0 8db50eb52063ea9d98b3eac91489a90f738986f6", - hex: "00148db50eb52063ea9d98b3eac91489a90f738986f6", - type: "WITNESSPUBKEYHASH", - reqSigs: 1, - addresses: ["tb1q3k6sadfqv04fmx9naty3fzdfpaecnphkfm3cf3"], - }, + scriptPubKey: Hex.from("00148db50eb52063ea9d98b3eac91489a90f738986f6"), }, ], merkleProof: diff --git a/typescript/test/data/redemption.ts b/typescript/test/data/redemption.ts index 9edb73c6f..8a361c746 100644 --- a/typescript/test/data/redemption.ts +++ b/typescript/test/data/redemption.ts @@ -10,6 +10,7 @@ import { } from "../../src/bitcoin" import { RedemptionRequest } from "../../src/redemption" import { Address } from "../../src/ethereum" +import { Hex } from "../../src" /** * Private key (testnet) of the wallet. @@ -527,66 +528,44 @@ export const redemptionProof: RedemptionProofTestData = { "3d28bb5bf73379da51bc683f4d0ed31d7b024466c619d80ebd9378077d900be3" ), outputIndex: 1, - scriptSig: { asm: "", hex: "" }, + scriptSig: Hex.from(""), }, ], outputs: [ { outputIndex: 0, value: BigNumber.from(15900), - scriptPubKey: { - asm: "OP_DUP OP_HASH160 4130879211c54df460e484ddf9aac009cb38ee74 OP_EQUALVERIFY OP_CHECKSIG", - hex: "76a9144130879211c54df460e484ddf9aac009cb38ee7488ac", - type: "PUBKEYHASH", - reqSigs: 1, - addresses: ["mmTeMR8RKu6QzMGTG4ipA71uewm3EuJng5"], - }, + scriptPubKey: Hex.from( + "76a9144130879211c54df460e484ddf9aac009cb38ee7488ac" + ), }, { outputIndex: 1, value: BigNumber.from(11300), - scriptPubKey: { - asm: "OP_0 4130879211c54df460e484ddf9aac009cb38ee74", - hex: "00144130879211c54df460e484ddf9aac009cb38ee74", - type: "WITNESSPUBKEYHASH", - reqSigs: 1, - addresses: ["tb1qgycg0ys3c4xlgc8ysnwln2kqp89n3mn5ts7z3l"], - }, + scriptPubKey: Hex.from( + "00144130879211c54df460e484ddf9aac009cb38ee74" + ), }, { outputIndex: 2, value: BigNumber.from(9900), - scriptPubKey: { - asm: "OP_HASH160 3ec459d0f3c29286ae5df5fcc421e2786024277e OP_EQUAL", - hex: "a9143ec459d0f3c29286ae5df5fcc421e2786024277e87", - type: "SCRIPTHASH", - reqSigs: 1, - addresses: ["2Mxy76sc1qAxiJ1fXMXDXqHvVcPLh6Lf12C"], - }, + scriptPubKey: Hex.from( + "a9143ec459d0f3c29286ae5df5fcc421e2786024277e87" + ), }, { outputIndex: 3, value: BigNumber.from(12900), - scriptPubKey: { - asm: "OP_0 86a303cdd2e2eab1d1679f1a813835dc5a1b65321077cdccaf08f98cbf04ca96", - hex: "002086a303cdd2e2eab1d1679f1a813835dc5a1b65321077cdccaf08f98cbf04ca96", - type: "WITNESSSCRIPTHASH", - reqSigs: 1, - addresses: [ - "tb1qs63s8nwjut4tr5t8nudgzwp4m3dpkefjzpmumn90pruce0cye2tq2jkq0y", - ], - }, + scriptPubKey: Hex.from( + "002086a303cdd2e2eab1d1679f1a813835dc5a1b65321077cdccaf08f98cbf04ca96" + ), }, { outputIndex: 4, value: BigNumber.from(1375180), - scriptPubKey: { - asm: "OP_0 8db50eb52063ea9d98b3eac91489a90f738986f6", - hex: "00148db50eb52063ea9d98b3eac91489a90f738986f6", - type: "WITNESSPUBKEYHASH", - reqSigs: 1, - addresses: ["tb1q3k6sadfqv04fmx9naty3fzdfpaecnphkfm3cf3"], - }, + scriptPubKey: Hex.from( + "00148db50eb52063ea9d98b3eac91489a90f738986f6" + ), }, ], }, diff --git a/typescript/test/electrum.test.ts b/typescript/test/electrum.test.ts index 59625486e..a0aee36c4 100644 --- a/typescript/test/electrum.test.ts +++ b/typescript/test/electrum.test.ts @@ -11,12 +11,38 @@ import { testnetUTXO, } from "./data/electrum" import { expect } from "chai" +import https from "https" -const testnetCredentials: ElectrumCredentials = { - host: "electrumx-server.test.tbtc.network", - port: 8443, - protocol: "wss", -} +const BLOCKSTREAM_TESTNET_API_URL = "https://blockstream.info/testnet/api" + +const testnetCredentials: ElectrumCredentials[] = [ + // electrumx wss + { + host: "electrumx-server.test.tbtc.network", + port: 8443, + protocol: "wss", + }, + // TODO: Enable after retries are implemented + // See: https://github.com/keep-network/tbtc-v2/issues/485 + // // electrs-esplora tcp + // { + // host: "electrum.blockstream.info", + // port: 60001, + // protocol: "tcp", + // }, + // // electrs-esplora ssl + // { + // host: "electrum.blockstream.info", + // port: 60002, + // protocol: "ssl", + // }, + // fulcrum tcp + { + host: "testnet.aranguren.org", + port: 51001, + protocol: "tcp", + }, +] /** * This test suite is meant to check the behavior of the Electrum-based @@ -30,77 +56,137 @@ const testnetCredentials: ElectrumCredentials = { * out of scope of this suite. The `broadcast` function was tested manually * though. */ -describe.skip("Electrum", () => { - let electrumClient: ElectrumClient +describe("Electrum", () => { + testnetCredentials.forEach((credentials) => { + describe(`${credentials.protocol}://${credentials.host}:${credentials.port}`, async () => { + let electrumClient: ElectrumClient - before(async () => { - electrumClient = new ElectrumClient(testnetCredentials) - }) + before(async () => { + electrumClient = new ElectrumClient(credentials) + }) - describe("findAllUnspentTransactionOutputs", () => { - it("should return proper UTXOs for the given address", async () => { - const result = await electrumClient.findAllUnspentTransactionOutputs( - testnetAddress - ) - expect(result).to.be.eql([testnetUTXO]) - }) - }) + describe("findAllUnspentTransactionOutputs", () => { + it("should return proper UTXOs for the given address", async () => { + const result = await electrumClient.findAllUnspentTransactionOutputs( + testnetAddress + ) + expect(result).to.be.eql([testnetUTXO]) + }) + }) - describe("getTransaction", () => { - it("should return proper transaction for the given hash", async () => { - const result = await electrumClient.getTransaction( - testnetTransaction.transactionHash - ) - expect(result).to.be.eql(testnetTransaction) - }) - }) + describe("getTransaction", () => { + it("should return proper transaction for the given hash", async () => { + const result = await electrumClient.getTransaction( + testnetTransaction.transactionHash + ) + expect(result).to.be.eql(testnetTransaction) + }) + // TODO: Add case when transaction doesn't exist + }) - describe("getRawTransaction", () => { - it("should return proper raw transaction for the given hash", async () => { - const result = await electrumClient.getRawTransaction( - testnetTransaction.transactionHash - ) - expect(result).to.be.eql(testnetRawTransaction) - }) - }) + describe("getRawTransaction", () => { + it("should return proper raw transaction for the given hash", async () => { + const result = await electrumClient.getRawTransaction( + testnetTransaction.transactionHash + ) + expect(result).to.be.eql(testnetRawTransaction) + }) + }) - describe("getTransactionConfirmations", () => { - it("should return proper confirmations number for the given hash", async () => { - const result = await electrumClient.getTransactionConfirmations( - testnetTransaction.transactionHash - ) - // Strict comparison is not possible as the number of confirmations - // constantly grows. We just make sure it's 6+. - expect(result).to.be.greaterThan(6) - }) - }) + describe("getTransactionConfirmations", () => { + let result: number - describe("latestBlockHeight", () => { - it("should return proper latest block height", async () => { - const result = await electrumClient.latestBlockHeight() - // Strict comparison is not possible as the latest block height - // constantly grows. We just make sure it's bigger than 0. - expect(result).to.be.greaterThan(0) - }) - }) + before(async () => { + result = await electrumClient.getTransactionConfirmations( + testnetTransaction.transactionHash + ) + }) - describe("getHeadersChain", () => { - it("should return proper headers chain", async () => { - const result = await electrumClient.getHeadersChain( - testnetHeadersChain.blockHeight, - testnetHeadersChain.headersChainLength - ) - expect(result).to.be.eql(testnetHeadersChain.headersChain) - }) - }) + it("should return value greater than 6", async () => { + // Strict comparison is not possible as the number of confirmations + // constantly grows. We just make sure it's 6+. + expect(result).to.be.greaterThan(6) + }) + + // This test depends on `latestBlockHeight` function. + it("should return proper confirmations number for the given hash", async () => { + const latestBlockHeight = await electrumClient.latestBlockHeight() - describe("getTransactionMerkle", () => { - it("should return proper transaction merkle", async () => { - const result = await electrumClient.getTransactionMerkle( - testnetTransaction.transactionHash, - testnetTransactionMerkleBranch.blockHeight - ) - expect(result).to.be.eql(testnetTransactionMerkleBranch) + const expectedResult = + latestBlockHeight - testnetTransactionMerkleBranch.blockHeight + + expect(result).to.be.closeTo(expectedResult, 3) + }) + }) + + describe("latestBlockHeight", () => { + let result: number + + before(async () => { + result = await electrumClient.latestBlockHeight() + }) + + it("should return value greater than 6", async () => { + // Strict comparison is not possible as the latest block height + // constantly grows. We just make sure it's bigger than 0. + expect(result).to.be.greaterThan(0) + }) + + // This test depends on fetching the expected latest block height from Blockstream API. + // It can fail if Blockstream API is down or if Blockstream API or if + // Electrum Server used in tests is out-of-sync with the Blockstream API. + it("should return proper latest block height", async () => { + const expectedResult = await getExpectedLatestBlockHeight() + + expect(result).to.be.closeTo(expectedResult, 3) + }) + }) + + describe("getHeadersChain", () => { + it("should return proper headers chain", async () => { + const result = await electrumClient.getHeadersChain( + testnetHeadersChain.blockHeight, + testnetHeadersChain.headersChainLength + ) + expect(result).to.be.eql(testnetHeadersChain.headersChain) + }) + }) + + describe("getTransactionMerkle", () => { + it("should return proper transaction merkle", async () => { + const result = await electrumClient.getTransactionMerkle( + testnetTransaction.transactionHash, + testnetTransactionMerkleBranch.blockHeight + ) + expect(result).to.be.eql(testnetTransactionMerkleBranch) + }) + }) }) }) }) + +/** + * Gets the height of the last block fetched from the Blockstream API. + * @returns Height of the last block. + */ +function getExpectedLatestBlockHeight(): Promise { + return new Promise((resolve, reject) => { + https + .get(`${BLOCKSTREAM_TESTNET_API_URL}/blocks/tip/height`, (resp) => { + let data = "" + + // A chunk of data has been received. + resp.on("data", (chunk) => { + data += chunk + }) + + // The whole response has been received. Print out the result. + resp.on("end", () => { + resolve(JSON.parse(data)) + }) + }) + .on("error", (err) => { + reject(err) + }) + }) +}