From 0b1cf57852e17e44f1fe02a68d0db48c514a2b72 Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Wed, 19 Jan 2022 12:20:08 -0800 Subject: [PATCH 1/2] fix(integration): Updating integration tests --- src/psf-slp-indexer.js | 3 +- src/utxo.js | 4 - .../chains/bchn/utxo-integration.js | 211 +++++++++--------- 3 files changed, 110 insertions(+), 108 deletions(-) diff --git a/src/psf-slp-indexer.js b/src/psf-slp-indexer.js index ac89dbc..872d99b 100644 --- a/src/psf-slp-indexer.js +++ b/src/psf-slp-indexer.js @@ -132,7 +132,8 @@ class PsfSlpIndexer { */ async balance (address) { try { - console.log('balance() address: ', address) + // console.log('balance() address: ', address) + // Handle single address. if (typeof address === 'string') { const response = await axios.post( diff --git a/src/utxo.js b/src/utxo.js index 8d1332d..500cc29 100644 --- a/src/utxo.js +++ b/src/utxo.js @@ -311,10 +311,6 @@ class UTXO { * - groupTokens: [] - NFT Group tokens, used to create NFT tokens. * - groupMintBatons: [] - Minting baton to create more NFT Group tokens. * - * Note: You can pass in an optional second Boolean argument. The default - * `false` will use the normal waterfall validation method. Set to `true`, - * SLP UTXOs will be validated with the whitelist filtered SLPDB. This will - * result is many more UTXOs in the `nullUtxos` array. * * @apiExample Example usage: * (async () => { diff --git a/test/integration/chains/bchn/utxo-integration.js b/test/integration/chains/bchn/utxo-integration.js index 65e9e32..f1dc876 100644 --- a/test/integration/chains/bchn/utxo-integration.js +++ b/test/integration/chains/bchn/utxo-integration.js @@ -13,84 +13,113 @@ describe('#UTXO', () => { if (process.env.IS_USING_FREE_TIER) await sleep(3000) }) - /* - describe('#get', () => { - it('should get hydrated and filtered UTXOs for an address', async () => { - // const addr = 'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9' - const addr = 'simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9' - const result = await bchjs.Utxo.get(addr) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) + if (process.env.TESTSLP) { + describe('#getOld', () => { + it('should get hydrated and filtered UTXOs for an address', async () => { + // const addr = 'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9' + const addr = 'simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9' - assert.isArray(result) - assert.property(result[0], 'address') - assert.property(result[0], 'bchUtxos') - assert.property(result[0], 'nullUtxos') - assert.property(result[0], 'slpUtxos') - assert.isArray(result[0].bchUtxos) - assert.isArray(result[0].nullUtxos) + const result = await bchjs.Utxo.getOld(addr) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isArray(result) + assert.property(result[0], 'address') + assert.property(result[0], 'bchUtxos') + assert.property(result[0], 'nullUtxos') + assert.property(result[0], 'slpUtxos') + assert.isArray(result[0].bchUtxos) + assert.isArray(result[0].nullUtxos) + }) + + it('should handle an array of addresses', async () => { + const addr = [ + 'simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9', + 'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9' + ] + + const result = await bchjs.Utxo.getOld(addr) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isArray(result) + assert.property(result[0], 'address') + assert.property(result[0], 'bchUtxos') + assert.property(result[0], 'nullUtxos') + assert.property(result[0], 'slpUtxos') + assert.isArray(result[0].bchUtxos) + assert.isArray(result[0].nullUtxos) + }) + + it('should handle NFTs and minting batons', async () => { + const addr = 'simpleledger:qrm0c67wwqh0w7wjxua2gdt2xggnm90xwsr5k22euj' + + const result = await bchjs.Utxo.getOld(addr) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isArray(result) + assert.property(result[0], 'address') + assert.property(result[0], 'bchUtxos') + assert.property(result[0], 'nullUtxos') + assert.property(result[0], 'slpUtxos') + assert.isArray(result[0].bchUtxos) + assert.isArray(result[0].nullUtxos) + + assert.isArray(result[0].slpUtxos.type1.mintBatons) + assert.isArray(result[0].slpUtxos.type1.tokens) + assert.isArray(result[0].slpUtxos.nft.groupMintBatons) + assert.isArray(result[0].slpUtxos.nft.groupTokens) + assert.isArray(result[0].slpUtxos.nft.tokens) + }) + + it('should use the whitelist when flag is set', async () => { + const addr = 'simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9' + const useWhitelist = true + + const result = await bchjs.Utxo.getOld(addr, useWhitelist) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isArray(result) + assert.property(result[0], 'address') + assert.property(result[0], 'bchUtxos') + assert.property(result[0], 'nullUtxos') + assert.property(result[0], 'slpUtxos') + assert.isArray(result[0].bchUtxos) + assert.isArray(result[0].nullUtxos) + + // Most token UTXOs should end up in the nullUtxos array. + assert.isAbove(result[0].bchUtxos.length, 0) + assert.isAbove(result[0].nullUtxos.length, 1) + }) }) - it('should handle an array of addresses', async () => { - const addr = [ - 'simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9', - 'bitcoincash:qqh793x9au6ehvh7r2zflzguanlme760wuzehgzjh9' - ] + describe('#findBiggestUtxo', () => { + it('should sort UTXOs from Electrumx', async () => { + const addr = 'bitcoincash:qq54fgjn3hz0357n8a6guy4demw9xfkjk5jcj0xr0z' - const result = await bchjs.Utxo.get(addr) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) + const electrumxUtxos = await bchjs.Electrumx.utxo(addr) + // console.log(`Electrumx utxos: ${JSON.stringify(electrumxUtxos, null, 2)}`) - assert.isArray(result) - assert.property(result[0], 'address') - assert.property(result[0], 'bchUtxos') - assert.property(result[0], 'nullUtxos') - assert.property(result[0], 'slpUtxos') - assert.isArray(result[0].bchUtxos) - assert.isArray(result[0].nullUtxos) + const result = bchjs.Utxo.findBiggestUtxo(electrumxUtxos.utxos) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, 'satoshis') + assert.equal(result.satoshis, 800) + }) + + it('should sort UTXOs from Utxos.get()', async () => { + const addr = 'bitcoincash:qq54fgjn3hz0357n8a6guy4demw9xfkjk5jcj0xr0z' + + const utxos = await bchjs.Utxo.getOld(addr) + // console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`) + + const result = bchjs.Utxo.findBiggestUtxo(utxos[0].bchUtxos) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.property(result, 'satoshis') + assert.equal(result.satoshis, 800) + }) }) - - it('should handle NFTs and minting batons', async () => { - const addr = 'simpleledger:qrm0c67wwqh0w7wjxua2gdt2xggnm90xwsr5k22euj' - - const result = await bchjs.Utxo.get(addr) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isArray(result) - assert.property(result[0], 'address') - assert.property(result[0], 'bchUtxos') - assert.property(result[0], 'nullUtxos') - assert.property(result[0], 'slpUtxos') - assert.isArray(result[0].bchUtxos) - assert.isArray(result[0].nullUtxos) - - assert.isArray(result[0].slpUtxos.type1.mintBatons) - assert.isArray(result[0].slpUtxos.type1.tokens) - assert.isArray(result[0].slpUtxos.nft.groupMintBatons) - assert.isArray(result[0].slpUtxos.nft.groupTokens) - assert.isArray(result[0].slpUtxos.nft.tokens) - }) - - it('should use the whitelist when flag is set', async () => { - const addr = 'simpleledger:qzv3zz2trz0xgp6a96lu4m6vp2nkwag0kvyucjzqt9' - const useWhitelist = true - - const result = await bchjs.Utxo.get(addr, useWhitelist) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.isArray(result) - assert.property(result[0], 'address') - assert.property(result[0], 'bchUtxos') - assert.property(result[0], 'nullUtxos') - assert.property(result[0], 'slpUtxos') - assert.isArray(result[0].bchUtxos) - assert.isArray(result[0].nullUtxos) - - // Most token UTXOs should end up in the nullUtxos array. - assert.isAbove(result[0].bchUtxos.length, 0) - assert.isAbove(result[0].nullUtxos.length, 1) - }) - }) -*/ + } describe('#hydrateTokenData', () => { it('should hydrate token UTXOs', async () => { @@ -138,7 +167,13 @@ describe('#UTXO', () => { ] const result = await bchjs.Utxo.hydrateTokenData(utxos) - console.log('result: ', result) + // console.log('result: ', result) + + assert.property(result[0], 'ticker') + assert.property(result[0], 'name') + assert.property(result[0], 'qtyStr') + assert.property(result[0], 'documentUri') + assert.property(result[0], 'documentHash') }) }) @@ -168,42 +203,12 @@ describe('#UTXO', () => { const addr = 'simpleledger:qrm0c67wwqh0w7wjxua2gdt2xggnm90xwsr5k22euj' const result = await bchjs.Utxo.get(addr) - console.log(`result: ${JSON.stringify(result, null, 2)}`) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) // Assert that minting batons are correctly identified. assert.isAbove(result.slpUtxos.type1.mintBatons.length, 0) }) }) - - /* - describe('#findBiggestUtxo', () => { - it('should sort UTXOs from Electrumx', async () => { - const addr = 'bitcoincash:qq54fgjn3hz0357n8a6guy4demw9xfkjk5jcj0xr0z' - - const electrumxUtxos = await bchjs.Electrumx.utxo(addr) - // console.log(`Electrumx utxos: ${JSON.stringify(electrumxUtxos, null, 2)}`) - - const result = bchjs.Utxo.findBiggestUtxo(electrumxUtxos.utxos) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'satoshis') - assert.equal(result.satoshis, 800) - }) - - it('should sort UTXOs from Utxos.get()', async () => { - const addr = 'bitcoincash:qq54fgjn3hz0357n8a6guy4demw9xfkjk5jcj0xr0z' - - const utxos = await bchjs.Utxo.get(addr) - // console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`) - - const result = bchjs.Utxo.findBiggestUtxo(utxos[0].bchUtxos) - // console.log(`result: ${JSON.stringify(result, null, 2)}`) - - assert.property(result, 'satoshis') - assert.equal(result.satoshis, 800) - }) - }) -*/ }) function sleep (ms) { From 6d7056808203f705265d6f289447f8e89b69db3f Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Wed, 19 Jan 2022 14:46:00 -0800 Subject: [PATCH 2/2] fix(Utxo.get()): Properly handling addresses with no SLP utxos --- src/utxo.js | 18 ++++++++++--- .../chains/bchn/utxo-integration.js | 10 ++++++++ test/unit/fixtures/utxo-mocks.js | 23 ++++++++++++++++- test/unit/utxo-unit.js | 25 +++++++++++++++++++ 4 files changed, 72 insertions(+), 4 deletions(-) diff --git a/src/utxo.js b/src/utxo.js index 500cc29..8bc61f1 100644 --- a/src/utxo.js +++ b/src/utxo.js @@ -357,10 +357,22 @@ class UTXO { // console.log(`utxoData: ${JSON.stringify(utxoData, null, 2)}`) const utxos = utxoData.utxos + let slpUtxos = [] + // Get SLP UTXOs from the psf-slp-indexer - const slpUtxoData = await this.psfSlpIndexer.balance(addr) - // console.log(`slpUtxoData: ${JSON.stringify(slpUtxoData, null, 2)}`) - const slpUtxos = slpUtxoData.balance.utxos + try { + const slpUtxoData = await this.psfSlpIndexer.balance(addr) + // console.log(`slpUtxoData: ${JSON.stringify(slpUtxoData, null, 2)}`) + + slpUtxos = slpUtxoData.balance.utxos + } catch (err) { + // console.log('err: ', err) + + // Exit quietly if address has no SLP UTXOs. Otherwise, throw the error. + if (err.error && !err.error.includes('Key not found in database')) { + throw err + } + } // Loop through the Fulcrum UTXOs. for (let i = 0; i < utxos.length; i++) { diff --git a/test/integration/chains/bchn/utxo-integration.js b/test/integration/chains/bchn/utxo-integration.js index f1dc876..9089900 100644 --- a/test/integration/chains/bchn/utxo-integration.js +++ b/test/integration/chains/bchn/utxo-integration.js @@ -208,6 +208,16 @@ describe('#UTXO', () => { // Assert that minting batons are correctly identified. assert.isAbove(result.slpUtxos.type1.mintBatons.length, 0) }) + + it('should return UTXOs for address with no SLP tokens', async () => { + const addr = 'bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7' + + const result = await bchjs.Utxo.get(addr) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.isAbove(result.bchUtxos.length, 0) + assert.equal(result.slpUtxos.type1.tokens.length, 0) + }) }) }) diff --git a/test/unit/fixtures/utxo-mocks.js b/test/unit/fixtures/utxo-mocks.js index 06a7488..6d8d3e6 100644 --- a/test/unit/fixtures/utxo-mocks.js +++ b/test/unit/fixtures/utxo-mocks.js @@ -328,6 +328,19 @@ const fulcrumUtxos01 = { ] } +const fulcrumUtxos02 = { + success: true, + utxos: [ + { + height: 674513, + tx_hash: + '705bcc442e5a2770e560b528f52a47b1dcc9ce9ab6a8de9dfdefa55177f00d04', + tx_pos: 3, + value: 38134 + } + ] +} + const psfSlpIndexerUtxos01 = { balance: { utxos: [ @@ -457,6 +470,12 @@ const genesisData03 = { } } +const noUtxoErr = { + success: false, + error: + 'Key not found in database [bitcoincash:qp3sn6vlwz28ntmf3wmyra7jqttfx7z6zgtkygjhc7]' +} + module.exports = { mockUtxoData, mockHydratedUtxos, @@ -464,9 +483,11 @@ module.exports = { mockEveryUtxoType, electrumxUtxos, fulcrumUtxos01, + fulcrumUtxos02, psfSlpIndexerUtxos01, tokenUtxos01, genesisData01, genesisData02, - genesisData03 + genesisData03, + noUtxoErr } diff --git a/test/unit/utxo-unit.js b/test/unit/utxo-unit.js index 7253283..52ef883 100644 --- a/test/unit/utxo-unit.js +++ b/test/unit/utxo-unit.js @@ -272,5 +272,30 @@ describe('#utxo', () => { assert.equal(result.slpUtxos.type1.mintBatons.length, 1) assert.equal(result.nullUtxos.length, 0) }) + + it('should handle an address with no SLP UTXOs', async () => { + // mock dependencies + sandbox + .stub(bchjs.Utxo.electrumx, 'utxo') + .resolves(mockData.fulcrumUtxos02) + + // Force psf-slp-indexer to return no UTXOs + sandbox + .stub(bchjs.Utxo.psfSlpIndexer, 'balance') + .rejects(mockData.noUtxoErr) + + // Mock function to return the same input. Good enough for this test. + sandbox.stub(bchjs.Utxo, 'hydrateTokenData').resolves(() => []) + + const addr = 'simpleledger:qrm0c67wwqh0w7wjxua2gdt2xggnm90xwsr5k22euj' + + const result = await bchjs.Utxo.get(addr) + // console.log(`result: ${JSON.stringify(result, null, 2)}`) + + assert.equal(result.bchUtxos.length, 1) + assert.equal(result.slpUtxos.type1.tokens.length, 0) + assert.equal(result.slpUtxos.type1.mintBatons.length, 0) + assert.equal(result.nullUtxos.length, 0) + }) }) })