/** * Used to force keys creation, could take long time to complete so use it in a background * thread. */ @VisibleForTesting public void maybeInitializeAllKeys() { lock.lock(); try { keys.maybeLookAhead(); } finally { lock.unlock(); } }
@Override public int numKeys() { lock.lock(); try { maybeLookAhead(); return simpleKeyChain.numKeys(); } finally { lock.unlock(); } }
/** * Pre-generate enough keys to reach the lookahead size. You can call this if you need to explicitly invoke * the lookahead procedure, but it's normally unnecessary as it will be done automatically when needed. */ public void maybeLookAhead() { lock.lock(); try { List<DeterministicKey> keys = maybeLookAhead(externalKey, issuedExternalKeys); keys.addAll(maybeLookAhead(internalKey, issuedInternalKeys)); // Batch add all keys at once so there's only one event listener invocation, as this will be listened to // by the wallet and used to rebuild/broadcast the Bloom filter. That's expensive so we don't want to do // it more often than necessary. simpleKeyChain.importKeys(keys); } finally { lock.unlock(); } }
@Override public BloomFilter getFilter(int size, double falsePositiveRate, long tweak) { lock.lock(); try { checkArgument(size >= numBloomFilterEntries(), "Bloom filter too small"); maybeLookAhead(); return simpleKeyChain.getFilter(size, falsePositiveRate, tweak); } finally { lock.unlock(); } }
private List<DeterministicKey> maybeLookAhead(DeterministicKey parent, int issued) { checkState(lock.isHeldByCurrentThread(), "Lock is held by another thread"); return maybeLookAhead(parent, issued, getLookaheadSize(), getLookaheadThreshold()); }
/** * Mark the DeterministicKey as used. * Also correct the issued{Internal|External}Keys counter, because all lower children seem to be requested already. * If the counter was updated, we also might trigger lookahead. */ public DeterministicKey markKeyAsUsed(DeterministicKey k) { int numChildren = k.getChildNumber().i() + 1; if (k.getParent() == internalKey) { if (issuedInternalKeys < numChildren) { issuedInternalKeys = numChildren; maybeLookAhead(); } } else if (k.getParent() == externalKey) { if (issuedExternalKeys < numChildren) { issuedExternalKeys = numChildren; maybeLookAhead(); } } return k; }
private List<DeterministicKey> getDeterministicKeys(int numberOfKeys, DeterministicKey parentKey, int index) { lock.lock(); try { // Optimization: potentially do a very quick key generation for just the number of keys we need if we // didn't already create them, ignoring the configured lookahead size. This ensures we'll be able to // retrieve the keys in the following loop, but if we're totally fresh and didn't get a chance to // calculate the lookahead keys yet, this will not block waiting to calculate 100+ EC point multiplies. // On slow/crappy Android phones looking ahead 100 keys can take ~5 seconds but the OS will kill us // if we block for just one second on the UI thread. Because UI threads may need an address in order // to render the screen, we need getKeys to be fast even if the wallet is totally brand new and lookahead // didn't happen yet. // // It's safe to do this because when a network thread tries to calculate a Bloom filter, we'll go ahead // and calculate the full lookahead zone there, so network requests will always use the right amount. List<DeterministicKey> lookahead = maybeLookAhead(parentKey, index, 0, 0); simpleKeyChain.importKeys(lookahead); List<DeterministicKey> keys = new ArrayList<DeterministicKey>(numberOfKeys); for (int i = 0; i < numberOfKeys; i++) { ImmutableList<ChildNumber> path = HDUtils.append(parentKey.getPath(), new ChildNumber(index - numberOfKeys + i, false)); keys.add(hierarchy.get(path, false, false)); } return keys; } finally { lock.unlock(); } }
/** * Returns keys used on external path. This may be fewer than the number that have been deserialized * or held in memory, because of the lookahead zone. */ public ArrayList<DeterministicKey> getIssuedExternalKeys() { lock.lock(); try { maybeLookAhead(); int treeSize = externalKey.getPath().size(); ArrayList<DeterministicKey> issuedKeys = new ArrayList<DeterministicKey>(); for (ECKey key : simpleKeyChain.getKeys()) { DeterministicKey detkey = (DeterministicKey) key; DeterministicKey parent = detkey.getParent(); if (parent == null) continue; if (detkey.getPath().size() <= treeSize) continue; if (parent.equals(internalKey)) continue; if (parent.equals(externalKey) && detkey.getChildNumber().num() >= issuedExternalKeys) continue; issuedKeys.add(detkey); } return issuedKeys; } finally { lock.unlock(); } }
/* package */ List<ECKey> getKeys(boolean includeLookahead) { maybeLookAhead(); List<ECKey> keys = simpleKeyChain.getKeys(); if (!includeLookahead) { int treeSize = internalKey.getPath().size(); List<ECKey> issuedKeys = new LinkedList<ECKey>(); for (ECKey key : keys) { DeterministicKey detkey = (DeterministicKey) key; DeterministicKey parent = detkey.getParent(); if (parent == null) continue; if (detkey.getPath().size() <= treeSize) continue; if (parent.equals(internalKey) && detkey.getChildNumber().num() > issuedInternalKeys) continue; if (parent.equals(externalKey) && detkey.getChildNumber().num() > issuedExternalKeys) continue; issuedKeys.add(detkey); } return issuedKeys; } return keys; }
chain.maybeLookAhead(); return chain;
chain.maybeLookAhead(); final List<ECKey> secondEvent = listenerKeys.get(0); assertEquals(12, secondEvent.size()); // (5 lookahead keys, +1 lookahead threshold) * 2 chains
public void serializeUnencrypted(SimpleHDKeyChain keyChain, String expectedSerialization) throws UnreadableWalletException { keyChain.setLookaheadSize(10); keyChain.maybeLookAhead(); DeterministicKey key1 = keyChain.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS); DeterministicKey key2 = keyChain.getKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);