/** * Returns the set of contiguous blocks between 'higher' and 'lower'. Higher is included, lower is * not. */ private static LinkedList<StoredBlock> getPartialChain( StoredBlock higher, StoredBlock lower, BlockStore store) throws BlockStoreException { checkArgument(higher.getHeight() > lower.getHeight(), "higher and lower are reversed"); LinkedList<StoredBlock> results = new LinkedList<StoredBlock>(); StoredBlock cursor = higher; while (true) { results.add(cursor); cursor = checkNotNull(cursor.getPrev(store), "Ran off the end of the chain"); if (cursor.equals(lower)) break; } return results; }
@Override public List<Protos.Key> serializeToProtobuf() { lock.lock(); try { // Most of the serialization work is delegated to the basic key chain, which will serialize // the bulk of the // data (handling encryption along the way), and letting us patch it up with the extra data we // care about. LinkedList<Protos.Key> entries = newLinkedList(); if (seed != null) { Protos.Key.Builder mnemonicEntry = BasicKeyChain.serializeEncryptableItem(seed); mnemonicEntry.setType(Protos.Key.Type.DETERMINISTIC_MNEMONIC); entries.add(mnemonicEntry.build()); } Map<ECKey, Protos.Key.Builder> keys = basicKeyChain.serializeToEditableProtobufs(); for (Map.Entry<ECKey, Protos.Key.Builder> entry : keys.entrySet()) { DeterministicKey key = (DeterministicKey) entry.getKey(); Protos.Key.Builder proto = entry.getValue(); proto.setType(Protos.Key.Type.DETERMINISTIC_KEY); final Protos.DeterministicKey.Builder detKey = proto.getDeterministicKeyBuilder(); detKey.setChainCode(ByteString.copyFrom(key.getChainCode())); for (ChildNumber num : key.getPath()) detKey.addPath(num.i()); if (key.equals(externalKey)) { detKey.setIssuedSubkeys(issuedExternalKeys); detKey.setLookaheadSize(lookaheadSize); } else if (key.equals(internalKey)) { detKey.setIssuedSubkeys(issuedInternalKeys); detKey.setLookaheadSize(lookaheadSize); } // Flag the very first key of following keychain. if (entries.isEmpty() && isFollowing()) { detKey.setIsFollowing(true); } if (key.getParent() != null) { // HD keys inherit the timestamp of their parent if they have one, so no need to serialize // it. proto.clearCreationTimestamp(); } entries.add(proto.build()); } return entries; } finally { lock.unlock(); } }
/** * Called as part of connecting a block when the new block results in a different chain having * higher total work. * * <p>if (shouldVerifyTransactions) Either newChainHead needs to be in the block store as a * FullStoredBlock, or (block != null && block.transactions != null) */ private void handleNewBestChain( StoredBlock storedPrev, StoredBlock newChainHead, Block block, boolean expensiveChecks) throws BlockStoreException, VerificationException, PrunedException { checkState(lock.isHeldByCurrentThread()); // This chain has overtaken the one we currently believe is best. Reorganize is required. // // Firstly, calculate the block at which the chain diverged. We only need to examine the // chain from beyond this block to find differences. StoredBlock head = getChainHead(); final StoredBlock splitPoint = findSplit(newChainHead, head, blockStore); log.info("Re-organize after split at height {}", splitPoint.getHeight()); log.info("Old chain head: {}", head.getHeader().getHashAsString()); log.info("New chain head: {}", newChainHead.getHeader().getHashAsString()); log.info("Split at block: {}", splitPoint.getHeader().getHashAsString()); // Then build a list of all blocks in the old part of the chain and the new part. final LinkedList<StoredBlock> oldBlocks = getPartialChain(head, splitPoint, blockStore); final LinkedList<StoredBlock> newBlocks = getPartialChain(newChainHead, splitPoint, blockStore); // Disconnect each transaction in the previous main chain that is no longer in the new main // chain StoredBlock storedNewHead = splitPoint; if (shouldVerifyTransactions()) { for (StoredBlock oldBlock : oldBlocks) { try { disconnectTransactions(oldBlock); } catch (PrunedException e) { // We threw away the data we need to re-org this deep! We need to go back to a peer with // full // block contents and ask them for the relevant data then rebuild the indexs. Or we could // just // give up and ask the human operator to help get us unstuck (eg, rescan from the genesis // block). // TODO: Retry adding this block when we get a block with hash e.getHash() throw e; } } StoredBlock cursor; // Walk in ascending chronological order. for (Iterator<StoredBlock> it = newBlocks.descendingIterator(); it.hasNext(); ) { cursor = it.next(); Block cursorBlock = cursor.getHeader(); if (expensiveChecks && cursorBlock.getTimeSeconds() <= getMedianTimestampOfRecentBlocks(cursor.getPrev(blockStore), blockStore)) throw new VerificationException("Block's timestamp is too early during reorg"); TransactionOutputChanges txOutChanges; if (cursor != newChainHead || block == null) txOutChanges = connectTransactions(cursor); else txOutChanges = connectTransactions(newChainHead.getHeight(), block); storedNewHead = addToBlockStore(storedNewHead, cursorBlock.cloneAsHeader(), txOutChanges); } } else { // (Finally) write block to block store storedNewHead = addToBlockStore(storedPrev, newChainHead.getHeader()); } // Now inform the listeners. This is necessary so the set of currently active transactions (that // we can spend) // can be updated to take into account the re-organize. We might also have received new coins we // didn't have // before and our previous spends might have been undone. for (final ListenerRegistration<BlockChainListener> registration : listeners) { if (registration.executor == Threading.SAME_THREAD) { // Short circuit the executor so we can propagate any exceptions. // TODO: Do we really need to do this or should it be irrelevant? registration.listener.reorganize(splitPoint, oldBlocks, newBlocks); } else { registration.executor.execute( new Runnable() { @Override public void run() { try { registration.listener.reorganize(splitPoint, oldBlocks, newBlocks); } catch (VerificationException e) { log.error("Block chain listener threw exception during reorg", e); } } }); } } // Update the pointer to the best known block. setChainHead(storedNewHead); }
/** * Returns all the key chains found in the given list of keys. Typically there will only be one, * but in the case of key rotation it can happen that there are multiple chains found. */ public static List<DeterministicKeyChain> fromProtobuf( List<Protos.Key> keys, @Nullable KeyCrypter crypter) throws UnreadableWalletException { List<DeterministicKeyChain> chains = newLinkedList(); DeterministicSeed seed = null; DeterministicKeyChain chain = null; int lookaheadSize = -1; for (Protos.Key key : keys) { final Protos.Key.Type t = key.getType(); if (t == Protos.Key.Type.DETERMINISTIC_MNEMONIC) { if (chain != null) { checkState(lookaheadSize >= 0); chain.setLookaheadSize(lookaheadSize); chain.maybeLookAhead(); chains.add(chain); chain = null; } long timestamp = key.getCreationTimestamp() / 1000; String passphrase = DEFAULT_PASSPHRASE_FOR_MNEMONIC; // FIXME allow non-empty passphrase if (key.hasSecretBytes()) { seed = new DeterministicSeed(key.getSecretBytes().toStringUtf8(), passphrase, timestamp); } else if (key.hasEncryptedData()) { EncryptedData data = new EncryptedData( key.getEncryptedData().getInitialisationVector().toByteArray(), key.getEncryptedData().getEncryptedPrivateKey().toByteArray()); seed = new DeterministicSeed(data, timestamp); } else { throw new UnreadableWalletException("Malformed key proto: " + key.toString()); } if (log.isDebugEnabled()) log.debug("Deserializing: DETERMINISTIC_MNEMONIC: {}", seed); } else if (t == Protos.Key.Type.DETERMINISTIC_KEY) { if (!key.hasDeterministicKey()) throw new UnreadableWalletException( "Deterministic key missing extra data: " + key.toString()); byte[] chainCode = key.getDeterministicKey().getChainCode().toByteArray(); // Deserialize the path through the tree. LinkedList<ChildNumber> path = newLinkedList(); for (int i : key.getDeterministicKey().getPathList()) path.add(new ChildNumber(i)); // Deserialize the public key and path. ECPoint pubkey = ECKey.CURVE.getCurve().decodePoint(key.getPublicKey().toByteArray()); final ImmutableList<ChildNumber> immutablePath = ImmutableList.copyOf(path); // Possibly create the chain, if we didn't already do so yet. boolean isWatchingAccountKey = false; boolean isFollowingKey = false; // save previous chain if any if the key is marked as following. Current key and the next // ones are to be // placed in new following key chain if (key.getDeterministicKey().getIsFollowing()) { if (chain != null) { checkState(lookaheadSize >= 0); chain.setLookaheadSize(lookaheadSize); chain.maybeLookAhead(); chains.add(chain); chain = null; seed = null; } isFollowingKey = true; } if (chain == null) { if (seed == null) { DeterministicKey accountKey = new DeterministicKey(immutablePath, chainCode, pubkey, null, null); if (!accountKey.getPath().equals(ACCOUNT_ZERO_PATH)) throw new UnreadableWalletException( "Expecting account key but found key with path: " + HDUtils.formatPath(accountKey.getPath())); chain = new DeterministicKeyChain(accountKey, isFollowingKey); isWatchingAccountKey = true; } else { chain = new DeterministicKeyChain(seed, crypter); chain.lookaheadSize = LAZY_CALCULATE_LOOKAHEAD; // If the seed is encrypted, then the chain is incomplete at this point. However, we // will load // it up below as we parse in the keys. We just need to check at the end that we've // loaded // everything afterwards. } } // Find the parent key assuming this is not the root key, and not an account key for a // watching chain. DeterministicKey parent = null; if (!path.isEmpty() && !isWatchingAccountKey) { ChildNumber index = path.removeLast(); parent = chain.hierarchy.get(path, false, false); path.add(index); } DeterministicKey detkey; if (key.hasSecretBytes()) { // Not encrypted: private key is available. final BigInteger priv = new BigInteger(1, key.getSecretBytes().toByteArray()); detkey = new DeterministicKey(immutablePath, chainCode, pubkey, priv, parent); } else { if (key.hasEncryptedData()) { Protos.EncryptedData proto = key.getEncryptedData(); EncryptedData data = new EncryptedData( proto.getInitialisationVector().toByteArray(), proto.getEncryptedPrivateKey().toByteArray()); checkNotNull(crypter, "Encountered an encrypted key but no key crypter provided"); detkey = new DeterministicKey(immutablePath, chainCode, crypter, pubkey, data, parent); } else { // No secret key bytes and key is not encrypted: either a watching key or private key // bytes // will be rederived on the fly from the parent. detkey = new DeterministicKey(immutablePath, chainCode, pubkey, null, parent); } } if (key.hasCreationTimestamp()) detkey.setCreationTimeSeconds(key.getCreationTimestamp() / 1000); if (log.isDebugEnabled()) log.debug("Deserializing: DETERMINISTIC_KEY: {}", detkey); if (!isWatchingAccountKey) { // If the non-encrypted case, the non-leaf keys (account, internal, external) have already // been // rederived and inserted at this point and the two lines below are just a no-op. In the // encrypted // case though, we can't rederive and we must reinsert, potentially building the heirarchy // object // if need be. if (path.size() == 0) { // Master key. chain.rootKey = detkey; chain.hierarchy = new DeterministicHierarchy(detkey); } else if (path.size() == 2) { if (detkey.getChildNumber().num() == 0) { chain.externalKey = detkey; chain.issuedExternalKeys = key.getDeterministicKey().getIssuedSubkeys(); lookaheadSize = Math.max(lookaheadSize, key.getDeterministicKey().getLookaheadSize()); } else if (detkey.getChildNumber().num() == 1) { chain.internalKey = detkey; chain.issuedInternalKeys = key.getDeterministicKey().getIssuedSubkeys(); } } } chain.hierarchy.putKey(detkey); chain.basicKeyChain.importKey(detkey); } } if (chain != null) { checkState(lookaheadSize >= 0); chain.setLookaheadSize(lookaheadSize); chain.maybeLookAhead(); chains.add(chain); } return chains; }