Bootstrap Rewards


This document defines for the procedure for bootstrapping a Marlin network with rewards.


Allocation of Bootstrapped Rewards

When a new network starts, it would not receive bootstrapping rewards directly. A Token weighted voting process(TODO: Hyperlink here) is used to allocate rewards to a blockchain platform that propagates blocks through the network.

Network Rewards

Rewards are given to the cluster that publishes the block to the miner who built on top of the previous block and was included in the canonical chain. The correct canonical block is determined by a

Rewards are given to the cluster that publishes the block to the miner who mines the next block in the canonical chain for the whitelisted blockchain protocols used in the network. The correct canonical block is determined by a voting process where the relayers can participate. Original producer can then claim the block reward by producing a witness for the transmission.


Name Type Value

Data Structures

Type Aliases

Name Type Description Examples
Address bytes32 Address of a user on Blockchain


struct Vote {
    Address proposerAddress
    uint salt
    uint currentBlockHeight



mapping(bytes32 => Vote) votesCache


bytes32 proposedBlockId


Bootstrapping rewards are provided to networks to make sure that there is enough incentive for nodes to pariticipate in the network even if the revenue due to forwarding packets is not very high.

The block hash of the associated blockchain and the addresses of miners who mined the blocks are agreed upon by voting on a smart contract.

Proposing a Block

The voting happens in intervals of BLOCK_VOTING_INTERVAL Blocks. Cluster can send a proposal for new block including the addresses of miners who mined blocks in the interval and the blockhash of the last block of the interval. Block can only be proposed for the next target height in the contract.

func proposeBlock(bytes32 blockHash, Address[] minerAddresses):
    if minerAddresses.length != BLOCK_VOTING_INTERVAL
    BlockVotingContract::submitBlockProposal(minerAddresses, blockHash).send(this.privKey)

Create Vote for Block

Clusters can vote on any proposal for the current target height. Relayer has to create a sealed vote by hashing proposerAddress, currentBlockHeight in the contract and random salt.

func createSealedVote(Vote vote):
    sealedVoteId = Keccak256(vote.proposerAddress, vote.currentContractBlockHeight, vote.salt)
    votesCache[sealedVoteId] = vote
    return sealedVoteId

Commit Vote

Sealed vote created by any cluster should be submitted to the BlockVotingContract.

func submitVote(uint blockHeight, bytes32 sealedVote):
    BlockVotingContract::sealedVote(blockHeight, sealedVote).send(this.privKey)

Reveal vote

The sealed vote can be revealed by the relayer by submitting the proposerAddresses, currentContractBlockHeight and salt used to create the vote which can be verified by the Block voting contract.

func revealVote(bytes32 sealedVoteId)
    vote = votesCache[sealedVoteId]
    BlockVotingContract::revealVote(vote.proposerAddress, vote.currentContractBlockHeight, vote.salt)

Rewarding the relayers

Clusters who provided block to the miner who mined the block can submit an attestation to the contract with necessary witness to claim the reward. Though an attestation doesn't guarentee that a message is relayer to the consumers. It is handled by the performance dispute contract as the data is claimed to be propagated through the network.

Note: We are working towards an approach where miner, who received the block from Marlin should include a noop tx specifying the clusterId through which the miner has received the block. Clusters can prove that the transaction was included in the block to claim the block reward.

func claimReward(Attestation attestation, Chunk chunk, uint256 blockHeight):
    BootstrapRewards::claimReward(attestation, blockHeight, chunk.getHeader()).send(self.privKey)

Bootstrap Rewards contract

struct Proposal {
    bytes32[] blockHash;
    uint blockHeight;
    uint voteCount;
    uint timestamp;
struct Claim {
    address claimer
uint currentBlockHeight
map((uint blockHeight, address proposer) => Proposal) proposals
map((uint blockHeight, address sender) -> bytes32 hashedVote) committedVotes
map(uint blockHeight -> Proposal proposal) blockAtHeight
map(bytes32 messageId => (uint chunkIndex => (Claim claim))) claims
func submitBlockProposal(blockHashes, blockHeight, clusterId):
    require(blockHeight > currentBlockHeight)
    require(blockHashes.length == BLOCK_VOTING_INTERVAL)
    // only clusters can submit block proposals
    require(ClusterMarketplace.getClusterAddress(clusterId) == msg.sender)
    proposals[blockHeight][msg.sender] = Proposal(blockHashes, blockHeight, 0, now)
    emit ProposalCreated(msg.sender, blockHashes, blockHeight)
func sealedVote(blockHeight, hashedVote):
    committedVotes[(blockHeight, msg.sender)] = hashedVote
func revealVote(proposerAddress, blockHeight, salt):
    require(keccak256(proposerAddress, blockHeight, salt) == committedVotes[(blockHeight, proposerAddress)])
    proposals[(blockHeight, msg.sender)].voteCount++
    delete committedVotes[(blockHeight, proposerAddress)]
func finalizeVote(proposerAddress, blockHeight):
    Proposal proposal = proposals[(blockHeight, proposerAddress)]
    require(now > proposal.timestamp + BLOCK_FINALIZATION_INTERVAL && proposal.voteCount > ClusterMarketplace.noOfClusters*PERCENTAGE_VOTES_FOR_FINALITY/100)
    delete proposals[blockHeight]
    proposal.timestamp = now
    blockAtHeight[blockHeight] = proposal
func submitRewardClaim(messageId, blockNo, chunkIndex, clusterId):
    // Verify if the cluster claiming is in fact claiming for correct messageId which is linked to block has so can't be faked
    bytes32 targetHash = keccak256(messageId + chunkHeader.chunkIndex)
    blockProposal = blockAtHeight[(1+(blockNo/BLOCK_VOTING_INTERVAL))* BLOCK_VOTING_INTERVAL]
    require(blockProposal.timestamp > 0 && blockProposal.blockHashes[blockNo%BLOCK_VOTING_INTERVAL].substring(0,8) == messageId)
    Claim currentClaim = claims[blockNo][chunkHeader.chunkIndex]
    // TODO: Cluster with the maximum hash gets the reward regardless of who propagates, Better way to do this allocation of reward ?
    if currentClaim.clusterId == 0 || abs(currentClaim.claimer - targetHash) > abs(attestation.clusterId - targetHash) 
        claims[blockheight][chunkHeader.chunkIndex] = Claim(attestation.clusterId)
func claimReward(blockHeight, chunkIndex, withdrawalAddress):
    require(now - blockAtHeight(blockHeight).timestamp > REWARD_CLAIM_INTERVAL)
    require(msg.sender = claims[blockHeight][chunkIndex].claimer)
    LINToken.send(withdrawalAddress, REWARD)