From d309863560c37a9bf0513b30d7d15e5ef15eae35 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Mon, 4 Mar 2013 18:52:09 +0100 Subject: [PATCH] Save the last seen chain height as well as hash in the wallet. --- core/src/bitcoin.proto | 6 +- .../java/com/google/bitcoin/core/Wallet.java | 23 +++- .../store/WalletProtobufSerializer.java | 11 +- .../main/java/org/bitcoinj/wallet/Protos.java | 124 +++++++++++++----- .../store/WalletProtobufSerializerTest.java | 2 + 5 files changed, 120 insertions(+), 46 deletions(-) diff --git a/core/src/bitcoin.proto b/core/src/bitcoin.proto index 4d0b12abe..b855c9401 100644 --- a/core/src/bitcoin.proto +++ b/core/src/bitcoin.proto @@ -192,10 +192,12 @@ message Wallet { // org.bitcoin.production = production network (Satoshi genesis block) // org.bitcoin.test = test network (Andresen genesis block) - // The Sha256 hash of the head of the best chain seen by this wallet. + // The SHA256 hash of the head of the best chain seen by this wallet. optional bytes last_seen_block_hash = 2; + // The height in the chain of the last seen block. + optional uint32 last_seen_block_height = 5; repeated Key key = 3; repeated Transaction transaction = 4; - repeated Extension extension = 10; + repeated Extension extension = 10; } // end of Wallet diff --git a/core/src/main/java/com/google/bitcoin/core/Wallet.java b/core/src/main/java/com/google/bitcoin/core/Wallet.java index e69e37e35..ae9740c3d 100644 --- a/core/src/main/java/com/google/bitcoin/core/Wallet.java +++ b/core/src/main/java/com/google/bitcoin/core/Wallet.java @@ -169,10 +169,8 @@ public class Wallet implements Serializable, BlockChainListener { private final NetworkParameters params; - /** - * The hash of the last block seen on the best chain - */ private Sha256Hash lastBlockSeenHash; + private int lastBlockSeenHeight = -1; private transient ArrayList eventListeners; @@ -1050,9 +1048,10 @@ public class Wallet implements Serializable, BlockChainListener { * transactions are extracted and sent to us UNLESS the new block caused a re-org, in which case this will * not be called (the {@link Wallet#reorganize(StoredBlock, java.util.List, java.util.List)} method will * call this one in that case).

- * + *

*

Used to update confidence data in each transaction and last seen block hash. Triggers auto saving. * Invokes the onWalletChanged event listener if there were any affected transactions.

+ * * @param block */ public synchronized void notifyNewBestBlock(StoredBlock block) throws VerificationException { @@ -1062,6 +1061,7 @@ public class Wallet implements Serializable, BlockChainListener { return; // Store the new block hash. setLastBlockSeenHash(newBlockHash); + setLastBlockSeenHeight(block.getHeight()); // TODO: Clarify the code below. // Notify all the BUILDING transactions of the new block. // This is so that they can update their work done and depth. @@ -1927,7 +1927,7 @@ public class Wallet implements Serializable, BlockChainListener { public synchronized BigInteger getBalance(CoinSelector selector) { checkNotNull(selector); LinkedList candidates = calculateSpendCandidates(true); - CoinSelection selection = selector.select(params.MAX_MONEY, candidates); + CoinSelection selection = selector.select(NetworkParameters.MAX_MONEY, candidates); return selection.valueGathered; } @@ -1951,7 +1951,8 @@ public class Wallet implements Serializable, BlockChainListener { builder.append(String.format(" %d pending transactions%n", pending.size())); builder.append(String.format(" %d inactive transactions%n", inactive.size())); builder.append(String.format(" %d dead transactions%n", dead.size())); - builder.append(String.format("Last seen best block: %s%n", getLastBlockSeenHash())); + builder.append(String.format("Last seen best block: (%d) %s%n", + getLastBlockSeenHeight(), getLastBlockSeenHash())); // Do the keys. builder.append("\nKeys:\n"); for (ECKey key : keychain) { @@ -2383,6 +2384,7 @@ public class Wallet implements Serializable, BlockChainListener { return earliestTime; } + /** Returns the hash of the last seen best-chain block. */ public Sha256Hash getLastBlockSeenHash() { return lastBlockSeenHash; } @@ -2390,6 +2392,15 @@ public class Wallet implements Serializable, BlockChainListener { public void setLastBlockSeenHash(Sha256Hash lastBlockSeenHash) { this.lastBlockSeenHash = lastBlockSeenHash; } + + public void setLastBlockSeenHeight(int lastBlockSeenHeight) { + this.lastBlockSeenHeight = lastBlockSeenHeight; + } + + /** Returns the height of the last seen best-chain block. Can be -1 if a wallet is old and doesn't have that data. */ + public int getLastBlockSeenHeight() { + return lastBlockSeenHeight; + } /** * Gets the number of elements that will be added to a bloom filter returned by getBloomFilter diff --git a/core/src/main/java/com/google/bitcoin/store/WalletProtobufSerializer.java b/core/src/main/java/com/google/bitcoin/store/WalletProtobufSerializer.java index 692451634..5e7be49d6 100644 --- a/core/src/main/java/com/google/bitcoin/store/WalletProtobufSerializer.java +++ b/core/src/main/java/com/google/bitcoin/store/WalletProtobufSerializer.java @@ -59,11 +59,6 @@ public class WalletProtobufSerializer { private Map txMap; private WalletExtensionSerializer helper; - // Temporary hack for migrating 0.5 wallets to 0.6 wallets. In 0.5 transactions stored the height at which they - // appeared in the block chain (for the current best chain) but not the depth. In 0.6 we store both and update - // every transaction every time we receive a block, so we need to fill out depth from best chain height. - private int chainHeight; - public WalletProtobufSerializer() { txMap = new HashMap(); helper = new WalletExtensionSerializer(); @@ -128,6 +123,7 @@ public class WalletProtobufSerializer { Sha256Hash lastSeenBlockHash = wallet.getLastBlockSeenHash(); if (lastSeenBlockHash != null) { walletBuilder.setLastSeenBlockHash(hashToByteString(lastSeenBlockHash)); + walletBuilder.setLastSeenBlockHeight(wallet.getLastBlockSeenHeight()); } Collection extensions = helper.getExtensionsToWrite(wallet); @@ -292,6 +288,11 @@ public class WalletProtobufSerializer { } else { wallet.setLastBlockSeenHash(byteStringToHash(walletProto.getLastSeenBlockHash())); } + if (!walletProto.hasLastSeenBlockHeight()) { + wallet.setLastBlockSeenHeight(-1); + } else { + wallet.setLastBlockSeenHeight(walletProto.getLastSeenBlockHeight()); + } for (Protos.Extension extProto : walletProto.getExtensionList()) { helper.readExtension(wallet, extProto); diff --git a/core/src/main/java/org/bitcoinj/wallet/Protos.java b/core/src/main/java/org/bitcoinj/wallet/Protos.java index f71843973..78ae82b97 100644 --- a/core/src/main/java/org/bitcoinj/wallet/Protos.java +++ b/core/src/main/java/org/bitcoinj/wallet/Protos.java @@ -5360,6 +5360,10 @@ public final class Protos { boolean hasLastSeenBlockHash(); com.google.protobuf.ByteString getLastSeenBlockHash(); + // optional uint32 last_seen_block_height = 5; + boolean hasLastSeenBlockHeight(); + int getLastSeenBlockHeight(); + // repeated .wallet.Key key = 3; java.util.List getKeyList(); @@ -5461,6 +5465,16 @@ public final class Protos { return lastSeenBlockHash_; } + // optional uint32 last_seen_block_height = 5; + public static final int LAST_SEEN_BLOCK_HEIGHT_FIELD_NUMBER = 5; + private int lastSeenBlockHeight_; + public boolean hasLastSeenBlockHeight() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + public int getLastSeenBlockHeight() { + return lastSeenBlockHeight_; + } + // repeated .wallet.Key key = 3; public static final int KEY_FIELD_NUMBER = 3; private java.util.List key_; @@ -5527,6 +5541,7 @@ public final class Protos { private void initFields() { networkIdentifier_ = ""; lastSeenBlockHash_ = com.google.protobuf.ByteString.EMPTY; + lastSeenBlockHeight_ = 0; key_ = java.util.Collections.emptyList(); transaction_ = java.util.Collections.emptyList(); extension_ = java.util.Collections.emptyList(); @@ -5577,6 +5592,9 @@ public final class Protos { for (int i = 0; i < transaction_.size(); i++) { output.writeMessage(4, transaction_.get(i)); } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + output.writeUInt32(5, lastSeenBlockHeight_); + } for (int i = 0; i < extension_.size(); i++) { output.writeMessage(10, extension_.get(i)); } @@ -5605,6 +5623,10 @@ public final class Protos { size += com.google.protobuf.CodedOutputStream .computeMessageSize(4, transaction_.get(i)); } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + size += com.google.protobuf.CodedOutputStream + .computeUInt32Size(5, lastSeenBlockHeight_); + } for (int i = 0; i < extension_.size(); i++) { size += com.google.protobuf.CodedOutputStream .computeMessageSize(10, extension_.get(i)); @@ -5740,21 +5762,23 @@ public final class Protos { bitField0_ = (bitField0_ & ~0x00000001); lastSeenBlockHash_ = com.google.protobuf.ByteString.EMPTY; bitField0_ = (bitField0_ & ~0x00000002); + lastSeenBlockHeight_ = 0; + bitField0_ = (bitField0_ & ~0x00000004); if (keyBuilder_ == null) { key_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000004); + bitField0_ = (bitField0_ & ~0x00000008); } else { keyBuilder_.clear(); } if (transactionBuilder_ == null) { transaction_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000008); + bitField0_ = (bitField0_ & ~0x00000010); } else { transactionBuilder_.clear(); } if (extensionBuilder_ == null) { extension_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000010); + bitField0_ = (bitField0_ & ~0x00000020); } else { extensionBuilder_.clear(); } @@ -5804,28 +5828,32 @@ public final class Protos { to_bitField0_ |= 0x00000002; } result.lastSeenBlockHash_ = lastSeenBlockHash_; + if (((from_bitField0_ & 0x00000004) == 0x00000004)) { + to_bitField0_ |= 0x00000004; + } + result.lastSeenBlockHeight_ = lastSeenBlockHeight_; if (keyBuilder_ == null) { - if (((bitField0_ & 0x00000004) == 0x00000004)) { + if (((bitField0_ & 0x00000008) == 0x00000008)) { key_ = java.util.Collections.unmodifiableList(key_); - bitField0_ = (bitField0_ & ~0x00000004); + bitField0_ = (bitField0_ & ~0x00000008); } result.key_ = key_; } else { result.key_ = keyBuilder_.build(); } if (transactionBuilder_ == null) { - if (((bitField0_ & 0x00000008) == 0x00000008)) { + if (((bitField0_ & 0x00000010) == 0x00000010)) { transaction_ = java.util.Collections.unmodifiableList(transaction_); - bitField0_ = (bitField0_ & ~0x00000008); + bitField0_ = (bitField0_ & ~0x00000010); } result.transaction_ = transaction_; } else { result.transaction_ = transactionBuilder_.build(); } if (extensionBuilder_ == null) { - if (((bitField0_ & 0x00000010) == 0x00000010)) { + if (((bitField0_ & 0x00000020) == 0x00000020)) { extension_ = java.util.Collections.unmodifiableList(extension_); - bitField0_ = (bitField0_ & ~0x00000010); + bitField0_ = (bitField0_ & ~0x00000020); } result.extension_ = extension_; } else { @@ -5853,11 +5881,14 @@ public final class Protos { if (other.hasLastSeenBlockHash()) { setLastSeenBlockHash(other.getLastSeenBlockHash()); } + if (other.hasLastSeenBlockHeight()) { + setLastSeenBlockHeight(other.getLastSeenBlockHeight()); + } if (keyBuilder_ == null) { if (!other.key_.isEmpty()) { if (key_.isEmpty()) { key_ = other.key_; - bitField0_ = (bitField0_ & ~0x00000004); + bitField0_ = (bitField0_ & ~0x00000008); } else { ensureKeyIsMutable(); key_.addAll(other.key_); @@ -5870,7 +5901,7 @@ public final class Protos { keyBuilder_.dispose(); keyBuilder_ = null; key_ = other.key_; - bitField0_ = (bitField0_ & ~0x00000004); + bitField0_ = (bitField0_ & ~0x00000008); keyBuilder_ = com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? getKeyFieldBuilder() : null; @@ -5883,7 +5914,7 @@ public final class Protos { if (!other.transaction_.isEmpty()) { if (transaction_.isEmpty()) { transaction_ = other.transaction_; - bitField0_ = (bitField0_ & ~0x00000008); + bitField0_ = (bitField0_ & ~0x00000010); } else { ensureTransactionIsMutable(); transaction_.addAll(other.transaction_); @@ -5896,7 +5927,7 @@ public final class Protos { transactionBuilder_.dispose(); transactionBuilder_ = null; transaction_ = other.transaction_; - bitField0_ = (bitField0_ & ~0x00000008); + bitField0_ = (bitField0_ & ~0x00000010); transactionBuilder_ = com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? getTransactionFieldBuilder() : null; @@ -5909,7 +5940,7 @@ public final class Protos { if (!other.extension_.isEmpty()) { if (extension_.isEmpty()) { extension_ = other.extension_; - bitField0_ = (bitField0_ & ~0x00000010); + bitField0_ = (bitField0_ & ~0x00000020); } else { ensureExtensionIsMutable(); extension_.addAll(other.extension_); @@ -5922,7 +5953,7 @@ public final class Protos { extensionBuilder_.dispose(); extensionBuilder_ = null; extension_ = other.extension_; - bitField0_ = (bitField0_ & ~0x00000010); + bitField0_ = (bitField0_ & ~0x00000020); extensionBuilder_ = com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? getExtensionFieldBuilder() : null; @@ -6006,6 +6037,11 @@ public final class Protos { addTransaction(subBuilder.buildPartial()); break; } + case 40: { + bitField0_ |= 0x00000004; + lastSeenBlockHeight_ = input.readUInt32(); + break; + } case 82: { org.bitcoinj.wallet.Protos.Extension.Builder subBuilder = org.bitcoinj.wallet.Protos.Extension.newBuilder(); input.readMessage(subBuilder, extensionRegistry); @@ -6078,13 +6114,34 @@ public final class Protos { return this; } + // optional uint32 last_seen_block_height = 5; + private int lastSeenBlockHeight_ ; + public boolean hasLastSeenBlockHeight() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + public int getLastSeenBlockHeight() { + return lastSeenBlockHeight_; + } + public Builder setLastSeenBlockHeight(int value) { + bitField0_ |= 0x00000004; + lastSeenBlockHeight_ = value; + onChanged(); + return this; + } + public Builder clearLastSeenBlockHeight() { + bitField0_ = (bitField0_ & ~0x00000004); + lastSeenBlockHeight_ = 0; + onChanged(); + return this; + } + // repeated .wallet.Key key = 3; private java.util.List key_ = java.util.Collections.emptyList(); private void ensureKeyIsMutable() { - if (!((bitField0_ & 0x00000004) == 0x00000004)) { + if (!((bitField0_ & 0x00000008) == 0x00000008)) { key_ = new java.util.ArrayList(key_); - bitField0_ |= 0x00000004; + bitField0_ |= 0x00000008; } } @@ -6200,7 +6257,7 @@ public final class Protos { public Builder clearKey() { if (keyBuilder_ == null) { key_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000004); + bitField0_ = (bitField0_ & ~0x00000008); onChanged(); } else { keyBuilder_.clear(); @@ -6256,7 +6313,7 @@ public final class Protos { keyBuilder_ = new com.google.protobuf.RepeatedFieldBuilder< org.bitcoinj.wallet.Protos.Key, org.bitcoinj.wallet.Protos.Key.Builder, org.bitcoinj.wallet.Protos.KeyOrBuilder>( key_, - ((bitField0_ & 0x00000004) == 0x00000004), + ((bitField0_ & 0x00000008) == 0x00000008), getParentForChildren(), isClean()); key_ = null; @@ -6268,9 +6325,9 @@ public final class Protos { private java.util.List transaction_ = java.util.Collections.emptyList(); private void ensureTransactionIsMutable() { - if (!((bitField0_ & 0x00000008) == 0x00000008)) { + if (!((bitField0_ & 0x00000010) == 0x00000010)) { transaction_ = new java.util.ArrayList(transaction_); - bitField0_ |= 0x00000008; + bitField0_ |= 0x00000010; } } @@ -6386,7 +6443,7 @@ public final class Protos { public Builder clearTransaction() { if (transactionBuilder_ == null) { transaction_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000008); + bitField0_ = (bitField0_ & ~0x00000010); onChanged(); } else { transactionBuilder_.clear(); @@ -6442,7 +6499,7 @@ public final class Protos { transactionBuilder_ = new com.google.protobuf.RepeatedFieldBuilder< org.bitcoinj.wallet.Protos.Transaction, org.bitcoinj.wallet.Protos.Transaction.Builder, org.bitcoinj.wallet.Protos.TransactionOrBuilder>( transaction_, - ((bitField0_ & 0x00000008) == 0x00000008), + ((bitField0_ & 0x00000010) == 0x00000010), getParentForChildren(), isClean()); transaction_ = null; @@ -6454,9 +6511,9 @@ public final class Protos { private java.util.List extension_ = java.util.Collections.emptyList(); private void ensureExtensionIsMutable() { - if (!((bitField0_ & 0x00000010) == 0x00000010)) { + if (!((bitField0_ & 0x00000020) == 0x00000020)) { extension_ = new java.util.ArrayList(extension_); - bitField0_ |= 0x00000010; + bitField0_ |= 0x00000020; } } @@ -6572,7 +6629,7 @@ public final class Protos { public Builder clearExtension() { if (extensionBuilder_ == null) { extension_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000010); + bitField0_ = (bitField0_ & ~0x00000020); onChanged(); } else { extensionBuilder_.clear(); @@ -6628,7 +6685,7 @@ public final class Protos { extensionBuilder_ = new com.google.protobuf.RepeatedFieldBuilder< org.bitcoinj.wallet.Protos.Extension, org.bitcoinj.wallet.Protos.Extension.Builder, org.bitcoinj.wallet.Protos.ExtensionOrBuilder>( extension_, - ((bitField0_ & 0x00000010) == 0x00000010), + ((bitField0_ & 0x00000020) == 0x00000020), getParentForChildren(), isClean()); extension_ = null; @@ -6730,12 +6787,13 @@ public final class Protos { "\007UNSPENT\020\004\022\t\n\005SPENT\020\005\022\014\n\010INACTIVE\020\002\022\010\n\004D" + "EAD\020\n\022\013\n\007PENDING\020\020\022\024\n\020PENDING_INACTIVE\020\022" + "\"8\n\tExtension\022\n\n\002id\030\001 \002(\t\022\014\n\004data\030\002 \002(\014\022" + - "\021\n\tmandatory\030\003 \002(\010\"\254\001\n\006Wallet\022\032\n\022network" + + "\021\n\tmandatory\030\003 \002(\010\"\314\001\n\006Wallet\022\032\n\022network" + "_identifier\030\001 \002(\t\022\034\n\024last_seen_block_has" + - "h\030\002 \001(\014\022\030\n\003key\030\003 \003(\0132\013.wallet.Key\022(\n\013tra" + - "nsaction\030\004 \003(\0132\023.wallet.Transaction\022$\n\te" + - "xtension\030\n \003(\0132\021.wallet.ExtensionB\035\n\023org" + - ".bitcoinj.walletB\006Protos" + "h\030\002 \001(\014\022\036\n\026last_seen_block_height\030\005 \001(\r\022" + + "\030\n\003key\030\003 \003(\0132\013.wallet.Key\022(\n\013transaction" + + "\030\004 \003(\0132\023.wallet.Transaction\022$\n\textension" + + "\030\n \003(\0132\021.wallet.ExtensionB\035\n\023org.bitcoin", + "j.walletB\006Protos" }; com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() { @@ -6803,7 +6861,7 @@ public final class Protos { internal_static_wallet_Wallet_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_wallet_Wallet_descriptor, - new java.lang.String[] { "NetworkIdentifier", "LastSeenBlockHash", "Key", "Transaction", "Extension", }, + new java.lang.String[] { "NetworkIdentifier", "LastSeenBlockHash", "LastSeenBlockHeight", "Key", "Transaction", "Extension", }, org.bitcoinj.wallet.Protos.Wallet.class, org.bitcoinj.wallet.Protos.Wallet.Builder.class); return null; diff --git a/core/src/test/java/com/google/bitcoin/store/WalletProtobufSerializerTest.java b/core/src/test/java/com/google/bitcoin/store/WalletProtobufSerializerTest.java index 4361cc144..5f34d2659 100644 --- a/core/src/test/java/com/google/bitcoin/store/WalletProtobufSerializerTest.java +++ b/core/src/test/java/com/google/bitcoin/store/WalletProtobufSerializerTest.java @@ -129,10 +129,12 @@ public class WalletProtobufSerializerTest { Block block = new Block(params, BlockTest.blockBytes); Sha256Hash blockHash = block.getHash(); wallet.setLastBlockSeenHash(blockHash); + wallet.setLastBlockSeenHeight(1); // Roundtrip the wallet and check it has stored the blockHash. Wallet wallet1 = roundTrip(wallet); assertEquals(blockHash, wallet1.getLastBlockSeenHash()); + assertEquals(1, wallet1.getLastBlockSeenHeight()); // Test the Satoshi genesis block (hash of all zeroes) is roundtripped ok. Block genesisBlock = NetworkParameters.prodNet().genesisBlock;