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;