Producer

Introduction

This document defines the protocol used to setup and teardown connections for the Producer.

Constants

Name Type Value
PRODUCER_STAKING_CONTRACT_ADDRESS bytes32 TBD
PRODUCER_STAKE_THRESHOLD uint TBD
PRODUCER_EXIT_WAIT_TIME uint TBD
STAKE_PER_BYTE uint TBD
STAKE_REFRESH_INTERVAL uint TBD

Data Structures

Producer

struct Producer {
    uint stake
    bytes32 withdrawalAddress
}

Storage

exitBlock

uint exitBlock

Operations

Producer Staking Contract

Producer Staking contract is used to lock stake of the producer. Relayer will only trasmit messages if producer already has stake locked with the contract and there are no exit requests for the stake. There is a waiting period of PRODUCER_EXIT_WAIT_TIME to exit before which stake cannot be withdrawn after the exit request.

Data Structures

struct Producer {
    uint256 stake;
    address withdrawalAddress;
    bytes32 minerAddress;
}
Storage
mapping(address => Producer) lockedStake;
mapping(address => uint256) unlockRequests;
Methods
function lockStake(address withdrawalAddress, uint256 stake, bytes32 minerAddress, bytes[] minerSignedMessage, uint256 minerAddressType):
    require(!lockedStake[msg.sender]);
    require(stake >= PRODUCER_STAKE_THRESHOLD);
    require(verifySignature(bytes32 minerAddress, bytes[] minerSignedMessage, uint256 minerAddressType)); // find a random message such that same signature cannot be reused by others or can be frontrun
    // message can contain the address of producer then all problems will be solved
    require(LINTokenContract.transferFrom(msg.sender, address(this), stake));
    lockedStake[msg.sender] = Producer(stake, withdrawalAddress, minerAddress); 
    emit ProducerStakeLocked(msg.sender, withdrawalAddress, stake)
function requestStakeUnlock():
    require(lockedStake[msg.sender]);
    require(!unlockRequests[msg.sender]);
    unlockRequests[msg.sender] = block.number;
    emit ProducerStakeUnlockRequested(msg.sender, block.number);
function unlockStake():
    require(unlockRequests[msg.sender] + PRODUCER_EXIT_WAIT_TIME >= block.number);
    uint stake = lockedStake[msg.sender].stake;
    delete lockedStake[msg.sender];
    delete unlockRequests[msg.sender];
    require(LINTokenContract.transfer(msg.sender, stake));
    emit ProducerExited(msg.sender, stake);

Setup

Allocate Stake

Producer of message stakes LIN with the network before joining. This stake is used to slash the producer in case the messages are spam. Stake should be allocated in the LIN contract for ProducerStakingContract to lock stake.

func allocateStake(bytes32 stake):
    LINTokenContract::approve(PRODUCER_STAKING_CONTRACT_ADDRESS, stake).send(self.privKey)

Stake LIN

As the stake is allocated to the ProducerStakingContract, producer can send a transaction to the lockStake method of ProducerStakingContract.

func lockStake(bytes32 stakerAddress, uint stake):
    ProducerStakingContract::lockStake(stakerAddress, stake).send(self.privKey)
func discoverPeers():
    ClusterId[] clusters = getClusterList()
    for cluster in clusters
        PeerId peerId = findPeer(cluster)
        Peer(peerId).connect()
func findPeer(bytes[] clusterId):
    // TODO: Implement cluster beacon in cluster spec
    return ClusterBeacon::findPeer(locationId)
func getClusterList():
    ClusterId[] clusterCreations = ClusterMarketplace::Events::ClusterCreated.filter('clusterId')
    ClusterId[] clusterClosureRequests = ClusterMarketplace::Events::ClusterClosureRequested.filter('clusterId')
    return getActiveClusters(clusterCreations, clusterClosureRequests)
func getActiveClusters(ClusterId[] clustersCreated, ClusterId[] clustersClosed):
   // Get clusters which are created but not closed by comparing both arrays
func joinNetwork():
    if !isProducerRegistered
        producerStake = getInputFromUser('producer_stake')
        allocateStake(producerStake)
        lockStake(this.stakerAddress, producerStake)
        self.acceptConnections = true
        self.totalStakeTokens = producerStake/STAKE_PER_BYTE
        self.currentStakeOffset = 0
    StakeRefreshIntervalTimer(STAKE_REFRESH_INTERVAL)
    discoverPeers()
func onStakeRefreshIntervalTimer():
    self.currentStakeOffset = 0

Teardown

Propose Exit

Producer can propose an exit by sending a transaction to ProducerStakingContract.

func proposeExit():
    ProducerStakingContract::proposeExit().send(self.privKey)

Wait and Exit

Producer should then wait for PRODUCER_EXIT_WAIT_TIME before the stake can be released. Proposer can send a transaction to ProducerStakingContract once the wait time is passed to collect the stake to the withdrawal address mentioned when registering.

func onProducerStakeUnlockRequested(bytes32 producer):
    if producer == self.pubKey:
        exitBlock = event.block + RELAYER_EXIT_WAIT_TIME
        disconnectPeers()
func disconnectPeers():
    for clusterPeers in ClusterMemberList:
        for peer in clusterPeers
            Peer(peer).disconnect()
            delete ClusterMemberList[clusterPeers][peer]
func onExitWaitCompleted():
    ProducerStakingContract::exit().send(self.privKey)

Performance Voting

Producer needs to send the list of acknowlegements received for a message to ensure that producer has in fact sent the data to significant number of clusters and haven't withheld the message.

Verify Acknowledgement

Producer listens for VerifyAckRequest event from the dispute resolution contract and submits the acknowledgments within a specified period.

func onDisputeRaised(bytes[][] producers):
    uint producerIndex = producers.find(this.pubKey)
    if producerIndex == -1:
        return
    PerfDisputeContract::verifyAcks(messageId, producerIndex, acks)