mirror of
https://github.com/bitcoinj/bitcoinj.git
synced 2025-01-19 05:33:44 +01:00
Fix issue 166. Consider intra-block dependencies when deciding whether to send transactions to the wallet.
This commit is contained in:
parent
577318b9ea
commit
4e3d5313e6
@ -172,10 +172,8 @@ public class BlockChain {
|
||||
// blocks validity so we can skip the merkle root verification if the contents aren't interesting. This saves
|
||||
// a lot of time for big blocks.
|
||||
boolean contentsImportant = false;
|
||||
HashMap<Wallet, List<Transaction>> walletToTxMap = new HashMap<Wallet, List<Transaction>>();
|
||||
if (block.transactions != null) {
|
||||
scanTransactions(block, walletToTxMap);
|
||||
contentsImportant = walletToTxMap.size() > 0;
|
||||
contentsImportant = containsRelevantTransactions(block);
|
||||
}
|
||||
|
||||
// Prove the block is internally valid: hash is lower than target, etc. This only checks the block contents
|
||||
@ -211,7 +209,7 @@ public class BlockChain {
|
||||
StoredBlock newStoredBlock = storedPrev.build(block);
|
||||
checkDifficultyTransitions(storedPrev, newStoredBlock);
|
||||
blockStore.put(newStoredBlock);
|
||||
connectBlock(newStoredBlock, storedPrev, walletToTxMap);
|
||||
connectBlock(newStoredBlock, storedPrev, block.transactions);
|
||||
}
|
||||
|
||||
if (tryConnecting)
|
||||
@ -222,15 +220,15 @@ public class BlockChain {
|
||||
}
|
||||
|
||||
private void connectBlock(StoredBlock newStoredBlock, StoredBlock storedPrev,
|
||||
HashMap<Wallet, List<Transaction>> newTransactions)
|
||||
List<Transaction> transactions)
|
||||
throws BlockStoreException, VerificationException {
|
||||
StoredBlock head = getChainHead();
|
||||
if (storedPrev.equals(head)) {
|
||||
// This block connects to the best known block, it is a normal continuation of the system.
|
||||
setChainHead(newStoredBlock);
|
||||
log.debug("Chain is now {} blocks high", newStoredBlock.getHeight());
|
||||
if (newTransactions != null)
|
||||
sendTransactionsToWallet(newStoredBlock, NewBlockType.BEST_CHAIN, newTransactions);
|
||||
if (transactions != null)
|
||||
sendTransactionsToWallet(newStoredBlock, NewBlockType.BEST_CHAIN, transactions);
|
||||
} else {
|
||||
// This block connects to somewhere other than the top of the best known chain. We treat these differently.
|
||||
//
|
||||
@ -245,7 +243,7 @@ public class BlockChain {
|
||||
// newStoredBlock is a part of the same chain, there's no fork. This happens when we receive a block
|
||||
// that we already saw and linked into the chain previously, which isn't the chain head.
|
||||
// Re-processing it is confusing for the wallet so just skip.
|
||||
log.debug("Saw duplicated block in main chain at height {}: {}",
|
||||
log.warn("Saw duplicated block in main chain at height {}: {}",
|
||||
newStoredBlock.getHeight(), newStoredBlock.getHeader().getHash());
|
||||
return;
|
||||
}
|
||||
@ -262,8 +260,8 @@ public class BlockChain {
|
||||
// We may not have any transactions if we received only a header, which can happen during fast catchup.
|
||||
// If we do, send them to the wallet but state that they are on a side chain so it knows not to try and
|
||||
// spend them until they become activated.
|
||||
if (newTransactions != null) {
|
||||
sendTransactionsToWallet(newStoredBlock, NewBlockType.SIDE_CHAIN, newTransactions);
|
||||
if (transactions != null) {
|
||||
sendTransactionsToWallet(newStoredBlock, NewBlockType.SIDE_CHAIN, transactions);
|
||||
}
|
||||
|
||||
if (haveNewBestChain)
|
||||
@ -352,17 +350,17 @@ public class BlockChain {
|
||||
}
|
||||
|
||||
private void sendTransactionsToWallet(StoredBlock block, NewBlockType blockType,
|
||||
HashMap<Wallet, List<Transaction>> newTransactions) throws VerificationException {
|
||||
for (Map.Entry<Wallet, List<Transaction>> entry : newTransactions.entrySet()) {
|
||||
try {
|
||||
List<Transaction> txns = entry.getValue();
|
||||
for (Transaction tx : txns) {
|
||||
entry.getKey().receiveFromBlock(tx, block, blockType);
|
||||
List<Transaction> transactions) throws VerificationException {
|
||||
for (Transaction tx : transactions) {
|
||||
for (Wallet wallet : wallets) {
|
||||
try {
|
||||
if (wallet.isTransactionRelevant(tx, true))
|
||||
wallet.receiveFromBlock(tx, block, blockType);
|
||||
} catch (ScriptException e) {
|
||||
// We don't want scripts we don't understand to break the block chain so just note that this tx was
|
||||
// not scanned here and continue.
|
||||
log.warn("Failed to parse a script: " + e.toString());
|
||||
}
|
||||
} catch (ScriptException e) {
|
||||
// We don't want scripts we don't understand to break the block chain so just note that this tx was
|
||||
// not scanned here and continue.
|
||||
log.warn("Failed to parse a script: " + e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -495,6 +493,24 @@ public class BlockChain {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if any connected wallet considers any transaction in the block to be relevant.
|
||||
*/
|
||||
private boolean containsRelevantTransactions(Block block) {
|
||||
for (Transaction tx : block.transactions) {
|
||||
try {
|
||||
for (Wallet wallet : wallets) {
|
||||
if (wallet.isTransactionRelevant(tx, true)) return true;
|
||||
}
|
||||
} catch (ScriptException e) {
|
||||
// We don't want scripts we don't understand to break the block chain so just note that this tx was
|
||||
// not scanned here and continue.
|
||||
log.warn("Failed to parse a script: " + e.toString());
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the block at the head of the current best chain. This is the block which represents the greatest
|
||||
* amount of cumulative work done.
|
||||
|
@ -230,6 +230,27 @@ public class BlockChainTest {
|
||||
assertEquals(b3, block[0].getHeader());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void intraBlockDependencies() throws Exception {
|
||||
// Covers issue 166 in which transactions that depend on each other inside a block were not always being
|
||||
// considered relevant.
|
||||
Address somebodyElse = new ECKey().toAddress(unitTestParams);
|
||||
Block b1 = unitTestParams.genesisBlock.createNextBlock(somebodyElse);
|
||||
ECKey key = new ECKey();
|
||||
wallet.addKey(key);
|
||||
Address addr = key.toAddress(unitTestParams);
|
||||
// Create a tx that gives us some coins, and another that spends it to someone else in the same block.
|
||||
Transaction t1 = TestUtils.createFakeTx(unitTestParams, Utils.toNanoCoins(1, 0), addr);
|
||||
Transaction t2 = new Transaction(unitTestParams);
|
||||
t2.addInput(t1.getOutputs().get(0));
|
||||
t2.addOutput(Utils.toNanoCoins(2, 0), somebodyElse);
|
||||
b1.addTransaction(t1);
|
||||
b1.addTransaction(t2);
|
||||
b1.solve();
|
||||
chain.add(b1);
|
||||
assertEquals(BigInteger.ZERO, wallet.getBalance());
|
||||
}
|
||||
|
||||
// Some blocks from the test net.
|
||||
private Block getBlock2() throws Exception {
|
||||
Block b2 = new Block(testNet);
|
||||
|
Loading…
Reference in New Issue
Block a user