From c3d785deb6a2292137872158d9dc9bc598810fea Mon Sep 17 00:00:00 2001 From: Daniel Graczer Date: Wed, 10 Jul 2024 15:00:52 +0700 Subject: [PATCH 1/3] feat: added original summa full relay --- src/relay/FullRelay.sol | 467 ++++++++++++++++++++++++++++++ src/relay/FullRelayWithVerify.sol | 360 +++++++++++++++++++++++ 2 files changed, 827 insertions(+) create mode 100644 src/relay/FullRelay.sol create mode 100644 src/relay/FullRelayWithVerify.sol diff --git a/src/relay/FullRelay.sol b/src/relay/FullRelay.sol new file mode 100644 index 00000000..716664fb --- /dev/null +++ b/src/relay/FullRelay.sol @@ -0,0 +1,467 @@ +pragma solidity ^0.5.10; + +/** + * @title Relay + */ +/** + * @author Summa (https://summa.one) + */ +import {SafeMath} from "@summa-tx/bitcoin-spv-sol/contracts/SafeMath.sol"; +import {TypedMemView} from "@summa-tx/bitcoin-spv-sol/contracts/TypedMemView.sol"; +import {ViewBTC} from "@summa-tx/bitcoin-spv-sol/contracts/ViewBTC.sol"; +import {ViewSPV} from "@summa-tx/bitcoin-spv-sol/contracts/ViewSPV.sol"; +import {IRelay} from "./Interfaces.sol"; + +import "truffle/console.sol"; + +contract Relay is IRelay { + using SafeMath for uint256; + using TypedMemView for bytes; + using TypedMemView for bytes29; + using ViewBTC for bytes29; + using ViewSPV for bytes29; + + /* using BytesLib for bytes; + using BTCUtils for bytes; + using ValidateSPV for bytes; */ + + // How often do we store the height? + // A higher number incurs less storage cost, but more lookup cost + uint32 public constant HEIGHT_INTERVAL = 4; + + bytes32 internal relayGenesis; + bytes32 internal bestKnownDigest; + bytes32 internal lastReorgCommonAncestor; + mapping(bytes32 => bytes32) internal previousBlock; + mapping(bytes32 => uint256) internal blockHeight; + + uint256 internal currentEpochDiff; + uint256 internal prevEpochDiff; + + /// @notice Gives a starting point for the relay + /// @dev We don't check this AT ALL really. Don't use relays with bad genesis + /// @param _genesisHeader The starting header + /// @param _height The starting height + /// @param _periodStart The hash of the first header in the genesis epoch + constructor(bytes memory _genesisHeader, uint256 _height, bytes32 _periodStart) public { + bytes29 _genesisView = _genesisHeader.ref(0).tryAsHeader(); + require(_genesisView.notNull(), "Stop being dumb"); + bytes32 _genesisDigest = _genesisView.hash256(); + + require( + _periodStart & bytes32(0x0000000000000000000000000000000000000000000000000000000000ffffff) == bytes32(0), + "Period start hash does not have work. Hint: wrong byte order?" + ); + + relayGenesis = _genesisDigest; + bestKnownDigest = _genesisDigest; + lastReorgCommonAncestor = _genesisDigest; + blockHeight[_genesisDigest] = _height; + blockHeight[_periodStart] = _height.sub(_height % 2016); + currentEpochDiff = _genesisView.diff(); + } + + /// @notice Getter for currentEpochDiff + /// @dev This is updated when a new heavist header has a new diff + /// @return The difficulty of the bestKnownDigest + function getCurrentEpochDifficulty() external view returns (uint256) { + return currentEpochDiff; + } + /// @notice Getter for prevEpochDiff + /// @dev This is updated when a difficulty change is accepted + /// @return The difficulty of the previous epoch + + function getPrevEpochDifficulty() external view returns (uint256) { + return prevEpochDiff; + } + + /// @notice Getter for relayGenesis + /// @dev This is an initialization parameter + /// @return The hash of the first block of the relay + function getRelayGenesis() public view returns (bytes32) { + return relayGenesis; + } + + /// @notice Getter for bestKnownDigest + /// @dev This updated only by calling markNewHeaviest + /// @return The hash of the best marked chain tip + function getBestKnownDigest() public view returns (bytes32) { + return bestKnownDigest; + } + + /// @notice Getter for relayGenesis + /// @dev This is updated only by calling markNewHeaviest + /// @return The hash of the shared ancestor of the most recent fork + function getLastReorgCommonAncestor() public view returns (bytes32) { + return lastReorgCommonAncestor; + } + + /// @notice Finds the height of a header by its digest + /// @dev Will fail if the header is unknown + /// @param _digest The header digest to search for + /// @return The height of the header, or error if unknown + function findHeight(bytes32 _digest) external view returns (uint256) { + return _findHeight(_digest); + } + + /// @notice Finds an ancestor for a block by its digest + /// @dev Will fail if the header is unknown + /// @param _digest The header digest to search for + /// @return The height of the header, or error if unknown + function findAncestor(bytes32 _digest, uint256 _offset) external view returns (bytes32) { + return _findAncestor(_digest, _offset); + } + + /// @notice Checks if a digest is an ancestor of the current one + /// @dev Limit the amount of lookups (and thus gas usage) with _limit + /// @param _ancestor The prospective ancestor + /// @param _descendant The descendant to check + /// @param _limit The maximum number of blocks to check + /// @return true if ancestor is at most limit blocks lower than descendant, otherwise false + function isAncestor(bytes32 _ancestor, bytes32 _descendant, uint256 _limit) external view returns (bool) { + return _isAncestor(_ancestor, _descendant, _limit); + } + + /// @notice Adds headers to storage after validating + /// @dev We check integrity and consistency of the header chain + /// @param _anchor The header immediately preceeding the new chain + /// @param _headers A tightly-packed list of 80-byte Bitcoin headers + /// @return True if successfully written, error otherwise + function addHeaders(bytes calldata _anchor, bytes calldata _headers) external returns (bool) { + bytes29 _headersView = _headers.ref(0).tryAsHeaderArray(); + bytes29 _anchorView = _anchor.ref(0).tryAsHeader(); + + require(_headersView.notNull(), "Header array length must be divisible by 80"); + require(_anchorView.notNull(), "Anchor must be 80 bytes"); + + return _addHeaders(_anchorView, _headersView, false); + } + + /// @notice Adds headers to storage, performs additional validation of retarget + /// @dev Checks the retarget, the heights, and the linkage + /// @param _oldPeriodStartHeader The first header in the difficulty period being closed + /// @param _oldPeriodEndHeader The last header in the difficulty period being closed + /// @param _headers A tightly-packed list of 80-byte Bitcoin headers + /// @return True if successfully written, error otherwise + function addHeadersWithRetarget( + bytes calldata _oldPeriodStartHeader, + bytes calldata _oldPeriodEndHeader, + bytes calldata _headers + ) external returns (bool) { + bytes29 _oldStart = _oldPeriodStartHeader.ref(0).tryAsHeader(); + bytes29 _oldEnd = _oldPeriodEndHeader.ref(0).tryAsHeader(); + bytes29 _headersView = _headers.ref(0).tryAsHeaderArray(); + + require( + _oldStart.notNull() && _oldEnd.notNull() && _headersView.notNull(), + "Bad args. Check header and array byte lengths." + ); + + return _addHeadersWithRetarget(_oldStart, _oldEnd, _headersView); + } + + /// @notice Gives a starting point for the relay + /// @dev We don't check this AT ALL really. Don't use relays with bad genesis + /// @param _ancestor The digest of the most recent common ancestor + /// @param _currentBest The 80-byte header referenced by bestKnownDigest + /// @param _newBest The 80-byte header to mark as the new best + /// @param _limit Limit the amount of traversal of the chain + /// @return True if successfully updates bestKnownDigest, error otherwise + function markNewHeaviest(bytes32 _ancestor, bytes calldata _currentBest, bytes calldata _newBest, uint256 _limit) + external + returns (bool) + { + bytes29 _new = _newBest.ref(0).tryAsHeader(); + bytes29 _current = _currentBest.ref(0).tryAsHeader(); + require(_new.notNull() && _current.notNull(), "Bad args. Check header and array byte lengths."); + return _markNewHeaviest(_ancestor, _current, _new, _limit); + } + + /// @notice Adds headers to storage after validating + /// @dev We check integrity and consistency of the header chain + /// @param _anchor The header immediately preceeding the new chain + /// @param _headers A tightly-packed list of new 80-byte Bitcoin headers to record + /// @param _internal True if called internally from addHeadersWithRetarget, false otherwise + /// @return True if successfully written, error otherwise + function _addHeaders(bytes29 _anchor, bytes29 _headers, bool _internal) internal returns (bool) { + /// Extract basic info + bytes32 _previousDigest = _anchor.hash256(); + uint256 _anchorHeight = _findHeight(_previousDigest); /* NB: errors if unknown */ + uint256 _target = _headers.indexHeaderArray(0).target(); + + require(_internal || _anchor.target() == _target, "Unexpected retarget on external call"); + + /* + NB: + 1. check that the header has sufficient work + 2. check that headers are in a coherent chain (no retargets, hash links good) + 3. Store the block connection + 4. Store the height + */ + uint256 _height; + bytes32 _currentDigest; + for (uint256 i = 0; i < _headers.len() / 80; i += 1) { + bytes29 _header = _headers.indexHeaderArray(i); + _height = _anchorHeight.add(i + 1); + _currentDigest = _header.hash256(); + + /* + NB: + if the block is already authenticated, we don't need to a work check + Or write anything to state. This saves gas + */ + if (previousBlock[_currentDigest] == bytes32(0)) { + require(TypedMemView.reverseUint256(uint256(_currentDigest)) <= _target, "Header work is insufficient"); + previousBlock[_currentDigest] = _previousDigest; + if (_height % HEIGHT_INTERVAL == 0) { + /* + NB: We store the height only every 4th header to save gas + */ + blockHeight[_currentDigest] = _height; + } + } + + /* NB: we do still need to make chain level checks tho */ + require(_header.target() == _target, "Target changed unexpectedly"); + require(_header.checkParent(_previousDigest), "Headers do not form a consistent chain"); + + _previousDigest = _currentDigest; + } + + emit Extension(_anchor.hash256(), _currentDigest); + return true; + } + + /// @notice Adds headers to storage, performs additional validation of retarget + /// @dev Checks the retarget, the heights, and the linkage + /// @param _oldStart The first header in the difficulty period being closed + /// @param _oldEnd The last header in the difficulty period being closed + /// @param _headers A tightly-packed list of 80-byte Bitcoin headers + /// @return True if successfully written, error otherwise + function _addHeadersWithRetarget(bytes29 _oldStart, bytes29 _oldEnd, bytes29 _headers) internal returns (bool) { + /* NB: requires that both blocks are known */ + uint256 _startHeight = _findHeight(_oldStart.hash256()); + uint256 _endHeight = _findHeight(_oldEnd.hash256()); + + /* NB: retargets should happen at 2016 block intervals */ + require(_endHeight % 2016 == 2015, "Must provide the last header of the closing difficulty period"); + require(_endHeight == _startHeight.add(2015), "Must provide exactly 1 difficulty period"); + require(_oldStart.diff() == _oldEnd.diff(), "Period header difficulties do not match"); + + /* NB: This comparison looks weird because header nBits encoding truncates targets */ + bytes29 _newStart = _headers.indexHeaderArray(0); + uint256 _actualTarget = _newStart.target(); + uint256 _expectedTarget = ViewBTC.retargetAlgorithm(_oldStart.target(), _oldStart.time(), _oldEnd.time()); + require((_actualTarget & _expectedTarget) == _actualTarget, "Invalid retarget provided"); + + // If the current known prevEpochDiff doesn't match, and this old period is near the chaintip/ + // update the stored prevEpochDiff + // Don't update if this is a deep past epoch + uint256 _oldDiff = _oldStart.diff(); + if (prevEpochDiff != _oldDiff && _endHeight > _findHeight(bestKnownDigest).sub(2016)) { + prevEpochDiff = _oldDiff; + } + + // Pass all but the first through to be added + return _addHeaders(_oldEnd, _headers, true); + } + + /// @notice Finds the height of a header by its digest + /// @dev Will fail if the header is unknown + /// @param _digest The header digest to search for + /// @return The height of the header + function _findHeight(bytes32 _digest) internal view returns (uint256) { + uint256 _height = 0; + bytes32 _current = _digest; + for (uint256 i = 0; i < HEIGHT_INTERVAL + 1; i = i.add(1)) { + _height = blockHeight[_current]; + if (_height == 0) { + _current = previousBlock[_current]; + } else { + return _height.add(i); + } + } + revert("Unknown block"); + } + + /// @notice Finds an ancestor for a block by its digest + /// @dev Will fail if the header is unknown + /// @param _digest The header digest to search for + /// @return The height of the header, or error if unknown + function _findAncestor(bytes32 _digest, uint256 _offset) internal view returns (bytes32) { + bytes32 _current = _digest; + for (uint256 i = 0; i < _offset; i = i.add(1)) { + _current = previousBlock[_current]; + } + require(_current != bytes32(0), "Unknown ancestor"); + return _current; + } + + /// @notice Checks if a digest is an ancestor of the current one + /// @dev Limit the amount of lookups (and thus gas usage) with _limit + /// @param _ancestor The prospective ancestor + /// @param _descendant The descendant to check + /// @param _limit The maximum number of blocks to check + /// @return true if ancestor is at most limit blocks lower than descendant, otherwise false + function _isAncestor(bytes32 _ancestor, bytes32 _descendant, uint256 _limit) internal view returns (bool) { + console.log("called"); + bytes32 _current = _descendant; + /* NB: 200 gas/read, so gas is capped at ~200 * limit */ + for (uint256 i = 0; i < _limit; i = i.add(1)) { + console.log(i); + if (_current == _ancestor) { + console.log("found"); + return true; + } + _current = previousBlock[_current]; + } + return false; + } + + /// @notice Marks the new best-known chain tip + /// @param _ancestor The digest of the most recent common ancestor + /// @param _current The 80-byte header referenced by bestKnownDigest + /// @param _new The 80-byte header to mark as the new best + /// @param _limit Limit the amount of traversal of the chain + /// @return True if successfully updates bestKnownDigest, error otherwise + function _markNewHeaviest( + bytes32 _ancestor, + bytes29 _current, // Header + bytes29 _new, // Header + uint256 _limit + ) internal returns (bool) { + require(_limit <= 2016, "Requested limit is greater than 1 difficulty period"); + + bytes32 _newBestDigest = _new.hash256(); + bytes32 _currentBestDigest = _current.hash256(); + require(_currentBestDigest == bestKnownDigest, "Passed in best is not best known"); + require(previousBlock[_newBestDigest] != bytes32(0), "New best is unknown"); + require( + _isMostRecentAncestor(_ancestor, bestKnownDigest, _newBestDigest, _limit), + "Ancestor must be heaviest common ancestor" + ); + require( + _heaviestFromAncestor(_ancestor, _current, _new) == _newBestDigest, + "New best hash does not have more work than previous" + ); + + bestKnownDigest = _newBestDigest; + lastReorgCommonAncestor = _ancestor; + + uint256 _newDiff = _new.diff(); + if (_newDiff != currentEpochDiff) { + currentEpochDiff = _newDiff; + } + + emit NewTip(_currentBestDigest, _newBestDigest, _ancestor); + return true; + } + + /// @notice Checks if a digest is an ancestor of the current one + /// @dev Limit the amount of lookups (and thus gas usage) with _limit + /// @param _ancestor The prospective shared ancestor + /// @param _left A chain tip + /// @param _right A chain tip + /// @param _limit The maximum number of blocks to check + /// @return true if it is the most recent common ancestor within _limit, false otherwise + function _isMostRecentAncestor(bytes32 _ancestor, bytes32 _left, bytes32 _right, uint256 _limit) + internal + view + returns (bool) + { + /* NB: sure why not */ + if (_ancestor == _left && _ancestor == _right) { + return true; + } + + bytes32 _leftCurrent = _left; + bytes32 _rightCurrent = _right; + bytes32 _leftPrev = _left; + bytes32 _rightPrev = _right; + + for (uint256 i = 0; i < _limit; i = i.add(1)) { + if (_leftPrev != _ancestor) { + _leftCurrent = _leftPrev; // cheap + _leftPrev = previousBlock[_leftPrev]; // expensive + } + if (_rightPrev != _ancestor) { + _rightCurrent = _rightPrev; // cheap + _rightPrev = previousBlock[_rightPrev]; // expensive + } + } + if (_leftCurrent == _rightCurrent) return false; /* NB: If the same, they're a nearer ancestor */ + if (_leftPrev != _rightPrev) return false; /* NB: Both must be ancestor */ + return true; + } + + /// @notice Decides which header is heaviest from the ancestor + /// @dev Does not support reorgs above 2017 blocks (: + /// @param _ancestor The prospective shared ancestor + /// @param _left A chain tip + /// @param _right A chain tip + /// @return true if it is the most recent common ancestor within _limit, false otherwise + function _heaviestFromAncestor(bytes32 _ancestor, bytes29 _left, bytes29 _right) internal view returns (bytes32) { + uint256 _ancestorHeight = _findHeight(_ancestor); + uint256 _leftHeight = _findHeight(_left.hash256()); + uint256 _rightHeight = _findHeight(_right.hash256()); + + require( + _leftHeight >= _ancestorHeight && _rightHeight >= _ancestorHeight, + "A descendant height is below the ancestor height" + ); + + /* NB: we can shortcut if one block is in a new difficulty window and the other isn't */ + uint256 _nextPeriodStartHeight = _ancestorHeight.add(2016).sub(_ancestorHeight % 2016); + bool _leftInPeriod = _leftHeight < _nextPeriodStartHeight; + bool _rightInPeriod = _rightHeight < _nextPeriodStartHeight; + + /* + NB: + 1. Left is in a new window, right is in the old window. Left is heavier + 2. Right is in a new window, left is in the old window. Right is heavier + 3. Both are in the same window, choose the higher one + 4. They're in different new windows. Choose the heavier one + */ + if (!_leftInPeriod && _rightInPeriod) return _left.hash256(); + if (_leftInPeriod && !_rightInPeriod) return _right.hash256(); + if (_leftInPeriod && _rightInPeriod) { + return _leftHeight >= _rightHeight ? _left.hash256() : _right.hash256(); + } else { + // if (!_leftInPeriod && !_rightInPeriod) { + if (((_leftHeight % 2016).mul(_left.diff())) < (_rightHeight % 2016).mul(_right.diff())) { + return _right.hash256(); + } else { + return _left.hash256(); + } + } + } +} + +// For unittests +contract TestRelay is Relay { + /// @notice Gives a starting point for the relay + /// @dev We don't check this AT ALL really. Don't use relays with bad genesis + /// @param _genesisHeader The starting header + /// @param _height The starting height + /// @param _periodStart The hash of the first header in the genesis epoch + constructor(bytes memory _genesisHeader, uint256 _height, bytes32 _periodStart) + public + Relay(_genesisHeader, _height, _periodStart) + {} + + function heaviestFromAncestor(bytes32 _ancestor, bytes calldata _left, bytes calldata _right) + external + view + returns (bytes32) + { + return _heaviestFromAncestor(_ancestor, _left.ref(0).tryAsHeader(), _right.ref(0).tryAsHeader()); + } + + function isMostRecentAncestor(bytes32 _ancestor, bytes32 _left, bytes32 _right, uint256 _limit) + external + view + returns (bool) + { + return _isMostRecentAncestor(_ancestor, _left, _right, _limit); + } +} diff --git a/src/relay/FullRelayWithVerify.sol b/src/relay/FullRelayWithVerify.sol new file mode 100644 index 00000000..b390bcea --- /dev/null +++ b/src/relay/FullRelayWithVerify.sol @@ -0,0 +1,360 @@ +pragma solidity ^0.5.10; + +/** + * @title OnDemandSPV + */ +/** + * @author Summa (https://summa.one) + */ +import {Relay} from "./Relay.sol"; +import {ISPVRequestManager, ISPVConsumer} from "./Interfaces.sol"; + +import {TypedMemView} from "@summa-tx/bitcoin-spv-sol/contracts/TypedMemView.sol"; +import {ViewBTC} from "@summa-tx/bitcoin-spv-sol/contracts/ViewBTC.sol"; +import {ViewSPV} from "@summa-tx/bitcoin-spv-sol/contracts/ViewSPV.sol"; +import {SafeMath} from "@summa-tx/bitcoin-spv-sol/contracts/SafeMath.sol"; +import "truffle/console.sol"; + +contract OnDemandSPV is ISPVRequestManager, Relay { + using SafeMath for uint256; + using TypedMemView for bytes; + using TypedMemView for bytes29; + using ViewBTC for bytes29; + using ViewSPV for bytes29; + + struct ProofRequest { + bytes32 spends; + bytes32 pays; + uint256 notBefore; + address consumer; + uint64 paysValue; + uint8 numConfs; + address owner; + RequestStates state; + } + + enum RequestStates { + NONE, + ACTIVE, + CLOSED + } + + mapping(bytes32 => bool) internal validatedTxns; // authenticated tx store + mapping(uint256 => ProofRequest) internal requests; // request info + uint256 public constant BASE_COST = 24 * 60 * 60; // 1 day + + uint256 public nextID; + bytes32 public latestValidatedTx; + uint256 public remoteGasAllowance = 500000; // maximum gas for callback call + + /// @notice Gives a starting point for the relay + /// @dev We don't check this AT ALL really. Don't use relays with bad genesis + /// @param _genesisHeader The starting header + /// @param _height The starting height + /// @param _periodStart The hash of the first header in the genesis epoch + constructor(bytes memory _genesisHeader, uint256 _height, bytes32 _periodStart, uint256 _firstID) + public + Relay(_genesisHeader, _height, _periodStart) + { + nextID = _firstID; + } + + /// @notice Cancel a bitcoin event request. + /// @dev Prevents the relay from forwarding tx infromation + /// @param _requestID The ID of the request to be cancelled + /// @return True if succesful, error otherwise + function cancelRequest(uint256 _requestID) external returns (bool) { + ProofRequest storage _req = requests[_requestID]; + require(_req.state == RequestStates.ACTIVE, "Request not active"); + require(msg.sender == _req.consumer || msg.sender == _req.owner, "Can only be cancelled by owner or consumer"); + _req.state = RequestStates.CLOSED; + emit RequestClosed(_requestID); + return true; + } + + function getLatestValidatedTx() external view returns (bytes32) { + return latestValidatedTx; + } + + /// @notice Retrieve info about a request + /// @dev Requests ids are numerical + /// @param _requestID The numerical ID of the request + /// @return A tuple representation of the request struct + function getRequest(uint256 _requestID) + external + view + returns ( + bytes32 spends, + bytes32 pays, + uint64 paysValue, + uint8 state, + address consumer, + address owner, + uint8 numConfs, + uint256 notBefore + ) + { + ProofRequest storage _req = requests[_requestID]; + spends = _req.spends; + pays = _req.pays; + paysValue = _req.paysValue; + state = uint8(_req.state); + consumer = _req.consumer; + owner = _req.owner; + numConfs = _req.numConfs; + notBefore = _req.notBefore; + } + + /// @notice Subscribe to a feed of Bitcoin txns matching a request + /// @dev The request can be a spent utxo and/or a created utxo + /// @param _spends An outpoint that must be spent in acceptable txns (optional) + /// @param _pays An output script that must be paid in acceptable txns (optional) + /// @param _paysValue A minimum value that must be paid to the output script (optional) + /// @param _consumer The address of a ISPVConsumer exposing spv + /// @param _numConfs The minimum number of Bitcoin confirmations to accept + /// @param _notBefore A timestamp before which proofs are not accepted + /// @return A unique request ID. + function request( + bytes calldata _spends, + bytes calldata _pays, + uint64 _paysValue, + address _consumer, + uint8 _numConfs, + uint256 _notBefore + ) external returns (uint256) { + return _request(_spends, _pays, _paysValue, _consumer, _numConfs, _notBefore); + } + + /// @notice Subscribe to a feed of Bitcoin txns matching a request + /// @dev The request can be a spent utxo and/or a created utxo + /// @param _spendsBytes An outpoint that must be spent in acceptable txns (optional) + /// @param _paysBytes An output script that must be paid in acceptable txns (optional) + /// @param _paysValue A minimum value that must be paid to the output script (optional) + /// @param _consumer The address of a ISPVConsumer exposing spv + /// @param _numConfs The minimum number of Bitcoin confirmations to accept + /// @param _notBefore A timestamp before which proofs are not accepted + /// @return A unique request ID + function _request( + bytes memory _spendsBytes, + bytes memory _paysBytes, + uint64 _paysValue, + address _consumer, + uint8 _numConfs, + uint256 _notBefore + ) internal returns (uint256) { + bytes29 _maybePays = _paysBytes.ref(0).tryAsSPK(); + bytes29 _maybeSpends = _spendsBytes.ref(0).castTo(uint40(ViewBTC.BTCTypes.Outpoint)); + + uint256 _requestID = nextID; + nextID = nextID + 1; + ProofRequest storage _req = requests[_requestID]; + _req.owner = msg.sender; + + // First add critical qualities + if (_maybeSpends.len() > 0) { + require(_maybeSpends.len() == 36, "Not a valid UTXO"); + _req.spends = _maybeSpends.keccak(); + } + if (_maybePays.isValid()) { + require( + _maybePays.payload().notNull() // standard output OR + || _maybePays.opReturnPayload().notNull(), // OP_RETURN output + "Not a standard output type" + ); + _req.pays = _maybePays.keccak(); + } + require(_req.spends != bytes32(0) || _req.pays != bytes32(0), "No request specified"); + + // Then fill in request details + if (_paysValue > 0) { + _req.paysValue = _paysValue; + } + if (_numConfs > 0 && _numConfs < 241) { + //241 is arbitray. 40 hours + _req.numConfs = _numConfs; + } + if (_notBefore > 0) { + _req.notBefore = _notBefore; + } + _req.consumer = _consumer; + _req.state = RequestStates.ACTIVE; + + emit NewProofRequest(msg.sender, _requestID, _paysValue, _spendsBytes, _paysBytes); + + return _requestID; + } + + /// @notice Provide a proof of a tx that satisfies some request + /// @dev The caller must specify which inputs, which outputs, and which request + /// @param _header The header containing the merkleroot committing to the tx + /// @param _proof The merkle proof intermediate nodes + /// @param _version The tx version, always the first 4 bytes of the tx + /// @param _locktime The tx locktime, always the last 4 bytes of the tx + /// @param _index The index of the tx in the merkle tree's leaves + /// @param _reqIndices The input and output index to check against the request, packed + /// @param _vin The tx input vector + /// @param _vout The tx output vector + /// @param _requestID The id of the request that has been triggered + /// @return True if succesful, error otherwise + function provideProof( + bytes calldata _header, + bytes calldata _proof, + bytes4 _version, + bytes4 _locktime, + uint256 _index, + uint16 _reqIndices, + bytes calldata _vin, + bytes calldata _vout, + uint256 _requestID + ) external returns (bool) { + return _provideProof(_header, _proof, _version, _locktime, _index, _reqIndices, _vin, _vout, _requestID); + } + + function _provideProof( + bytes memory _header, + bytes memory _proof, + bytes4 _version, + bytes4 _locktime, + uint256 _index, + uint16 _reqIndices, + bytes memory _vin, + bytes memory _vout, + uint256 _requestID + ) internal returns (bool) { + bytes32 _txid = abi.encodePacked(_version, _vin, _vout, _locktime).ref(0).hash256(); + /* + NB: This shortcuts validation of any txn we've seen before. + Repeats can omit header, proof, and index + */ + if (!validatedTxns[_txid]) { + _checkInclusion( + _header.ref(0).tryAsHeader().assertValid(), + _proof.ref(0).tryAsMerkleArray().assertValid(), + _index, + _txid, + _requestID + ); + validatedTxns[_txid] = true; + latestValidatedTx = _txid; + } + _checkRequests(_reqIndices, _vin, _vout, _requestID); + _callCallback(_txid, _reqIndices, _vin, _vout, _requestID); + return true; + } + + /// @notice Notify a consumer that one of its requests has been triggered + /// @dev We include information about the tx that triggered it, so the consumer can take actions + /// @param _vin The tx input vector + /// @param _vout The tx output vector + /// @param _txid The transaction ID + /// @param _requestID The id of the request that has been triggered + function _callCallback(bytes32 _txid, uint16 _reqIndices, bytes memory _vin, bytes memory _vout, uint256 _requestID) + internal + returns (bool) + { + ProofRequest storage _req = requests[_requestID]; + ISPVConsumer c = ISPVConsumer(_req.consumer); + + uint8 _inputIndex = uint8(_reqIndices >> 8); + uint8 _outputIndex = uint8(_reqIndices & 0xff); + + /* + NB: + We want to make the remote call, but we don't care about results + We use the low-level call so that we can ignore reverts and set gas + */ + address(c).call.gas(remoteGasAllowance)( + abi.encodePacked(c.spv.selector, abi.encode(_txid, _vin, _vout, _requestID, _inputIndex, _outputIndex)) + ); + + emit RequestFilled(_txid, _requestID); + + return true; + } + + // function headerHash(bytes memory _header) public returns (bytes32) { + // return _header.hash256(); + // } + + /// @notice Verifies inclusion of a tx in a header, and that header in the Relay chain + /// @dev Specifically we check that both the best tip and the heaviest common header confirm it + /// @param _header The header containing the merkleroot committing to the tx + /// @param _proof The merkle proof intermediate nodes + /// @param _index The index of the tx in the merkle tree's leaves + /// @param _txid The txid that is the proof leaf + /// @param _requestID The ID of the request to check against + function _checkInclusion( + bytes29 _header, // Header + bytes29 _proof, // MerkleArray + uint256 _index, + bytes32 _txid, + uint256 _requestID + ) internal view returns (bool) { + require(ViewSPV.prove(_txid, _header.merkleRoot(), _proof, _index), "Bad inclusion proof"); + + bytes32 _headerHash = _header.hash256(); + console.log("headerHashnow:"); + console.logBytes32(_headerHash); + bytes32 _GCD = getLastReorgCommonAncestor(); + + // console.log("heer"); + // console.logBytes(_GCD); + console.log("calling"); + require(_isAncestor(_headerHash, _GCD, 240), "GCD does not confirm header"); + console.log("ok wtf"); + uint8 _numConfs = requests[_requestID].numConfs; + require(_getConfs(_headerHash) >= _numConfs, "Insufficient confirmations"); + + return true; + } + + /// @notice Finds the number of headers on top of the argument + /// @dev Bounded to 6400 gas (8 looksups) max + /// @param _headerHash The LE double-sha2 header hash + /// @return The number of headers on top + function _getConfs(bytes32 _headerHash) internal view returns (uint8) { + return uint8(_findHeight(bestKnownDigest) - _findHeight(_headerHash)); + } + + /// @notice Verifies that a tx meets the requester's request + /// @dev Requests can be specify an input, and output, and/or an output value + /// @param _reqIndices The input and output index to check against the request, packed + /// @param _vinBytes The tx input vector + /// @param _voutBytes The tx output vector + /// @param _requestID The id of the request to check + function _checkRequests(uint16 _reqIndices, bytes memory _vinBytes, bytes memory _voutBytes, uint256 _requestID) + internal + view + returns (bool) + { + bytes29 _vin = _vinBytes.ref(0).tryAsVin(); + bytes29 _vout = _voutBytes.ref(0).tryAsVout(); + require(_vin.notNull(), "Vin is malformatted"); + require(_vout.notNull(), "Vout is malformatted"); + + uint8 _inputIndex = uint8(_reqIndices >> 8); + uint8 _outputIndex = uint8(_reqIndices & 0xff); + + ProofRequest storage _req = requests[_requestID]; + require(_req.notBefore <= block.timestamp, "Request is submitted too early"); + require(_req.state == RequestStates.ACTIVE, "Request is not active"); + + bytes32 _pays = _req.pays; + bool _hasPays = _pays != bytes32(0); + if (_hasPays) { + bytes29 _out = _vout.indexVout(_outputIndex); + bytes29 _scriptPubkey = _out.scriptPubkey(); + require(_scriptPubkey.keccak() == _pays, "Does not match pays request"); + uint64 _paysValue = _req.paysValue; + require(_paysValue == 0 || _out.value() >= _paysValue, "Does not match value request"); + } + + bytes32 _spends = _req.spends; + bool _hasSpends = _spends != bytes32(0); + if (_hasSpends) { + bytes29 _in = _vin.indexVin(_inputIndex); + require(!_hasSpends || _in.outpoint().keccak() == _spends, "Does not match spends request"); + } + return true; + } +} From 4a7db25eb093020f4fda71c773e7e2825617ba25 Mon Sep 17 00:00:00 2001 From: Daniel Graczer Date: Wed, 10 Jul 2024 15:05:06 +0700 Subject: [PATCH 2/3] feat: changes on top of summa full relay --- foundry.toml | 3 +- src/relay/FullRelay.sol | 204 +++++----- src/relay/FullRelayInterfaces.sol | 31 ++ src/relay/FullRelayWithVerify.sol | 385 +++--------------- test/WitnessTx.t.sol | 1 - test/fullRelay/FullRelayAddHeaderTest.t.sol | 72 ++++ .../FullRelayAddHeaderWithRetargetTest.t.sol | 68 ++++ .../fullRelay/FullRelayConstructionTest.t.sol | 54 +++ .../fullRelay/FullRelayFindAncestorTest.t.sol | 38 ++ test/fullRelay/FullRelayFindHeightTest.t.sol | 36 ++ .../FullRelayHeaviestFromAncestorTest.t.sol | 85 ++++ ...HeaviestFromAncestorWithRetargetTest.t.sol | 58 +++ test/fullRelay/FullRelayIsAncestorTest.t.sol | 33 ++ .../FullRelayIsMostAncestorTest.t.sol | 63 +++ .../fullRelay/FullRelayMarkHeaviestTest.t.sol | 88 ++++ test/fullRelay/FullRelayTestUtils.sol | 97 +++++ test/fullRelay/FullRelayWithVerifyTest.t.sol | 63 +++ test/fullRelay/testData/headers.json | 252 ++++++++++++ .../testData/headersReorgAndRetarget.json | 102 +++++ test/fullRelay/testData/headersVerify.json | 13 + .../testData/headersWithRetarget.json | 238 +++++++++++ 21 files changed, 1558 insertions(+), 426 deletions(-) create mode 100644 src/relay/FullRelayInterfaces.sol create mode 100644 test/fullRelay/FullRelayAddHeaderTest.t.sol create mode 100644 test/fullRelay/FullRelayAddHeaderWithRetargetTest.t.sol create mode 100644 test/fullRelay/FullRelayConstructionTest.t.sol create mode 100644 test/fullRelay/FullRelayFindAncestorTest.t.sol create mode 100644 test/fullRelay/FullRelayFindHeightTest.t.sol create mode 100644 test/fullRelay/FullRelayHeaviestFromAncestorTest.t.sol create mode 100644 test/fullRelay/FullRelayHeaviestFromAncestorWithRetargetTest.t.sol create mode 100644 test/fullRelay/FullRelayIsAncestorTest.t.sol create mode 100644 test/fullRelay/FullRelayIsMostAncestorTest.t.sol create mode 100644 test/fullRelay/FullRelayMarkHeaviestTest.t.sol create mode 100644 test/fullRelay/FullRelayTestUtils.sol create mode 100644 test/fullRelay/FullRelayWithVerifyTest.t.sol create mode 100644 test/fullRelay/testData/headers.json create mode 100644 test/fullRelay/testData/headersReorgAndRetarget.json create mode 100644 test/fullRelay/testData/headersVerify.json create mode 100644 test/fullRelay/testData/headersWithRetarget.json diff --git a/foundry.toml b/foundry.toml index 634a9686..2464b1e1 100644 --- a/foundry.toml +++ b/foundry.toml @@ -1,5 +1,5 @@ [profile.default] -fs_permissions = [{ access = "read", path = "./optimized-out" }] +fs_permissions = [{ access = "read", path = "./optimized-out" }, { access = "read", path = "./test/fullRelay/testData/" }] src = "src" out = "out" libs = ["lib"] @@ -27,4 +27,3 @@ remappings = [ "@openzeppelin/=lib/openzeppelin-contracts/", "openzeppelin-contracts/=lib/openzeppelin-contracts/contracts/", ] - diff --git a/src/relay/FullRelay.sol b/src/relay/FullRelay.sol index 716664fb..b3b28a99 100644 --- a/src/relay/FullRelay.sol +++ b/src/relay/FullRelay.sol @@ -1,29 +1,31 @@ -pragma solidity ^0.5.10; +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.17; /** * @title Relay + * @author Distributed Crafts (https://www.gobob.xyz/) + * + * Forked from https://github.com/summa-tx/relays + * Changes made: + * 1. dependency changes + * - changed summa-tx/bitcoin-spv to keep-network/bitcoin-spv-sol + * - remove SafeMath + * 2. test changes + * - fixed some tests that were written incorrectly in the summa repo + * - ported Truffle javascript tests to Foundry solidity + * - new tests added + * 3. solidity compiler version upgraded to 0.8.17 + * 4. OnDemandSPV was gutted and only the verification part was kept */ -/** - * @author Summa (https://summa.one) - */ -import {SafeMath} from "@summa-tx/bitcoin-spv-sol/contracts/SafeMath.sol"; -import {TypedMemView} from "@summa-tx/bitcoin-spv-sol/contracts/TypedMemView.sol"; -import {ViewBTC} from "@summa-tx/bitcoin-spv-sol/contracts/ViewBTC.sol"; -import {ViewSPV} from "@summa-tx/bitcoin-spv-sol/contracts/ViewSPV.sol"; -import {IRelay} from "./Interfaces.sol"; - -import "truffle/console.sol"; - -contract Relay is IRelay { - using SafeMath for uint256; - using TypedMemView for bytes; - using TypedMemView for bytes29; - using ViewBTC for bytes29; - using ViewSPV for bytes29; - - /* using BytesLib for bytes; +import {IFullRelay} from "./FullRelayInterfaces.sol"; + +import {BytesLib} from "@bob-collective/bitcoin-spv/BytesLib.sol"; +import {BTCUtils} from "@bob-collective/bitcoin-spv/BTCUtils.sol"; +import {SafeMath} from "@bob-collective/bitcoin-spv/SafeMath.sol"; + +contract FullRelay is IFullRelay { using BTCUtils for bytes; - using ValidateSPV for bytes; */ + using BTCUtils for uint256; // How often do we store the height? // A higher number incurs less storage cost, but more lookup cost @@ -43,10 +45,9 @@ contract Relay is IRelay { /// @param _genesisHeader The starting header /// @param _height The starting height /// @param _periodStart The hash of the first header in the genesis epoch - constructor(bytes memory _genesisHeader, uint256 _height, bytes32 _periodStart) public { - bytes29 _genesisView = _genesisHeader.ref(0).tryAsHeader(); - require(_genesisView.notNull(), "Stop being dumb"); - bytes32 _genesisDigest = _genesisView.hash256(); + constructor(bytes memory _genesisHeader, uint256 _height, bytes32 _periodStart) { + require(isHeaderValidLength(_genesisHeader), "Bad genesis block"); + bytes32 _genesisDigest = _genesisHeader.hash256(); require( _periodStart & bytes32(0x0000000000000000000000000000000000000000000000000000000000ffffff) == bytes32(0), @@ -57,8 +58,29 @@ contract Relay is IRelay { bestKnownDigest = _genesisDigest; lastReorgCommonAncestor = _genesisDigest; blockHeight[_genesisDigest] = _height; - blockHeight[_periodStart] = _height.sub(_height % 2016); - currentEpochDiff = _genesisView.diff(); + blockHeight[_periodStart] = _height - (_height % 2016); + currentEpochDiff = _genesisHeader.extractDifficulty(); + } + + /// @notice Checks whether the header is 80 bytes long + /// @param _header The header for which the length is checked + /// @return True if the header's length is 80 bytes, and false otherwise + function isHeaderValidLength(bytes memory _header) internal pure returns (bool) { + return _header.length == 80; + } + + /// @notice Checks whether the header chain's length is a multiple of 80 bytes + /// @param _headerChain The header chain for which the length is checked + /// @return True if the header chain's length is a multiple of 80 bytes, and false otherwise + function isHeaderChainValidLength(bytes memory _headerChain) internal pure returns (bool) { + return _headerChain.length % 80 == 0; + } + + /// @notice Checks whether the merkle proof array's length is a multiple of 32 bytes + /// @param _merkleProofArray The merkle proof array for which the length is checked + /// @return True if the merkle proof array's length is a multiple of 32 bytes, and false otherwise + function isMerkleArrayValidLength(bytes memory _merkleProofArray) internal pure returns (bool) { + return _merkleProofArray.length % 32 == 0; } /// @notice Getter for currentEpochDiff @@ -128,13 +150,10 @@ contract Relay is IRelay { /// @param _headers A tightly-packed list of 80-byte Bitcoin headers /// @return True if successfully written, error otherwise function addHeaders(bytes calldata _anchor, bytes calldata _headers) external returns (bool) { - bytes29 _headersView = _headers.ref(0).tryAsHeaderArray(); - bytes29 _anchorView = _anchor.ref(0).tryAsHeader(); - - require(_headersView.notNull(), "Header array length must be divisible by 80"); - require(_anchorView.notNull(), "Anchor must be 80 bytes"); + require(isHeaderChainValidLength(_headers), "Header array length must be divisible by 80"); + require(isHeaderValidLength(_anchor), "Anchor must be 80 bytes"); - return _addHeaders(_anchorView, _headersView, false); + return _addHeaders(_anchor, _headers, false); } /// @notice Adds headers to storage, performs additional validation of retarget @@ -148,16 +167,13 @@ contract Relay is IRelay { bytes calldata _oldPeriodEndHeader, bytes calldata _headers ) external returns (bool) { - bytes29 _oldStart = _oldPeriodStartHeader.ref(0).tryAsHeader(); - bytes29 _oldEnd = _oldPeriodEndHeader.ref(0).tryAsHeader(); - bytes29 _headersView = _headers.ref(0).tryAsHeaderArray(); - require( - _oldStart.notNull() && _oldEnd.notNull() && _headersView.notNull(), + isHeaderValidLength(_oldPeriodStartHeader) && isHeaderValidLength(_oldPeriodEndHeader) + && isHeaderChainValidLength(_headers), "Bad args. Check header and array byte lengths." ); - return _addHeadersWithRetarget(_oldStart, _oldEnd, _headersView); + return _addHeadersWithRetarget(_oldPeriodStartHeader, _oldPeriodEndHeader, _headers); } /// @notice Gives a starting point for the relay @@ -171,10 +187,11 @@ contract Relay is IRelay { external returns (bool) { - bytes29 _new = _newBest.ref(0).tryAsHeader(); - bytes29 _current = _currentBest.ref(0).tryAsHeader(); - require(_new.notNull() && _current.notNull(), "Bad args. Check header and array byte lengths."); - return _markNewHeaviest(_ancestor, _current, _new, _limit); + require( + isHeaderValidLength(_newBest) && isHeaderValidLength(_currentBest), + "Bad args. Check header and array byte lengths." + ); + return _markNewHeaviest(_ancestor, _currentBest, _newBest, _limit); } /// @notice Adds headers to storage after validating @@ -183,13 +200,13 @@ contract Relay is IRelay { /// @param _headers A tightly-packed list of new 80-byte Bitcoin headers to record /// @param _internal True if called internally from addHeadersWithRetarget, false otherwise /// @return True if successfully written, error otherwise - function _addHeaders(bytes29 _anchor, bytes29 _headers, bool _internal) internal returns (bool) { + function _addHeaders(bytes memory _anchor, bytes memory _headers, bool _internal) internal returns (bool) { /// Extract basic info bytes32 _previousDigest = _anchor.hash256(); uint256 _anchorHeight = _findHeight(_previousDigest); /* NB: errors if unknown */ - uint256 _target = _headers.indexHeaderArray(0).target(); + uint256 _target = _headers.extractTarget(); - require(_internal || _anchor.target() == _target, "Unexpected retarget on external call"); + require(_internal || _anchor.extractTarget() == _target, "Unexpected retarget on external call"); /* NB: @@ -200,10 +217,10 @@ contract Relay is IRelay { */ uint256 _height; bytes32 _currentDigest; - for (uint256 i = 0; i < _headers.len() / 80; i += 1) { - bytes29 _header = _headers.indexHeaderArray(i); - _height = _anchorHeight.add(i + 1); - _currentDigest = _header.hash256(); + uint256 _headersLength = _headers.length; + for (uint256 start = 0; start < _headersLength; start += 80) { + _height = _anchorHeight + (start / 80 + 1); + _currentDigest = _headers.hash256Slice(start, 80); /* NB: @@ -211,7 +228,7 @@ contract Relay is IRelay { Or write anything to state. This saves gas */ if (previousBlock[_currentDigest] == bytes32(0)) { - require(TypedMemView.reverseUint256(uint256(_currentDigest)) <= _target, "Header work is insufficient"); + require(uint256(_currentDigest).reverseUint256() <= _target, "Header work is insufficient"); previousBlock[_currentDigest] = _previousDigest; if (_height % HEIGHT_INTERVAL == 0) { /* @@ -222,8 +239,8 @@ contract Relay is IRelay { } /* NB: we do still need to make chain level checks tho */ - require(_header.target() == _target, "Target changed unexpectedly"); - require(_header.checkParent(_previousDigest), "Headers do not form a consistent chain"); + require(_headers.extractTargetAt(start) == _target, "Target changed unexpectedly"); + require(_headers.extractPrevBlockLEAt(start) == _previousDigest, "Headers do not form a consistent chain"); _previousDigest = _currentDigest; } @@ -238,27 +255,31 @@ contract Relay is IRelay { /// @param _oldEnd The last header in the difficulty period being closed /// @param _headers A tightly-packed list of 80-byte Bitcoin headers /// @return True if successfully written, error otherwise - function _addHeadersWithRetarget(bytes29 _oldStart, bytes29 _oldEnd, bytes29 _headers) internal returns (bool) { + function _addHeadersWithRetarget(bytes memory _oldStart, bytes memory _oldEnd, bytes memory _headers) + internal + returns (bool) + { /* NB: requires that both blocks are known */ uint256 _startHeight = _findHeight(_oldStart.hash256()); uint256 _endHeight = _findHeight(_oldEnd.hash256()); /* NB: retargets should happen at 2016 block intervals */ require(_endHeight % 2016 == 2015, "Must provide the last header of the closing difficulty period"); - require(_endHeight == _startHeight.add(2015), "Must provide exactly 1 difficulty period"); - require(_oldStart.diff() == _oldEnd.diff(), "Period header difficulties do not match"); + require(_endHeight == _startHeight + 2015, "Must provide exactly 1 difficulty period"); + require(_oldStart.extractDifficulty() == _oldEnd.extractDifficulty(), "Period header difficulties do not match"); /* NB: This comparison looks weird because header nBits encoding truncates targets */ - bytes29 _newStart = _headers.indexHeaderArray(0); - uint256 _actualTarget = _newStart.target(); - uint256 _expectedTarget = ViewBTC.retargetAlgorithm(_oldStart.target(), _oldStart.time(), _oldEnd.time()); + uint256 _actualTarget = _headers.extractTarget(); + uint256 _expectedTarget = BTCUtils.retargetAlgorithm( + _oldStart.extractTarget(), _oldStart.extractTimestamp(), _oldEnd.extractTimestamp() + ); require((_actualTarget & _expectedTarget) == _actualTarget, "Invalid retarget provided"); // If the current known prevEpochDiff doesn't match, and this old period is near the chaintip/ // update the stored prevEpochDiff // Don't update if this is a deep past epoch - uint256 _oldDiff = _oldStart.diff(); - if (prevEpochDiff != _oldDiff && _endHeight > _findHeight(bestKnownDigest).sub(2016)) { + uint256 _oldDiff = _oldStart.extractDifficulty(); + if (prevEpochDiff != _oldDiff && _endHeight > _findHeight(bestKnownDigest) - 2016) { prevEpochDiff = _oldDiff; } @@ -273,12 +294,12 @@ contract Relay is IRelay { function _findHeight(bytes32 _digest) internal view returns (uint256) { uint256 _height = 0; bytes32 _current = _digest; - for (uint256 i = 0; i < HEIGHT_INTERVAL + 1; i = i.add(1)) { + for (uint256 i = 0; i < HEIGHT_INTERVAL + 1; ++i) { _height = blockHeight[_current]; if (_height == 0) { _current = previousBlock[_current]; } else { - return _height.add(i); + return _height + i; } } revert("Unknown block"); @@ -290,7 +311,7 @@ contract Relay is IRelay { /// @return The height of the header, or error if unknown function _findAncestor(bytes32 _digest, uint256 _offset) internal view returns (bytes32) { bytes32 _current = _digest; - for (uint256 i = 0; i < _offset; i = i.add(1)) { + for (uint256 i = 0; i < _offset; ++i) { _current = previousBlock[_current]; } require(_current != bytes32(0), "Unknown ancestor"); @@ -303,14 +324,11 @@ contract Relay is IRelay { /// @param _descendant The descendant to check /// @param _limit The maximum number of blocks to check /// @return true if ancestor is at most limit blocks lower than descendant, otherwise false - function _isAncestor(bytes32 _ancestor, bytes32 _descendant, uint256 _limit) internal view returns (bool) { - console.log("called"); + function _isAncestor(bytes32 _ancestor, bytes32 _descendant, uint256 _limit) internal view virtual returns (bool) { bytes32 _current = _descendant; /* NB: 200 gas/read, so gas is capped at ~200 * limit */ - for (uint256 i = 0; i < _limit; i = i.add(1)) { - console.log(i); + for (uint256 i = 0; i < _limit; ++i) { if (_current == _ancestor) { - console.log("found"); return true; } _current = previousBlock[_current]; @@ -326,8 +344,8 @@ contract Relay is IRelay { /// @return True if successfully updates bestKnownDigest, error otherwise function _markNewHeaviest( bytes32 _ancestor, - bytes29 _current, // Header - bytes29 _new, // Header + bytes memory _current, // Header + bytes memory _new, // Header uint256 _limit ) internal returns (bool) { require(_limit <= 2016, "Requested limit is greater than 1 difficulty period"); @@ -348,7 +366,7 @@ contract Relay is IRelay { bestKnownDigest = _newBestDigest; lastReorgCommonAncestor = _ancestor; - uint256 _newDiff = _new.diff(); + uint256 _newDiff = _new.extractDifficulty(); if (_newDiff != currentEpochDiff) { currentEpochDiff = _newDiff; } @@ -379,7 +397,7 @@ contract Relay is IRelay { bytes32 _leftPrev = _left; bytes32 _rightPrev = _right; - for (uint256 i = 0; i < _limit; i = i.add(1)) { + for (uint256 i = 0; i < _limit; ++i) { if (_leftPrev != _ancestor) { _leftCurrent = _leftPrev; // cheap _leftPrev = previousBlock[_leftPrev]; // expensive @@ -400,7 +418,11 @@ contract Relay is IRelay { /// @param _left A chain tip /// @param _right A chain tip /// @return true if it is the most recent common ancestor within _limit, false otherwise - function _heaviestFromAncestor(bytes32 _ancestor, bytes29 _left, bytes29 _right) internal view returns (bytes32) { + function _heaviestFromAncestor(bytes32 _ancestor, bytes memory _left, bytes memory _right) + internal + view + returns (bytes32) + { uint256 _ancestorHeight = _findHeight(_ancestor); uint256 _leftHeight = _findHeight(_left.hash256()); uint256 _rightHeight = _findHeight(_right.hash256()); @@ -411,7 +433,7 @@ contract Relay is IRelay { ); /* NB: we can shortcut if one block is in a new difficulty window and the other isn't */ - uint256 _nextPeriodStartHeight = _ancestorHeight.add(2016).sub(_ancestorHeight % 2016); + uint256 _nextPeriodStartHeight = _ancestorHeight + 2016 - (_ancestorHeight % 2016); bool _leftInPeriod = _leftHeight < _nextPeriodStartHeight; bool _rightInPeriod = _rightHeight < _nextPeriodStartHeight; @@ -428,7 +450,8 @@ contract Relay is IRelay { return _leftHeight >= _rightHeight ? _left.hash256() : _right.hash256(); } else { // if (!_leftInPeriod && !_rightInPeriod) { - if (((_leftHeight % 2016).mul(_left.diff())) < (_rightHeight % 2016).mul(_right.diff())) { + if (((_leftHeight % 2016) * _left.extractDifficulty()) < (_rightHeight % 2016) * _right.extractDifficulty()) + { return _right.hash256(); } else { return _left.hash256(); @@ -436,32 +459,3 @@ contract Relay is IRelay { } } } - -// For unittests -contract TestRelay is Relay { - /// @notice Gives a starting point for the relay - /// @dev We don't check this AT ALL really. Don't use relays with bad genesis - /// @param _genesisHeader The starting header - /// @param _height The starting height - /// @param _periodStart The hash of the first header in the genesis epoch - constructor(bytes memory _genesisHeader, uint256 _height, bytes32 _periodStart) - public - Relay(_genesisHeader, _height, _periodStart) - {} - - function heaviestFromAncestor(bytes32 _ancestor, bytes calldata _left, bytes calldata _right) - external - view - returns (bytes32) - { - return _heaviestFromAncestor(_ancestor, _left.ref(0).tryAsHeader(), _right.ref(0).tryAsHeader()); - } - - function isMostRecentAncestor(bytes32 _ancestor, bytes32 _left, bytes32 _right, uint256 _limit) - external - view - returns (bool) - { - return _isMostRecentAncestor(_ancestor, _left, _right, _limit); - } -} diff --git a/src/relay/FullRelayInterfaces.sol b/src/relay/FullRelayInterfaces.sol new file mode 100644 index 00000000..73b79fb9 --- /dev/null +++ b/src/relay/FullRelayInterfaces.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.17; + +interface IFullRelay { + event Extension(bytes32 indexed _first, bytes32 indexed _last); + event NewTip(bytes32 indexed _from, bytes32 indexed _to, bytes32 indexed _gcd); + + function getCurrentEpochDifficulty() external view returns (uint256); + function getPrevEpochDifficulty() external view returns (uint256); + function getRelayGenesis() external view returns (bytes32); + function getBestKnownDigest() external view returns (bytes32); + function getLastReorgCommonAncestor() external view returns (bytes32); + + function findHeight(bytes32 _digest) external view returns (uint256); + + function findAncestor(bytes32 _digest, uint256 _offset) external view returns (bytes32); + + function isAncestor(bytes32 _ancestor, bytes32 _descendant, uint256 _limit) external view returns (bool); + + function addHeaders(bytes calldata _anchor, bytes calldata _headers) external returns (bool); + + function addHeadersWithRetarget( + bytes calldata _oldPeriodStartHeader, + bytes calldata _oldPeriodEndHeader, + bytes calldata _headers + ) external returns (bool); + + function markNewHeaviest(bytes32 _ancestor, bytes calldata _currentBest, bytes calldata _newBest, uint256 _limit) + external + returns (bool); +} diff --git a/src/relay/FullRelayWithVerify.sol b/src/relay/FullRelayWithVerify.sol index b390bcea..cfd68896 100644 --- a/src/relay/FullRelayWithVerify.sol +++ b/src/relay/FullRelayWithVerify.sol @@ -1,360 +1,109 @@ -pragma solidity ^0.5.10; +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.17; -/** - * @title OnDemandSPV - */ -/** - * @author Summa (https://summa.one) - */ -import {Relay} from "./Relay.sol"; -import {ISPVRequestManager, ISPVConsumer} from "./Interfaces.sol"; +import {FullRelay} from "./FullRelay.sol"; -import {TypedMemView} from "@summa-tx/bitcoin-spv-sol/contracts/TypedMemView.sol"; -import {ViewBTC} from "@summa-tx/bitcoin-spv-sol/contracts/ViewBTC.sol"; -import {ViewSPV} from "@summa-tx/bitcoin-spv-sol/contracts/ViewSPV.sol"; -import {SafeMath} from "@summa-tx/bitcoin-spv-sol/contracts/SafeMath.sol"; -import "truffle/console.sol"; +import {SafeMath} from "@bob-collective/bitcoin-spv/SafeMath.sol"; +import {BytesLib} from "@bob-collective/bitcoin-spv/BytesLib.sol"; +import {BTCUtils} from "@bob-collective/bitcoin-spv/BTCUtils.sol"; +import {ValidateSPV} from "@bob-collective/bitcoin-spv/ValidateSPV.sol"; -contract OnDemandSPV is ISPVRequestManager, Relay { +contract FullRelayWithVerify is FullRelay { using SafeMath for uint256; - using TypedMemView for bytes; - using TypedMemView for bytes29; - using ViewBTC for bytes29; - using ViewSPV for bytes29; - - struct ProofRequest { - bytes32 spends; - bytes32 pays; - uint256 notBefore; - address consumer; - uint64 paysValue; - uint8 numConfs; - address owner; - RequestStates state; - } - - enum RequestStates { - NONE, - ACTIVE, - CLOSED - } - - mapping(bytes32 => bool) internal validatedTxns; // authenticated tx store - mapping(uint256 => ProofRequest) internal requests; // request info - uint256 public constant BASE_COST = 24 * 60 * 60; // 1 day - - uint256 public nextID; - bytes32 public latestValidatedTx; - uint256 public remoteGasAllowance = 500000; // maximum gas for callback call + using BTCUtils for bytes; + using BTCUtils for uint256; /// @notice Gives a starting point for the relay /// @dev We don't check this AT ALL really. Don't use relays with bad genesis /// @param _genesisHeader The starting header /// @param _height The starting height /// @param _periodStart The hash of the first header in the genesis epoch - constructor(bytes memory _genesisHeader, uint256 _height, bytes32 _periodStart, uint256 _firstID) - public - Relay(_genesisHeader, _height, _periodStart) - { - nextID = _firstID; - } - - /// @notice Cancel a bitcoin event request. - /// @dev Prevents the relay from forwarding tx infromation - /// @param _requestID The ID of the request to be cancelled - /// @return True if succesful, error otherwise - function cancelRequest(uint256 _requestID) external returns (bool) { - ProofRequest storage _req = requests[_requestID]; - require(_req.state == RequestStates.ACTIVE, "Request not active"); - require(msg.sender == _req.consumer || msg.sender == _req.owner, "Can only be cancelled by owner or consumer"); - _req.state = RequestStates.CLOSED; - emit RequestClosed(_requestID); - return true; - } - - function getLatestValidatedTx() external view returns (bytes32) { - return latestValidatedTx; - } - - /// @notice Retrieve info about a request - /// @dev Requests ids are numerical - /// @param _requestID The numerical ID of the request - /// @return A tuple representation of the request struct - function getRequest(uint256 _requestID) - external - view - returns ( - bytes32 spends, - bytes32 pays, - uint64 paysValue, - uint8 state, - address consumer, - address owner, - uint8 numConfs, - uint256 notBefore - ) - { - ProofRequest storage _req = requests[_requestID]; - spends = _req.spends; - pays = _req.pays; - paysValue = _req.paysValue; - state = uint8(_req.state); - consumer = _req.consumer; - owner = _req.owner; - numConfs = _req.numConfs; - notBefore = _req.notBefore; - } - - /// @notice Subscribe to a feed of Bitcoin txns matching a request - /// @dev The request can be a spent utxo and/or a created utxo - /// @param _spends An outpoint that must be spent in acceptable txns (optional) - /// @param _pays An output script that must be paid in acceptable txns (optional) - /// @param _paysValue A minimum value that must be paid to the output script (optional) - /// @param _consumer The address of a ISPVConsumer exposing spv - /// @param _numConfs The minimum number of Bitcoin confirmations to accept - /// @param _notBefore A timestamp before which proofs are not accepted - /// @return A unique request ID. - function request( - bytes calldata _spends, - bytes calldata _pays, - uint64 _paysValue, - address _consumer, - uint8 _numConfs, - uint256 _notBefore - ) external returns (uint256) { - return _request(_spends, _pays, _paysValue, _consumer, _numConfs, _notBefore); - } - - /// @notice Subscribe to a feed of Bitcoin txns matching a request - /// @dev The request can be a spent utxo and/or a created utxo - /// @param _spendsBytes An outpoint that must be spent in acceptable txns (optional) - /// @param _paysBytes An output script that must be paid in acceptable txns (optional) - /// @param _paysValue A minimum value that must be paid to the output script (optional) - /// @param _consumer The address of a ISPVConsumer exposing spv - /// @param _numConfs The minimum number of Bitcoin confirmations to accept - /// @param _notBefore A timestamp before which proofs are not accepted - /// @return A unique request ID - function _request( - bytes memory _spendsBytes, - bytes memory _paysBytes, - uint64 _paysValue, - address _consumer, - uint8 _numConfs, - uint256 _notBefore - ) internal returns (uint256) { - bytes29 _maybePays = _paysBytes.ref(0).tryAsSPK(); - bytes29 _maybeSpends = _spendsBytes.ref(0).castTo(uint40(ViewBTC.BTCTypes.Outpoint)); - - uint256 _requestID = nextID; - nextID = nextID + 1; - ProofRequest storage _req = requests[_requestID]; - _req.owner = msg.sender; - - // First add critical qualities - if (_maybeSpends.len() > 0) { - require(_maybeSpends.len() == 36, "Not a valid UTXO"); - _req.spends = _maybeSpends.keccak(); - } - if (_maybePays.isValid()) { - require( - _maybePays.payload().notNull() // standard output OR - || _maybePays.opReturnPayload().notNull(), // OP_RETURN output - "Not a standard output type" - ); - _req.pays = _maybePays.keccak(); - } - require(_req.spends != bytes32(0) || _req.pays != bytes32(0), "No request specified"); - - // Then fill in request details - if (_paysValue > 0) { - _req.paysValue = _paysValue; - } - if (_numConfs > 0 && _numConfs < 241) { - //241 is arbitray. 40 hours - _req.numConfs = _numConfs; - } - if (_notBefore > 0) { - _req.notBefore = _notBefore; - } - _req.consumer = _consumer; - _req.state = RequestStates.ACTIVE; - - emit NewProofRequest(msg.sender, _requestID, _paysValue, _spendsBytes, _paysBytes); - - return _requestID; - } + constructor(bytes memory _genesisHeader, uint256 _height, bytes32 _periodStart) + FullRelay(_genesisHeader, _height, _periodStart) + {} /// @notice Provide a proof of a tx that satisfies some request /// @dev The caller must specify which inputs, which outputs, and which request /// @param _header The header containing the merkleroot committing to the tx /// @param _proof The merkle proof intermediate nodes - /// @param _version The tx version, always the first 4 bytes of the tx - /// @param _locktime The tx locktime, always the last 4 bytes of the tx + /// @param _txId The transaction id to verify /// @param _index The index of the tx in the merkle tree's leaves - /// @param _reqIndices The input and output index to check against the request, packed - /// @param _vin The tx input vector - /// @param _vout The tx output vector - /// @param _requestID The id of the request that has been triggered - /// @return True if succesful, error otherwise - function provideProof( - bytes calldata _header, - bytes calldata _proof, - bytes4 _version, - bytes4 _locktime, - uint256 _index, - uint16 _reqIndices, - bytes calldata _vin, - bytes calldata _vout, - uint256 _requestID - ) external returns (bool) { - return _provideProof(_header, _proof, _version, _locktime, _index, _reqIndices, _vin, _vout, _requestID); - } - - function _provideProof( - bytes memory _header, - bytes memory _proof, - bytes4 _version, - bytes4 _locktime, - uint256 _index, - uint16 _reqIndices, - bytes memory _vin, - bytes memory _vout, - uint256 _requestID - ) internal returns (bool) { - bytes32 _txid = abi.encodePacked(_version, _vin, _vout, _locktime).ref(0).hash256(); - /* - NB: This shortcuts validation of any txn we've seen before. - Repeats can omit header, proof, and index - */ - if (!validatedTxns[_txid]) { - _checkInclusion( - _header.ref(0).tryAsHeader().assertValid(), - _proof.ref(0).tryAsMerkleArray().assertValid(), - _index, - _txid, - _requestID - ); - validatedTxns[_txid] = true; - latestValidatedTx = _txid; - } - _checkRequests(_reqIndices, _vin, _vout, _requestID); - _callCallback(_txid, _reqIndices, _vin, _vout, _requestID); - return true; - } - - /// @notice Notify a consumer that one of its requests has been triggered - /// @dev We include information about the tx that triggered it, so the consumer can take actions - /// @param _vin The tx input vector - /// @param _vout The tx output vector - /// @param _txid The transaction ID - /// @param _requestID The id of the request that has been triggered - function _callCallback(bytes32 _txid, uint16 _reqIndices, bytes memory _vin, bytes memory _vout, uint256 _requestID) - internal - returns (bool) + /// @param _numConfs Number of confirmations required + function verifyProof(bytes calldata _header, bytes calldata _proof, bytes32 _txId, uint256 _index, uint8 _numConfs) + external + view { - ProofRequest storage _req = requests[_requestID]; - ISPVConsumer c = ISPVConsumer(_req.consumer); - - uint8 _inputIndex = uint8(_reqIndices >> 8); - uint8 _outputIndex = uint8(_reqIndices & 0xff); - - /* - NB: - We want to make the remote call, but we don't care about results - We use the low-level call so that we can ignore reverts and set gas - */ - address(c).call.gas(remoteGasAllowance)( - abi.encodePacked(c.spv.selector, abi.encode(_txid, _vin, _vout, _requestID, _inputIndex, _outputIndex)) - ); - - emit RequestFilled(_txid, _requestID); - - return true; - } + require(isHeaderValidLength(_header), "Bad header block"); + require(isMerkleArrayValidLength(_proof), "Bad merkle array proof"); - // function headerHash(bytes memory _header) public returns (bytes32) { - // return _header.hash256(); - // } - - /// @notice Verifies inclusion of a tx in a header, and that header in the Relay chain - /// @dev Specifically we check that both the best tip and the heaviest common header confirm it - /// @param _header The header containing the merkleroot committing to the tx - /// @param _proof The merkle proof intermediate nodes - /// @param _index The index of the tx in the merkle tree's leaves - /// @param _txid The txid that is the proof leaf - /// @param _requestID The ID of the request to check against - function _checkInclusion( - bytes29 _header, // Header - bytes29 _proof, // MerkleArray - uint256 _index, - bytes32 _txid, - uint256 _requestID - ) internal view returns (bool) { - require(ViewSPV.prove(_txid, _header.merkleRoot(), _proof, _index), "Bad inclusion proof"); + require(ValidateSPV.prove(_txId, _header.extractMerkleRootLE(), _proof, _index), "Bad inclusion proof"); bytes32 _headerHash = _header.hash256(); - console.log("headerHashnow:"); - console.logBytes32(_headerHash); bytes32 _GCD = getLastReorgCommonAncestor(); - // console.log("heer"); - // console.logBytes(_GCD); - console.log("calling"); require(_isAncestor(_headerHash, _GCD, 240), "GCD does not confirm header"); - console.log("ok wtf"); - uint8 _numConfs = requests[_requestID].numConfs; require(_getConfs(_headerHash) >= _numConfs, "Insufficient confirmations"); - - return true; } /// @notice Finds the number of headers on top of the argument /// @dev Bounded to 6400 gas (8 looksups) max /// @param _headerHash The LE double-sha2 header hash /// @return The number of headers on top - function _getConfs(bytes32 _headerHash) internal view returns (uint8) { + function _getConfs(bytes32 _headerHash) internal view virtual returns (uint8) { return uint8(_findHeight(bestKnownDigest) - _findHeight(_headerHash)); } +} - /// @notice Verifies that a tx meets the requester's request - /// @dev Requests can be specify an input, and output, and/or an output value - /// @param _reqIndices The input and output index to check against the request, packed - /// @param _vinBytes The tx input vector - /// @param _voutBytes The tx output vector - /// @param _requestID The id of the request to check - function _checkRequests(uint16 _reqIndices, bytes memory _vinBytes, bytes memory _voutBytes, uint256 _requestID) - internal +// For unittests +contract TestRelay is FullRelayWithVerify { + /// @notice Gives a starting point for the relay + /// @dev We don't check this AT ALL really. Don't use relays with bad genesis + /// @param _genesisHeader The starting header + /// @param _height The starting height + /// @param _periodStart The hash of the first header in the genesis epoch + constructor(bytes memory _genesisHeader, uint256 _height, bytes32 _periodStart) + FullRelayWithVerify(_genesisHeader, _height, _periodStart) + {} + + bool isAncestorOverride; + bool isAncestorOverrideRes; + + function heaviestFromAncestor(bytes32 _ancestor, bytes calldata _left, bytes calldata _right) + external view - returns (bool) + returns (bytes32) { - bytes29 _vin = _vinBytes.ref(0).tryAsVin(); - bytes29 _vout = _voutBytes.ref(0).tryAsVout(); - require(_vin.notNull(), "Vin is malformatted"); - require(_vout.notNull(), "Vout is malformatted"); + return _heaviestFromAncestor(_ancestor, _left, _right); + } - uint8 _inputIndex = uint8(_reqIndices >> 8); - uint8 _outputIndex = uint8(_reqIndices & 0xff); + function isMostRecentAncestor(bytes32 _ancestor, bytes32 _left, bytes32 _right, uint256 _limit) + external + view + returns (bool) + { + return _isMostRecentAncestor(_ancestor, _left, _right, _limit); + } - ProofRequest storage _req = requests[_requestID]; - require(_req.notBefore <= block.timestamp, "Request is submitted too early"); - require(_req.state == RequestStates.ACTIVE, "Request is not active"); + function setAncestorOverride(bool _isAncestorOverride, bool _isAncestorOverrideRes) public { + isAncestorOverride = _isAncestorOverride; + isAncestorOverrideRes = _isAncestorOverrideRes; + } - bytes32 _pays = _req.pays; - bool _hasPays = _pays != bytes32(0); - if (_hasPays) { - bytes29 _out = _vout.indexVout(_outputIndex); - bytes29 _scriptPubkey = _out.scriptPubkey(); - require(_scriptPubkey.keccak() == _pays, "Does not match pays request"); - uint64 _paysValue = _req.paysValue; - require(_paysValue == 0 || _out.value() >= _paysValue, "Does not match value request"); + function _isAncestor(bytes32 _ancestor, bytes32 _descendant, uint256 _limit) + internal + view + virtual + override + returns (bool) + { + if (isAncestorOverride) { + return isAncestorOverrideRes; + } else { + return super._isAncestor(_ancestor, _descendant, _limit); } + } - bytes32 _spends = _req.spends; - bool _hasSpends = _spends != bytes32(0); - if (_hasSpends) { - bytes29 _in = _vin.indexVin(_inputIndex); - require(!_hasSpends || _in.outpoint().keccak() == _spends, "Does not match spends request"); - } - return true; + function _getConfs(bytes32) internal view virtual override returns (uint8) { + return 8; } } diff --git a/test/WitnessTx.t.sol b/test/WitnessTx.t.sol index f259f28e..35d24fa5 100644 --- a/test/WitnessTx.t.sol +++ b/test/WitnessTx.t.sol @@ -36,7 +36,6 @@ contract WitnessTxTest is Test { txInfo.info.locktime ).hash256View(); - console2.logBytes32(wTxHash); assertEq(wTxId, wTxHash); } diff --git a/test/fullRelay/FullRelayAddHeaderTest.t.sol b/test/fullRelay/FullRelayAddHeaderTest.t.sol new file mode 100644 index 00000000..6e95618a --- /dev/null +++ b/test/fullRelay/FullRelayAddHeaderTest.t.sol @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.17; + +import {FullRelayTestUtils} from "./FullRelayTestUtils.sol"; +import {IFullRelay} from "../../src/relay/FullRelayInterfaces.sol"; + +import {stdJson} from "forge-std/StdJson.sol"; +import {console} from "forge-std/Test.sol"; + +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; + +contract FullRelayAddHeaderTest is FullRelayTestUtils { + using stdJson for string; + + uint256 constant CHAIN_LENGTH = 18; + + bytes headers; + bytes genesisHex; + bytes orphan562630Hex; + bytes regularChainBadHeaderHex; + + constructor() FullRelayTestUtils("headers.json", ".genesis.hex", ".genesis.height", ".orphan_562630.digest_le") { + headers = getHeaders("chain", 0, CHAIN_LENGTH); + genesisHex = json.readBytes(".genesis.hex"); + orphan562630Hex = json.readBytes(".orphan_562630.hex"); + regularChainBadHeaderHex = json.readBytes(".badHeader.hex"); + } + + function testIncorrectAnchorLength() public { + vm.expectRevert(bytes("Anchor must be 80 bytes")); + relay.addHeaders("00", headers); + } + + function testExternalRetarget() public { + bytes memory badHeaders = + hex"0000002073bd2184edd9c4fc76642ea6754ee40136970efc10c4190000000000000000000296ef123ea96da5cf695f22bf7d94be87d49db1ad7ac371ac43c4da4161c8c216349c5ba11928170d38782b0000002073bd2184edd9c4fc76642ea6754ee40136970efc10c4190000000000000000005af53b865c27c6e9b5e5db4c3ea8e024f8329178a79ddb39f7727ea2fe6e6825d1349c5ba1192817e2d951590000002073bd2184edd9c4fc76642ea6754ee40136970efc10c419000000000000000000c63a8848a448a43c9e4402bd893f701cd11856e14cbbe026699e8fdc445b35a8d93c9c5ba1192817b945dc6c00000020f402c0b551b944665332466753f1eebb846a64ef24c71700000000000000000033fc68e070964e908d961cd11033896fa6c9b8b76f64a2db7ea928afa7e304257d3f9c5ba11928176164145d0000ff3f63d40efa46403afd71a254b54f2b495b7b0164991c2d22000000000000000000f046dc1b71560b7d0786cfbdb25ae320bd9644c98d5c7c77bf9df05cbe96212758419c5ba1192817a2bb2caa00000020e2d4f0edd5edd80bdcb880535443747c6b22b48fb6200d0000000000000000001d3799aa3eb8d18916f46bf2cf807cb89a9b1b4c56c3f2693711bf1064d9a32435429c5ba1192817752e49ae0000002022dba41dff28b337ee3463bf1ab1acf0e57443e0f7ab1d000000000000000000c3aadcc8def003ecbd1ba514592a18baddddcd3a287ccf74f584b04c5c10044e97479c5ba1192817c341f595"; + vm.expectRevert(bytes("Unexpected retarget on external call")); + relay.addHeaders(genesisHex, badHeaders); + } + + function testIncorrectHeaderChainLength() public { + bytes memory badHeaders = bytes.concat(headers, hex"42"); + vm.expectRevert(bytes("Header array length must be divisible by 80")); + relay.addHeaders(genesisHex, badHeaders); + } + + function testIInsufficientWork() public { + bytes memory badHeaders = bytes.concat( + headers, + hex"0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + ); + vm.expectRevert(bytes("Header work is insufficient")); + relay.addHeaders(genesisHex, badHeaders); + } + + function testTargetCangesMidchain() public { + bytes memory badHeaders = bytes.concat(genesisHex, regularChainBadHeaderHex); + vm.expectRevert(bytes("Headers do not form a consistent chain")); + relay.addHeaders(genesisHex, badHeaders); + } + + function testExtensionEventFiring() public { + bytes32 genesisDigestLe = json.readBytes32(".genesis.digest_le"); + bytes32 lastChainHeaderDigestLe = + json.readBytes32(string.concat(".chain[", Strings.toString(CHAIN_LENGTH - 1), "].digest_le")); + + vm.expectEmit(); + emit IFullRelay.Extension(genesisDigestLe, lastChainHeaderDigestLe); + + relay.addHeaders(genesisHex, headers); + } +} diff --git a/test/fullRelay/FullRelayAddHeaderWithRetargetTest.t.sol b/test/fullRelay/FullRelayAddHeaderWithRetargetTest.t.sol new file mode 100644 index 00000000..96df382a --- /dev/null +++ b/test/fullRelay/FullRelayAddHeaderWithRetargetTest.t.sol @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.17; + +import {FullRelayTestUtils} from "./FullRelayTestUtils.sol"; +import {IFullRelay} from "../../src/relay/FullRelayInterfaces.sol"; +import {TestRelay} from "../../src/relay/FullRelayWithVerify.sol"; + +import {stdJson} from "forge-std/StdJson.sol"; +import {console} from "forge-std/Test.sol"; + +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; + +contract FullRelayAddHeaderWithRetargetTest is FullRelayTestUtils { + using stdJson for string; + + bytes headers; + bytes preChange; + bytes firstHeaderHex; + bytes32 firstHeaderDigestLe; + bytes lastHeaderHex; + uint256 lastHeaderHeight; + bytes genesisHex; + + constructor() + FullRelayTestUtils("headersWithRetarget.json", ".chain[1].hex", ".chain[1].height", ".oldPeriodStart.digest_le") + { + preChange = getHeaders("chain", 2, 9); + headers = getHeaders("chain", 9, 15); + genesisHex = json.readBytes(".chain[1].hex"); + firstHeaderHex = json.readBytes(".oldPeriodStart.hex"); + firstHeaderDigestLe = json.readBytes32(".oldPeriodStart.digest_le"); + lastHeaderHex = json.readBytes(".chain[8].hex"); + lastHeaderHeight = json.readUint(".chain[8].height"); + relay.addHeaders(genesisHex, preChange); + } + + function testOldPeriodStartHeaderUnknown() public { + vm.expectRevert(bytes("Bad args. Check header and array byte lengths.")); + relay.addHeadersWithRetarget(hex"00", lastHeaderHex, headers); + } + + function testOldPeriodEndHeaderUnknown() public { + vm.expectRevert(bytes("Unknown block")); + relay.addHeadersWithRetarget(firstHeaderHex, json.readBytes(".chain[15].hex"), headers); + } + + function testOldPeriodEndMismatch() public { + vm.expectRevert(bytes("Must provide the last header of the closing difficulty period")); + relay.addHeadersWithRetarget(firstHeaderHex, firstHeaderHex, headers); + } + + function testOldPeriodStartToEndNot2015Blocks() public { + vm.expectRevert(bytes("Must provide exactly 1 difficulty period")); + relay.addHeadersWithRetarget(lastHeaderHex, lastHeaderHex, headers); + } + + function testRetargetPerformedIncorrectly() public { + relay = new TestRelay(genesisHex, lastHeaderHeight, firstHeaderDigestLe); + + vm.expectRevert(bytes("Invalid retarget provided")); + relay.addHeadersWithRetarget(firstHeaderHex, genesisHex, headers); + } + + function testAppendsNewLinksToTheChain() public { + relay.addHeadersWithRetarget(firstHeaderHex, lastHeaderHex, headers); + assert(relay.findHeight(json.readBytes32(".chain[10].digest_le")) == lastHeaderHeight + 2); + } +} diff --git a/test/fullRelay/FullRelayConstructionTest.t.sol b/test/fullRelay/FullRelayConstructionTest.t.sol new file mode 100644 index 00000000..b8fa0b4a --- /dev/null +++ b/test/fullRelay/FullRelayConstructionTest.t.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.17; + +import {FullRelayTestUtils} from "./FullRelayTestUtils.sol"; +import {IFullRelay} from "../../src/relay/FullRelayInterfaces.sol"; +import {TestRelay} from "../../src/relay/FullRelayWithVerify.sol"; + +import {stdJson} from "forge-std/StdJson.sol"; + +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; + +contract FullRelayConstructionTest is FullRelayTestUtils { + using stdJson for string; + + bytes32 genesisDigestLe; + bytes32 orphanDigestLe; + uint256 genesisDifficulty; + uint256 genesisHeight; + bytes genesisHex; + + constructor() FullRelayTestUtils("headers.json", ".genesis.hex", ".genesis.height", ".orphan_562630.digest_le") { + genesisDigestLe = json.readBytes32(".genesis.digest_le"); + orphanDigestLe = json.readBytes32(".orphan_562630.digest"); + genesisDifficulty = json.readUint(".genesis.difficulty"); + genesisHeight = json.readUint(".genesis.height"); + genesisHex = json.readBytes(".genesis.hex"); + } + + function testBadGenesisBlock() external { + vm.expectRevert(bytes("Bad genesis block")); + relay = new TestRelay("", genesisHeight, orphanDigestLe); + } + + function testPeriodStartWrongByteOrder() external { + vm.expectRevert(bytes("Period start hash does not have work. Hint: wrong byte order?")); + new TestRelay(genesisHex, genesisHeight, orphanDigestLe); + } + + function testStoresGenesisBlockInfo() external { + assertEq(relay.getRelayGenesis(), genesisDigestLe); + + assertEq(relay.getBestKnownDigest(), genesisDigestLe); + + assertEq(relay.getLastReorgCommonAncestor(), genesisDigestLe); + + assertEq(relay.findAncestor(genesisDigestLe, 0), genesisDigestLe); + + assertEq(relay.findHeight(genesisDigestLe), genesisHeight); + + assertEq(relay.getCurrentEpochDifficulty(), genesisDifficulty); + + assertEq(relay.getPrevEpochDifficulty(), 0); + } +} diff --git a/test/fullRelay/FullRelayFindAncestorTest.t.sol b/test/fullRelay/FullRelayFindAncestorTest.t.sol new file mode 100644 index 00000000..6c9edfb7 --- /dev/null +++ b/test/fullRelay/FullRelayFindAncestorTest.t.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.17; + +import {FullRelayTestUtils} from "./FullRelayTestUtils.sol"; +import {IFullRelay} from "../../src/relay/FullRelayInterfaces.sol"; +import {FullRelay} from "../../src/relay/FullRelay.sol"; + +import {stdJson} from "forge-std/StdJson.sol"; +import {console} from "forge-std/Test.sol"; + +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; + +contract FullRelayFindAncestorTest is FullRelayTestUtils { + using stdJson for string; + + uint256 constant blockNumberAfterGenesis = 6; + + constructor() + FullRelayTestUtils("headers.json", ".genesis.hex", ".fakePeriodStartHeader.height", ".genesis.digest_le") + { + relay.addHeaders(json.readBytes(".genesis.hex"), getHeaders("chain", 0, blockNumberAfterGenesis)); + } + + function testFindUnknownAncestor() public { + vm.expectRevert(bytes("Unknown ancestor")); + relay.findAncestor(hex"00000000000000000000000000000000", 3); + } + + function testFindKnownAncestor() public { + bytes32[] memory digestLes = getDigestLes("chain", 0, blockNumberAfterGenesis); + for (uint256 i; i < blockNumberAfterGenesis; ++i) { + assertEq(relay.findAncestor(digestLes[i], 0), digestLes[i]); + if (i > 0) { + assertEq(relay.findAncestor(digestLes[i], 1), digestLes[i - 1]); + } + } + } +} diff --git a/test/fullRelay/FullRelayFindHeightTest.t.sol b/test/fullRelay/FullRelayFindHeightTest.t.sol new file mode 100644 index 00000000..cffbe881 --- /dev/null +++ b/test/fullRelay/FullRelayFindHeightTest.t.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.17; + +import {FullRelayTestUtils} from "./FullRelayTestUtils.sol"; +import {IFullRelay} from "../../src/relay/FullRelayInterfaces.sol"; +import {FullRelay} from "../../src/relay/FullRelay.sol"; + +import {stdJson} from "forge-std/StdJson.sol"; +import {console} from "forge-std/Test.sol"; + +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; + +contract FullRelayFindHeightTest is FullRelayTestUtils { + using stdJson for string; + + uint256 constant blockNumberAfterGenesis = 6; + + constructor() + FullRelayTestUtils("headers.json", ".genesis.hex", ".genesis.height", ".fakePeriodStartHeader.digest_le") + { + relay.addHeaders(json.readBytes(".genesis.hex"), getHeaders("chain", 0, blockNumberAfterGenesis)); + } + + function testFindUnknownBlock() public { + vm.expectRevert(bytes("Unknown block")); + relay.findHeight(hex"00000000000000000000000000000000"); + } + + function testFindHeightOfExistingBlocks() public { + bytes32[] memory digestLes = getDigestLes("chain", 0, blockNumberAfterGenesis); + uint256[] memory blockHeights = getBlockHeights("chain", 0, blockNumberAfterGenesis); + for (uint256 i; i < blockNumberAfterGenesis; ++i) { + assertEq(relay.findHeight(digestLes[i]), blockHeights[i]); + } + } +} diff --git a/test/fullRelay/FullRelayHeaviestFromAncestorTest.t.sol b/test/fullRelay/FullRelayHeaviestFromAncestorTest.t.sol new file mode 100644 index 00000000..5f24c8aa --- /dev/null +++ b/test/fullRelay/FullRelayHeaviestFromAncestorTest.t.sol @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.17; + +import {FullRelayTestUtils} from "./FullRelayTestUtils.sol"; +import {IFullRelay} from "../../src/relay/FullRelayInterfaces.sol"; +import {FullRelay} from "../../src/relay/FullRelay.sol"; + +import {stdJson} from "forge-std/StdJson.sol"; +import {console} from "forge-std/Test.sol"; + +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; + +contract FullRelayHeaviestFromAncestorTest is FullRelayTestUtils { + using stdJson for string; + + bytes32 genesisDigestLe; + bytes32[] digestLes; + bytes[] headersHexes; + bytes headersWithMain; + bytes headersWithOrphan; + bytes32 unknownDigestLe; + bytes unknownHex; + uint256 constant blockNumberAfterGenesis = 8; + + constructor() FullRelayTestUtils("headers.json", ".genesis.hex", ".genesis.height", ".genesis.digest_le") { + digestLes = getDigestLes("chain", 0, blockNumberAfterGenesis); + genesisDigestLe = json.readBytes32(".genesis.digest_le"); + + headersHexes = getHeaderHexes("chain", 0, blockNumberAfterGenesis); + bytes memory headers = getHeaders("chain", 0, blockNumberAfterGenesis); + headersWithMain = + bytes.concat(headers, getHeaders("chain", blockNumberAfterGenesis, blockNumberAfterGenesis + 1)); + headersWithOrphan = bytes.concat(headers, json.readBytes(".orphan_562630.hex")); + unknownDigestLe = + json.readBytes32(string.concat(".chain[", Strings.toString(blockNumberAfterGenesis + 2), "].digest_le")); + unknownHex = json.readBytes(string.concat(".chain[", Strings.toString(blockNumberAfterGenesis + 2), "].hex")); + + bytes memory genesisHex = json.readBytes(".genesis.hex"); + relay.addHeaders(genesisHex, headersWithMain); + relay.addHeaders(genesisHex, headersWithOrphan); + } + + function testUnknownAncestor() public { + vm.expectRevert(bytes("Unknown block")); + relay.heaviestFromAncestor(unknownDigestLe, headersHexes[3], headersHexes[4]); + } + + function testUnknownLeft() public { + vm.expectRevert(bytes("Unknown block")); + relay.heaviestFromAncestor(digestLes[3], unknownHex, headersHexes[4]); + } + + function testUnknownRight() public { + vm.expectRevert(bytes("Unknown block")); + relay.heaviestFromAncestor(digestLes[3], headersHexes[4], unknownHex); + } + + function testDescendantLeftBelowAncestor() public { + vm.expectRevert(bytes("A descendant height is below the ancestor height")); + relay.heaviestFromAncestor(digestLes[3], headersHexes[2], headersHexes[4]); + } + + function testDescendantRightBelowAncestor() public { + vm.expectRevert(bytes("A descendant height is below the ancestor height")); + relay.heaviestFromAncestor(digestLes[3], headersHexes[4], headersHexes[2]); + } + + function testLeftIsHeavier() public { + assertEq(relay.heaviestFromAncestor(digestLes[3], headersHexes[5], headersHexes[4]), digestLes[5]); + } + + function testRightIsHeavier() public { + assertEq(relay.heaviestFromAncestor(digestLes[3], headersHexes[4], headersHexes[5]), digestLes[5]); + } + + function testEqualWeightsReturnsLeft() public { + bytes32 orpan562640DigestLe = json.readBytes32(".orphan_562630.digest_le"); + bytes memory orpan562640HeaderHex = json.readBytes(".orphan_562630.hex"); + bytes32 headerDigestLeMain = getDigestLes("chain", blockNumberAfterGenesis, blockNumberAfterGenesis + 1)[0]; + bytes memory headerHexMain = getHeaderHexes("chain", blockNumberAfterGenesis, blockNumberAfterGenesis + 1)[0]; + + assertEq(relay.heaviestFromAncestor(digestLes[3], headerHexMain, orpan562640HeaderHex), headerDigestLeMain); + assertEq(relay.heaviestFromAncestor(digestLes[3], orpan562640HeaderHex, headerHexMain), orpan562640DigestLe); + } +} diff --git a/test/fullRelay/FullRelayHeaviestFromAncestorWithRetargetTest.t.sol b/test/fullRelay/FullRelayHeaviestFromAncestorWithRetargetTest.t.sol new file mode 100644 index 00000000..decb0b73 --- /dev/null +++ b/test/fullRelay/FullRelayHeaviestFromAncestorWithRetargetTest.t.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.17; + +import {FullRelayTestUtils} from "./FullRelayTestUtils.sol"; +import {IFullRelay} from "../../src/relay/FullRelayInterfaces.sol"; +import {FullRelay} from "../../src/relay/FullRelay.sol"; + +import {stdJson} from "forge-std/StdJson.sol"; +import {console} from "forge-std/Test.sol"; + +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; + +contract FullRelayHeaviestFromAncestorWithRetargetTest is FullRelayTestUtils { + using stdJson for string; + + bytes[] preHeaderHexes; + bytes[] postHeaderHexes; + bytes[] postShortHeaderHexes; + + bytes32 genesisDigestLe; + bytes orphanHex; + bytes32 orphanDigestLe; + + uint256 constant preLength = 5; + uint256 constant postLength = 8; + + constructor() + FullRelayTestUtils("headersReorgAndRetarget.json", ".genesis.hex", ".genesis.height", ".oldPeriodStart.digest_le") + { + preHeaderHexes = getHeaderHexes("preRetargetChain", 0, preLength); + postHeaderHexes = getHeaderHexes("postRetargetChain", 0, postLength); + postShortHeaderHexes = getHeaderHexes("postRetargetChain", 0, postLength - 2); + bytes memory preHeaders = getHeaders("preRetargetChain", 0, preLength); + bytes memory postHeaders = getHeaders("postRetargetChain", 0, postLength); + bytes memory postShortHeaders = getHeaders("postRetargetChain", 0, postLength - 2); + + orphanHex = json.readBytes(".orphan_437478.hex"); + orphanDigestLe = json.readBytes32(".orphan_437478.digest_le"); + bytes memory postWithOrphan = bytes.concat(postShortHeaders, orphanHex); + + bytes memory genesisHex = json.readBytes(".genesis.hex"); + genesisDigestLe = json.readBytes32(".genesis.digest_le"); + bytes memory oldPeriodStartHex = json.readBytes(".oldPeriodStart.hex"); + relay.addHeaders(genesisHex, preHeaders); + relay.addHeadersWithRetarget(oldPeriodStartHex, preHeaderHexes[preLength - 1], postHeaders); + relay.addHeadersWithRetarget(oldPeriodStartHex, preHeaderHexes[preLength - 1], postWithOrphan); + } + + function testHandlingDescendantsAtDifferentDifficultyPeriod() public { + assertEq(relay.heaviestFromAncestor(genesisDigestLe, orphanHex, preHeaderHexes[3]), orphanDigestLe); + assertEq(relay.heaviestFromAncestor(genesisDigestLe, preHeaderHexes[3], orphanHex), orphanDigestLe); + } + + function testHandlingDescendantsWhenBothAtDifferentNewDifficultyPeriod() public { + assertEq(relay.heaviestFromAncestor(genesisDigestLe, orphanHex, postHeaderHexes[3]), orphanDigestLe); + assertEq(relay.heaviestFromAncestor(genesisDigestLe, postHeaderHexes[3], orphanHex), orphanDigestLe); + } +} diff --git a/test/fullRelay/FullRelayIsAncestorTest.t.sol b/test/fullRelay/FullRelayIsAncestorTest.t.sol new file mode 100644 index 00000000..ffc64250 --- /dev/null +++ b/test/fullRelay/FullRelayIsAncestorTest.t.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.17; + +import {FullRelayTestUtils} from "./FullRelayTestUtils.sol"; +import {IFullRelay} from "../../src/relay/FullRelayInterfaces.sol"; +import {FullRelay} from "../../src/relay/FullRelay.sol"; + +import {stdJson} from "forge-std/StdJson.sol"; +import {console} from "forge-std/Test.sol"; + +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; + +contract FullRelayIsAncestorTest is FullRelayTestUtils { + using stdJson for string; + + bytes32 genesisDigestLe; + bytes32[] digestLes; + uint256 blockNumberAfterGenesis = 6; + + constructor() FullRelayTestUtils("headers.json", ".genesis.hex", ".genesis.height", ".genesis.digest_le") { + digestLes = getDigestLes("chain", 0, blockNumberAfterGenesis); + genesisDigestLe = json.readBytes32(".genesis.digest_le"); + relay.addHeaders(json.readBytes(".genesis.hex"), getHeaders("chain", 0, blockNumberAfterGenesis)); + } + + function testExceedSearchLimit() public { + assertFalse(relay.isAncestor(genesisDigestLe, digestLes[3], 1)); + } + + function testAncestorFound() public { + assertTrue(relay.isAncestor(genesisDigestLe, digestLes[3], 5)); + } +} diff --git a/test/fullRelay/FullRelayIsMostAncestorTest.t.sol b/test/fullRelay/FullRelayIsMostAncestorTest.t.sol new file mode 100644 index 00000000..1211f9fc --- /dev/null +++ b/test/fullRelay/FullRelayIsMostAncestorTest.t.sol @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.17; + +import {FullRelayTestUtils} from "./FullRelayTestUtils.sol"; +import {IFullRelay} from "../../src/relay/FullRelayInterfaces.sol"; +import {FullRelay} from "../../src/relay/FullRelay.sol"; + +import {stdJson} from "forge-std/StdJson.sol"; +import {console} from "forge-std/Test.sol"; + +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; + +contract FullRelayIsMostAncestorTest is FullRelayTestUtils { + using stdJson for string; + + bytes[] preHeaderHexes; + bytes32[] postDigestLes; + + bytes32 genesisDigestLe; + bytes orphanHex; + bytes32 orphanDigestLe; + + uint256 constant preLength = 5; + uint256 constant postLength = 8; + + constructor() + FullRelayTestUtils("headersReorgAndRetarget.json", ".genesis.hex", ".genesis.height", ".oldPeriodStart.digest_le") + { + preHeaderHexes = getHeaderHexes("preRetargetChain", 0, preLength); + postDigestLes = getDigestLes("postRetargetChain", 0, postLength); + bytes memory preHeaders = getHeaders("preRetargetChain", 0, preLength); + bytes memory postHeaders = getHeaders("postRetargetChain", 0, postLength); + bytes memory postShortHeaders = getHeaders("postRetargetChain", 0, postLength - 2); + + orphanHex = json.readBytes(".orphan_437478.hex"); + orphanDigestLe = json.readBytes32(".orphan_437478.digest_le"); + bytes memory postWithOrphan = bytes.concat(postShortHeaders, orphanHex); + + bytes memory genesisHex = json.readBytes(".genesis.hex"); + genesisDigestLe = json.readBytes32(".genesis.digest_le"); + bytes memory oldPeriodStartHex = json.readBytes(".oldPeriodStart.hex"); + relay.addHeaders(genesisHex, preHeaders); + relay.addHeadersWithRetarget(oldPeriodStartHex, preHeaderHexes[preLength - 1], postHeaders); + relay.addHeadersWithRetarget(oldPeriodStartHex, preHeaderHexes[preLength - 1], postWithOrphan); + } + + function testReturnsFalseIfMoreRecentAncestorFound() public { + assertFalse(relay.isMostRecentAncestor(postDigestLes[0], postDigestLes[3], postDigestLes[2], 5)); + } + + function testReturnsFalseIfLimitExceeded() public { + assertFalse(relay.isMostRecentAncestor(postDigestLes[1], postDigestLes[3], postDigestLes[2], 1)); + } + + function testReturnsTrueIfWithinLimit() public { + assertTrue(relay.isMostRecentAncestor(postDigestLes[2], postDigestLes[3], postDigestLes[2], 5)); + assertTrue(relay.isMostRecentAncestor(postDigestLes[5], postDigestLes[6], orphanDigestLe, 5)); + } + + function testLeftAndRightAndAncestorSame() public { + assertTrue(relay.isMostRecentAncestor(postDigestLes[3], postDigestLes[3], postDigestLes[3], 5)); + } +} diff --git a/test/fullRelay/FullRelayMarkHeaviestTest.t.sol b/test/fullRelay/FullRelayMarkHeaviestTest.t.sol new file mode 100644 index 00000000..7e1ff6c5 --- /dev/null +++ b/test/fullRelay/FullRelayMarkHeaviestTest.t.sol @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.17; + +import {FullRelayTestUtils} from "./FullRelayTestUtils.sol"; +import {IFullRelay} from "../../src/relay/FullRelayInterfaces.sol"; +import {FullRelay} from "../../src/relay/FullRelay.sol"; + +import {stdJson} from "forge-std/StdJson.sol"; +import {console} from "forge-std/Test.sol"; + +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; + +contract FullRelayMarkHeaviestTest is FullRelayTestUtils { + using stdJson for string; + + bytes[] preHeaderHexes; + bytes[] postHeaderHexes; + bytes32[] preDigestLes; + bytes32[] postDigestLes; + + bytes genesisHex; + bytes32 genesisDigestLe; + bytes orphanHex; + bytes32 orphanDigestLe; + bytes oldPeriodStartHex; + bytes32 oldPeriodStartDigestLe; + + uint256 constant preLength = 5; + uint256 constant postLength = 8; + + constructor() + FullRelayTestUtils("headersReorgAndRetarget.json", ".genesis.hex", ".genesis.height", ".oldPeriodStart.digest_le") + { + preHeaderHexes = getHeaderHexes("preRetargetChain", 0, preLength); + postHeaderHexes = getHeaderHexes("postRetargetChain", 0, postLength); + preDigestLes = getDigestLes("preRetargetChain", 0, preLength); + postDigestLes = getDigestLes("postRetargetChain", 0, postLength); + bytes memory preHeaders = getHeaders("preRetargetChain", 0, preLength); + bytes memory postHeaders = getHeaders("postRetargetChain", 0, postLength); + bytes memory postShortHeaders = getHeaders("postRetargetChain", 0, postLength - 2); + + orphanHex = json.readBytes(".orphan_437478.hex"); + orphanDigestLe = json.readBytes32(".orphan_437478.digest_le"); + bytes memory postWithOrphan = bytes.concat(postShortHeaders, orphanHex); + + oldPeriodStartHex = json.readBytes(".oldPeriodStart.hex"); + oldPeriodStartDigestLe = json.readBytes32(".oldPeriodStart.digest_le"); + + genesisHex = json.readBytes(".genesis.hex"); + genesisDigestLe = json.readBytes32(".genesis.digest_le"); + relay.addHeaders(genesisHex, preHeaders); + relay.addHeadersWithRetarget(oldPeriodStartHex, preHeaderHexes[preLength - 1], postHeaders); + relay.addHeadersWithRetarget(oldPeriodStartHex, preHeaderHexes[preLength - 1], postWithOrphan); + } + + function testPassedInNotTheBestKnown() public { + vm.expectRevert(bytes("Passed in best is not best known")); + relay.markNewHeaviest(oldPeriodStartDigestLe, oldPeriodStartHex, oldPeriodStartHex, 10); + } + + function testPassedInBestKnowIsUnknown() public { + vm.expectRevert(bytes("New best is unknown")); + relay.markNewHeaviest( + genesisDigestLe, + genesisHex, + hex"9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999", + 10 + ); + } + + function testPassedInAncestorNotTheHeaviestCommon() public { + relay.markNewHeaviest(genesisDigestLe, genesisHex, preHeaderHexes[0], 10); + + vm.expectRevert(bytes("Ancestor must be heaviest common ancestor")); + relay.markNewHeaviest(genesisDigestLe, preHeaderHexes[0], preHeaderHexes[1], 10); + } + + function testSuccessfullyMarkHeaviest() public { + relay.markNewHeaviest(genesisDigestLe, genesisHex, preHeaderHexes[0], 10); + + vm.expectEmit(); + emit IFullRelay.NewTip(preDigestLes[0], orphanDigestLe, preDigestLes[0]); + relay.markNewHeaviest(preDigestLes[0], preHeaderHexes[0], orphanHex, 20); + + vm.expectRevert(bytes("New best hash does not have more work than previous")); + relay.markNewHeaviest(postDigestLes[5], orphanHex, postHeaderHexes[6], 10); + } +} diff --git a/test/fullRelay/FullRelayTestUtils.sol b/test/fullRelay/FullRelayTestUtils.sol new file mode 100644 index 00000000..2b7bf3ef --- /dev/null +++ b/test/fullRelay/FullRelayTestUtils.sol @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.17; + +import {TestRelay} from "../../src/relay/FullRelayWithVerify.sol"; + +import {stdJson} from "forge-std/StdJson.sol"; +import {Test, console} from "forge-std/Test.sol"; + +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; + +contract FullRelayTestUtils is Test { + using stdJson for string; + + TestRelay relay; + string json; + + constructor( + string memory testFileName, + string memory genesisHexPath, + string memory genesisHeightPath, + string memory periodStartPath + ) { + string memory root = vm.projectRoot(); + string memory path = string.concat(root, "/test/fullRelay/testData/", testFileName); + json = vm.readFile(path); + relay = new TestRelay( + json.readBytes(genesisHexPath), json.readUint(genesisHeightPath), json.readBytes32(periodStartPath) + ); + } + + function getHeaders(string memory chainName, uint256 from, uint256 to) public view returns (bytes memory headers) { + bytes[] memory headerHexes = getHeaderHexes(chainName, from, to); + for (uint256 i = 0; i < to - from; i++) { + headers = bytes.concat(headers, headerHexes[i]); + } + } + + function getHeaderHexes(string memory chainName, uint256 from, uint256 to) + public + view + returns (bytes[] memory elements) + { + return getBytes(chainName, from, to, "hex"); + } + + function getDigestLes(string memory chainName, uint256 from, uint256 to) + public + view + returns (bytes32[] memory elements) + { + return getBytes32Array(chainName, from, to, "digest_le"); + } + + function getBlockHeights(string memory chainName, uint256 from, uint256 to) + public + view + returns (uint256[] memory elements) + { + return getUint256Array(chainName, from, to, "height"); + } + + function getBytes(string memory chainName, uint256 from, uint256 to, string memory elementName) + internal + view + returns (bytes[] memory elements) + { + elements = new bytes[](to - from); + for (uint256 i = from; i < to; i++) { + elements[i - from] = + json.readBytes(string.concat(".", chainName, "[", Strings.toString(i), "].", elementName)); + } + } + + function getBytes32Array(string memory chainName, uint256 from, uint256 to, string memory elementName) + internal + view + returns (bytes32[] memory elements) + { + elements = new bytes32[](to - from); + for (uint256 i = from; i < to; i++) { + elements[i - from] = + json.readBytes32(string.concat(".", chainName, "[", Strings.toString(i), "].", elementName)); + } + } + + function getUint256Array(string memory chainName, uint256 from, uint256 to, string memory elementName) + internal + view + returns (uint256[] memory elements) + { + elements = new uint256[](to - from); + for (uint256 i = from; i < to; i++) { + elements[i - from] = + json.readUint(string.concat(".", chainName, "[", Strings.toString(i), "].", elementName)); + } + } +} diff --git a/test/fullRelay/FullRelayWithVerifyTest.t.sol b/test/fullRelay/FullRelayWithVerifyTest.t.sol new file mode 100644 index 00000000..44a1b3ec --- /dev/null +++ b/test/fullRelay/FullRelayWithVerifyTest.t.sol @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.17; + +import {FullRelayTestUtils} from "./FullRelayTestUtils.sol"; +import {IFullRelay} from "../../src/relay/FullRelayInterfaces.sol"; + +import {stdJson} from "forge-std/StdJson.sol"; +import {console} from "forge-std/Test.sol"; + +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; + +contract FullRelayWithVerifyTest is FullRelayTestUtils { + using stdJson for string; + + uint256 constant CHAIN_LENGTH = 18; + bytes header = + hex"0000002073bd2184edd9c4fc76642ea6754ee40136970efc10c4190000000000000000000296ef123ea96da5cf695f22bf7d94be87d49db1ad7ac371ac43c4da4161c8c216349c5ba11928170d38782b"; + bytes proof = + hex"e35a0d6de94b656694589964a252957e4673a9fb1d2f8b4a92e3f0a7bb654fddb94e5a1e6d7f7f499fd1be5dd30a73bf5584bf137da5fdd77cc21aeb95b9e35788894be019284bd4fbed6dd6118ac2cb6d26bc4be4e423f55a3a48f2874d8d02a65d9c87d07de21d4dfe7b0a9f4a23cc9a58373e9e6931fefdb5afade5df54c91104048df1ee999240617984e18b6f931e2373673d0195b8c6987d7ff7650d5ce53bcec46e13ab4f2da1146a7fc621ee672f62bc22742486392d75e55e67b09960c3386a0b49e75f1723d6ab28ac9a2028a0c72866e2111d79d4817b88e17c821937847768d92837bae3832bb8e5a4ab4434b97e00a6c10182f211f592409068d6f5652400d9a3d1cc150a7fb692e874cc42d76bdafc842f2fe0f835a7c24d2d60c109b187d64571efbaa8047be85821f8e67e0e85f2f5894bc63d00c2ed9d64"; + bytes32 txToVerify = hex"48e5a1a0e616d8fd92b4ef228c424e0c816799a256c6a90892195ccfc53300d6"; + uint256 txId = 281; + + constructor() FullRelayTestUtils("headers.json", ".genesis.hex", ".genesis.height", ".genesis.digest_le") { + relay.setAncestorOverride(true, true); + } + + function testMalformedProofSupplied() public { + vm.expectRevert(bytes("Bad merkle array proof")); + relay.verifyProof(header, hex"00", txToVerify, txId, 0); + } + + function testIncorrectProofSupplied() public { + vm.expectRevert(bytes("Bad inclusion proof")); + relay.verifyProof( + header, hex"0000000000000000000000000000000000000000000000000000000000000000", txToVerify, txId, 0 + ); + } + + function testIncorrectTxSupplied() public { + vm.expectRevert(bytes("Bad inclusion proof")); + relay.verifyProof(header, proof, hex"00", txId, 0); + } + + function testIncorrectTxIdSupplied() public { + vm.expectRevert(bytes("Bad inclusion proof")); + relay.verifyProof(header, proof, txToVerify, txId + 1, 0); + } + + function testGCDDoesntConfirmHeader() public { + relay.setAncestorOverride(false, false); + vm.expectRevert(bytes("GCD does not confirm header")); + relay.verifyProof(header, proof, txToVerify, txId, 0); + } + + function testInsufficientConfirmations() public { + vm.expectRevert(bytes("Insufficient confirmations")); + relay.verifyProof(header, proof, txToVerify, txId, 9); + } + + function testSuccessfullyVerify() public view { + relay.verifyProof(header, proof, txToVerify, txId, 0); + } +} diff --git a/test/fullRelay/testData/headers.json b/test/fullRelay/testData/headers.json new file mode 100644 index 00000000..e563f10e --- /dev/null +++ b/test/fullRelay/testData/headers.json @@ -0,0 +1,252 @@ +{ + "orphan_562630": { + "digest": "0x0000000000000000000f021d6ba1384b7db92589b2eee80d01dcc0d5afb30452", + "digest_le": "0x5204b3afd5c0dc010de8eeb28925b97d4b38a16b1d020f000000000000000000", + "version": 545259520, + "prev_block": "0x000000000000000000055f9b2b220c70f52fa5e7f7ad1036a8fa05a0f7e4ff20", + "merkle_root": "0x58c8af6bf8e8e00c3d6ff512b2133533ebdcde164de306e6b65e53157fc22b53", + "timestamp": 1549915111, + "nbits": "0x886f2e17", + "difficulty": 6061518831027, + "hex": "0x0000802020ffe4f7a005faa83610adf7e7a52ff5700c222b9b5f0500000000000000000058c8af6bf8e8e00c3d6ff512b2133533ebdcde164de306e6b65e53157fc22b53e7d3615c886f2e17ed205930", + "height": 562630 + }, + "badHeader": { + "digest_le": "0xbaaea6746f4c16ccb7cd961655b636d39b5fe1519b8f15000000000000000000", + "hex": "0x00000020fe70e48339d6b17fbbf1340d245338f57336e97767cc240000000000000000005af53b865c27c6e9b5e5db4c3ea8e024f8329178a79ddb39f7727ea2fe6e6825d1349c5ba1192817e2d95159" + }, + "fakePeriodStartHeader": { + "digest_le": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee000000", + "height": 562464 + }, + "genesis": { + "digest": "0x00000000000000000021ce9ccc1691e25066eb388be7821a4906dbee4b611546", + "digest_le": "0x4615614beedb06491a82e78b38eb6650e29116cc9cce21000000000000000000", + "version": 536870912, + "prev_block": "0x000000000000000000148e27320434ecb256e42a7657f3305f3289592b9662db", + "merkle_root": "0xd1dd4e30908c361dfeabfb1e560281c1a270bde3c8719dbda7c8480053175944", + "timestamp": 1549909824, + "nbits": "0x886f2e17", + "difficulty": 6061518831027, + "hex": "0x00000020db62962b5989325f30f357762ae456b2ec340432278e14000000000000000000d1dd4e30908c361dfeabfb1e560281c1a270bde3c8719dbda7c848005317594440bf615c886f2e17bd6b082d", + "height": 562621 + }, + "chain": [ + { + "digest": "0x0000000000000000000f498a3b8394685a70ba0b0d8cb27850b1f499a380b5b8", + "digest_le": "0xb8b580a399f4b15078b28c0d0bba705a6894833b8a490f000000000000000000", + "version": 536870912, + "prev_block": "0x00000000000000000021ce9ccc1691e25066eb388be7821a4906dbee4b611546", + "merkle_root": "0xb034884fc285ff1acc861af67be0d87f5a610daa459d75a58503a01febcc287a", + "timestamp": 1549910068, + "nbits": "0x886f2e17", + "difficulty": 6061518831027, + "hex": "0x000000204615614beedb06491a82e78b38eb6650e29116cc9cce21000000000000000000b034884fc285ff1acc861af67be0d87f5a610daa459d75a58503a01febcc287a34c0615c886f2e17046e7325", + "height": 562622 + }, + { + "digest": "0x0000000000000000002066539e823973bbd8174973a5af1634ab56f685a949f5", + "digest_le": "0xf549a985f656ab3416afa5734917d8bb7339829e536620000000000000000000", + "version": 536870912, + "prev_block": "0x0000000000000000000f498a3b8394685a70ba0b0d8cb27850b1f499a380b5b8", + "merkle_root": "0xb16c32aa36d3b70749e7febbb9e733321530cc9a390ccb62dfb78e3955859d4c", + "timestamp": 1549910084, + "nbits": "0x886f2e17", + "difficulty": 6061518831027, + "hex": "0x00000020b8b580a399f4b15078b28c0d0bba705a6894833b8a490f000000000000000000b16c32aa36d3b70749e7febbb9e733321530cc9a390ccb62dfb78e3955859d4c44c0615c886f2e1744ea7cc4", + "height": 562623 + }, + { + "digest": "0x00000000000000000014c9dc88648827978218b292dbdf0aa92aaf28aef9ff8b", + "digest_le": "0x8bfff9ae28af2aa90adfdb92b218829727886488dcc914000000000000000000", + "version": 536870912, + "prev_block": "0x0000000000000000002066539e823973bbd8174973a5af1634ab56f685a949f5", + "merkle_root": "0xaf9c9fe22494c39cf382b5c8dcef91f079ad84cb9838387aaa17948fbf257534", + "timestamp": 1549910576, + "nbits": "0x886f2e17", + "difficulty": 6061518831027, + "hex": "0x00000020f549a985f656ab3416afa5734917d8bb7339829e536620000000000000000000af9c9fe22494c39cf382b5c8dcef91f079ad84cb9838387aaa17948fbf25753430c2615c886f2e170a654758", + "height": 562624 + }, + { + "digest": "0x0000000000000000000d386f066623139bec7039e090c008744f49fe3900e2fe", + "digest_le": "0xfee20039fe494f7408c090e03970ec9b132366066f380d000000000000000000", + "version": 1073733632, + "prev_block": "0x00000000000000000014c9dc88648827978218b292dbdf0aa92aaf28aef9ff8b", + "merkle_root": "0xe78265c12495ef469b3e85aa570667fdcb5bf534304fcbe4621c706d0e7ca814", + "timestamp": 1549910941, + "nbits": "0x886f2e17", + "difficulty": 6061518831027, + "hex": "0x00e0ff3f8bfff9ae28af2aa90adfdb92b218829727886488dcc914000000000000000000e78265c12495ef469b3e85aa570667fdcb5bf534304fcbe4621c706d0e7ca8149dc3615c886f2e171c3fa2b8", + "height": 562625 + }, + { + "digest": "0x0000000000000000001d3023a708fb47b9540432d9b2d20471914f5eb16b40d2", + "digest_le": "0xd2406bb15e4f917104d2b2d9320454b947fb08a723301d000000000000000000", + "version": 536870912, + "prev_block": "0x0000000000000000000d386f066623139bec7039e090c008744f49fe3900e2fe", + "merkle_root": "0x6f510b84a156d42cb64f30b97a7fc6dd030c9b77e19bbcd850c9f3c69bc533da", + "timestamp": 1549910990, + "nbits": "0x886f2e17", + "difficulty": 6061518831027, + "hex": "0x00000020fee20039fe494f7408c090e03970ec9b132366066f380d0000000000000000006f510b84a156d42cb64f30b97a7fc6dd030c9b77e19bbcd850c9f3c69bc533dacec3615c886f2e170a1a921d", + "height": 562626 + }, + { + "digest": "0x0000000000000000002b2d22288b3c99e828b238bbd680e2f41b0ad3345df296", + "digest_le": "0x96f25d34d30a1bf4e280d6bb38b228e8993c8b28222d2b000000000000000000", + "version": 536870912, + "prev_block": "0x0000000000000000001d3023a708fb47b9540432d9b2d20471914f5eb16b40d2", + "merkle_root": "0x6aa31402bb974ebcd45eb2b0be7df8cc48ea9721493978e8715ce93b55d5ea20", + "timestamp": 1549911505, + "nbits": "0x886f2e17", + "difficulty": 6061518831027, + "hex": "0x00000020d2406bb15e4f917104d2b2d9320454b947fb08a723301d0000000000000000006aa31402bb974ebcd45eb2b0be7df8cc48ea9721493978e8715ce93b55d5ea20d1c5615c886f2e1767973d73", + "height": 562627 + }, + { + "digest": "0x0000000000000000002470635e016bac1e96ed4a68af6a3b9b606062701869b8", + "digest_le": "0xb86918706260609b3b6aaf684aed961eac6b015e637024000000000000000000", + "version": 536928256, + "prev_block": "0x0000000000000000002b2d22288b3c99e828b238bbd680e2f41b0ad3345df296", + "merkle_root": "0x2488333a3bab6cc72d04ee1523ae83c9559938bef8521cb624c9641bb58cabe9", + "timestamp": 1549913683, + "nbits": "0x886f2e17", + "difficulty": 6061518831027, + "hex": "0x00e0002096f25d34d30a1bf4e280d6bb38b228e8993c8b28222d2b0000000000000000002488333a3bab6cc72d04ee1523ae83c9559938bef8521cb624c9641bb58cabe953ce615c886f2e172e0885ce", + "height": 562628 + }, + { + "digest": "0x000000000000000000055f9b2b220c70f52fa5e7f7ad1036a8fa05a0f7e4ff20", + "digest_le": "0x20ffe4f7a005faa83610adf7e7a52ff5700c222b9b5f05000000000000000000", + "version": 536870912, + "prev_block": "0x0000000000000000002470635e016bac1e96ed4a68af6a3b9b606062701869b8", + "merkle_root": "0xe72e0a6fd324d36edee39f9336c56f129f1ef7c2ec26d0dfb01e4520fb7a8d7f", + "timestamp": 1549913805, + "nbits": "0x886f2e17", + "difficulty": 6061518831027, + "hex": "0x00000020b86918706260609b3b6aaf684aed961eac6b015e637024000000000000000000e72e0a6fd324d36edee39f9336c56f129f1ef7c2ec26d0dfb01e4520fb7a8d7fcdce615c886f2e17415cdb46", + "height": 562629 + }, + { + "digest": "0x0000000000000000000a03b17c49727b1df86200f228bfa71e3a38420c4b2151", + "digest_le": "0x51214b0c42383a1ea7bf28f20062f81d7b72497cb1030a000000000000000000", + "version": 536870912, + "prev_block": "0x000000000000000000055f9b2b220c70f52fa5e7f7ad1036a8fa05a0f7e4ff20", + "merkle_root": "0x9d1479517fda612a10a279b2339952bbdba8fe47c8fec644921d146ec79482ec", + "timestamp": 1549915115, + "nbits": "0x886f2e17", + "difficulty": 6061518831027, + "hex": "0x0000002020ffe4f7a005faa83610adf7e7a52ff5700c222b9b5f050000000000000000009d1479517fda612a10a279b2339952bbdba8fe47c8fec644921d146ec79482ecebd3615c886f2e179234c38e", + "height": 562630 + }, + { + "digest": "0x0000000000000000000864d592bfb200c2933037055280b971f9f4e15c5101bf", + "digest_le": "0xbf01515ce1f4f971b9805205373093c200b2bf92d56408000000000000000000", + "version": 536870912, + "prev_block": "0x0000000000000000000a03b17c49727b1df86200f228bfa71e3a38420c4b2151", + "merkle_root": "0x00af444756eb5313dae6cb8dc7b4e00ae7d79cfa67a85b4b486a9583896ab331", + "timestamp": 1549916235, + "nbits": "0x886f2e17", + "difficulty": 6061518831027, + "hex": "0x0000002051214b0c42383a1ea7bf28f20062f81d7b72497cb1030a00000000000000000000af444756eb5313dae6cb8dc7b4e00ae7d79cfa67a85b4b486a9583896ab3314bd8615c886f2e17f152bc1f", + "height": 562631 + }, + { + "digest": "0x000000000000000000103a511d5bb1289dc716e2f78619f8e685156c0192c2a4", + "digest_le": "0xa4c292016c1585e6f81986f7e216c79d28b15b1d513a10000000000000000000", + "version": 536928256, + "prev_block": "0x0000000000000000000864d592bfb200c2933037055280b971f9f4e15c5101bf", + "merkle_root": "0xb2c2fcb555d6e2d677bb9919cc2d9660c81879225d15f53f679e3fdbfad129d0", + "timestamp": 1549916978, + "nbits": "0x886f2e17", + "difficulty": 6061518831027, + "hex": "0x00e00020bf01515ce1f4f971b9805205373093c200b2bf92d56408000000000000000000b2c2fcb555d6e2d677bb9919cc2d9660c81879225d15f53f679e3fdbfad129d032db615c886f2e17155f22df", + "height": 562632 + }, + { + "digest": "0x00000000000000000020cc2730fa1682e8db65dd5a3b34483773905fb4117d57", + "digest_le": "0x577d11b45f90733748343b5add65dbe88216fa3027cc20000000000000000000", + "version": 536870912, + "prev_block": "0x000000000000000000103a511d5bb1289dc716e2f78619f8e685156c0192c2a4", + "merkle_root": "0x4e33f75c5f63371d4a05e7ab93afb7c1caa22d2f9d4fce61e194ab8ffe741f35", + "timestamp": 1549917888, + "nbits": "0x886f2e17", + "difficulty": 6061518831027, + "hex": "0x00000020a4c292016c1585e6f81986f7e216c79d28b15b1d513a100000000000000000004e33f75c5f63371d4a05e7ab93afb7c1caa22d2f9d4fce61e194ab8ffe741f35c0de615c886f2e17032ddf4b", + "height": 562633 + }, + { + "digest": "0x00000000000000000025b7bece641ab4df35f145f488bd8192ff120184d6f2f5", + "digest_le": "0xf5f2d6840112ff9281bd88f445f135dfb41a64cebeb725000000000000000000", + "version": 536870912, + "prev_block": "0x00000000000000000020cc2730fa1682e8db65dd5a3b34483773905fb4117d57", + "merkle_root": "0xef200d52b09ae1902d62476f09405e21fa81cd7972ccfeaf37b47b00ef4e2180", + "timestamp": 1549917901, + "nbits": "0x886f2e17", + "difficulty": 6061518831027, + "hex": "0x00000020577d11b45f90733748343b5add65dbe88216fa3027cc20000000000000000000ef200d52b09ae1902d62476f09405e21fa81cd7972ccfeaf37b47b00ef4e2180cdde615c886f2e171f2b5f80", + "height": 562634 + }, + { + "digest": "0x00000000000000000000c0be9a85e087242e1bb31aa3b27ef0251eb7c59487f9", + "digest_le": "0xf98794c5b71e25f07eb2a31ab31b2e2487e0859abec000000000000000000000", + "version": 536870912, + "prev_block": "0x00000000000000000025b7bece641ab4df35f145f488bd8192ff120184d6f2f5", + "merkle_root": "0xa91738fc7b8628e70906164624f80ce54bafe26fe0ef8678f569b0b64e83589c", + "timestamp": 1549918387, + "nbits": "0x886f2e17", + "difficulty": 6061518831027, + "hex": "0x00000020f5f2d6840112ff9281bd88f445f135dfb41a64cebeb725000000000000000000a91738fc7b8628e70906164624f80ce54bafe26fe0ef8678f569b0b64e83589cb3e0615c886f2e17c624e58c", + "height": 562635 + }, + { + "digest": "0x0000000000000000002884d4cdb2d3698853343db2dadd7154e451b7fc8fd10e", + "digest_le": "0x0ed18ffcb751e45471dddab23d34538869d3b2cdd48428000000000000000000", + "version": 536928256, + "prev_block": "0x00000000000000000000c0be9a85e087242e1bb31aa3b27ef0251eb7c59487f9", + "merkle_root": "0xc29b14f0fe90ac2173197665d460df45c37ccf0c873276f59d095cbed4bcc7c2", + "timestamp": 1549919482, + "nbits": "0x886f2e17", + "difficulty": 6061518831027, + "hex": "0x00e00020f98794c5b71e25f07eb2a31ab31b2e2487e0859abec000000000000000000000c29b14f0fe90ac2173197665d460df45c37ccf0c873276f59d095cbed4bcc7c2fae4615c886f2e178a505d9d", + "height": 562636 + }, + { + "digest": "0x0000000000000000002a0336c2ede62484bcab6b1f345c42b0002ad6c817ae30", + "digest_le": "0x30ae17c8d62a00b0425c341f6babbc8424e6edc236032a000000000000000000", + "version": 536870912, + "prev_block": "0x0000000000000000002884d4cdb2d3698853343db2dadd7154e451b7fc8fd10e", + "merkle_root": "0x3505702919866f91f1196e078799287f80be0b2c3af830ed6011ce89fc7f0d65", + "timestamp": 1549919612, + "nbits": "0x886f2e17", + "difficulty": 6061518831027, + "hex": "0x000000200ed18ffcb751e45471dddab23d34538869d3b2cdd484280000000000000000003505702919866f91f1196e078799287f80be0b2c3af830ed6011ce89fc7f0d657ce5615c886f2e17d759c936", + "height": 562637 + }, + { + "digest": "0x0000000000000000002d4de5bd28d4445cf36bfb83d341c084440fb69320f351", + "digest_le": "0x51f32093b60f4484c041d383fb6bf35c44d428bde54d2d000000000000000000", + "version": 545259520, + "prev_block": "0x0000000000000000002a0336c2ede62484bcab6b1f345c42b0002ad6c817ae30", + "merkle_root": "0x097a32800849429e29be38addab29936925589e1e547d1ce968d62e566a45882", + "timestamp": 1549919729, + "nbits": "0x886f2e17", + "difficulty": 6061518831027, + "hex": "0x0000802030ae17c8d62a00b0425c341f6babbc8424e6edc236032a000000000000000000097a32800849429e29be38addab29936925589e1e547d1ce968d62e566a45882f1e5615c886f2e1796e65983", + "height": 562638 + }, + { + "digest": "0x0000000000000000000fb8dba7f48eb65dbc24c6dc03d6f84823b5d59c7c4148", + "digest_le": "0x48417c9cd5b52348f8d603dcc624bc5db68ef4a7dbb80f000000000000000000", + "version": 545259520, + "prev_block": "0x0000000000000000002d4de5bd28d4445cf36bfb83d341c084440fb69320f351", + "merkle_root": "0x1888692df72fdbc27082d23e323b6acacaa5424d191dbaa7f13dfed36cdfbee9", + "timestamp": 1549919749, + "nbits": "0x886f2e17", + "difficulty": 6061518831027, + "hex": "0x0000802051f32093b60f4484c041d383fb6bf35c44d428bde54d2d0000000000000000001888692df72fdbc27082d23e323b6acacaa5424d191dbaa7f13dfed36cdfbee905e6615c886f2e177b59d28f", + "height": 562639 + } + ] +} diff --git a/test/fullRelay/testData/headersReorgAndRetarget.json b/test/fullRelay/testData/headersReorgAndRetarget.json new file mode 100644 index 00000000..165a26ed --- /dev/null +++ b/test/fullRelay/testData/headersReorgAndRetarget.json @@ -0,0 +1,102 @@ +{ + "orphan_437478": { + "digest": "0x0000000000000000011da79d23e40a8360ed09d7f7e67e85e5fcf14377694df2", + "digest_le": "0xf24d697743f1fce5857ee6f7d709ed60830ae4239da71d010000000000000000", + "hex": "0x00000020e156ef206dc738dbe7b7bd449f90b65771292f146213fb000000000000000000c52c39a4e31158ff7c34417c9750038b27c9a94dc08210e53ba624667c9310973d161e5874510418a9f7e0d0", + "height": 437478 + }, + "oldPeriodStart": { + "digest": "0x00000000000000000082ed5c599748dbaf59b084445c3f0c107fef194fe9009b", + "digest_le": "0x9b00e94f19ef7f100c3f5c4484b059afdb4897595ced82000000000000000000", + "hex": "0x00000020a32e8e27455216a02d4704b3a3c4731cf34117d468d97f020000000000000000948fa46542c2d31a37733dca03b2cc82ffadc067fd603479f64d773fcfc13801d2a90b58d25504183d4ee3b4", + "height": 435456 + }, + "genesis": { + "digest": "0x00000000000000000073a63eb7045711d8b7d27f2a13948cd70a858980acd416", + "digest_le": "0x16d4ac8089850ad78c94132a7fd2b7d8115704b73ea673000000000000000000", + "hex": "0x00000020d0eb8cc9a5668ddabb83e6f657d940fd56902ceb039f200000000000000000005e407cf51ee81930145648d1e72c1caeafa6f6bfc4a7ec3988bcb7cf69a1594af3fe1d58d2550418e90ec632", + "height": 437466 + }, + "preRetargetChain": [ + { + "digest": "0x0000000000000000033c5bbb5aa43f276e3701168b7e43b40a41dd960101bc58", + "digest_le": "0x58bc010196dd410ab4437e8b1601376e273fa45abb5b3c030000000000000000", + "hex": "0x0000002016d4ac8089850ad78c94132a7fd2b7d8115704b73ea673000000000000000000993956e6dbdabd742e9078408e4a5a75e3760d18a24cc6b18e65b6bb3b15ed0f5b001e58d2550418183b1b41", + "height": 437467 + }, + { + "digest": "0x0000000000000000039c463b6149a95a3ba6e0d39aa90755f56477bbd9711901", + "digest_le": "0x011971d9bb7764f55507a99ad3e0a63b5aa949613b469c030000000000000000", + "hex": "0x0000002058bc010196dd410ab4437e8b1601376e273fa45abb5b3c03000000000000000035092ae7558bab7b92b811bd07bd3a5a3c16a84e37a67e47817e813728f6f5e612011e58d255041805ebb671", + "height": 437468 + }, + { + "digest": "0x0000000000000000020daace431ae4ab0509e877c069fb35d98a190af3df1473", + "digest_le": "0x7314dff30a198ad935fb69c077e80905abe41a43ceaa0d020000000000000000", + "hex": "0x00000020011971d9bb7764f55507a99ad3e0a63b5aa949613b469c030000000000000000ea0ac12b8c46d54d1eac6637b916ae7775aad858dc1c8a43cff50c40bb51a3bc2c081e58d255041859d608ec", + "height": 437469 + }, + { + "digest": "0x000000000000000003f58f0d85e555bc292b518fa012a90c8b5e45d24993fff2", + "digest_le": "0xf2ff9349d2455e8b0ca912a08f512b29bc55e5850d8ff5030000000000000000", + "hex": "0x000000207314dff30a198ad935fb69c077e80905abe41a43ceaa0d020000000000000000d3439b160498d4464fcb6eee7ac75e801933067932f30234e3b924e2671e31a4e1091e58d25504188ccd158e", + "height": 437470 + }, + { + "digest": "0x00000000000000000364a23184b8a2c009d13172094421c22e4d9bc85dcf90a5", + "digest_le": "0xa590cf5dc89b4d2ec22144097231d109c0a2b88431a264030000000000000000", + "hex": "0x00000020f2ff9349d2455e8b0ca912a08f512b29bc55e5850d8ff503000000000000000051aa623be08b19c2a6984e0c229bbffce20673675267eab5bf09a963b0547c673c0c1e58d25504183e20ab5b", + "height": 437471 + } + ], + "postRetargetChain": [ + { + "digest": "0x000000000000000003c75c23f1444676c6832d45badd6ae89b8d1903d92563b7", + "digest_le": "0xb76325d903198d9be86addba452d83c6764644f1235cc7030000000000000000", + "hex": "0x00000020a590cf5dc89b4d2ec22144097231d109c0a2b88431a264030000000000000000a37e2d39aaf31d8bd7c00a07fa9784fee88ab361082f03601119dac4a0d87c8d020d1e58745104189b665b65", + "height": 437472 + }, + { + "digest": "0x0000000000000000006084d9f6fb8b81349fcde72e85ab5e39e9d0b728f32b61", + "digest_le": "0x612bf328b7d0e9395eab852ee7cd9f34818bfbf6d98460000000000000000000", + "hex": "0x00000020b76325d903198d9be86addba452d83c6764644f1235cc70300000000000000001d9e6cc2b0871a8f88db857db870dfa7000f80f3c89c7a0eaaa925974fc392459e111e5874510418f4dcbaed", + "height": 437473 + }, + { + "digest": "0x000000000000000003a15741d88dcbdab6c39b17dddf4829b6b8b509313bb14d", + "digest_le": "0x4db13b3109b5b8b62948dfdd179bc3b6dacb8dd84157a1030000000000000000", + "hex": "0x00000020612bf328b7d0e9395eab852ee7cd9f34818bfbf6d984600000000000000000001698022f9aeaccae63250f24ad66093504b088686d2e307bf7653c700b7e23d1e2111e5874510418914866a7", + "height": 437474 + }, + { + "digest": "0x000000000000000000a024f601c50d9da8c3f09cc8feba477dd11370756fcda8", + "digest_le": "0xa8cd6f757013d17d47bafec89cf0c3a89d0dc501f624a0000000000000000000", + "hex": "0x000000204db13b3109b5b8b62948dfdd179bc3b6dacb8dd84157a10300000000000000005c93b3ce08ce22ba64fb26b8e75d695ac8c2bea0badd34200e698ccc73ca421b97121e58745104183ded0054", + "height": 437475 + }, + { + "digest": "0x000000000000000003464978084011690021563526c9a5e2cbb17fbb76073adb", + "digest_le": "0xdb3a0776bb7fb1cbe2a5c9263556210069114008784946030000000000000000", + "hex": "0x00000020a8cd6f757013d17d47bafec89cf0c3a89d0dc501f624a00000000000000000004d7fd5d8c9984f2be03a80a75a4b6fe02dfda894181fcab0b6beb60698c7d6f0db121e587451041816e7fbbd", + "height": 437476 + }, + { + "digest": "0x000000000000000000fb1362142f297157b6909f44bdb7e7db38c76d20ef56e1", + "digest_le": "0xe156ef206dc738dbe7b7bd449f90b65771292f146213fb000000000000000000", + "hex": "0x00000020db3a0776bb7fb1cbe2a5c9263556210069114008784946030000000000000000c8cbccf35d668296e8da72aed974983a8679e5077445b79d1e3e6c4d1bbff2c730161e58745104188674ac6f", + "height": 437477 + }, + { + "digest": "0x0000000000000000013f1f231297f03c154bf918650793a786b3468ab9c339d3", + "digest_le": "0xd339c3b98a46b386a793076518f94b153cf09712231f3f010000000000000000", + "hex": "0x00000020e156ef206dc738dbe7b7bd449f90b65771292f146213fb000000000000000000f7e22ae2e5442bac43e18fc032dafd058fbdf886e33a3330e7597ce6eb4c073347161e58745104186cff9846", + "height": 437478 + }, + { + "digest": "0x000000000000000003a243f554646535630675ac1fb5a922a5bacc4df24d77a0", + "digest_le": "0xa0774df24dccbaa522a9b51fac75066335656454f543a2030000000000000000", + "hex": "0x00000020d339c3b98a46b386a793076518f94b153cf09712231f3f01000000000000000018f078c9c73734d892fef90854381524f30017370d12dfa8319313c5729dbf85b8191e58745104187d43c005", + "height": 437479 + } + ] +} diff --git a/test/fullRelay/testData/headersVerify.json b/test/fullRelay/testData/headersVerify.json new file mode 100644 index 00000000..72ccb767 --- /dev/null +++ b/test/fullRelay/testData/headersVerify.json @@ -0,0 +1,13 @@ +{ + "OP_RETURN_TX": "0x010000000001011746bd867400f3494b8f44c24b83e1aa58c4f0ff25b4a61cffeffd4bc0f9ba300000000000ffffffff024897070000000000220020a4333e5612ab1a1043b25755c89b16d55184a42f81799e623e6bc39db8539c180000000000000000166a14edb1b5c2f39af0fec151732585b1049b07895211024730440220276e0ec78028582054d86614c65bc4bf85ff5710b9d3a248ca28dd311eb2fa6802202ec950dd2a8c9435ff2d400cc45d7a4854ae085f49e05cc3f503834546d410de012103732783eef3af7e04d3af444430a629b16a9261e4025f52bf4d6d026299c37c7400000000", + "OP_RETURN_PROOF": "0xe35a0d6de94b656694589964a252957e4673a9fb1d2f8b4a92e3f0a7bb654fddb94e5a1e6d7f7f499fd1be5dd30a73bf5584bf137da5fdd77cc21aeb95b9e35788894be019284bd4fbed6dd6118ac2cb6d26bc4be4e423f55a3a48f2874d8d02a65d9c87d07de21d4dfe7b0a9f4a23cc9a58373e9e6931fefdb5afade5df54c91104048df1ee999240617984e18b6f931e2373673d0195b8c6987d7ff7650d5ce53bcec46e13ab4f2da1146a7fc621ee672f62bc22742486392d75e55e67b09960c3386a0b49e75f1723d6ab28ac9a2028a0c72866e2111d79d4817b88e17c821937847768d92837bae3832bb8e5a4ab4434b97e00a6c10182f211f592409068d6f5652400d9a3d1cc150a7fb692e874cc42d76bdafc842f2fe0f835a7c24d2d60c109b187d64571efbaa8047be85821f8e67e0e85f2f5894bc63d00c2ed9d64", + "OP_RETURN_INDEX": 281, + "OP_RETURN_VERSION": "0x01000000", + "OP_RETURN_VIN": "0x011746bd867400f3494b8f44c24b83e1aa58c4f0ff25b4a61cffeffd4bc0f9ba300000000000ffffffff", + "OP_RETURN_VOUT": "0x024897070000000000220020a4333e5612ab1a1043b25755c89b16d55184a42f81799e623e6bc39db8539c180000000000000000166a14edb1b5c2f39af0fec151732585b1049b07895211", + "OP_RETURN_LOCKTIME": "0x00000000", + "OP_RETURN_HEADER": "0x0000002073bd2184edd9c4fc76642ea6754ee40136970efc10c4190000000000000000000296ef123ea96da5cf695f22bf7d94be87d49db1ad7ac371ac43c4da4161c8c216349c5ba11928170d38782b", + "OP_RETURN_SPENDS_0": "0x1746bd867400f3494b8f44c24b83e1aa58c4f0ff25b4a61cffeffd4bc0f9ba3000000000", + "OP_RETURN_PAYS_1": "0x166a14edb1b5c2f39af0fec151732585b1049b07895211", + "OP_RETURN_TX_ID_LE": "0x48e5a1a0e616d8fd92b4ef228c424e0c816799a256c6a90892195ccfc53300d6" +} diff --git a/test/fullRelay/testData/headersWithRetarget.json b/test/fullRelay/testData/headersWithRetarget.json new file mode 100644 index 00000000..4e022328 --- /dev/null +++ b/test/fullRelay/testData/headersWithRetarget.json @@ -0,0 +1,238 @@ +{ + "oldPeriodStart": { + "digest": "0x00000000000000000015038a38aa780723a79ae8fc6f1881240dac31aea9189d", + "digest_le": "0x9d18a9ae31ac0d2481186ffce89aa7230778aa388a0315000000000000000000", + "height": 552384, + "hex": "0x00000020e2acb3e71e4e443af48e81d381dea7d35e2e8d5e69fe150000000000000000007f2ada224dc4afba6ca37010b099c02322cb5df24fcedb0ff5b87fb3ca64eeaea01a055c7cd9311771f2861e" + }, + "genesis": { + "digest": "0x0000000000000000001bc127b294624e1edb4928966bb22b14216dda5421319e", + "digest_le": "0x9e312154da6d21142bb26b962849db1e4e6294b227c11b000000000000000000", + "version": 541065216, + "prev_block": "0x0000000000000000000b0539fec4f09b8c189193de81e41ebed6ec34855a371d", + "merkle_root": "0x27852cd901347135f77edb0efdae704aeba4414ff6a25ac3ac1a8f2991e13f63", + "timestamp": 1545171245, + "nbits": "0x7cd93117", + "difficulty": 5646403851534, + "hex": "0x000040201d375a8534ecd6be1ee481de9391188c9bf0c4fe39050b00000000000000000027852cd901347135f77edb0efdae704aeba4414ff6a25ac3ac1a8f2991e13f632d71195c7cd931174c4002c1", + "height": 554390 + }, + "chain": [ + { + "digest": "0x00000000000000000001722a7c814b0b01c0c6ec9fdcb6cb7eeef4129118615e", + "digest_le": "0x5e61189112f4ee7ecbb6dc9fecc6c0010b4b817c2a7201000000000000000000", + "version": 541065216, + "prev_block": "0x0000000000000000001bc127b294624e1edb4928966bb22b14216dda5421319e", + "merkle_root": "0x33d1d6c65cbfbc8f040e42b0a78047e6553633a0d9fe6cf794f8c93326361b7d", + "timestamp": 1545171316, + "nbits": "0x7cd93117", + "difficulty": 5646403851534, + "hex": "0x000040209e312154da6d21142bb26b962849db1e4e6294b227c11b00000000000000000033d1d6c65cbfbc8f040e42b0a78047e6553633a0d9fe6cf794f8c93326361b7d7471195c7cd9311715368c2d", + "height": 554391 + }, + { + "digest": "0x0000000000000000000feb5dfd3e00f1b5ad65d5826c4306f9eef212fefbafe0", + "digest_le": "0xe0affbfe12f2eef906436c82d565adb5f1003efd5deb0f000000000000000000", + "version": 536870912, + "prev_block": "0x00000000000000000001722a7c814b0b01c0c6ec9fdcb6cb7eeef4129118615e", + "merkle_root": "0x02b04e0b564fc59be33622435b2049bae3dd0c932cf2798bd5d3450324307616", + "timestamp": 1545171411, + "nbits": "0x7cd93117", + "difficulty": 5646403851534, + "hex": "0x000000205e61189112f4ee7ecbb6dc9fecc6c0010b4b817c2a720100000000000000000002b04e0b564fc59be33622435b2049bae3dd0c932cf2798bd5d3450324307616d371195c7cd9311796441d48", + "height": 554392 + }, + { + "digest": "0x0000000000000000002abcfd4ea5d17a8440d72886349ec778a62cd226c3c541", + "digest_le": "0x41c5c326d22ca678c79e348628d740847ad1a54efdbc2a000000000000000000", + "version": 536870912, + "prev_block": "0x0000000000000000000feb5dfd3e00f1b5ad65d5826c4306f9eef212fefbafe0", + "merkle_root": "0x51adeb94fc008a292e0c8774161f6b5a43fe4c68d440829118cbd74010d01944", + "timestamp": 1545172119, + "nbits": "0x7cd93117", + "difficulty": 5646403851534, + "hex": "0x00000020e0affbfe12f2eef906436c82d565adb5f1003efd5deb0f00000000000000000051adeb94fc008a292e0c8774161f6b5a43fe4c68d440829118cbd74010d019449774195c7cd93117a27cc4a6", + "height": 554393 + }, + { + "digest": "0x00000000000000000007eed878624a29fb4ef29b49e3e0080313be104f0d9088", + "digest_le": "0x88900d4f10be130308e0e3499bf24efb294a6278d8ee07000000000000000000", + "version": 536870912, + "prev_block": "0x0000000000000000002abcfd4ea5d17a8440d72886349ec778a62cd226c3c541", + "merkle_root": "0xee64ff84da2777ec09aaf34d6f9fad9b445853aeb7421ece6b200a102c7f1726", + "timestamp": 1545172682, + "nbits": "0x7cd93117", + "difficulty": 5646403851534, + "hex": "0x0000002041c5c326d22ca678c79e348628d740847ad1a54efdbc2a000000000000000000ee64ff84da2777ec09aaf34d6f9fad9b445853aeb7421ece6b200a102c7f1726ca76195c7cd93117301e0ab3", + "height": 554394 + }, + { + "digest": "0x00000000000000000003a4519ac0b57ac21fa2b38f69b53fc71d0c7f08253d8e", + "digest_le": "0x8e3d25087f0c1dc73fb5698fb3a21fc27ab5c09a51a403000000000000000000", + "version": 536870912, + "prev_block": "0x00000000000000000007eed878624a29fb4ef29b49e3e0080313be104f0d9088", + "merkle_root": "0x7e9746daba41c5604e8708db20a07493aaa5a26c583dd4025d68f56e14536773", + "timestamp": 1545174730, + "nbits": "0x7cd93117", + "difficulty": 5646403851534, + "hex": "0x0000002088900d4f10be130308e0e3499bf24efb294a6278d8ee070000000000000000007e9746daba41c5604e8708db20a07493aaa5a26c583dd4025d68f56e14536773ca7e195c7cd93117ae46eb69", + "height": 554395 + }, + { + "digest": "0x0000000000000000000e18ce18455272e5c53eb69c0f884c5d27405fdc5840b8", + "digest_le": "0xb84058dc5f40275d4c880f9cb63ec5e572524518ce180e000000000000000000", + "version": 536870912, + "prev_block": "0x00000000000000000003a4519ac0b57ac21fa2b38f69b53fc71d0c7f08253d8e", + "merkle_root": "0x2fe4c18dc554ac199203ac26358814752bc0db76ed72d2673d3d4f6b20d66760", + "timestamp": 1545174899, + "nbits": "0x7cd93117", + "difficulty": 5646403851534, + "hex": "0x000000208e3d25087f0c1dc73fb5698fb3a21fc27ab5c09a51a4030000000000000000002fe4c18dc554ac199203ac26358814752bc0db76ed72d2673d3d4f6b20d66760737f195c7cd931171e60b26b", + "height": 554396 + }, + { + "digest": "0x00000000000000000011e7d4bd9857ee4472c2a6d028d5dd44de566537c13336", + "digest_le": "0x3633c1376556de44ddd528d0a6c27244ee5798bdd4e711000000000000000000", + "version": 536870912, + "prev_block": "0x0000000000000000000e18ce18455272e5c53eb69c0f884c5d27405fdc5840b8", + "merkle_root": "0x8a9ec4242c4003dde9ae9ac167dd36614ee00c56b8ffdf63abf1d0dbdcdcb24f", + "timestamp": 1545174978, + "nbits": "0x7cd93117", + "difficulty": 5646403851534, + "hex": "0x00000020b84058dc5f40275d4c880f9cb63ec5e572524518ce180e0000000000000000008a9ec4242c4003dde9ae9ac167dd36614ee00c56b8ffdf63abf1d0dbdcdcb24fc27f195c7cd931173f2077a9", + "height": 554397 + }, + { + "digest": "0x000000000000000000011afa4cbe73638a5a9122f83dd0542d887f119cc3a02d", + "digest_le": "0x2da0c39c117f882d54d03df822915a8a6373be4cfa1a01000000000000000000", + "version": 536870912, + "prev_block": "0x00000000000000000011e7d4bd9857ee4472c2a6d028d5dd44de566537c13336", + "merkle_root": "0x8a5a4c9ebf9b6b77e3f63e1f4ddbbe1aeec4b7592554cb3c003068f849647b14", + "timestamp": 1545175153, + "nbits": "0x7cd93117", + "difficulty": 5646403851534, + "hex": "0x000000203633c1376556de44ddd528d0a6c27244ee5798bdd4e7110000000000000000008a5a4c9ebf9b6b77e3f63e1f4ddbbe1aeec4b7592554cb3c003068f849647b147180195c7cd93117b8c8e83f", + "height": 554398 + }, + { + "digest": "0x00000000000000000015a08d0a60237487070fe0d956d5fb5fd9d21ad6d7b2d3", + "digest_le": "0xd3b2d7d61ad2d95ffbd556d9e00f07877423600a8da015000000000000000000", + "version": 536870912, + "prev_block": "0x000000000000000000011afa4cbe73638a5a9122f83dd0542d887f119cc3a02d", + "merkle_root": "0xdd6f24bd263432435f0954c59c022cfd8e5190f4615fc2c249815244a3fe09b3", + "timestamp": 1545175878, + "nbits": "0x7cd93117", + "difficulty": 5646403851534, + "hex": "0x000000202da0c39c117f882d54d03df822915a8a6373be4cfa1a01000000000000000000dd6f24bd263432435f0954c59c022cfd8e5190f4615fc2c249815244a3fe09b34683195c7cd931170f68c64a", + "height": 554399 + }, + { + "digest": "0x000000000000000000043c0b1ba0e06f1569ff7cebca6a78a84f4025712067ae", + "digest_le": "0xae67207125404fa8786acaeb7cff69156fe0a01b0b3c04000000000000000000", + "version": 541065216, + "prev_block": "0x00000000000000000015a08d0a60237487070fe0d956d5fb5fd9d21ad6d7b2d3", + "merkle_root": "0xd192743a2c190a7421f92fefe92505579d7b8eda568cacee13b25751ac704c66", + "timestamp": 1545175965, + "nbits": "0xf41e3717", + "difficulty": 5106422924659, + "hex": "0x00004020d3b2d7d61ad2d95ffbd556d9e00f07877423600a8da015000000000000000000d192743a2c190a7421f92fefe92505579d7b8eda568cacee13b25751ac704c669d83195cf41e371721bae3e7", + "height": 554400 + }, + { + "digest": "0x0000000000000000000fe74b42a4f1433431036b7062c2250fd071857433b3f1", + "digest_le": "0xf1b333748571d00f25c262706b03313443f1a4424be70f000000000000000000", + "version": 536870912, + "prev_block": "0x000000000000000000043c0b1ba0e06f1569ff7cebca6a78a84f4025712067ae", + "merkle_root": "0xb668f999166662460c4a9717d3c8e72e3b0c24863fccdd90295dfb0b047aa1f3", + "timestamp": 1545176308, + "nbits": "0xf41e3717", + "difficulty": 5106422924659, + "hex": "0x00000020ae67207125404fa8786acaeb7cff69156fe0a01b0b3c04000000000000000000b668f999166662460c4a9717d3c8e72e3b0c24863fccdd90295dfb0b047aa1f3f484195cf41e37176b176d83", + "height": 554401 + }, + { + "digest": "0x0000000000000000000789dee2d41f510b42e31378ab470f80f8499f368623b1", + "digest_le": "0xb12386369f49f8800f47ab7813e3420b511fd4e2de8907000000000000000000", + "version": 545259520, + "prev_block": "0x0000000000000000000fe74b42a4f1433431036b7062c2250fd071857433b3f1", + "merkle_root": "0x4d7b233c0f561f7e5d57fb1d9bae5c72576787da5c13ec792d1d87eb1a62795e", + "timestamp": 1545176506, + "nbits": "0xf41e3717", + "difficulty": 5106422924659, + "hex": "0x00008020f1b333748571d00f25c262706b03313443f1a4424be70f0000000000000000004d7b233c0f561f7e5d57fb1d9bae5c72576787da5c13ec792d1d87eb1a62795eba85195cf41e3717d904e9c3", + "height": 554402 + }, + { + "digest": "0x0000000000000000001d6bb1215bbcfd540e984827e5c3f172423384fb69d1e9", + "digest_le": "0xe9d169fb84334272f1c3e52748980e54fdbc5b21b16b1d000000000000000000", + "version": 536870912, + "prev_block": "0x0000000000000000000789dee2d41f510b42e31378ab470f80f8499f368623b1", + "merkle_root": "0x026069ddf2c58926646215a8434823226646051ebcb77b6644d9791732763da8", + "timestamp": 1545176517, + "nbits": "0xf41e3717", + "difficulty": 5106422924659, + "hex": "0x00000020b12386369f49f8800f47ab7813e3420b511fd4e2de8907000000000000000000026069ddf2c58926646215a8434823226646051ebcb77b6644d9791732763da8c585195cf41e3717689570ae", + "height": 554403 + }, + { + "digest": "0x00000000000000000029c0ca7e48e6331ed967da9cac75acb791b650712daf1f", + "digest_le": "0x1faf2d7150b691b7ac75ac9cda67d91e33e6487ecac029000000000000000000", + "version": 536870912, + "prev_block": "0x0000000000000000001d6bb1215bbcfd540e984827e5c3f172423384fb69d1e9", + "merkle_root": "0x6a504c582c322e88b8478b199536df2ad4b052ffe065975bb8e74321c09e117e", + "timestamp": 1545176820, + "nbits": "0xf41e3717", + "difficulty": 5106422924659, + "hex": "0x00000020e9d169fb84334272f1c3e52748980e54fdbc5b21b16b1d0000000000000000006a504c582c322e88b8478b199536df2ad4b052ffe065975bb8e74321c09e117ef486195cf41e37176b029121", + "height": 554404 + }, + { + "digest": "0x0000000000000000002fcbcba6a3e4f8da4b5dfdfdc63e18c348cceb9f0fa967", + "digest_le": "0x67a90f9febcc48c3183ec6fdfd5d4bdaf8e4a3a6cbcb2f000000000000000000", + "version": 536928256, + "prev_block": "0x00000000000000000029c0ca7e48e6331ed967da9cac75acb791b650712daf1f", + "merkle_root": "0x74e5c75fe71825768d052f2a0610686ae0ffcf5123f6752794e238b3f0746227", + "timestamp": 1545176889, + "nbits": "0xf41e3717", + "difficulty": 5106422924659, + "hex": "0x00e000201faf2d7150b691b7ac75ac9cda67d91e33e6487ecac02900000000000000000074e5c75fe71825768d052f2a0610686ae0ffcf5123f6752794e238b3f07462273987195cf41e3717330c9ed7", + "height": 554405 + }, + { + "digest": "0x0000000000000000002c19b616a0d5bb80bba44cdfb74aac6f78d2ae522b8e43", + "digest_le": "0x438e2b52aed2786fac4ab7df4ca4bb80bbd5a016b6192c000000000000000000", + "version": 536870912, + "prev_block": "0x0000000000000000002fcbcba6a3e4f8da4b5dfdfdc63e18c348cceb9f0fa967", + "merkle_root": "0x65eab61ce12f91069f56228e50fdc1a65e2d83d420f8d66c2e176e1a88eed309", + "timestamp": 1545179043, + "nbits": "0xf41e3717", + "difficulty": 5106422924659, + "hex": "0x0000002067a90f9febcc48c3183ec6fdfd5d4bdaf8e4a3a6cbcb2f00000000000000000065eab61ce12f91069f56228e50fdc1a65e2d83d420f8d66c2e176e1a88eed309a38f195cf41e37175cbf7945", + "height": 554406 + }, + { + "digest": "0x0000000000000000000953265f624e5152f43eb96bbed63557f42e6a79d0e8f3", + "digest_le": "0xf3e8d0796a2ef45735d6be6bb93ef452514e625f265309000000000000000000", + "version": 536870912, + "prev_block": "0x0000000000000000002c19b616a0d5bb80bba44cdfb74aac6f78d2ae522b8e43", + "merkle_root": "0xf8b338b6c621ad491059ef31ab60d25cb9e6abc1cdaff51aad421e3a2de88aa1", + "timestamp": 1545179339, + "nbits": "0xf41e3717", + "difficulty": 5106422924659, + "hex": "0x00000020438e2b52aed2786fac4ab7df4ca4bb80bbd5a016b6192c000000000000000000f8b338b6c621ad491059ef31ab60d25cb9e6abc1cdaff51aad421e3a2de88aa1cb90195cf41e371764f3e05c", + "height": 554407 + }, + { + "digest": "0x0000000000000000002955f34256d46a3b73171b71d5b4fc384b4b9cf966e46d", + "digest_le": "0x6de466f99c4b4b38fcb4d5711b17733b6ad45642f35529000000000000000000", + "version": 536870912, + "prev_block": "0x0000000000000000000953265f624e5152f43eb96bbed63557f42e6a79d0e8f3", + "merkle_root": "0xe5a6bbbffe2b8dfbbf5e2ae88a9ad09f9ed618c44347fa551e908799519ea07e", + "timestamp": 1545179344, + "nbits": "0xf41e3717", + "difficulty": 5106422924659, + "hex": "0x00000020f3e8d0796a2ef45735d6be6bb93ef452514e625f265309000000000000000000e5a6bbbffe2b8dfbbf5e2ae88a9ad09f9ed618c44347fa551e908799519ea07ed090195cf41e37179d4d56e1", + "height": 554408 + } + ] +} From 60f8c087a7172b91520466a6b43eeedfc610276a Mon Sep 17 00:00:00 2001 From: ferencdg Date: Wed, 10 Jul 2024 08:06:09 +0000 Subject: [PATCH 3/3] chore: Generate Foundry docs --- .../relay/FullRelay.sol/contract.FullRelay.md | 608 ++++++++++++++++++ .../interface.IFullRelay.md | 101 +++ .../contract.FullRelayWithVerify.md | 74 +++ .../contract.TestRelay.md | 89 +++ 4 files changed, 872 insertions(+) create mode 100644 docs/docs/contracts/src/src/relay/FullRelay.sol/contract.FullRelay.md create mode 100644 docs/docs/contracts/src/src/relay/FullRelayInterfaces.sol/interface.IFullRelay.md create mode 100644 docs/docs/contracts/src/src/relay/FullRelayWithVerify.sol/contract.FullRelayWithVerify.md create mode 100644 docs/docs/contracts/src/src/relay/FullRelayWithVerify.sol/contract.TestRelay.md diff --git a/docs/docs/contracts/src/src/relay/FullRelay.sol/contract.FullRelay.md b/docs/docs/contracts/src/src/relay/FullRelay.sol/contract.FullRelay.md new file mode 100644 index 00000000..c40a48f8 --- /dev/null +++ b/docs/docs/contracts/src/src/relay/FullRelay.sol/contract.FullRelay.md @@ -0,0 +1,608 @@ +# FullRelay +[Git Source](https://github.com/bob-collective/bob/blob/master/src/relay/FullRelay.sol) + +**Inherits:** +[IFullRelay](../../relay/FullRelayInterfaces.sol/interface.IFullRelay.md) + +**Author:** +Distributed Crafts (https://www.gobob.xyz/) +Forked from https://github.com/summa-tx/relays +Changes made: +1. dependency changes +- changed summa-tx/bitcoin-spv to keep-network/bitcoin-spv-sol +- remove SafeMath +2. test changes +- fixed some tests that were written incorrectly in the summa repo +- ported Truffle javascript tests to Foundry solidity +- new tests added +3. solidity compiler version upgraded to 0.8.17 +4. OnDemandSPV was gutted and only the verification part was kept + + +## State Variables +### HEIGHT_INTERVAL + +```solidity +uint32 public constant HEIGHT_INTERVAL = 4; +``` + + +### relayGenesis + +```solidity +bytes32 internal relayGenesis; +``` + + +### bestKnownDigest + +```solidity +bytes32 internal bestKnownDigest; +``` + + +### lastReorgCommonAncestor + +```solidity +bytes32 internal lastReorgCommonAncestor; +``` + + +### previousBlock + +```solidity +mapping(bytes32 => bytes32) internal previousBlock; +``` + + +### blockHeight + +```solidity +mapping(bytes32 => uint256) internal blockHeight; +``` + + +### currentEpochDiff + +```solidity +uint256 internal currentEpochDiff; +``` + + +### prevEpochDiff + +```solidity +uint256 internal prevEpochDiff; +``` + + +## Functions +### constructor + +Gives a starting point for the relay + +*We don't check this AT ALL really. Don't use relays with bad genesis* + + +```solidity +constructor(bytes memory _genesisHeader, uint256 _height, bytes32 _periodStart); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_genesisHeader`|`bytes`| The starting header| +|`_height`|`uint256`| The starting height| +|`_periodStart`|`bytes32`| The hash of the first header in the genesis epoch| + + +### isHeaderValidLength + +Checks whether the header is 80 bytes long + + +```solidity +function isHeaderValidLength(bytes memory _header) internal pure returns (bool); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_header`|`bytes`| The header for which the length is checked| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`bool`|True if the header's length is 80 bytes, and false otherwise| + + +### isHeaderChainValidLength + +Checks whether the header chain's length is a multiple of 80 bytes + + +```solidity +function isHeaderChainValidLength(bytes memory _headerChain) internal pure returns (bool); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_headerChain`|`bytes`| The header chain for which the length is checked| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`bool`|True if the header chain's length is a multiple of 80 bytes, and false otherwise| + + +### isMerkleArrayValidLength + +Checks whether the merkle proof array's length is a multiple of 32 bytes + + +```solidity +function isMerkleArrayValidLength(bytes memory _merkleProofArray) internal pure returns (bool); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_merkleProofArray`|`bytes`| The merkle proof array for which the length is checked| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`bool`|True if the merkle proof array's length is a multiple of 32 bytes, and false otherwise| + + +### getCurrentEpochDifficulty + +Getter for currentEpochDiff + +*This is updated when a new heavist header has a new diff* + + +```solidity +function getCurrentEpochDifficulty() external view returns (uint256); +``` +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`uint256`|The difficulty of the bestKnownDigest| + + +### getPrevEpochDifficulty + +Getter for prevEpochDiff + +*This is updated when a difficulty change is accepted* + + +```solidity +function getPrevEpochDifficulty() external view returns (uint256); +``` +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`uint256`|The difficulty of the previous epoch| + + +### getRelayGenesis + +Getter for relayGenesis + +*This is an initialization parameter* + + +```solidity +function getRelayGenesis() public view returns (bytes32); +``` +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`bytes32`|The hash of the first block of the relay| + + +### getBestKnownDigest + +Getter for bestKnownDigest + +*This updated only by calling markNewHeaviest* + + +```solidity +function getBestKnownDigest() public view returns (bytes32); +``` +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`bytes32`|The hash of the best marked chain tip| + + +### getLastReorgCommonAncestor + +Getter for relayGenesis + +*This is updated only by calling markNewHeaviest* + + +```solidity +function getLastReorgCommonAncestor() public view returns (bytes32); +``` +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`bytes32`|The hash of the shared ancestor of the most recent fork| + + +### findHeight + +Finds the height of a header by its digest + +*Will fail if the header is unknown* + + +```solidity +function findHeight(bytes32 _digest) external view returns (uint256); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_digest`|`bytes32`| The header digest to search for| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`uint256`|The height of the header, or error if unknown| + + +### findAncestor + +Finds an ancestor for a block by its digest + +*Will fail if the header is unknown* + + +```solidity +function findAncestor(bytes32 _digest, uint256 _offset) external view returns (bytes32); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_digest`|`bytes32`| The header digest to search for| +|`_offset`|`uint256`|| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`bytes32`|The height of the header, or error if unknown| + + +### isAncestor + +Checks if a digest is an ancestor of the current one + +*Limit the amount of lookups (and thus gas usage) with _limit* + + +```solidity +function isAncestor(bytes32 _ancestor, bytes32 _descendant, uint256 _limit) external view returns (bool); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_ancestor`|`bytes32`| The prospective ancestor| +|`_descendant`|`bytes32`| The descendant to check| +|`_limit`|`uint256`| The maximum number of blocks to check| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`bool`|true if ancestor is at most limit blocks lower than descendant, otherwise false| + + +### addHeaders + +Adds headers to storage after validating + +*We check integrity and consistency of the header chain* + + +```solidity +function addHeaders(bytes calldata _anchor, bytes calldata _headers) external returns (bool); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_anchor`|`bytes`| The header immediately preceeding the new chain| +|`_headers`|`bytes`| A tightly-packed list of 80-byte Bitcoin headers| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`bool`|True if successfully written, error otherwise| + + +### addHeadersWithRetarget + +Adds headers to storage, performs additional validation of retarget + +*Checks the retarget, the heights, and the linkage* + + +```solidity +function addHeadersWithRetarget( + bytes calldata _oldPeriodStartHeader, + bytes calldata _oldPeriodEndHeader, + bytes calldata _headers +) external returns (bool); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_oldPeriodStartHeader`|`bytes`|The first header in the difficulty period being closed| +|`_oldPeriodEndHeader`|`bytes`| The last header in the difficulty period being closed| +|`_headers`|`bytes`| A tightly-packed list of 80-byte Bitcoin headers| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`bool`|True if successfully written, error otherwise| + + +### markNewHeaviest + +Gives a starting point for the relay + +*We don't check this AT ALL really. Don't use relays with bad genesis* + + +```solidity +function markNewHeaviest(bytes32 _ancestor, bytes calldata _currentBest, bytes calldata _newBest, uint256 _limit) + external + returns (bool); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_ancestor`|`bytes32`| The digest of the most recent common ancestor| +|`_currentBest`|`bytes`| The 80-byte header referenced by bestKnownDigest| +|`_newBest`|`bytes`| The 80-byte header to mark as the new best| +|`_limit`|`uint256`| Limit the amount of traversal of the chain| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`bool`|True if successfully updates bestKnownDigest, error otherwise| + + +### _addHeaders + +Adds headers to storage after validating + +*We check integrity and consistency of the header chain* + + +```solidity +function _addHeaders(bytes memory _anchor, bytes memory _headers, bool _internal) internal returns (bool); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_anchor`|`bytes`| The header immediately preceeding the new chain| +|`_headers`|`bytes`| A tightly-packed list of new 80-byte Bitcoin headers to record| +|`_internal`|`bool`| True if called internally from addHeadersWithRetarget, false otherwise| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`bool`|True if successfully written, error otherwise| + + +### _addHeadersWithRetarget + +Extract basic info + +Adds headers to storage, performs additional validation of retarget + +*Checks the retarget, the heights, and the linkage* + + +```solidity +function _addHeadersWithRetarget(bytes memory _oldStart, bytes memory _oldEnd, bytes memory _headers) + internal + returns (bool); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_oldStart`|`bytes`| The first header in the difficulty period being closed| +|`_oldEnd`|`bytes`| The last header in the difficulty period being closed| +|`_headers`|`bytes`| A tightly-packed list of 80-byte Bitcoin headers| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`bool`|True if successfully written, error otherwise| + + +### _findHeight + +Finds the height of a header by its digest + +*Will fail if the header is unknown* + + +```solidity +function _findHeight(bytes32 _digest) internal view returns (uint256); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_digest`|`bytes32`| The header digest to search for| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`uint256`|The height of the header| + + +### _findAncestor + +Finds an ancestor for a block by its digest + +*Will fail if the header is unknown* + + +```solidity +function _findAncestor(bytes32 _digest, uint256 _offset) internal view returns (bytes32); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_digest`|`bytes32`| The header digest to search for| +|`_offset`|`uint256`|| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`bytes32`|The height of the header, or error if unknown| + + +### _isAncestor + +Checks if a digest is an ancestor of the current one + +*Limit the amount of lookups (and thus gas usage) with _limit* + + +```solidity +function _isAncestor(bytes32 _ancestor, bytes32 _descendant, uint256 _limit) internal view virtual returns (bool); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_ancestor`|`bytes32`| The prospective ancestor| +|`_descendant`|`bytes32`| The descendant to check| +|`_limit`|`uint256`| The maximum number of blocks to check| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`bool`|true if ancestor is at most limit blocks lower than descendant, otherwise false| + + +### _markNewHeaviest + +Marks the new best-known chain tip + + +```solidity +function _markNewHeaviest(bytes32 _ancestor, bytes memory _current, bytes memory _new, uint256 _limit) + internal + returns (bool); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_ancestor`|`bytes32`| The digest of the most recent common ancestor| +|`_current`|`bytes`| The 80-byte header referenced by bestKnownDigest| +|`_new`|`bytes`| The 80-byte header to mark as the new best| +|`_limit`|`uint256`| Limit the amount of traversal of the chain| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`bool`|True if successfully updates bestKnownDigest, error otherwise| + + +### _isMostRecentAncestor + +Checks if a digest is an ancestor of the current one + +*Limit the amount of lookups (and thus gas usage) with _limit* + + +```solidity +function _isMostRecentAncestor(bytes32 _ancestor, bytes32 _left, bytes32 _right, uint256 _limit) + internal + view + returns (bool); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_ancestor`|`bytes32`| The prospective shared ancestor| +|`_left`|`bytes32`| A chain tip| +|`_right`|`bytes32`| A chain tip| +|`_limit`|`uint256`| The maximum number of blocks to check| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`bool`|true if it is the most recent common ancestor within _limit, false otherwise| + + +### _heaviestFromAncestor + +Decides which header is heaviest from the ancestor + +*Does not support reorgs above 2017 blocks (:* + + +```solidity +function _heaviestFromAncestor(bytes32 _ancestor, bytes memory _left, bytes memory _right) + internal + view + returns (bytes32); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_ancestor`|`bytes32`| The prospective shared ancestor| +|`_left`|`bytes`| A chain tip| +|`_right`|`bytes`| A chain tip| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`bytes32`|true if it is the most recent common ancestor within _limit, false otherwise| + + diff --git a/docs/docs/contracts/src/src/relay/FullRelayInterfaces.sol/interface.IFullRelay.md b/docs/docs/contracts/src/src/relay/FullRelayInterfaces.sol/interface.IFullRelay.md new file mode 100644 index 00000000..e3fd68d5 --- /dev/null +++ b/docs/docs/contracts/src/src/relay/FullRelayInterfaces.sol/interface.IFullRelay.md @@ -0,0 +1,101 @@ +# IFullRelay +[Git Source](https://github.com/bob-collective/bob/blob/master/src/relay/FullRelayInterfaces.sol) + + +## Functions +### getCurrentEpochDifficulty + + +```solidity +function getCurrentEpochDifficulty() external view returns (uint256); +``` + +### getPrevEpochDifficulty + + +```solidity +function getPrevEpochDifficulty() external view returns (uint256); +``` + +### getRelayGenesis + + +```solidity +function getRelayGenesis() external view returns (bytes32); +``` + +### getBestKnownDigest + + +```solidity +function getBestKnownDigest() external view returns (bytes32); +``` + +### getLastReorgCommonAncestor + + +```solidity +function getLastReorgCommonAncestor() external view returns (bytes32); +``` + +### findHeight + + +```solidity +function findHeight(bytes32 _digest) external view returns (uint256); +``` + +### findAncestor + + +```solidity +function findAncestor(bytes32 _digest, uint256 _offset) external view returns (bytes32); +``` + +### isAncestor + + +```solidity +function isAncestor(bytes32 _ancestor, bytes32 _descendant, uint256 _limit) external view returns (bool); +``` + +### addHeaders + + +```solidity +function addHeaders(bytes calldata _anchor, bytes calldata _headers) external returns (bool); +``` + +### addHeadersWithRetarget + + +```solidity +function addHeadersWithRetarget( + bytes calldata _oldPeriodStartHeader, + bytes calldata _oldPeriodEndHeader, + bytes calldata _headers +) external returns (bool); +``` + +### markNewHeaviest + + +```solidity +function markNewHeaviest(bytes32 _ancestor, bytes calldata _currentBest, bytes calldata _newBest, uint256 _limit) + external + returns (bool); +``` + +## Events +### Extension + +```solidity +event Extension(bytes32 indexed _first, bytes32 indexed _last); +``` + +### NewTip + +```solidity +event NewTip(bytes32 indexed _from, bytes32 indexed _to, bytes32 indexed _gcd); +``` + diff --git a/docs/docs/contracts/src/src/relay/FullRelayWithVerify.sol/contract.FullRelayWithVerify.md b/docs/docs/contracts/src/src/relay/FullRelayWithVerify.sol/contract.FullRelayWithVerify.md new file mode 100644 index 00000000..2388e62a --- /dev/null +++ b/docs/docs/contracts/src/src/relay/FullRelayWithVerify.sol/contract.FullRelayWithVerify.md @@ -0,0 +1,74 @@ +# FullRelayWithVerify +[Git Source](https://github.com/bob-collective/bob/blob/master/src/relay/FullRelayWithVerify.sol) + +**Inherits:** +[FullRelay](../../relay/FullRelay.sol/contract.FullRelay.md) + + +## Functions +### constructor + +Gives a starting point for the relay + +*We don't check this AT ALL really. Don't use relays with bad genesis* + + +```solidity +constructor(bytes memory _genesisHeader, uint256 _height, bytes32 _periodStart) + FullRelay(_genesisHeader, _height, _periodStart); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_genesisHeader`|`bytes`| The starting header| +|`_height`|`uint256`| The starting height| +|`_periodStart`|`bytes32`| The hash of the first header in the genesis epoch| + + +### verifyProof + +Provide a proof of a tx that satisfies some request + +*The caller must specify which inputs, which outputs, and which request* + + +```solidity +function verifyProof(bytes calldata _header, bytes calldata _proof, bytes32 _txId, uint256 _index, uint8 _numConfs) + external + view; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_header`|`bytes`| The header containing the merkleroot committing to the tx| +|`_proof`|`bytes`| The merkle proof intermediate nodes| +|`_txId`|`bytes32`| The transaction id to verify| +|`_index`|`uint256`| The index of the tx in the merkle tree's leaves| +|`_numConfs`|`uint8`| Number of confirmations required| + + +### _getConfs + +Finds the number of headers on top of the argument + +*Bounded to 6400 gas (8 looksups) max* + + +```solidity +function _getConfs(bytes32 _headerHash) internal view virtual returns (uint8); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_headerHash`|`bytes32`| The LE double-sha2 header hash| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`uint8`|The number of headers on top| + + diff --git a/docs/docs/contracts/src/src/relay/FullRelayWithVerify.sol/contract.TestRelay.md b/docs/docs/contracts/src/src/relay/FullRelayWithVerify.sol/contract.TestRelay.md new file mode 100644 index 00000000..91c20c14 --- /dev/null +++ b/docs/docs/contracts/src/src/relay/FullRelayWithVerify.sol/contract.TestRelay.md @@ -0,0 +1,89 @@ +# TestRelay +[Git Source](https://github.com/bob-collective/bob/blob/master/src/relay/FullRelayWithVerify.sol) + +**Inherits:** +[FullRelayWithVerify](../../relay/FullRelayWithVerify.sol/contract.FullRelayWithVerify.md) + + +## State Variables +### isAncestorOverride + +```solidity +bool isAncestorOverride; +``` + + +### isAncestorOverrideRes + +```solidity +bool isAncestorOverrideRes; +``` + + +## Functions +### constructor + +Gives a starting point for the relay + +*We don't check this AT ALL really. Don't use relays with bad genesis* + + +```solidity +constructor(bytes memory _genesisHeader, uint256 _height, bytes32 _periodStart) + FullRelayWithVerify(_genesisHeader, _height, _periodStart); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_genesisHeader`|`bytes`| The starting header| +|`_height`|`uint256`| The starting height| +|`_periodStart`|`bytes32`| The hash of the first header in the genesis epoch| + + +### heaviestFromAncestor + + +```solidity +function heaviestFromAncestor(bytes32 _ancestor, bytes calldata _left, bytes calldata _right) + external + view + returns (bytes32); +``` + +### isMostRecentAncestor + + +```solidity +function isMostRecentAncestor(bytes32 _ancestor, bytes32 _left, bytes32 _right, uint256 _limit) + external + view + returns (bool); +``` + +### setAncestorOverride + + +```solidity +function setAncestorOverride(bool _isAncestorOverride, bool _isAncestorOverrideRes) public; +``` + +### _isAncestor + + +```solidity +function _isAncestor(bytes32 _ancestor, bytes32 _descendant, uint256 _limit) + internal + view + virtual + override + returns (bool); +``` + +### _getConfs + + +```solidity +function _getConfs(bytes32) internal view virtual override returns (uint8); +``` +