mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-01-31 15:22:16 +00:00
Support watching of scripts/addresses in wallet
This commit is contained in:
parent
2271e7198e
commit
da2e3e6c98
@ -77,6 +77,14 @@ message Key {
|
|||||||
optional int64 creation_timestamp = 5;
|
optional int64 creation_timestamp = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message Script {
|
||||||
|
required bytes program = 1;
|
||||||
|
|
||||||
|
// Timestamp stored as millis since epoch. Useful for skipping block bodies before this point
|
||||||
|
// when watching for scripts on the blockchain.
|
||||||
|
required int64 creation_timestamp = 2;
|
||||||
|
}
|
||||||
|
|
||||||
message TransactionInput {
|
message TransactionInput {
|
||||||
// Hash of the transaction this input is using.
|
// Hash of the transaction this input is using.
|
||||||
required bytes transaction_out_point_hash = 1;
|
required bytes transaction_out_point_hash = 1;
|
||||||
@ -257,6 +265,7 @@ message Wallet {
|
|||||||
|
|
||||||
repeated Key key = 3;
|
repeated Key key = 3;
|
||||||
repeated Transaction transaction = 4;
|
repeated Transaction transaction = 4;
|
||||||
|
repeated Script watched_script = 15;
|
||||||
|
|
||||||
optional EncryptionType encryption_type = 5 [default=UNENCRYPTED];
|
optional EncryptionType encryption_type = 5 [default=UNENCRYPTED];
|
||||||
optional ScryptParameters encryption_parameters = 6;
|
optional ScryptParameters encryption_parameters = 6;
|
||||||
@ -280,5 +289,5 @@ message Wallet {
|
|||||||
// can be used to recover a compromised wallet, or just as part of preventative defence-in-depth measures.
|
// can be used to recover a compromised wallet, or just as part of preventative defence-in-depth measures.
|
||||||
optional uint64 key_rotation_time = 13;
|
optional uint64 key_rotation_time = 13;
|
||||||
|
|
||||||
// Next tag: 15
|
// Next tag: 16
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
package com.google.bitcoin.core;
|
package com.google.bitcoin.core;
|
||||||
|
|
||||||
|
import com.google.bitcoin.script.Script;
|
||||||
|
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -48,6 +50,11 @@ public abstract class AbstractWalletEventListener implements WalletEventListener
|
|||||||
onChange();
|
onChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onScriptsAdded(Wallet wallet, List<Script> scripts) {
|
||||||
|
onChange();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onWalletChanged(Wallet wallet) {
|
public void onWalletChanged(Wallet wallet) {
|
||||||
onChange();
|
onChange();
|
||||||
|
@ -40,4 +40,6 @@ public interface PeerFilterProvider {
|
|||||||
* Default value should be an empty bloom filter with the given size, falsePositiveRate, and nTweak.
|
* Default value should be an empty bloom filter with the given size, falsePositiveRate, and nTweak.
|
||||||
*/
|
*/
|
||||||
public BloomFilter getBloomFilter(int size, double falsePositiveRate, long nTweak);
|
public BloomFilter getBloomFilter(int size, double falsePositiveRate, long nTweak);
|
||||||
|
|
||||||
|
boolean isRequiringUpdateAllBloomFilter();
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ package com.google.bitcoin.core;
|
|||||||
import com.google.bitcoin.core.Peer.PeerHandler;
|
import com.google.bitcoin.core.Peer.PeerHandler;
|
||||||
import com.google.bitcoin.discovery.PeerDiscovery;
|
import com.google.bitcoin.discovery.PeerDiscovery;
|
||||||
import com.google.bitcoin.discovery.PeerDiscoveryException;
|
import com.google.bitcoin.discovery.PeerDiscoveryException;
|
||||||
|
import com.google.bitcoin.script.Script;
|
||||||
import com.google.bitcoin.utils.ListenerRegistration;
|
import com.google.bitcoin.utils.ListenerRegistration;
|
||||||
import com.google.bitcoin.utils.Threading;
|
import com.google.bitcoin.utils.Threading;
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
@ -131,6 +132,7 @@ public class PeerGroup extends AbstractIdleService implements TransactionBroadca
|
|||||||
private void onChanged() {
|
private void onChanged() {
|
||||||
recalculateFastCatchupAndFilter();
|
recalculateFastCatchupAndFilter();
|
||||||
}
|
}
|
||||||
|
@Override public void onScriptsAdded(Wallet wallet, List<Script> scripts) { onChanged(); }
|
||||||
@Override public void onKeysAdded(Wallet wallet, List<ECKey> keys) { onChanged(); }
|
@Override public void onKeysAdded(Wallet wallet, List<ECKey> keys) { onChanged(); }
|
||||||
@Override public void onCoinsReceived(Wallet wallet, Transaction tx, BigInteger prevBalance, BigInteger newBalance) { onChanged(); }
|
@Override public void onCoinsReceived(Wallet wallet, Transaction tx, BigInteger prevBalance, BigInteger newBalance) { onChanged(); }
|
||||||
@Override public void onCoinsSent(Wallet wallet, Transaction tx, BigInteger prevBalance, BigInteger newBalance) { onChanged(); }
|
@Override public void onCoinsSent(Wallet wallet, Transaction tx, BigInteger prevBalance, BigInteger newBalance) { onChanged(); }
|
||||||
@ -678,9 +680,11 @@ public class PeerGroup extends AbstractIdleService implements TransactionBroadca
|
|||||||
return;
|
return;
|
||||||
long earliestKeyTimeSecs = Long.MAX_VALUE;
|
long earliestKeyTimeSecs = Long.MAX_VALUE;
|
||||||
int elements = 0;
|
int elements = 0;
|
||||||
|
boolean requiresUpdateAll = false;
|
||||||
for (PeerFilterProvider p : peerFilterProviders) {
|
for (PeerFilterProvider p : peerFilterProviders) {
|
||||||
earliestKeyTimeSecs = Math.min(earliestKeyTimeSecs, p.getEarliestKeyCreationTime());
|
earliestKeyTimeSecs = Math.min(earliestKeyTimeSecs, p.getEarliestKeyCreationTime());
|
||||||
elements += p.getBloomFilterElementCount();
|
elements += p.getBloomFilterElementCount();
|
||||||
|
requiresUpdateAll = requiresUpdateAll || p.isRequiringUpdateAllBloomFilter();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (elements > 0) {
|
if (elements > 0) {
|
||||||
@ -689,7 +693,9 @@ public class PeerGroup extends AbstractIdleService implements TransactionBroadca
|
|||||||
// The constant 100 here is somewhat arbitrary, but makes sense for small to medium wallets -
|
// The constant 100 here is somewhat arbitrary, but makes sense for small to medium wallets -
|
||||||
// it will likely mean we never need to create a filter with different parameters.
|
// it will likely mean we never need to create a filter with different parameters.
|
||||||
lastBloomFilterElementCount = elements > lastBloomFilterElementCount ? elements + 100 : lastBloomFilterElementCount;
|
lastBloomFilterElementCount = elements > lastBloomFilterElementCount ? elements + 100 : lastBloomFilterElementCount;
|
||||||
BloomFilter filter = new BloomFilter(lastBloomFilterElementCount, bloomFilterFPRate, bloomFilterTweak);
|
BloomFilter.BloomUpdate bloomFlags =
|
||||||
|
requiresUpdateAll ? BloomFilter.BloomUpdate.UPDATE_ALL : BloomFilter.BloomUpdate.UPDATE_P2PUBKEY_ONLY;
|
||||||
|
BloomFilter filter = new BloomFilter(lastBloomFilterElementCount, bloomFilterFPRate, bloomFilterTweak, bloomFlags);
|
||||||
for (PeerFilterProvider p : peerFilterProviders)
|
for (PeerFilterProvider p : peerFilterProviders)
|
||||||
filter.merge(p.getBloomFilter(lastBloomFilterElementCount, bloomFilterFPRate, bloomFilterTweak));
|
filter.merge(p.getBloomFilter(lastBloomFilterElementCount, bloomFilterFPRate, bloomFilterTweak));
|
||||||
if (!filter.equals(bloomFilter)) {
|
if (!filter.equals(bloomFilter)) {
|
||||||
|
@ -219,7 +219,7 @@ public class Transaction extends ChildMessage implements Serializable {
|
|||||||
// This is tested in WalletTest.
|
// This is tested in WalletTest.
|
||||||
BigInteger v = BigInteger.ZERO;
|
BigInteger v = BigInteger.ZERO;
|
||||||
for (TransactionOutput o : outputs) {
|
for (TransactionOutput o : outputs) {
|
||||||
if (!o.isMine(wallet)) continue;
|
if (!o.isMineOrWatched(wallet)) continue;
|
||||||
if (!includeSpent && !o.isAvailableForSpending()) continue;
|
if (!includeSpent && !o.isAvailableForSpending()) continue;
|
||||||
v = v.add(o.getValue());
|
v = v.add(o.getValue());
|
||||||
}
|
}
|
||||||
@ -234,7 +234,7 @@ public class Transaction extends ChildMessage implements Serializable {
|
|||||||
boolean isActuallySpent = true;
|
boolean isActuallySpent = true;
|
||||||
for (TransactionOutput o : outputs) {
|
for (TransactionOutput o : outputs) {
|
||||||
if (o.isAvailableForSpending()) {
|
if (o.isAvailableForSpending()) {
|
||||||
if (o.isMine(wallet)) isActuallySpent = false;
|
if (o.isMineOrWatched(wallet)) isActuallySpent = false;
|
||||||
if (o.getSpentBy() != null) {
|
if (o.getSpentBy() != null) {
|
||||||
log.error("isAvailableForSpending != spentBy");
|
log.error("isAvailableForSpending != spentBy");
|
||||||
return false;
|
return false;
|
||||||
@ -340,7 +340,7 @@ public class Transaction extends ChildMessage implements Serializable {
|
|||||||
continue;
|
continue;
|
||||||
// The connected output may be the change to the sender of a previous input sent to this wallet. In this
|
// The connected output may be the change to the sender of a previous input sent to this wallet. In this
|
||||||
// case we ignore it.
|
// case we ignore it.
|
||||||
if (!connected.isMine(wallet))
|
if (!connected.isMineOrWatched(wallet))
|
||||||
continue;
|
continue;
|
||||||
v = v.add(connected.getValue());
|
v = v.add(connected.getValue());
|
||||||
}
|
}
|
||||||
@ -405,7 +405,7 @@ public class Transaction extends ChildMessage implements Serializable {
|
|||||||
public boolean isEveryOwnedOutputSpent(Wallet wallet) {
|
public boolean isEveryOwnedOutputSpent(Wallet wallet) {
|
||||||
maybeParse();
|
maybeParse();
|
||||||
for (TransactionOutput output : outputs) {
|
for (TransactionOutput output : outputs) {
|
||||||
if (output.isAvailableForSpending() && output.isMine(wallet))
|
if (output.isAvailableForSpending() && output.isMineOrWatched(wallet))
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -250,6 +250,27 @@ public class TransactionOutput extends ChildMessage implements Serializable {
|
|||||||
return scriptBytes;
|
return scriptBytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if this output is to a key in the wallet or to an address/script we are watching.
|
||||||
|
*/
|
||||||
|
public boolean isMineOrWatched(Wallet wallet) {
|
||||||
|
return isMine(wallet) || isWatched(wallet);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if this output is to a key, or an address we have the keys for, in the wallet.
|
||||||
|
*/
|
||||||
|
public boolean isWatched(Wallet wallet) {
|
||||||
|
try {
|
||||||
|
Script script = getScriptPubKey();
|
||||||
|
return wallet.isWatchedScript(script);
|
||||||
|
} catch (ScriptException e) {
|
||||||
|
// Just means we didn't understand the output of this transaction: ignore it.
|
||||||
|
log.debug("Could not parse tx output script: {}", e.toString());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if this output is to a key, or an address we have the keys for, in the wallet.
|
* Returns true if this output is to a key, or an address we have the keys for, in the wallet.
|
||||||
*/
|
*/
|
||||||
|
@ -21,6 +21,9 @@ import com.google.bitcoin.core.WalletTransaction.Pool;
|
|||||||
import com.google.bitcoin.crypto.KeyCrypter;
|
import com.google.bitcoin.crypto.KeyCrypter;
|
||||||
import com.google.bitcoin.crypto.KeyCrypterException;
|
import com.google.bitcoin.crypto.KeyCrypterException;
|
||||||
import com.google.bitcoin.crypto.KeyCrypterScrypt;
|
import com.google.bitcoin.crypto.KeyCrypterScrypt;
|
||||||
|
import com.google.bitcoin.script.Script;
|
||||||
|
import com.google.bitcoin.script.ScriptBuilder;
|
||||||
|
import com.google.bitcoin.script.ScriptChunk;
|
||||||
import com.google.bitcoin.store.UnreadableWalletException;
|
import com.google.bitcoin.store.UnreadableWalletException;
|
||||||
import com.google.bitcoin.store.WalletProtobufSerializer;
|
import com.google.bitcoin.store.WalletProtobufSerializer;
|
||||||
import com.google.bitcoin.utils.ListenerRegistration;
|
import com.google.bitcoin.utils.ListenerRegistration;
|
||||||
@ -94,6 +97,7 @@ import static com.google.common.base.Preconditions.*;
|
|||||||
public class Wallet implements Serializable, BlockChainListener, PeerFilterProvider {
|
public class Wallet implements Serializable, BlockChainListener, PeerFilterProvider {
|
||||||
private static final Logger log = LoggerFactory.getLogger(Wallet.class);
|
private static final Logger log = LoggerFactory.getLogger(Wallet.class);
|
||||||
private static final long serialVersionUID = 2L;
|
private static final long serialVersionUID = 2L;
|
||||||
|
private static final int MINIMUM_BLOOM_DATA_LENGTH = 8;
|
||||||
|
|
||||||
protected final ReentrantLock lock = Threading.lock("wallet");
|
protected final ReentrantLock lock = Threading.lock("wallet");
|
||||||
|
|
||||||
@ -127,6 +131,9 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
|
|||||||
// A list of public/private EC keys owned by this user. Access it using addKey[s], hasKey[s] and findPubKeyFromHash.
|
// A list of public/private EC keys owned by this user. Access it using addKey[s], hasKey[s] and findPubKeyFromHash.
|
||||||
private ArrayList<ECKey> keychain;
|
private ArrayList<ECKey> keychain;
|
||||||
|
|
||||||
|
// A list of scripts watched by this wallet.
|
||||||
|
private Set<Script> watchedScripts;
|
||||||
|
|
||||||
private final NetworkParameters params;
|
private final NetworkParameters params;
|
||||||
|
|
||||||
private Sha256Hash lastBlockSeenHash;
|
private Sha256Hash lastBlockSeenHash;
|
||||||
@ -192,6 +199,7 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
|
|||||||
this.keyCrypter = keyCrypter;
|
this.keyCrypter = keyCrypter;
|
||||||
this.params = checkNotNull(params);
|
this.params = checkNotNull(params);
|
||||||
keychain = new ArrayList<ECKey>();
|
keychain = new ArrayList<ECKey>();
|
||||||
|
watchedScripts = Sets.newHashSet();
|
||||||
unspent = new HashMap<Sha256Hash, Transaction>();
|
unspent = new HashMap<Sha256Hash, Transaction>();
|
||||||
spent = new HashMap<Sha256Hash, Transaction>();
|
spent = new HashMap<Sha256Hash, Transaction>();
|
||||||
pending = new HashMap<Sha256Hash, Transaction>();
|
pending = new HashMap<Sha256Hash, Transaction>();
|
||||||
@ -245,6 +253,18 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a snapshot of the watched scripts. This view is not live.
|
||||||
|
*/
|
||||||
|
public List<Script> getWatchedScripts() {
|
||||||
|
lock.lock();
|
||||||
|
try {
|
||||||
|
return new ArrayList<Script>(watchedScripts);
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes the given key from the keychain. Be very careful with this - losing a private key <b>destroys the
|
* Removes the given key from the keychain. Be very careful with this - losing a private key <b>destroys the
|
||||||
* money associated with it</b>.
|
* money associated with it</b>.
|
||||||
@ -1922,6 +1942,29 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LinkedList<TransactionOutput> getWatchedOutputs(boolean excludeImmatureCoinbases) {
|
||||||
|
lock.lock();
|
||||||
|
try {
|
||||||
|
LinkedList<TransactionOutput> candidates = Lists.newLinkedList();
|
||||||
|
for (Transaction tx : Iterables.concat(unspent.values(), pending.values())) {
|
||||||
|
if (excludeImmatureCoinbases && !tx.isMature()) continue;
|
||||||
|
for (TransactionOutput output : tx.getOutputs()) {
|
||||||
|
if (!output.isAvailableForSpending()) continue;
|
||||||
|
try {
|
||||||
|
Script scriptPubKey = output.getScriptPubKey();
|
||||||
|
if (!watchedScripts.contains(scriptPubKey)) continue;
|
||||||
|
candidates.add(output);
|
||||||
|
} catch (ScriptException e) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return candidates;
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Returns the address used for change outputs. Note: this will probably go away in future. */
|
/** Returns the address used for change outputs. Note: this will probably go away in future. */
|
||||||
public Address getChangeAddress() {
|
public Address getChangeAddress() {
|
||||||
lock.lock();
|
lock.lock();
|
||||||
@ -1980,6 +2023,75 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if we are watching this address.
|
||||||
|
*/
|
||||||
|
public boolean isAddressWatched(Address address) {
|
||||||
|
Script script = ScriptBuilder.createOutputScript(address);
|
||||||
|
return isWatchedScript(script);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** See {@link #addWatchedAddress(Address, long)} */
|
||||||
|
public boolean addWatchedAddress(final Address address) {
|
||||||
|
long now = Utils.now().getTime() / 1000;
|
||||||
|
return addWatchedAddresses(Lists.newArrayList(address), now) == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the given address to the wallet to be watched. Outputs can be retrieved
|
||||||
|
* by {@link #getWatchedOutputs(boolean)}.
|
||||||
|
*
|
||||||
|
* @param creationTime creation time in seconds since the epoch, for scanning the blockchain
|
||||||
|
*
|
||||||
|
* @return whether the address was added successfully (not already present)
|
||||||
|
*/
|
||||||
|
public boolean addWatchedAddress(final Address address, long creationTime) {
|
||||||
|
return addWatchedAddresses(Lists.newArrayList(address), creationTime) == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the given address to the wallet to be watched. Outputs can be retrieved
|
||||||
|
* by {@link #getWatchedOutputs(boolean)}.
|
||||||
|
*
|
||||||
|
* @return how many addresses were added successfully
|
||||||
|
*/
|
||||||
|
public int addWatchedAddresses(final List<Address> addresses, long creationTime) {
|
||||||
|
List<Script> scripts = Lists.newArrayList();
|
||||||
|
|
||||||
|
for (Address address : addresses) {
|
||||||
|
Script script = ScriptBuilder.createOutputScript(address);
|
||||||
|
script.setCreationTimeSeconds(creationTime);
|
||||||
|
scripts.add(script);
|
||||||
|
}
|
||||||
|
|
||||||
|
return addWatchedScripts(scripts);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the given output scripts to the wallet to be watched. Outputs can be retrieved
|
||||||
|
* by {@link #getWatchedOutputs(boolean)}.
|
||||||
|
*
|
||||||
|
* @return how many scripts were added successfully
|
||||||
|
*/
|
||||||
|
public int addWatchedScripts(final List<Script> scripts) {
|
||||||
|
lock.lock();
|
||||||
|
try {
|
||||||
|
int added = 0;
|
||||||
|
for (final Script script : scripts) {
|
||||||
|
if (watchedScripts.contains(script)) continue;
|
||||||
|
|
||||||
|
watchedScripts.add(script);
|
||||||
|
added++;
|
||||||
|
}
|
||||||
|
|
||||||
|
queueOnScriptsAdded(scripts);
|
||||||
|
saveNow();
|
||||||
|
return added;
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Locates a keypair from the keychain given the hash of the public key. This is needed when finding out which
|
* Locates a keypair from the keychain given the hash of the public key. This is needed when finding out which
|
||||||
* key we need to use to redeem a transaction output.
|
* key we need to use to redeem a transaction output.
|
||||||
@ -2015,6 +2127,16 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
|
|||||||
return findKeyFromPubHash(pubkeyHash) != null;
|
return findKeyFromPubHash(pubkeyHash) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns true if this wallet is watching transactions for outputs with the script. */
|
||||||
|
public boolean isWatchedScript(Script script) {
|
||||||
|
lock.lock();
|
||||||
|
try {
|
||||||
|
return watchedScripts.contains(script);
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Locates a keypair from the keychain given the raw public key bytes.
|
* Locates a keypair from the keychain given the raw public key bytes.
|
||||||
* @return ECKey or null if no such key was found.
|
* @return ECKey or null if no such key was found.
|
||||||
@ -2107,6 +2229,27 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns the available balance, including any unspent balance at watched addresses */
|
||||||
|
public BigInteger getWatchedBalance() {
|
||||||
|
return getWatchedBalance(coinSelector);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the balance that would be considered spendable by the given coin selector, including
|
||||||
|
* any unspent balance at watched addresses.
|
||||||
|
*/
|
||||||
|
public BigInteger getWatchedBalance(CoinSelector selector) {
|
||||||
|
lock.lock();
|
||||||
|
try {
|
||||||
|
checkNotNull(selector);
|
||||||
|
LinkedList<TransactionOutput> candidates = getWatchedOutputs(true);
|
||||||
|
CoinSelection selection = selector.select(NetworkParameters.MAX_MONEY, candidates);
|
||||||
|
return selection.valueGathered;
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return toString(false, true, true, null);
|
return toString(false, true, true, null);
|
||||||
@ -2395,8 +2538,8 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the earliest creation time of the keys in this wallet, in seconds since the epoch, ie the min of
|
* Returns the earliest creation time of keys or watched scripts in this wallet, in seconds since the epoch, ie the min
|
||||||
* {@link com.google.bitcoin.core.ECKey#getCreationTimeSeconds()}. This can return zero if at least one key does
|
* of {@link com.google.bitcoin.core.ECKey#getCreationTimeSeconds()}. This can return zero if at least one key does
|
||||||
* not have that data (was created before key timestamping was implemented). <p>
|
* not have that data (was created before key timestamping was implemented). <p>
|
||||||
*
|
*
|
||||||
* This method is most often used in conjunction with {@link PeerGroup#setFastCatchupTimeSecs(long)} in order to
|
* This method is most often used in conjunction with {@link PeerGroup#setFastCatchupTimeSecs(long)} in order to
|
||||||
@ -2410,13 +2553,13 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
|
|||||||
public long getEarliestKeyCreationTime() {
|
public long getEarliestKeyCreationTime() {
|
||||||
lock.lock();
|
lock.lock();
|
||||||
try {
|
try {
|
||||||
if (keychain.size() == 0) {
|
|
||||||
return Utils.now().getTime() / 1000;
|
|
||||||
}
|
|
||||||
long earliestTime = Long.MAX_VALUE;
|
long earliestTime = Long.MAX_VALUE;
|
||||||
for (ECKey key : keychain) {
|
for (ECKey key : keychain)
|
||||||
earliestTime = Math.min(key.getCreationTimeSeconds(), earliestTime);
|
earliestTime = Math.min(key.getCreationTimeSeconds(), earliestTime);
|
||||||
}
|
for (Script script : watchedScripts)
|
||||||
|
earliestTime = Math.min(script.getCreationTimeSeconds(), earliestTime);
|
||||||
|
if (earliestTime == Long.MAX_VALUE)
|
||||||
|
return Utils.now().getTime() / 1000;
|
||||||
return earliestTime;
|
return earliestTime;
|
||||||
} finally {
|
} finally {
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
@ -2805,9 +2948,24 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Some scripts may have more than one bloom element. That should normally be okay,
|
||||||
|
// because under-counting just increases false-positive rate.
|
||||||
|
size += watchedScripts.size();
|
||||||
|
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If we are watching any scripts, the bloom filter must update on peers whenever an output is
|
||||||
|
* identified. This is because we don't necessarily have the associated pubkey, so we can't
|
||||||
|
* watch for it on spending transactions.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean isRequiringUpdateAllBloomFilter() {
|
||||||
|
return !watchedScripts.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a bloom filter that contains all of the public keys from this wallet, and which will provide the given
|
* Gets a bloom filter that contains all of the public keys from this wallet, and which will provide the given
|
||||||
* false-positive rate. See the docs for {@link BloomFilter} for a brief explanation of anonymity when using filters.
|
* false-positive rate. See the docs for {@link BloomFilter} for a brief explanation of anonymity when using filters.
|
||||||
@ -2815,7 +2973,7 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
|
|||||||
public BloomFilter getBloomFilter(double falsePositiveRate) {
|
public BloomFilter getBloomFilter(double falsePositiveRate) {
|
||||||
return getBloomFilter(getBloomFilterElementCount(), falsePositiveRate, (long)(Math.random()*Long.MAX_VALUE));
|
return getBloomFilter(getBloomFilterElementCount(), falsePositiveRate, (long)(Math.random()*Long.MAX_VALUE));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a bloom filter that contains all of the public keys from this wallet,
|
* Gets a bloom filter that contains all of the public keys from this wallet,
|
||||||
* and which will provide the given false-positive rate if it has size elements.
|
* and which will provide the given false-positive rate if it has size elements.
|
||||||
@ -2835,6 +2993,17 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
|
|||||||
filter.insert(key.getPubKey());
|
filter.insert(key.getPubKey());
|
||||||
filter.insert(key.getPubKeyHash());
|
filter.insert(key.getPubKeyHash());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (Script script : watchedScripts) {
|
||||||
|
for (ScriptChunk chunk : script.getChunks()) {
|
||||||
|
// Only add long (at least 64 bit) data to the bloom filter.
|
||||||
|
// If any long constants become popular in scripts, we will need logic
|
||||||
|
// here to exclude them.
|
||||||
|
if (!chunk.isOpCode() && chunk.data.length >= MINIMUM_BLOOM_DATA_LENGTH) {
|
||||||
|
filter.insert(chunk.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
}
|
}
|
||||||
@ -2842,7 +3011,8 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
|
|||||||
for (int i = 0; i < tx.getOutputs().size(); i++) {
|
for (int i = 0; i < tx.getOutputs().size(); i++) {
|
||||||
TransactionOutput out = tx.getOutputs().get(i);
|
TransactionOutput out = tx.getOutputs().get(i);
|
||||||
try {
|
try {
|
||||||
if (out.isMine(this) && out.getScriptPubKey().isSentToRawPubKey()) {
|
if ((out.isMine(this) && out.getScriptPubKey().isSentToRawPubKey()) ||
|
||||||
|
out.isWatched(this)) {
|
||||||
TransactionOutPoint outPoint = new TransactionOutPoint(params, i, tx);
|
TransactionOutPoint outPoint = new TransactionOutPoint(params, i, tx);
|
||||||
filter.insert(outPoint.bitcoinSerialize());
|
filter.insert(outPoint.bitcoinSerialize());
|
||||||
}
|
}
|
||||||
@ -2851,6 +3021,7 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return filter;
|
return filter;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3110,6 +3281,18 @@ public class Wallet implements Serializable, BlockChainListener, PeerFilterProvi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void queueOnScriptsAdded(final List<Script> scripts) {
|
||||||
|
checkState(lock.isHeldByCurrentThread());
|
||||||
|
for (final ListenerRegistration<WalletEventListener> registration : eventListeners) {
|
||||||
|
registration.executor.execute(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
registration.listener.onScriptsAdded(Wallet.this, scripts);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
// Fee calculation code.
|
// Fee calculation code.
|
||||||
|
@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
package com.google.bitcoin.core;
|
package com.google.bitcoin.core;
|
||||||
|
|
||||||
|
import com.google.bitcoin.script.Script;
|
||||||
|
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -119,4 +121,7 @@ public interface WalletEventListener {
|
|||||||
* or due to some other automatic derivation.
|
* or due to some other automatic derivation.
|
||||||
*/
|
*/
|
||||||
void onKeysAdded(Wallet wallet, List<ECKey> keys);
|
void onKeysAdded(Wallet wallet, List<ECKey> keys);
|
||||||
|
|
||||||
|
/** Called whenever a new watched script is added to the wallet. */
|
||||||
|
void onScriptsAdded(Wallet wallet, List<Script> scripts);
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ import com.google.bitcoin.core.ECKey;
|
|||||||
import com.google.bitcoin.core.Transaction;
|
import com.google.bitcoin.core.Transaction;
|
||||||
import com.google.bitcoin.core.Wallet;
|
import com.google.bitcoin.core.Wallet;
|
||||||
import com.google.bitcoin.core.WalletEventListener;
|
import com.google.bitcoin.core.WalletEventListener;
|
||||||
|
import com.google.bitcoin.script.Script;
|
||||||
|
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -49,4 +50,7 @@ public class NativeWalletEventListener implements WalletEventListener {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public native void onKeysAdded(Wallet wallet, List<ECKey> keys);
|
public native void onKeysAdded(Wallet wallet, List<ECKey> keys);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public native void onScriptsAdded(Wallet wallet, List<Script> scripts);
|
||||||
}
|
}
|
||||||
|
@ -61,6 +61,9 @@ public class Script {
|
|||||||
// must preserve the exact bytes that we read off the wire, along with the parsed form.
|
// must preserve the exact bytes that we read off the wire, along with the parsed form.
|
||||||
protected byte[] program;
|
protected byte[] program;
|
||||||
|
|
||||||
|
// Creation time of the associated keys in seconds since the epoch.
|
||||||
|
private long creationTimeSeconds;
|
||||||
|
|
||||||
/** Creates an empty script that serializes to nothing. */
|
/** Creates an empty script that serializes to nothing. */
|
||||||
private Script() {
|
private Script() {
|
||||||
chunks = Lists.newArrayList();
|
chunks = Lists.newArrayList();
|
||||||
@ -69,6 +72,7 @@ public class Script {
|
|||||||
// Used from ScriptBuilder.
|
// Used from ScriptBuilder.
|
||||||
Script(List<ScriptChunk> chunks) {
|
Script(List<ScriptChunk> chunks) {
|
||||||
this.chunks = Collections.unmodifiableList(new ArrayList<ScriptChunk>(chunks));
|
this.chunks = Collections.unmodifiableList(new ArrayList<ScriptChunk>(chunks));
|
||||||
|
creationTimeSeconds = Utils.now().getTime() / 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -79,6 +83,21 @@ public class Script {
|
|||||||
public Script(byte[] programBytes) throws ScriptException {
|
public Script(byte[] programBytes) throws ScriptException {
|
||||||
program = programBytes;
|
program = programBytes;
|
||||||
parse(programBytes);
|
parse(programBytes);
|
||||||
|
creationTimeSeconds = Utils.now().getTime() / 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Script(byte[] programBytes, long creationTimeSeconds) throws ScriptException {
|
||||||
|
program = programBytes;
|
||||||
|
parse(programBytes);
|
||||||
|
this.creationTimeSeconds = creationTimeSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getCreationTimeSeconds() {
|
||||||
|
return creationTimeSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCreationTimeSeconds(long creationTimeSeconds) {
|
||||||
|
this.creationTimeSeconds = creationTimeSeconds;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -97,6 +116,11 @@ public class Script {
|
|||||||
buf.append("] ");
|
buf.append("] ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (creationTimeSeconds != 0) {
|
||||||
|
buf.append(" timestamp:").append(creationTimeSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
return buf.toString();
|
return buf.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,7 +134,8 @@ public class Script {
|
|||||||
for (ScriptChunk chunk : chunks) {
|
for (ScriptChunk chunk : chunks) {
|
||||||
chunk.write(bos);
|
chunk.write(bos);
|
||||||
}
|
}
|
||||||
return bos.toByteArray();
|
program = bos.toByteArray();
|
||||||
|
return program;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new RuntimeException(e); // Cannot happen.
|
throw new RuntimeException(e); // Cannot happen.
|
||||||
}
|
}
|
||||||
@ -1241,4 +1266,25 @@ public class Script {
|
|||||||
throw new ScriptException("P2SH script execution resulted in a non-true stack");
|
throw new ScriptException("P2SH script execution resulted in a non-true stack");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Utility that doesn't copy for internal use
|
||||||
|
private byte[] getQuickProgram() {
|
||||||
|
if (program != null)
|
||||||
|
return program;
|
||||||
|
return getProgram();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (!(obj instanceof Script))
|
||||||
|
return false;
|
||||||
|
Script s = (Script)obj;
|
||||||
|
return Arrays.equals(getQuickProgram(), s.getQuickProgram());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
byte[] bytes = getQuickProgram();
|
||||||
|
return Arrays.hashCode(bytes);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,8 @@ import com.google.bitcoin.core.TransactionConfidence.ConfidenceType;
|
|||||||
import com.google.bitcoin.crypto.EncryptedPrivateKey;
|
import com.google.bitcoin.crypto.EncryptedPrivateKey;
|
||||||
import com.google.bitcoin.crypto.KeyCrypter;
|
import com.google.bitcoin.crypto.KeyCrypter;
|
||||||
import com.google.bitcoin.crypto.KeyCrypterScrypt;
|
import com.google.bitcoin.crypto.KeyCrypterScrypt;
|
||||||
|
import com.google.bitcoin.script.Script;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
import com.google.protobuf.ByteString;
|
import com.google.protobuf.ByteString;
|
||||||
import com.google.protobuf.TextFormat;
|
import com.google.protobuf.TextFormat;
|
||||||
import org.bitcoinj.wallet.Protos;
|
import org.bitcoinj.wallet.Protos;
|
||||||
@ -36,6 +38,7 @@ import java.net.InetAddress;
|
|||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.ListIterator;
|
import java.util.ListIterator;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@ -83,7 +86,7 @@ public class WalletProtobufSerializer {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Formats the given wallet (transactions and keys) to the given output stream in protocol buffer format.<p>
|
* Formats the given wallet (transactions and keys) to the given output stream in protocol buffer format.<p>
|
||||||
*
|
*
|
||||||
* Equivalent to <tt>walletToProto(wallet).writeTo(output);</tt>
|
* Equivalent to <tt>walletToProto(wallet).writeTo(output);</tt>
|
||||||
*/
|
*/
|
||||||
public void writeWallet(Wallet wallet, OutputStream output) throws IOException {
|
public void writeWallet(Wallet wallet, OutputStream output) throws IOException {
|
||||||
@ -154,6 +157,16 @@ public class WalletProtobufSerializer {
|
|||||||
walletBuilder.addKey(keyBuilder);
|
walletBuilder.addKey(keyBuilder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (Script script : wallet.getWatchedScripts()) {
|
||||||
|
Protos.Script protoScript =
|
||||||
|
Protos.Script.newBuilder()
|
||||||
|
.setProgram(ByteString.copyFrom(script.getProgram()))
|
||||||
|
.setCreationTimestamp(script.getCreationTimeSeconds() * 1000)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
walletBuilder.addWatchedScript(protoScript);
|
||||||
|
}
|
||||||
|
|
||||||
// Populate the lastSeenBlockHash field.
|
// Populate the lastSeenBlockHash field.
|
||||||
Sha256Hash lastSeenBlockHash = wallet.getLastBlockSeenHash();
|
Sha256Hash lastSeenBlockHash = wallet.getLastBlockSeenHash();
|
||||||
if (lastSeenBlockHash != null) {
|
if (lastSeenBlockHash != null) {
|
||||||
@ -403,6 +416,20 @@ public class WalletProtobufSerializer {
|
|||||||
wallet.addKey(ecKey);
|
wallet.addKey(ecKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<Script> scripts = Lists.newArrayList();
|
||||||
|
for (Protos.Script protoScript : walletProto.getWatchedScriptList()) {
|
||||||
|
try {
|
||||||
|
Script script =
|
||||||
|
new Script(protoScript.getProgram().toByteArray(),
|
||||||
|
protoScript.getCreationTimestamp() / 1000);
|
||||||
|
scripts.add(script);
|
||||||
|
} catch (ScriptException e) {
|
||||||
|
throw new UnreadableWalletException("Unparseable script in wallet");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wallet.addWatchedScripts(scripts);
|
||||||
|
|
||||||
// Read all transactions and insert into the txMap.
|
// Read all transactions and insert into the txMap.
|
||||||
for (Protos.Transaction txProto : walletProto.getTransactionList()) {
|
for (Protos.Transaction txProto : walletProto.getTransactionList()) {
|
||||||
readTransaction(txProto, wallet.getParams());
|
readTransaction(txProto, wallet.getParams());
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -127,6 +127,11 @@ public class BitcoindComparisonTool {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isRequiringUpdateAllBloomFilter() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override public BloomFilter getBloomFilter(int size, double falsePositiveRate, long nTweak) {
|
@Override public BloomFilter getBloomFilter(int size, double falsePositiveRate, long nTweak) {
|
||||||
BloomFilter filter = new BloomFilter(1, 0.99, 0);
|
BloomFilter filter = new BloomFilter(1, 0.99, 0);
|
||||||
filter.setMatchAll();
|
filter.setMatchAll();
|
||||||
|
@ -648,7 +648,7 @@ public class WalletTest extends TestWithWallet {
|
|||||||
assertEquals(send1, eventDead[0]);
|
assertEquals(send1, eventDead[0]);
|
||||||
assertEquals(send2, eventReplacement[0]);
|
assertEquals(send2, eventReplacement[0]);
|
||||||
assertEquals(TransactionConfidence.ConfidenceType.DEAD,
|
assertEquals(TransactionConfidence.ConfidenceType.DEAD,
|
||||||
send1.getConfidence().getConfidenceType());
|
send1.getConfidence().getConfidenceType());
|
||||||
assertEquals(send2, received.getOutput(0).getSpentBy().getParentTransaction());
|
assertEquals(send2, received.getOutput(0).getSpentBy().getParentTransaction());
|
||||||
|
|
||||||
TestUtils.DoubleSpends doubleSpends = TestUtils.createFakeDoubleSpendTxns(params, myAddress);
|
TestUtils.DoubleSpends doubleSpends = TestUtils.createFakeDoubleSpendTxns(params, myAddress);
|
||||||
@ -659,7 +659,7 @@ public class WalletTest extends TestWithWallet {
|
|||||||
sendMoneyToWallet(doubleSpends.t2, AbstractBlockChain.NewBlockType.BEST_CHAIN);
|
sendMoneyToWallet(doubleSpends.t2, AbstractBlockChain.NewBlockType.BEST_CHAIN);
|
||||||
Threading.waitForUserCode();
|
Threading.waitForUserCode();
|
||||||
assertEquals(TransactionConfidence.ConfidenceType.DEAD,
|
assertEquals(TransactionConfidence.ConfidenceType.DEAD,
|
||||||
doubleSpends.t1.getConfidence().getConfidenceType());
|
doubleSpends.t1.getConfidence().getConfidenceType());
|
||||||
assertEquals(doubleSpends.t2, doubleSpends.t1.getConfidence().getOverridingTransaction());
|
assertEquals(doubleSpends.t2, doubleSpends.t1.getConfidence().getOverridingTransaction());
|
||||||
assertEquals(5, eventWalletChanged[0]);
|
assertEquals(5, eventWalletChanged[0]);
|
||||||
}
|
}
|
||||||
@ -831,7 +831,7 @@ public class WalletTest extends TestWithWallet {
|
|||||||
// Check we got them back in order.
|
// Check we got them back in order.
|
||||||
List<Transaction> transactions = wallet.getTransactionsByTime();
|
List<Transaction> transactions = wallet.getTransactionsByTime();
|
||||||
assertEquals(tx2, transactions.get(0));
|
assertEquals(tx2, transactions.get(0));
|
||||||
assertEquals(tx1, transactions.get(1));
|
assertEquals(tx1, transactions.get(1));
|
||||||
assertEquals(2, transactions.size());
|
assertEquals(2, transactions.size());
|
||||||
// Check we get only the last transaction if we request a subrage.
|
// Check we get only the last transaction if we request a subrage.
|
||||||
transactions = wallet.getRecentTransactions(1, false);
|
transactions = wallet.getRecentTransactions(1, false);
|
||||||
@ -873,6 +873,20 @@ public class WalletTest extends TestWithWallet {
|
|||||||
assertEquals(now + 60, wallet.getEarliestKeyCreationTime());
|
assertEquals(now + 60, wallet.getEarliestKeyCreationTime());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void scriptCreationTime() throws Exception {
|
||||||
|
wallet = new Wallet(params);
|
||||||
|
long now = Utils.rollMockClock(0).getTime() / 1000; // Fix the mock clock.
|
||||||
|
// No keys returns current time.
|
||||||
|
assertEquals(now, wallet.getEarliestKeyCreationTime());
|
||||||
|
Utils.rollMockClock(60);
|
||||||
|
wallet.addWatchedAddress(new ECKey().toAddress(params));
|
||||||
|
|
||||||
|
Utils.rollMockClock(60);
|
||||||
|
wallet.addKey(new ECKey());
|
||||||
|
assertEquals(now + 60, wallet.getEarliestKeyCreationTime());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void spendToSameWallet() throws Exception {
|
public void spendToSameWallet() throws Exception {
|
||||||
// Test that a spend to the same wallet is dealt with correctly.
|
// Test that a spend to the same wallet is dealt with correctly.
|
||||||
@ -950,6 +964,73 @@ public class WalletTest extends TestWithWallet {
|
|||||||
log.info(t2.toString(chain));
|
log.info(t2.toString(chain));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void watchingScripts() throws Exception {
|
||||||
|
// Verify that pending transactions to watched addresses are relevant
|
||||||
|
ECKey key = new ECKey();
|
||||||
|
Address watchedAddress = key.toAddress(params);
|
||||||
|
wallet.addWatchedAddress(watchedAddress);
|
||||||
|
BigInteger value = toNanoCoins(5, 0);
|
||||||
|
Transaction t1 = createFakeTx(params, value, watchedAddress);
|
||||||
|
assertTrue(wallet.isPendingTransactionRelevant(t1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void watchingScriptsConfirmed() throws Exception {
|
||||||
|
ECKey key = new ECKey();
|
||||||
|
Address watchedAddress = key.toAddress(params);
|
||||||
|
wallet.addWatchedAddress(watchedAddress);
|
||||||
|
Transaction t1 = createFakeTx(params, CENT, watchedAddress);
|
||||||
|
StoredBlock b3 = createFakeBlock(blockStore, t1).storedBlock;
|
||||||
|
wallet.receiveFromBlock(t1, b3, BlockChain.NewBlockType.BEST_CHAIN, 0);
|
||||||
|
assertEquals(BigInteger.ZERO, wallet.getBalance());
|
||||||
|
assertEquals(CENT, wallet.getWatchedBalance());
|
||||||
|
|
||||||
|
// We can't spend watched balances
|
||||||
|
Address notMyAddr = new ECKey().toAddress(params);
|
||||||
|
assertNull(wallet.createSend(notMyAddr, CENT));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void watchingScriptsSentFrom() throws Exception {
|
||||||
|
ECKey key = new ECKey();
|
||||||
|
ECKey notMyAddr = new ECKey();
|
||||||
|
Address watchedAddress = key.toAddress(params);
|
||||||
|
wallet.addWatchedAddress(watchedAddress);
|
||||||
|
Transaction t1 = createFakeTx(params, CENT, watchedAddress);
|
||||||
|
Transaction t2 = createFakeTx(params, COIN, notMyAddr);
|
||||||
|
StoredBlock b1 = createFakeBlock(blockStore, t1).storedBlock;
|
||||||
|
Transaction st2 = new Transaction(params);
|
||||||
|
st2.addOutput(CENT, notMyAddr);
|
||||||
|
st2.addOutput(COIN, notMyAddr);
|
||||||
|
st2.addInput(t1.getOutput(0));
|
||||||
|
st2.addInput(t2.getOutput(0));
|
||||||
|
wallet.receiveFromBlock(t1, b1, BlockChain.NewBlockType.BEST_CHAIN, 0);
|
||||||
|
wallet.receiveFromBlock(st2, b1, BlockChain.NewBlockType.BEST_CHAIN, 0);
|
||||||
|
assertEquals(CENT, st2.getValueSentFromMe(wallet));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void watchingScriptsBloomFilter() throws Exception {
|
||||||
|
assertFalse(wallet.isRequiringUpdateAllBloomFilter());
|
||||||
|
|
||||||
|
ECKey key = new ECKey();
|
||||||
|
Address watchedAddress = key.toAddress(params);
|
||||||
|
wallet.addWatchedAddress(watchedAddress);
|
||||||
|
|
||||||
|
assertTrue(wallet.isRequiringUpdateAllBloomFilter());
|
||||||
|
Transaction t1 = createFakeTx(params, CENT, watchedAddress);
|
||||||
|
StoredBlock b1 = createFakeBlock(blockStore, t1).storedBlock;
|
||||||
|
|
||||||
|
TransactionOutPoint outPoint = new TransactionOutPoint(params, 0, t1);
|
||||||
|
|
||||||
|
// Note that this has a 1e-12 chance of failing this unit test due to a false positive
|
||||||
|
assertFalse(wallet.getBloomFilter(1e-12).contains(outPoint.bitcoinSerialize()));
|
||||||
|
|
||||||
|
wallet.receiveFromBlock(t1, b1, BlockChain.NewBlockType.BEST_CHAIN, 0);
|
||||||
|
assertTrue(wallet.getBloomFilter(1e-12).contains(outPoint.bitcoinSerialize()));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void autosaveImmediate() throws Exception {
|
public void autosaveImmediate() throws Exception {
|
||||||
// Test that the wallet will save itself automatically when it changes.
|
// Test that the wallet will save itself automatically when it changes.
|
||||||
|
@ -5,6 +5,7 @@ import com.google.bitcoin.core.*;
|
|||||||
import com.google.bitcoin.core.TransactionConfidence.ConfidenceType;
|
import com.google.bitcoin.core.TransactionConfidence.ConfidenceType;
|
||||||
import com.google.bitcoin.params.MainNetParams;
|
import com.google.bitcoin.params.MainNetParams;
|
||||||
import com.google.bitcoin.params.UnitTestParams;
|
import com.google.bitcoin.params.UnitTestParams;
|
||||||
|
import com.google.bitcoin.script.ScriptBuilder;
|
||||||
import com.google.bitcoin.utils.BriefLogFormatter;
|
import com.google.bitcoin.utils.BriefLogFormatter;
|
||||||
import com.google.bitcoin.utils.TestUtils;
|
import com.google.bitcoin.utils.TestUtils;
|
||||||
import com.google.bitcoin.utils.Threading;
|
import com.google.bitcoin.utils.Threading;
|
||||||
@ -18,6 +19,7 @@ import java.io.ByteArrayOutputStream;
|
|||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
@ -27,19 +29,24 @@ import static org.junit.Assert.*;
|
|||||||
public class WalletProtobufSerializerTest {
|
public class WalletProtobufSerializerTest {
|
||||||
static final NetworkParameters params = UnitTestParams.get();
|
static final NetworkParameters params = UnitTestParams.get();
|
||||||
private ECKey myKey;
|
private ECKey myKey;
|
||||||
|
private ECKey myWatchedKey;
|
||||||
private Address myAddress;
|
private Address myAddress;
|
||||||
private Wallet myWallet;
|
private Wallet myWallet;
|
||||||
|
|
||||||
public static String WALLET_DESCRIPTION = "The quick brown fox lives in \u4f26\u6566"; // Beijing in Chinese
|
public static String WALLET_DESCRIPTION = "The quick brown fox lives in \u4f26\u6566"; // Beijing in Chinese
|
||||||
|
private long mScriptCreationTime;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
BriefLogFormatter.initVerbose();
|
BriefLogFormatter.initVerbose();
|
||||||
|
myWatchedKey = new ECKey();
|
||||||
myKey = new ECKey();
|
myKey = new ECKey();
|
||||||
myKey.setCreationTimeSeconds(123456789L);
|
myKey.setCreationTimeSeconds(123456789L);
|
||||||
myAddress = myKey.toAddress(params);
|
myAddress = myKey.toAddress(params);
|
||||||
myWallet = new Wallet(params);
|
myWallet = new Wallet(params);
|
||||||
myWallet.addKey(myKey);
|
myWallet.addKey(myKey);
|
||||||
|
mScriptCreationTime = new Date().getTime() / 1000 - 1234;
|
||||||
|
myWallet.addWatchedAddress(myWatchedKey.toAddress(params), mScriptCreationTime);
|
||||||
myWallet.setDescription(WALLET_DESCRIPTION);
|
myWallet.setDescription(WALLET_DESCRIPTION);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,6 +62,11 @@ public class WalletProtobufSerializerTest {
|
|||||||
wallet1.findKeyFromPubHash(myKey.getPubKeyHash()).getPrivKeyBytes());
|
wallet1.findKeyFromPubHash(myKey.getPubKeyHash()).getPrivKeyBytes());
|
||||||
assertEquals(myKey.getCreationTimeSeconds(),
|
assertEquals(myKey.getCreationTimeSeconds(),
|
||||||
wallet1.findKeyFromPubHash(myKey.getPubKeyHash()).getCreationTimeSeconds());
|
wallet1.findKeyFromPubHash(myKey.getPubKeyHash()).getCreationTimeSeconds());
|
||||||
|
assertEquals(mScriptCreationTime,
|
||||||
|
wallet1.getWatchedScripts().get(0).getCreationTimeSeconds());
|
||||||
|
assertEquals(1, wallet1.getWatchedScripts().size());
|
||||||
|
assertEquals(ScriptBuilder.createOutputScript(myWatchedKey.toAddress(params)),
|
||||||
|
wallet1.getWatchedScripts().get(0));
|
||||||
assertEquals(WALLET_DESCRIPTION, wallet1.getDescription());
|
assertEquals(WALLET_DESCRIPTION, wallet1.getDescription());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user