/** * @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 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 BlockInterface $block * @param TransactionSerializerInterface $txSerializer * @param BlockSerializerInterface $blockSerializer * @param bool $checkSize * @param bool $checkMerkleRoot * @return $this * @throws MerkleTreeEmpty */ public function check(BlockInterface $block, TransactionSerializerInterface $txSerializer, BlockSerializerInterface $blockSerializer, $checkSize = true, $checkMerkleRoot = true) { $params = $this->consensus->getParams(); if ($checkMerkleRoot && $this->calcMerkleRoot($block, $txSerializer)->equals($block->getHeader()->getMerkleRoot()) === false) { throw new \RuntimeException('BlockCheck: failed to verify merkle root'); } $txCount = count($block->getTransactions()); if ($checkSize && (0 === $txCount || $blockSerializer->serialize($block)->getSize() > $params->maxBlockSizeBytes())) { throw new \RuntimeException('BlockCheck: Zero transactions, or block exceeds max size'); } // The first transaction is coinbase, and only the first transaction is coinbase. if (!$block->getTransaction(0)->isCoinbase()) { throw new \RuntimeException('BlockCheck: First transaction was not coinbase'); } for ($i = 1; $i < $txCount; $i++) { if ($block->getTransaction($i)->isCoinbase()) { throw new \RuntimeException('BlockCheck: more than one coinbase'); } } $nSigOps = 0; foreach ($block->getTransactions() as $transaction) { $this->checkTransaction($transaction, $txSerializer, $checkSize); $nSigOps += $this->getLegacySigOps($transaction); } if ($nSigOps > $params->getMaxBlockSigOps()) { throw new \RuntimeException('BlockCheck: sigops exceeds maximum allowed'); } return $this; }
/** * @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 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 * @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); }