class HDCProtocol(BaseProtocol):

    """
    HydraChain Wire Protocol
    """
    protocol_id = 1
    network_id = 0
    max_cmd_id = 15  # FIXME
    name = 'hdc'
    version = 1
    max_getproposals_count = 10

    def __init__(self, peer, service):
        # required by P2PProtocol
        self.config = peer.config
        BaseProtocol.__init__(self, peer, service)

    class status(BaseProtocol.command):

        """
        protocolVersion: The version of the HydraChain protocol this peer implements.
        networkID: The network version of Ethereum for this peer.
        GenesisHash: The hash of the Genesis block.
        current_lockset: The lockset of the current round from the responding peer
        """
        cmd_id = 0
        sent = False

        structure = [
            ('eth_version', rlp.sedes.big_endian_int),
            ('network_id', rlp.sedes.big_endian_int),
            ('genesis_hash', rlp.sedes.binary),
            ('current_lockset', LockSet)
        ]

        def create(self, proto, genesis_hash, current_lockset):
            self.sent = True
            network_id = proto.service.app.config['eth'].get('network_id', proto.network_id)
            return [proto.version, network_id, genesis_hash, current_lockset]

    class transactions(BaseProtocol.command):

        """
        Specify (a) transaction(s) that the peer should make sure is included on its transaction
        queue. The items in the list (following the first item 0x12) are transactions in the
        format described in the main Ethereum specification. Nodes must not resend the same
        transaction to a peer in the same session. This packet must contain at least one (new)
        transaction.
        """
        cmd_id = 1
        structure = rlp.sedes.CountableList(Transaction)

        # todo: bloomfilter: so we don't send tx to the originating peer

        @classmethod
        def decode_payload(cls, rlp_data):
            # convert to dict
            txs = []
            for i, tx in enumerate(rlp.decode_lazy(rlp_data)):
                txs.append(Transaction.deserialize(tx))
                if not i % 10:
                    gevent.sleep(0.0001)
            return txs

    class getblockproposals(BaseProtocol.command):

        """
        Requests a BlockProposals message detailing a number of blocks to be sent, each referred to
        by block number. Note: Don't expect that the peer necessarily give you all these blocks
        in a single message - you might have to re-request them.
        """
        cmd_id = 2
        structure = rlp.sedes.CountableList(rlp.sedes.big_endian_int)

    class blockproposals(BaseProtocol.command):

        """
        BlockProposals sent in response to a getproposals request
        """
        cmd_id = 3
        structure = rlp.sedes.CountableList(BlockProposal)

        @classmethod
        def encode_payload(cls, list_of_rlp):
            """
            rlp data directly from the database
            """
            assert isinstance(list_of_rlp, tuple)
            assert not list_of_rlp or isinstance(list_of_rlp[0], bytes)
            return rlp.encode([rlp.codec.RLPData(x) for x in list_of_rlp], infer_serializer=False)

    class newblockproposal(BaseProtocol.command):

        """
        Specify a single BlockProposal that the peer should know about.
        """
        cmd_id = 4
        structure = [('proposal', BlockProposal)]

    class votinginstruction(BaseProtocol.command):

        """
        Specify a single VotingInstruction that the peer should know about.
        """
        cmd_id = 5
        structure = [('votinginstruction', VotingInstruction)]

    class vote(BaseProtocol.command):

        """
        Specify a single Vote that the peer should know about.
        """
        cmd_id = 6
        structure = [('vote', Vote)]

    class ready(BaseProtocol.command):
        cmd_id = 7
        structure = [('ready', Ready)] class HDCProtocolError(SubProtocolError):
    pass class Signed(RLPHashable):

    fields = [
        ('v', big_endian_int),
        ('r', big_endian_int),
        ('s', big_endian_int),
    ]

    _sender = None

    def __init__(self, *args, **kargs):
        super(Signed, self).__init__(*args, **kargs)

    def sign(self, privkey):
        """Sign this with a private key"""
        if self.v:
            raise InvalidSignature("already signed")

        if privkey in (0, '', '\x00' * 32):
            raise InvalidSignature("Zero privkey cannot sign")
        rawhash = sha3(rlp.encode(self, self.__class__.exclude(['v', 'r', 's'])))

        if len(privkey) == 64:
            privkey = encode_privkey(privkey, 'bin')

        pk = PrivateKey(privkey, raw=True)
        signature = pk.ecdsa_recoverable_serialize(pk.ecdsa_sign_recoverable(rawhash, raw=True))

        signature = signature[0] + chr(signature[1])

        self.v = ord(signature[64]) + 27
        self.r = big_endian_to_int(signature[0:32])
        self.s = big_endian_to_int(signature[32:64])

        self._sender = None
        return self

    @property
    def sender(self):
        if not self._sender:
            self._sender = self.recover_sender()
        return self._sender

    def recover_sender(self):
        if self.v:
            if self.r >= N or self.s >= P or self.v < 27 or self.v > 28 \
               or self.r == 0 or self.s == 0:
                raise InvalidSignature()
            rlpdata = rlp.encode(self, self.__class__.exclude(['v', 'r', 's']))
            rawhash = sha3(rlpdata)
            pk = PublicKey(flags=ALL_FLAGS)
            try:
                pk.public_key = pk.ecdsa_recover(
                    rawhash,
                    pk.ecdsa_recoverable_deserialize(
                        zpad(
                            "".join(chr(c) for c in int_to_32bytearray(self.r)),
                            32
                        ) + zpad(
                            "".join(chr(c) for c in int_to_32bytearray(self.s)),
                            32
                        ),
                        self.v - 27
                    ),
                    raw=True
                )
                pub = pk.serialize(compressed=False)
            except Exception:
                raise InvalidSignature()
            if pub[1:] == "\x00" * 32:
                raise InvalidSignature()
            pub = encode_pubkey(pub, 'bin')
            return sha3(pub[1:])[-20:]

    @property
    def hash(self):
        "signatures are non deterministic"
        if self.sender is None:
            raise MissingSignatureError()

        class HashSerializable(rlp.Serializable):
            fields = [(field, sedes) for field, sedes in self.fields
                      if field not in ('v', 'r', 's')] + [('_sender', binary)]
            _sedes = None
        return sha3(rlp.encode(self, HashSerializable)) class VotingInstruction(Proposal):

    fields = [
        ('height', big_endian_int),
        ('round', big_endian_int),
        ('round_lockset', LockSet)
    ] + Signed.fields

    def __init__(self, height, round, round_lockset, v=0, r=0, s=0):
        super(VotingInstruction, self).__init__(height, round, round_lockset, v, r, s)
        if not round > 0:
            raise InvalidProposalError('VotingInstructions must have R>0')
        if not self.lockset.has_quorum_possible:
            raise InvalidProposalError('VotingInstruction requires quorum possible')
        if not (round == self.lockset.round + 1):
            raise InvalidProposalError('Rn round lockset must be from previous round')
        if not (height == self.lockset.height):
            raise InvalidProposalError('height mismatch')
        if not round > 0:
            raise InvalidProposalError('VotingInstructions must have R>0')
        assert round == round_lockset.round + 1
        assert height == round_lockset.height

    @property
    def blockhash(self):
        return self.round_lockset.has_quorum_possible

    @property
    def lockset(self):
        return self.round_lockset

    def __repr__(self):
        return "<%s %r BH:%s>" % (self.__class__.__name__, phx(self.sender), phx(self.blockhash))

    def validate_votes(self, validators_H):
        "set of validators may change between heights"
        assert self.sender
        if not self.round_lockset.num_eligible_votes == len(validators_H):
            raise InvalidProposalError('round_lockset num_eligible_votes mismatch')
        for v in self.round_lockset:
            if v.sender not in validators_H:
                raise InvalidProposalError('invalid signer') class BlockProposal(Proposal):

    fields = [
        ('height', big_endian_int),
        ('round', big_endian_int),
        ('block', TransientBlock),
        ('signing_lockset', LockSet),
        ('round_lockset', LockSet)
    ] + Signed.fields

    def __init__(self, height, round, block, signing_lockset,
                 round_lockset=None, v=0, r=0, s=0):
        """
        if round == 0 the signing_lockset also proves,
        that proposal is eligible and we need not round_lockset
        """
        assert isinstance(block, (Block, TransientBlock))
        assert isinstance(signing_lockset, LockSet)
        assert round_lockset is None or isinstance(round_lockset, LockSet)
        assert round >= 0
        assert height > 0
        if round > 0 and not round_lockset:
            raise InvalidProposalError('R>0 needs a round lockset')
        if round == 0 and round_lockset:
            raise InvalidProposalError('R0 must not have a round lockset')
        self.height = height
        self.round = round
        self.block = block
        self.signing_lockset = signing_lockset
        self.round_lockset = round_lockset or LockSet(0)

        super(BlockProposal, self).__init__(height, round, block, signing_lockset,
                                            self.round_lockset, v, r, s)

        if block.header.number != self.height:
            raise InvalidProposalError('lockset.height / block.number mismatch')
        if self.round_lockset and height != self.round_lockset.height:
            raise InvalidProposalError('height mismatch')
        if not (round > 0 or self.lockset.has_quorum):
            raise InvalidProposalError('R0 lockset == signing lockset needs quorum')
        if not (round > 0 or self.lockset.height == block.header.number - 1):
            raise InvalidProposalError('R0 round lockset must be from previous height')
        if not (round == 0 or round == self.lockset.round + 1):
            raise InvalidProposalError('Rn round lockset must be from previous round')
        if not self.signing_lockset.has_quorum:
            raise InvalidProposalError('signing lockset needs quorum')
        if not (self.signing_lockset.height == self.height - 1):
            raise InvalidProposalError('signing lockset height mismatch')
        if self.round_lockset and not round_lockset.has_noquorum:
            raise InvalidProposalError('at R>0 can only propose if there is a NoQuorum for R-1')

        self.rawhash = sha3(rlp.encode(self, self.__class__.exclude(['v', 'r', 's'])))
        if self.v:  # validate sender == block.coinbase
            assert self.sender

    @property
    def lockset(self):
        return self.round_lockset or self.signing_lockset

    @property
    def sender(self):
        # double check unmutable
        s = super(BlockProposal, self).sender
        if not s:
            raise InvalidProposalError('signature missing')
        assert self.rawhash
        assert self.v
        _rawhash = sha3(rlp.encode(self, self.__class__.exclude(['v', 'r', 's'])))
        assert self.rawhash == _rawhash
        assert len(s) == 20
        assert len(self.block.header.coinbase) == 20
        if s != self.block.header.coinbase:
            raise InvalidProposalError('signature does not match coinbase')
        return s

    def sign(self, privkey):
        super(BlockProposal, self).sign(privkey)
        if self.sender != self.block.header.coinbase:
            raise InvalidProposalError('signature does not match coinbase')

    def validate_votes(self, validators_H, validators_prevH):
        "set of validators may change between heights"
        assert self.sender

        def check(lockset, validators):
            if not lockset.num_eligible_votes == len(validators):
                raise InvalidProposalError('lockset num_eligible_votes mismatch')
            for v in lockset:
                if v.sender not in validators:
                    raise InvalidProposalError('invalid signer')
        if self.round_lockset:
            check(self.round_lockset, validators_H)
        check(self.signing_lockset, validators_prevH)

        return True

    def __repr__(self):
        return "<%s S:%r H:%d R:%d BH:%s>" % (self.__class__.__name__, phx(self._sender),
                                              self.height, self.round, phx(self.blockhash))

    @property
    def blockhash(self):
        return self.block.hash class VoteBlock(Vote):
    pass class VoteNil(Vote):
    pass class HDCBlockHeader(BlockHeader):

    def check_pow(self, nonce=None):
        return True class LockSet(RLPHashable):  # careful, is mutable!

    fields = [
        ('num_eligible_votes', big_endian_int),
        ('votes', CountableList(Vote))
    ]

    processed = False

    def __init__(self, num_eligible_votes, votes=None):
        self.num_eligible_votes = num_eligible_votes
        self.votes = []
        for v in votes or []:
            self.add(v)

    # @property
    # def size(self):
    #     return len(self.votes) * 67 + 5

    def copy(self):
        return LockSet(self.num_eligible_votes, self.votes)

    @property
    def state(self):
        if not self.is_valid:
            s = 'I'
        elif self.has_quorum:
            s = 'Q'
        elif self.has_quorum_possible:
            s = 'P'
        elif self.has_noquorum:
            s = 'N'
        else:
            raise Exception('no valid state')
        return '%s:%d' % (s, len(self))

    def __repr__(self):
        if self.votes:
            return '<LockSet(%s H:%d R:%d)>' % (self.state, self.height, self.round)
        return '<LockSet(I:0)>'

    def add(self, vote, force_replace=False):
        assert isinstance(vote, Vote)
        if not vote.sender:
            raise InvalidVoteError('no signature')
        if vote not in self.votes:
            if len(self) and self.hr != vote.hr:
                raise InvalidVoteError('inconsistent height, round')
            signee = self.signee
            if vote.sender in signee:
                if not force_replace:
                    raise DoubleVotingError(vote.sender)  # different votes on the same H,R
                self.votes.remove(self.votes[signee.index(vote.sender)])
            self.votes.append(vote)
            return True

    def __len__(self):
        return len(self.votes)

    def __iter__(self):
        return iter(self.votes)

    @property
    def signee(self):
        return [v.sender for v in self.votes]

    def blockhashes(self):
        assert self.is_valid
        c = Counter(v.blockhash for v in self.votes if isinstance(v, VoteBlock))
        # deterministc sort necessary
        return sorted(c.most_common(), cmp=lambda a, b: cmp((b[1], b[0]), (a[1], a[0])))

    @property
    def hr(self):
        """compute (height,round)
        We might have multiple rounds before we see consensus for a certain height.
        If everything is good, round should always be 0.
        """
        assert len(self), 'no votes, can not determine height'
        h = set([(v.height, v.round) for v in self.votes])
        assert len(h) == 1, len(h)
        return h.pop()

    height = property(lambda self: self.hr[0])
    round = property(lambda self: self.hr[1])

    @property
    def is_valid(self):
        return len(self) > 2 / 3. * self.num_eligible_votes and self.hr

    @property
    def has_quorum(self):
        """
        we've seen +2/3 of all eligible votes voting for one block.
        there is a quorum.
        """
        assert self.is_valid
        bhs = self.blockhashes()
        if bhs and bhs[0][1] > 2 / 3. * self.num_eligible_votes:
            return bhs[0][0]

    @property
    def has_noquorum(self):
        """
        less than 1/3 of the known votes are on the same block
        """
        assert self.is_valid
        bhs = self.blockhashes()
        if not bhs or bhs[0][1] <= 1 / 3. * self.num_eligible_votes:
            assert not self.has_quorum_possible
            return True

    @property
    def has_quorum_possible(self):
        """
        we've seen +1/3 of all eligible votes voting for one block.
        at least one vote was from a honest node.
        we can assume that this block is agreeable.
        """
        if self.has_quorum:
            return
        assert self.is_valid  # we could tell that earlier
        bhs = self.blockhashes()
        if bhs and bhs[0][1] > 1 / 3. * self.num_eligible_votes:
            return bhs[0][0]

    def check(self):
        "either invalid or one of quorum, noquorum, quorumpossible"
        if not self.is_valid:
            return True
        test = (self.has_quorum, self.has_quorum_possible, self.has_noquorum)
        assert 1 == len([x for x in test if x is not None])
        return True class Ready(Signed):

    """
    Used to sync during the startup sequence
    """
    fields = [
        ('nonce', big_endian_int),
        ('current_lockset', LockSet)
    ] + Signed.fields

    def __init__(self, nonce, current_lockset, v=0, r=0, s=0):
        super(Ready, self).__init__(nonce, current_lockset, v, r, s)

    def __repr__(self):
        return '<Ready(n:{})>'.format(self.nonce) def phx(x):
    return x.encode('hex')[:8] class ConsensusManager(object):

    allow_empty_blocks = False
    num_initial_blocks = 10
    round_timeout = 3  # timeout when waiting for proposal
    round_timeout_factor = 1.5  # timeout increase per round
    transaction_timeout = 0.5  # delay when waiting for new transaction

    def __init__(self, chainservice, consensus_contract, privkey):
        self.chainservice = chainservice
        self.chain = chainservice.chain
        self.contract = consensus_contract
        self.privkey = privkey

        self.synchronizer = Synchronizer(self)
        self.heights = ManagerDict(HeightManager, self)
        self.block_candidates = dict()  # blockhash : BlockProposal

        self.tracked_protocol_failures = list()

        # wait for enough validators in order to start
        self.ready_validators = set()  # addresses
        self.ready_nonce = 0

        assert self.contract.isvalidator(self.coinbase)
        self.initialize_locksets()

        self.ready_validators = set([self.coinbase])  # old votes dont count

    def initialize_locksets(self):
        log.debug('initializing locksets')
        # sign genesis
        v = self.sign(VoteBlock(0, 0, self.chainservice.chain.genesis.hash))
        self.add_vote(v)

        # add initial lockset
        head_proposal = self.load_proposal(self.head.hash)
        # assert head_proposal
        if head_proposal:
            assert head_proposal.blockhash == self.head.hash
            for v in head_proposal.signing_lockset:
                self.add_vote(v)  # head - 1 , height -2
            assert self.heights[self.head.header.number - 1].has_quorum
        last_committing_lockset = self.load_last_committing_lockset()
        if last_committing_lockset:
            assert last_committing_lockset.has_quorum == self.head.hash
            for v in last_committing_lockset.votes:
                self.add_vote(v)  # head  , height - 1
            assert self.heights[self.head.header.number].has_quorum
        else:
            assert self.head.header.number == 0
        assert self.highest_committing_lockset
        assert self.last_committing_lockset
        assert self.last_valid_lockset

    # persist proposals and last committing lockset

    def store_last_committing_lockset(self, ls):
        assert isinstance(ls, LockSet)
        assert ls.has_quorum
        self.chainservice.db.put('last_committing_lockset', rlp.encode(ls))

    def load_last_committing_lockset(self):
        try:
            data = self.chainservice.db.get('last_committing_lockset')
        except KeyError:
            self.log('no last_committing_lockset could be loaded')
            return
        return rlp.decode(data, sedes=LockSet)

    def store_proposal(self, p):
        assert isinstance(p, BlockProposal)
        self.chainservice.db.put('blockproposal:%s' % p.blockhash, rlp.encode(p))

    def load_proposal_rlp(self, blockhash):
        try:
            prlp = self.chainservice.db.get('blockproposal:%s' % blockhash)
            assert isinstance(prlp, bytes)
            return prlp
        except KeyError:
            return None

    def load_proposal(self, blockhash):
        prlp = self.load_proposal_rlp(blockhash)
        if prlp:
            return rlp.decode(prlp, sedes=BlockProposal)

    def get_blockproposal(self, blockhash):
        return self.block_candidates.get(blockhash) or self.load_proposal(blockhash)

    def has_blockproposal(self, blockhash):
        return bool(self.load_proposal_rlp(blockhash))

    def get_blockproposal_rlp_by_height(self, height):
        assert 0 < height < self.height
        bh = self.chainservice.chain.index.get_block_by_number(height)
        return self.load_proposal_rlp(bh)

    @property
    def coinbase(self):
        return self.chain.coinbase

    def set_proposal_lock(self, block):
        self.chainservice.set_proposal_lock(block)

    def __repr__(self):
        return '<CP A:%r H:%d R:%d L:%r %s>' % (phx(self.coinbase), self.height, self.round,
                                                self.active_round.lock,
                                                self.active_round.lockset.state)

    def log(self, tag, **kargs):
        # if self.coinbase != 0: return
        t = int(self.chainservice.now)
        c = lambda x: cstr(self.coinbase, x)
        msg = ' '.join([str(t), c(repr(self)), tag, (' %r' % kargs if kargs else '')])
        log.debug(msg)

    @property
    def head(self):
        return self.chain.head

    @property
    def height(self):
        return self.head.number + 1

    @property
    def round(self):
        return self.heights[self.height].round

    # message handling

    def broadcast(self, m):
        self.log('broadcasting', message=m)
        self.chainservice.broadcast(m)

    # validator ready handling

    @property
    def is_ready(self):
        return len(self.ready_validators) > len(self.contract.validators) * 2 / 3.

    def send_ready(self):
        self.log('cm.send_ready')
        assert not self.is_ready
        r = Ready(self.ready_nonce, self.active_round.lockset)
        self.sign(r)
        self.broadcast(r)
        self.ready_nonce += 1

    def add_ready(self, ready, proto=None):
        assert isinstance(ready, Ready)
        assert self.contract.isvalidator(ready.sender)
        self.ready_validators.add(ready.sender)
        self.log('cm.add_ready', validator=ready.sender)
        if self.is_ready:
            self.log('cm.add_ready, sufficient count of validators ready',
                     num=len(self.ready_validators))
        else:
            self.send_ready()

    def add_vote(self, v, proto=None):
        assert isinstance(v, Vote)
        assert self.contract.isvalidator(v.sender)
        self.ready_validators.add(v.sender)
        # exception for externaly received votes signed by self, necessary for resyncing
        is_own_vote = bool(v.sender == self.coinbase)
        try:
            success = self.heights[v.height].add_vote(v, force_replace=is_own_vote)
        except DoubleVotingError:
            ls = self.heights[v.height].rounds[v.round].lockset
            self.tracked_protocol_failures.append(DoubleVotingEvidence(proto, v, ls))
            log.warn('double voting detected', vote=v, ls=ls)
        return success

    def add_proposal(self, p, proto=None):
        assert isinstance(p, Proposal)
        assert proto is None or isinstance(proto, HDCProtocol)

        def check(valid):
            if not valid:
                self.tracked_protocol_failures.append(InvalidProposalEvidence(None, p))
                log.warn('invalid proposal', p=p)
                raise InvalidProposalError()
            return True

        self.log('cm.add_proposal', p=p)
        if p.height < self.height:
            self.log('proposal from the past')
            return

        if not check(self.contract.isvalidator(p.sender) and self.contract.isproposer(p)):
            return
        self.ready_validators.add(p.sender)

        if not check(p.lockset.is_valid):
            return
        if not check(p.lockset.height == p.height or p.round == 0):
            return
        if not check(p.round - p.lockset.round == 1 or p.round == 0):
            return

        # proposal is valid
        if proto is not None:  # inactive proto is False
            self.synchronizer.on_proposal(p, proto)

        for v in p.lockset:
            self.add_vote(v)  # implicitly checks their validity
        if isinstance(p, BlockProposal):
            if not check(p.block.number == p.height):
                return
            if not check(p.lockset.has_noquorum or p.round == 0):
                return
            # validation
            if p.height > self.height:
                self.log('proposal from the future, not in sync', p=p)
                return  # note: we are not broadcasting this, as we could not validate
            blk = self.chainservice.link_block(p.block)
            if not check(blk):
                # safeguard for forks:
                # if there is a quorum on a block which can not be applied: panic!
                ls = self.heights[p.height].last_quorum_lockset
                if ls and ls.has_quorum == p.blockhash:
                    raise ForkDetectedEvidence(proto, (self.head, p, ls))
                    sys.exit(1)
                return
            p._mutable = True
            p._cached_rlp = None
            p.block = blk  # block linked to chain
            self.log('successfully linked block')
            self.add_block_proposal(p)  # implicitly checks the votes validity
        else:
            assert isinstance(p, VotingInstruction)
            assert p.lockset.round == p.round - 1 and p.height == p.lockset.height
            assert p.round > 0
            assert p.lockset.has_quorum_possible
            assert not p.lockset.has_quorum
            if not check(p.lockset.has_quorum_possible and not p.lockset.has_quorum):
                return
        is_valid = self.heights[p.height].add_proposal(p)
        return is_valid  # can be broadcasted

    def add_lockset(self, ls, proto=None):
        assert ls.is_valid
        for v in ls:
            self.add_vote(v)  # implicitly checks their validity

    def add_block_proposal(self, p):
        assert isinstance(p, BlockProposal)
        if self.has_blockproposal(p.blockhash):
            self.log('known block_proposal')
            return
        assert p.signing_lockset.has_quorum  # on previous block
        assert p.signing_lockset.height == p.height - 1
        for v in p.signing_lockset:
            self.add_vote(v)
        self.block_candidates[p.blockhash] = p

    @property
    def last_committing_lockset(self):
        return self.heights[self.height - 1].last_quorum_lockset

    @property
    def highest_committing_lockset(self):
        for height in self.heights:
            ls = self.heights[height].last_quorum_lockset
            if ls:
                return ls

    @property
    def last_valid_lockset(self):
        return self.heights[self.height].last_valid_lockset or self.last_committing_lockset

    @property
    def last_lock(self):
        return self.heights[self.height].last_lock

    @property
    def last_blockproposal(self):
        # valid block proposal on currrent height
        p = self.heights[self.height].last_voted_blockproposal
        if p:
            return p
        elif self.height > 1:  # or last block
            return self.get_blockproposal(self.head.hash)

    @property
    def active_round(self):
        hm = self.heights[self.height]
        return hm.rounds[hm.round]

    def setup_alarm(self):
        ar = self.active_round
        delay = ar.get_timeout()
        self.log('in set up alarm', delay=delay)
        if self.is_waiting_for_proposal:
            if delay is not None:
                self.chainservice.setup_alarm(delay, self.on_alarm, ar)
                self.log('set up alarm on timeout', now=self.chainservice.now,
                         delay=delay, triggered=delay + self.chainservice.now)
        else:
            self.chainservice.setup_transaction_alarm(self.on_alarm, ar)
            self.log('set up alarm on tx', now=self.chainservice.now)

    def on_alarm(self, ar):
        assert isinstance(ar, RoundManager)
        if self.active_round == ar:
            self.log('on alarm, matched', ts=self.chainservice.now)
            if not self.is_ready:
                # defer alarm if not ready
                self.log('not ready defering alarm', ts=self.chainservice.now)
                self.setup_alarm()
            elif not self.is_waiting_for_proposal:
                # defer alarm if there are no pending transactions
                self.log('no txs defering alarm', ts=self.chainservice.now)
                self.setup_alarm()
            else:
                self.process()

    @property
    def is_waiting_for_proposal(self):
        return self.allow_empty_blocks \
            or self.has_pending_transactions \
            or self.height <= self.num_initial_blocks

    @property
    def has_pending_transactions(self):
        return self.chain.head_candidate.num_transactions() > 0

    def process(self):
        r = self._process()
        return r

    def _process(self):
        self.log('-' * 40)
        self.log('in process')
        if not self.is_ready:
            self.log('not ready ')
            self.setup_alarm()
            return
        self.commit()
        self.heights[self.height].process()
        if self.commit():  # re enter process if we did commit (e.g. to immediately propose)
            return self._process()
        self.cleanup()
        self.synchronizer.process()
        self.setup_alarm()

        for f in self.tracked_protocol_failures:
            if not isinstance(f, FailedToProposeEvidence):
                log.warn('protocol failure', incident=f)

    start = process

    def commit(self):
        self.log('in commit')
        for p in [c for c in self.block_candidates.values() if c.block.prevhash == self.head.hash]:
            assert isinstance(p, BlockProposal)
            ls = self.heights[p.height].last_quorum_lockset
            if ls and ls.has_quorum == p.blockhash:
                self.store_proposal(p)
                self.store_last_committing_lockset(ls)
                success = self.chainservice.commit_block(p.block)
                assert success
                if success:
                    self.log('commited', p=p, hash=phx(p.blockhash))
                    assert self.head == p.block
                    self.commit()  # commit all possible
                    return True
                else:
                    self.log('could not commit', p=p)
            else:
                self.log('no quorum for', p=p)
                if ls:
                    self.log('votes', votes=ls.votes)

    def cleanup(self):
        self.log('in cleanup')
        for p in self.block_candidates.values():
            if self.head.number >= p.height:
                self.block_candidates.pop(p.blockhash)
        for h in list(self.heights):
            if self.heights[h].height < self.head.number:
                self.heights.pop(h)

    def mk_lockset(self, height):
        return LockSet(num_eligible_votes=self.contract.num_eligible_votes(height))

    def sign(self, o):
        assert isinstance(o, Signed)
        return o.sign(self.privkey) class ConsensusContract(object):

    def __init__(self, validators):
        for v in validators:
            assert isaddress(v)
        self.validators = validators

    def proposer(self, height, round_):
        v = abs(hash(repr((height, round_))))
        return self.validators[v % len(self.validators)]

    def isvalidator(self, address, height=0):
        assert isaddress(address)
        assert len(self.validators)
        return address in self.validators

    def isproposer(self, p):
        assert isinstance(p, Proposal)
        return p.sender == self.proposer(p.height, p.round)

    def num_eligible_votes(self, height):
        if height == 0:
            return 0
        return len(self.validators)
import time
import rlp
import gevent
import gevent.lock
from ethereum.config import Env
from ethereum.utils import sha3
from rlp.utils import encode_hex
from ethereum import processblock
from ethereum.slogging import get_logger
from ethereum.chain import Chain
from ethereum.refcount_db import RefcountDB
from ethereum.blocks import Block, VerificationFailed
from ethereum.transactions import Transaction
from devp2p.service import WiredService
from ethereum import config as ethereum_config
from collections import deque
from gevent.queue import Queue
from pyethapp.eth_service import ChainService as eth_ChainService
from .consensus.protocol import HDCProtocol, HDCProtocolError
from .consensus.base import (Signed, VotingInstruction, BlockProposal, VoteBlock, VoteNil,
                             HDCBlockHeader, LockSet, Ready)
from .consensus.utils import phx
from .consensus.manager import ConsensusManager
from .consensus.contract import ConsensusContract
                self.proposal_lock.release()
                log.debug('released')
                assert not self.proposal_lock.is_locked()
            else:
                log.debug('could not release', head=blk, lock=self.proposal_lock)

    ###############################################################################

    @property
    def is_syncing(self):
        return self.consensus_manager.synchronizer.is_syncing

    @property
    def is_mining(self):
        return self.chain.coinbase in self.config['hdc']['validators']

    # wire protocol receivers ###########

    # transactions

    def on_receive_transactions(self, proto, transactions):
        "receives rlp.decoded serialized"
        log.debug('----------------------------------')
        log.debug('remote_transactions_received', count=len(transactions), remote_id=proto)

        def _add_txs():
            for tx in transactions:
                self.add_transaction(tx, origin=proto)
        gevent.spawn(_add_txs)  # so the locks in add_transaction won't lock the connection

    # blocks / proposals ################

    def on_receive_getblockproposals(self, proto, blocknumbers):
        log.debug('----------------------------------')
        log.debug("on_receive_getblockproposals", count=len(blocknumbers))
        found = []
        for i, height in enumerate(blocknumbers):
            if i == self.wire_protocol.max_getproposals_count:
                break
            assert isinstance(height, int)  # integers
            assert i == 0 or height > blocknumbers[i - 1]   # sorted
            if height > self.chain.head.number:
                log.debug("unknown block requested", height=height)
                break
            rlp_data = self.consensus_manager.get_blockproposal_rlp_by_height(height)
            assert isinstance(rlp_data, bytes)
            found.append(rlp_data)
        if found:
            log.debug("found", count=len(found))
            proto.send_blockproposals(*found)

    def on_receive_blockproposals(self, proto, proposals):
        log.debug('----------------------------------')
        self.consensus_manager.log('received proposals', sender=proto)
        log.debug("recv proposals", num=len(proposals), remote_id=proto)
        self.consensus_manager.synchronizer.receive_blockproposals(proposals)

    def on_receive_newblockproposal(self, proto, proposal):
        if proposal.hash in self.broadcast_filter:
            return
        log.debug('----------------------------------')
        self.consensus_manager.log('receive proposal', sender=proto)
        log.debug("recv newblockproposal", proposal=proposal, remote_id=proto)
        # self.synchronizer.receive_newproposal(proto, proposal)
        assert isinstance(proposal, BlockProposal)
        assert isinstance(proposal.block.header, HDCBlockHeader)
        isvalid = self.consensus_manager.add_proposal(proposal, proto)
        if isvalid:
            self.broadcast(proposal, origin=proto)
        self.consensus_manager.process()

    def on_receive_votinginstruction(self, proto, votinginstruction):
        if votinginstruction.hash in self.broadcast_filter:
            return
        log.debug('----------------------------------')
        log.debug("recv votinginstruction", proposal=votinginstruction, remote_id=proto)
        # self.synchronizer.receive_newproposal(proto, proposal)
        isvalid = self.consensus_manager.add_proposal(votinginstruction, proto)
        if isvalid:
            self.broadcast(votinginstruction, origin=proto)

        self.consensus_manager.process()

    #  votes

    def on_receive_vote(self, proto, vote):
        self.consensus_manager.log('on_receive_vote', v=vote)
        if vote.hash in self.broadcast_filter:
            log.debug('filtered!!!')
            return
        log.debug('----------------------------------')
        log.debug("recv vote", vote=vote, remote_id=proto)
        isvalid = self.consensus_manager.add_vote(vote, proto)
        if isvalid:
            self.broadcast(vote, origin=proto)
        self.consensus_manager.process()

    def on_receive_ready(self, proto, ready):
        if ready.hash in self.broadcast_filter:
            return
        log.debug('----------------------------------')
        log.debug("recv ready", ready=ready, remote_id=proto)
        self.consensus_manager.add_ready(ready, proto)
        self.broadcast(ready, origin=proto)
        self.consensus_manager.process()

    #  start

    def on_receive_status(self, proto, eth_version, network_id, genesis_hash, current_lockset):
        log.debug('----------------------------------')
        log.debug('status received', proto=proto, eth_version=eth_version)
        assert eth_version == proto.version, (eth_version, proto.version)
        if network_id != self.config['eth'].get('network_id', proto.network_id):
            log.warn("invalid network id", remote_network_id=network_id,
                     expected_network_id=self.config['eth'].get('network_id', proto.network_id))
            raise HDCProtocolError('wrong network_id')

        # check genesis
        if genesis_hash != self.chain.genesis.hash:
            log.warn("invalid genesis hash", remote_id=proto, genesis=genesis_hash.encode('hex'))

            raise HDCProtocolError('wrong genesis block')