/** * This is broken for blocks that do not pass BIP30, so all BIP30-failing blocks which are allowed * to fail BIP30 must be checkpointed. */ @Override protected void disconnectTransactions(StoredBlock oldBlock) throws PrunedException, BlockStoreException { checkState(lock.isLocked()); blockStore.beginDatabaseBatchWrite(); try { StoredUndoableBlock undoBlock = blockStore.getUndoBlock(oldBlock.getHeader().getHash()); if (undoBlock == null) throw new PrunedException(oldBlock.getHeader().getHash()); TransactionOutputChanges txOutChanges = undoBlock.getTxOutChanges(); for (StoredTransactionOutput out : txOutChanges.txOutsSpent) blockStore.addUnspentTransactionOutput(out); for (StoredTransactionOutput out : txOutChanges.txOutsCreated) blockStore.removeUnspentTransactionOutput(out); } catch (PrunedException e) { blockStore.abortDatabaseBatchWrite(); throw e; } catch (BlockStoreException e) { blockStore.abortDatabaseBatchWrite(); throw e; } }
@Override /** Used during reorgs to connect a block previously on a fork */ protected synchronized TransactionOutputChanges connectTransactions(StoredBlock newBlock) throws VerificationException, BlockStoreException, PrunedException { checkState(lock.isLocked()); if (!params.passesCheckpoint(newBlock.getHeight(), newBlock.getHeader().getHash())) throw new VerificationException("Block failed checkpoint lockin at " + newBlock.getHeight()); blockStore.beginDatabaseBatchWrite(); StoredUndoableBlock block = blockStore.getUndoBlock(newBlock.getHeader().getHash()); if (block == null) { // We're trying to re-org too deep and the data needed has been deleted. blockStore.abortDatabaseBatchWrite(); throw new PrunedException(newBlock.getHeader().getHash()); } TransactionOutputChanges txOutChanges; try { List<Transaction> transactions = block.getTransactions(); if (transactions != null) { LinkedList<StoredTransactionOutput> txOutsSpent = new LinkedList<StoredTransactionOutput>(); LinkedList<StoredTransactionOutput> txOutsCreated = new LinkedList<StoredTransactionOutput>(); long sigOps = 0; final boolean enforcePayToScriptHash = newBlock.getHeader().getTimeSeconds() >= NetworkParameters.BIP16_ENFORCE_TIME; if (!params.isCheckpoint(newBlock.getHeight())) { for (Transaction tx : transactions) { Sha256Hash hash = tx.getHash(); if (blockStore.hasUnspentOutputs(hash, tx.getOutputs().size())) throw new VerificationException("Block failed BIP30 test!"); } } BigInteger totalFees = BigInteger.ZERO; BigInteger coinbaseValue = null; if (scriptVerificationExecutor.isShutdown()) scriptVerificationExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); List<Future<VerificationException>> listScriptVerificationResults = new ArrayList<Future<VerificationException>>(transactions.size()); for (final Transaction tx : transactions) { boolean isCoinBase = tx.isCoinBase(); BigInteger valueIn = BigInteger.ZERO; BigInteger valueOut = BigInteger.ZERO; if (!isCoinBase) { for (int index = 0; index < tx.getInputs().size(); index++) { final TransactionInput in = tx.getInputs().get(index); final StoredTransactionOutput prevOut = blockStore.getTransactionOutput( in.getOutpoint().getHash(), in.getOutpoint().getIndex()); if (prevOut == null) throw new VerificationException( "Attempted spend of a non-existent or already spent output!"); if (newBlock.getHeight() - prevOut.getHeight() < params.getSpendableCoinbaseDepth()) throw new VerificationException( "Tried to spend coinbase at depth " + (newBlock.getHeight() - prevOut.getHeight())); valueIn = valueIn.add(prevOut.getValue()); if (enforcePayToScriptHash) { Script script = new Script( params, prevOut.getScriptBytes(), 0, prevOut.getScriptBytes().length); if (script.isPayToScriptHash()) sigOps += Script.getP2SHSigOpCount(in.getScriptBytes()); if (sigOps > Block.MAX_BLOCK_SIGOPS) 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 net.usecredits.credits.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); blockStore.removeUnspentTransactionOutput(prevOut); txOutsSpent.add(prevOut); } } Sha256Hash hash = tx.getHash(); for (TransactionOutput out : tx.getOutputs()) { valueOut = valueOut.add(out.getValue()); StoredTransactionOutput newOut = new StoredTransactionOutput( hash, out.getIndex(), out.getValue(), newBlock.getHeight(), isCoinBase, out.getScriptBytes()); blockStore.addUnspentTransactionOutput(newOut); txOutsCreated.add(newOut); } // All values were already checked for being non-negative (as it is verified in // Transaction.verify()) // but we check again here just for defence in depth. Transactions with zero output value // are OK. if (valueOut.compareTo(BigInteger.ZERO) < 0 || valueOut.compareTo(params.MAX_MONEY) > 0) throw new VerificationException("Transaction output value out of rage"); if (isCoinBase) { coinbaseValue = valueOut; } else { if (valueIn.compareTo(valueOut) < 0 || valueIn.compareTo(params.MAX_MONEY) > 0) throw new VerificationException("Transaction input value out of range"); totalFees = totalFees.add(valueIn.subtract(valueOut)); } } if (totalFees.compareTo(params.MAX_MONEY) > 0 || newBlock .getHeader() .getBlockInflation(newBlock.getHeight()) .add(totalFees) .compareTo(coinbaseValue) < 0) throw new VerificationException("Transaction fees out of range"); txOutChanges = new TransactionOutputChanges(txOutsCreated, txOutsSpent); for (Future<VerificationException> future : listScriptVerificationResults) { VerificationException e; try { e = future.get(); } catch (InterruptedException thrownE) { throw new RuntimeException(thrownE); // Shouldn't happen } catch (ExecutionException thrownE) { log.error("Script.correctlySpends threw a non-normal exception: " + thrownE.getCause()); throw new VerificationException( "Bug in Script.correctlySpends, likely script malformed in some new and interesting way."); } if (e != null) throw e; } } else { txOutChanges = block.getTxOutChanges(); if (!params.isCheckpoint(newBlock.getHeight())) for (StoredTransactionOutput out : txOutChanges.txOutsCreated) { Sha256Hash hash = out.getHash(); if (blockStore.getTransactionOutput(hash, out.getIndex()) != null) throw new VerificationException("Block failed BIP30 test!"); } for (StoredTransactionOutput out : txOutChanges.txOutsCreated) blockStore.addUnspentTransactionOutput(out); for (StoredTransactionOutput out : txOutChanges.txOutsSpent) blockStore.removeUnspentTransactionOutput(out); } } catch (VerificationException e) { scriptVerificationExecutor.shutdownNow(); blockStore.abortDatabaseBatchWrite(); throw e; } catch (BlockStoreException e) { scriptVerificationExecutor.shutdownNow(); blockStore.abortDatabaseBatchWrite(); throw e; } return txOutChanges; }