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:
parent
2e6e0661cb
commit
1f904d28e1
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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.
|
||||
|
Loading…
x
Reference in New Issue
Block a user