diff --git a/typescript/test/data/electrum.ts b/typescript/test/data/electrum.ts index f17708bf4..9bcc3bb1a 100644 --- a/typescript/test/data/electrum.ts +++ b/typescript/test/data/electrum.ts @@ -9,106 +9,121 @@ import { BigNumber } from "ethers" import { Hex } from "../../src" /** - * Bitcoin testnet address used for Electrum client tests. + * Bitcoin testnet test data. */ -export const testnetAddress: string = - "tb1qfdru0xx39mw30ha5a2vw23reymmxgucujfnc7l" +export namespace TestnetTestData { + /** + * Bitcoin testnet address used for Electrum client tests. + */ + export const address: string = "tb1qfdru0xx39mw30ha5a2vw23reymmxgucujfnc7l" -/** - * A testnet transaction originating from {@link testnetAddress} - */ -export const testnetTransaction: Transaction = { - transactionHash: TransactionHash.from( - "72e7fd57c2adb1ed2305c4247486ff79aec363296f02ec65be141904f80d214e" - ), + /** + * A testnet transaction originating from {@link testnetAddress} + */ + export const transaction: Transaction = { + transactionHash: TransactionHash.from( + "72e7fd57c2adb1ed2305c4247486ff79aec363296f02ec65be141904f80d214e" + ), - inputs: [ - { - transactionHash: TransactionHash.from( - "c6ffe9e0f8cca057acad211023ff6b9d46604fbbcb76c6dd669c20b22985f802" - ), - outputIndex: 1, - scriptSig: Hex.from(""), - }, - ], + inputs: [ + { + transactionHash: TransactionHash.from( + "c6ffe9e0f8cca057acad211023ff6b9d46604fbbcb76c6dd669c20b22985f802" + ), + outputIndex: 1, + scriptSig: Hex.from(""), + }, + ], - outputs: [ - { - outputIndex: 0, - value: BigNumber.from(101), - scriptPubKey: Hex.from("00144b47c798d12edd17dfb4ea98e5447926f664731c"), - }, - { - outputIndex: 1, - value: BigNumber.from(9125), - scriptPubKey: Hex.from("0014f1f22fbcff25f9d10922a155082f33de50d9c3cd"), - }, - ], -} + outputs: [ + { + outputIndex: 0, + value: BigNumber.from(101), + scriptPubKey: Hex.from("00144b47c798d12edd17dfb4ea98e5447926f664731c"), + }, + { + outputIndex: 1, + value: BigNumber.from(9125), + scriptPubKey: Hex.from("0014f1f22fbcff25f9d10922a155082f33de50d9c3cd"), + }, + ], + } -/** - * Raw transaction corresponding to {@link testnetTransaction} - */ -export const testnetRawTransaction: RawTransaction = { - transactionHex: - "0200000000010102f88529b2209c66ddc676cbbb4f60469d6bff231021adac57a0ccf8e" + - "0e9ffc60100000000fdffffff0265000000000000001600144b47c798d12edd17dfb4ea" + - "98e5447926f664731ca523000000000000160014f1f22fbcff25f9d10922a155082f33d" + - "e50d9c3cd0247304402205b284e74ffd399c1cf5446082a68025a6bf9a3e49e94177de4" + - "4e6d8b4cd6d6a602205eea19d0302e41b8bbac9427e30e88e23875bd47be65572da374d" + - "f7fbdd479220121032f5cc5c735e61a51119a907a0cbece5077c7e1362e322ceb9fbf75" + - "ffb9adb5283df21700", -} + /** + * Raw transaction corresponding to {@link transaction} + */ + export const rawTransaction: RawTransaction = { + transactionHex: + "0200000000010102f88529b2209c66ddc676cbbb4f60469d6bff231021adac57a0ccf8e" + + "0e9ffc60100000000fdffffff0265000000000000001600144b47c798d12edd17dfb4ea" + + "98e5447926f664731ca523000000000000160014f1f22fbcff25f9d10922a155082f33d" + + "e50d9c3cd0247304402205b284e74ffd399c1cf5446082a68025a6bf9a3e49e94177de4" + + "4e6d8b4cd6d6a602205eea19d0302e41b8bbac9427e30e88e23875bd47be65572da374d" + + "f7fbdd479220121032f5cc5c735e61a51119a907a0cbece5077c7e1362e322ceb9fbf75" + + "ffb9adb5283df21700", + } -/** - * An UTXO being result of {@link testnetTransaction} - */ -export const testnetUTXO: UnspentTransactionOutput = { - transactionHash: testnetTransaction.transactionHash, - outputIndex: 0, - value: BigNumber.from(101), -} + /** + * An UTXO being result of {@link transaction} + */ + export const UTXO: UnspentTransactionOutput = { + transactionHash: transaction.transactionHash, + outputIndex: 0, + value: BigNumber.from(101), + } -/** - * Testnet headers chain used for Electrum client tests. - */ -export const testnetHeadersChain = { - blockHeight: 1569342, - headersChainLength: 6, - headersChain: - "00000020a114bf2d1e930390044cc4e00dd2f490a36dcecb4b6bb702b50200000000000" + - "0583b7a45472123fac1003384cc60fce2129c8d7364969dfa35021ab26c0b0449bccc2e" + - "5dffff001d061013a10000ff3f3210404a744c3170cdd6ad7fc901194c913004faa87f2" + - "91cd3faac2b00000000ecc1620341eeee4881423cab631e6e4a0b003c05ffc2dfc132a2" + - "a902a45df2c573d02e5d148c031af7358d5f00e0ff3fd83c1e679a4766043e3dbc62287" + - "0e64ba4c2cacfa2f45563210100000000000071d244c45daecf0abf15c5f4e47f123109" + - "12918ca56b89c3dfb68103371ae6bf98d42e5d148c031aca5d525e00000020d2a6ad530" + - "4a5bbe4948666fd6775dc2cde9c0cef7060a471fe01000000000000597701d1165c140f" + - "471f2684f1f6b3e97765ee5492619582af5e6d192895e7d34cd92e5dffff001dbb34c42" + - "400000020b9b3fcbb515c899b10bf3889d432ca2782cfad01f9c2cf329fb60e00000000" + - "0048d580fbe9ccf1cadaffe0e780eab57ea401f6260f38bd459d32cc3eef6cbd33ffd92" + - "e5d148c031a3c4277f40000002024af4d64067c20a1ed5cb9fbd432a98fe659e3653378" + - "e6b9ed00000000000000fea85a41c80b307f9cdfd22ac52521ba89ea6467769206d8988" + - "9663cb7742e7358db2e5d148c031a7d30031a0000ff3f6418122efc0ddf2416189b01c0" + - "d98ab7e5072fe1e99c3e275401000000000000496c06f87b8d442db7c6bd36ff05e3a7a" + - "0edb3e0124d26c61d44c584ba1f8ff86bdc2e5d148c031a411e00ae", + /** + * Testnet headers chain used for Electrum client tests. + */ + export const headersChain = { + blockHeight: 1569342, + headersChainLength: 6, + headersChain: + "00000020a114bf2d1e930390044cc4e00dd2f490a36dcecb4b6bb702b50200000000000" + + "0583b7a45472123fac1003384cc60fce2129c8d7364969dfa35021ab26c0b0449bccc2e" + + "5dffff001d061013a10000ff3f3210404a744c3170cdd6ad7fc901194c913004faa87f2" + + "91cd3faac2b00000000ecc1620341eeee4881423cab631e6e4a0b003c05ffc2dfc132a2" + + "a902a45df2c573d02e5d148c031af7358d5f00e0ff3fd83c1e679a4766043e3dbc62287" + + "0e64ba4c2cacfa2f45563210100000000000071d244c45daecf0abf15c5f4e47f123109" + + "12918ca56b89c3dfb68103371ae6bf98d42e5d148c031aca5d525e00000020d2a6ad530" + + "4a5bbe4948666fd6775dc2cde9c0cef7060a471fe01000000000000597701d1165c140f" + + "471f2684f1f6b3e97765ee5492619582af5e6d192895e7d34cd92e5dffff001dbb34c42" + + "400000020b9b3fcbb515c899b10bf3889d432ca2782cfad01f9c2cf329fb60e00000000" + + "0048d580fbe9ccf1cadaffe0e780eab57ea401f6260f38bd459d32cc3eef6cbd33ffd92" + + "e5d148c031a3c4277f40000002024af4d64067c20a1ed5cb9fbd432a98fe659e3653378" + + "e6b9ed00000000000000fea85a41c80b307f9cdfd22ac52521ba89ea6467769206d8988" + + "9663cb7742e7358db2e5d148c031a7d30031a0000ff3f6418122efc0ddf2416189b01c0" + + "d98ab7e5072fe1e99c3e275401000000000000496c06f87b8d442db7c6bd36ff05e3a7a" + + "0edb3e0124d26c61d44c584ba1f8ff86bdc2e5d148c031a411e00ae", + } + + /** + * Transaction merkle branch corresponding to {@link transaction} + */ + export const transactionMerkleBranch: TransactionMerkleBranch = { + blockHeight: 1569342, + merkle: [ + "8b5bbb5bdf6727bf70fad4f46fe4eaab04c98119ffbd2d95c29adf32d26f8452", + "53637bacb07965e4a8220836861d1b16c6da29f10ea9ab53fc4eca73074f98b9", + "0267e738108d094ceb05217e2942e9c2a4c6389ac47f476f572c9a319ce4dfbc", + "34e00deec50c48d99678ca2b52b82d6d5432326159c69e7233d0dde0924874b4", + "7a53435e6c86a3620cdbae510901f17958f0540314214379197874ed8ed7a913", + "6315dbb7ce350ceaa16cd4c35c5a147005e8b38ca1e9531bd7320629e8d17f5b", + "40380cdadc0206646208871e952af9dcfdff2f104305ce463aed5eeaf7725d2f", + "5d74bae6a71fd1cff2416865460583319a40343650bd4bb89de0a6ae82097037", + "296ddccfc659e0009aad117c8ed15fb6ff81c2bade73fbc89666a22708d233f9", + ], + position: 176, + } } /** - * Transaction merkle branch corresponding to {@link testnetTransaction} + * Bitcoin mainnet test data. */ -export const testnetTransactionMerkleBranch: TransactionMerkleBranch = { - blockHeight: 1569342, - merkle: [ - "8b5bbb5bdf6727bf70fad4f46fe4eaab04c98119ffbd2d95c29adf32d26f8452", - "53637bacb07965e4a8220836861d1b16c6da29f10ea9ab53fc4eca73074f98b9", - "0267e738108d094ceb05217e2942e9c2a4c6389ac47f476f572c9a319ce4dfbc", - "34e00deec50c48d99678ca2b52b82d6d5432326159c69e7233d0dde0924874b4", - "7a53435e6c86a3620cdbae510901f17958f0540314214379197874ed8ed7a913", - "6315dbb7ce350ceaa16cd4c35c5a147005e8b38ca1e9531bd7320629e8d17f5b", - "40380cdadc0206646208871e952af9dcfdff2f104305ce463aed5eeaf7725d2f", - "5d74bae6a71fd1cff2416865460583319a40343650bd4bb89de0a6ae82097037", - "296ddccfc659e0009aad117c8ed15fb6ff81c2bade73fbc89666a22708d233f9", - ], - position: 176, +export namespace MainnetTestData { + // TODO: Provide actual mainnet test data of TBTCv2 funding transaction. + /** + * Bitcoin mainnet address used for Electrum client tests. + */ + export const address: string = "placeholder" } diff --git a/typescript/test/electrum.test.ts b/typescript/test/electrum.test.ts index 47cbdd7b9..522a5680e 100644 --- a/typescript/test/electrum.test.ts +++ b/typescript/test/electrum.test.ts @@ -3,20 +3,23 @@ import { Client as ElectrumClient, } from "../src/electrum" import { BitcoinNetwork } from "../src/bitcoin/network" -import { - testnetAddress, - testnetHeadersChain, - testnetRawTransaction, - testnetTransaction, - testnetTransactionMerkleBranch, - testnetUTXO, -} from "./data/electrum" +import { TestnetTestData, MainnetTestData } from "./data/electrum" import { expect } from "chai" import https from "https" -const BLOCKSTREAM_TESTNET_API_URL = "https://blockstream.info/testnet/api" +const electrumTestData = new Map([ + [BitcoinNetwork.Testnet.toString(), TestnetTestData], + [BitcoinNetwork.Mainnet.toString(), MainnetTestData], +]) + +const blockstreamApiUrl = new Map([ + [BitcoinNetwork.Testnet.toString(), "https://blockstream.info/testnet/api"], + [BitcoinNetwork.Mainnet.toString(), "https://blockstream.info/api"], +]) + +type TestElectrumCredentials = ElectrumCredentials & { network: BitcoinNetwork } -const testnetCredentials: ElectrumCredentials[] = [ +const servers: TestElectrumCredentials[] = [ // TODO: Enable all protocols test for test.tbtc.network servers once they are // publicly exposed. // // electrumx tcp @@ -42,12 +45,14 @@ const testnetCredentials: ElectrumCredentials[] = [ host: "electrumx-server.test.tbtc.network", port: 8443, protocol: "wss", + network: BitcoinNetwork.Testnet, }, // electrs-esplora tcp { host: "electrum.blockstream.info", port: 60001, protocol: "tcp", + network: BitcoinNetwork.Testnet, }, // FIXME: https://github.com/keep-network/tbtc-v2/issues/502 // // electrs-esplora ssl @@ -61,6 +66,7 @@ const testnetCredentials: ElectrumCredentials[] = [ host: "testnet.aranguren.org", port: 51001, protocol: "tcp", + network: BitcoinNetwork.Testnet, }, // FIXME: https://github.com/keep-network/tbtc-v2/issues/502 // fulcrum ssl @@ -69,6 +75,22 @@ const testnetCredentials: ElectrumCredentials[] = [ // port: 51002, // protocol: "ssl", // }, + { + host: "electrumx-server.tbtc.network", + port: 8443, + protocol: "wss", + network: BitcoinNetwork.Mainnet, + }, + // electrs-esplora tcp + { + host: "electrum.blockstream.info", + port: 50001, + protocol: "tcp", + network: BitcoinNetwork.Mainnet, + }, + // TODO: Add more servers + // TODO: Consider extracting communication with real servers to integration tests suite + // and mock electrum server. ] /** @@ -83,142 +105,160 @@ const testnetCredentials: ElectrumCredentials[] = [ * out of scope of this suite. The `broadcast` function was tested manually * though. */ -describe("Electrum", () => { - testnetCredentials.forEach((credentials) => { - describe(`${credentials.protocol}://${credentials.host}:${credentials.port}`, async () => { - let electrumClient: ElectrumClient +describe.only("Electrum", () => { + servers.forEach((server) => { + context( + `${server.network}: ${server.protocol}://${server.host}:${server.port}`, + async () => { + // Skip some tests for mainnet until test data are provided. + const describeSkipMainnet = + server.network === BitcoinNetwork.Mainnet ? describe.skip : describe - before(async () => { - electrumClient = new ElectrumClient(credentials) - }) + let testData: any + let electrumClient: ElectrumClient - describe("getNetwork", () => { - it("should return proper network", async () => { - const result = await electrumClient.getNetwork() - expect(result).to.be.eql(BitcoinNetwork.Testnet) + before(async () => { + testData = electrumTestData.get(server.network.toString()) + electrumClient = new ElectrumClient(server) }) - }) - 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("getNetwork", () => { + it("should return proper network", async () => { + const result = await electrumClient.getNetwork() + expect(result).to.be.eql(server.network) + }) }) - }) - 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) + describeSkipMainnet("findAllUnspentTransactionOutputs", () => { + it("should return proper UTXOs for the given address", async () => { + const result = + await electrumClient.findAllUnspentTransactionOutputs( + testData.address + ) + expect(result).to.be.eql([testData.UTXO]) + }) }) - // 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) + describeSkipMainnet("getTransaction", () => { + it("should return proper transaction for the given hash", async () => { + const result = await electrumClient.getTransaction( + testData.transaction.transactionHash + ) + expect(result).to.be.eql(testData.transaction) + }) + // TODO: Add case when transaction doesn't exist }) - }) - - describe("getTransactionConfirmations", () => { - let result: number - before(async () => { - result = await electrumClient.getTransactionConfirmations( - testnetTransaction.transactionHash - ) + describeSkipMainnet("getRawTransaction", () => { + it("should return proper raw transaction for the given hash", async () => { + const result = await electrumClient.getRawTransaction( + testData.transaction.transactionHash + ) + expect(result).to.be.eql(testData.rawTransaction) + }) }) - 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) - }) + describeSkipMainnet("getTransactionConfirmations", () => { + let result: number - // This test depends on `latestBlockHeight` function. - it("should return proper confirmations number for the given hash", async () => { - const latestBlockHeight = await electrumClient.latestBlockHeight() + before(async () => { + result = await electrumClient.getTransactionConfirmations( + testData.transaction.transactionHash + ) + }) - const expectedResult = - latestBlockHeight - testnetTransactionMerkleBranch.blockHeight + 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) + }) - expect(result).to.be.closeTo(expectedResult, 3) - }) - }) + // This test depends on `latestBlockHeight` function. + it("should return proper confirmations number for the given hash", async () => { + const latestBlockHeight = await electrumClient.latestBlockHeight() - describe("latestBlockHeight", () => { - let result: number + const expectedResult = + latestBlockHeight - testData.transactionMerkleBranch.blockHeight - before(async () => { - result = await electrumClient.latestBlockHeight() + expect(result).to.be.closeTo(expectedResult, 3) + }) }) - 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) - }) + describe("latestBlockHeight", () => { + let result: number - // 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() + before(async () => { + result = await electrumClient.latestBlockHeight() + }) - expect(result).to.be.closeTo(expectedResult, 3) + 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( + server.network + ) + + 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) + describeSkipMainnet("getHeadersChain", () => { + it("should return proper headers chain", async () => { + const result = await electrumClient.getHeadersChain( + testData.headersChain.blockHeight, + testData.headersChain.headersChainLength + ) + expect(result).to.be.eql(testData.headersChain.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) + describeSkipMainnet("getTransactionMerkle", () => { + it("should return proper transaction merkle", async () => { + const result = await electrumClient.getTransactionMerkle( + testData.transaction.transactionHash, + testData.transactionMerkleBranch.blockHeight + ) + expect(result).to.be.eql(testData.transactionMerkleBranch) + }) }) - }) - }) + } + ) }) }) /** * Gets the height of the last block fetched from the Blockstream API. + * @param network Bitcoin network. * @returns Height of the last block. */ -function getExpectedLatestBlockHeight(): Promise { +function getExpectedLatestBlockHeight( + network: BitcoinNetwork +): Promise { return new Promise((resolve, reject) => { https - .get(`${BLOCKSTREAM_TESTNET_API_URL}/blocks/tip/height`, (resp) => { - let data = "" + .get( + `${blockstreamApiUrl.get(network.toString())}/blocks/tip/height`, + (resp) => { + let data = "" - // A chunk of data has been received. - resp.on("data", (chunk) => { - data += chunk - }) + // 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)) - }) - }) + // The whole response has been received. Print out the result. + resp.on("end", () => { + resolve(JSON.parse(data)) + }) + } + ) .on("error", (err) => { reject(err) })