diff --git a/HDWallet.test.js b/HDWallet.test.js index 73c80da9..82954158 100644 --- a/HDWallet.test.js +++ b/HDWallet.test.js @@ -86,10 +86,21 @@ it('HD (BIP49) can work with a gap', async function() { it('Segwit HD (BIP49) can batch fetch many txs', async function() { jasmine.DEFAULT_TIMEOUT_INTERVAL = 300 * 1000; let hd = new HDSegwitP2SHWallet(); - hd._xpub = 'ypub6WZ2c7YJ1SQ1rBYftwMqwV9bBmybXzETFxWmkzMz25bCf6FkDdXjNgR7zRW8JGSnoddNdUH7ZQS7JeQAddxdGpwgPskcsXFcvSn1JdGVcPQ'; // cant fetch txs + hd._xpub = 'ypub6WZ2c7YJ1SQ1rBYftwMqwV9bBmybXzETFxWmkzMz25bCf6FkDdXjNgR7zRW8JGSnoddNdUH7ZQS7JeQAddxdGpwgPskcsXFcvSn1JdGVcPQ'; await hd.fetchBalance(); await hd.fetchTransactions(); - assert.ok(hd.transactions.length > 0); + assert.ok(hd.getTransactions().length === 153); +}); + +it('Segwit HD (BIP49) can fetch more data if pointers to last_used_addr are lagging behind', async function() { + jasmine.DEFAULT_TIMEOUT_INTERVAL = 300 * 1000; + let hd = new HDSegwitP2SHWallet(); + hd._xpub = 'ypub6WZ2c7YJ1SQ1rBYftwMqwV9bBmybXzETFxWmkzMz25bCf6FkDdXjNgR7zRW8JGSnoddNdUH7ZQS7JeQAddxdGpwgPskcsXFcvSn1JdGVcPQ'; + hd.next_free_change_address_index = 40; + hd.next_free_address_index = 50; + await hd.fetchBalance(); + await hd.fetchTransactions(); + assert.strictEqual(hd.getTransactions().length, 153); }); it('Segwit HD (BIP49) can generate addressess only via ypub', function() { diff --git a/class/abstract-hd-wallet.js b/class/abstract-hd-wallet.js index 227b0bb9..78b3d9c2 100644 --- a/class/abstract-hd-wallet.js +++ b/class/abstract-hd-wallet.js @@ -17,7 +17,7 @@ export class AbstractHDWallet extends LegacyWallet { this._xpub = ''; // cache this.usedAddresses = []; this._address_to_wif_cache = {}; - this.gap_limit = 3; + this.gap_limit = 20; } prepareForSerialization() { @@ -115,7 +115,7 @@ export class AbstractHDWallet extends LegacyWallet { // looking for free external address let freeAddress = ''; let c; - for (c = 0; c < Math.max(5, this.usedAddresses.length); c++) { + for (c = 0; c < this.gap_limit + 1; c++) { if (this.next_free_address_index + c < 0) continue; let address = this._getExternalAddressByIndex(this.next_free_address_index + c); this.external_addresses_cache[this.next_free_address_index + c] = address; // updating cache just for any case @@ -153,7 +153,7 @@ export class AbstractHDWallet extends LegacyWallet { // looking for free internal address let freeAddress = ''; let c; - for (c = 0; c < Math.max(5, this.usedAddresses.length); c++) { + for (c = 0; c < this.gap_limit + 1; c++) { if (this.next_free_change_address_index + c < 0) continue; let address = this._getInternalAddressByIndex(this.next_free_change_address_index + c); this.internal_addresses_cache[this.next_free_change_address_index + c] = address; // updating cache just for any case @@ -432,6 +432,32 @@ export class AbstractHDWallet extends LegacyWallet { } async _fetchBalance() { + // probing future addressess in hierarchy whether they have any transactions, in case + // our 'next free addr' pointers are lagging behind + let tryAgain = false; + let txs = await BlueElectrum.getTransactionsByAddress( + this._getExternalAddressByIndex(this.next_free_address_index + this.gap_limit - 1), + ); + if (txs.length > 0) { + // whoa, someone uses our wallet outside! better catch up + this.next_free_address_index += this.gap_limit; + tryAgain = true; + } + + txs = await BlueElectrum.getTransactionsByAddress( + this._getInternalAddressByIndex(this.next_free_change_address_index + this.gap_limit - 1), + ); + if (txs.length > 0) { + this.next_free_change_address_index += this.gap_limit; + tryAgain = true; + } + + // FIXME: refactor me ^^^ can be batched in single call + + if (tryAgain) return this._fetchBalance(); + + // next, business as usuall. fetch balances + this.usedAddresses = []; // generating all involved addresses: for (let c = 0; c < this.next_free_address_index + this.gap_limit; c++) { diff --git a/class/hd-segwit-bech32-wallet.js b/class/hd-segwit-bech32-wallet.js index 8a6d261d..0202ac17 100644 --- a/class/hd-segwit-bech32-wallet.js +++ b/class/hd-segwit-bech32-wallet.js @@ -52,8 +52,6 @@ export class HDSegwitBech32Wallet extends AbstractHDWallet { this._txs_by_external_index = {}; this._txs_by_internal_index = {}; - - this.gap_limit = 20; } /** @@ -264,6 +262,32 @@ export class HDSegwitBech32Wallet extends AbstractHDWallet { } async _fetchBalance() { + // probing future addressess in hierarchy whether they have any transactions, in case + // our 'next free addr' pointers are lagging behind + let tryAgain = false; + let txs = await BlueElectrum.getTransactionsByAddress( + this._getExternalAddressByIndex(this.next_free_address_index + this.gap_limit - 1), + ); + if (txs.length > 0) { + // whoa, someone uses our wallet outside! better catch up + this.next_free_address_index += this.gap_limit; + tryAgain = true; + } + + txs = await BlueElectrum.getTransactionsByAddress( + this._getInternalAddressByIndex(this.next_free_change_address_index + this.gap_limit - 1), + ); + if (txs.length > 0) { + this.next_free_change_address_index += this.gap_limit; + tryAgain = true; + } + + // FIXME: refactor me ^^^ can be batched in single call + + if (tryAgain) return this._fetchBalance(); + + // next, business as usuall. fetch balances + let addresses2fetch = []; // generating all involved addresses. diff --git a/screen/selftest.js b/screen/selftest.js index 26ed913c..3e6ca580 100644 --- a/screen/selftest.js +++ b/screen/selftest.js @@ -270,8 +270,7 @@ export default class Selftest extends Component { await hd4.fetchBalance(); if (hd4.getBalance() !== 200000) throw new Error('Could not fetch HD Bech32 balance'); await hd4.fetchTransactions(); - if (hd4.getTransactions().length !== 4) - throw new Error('Could not fetch HD Bech32 transactions'); + if (hd4.getTransactions().length !== 4) throw new Error('Could not fetch HD Bech32 transactions'); } else { console.warn('skipping RN-specific test'); }