/** * @param ChainState $state * @param BlockInterface $block * @return UtxoView */ public function fetchView(ChainState $state, BlockInterface $block) { $txs = $block->getTransactions(); $txCount = count($txs); if (1 === $txCount) { return new UtxoView([]); } list($required, $newUnspent) = $this->filter($block); $found = $this->db->fetchUtxos($required, $block->getHeader()->getPrevBlock()); return new UtxoView(array_merge($found, $newUnspent)); }
/** * @param BlockInterface $block * @return \BitWasp\Buffertools\Buffer */ public function serialize(BlockInterface $block) { return Buffertools::concat($this->headerSerializer->serialize($block->getHeader()), $this->getTxsTemplate()->write([$block->getTransactions()->all()])); }
/** * @param BlockInterface $block * @param BlockData $blockData * @param bool $checkSignatures * @param bool $flags * @param $height */ public function checkBlockData(BlockInterface $block, BlockData $blockData, $checkSignatures, $flags, $height) { $validation = new ScriptValidation($checkSignatures, $flags); foreach ($block->getTransactions() as $tx) { $blockData->nSigOps += $this->blockCheck->getLegacySigOps($tx); if ($blockData->nSigOps > $this->consensus->getParams()->getMaxBlockSigOps()) { throw new \RuntimeException('Blocks::accept() - too many sigops'); } if (!$tx->isCoinbase()) { if ($flags & InterpreterInterface::VERIFY_P2SH) { $blockData->nSigOps += $this->blockCheck->getP2shSigOps($blockData->utxoView, $tx); if ($blockData->nSigOps > $this->consensus->getParams()->getMaxBlockSigOps()) { throw new \RuntimeException('Blocks::accept() - too many sigops'); } } $blockData->nFees = $this->math->add($blockData->nFees, $blockData->utxoView->getFeePaid($this->math, $tx)); $this->blockCheck->checkInputs($blockData->utxoView, $tx, $height, $flags, $validation); } } if ($validation->active() && !$validation->result()) { throw new \RuntimeException('ScriptValidation failed!'); } $this->blockCheck->checkCoinbaseSubsidy($block->getTransaction(0), $blockData->nFees, $height); }
/** * @param BlockInterface $block * @return UtxoView */ public function fetchUtxoView(BlockInterface $block) { $txs = $block->getTransactions(); $txCount = count($txs); if (1 == $txCount) { return new UtxoView([]); } list($required, $outputSet) = $this->filterUtxoRequest($block); $requiredCount = count($required); $initialCount = count($outputSet); $joinList = ''; $queryValues = ['hash' => $block->getHeader()->getPrevBlock()]; for ($i = 0, $c = count($required), $last = $c - 1; $i < $c; $i++) { list($txid, $vout, $txidx) = $required[$i]; if (0 == $i) { $joinList .= "SELECT :hashParent{$i} as hashParent, :noutparent{$i} as nOut, :txidx{$i} as txidx\n"; } else { $joinList .= " SELECT :hashParent{$i}, :noutparent{$i}, :txidx{$i} \n"; } if ($i < $last) { $joinList .= " UNION ALL\n"; } $queryValues["hashParent{$i}"] = $txid; $queryValues["noutparent{$i}"] = $vout; $queryValues["txidx{$i}"] = $txidx; } $sql = "\n SELECT listed.hashParent as txid, listed.nOut as vout,\n o.value, o.scriptPubKey,\n allowed_block.height, listed.txidx\n FROM transaction_output o\n INNER JOIN (\n {$joinList}\n ) as listed ON (listed.hashParent = o.parent_tx AND listed.nOut = o.nOutput)\n INNER JOIN block_transactions as bt on listed.hashParent = bt.transaction_hash\n JOIN (\n SELECT parent.hash, parent.height\n FROM headerIndex AS tip,\n headerIndex AS parent\n WHERE tip.hash = :hash AND tip.lft BETWEEN parent.lft AND parent.rgt\n ) as allowed_block on bt.block_hash = allowed_block.hash\n "; $stmt = $this->dbh->prepare($sql); $stmt->execute($queryValues); foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $utxo) { $outputSet[] = new Utxo($utxo['txid'], $utxo['vout'], new TransactionOutput($utxo['value'], new Script(new Buffer($utxo['scriptPubKey'])))); } if (count($outputSet) !== $initialCount + $requiredCount) { throw new \RuntimeException('Utxo was not found'); } echo "Fetched {$requiredCount} of " . ($initialCount + $requiredCount) . "\n"; return new UtxoView($outputSet); }
/** * @param BlockInterface $block * @param BlockIndex $prevBlockIndex * @return bool */ public function checkContextual(BlockInterface $block, BlockIndex $prevBlockIndex) { $newHeight = $prevBlockIndex->getHeight() + 1; $newTime = $block->getHeader()->getTimestamp(); foreach ($block->getTransactions() as $transaction) { if (!$this->checkTransactionIsFinal($transaction, $newHeight, $newTime)) { throw new \RuntimeException('Block contains a non-final transaction'); } } return $this; }
/** * @param BlockIndexInterface $index * @param BlockInterface $block * @param BlockData $blockData */ public function logBlock(BlockIndexInterface $index, BlockInterface $block, BlockData $blockData) { echo count($blockData->remainingNew) . "created and " . count($blockData->requiredOutpoints) . " destroyed\n"; $this->log('block', ['hash' => $index->getHash()->getHex(), 'height' => $index->getHeight(), 'txs' => count($block->getTransactions()), 'nFees' => gmp_strval($blockData->nFees, 10), 'nSigOps' => $blockData->nSigOps, 'utxos' => ['created' => count($blockData->remainingNew), 'removed' => count($blockData->requiredOutpoints)]]); }
/** * @param int $blockId * @param BlockInterface $block * @param HashStorage $hashStorage * @return bool */ public function insertBlockTransactions($blockId, BlockInterface $block, HashStorage $hashStorage) { $txListBind = []; $txListData = []; $temp = []; // Prepare SQL statement adding all transaction inputs in this block. $inBind = []; $inData = []; // Prepare SQL statement adding all transaction outputs in this block $outBind = []; $outData = []; // Add all transactions in the block $txBind = []; $txData = []; /** @var BufferInterface $txHash */ $transactions = $block->getTransactions(); foreach ($transactions as $i => $tx) { $txHash = $hashStorage[$tx]; $hash = $txHash->getBinary(); $temp[$i] = $hash; $valueOut = $tx->getValueOut(); $nOut = count($tx->getOutputs()); $nIn = count($tx->getInputs()); $txListBind[] = " ( :headerId, :txId{$i}) "; $txBind[] = " ( :hash{$i} , :version{$i} , :nLockTime{$i} , :nOut{$i} , :nValueOut{$i} , :nFee{$i} , :isCoinbase{$i} ) "; $txData["hash{$i}"] = $hash; $txData["nOut{$i}"] = $nOut; $txData["nValueOut{$i}"] = $valueOut; $txData["nFee{$i}"] = '0'; $txData["nLockTime{$i}"] = $tx->getLockTime(); $txData["isCoinbase{$i}"] = (int) $tx->isCoinbase(); $txData["version{$i}"] = $tx->getVersion(); for ($j = 0; $j < $nIn; $j++) { $input = $tx->getInput($j); $inBind[] = " ( :parentId{$i} , :nInput" . $i . "n" . $j . ", :hashPrevOut" . $i . "n" . $j . ", :nPrevOut" . $i . "n" . $j . ", :scriptSig" . $i . "n" . $j . ", :nSequence" . $i . "n" . $j . " ) "; $outpoint = $input->getOutPoint(); $inData["hashPrevOut" . $i . "n" . $j] = $outpoint->getTxId()->getBinary(); $inData["nPrevOut" . $i . "n" . $j] = (int) $outpoint->getVout(); $inData["scriptSig" . $i . "n" . $j] = $input->getScript()->getBinary(); $inData["nSequence" . $i . "n" . $j] = $input->getSequence(); $inData["nInput" . $i . "n" . $j] = $j; } for ($k = 0; $k < $nOut; $k++) { $output = $tx->getOutput($k); $outBind[] = " ( :parentId{$i} , :nOutput" . $i . "n" . $k . ", :value" . $i . "n" . $k . ", :scriptPubKey" . $i . "n" . $k . " ) "; $outData["value" . $i . "n" . $k] = $output->getValue(); $outData["scriptPubKey" . $i . "n" . $k] = $output->getScript()->getBinary(); $outData["nOutput" . $i . "n" . $k] = $k; } } $insertTx = $this->dbh->prepare('INSERT INTO transactions (hash, version, nLockTime, nOut, valueOut, valueFee, isCoinbase ) VALUES ' . implode(', ', $txBind)); $insertTx->execute($txData); unset($txBind); // Populate inserts $txListData['headerId'] = $blockId; $lastId = (int) $this->dbh->lastInsertId(); foreach ($temp as $i => $hash) { $rowId = $i + $lastId; $val = $rowId; $outData["parentId{$i}"] = $val; $inData["parentId{$i}"] = $val; $txListData["txId{$i}"] = $val; } unset($val); $insertTxList = $this->dbh->prepare('INSERT INTO block_transactions (block_hash, transaction_hash) VALUES ' . implode(', ', $txListBind)); unset($txListBind); $insertInputs = $this->dbh->prepare('INSERT INTO transaction_input (parent_tx, nInput, hashPrevOut, nPrevOut, scriptSig, nSequence) VALUES ' . implode(', ', $inBind)); unset($inBind); $insertOutputs = $this->dbh->prepare('INSERT INTO transaction_output (parent_tx, nOutput, value, scriptPubKey) VALUES ' . implode(', ', $outBind)); unset($outBind); $insertTxList->execute($txListData); $insertInputs->execute($inData); $insertOutputs->execute($outData); return true; }