3
0
mirror of https://github.com/Qortal/altcoinj.git synced 2025-02-12 18:25:51 +00:00

Fix balooning memory usage in FullPrunedBlockChain.

This fixes a bug which had been previously solved with a dumb
workaround.
This commit is contained in:
Matt Corallo 2013-03-30 20:00:04 -04:00 committed by Mike Hearn
parent 2e6e0661cb
commit 1f904d28e1
3 changed files with 44 additions and 49 deletions

View File

@ -207,6 +207,7 @@ public abstract class AbstractBlockChain {
* Processes a received block and tries to add it to the chain. If there's something wrong with the block an
* exception is thrown. If the block is OK but cannot be connected to the chain at this time, returns false.
* If the block can be connected to the chain, returns true.
* Accessing block's transactions in another thread while this method runs may result in undefined behavior.
*/
public boolean add(Block block) throws VerificationException, PrunedException {
try {

View File

@ -25,6 +25,7 @@ import java.math.BigInteger;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.concurrent.*;
import static com.google.common.base.Preconditions.checkState;
@ -95,8 +96,30 @@ public class FullPrunedBlockChain extends AbstractBlockChain {
//TODO: Remove lots of duplicated code in the two connectTransactions
// TODO: execute in order of largest transaction (by input count) first
ExecutorService scriptVerificationExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
class Verifyer implements Callable<VerificationException> {
final Transaction tx;
final List<Script> prevOutScripts;
final boolean enforcePayToScriptHash;
Verifyer(final Transaction tx, final List<Script> prevOutScripts, final boolean enforcePayToScriptHash) {
this.tx = tx; this.prevOutScripts = prevOutScripts; this.enforcePayToScriptHash = enforcePayToScriptHash;
}
@Override
public VerificationException call() throws Exception {
try{
ListIterator<Script> prevOutIt = prevOutScripts.listIterator();
for (int index = 0; index < tx.getInputs().size(); index++) {
tx.getInputs().get(index).getScriptSig().correctlySpends(tx, index, prevOutIt.next(), enforcePayToScriptHash);
}
} catch (VerificationException e) {
return e;
}
return null;
}
}
@Override
protected TransactionOutputChanges connectTransactions(int height, Block block)
throws VerificationException, BlockStoreException {
@ -134,10 +157,11 @@ public class FullPrunedBlockChain extends AbstractBlockChain {
}
BigInteger totalFees = BigInteger.ZERO;
BigInteger coinbaseValue = null;
for (Transaction tx : block.transactions) {
for (final Transaction tx : block.transactions) {
boolean isCoinBase = tx.isCoinBase();
BigInteger valueIn = BigInteger.ZERO;
BigInteger valueOut = BigInteger.ZERO;
final List<Script> prevOutScripts = new LinkedList<Script>();
if (!isCoinBase) {
// For each input of the transaction remove the corresponding output from the set of unspent
// outputs.
@ -161,32 +185,7 @@ public class FullPrunedBlockChain extends AbstractBlockChain {
throw new VerificationException("Too many P2SH SigOps in block");
}
// All of these copies are terribly ugly, however without them,
// I see some odd concurrency issues where scripts throw exceptions
// (mostly "Attempted OP_* on empty stack" or similar) when they shouldn't.
// In my tests, total time spent in com.google.bitcoin.core when
// downloading the chain is < 0.5%, so doing this is no big efficiency issue.
// TODO: Find out the underlying issue and create a better work-around
final int currentIndex = index;
final Transaction txCache;
try {
txCache = new Transaction(params, tx.unsafeBitcoinSerialize());
} catch (ProtocolException e1) {
throw new RuntimeException(e1);
}
final Script scriptSig = in.getScriptSig();
final Script scriptPubKey = new Script(params, prevOut.getScriptBytes(), 0, prevOut.getScriptBytes().length);
FutureTask<VerificationException> future = new FutureTask<VerificationException>(new Callable<VerificationException>() {
public VerificationException call() {
try{
scriptSig.correctlySpends(txCache, currentIndex, scriptPubKey, enforcePayToScriptHash);
} catch (VerificationException e) {
return e;
}
return null;
}});
scriptVerificationExecutor.execute(future);
listScriptVerificationResults.add(future);
prevOutScripts.add(new Script(params, prevOut.getScriptBytes(), 0, prevOut.getScriptBytes().length));
//in.getScriptSig().correctlySpends(tx, index, new Script(params, prevOut.getScriptBytes(), 0, prevOut.getScriptBytes().length));
@ -214,6 +213,13 @@ public class FullPrunedBlockChain extends AbstractBlockChain {
throw new VerificationException("Transaction input value out of range");
totalFees = totalFees.add(valueIn.subtract(valueOut));
}
if (!isCoinBase) {
// Because correctlySpends modifies transactions, this must come after we are done with tx
FutureTask<VerificationException> future = new FutureTask<VerificationException>(new Verifyer(tx, prevOutScripts, enforcePayToScriptHash));
scriptVerificationExecutor.execute(future);
listScriptVerificationResults.add(future);
}
}
if (totalFees.compareTo(params.MAX_MONEY) > 0 || block.getBlockInflation(height).add(totalFees).compareTo(coinbaseValue) < 0)
throw new VerificationException("Transaction fees out of range");
@ -284,6 +290,7 @@ public class FullPrunedBlockChain extends AbstractBlockChain {
boolean isCoinBase = tx.isCoinBase();
BigInteger valueIn = BigInteger.ZERO;
BigInteger valueOut = BigInteger.ZERO;
final List<Script> prevOutScripts = new LinkedList<Script>();
if (!isCoinBase) {
for (int index = 0; index < tx.getInputs().size(); index++) {
final TransactionInput in = tx.getInputs().get(index);
@ -302,28 +309,7 @@ public class FullPrunedBlockChain extends AbstractBlockChain {
throw new VerificationException("Too many P2SH SigOps in block");
}
// All of these copies are terribly ugly, however without them,
// I see some odd concurrency issues where scripts throw exceptions
// (mostly "Attempted OP_* on empty stack" or similar) when they shouldn't.
// In my tests, total time spent in com.google.bitcoin.core when
// downloading the chain is < 0.5%, so doing this is no big efficiency issue.
// TODO: Find out the underlying issue and create a better work-around
// TODO: Thoroughly test that this fixes the issue like the non-StoredBlock version does
final int currentIndex = index;
final Script scriptSig = in.getScriptSig();
final Script scriptPubKey = new Script(params, prevOut.getScriptBytes(), 0, prevOut.getScriptBytes().length);
FutureTask<VerificationException> future = new FutureTask<VerificationException>(new Callable<VerificationException>() {
public VerificationException call() {
try{
scriptSig.correctlySpends(tx, currentIndex, scriptPubKey, enforcePayToScriptHash);
} catch (VerificationException e) {
return e;
}
return null;
}
});
scriptVerificationExecutor.execute(future);
listScriptVerificationResults.add(future);
prevOutScripts.add(new Script(params, prevOut.getScriptBytes(), 0, prevOut.getScriptBytes().length));
blockStore.removeUnspentTransactionOutput(prevOut);
txOutsSpent.add(prevOut);
@ -349,6 +335,13 @@ public class FullPrunedBlockChain extends AbstractBlockChain {
throw new VerificationException("Transaction input value out of range");
totalFees = totalFees.add(valueIn.subtract(valueOut));
}
if (!isCoinBase) {
// Because correctlySpends modifies transactions, this must come after we are done with tx
FutureTask<VerificationException> future = new FutureTask<VerificationException>(new Verifyer(tx, prevOutScripts, enforcePayToScriptHash));
scriptVerificationExecutor.execute(future);
listScriptVerificationResults.add(future);
}
}
if (totalFees.compareTo(params.MAX_MONEY) > 0 ||
newBlock.getHeader().getBlockInflation(newBlock.getHeight()).add(totalFees).compareTo(coinbaseValue) < 0)

View File

@ -1480,6 +1480,7 @@ public class Script {
/**
* Verifies that this script (interpreted as a scriptSig) correctly spends the given scriptPubKey.
* @param txContainingThis The transaction in which this input scriptSig resides.
* Accessing txContainingThis from another thread while this method runs results in undefined behavior.
* @param scriptSigIndex The index in txContainingThis of the scriptSig (note: NOT the index of the scriptPubKey).
* @param scriptPubKey The connected scriptPubKey containing the conditions needed to claim the value.
* @param enforceP2SH Whether "pay to script hash" rules should be enforced. If in doubt, set to true.