Functions: Storage and Verification
initialize
Initializes BTC-Relay with the first Bitcoin block to be tracked and initializes all data structures (see Data Model).
Note
BTC-Relay does not have to be initialized with Bitcoin’s genesis block! The first block to be tracked can be selected freely.
Warning
Caution when setting the first block in BTC-Relay: only succeeding blocks can be submitted and predecessors and blocks from other chains will be rejected! Similarly, caution is required with the initial block height argument, since if an incorrect value is used, all subsequently reported block heights will be incorrect.
Specification
Function Signature
initialize(relayer, rawBlockHeader, blockHeight)
Parameters
relayer: the account submitting the blockrawBlockHeader: 80 byte raw Bitcoin block header, see RawBlockHeader.blockHeight: integer Bitcoin block height of the submitted block header
Events
Initialized(blockHeight, blockHash, relayer): if the first block header was stored successfully, emit an event with the stored block’s height (blockHeight) and the (PoW) block hash (blockHash).
Errors
ERR_ALREADY_INITIALIZED = "Already initialized": return error if this function is called after BTC-Relay has already been initialized.
Preconditions
This is the first time this function is called, i.e., when BTC-Relay is being deployed.
The blockheader MUST be parsable.
blockHeightMUST match the height on the bitcoin chain. Note that the parachain can not check this - it’s the caller’s responsability!rawBlockHeaderMUST match a block on the bitcoin main chain. Note that the parachain can not check this - it’s the caller’s responsability!rawBlockHeaderMUST be a block mined after December 2015 - see Block Headers. This is NOT checked by the parachain - it’s the caller’s responsibility!
Postconditions
Let blockHeader be the parsed rawBlockHeader. Then:
ChainsIndex[0]MUST be set to a newBlockChainvalue, whereBlockChain.chainId = 0and``BlockChain.startHeight = BlockChain.maxHeight = blockHeight``A value
blockof typeRichBlockHeaderMUST be added toBlockHeaders, where:block.basic_block_header = blockHeaderblock.chainRef = 0block.paraHeightis the current activeBlockCount (see the Security module)block.blockHeight = blockHeight
BestBlockHeightMUST beChainsIndex[0].maxHeightBestBlockMUST beblockHeader.hashStartBlockHeightMUST be set toblockHeight
storeBlockHeader
Method to submit block headers to the BTC-Relay. This function calls verifyBlockHeader to check that the block header is valid. If so, from the block header and stores the hash, height and Merkle tree root of the given block header in BlockHeaders.
If the block header extends an existing BlockChain entry in Chains, it appends the block hash to the chains mapping and increments the maxHeight. Otherwise, a new Blockchain entry is created.
Specification
Function Signature
storeBlockHeader(relayer, rawBlockHeader)
Parameters
relayer: the account submitting the blockrawBlockHeader: 80 byte raw Bitcoin block header, see RawBlockHeader.
Events
StoreMainChainHeader(blockHeight, blockHash, relayer): if the block header was successful appended to the currently longest chain (main chain) emit an event with the stored block’s height (blockHeight) and the (PoW) block hash (blockHash).StoreForkHeader(forkId, blockHeight, blockHash, relayer): if the block header was successful appended to a new or existing fork, emit an event with the block height (blockHeight) and the (PoW) block hash (blockHash).
Invariants
The values in
ChainsMUST be such that for each0 < i < j,ChainsIndex[Chains[i]].maxHeight >= ChainsIndex[Chains[j]].maxHeight.The keys in
ChainsMUST be consecutive, i.e. for eachi, ifChains[i]does not exist,Chains[i+1]MUST NOT exist either.The keys in
ChainsIndexMUST be consecutive, i.e. for eachi, ifChainsIndex[i]does not exist,ChainsIndex[i+1]MUST NOT exist either.For all
i > 0the following MUST hold: ChainsIndex[i].maxHeight < ChainsIndex[0].maxHeight + STABLE_BITCOIN_CONFIRMATIONS.For all
i, the following MUST hold:ChainsIndex[i].chainRef == i.BestBlock.chainRefMUST be 0BestBlock.blockHeightMUST beChainsIndex[0].maxHeightBestBlockHeightMUST beChainsIndex[0].maxHeight
Preconditions
The BTC Parachain status MUST NOT be set to
SHUTDOWN: 3.The given
rawBlockHeaderMUST parse be parsable intoblockHeader.There MUST be a block header
prevHeaderstored inBlockHeaderswith a hash equal toblockHeader.hashPrevBlock.A block chain
prevBlockchainMUST be stored inChainsIndex[prevHeader.chainRef].verifyBlockHeader MUST return
Okwhen called withblockHeader,prevHeader.blockHeight + 1andprevHeader.- If
prevHeaderis the last element a chain (i.e.blockHeaderdoes not create a new fork), then: prevBlockChainMUST NOT already contain a block of heightprevHeader.blockHeight + 1.If
prevBlockChain.chain_idis _not_ zero (i.e. the block is being added to a fork rather than the main chain), and the fork isSTABLE_BITCOIN_CONFIRMATIONSblocks ahead of the main chain, then calling swapMainBlockchain with this fork MUST returnOk.
- If
Postconditions
If
prevHeaderis the last element a chain (i.e.blockHeaderdoes not create a new fork), then:ChainsHashes[prevBlockChain.chain_id, prevHeader.blockHeight + 1]MUST be set toblockHeader.hash.ChainsIndex[prevBlockChain.chain_id].max_heightMUST be increased by 1.If
prevBlockChain.chain_idis zero (i.e. the a block is being added to the main chain), then:BestBlockMUST be set toblockHeader.hashBestBlockHeightMUST be set toprevHeader.blockHeight + 1
If
prevBlockChain.chain_idis _not_ zero (i.e. the block is being added to a fork rather than the main chain), then:If the fork is
STABLE_BITCOIN_CONFIRMATIONSblocks ahead of the main chain, i.e.prevHeader.blockHeight + 1 >= BestBlockHeight + STABLE_BITCOIN_CONFIRMATIONS, then the fork is moved to the mainchain. That is, swapMainBlockchain MUST be called with the fork as argument.
A new
RichBlockHeaderMUST be stored inBlockHeadersthat is constructed as follows:RichBlockHeader.blockHeader = blockHeader,RichBlockHeader.blockHeight = prevBlock.blockHeight + 1,RichBlockHeader.chainRef = prevBlockChain.chainId,RichBlockHeader.paraHeightis set to the current active block count - see the security module for details
If
prevHeaderis not the last element a chain (i.e.blockHeadercreates a new fork), then:ChainCounterMUST be incremented. LetnewChainCounterbe the incremented value, thenChainsHashes[newChainCounter, prevHeader.blockHeight + 1]MUST be set toblockHeader.hash.A new blockchain MUST be inserted into
ChainsIndex. LetnewChainbe the newly inserted chain. ThennewChainMUST have the following values:newChain.chainId = newChainCounter,newChain.startHeight = prevHeader.blockHeight + 1,newChain.maxHeight = prevHeader.blockHeight + 1,
A new value MUST be added to
Chainsthat is equal tonewChainCounterin a way that maintains the invariants specified above.A new
RichBlockHeaderMUST be stored inBlockHeadersthat is constructed as follows:RichBlockHeader.blockHeader = blockHeader,RichBlockHeader.blockHeight = newChain.blockHeight + 1,RichBlockHeader.chainRef = prevBlockChain.chainId,RichBlockHeader.paraHeightis set to the current active block count - see the security module for details
BestBlockHeightMUST be set toChains[0].max_heightBestBlockMUST be set toChainsHashes[0, Chains[0].max_height
Warning
The BTC-Relay does not necessarily have the same view of the Bitcoin blockchain as the user’s local Bitcoin client. This can happen if (i) the BTC-Relay is under attack, (ii) the BTC-Relay is out of sync, or, similarly, (iii) if the user’s local Bitcoin client is under attack or out of sync (see Security).
Note
The 80 bytes block header can be retrieved from the bitcoin-rpc client by calling the getBlock and setting verbosity to 0 (getBlock <blockHash> 0).
swapMainBlockchain
Specification
Function Signature
swapMainBlockchain(fork)
Parameters
fork: pointer to aBlockChainentry inChains.
Preconditions
forkisSTABLE_BITCOIN_CONFIRMATIONSblocks ahead of the main chain, i.e.fork.maxHeight >= BestBlockHeight + STABLE_BITCOIN_CONFIRMATIONS
Postconditions
Let lastBlock be the last rich block header in fork, i.e. the blockheader for which lastBlock.blockHeight == fork.maxHeight and lastBlock.chainRef == fork.chainId holds. Then:
Each ancestor
aoflastBlockMUST move to the main chain, i.e.a.chainRefMUST be set toMAIN_CHAIN_ID.ChainsIndex[MAIN_CHAIN_ID].maxHeightMUST be set tolastBlock.blockHeight.Each fork
forkexcept the main chain that contains an ancestor oflastBlockMUST setfork.startHeightto the lowestblockHeightin the fork that is not an ancestor oflastBlock.Each block
bin the mainchain that is not an acestor oflastBlockMUST move toprevBlockChain, i.e.b.chainRef = prevBlockChain.chainId.prevBlockChain.startHeightMUST be set to the lowestblockHeightof all blocksbthat haveb.chainRef == prevBlockChain.chainId.prevBlockChain.maxHeightMUST be set to the highestblockHeightof all blocksbthat haveb.chainRef == prevBlockChain.chainId.
The figure below ilustrates an example execution of this function.
Fig. 11 On the left is an example of the state of ChainsIndex prior to calling swapMainBlockchain, and on the right is the corresponding state after the function returns.
In contrast the the figure about, when looking up the chains through the Chains map, the chains are sorted by maxHeight, and the same execution would look as follows:
Fig. 12 On the left is an example of the state of Chains prior to calling swapMainBlockchain, and on the right is the corresponding state after the function returns.
verifyBlockHeader
The verifyBlockHeader function verifies Bitcoin block headers. It returns Ok if the blockheader is valid, otherwise an error.
Note
This function does not check whether the submitted block header extends the main chain or a fork. This check is performed in storeBlockHeader.
Specification
Function Signature
verifyBlockHeader(blockHeader, blockHeight, prevBlockHeader)
Parameters
blockHeader: the BlockHeader to check.blockHeight: height of the block.prevBlockHeader: the RichBlockHeader that is the block header’s predecessor.
Returns
Ok(())if all checks pass successfully, otherwise an error.
Errors
ERR_DUPLICATE_BLOCK = "Block already stored": return error if the submitted block header is already stored in BTC-Relay (duplicate PoWblockHash).ERR_LOW_DIFF = "PoW hash does not meet difficulty target of header": return error when the header’sblockHashdoes not meet thetargetspecified in the block header.ERR_DIFF_TARGET_HEADER = "Incorrect difficulty target specified in block header": return error if thetargetspecified in the block header is incorrect for its block height (difficulty re-target not executed).
Preconditions
A block with the
blockHeader.hashMUST NOT already have been stored.blockHeader.hashMUST be be belowBlockHeader.targetblockHeader.targetMUST match the expected target, which is calculated based on previous targets and timestamps. See the Bitcoin Wiki for more information.
Postconditions
Ok(())MUST be returned.
verifyTransactionInclusion
The verifyTransactionInclusion function is one of the core components of the BTC-Relay: this function checks if a given transaction was indeed included in a given block (as stored in BlockHeaders and tracked by Chains), by reconstructing the Merkle tree root (given a Merkle proof). Also checks if sufficient confirmations have passed since the inclusion of the transaction (considering the current state of the BTC-Relay Chains).
Specification
Function Signature
verifyTransactionInclusion(txId, merkleProof, confirmations, insecure)
Parameters
txId: 32 byte hash identifier of the transaction.merkleProof: Merkle tree path (concatenated LE sha256 hashes, dynamic sized).confirmations: integer number of confirmation required.
Note
The Merkle proof for a Bitcoin transaction can be retrieved using the bitcoin-rpc gettxoutproof method and dropping the first 170 characters. The Merkle proof thereby consists of a list of SHA256 hashes, as well as an indicator in which order the hash concatenation is to be applied (left or right).
Returns
True: if the giventxIdappears in at the position specified bytxIndexin the transaction Merkle tree of the block at heightblockHeightand sufficient confirmations have passed since inclusion.Error otherwise.
Events
VerifyTransaction(txId, txBlockHeight, confirmations): if verification was successful, emit an event specifying thetxId, theblockHeightand the requested number ofconfirmations.
Errors
ERR_SHUTDOWN = "BTC Parachain has shut down": the BTC Parachain has been shutdown by a manual intervention of the Governance Mechanism.ERR_MALFORMED_TXID = "Malformed transaction identifier": return error if the transaction identifier (txId) is malformed.ERR_CONFIRMATIONS = "Transaction has less confirmations than requested": return error if the block in which the transaction specified bytxIdwas included has less confirmations than requested.ERR_INVALID_MERKLE_PROOF = "Invalid Merkle Proof": return error if the Merkle proof is malformed or fails verification (does not hash to Merkle root).ERR_ONGOING_FORK = "Verification disabled due to ongoing fork": return error if themainChainis not at leastSTABLE_BITCOIN_CONFIRMATIONSahead of the next best fork.
Preconditions
The BTC Parachain status must not be set to
SHUTDOWN: 3. IfSHUTDOWNis set, all transaction verification is disabled.
Function Sequence
Check that
txIdis 32 bytes long. ReturnERR_MALFORMED_TXIDerror if this check fails.Check that the current
BestBlockHeightexceedstxBlockHeightby the requested confirmations. ReturnERR_CONFIRMATIONSif this check fails.
If
insecure == True, check against user-definedconfirmationsonlyIf
insecure == True, check againstmax(confirmations, STABLE_BITCOIN_CONFIRMATIONS).
Check if the Bitcoin block was stored for a sufficient number of blocks (on the parachain) to ensure that staked relayers had the time to flag the block as potentially invalid. Check performed against
STABLE_PARACHAIN_CONFIRMATIONS.Extract the block header from
BlockHeadersusing theblockHashtracked inChainsat the passedtxBlockHeight.Check that the first 32 bytes of
merkleProofare equal to thetxIdand the last 32 bytes are equal to themerkleRootof the specified block header. Also check that themerkleProofsize is either exactly 32 bytes, or is 64 bytes or more and a power of 2. ReturnERR_INVALID_MERKLE_PROOFif one of these checks fails.Call computeMerkle passing
txId,txIndexandmerkleProofas parameters.
If this call returns the
merkleRoot, emit aVerifyTransaction(txId, txBlockHeight, confirmations)event and returnTrue.Otherwise return
ERR_INVALID_MERKLE_PROOF.
Fig. 13 The steps to verify a transaction in the verifyTransactionInclusion function.
validateTransaction
Given a raw Bitcoin transaction, this function
Parses and extracts
the value and recipient address of the Payment UTXO,
[Optionally] the OP_RETURN value of the Data UTXO.
Validates the extracted values against the function parameters.
Note
See Bitcoin Data Model for more details on the transaction structure, and Accepted Bitcoin Transaction Format for the transaction format of Bitcoin transactions validated in this function.
Specification
Function Signature
validateTransaction(rawTx, paymentValue, recipientBtcAddress, opReturnId)
Parameters
rawTx: raw Bitcoin transaction including the transaction inputs and outputs.paymentValue: integer value of BTC sent in the (first) Payment UTXO of transaction.recipientBtcAddress: 20 byte Bitcoin address of recipient of the BTC in the (first) Payment UTXO.opReturnId: [Optional] 32 byte hash identifier expected in OP_RETURN (see Replay Attacks).
Returns
True: if the transaction was successfully parsed and validation of the passed values was correct.Error otherwise.
Events
ValidateTransaction(txId, paymentValue, recipientBtcAddress, opReturnId): if parsing and validation was successful, emit an event specifying thetxId, thepaymentValue, therecipientBtcAddressand theopReturnId.
Errors
ERR_INSUFFICIENT_VALUE = "Value of payment below requested amount": return error the value of the (first) Payment UTXO is lower thanpaymentValue.ERR_TX_FORMAT = "Transaction has incorrect format": return error if the transaction has an incorrect format (see Accepted Bitcoin Transaction Format).ERR_WRONG_RECIPIENT = "Incorrect recipient Bitcoin address": return error if the recipient specified in the (first) Payment UTXO does not match the givenrecipientBtcAddress.ERR_INVALID_OPRETURN = "Incorrect identifier in OP_RETURN field": return error if the OP_RETURN field of the (second) Data UTXO does not match the givenopReturnId.
Preconditions
The BTC Parachain status must not be set to
SHUTDOWN: 3. IfSHUTDOWNis set, all transaction validation is disabled.
Function Sequence
See the raw Transaction Format section in the Bitcoin Developer Reference for a full specification of Bitcoin’s transaction format (and how to extract inputs, outputs etc. from the raw transaction format).
Extract the
outputsfromrawTxusing extractOutputs.
Extract the value of the Payment UTXO using extractOutputValue and check that it is equal (or greater) than
paymentValue. ReturnERR_INSUFFICIENT_VALUEif this check fails.Extract the Bitcoin address specified as recipient in the Payment UTXO using extractOutputAddress and check that it matches
recipientBtcAddress. ReturnERR_WRONG_RECIPIENTif this check fails, or the error returned by extractOutputAddress (if the output was malformed).Extract the OP_RETURN value from the Data UTXO using extractOPRETURN and check that it matches
opReturnId. ReturnERR_INVALID_OPRETURNerror if this check fails, or the error returned by extractOPRETURN (if the output was malformed).
verifyAndValidateTransaction
The verifyAndValidateTransaction function is a wrapper around the verifyTransactionInclusion and the validateTransaction functions. It adds an additional check to verify that the validated transaction is the one included in the specified block.
Specification
Function Signature
verifyAndValidateTransaction(merkleProof, confirmations, rawTx, paymentValue, recipientBtcAddress, opReturnId)
Parameters
txId: 32 byte hash identifier of the transaction.merkleProof: Merkle tree path (concatenated LE sha256 hashes, dynamic sized).confirmations: integer number of confirmation required.rawTx: raw Bitcoin transaction including the transaction inputs and outputs.paymentValue: integer value of BTC sent in the (first) Payment UTXO of transaction.recipientBtcAddress: 20 byte Bitcoin address of recipient of the BTC in the (first) Payment UTXO.opReturnId: [Optional] 32 byte hash identifier expected in OP_RETURN (see Replay Attacks).
Returns
True: If the same transaction has been verified and validated.Error otherwise.
Function Sequence
Parse the
rawTxto get the tx id.Call verifyTransactionInclusion with the applicable parameters.
Call validateTransaction with the applicable parameters.
flagBlockError
Flags tracked Bitcoin block headers when Staked Relayers report and agree on a NO_DATA_BTC_RELAY or INVALID_BTC_RELAY failure.
Attention
This function does not validate the Staked Relayers accusation. Instead, it is put up to a majority vote among all Staked Relayers in the form of a
Note
This function can only be called from the Security module of interBTC, after Staked Relayers have achieved a majority vote on a BTC Parachain status update indicating a BTC-Relay failure.
Specification
Function Signature
flagBlockError(blockHash, errors)
Parameters
blockHash: SHA256 block hash of the block containing the error.errors: list ofErrorCodeentries which are to be flagged for the block with the given blockHash. Can be “NO_DATA_BTC_RELAY” or “INVALID_BTC_RELAY”.
Events
FlagBTCBlockError(blockHash, chainId, errors)- emits an event indicating that a Bitcoin block hash (identifiedblockHash) in aBlockChainentry (chainId) was flagged with errors (errorslist ofErrorCodeentries).
Errors
ERR_UNKNOWN_ERRORCODE = "The reported error code is unknown": The reportedErrorCodecan only beNO_DATA_BTC_RELAYorINVALID_BTC_RELAY.ERR_BLOCK_NOT_FOUND = "No Bitcoin block header found with the given block hash": NoRichBlockHeaderentry exists with the given block hash.ERR_ALREADY_REPORTED = "This error has already been reported for the given block hash and is pending confirmation": The error reported for the given block hash is currently pending a vote by Staked Relayers.
Function Sequence
Check if
errorscontainsNO_DATA_BTC_RELAYorINVALID_BTC_RELAY. If neither match, returnERR_UNKNOWN_ERRORCODE.Retrieve the
RichBlockHeaderentry fromBlockHeadersusingblockHash. ReturnERR_BLOCK_NOT_FOUNDif no block header can be found.Retrieve the
BlockChainentry for the givenRichBlockHeaderusingChainsIndexfor lookup with the block header’schainRefas key.Flag errors in the
BlockChainentry:If
errorscontainsNO_DATA_BTC_RELAY, append theRichBlockHeader.blockHeighttoBlockChain.noDataIf
errorscontainsINVALID_BTC_RELAY, append theRichBlockHeader.blockHeighttoBlockChain.invalid.
Emit
FlagBTCBlockError(blockHash, chainId, errors)event, with the givenblockHash, thechainIdof the flaggedBlockChainentry and the givenerrorsas parameters.Return
clearBlockError
Clears ErrorCode entries given as parameters from the status of a RichBlockHeader. Can be NO_DATA_BTC_RELAY or INVALID_BTC_RELAY failure.
Note
This function can only be called from the Security module of interBTC, after Staked Relayers have achieved a majority vote on a BTC Parachain status update indicating that a RichBlockHeader entry no longer has the specified errors.
Specification
Function Signature
flagBlockError(blockHash, errors)
Parameters
blockHash: SHA256 block hash of the block containing the error.errors: list ofErrorCodeentries which are to be cleared from the block with the given blockHash. Can beNO_DATA_BTC_RELAYorINVALID_BTC_RELAY.
Events
ClearBlockError(blockHash, chainId, errors)- emits an event indicating that a Bitcoin block hash (identifiedblockHash) in aBlockChainentry (chainId) was cleared from the given errors (errorslist ofErrorCodeentries).
Errors
ERR_UNKNOWN_ERRORCODE = "The reported error code is unknown": The reportedErrorCodecan only beNO_DATA_BTC_RELAYorINVALID_BTC_RELAY.ERR_BLOCK_NOT_FOUND = "No Bitcoin block header found with the given block hash": NoRichBlockHeaderentry exists with the given block hash.ERR_ALREADY_REPORTED = "This error has already been reported for the given block hash and is pending confirmation": The error reported for the given block hash is currently pending a vote by Staked Relayers.
Function Sequence
Check if
errorscontainsNO_DATA_BTC_RELAYorINVALID_BTC_RELAY. If neither match, returnERR_UNKNOWN_ERRORCODE.Retrieve the
RichBlockHeaderentry fromBlockHeadersusingblockHash. ReturnERR_BLOCK_NOT_FOUNDif no block header can be found.Retrieve the
BlockChainentry for the givenRichBlockHeaderusingChainsIndexfor lookup with the block header’schainRefas key.Un-flag error codes in the
BlockChainentry.If
errorscontainsNO_DATA_BTC_RELAY: removeRichBlockHeader.blockHeightfromBlockChain.noDataIf
errorscontainsINVALID_BTC_RELAY: removeRichBlockHeader.blockHeightfromBlockChain.invalid
Emit
ClearBlockError(blockHash, chainId, errors)event, with the givenblockHash, thechainIdof the flaggedBlockChainentry and the givenerrorsas parameters.Return