/** * Process a block against the given state of the chain. * @param BlockInterface $block * @return bool */ public function process(BlockInterface $block) { // Ignore the genesis block $header = $block->getHeader(); $hash = $header->getBlockHash(); if ($hash === $this->genesis->getHeader()->getBlockHash()) { return true; } if ($this->index()->height()->contains($hash)) { return true; } try { // Attempt to add it to the chain $this->add($block); $this->pow->checkHeader($header); $result = true; } catch (BlockPrevNotFound $e) { // If it fails because it doesn't elongate the chain, process it as an orphan. // Result will be determined $result = $this->processOrphan($block); } catch (BlockPowError $e) { $result = false; // Invalid block. } return $result; }
/** * @param ChainState $state * @param BlockInterface $block * @param Headers $headers * @param UtxoIdx $utxoIdx * @return BlockIndex */ public function accept(ChainState $state, BlockInterface $block, Headers $headers, UtxoIdx $utxoIdx) { $bestBlock = $state->getLastBlock(); if ($bestBlock->getHash() !== $block->getHeader()->getPrevBlock()) { throw new \RuntimeException('Blocks:accept() Block does not extend this chain!'); } $index = $headers->accept($state, $block->getHeader()); $this->blockCheck->check($block)->checkContextual($block, $bestBlock); //$view = $utxoIdx->fetchView($state, $block); $view = $this->db->fetchUtxoView($block); $flagP2sh = $this->consensus->scriptVerifyPayToScriptHash($bestBlock->getHeader()->getTimestamp()); $flags = new Flags($flagP2sh ? InterpreterInterface::VERIFY_P2SH : InterpreterInterface::VERIFY_NONE); $nInputs = 0; $nFees = 0; $nSigOps = 0; $txs = $block->getTransactions(); foreach ($block->getTransactions() as $tx) { $nInputs += count($tx->getInputs()); $nSigOps += $this->blockCheck->getLegacySigOps($tx); if ($nSigOps > $this->consensus->getParams()->getMaxBlockSigOps()) { throw new \RuntimeException('Blocks::accept() - too many sigops'); } if (!$tx->isCoinbase()) { if ($flagP2sh) { $nSigOps = $this->blockCheck->getP2shSigOps($view, $tx); if ($nSigOps > $this->consensus->getParams()->getMaxBlockSigOps()) { throw new \RuntimeException('Blocks::accept() - too many sigops'); } } $fee = $this->math->sub($view->getValueIn($this->math, $tx), $tx->getValueOut()); $nFees = $this->math->add($nFees, $fee); $this->blockCheck->checkInputs($view, $tx, $index->getHeight(), $flags); } } $this->blockCheck->checkCoinbaseSubsidy($txs[0], $nFees, $index->getHeight()); $this->db->insertBlock($block); $state->updateLastBlock($index); return $index; }
/** * @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 HeaderChainViewInterface $chainView * @param Headers $headers * @param bool $checkSignatures * @param bool $checkSize * @param bool $checkMerkleRoot * @return BlockIndexInterface */ public function accept(BlockInterface $block, HeaderChainViewInterface $chainView, Headers $headers, $checkSignatures = true, $checkSize = true, $checkMerkleRoot = true) { $hash = $block->getHeader()->getHash(); $index = $headers->accept($hash, $block->getHeader(), true); $outpointSerializer = new CachingOutPointSerializer(); $txSerializer = new CachingTransactionSerializer(new TransactionInputSerializer($outpointSerializer)); $blockSerializer = new CachingBlockSerializer($this->math, new BlockHeaderSerializer(), $txSerializer); $utxoSet = new UtxoSet($this->db, $outpointSerializer); $blockData = $this->prepareBatch($block, $txSerializer, $utxoSet); $this->blockCheck->check($block, $txSerializer, $blockSerializer, $checkSize, $checkMerkleRoot)->checkContextual($block, $chainView->getLastBlock()); $forks = $this->prepareForks($chainView, $index); $this->checkBlockData($block, $blockData, $checkSignatures, $forks->getFlags(), $index->getHeight()); $this->db->transaction(function () use($hash, $block, $blockSerializer, $utxoSet, $blockData) { $this->db->insertBlock($hash, $block, $blockSerializer); $utxoSet->applyBlock($blockData); }); $chainView->blocks()->updateTip($index); $forks->next($index); $this->emit('block', [$index, $block, $blockData]); print_r($outpointSerializer->stats()); return $index; }
/** * @param BlockInterface $block * @return bool * @throws \Exception */ public function insertBlock(BlockInterface $block) { if ($this->debug) { echo "db: called insertBlock \n"; } $blockHash = $block->getHeader()->getHash()->getHex(); try { $this->dbh->beginTransaction(); $txListBind = []; $txListData = ['blockHash' => $blockHash]; // 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 = []; $transactions = $block->getTransactions(); foreach ($transactions as $i => $tx) { $hash = $tx->getTxId()->getHex(); $valueOut = $tx->getValueOut(); $nOut = count($tx->getOutputs()); $nIn = count($tx->getInputs()); $txListBind[] = " ( :blockHash, :tx{$i}) "; $txListData["tx{$i}"] = $hash; $txBind[] = " ( :hash{$i} , :version{$i} , :nLockTime{$i} , :tx{$i} , :nOut{$i} , :nValueOut{$i} , :nFee{$i} , :isCoinbase{$i} ) "; $txData["hash{$i}"] = $hash; $txData["tx{$i}"] = $tx->getBinary(); $txData["nOut{$i}"] = $nOut; $txData["nValueOut{$i}"] = $valueOut; $txData["nFee{$i}"] = '0'; $txData["nLockTime{$i}"] = $tx->getLockTime(); $txData["isCoinbase{$i}"] = $tx->isCoinbase(); $txData["version{$i}"] = $tx->getVersion(); $inData["parenthash{$i}"] = $hash; for ($j = 0; $j < $nIn; $j++) { $input = $tx->getInput($j); $inBind[] = " ( :parenthash{$i} , :nInput{$i}{$j}, :hashPrevOut{$i}{$j}, :nPrevOut{$i}{$j}, :scriptSig{$i}{$j}, :nSequence{$i}{$j} ) "; $inData["nInput{$i}{$j}"] = $j; $inData["hashPrevOut{$i}{$j}"] = $input->getTransactionId(); $inData["nPrevOut{$i}{$j}"] = $input->getVout(); $inData["scriptSig{$i}{$j}"] = $input->getScript()->getBinary(); $inData["nSequence{$i}{$j}"] = $input->getSequence(); } $outData["parenthash{$i}"] = $hash; for ($k = 0; $k < $nOut; $k++) { $output = $tx->getOutput($k); $outBind[] = " ( :parenthash{$i} , :nOutput{$i}{$k}, :value{$i}{$k}, :scriptPubKey{$i}{$k} ) "; $outData["nOutput{$i}{$k}"] = $k; $outData["value{$i}{$k}"] = $output->getValue(); $outData["scriptPubKey{$i}{$k}"] = $output->getScript()->getBinary(); } } // Finish & prepare each statement // Insert the blocks hash $blockInsert = $this->dbh->prepare("INSERT INTO blockIndex ( hash ) VALUES ( :hash )"); $blockInsert->bindValue(':hash', $blockHash); $insertTx = $this->dbh->prepare("INSERT INTO transactions (hash, version, nLockTime, transaction, nOut, valueOut, valueFee, isCoinbase ) VALUES " . implode(", ", $txBind)); unset($txBind); $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); $blockInsert->execute(); $insertTxList->execute($txListData); $insertTx->execute($txData); $insertInputs->execute($inData); $insertOutputs->execute($outData); $this->dbh->commit(); return true; } catch (\Exception $e) { $this->dbh->rollBack(); echo "INSERT FAIL!\n"; echo $e->getMessage() . "\n"; die("this shouldn't happen"); } throw new \RuntimeException('MySqlDb: '); }
/** * @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 BlockInterface $blk * @return string */ private function cacheIndexBlk(BlockInterface $blk) { return $this->cacheIndex($blk->getHeader()->getBlockHash()); }