/** * @param Consensus $consensus * @param EcAdapterInterface $ecAdapter * @param ScriptCheckInterface $scriptCheck */ public function __construct(Consensus $consensus, EcAdapterInterface $ecAdapter, ScriptCheckInterface $scriptCheck) { $this->consensus = $consensus; $this->math = $ecAdapter->getMath(); $this->scriptCheck = $scriptCheck; $this->params = $consensus->getParams(); }
/** * @param ChainAccessInterface $chain * @param BlockIndexInterface $prevIndex * @return int */ public function getWorkRequired(ChainAccessInterface $chain, BlockIndexInterface $prevIndex) { $params = $this->consensus->getParams(); if (($prevIndex->getHeight() + 1) % $params->powRetargetInterval() !== 0) { // No change in difficulty return $prevIndex->getHeader()->getBits(); } // Re-target $heightLastRetarget = $prevIndex->getHeight() - ($params->powRetargetInterval() - 1); $lastTime = $chain->fetchAncestor($heightLastRetarget)->getHeader()->getTimestamp(); return $this->consensus->calculateNextWorkRequired($prevIndex, $lastTime); }
/** * @param UtxoView $view * @param TransactionInterface $tx * @param int $spendHeight * @return $this */ public function checkContextualInputs(UtxoView $view, TransactionInterface $tx, $spendHeight) { $valueIn = gmp_init(0); for ($i = 0, $nInputs = count($tx->getInputs()); $i < $nInputs; $i++) { /*if ($out->isCoinbase()) { // todo: cb / height if ($spendHeight - $out->getHeight() < $this->params->coinbaseMaturityAge()) { return false; } }*/ $value = gmp_init($view->fetchByInput($tx->getInput($i))->getOutput()->getValue(), 10); $valueIn = $this->math->add($valueIn, $value); $this->consensus->checkAmount($valueIn); } $valueOut = gmp_init(0); foreach ($tx->getOutputs() as $output) { $valueOut = $this->math->add($valueOut, gmp_init($output->getValue(), 10)); $this->consensus->checkAmount($valueOut); } if ($this->math->cmp($valueIn, $valueOut) < 0) { throw new \RuntimeException('Value-in is less than value-out'); } $fee = $this->math->sub($valueIn, $valueOut); $this->consensus->checkAmount($fee); return $this; }
/** * @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 BlockHeaderInterface[] $headers * @return HeadersBatch */ public function prepareBatch(array $headers) { $countHeaders = count($headers); if (0 === $countHeaders) { return new HeadersBatch($this->chains->best($this->math), []); } $bestPrev = null; $firstUnknown = null; $hashStorage = new HashStorage(); foreach ($headers as $i => &$head) { if ($this->chains->isKnownHeader($head->getPrevBlock())) { $bestPrev = $head->getPrevBlock(); } $hash = Hash::sha256d($head->getBuffer())->flip(); $hashStorage->attach($head, $hash); if ($firstUnknown === null && !$this->chains->isKnownHeader($hash)) { $firstUnknown = $i; } } if (!$bestPrev instanceof BufferInterface) { throw new \RuntimeException('Headers::accept(): Unknown start header'); } $view = $this->chains->isTip($bestPrev); if ($view === false) { throw new \RuntimeException('Headers::accept(): Unhandled fork'); } $prevIndex = $view->getIndex(); $access = $this->chains->access($view); $batch = []; if ($firstUnknown !== null) { $versionInfo = $this->db->findSuperMajorityInfoByView($view); $forks = new Forks($this->consensus->getParams(), $prevIndex, $versionInfo); for ($i = $firstUnknown; $i < $countHeaders; $i++) { /** * @var BufferInterface $hash * @var BlockHeaderInterface $header */ $header = $headers[$i]; $hash = $hashStorage[$header]; $this->headerCheck->check($hash, $header); $index = $this->getNextIndex($hash, $prevIndex, $header); $forks->next($index); $this->headerCheck->checkContextual($access, $index, $prevIndex, $forks); $batch[] = $index; $prevIndex = $index; } } return new HeadersBatch($view, $batch); }
/** * @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); }