Browse Source

FIX: HD wallet update if it was heavily used externally (closes #495)

fixqramount
Overtorment 5 years ago
parent
commit
544a9e61f1
  1. 15
      HDWallet.test.js
  2. 32
      class/abstract-hd-wallet.js
  3. 28
      class/hd-segwit-bech32-wallet.js
  4. 3
      screen/selftest.js

15
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() {

32
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++) {

28
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.

3
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');
}

Loading…
Cancel
Save