mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-07-31 12:01:24 +00:00
Compare commits
38 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
2cfa39f2cf | ||
|
c808ae7bb4 | ||
|
ca2d847fe7 | ||
|
e6ca742057 | ||
|
f867998c52 | ||
|
db296193f9 | ||
|
d6b4b55e83 | ||
|
1d4ff1770e | ||
|
cb45e306df | ||
|
4b5e8fcdb0 | ||
|
bce6968aec | ||
|
511d6310d4 | ||
|
3f2c372097 | ||
|
01a55b557f | ||
|
bdada0447f | ||
|
0d9f8b7867 | ||
|
b883d88468 | ||
|
e89cc1a41d | ||
|
0e809b2b31 | ||
|
a26b18c8fd | ||
|
4d8e9088f2 | ||
|
1134572f61 | ||
|
d8f7eab42b | ||
|
a3bbde87c6 | ||
|
777e6781d7 | ||
|
4abdf44449 | ||
|
a0edf70bc3 | ||
|
c261f75e8a | ||
|
6680846949 | ||
|
1e227e521a | ||
|
1c8be1cc69 | ||
|
91fa22181a | ||
|
51186b8fc1 | ||
|
bfcbe7f298 | ||
|
2d1479eca9 | ||
|
79f093f0c4 | ||
|
1fda444af7 | ||
|
75cbaed15c |
58
core/pom.xml
58
core/pom.xml
@@ -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>
|
||||
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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 {
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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);
|
||||
|
@@ -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>();
|
||||
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -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;
|
||||
|
||||
|
@@ -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);
|
||||
|
||||
|
@@ -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 {
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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, ...
|
||||
|
@@ -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 + "/";
|
||||
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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");
|
||||
|
||||
|
@@ -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();
|
||||
|
@@ -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))
|
||||
|
@@ -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;
|
||||
}
|
||||
});
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
@@ -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;
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
|
@@ -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
|
||||
|
@@ -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));
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
|
@@ -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();
|
||||
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@@ -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
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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());
|
||||
}
|
||||
}
|
||||
|
@@ -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 {
|
||||
|
@@ -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());
|
||||
|
@@ -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
59
pom.xml
@@ -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>
|
||||
|
||||
|
@@ -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>
|
||||
|
||||
|
@@ -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) {
|
||||
|
Reference in New Issue
Block a user