{"action":"created","comment":{"url":"https://api.github.com/repos/poanetwork/hbbft/pulls/comments/198193532","pull_request_review_id":132079723,"id":198193532,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDE5ODE5MzUzMg==","diff_hunk":"@@ -0,0 +1,450 @@\n+use std::collections::{BTreeMap, HashMap, VecDeque};\n+use std::fmt::Debug;\n+use std::hash::Hash;\n+use std::mem;\n+use std::rc::Rc;\n+\n+use bincode;\n+use clear_on_drop::ClearOnDrop;\n+use serde::{Deserialize, Serialize};\n+\n+use crypto::{PublicKey, PublicKeySet, SecretKey, Signature};\n+use honey_badger::{self, HoneyBadger};\n+use messaging::{DistAlgorithm, NetworkInfo, TargetedMessage};\n+use sync_key_gen::{Accept, Propose, SyncKeyGen};\n+\n+error_chain!{\n+    links {\n+        HoneyBadger(honey_badger::Error, honey_badger::ErrorKind);\n+    }\n+\n+    foreign_links {\n+        Bincode(Box<bincode::ErrorKind>);\n+    }\n+\n+    errors {\n+        UnknownSender\n+    }\n+}\n+\n+/// A node change action: adding or removing a node.\n+#[derive(Clone, Eq, PartialEq, Serialize, Deserialize, Hash, Debug)]\n+pub enum Change<NodeUid> {\n+    /// Add a node. The public key is used only temporarily, for key generation.\n+    Add(NodeUid, PublicKey),\n+    /// Remove a node.\n+    Remove(NodeUid),\n+}\n+\n+/// The user input for `DynamicHoneyBadger`.\n+#[derive(Clone, Debug)]\n+pub enum Input<Tx, NodeUid> {\n+    /// A user-defined transaction.\n+    User(Tx),\n+    /// A vote to change the set of nodes.\n+    Change(Change<NodeUid>),\n+}\n+\n+/// A Honey Badger instance that can handle adding and removing nodes.\n+// TODO: Handle the joining process correctly in the new node. Allow the new node to contribute its\n+// key generation input.\n+pub struct DynamicHoneyBadger<Tx, NodeUid>\n+where\n+    Tx: Eq + Serialize + for<'r> Deserialize<'r> + Debug + Hash,\n+    NodeUid: Ord + Clone + Serialize + for<'r> Deserialize<'r> + Debug,\n+{\n+    /// Shared network data.\n+    netinfo: NetworkInfo<NodeUid>,\n+    /// The target number of transactions per batch.\n+    batch_size: usize,\n+    /// The first epoch after the latest node change.\n+    start_epoch: u64,\n+    /// Collected votes for adding or removing nodes.\n+    votes: BTreeMap<NodeUid, Change<NodeUid>>,\n+    /// The number of nodes currently voting for a change.\n+    vote_counts: HashMap<Change<NodeUid>, usize>,\n+    /// The `HoneyBadger` instance with the current set of nodes.\n+    honey_badger: HoneyBadger<Transaction<Tx, NodeUid>, NodeUid>,\n+    /// The current key generation process.\n+    key_gen: Option<SyncKeyGen<NodeUid>>,\n+    /// A queue for messages from future epochs that cannot be handled yet.\n+    incoming_queue: Vec<(u64, NodeUid, honey_badger::Message<NodeUid>)>,\n+    /// The messages that need to be sent to other nodes.\n+    messages: MessageQueue<NodeUid>,\n+    /// The outputs from completed epochs.\n+    output: VecDeque<Batch<Tx, NodeUid>>,\n+}\n+\n+impl<Tx, NodeUid> DistAlgorithm for DynamicHoneyBadger<Tx, NodeUid>\n+where\n+    Tx: Eq + Serialize + for<'r> Deserialize<'r> + Debug + Hash,\n+    NodeUid: Eq + Ord + Clone + Serialize + for<'r> Deserialize<'r> + Debug + Hash,\n+{\n+    type NodeUid = NodeUid;\n+    type Input = Input<Tx, NodeUid>;\n+    type Output = Batch<Tx, NodeUid>;\n+    type Message = Message<NodeUid>;\n+    type Error = Error;\n+\n+    fn input(&mut self, input: Self::Input) -> Result<()> {\n+        self.honey_badger.input(input.into())?;\n+        self.process_output()\n+    }\n+\n+    fn handle_message(&mut self, sender_id: &NodeUid, message: Self::Message) -> Result<()> {\n+        if !self.netinfo.all_uids().contains(sender_id) {\n+            return Err(ErrorKind::UnknownSender.into());\n+        }\n+        match message {\n+            Message::HoneyBadger(start_epoch, hb_msg) => {\n+                self.handle_honey_badger_message(sender_id, start_epoch, hb_msg)\n+            }\n+        }\n+    }\n+\n+    fn next_message(&mut self) -> Option<TargetedMessage<Self::Message, NodeUid>> {\n+        self.messages.pop_front()\n+    }\n+\n+    fn next_output(&mut self) -> Option<Self::Output> {\n+        self.output.pop_front()\n+    }\n+\n+    fn terminated(&self) -> bool {\n+        false\n+    }\n+\n+    fn our_id(&self) -> &NodeUid {\n+        self.netinfo.our_uid()\n+    }\n+}\n+\n+impl<Tx, NodeUid> DynamicHoneyBadger<Tx, NodeUid>\n+where\n+    Tx: Eq + Serialize + for<'r> Deserialize<'r> + Debug + Hash,\n+    NodeUid: Eq + Ord + Clone + Debug + Serialize + for<'r> Deserialize<'r> + Hash,\n+{\n+    /// Returns a new instance with the given parameters, starting at epoch `0`.\n+    pub fn new(netinfo: NetworkInfo<NodeUid>, batch_size: usize) -> Result<Self> {\n+        let honey_badger = HoneyBadger::new(Rc::new(netinfo.clone()), batch_size, None)?;\n+        let dyn_hb = DynamicHoneyBadger {\n+            netinfo,\n+            batch_size,\n+            start_epoch: 0,\n+            votes: BTreeMap::new(),\n+            vote_counts: HashMap::new(),\n+            honey_badger,\n+            key_gen: None,\n+            incoming_queue: Vec::new(),\n+            messages: MessageQueue(VecDeque::new()),\n+            output: VecDeque::new(),\n+        };\n+        Ok(dyn_hb)\n+    }\n+\n+    /// Handles a message for the `HoneyBadger` instance.\n+    fn handle_honey_badger_message(\n+        &mut self,\n+        sender_id: &NodeUid,\n+        epoch: u64,\n+        message: honey_badger::Message<NodeUid>,\n+    ) -> Result<()> {\n+        if epoch < self.start_epoch {\n+            return Ok(()); // Obsolete message.\n+        }\n+        if epoch > self.start_epoch {\n+            // Message cannot be handled yet. Save it for later.\n+            let entry = (epoch, sender_id.clone(), message);\n+            self.incoming_queue.push(entry);\n+            return Ok(());\n+        }\n+        // Handle the message and put the outgoing messages into the queue.\n+        self.honey_badger.handle_message(sender_id, message)?;\n+        self.process_output()?;\n+        Ok(())\n+    }\n+\n+    /// Processes all pending batches output by Honey Badger.\n+    fn process_output(&mut self) -> Result<()> {\n+        let mut changed = false;\n+        while let Some(hb_batch) = self.honey_badger.next_output() {\n+            // Create the batch we output ourselves. It will contain the _user_ transactions of\n+            // `hb_batch`, and the applied change, if any.\n+            let mut batch = Batch::new(hb_batch.epoch + self.start_epoch);\n+            // The change that currently has a majority. All key generation messages in this batch\n+            // are related to this change.\n+            let change = self.current_majority().cloned();\n+            // Add the user transactions to `batch` and handle votes and DKG messages.\n+            for (id, tx_vec) in hb_batch.transactions {\n+                let entry = batch.transactions.entry(id.clone());\n+                let id_txs = entry.or_insert_with(Vec::new);\n+                for tx in tx_vec {\n+                    use self::Transaction::*;\n+                    match tx {\n+                        User(tx) => id_txs.push(tx),\n+                        Change(change) => self.insert_vote(id.clone(), change),\n+                        Propose(s_id, propose, sig) => self.handle_propose(&s_id, propose, &*sig)?,\n+                        Accept(s_id, accept, sig) => self.handle_accept(&s_id, accept, &*sig)?,\n+                    }\n+                }\n+            }\n+            // If DKG completed, apply the change.\n+            if let Some(change) = change.as_ref() {\n+                if let Some((pub_key_set, sk)) = self.get_key_gen_output() {\n+                    self.start_epoch = hb_batch.epoch + 1;\n+                    self.apply_change(change, pub_key_set, sk)?;\n+                    batch.change = Some(change.clone());\n+                    changed = true;\n+                }\n+            }\n+            // If a new change has a majority, restart DKG.\n+            if self.current_majority() != change.as_ref() {\n+                if let Some(change) = self.current_majority().cloned() {\n+                    self.start_key_gen(change)?;\n+                } else {\n+                    self.key_gen = None;\n+                }\n+            }\n+            self.output.push_back(batch);\n+        }\n+        self.messages\n+            .extend_with_epoch(self.start_epoch, &mut self.honey_badger);\n+        // If `start_epoch` changed, we can now handle some queued messages.\n+        if changed {\n+            let queue = mem::replace(&mut self.incoming_queue, Vec::new());\n+            for (epoch, sender_id, msg) in queue {\n+                self.handle_honey_badger_message(&sender_id, epoch, msg)?;\n+            }\n+        }\n+        Ok(())\n+    }\n+\n+    /// Restarts Honey Badger with a new set of nodes, and resets the Key Generation.\n+    fn apply_change(\n+        &mut self,\n+        change: &Change<NodeUid>,\n+        pub_key_set: PublicKeySet,\n+        sk: ClearOnDrop<Box<SecretKey>>,\n+    ) -> Result<()> {\n+        self.votes.clear();\n+        self.vote_counts.clear();\n+        self.key_gen = None;\n+        let mut all_uids = self.netinfo.all_uids().clone();\n+        if !match *change {\n+            Change::Remove(ref id) => all_uids.remove(id),\n+            Change::Add(ref id, _) => all_uids.insert(id.clone()),\n+        } {\n+            debug!(\"No-op change: {:?}\", change);\n+        }\n+        let netinfo = NetworkInfo::new(self.our_id().clone(), all_uids, sk, pub_key_set);\n+        self.netinfo = netinfo.clone();\n+        // TODO: Drop the buffer if this node was removed, to become an observer.\n+        let buffer = self.honey_badger.drain_buffer();\n+        self.honey_badger = HoneyBadger::new(Rc::new(netinfo), self.batch_size, buffer)?;\n+        Ok(())\n+    }\n+\n+    /// Starts Key Generation for the set of nodes implied by the `change`.\n+    fn start_key_gen(&mut self, change: Change<NodeUid>) -> Result<()> {\n+        // Use the existing key shares - with the change applied - as keys for DKG.\n+        let mut pub_keys = self.netinfo.public_key_map();\n+        if match change {\n+            Change::Remove(id) => pub_keys.remove(&id).is_none(),\n+            Change::Add(id, pub_key) => pub_keys.insert(id, pub_key).is_some(),\n+        } {\n+            debug!(\"No-op change: {:?}\", self.current_majority().unwrap());\n+        }\n+        // TODO: This needs to be the same as `num_faulty` will be in the _new_\n+        // `NetworkInfo` if the change goes through. It would be safer to deduplicate.\n+        let threshold = (pub_keys.len() - 1) / 3;\n+        let sk = self.netinfo.secret_key().clone();\n+        let our_uid = self.our_id().clone();\n+        let (key_gen, propose) = SyncKeyGen::new(&our_uid, sk, pub_keys, threshold);\n+        self.key_gen = Some(key_gen);\n+        let sig = self.sign(&propose)?;\n+        let tx = Transaction::Propose(our_uid, propose, sig);\n+        self.honey_badger.input(tx)?;\n+        Ok(())\n+    }\n+\n+    /// Handles a `Propose` message that was output by Honey Badger.\n+    fn handle_propose(\n+        &mut self,\n+        sender_id: &NodeUid,\n+        propose: Propose,\n+        sig: &Signature,\n+    ) -> Result<()> {\n+        if !self.verify_signature(sender_id, sig, &propose)? {\n+            debug!(\"Invalid signature from {:?} for: {:?}.\", sender_id, propose);\n+            return Ok(());\n+        }\n+        let handle =\n+            |key_gen: &mut SyncKeyGen<NodeUid>| key_gen.handle_propose(&sender_id, propose);\n+        let accept = match self.key_gen.as_mut().and_then(handle) {\n+            Some(accept) => accept,\n+            None => return Ok(()),\n+        };\n+        let our_id = self.our_id().clone();\n+        let sig = self.sign(&accept)?;\n+        let tx = Transaction::Accept(our_id, accept, sig);\n+        self.honey_badger.input(tx)?;\n+        Ok(())\n+    }\n+\n+    /// Handles an `Accept` message that was output by Honey Badger.\n+    fn handle_accept(\n+        &mut self,\n+        sender_id: &NodeUid,\n+        accept: Accept,\n+        sig: &Signature,\n+    ) -> Result<()> {\n+        if self.verify_signature(sender_id, sig, &accept)? {\n+            if let Some(key_gen) = self.key_gen.as_mut() {\n+                key_gen.handle_accept(&sender_id, accept);\n+            }\n+        }\n+        Ok(())\n+    }\n+\n+    /// If the current Key Generation process is ready, returns the generated key set.\n+    fn get_key_gen_output(&self) -> Option<(PublicKeySet, ClearOnDrop<Box<SecretKey>>)> {\n+        // TODO: Once we've upgraded to Rust 1.27.0, we can use `Option::filter` here.\n+        self.key_gen\n+            .iter()\n+            .filter(|key_gen| key_gen.is_ready())\n+            .map(SyncKeyGen::generate)\n+            .next()\n+    }\n+\n+    /// Returns a signature of `payload`, or an error if serialization fails.\n+    fn sign<T: Serialize>(&self, payload: &T) -> Result<Box<Signature>> {\n+        let ser = bincode::serialize(payload)?;\n+        Ok(Box::new(self.netinfo.secret_key().sign(ser)))\n+    }\n+\n+    /// Returns `true` if the signature of the payload by the node with the specified ID is valid.\n+    /// Returns an error if the payload fails to serialize.\n+    fn verify_signature<T: Serialize>(\n+        &self,\n+        id: &NodeUid,\n+        sig: &Signature,\n+        payload: &T,\n+    ) -> Result<bool> {\n+        let ser = bincode::serialize(payload)?;\n+        let pk_opt = self.netinfo.public_key_share(&id);\n+        Ok(pk_opt.map_or(false, |pk| pk.verify(&sig, ser)))\n+    }\n+\n+    /// Adds a vote for a node change by the node with `id`.\n+    fn insert_vote(&mut self, id: NodeUid, change: Change<NodeUid>) {\n+        if let Some(old_change) = self.votes.insert(id, change.clone()) {\n+            let decrement = |count: &mut usize| {\n+                *count -= 1;\n+                *count\n+            };\n+            if Some(0) == self.vote_counts.get_mut(&old_change).map(decrement) {\n+                self.vote_counts.remove(&old_change);\n+            }\n+        }\n+        *self.vote_counts.entry(change).or_insert(0) += 1;\n+    }\n+\n+    /// Returns the change that currently has a majority of votes, if any.\n+    fn current_majority(&self) -> Option<&Change<NodeUid>> {\n+        self.vote_counts\n+            .iter()\n+            .find(|&(_, count)| count * 2 > self.netinfo.num_nodes())\n+            .map(|(change, _)| change)\n+    }\n+}\n+\n+/// The transactions for the internal `HoneyBadger` instance: this includes both user-defined\n+/// \"regular\" transactions as well as internal transactions for coordinating node additions and\n+/// removals and key generation.\n+#[derive(Eq, PartialEq, Debug, Serialize, Deserialize, Hash)]\n+enum Transaction<Tx, NodeUid> {\n+    /// A user-defined transaction.\n+    User(Tx),\n+    /// A vote by an existing node to add or remove a node.\n+    Change(Change<NodeUid>),\n+    /// A proposal message for key generation.\n+    Propose(NodeUid, Propose, Box<Signature>),\n+    /// An accept message for key generation.\n+    Accept(NodeUid, Accept, Box<Signature>),\n+}\n+\n+impl<Tx, NodeUid> From<Input<Tx, NodeUid>> for Transaction<Tx, NodeUid> {\n+    fn from(input: Input<Tx, NodeUid>) -> Transaction<Tx, NodeUid> {\n+        match input {\n+            Input::User(tx) => Transaction::User(tx),\n+            Input::Change(change) => Transaction::Change(change),\n+        }\n+    }\n+}\n+\n+/// A batch of transactions the algorithm has output.\n+#[derive(Clone)]\n+pub struct Batch<Tx, NodeUid> {\n+    pub epoch: u64,\n+    pub transactions: BTreeMap<NodeUid, Vec<Tx>>,\n+    pub change: Option<Change<NodeUid>>,\n+}\n+\n+impl<Tx, NodeUid: Ord> Batch<Tx, NodeUid> {\n+    /// Returns a new, empty batch with the given epoch.\n+    pub fn new(epoch: u64) -> Self {\n+        Batch {\n+            epoch,\n+            transactions: BTreeMap::new(),\n+            change: None,\n+        }\n+    }\n+\n+    /// Returns an iterator over all transactions included in the batch.\n+    pub fn iter(&self) -> impl Iterator<Item = &Tx> {\n+        self.transactions.values().flat_map(|vec| vec)\n+    }\n+\n+    /// Returns the number of transactions in the batch (without detecting duplicates).\n+    pub fn len(&self) -> usize {\n+        self.transactions.values().map(Vec::len).sum()\n+    }\n+\n+    /// Returns `true` if the batch contains no transactions.\n+    pub fn is_empty(&self) -> bool {\n+        self.transactions.values().all(Vec::is_empty)\n+    }\n+\n+    /// Returns the change to the set of participating nodes, if any.\n+    pub fn change(&self) -> Option<&Change<NodeUid>> {\n+        self.change.as_ref()\n+    }\n+}\n+\n+/// A message sent to or received from another node's Honey Badger instance.\n+#[cfg_attr(feature = \"serialization-serde\", derive(Serialize, Deserialize))]\n+#[derive(Debug, Clone)]\n+pub enum Message<NodeUid> {\n+    /// A message belonging to the `HoneyBadger` algorithm started in the given epoch.\n+    HoneyBadger(u64, honey_badger::Message<NodeUid>),","path":"src/dynamic_honey_badger.rs","position":429,"original_position":429,"commit_id":"a2c213d0aaf27f0c89baf533c0db7ccbd16965c3","original_commit_id":"a2c213d0aaf27f0c89baf533c0db7ccbd16965c3","user":{"login":"afck","id":7894725,"node_id":"MDQ6VXNlcjc4OTQ3MjU=","avatar_url":"https://avatars0.githubusercontent.com/u/7894725?v=4","gravatar_id":"","url":"https://api.github.com/users/afck","html_url":"https://github.com/afck","followers_url":"https://api.github.com/users/afck/followers","following_url":"https://api.github.com/users/afck/following{/other_user}","gists_url":"https://api.github.com/users/afck/gists{/gist_id}","starred_url":"https://api.github.com/users/afck/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/afck/subscriptions","organizations_url":"https://api.github.com/users/afck/orgs","repos_url":"https://api.github.com/users/afck/repos","events_url":"https://api.github.com/users/afck/events{/privacy}","received_events_url":"https://api.github.com/users/afck/received_events","type":"User","site_admin":false},"body":"Yes, we will need additional messages so that the joining node can take part in the key generation (DKG): It will have to send its `Propose` and `Accept`s as messages to the other nodes.\r\n\r\nIn fact, I think I'll insert this messaging step for all nodes, to prevent censorship. So every node will send its votes and DKG messages first as messages, and everyone who receives a signed message from a peer will propose it in Honey Badger. (Instead of only proposing their own messages, like they do now.)","created_at":"2018-06-26T15:34:07Z","updated_at":"2018-06-26T15:34:07Z","html_url":"https://github.com/poanetwork/hbbft/pull/86#discussion_r198193532","pull_request_url":"https://api.github.com/repos/poanetwork/hbbft/pulls/86","author_association":"CONTRIBUTOR","_links":{"self":{"href":"https://api.github.com/repos/poanetwork/hbbft/pulls/comments/198193532"},"html":{"href":"https://github.com/poanetwork/hbbft/pull/86#discussion_r198193532"},"pull_request":{"href":"https://api.github.com/repos/poanetwork/hbbft/pulls/86"}},"in_reply_to_id":198188752},"pull_request":{"url":"https://api.github.com/repos/poanetwork/hbbft/pulls/86","id":197379584,"node_id":"MDExOlB1bGxSZXF1ZXN0MTk3Mzc5NTg0","html_url":"https://github.com/poanetwork/hbbft/pull/86","diff_url":"https://github.com/poanetwork/hbbft/pull/86.diff","patch_url":"https://github.com/poanetwork/hbbft/pull/86.patch","issue_url":"https://api.github.com/repos/poanetwork/hbbft/issues/86","number":86,"state":"open","locked":false,"title":"Add DynamicHoneyBadger (part 1).","user":{"login":"afck","id":7894725,"node_id":"MDQ6VXNlcjc4OTQ3MjU=","avatar_url":"https://avatars0.githubusercontent.com/u/7894725?v=4","gravatar_id":"","url":"https://api.github.com/users/afck","html_url":"https://github.com/afck","followers_url":"https://api.github.com/users/afck/followers","following_url":"https://api.github.com/users/afck/following{/other_user}","gists_url":"https://api.github.com/users/afck/gists{/gist_id}","starred_url":"https://api.github.com/users/afck/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/afck/subscriptions","organizations_url":"https://api.github.com/users/afck/orgs","repos_url":"https://api.github.com/users/afck/repos","events_url":"https://api.github.com/users/afck/events{/privacy}","received_events_url":"https://api.github.com/users/afck/received_events","type":"User","site_admin":false},"body":"`DynamicHoneyBadger` is supposed to allow adding and removing nodes. (See https://github.com/poanetwork/hbbft/issues/47#issuecomment-394640406) Internally, it performs Distributed Key Generation and restarts Honey Badger, but all that should be hidden away from the user.\r\n\r\nThis is still incomplete in several ways, but removing a node works.","created_at":"2018-06-26T10:47:36Z","updated_at":"2018-06-26T15:34:07Z","closed_at":null,"merged_at":null,"merge_commit_sha":"58ce75576944445ded9b601f488ceaba76c9aaa8","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/poanetwork/hbbft/pulls/86/commits","review_comments_url":"https://api.github.com/repos/poanetwork/hbbft/pulls/86/comments","review_comment_url":"https://api.github.com/repos/poanetwork/hbbft/pulls/comments{/number}","comments_url":"https://api.github.com/repos/poanetwork/hbbft/issues/86/comments","statuses_url":"https://api.github.com/repos/poanetwork/hbbft/statuses/a2c213d0aaf27f0c89baf533c0db7ccbd16965c3","head":{"label":"poanetwork:afck-dyn-hb","ref":"afck-dyn-hb","sha":"a2c213d0aaf27f0c89baf533c0db7ccbd16965c3","user":{"login":"poanetwork","id":28943015,"node_id":"MDEyOk9yZ2FuaXphdGlvbjI4OTQzMDE1","avatar_url":"https://avatars2.githubusercontent.com/u/28943015?v=4","gravatar_id":"","url":"https://api.github.com/users/poanetwork","html_url":"https://github.com/poanetwork","followers_url":"https://api.github.com/users/poanetwork/followers","following_url":"https://api.github.com/users/poanetwork/following{/other_user}","gists_url":"https://api.github.com/users/poanetwork/gists{/gist_id}","starred_url":"https://api.github.com/users/poanetwork/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/poanetwork/subscriptions","organizations_url":"https://api.github.com/users/poanetwork/orgs","repos_url":"https://api.github.com/users/poanetwork/repos","events_url":"https://api.github.com/users/poanetwork/events{/privacy}","received_events_url":"https://api.github.com/users/poanetwork/received_events","type":"Organization","site_admin":false},"repo":{"id":125307734,"node_id":"MDEwOlJlcG9zaXRvcnkxMjUzMDc3MzQ=","name":"hbbft","full_name":"poanetwork/hbbft","owner":{"login":"poanetwork","id":28943015,"node_id":"MDEyOk9yZ2FuaXphdGlvbjI4OTQzMDE1","avatar_url":"https://avatars2.githubusercontent.com/u/28943015?v=4","gravatar_id":"","url":"https://api.github.com/users/poanetwork","html_url":"https://github.com/poanetwork","followers_url":"https://api.github.com/users/poanetwork/followers","following_url":"https://api.github.com/users/poanetwork/following{/other_user}","gists_url":"https://api.github.com/users/poanetwork/gists{/gist_id}","starred_url":"https://api.github.com/users/poanetwork/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/poanetwork/subscriptions","organizations_url":"https://api.github.com/users/poanetwork/orgs","repos_url":"https://api.github.com/users/poanetwork/repos","events_url":"https://api.github.com/users/poanetwork/events{/privacy}","received_events_url":"https://api.github.com/users/poanetwork/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/poanetwork/hbbft","description":" An implementation of the paper \"Honey Badger of BFT Protocols\" in Rust. This is a modular library of consensus.","fork":false,"url":"https://api.github.com/repos/poanetwork/hbbft","forks_url":"https://api.github.com/repos/poanetwork/hbbft/forks","keys_url":"https://api.github.com/repos/poanetwork/hbbft/keys{/key_id}","collaborators_url":"https://api.github.com/repos/poanetwork/hbbft/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/poanetwork/hbbft/teams","hooks_url":"https://api.github.com/repos/poanetwork/hbbft/hooks","issue_events_url":"https://api.github.com/repos/poanetwork/hbbft/issues/events{/number}","events_url":"https://api.github.com/repos/poanetwork/hbbft/events","assignees_url":"https://api.github.com/repos/poanetwork/hbbft/assignees{/user}","branches_url":"https://api.github.com/repos/poanetwork/hbbft/branches{/branch}","tags_url":"https://api.github.com/repos/poanetwork/hbbft/tags","blobs_url":"https://api.github.com/repos/poanetwork/hbbft/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/poanetwork/hbbft/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/poanetwork/hbbft/git/refs{/sha}","trees_url":"https://api.github.com/repos/poanetwork/hbbft/git/trees{/sha}","statuses_url":"https://api.github.com/repos/poanetwork/hbbft/statuses/{sha}","languages_url":"https://api.github.com/repos/poanetwork/hbbft/languages","stargazers_url":"https://api.github.com/repos/poanetwork/hbbft/stargazers","contributors_url":"https://api.github.com/repos/poanetwork/hbbft/contributors","subscribers_url":"https://api.github.com/repos/poanetwork/hbbft/subscribers","subscription_url":"https://api.github.com/repos/poanetwork/hbbft/subscription","commits_url":"https://api.github.com/repos/poanetwork/hbbft/commits{/sha}","git_commits_url":"https://api.github.com/repos/poanetwork/hbbft/git/commits{/sha}","comments_url":"https://api.github.com/repos/poanetwork/hbbft/comments{/number}","issue_comment_url":"https://api.github.com/repos/poanetwork/hbbft/issues/comments{/number}","contents_url":"https://api.github.com/repos/poanetwork/hbbft/contents/{+path}","compare_url":"https://api.github.com/repos/poanetwork/hbbft/compare/{base}...{head}","merges_url":"https://api.github.com/repos/poanetwork/hbbft/merges","archive_url":"https://api.github.com/repos/poanetwork/hbbft/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/poanetwork/hbbft/downloads","issues_url":"https://api.github.com/repos/poanetwork/hbbft/issues{/number}","pulls_url":"https://api.github.com/repos/poanetwork/hbbft/pulls{/number}","milestones_url":"https://api.github.com/repos/poanetwork/hbbft/milestones{/number}","notifications_url":"https://api.github.com/repos/poanetwork/hbbft/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/poanetwork/hbbft/labels{/name}","releases_url":"https://api.github.com/repos/poanetwork/hbbft/releases{/id}","deployments_url":"https://api.github.com/repos/poanetwork/hbbft/deployments","created_at":"2018-03-15T03:32:34Z","updated_at":"2018-06-26T13:12:50Z","pushed_at":"2018-06-26T13:15:57Z","git_url":"git://github.com/poanetwork/hbbft.git","ssh_url":"git@github.com:poanetwork/hbbft.git","clone_url":"https://github.com/poanetwork/hbbft.git","svn_url":"https://github.com/poanetwork/hbbft","homepage":"","size":505,"stargazers_count":21,"watchers_count":21,"language":"Rust","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":6,"mirror_url":null,"archived":false,"open_issues_count":16,"license":{"key":"gpl-3.0","name":"GNU General Public License v3.0","spdx_id":"GPL-3.0","url":"https://api.github.com/licenses/gpl-3.0","node_id":"MDc6TGljZW5zZTk="},"forks":6,"open_issues":16,"watchers":21,"default_branch":"master"}},"base":{"label":"poanetwork:master","ref":"master","sha":"78fdf6354048c69f4657bac9006ee95f9930bfd5","user":{"login":"poanetwork","id":28943015,"node_id":"MDEyOk9yZ2FuaXphdGlvbjI4OTQzMDE1","avatar_url":"https://avatars2.githubusercontent.com/u/28943015?v=4","gravatar_id":"","url":"https://api.github.com/users/poanetwork","html_url":"https://github.com/poanetwork","followers_url":"https://api.github.com/users/poanetwork/followers","following_url":"https://api.github.com/users/poanetwork/following{/other_user}","gists_url":"https://api.github.com/users/poanetwork/gists{/gist_id}","starred_url":"https://api.github.com/users/poanetwork/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/poanetwork/subscriptions","organizations_url":"https://api.github.com/users/poanetwork/orgs","repos_url":"https://api.github.com/users/poanetwork/repos","events_url":"https://api.github.com/users/poanetwork/events{/privacy}","received_events_url":"https://api.github.com/users/poanetwork/received_events","type":"Organization","site_admin":false},"repo":{"id":125307734,"node_id":"MDEwOlJlcG9zaXRvcnkxMjUzMDc3MzQ=","name":"hbbft","full_name":"poanetwork/hbbft","owner":{"login":"poanetwork","id":28943015,"node_id":"MDEyOk9yZ2FuaXphdGlvbjI4OTQzMDE1","avatar_url":"https://avatars2.githubusercontent.com/u/28943015?v=4","gravatar_id":"","url":"https://api.github.com/users/poanetwork","html_url":"https://github.com/poanetwork","followers_url":"https://api.github.com/users/poanetwork/followers","following_url":"https://api.github.com/users/poanetwork/following{/other_user}","gists_url":"https://api.github.com/users/poanetwork/gists{/gist_id}","starred_url":"https://api.github.com/users/poanetwork/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/poanetwork/subscriptions","organizations_url":"https://api.github.com/users/poanetwork/orgs","repos_url":"https://api.github.com/users/poanetwork/repos","events_url":"https://api.github.com/users/poanetwork/events{/privacy}","received_events_url":"https://api.github.com/users/poanetwork/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/poanetwork/hbbft","description":" An implementation of the paper \"Honey Badger of BFT Protocols\" in Rust. This is a modular library of consensus.","fork":false,"url":"https://api.github.com/repos/poanetwork/hbbft","forks_url":"https://api.github.com/repos/poanetwork/hbbft/forks","keys_url":"https://api.github.com/repos/poanetwork/hbbft/keys{/key_id}","collaborators_url":"https://api.github.com/repos/poanetwork/hbbft/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/poanetwork/hbbft/teams","hooks_url":"https://api.github.com/repos/poanetwork/hbbft/hooks","issue_events_url":"https://api.github.com/repos/poanetwork/hbbft/issues/events{/number}","events_url":"https://api.github.com/repos/poanetwork/hbbft/events","assignees_url":"https://api.github.com/repos/poanetwork/hbbft/assignees{/user}","branches_url":"https://api.github.com/repos/poanetwork/hbbft/branches{/branch}","tags_url":"https://api.github.com/repos/poanetwork/hbbft/tags","blobs_url":"https://api.github.com/repos/poanetwork/hbbft/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/poanetwork/hbbft/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/poanetwork/hbbft/git/refs{/sha}","trees_url":"https://api.github.com/repos/poanetwork/hbbft/git/trees{/sha}","statuses_url":"https://api.github.com/repos/poanetwork/hbbft/statuses/{sha}","languages_url":"https://api.github.com/repos/poanetwork/hbbft/languages","stargazers_url":"https://api.github.com/repos/poanetwork/hbbft/stargazers","contributors_url":"https://api.github.com/repos/poanetwork/hbbft/contributors","subscribers_url":"https://api.github.com/repos/poanetwork/hbbft/subscribers","subscription_url":"https://api.github.com/repos/poanetwork/hbbft/subscription","commits_url":"https://api.github.com/repos/poanetwork/hbbft/commits{/sha}","git_commits_url":"https://api.github.com/repos/poanetwork/hbbft/git/commits{/sha}","comments_url":"https://api.github.com/repos/poanetwork/hbbft/comments{/number}","issue_comment_url":"https://api.github.com/repos/poanetwork/hbbft/issues/comments{/number}","contents_url":"https://api.github.com/repos/poanetwork/hbbft/contents/{+path}","compare_url":"https://api.github.com/repos/poanetwork/hbbft/compare/{base}...{head}","merges_url":"https://api.github.com/repos/poanetwork/hbbft/merges","archive_url":"https://api.github.com/repos/poanetwork/hbbft/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/poanetwork/hbbft/downloads","issues_url":"https://api.github.com/repos/poanetwork/hbbft/issues{/number}","pulls_url":"https://api.github.com/repos/poanetwork/hbbft/pulls{/number}","milestones_url":"https://api.github.com/repos/poanetwork/hbbft/milestones{/number}","notifications_url":"https://api.github.com/repos/poanetwork/hbbft/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/poanetwork/hbbft/labels{/name}","releases_url":"https://api.github.com/repos/poanetwork/hbbft/releases{/id}","deployments_url":"https://api.github.com/repos/poanetwork/hbbft/deployments","created_at":"2018-03-15T03:32:34Z","updated_at":"2018-06-26T13:12:50Z","pushed_at":"2018-06-26T13:15:57Z","git_url":"git://github.com/poanetwork/hbbft.git","ssh_url":"git@github.com:poanetwork/hbbft.git","clone_url":"https://github.com/poanetwork/hbbft.git","svn_url":"https://github.com/poanetwork/hbbft","homepage":"","size":505,"stargazers_count":21,"watchers_count":21,"language":"Rust","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":6,"mirror_url":null,"archived":false,"open_issues_count":16,"license":{"key":"gpl-3.0","name":"GNU General Public License v3.0","spdx_id":"GPL-3.0","url":"https://api.github.com/licenses/gpl-3.0","node_id":"MDc6TGljZW5zZTk="},"forks":6,"open_issues":16,"watchers":21,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/poanetwork/hbbft/pulls/86"},"html":{"href":"https://github.com/poanetwork/hbbft/pull/86"},"issue":{"href":"https://api.github.com/repos/poanetwork/hbbft/issues/86"},"comments":{"href":"https://api.github.com/repos/poanetwork/hbbft/issues/86/comments"},"review_comments":{"href":"https://api.github.com/repos/poanetwork/hbbft/pulls/86/comments"},"review_comment":{"href":"https://api.github.com/repos/poanetwork/hbbft/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/poanetwork/hbbft/pulls/86/commits"},"statuses":{"href":"https://api.github.com/repos/poanetwork/hbbft/statuses/a2c213d0aaf27f0c89baf533c0db7ccbd16965c3"}},"author_association":"CONTRIBUTOR"}}
{"actor":{"display_login":"afck"}}