Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion core/src/main/java/org/bitcoinj/core/Transaction.java
Original file line number Diff line number Diff line change
Expand Up @@ -1569,7 +1569,12 @@ public void verify() throws VerificationException {
}

if (isCoinBase()) {
if (inputs.get(0).getScriptBytes().length < 2 || inputs.get(0).getScriptBytes().length > 100)
final int scriptLength = inputs.get(0).getScriptBytes().length;
int scriptMaxLength = 2;
if (getType() == Type.TRANSACTION_COINBASE) {
scriptMaxLength = 1;
}
if (scriptLength < scriptMaxLength || scriptLength > 100)
throw new VerificationException.CoinbaseScriptSizeOutOfRange();
} else {
for (TransactionInput input : inputs)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -300,9 +300,8 @@ public void updatedBlockTip(StoredBlock tip) {
}

protected boolean shouldProcessMNListDiff() {

return masternodeSync.hasSyncFlag(MasternodeSync.SYNC_FLAGS.SYNC_DMN_LIST) ||
masternodeSync.hasSyncFlag(MasternodeSync.SYNC_FLAGS.SYNC_QUORUM_LIST);
return masternodeSync != null && (masternodeSync.hasSyncFlag(MasternodeSync.SYNC_FLAGS.SYNC_DMN_LIST) ||
masternodeSync.hasSyncFlag(MasternodeSync.SYNC_FLAGS.SYNC_QUORUM_LIST));
}

@Override
Expand Down Expand Up @@ -444,7 +443,9 @@ public void close() {
quorumState.close();
quorumRotationState.close();

peerGroup.removePreMessageReceivedEventListener(preMessageReceivedEventListener);
if (peerGroup != null) {
peerGroup.removePreMessageReceivedEventListener(preMessageReceivedEventListener);
}
threadPool.shutdown();
// Don't wait at all - let it die naturally to avoid blocking
if (!threadPool.isTerminated()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,9 @@ public void setBlockChain(PeerGroup peerGroup, AbstractBlockChain blockChain, Ab
public void close() {
blockChain = null;
headerChain = null;
peerGroup.removePreMessageReceivedEventListener(preMessageReceivedEventListener);
if (peerGroup != null) {
peerGroup.removePreMessageReceivedEventListener(preMessageReceivedEventListener);
}
peerGroup = null;
super.close();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,9 @@ public void close(PeerGroup peerGroup) {
peerGroup.removeOnTransactionBroadcastListener(this.transactionBroadcastListener);
peerGroup.removePreMessageReceivedEventListener(preMessageReceivedEventListener);
}
chainLocksHandler.removeChainLockListener(this.chainLockListener);
if (chainLocksHandler != null) {
chainLocksHandler.removeChainLockListener(this.chainLockListener);
}
wallets.forEach(wallet -> wallet.removeCoinsSentEventListener(coinsSentEventListener));
try {
if (!scheduledExecutorService.awaitTermination(3000, TimeUnit.MILLISECONDS)) {
Expand Down
166 changes: 145 additions & 21 deletions core/src/main/java/org/bitcoinj/wallet/Wallet.java
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ protected boolean removeEldestEntry(Map.Entry<Sha256Hash, Transaction> eldest) {
private int onWalletChangedSuppressions;
private boolean insideReorg;
private Map<Transaction, TransactionConfidence.Listener.ChangeReason> confidenceChanged;
private final ArrayList<Transaction> manualConfidenceChangeTransactions = Lists.newArrayList();
protected volatile WalletFiles vFileManager;
// Object that is used to send transactions asynchronously when the wallet requires it.
protected volatile TransactionBroadcaster vTransactionBroadcaster;
Expand Down Expand Up @@ -267,7 +268,7 @@ protected boolean removeEldestEntry(Map.Entry<Sha256Hash, Transaction> eldest) {
@GuardedBy("lock") protected HashSet<TransactionOutPoint> lockedOutputs = Sets.newHashSet();
// save now on blocks with transactions
private boolean saveOnNextBlock = true;

private boolean notifyTxOnNextBlock = true;

/**
* Creates a new, empty wallet with a randomly chosen seed and no transactions. Make sure to provide for sufficient
Expand Down Expand Up @@ -1865,6 +1866,14 @@ public boolean getSaveOnNextBlock() {
return saveOnNextBlock;
}

public void setNotifyTxOnNextBlock(boolean notifyTxOnNextBlock) {
this.notifyTxOnNextBlock = notifyTxOnNextBlock;
}

public boolean isNotifyTxOnNextBlock() {
return notifyTxOnNextBlock;
}

/**
* Uses protobuf serialization to save the wallet to the given file stream. To learn more about this file format, see
* {@link WalletProtobufSerializer}.
Expand Down Expand Up @@ -1963,6 +1972,48 @@ public void isConsistentOrThrow() throws IllegalStateException {
}
}

/**
* Validates that a single transaction is internally consistent within the wallet.
* This is a lightweight alternative to {@link #isConsistentOrThrow()} for cases where
* the wallet is already known to be consistent and we only need to verify one
transaction.
*
* @param tx The transaction to validate
* @return true if the transaction is consistent with its current pool state
* @throws IllegalStateException if the transaction is in an inconsistent state
*/
// TODO: a test fails when using this in receive()
public boolean validateTransaction(Transaction tx) {
lock.lock();
try {
Sha256Hash txHash = tx.getTxId();

// Determine which pool the transaction is in and validate accordingly
if (spent.containsKey(txHash)) {
if (!isTxConsistent(tx, true)) {
throw new IllegalStateException("Inconsistent spent tx: " + txHash);
}
} else if (unspent.containsKey(txHash)) {
if (!isTxConsistent(tx, false)) {
throw new IllegalStateException("Inconsistent unspent tx: " + txHash);
}
} else if (pending.containsKey(txHash) || dead.containsKey(txHash)) {
// For pending/dead transactions, check as unspent
if (!isTxConsistent(tx, false)) {
throw new IllegalStateException("Inconsistent pending/dead tx: " +
txHash);
}
}
// If not in any pool, nothing to validate

return true;
} finally {
lock.unlock();
}
}



/*
* If isSpent - check that all my outputs spent, otherwise check that there at least
* one unspent.
Expand Down Expand Up @@ -2493,6 +2544,8 @@ private void receive(Transaction tx, StoredBlock block, BlockChain.NewBlockType
extension.processTransaction(tx, block, blockType);
}
isConsistentOrThrow();
// TODO: fix issues in this function as a replacement for isConsistentOrThrow()
// validateTransaction(tx);
// Optimization for the case where a block has tons of relevant transactions.
saveLater();
hardSaveOnNextBlock = true;
Expand Down Expand Up @@ -2578,29 +2631,52 @@ public void notifyNewBestBlock(StoredBlock block) throws VerificationException {
setLastBlockSeenTimeSecs(block.getHeader().getTimeSeconds());
// Notify all the BUILDING transactions of the new block.
// This is so that they can update their depth.
Set<Transaction> transactions = getTransactions(true);
for (Transaction tx : transactions) {
if (ignoreNextNewBlock.contains(tx.getTxId())) {
// tx was already processed in receive() due to it appearing in this block, so we don't want to
// increment the tx confidence depth twice, it'd result in miscounting.
ignoreNextNewBlock.remove(tx.getTxId());
} else {
TransactionConfidence confidence = tx.getConfidence();
if (confidence.getConfidenceType() == ConfidenceType.BUILDING) {
// Erase the set of seen peers once the tx is so deep that it seems unlikely to ever go
// pending again. We could clear this data the moment a tx is seen in the block chain, but
// in cases where the chain re-orgs, this would mean that wallets would perceive a newly
// pending tx has zero confidence at all, which would not be right: we expect it to be
// included once again. We could have a separate was-in-chain-and-now-isn't confidence type
// but this way is backwards compatible with existing software, and the new state probably
// wouldn't mean anything different to just remembering peers anyway.
if (confidence.incrementDepthInBlocks() > context.getEventHorizon())
confidence.clearBroadcastBy();
confidenceChanged.put(tx, TransactionConfidence.Listener.ChangeReason.DEPTH);
if (notifyTxOnNextBlock) {
Set<Transaction> transactions = getTransactions(true);
for (Transaction tx : transactions) {
if (ignoreNextNewBlock.contains(tx.getTxId())) {
// tx was already processed in receive() due to it appearing in this block, so we don't want to
// increment the tx confidence depth twice, it'd result in miscounting.
ignoreNextNewBlock.remove(tx.getTxId());
} else {
TransactionConfidence confidence = tx.getConfidence();
if (confidence.getConfidenceType() == ConfidenceType.BUILDING) {
// Erase the set of seen peers once the tx is so deep that it seems unlikely to ever go
// pending again. We could clear this data the moment a tx is seen in the block chain, but
// in cases where the chain re-orgs, this would mean that wallets would perceive a newly
// pending tx has zero confidence at all, which would not be right: we expect it to be
// included once again. We could have a separate was-in-chain-and-now-isn't confidence type
// but this way is backwards compatible with existing software, and the new state probably
// wouldn't mean anything different to just remembering peers anyway.
if (confidence.incrementDepthInBlocks() > context.getEventHorizon())
confidence.clearBroadcastBy();
confidenceChanged.put(tx, TransactionConfidence.Listener.ChangeReason.DEPTH);
}
}
}
} else {
for (Transaction tx : manualConfidenceChangeTransactions) {
if (ignoreNextNewBlock.contains(tx.getTxId())) {
// tx was already processed in receive() due to it appearing in this block, so we don't want to
// increment the tx confidence depth twice, it'd result in miscounting.
ignoreNextNewBlock.remove(tx.getTxId());
} else {
TransactionConfidence confidence = tx.getConfidence();
if (confidence.getConfidenceType() == ConfidenceType.BUILDING) {
// Erase the set of seen peers once the tx is so deep that it seems unlikely to ever go
// pending again. We could clear this data the moment a tx is seen in the block chain, but
// in cases where the chain re-orgs, this would mean that wallets would perceive a newly
// pending tx has zero confidence at all, which would not be right: we expect it to be
// included once again. We could have a separate was-in-chain-and-now-isn't confidence type
// but this way is backwards compatible with existing software, and the new state probably
// wouldn't mean anything different to just remembering peers anyway.
if (confidence.incrementDepthInBlocks() > context.getEventHorizon())
confidence.clearBroadcastBy();
confidenceChanged.put(tx, TransactionConfidence.Listener.ChangeReason.DEPTH);
}
}
}
}

informConfidenceListenersIfNotReorganizing();
maybeQueueOnWalletChanged();

Expand All @@ -2616,6 +2692,36 @@ public void notifyNewBestBlock(StoredBlock block) throws VerificationException {
}
}

/**
* update transaction depths and notify
*/
public void updateTransactionDepth() {
lock.lock();
try {
Set<Transaction> transactions = getTransactions(true);
for (Transaction tx : transactions) {
TransactionConfidence confidence = tx.getConfidence();
if (confidence.getConfidenceType() == ConfidenceType.BUILDING) {
// Erase the set of seen peers once the tx is so deep that it seems unlikely to ever go
// pending again. We could clear this data the moment a tx is seen in the block chain, but
// in cases where the chain re-orgs, this would mean that wallets would perceive a newly
// pending tx has zero confidence at all, which would not be right: we expect it to be
// included once again. We could have a separate was-in-chain-and-now-isn't confidence type
// but this way is backwards compatible with existing software, and the new state probably
// wouldn't mean anything different to just remembering peers anyway.
confidence.setDepthInBlocks(lastBlockSeenHeight - confidence.getAppearedAtChainHeight() + 1);
if (confidence.getDepthInBlocks() > context.getEventHorizon())
confidence.clearBroadcastBy();
confidenceChanged.put(tx, TransactionConfidence.Listener.ChangeReason.DEPTH);
}
}
informConfidenceListenersIfNotReorganizing();
} finally {
lock.unlock();
}
}


/**
* 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.
Expand Down Expand Up @@ -6477,4 +6583,22 @@ public void unlockOutput(TransactionOutPoint outPoint) {
lock.unlock();
}
}

public void addManualNotifyConfidenceChangeTransaction(Transaction tx) {
lock.lock();
try {
manualConfidenceChangeTransactions.add(tx);
} finally {
lock.unlock();
}
}

public void removeManualNotifyConfidenceChangeTransaction(Transaction tx) {
lock.lock();
try {
manualConfidenceChangeTransactions.remove(tx);
} finally {
lock.unlock();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ public void transactionReportTest() {
info("getTransactionReport: {}", watch1);
}

@Test
@Test @Ignore // this test fails with java.lang.OutOfMemoryError: Java heap space
public void walletToStringTest() {
Stopwatch watch0 = Stopwatch.createStarted();
wallet.toString(false, false, true, null);
Expand Down
Loading