/** * @param BlockIndex $index */ public function add(BlockIndex $index) { if ($index->getHeader()->getPrevBlock() !== $this->getHash($index->getHeight() - 1)->getHex()) { throw new \RuntimeException('ChainCache: New BlockIndex does not refer to last'); } $binary = hex2bin($index->getHash()); $this->hashByHeight[] = $binary; $this->heightByHash[$binary] = $index->getHeight(); }
public function testIsNext() { $first = new BlockIndex(new Buffer('aa', 32), 0, 0, new BlockHeader(0, new Buffer('', 32), new Buffer('', 32), 0, new Buffer(), 0)); $nextGood = new BlockIndex(new Buffer('bb'), 1, 0, new BlockHeader(0, new Buffer('aa', 32), new Buffer('', 32), 0, new Buffer(), 0)); $nextBadHeight = new BlockIndex(Buffer::hex('bc', 32), 222, 0, new BlockHeader(0, new Buffer('aa', 32), new Buffer('', 32), 0, new Buffer(), 0)); $nextBadHash = new BlockIndex(Buffer::hex('bd', 32), 1, 0, new BlockHeader(0, new Buffer('22', 32), new Buffer('', 32), 0, new Buffer(), 0)); $this->assertFalse($first->isNext($nextBadHash)); $this->assertFalse($first->isNext($nextBadHeight)); $this->assertTrue($first->isNext($nextGood)); }
/** * @param BlockIndex $index */ public function updateTip(BlockIndex $index) { if ($this->index->getHash() !== $index->getHeader()->getPrevBlock()) { throw new \RuntimeException('Header: Header does not extend this chain'); } if ($index->getHeight() - 1 != $this->index->getHeight()) { throw new \RuntimeException('Header: Incorrect chain height'); } $this->chainCache->add($index); $this->index = $index; }
/** * @param BlockIndex $prevIndex * @param $timeFirstBlock * @return int|string */ public function calculateNextWorkRequired(BlockIndex $prevIndex, $timeFirstBlock) { $header = $prevIndex->getHeader(); $math = $this->math; $timespan = $math->sub($header->getTimestamp(), $timeFirstBlock); $lowest = $math->div($this->params->powTargetTimespan(), 4); $highest = $math->mul($this->params->powTargetTimespan(), 4); if ($math->cmp($timespan, $lowest) < 0) { $timespan = $lowest; } if ($math->cmp($timespan, $highest) > 0) { $timespan = $highest; } $target = $math->compact()->set($header->getBits()->getInt()); $limit = $this->math->compact()->set($this->params->powBitsLimit()); $new = bcdiv(bcmul($target, $timespan), $this->params->powTargetTimespan()); if ($math->cmp($new, $limit) > 0) { $new = $limit; } return $math->compact()->read($new, false); }
/** * @param Buffer|null $hashStop * @return BlockLocator */ public function getBlockLocator(Buffer $hashStop = null) { echo 'Produce Blocks locator (' . $this->lastBlock->getHeight() . ') ' . PHP_EOL; return $this->getLocator($this->lastBlock->getHeight(), $hashStop); }
/** * @param BlockIndex $prevIndex * @param BlockHeaderInterface $header * @return BlockIndex */ public function makeIndex(BlockIndex $prevIndex, BlockHeaderInterface $header) { return new BlockIndex($header->getHash()->getHex(), $this->math->add($prevIndex->getHeight(), 1), $this->math->add($this->pow->getWork($header->getBits()), $prevIndex->getWork()), $header); }
/** * @param BlockIndex $startIndex * @param BlockIndex[] $index * @return bool * @throws \Exception */ public function insertIndexBatch(BlockIndex $startIndex, array $index) { if ($this->debug) { echo "db: called insertIndexBATCH\n"; } if (null == $this->fetchLftStmt) { $this->fetchLftStmt = $this->dbh->prepare('SELECT lft FROM headerIndex WHERE hash = :prevBlock'); $this->updateIndicesStmt = $this->dbh->prepare(' UPDATE headerIndex SET rgt = rgt + :nTimes2 WHERE rgt > :myLeft ; UPDATE headerIndex SET lft = lft + :nTimes2 WHERE lft > :myLeft ; '); } $fetchParent = $this->fetchLftStmt; $updateIndices = $this->updateIndicesStmt; $fetchParent->bindParam(':prevBlock', $startIndex->getHash()); if ($fetchParent->execute()) { foreach ($fetchParent->fetchAll() as $record) { $myLeft = $record['lft']; } } $fetchParent->closeCursor(); if (!isset($myLeft)) { throw new \RuntimeException('Failed to extract header position'); } $totalN = count($index); $nTimesTwo = 2 * $totalN; $leftOffset = $myLeft; $rightOffset = $myLeft + $nTimesTwo; $this->dbh->beginTransaction(); try { if ($updateIndices->execute(['nTimes2' => $nTimesTwo, 'myLeft' => $myLeft])) { $updateIndices->closeCursor(); $values = []; $query = []; foreach ($index as $c => $i) { $query[] = "(:hash{$c} , :height{$c} , :work{$c} ,\n :version{$c} , :prevBlock{$c} , :merkleRoot{$c} ,\n :nBits{$c} , :nTimestamp{$c} , :nNonce{$c} ,\n :lft{$c} , :rgt{$c} )"; $values['hash' . $c] = $i->getHash(); $values['height' . $c] = $i->getHeight(); $values['work' . $c] = $i->getWork(); $header = $i->getHeader(); $values['version' . $c] = $header->getVersion(); $values['prevBlock' . $c] = $header->getPrevBlock(); $values['merkleRoot' . $c] = $header->getMerkleRoot(); $values['nBits' . $c] = $header->getBits()->getInt(); $values['nTimestamp' . $c] = $header->getTimestamp(); $values['nNonce' . $c] = $header->getNonce(); $values['lft' . $c] = $leftOffset + 1 + $c; $values['rgt' . $c] = $rightOffset - $c; } $stmt = $this->dbh->prepare("\n INSERT INTO headerIndex (hash, height, work, version, prevBlock, merkleRoot, nBits, nTimestamp, nNonce, lft, rgt )\n VALUES " . implode(', ', $query)); $count = $stmt->execute($values); $this->dbh->commit(); if ($count === $totalN) { return true; } else { throw new \RuntimeException('Strange: Failed to update chain!'); } } } catch (\Exception $e) { $this->dbh->rollBack(); throw $e; } throw new \RuntimeException('Failed to update chain!'); }
/** * @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; }