38 Commits

Author SHA1 Message Date
Andreas Schildbach
2cfa39f2cf Version 0.10.2 2013-10-20 17:22:39 +02:00
Mike Hearn
c808ae7bb4 FullBlockTestGenerator: treat coinbase scriptSig as a 16-bit counter not 8 bit.
This bug led to mysterious failures that only showed up when tests were run in a certain order and the counter happened to wrap around exactly.
2013-10-14 19:34:56 +02:00
Mike Hearn
ca2d847fe7 Don't run confidence listeners if we get duplicate invs from the same peer (can happen if we connect to the same peer IP multiple times). 2013-10-14 13:26:14 +02:00
Mike Hearn
e6ca742057 Wallet: track relative ordering of transactions within a block.
Ensures re-orgs don't replay transactions out of order. Resolves issue 468.
2013-10-14 11:22:32 +02:00
Mike Hearn
f867998c52 Refactor/bugfix broadcast of pending transactions when a peergroup starts up.
Previously the PeerGroup itself would broadcast the pending transactions by simply sending an inv with them all to every peer. This is a good way to get a transaction blasted out if there are no problems with it, but it means we cannot track propagation and the numBroadcastPeers() value was correspondingly not increased. This seems to be causing issues with the Android wallet. So try out a different approach - have the wallet use broadcastTransaction as per normal on the PeerGroup when it's added. The TX will be propagated and watched as with a normal spend.
2013-10-11 17:36:16 +02:00
Mike Hearn
db296193f9 WalletTool: fix typo 2013-10-11 17:35:55 +02:00
Mike Hearn
d6b4b55e83 Use earliest key time minus a week for setting fast catchup time and selecting a checkpoint.
This handles clock drift both in the block headers and possibly wrong times in the users clock (broken timezone, etc).

Resolves issue 460.
2013-10-08 12:32:34 +02:00
Mike Hearn
1d4ff1770e Threading: resolve a race in the unit tests: if the user thread wasn't initialised, don't crash trying to test if we're on it. 2013-10-08 12:32:34 +02:00
Mike Hearn
cb45e306df Script: clone tx before performing correctlySpends check. This prevents thread safety issues and corrupted transactions if validation fails. 2013-10-08 12:32:23 +02:00
Mike Hearn
4b5e8fcdb0 TransactionOutput: tighter checks on values when constructing (don't allow negative values, etc). 2013-10-08 12:05:45 +02:00
Mike Hearn
bce6968aec TransactionInput: better toString 2013-10-08 12:03:43 +02:00
Mike Hearn
511d6310d4 ECKey: don't crash when signing non-ASCII text 2013-10-03 00:02:23 +02:00
Mike Hearn
3f2c372097 Wallet: add a bit more logging to make output during chain splits less confusing. 2013-10-03 00:02:23 +02:00
Mike Hearn
01a55b557f Wallet: another re-org fix. 2013-10-03 00:02:23 +02:00
Mike Hearn
bdada0447f Wallet: use a single hashmap to track all transactions, then use it in receive to re-canonicalize the transactions.
Long story short, I'm a shitty programmer it seems. The Wallet will at some point be modified to track just bags of outputs derived from Transaction objects, and Transactions/Blocks will become immutable. At that point there won't be any confusion between mutable data associated with the deserialised objects.

Resolves issue 453.
2013-10-03 00:02:23 +02:00
Mike Hearn
0d9f8b7867 Make MemoryBlockStore store only a rolling window of the last 5000 blocks. Fixes BuildCheckpoints which was trying to store every block header and running out of heap space. 2013-10-03 00:02:23 +02:00
Mike Hearn
b883d88468 PeerGroup: correct logic for setting ver packet pre-filtering relay flag. 2013-10-03 00:02:23 +02:00
Mike Hearn
e89cc1a41d Make RegTestParams use testnet addresses, to follow sipa's upstream change. 2013-10-03 00:02:22 +02:00
Mike Hearn
0e809b2b31 Wallet: comment for AllowUnconfirmedCoinSelector.get() 2013-10-03 00:02:22 +02:00
Matt Corallo
a26b18c8fd Clarify PeerEventListener JavaDocs a bit 2013-10-02 23:46:58 +02:00
Andreas Schildbach
4d8e9088f2 Prepare 0.10.2-SNAPSHOT 2013-10-02 23:45:26 +02:00
Andreas Schildbach
1134572f61 Version 0.10.1 2013-08-15 14:11:17 +02:00
Mike Hearn
d8f7eab42b Wallet: when "emptying" the wallet, only actually empty out the coins that would be considered selectable by the default coin selector.
By default that means unconfirmed coins won't be emptied, to avoid the empty tx becoming dependent on a tx that may never confirm.

 Resolves issue 438.
2013-08-15 13:54:26 +02:00
Andreas Schildbach
a3bbde87c6 Prepare 0.10.1-SNAPSHOT 2013-08-15 13:53:42 +02:00
Mike Hearn
777e6781d7 Version 0.10 2013-08-11 16:54:32 +02:00
Mike Hearn
4abdf44449 Remove verifier from examples/pom.xml, it requires us to wait for reproducible build support to land. 2013-08-11 16:50:24 +02:00
Mike Hearn
a0edf70bc3 Move verifier XML around. 2013-08-11 16:50:24 +02:00
Mike Hearn
c261f75e8a Wallet: support for key rotation.
Key rotation allows you to specify a timestamp, and any money controlled by any keys created before that time will be automatically respent to keys created after it.
2013-08-11 16:50:24 +02:00
Mike Hearn
6680846949 PeerGroupTest: Fix a race. 2013-08-11 16:50:24 +02:00
Mike Hearn
1e227e521a Wallet: allow SendRequests to override the default coin selector. 2013-08-11 16:50:24 +02:00
Mike Hearn
1c8be1cc69 PeerGroup/Wallet: give the wallet a reference to a transaction broadcaster, so it can make its own transactions and broadcast them outside the context of a user initiated spend.
Later, we can change the mechanism used to broadcast pending transactions so the wallet does that itself.
2013-08-11 16:50:24 +02:00
Mike Hearn
91fa22181a Wallet: Minor nullity annotation and param genericity tweaks. 2013-08-11 16:50:24 +02:00
Matt Corallo
51186b8fc1 Fix manually-added transactions that make it into blocks. 2013-08-11 16:50:24 +02:00
Mike Hearn
bfcbe7f298 Add a remark to the BloomFilter javadocs about when you would want to use full-match filters. 2013-08-11 16:50:23 +02:00
Matt Corallo
2d1479eca9 Fix block tests 2013-08-11 16:50:23 +02:00
Matt Corallo
79f093f0c4 Implement mempool-test support in BitcoindComparisonTool 2013-08-11 16:50:23 +02:00
Matt Corallo
1fda444af7 InventoryItem.hashCode()
Conflicts:
	core/src/main/java/com/google/bitcoin/core/InventoryItem.java
2013-08-11 16:50:23 +02:00
Matt Corallo
75cbaed15c Make BloomFilter support match-all filters better 2013-08-11 16:50:23 +02:00
46 changed files with 1832 additions and 588 deletions

View File

@@ -22,7 +22,7 @@
<parent>
<groupId>com.google</groupId>
<artifactId>bitcoinj-parent</artifactId>
<version>0.10-SNAPSHOT</version>
<version>0.10.2</version>
</parent>
<artifactId>bitcoinj</artifactId>
@@ -104,6 +104,62 @@
</execution>
</executions>
</plugin>
<!-- Verify the dependency chain: see https://github.com/gary-rowe/BitcoinjEnforcerRules for
more information on this.
-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>1.2</version>
<executions>
<execution>
<id>enforce</id>
<phase>verify</phase>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<digestRule implementation="uk.co.froot.maven.enforcer.DigestRule">
<!-- Create a snapshot to build the list of URNs below -->
<buildSnapshot>true</buildSnapshot>
<!-- List of required hashes -->
<!-- Format is URN of groupId:artifactId:version:type:classifier:scope:hash -->
<!-- classifier is "null" if not present -->
<urns>
<urn>com.google.code.findbugs:jsr305:1.3.9:jar:null:compile:40719ea6961c0cb6afaeb6a921eaa1f6afd4cfdf</urn>
<urn>com.google.guava:guava:13.0.1:jar:null:compile:0d6f22b1e60a2f1ef99e22c9f5fde270b2088365</urn>
<urn>com.google.protobuf:protobuf-java:2.4.1:jar:null:compile:0c589509ec6fd86d5d2fda37e07c08538235d3b9</urn>
<urn>com.h2database:h2:1.3.167:jar:null:compile:d3867d586f087e53eb12fc65e5693d8ee9a5da17</urn>
<urn>com.lambdaworks:scrypt:1.3.3:jar:null:compile:06d6813de41e177189e1722717979b4fb5454b1d</urn>
<urn>com.madgag:sc-light-jdk15on:1.47.0.2:jar:null:compile:d5c98671cc97fa0d928be1c7eb5edd3fb95d3234</urn>
<urn>io.netty:netty:3.6.3.Final:jar:null:compile:1eebfd2f79dd72c44d09d9917c549c60322462b8</urn>
<urn>net.jcip:jcip-annotations:1.0:jar:null:compile:afba4942caaeaf46aab0b976afd57cc7c181467e</urn>
<urn>net.sf.jopt-simple:jopt-simple:4.3:jar:null:compile:88ffca34311a6564a98f14820431e17b4382a069</urn>
<urn>org.slf4j:slf4j-api:1.6.4:jar:null:compile:2396d74b12b905f780ed7966738bb78438e8371a</urn>
<urn>org.slf4j:slf4j-jdk14:1.6.4:jar:null:runtime:6b32bc7c42b2509525ce812cb49bf96e7bf64141</urn>
<!-- A check for the rules themselves -->
<urn>uk.co.froot.maven.enforcer:digest-enforcer-rules:0.0.1:jar:null:runtime:16a9e04f3fe4bb143c42782d07d5faf65b32106f</urn>
</urns>
</digestRule>
</rules>
</configuration>
</execution>
</executions>
<!-- Ensure we download the enforcer rules -->
<dependencies>
<dependency>
<groupId>uk.co.froot.maven.enforcer</groupId>
<artifactId>digest-enforcer-rules</artifactId>
<version>0.0.1</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>

View File

@@ -185,11 +185,27 @@ message Transaction {
repeated TransactionInput transaction_input = 6;
repeated TransactionOutput transaction_output = 7;
// A list of blocks in which the transaction has been observed (on any chain).
// A list of blocks in which the transaction has been observed (on any chain). Also, a number used to disambiguate
// ordering within a block.
repeated bytes block_hash = 8;
repeated int32 block_relativity_offsets = 11;
// Data describing where the transaction is in the chain.
optional TransactionConfidence confidence = 9;
// For what purpose the transaction was created.
enum Purpose {
// Old wallets or the purpose genuinely is a mystery (e.g. imported from some external source).
UNKNOWN = 0;
// Created in response to a user request for payment. This is the normal case.
USER_PAYMENT = 1;
// Created automatically to move money from rotated keys.
KEY_ROTATION = 2;
// In future: de/refragmentation, privacy boosting/mixing, child-pays-for-parent fees, etc.
}
optional Purpose purpose = 10 [default = UNKNOWN];
// Next tag: 12
}
/** The parameters used in the scrypt key derivation function.
@@ -257,4 +273,9 @@ message Wallet {
optional string description = 11;
// (The field number 12 is used by last_seen_block_height)
} // end of Wallet
// UNIX time in seconds since the epoch. If set, then any keys created before this date are assumed to be no longer
// wanted. Money sent to them will be re-spent automatically to the first key that was created after this time. It
// can be used to recover a compromised wallet, or just as part of preventative defence-in-depth measures.
optional uint64 key_rotation_time = 13;
}

View File

@@ -529,18 +529,21 @@ public abstract class AbstractBlockChain {
// is relevant to both of them, they don't end up accidentally sharing the same object (which can
// result in temporary in-memory corruption during re-orgs). See bug 257. We only duplicate in
// the case of multiple wallets to avoid an unnecessary efficiency hit in the common case.
sendTransactionsToListener(newStoredBlock, newBlockType, listener, block.transactions, !first);
sendTransactionsToListener(newStoredBlock, newBlockType, listener, 0, block.transactions, !first);
} else if (filteredTxHashList != null) {
checkArgument(filteredTxn != null);
checkNotNull(filteredTxn);
// We must send transactions to listeners in the order they appeared in the block - thus we iterate over the
// set of hashes and call sendTransactionsToListener with individual txn when they have not already been
// seen in loose broadcasts - otherwise notifyTransactionIsInBlock on the hash
// seen in loose broadcasts - otherwise notifyTransactionIsInBlock on the hash.
int relativityOffset = 0;
for (Sha256Hash hash : filteredTxHashList) {
Transaction tx = filteredTxn.get(hash);
if (tx != null)
sendTransactionsToListener(newStoredBlock, newBlockType, listener, Arrays.asList(tx), !first);
sendTransactionsToListener(newStoredBlock, newBlockType, listener, relativityOffset,
Arrays.asList(tx), !first);
else
listener.notifyTransactionIsInBlock(hash, newStoredBlock, newBlockType);
listener.notifyTransactionIsInBlock(hash, newStoredBlock, newBlockType, relativityOffset);
relativityOffset++;
}
}
}
@@ -702,6 +705,7 @@ public abstract class AbstractBlockChain {
private static void sendTransactionsToListener(StoredBlock block, NewBlockType blockType,
BlockChainListener listener,
int relativityOffset,
List<Transaction> transactions,
boolean clone) throws VerificationException {
for (Transaction tx : transactions) {
@@ -709,7 +713,7 @@ public abstract class AbstractBlockChain {
if (listener.isTransactionRelevant(tx)) {
if (clone)
tx = new Transaction(tx.params, tx.bitcoinSerialize());
listener.receiveFromBlock(tx, block, blockType);
listener.receiveFromBlock(tx, block, blockType, relativityOffset++);
}
} catch (ScriptException e) {
// We don't want scripts we don't understand to break the block chain so just note that this tx was

View File

@@ -22,19 +22,26 @@ import java.util.List;
* Default no-op implementation of {@link BlockChainListener}.
*/
public class AbstractBlockChainListener implements BlockChainListener {
@Override
public void notifyNewBestBlock(StoredBlock block) throws VerificationException {
}
@Override
public void reorganize(StoredBlock splitPoint, List<StoredBlock> oldBlocks, List<StoredBlock> newBlocks) throws VerificationException {
}
@Override
public boolean isTransactionRelevant(Transaction tx) throws ScriptException {
return false;
}
public void receiveFromBlock(Transaction tx, StoredBlock block, BlockChain.NewBlockType blockType) throws VerificationException {
@Override
public void receiveFromBlock(Transaction tx, StoredBlock block, BlockChain.NewBlockType blockType,
int relativityOffset) throws VerificationException {
}
public void notifyTransactionIsInBlock(Sha256Hash txHash, StoredBlock block, BlockChain.NewBlockType blockType) throws VerificationException {
@Override
public void notifyTransactionIsInBlock(Sha256Hash txHash, StoredBlock block, BlockChain.NewBlockType blockType,
int relativityOffset) throws VerificationException {
}
}

View File

@@ -59,18 +59,30 @@ public interface BlockChainListener {
* <p>A transaction may be received multiple times if is included into blocks in parallel chains. The blockType
* parameter describes whether the containing block is on the main/best chain or whether it's on a presently
* inactive side chain.</p>
*
* <p>The relativityOffset parameter is an arbitrary number used to establish an ordering between transactions
* within the same block. In the case where full blocks are being downloaded, it is simply the index of the
* transaction within that block. When Bloom filtering is in use, we don't find out the exact offset into a block
* that a transaction occurred at, so the relativity count is not reflective of anything in an absolute sense but
* rather exists only to order the transaction relative to the others.</p>
*/
void receiveFromBlock(Transaction tx, StoredBlock block,
BlockChain.NewBlockType blockType) throws VerificationException;
BlockChain.NewBlockType blockType,
int relativityOffset) throws VerificationException;
/**
* <p>Called by the {@link BlockChain} when we receive a new filtered block that contains the given transaction
* hash in its merkle tree.</p>
* <p>Called by the {@link BlockChain} when we receive a new {@link FilteredBlock} that contains the given
* transaction hash in its merkle tree.</p>
*
* <p>A transaction may be received multiple times if is included into blocks in parallel chains. The blockType
* parameter describes whether the containing block is on the main/best chain or whether it's on a presently
* inactive side chain.</p>
*
* <p>The relativityOffset parameter in this case is an arbitrary (meaningless) number, that is useful only when
* compared to the relativity count of another transaction received inside the same block. It is used to establish
* an ordering of transactions relative to one another.</p>
*/
void notifyTransactionIsInBlock(Sha256Hash txHash, StoredBlock block,
BlockChain.NewBlockType blockType) throws VerificationException;
BlockChain.NewBlockType blockType,
int relativityOffset) throws VerificationException;
}

View File

@@ -17,12 +17,13 @@
package com.google.bitcoin.core;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays;
import static com.google.common.base.Preconditions.checkArgument;
/**
* <p>A Bloom filter is a probabilistic data structure which can be sent to another client so that it can avoid
* sending us transactions that aren't relevant to our set of keys. This allows for significantly more efficient
@@ -233,15 +234,41 @@ public class BloomFilter extends Message {
}
/**
* Copies filter into this.
* filter must have the same size, hash function count and nTweak or an exception will be thrown.
* Sets this filter to match all objects. A Bloom filter which matches everything may seem pointless, however,
* it is useful in order to reduce steady state bandwidth usage when you want full blocks. Instead of receiving
* all transaction data twice, you will receive the vast majority of all transactions just once, at broadcast time.
* Solved blocks will then be send just as Merkle trees of tx hashes, meaning a constant 32 bytes of data for each
* transaction instead of 100-300 bytes as per usual.
*/
public void setMatchAll() {
data = new byte[] {(byte) 0xff};
}
/**
* Copies filter into this. Filter must have the same size, hash function count and nTweak or an
* IllegalArgumentException will be thrown.
*/
public void merge(BloomFilter filter) {
Preconditions.checkArgument(filter.data.length == this.data.length &&
filter.hashFuncs == this.hashFuncs &&
filter.nTweak == this.nTweak);
for (int i = 0; i < data.length; i++)
this.data[i] |= filter.data[i];
if (!this.matchesAll() && !filter.matchesAll()) {
checkArgument(filter.data.length == this.data.length &&
filter.hashFuncs == this.hashFuncs &&
filter.nTweak == this.nTweak);
for (int i = 0; i < data.length; i++)
this.data[i] |= filter.data[i];
} else {
this.data = new byte[] {(byte) 0xff};
}
}
/**
* Returns true if this filter will match anything. See {@link com.google.bitcoin.core.BloomFilter#setMatchAll()}
* for when this can be a useful thing to do.
*/
public boolean matchesAll() {
for (byte b : data)
if (b != (byte) 0xff)
return false;
return true;
}
@Override

View File

@@ -22,7 +22,10 @@ import com.google.bitcoin.store.FullPrunedBlockStore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.security.DigestInputStream;
import java.security.MessageDigest;
@@ -34,22 +37,33 @@ import java.util.TreeMap;
import static com.google.common.base.Preconditions.*;
/**
* <p>Vends hard-coded {@link StoredBlock}s for blocks throughout the chain. Checkpoints serve several purposes:</p>
* <p>Vends hard-coded {@link StoredBlock}s for blocks throughout the chain. Checkpoints serve two purposes:</p>
* <ol>
* <li>They act as a safety mechanism against huge re-orgs that could rewrite large chunks of history, thus
* constraining the block chain to be a consensus mechanism only for recent parts of the timeline.</li>
* <li>They allow synchronization to the head of the chain for new wallets/users much faster than syncing all
* headers from the genesis block.</li>
* <li>They mark each BIP30-violating block, which simplifies full verification logic quite significantly. BIP30
* handles the case of blocks that contained duplicated coinbase transactions.</li>
* </ol>
*
* <p>Checkpoints are used by a {@link BlockChain} to initialize fresh {@link com.google.bitcoin.store.SPVBlockStore}s,
* and by {@link FullPrunedBlockChain} to prevent re-orgs beyond them.</p>
* <p>Checkpoints are used by the SPV {@link BlockChain} to initialize fresh
* {@link com.google.bitcoin.store.SPVBlockStore}s. They are not used by fully validating mode, which instead has a
* different concept of checkpoints that are used to hard-code the validity of blocks that violate BIP30 (duplicate
* coinbase transactions). Those "checkpoints" can be found in NetworkParameters.</p>
*
* <p>The file format consists of the string "CHECKPOINTS 1", followed by a uint32 containing the number of signatures
* to read. The value may not be larger than 256 (so it could have been a byte but isn't for historical reasons).
* If the number of signatures is larger than zero, each 65 byte ECDSA secp256k1 signature then follows. The signatures
* sign the hash of all bytes that follow the last signature.</p>
*
* <p>After the signatures come an int32 containing the number of checkpoints in the file. Then each checkpoint follows
* one after the other. A checkpoint is 12 bytes for the total work done field, 4 bytes for the height, 80 bytes
* for the block header and then 1 zero byte at the end (i.e. number of transactions in the block: always zero).</p>
*/
public class CheckpointManager {
private static final Logger log = LoggerFactory.getLogger(CheckpointManager.class);
private static final int MAX_SIGNATURES = 256;
// Map of block header time to data.
protected final TreeMap<Long, StoredBlock> checkpoints = new TreeMap<Long, StoredBlock>();
@@ -58,10 +72,11 @@ public class CheckpointManager {
public CheckpointManager(NetworkParameters params, InputStream inputStream) throws IOException {
this.params = checkNotNull(params);
checkNotNull(inputStream);
DataInputStream dis = null;
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
DigestInputStream digestInputStream = new DigestInputStream(checkNotNull(inputStream), digest);
DigestInputStream digestInputStream = new DigestInputStream(inputStream, digest);
dis = new DataInputStream(digestInputStream);
digestInputStream.on(false);
String magic = "CHECKPOINTS 1";
@@ -69,7 +84,7 @@ public class CheckpointManager {
dis.readFully(header);
if (!Arrays.equals(header, magic.getBytes("US-ASCII")))
throw new IOException("Header bytes did not match expected version");
int numSignatures = dis.readInt();
int numSignatures = checkPositionIndex(dis.readInt(), MAX_SIGNATURES, "Num signatures out of range");
for (int i = 0; i < numSignatures; i++) {
byte[] sig = new byte[65];
dis.readFully(sig);
@@ -104,18 +119,16 @@ public class CheckpointManager {
* you would want to know the checkpoint before the earliest wallet birthday.
*/
public StoredBlock getCheckpointBefore(long time) {
checkArgument(time > params.getGenesisBlock().getTimeSeconds());
// This is thread safe because the map never changes after creation.
Map.Entry<Long, StoredBlock> entry = checkpoints.floorEntry(time);
if (entry == null) {
try {
Block genesis = params.getGenesisBlock().cloneAsHeader();
return new StoredBlock(genesis, genesis.getWork(), 0);
} catch (VerificationException e) {
throw new RuntimeException(e); // Cannot happen.
}
try {
checkArgument(time > params.getGenesisBlock().getTimeSeconds());
// This is thread safe because the map never changes after creation.
Map.Entry<Long, StoredBlock> entry = checkpoints.floorEntry(time);
if (entry != null) return entry.getValue();
Block genesis = params.getGenesisBlock().cloneAsHeader();
return new StoredBlock(genesis, genesis.getWork(), 0);
} catch (VerificationException e) {
throw new RuntimeException(e); // Cannot happen.
}
return entry.getValue();
}
/** Returns the number of checkpoints that were loaded. */
@@ -129,15 +142,20 @@ public class CheckpointManager {
}
/**
* Convenience method that creates a CheckpointManager, loads the given data, gets the checkpoint for the given
* <p>Convenience method that creates a CheckpointManager, loads the given data, gets the checkpoint for the given
* time, then inserts it into the store and sets that to be the chain head. Useful when you have just created
* a new store from scratch and want to use configure it all in one go.
* a new store from scratch and want to use configure it all in one go.</p>
*
* <p>Note that time is adjusted backwards by a week to account for possible clock drift in the block headers.</p>
*/
public static void checkpoint(NetworkParameters params, InputStream checkpoints, BlockStore store, long time)
throws IOException, BlockStoreException {
checkNotNull(params);
checkNotNull(store);
checkArgument(!(store instanceof FullPrunedBlockStore), "You cannot use checkpointing with a full store.");
time -= 86400 * 7;
BufferedInputStream stream = new BufferedInputStream(checkpoints);
CheckpointManager manager = new CheckpointManager(params, stream);
StoredBlock checkpoint = manager.getCheckpointBefore(time);

View File

@@ -33,7 +33,7 @@ public class FilteredBlock extends Message {
private PartialMerkleTree merkleTree;
private List<Sha256Hash> cachedTransactionHashes = null;
// A set of transactions who's hashes are a subset of getTransactionHashes()
// A set of transactions whose hashes are a subset of getTransactionHashes()
// These were relayed as a part of the filteredblock getdata, ie likely weren't previously received as loose transactions
private Map<Sha256Hash, Transaction> associatedTransactions = new HashMap<Sha256Hash, Transaction>();

View File

@@ -42,4 +42,14 @@ public class InventoryItem {
public String toString() {
return type.toString() + ": " + hash;
}
public int hashCode() {
return hash.hashCode() + type.ordinal();
}
public boolean equals(Object o) {
return o instanceof InventoryItem &&
((InventoryItem)o).type == this.type &&
((InventoryItem)o).hash.equals(this.hash);
}
}

View File

@@ -268,8 +268,8 @@ public class MemoryPool {
private void markBroadcast(PeerAddress byPeer, Transaction tx) {
checkState(lock.isHeldByCurrentThread());
final TransactionConfidence confidence = tx.getConfidence();
confidence.markBroadcastBy(byPeer);
confidence.queueListeners(TransactionConfidence.Listener.ChangeReason.SEEN_PEERS);
if (confidence.markBroadcastBy(byPeer))
confidence.queueListeners(TransactionConfidence.Listener.ChangeReason.SEEN_PEERS);
}
/**

View File

@@ -505,6 +505,11 @@ public abstract class Message implements Serializable {
return cursor < bytes.length;
}
/** Network parameters this message was created with. */
public NetworkParameters getParams() {
return params;
}
public static class LazyParseException extends RuntimeException {
private static final long serialVersionUID = 6971943053112975594L;

View File

@@ -44,7 +44,8 @@ public interface PeerEventListener {
public void onChainDownloadStarted(Peer peer, int blocksLeft);
/**
* Called when a peer is connected
* Called when a peer is connected. If this listener is registered to a {@link Peer} instead of a {@link PeerGroup},
* this will never be called.
*
* @param peer
* @param peerCount the total number of connected peers
@@ -53,7 +54,8 @@ public interface PeerEventListener {
/**
* Called when a peer is disconnected. Note that this won't be called if the listener is registered on a
* {@link PeerGroup} and the group is in the process of shutting down.
* {@link PeerGroup} and the group is in the process of shutting down. If this listener is registered to a
* {@link Peer} instead of a {@link PeerGroup}, this will never be called.
*
* @param peer
* @param peerCount the total number of connected peers
@@ -61,10 +63,13 @@ public interface PeerEventListener {
public void onPeerDisconnected(Peer peer, int peerCount);
/**
* Called when a message is received by a peer, before the message is processed. The returned message is
* <p>Called when a message is received by a peer, before the message is processed. The returned message is
* processed instead. Returning null will cause the message to be ignored by the Peer returning the same message
* object allows you to see the messages received but not change them. The result from one event listeners
* callback is passed as "m" to the next, forming a chain.
* callback is passed as "m" to the next, forming a chain.</p>
*
* <p>Note that this will never be called if registered with any executor other than
* {@link com.google.bitcoin.utils.Threading#SAME_THREAD}</p>
*/
public Message onPreMessageReceived(Peer peer, Message m);

View File

@@ -402,7 +402,9 @@ public class PeerGroup extends AbstractIdleService implements TransactionBroadca
// Note that the default here means that no tx invs will be received if no wallet is ever added
lock.lock();
try {
ver.relayTxesBeforeFilter = chain != null && chain.shouldVerifyTransactions() && peerFilterProviders.size() > 0;
boolean spvMode = chain != null && !chain.shouldVerifyTransactions();
boolean willSendFilter = spvMode && peerFilterProviders.size() > 0;
ver.relayTxesBeforeFilter = !willSendFilter;
} finally {
lock.unlock();
}
@@ -618,13 +620,11 @@ public class PeerGroup extends AbstractIdleService implements TransactionBroadca
public void addWallet(Wallet wallet) {
lock.lock();
try {
Preconditions.checkNotNull(wallet);
Preconditions.checkState(!wallets.contains(wallet));
checkNotNull(wallet);
checkState(!wallets.contains(wallet));
wallets.add(wallet);
announcePendingWalletTransactions(Collections.singletonList(wallet), peers);
wallet.setTransactionBroadcaster(this);
wallet.addEventListener(walletEventListener); // TODO: Run this in the current peer thread.
addPeerFilterProvider(wallet);
} finally {
lock.unlock();
@@ -641,8 +641,8 @@ public class PeerGroup extends AbstractIdleService implements TransactionBroadca
public void addPeerFilterProvider(PeerFilterProvider provider) {
lock.lock();
try {
Preconditions.checkNotNull(provider);
Preconditions.checkState(!peerFilterProviders.contains(provider));
checkNotNull(provider);
checkState(!peerFilterProviders.contains(provider));
peerFilterProviders.add(provider);
// Don't bother downloading block bodies before the oldest keys in all our wallets. Make sure we recalculate
@@ -663,6 +663,7 @@ public class PeerGroup extends AbstractIdleService implements TransactionBroadca
wallets.remove(checkNotNull(wallet));
peerFilterProviders.remove(wallet);
wallet.removeEventListener(walletEventListener);
wallet.setTransactionBroadcaster(null);
}
/**
@@ -675,10 +676,10 @@ public class PeerGroup extends AbstractIdleService implements TransactionBroadca
// Fully verifying mode doesn't use this optimization (it can't as it needs to see all transactions).
if (chain != null && chain.shouldVerifyTransactions())
return;
long earliestKeyTime = Long.MAX_VALUE;
long earliestKeyTimeSecs = Long.MAX_VALUE;
int elements = 0;
for (PeerFilterProvider p : peerFilterProviders) {
earliestKeyTime = Math.min(earliestKeyTime, p.getEarliestKeyCreationTime());
earliestKeyTimeSecs = Math.min(earliestKeyTimeSecs, p.getEarliestKeyCreationTime());
elements += p.getBloomFilterElementCount();
}
@@ -701,8 +702,13 @@ public class PeerGroup extends AbstractIdleService implements TransactionBroadca
}
}
}
// Now adjust the earliest key time backwards by a week to handle the case of clock drift. This can occur
// both in block header timestamps and if the users clock was out of sync when the key was first created
// (to within a small amount of tolerance).
earliestKeyTimeSecs -= 86400 * 7;
// Do this last so that bloomFilter is already set when it gets called.
setFastCatchupTimeSecs(earliestKeyTime);
setFastCatchupTimeSecs(earliestKeyTimeSecs);
} finally {
lock.unlock();
}
@@ -861,16 +867,6 @@ public class PeerGroup extends AbstractIdleService implements TransactionBroadca
}
// Make sure the peer knows how to upload transactions that are requested from us.
peer.addEventListener(getDataListener, Threading.SAME_THREAD);
// Now tell the peers about any transactions we have which didn't appear in the chain yet. These are not
// necessarily spends we created. They may also be transactions broadcast across the network that we saw,
// which are relevant to us, and which we therefore wish to help propagate (ie they send us coins).
//
// Note that this can cause a DoS attack against us if a malicious remote peer knows what keys we own, and
// then sends us fake relevant transactions. We'll attempt to relay the bad transactions, our badness score
// in the Satoshi client will increase and we'll get disconnected.
//
// TODO: Find a way to balance the desire to propagate useful transactions against DoS attacks.
announcePendingWalletTransactions(wallets, Collections.singletonList(peer));
// And set up event listeners for clients. This will allow them to find out about new transactions and blocks.
for (ListenerRegistration<PeerEventListener> registration : peerEventListeners) {
peer.addEventListener(registration.listener, registration.executor);
@@ -943,30 +939,6 @@ public class PeerGroup extends AbstractIdleService implements TransactionBroadca
pingRunnable[0].run();
}
/** Returns true if at least one peer received an inv. */
private boolean announcePendingWalletTransactions(List<Wallet> announceWallets,
List<Peer> announceToPeers) {
checkState(lock.isHeldByCurrentThread());
// Build up an inv announcing the hashes of all pending transactions in all our wallets.
InventoryMessage inv = new InventoryMessage(params);
for (Wallet w : announceWallets) {
for (Transaction tx : w.getPendingTransactions()) {
inv.addTransaction(tx);
}
}
// Don't send empty inv messages.
if (inv.getItems().size() == 0) {
return true;
}
boolean success = false;
for (Peer p : announceToPeers) {
log.info("{}: Announcing {} pending wallet transactions", p.getAddress(), inv.getItems().size());
p.sendMessage(inv);
success = true;
}
return success;
}
private void setDownloadPeer(Peer peer) {
lock.lock();
try {

View File

@@ -22,6 +22,7 @@ import com.google.bitcoin.script.Script;
import com.google.bitcoin.script.ScriptBuilder;
import com.google.bitcoin.script.ScriptOpCodes;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongycastle.crypto.params.KeyParameter;
@@ -32,6 +33,8 @@ import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import javax.annotation.Nullable;
import static com.google.bitcoin.core.Utils.*;
/**
@@ -90,13 +93,14 @@ public class Transaction extends ChildMessage implements Serializable {
// Data about how confirmed this tx is. Serialized, may be null.
private TransactionConfidence confidence;
// This records which blocks the transaction has been included in. For most transactions this set will have a
// single member. In the case of a chain split a transaction may appear in multiple blocks but only one of them
// is part of the best chain. It's not valid to have an identical transaction appear in two blocks in the same chain
// but this invariant is expensive to check, so it's not directly enforced anywhere.
// Records a map of which blocks the transaction has appeared in (keys) to an index within that block (values).
// The "index" is not a real index, instead the values are only meaningful relative to each other. For example,
// consider two transactions that appear in the same block, t1 and t2, where t2 spends an output of t1. Both
// will have the same block hash as a key in their appearsInHashes, but the counter would be 1 and 2 respectively
// regardless of where they actually appeared in the block.
//
// If this transaction is not stored in the wallet, appearsInHashes is null.
private Set<Sha256Hash> appearsInHashes;
private Map<Sha256Hash, Integer> appearsInHashes;
// Transactions can be encoded in a way that will use more bytes than is optimal
// (due to VarInts having multiple encodings)
@@ -105,6 +109,23 @@ public class Transaction extends ChildMessage implements Serializable {
// can properly keep track of optimal encoded size
private transient int optimalEncodingMessageSize;
/**
* This enum describes the underlying reason the transaction was created. It's useful for rendering wallet GUIs
* more appropriately.
*/
public enum Purpose {
/** Used when the purpose of a transaction is genuinely unknown. */
UNKNOWN,
/** Transaction created to satisfy a user payment request. */
USER_PAYMENT,
/** Transaction automatically created and broadcast in order to reallocate money from old to new keys. */
KEY_ROTATION,
// In future: de/refragmentation, privacy boosting/mixing, child-pays-for-parent fees, etc.
}
private Purpose purpose = Purpose.UNKNOWN;
public Transaction(NetworkParameters params) {
super(params);
version = 1;
@@ -237,11 +258,13 @@ public class Transaction extends ChildMessage implements Serializable {
}
/**
* Returns a set of blocks which contain the transaction, or null if this transaction doesn't have that data
* because it's not stored in the wallet or because it has never appeared in a block.
* Returns a map of block [hashes] which contain the transaction mapped to relativity counters, or null if this
* transaction doesn't have that data because it's not stored in the wallet or because it has never appeared in a
* block.
*/
public Collection<Sha256Hash> getAppearsInHashes() {
return appearsInHashes;
@Nullable
public Map<Sha256Hash, Integer> getAppearsInHashes() {
return appearsInHashes != null ? ImmutableMap.copyOf(appearsInHashes) : null;
}
/**
@@ -253,7 +276,7 @@ public class Transaction extends ChildMessage implements Serializable {
}
/**
* <p>Puts the given block in the internal serializable set of blocks in which this transaction appears. This is
* <p>Puts the given block in the internal set of blocks in which this transaction appears. This is
* used by the wallet to ensure transactions that appear on side chains are recorded properly even though the
* block stores do not save the transaction data at all.</p>
*
@@ -264,14 +287,15 @@ public class Transaction extends ChildMessage implements Serializable {
*
* @param block The {@link StoredBlock} in which the transaction has appeared.
* @param bestChain whether to set the updatedAt timestamp from the block header (only if not already set)
* @param relativityOffset A number that disambiguates the order of transactions within a block.
*/
public void setBlockAppearance(StoredBlock block, boolean bestChain) {
public void setBlockAppearance(StoredBlock block, boolean bestChain, int relativityOffset) {
long blockTime = block.getHeader().getTimeSeconds() * 1000;
if (bestChain && (updatedAt == null || updatedAt.getTime() == 0 || updatedAt.getTime() > blockTime)) {
updatedAt = new Date(blockTime);
}
addBlockAppearance(block.getHeader().getHash());
addBlockAppearance(block.getHeader().getHash(), relativityOffset);
if (bestChain) {
TransactionConfidence transactionConfidence = getConfidence();
@@ -286,11 +310,12 @@ public class Transaction extends ChildMessage implements Serializable {
}
}
public void addBlockAppearance(final Sha256Hash blockHash) {
public void addBlockAppearance(final Sha256Hash blockHash, int relativityOffset) {
if (appearsInHashes == null) {
appearsInHashes = new HashSet<Sha256Hash>();
// TODO: This could be a lot more memory efficient as we'll typically only store one element.
appearsInHashes = new TreeMap<Sha256Hash, Integer>();
}
appearsInHashes.add(blockHash);
appearsInHashes.put(blockHash, relativityOffset);
}
/**
@@ -972,7 +997,7 @@ public class Transaction extends ChildMessage implements Serializable {
// that position are "nulled out". Unintuitively, the value in a "null" transaction is set to -1.
this.outputs = new ArrayList<TransactionOutput>(this.outputs.subList(0, inputIndex + 1));
for (int i = 0; i < inputIndex; i++)
this.outputs.set(i, new TransactionOutput(params, this, BigInteger.valueOf(-1), new byte[] {}));
this.outputs.set(i, new TransactionOutput(params, this, NEGATIVE_ONE, new byte[] {}));
// The signature isn't broken by new versions of the transaction issued by other parties.
for (int i = 0; i < inputs.size(); i++)
if (i != inputIndex)
@@ -1212,4 +1237,20 @@ public class Transaction extends ChildMessage implements Serializable {
else
return new Date(getLockTime()*1000);
}
/**
* Returns the purpose for which this transaction was created. See the javadoc for {@link Purpose} for more
* information on the point of this field and what it can be.
*/
public Purpose getPurpose() {
return purpose;
}
/**
* Marks the transaction as being created for the given purpose. See the javadoc for {@link Purpose} for more
* information on the point of this field and what it can be.
*/
public void setPurpose(Purpose purpose) {
this.purpose = purpose;
}
}

View File

@@ -259,12 +259,13 @@ public class TransactionConfidence implements Serializable {
*
* @param address IP address of the peer, used as a proxy for identity.
*/
public synchronized void markBroadcastBy(PeerAddress address) {
public synchronized boolean markBroadcastBy(PeerAddress address) {
if (!broadcastBy.addIfAbsent(address))
return; // Duplicate.
return false; // Duplicate.
if (getConfidenceType() == ConfidenceType.UNKNOWN) {
this.confidenceType = ConfidenceType.PENDING;
}
return true;
}
/**

View File

@@ -259,9 +259,7 @@ public class TransactionInput extends ChildMessage implements Serializable {
if (isCoinBase())
return "TxIn: COINBASE";
try {
return "TxIn from tx " + outpoint + " (pubkey: " + Utils.bytesToHexString(getScriptSig().getPubKey()) +
") script:" +
getScriptSig().toString();
return "TxIn for [" + outpoint + "]: " + getScriptSig();
} catch (ScriptException e) {
throw new RuntimeException(e);
}

View File

@@ -21,12 +21,14 @@ import com.google.bitcoin.script.ScriptBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.math.BigInteger;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
@@ -106,6 +108,10 @@ public class TransactionOutput extends ChildMessage implements Serializable {
public TransactionOutput(NetworkParameters params, Transaction parent, BigInteger value, byte[] scriptBytes) {
super(params);
// Negative values obviously make no sense, except for -1 which is used as a sentinel value when calculating
// SIGHASH_SINGLE signatures, so unfortunately we have to allow that here.
checkArgument(value.compareTo(BigInteger.ZERO) >= 0 || value.equals(Utils.NEGATIVE_ONE), "Negative values not allowed");
checkArgument(value.compareTo(NetworkParameters.MAX_MONEY) < 0, "Values larger than MAX_MONEY not allowed");
this.value = value;
this.scriptBytes = scriptBytes;
parentTransaction = parent;
@@ -283,6 +289,14 @@ public class TransactionOutput extends ChildMessage implements Serializable {
return spentBy;
}
/**
* Returns the transaction that owns this output, or null if this is a free standing object.
*/
@Nullable
public Transaction getParentTransaction() {
return parentTransaction;
}
/**
* Ensure object is fully parsed before invoking java serialization. The backing byte array
* is transient so if the object has parseLazy = true and hasn't invoked checkParse yet

View File

@@ -16,15 +16,16 @@
package com.google.bitcoin.core;
import com.google.common.base.Charsets;
import com.google.common.primitives.UnsignedLongs;
import org.spongycastle.crypto.digests.RIPEMD160Digest;
import org.spongycastle.util.encoders.Hex;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
@@ -37,6 +38,7 @@ import static com.google.common.base.Preconditions.checkArgument;
* To enable debug logging from the library, run with -Dbitcoinj.logging=true on your command line.
*/
public class Utils {
public static final BigInteger NEGATIVE_ONE = BigInteger.valueOf(-1);
private static final MessageDigest digest;
static {
try {
@@ -48,6 +50,7 @@ public class Utils {
/** The string that prefixes all text messages signed using Bitcoin keys. */
public static final String BITCOIN_SIGNED_MESSAGE_HEADER = "Bitcoin Signed Message:\n";
public static final byte[] BITCOIN_SIGNED_MESSAGE_HEADER_BYTES = BITCOIN_SIGNED_MESSAGE_HEADER.getBytes(Charsets.UTF_8);
// TODO: Replace this nanocoins business with something better.
@@ -510,20 +513,18 @@ public class Utils {
* <tt><p>[24] "Bitcoin Signed Message:\n" [message.length as a varint] message</p></tt>
*/
public static byte[] formatMessageForSigning(String message) {
VarInt size = new VarInt(message.length());
int totalSize = 1 + BITCOIN_SIGNED_MESSAGE_HEADER.length() + size.getSizeInBytes() + message.length();
byte[] result = new byte[totalSize];
int cursor = 0;
result[cursor++] = (byte) BITCOIN_SIGNED_MESSAGE_HEADER.length();
byte[] bytes = BITCOIN_SIGNED_MESSAGE_HEADER.getBytes(Charset.forName("UTF-8"));
System.arraycopy(bytes, 0, result, cursor, bytes.length);
cursor += bytes.length;
bytes = size.encode();
System.arraycopy(bytes, 0, result, cursor, bytes.length);
cursor += bytes.length;
bytes = message.getBytes(Charset.forName("UTF-8"));
System.arraycopy(bytes, 0, result, cursor, bytes.length);
return result;
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
bos.write(BITCOIN_SIGNED_MESSAGE_HEADER_BYTES.length);
bos.write(BITCOIN_SIGNED_MESSAGE_HEADER_BYTES);
byte[] messageBytes = message.getBytes(Charsets.UTF_8);
VarInt size = new VarInt(messageBytes.length);
bos.write(size.encode());
bos.write(messageBytes);
return bos.toByteArray();
} catch (IOException e) {
throw new RuntimeException(e); // Cannot happen.
}
}
// 00000001, 00000010, 00000100, 00001000, ...

View File

@@ -73,7 +73,8 @@ public class VersionMessage extends Message {
public boolean relayTxesBeforeFilter;
/** The version of this library release, as a string. */
public static final String BITCOINJ_VERSION = "0.10-SNAPSHOT";
public static final String BITCOINJ_VERSION = "0.10.2";
/** The value that is prepended to the subVer field of this application. */
public static final String LIBRARY_SUBVER = "/BitCoinJ:" + BITCOINJ_VERSION + "/";

View File

@@ -1,5 +1,5 @@
/**
* Copyright 2011 Google Inc.
* Copyright 2013 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,9 +25,13 @@ import com.google.bitcoin.store.UnreadableWalletException;
import com.google.bitcoin.store.WalletProtobufSerializer;
import com.google.bitcoin.utils.ListenerRegistration;
import com.google.bitcoin.utils.Threading;
import com.google.bitcoin.wallet.KeyTimeCoinSelector;
import com.google.bitcoin.wallet.WalletFiles;
import com.google.common.base.Preconditions;
import com.google.common.collect.*;
import com.google.common.primitives.Ints;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
import org.bitcoinj.wallet.Protos.Wallet.EncryptionType;
@@ -35,12 +39,15 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongycastle.crypto.params.KeyParameter;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import java.io.*;
import java.math.BigInteger;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import static com.google.bitcoin.core.Utils.bitcoinValueToFriendlyString;
@@ -48,8 +55,14 @@ import static com.google.common.base.Preconditions.*;
// To do list:
//
// - Make the keychain member protected and switch it to be a hashmap of some kind so key lookup ops are faster.
// - Refactor how keys are managed to better handle things like deterministic wallets in future.
// This whole class has evolved over a period of years and needs a ground-up rewrite.
//
// - Take all wallet-relevant data out of Transaction and put it into WalletTransaction. Make Transaction immutable.
// - Only store relevant transaction outputs, don't bother storing the rest of the data.
// - Split block chain and tx output tracking into a superclass that doesn't have any key or spending related code.
// - Simplify how transactions are tracked and stored: in particular, have the wallet maintain positioning information
// for transactions independent of the transactions themselves, so the timeline can be walked without having to
// process and sort every single transaction.
// - Decompose the class where possible: break logic out into classes that can be customized/replaced by the user.
// - [Auto]saving to a backing store
// - Key management
@@ -110,6 +123,9 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
final Map<Sha256Hash, Transaction> spent;
final Map<Sha256Hash, Transaction> dead;
// All transactions together.
final Map<Sha256Hash, Transaction> transactions;
// A list of public/private EC keys owned by this user. Access it using addKey[s], hasKey[s] and findPubKeyFromHash.
private ArrayList<ECKey> keychain;
@@ -138,6 +154,12 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
private boolean insideReorg;
private Map<Transaction, TransactionConfidence.Listener.ChangeReason> confidenceChanged;
private volatile WalletFiles vFileManager;
// Object that is used to send transactions asynchronously when the wallet requires it.
private volatile TransactionBroadcaster vTransactionBroadcaster;
// UNIX time in seconds. Money controlled by keys created before this time will be automatically respent to a key
// that was created after it. Useful when you believe some keys have been compromised.
private volatile long vKeyRotationTimestamp;
private volatile boolean vKeyRotationEnabled;
/** Represents the results of a {@link CoinSelector#select(java.math.BigInteger, java.util.LinkedList)} operation */
public static class CoinSelection {
@@ -232,7 +254,7 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
/**
* This coin selector will select any transaction at all, regardless of where it came from or whether it was
* confirmed yet.
* confirmed yet. However immature coinbases will not be included (would be a protocol violation).
*/
public static class AllowUnconfirmedCoinSelector extends DefaultCoinSelector {
@Override protected boolean shouldSelect(Transaction tx) {
@@ -240,6 +262,8 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
}
private static AllowUnconfirmedCoinSelector instance;
/** Returns a global static instance of the selector. */
public static AllowUnconfirmedCoinSelector get() {
// This doesn't have to be thread safe as the object has no state, so discarded duplicates are harmless.
if (instance == null)
@@ -281,6 +305,7 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
spent = new HashMap<Sha256Hash, Transaction>();
pending = new HashMap<Sha256Hash, Transaction>();
dead = new HashMap<Sha256Hash, Transaction>();
transactions = new HashMap<Sha256Hash, Transaction>();
eventListeners = new CopyOnWriteArrayList<ListenerRegistration<WalletEventListener>>();
extensions = new HashMap<String, WalletExtension>();
confidenceChanged = new HashMap<Transaction, TransactionConfidence.Listener.ChangeReason>();
@@ -475,8 +500,8 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
files.saveLater();
}
/** If auto saving is enabled, do an immediate sync write to disk ignoring any delays. */
private void saveNow() {
// If auto saving is enabled, do an immediate sync write to disk ignoring any delays.
WalletFiles files = vFileManager;
if (files != null) {
try {
@@ -606,16 +631,24 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
* block might change which chain is best causing a reorganize. A re-org can totally change our balance!
*/
public void notifyTransactionIsInBlock(Sha256Hash txHash, StoredBlock block,
BlockChain.NewBlockType blockType) throws VerificationException {
BlockChain.NewBlockType blockType,
int relativityOffset) throws VerificationException {
lock.lock();
try {
Transaction tx = pending.get(txHash);
if (tx == null)
Transaction tx = transactions.get(txHash);
if (tx == null) {
log.error("TX {} not found despite being sent to wallet", txHash);
return;
receive(tx, block, blockType);
}
receive(tx, block, blockType, relativityOffset);
} finally {
lock.unlock();
}
if (blockType == AbstractBlockChain.NewBlockType.BEST_CHAIN) {
// If some keys are considered to be bad, possibly move money assigned to them now.
// This has to run outside the wallet lock as it may trigger broadcasting of new transactions.
maybeRotateKeys();
}
}
/** The results of examining the dependency graph of a pending transaction for protocol abuse. */
@@ -681,6 +714,8 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
} finally {
lock.unlock();
}
// maybeRotateKeys() will ignore pending transactions so we don't bother calling it here (see the comments
// in that function for an explanation of why).
}
/**
@@ -819,17 +854,25 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
* inactive side chain. We must still record these transactions and the blocks they appear in because a future
* block might change which chain is best causing a reorganize. A re-org can totally change our balance!
*/
@Override
public void receiveFromBlock(Transaction tx, StoredBlock block,
BlockChain.NewBlockType blockType) throws VerificationException {
BlockChain.NewBlockType blockType,
int relativityOffset) throws VerificationException {
lock.lock();
try {
receive(tx, block, blockType);
receive(tx, block, blockType, relativityOffset);
} finally {
lock.unlock();
}
if (blockType == AbstractBlockChain.NewBlockType.BEST_CHAIN) {
// If some keys are considered to be bad, possibly move money assigned to them now.
// This has to run outside the wallet lock as it may trigger broadcasting of new transactions.
maybeRotateKeys();
}
}
private void receive(Transaction tx, StoredBlock block, BlockChain.NewBlockType blockType) throws VerificationException {
private void receive(Transaction tx, StoredBlock block, BlockChain.NewBlockType blockType,
int relativityOffset) throws VerificationException {
// Runs in a peer thread.
checkState(lock.isHeldByCurrentThread());
BigInteger prevBalance = getBalance();
@@ -841,21 +884,23 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
BigInteger valueSentToMe = tx.getValueSentToMe(this);
BigInteger valueDifference = valueSentToMe.subtract(valueSentFromMe);
log.info("Received tx {} for {} BTC: {} in block {}", new Object[]{sideChain ? "on a side chain" : "",
bitcoinValueToFriendlyString(valueDifference), tx.getHashAsString(),
log.info("Received tx{} for {} BTC: {} [{}] in block {}", new Object[]{sideChain ? " on a side chain" : "",
bitcoinValueToFriendlyString(valueDifference), tx.getHashAsString(), relativityOffset,
block != null ? block.getHeader().getHash() : "(unit test)"});
onWalletChangedSuppressions++;
// If this transaction is already in the wallet we may need to move it into a different pool. At the very
// least we need to ensure we're manipulating the canonical object rather than a duplicate.
Transaction wtx;
if ((wtx = pending.remove(txHash)) != null) {
log.info(" <-pending");
// Make sure "tx" is always the canonical object we want to manipulate, send to event handlers, etc.
tx = wtx;
{
Transaction tmp = transactions.get(tx.getHash());
if (tmp != null)
tx = tmp;
}
boolean wasPending = wtx != null;
boolean wasPending = pending.remove(txHash) != null;
if (wasPending)
log.info(" <-pending");
if (bestChain) {
if (wasPending) {
@@ -866,7 +911,7 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
if (spentBy != null) spentBy.disconnect();
}
}
processTxFromBestChain(tx);
processTxFromBestChain(tx, wasPending);
} else {
checkState(sideChain);
// Transactions that appear in a side chain will have that appearance recorded below - we assume that
@@ -875,6 +920,7 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
if (wasPending) {
// Just put it back in without touching the connections or confidence.
addWalletTransaction(Pool.PENDING, tx);
log.info(" ->pending");
} else {
// Ignore the case where a tx appears on a side chain at the same time as the best chain (this is
// quite normal and expected).
@@ -890,7 +936,7 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
if (block != null) {
// Mark the tx as appearing in this block so we can find it later after a re-org. This also tells the tx
// confidence object about the block and sets its work done/depth appropriately.
tx.setBlockAppearance(block, bestChain);
tx.setBlockAppearance(block, bestChain, relativityOffset);
if (bestChain) {
// Don't notify this tx of work done in notifyNewBestBlock which will be called immediately after
// this method has been called by BlockChain for all relevant transactions. Otherwise we'd double
@@ -995,7 +1041,7 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
* Handle when a transaction becomes newly active on the best chain, either due to receiving a new block or a
* re-org. Places the tx into the right pool, handles coinbase transactions, handles double-spends and so on.
*/
private void processTxFromBestChain(Transaction tx) throws VerificationException {
private void processTxFromBestChain(Transaction tx, boolean forceAddToPool) throws VerificationException {
checkState(lock.isHeldByCurrentThread());
checkState(!pending.containsKey(tx.getHash()));
@@ -1032,6 +1078,10 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
// Didn't send us any money, but did spend some. Keep it around for record keeping purposes.
log.info(" tx {} ->spent", tx.getHashAsString());
addWalletTransaction(Pool.SPENT, tx);
} else if (forceAddToPool) {
// Was manually added to pending, so we should keep it to notify the user of confidence information
log.info(" tx {} ->spent (manually added)", tx.getHashAsString());
addWalletTransaction(Pool.SPENT, tx);
}
checkForDoubleSpendAgainstPending(tx, true);
@@ -1340,6 +1390,7 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
*/
private void addWalletTransaction(Pool pool, Transaction tx) {
checkState(lock.isHeldByCurrentThread());
transactions.put(tx.getHash(), tx);
switch (pool) {
case UNSPENT:
checkState(unspent.put(tx.getHash(), tx) == null);
@@ -1411,19 +1462,11 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
/**
* Returns a transaction object given its hash, if it exists in this wallet, or null otherwise.
*/
@Nullable
public Transaction getTransaction(Sha256Hash hash) {
lock.lock();
try {
Transaction tx;
if ((tx = pending.get(hash)) != null)
return tx;
else if ((tx = unspent.get(hash)) != null)
return tx;
else if ((tx = spent.get(hash)) != null)
return tx;
else if ((tx = dead.get(hash)) != null)
return tx;
return null;
return transactions.get(hash);
} finally {
lock.unlock();
}
@@ -1442,6 +1485,7 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
spent.clear();
pending.clear();
dead.clear();
transactions.clear();
saveLater();
} else {
throw new UnsupportedOperationException();
@@ -1533,9 +1577,9 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
public Transaction tx;
/**
* When emptyWallet is set, all available coins are sent to the first output in tx (its value is ignored and set
* to {@link com.google.bitcoin.core.Wallet#getBalance()} - the fees required for the transaction). Any
* additional outputs are removed.
* When emptyWallet is set, all coins selected by the coin selector are sent to the first output in tx
* (its value is ignored and set to {@link com.google.bitcoin.core.Wallet#getBalance()} - the fees required
* for the transaction). Any additional outputs are removed.
*/
public boolean emptyWallet = false;
@@ -1605,6 +1649,13 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
*/
public KeyParameter aesKey = null;
/**
* If not null, the {@link Wallet.CoinSelector} to use instead of the wallets default. Coin selectors are
* responsible for choosing which transaction outputs (coins) in a wallet to use given the desired send value
* amount.
*/
public CoinSelector coinSelector = null;
// Tracks if this has been passed to wallet.completeTx already: just a safety check.
private boolean completed;
@@ -1659,7 +1710,7 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
* {@link Wallet#getChangeAddress()}, so you must have added at least one key.</p>
*
* <p>If you just want to send money quickly, you probably want
* {@link Wallet#sendCoins(PeerGroup, Address, java.math.BigInteger)} instead. That will create the sending
* {@link Wallet#sendCoins(TransactionBroadcaster, Address, java.math.BigInteger)} instead. That will create the sending
* transaction, commit to the wallet and broadcast it to the network all in one go. This method is lower level
* and lets you see the proposed transaction before anything is done with it.</p>
*
@@ -1681,6 +1732,7 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
* @return either the created Transaction or null if there are insufficient coins.
* coins as spent until commitTx is called on the result.
*/
@Nullable
public Transaction createSend(Address address, BigInteger nanocoins) {
SendRequest req = SendRequest.to(address, nanocoins);
if (completeTx(req)) {
@@ -1698,6 +1750,7 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
*
* @return the Transaction that was created, or null if there are insufficient coins in the wallet.
*/
@Nullable
public Transaction sendCoinsOffline(SendRequest request) {
lock.lock();
try {
@@ -1728,18 +1781,19 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
* <p>You MUST ensure that value is smaller than {@link Transaction#MIN_NONDUST_OUTPUT} or the transaction will
* almost certainly be rejected by the network as dust.</p>
*
* @param peerGroup a PeerGroup to use for broadcast or null.
* @param broadcaster a {@link TransactionBroadcaster} to use to send the transactions out.
* @param to Which address to send coins to.
* @param value How much value to send. You can use Utils.toNanoCoins() to calculate this.
* @return An object containing the transaction that was created, and a future for the broadcast of it.
*/
public SendResult sendCoins(PeerGroup peerGroup, Address to, BigInteger value) {
@Nullable
public SendResult sendCoins(TransactionBroadcaster broadcaster, Address to, BigInteger value) {
SendRequest request = SendRequest.to(to, value);
return sendCoins(peerGroup, request);
return sendCoins(broadcaster, request);
}
/**
* <p>Sends coins according to the given request, via the given {@link PeerGroup}.</p>
* <p>Sends coins according to the given request, via the given {@link TransactionBroadcaster}.</p>
*
* <p>The returned object provides both the transaction, and a future that can be used to learn when the broadcast
* is complete. Complete means, if the PeerGroup is limited to only one connection, when it was written out to
@@ -1755,7 +1809,9 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
*/
@Nullable
public SendResult sendCoins(TransactionBroadcaster broadcaster, SendRequest request) {
// Does not need to be synchronized as sendCoinsOffline is and the rest is all thread-local.
// Should not be locked here, as we're going to call into the broadcaster and that might want to hold its
// own lock. sendCoinsOffline handles everything that needs to be locked.
checkState(!lock.isHeldByCurrentThread());
// Commit the TX to the wallet immediately so the spent coins won't be reused.
// TODO: We should probably allow the request to specify tx commit only after the network has accepted it.
@@ -1773,6 +1829,21 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
return result;
}
/**
* Satisfies the given {@link SendRequest} using the default transaction broadcaster configured either via
* {@link PeerGroup#addWallet(Wallet)} or directly with {@link #setTransactionBroadcaster(TransactionBroadcaster)}.
*
* @param request the SendRequest that describes what to do, get one using static methods on SendRequest itself.
* @return An object containing the transaction that was created, and a future for the broadcast of it.
* @throws IllegalStateException if no transaction broadcaster has been configured.
*/
@Nullable
public SendResult sendCoins(SendRequest request) {
TransactionBroadcaster broadcaster = vTransactionBroadcaster;
checkState(broadcaster != null, "No transaction broadcaster is configured");
return sendCoins(broadcaster, request);
}
/**
* Sends coins to the given address, via the given {@link Peer}. Change is returned to {@link Wallet#getChangeAddress()}.
* If an exception is thrown by {@link Peer#sendMessage(Message)} the transaction is still committed, so the
@@ -1862,28 +1933,22 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
bestCoinSelection = feeCalculation.bestCoinSelection;
bestChangeOutput = feeCalculation.bestChangeOutput;
} else {
BigInteger valueGathered = BigInteger.ZERO;
for (TransactionOutput output : candidates)
valueGathered = valueGathered.add(output.getValue());
bestCoinSelection = new CoinSelection(valueGathered, candidates);
req.tx.getOutput(0).setValue(valueGathered);
// We're being asked to empty the wallet. What this means is ensuring "tx" has only a single output
// of the total value we can currently spend as determined by the selector, and then subtracting the fee.
checkState(req.tx.getOutputs().size() == 1, "Empty wallet TX must have a single output only.");
CoinSelector selector = req.coinSelector == null ? coinSelector : req.coinSelector;
bestCoinSelection = selector.select(NetworkParameters.MAX_MONEY, candidates);
req.tx.getOutput(0).setValue(bestCoinSelection.valueGathered);
}
for (TransactionOutput output : bestCoinSelection.gathered)
req.tx.addInput(output);
if (req.ensureMinRequiredFee && req.emptyWallet) {
TransactionOutput output = req.tx.getOutput(0);
// Check if we need additional fee due to the transaction's size
int size = req.tx.bitcoinSerialize().length;
size += estimateBytesForSigning(bestCoinSelection);
BigInteger fee = (req.fee == null ? BigInteger.ZERO : req.fee)
.add(BigInteger.valueOf((size / 1000) + 1).multiply(req.feePerKb == null ? BigInteger.ZERO : req.feePerKb));
output.setValue(output.getValue().subtract(fee));
// Check if we need additional fee due to the output's value
if (output.getValue().compareTo(Utils.CENT) < 0 && fee.compareTo(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE) < 0)
output.setValue(output.getValue().subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.subtract(fee)));
if (output.getMinNonDustValue().compareTo(output.getValue()) > 0)
final BigInteger baseFee = req.fee == null ? BigInteger.ZERO : req.fee;
final BigInteger feePerKb = req.feePerKb == null ? BigInteger.ZERO : req.feePerKb;
Transaction tx = req.tx;
if (!adjustOutputDownwardsForFee(tx, bestCoinSelection, baseFee, feePerKb))
return false;
}
@@ -1922,6 +1987,10 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
// the transaction is confirmed. We deliberately won't bother notifying listeners here as there's not much
// point - the user isn't interested in a confidence transition they made themselves.
req.tx.getConfidence().setSource(TransactionConfidence.Source.SELF);
// Label the transaction as being a user requested payment. This can be used to render GUI wallet
// transaction lists more appropriately, especially when the wallet starts to generate transactions itself
// for internal purposes.
req.tx.setPurpose(Transaction.Purpose.USER_PAYMENT);
req.completed = true;
req.fee = calculatedFee;
log.info(" completed: {}", req.tx);
@@ -1931,6 +2000,20 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
}
}
/** Reduce the value of the first output of a transaction to pay the given feePerKb as appropriate for its size. */
private boolean adjustOutputDownwardsForFee(Transaction tx, CoinSelection coinSelection, BigInteger baseFee, BigInteger feePerKb) {
TransactionOutput output = tx.getOutput(0);
// Check if we need additional fee due to the transaction's size
int size = tx.bitcoinSerialize().length;
size += estimateBytesForSigning(coinSelection);
BigInteger fee = baseFee.add(BigInteger.valueOf((size / 1000) + 1).multiply(feePerKb));
output.setValue(output.getValue().subtract(fee));
// Check if we need additional fee due to the output's value
if (output.getValue().compareTo(Utils.CENT) < 0 && fee.compareTo(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE) < 0)
output.setValue(output.getValue().subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.subtract(fee)));
return output.getMinNonDustValue().compareTo(output.getValue()) <= 0;
}
/**
* Returns a list of all possible outputs we could possibly spend, potentially even including immature coinbases
* (which the protocol may forbid us from spending). In other words, return all outputs that this wallet holds
@@ -2226,6 +2309,20 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
}
}
private static class TxOffsetPair implements Comparable<TxOffsetPair> {
public final Transaction tx;
public final int offset;
public TxOffsetPair(Transaction tx, int offset) {
this.tx = tx;
this.offset = offset;
}
@Override public int compareTo(TxOffsetPair o) {
return Ints.compare(offset, o.offset);
}
}
/**
* <p>Don't call this directly. It's not intended for API users.</p>
*
@@ -2258,14 +2355,17 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
checkState(onWalletChangedSuppressions == 0);
onWalletChangedSuppressions++;
// Map block hash to transactions that appear in it.
Multimap<Sha256Hash, Transaction> mapBlockTx = ArrayListMultimap.create();
// Map block hash to transactions that appear in it. We ensure that the map values are sorted according
// to their relative position within those blocks.
ArrayListMultimap<Sha256Hash, TxOffsetPair> mapBlockTx = ArrayListMultimap.create();
for (Transaction tx : getTransactions(true)) {
Collection<Sha256Hash> appearsIn = tx.getAppearsInHashes();
Map<Sha256Hash, Integer> appearsIn = tx.getAppearsInHashes();
if (appearsIn == null) continue; // Pending.
for (Sha256Hash block : appearsIn)
mapBlockTx.put(block, tx);
for (Map.Entry<Sha256Hash, Integer> block : appearsIn.entrySet())
mapBlockTx.put(block.getKey(), new TxOffsetPair(tx, block.getValue()));
}
for (Sha256Hash blockHash : mapBlockTx.keySet())
Collections.sort(mapBlockTx.get(blockHash));
List<Sha256Hash> oldBlockHashes = new ArrayList<Sha256Hash>(oldBlocks.size());
log.info("Old part of chain (top to bottom):");
@@ -2280,12 +2380,11 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
Collections.reverse(newBlocks); // Need bottom-to-top but we get top-to-bottom.
// For each block in the old chain, disconnect the transactions. It doesn't matter if
// we don't do it in the exact ordering they appeared in the chain, all we're doing is ensuring all
// the outputs are freed up so we can connect them back again in the next step.
// For each block in the old chain, disconnect the transactions in reverse order.
LinkedList<Transaction> oldChainTxns = Lists.newLinkedList();
for (Sha256Hash blockHash : oldBlockHashes) {
for (Transaction tx : mapBlockTx.get(blockHash)) {
for (TxOffsetPair pair : mapBlockTx.get(blockHash)) {
Transaction tx = pair.tx;
final Sha256Hash txHash = tx.getHash();
if (tx.isCoinBase()) {
log.warn("Coinbase tx {} -> dead", tx.getHash());
@@ -2357,10 +2456,10 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
// conflict.
for (StoredBlock block : newBlocks) {
log.info("Replaying block {}", block.getHeader().getHashAsString());
for (Transaction tx : mapBlockTx.get(block.getHeader().getHash())) {
log.info(" tx {}", tx.getHash());
for (TxOffsetPair pair : mapBlockTx.get(block.getHeader().getHash())) {
log.info(" tx {}", pair.tx.getHash());
try {
receive(tx, block, BlockChain.NewBlockType.BEST_CHAIN);
receive(pair.tx, block, BlockChain.NewBlockType.BEST_CHAIN, pair.offset);
} catch (ScriptException e) {
throw new RuntimeException(e); // Cannot happen as these blocks were already verified.
}
@@ -2841,12 +2940,13 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
/**
* A coin selector is responsible for choosing which outputs to spend when creating transactions. The default
* selector implements a policy of spending transactions that appeared in the best chain and pending transactions
* that were created by this wallet, but not others.
* that were created by this wallet, but not others. You can override the coin selector for any given send
* operation by changing {@link Wallet.SendRequest#coinSelector}.
*/
public void setCoinSelector(CoinSelector coinSelector) {
public void setCoinSelector(@Nonnull CoinSelector coinSelector) {
lock.lock();
try {
this.coinSelector = coinSelector;
this.coinSelector = checkNotNull(coinSelector);
} finally {
lock.unlock();
}
@@ -3083,12 +3183,17 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Fee calculation code.
private class FeeCalculation {
private CoinSelection bestCoinSelection;
private TransactionOutput bestChangeOutput;
public FeeCalculation(SendRequest req, BigInteger value, List<TransactionInput> originalInputs,
boolean needAtLeastReferenceFee, LinkedList<TransactionOutput> candidates) throws InsufficientMoneyException {
checkState(lock.isHeldByCurrentThread());
// There are 3 possibilities for what adding change might do:
// 1) No effect
// 2) Causes increase in fee (change < 0.01 COINS)
@@ -3125,7 +3230,8 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
BigInteger additionalValueSelected = additionalValueForNextCategory;
// Of the coins we could spend, pick some that we actually will spend.
CoinSelection selection = coinSelector.select(valueNeeded, candidates);
CoinSelector selector = req.coinSelector == null ? coinSelector : req.coinSelector;
CoinSelection selection = selector.select(valueNeeded, candidates);
// Can we afford this?
if (selection.valueGathered.compareTo(valueNeeded) < 0)
break;
@@ -3290,4 +3396,194 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
}
return size;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Managing wallet-triggered transaction broadcast and key rotation.
/**
* <p>Specifies that the given {@link TransactionBroadcaster}, typically a {@link PeerGroup}, should be used for
* sending transactions to the Bitcoin network by default. Some sendCoins methods let you specify a broadcaster
* explicitly, in that case, they don't use this broadcaster. If null is specified then the wallet won't attempt
* to broadcast transactions itself.</p>
*
* <p>You don't normally need to call this. A {@link PeerGroup} will automatically set itself as the wallets
* broadcaster when you use {@link PeerGroup#addWallet(Wallet)}. A wallet can use the broadcaster when you ask
* it to send money, but in future also at other times to implement various features that may require asynchronous
* re-organisation of the wallet contents on the block chain. For instance, in future the wallet may choose to
* optimise itself to reduce fees or improve privacy.</p>
*/
public void setTransactionBroadcaster(@Nullable com.google.bitcoin.core.TransactionBroadcaster broadcaster) {
lock.lock();
try {
if (vTransactionBroadcaster == broadcaster)
return;
vTransactionBroadcaster = broadcaster;
if (broadcaster == null)
return;
// Now use it to upload any pending transactions we have that are marked as not being seen by any peers yet.
for (Transaction tx : pending.values()) {
checkState(tx.getConfidence().getConfidenceType() == ConfidenceType.PENDING);
if (tx.getConfidence().numBroadcastPeers() == 0) {
log.info("New broadcaster so uploading waiting tx {}", tx.getHash());
broadcaster.broadcastTransaction(tx);
}
}
} finally {
lock.unlock();
}
}
/**
* When a key rotation time is set, and money controlled by keys created before the given timestamp T will be
* automatically respent to any key that was created after T. This can be used to recover from a situation where
* a set of keys is believed to be compromised. Once the time is set transactions will be created and broadcast
* immediately. New coins that come in after calling this method will be automatically respent immediately. The
* rotation time is persisted to the wallet. You can stop key rotation by calling this method again with zero
* as the argument.
*/
public void setKeyRotationTime(Date time) {
setKeyRotationTime(time.getTime() / 1000);
}
/**
* Returns a UNIX time since the epoch in seconds, or zero if unconfigured.
*/
public Date getKeyRotationTime() {
return new Date(vKeyRotationTimestamp * 1000);
}
/**
* <p>When a key rotation time is set, and money controlled by keys created before the given timestamp T will be
* automatically respent to any key that was created after T. This can be used to recover from a situation where
* a set of keys is believed to be compromised. Once the time is set transactions will be created and broadcast
* immediately. New coins that come in after calling this method will be automatically respent immediately. The
* rotation time is persisted to the wallet. You can stop key rotation by calling this method again with zero
* as the argument, or by using {@link #setKeyRotationEnabled(boolean)}.</p>
*
* <p>Note that this method won't do anything unless you call {@link #setKeyRotationEnabled(boolean)} first.</p>
*/
public void setKeyRotationTime(long unixTimeSeconds) {
vKeyRotationTimestamp = unixTimeSeconds;
if (unixTimeSeconds > 0) {
log.info("Key rotation time set: {}", unixTimeSeconds);
maybeRotateKeys();
}
saveNow();
}
/** Toggles key rotation on and off. Note that this state is not serialized. Activating it can trigger tx sends. */
public void setKeyRotationEnabled(boolean enabled) {
vKeyRotationEnabled = enabled;
if (enabled)
maybeRotateKeys();
}
/** Returns whether the keys creation time is before the key rotation time, if one was set. */
public boolean isKeyRotating(ECKey key) {
long time = vKeyRotationTimestamp;
return time != 0 && key.getCreationTimeSeconds() < time;
}
// Checks to see if any coins are controlled by rotating keys and if so, spends them.
private void maybeRotateKeys() {
checkState(!lock.isHeldByCurrentThread());
// TODO: Handle chain replays and encrypted wallets here.
if (!vKeyRotationEnabled) return;
// Snapshot volatiles so this method has an atomic view.
long keyRotationTimestamp = vKeyRotationTimestamp;
if (keyRotationTimestamp == 0) return; // Nothing to do.
TransactionBroadcaster broadcaster = vTransactionBroadcaster;
// Because transactions are size limited, we might not be able to re-key the entire wallet in one go. So
// loop around here until we no longer produce transactions with the max number of inputs. That means we're
// fully done, at least for now (we may still get more transactions later and this method will be reinvoked).
Transaction tx;
do {
tx = rekeyOneBatch(keyRotationTimestamp, broadcaster);
} while (tx != null && tx.getInputs().size() == KeyTimeCoinSelector.MAX_SIMULTANEOUS_INPUTS);
}
private Transaction rekeyOneBatch(long keyRotationTimestamp, final TransactionBroadcaster broadcaster) {
final Transaction rekeyTx;
lock.lock();
try {
// Firstly, see if we have any keys that are beyond the rotation time, and any before.
ECKey safeKey = null;
boolean haveRotatingKeys = false;
for (ECKey key : keychain) {
final long t = key.getCreationTimeSeconds();
if (t < keyRotationTimestamp) {
haveRotatingKeys = true;
} else {
safeKey = key;
}
}
if (!haveRotatingKeys) return null;
if (safeKey == null) {
log.warn("Key rotation requested but no keys newer than the timestamp are available.");
return null;
}
// Build the transaction using some custom logic for our special needs. Last parameter to
// KeyTimeCoinSelector is whether to ignore pending transactions or not.
//
// We ignore pending outputs because trying to rotate these is basically racing an attacker, and
// we're quite likely to lose and create stuck double spends. Also, some users who have 0.9 wallets
// have already got stuck double spends in their wallet due to the Bloom-filtering block reordering
// bug that was fixed in 0.10, thus, making a re-key transaction depend on those would cause it to
// never confirm at all.
CoinSelector selector = new KeyTimeCoinSelector(this, keyRotationTimestamp, true);
CoinSelection toMove = selector.select(BigInteger.ZERO, calculateAllSpendCandidates(true));
if (toMove.valueGathered.equals(BigInteger.ZERO)) return null; // Nothing to do.
rekeyTx = new Transaction(params);
for (TransactionOutput output : toMove.gathered) {
rekeyTx.addInput(output);
}
rekeyTx.addOutput(toMove.valueGathered, safeKey);
if (!adjustOutputDownwardsForFee(rekeyTx, toMove, BigInteger.ZERO, Transaction.REFERENCE_DEFAULT_MIN_TX_FEE)) {
log.error("Failed to adjust rekey tx for fees.");
return null;
}
rekeyTx.getConfidence().setSource(TransactionConfidence.Source.SELF);
rekeyTx.setPurpose(Transaction.Purpose.KEY_ROTATION);
rekeyTx.signInputs(Transaction.SigHash.ALL, this);
// KeyTimeCoinSelector should never select enough inputs to push us oversize.
checkState(rekeyTx.bitcoinSerialize().length < Transaction.MAX_STANDARD_TX_SIZE);
commitTx(rekeyTx);
} catch (VerificationException e) {
throw new RuntimeException(e); // Cannot happen.
} finally {
lock.unlock();
}
if (broadcaster == null)
return rekeyTx;
log.info("Attempting to send key rotation tx: {}", rekeyTx);
// We must broadcast the tx in a separate thread to avoid inverting any locks. Otherwise we may be running
// with the blockchain lock held (whilst receiving a block) and thus re-entering the peerGroup would invert
// blockchain <-> peergroup.
new Thread() {
@Override
public void run() {
// Handle the future results just for logging.
try {
Futures.addCallback(broadcaster.broadcastTransaction(rekeyTx), new FutureCallback<Transaction>() {
@Override
public void onSuccess(Transaction transaction) {
log.info("Successfully broadcast key rotation tx: {}", transaction);
}
@Override
public void onFailure(Throwable throwable) {
log.error("Failed to broadcast key rotation tx", throwable);
}
});
} catch (Exception e) {
log.error("Failed to broadcast rekey tx, will try again later", e);
}
}
}.start();
return rekeyTx;
}
}

View File

@@ -38,8 +38,10 @@ public class NativeBlockChainListener implements BlockChainListener {
public native boolean isTransactionRelevant(Transaction tx) throws ScriptException;
@Override
public native void receiveFromBlock(Transaction tx, StoredBlock block, BlockChain.NewBlockType blockType) throws VerificationException;
public native void receiveFromBlock(Transaction tx, StoredBlock block, BlockChain.NewBlockType blockType,
int relativityOffset) throws VerificationException;
@Override
public native void notifyTransactionIsInBlock(Sha256Hash txHash, StoredBlock block, BlockChain.NewBlockType blockType) throws VerificationException;
public native void notifyTransactionIsInBlock(Sha256Hash txHash, StoredBlock block, BlockChain.NewBlockType blockType,
int relativityOffset) throws VerificationException;
}

View File

@@ -34,9 +34,6 @@ public class RegTestParams extends TestNet2Params {
proofOfWorkLimit = PROOF_OF_WORK_LIMIT;
subsidyDecreaseBlockCount = 10000;
port = 18444;
acceptableAddressCodes = new int[] { 0 };
addressHeader = 0;
dumpedPrivateKeyHeader = 128;
}
@Override

View File

@@ -1168,6 +1168,13 @@ public class Script {
*/
public void correctlySpends(Transaction txContainingThis, long scriptSigIndex, Script scriptPubKey,
boolean enforceP2SH) throws ScriptException {
// Clone the transaction because executing the script involves editing it, and if we die, we'll leave
// the tx half broken (also it's not so thread safe to work on it directly.
try {
txContainingThis = new Transaction(txContainingThis.getParams(), txContainingThis.bitcoinSerialize());
} catch (ProtocolException e) {
throw new RuntimeException(e); // Should not happen unless we were given a totally broken transaction.
}
if (getProgram().length > 10000 || scriptPubKey.getProgram().length > 10000)
throw new ScriptException("Script larger than 10,000 bytes");

View File

@@ -18,18 +18,22 @@ package com.google.bitcoin.store;
import com.google.bitcoin.core.*;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Keeps {@link com.google.bitcoin.core.StoredBlock}s in memory. Used primarily for unit testing.
*/
public class MemoryBlockStore implements BlockStore {
private Map<Sha256Hash, StoredBlock> blockMap;
private LinkedHashMap<Sha256Hash, StoredBlock> blockMap = new LinkedHashMap<Sha256Hash, StoredBlock>() {
@Override
protected boolean removeEldestEntry(Map.Entry<Sha256Hash, StoredBlock> eldest) {
return blockMap.size() > 5000;
}
};
private StoredBlock chainHead;
public MemoryBlockStore(NetworkParameters params) {
blockMap = new HashMap<Sha256Hash, StoredBlock>();
// Insert the genesis block.
try {
Block genesisHeader = params.getGenesisBlock().cloneAsHeader();

View File

@@ -167,6 +167,11 @@ public class WalletProtobufSerializer {
}
}
if (wallet.getKeyRotationTime() != null) {
long timeSecs = wallet.getKeyRotationTime().getTime() / 1000;
walletBuilder.setKeyRotationTime(timeSecs);
}
populateExtensions(wallet, walletBuilder);
// Populate the wallet version.
@@ -229,9 +234,11 @@ public class WalletProtobufSerializer {
}
// Handle which blocks tx was seen in.
if (tx.getAppearsInHashes() != null) {
for (Sha256Hash hash : tx.getAppearsInHashes()) {
txBuilder.addBlockHash(hashToByteString(hash));
final Map<Sha256Hash, Integer> appearsInHashes = tx.getAppearsInHashes();
if (appearsInHashes != null) {
for (Map.Entry<Sha256Hash, Integer> entry : appearsInHashes.entrySet()) {
txBuilder.addBlockHash(hashToByteString(entry.getKey()));
txBuilder.addBlockRelativityOffsets(entry.getValue());
}
}
@@ -240,6 +247,16 @@ public class WalletProtobufSerializer {
Protos.TransactionConfidence.Builder confidenceBuilder = Protos.TransactionConfidence.newBuilder();
writeConfidence(txBuilder, confidence, confidenceBuilder);
}
Protos.Transaction.Purpose purpose;
switch (tx.getPurpose()) {
case UNKNOWN: purpose = Protos.Transaction.Purpose.UNKNOWN; break;
case USER_PAYMENT: purpose = Protos.Transaction.Purpose.USER_PAYMENT; break;
case KEY_ROTATION: purpose = Protos.Transaction.Purpose.KEY_ROTATION; break;
default:
throw new RuntimeException("New tx purpose serialization not implemented.");
}
txBuilder.setPurpose(purpose);
return txBuilder.build();
}
@@ -396,6 +413,10 @@ public class WalletProtobufSerializer {
wallet.setLastBlockSeenHeight(walletProto.getLastSeenBlockHeight());
}
if (walletProto.hasKeyRotationTime()) {
wallet.setKeyRotationTime(new Date(walletProto.getKeyRotationTime() * 1000));
}
loadExtensions(wallet, walletProto);
if (walletProto.hasVersion()) {
@@ -462,14 +483,30 @@ public class WalletProtobufSerializer {
tx.addInput(input);
}
for (ByteString blockHash : txProto.getBlockHashList()) {
tx.addBlockAppearance(byteStringToHash(blockHash));
for (int i = 0; i < txProto.getBlockHashCount(); i++) {
ByteString blockHash = txProto.getBlockHash(i);
int relativityOffset = 0;
if (txProto.getBlockRelativityOffsetsCount() > 0)
relativityOffset = txProto.getBlockRelativityOffsets(i);
tx.addBlockAppearance(byteStringToHash(blockHash), relativityOffset);
}
if (txProto.hasLockTime()) {
tx.setLockTime(0xffffffffL & txProto.getLockTime());
}
if (txProto.hasPurpose()) {
switch (txProto.getPurpose()) {
case UNKNOWN: tx.setPurpose(Transaction.Purpose.UNKNOWN); break;
case USER_PAYMENT: tx.setPurpose(Transaction.Purpose.USER_PAYMENT); break;
case KEY_ROTATION: tx.setPurpose(Transaction.Purpose.KEY_ROTATION); break;
default: throw new RuntimeException("New purpose serialization not implemented");
}
} else {
// Old wallet: assume a user payment as that's the only reason a new tx would have been created back then.
tx.setPurpose(Transaction.Purpose.USER_PAYMENT);
}
// Transaction should now be complete.
Sha256Hash protoHash = byteStringToHash(txProto.getHash());
if (!tx.getHash().equals(protoHash))

View File

@@ -50,7 +50,7 @@ public class Threading {
public static final Executor SAME_THREAD;
// For safety reasons keep track of the thread we use to run user-provided event listeners to avoid deadlock.
private static WeakReference<Thread> userThread;
private static volatile WeakReference<Thread> vUserThread;
/**
* Put a dummy task into the queue and wait for it to be run. Because it's single threaded, this means all
@@ -63,8 +63,10 @@ public class Threading {
// If this assert fires it means you have a bug in your code - you can't call this method inside your own
// event handlers because it would never return. If you aren't calling this method explicitly, then that
// means there's a bug in bitcoinj.
checkState(userThread.get() != null && userThread.get() != Thread.currentThread(),
"waitForUserCode() run on user code thread would deadlock.");
if (vUserThread != null) {
checkState(vUserThread.get() != null && vUserThread.get() != Thread.currentThread(),
"waitForUserCode() run on user code thread would deadlock.");
}
Futures.getUnchecked(USER_THREAD.submit(Callables.returning(null)));
}
@@ -93,7 +95,7 @@ public class Threading {
t.setName("bitcoinj user thread");
t.setDaemon(true);
t.setUncaughtExceptionHandler(uncaughtExceptionHandler);
userThread = new WeakReference<Thread>(t);
vUserThread = new WeakReference<Thread>(t);
return t;
}
});

View File

@@ -0,0 +1,86 @@
/**
* Copyright 2013 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.bitcoin.wallet;
import com.google.bitcoin.core.*;
import com.google.bitcoin.script.Script;
import com.google.common.collect.Lists;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.math.BigInteger;
import java.util.LinkedList;
/**
* A coin selector that takes all coins assigned to keys created before the given timestamp.
* Used as part of the implementation of {@link Wallet#setKeyRotationTime(java.util.Date)}.
*/
public class KeyTimeCoinSelector implements Wallet.CoinSelector {
private static final Logger log = LoggerFactory.getLogger(KeyTimeCoinSelector.class);
/** A number of inputs chosen to avoid hitting {@link com.google.bitcoin.core.Transaction.MAX_STANDARD_TX_SIZE} */
public static final int MAX_SIMULTANEOUS_INPUTS = 600;
private final long unixTimeSeconds;
private final Wallet wallet;
private final boolean ignorePending;
public KeyTimeCoinSelector(Wallet wallet, long unixTimeSeconds, boolean ignorePending) {
this.unixTimeSeconds = unixTimeSeconds;
this.wallet = wallet;
this.ignorePending = ignorePending;
}
@Override
public Wallet.CoinSelection select(BigInteger target, LinkedList<TransactionOutput> candidates) {
try {
LinkedList<TransactionOutput> gathered = Lists.newLinkedList();
BigInteger valueGathered = BigInteger.ZERO;
for (TransactionOutput output : candidates) {
if (ignorePending && !isConfirmed(output))
continue;
// Find the key that controls output, assuming it's a regular pay-to-pubkey or pay-to-address output.
// We ignore any other kind of exotic output on the assumption we can't spend it ourselves.
final Script scriptPubKey = output.getScriptPubKey();
ECKey controllingKey;
if (scriptPubKey.isSentToRawPubKey()) {
controllingKey = wallet.findKeyFromPubKey(scriptPubKey.getPubKey());
} else if (scriptPubKey.isSentToAddress()) {
controllingKey = wallet.findKeyFromPubHash(scriptPubKey.getPubKeyHash());
} else {
log.info("Skipping tx output {} because it's not of simple form.", output);
continue;
}
if (controllingKey.getCreationTimeSeconds() >= unixTimeSeconds) continue;
// It's older than the cutoff time so select.
valueGathered = valueGathered.add(output.getValue());
gathered.push(output);
if (gathered.size() >= MAX_SIMULTANEOUS_INPUTS) {
log.warn("Reached {} inputs, going further would yield a tx that is too large, stopping here.", gathered.size());
break;
}
}
return new Wallet.CoinSelection(valueGathered, gathered);
} catch (ScriptException e) {
throw new RuntimeException(e); // We should never have problems understanding scripts in our wallet.
}
}
private boolean isConfirmed(TransactionOutput output) {
return output.getParentTransaction().getConfidence().getConfidenceType().equals(TransactionConfidence.ConfidenceType.BUILDING);
}
}

View File

@@ -3951,10 +3951,19 @@ public final class Protos {
int getBlockHashCount();
com.google.protobuf.ByteString getBlockHash(int index);
// repeated int32 block_relativity_offsets = 11;
java.util.List<java.lang.Integer> getBlockRelativityOffsetsList();
int getBlockRelativityOffsetsCount();
int getBlockRelativityOffsets(int index);
// optional .wallet.TransactionConfidence confidence = 9;
boolean hasConfidence();
org.bitcoinj.wallet.Protos.TransactionConfidence getConfidence();
org.bitcoinj.wallet.Protos.TransactionConfidenceOrBuilder getConfidenceOrBuilder();
// optional .wallet.Transaction.Purpose purpose = 10 [default = UNKNOWN];
boolean hasPurpose();
org.bitcoinj.wallet.Protos.Transaction.Purpose getPurpose();
}
public static final class Transaction extends
com.google.protobuf.GeneratedMessage
@@ -4065,6 +4074,78 @@ public final class Protos {
// @@protoc_insertion_point(enum_scope:wallet.Transaction.Pool)
}
public enum Purpose
implements com.google.protobuf.ProtocolMessageEnum {
UNKNOWN(0, 0),
USER_PAYMENT(1, 1),
KEY_ROTATION(2, 2),
;
public static final int UNKNOWN_VALUE = 0;
public static final int USER_PAYMENT_VALUE = 1;
public static final int KEY_ROTATION_VALUE = 2;
public final int getNumber() { return value; }
public static Purpose valueOf(int value) {
switch (value) {
case 0: return UNKNOWN;
case 1: return USER_PAYMENT;
case 2: return KEY_ROTATION;
default: return null;
}
}
public static com.google.protobuf.Internal.EnumLiteMap<Purpose>
internalGetValueMap() {
return internalValueMap;
}
private static com.google.protobuf.Internal.EnumLiteMap<Purpose>
internalValueMap =
new com.google.protobuf.Internal.EnumLiteMap<Purpose>() {
public Purpose findValueByNumber(int number) {
return Purpose.valueOf(number);
}
};
public final com.google.protobuf.Descriptors.EnumValueDescriptor
getValueDescriptor() {
return getDescriptor().getValues().get(index);
}
public final com.google.protobuf.Descriptors.EnumDescriptor
getDescriptorForType() {
return getDescriptor();
}
public static final com.google.protobuf.Descriptors.EnumDescriptor
getDescriptor() {
return org.bitcoinj.wallet.Protos.Transaction.getDescriptor().getEnumTypes().get(1);
}
private static final Purpose[] VALUES = {
UNKNOWN, USER_PAYMENT, KEY_ROTATION,
};
public static Purpose valueOf(
com.google.protobuf.Descriptors.EnumValueDescriptor desc) {
if (desc.getType() != getDescriptor()) {
throw new java.lang.IllegalArgumentException(
"EnumValueDescriptor is not for this type.");
}
return VALUES[desc.getIndex()];
}
private final int index;
private final int value;
private Purpose(int index, int value) {
this.index = index;
this.value = value;
}
// @@protoc_insertion_point(enum_scope:wallet.Transaction.Purpose)
}
private int bitField0_;
// required int32 version = 1;
public static final int VERSION_FIELD_NUMBER = 1;
@@ -4172,6 +4253,20 @@ public final class Protos {
return blockHash_.get(index);
}
// repeated int32 block_relativity_offsets = 11;
public static final int BLOCK_RELATIVITY_OFFSETS_FIELD_NUMBER = 11;
private java.util.List<java.lang.Integer> blockRelativityOffsets_;
public java.util.List<java.lang.Integer>
getBlockRelativityOffsetsList() {
return blockRelativityOffsets_;
}
public int getBlockRelativityOffsetsCount() {
return blockRelativityOffsets_.size();
}
public int getBlockRelativityOffsets(int index) {
return blockRelativityOffsets_.get(index);
}
// optional .wallet.TransactionConfidence confidence = 9;
public static final int CONFIDENCE_FIELD_NUMBER = 9;
private org.bitcoinj.wallet.Protos.TransactionConfidence confidence_;
@@ -4185,6 +4280,16 @@ public final class Protos {
return confidence_;
}
// optional .wallet.Transaction.Purpose purpose = 10 [default = UNKNOWN];
public static final int PURPOSE_FIELD_NUMBER = 10;
private org.bitcoinj.wallet.Protos.Transaction.Purpose purpose_;
public boolean hasPurpose() {
return ((bitField0_ & 0x00000040) == 0x00000040);
}
public org.bitcoinj.wallet.Protos.Transaction.Purpose getPurpose() {
return purpose_;
}
private void initFields() {
version_ = 0;
hash_ = com.google.protobuf.ByteString.EMPTY;
@@ -4194,7 +4299,9 @@ public final class Protos {
transactionInput_ = java.util.Collections.emptyList();
transactionOutput_ = java.util.Collections.emptyList();
blockHash_ = java.util.Collections.emptyList();;
blockRelativityOffsets_ = java.util.Collections.emptyList();;
confidence_ = org.bitcoinj.wallet.Protos.TransactionConfidence.getDefaultInstance();
purpose_ = org.bitcoinj.wallet.Protos.Transaction.Purpose.UNKNOWN;
}
private byte memoizedIsInitialized = -1;
public final boolean isInitialized() {
@@ -4261,6 +4368,12 @@ public final class Protos {
if (((bitField0_ & 0x00000020) == 0x00000020)) {
output.writeMessage(9, confidence_);
}
if (((bitField0_ & 0x00000040) == 0x00000040)) {
output.writeEnum(10, purpose_.getNumber());
}
for (int i = 0; i < blockRelativityOffsets_.size(); i++) {
output.writeInt32(11, blockRelativityOffsets_.get(i));
}
getUnknownFields().writeTo(output);
}
@@ -4311,6 +4424,19 @@ public final class Protos {
size += com.google.protobuf.CodedOutputStream
.computeMessageSize(9, confidence_);
}
if (((bitField0_ & 0x00000040) == 0x00000040)) {
size += com.google.protobuf.CodedOutputStream
.computeEnumSize(10, purpose_.getNumber());
}
{
int dataSize = 0;
for (int i = 0; i < blockRelativityOffsets_.size(); i++) {
dataSize += com.google.protobuf.CodedOutputStream
.computeInt32SizeNoTag(blockRelativityOffsets_.get(i));
}
size += dataSize;
size += 1 * getBlockRelativityOffsetsList().size();
}
size += getUnknownFields().getSerializedSize();
memoizedSerializedSize = size;
return size;
@@ -4462,12 +4588,16 @@ public final class Protos {
}
blockHash_ = java.util.Collections.emptyList();;
bitField0_ = (bitField0_ & ~0x00000080);
blockRelativityOffsets_ = java.util.Collections.emptyList();;
bitField0_ = (bitField0_ & ~0x00000100);
if (confidenceBuilder_ == null) {
confidence_ = org.bitcoinj.wallet.Protos.TransactionConfidence.getDefaultInstance();
} else {
confidenceBuilder_.clear();
}
bitField0_ = (bitField0_ & ~0x00000100);
bitField0_ = (bitField0_ & ~0x00000200);
purpose_ = org.bitcoinj.wallet.Protos.Transaction.Purpose.UNKNOWN;
bitField0_ = (bitField0_ & ~0x00000400);
return this;
}
@@ -4549,7 +4679,12 @@ public final class Protos {
bitField0_ = (bitField0_ & ~0x00000080);
}
result.blockHash_ = blockHash_;
if (((from_bitField0_ & 0x00000100) == 0x00000100)) {
if (((bitField0_ & 0x00000100) == 0x00000100)) {
blockRelativityOffsets_ = java.util.Collections.unmodifiableList(blockRelativityOffsets_);
bitField0_ = (bitField0_ & ~0x00000100);
}
result.blockRelativityOffsets_ = blockRelativityOffsets_;
if (((from_bitField0_ & 0x00000200) == 0x00000200)) {
to_bitField0_ |= 0x00000020;
}
if (confidenceBuilder_ == null) {
@@ -4557,6 +4692,10 @@ public final class Protos {
} else {
result.confidence_ = confidenceBuilder_.build();
}
if (((from_bitField0_ & 0x00000400) == 0x00000400)) {
to_bitField0_ |= 0x00000040;
}
result.purpose_ = purpose_;
result.bitField0_ = to_bitField0_;
onBuilt();
return result;
@@ -4650,9 +4789,22 @@ public final class Protos {
}
onChanged();
}
if (!other.blockRelativityOffsets_.isEmpty()) {
if (blockRelativityOffsets_.isEmpty()) {
blockRelativityOffsets_ = other.blockRelativityOffsets_;
bitField0_ = (bitField0_ & ~0x00000100);
} else {
ensureBlockRelativityOffsetsIsMutable();
blockRelativityOffsets_.addAll(other.blockRelativityOffsets_);
}
onChanged();
}
if (other.hasConfidence()) {
mergeConfidence(other.getConfidence());
}
if (other.hasPurpose()) {
setPurpose(other.getPurpose());
}
this.mergeUnknownFields(other.getUnknownFields());
return this;
}
@@ -4767,6 +4919,31 @@ public final class Protos {
setConfidence(subBuilder.buildPartial());
break;
}
case 80: {
int rawValue = input.readEnum();
org.bitcoinj.wallet.Protos.Transaction.Purpose value = org.bitcoinj.wallet.Protos.Transaction.Purpose.valueOf(rawValue);
if (value == null) {
unknownFields.mergeVarintField(10, rawValue);
} else {
bitField0_ |= 0x00000400;
purpose_ = value;
}
break;
}
case 88: {
ensureBlockRelativityOffsetsIsMutable();
blockRelativityOffsets_.add(input.readInt32());
break;
}
case 90: {
int length = input.readRawVarint32();
int limit = input.pushLimit(length);
while (input.getBytesUntilLimit() > 0) {
addBlockRelativityOffsets(input.readInt32());
}
input.popLimit(limit);
break;
}
}
}
}
@@ -5307,12 +5484,57 @@ public final class Protos {
return this;
}
// repeated int32 block_relativity_offsets = 11;
private java.util.List<java.lang.Integer> blockRelativityOffsets_ = java.util.Collections.emptyList();;
private void ensureBlockRelativityOffsetsIsMutable() {
if (!((bitField0_ & 0x00000100) == 0x00000100)) {
blockRelativityOffsets_ = new java.util.ArrayList<java.lang.Integer>(blockRelativityOffsets_);
bitField0_ |= 0x00000100;
}
}
public java.util.List<java.lang.Integer>
getBlockRelativityOffsetsList() {
return java.util.Collections.unmodifiableList(blockRelativityOffsets_);
}
public int getBlockRelativityOffsetsCount() {
return blockRelativityOffsets_.size();
}
public int getBlockRelativityOffsets(int index) {
return blockRelativityOffsets_.get(index);
}
public Builder setBlockRelativityOffsets(
int index, int value) {
ensureBlockRelativityOffsetsIsMutable();
blockRelativityOffsets_.set(index, value);
onChanged();
return this;
}
public Builder addBlockRelativityOffsets(int value) {
ensureBlockRelativityOffsetsIsMutable();
blockRelativityOffsets_.add(value);
onChanged();
return this;
}
public Builder addAllBlockRelativityOffsets(
java.lang.Iterable<? extends java.lang.Integer> values) {
ensureBlockRelativityOffsetsIsMutable();
super.addAll(values, blockRelativityOffsets_);
onChanged();
return this;
}
public Builder clearBlockRelativityOffsets() {
blockRelativityOffsets_ = java.util.Collections.emptyList();;
bitField0_ = (bitField0_ & ~0x00000100);
onChanged();
return this;
}
// optional .wallet.TransactionConfidence confidence = 9;
private org.bitcoinj.wallet.Protos.TransactionConfidence confidence_ = org.bitcoinj.wallet.Protos.TransactionConfidence.getDefaultInstance();
private com.google.protobuf.SingleFieldBuilder<
org.bitcoinj.wallet.Protos.TransactionConfidence, org.bitcoinj.wallet.Protos.TransactionConfidence.Builder, org.bitcoinj.wallet.Protos.TransactionConfidenceOrBuilder> confidenceBuilder_;
public boolean hasConfidence() {
return ((bitField0_ & 0x00000100) == 0x00000100);
return ((bitField0_ & 0x00000200) == 0x00000200);
}
public org.bitcoinj.wallet.Protos.TransactionConfidence getConfidence() {
if (confidenceBuilder_ == null) {
@@ -5331,7 +5553,7 @@ public final class Protos {
} else {
confidenceBuilder_.setMessage(value);
}
bitField0_ |= 0x00000100;
bitField0_ |= 0x00000200;
return this;
}
public Builder setConfidence(
@@ -5342,12 +5564,12 @@ public final class Protos {
} else {
confidenceBuilder_.setMessage(builderForValue.build());
}
bitField0_ |= 0x00000100;
bitField0_ |= 0x00000200;
return this;
}
public Builder mergeConfidence(org.bitcoinj.wallet.Protos.TransactionConfidence value) {
if (confidenceBuilder_ == null) {
if (((bitField0_ & 0x00000100) == 0x00000100) &&
if (((bitField0_ & 0x00000200) == 0x00000200) &&
confidence_ != org.bitcoinj.wallet.Protos.TransactionConfidence.getDefaultInstance()) {
confidence_ =
org.bitcoinj.wallet.Protos.TransactionConfidence.newBuilder(confidence_).mergeFrom(value).buildPartial();
@@ -5358,7 +5580,7 @@ public final class Protos {
} else {
confidenceBuilder_.mergeFrom(value);
}
bitField0_ |= 0x00000100;
bitField0_ |= 0x00000200;
return this;
}
public Builder clearConfidence() {
@@ -5368,11 +5590,11 @@ public final class Protos {
} else {
confidenceBuilder_.clear();
}
bitField0_ = (bitField0_ & ~0x00000100);
bitField0_ = (bitField0_ & ~0x00000200);
return this;
}
public org.bitcoinj.wallet.Protos.TransactionConfidence.Builder getConfidenceBuilder() {
bitField0_ |= 0x00000100;
bitField0_ |= 0x00000200;
onChanged();
return getConfidenceFieldBuilder().getBuilder();
}
@@ -5397,6 +5619,30 @@ public final class Protos {
return confidenceBuilder_;
}
// optional .wallet.Transaction.Purpose purpose = 10 [default = UNKNOWN];
private org.bitcoinj.wallet.Protos.Transaction.Purpose purpose_ = org.bitcoinj.wallet.Protos.Transaction.Purpose.UNKNOWN;
public boolean hasPurpose() {
return ((bitField0_ & 0x00000400) == 0x00000400);
}
public org.bitcoinj.wallet.Protos.Transaction.Purpose getPurpose() {
return purpose_;
}
public Builder setPurpose(org.bitcoinj.wallet.Protos.Transaction.Purpose value) {
if (value == null) {
throw new NullPointerException();
}
bitField0_ |= 0x00000400;
purpose_ = value;
onChanged();
return this;
}
public Builder clearPurpose() {
bitField0_ = (bitField0_ & ~0x00000400);
purpose_ = org.bitcoinj.wallet.Protos.Transaction.Purpose.UNKNOWN;
onChanged();
return this;
}
// @@protoc_insertion_point(builder_scope:wallet.Transaction)
}
@@ -6505,6 +6751,10 @@ public final class Protos {
// optional string description = 11;
boolean hasDescription();
String getDescription();
// optional uint64 key_rotation_time = 13;
boolean hasKeyRotationTime();
long getKeyRotationTime();
}
public static final class Wallet extends
com.google.protobuf.GeneratedMessage
@@ -6784,6 +7034,16 @@ public final class Protos {
}
}
// optional uint64 key_rotation_time = 13;
public static final int KEY_ROTATION_TIME_FIELD_NUMBER = 13;
private long keyRotationTime_;
public boolean hasKeyRotationTime() {
return ((bitField0_ & 0x00000080) == 0x00000080);
}
public long getKeyRotationTime() {
return keyRotationTime_;
}
private void initFields() {
networkIdentifier_ = "";
lastSeenBlockHash_ = com.google.protobuf.ByteString.EMPTY;
@@ -6795,6 +7055,7 @@ public final class Protos {
version_ = 0;
extension_ = java.util.Collections.emptyList();
description_ = "";
keyRotationTime_ = 0L;
}
private byte memoizedIsInitialized = -1;
public final boolean isInitialized() {
@@ -6866,6 +7127,9 @@ public final class Protos {
if (((bitField0_ & 0x00000004) == 0x00000004)) {
output.writeUInt32(12, lastSeenBlockHeight_);
}
if (((bitField0_ & 0x00000080) == 0x00000080)) {
output.writeUInt64(13, keyRotationTime_);
}
getUnknownFields().writeTo(output);
}
@@ -6915,6 +7179,10 @@ public final class Protos {
size += com.google.protobuf.CodedOutputStream
.computeUInt32Size(12, lastSeenBlockHeight_);
}
if (((bitField0_ & 0x00000080) == 0x00000080)) {
size += com.google.protobuf.CodedOutputStream
.computeUInt64Size(13, keyRotationTime_);
}
size += getUnknownFields().getSerializedSize();
memoizedSerializedSize = size;
return size;
@@ -7079,6 +7347,8 @@ public final class Protos {
}
description_ = "";
bitField0_ = (bitField0_ & ~0x00000200);
keyRotationTime_ = 0L;
bitField0_ = (bitField0_ & ~0x00000400);
return this;
}
@@ -7176,6 +7446,10 @@ public final class Protos {
to_bitField0_ |= 0x00000040;
}
result.description_ = description_;
if (((from_bitField0_ & 0x00000400) == 0x00000400)) {
to_bitField0_ |= 0x00000080;
}
result.keyRotationTime_ = keyRotationTime_;
result.bitField0_ = to_bitField0_;
onBuilt();
return result;
@@ -7291,6 +7565,9 @@ public final class Protos {
if (other.hasDescription()) {
setDescription(other.getDescription());
}
if (other.hasKeyRotationTime()) {
setKeyRotationTime(other.getKeyRotationTime());
}
this.mergeUnknownFields(other.getUnknownFields());
return this;
}
@@ -7413,6 +7690,11 @@ public final class Protos {
lastSeenBlockHeight_ = input.readUInt32();
break;
}
case 104: {
bitField0_ |= 0x00000400;
keyRotationTime_ = input.readUInt64();
break;
}
}
}
}
@@ -8229,6 +8511,27 @@ public final class Protos {
onChanged();
}
// optional uint64 key_rotation_time = 13;
private long keyRotationTime_ ;
public boolean hasKeyRotationTime() {
return ((bitField0_ & 0x00000400) == 0x00000400);
}
public long getKeyRotationTime() {
return keyRotationTime_;
}
public Builder setKeyRotationTime(long value) {
bitField0_ |= 0x00000400;
keyRotationTime_ = value;
onChanged();
return this;
}
public Builder clearKeyRotationTime() {
bitField0_ = (bitField0_ & ~0x00000400);
keyRotationTime_ = 0L;
onChanged();
return this;
}
// @@protoc_insertion_point(builder_scope:wallet.Wallet)
}
@@ -8326,32 +8629,36 @@ public final class Protos {
"\n\010BUILDING\020\001\022\013\n\007PENDING\020\002\022\025\n\021NOT_IN_BEST" +
"_CHAIN\020\003\022\010\n\004DEAD\020\004\"A\n\006Source\022\022\n\016SOURCE_U" +
"NKNOWN\020\000\022\022\n\016SOURCE_NETWORK\020\001\022\017\n\013SOURCE_S" +
"ELF\020\002\"\211\003\n\013Transaction\022\017\n\007version\030\001 \002(\005\022\014" +
"ELF\020\002\"\236\004\n\013Transaction\022\017\n\007version\030\001 \002(\005\022\014" +
"\n\004hash\030\002 \002(\014\022&\n\004pool\030\003 \001(\0162\030.wallet.Tran" +
"saction.Pool\022\021\n\tlock_time\030\004 \001(\r\022\022\n\nupdat",
"ed_at\030\005 \001(\003\0223\n\021transaction_input\030\006 \003(\0132\030" +
".wallet.TransactionInput\0225\n\022transaction_" +
"output\030\007 \003(\0132\031.wallet.TransactionOutput\022" +
"\022\n\nblock_hash\030\010 \003(\014\0221\n\nconfidence\030\t \001(\0132" +
"\035.wallet.TransactionConfidence\"Y\n\004Pool\022\013" +
"\n\007UNSPENT\020\004\022\t\n\005SPENT\020\005\022\014\n\010INACTIVE\020\002\022\010\n\004" +
"DEAD\020\n\022\013\n\007PENDING\020\020\022\024\n\020PENDING_INACTIVE\020" +
"\022\"N\n\020ScryptParameters\022\014\n\004salt\030\001 \002(\014\022\020\n\001n" +
"\030\002 \001(\003:\00516384\022\014\n\001r\030\003 \001(\005:\0018\022\014\n\001p\030\004 \001(\005:\001" +
"1\"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\"\255\003\n\006Wallet\022\032\n\022networ" +
"k_identifier\030\001 \002(\t\022\034\n\024last_seen_block_ha" +
"sh\030\002 \001(\014\022\036\n\026last_seen_block_height\030\014 \001(\r" +
"\022\030\n\003key\030\003 \003(\0132\013.wallet.Key\022(\n\013transactio" +
"n\030\004 \003(\0132\023.wallet.Transaction\022C\n\017encrypti" +
"on_type\030\005 \001(\0162\035.wallet.Wallet.Encryption" +
"Type:\013UNENCRYPTED\0227\n\025encryption_paramete" +
"rs\030\006 \001(\0132\030.wallet.ScryptParameters\022\017\n\007ve" +
"rsion\030\007 \001(\005\022$\n\textension\030\n \003(\0132\021.wallet." +
"Extension\022\023\n\013description\030\013 \001(\t\";\n\016Encryp",
"tionType\022\017\n\013UNENCRYPTED\020\001\022\030\n\024ENCRYPTED_S" +
"CRYPT_AES\020\002B\035\n\023org.bitcoinj.walletB\006Prot" +
"os"
"\022\n\nblock_hash\030\010 \003(\014\022 \n\030block_relativity_" +
"offsets\030\013 \003(\005\0221\n\nconfidence\030\t \001(\0132\035.wall" +
"et.TransactionConfidence\0225\n\007purpose\030\n \001(" +
"\0162\033.wallet.Transaction.Purpose:\007UNKNOWN\"" +
"Y\n\004Pool\022\013\n\007UNSPENT\020\004\022\t\n\005SPENT\020\005\022\014\n\010INACT" +
"IVE\020\002\022\010\n\004DEAD\020\n\022\013\n\007PENDING\020\020\022\024\n\020PENDING_" +
"INACTIVE\020\022\":\n\007Purpose\022\013\n\007UNKNOWN\020\000\022\020\n\014US",
"ER_PAYMENT\020\001\022\020\n\014KEY_ROTATION\020\002\"N\n\020Scrypt" +
"Parameters\022\014\n\004salt\030\001 \002(\014\022\020\n\001n\030\002 \001(\003:\005163" +
"84\022\014\n\001r\030\003 \001(\005:\0018\022\014\n\001p\030\004 \001(\005:\0011\"8\n\tExtens" +
"ion\022\n\n\002id\030\001 \002(\t\022\014\n\004data\030\002 \002(\014\022\021\n\tmandato" +
"ry\030\003 \002(\010\"\310\003\n\006Wallet\022\032\n\022network_identifie" +
"r\030\001 \002(\t\022\034\n\024last_seen_block_hash\030\002 \001(\014\022\036\n" +
"\026last_seen_block_height\030\014 \001(\r\022\030\n\003key\030\003 \003" +
"(\0132\013.wallet.Key\022(\n\013transaction\030\004 \003(\0132\023.w" +
"allet.Transaction\022C\n\017encryption_type\030\005 \001" +
"(\0162\035.wallet.Wallet.EncryptionType:\013UNENC",
"RYPTED\0227\n\025encryption_parameters\030\006 \001(\0132\030." +
"wallet.ScryptParameters\022\017\n\007version\030\007 \001(\005" +
"\022$\n\textension\030\n \003(\0132\021.wallet.Extension\022\023" +
"\n\013description\030\013 \001(\t\022\031\n\021key_rotation_time" +
"\030\r \001(\004\";\n\016EncryptionType\022\017\n\013UNENCRYPTED\020" +
"\001\022\030\n\024ENCRYPTED_SCRYPT_AES\020\002B\035\n\023org.bitco" +
"inj.walletB\006Protos"
};
com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() {
@@ -8411,7 +8718,7 @@ public final class Protos {
internal_static_wallet_Transaction_fieldAccessorTable = new
com.google.protobuf.GeneratedMessage.FieldAccessorTable(
internal_static_wallet_Transaction_descriptor,
new java.lang.String[] { "Version", "Hash", "Pool", "LockTime", "UpdatedAt", "TransactionInput", "TransactionOutput", "BlockHash", "Confidence", },
new java.lang.String[] { "Version", "Hash", "Pool", "LockTime", "UpdatedAt", "TransactionInput", "TransactionOutput", "BlockHash", "BlockRelativityOffsets", "Confidence", "Purpose", },
org.bitcoinj.wallet.Protos.Transaction.class,
org.bitcoinj.wallet.Protos.Transaction.Builder.class);
internal_static_wallet_ScryptParameters_descriptor =
@@ -8435,7 +8742,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", "LastSeenBlockHeight", "Key", "Transaction", "EncryptionType", "EncryptionParameters", "Version", "Extension", "Description", },
new java.lang.String[] { "NetworkIdentifier", "LastSeenBlockHash", "LastSeenBlockHeight", "Key", "Transaction", "EncryptionType", "EncryptionParameters", "Version", "Extension", "Description", "KeyRotationTime", },
org.bitcoinj.wallet.Protos.Wallet.class,
org.bitcoinj.wallet.Protos.Wallet.Builder.class);
return null;

View File

@@ -22,12 +22,14 @@ import com.google.bitcoin.store.FullPrunedBlockStore;
import com.google.bitcoin.store.H2FullPrunedBlockStore;
import com.google.bitcoin.utils.BlockFileLoader;
import com.google.bitcoin.utils.BriefLogFormatter;
import com.google.bitcoin.utils.Threading;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.net.InetAddress;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
/**
* A tool for comparing the blocks which are accepted/rejected by bitcoind/bitcoinj
@@ -43,7 +45,8 @@ public class BitcoindComparisonTool {
private static PeerGroup peers;
private static Sha256Hash bitcoindChainHead;
private static volatile Peer bitcoind;
private static volatile InventoryMessage mostRecentInv = null;
public static void main(String[] args) throws Exception {
BriefLogFormatter.init();
System.out.println("USAGE: bitcoinjBlockStoreLocation runLargeReorgs(1/0) [port=18444]");
@@ -55,7 +58,7 @@ public class BitcoindComparisonTool {
blockFile.deleteOnExit();
FullBlockTestGenerator generator = new FullBlockTestGenerator(params);
BlockAndValidityList blockList = generator.getBlocksToTest(true, runLargeReorgs, blockFile);
RuleList blockList = generator.getBlocksToTest(false, runLargeReorgs, blockFile);
Iterator<Block> blocks = new BlockFileLoader(params, Arrays.asList(blockFile));
try {
@@ -75,6 +78,7 @@ public class BitcoindComparisonTool {
peers.addAddress(new PeerAddress(InetAddress.getByName("localhost"), args.length > 2 ? Integer.parseInt(args[2]) : params.getPort()));
final Set<Sha256Hash> blocksRequested = Collections.synchronizedSet(new HashSet<Sha256Hash>());
final AtomicInteger unexpectedInvs = new AtomicInteger(0);
peers.addEventListener(new AbstractPeerEventListener() {
@Override
public void onPeerConnected(Peer peer, int peerCount) {
@@ -104,9 +108,30 @@ public class BitcoindComparisonTool {
if (item.type == InventoryItem.Type.Block)
blocksRequested.add(item.hash);
return null;
} else if (m instanceof InventoryMessage) {
if (mostRecentInv != null) {
log.error("Got an inv when we weren't expecting one");
unexpectedInvs.incrementAndGet();
}
mostRecentInv = (InventoryMessage) m;
}
return m;
}
}, Threading.SAME_THREAD);
peers.addPeerFilterProvider(new PeerFilterProvider() {
@Override public long getEarliestKeyCreationTime() {
return Long.MAX_VALUE;
}
@Override public int getBloomFilterElementCount() {
return 1;
}
@Override public BloomFilter getBloomFilter(int size, double falsePositiveRate, long nTweak) {
BloomFilter filter = new BloomFilter(1, 0.99, 0);
filter.setMatchAll();
return filter;
}
});
bitcoindChainHead = params.getGenesisBlock().getHash();
@@ -125,61 +150,97 @@ public class BitcoindComparisonTool {
int differingBlocks = 0;
int invalidBlocks = 0;
for (BlockAndValidity block : blockList.list) {
boolean threw = false;
Block nextBlock = blocks.next();
try {
if (chain.add(nextBlock) != block.connects) {
log.error("Block didn't match connects flag on block \"" + block.blockName + "\"");
int mempoolRulesFailed = 0;
for (Rule rule : blockList.list) {
if (rule instanceof BlockAndValidity) {
BlockAndValidity block = (BlockAndValidity) rule;
boolean threw = false;
Block nextBlock = blocks.next();
try {
if (chain.add(nextBlock) != block.connects) {
log.error("Block didn't match connects flag on block \"" + block.ruleName + "\"");
invalidBlocks++;
}
} catch (VerificationException e) {
threw = true;
if (!block.throwsException) {
log.error("Block didn't match throws flag on block \"" + block.ruleName + "\"");
e.printStackTrace();
invalidBlocks++;
} else if (block.connects) {
log.error("Block didn't match connects flag on block \"" + block.ruleName + "\"");
e.printStackTrace();
invalidBlocks++;
}
}
if (!threw && block.throwsException) {
log.error("Block didn't match throws flag on block \"" + block.ruleName + "\"");
invalidBlocks++;
} else if (!chain.getChainHead().getHeader().getHash().equals(block.hashChainTipAfterBlock)) {
log.error("New block head didn't match the correct value after block \"" + block.ruleName + "\"");
invalidBlocks++;
} else if (chain.getChainHead().getHeight() != block.heightAfterBlock) {
log.error("New block head didn't match the correct height after block " + block.ruleName);
invalidBlocks++;
}
} catch (VerificationException e) {
threw = true;
if (!block.throwsException) {
log.error("Block didn't match throws flag on block \"" + block.blockName + "\"");
e.printStackTrace();
invalidBlocks++;
} else if (block.connects) {
log.error("Block didn't match connects flag on block \"" + block.blockName + "\"");
e.printStackTrace();
invalidBlocks++;
InventoryMessage message = new InventoryMessage(params);
message.addBlock(nextBlock);
bitcoind.sendMessage(message);
// bitcoind doesn't request blocks inline so we can't rely on a ping for synchronization
for (int i = 0; !blocksRequested.contains(nextBlock.getHash()); i++) {
if (i % 20 == 19)
log.error("bitcoind still hasn't requested block " + block.ruleName + " with hash " + nextBlock.getHash());
Thread.sleep(50);
}
bitcoind.sendMessage(nextBlock);
locator.clear();
locator.add(bitcoindChainHead);
bitcoind.sendMessage(new GetHeadersMessage(params, locator, hashTo));
bitcoind.ping().get();
if (!chain.getChainHead().getHeader().getHash().equals(bitcoindChainHead)) {
differingBlocks++;
log.error("bitcoind and bitcoinj acceptance differs on block \"" + block.ruleName + "\"");
}
log.info("Block \"" + block.ruleName + "\" completed processing");
} else if (rule instanceof MemoryPoolState) {
MemoryPoolMessage message = new MemoryPoolMessage();
bitcoind.sendMessage(message);
bitcoind.ping().get();
if (mostRecentInv == null && !((MemoryPoolState) rule).mempool.isEmpty()) {
log.error("bitcoind had an empty mempool, but we expected some transactions on rule " + rule.ruleName);
mempoolRulesFailed++;
} else if (mostRecentInv != null && ((MemoryPoolState) rule).mempool.isEmpty()) {
log.error("bitcoind had a non-empty mempool, but we expected an empty one on rule " + rule.ruleName);
mempoolRulesFailed++;
} else if (mostRecentInv != null) {
Set<InventoryItem> originalRuleSet = new HashSet<InventoryItem>(((MemoryPoolState)rule).mempool);
boolean matches = mostRecentInv.items.size() == ((MemoryPoolState)rule).mempool.size();
for (InventoryItem item : mostRecentInv.items)
if (!((MemoryPoolState) rule).mempool.remove(item))
matches = false;
if (matches)
continue;
log.error("bitcoind's mempool didn't match what we were expecting on rule " + rule.ruleName);
log.info(" bitcoind's mempool was: ");
for (InventoryItem item : mostRecentInv.items)
log.info(" " + item.hash);
log.info(" The expected mempool was: ");
for (InventoryItem item : originalRuleSet)
log.info(" " + item.hash);
mempoolRulesFailed++;
}
mostRecentInv = null;
} else {
log.error("Unknown rule");
}
if (!threw && block.throwsException) {
log.error("Block didn't match throws flag on block \"" + block.blockName + "\"");
invalidBlocks++;
} else if (!chain.getChainHead().getHeader().getHash().equals(block.hashChainTipAfterBlock)) {
log.error("New block head didn't match the correct value after block \"" + block.blockName + "\"");
invalidBlocks++;
} else if (chain.getChainHead().getHeight() != block.heightAfterBlock) {
log.error("New block head didn't match the correct height after block " + block.blockName);
invalidBlocks++;
}
InventoryMessage message = new InventoryMessage(params);
message.addBlock(nextBlock);
bitcoind.sendMessage(message);
// bitcoind doesn't request blocks inline so we can't rely on a ping for synchronization
for (int i = 0; !blocksRequested.contains(nextBlock.getHash()); i++) {
if (i % 20 == 19)
log.error("bitcoind still hasn't requested block " + block.blockName);
Thread.sleep(50);
}
bitcoind.sendMessage(nextBlock);
locator.clear();
locator.add(bitcoindChainHead);
bitcoind.sendMessage(new GetHeadersMessage(params, locator, hashTo));
bitcoind.ping().get();
if (!chain.getChainHead().getHeader().getHash().equals(bitcoindChainHead)) {
differingBlocks++;
log.error("bitcoind and bitcoinj acceptance differs on block \"" + block.blockName + "\"");
}
log.info("Block \"" + block.blockName + "\" completed processing");
}
log.info("Done testing.\n" +
"Blocks which were not handled the same between bitcoind/bitcoinj: " + differingBlocks + "\n" +
"Blocks which should/should not have been accepted but weren't/were: " + invalidBlocks);
System.exit(differingBlocks > 0 || invalidBlocks > 0 ? 1 : 0);
"Blocks which should/should not have been accepted but weren't/were: " + invalidBlocks + "\n" +
"Transactions which were/weren't in memory pool but shouldn't/should have been: " + mempoolRulesFailed + "\n" +
"Unexpected inv messages: " + unexpectedInvs.get());
System.exit(differingBlocks > 0 || invalidBlocks > 0 || mempoolRulesFailed > 0 || unexpectedInvs.get() > 0 ? 1 : 0);
}
}

View File

@@ -69,8 +69,9 @@ public class BlockChainTest {
unitTestParams = UnitTestParams.get();
wallet = new Wallet(unitTestParams) {
@Override
public void receiveFromBlock(Transaction tx, StoredBlock block, BlockChain.NewBlockType blockType) throws VerificationException {
super.receiveFromBlock(tx, block, blockType);
public void receiveFromBlock(Transaction tx, StoredBlock block, BlockChain.NewBlockType blockType,
int relativityOffset) throws VerificationException {
super.receiveFromBlock(tx, block, blockType, relativityOffset);
BlockChainTest.this.block[0] = block;
if (tx.isCoinBase()) {
BlockChainTest.this.coinbaseTransaction = tx;

View File

@@ -26,12 +26,15 @@ import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.math.BigInteger;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.junit.Assert.*;
public class ChainSplitTest {
@@ -43,6 +46,7 @@ public class ChainSplitTest {
private Address coinsTo;
private Address coinsTo2;
private Address someOtherGuy;
private MemoryBlockStore blockStore;
@Before
public void setUp() throws Exception {
@@ -52,7 +56,8 @@ public class ChainSplitTest {
wallet = new Wallet(unitTestParams);
wallet.addKey(new ECKey());
wallet.addKey(new ECKey());
chain = new BlockChain(unitTestParams, wallet, new MemoryBlockStore(unitTestParams));
blockStore = new MemoryBlockStore(unitTestParams);
chain = new BlockChain(unitTestParams, wallet, blockStore);
coinsTo = wallet.getKeys().get(0).toAddress(unitTestParams);
coinsTo2 = wallet.getKeys().get(1).toAddress(unitTestParams);
someOtherGuy = new ECKey().toAddress(unitTestParams);
@@ -84,7 +89,7 @@ public class ChainSplitTest {
Threading.waitForUserCode();
assertFalse(reorgHappened.get());
assertEquals(2, walletChanged.get());
// We got two blocks which generated 50 coins each, to us.
// We got two blocks which sent 50 coins each to us.
assertEquals("100.00", Utils.bitcoinValueToFriendlyString(wallet.getBalance()));
// We now have the following chain:
// genesis -> b1 -> b2
@@ -110,10 +115,13 @@ public class ChainSplitTest {
Block b7 = b1.createNextBlock(coinsTo);
assertTrue(chain.add(b7));
Block b8 = b1.createNextBlock(coinsTo);
b8.addTransaction(b7.getTransactions().get(1));
final Transaction t = b7.getTransactions().get(1);
final Sha256Hash tHash = t.getHash();
b8.addTransaction(t);
b8.solve();
assertTrue(chain.add(b8));
assertTrue(chain.add(roundtrip(b8)));
Threading.waitForUserCode();
assertEquals(2, wallet.getTransaction(tHash).getAppearsInHashes().size());
assertFalse(reorgHappened.get()); // No re-org took place.
assertEquals(5, walletChanged.get());
assertEquals("100.00", Utils.bitcoinValueToFriendlyString(wallet.getBalance()));
@@ -181,7 +189,7 @@ public class ChainSplitTest {
Block b2 = b1.createNextBlock(someOtherGuy);
b2.addTransaction(spend);
b2.solve();
chain.add(b2);
chain.add(roundtrip(b2));
// We have 40 coins in change.
assertEquals(ConfidenceType.BUILDING, spend.getConfidence().getConfidenceType());
// genesis -> b1 (receive coins) -> b2 (spend coins)
@@ -215,7 +223,7 @@ public class ChainSplitTest {
Block b3 = b1.createNextBlock(someOtherGuy);
b3.addTransaction(spend);
b3.solve();
chain.add(b3);
chain.add(roundtrip(b3));
// The external spend is now pending.
assertEquals(Utils.toNanoCoins(0, 0), wallet.getBalance());
Transaction tx = wallet.getTransaction(spend.getHash());
@@ -232,6 +240,7 @@ public class ChainSplitTest {
// Test the standard case in which a block containing identical transactions appears on a side chain.
Block b1 = unitTestParams.getGenesisBlock().createNextBlock(coinsTo);
chain.add(b1);
final Transaction t = b1.transactions.get(1);
assertEquals("50.00", Utils.bitcoinValueToFriendlyString(wallet.getBalance()));
// genesis -> b1
// -> b2
@@ -239,11 +248,21 @@ public class ChainSplitTest {
Transaction b2coinbase = b2.transactions.get(0);
b2.transactions.clear();
b2.addTransaction(b2coinbase);
b2.addTransaction(b1.transactions.get(1));
b2.addTransaction(t);
b2.solve();
chain.add(b2);
chain.add(roundtrip(b2));
assertEquals("50.00", Utils.bitcoinValueToFriendlyString(wallet.getBalance()));
assertTrue(wallet.isConsistent());
assertEquals(2, wallet.getTransaction(t.getHash()).getAppearsInHashes().size());
// -> b2 -> b3
Block b3 = b2.createNextBlock(someOtherGuy);
chain.add(b3);
assertEquals("50.00", Utils.bitcoinValueToFriendlyString(wallet.getBalance()));
}
private Block roundtrip(Block b2) throws ProtocolException {
return new Block(unitTestParams, b2.bitcoinSerialize());
}
@Test
@@ -261,7 +280,7 @@ public class ChainSplitTest {
Block b3 = b1.createNextBlock(someOtherGuy);
b3.addTransaction(b2.transactions.get(1));
b3.solve();
chain.add(b3);
chain.add(roundtrip(b3));
assertEquals("50.00", Utils.bitcoinValueToFriendlyString(wallet.getBalance()));
}
@@ -291,13 +310,13 @@ public class ChainSplitTest {
Block b2 = b1.createNextBlock(new ECKey().toAddress(unitTestParams));
b2.addTransaction(t1);
b2.solve();
chain.add(b2);
chain.add(roundtrip(b2));
// Now we make a double spend become active after a re-org.
Block b3 = b1.createNextBlock(new ECKey().toAddress(unitTestParams));
b3.addTransaction(t2);
b3.solve();
chain.add(b3); // Side chain.
chain.add(roundtrip(b3)); // Side chain.
Block b4 = b3.createNextBlock(new ECKey().toAddress(unitTestParams));
chain.add(b4); // New best chain.
Threading.waitForUserCode();
@@ -328,9 +347,9 @@ public class ChainSplitTest {
Block b1 = unitTestParams.getGenesisBlock().createNextBlock(coinsTo);
chain.add(b1);
Transaction t1 = wallet.createSend(someOtherGuy, Utils.toNanoCoins(10, 0));
Transaction t1 = checkNotNull(wallet.createSend(someOtherGuy, Utils.toNanoCoins(10, 0)));
Address yetAnotherGuy = new ECKey().toAddress(unitTestParams);
Transaction t2 = wallet.createSend(yetAnotherGuy, Utils.toNanoCoins(20, 0));
Transaction t2 = checkNotNull(wallet.createSend(yetAnotherGuy, Utils.toNanoCoins(20, 0)));
wallet.commitTx(t1);
// t1 is still pending ...
Block b2 = b1.createNextBlock(new ECKey().toAddress(unitTestParams));
@@ -344,7 +363,7 @@ public class ChainSplitTest {
Block b3 = b1.createNextBlock(new ECKey().toAddress(unitTestParams));
b3.addTransaction(t2);
b3.solve();
chain.add(b3); // Side chain.
chain.add(roundtrip(b3)); // Side chain.
Block b4 = b3.createNextBlock(new ECKey().toAddress(unitTestParams));
chain.add(b4); // New best chain.
Threading.waitForUserCode();
@@ -365,6 +384,8 @@ public class ChainSplitTest {
assertEquals(Utils.toNanoCoins(0, 0), wallet.getBalance());
// t2 is pending - resurrected double spends take precedence over our dead transactions (which are in nobodies
// mempool by this point).
t1 = checkNotNull(wallet.getTransaction(t1.getHash()));
t2 = checkNotNull(wallet.getTransaction(t2.getHash()));
assertEquals(ConfidenceType.DEAD, t1.getConfidence().getConfidenceType());
assertEquals(ConfidenceType.PENDING, t2.getConfidence().getConfidenceType());
}
@@ -511,6 +532,41 @@ public class ChainSplitTest {
assertEquals(newWork3.add(extraWork), txns.get(2).getConfidence().getWorkDone());
}
@Test
public void orderingInsideBlock() throws Exception {
// Test that transactions received in the same block have their ordering preserved when reorganising.
// This covers issue 468.
// Receive some money to the wallet.
Transaction t1 = TestUtils.createFakeTx(unitTestParams, Utils.COIN, coinsTo);
final Block b1 = TestUtils.makeSolvedTestBlock(unitTestParams.genesisBlock, t1);
chain.add(b1);
// Send a couple of payments one after the other (so the second depends on the change output of the first).
wallet.allowSpendingUnconfirmedTransactions();
Transaction t2 = checkNotNull(wallet.createSend(new ECKey().toAddress(unitTestParams), Utils.CENT));
wallet.commitTx(t2);
Transaction t3 = checkNotNull(wallet.createSend(new ECKey().toAddress(unitTestParams), Utils.CENT));
wallet.commitTx(t3);
chain.add(TestUtils.makeSolvedTestBlock(b1, t2, t3));
final BigInteger coins0point98 = Utils.COIN.subtract(Utils.CENT).subtract(Utils.CENT);
assertEquals(coins0point98, wallet.getBalance());
// Now round trip the wallet and force a re-org.
ByteArrayOutputStream bos = new ByteArrayOutputStream();
wallet.saveToFileStream(bos);
wallet = Wallet.loadFromFileStream(new ByteArrayInputStream(bos.toByteArray()));
final Block b2 = TestUtils.makeSolvedTestBlock(b1, t2, t3);
final Block b3 = TestUtils.makeSolvedTestBlock(b2);
chain.add(b2);
chain.add(b3);
// And verify that the balance is as expected. Because signatures are currently non-deterministic if the order
// isn't being stored correctly this should fail 50% of the time.
assertEquals(coins0point98, wallet.getBalance());
}
@Test
public void coinbaseDeath() throws Exception {
// Check that a coinbase tx is marked as dead after a reorg rather than pending as normal non-double-spent

View File

@@ -75,7 +75,7 @@ public class CoinbaseBlockTest {
// Give the wallet the first transaction in the block - this is the coinbase tx.
List<Transaction> transactions = block.getTransactions();
assertNotNull(transactions);
wallet.receiveFromBlock(transactions.get(0), storedBlock, NewBlockType.BEST_CHAIN);
wallet.receiveFromBlock(transactions.get(0), storedBlock, NewBlockType.BEST_CHAIN, 0);
// Coinbase transaction should have been received successfully but be unavailable to spend (too young).
assertEquals(BALANCE_AFTER_BLOCK, wallet.getBalance(BalanceType.ESTIMATED));

View File

@@ -173,7 +173,7 @@ public class ECKeyTest {
@Test
public void signTextMessage() throws Exception {
ECKey key = new ECKey();
String message = "Hello World!";
String message = "聡中本";
String signatureBase64 = key.signMessage(message);
log.info("Message signed with " + key.toAddress(MainNetParams.get()) + ": " + signatureBase64);
// Should verify correctly.
@@ -454,29 +454,15 @@ public class ECKeyTest {
}
}
private boolean checkSomeBytesAreNonZero(byte[] bytes) {
if (bytes == null) {
return false;
} else {
for (int i = 0; i < bytes.length; i++) {
if (bytes[i] != 0) {
return true;
}
}
return false;
}
private static boolean checkSomeBytesAreNonZero(byte[] bytes) {
if (bytes == null) return false;
for (byte b : bytes) if (b != 0) return true;
return false;
}
private boolean checkAllBytesAreZero(byte[] bytes) {
if (bytes == null) {
return true;
} else {
for (int i = 0; i < bytes.length; i++) {
if (bytes[i] != 0) {
return false;
}
}
return true;
}
private static boolean checkAllBytesAreZero(byte[] bytes) {
if (bytes == null) return true;
for (byte b : bytes) if (b != 0) return false;
return true;
}
}

View File

@@ -121,7 +121,7 @@ public class FilteredBlockAndPartialMerkleTreeTests extends TestWithPeerGroup {
for (Transaction tx : transactions) {
assertTrue(tx.getConfidence().getConfidenceType() == ConfidenceType.BUILDING);
assertTrue(tx.getConfidence().getDepthInBlocks() == 1);
assertTrue(tx.getAppearsInHashes().contains(block.getHash()));
assertTrue(tx.getAppearsInHashes().keySet().contains(block.getHash()));
assertTrue(tx.getAppearsInHashes().size() == 1);
}

View File

@@ -14,15 +14,19 @@ import java.util.*;
import static com.google.bitcoin.script.ScriptOpCodes.*;
class BlockAndValidity {
/**
* Represents a block which is sent to the tested application and which the application must either reject or accept,
* depending on the flags in the rule
*/
class BlockAndValidity extends Rule {
Block block;
boolean connects;
boolean throwsException;
Sha256Hash hashChainTipAfterBlock;
int heightAfterBlock;
String blockName;
public BlockAndValidity(Map<Sha256Hash, Integer> blockToHeightMap, Block block, boolean connects, boolean throwsException, Sha256Hash hashChainTipAfterBlock, int heightAfterBlock, String blockName) {
super(blockName);
if (connects && throwsException)
throw new RuntimeException("A block cannot connect if an exception was thrown while adding it.");
this.block = block;
@@ -30,8 +34,7 @@ class BlockAndValidity {
this.throwsException = throwsException;
this.hashChainTipAfterBlock = hashChainTipAfterBlock;
this.heightAfterBlock = heightAfterBlock;
this.blockName = blockName;
// Double-check that we are always marking any given block at the same height
Integer height = blockToHeightMap.get(hashChainTipAfterBlock);
if (height != null)
@@ -41,6 +44,25 @@ class BlockAndValidity {
}
}
/**
* A test which checks the mempool state (ie defined which transactions should be in memory pool
*/
class MemoryPoolState extends Rule {
Set<InventoryItem> mempool;
public MemoryPoolState(Set<InventoryItem> mempool, String ruleName) {
super(ruleName);
this.mempool = mempool;
}
}
/** An arbitrary rule which the testing client must match */
class Rule {
String ruleName;
Rule(String ruleName) {
this.ruleName = ruleName;
}
}
class TransactionOutPointWithValue {
public TransactionOutPoint outpoint;
public BigInteger value;
@@ -52,10 +74,10 @@ class TransactionOutPointWithValue {
}
}
class BlockAndValidityList {
public List<BlockAndValidity> list;
class RuleList {
public List<Rule> list;
public int maximumReorgBlockCount;
public BlockAndValidityList(List<BlockAndValidity> list, int maximumReorgBlockCount) {
public RuleList(List<Rule> list, int maximumReorgBlockCount) {
this.list = list;
this.maximumReorgBlockCount = maximumReorgBlockCount;
}
@@ -77,24 +99,24 @@ public class FullBlockTestGenerator {
Utils.rollMockClock(0); // Set a mock clock for timestamp tests
}
public BlockAndValidityList getBlocksToTest(boolean addSigExpensiveBlocks, boolean runLargeReorgs, File blockStorageFile) throws ScriptException, ProtocolException, IOException {
public RuleList getBlocksToTest(boolean addSigExpensiveBlocks, boolean runLargeReorgs, File blockStorageFile) throws ScriptException, ProtocolException, IOException {
final FileOutputStream outStream = blockStorageFile != null ? new FileOutputStream(blockStorageFile) : null;
List<BlockAndValidity> blocks = new LinkedList<BlockAndValidity>() {
List<Rule> blocks = new LinkedList<Rule>() {
@Override
public boolean add(BlockAndValidity element) {
if (outStream != null) {
public boolean add(Rule element) {
if (outStream != null && element instanceof BlockAndValidity) {
try {
outStream.write((int) (params.getPacketMagic() >>> 24));
outStream.write((int) (params.getPacketMagic() >>> 16));
outStream.write((int) (params.getPacketMagic() >>> 8));
outStream.write((int) (params.getPacketMagic() >>> 0));
byte[] block = element.block.bitcoinSerialize();
byte[] block = ((BlockAndValidity)element).block.bitcoinSerialize();
byte[] length = new byte[4];
Utils.uint32ToByteArrayBE(block.length, length, 0);
outStream.write(Utils.reverseBytes(length));
outStream.write(block);
element.block = null;
((BlockAndValidity)element).block = null;
} catch (IOException e) {
throw new RuntimeException(e);
}
@@ -102,7 +124,7 @@ public class FullBlockTestGenerator {
return super.add(element);
}
};
BlockAndValidityList ret = new BlockAndValidityList(blocks, 10);
RuleList ret = new RuleList(blocks, 10);
Queue<TransactionOutPointWithValue> spendableOutputs = new LinkedList<TransactionOutPointWithValue>();
@@ -1095,11 +1117,16 @@ public class FullBlockTestGenerator {
Block b61 = createNextBlock(b60, chainHeadHeight + 19, out18, null);
{
byte[] scriptBytes = b61.getTransactions().get(0).getInputs().get(0).getScriptBytes();
scriptBytes[0]--; // createNextBlock will increment the first script byte on each new block
b61.getTransactions().get(0).getInputs().get(0).setScriptBytes(scriptBytes);
final Transaction tx = b61.getTransactions().get(0);
byte[] scriptBytes = tx.getInputs().get(0).getScriptBytes();
// createNextBlock will increment a uint16 in the first script bytes of each new block.
int tmp = (scriptBytes[0] | (scriptBytes[1] << 8)) - 1;
scriptBytes[0] = (byte) tmp;
scriptBytes[1] = (byte) (tmp >> 8);
tx.getInputs().get(0).setScriptBytes(scriptBytes);
b61.unCache();
Preconditions.checkState(b61.getTransactions().get(0).equals(b60.getTransactions().get(0)));
final Transaction tx2 = b60.getTransactions().get(0);
Preconditions.checkState(tx.equals(tx2));
}
b61.solve();
blocks.add(new BlockAndValidity(blockToHeightMap, b61, false, true, b60.getHash(), chainHeadHeight + 18, "b61"));
@@ -1270,6 +1297,11 @@ public class FullBlockTestGenerator {
}
b69.solve();
blocks.add(new BlockAndValidity(blockToHeightMap, b69, true, false, b69.getHash(), chainHeadHeight + 21, "b69"));
spendableOutputs.offer(new TransactionOutPointWithValue(
new TransactionOutPoint(params, 0, b69.getTransactions().get(0).getHash()),
b69.getTransactions().get(0).getOutputs().get(0).getValue(),
b69.getTransactions().get(0).getOutputs().get(0).getScriptPubKey()));
// Test spending the outpoint of a non-existent transaction
// -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) -> b69 (20)
@@ -1419,29 +1451,93 @@ public class FullBlockTestGenerator {
b76.getTransactions().get(0).getOutputs().get(0).getValue(),
b76.getTransactions().get(0).getOutputs().get(0).getScriptPubKey()));
// Test transaction resurrection
// -> b77 (24) -> b78 (22) -> b79 (23)
// \-> b80 (22) -> b81 (23) -> b82 (24)
// b78 creates a tx, which is spent in b79. after b82, both should be in mempool
//
TransactionOutPointWithValue out24 = spendableOutputs.poll(); Preconditions.checkState(out24 != null);
TransactionOutPointWithValue out25 = spendableOutputs.poll(); Preconditions.checkState(out25 != null);
TransactionOutPointWithValue out26 = spendableOutputs.poll(); Preconditions.checkState(out26 != null);
TransactionOutPointWithValue out27 = spendableOutputs.poll(); Preconditions.checkState(out27 != null);
Block b77 = createNextBlock(b76, chainHeadHeight + 25, out24, null);
blocks.add(new BlockAndValidity(blockToHeightMap, b77, true, false, b77.getHash(), chainHeadHeight + 25, "b77"));
Block b78 = createNextBlock(b77, chainHeadHeight + 26, out25, null);
Transaction b78tx = new Transaction(params);
{
b78tx.addOutput(new TransactionOutput(params, b78tx, BigInteger.ZERO, new byte[]{OP_TRUE}));
addOnlyInputToTransaction(b78tx, new TransactionOutPointWithValue(
new TransactionOutPoint(params, 1, b77.getTransactions().get(1).getHash()),
BigInteger.valueOf(1), b77.getTransactions().get(1).getOutputs().get(1).getScriptPubKey()));
b78.addTransaction(b78tx);
}
b78.solve();
blocks.add(new BlockAndValidity(blockToHeightMap, b78, true, false, b78.getHash(), chainHeadHeight + 26, "b78"));
Block b79 = createNextBlock(b78, chainHeadHeight + 27, out26, null);
Transaction b79tx = new Transaction(params);
{
b79tx.addOutput(new TransactionOutput(params, b79tx, BigInteger.ZERO, new byte[]{OP_TRUE}));
b79tx.addInput(new TransactionInput(params, b79tx, new byte[]{OP_TRUE}, new TransactionOutPoint(params, 0, b78tx.getHash())));
b79.addTransaction(b79tx);
}
b79.solve();
blocks.add(new BlockAndValidity(blockToHeightMap, b79, true, false, b79.getHash(), chainHeadHeight + 27, "b79"));
blocks.add(new MemoryPoolState(new HashSet<InventoryItem>(), "post-b79 empty mempool"));
Block b80 = createNextBlock(b77, chainHeadHeight + 26, out25, null);
blocks.add(new BlockAndValidity(blockToHeightMap, b80, true, false, b79.getHash(), chainHeadHeight + 27, "b80"));
spendableOutputs.offer(new TransactionOutPointWithValue(
new TransactionOutPoint(params, 0, b80.getTransactions().get(0).getHash()),
b80.getTransactions().get(0).getOutputs().get(0).getValue(),
b80.getTransactions().get(0).getOutputs().get(0).getScriptPubKey()));
Block b81 = createNextBlock(b80, chainHeadHeight + 27, out26, null);
blocks.add(new BlockAndValidity(blockToHeightMap, b81, true, false, b79.getHash(), chainHeadHeight + 27, "b81"));
spendableOutputs.offer(new TransactionOutPointWithValue(
new TransactionOutPoint(params, 0, b81.getTransactions().get(0).getHash()),
b81.getTransactions().get(0).getOutputs().get(0).getValue(),
b81.getTransactions().get(0).getOutputs().get(0).getScriptPubKey()));
Block b82 = createNextBlock(b81, chainHeadHeight + 28, out27, null);
blocks.add(new BlockAndValidity(blockToHeightMap, b82, true, false, b82.getHash(), chainHeadHeight + 28, "b82"));
spendableOutputs.offer(new TransactionOutPointWithValue(
new TransactionOutPoint(params, 0, b82.getTransactions().get(0).getHash()),
b82.getTransactions().get(0).getOutputs().get(0).getValue(),
b82.getTransactions().get(0).getOutputs().get(0).getScriptPubKey()));
HashSet<InventoryItem> post82Mempool = new HashSet<InventoryItem>();
post82Mempool.add(new InventoryItem(InventoryItem.Type.Transaction, b78tx.getHash()));
post82Mempool.add(new InventoryItem(InventoryItem.Type.Transaction, b79tx.getHash()));
blocks.add(new MemoryPoolState(post82Mempool, "post-b82 tx resurrection"));
// The remaining tests arent designed to fit in the standard flow, and thus must always come last
// Add new tests here.
//TODO: Explicitly address MoneyRange() checks
// Test massive reorgs (in terms of tx count)
// -> b60 (17) -> b64 (18) -> b65 (19) -> b69 (20) -> b72 (21) -> b1001 (22) -> lots of outputs -> lots of spends
// Reorg back to:
// -> b60 (17) -> b64 (18) -> b65 (19) -> b69 (20) -> b72 (21) -> b1001 (22) -> empty blocks
//
TransactionOutPointWithValue out24 = spendableOutputs.poll(); Preconditions.checkState(out24 != null);
TransactionOutPointWithValue out28 = spendableOutputs.poll(); Preconditions.checkState(out28 != null);
Block b1001 = createNextBlock(b76, chainHeadHeight + 25, out24, null);
blocks.add(new BlockAndValidity(blockToHeightMap, b1001, true, false, b1001.getHash(), chainHeadHeight + 25, "b1001"));
Block b1001 = createNextBlock(b82, chainHeadHeight + 29, out28, null);
blocks.add(new BlockAndValidity(blockToHeightMap, b1001, true, false, b1001.getHash(), chainHeadHeight + 29, "b1001"));
spendableOutputs.offer(new TransactionOutPointWithValue(
new TransactionOutPoint(params, 0, b1001.getTransactions().get(0).getHash()),
b1001.getTransactions().get(0).getOutputs().get(0).getValue(),
b1001.getTransactions().get(0).getOutputs().get(0).getScriptPubKey()));
int nextHeight = chainHeadHeight + 30;
if (runLargeReorgs) {
// No way you can fit this test in memory
Preconditions.checkArgument(blockStorageFile != null);
Block lastBlock = b1001;
int nextHeight = chainHeadHeight + 26;
TransactionOutPoint lastOutput = new TransactionOutPoint(params, 2, b1001.getTransactions().get(1).getHash());
int blockCountAfter1001;
@@ -1522,8 +1618,6 @@ public class FullBlockTestGenerator {
ret.maximumReorgBlockCount = Math.max(ret.maximumReorgBlockCount, blockCountAfter1001);
}
//TODO: Explicitly address MoneyRange() checks
if (outStream != null)
outStream.close();

View File

@@ -64,39 +64,42 @@ public class FullPrunedBlockChainTest {
public void testGeneratedChain() throws Exception {
// Tests various test cases from FullBlockTestGenerator
FullBlockTestGenerator generator = new FullBlockTestGenerator(params);
BlockAndValidityList blockList = generator.getBlocksToTest(false, false, null);
RuleList blockList = generator.getBlocksToTest(false, false, null);
store = new MemoryFullPrunedBlockStore(params, blockList.maximumReorgBlockCount);
chain = new FullPrunedBlockChain(params, store);
for (BlockAndValidity block : blockList.list) {
for (Rule rule : blockList.list) {
if (!(rule instanceof BlockAndValidity))
continue;
BlockAndValidity block = (BlockAndValidity) rule;
boolean threw = false;
try {
if (chain.add(block.block) != block.connects) {
log.error("Block didn't match connects flag on block " + block.blockName);
log.error("Block didn't match connects flag on block " + block.ruleName);
fail();
}
} catch (VerificationException e) {
threw = true;
if (!block.throwsException) {
log.error("Block didn't match throws flag on block " + block.blockName);
log.error("Block didn't match throws flag on block " + block.ruleName);
throw e;
}
if (block.connects) {
log.error("Block didn't match connects flag on block " + block.blockName);
log.error("Block didn't match connects flag on block " + block.ruleName);
fail();
}
}
if (!threw && block.throwsException) {
log.error("Block didn't match throws flag on block " + block.blockName);
log.error("Block didn't match throws flag on block " + block.ruleName);
fail();
}
if (!chain.getChainHead().getHeader().getHash().equals(block.hashChainTipAfterBlock)) {
log.error("New block head didn't match the correct value after block " + block.blockName);
log.error("New block head didn't match the correct value after block " + block.ruleName);
fail();
}
if (chain.getChainHead().getHeight() != block.heightAfterBlock) {
log.error("New block head didn't match the correct height after block " + block.blockName);
log.error("New block head didn't match the correct height after block " + block.ruleName);
fail();
}
}

View File

@@ -0,0 +1,55 @@
/**
* Copyright 2013 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.bitcoin.core;
import com.google.bitcoin.utils.Threading;
import com.google.common.util.concurrent.SettableFuture;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.locks.ReentrantLock;
public class MockTransactionBroadcaster implements TransactionBroadcaster {
private ReentrantLock lock = Threading.lock("mock tx broadcaster");
public LinkedBlockingQueue<Transaction> broadcasts = new LinkedBlockingQueue<Transaction>();
public MockTransactionBroadcaster(Wallet wallet) {
// This code achieves nothing directly, but it sets up the broadcaster/peergroup > wallet lock ordering
// so inversions can be caught.
lock.lock();
try {
wallet.getPendingTransactions();
} finally {
lock.unlock();
}
}
@Override
public SettableFuture<Transaction> broadcastTransaction(Transaction tx) {
// Use a lock just to catch lock ordering inversions.
lock.lock();
try {
SettableFuture<Transaction> result = SettableFuture.create();
broadcasts.put(tx);
return result;
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
}
}

View File

@@ -27,12 +27,12 @@ import org.junit.Test;
import java.math.BigInteger;
import java.net.InetSocketAddress;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.junit.Assert.*;
public class PeerGroupTest extends TestWithPeerGroup {
@@ -247,6 +247,7 @@ public class PeerGroupTest extends TestWithPeerGroup {
});
// A straggler reports in.
inbound(p3, inv);
Threading.waitForUserCode();
assertEquals(tx, event[1]);
assertEquals(3, tx.getConfidence().numBroadcastPeers());
assertTrue(tx.getConfidence().wasBroadcastBy(peerOf(p3).getAddress()));
@@ -302,7 +303,7 @@ public class PeerGroupTest extends TestWithPeerGroup {
Threading.waitForUserCode();
assertTrue(sendResult.broadcastComplete.isDone());
assertEquals(transactions[0], sendResult.tx);
assertEquals(transactions[0].getConfidence().numBroadcastPeers(), 2);
assertEquals(2, transactions[0].getConfidence().numBroadcastPeers());
// Confirm it.
Block b2 = TestUtils.createFakeBlock(blockStore, t1).block;
inbound(p1, b2);
@@ -312,45 +313,34 @@ public class PeerGroupTest extends TestWithPeerGroup {
peerGroup.removeWallet(wallet);
Wallet.SendRequest req = Wallet.SendRequest.to(dest, Utils.toNanoCoins(2, 0));
req.ensureMinRequiredFee = false;
Transaction t3 = wallet.sendCoinsOffline(req);
Transaction t3 = checkNotNull(wallet.sendCoinsOffline(req));
assertNull(outbound(p1)); // Nothing sent.
// Add the wallet to the peer group (simulate initialization). Transactions should be announced.
peerGroup.addWallet(wallet);
// Transaction announced to the first peer.
InventoryMessage inv1 = (InventoryMessage) outbound(p1);
// Filter is still the same as it was, so it is not rebroadcast
assertEquals(t3.getHash(), inv1.getItems().get(0).hash);
// Peer asks for the transaction, and get it.
GetDataMessage getdata = new GetDataMessage(params);
getdata.addItem(inv1.getItems().get(0));
inbound(p1, getdata);
Transaction t4 = (Transaction) outbound(p1);
assertEquals(t3, t4);
FakeChannel p3 = connectPeer(3);
assertTrue(outbound(p3) instanceof InventoryMessage);
control.verify();
assertEquals(t3.getHash(), ((Transaction) outbound(p1)).getHash());
}
@Test
public void testWalletCatchupTime() throws Exception {
// Check the fast catchup time was initialized to something around the current runtime. The wallet was
// already added to the peer in setup.
long time = new Date().getTime() / 1000;
assertTrue(peerGroup.getFastCatchupTimeSecs() > time - 10000);
// Check the fast catchup time was initialized to something around the current runtime minus a week.
// The wallet was already added to the peer in setup.
final int WEEK = 86400 * 7;
final long now = Utils.now().getTime() / 1000;
assertTrue(peerGroup.getFastCatchupTimeSecs() > now - WEEK - 10000);
Wallet w2 = new Wallet(params);
ECKey key1 = new ECKey();
key1.setCreationTimeSeconds(time - 86400); // One day ago.
key1.setCreationTimeSeconds(now - 86400); // One day ago.
w2.addKey(key1);
peerGroup.addWallet(w2);
Threading.waitForUserCode();
assertEquals(peerGroup.getFastCatchupTimeSecs(), time - 86400);
assertEquals(peerGroup.getFastCatchupTimeSecs(), now - 86400 - WEEK);
// Adding a key to the wallet should update the fast catchup time.
ECKey key2 = new ECKey();
key2.setCreationTimeSeconds(time - 100000);
key2.setCreationTimeSeconds(now - 100000);
w2.addKey(key2);
Threading.waitForUserCode();
assertEquals(peerGroup.getFastCatchupTimeSecs(), time - 100000);
assertEquals(peerGroup.getFastCatchupTimeSecs(), now - WEEK - 100000);
}
@Test

View File

@@ -63,7 +63,7 @@ public class TestWithWallet {
wallet.receivePending(tx, new ArrayList<Transaction>());
} else {
TestUtils.BlockPair bp = createFakeBlock(blockStore, tx);
wallet.receiveFromBlock(tx, bp.storedBlock, type);
wallet.receiveFromBlock(tx, bp.storedBlock, type, 0);
if (type == AbstractBlockChain.NewBlockType.BEST_CHAIN)
wallet.notifyNewBestBlock(bp.storedBlock);
}

View File

@@ -22,7 +22,9 @@ import com.google.bitcoin.core.WalletTransaction.Pool;
import com.google.bitcoin.crypto.KeyCrypter;
import com.google.bitcoin.crypto.KeyCrypterException;
import com.google.bitcoin.crypto.KeyCrypterScrypt;
import com.google.bitcoin.store.WalletProtobufSerializer;
import com.google.bitcoin.utils.Threading;
import com.google.bitcoin.wallet.KeyTimeCoinSelector;
import com.google.bitcoin.wallet.WalletFiles;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.ListenableFuture;
@@ -46,9 +48,7 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import static com.google.bitcoin.core.TestUtils.*;
import static com.google.bitcoin.core.TestUtils.makeSolvedTestBlock;
import static com.google.bitcoin.core.Utils.bitcoinValueToFriendlyString;
import static com.google.bitcoin.core.Utils.toNanoCoins;
import static com.google.bitcoin.core.Utils.*;
import static org.junit.Assert.*;
public class WalletTest extends TestWithWallet {
@@ -163,6 +163,7 @@ public class WalletTest extends TestWithWallet {
assertEquals("Wrong number of UNSPENT.3", 1, wallet.getPoolSize(WalletTransaction.Pool.UNSPENT));
assertEquals("Wrong number of ALL.3", 1, wallet.getPoolSize(WalletTransaction.Pool.ALL));
assertEquals(TransactionConfidence.Source.SELF, t2.getConfidence().getSource());
assertEquals(Transaction.Purpose.USER_PAYMENT, t2.getPurpose());
assertEquals(wallet.getChangeAddress(), t2.getOutput(1).getScriptPubKey().getToAddress(params));
// Do some basic sanity checks.
@@ -252,8 +253,8 @@ public class WalletTest extends TestWithWallet {
assertTrue(wallet.isConsistent());
// t2 and t3 gets confirmed in the same block.
BlockPair bp = createFakeBlock(blockStore, t2, t3);
wallet.receiveFromBlock(t2, bp.storedBlock, AbstractBlockChain.NewBlockType.BEST_CHAIN);
wallet.receiveFromBlock(t3, bp.storedBlock, AbstractBlockChain.NewBlockType.BEST_CHAIN);
wallet.receiveFromBlock(t2, bp.storedBlock, AbstractBlockChain.NewBlockType.BEST_CHAIN, 0);
wallet.receiveFromBlock(t3, bp.storedBlock, AbstractBlockChain.NewBlockType.BEST_CHAIN, 1);
wallet.notifyNewBestBlock(bp.storedBlock);
assertTrue(wallet.isConsistent());
}
@@ -338,7 +339,7 @@ public class WalletTest extends TestWithWallet {
// Now confirm the transaction by including it into a block.
StoredBlock b3 = createFakeBlock(blockStore, spend).storedBlock;
wallet.receiveFromBlock(spend, b3, BlockChain.NewBlockType.BEST_CHAIN);
wallet.receiveFromBlock(spend, b3, BlockChain.NewBlockType.BEST_CHAIN, 0);
// Change is confirmed. We started with 5.50 so we should have 4.50 left.
BigInteger v4 = toNanoCoins(4, 50);
@@ -441,13 +442,13 @@ public class WalletTest extends TestWithWallet {
Address someOtherGuy = new ECKey().toAddress(params);
TransactionOutput output = new TransactionOutput(params, tx, Utils.toNanoCoins(0, 5), someOtherGuy);
tx.addOutput(output);
wallet.receiveFromBlock(tx, null, BlockChain.NewBlockType.BEST_CHAIN);
wallet.receiveFromBlock(tx, null, BlockChain.NewBlockType.BEST_CHAIN, 0);
assertTrue("Wallet is not consistent", wallet.isConsistent());
Transaction txClone = new Transaction(params, tx.bitcoinSerialize());
try {
wallet.receiveFromBlock(txClone, null, BlockChain.NewBlockType.BEST_CHAIN);
wallet.receiveFromBlock(txClone, null, BlockChain.NewBlockType.BEST_CHAIN, 0);
fail("Illegal argument not thrown when it should have been.");
} catch (IllegalStateException ex) {
// expected
@@ -461,7 +462,7 @@ public class WalletTest extends TestWithWallet {
Address someOtherGuy = new ECKey().toAddress(params);
TransactionOutput output = new TransactionOutput(params, tx, Utils.toNanoCoins(0, 5), someOtherGuy);
tx.addOutput(output);
wallet.receiveFromBlock(tx, null, BlockChain.NewBlockType.BEST_CHAIN);
wallet.receiveFromBlock(tx, null, BlockChain.NewBlockType.BEST_CHAIN, 0);
assertTrue(wallet.isConsistent());
@@ -1033,7 +1034,7 @@ public class WalletTest extends TestWithWallet {
tx2.addOutput(Utils.toNanoCoins(0, 1), wallet.getChangeAddress());
wallet.receivePending(tx2, null);
BlockPair bp = createFakeBlock(blockStore, tx1);
wallet.receiveFromBlock(tx1, bp.storedBlock, AbstractBlockChain.NewBlockType.BEST_CHAIN);
wallet.receiveFromBlock(tx1, bp.storedBlock, AbstractBlockChain.NewBlockType.BEST_CHAIN, 0);
wallet.notifyNewBestBlock(bp.storedBlock);
assertEquals(BigInteger.ZERO, wallet.getBalance());
assertEquals(1, wallet.getPoolSize(Pool.SPENT));
@@ -1209,12 +1210,12 @@ public class WalletTest extends TestWithWallet {
// Generate a few outputs to us that are far too small to spend reasonably
StoredBlock block = new StoredBlock(makeSolvedTestBlock(blockStore, notMyAddr), BigInteger.ONE, 1);
Transaction tx1 = createFakeTx(params, BigInteger.ONE, myAddress);
wallet.receiveFromBlock(tx1, block, AbstractBlockChain.NewBlockType.BEST_CHAIN);
wallet.receiveFromBlock(tx1, block, AbstractBlockChain.NewBlockType.BEST_CHAIN, 0);
Transaction tx2 = createFakeTx(params, BigInteger.ONE, myAddress);
assertTrue(!tx1.getHash().equals(tx2.getHash()));
wallet.receiveFromBlock(tx2, block, AbstractBlockChain.NewBlockType.BEST_CHAIN);
wallet.receiveFromBlock(tx2, block, AbstractBlockChain.NewBlockType.BEST_CHAIN, 1);
Transaction tx3 = createFakeTx(params, BigInteger.TEN, myAddress);
wallet.receiveFromBlock(tx3, block, AbstractBlockChain.NewBlockType.BEST_CHAIN);
wallet.receiveFromBlock(tx3, block, AbstractBlockChain.NewBlockType.BEST_CHAIN, 2);
// No way we can add nearly enough fee
assertNull(wallet.createSend(notMyAddr, BigInteger.ONE));
@@ -1227,10 +1228,10 @@ public class WalletTest extends TestWithWallet {
// Add some reasonable-sized outputs
block = new StoredBlock(makeSolvedTestBlock(blockStore, notMyAddr), BigInteger.ONE, 1);
Transaction tx4 = createFakeTx(params, Utils.COIN, myAddress);
wallet.receiveFromBlock(tx4, block, AbstractBlockChain.NewBlockType.BEST_CHAIN);
wallet.receiveFromBlock(tx4, block, AbstractBlockChain.NewBlockType.BEST_CHAIN, 0);
// Simple test to make sure if we have an ouput < 0.01 we get a fee
Transaction spend1 = wallet.createSend(notMyAddr, Utils.CENT.subtract(BigInteger.ONE));
Transaction spend1 = wallet.createSend(notMyAddr, CENT.subtract(BigInteger.ONE));
assertEquals(2, spend1.getOutputs().size());
// We optimize for priority, so the output selected should be the largest one.
// We should have paid the default minfee.
@@ -1238,13 +1239,13 @@ public class WalletTest extends TestWithWallet {
Utils.COIN.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE));
// But not at exactly 0.01
Transaction spend2 = wallet.createSend(notMyAddr, Utils.CENT);
Transaction spend2 = wallet.createSend(notMyAddr, CENT);
assertEquals(2, spend2.getOutputs().size());
// We optimize for priority, so the output selected should be the largest one
assertEquals(Utils.COIN, spend2.getOutput(0).getValue().add(spend2.getOutput(1).getValue()));
// ...but not more fee than what we request
SendRequest request3 = SendRequest.to(notMyAddr, Utils.CENT.subtract(BigInteger.ONE));
SendRequest request3 = SendRequest.to(notMyAddr, CENT.subtract(BigInteger.ONE));
request3.fee = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.add(BigInteger.ONE);
assertTrue(wallet.completeTx(request3));
assertEquals(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.add(BigInteger.ONE), request3.fee);
@@ -1255,7 +1256,7 @@ public class WalletTest extends TestWithWallet {
Utils.COIN.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.add(BigInteger.ONE)));
// ...unless we need it
SendRequest request4 = SendRequest.to(notMyAddr, Utils.CENT.subtract(BigInteger.ONE));
SendRequest request4 = SendRequest.to(notMyAddr, CENT.subtract(BigInteger.ONE));
request4.fee = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.subtract(BigInteger.ONE);
assertTrue(wallet.completeTx(request4));
assertEquals(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE, request4.fee);
@@ -1265,7 +1266,7 @@ public class WalletTest extends TestWithWallet {
assertEquals(spend4.getOutput(0).getValue().add(spend4.getOutput(1).getValue()),
Utils.COIN.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE));
SendRequest request5 = SendRequest.to(notMyAddr, Utils.COIN.subtract(Utils.CENT.subtract(BigInteger.ONE)));
SendRequest request5 = SendRequest.to(notMyAddr, Utils.COIN.subtract(CENT.subtract(BigInteger.ONE)));
assertTrue(wallet.completeTx(request5));
assertEquals(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE, request5.fee);
Transaction spend5 = request5.tx;
@@ -1275,7 +1276,7 @@ public class WalletTest extends TestWithWallet {
assertEquals(spend5.getOutput(0).getValue().add(spend5.getOutput(1).getValue()),
Utils.COIN.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE));
SendRequest request6 = SendRequest.to(notMyAddr, Utils.COIN.subtract(Utils.CENT));
SendRequest request6 = SendRequest.to(notMyAddr, Utils.COIN.subtract(CENT));
assertTrue(wallet.completeTx(request6));
assertEquals(BigInteger.ZERO, request6.fee);
Transaction spend6 = request6.tx;
@@ -1284,8 +1285,8 @@ public class WalletTest extends TestWithWallet {
// We optimize for priority, so the output selected should be the largest one
assertEquals(Utils.COIN, spend6.getOutput(0).getValue().add(spend6.getOutput(1).getValue()));
SendRequest request7 = SendRequest.to(notMyAddr, Utils.COIN.subtract(Utils.CENT.subtract(BigInteger.valueOf(2)).multiply(BigInteger.valueOf(2))));
request7.tx.addOutput(Utils.CENT.subtract(BigInteger.ONE), notMyAddr);
SendRequest request7 = SendRequest.to(notMyAddr, Utils.COIN.subtract(CENT.subtract(BigInteger.valueOf(2)).multiply(BigInteger.valueOf(2))));
request7.tx.addOutput(CENT.subtract(BigInteger.ONE), notMyAddr);
assertTrue(wallet.completeTx(request7));
assertEquals(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE, request7.fee);
Transaction spend7 = request7.tx;
@@ -1340,9 +1341,9 @@ public class WalletTest extends TestWithWallet {
// Remove the coin from our wallet
wallet.commitTx(spend11);
Transaction tx5 = createFakeTx(params, Utils.CENT, myAddress);
wallet.receiveFromBlock(tx5, block, AbstractBlockChain.NewBlockType.BEST_CHAIN);
assertEquals(Utils.CENT, wallet.getBalance());
Transaction tx5 = createFakeTx(params, CENT, myAddress);
wallet.receiveFromBlock(tx5, block, AbstractBlockChain.NewBlockType.BEST_CHAIN, 0);
assertEquals(CENT, wallet.getBalance());
// Now test coin selection properly selects coin*depth
for (int i = 0; i < 100; i++) {
@@ -1352,33 +1353,33 @@ public class WalletTest extends TestWithWallet {
block = new StoredBlock(makeSolvedTestBlock(blockStore, notMyAddr), BigInteger.ONE, 1);
Transaction tx6 = createFakeTx(params, Utils.COIN, myAddress);
wallet.receiveFromBlock(tx6, block, AbstractBlockChain.NewBlockType.BEST_CHAIN);
wallet.receiveFromBlock(tx6, block, AbstractBlockChain.NewBlockType.BEST_CHAIN, 1);
assertTrue(tx5.getOutput(0).isMine(wallet) && tx5.getOutput(0).isAvailableForSpending() && tx5.getConfidence().getDepthInBlocks() == 100);
assertTrue(tx6.getOutput(0).isMine(wallet) && tx6.getOutput(0).isAvailableForSpending() && tx6.getConfidence().getDepthInBlocks() == 1);
// tx5 and tx6 have exactly the same coin*depth, so the larger should be selected...
Transaction spend12 = wallet.createSend(notMyAddr, Utils.CENT);
Transaction spend12 = wallet.createSend(notMyAddr, CENT);
assertTrue(spend12.getOutputs().size() == 2 && spend12.getOutput(0).getValue().add(spend12.getOutput(1).getValue()).equals(Utils.COIN));
wallet.notifyNewBestBlock(block);
assertTrue(tx5.getOutput(0).isMine(wallet) && tx5.getOutput(0).isAvailableForSpending() && tx5.getConfidence().getDepthInBlocks() == 101);
assertTrue(tx6.getOutput(0).isMine(wallet) && tx6.getOutput(0).isAvailableForSpending() && tx6.getConfidence().getDepthInBlocks() == 1);
// Now tx5 has slightly higher coin*depth than tx6...
Transaction spend13 = wallet.createSend(notMyAddr, Utils.CENT);
assertTrue(spend13.getOutputs().size() == 1 && spend13.getOutput(0).getValue().equals(Utils.CENT));
Transaction spend13 = wallet.createSend(notMyAddr, CENT);
assertTrue(spend13.getOutputs().size() == 1 && spend13.getOutput(0).getValue().equals(CENT));
block = new StoredBlock(makeSolvedTestBlock(blockStore, notMyAddr), BigInteger.ONE, 1);
wallet.notifyNewBestBlock(block);
assertTrue(tx5.getOutput(0).isMine(wallet) && tx5.getOutput(0).isAvailableForSpending() && tx5.getConfidence().getDepthInBlocks() == 102);
assertTrue(tx6.getOutput(0).isMine(wallet) && tx6.getOutput(0).isAvailableForSpending() && tx6.getConfidence().getDepthInBlocks() == 2);
// Now tx6 has higher coin*depth than tx5...
Transaction spend14 = wallet.createSend(notMyAddr, Utils.CENT);
Transaction spend14 = wallet.createSend(notMyAddr, CENT);
assertTrue(spend14.getOutputs().size() == 2 && spend14.getOutput(0).getValue().add(spend14.getOutput(1).getValue()).equals(Utils.COIN));
// Now test feePerKb
SendRequest request15 = SendRequest.to(notMyAddr, Utils.CENT);
SendRequest request15 = SendRequest.to(notMyAddr, CENT);
for (int i = 0; i < 29; i++)
request15.tx.addOutput(Utils.CENT, notMyAddr);
request15.tx.addOutput(CENT, notMyAddr);
assertTrue(request15.tx.bitcoinSerialize().length > 1000);
request15.feePerKb = BigInteger.ONE;
assertTrue(wallet.completeTx(request15));
@@ -1392,10 +1393,10 @@ public class WalletTest extends TestWithWallet {
outValue15 = outValue15.add(out.getValue());
assertEquals(Utils.COIN.subtract(BigInteger.valueOf(2)), outValue15);
SendRequest request16 = SendRequest.to(notMyAddr, Utils.CENT);
SendRequest request16 = SendRequest.to(notMyAddr, CENT);
request16.feePerKb = BigInteger.ZERO;
for (int i = 0; i < 29; i++)
request16.tx.addOutput(Utils.CENT, notMyAddr);
request16.tx.addOutput(CENT, notMyAddr);
assertTrue(request16.tx.bitcoinSerialize().length > 1000);
assertTrue(wallet.completeTx(request16));
// Of course the fee shouldn't be added if feePerKb == 0
@@ -1409,10 +1410,10 @@ public class WalletTest extends TestWithWallet {
assertEquals(Utils.COIN, outValue16);
// Create a transaction whose max size could be up to 999 (if signatures were maximum size)
SendRequest request17 = SendRequest.to(notMyAddr, Utils.CENT);
SendRequest request17 = SendRequest.to(notMyAddr, CENT);
for (int i = 0; i < 22; i++)
request17.tx.addOutput(Utils.CENT, notMyAddr);
request17.tx.addOutput(new TransactionOutput(params, request17.tx, Utils.CENT, new byte[15]));
request17.tx.addOutput(CENT, notMyAddr);
request17.tx.addOutput(new TransactionOutput(params, request17.tx, CENT, new byte[15]));
request17.feePerKb = BigInteger.ONE;
assertTrue(wallet.completeTx(request17));
assertEquals(BigInteger.ONE, request17.fee);
@@ -1424,9 +1425,9 @@ public class WalletTest extends TestWithWallet {
assertEquals(999, theoreticalMaxLength17);
Transaction spend17 = request17.tx;
{
// Its actual size must be between 997 and 999 (inclusive) as signatures have a 3-byte size range (almost always)
// Its actual size must be between 996 and 999 (inclusive) as signatures have a 3-byte size range (almost always)
final int length = spend17.bitcoinSerialize().length;
assertTrue(Integer.toString(length), length >= 997 && length <= 999);
assertTrue(Integer.toString(length), length >= 996 && length <= 999);
}
// Now check that it got a fee of 1 since its max size is 999 (1kb).
assertEquals(25, spend17.getOutputs().size());
@@ -1437,10 +1438,10 @@ public class WalletTest extends TestWithWallet {
assertEquals(Utils.COIN.subtract(BigInteger.ONE), outValue17);
// Create a transaction who's max size could be up to 1001 (if signatures were maximum size)
SendRequest request18 = SendRequest.to(notMyAddr, Utils.CENT);
SendRequest request18 = SendRequest.to(notMyAddr, CENT);
for (int i = 0; i < 22; i++)
request18.tx.addOutput(Utils.CENT, notMyAddr);
request18.tx.addOutput(new TransactionOutput(params, request18.tx, Utils.CENT, new byte[17]));
request18.tx.addOutput(CENT, notMyAddr);
request18.tx.addOutput(new TransactionOutput(params, request18.tx, CENT, new byte[17]));
request18.feePerKb = BigInteger.ONE;
assertTrue(wallet.completeTx(request18));
assertEquals(BigInteger.valueOf(2), request18.fee);
@@ -1463,11 +1464,11 @@ public class WalletTest extends TestWithWallet {
assertEquals(outValue18, Utils.COIN.subtract(BigInteger.valueOf(2)));
// Now create a transaction that will spend COIN + fee, which makes it require both inputs
assertEquals(wallet.getBalance(), Utils.CENT.add(Utils.COIN));
SendRequest request19 = SendRequest.to(notMyAddr, Utils.CENT);
assertEquals(wallet.getBalance(), CENT.add(Utils.COIN));
SendRequest request19 = SendRequest.to(notMyAddr, CENT);
request19.feePerKb = BigInteger.ZERO;
for (int i = 0; i < 99; i++)
request19.tx.addOutput(Utils.CENT, notMyAddr);
request19.tx.addOutput(CENT, notMyAddr);
// If we send now, we shouldn't need a fee and should only have to spend our COIN
assertTrue(wallet.completeTx(request19));
assertEquals(BigInteger.ZERO, request19.fee);
@@ -1485,14 +1486,14 @@ public class WalletTest extends TestWithWallet {
outValue19 = outValue19.add(out.getValue());
// But now our change output is CENT-minfee, so we have to pay min fee
// Change this assert when we eventually randomize output order
assertEquals(request19.tx.getOutput(request19.tx.getOutputs().size() - 1).getValue(), Utils.CENT.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE));
assertEquals(outValue19, Utils.COIN.add(Utils.CENT).subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE));
assertEquals(request19.tx.getOutput(request19.tx.getOutputs().size() - 1).getValue(), CENT.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE));
assertEquals(outValue19, Utils.COIN.add(CENT).subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE));
// Create another transaction that will spend COIN + fee, which makes it require both inputs
SendRequest request20 = SendRequest.to(notMyAddr, Utils.CENT);
SendRequest request20 = SendRequest.to(notMyAddr, CENT);
request20.feePerKb = BigInteger.ZERO;
for (int i = 0; i < 99; i++)
request20.tx.addOutput(Utils.CENT, notMyAddr);
request20.tx.addOutput(CENT, notMyAddr);
// If we send now, we shouldn't have a fee and should only have to spend our COIN
assertTrue(wallet.completeTx(request20));
assertEquals(BigInteger.ZERO, request20.fee);
@@ -1510,15 +1511,15 @@ public class WalletTest extends TestWithWallet {
for (TransactionOutput out : request20.tx.getOutputs())
outValue20 = outValue20.add(out.getValue());
// This time the fee we wanted to pay was more, so that should be what we paid
assertEquals(outValue20, Utils.COIN.add(Utils.CENT).subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.multiply(BigInteger.valueOf(4))));
assertEquals(outValue20, Utils.COIN.add(CENT).subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.multiply(BigInteger.valueOf(4))));
// Same as request 19, but make the change 0 (so it doesnt force fee) and make us require min fee as a
// result of an output < CENT.
SendRequest request21 = SendRequest.to(notMyAddr, Utils.CENT);
SendRequest request21 = SendRequest.to(notMyAddr, CENT);
request21.feePerKb = BigInteger.ZERO;
for (int i = 0; i < 99; i++)
request21.tx.addOutput(Utils.CENT, notMyAddr);
request21.tx.addOutput(Utils.CENT.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE), notMyAddr);
request21.tx.addOutput(CENT, notMyAddr);
request21.tx.addOutput(CENT.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE), notMyAddr);
// If we send without a feePerKb, we should still require REFERENCE_DEFAULT_MIN_TX_FEE because we have an output < 0.01
assertTrue(wallet.completeTx(request21));
assertEquals(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE, request21.fee);
@@ -1526,14 +1527,14 @@ public class WalletTest extends TestWithWallet {
BigInteger outValue21 = BigInteger.ZERO;
for (TransactionOutput out : request21.tx.getOutputs())
outValue21 = outValue21.add(out.getValue());
assertEquals(outValue21, Utils.COIN.add(Utils.CENT).subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE));
assertEquals(outValue21, Utils.COIN.add(CENT).subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE));
// Test feePerKb when we aren't using ensureMinRequiredFee
// Same as request 19
SendRequest request25 = SendRequest.to(notMyAddr, Utils.CENT);
SendRequest request25 = SendRequest.to(notMyAddr, CENT);
request25.feePerKb = BigInteger.ZERO;
for (int i = 0; i < 70; i++)
request25.tx.addOutput(Utils.CENT, notMyAddr);
request25.tx.addOutput(CENT, notMyAddr);
// If we send now, we shouldn't need a fee and should only have to spend our COIN
assertTrue(wallet.completeTx(request25));
assertEquals(BigInteger.ZERO, request25.fee);
@@ -1542,10 +1543,10 @@ public class WalletTest extends TestWithWallet {
// Now reset request19 and give it a fee per kb
request25.tx.clearInputs();
request25 = SendRequest.forTx(request25.tx);
request25.feePerKb = Utils.CENT.divide(BigInteger.valueOf(3));
request25.feePerKb = CENT.divide(BigInteger.valueOf(3));
request25.ensureMinRequiredFee = false;
assertTrue(wallet.completeTx(request25));
assertEquals(Utils.CENT.subtract(BigInteger.ONE), request25.fee);
assertEquals(CENT.subtract(BigInteger.ONE), request25.fee);
assertEquals(2, request25.tx.getInputs().size());
BigInteger outValue25 = BigInteger.ZERO;
for (TransactionOutput out : request25.tx.getOutputs())
@@ -1558,17 +1559,17 @@ public class WalletTest extends TestWithWallet {
// Spend our CENT output.
Transaction spendTx5 = new Transaction(params);
spendTx5.addOutput(Utils.CENT, notMyAddr);
spendTx5.addOutput(CENT, notMyAddr);
spendTx5.addInput(tx5.getOutput(0));
spendTx5.signInputs(SigHash.ALL, wallet);
wallet.receiveFromBlock(spendTx5, block, AbstractBlockChain.NewBlockType.BEST_CHAIN);
wallet.receiveFromBlock(spendTx5, block, AbstractBlockChain.NewBlockType.BEST_CHAIN, 4);
assertEquals(Utils.COIN, wallet.getBalance());
// Ensure change is discarded if it results in a fee larger than the chain (same as 8 and 9 but with feePerKb)
SendRequest request26 = SendRequest.to(notMyAddr, Utils.CENT);
SendRequest request26 = SendRequest.to(notMyAddr, CENT);
for (int i = 0; i < 98; i++)
request26.tx.addOutput(Utils.CENT, notMyAddr);
request26.tx.addOutput(Utils.CENT.subtract(
request26.tx.addOutput(CENT, notMyAddr);
request26.tx.addOutput(CENT.subtract(
Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.add(Transaction.MIN_NONDUST_OUTPUT)), notMyAddr);
assertTrue(request26.tx.bitcoinSerialize().length > 1000);
request26.feePerKb = BigInteger.ONE;
@@ -1597,14 +1598,14 @@ public class WalletTest extends TestWithWallet {
// Generate a ton of small outputs
StoredBlock block = new StoredBlock(makeSolvedTestBlock(blockStore, notMyAddr), BigInteger.ONE, 1);
int i = 0;
while (i <= Utils.CENT.divide(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE).longValue()) {
while (i <= CENT.divide(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE).longValue()) {
Transaction tx = createFakeTxWithChangeAddress(params, Transaction.REFERENCE_DEFAULT_MIN_TX_FEE, myAddress, notMyAddr);
tx.getInput(0).setSequenceNumber(i++); // Keep every transaction unique
wallet.receiveFromBlock(tx, block, AbstractBlockChain.NewBlockType.BEST_CHAIN);
wallet.receiveFromBlock(tx, block, AbstractBlockChain.NewBlockType.BEST_CHAIN, i);
}
// Create a spend that will throw away change (category 3 type 2 in which the change causes fee which is worth more than change)
SendRequest request1 = SendRequest.to(notMyAddr, Utils.CENT.add(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE).subtract(BigInteger.ONE));
SendRequest request1 = SendRequest.to(notMyAddr, CENT.add(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE).subtract(BigInteger.ONE));
assertTrue(wallet.completeTx(request1));
assertEquals(BigInteger.ONE, request1.fee);
assertEquals(request1.tx.getInputs().size(), i); // We should have spent all inputs
@@ -1612,10 +1613,10 @@ public class WalletTest extends TestWithWallet {
// Give us one more input...
Transaction tx1 = createFakeTxWithChangeAddress(params, Transaction.REFERENCE_DEFAULT_MIN_TX_FEE, myAddress, notMyAddr);
tx1.getInput(0).setSequenceNumber(i++); // Keep every transaction unique
wallet.receiveFromBlock(tx1, block, AbstractBlockChain.NewBlockType.BEST_CHAIN);
wallet.receiveFromBlock(tx1, block, AbstractBlockChain.NewBlockType.BEST_CHAIN, i);
// ... and create a spend that will throw away change (category 3 type 1 in which the change causes dust output)
SendRequest request2 = SendRequest.to(notMyAddr, Utils.CENT.add(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE).subtract(BigInteger.ONE));
SendRequest request2 = SendRequest.to(notMyAddr, CENT.add(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE).subtract(BigInteger.ONE));
assertTrue(wallet.completeTx(request2));
assertEquals(BigInteger.ONE, request2.fee);
assertEquals(request2.tx.getInputs().size(), i - 1); // We should have spent all inputs - 1
@@ -1623,31 +1624,31 @@ public class WalletTest extends TestWithWallet {
// Give us one more input...
Transaction tx2 = createFakeTxWithChangeAddress(params, Transaction.REFERENCE_DEFAULT_MIN_TX_FEE, myAddress, notMyAddr);
tx2.getInput(0).setSequenceNumber(i++); // Keep every transaction unique
wallet.receiveFromBlock(tx2, block, AbstractBlockChain.NewBlockType.BEST_CHAIN);
wallet.receiveFromBlock(tx2, block, AbstractBlockChain.NewBlockType.BEST_CHAIN, i);
// ... and create a spend that will throw away change (category 3 type 1 in which the change causes dust output)
// but that also could have been category 2 if it wanted
SendRequest request3 = SendRequest.to(notMyAddr, Utils.CENT.add(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE).subtract(BigInteger.ONE));
SendRequest request3 = SendRequest.to(notMyAddr, CENT.add(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE).subtract(BigInteger.ONE));
assertTrue(wallet.completeTx(request3));
assertEquals(BigInteger.ONE, request3.fee);
assertEquals(request3.tx.getInputs().size(), i - 2); // We should have spent all inputs - 2
//
SendRequest request4 = SendRequest.to(notMyAddr, Utils.CENT.add(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE).subtract(BigInteger.ONE));
SendRequest request4 = SendRequest.to(notMyAddr, CENT.add(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE).subtract(BigInteger.ONE));
request4.feePerKb = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.divide(BigInteger.valueOf(request3.tx.bitcoinSerialize().length));
assertTrue(wallet.completeTx(request4));
assertEquals(BigInteger.ONE, request4.fee);
assertEquals(request4.tx.getInputs().size(), i - 2); // We should have spent all inputs - 2
// Give us a few more inputs...
while (wallet.getBalance().compareTo(Utils.CENT.shiftLeft(1)) < 0) {
while (wallet.getBalance().compareTo(CENT.shiftLeft(1)) < 0) {
Transaction tx3 = createFakeTxWithChangeAddress(params, Transaction.REFERENCE_DEFAULT_MIN_TX_FEE, myAddress, notMyAddr);
tx3.getInput(0).setSequenceNumber(i++); // Keep every transaction unique
wallet.receiveFromBlock(tx3, block, AbstractBlockChain.NewBlockType.BEST_CHAIN);
wallet.receiveFromBlock(tx3, block, AbstractBlockChain.NewBlockType.BEST_CHAIN, i);
}
// ...that is just slightly less than is needed for category 1
SendRequest request5 = SendRequest.to(notMyAddr, Utils.CENT.add(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE).subtract(BigInteger.ONE));
SendRequest request5 = SendRequest.to(notMyAddr, CENT.add(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE).subtract(BigInteger.ONE));
assertTrue(wallet.completeTx(request5));
assertEquals(BigInteger.ONE, request5.fee);
assertEquals(1, request5.tx.getOutputs().size()); // We should have no change output
@@ -1655,10 +1656,10 @@ public class WalletTest extends TestWithWallet {
// Give us one more input...
Transaction tx4 = createFakeTxWithChangeAddress(params, Transaction.REFERENCE_DEFAULT_MIN_TX_FEE, myAddress, notMyAddr);
tx4.getInput(0).setSequenceNumber(i); // Keep every transaction unique
wallet.receiveFromBlock(tx4, block, AbstractBlockChain.NewBlockType.BEST_CHAIN);
wallet.receiveFromBlock(tx4, block, AbstractBlockChain.NewBlockType.BEST_CHAIN, i);
// ... that puts us in category 1 (no fee!)
SendRequest request6 = SendRequest.to(notMyAddr, Utils.CENT.add(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE).subtract(BigInteger.ONE));
SendRequest request6 = SendRequest.to(notMyAddr, CENT.add(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE).subtract(BigInteger.ONE));
assertTrue(wallet.completeTx(request6));
assertEquals(BigInteger.ZERO, request6.fee);
assertEquals(2, request6.tx.getOutputs().size()); // We should have a change output
@@ -1678,14 +1679,14 @@ public class WalletTest extends TestWithWallet {
// Generate a ton of small outputs
StoredBlock block = new StoredBlock(makeSolvedTestBlock(blockStore, notMyAddr), BigInteger.ONE, 1);
int i = 0;
while (i <= Utils.CENT.divide(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.multiply(BigInteger.TEN)).longValue()) {
while (i <= CENT.divide(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.multiply(BigInteger.TEN)).longValue()) {
Transaction tx = createFakeTxWithChangeAddress(params, Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.multiply(BigInteger.TEN), myAddress, notMyAddr);
tx.getInput(0).setSequenceNumber(i++); // Keep every transaction unique
wallet.receiveFromBlock(tx, block, AbstractBlockChain.NewBlockType.BEST_CHAIN);
wallet.receiveFromBlock(tx, block, AbstractBlockChain.NewBlockType.BEST_CHAIN, i);
}
// The selector will choose 2 with MIN_TX_FEE fee
SendRequest request1 = SendRequest.to(notMyAddr, Utils.CENT.add(BigInteger.ONE));
SendRequest request1 = SendRequest.to(notMyAddr, CENT.add(BigInteger.ONE));
assertTrue(wallet.completeTx(request1));
assertEquals(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE, request1.fee);
assertEquals(request1.tx.getInputs().size(), i); // We should have spent all inputs
@@ -1704,17 +1705,17 @@ public class WalletTest extends TestWithWallet {
// Generate a ton of small outputs
StoredBlock block = new StoredBlock(makeSolvedTestBlock(blockStore, notMyAddr), BigInteger.ONE, 1);
Transaction tx = createFakeTx(params, Utils.COIN, myAddress);
wallet.receiveFromBlock(tx, block, AbstractBlockChain.NewBlockType.BEST_CHAIN);
Transaction tx2 = createFakeTx(params, Utils.CENT, myAddress);
wallet.receiveFromBlock(tx2, block, AbstractBlockChain.NewBlockType.BEST_CHAIN);
wallet.receiveFromBlock(tx, block, AbstractBlockChain.NewBlockType.BEST_CHAIN, 0);
Transaction tx2 = createFakeTx(params, CENT, myAddress);
wallet.receiveFromBlock(tx2, block, AbstractBlockChain.NewBlockType.BEST_CHAIN, 1);
Transaction tx3 = createFakeTx(params, BigInteger.ONE, myAddress);
wallet.receiveFromBlock(tx3, block, AbstractBlockChain.NewBlockType.BEST_CHAIN);
wallet.receiveFromBlock(tx3, block, AbstractBlockChain.NewBlockType.BEST_CHAIN, 2);
// Create a transaction who's max size could be up to 1000 (if signatures were maximum size)
SendRequest request1 = SendRequest.to(notMyAddr, Utils.COIN.subtract(Utils.CENT.multiply(BigInteger.valueOf(17))));
SendRequest request1 = SendRequest.to(notMyAddr, Utils.COIN.subtract(CENT.multiply(BigInteger.valueOf(17))));
for (int i = 0; i < 16; i++)
request1.tx.addOutput(Utils.CENT, notMyAddr);
request1.tx.addOutput(new TransactionOutput(params, request1.tx, Utils.CENT, new byte[16]));
request1.tx.addOutput(CENT, notMyAddr);
request1.tx.addOutput(new TransactionOutput(params, request1.tx, CENT, new byte[16]));
request1.fee = BigInteger.ONE;
request1.feePerKb = BigInteger.ONE;
// We get a category 2 using COIN+CENT
@@ -1727,13 +1728,13 @@ public class WalletTest extends TestWithWallet {
// We then add one more satoshi output to the wallet
Transaction tx4 = createFakeTx(params, BigInteger.ONE, myAddress);
wallet.receiveFromBlock(tx4, block, AbstractBlockChain.NewBlockType.BEST_CHAIN);
wallet.receiveFromBlock(tx4, block, AbstractBlockChain.NewBlockType.BEST_CHAIN, 3);
// Create a transaction who's max size could be up to 1000 (if signatures were maximum size)
SendRequest request2 = SendRequest.to(notMyAddr, Utils.COIN.subtract(Utils.CENT.multiply(BigInteger.valueOf(17))));
SendRequest request2 = SendRequest.to(notMyAddr, Utils.COIN.subtract(CENT.multiply(BigInteger.valueOf(17))));
for (int i = 0; i < 16; i++)
request2.tx.addOutput(Utils.CENT, notMyAddr);
request2.tx.addOutput(new TransactionOutput(params, request2.tx, Utils.CENT, new byte[16]));
request2.tx.addOutput(CENT, notMyAddr);
request2.tx.addOutput(new TransactionOutput(params, request2.tx, CENT, new byte[16]));
request2.feePerKb = BigInteger.ONE;
// The process is the same as above, but now we can complete category 1 with one more input, and pay a fee of 2
assertTrue(wallet.completeTx(request2));
@@ -1752,44 +1753,44 @@ public class WalletTest extends TestWithWallet {
// Generate a few outputs to us
StoredBlock block = new StoredBlock(makeSolvedTestBlock(blockStore, notMyAddr), BigInteger.ONE, 1);
Transaction tx1 = createFakeTx(params, Utils.COIN, myAddress);
wallet.receiveFromBlock(tx1, block, AbstractBlockChain.NewBlockType.BEST_CHAIN);
wallet.receiveFromBlock(tx1, block, AbstractBlockChain.NewBlockType.BEST_CHAIN, 0);
Transaction tx2 = createFakeTx(params, Utils.COIN, myAddress); assertTrue(!tx1.getHash().equals(tx2.getHash()));
wallet.receiveFromBlock(tx2, block, AbstractBlockChain.NewBlockType.BEST_CHAIN);
Transaction tx3 = createFakeTx(params, Utils.CENT, myAddress);
wallet.receiveFromBlock(tx3, block, AbstractBlockChain.NewBlockType.BEST_CHAIN);
wallet.receiveFromBlock(tx2, block, AbstractBlockChain.NewBlockType.BEST_CHAIN, 1);
Transaction tx3 = createFakeTx(params, CENT, myAddress);
wallet.receiveFromBlock(tx3, block, AbstractBlockChain.NewBlockType.BEST_CHAIN, 2);
SendRequest request1 = SendRequest.to(notMyAddr, Utils.CENT);
SendRequest request1 = SendRequest.to(notMyAddr, CENT);
// If we just complete as-is, we will use one of the COIN outputs to get higher priority,
// resulting in a change output
assertNotNull(wallet.completeTx(request1));
assertEquals(1, request1.tx.getInputs().size());
assertEquals(2, request1.tx.getOutputs().size());
assertEquals(Utils.CENT, request1.tx.getOutput(0).getValue());
assertEquals(Utils.COIN.subtract(Utils.CENT), request1.tx.getOutput(1).getValue());
assertEquals(CENT, request1.tx.getOutput(0).getValue());
assertEquals(Utils.COIN.subtract(CENT), request1.tx.getOutput(1).getValue());
// Now create an identical request2 and add an unsigned spend of the CENT output
SendRequest request2 = SendRequest.to(notMyAddr, Utils.CENT);
SendRequest request2 = SendRequest.to(notMyAddr, CENT);
request2.tx.addInput(tx3.getOutput(0));
// Now completeTx will result in one input, one output
assertTrue(wallet.completeTx(request2));
assertEquals(1, request2.tx.getInputs().size());
assertEquals(1, request2.tx.getOutputs().size());
assertEquals(Utils.CENT, request2.tx.getOutput(0).getValue());
assertEquals(CENT, request2.tx.getOutput(0).getValue());
// Make sure it was properly signed
request2.tx.getInput(0).getScriptSig().correctlySpends(request2.tx, 0, tx3.getOutput(0).getScriptPubKey(), true);
// However, if there is no connected output, we will grab a COIN output anyway and add the CENT to fee
SendRequest request3 = SendRequest.to(notMyAddr, Utils.CENT);
SendRequest request3 = SendRequest.to(notMyAddr, CENT);
request3.tx.addInput(new TransactionInput(params, request3.tx, new byte[]{}, new TransactionOutPoint(params, 0, tx3.getHash())));
// Now completeTx will result in two inputs, two outputs and a fee of a CENT
// Note that it is simply assumed that the inputs are correctly signed, though in fact the first is not
assertTrue(wallet.completeTx(request3));
assertEquals(2, request3.tx.getInputs().size());
assertEquals(2, request3.tx.getOutputs().size());
assertEquals(Utils.CENT, request3.tx.getOutput(0).getValue());
assertEquals(Utils.COIN.subtract(Utils.CENT), request3.tx.getOutput(1).getValue());
assertEquals(CENT, request3.tx.getOutput(0).getValue());
assertEquals(Utils.COIN.subtract(CENT), request3.tx.getOutput(1).getValue());
SendRequest request4 = SendRequest.to(notMyAddr, Utils.CENT);
SendRequest request4 = SendRequest.to(notMyAddr, CENT);
request4.tx.addInput(tx3.getOutput(0));
// Now if we manually sign it, completeTx will not replace our signature
request4.tx.signInputs(SigHash.ALL, wallet);
@@ -1797,7 +1798,7 @@ public class WalletTest extends TestWithWallet {
assertTrue(wallet.completeTx(request4));
assertEquals(1, request4.tx.getInputs().size());
assertEquals(1, request4.tx.getOutputs().size());
assertEquals(Utils.CENT, request4.tx.getOutput(0).getValue());
assertEquals(CENT, request4.tx.getOutput(0).getValue());
assertArrayEquals(scriptSig, request4.tx.getInput(0).getScriptBytes());
}
@@ -1834,7 +1835,7 @@ public class WalletTest extends TestWithWallet {
Random rng = new Random();
for (int i = 0; i < rng.nextInt(100) + 1; i++) {
Transaction tx = createFakeTx(params, BigInteger.valueOf(rng.nextInt((int) Utils.COIN.longValue())), myAddress);
wallet.receiveFromBlock(tx, block, AbstractBlockChain.NewBlockType.BEST_CHAIN);
wallet.receiveFromBlock(tx, block, AbstractBlockChain.NewBlockType.BEST_CHAIN, i);
}
SendRequest request = SendRequest.emptyWallet(new ECKey().toAddress(params));
assertTrue(wallet.completeTx(request));
@@ -1847,29 +1848,42 @@ public class WalletTest extends TestWithWallet {
Address outputKey = new ECKey().toAddress(params);
// Add exactly 0.01
StoredBlock block = new StoredBlock(makeSolvedTestBlock(blockStore, outputKey), BigInteger.ONE, 1);
Transaction tx = createFakeTx(params, Utils.CENT, myAddress);
wallet.receiveFromBlock(tx, block, AbstractBlockChain.NewBlockType.BEST_CHAIN);
Transaction tx = createFakeTx(params, CENT, myAddress);
wallet.receiveFromBlock(tx, block, AbstractBlockChain.NewBlockType.BEST_CHAIN, 0);
SendRequest request = SendRequest.emptyWallet(outputKey);
assertTrue(wallet.completeTx(request));
wallet.commitTx(request.tx);
assertEquals(BigInteger.ZERO, wallet.getBalance());
assertEquals(Utils.CENT, request.tx.getOutput(0).getValue());
assertEquals(CENT, request.tx.getOutput(0).getValue());
// Add just under 0.01
StoredBlock block2 = new StoredBlock(block.getHeader().createNextBlock(outputKey), BigInteger.ONE, 2);
tx = createFakeTx(params, Utils.CENT.subtract(BigInteger.ONE), myAddress);
wallet.receiveFromBlock(tx, block2, AbstractBlockChain.NewBlockType.BEST_CHAIN);
// Add 1 confirmed cent and 1 unconfirmed cent. Verify only one cent is emptied because of the coin selection
// policies that are in use by default.
block = new StoredBlock(makeSolvedTestBlock(blockStore, outputKey), BigInteger.ONE, 1);
tx = createFakeTx(params, CENT, myAddress);
wallet.receiveFromBlock(tx, block, AbstractBlockChain.NewBlockType.BEST_CHAIN, 0);
tx = createFakeTx(params, CENT, myAddress);
wallet.receivePending(tx, null);
request = SendRequest.emptyWallet(outputKey);
assertTrue(wallet.completeTx(request));
wallet.commitTx(request.tx);
assertEquals(BigInteger.ZERO, wallet.getBalance());
assertEquals(Utils.CENT.subtract(BigInteger.ONE).subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE), request.tx.getOutput(0).getValue());
assertEquals(CENT, request.tx.getOutput(0).getValue());
// Add just under 0.01
StoredBlock block2 = new StoredBlock(block.getHeader().createNextBlock(outputKey), BigInteger.ONE, 2);
tx = createFakeTx(params, CENT.subtract(BigInteger.ONE), myAddress);
wallet.receiveFromBlock(tx, block2, AbstractBlockChain.NewBlockType.BEST_CHAIN, 0);
request = SendRequest.emptyWallet(outputKey);
assertTrue(wallet.completeTx(request));
wallet.commitTx(request.tx);
assertEquals(BigInteger.ZERO, wallet.getBalance());
assertEquals(CENT.subtract(BigInteger.ONE).subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE), request.tx.getOutput(0).getValue());
// Add an unsendable value
StoredBlock block3 = new StoredBlock(block2.getHeader().createNextBlock(outputKey), BigInteger.ONE, 3);
BigInteger outputValue = Transaction.MIN_NONDUST_OUTPUT.add(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE).subtract(BigInteger.ONE);
tx = createFakeTx(params, outputValue, myAddress);
wallet.receiveFromBlock(tx, block3, AbstractBlockChain.NewBlockType.BEST_CHAIN);
wallet.receiveFromBlock(tx, block3, AbstractBlockChain.NewBlockType.BEST_CHAIN, 0);
request = SendRequest.emptyWallet(outputKey);
assertFalse(wallet.completeTx(request));
request.ensureMinRequiredFee = false;
@@ -1878,4 +1892,107 @@ public class WalletTest extends TestWithWallet {
assertEquals(BigInteger.ZERO, wallet.getBalance());
assertEquals(outputValue, request.tx.getOutput(0).getValue());
}
@Test
public void keyRotation() throws Exception {
// Watch out for wallet-initiated broadcasts.
MockTransactionBroadcaster broadcaster = new MockTransactionBroadcaster(wallet);
wallet.setTransactionBroadcaster(broadcaster);
wallet.setKeyRotationEnabled(true);
// Send three cents to two different keys, then add a key and mark the initial keys as compromised.
ECKey key1 = new ECKey();
ECKey key2 = new ECKey();
wallet.addKey(key1);
wallet.addKey(key2);
sendMoneyToWallet(wallet, CENT, key1.toAddress(params), AbstractBlockChain.NewBlockType.BEST_CHAIN);
sendMoneyToWallet(wallet, CENT, key2.toAddress(params), AbstractBlockChain.NewBlockType.BEST_CHAIN);
sendMoneyToWallet(wallet, CENT, key2.toAddress(params), AbstractBlockChain.NewBlockType.BEST_CHAIN);
Utils.rollMockClock(86400);
Date compromiseTime = Utils.now();
assertEquals(0, broadcaster.broadcasts.size());
assertFalse(wallet.isKeyRotating(key1));
// Rotate the wallet.
ECKey key3 = new ECKey();
wallet.addKey(key3);
// We see a broadcast triggered by setting the rotation time.
wallet.setKeyRotationTime(compromiseTime);
assertTrue(wallet.isKeyRotating(key1));
Transaction tx = broadcaster.broadcasts.take();
final BigInteger THREE_CENTS = CENT.add(CENT).add(CENT);
assertEquals(THREE_CENTS, tx.getValueSentFromMe(wallet));
assertEquals(THREE_CENTS.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE), tx.getValueSentToMe(wallet));
// TX is a raw pay to pubkey.
assertArrayEquals(key3.getPubKey(), tx.getOutput(0).getScriptPubKey().getPubKey());
assertEquals(3, tx.getInputs().size());
// It confirms.
sendMoneyToWallet(tx, AbstractBlockChain.NewBlockType.BEST_CHAIN);
// Now receive some more money to key3 (secure) via a new block and check that nothing happens.
sendMoneyToWallet(wallet, CENT, key3.toAddress(params), AbstractBlockChain.NewBlockType.BEST_CHAIN);
assertTrue(broadcaster.broadcasts.isEmpty());
// Receive money via a new block on key1 and ensure it's immediately moved.
sendMoneyToWallet(wallet, CENT, key1.toAddress(params), AbstractBlockChain.NewBlockType.BEST_CHAIN);
tx = broadcaster.broadcasts.take();
assertArrayEquals(key3.getPubKey(), tx.getOutput(0).getScriptPubKey().getPubKey());
assertEquals(1, tx.getInputs().size());
assertEquals(1, tx.getOutputs().size());
assertEquals(CENT.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE), tx.getOutput(0).getValue());
assertEquals(Transaction.Purpose.KEY_ROTATION, tx.getPurpose());
// We don't attempt to race an attacker against unconfirmed transactions.
// Now round-trip the wallet and check the protobufs are storing the data correctly.
Protos.Wallet protos = new WalletProtobufSerializer().walletToProto(wallet);
wallet = new Wallet(params);
new WalletProtobufSerializer().readWallet(protos, wallet);
tx = wallet.getTransaction(tx.getHash());
assertEquals(Transaction.Purpose.KEY_ROTATION, tx.getPurpose());
// Have to divide here to avoid mismatch due to second-level precision in serialisation.
assertEquals(compromiseTime.getTime() / 1000, wallet.getKeyRotationTime().getTime() / 1000);
// Make a normal spend and check it's all ok.
final Address address = new ECKey().toAddress(params);
wallet.sendCoins(broadcaster, address, wallet.getBalance());
tx = broadcaster.broadcasts.take();
assertArrayEquals(address.getHash160(), tx.getOutput(0).getScriptPubKey().getPubKeyHash());
// We have to race here because we're checking for the ABSENCE of a broadcast, and if there were to be one,
// it'd be happening in parallel.
assertEquals(null, broadcaster.broadcasts.poll(1, TimeUnit.SECONDS));
}
@Test
public void fragmentedReKeying() throws Exception {
// Send lots of small coins and check the fee is correct.
ECKey key = new ECKey();
wallet.addKey(key);
Address address = key.toAddress(params);
Utils.rollMockClock(86400);
for (int i = 0; i < 800; i++) {
sendMoneyToWallet(wallet, Utils.CENT, address, AbstractBlockChain.NewBlockType.BEST_CHAIN);
}
MockTransactionBroadcaster broadcaster = new MockTransactionBroadcaster(wallet);
wallet.setTransactionBroadcaster(broadcaster);
wallet.setKeyRotationEnabled(true);
Date compromise = Utils.now();
Utils.rollMockClock(86400);
wallet.addKey(new ECKey());
wallet.setKeyRotationTime(compromise);
Transaction tx = broadcaster.broadcasts.take();
final BigInteger valueSentToMe = tx.getValueSentToMe(wallet);
BigInteger fee = tx.getValueSentFromMe(wallet).subtract(valueSentToMe);
assertEquals(BigInteger.valueOf(900000), fee);
assertEquals(KeyTimeCoinSelector.MAX_SIMULTANEOUS_INPUTS, tx.getInputs().size());
assertEquals(BigInteger.valueOf(599100000), valueSentToMe);
tx = broadcaster.broadcasts.take();
assertNotNull(tx);
assertEquals(200, tx.getInputs().size());
}
}

View File

@@ -692,7 +692,7 @@ public class PaymentChannelStateTest extends TestWithWallet {
// Now give the server enough coins to pay the fee
StoredBlock block = new StoredBlock(makeSolvedTestBlock(blockStore, new ECKey().toAddress(params)), BigInteger.ONE, 1);
Transaction tx1 = createFakeTx(params, Utils.COIN, serverKey.toAddress(params));
serverWallet.receiveFromBlock(tx1, block, AbstractBlockChain.NewBlockType.BEST_CHAIN);
serverWallet.receiveFromBlock(tx1, block, AbstractBlockChain.NewBlockType.BEST_CHAIN, 0);
// The contract is still not worth redeeming - its worth less than we pay in fee
try {
@@ -787,7 +787,7 @@ public class PaymentChannelStateTest extends TestWithWallet {
doubleSpendContract = new Transaction(params, doubleSpendContract.bitcoinSerialize());
StoredBlock block = new StoredBlock(params.getGenesisBlock().createNextBlock(myKey.toAddress(params)), BigInteger.TEN, 1);
serverWallet.receiveFromBlock(doubleSpendContract, block, AbstractBlockChain.NewBlockType.BEST_CHAIN);
serverWallet.receiveFromBlock(doubleSpendContract, block, AbstractBlockChain.NewBlockType.BEST_CHAIN, 0);
// Now if we try to spend again the server will reject it since it saw a double-spend
try {

View File

@@ -99,7 +99,7 @@ public class WalletProtobufSerializerTest {
// t1 spends to our wallet.
myWallet.receivePending(doubleSpends.t1, null);
// t2 rolls back t1 and spends somewhere else.
myWallet.receiveFromBlock(doubleSpends.t2, null, BlockChain.NewBlockType.BEST_CHAIN);
myWallet.receiveFromBlock(doubleSpends.t2, null, BlockChain.NewBlockType.BEST_CHAIN, 0);
Wallet wallet1 = roundTrip(myWallet);
assertEquals(1, wallet1.getTransactions(true).size());
Transaction t1 = wallet1.getTransaction(doubleSpends.t1.getHash());

View File

@@ -21,7 +21,7 @@
<parent>
<groupId>com.google</groupId>
<artifactId>bitcoinj-parent</artifactId>
<version>0.10-SNAPSHOT</version>
<version>0.10.2</version>
</parent>
<modelVersion>4.0.0</modelVersion>

59
pom.xml
View File

@@ -4,7 +4,7 @@
<groupId>com.google</groupId>
<artifactId>bitcoinj-parent</artifactId>
<version>0.10-SNAPSHOT</version>
<version>0.10.2</version>
<packaging>pom</packaging>
<modules>
@@ -68,63 +68,6 @@
<showWarnings>true</showWarnings>
</configuration>
</plugin>
<!-- Verify the dependency chain: see https://github.com/gary-rowe/BitcoinjEnforcerRules for
more information on this.
-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>1.2</version>
<executions>
<execution>
<id>enforce</id>
<phase>verify</phase>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<digestRule implementation="uk.co.froot.maven.enforcer.DigestRule">
<!-- Create a snapshot to build the list of URNs below -->
<buildSnapshot>true</buildSnapshot>
<!-- List of required hashes -->
<!-- Format is URN of groupId:artifactId:version:type:classifier:scope:hash -->
<!-- classifier is "null" if not present -->
<urns>
<urn>com.google.code.findbugs:jsr305:1.3.9:jar:null:compile:40719ea6961c0cb6afaeb6a921eaa1f6afd4cfdf</urn>
<urn>com.google.guava:guava:13.0.1:jar:null:compile:0d6f22b1e60a2f1ef99e22c9f5fde270b2088365</urn>
<urn>com.google.protobuf:protobuf-java:2.4.1:jar:null:compile:0c589509ec6fd86d5d2fda37e07c08538235d3b9</urn>
<urn>com.h2database:h2:1.3.167:jar:null:compile:d3867d586f087e53eb12fc65e5693d8ee9a5da17</urn>
<urn>com.lambdaworks:scrypt:1.3.3:jar:null:compile:06d6813de41e177189e1722717979b4fb5454b1d</urn>
<urn>com.madgag:sc-light-jdk15on:1.47.0.2:jar:null:compile:d5c98671cc97fa0d928be1c7eb5edd3fb95d3234</urn>
<urn>io.netty:netty:3.6.3.Final:jar:null:compile:1eebfd2f79dd72c44d09d9917c549c60322462b8</urn>
<urn>net.jcip:jcip-annotations:1.0:jar:null:compile:afba4942caaeaf46aab0b976afd57cc7c181467e</urn>
<urn>net.sf.jopt-simple:jopt-simple:4.3:jar:null:compile:88ffca34311a6564a98f14820431e17b4382a069</urn>
<urn>org.slf4j:slf4j-api:1.6.4:jar:null:compile:2396d74b12b905f780ed7966738bb78438e8371a</urn>
<urn>org.slf4j:slf4j-jdk14:1.6.4:jar:null:runtime:6b32bc7c42b2509525ce812cb49bf96e7bf64141</urn>
<!-- A check for the rules themselves -->
<urn>uk.co.froot.maven.enforcer:digest-enforcer-rules:0.0.1:jar:null:runtime:16a9e04f3fe4bb143c42782d07d5faf65b32106f</urn>
</urns>
</digestRule>
</rules>
</configuration>
</execution>
</executions>
<!-- Ensure we download the enforcer rules -->
<dependencies>
<dependency>
<groupId>uk.co.froot.maven.enforcer</groupId>
<artifactId>digest-enforcer-rules</artifactId>
<version>0.0.1</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>

View File

@@ -21,7 +21,7 @@
<parent>
<groupId>com.google</groupId>
<artifactId>bitcoinj-parent</artifactId>
<version>0.10-SNAPSHOT</version>
<version>0.10.2</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -68,7 +68,8 @@ public class WalletTool {
" --chain=<file> Specifies the name of the file that stores the block chain.\n" +
" --force Overrides any safety checks on the requested action.\n" +
" --date Provide a date in form YYYY/MM/DD to any action that requires one.\n" +
" --peers=1.2.3.4 Comma separaterd IP addresses/domain names for connections instead of peer discovery.\n" +
" --peers=1.2.3.4 Comma separated IP addresses/domain names for connections instead of peer discovery.\n" +
" --offline If specified when sending, don't try and connect, just write the tx to the wallet.\n" +
" --condition=... Allows you to specify a numeric condition for other commands. The format is\n" +
" one of the following operators = < > <= >= immediately followed by a number.\n" +
" For example --condition=\">5.10\" or --condition=\"<=1\"\n" +
@@ -259,6 +260,7 @@ public class WalletTool {
conditionFlag = parser.accepts("condition").withRequiredArg();
parser.accepts("locktime").withRequiredArg();
parser.accepts("allow-unconfirmed");
parser.accepts("offline");
OptionSpec<String> passwordFlag = parser.accepts("password").withRequiredArg();
options = parser.parse(args);
@@ -397,7 +399,7 @@ public class WalletTool {
shutdown();
}
private static void send(List<String> outputs, BigInteger fee, String lockTimeStr, boolean allowUnconfirmed) {
private static void send(List<String> outputs, BigInteger fee, String lockTimeStr, boolean allowUnconfirmed) throws VerificationException {
try {
// Convert the input strings to outputs.
Transaction t = new Transaction(params);
@@ -467,6 +469,12 @@ public class WalletTool {
throw new RuntimeException(e);
}
t = req.tx; // Not strictly required today.
System.out.println(t.getHashAsString());
if (options.has("offline")) {
wallet.commitTx(t);
return;
}
setup();
peers.startAndWait();
// Wait for peers to connect, the tx to be sent to one of them and for it to be propagated across the
@@ -478,7 +486,6 @@ public class WalletTool {
// completes before the remote peer actually hears the message.
Thread.sleep(5000);
}
System.out.println(t.getHashAsString());
} catch (BlockStoreException e) {
throw new RuntimeException(e);
} catch (KeyCrypterException e) {