├── .gitignore ├── LICENSE ├── README.md ├── cmd ├── dummy_client │ └── main.go └── main.go ├── common ├── future.go ├── lru.go ├── lru_test.go ├── rolling_list.go ├── rolling_list_test.go └── test_logger.go ├── crypto ├── crypto_test.go ├── pem_key.go └── utils.go ├── docker ├── babble │ └── Dockerfile ├── dummy │ ├── Dockerfile │ └── dummy ├── makefile └── scripts │ ├── bombard.sh │ ├── build-conf.sh │ ├── build-images.sh │ ├── demo.sh │ ├── run-testnet.sh │ ├── stop-testnet.sh │ └── watch.sh ├── glide.lock ├── glide.yaml ├── hashgraph ├── caches.go ├── caches_test.go ├── consensus_sorter.go ├── event.go ├── event_test.go ├── hashgraph.go ├── hashgraph_test.go ├── inmem_store.go ├── inmem_store_test.go ├── roundInfo.go └── store.go ├── img └── demo.png ├── net ├── commands.go ├── inmem_transport.go ├── inmem_transport_test.go ├── net_transport.go ├── net_transport_test.go ├── peer.go ├── peer_test.go ├── tcp_transport.go ├── tcp_transport_test.go ├── transport.go └── transport_test.go ├── node ├── config.go ├── core.go ├── core_test.go ├── node.go ├── node_test.go └── peer_selector.go ├── proxy ├── app │ ├── inmem_app_proxy.go │ ├── socket_app_proxy.go │ ├── socket_app_proxy_client.go │ └── socket_app_proxy_server.go ├── babble │ ├── socket_babble_proxy.go │ ├── socket_babble_proxy_client.go │ └── socket_babble_proxy_server.go ├── dummy.go ├── proxy.go └── socket_proxy_test.go ├── service └── service.go └── terraform ├── example.tf ├── makefile ├── scripts ├── bombard.sh ├── build-conf.sh ├── remote-kill.sh ├── remote-run.sh └── watch.sh └── variables.tf /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | 3 | */*.test 4 | */*.out 5 | 6 | */messages.txt 7 | 8 | cmd/debug 9 | 10 | vendor 11 | 12 | docker/babble/babble 13 | docker/dummy/dummy 14 | docker/conf/* 15 | 16 | terraform/babble.pem 17 | terraform/terraform.tfstate* 18 | terraform/secret.tfvars 19 | terraform/conf/* 20 | terraform/ips.dat 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BABBLE 2 | ## Consensus platform for distributed applications. 3 | 4 | Nodes in a distributed application require a component to communicate transactions 5 | between all participants before processing them locally in a consistent order. 6 | Babble is a plug and play solution for this component. It uses the Hashgraph 7 | consensus algorithm which offers definitive advantages over other BFT systems. 8 | 9 | ## Architecture 10 | ``` 11 | ======================================== 12 | = APP = 13 | = = 14 | = =============== ============== = 15 | = = Service = <-- = State = = 16 | = = = = = = 17 | = =============== ============== = 18 | = | | = 19 | = ======================== = 20 | = = Babble Proxy = = 21 | = ======================== = 22 | = | ^ = 23 | ===========|================|=========== 24 | | | 25 | --------- SubmitTx(tx) ---- CommitTx(tx) ------- (JSON-RPC/TCP) 26 | | | 27 | ==============|================|=============================== 28 | = BABBLE | | = 29 | = v | = 30 | = ======================== = 31 | = = App Proxy = = 32 | = = = = 33 | = ======================== = 34 | = | = 35 | = ======================================= = 36 | = = Core = = 37 | = = = = 38 | = = = ============ = 39 | = = ============= =========== = = Service = = 40 | = = = Hashgraph = = Store = = -- = = <----> HTTP API 41 | = = ============= =========== = = = = 42 | = = = ============ = 43 | = = = = 44 | = ======================================= = 45 | = | = 46 | = ======================================= = 47 | = = Transport = = 48 | = = = = 49 | = ======================================= = 50 | = ^ = 51 | ======================|======================================== 52 | | 53 | v 54 | 55 | Network 56 | ``` 57 | The above diagram shows how Babble fits in the typical architecture of a distributed 58 | application. Users interact with an App's Service which reads data from its State. 59 | However, the Service never updates the State directly. Instead, it passes commands 60 | to an ordering system which communicates to other nodes and feeds the commands back 61 | to the State in consensus order. Babble is an ordering system that plugs into the 62 | App thanks to a very simple JSON-RPC interface over TCP. 63 | 64 | ### Proxy 65 | 66 | The Babble node and the App are loosely coupled and can run in separate processes. 67 | They communicate via a very simple **JSON-RPC** interface over a **TCP** connection. 68 | 69 | The App submits transactions for consensus ordering via the **SubmitTx** endpoint 70 | exposed by the **App Proxy**. Babble asynchrously processes transactions and 71 | eventually feeds them back to the App, in consensus order, with a **CommitTx** 72 | message. 73 | 74 | Transactions are just raw bytes and Babble does not need to know what 75 | they represent. Therefore, encoding and decoding transactions is done by the App. 76 | 77 | Apps must implement their own **Babble Proxy** to submit and receive committed 78 | transactions from Babble. The advantage of using a JSON-RPC API is that there is 79 | no restriction on the programming language for the App. It only requires a component 80 | that sends SubmitTx messages to Babble and exposes a TCP enpoint where Babble can 81 | send CommitTx messages. 82 | 83 | When launching a Babble node, one must specify the address and port exposed by the 84 | Babble Proxy of the App. It is also possible to configure which address and port 85 | the App Proxy exposes. 86 | 87 | Example SubmitTx request (from App to Babble): 88 | ```json 89 | request: {"method":"Babble.SubmitTx","params":["Y2xpZW50IDE6IGhlbGxv"],"id":0} 90 | response: {"id":0,"result":true,"error":null} 91 | ``` 92 | 93 | Note that the Proxy API is **not** over HTTP; It is raw JSON over TCP. Here is an 94 | example of how to make a SubmitTx request manually: 95 | ```bash 96 | printf "{\"method\":\"Babble.SubmitTx\",\"params\":[\"Y2xpZW50IDE6IGhlbGxv\"],\"id\":0}" | nc -v 172.77.5.1 1338 97 | ``` 98 | 99 | Example CommitTx request (from Babble to App): 100 | ```json 101 | request: {"method":"State.CommitTx","params":["Y2xpZW50IDE6IGhlbGxv"],"id":0} 102 | response: {"id":0,"result":true,"error":null} 103 | ``` 104 | The content of "params" is the base64 encoding of the raw transaction bytes ("client1: hello"). 105 | 106 | ### Transport 107 | 108 | Babble nodes communicate with other Babble nodes in a fully connected Peer To Peer 109 | network. Nodes gossip by repeatedly choosing another node at random and asking that 110 | node for all the new information it has about the Hashgraph. The gossip protocol 111 | is extremely simple and serves the dual purpose of gossiping about transactions 112 | and about the gossip itself (the Hashgraph). The Hashraph contains enough information 113 | to compute a consensus ordering of transactions. 114 | 115 | The communication mechanism is a custom RPC protocol over TCP connections. At the 116 | moment, there is only one type of RPC command: **Sync**. When node **A** wants to 117 | sync with node **B**, it sends a **SyncRequest** to **B** containing what it knows 118 | about the Hashgraph. **B** computes what it knows that **A** doesn't know and 119 | returns a **SyncResponse** with the corresponding events in topological order. 120 | Upon receiving the **SyncResponse**, **A** updates its Hashgraph accordingly and 121 | calculates the consensus order. 122 | 123 | The list of peers must be predefined and known to all peers. At the moment, it is 124 | not possible to dynamically modify the list of peers while the network is running 125 | but this is not a limitation of the Hashgraph algorithm, just an implemention 126 | prioritization. 127 | 128 | ### Core 129 | 130 | The core of Babble is the component that maintains and computes the Hashgraph. 131 | The consensus algorithm, invented by Leemon Baird, is best described in the [white-paper](http://www.swirlds.com/downloads/SWIRLDS-TR-2016-01.pdf) 132 | and its [accompanying document](http://www.swirlds.com/downloads/SWIRLDS-TR-2016-02.pdf). 133 | 134 | The Hashgraph itself is a data structure that contains all the information about 135 | the history of the gossip and thereby grows and grows in size as gossip spreads. 136 | There are various strategies to keep the size of the Hashgraph limited. In our 137 | implementation, the **Hashgraph** object has a dependency on a **Store** object 138 | which contains the actual data and is abstracted behind an interface. 139 | 140 | The current implementation of the **Store** interface uses a set of in-memory LRU 141 | caches which can be extended to persist stale items to disk. The size of the LRU 142 | caches is configurable. 143 | 144 | ### Service 145 | 146 | The Service exposes an HTTP API to query information about a node. At the 147 | moment, it only exposes a **Stats** endpoint: 148 | 149 | ```bash 150 | $curl -s http://[ip]:8080/Stats | jq 151 | { 152 | "consensus_events": "199993", 153 | "consensus_transactions": "0", 154 | "events_per_second": "264.65", 155 | "id": "0", 156 | "last_consensus_round": "21999", 157 | "num_peers": "3", 158 | "round_events": "10", 159 | "rounds_per_second": "29.11", 160 | "sync_rate": "1.00", 161 | "transaction_pool": "0", 162 | "undetermined_events": "24" 163 | } 164 | ``` 165 | 166 | ## Usage 167 | 168 | ### Go 169 | Babble is written in [Golang](https://golang.org/). Hence, the first step is to install Go which is both 170 | the programming language and a CLI tool for managing Go code. Go is very opinionated 171 | and will require you to [define a workspace](https://golang.org/doc/code.html#Workspaces) where all your gocode will reside. 172 | 173 | ### Babble and dependencies 174 | Clone the [repository](https://github.com/babbleio/babble) in the appropriate GOPATH subdirectory: 175 | 176 | ```bash 177 | $ mkdir -p $GOPATH/src/github.com/babbleio/ 178 | $ cd $GOPATH/src/github.com/babbleio 179 | [...]/babbleio$ git clone https://github.com/babbleio/babble.git 180 | ``` 181 | Babble uses [Glide](http://github.com/Masterminds/glide) to manage dependencies. 182 | 183 | ```bash 184 | [...]/babble$ sudo add-apt-repository ppa:masterminds/glide && sudo apt-get update 185 | [...]/babble$ sudo apt-get install glide 186 | [...]/babble$ glide install 187 | ``` 188 | This will download all dependencies and put them in the **vendor** folder. 189 | 190 | ### Testing 191 | 192 | Babble has extensive unit-testing. Use the Go tool to run tests: 193 | ```bash 194 | [...]/babble$ go test $(glide novendor) 195 | ``` 196 | 197 | If everything goes well, it should output something along these lines: 198 | ``` 199 | ok github.com/babbleio/babble/net 0.052s 200 | ok github.com/babbleio/babble/common 0.011s 201 | ? github.com/babbleio/babble/cmd [no test files] 202 | ? github.com/babbleio/babble/cmd/dummy_client [no test files] 203 | ok github.com/babbleio/babble/hashgraph 0.174s 204 | ok github.com/babbleio/babble/node 1.699s 205 | ok github.com/babbleio/babble/proxy 0.018s 206 | ok github.com/babbleio/babble/crypto 0.028s 207 | ``` 208 | 209 | ### Docker Testnet 210 | 211 | To see Babble in action, we have provided a series of scripts to bootstrap a 212 | test network locally. 213 | 214 | Make sure you have [Docker](https://docker.com) installed. 215 | 216 | Then, run the testnet: 217 | 218 | ```bash 219 | [...]/babble$ cd docker 220 | [...]/babble/docker$ make 221 | ``` 222 | 223 | Once the testnet is started, a script is automatically launched to monitor consensus 224 | figures: 225 | 226 | ``` 227 | consensus_events:131055 consensus_transactions:0 events_per_second:265.53 id:0 last_consensus_round:14432 num_peers:3 round_events:10 rounds_per_second:29.24 sync_rate:1.00 transaction_pool:0 undetermined_events:26 228 | consensus_events:131055 consensus_transactions:0 events_per_second:266.39 id:3 last_consensus_round:14432 num_peers:3 round_events:10 rounds_per_second:29.34 sync_rate:1.00 transaction_pool:0 undetermined_events:25 229 | consensus_events:131055 consensus_transactions:0 events_per_second:267.30 id:2 last_consensus_round:14432 num_peers:3 round_events:10 rounds_per_second:29.44 sync_rate:1.00 transaction_pool:0 undetermined_events:31 230 | consensus_events:131067 consensus_transactions:0 events_per_second:268.27 id:1 last_consensus_round:14433 num_peers:3 round_events:11 rounds_per_second:29.54 sync_rate:1.00 transaction_pool:0 undetermined_events:21 231 | ``` 232 | 233 | Running ```docker ps -a``` will show you that 8 docker containers have been launched: 234 | ``` 235 | [...]/babble/docker$ docker ps -a 236 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 237 | 9e4c863c9e83 dummy "dummy '--name=cli..." 9 seconds ago Up 8 seconds 1339/tcp client4 238 | 40c92938a986 babble "babble run --cach..." 10 seconds ago Up 9 seconds 1337-1338/tcp node4 239 | 4c1eb201d29d dummy "dummy '--name=cli..." 10 seconds ago Up 9 seconds 1339/tcp client3 240 | cda62387e4fd babble "babble run --cach..." 11 seconds ago Up 9 seconds 1337-1338/tcp node3 241 | 765c73d66bcf dummy "dummy '--name=cli..." 11 seconds ago Up 10 seconds 1339/tcp client2 242 | a6895aaa141a babble "babble run --cach..." 11 seconds ago Up 10 seconds 1337-1338/tcp node2 243 | 8f996e13eda7 dummy "dummy '--name=cli..." 12 seconds ago Up 11 seconds 1339/tcp client1 244 | 36c97a968d22 babble "babble run --cach..." 12 seconds ago Up 11 seconds 1337-1338/tcp node1 245 | 246 | ``` 247 | Indeed, each node is comprised of an App and a Babble node (cf Architecture section). 248 | 249 | Run the **demo** script to play with the **Dummy App** which is a simple chat application 250 | powered by the Babble consensus platform: 251 | 252 | ``` 253 | [...]/babble/docker$ make demo 254 | ``` 255 | ![Demo](img/demo.png) 256 | 257 | 258 | Finally, stop the testnet: 259 | ``` 260 | [...]/babble/docker$ make stop 261 | ``` 262 | 263 | ### Terraform 264 | 265 | We have also created a set of scripts to deploy Babble testnets in AWS. This 266 | requires [Terraform](https://www.terraform.io/) and authentication keys for AWS. As it would be too slow to 267 | copy Babble over the network onto every node, it is best to create a custom AWS image (AMI) 268 | with Babble preinstalled in ~/bin. Basically the Terraform scripts launch a certain 269 | number of nodes in a subnet and starts Babble on them. 270 | 271 | ```bash 272 | [...]/babble$ cd terraform 273 | [...]/babble/terraform$ make "nodes=12" 274 | [...]/babble/terraform$ make watch # monitor Stats 275 | [...]/babble/terraform$ make bombard # send a bunch of transactions 276 | [...]/babble/terraform$ ssh -i babble.pem ubuntu@[public ip] # ssh into a node 277 | [...]/babble/terraform$ make destroy #destroy resources 278 | ``` -------------------------------------------------------------------------------- /cmd/dummy_client/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Mosaic Networks Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package main 17 | 18 | import ( 19 | "bufio" 20 | "fmt" 21 | "os" 22 | 23 | "github.com/Sirupsen/logrus" 24 | "gopkg.in/urfave/cli.v1" 25 | 26 | "github.com/babbleio/babble/proxy" 27 | ) 28 | 29 | var ( 30 | NameFlag = cli.StringFlag{ 31 | Name: "name", 32 | Usage: "Client Name", 33 | } 34 | ProxyAddressFlag = cli.StringFlag{ 35 | Name: "proxy_addr", 36 | Usage: "IP:Port to bind Proxy Server", 37 | Value: "127.0.0.1:1338", 38 | } 39 | ClientAddressFlag = cli.StringFlag{ 40 | Name: "client_addr", 41 | Usage: "IP:Port of Client App", 42 | Value: "127.0.0.1:1339", 43 | } 44 | LogLevelFlag = cli.StringFlag{ 45 | Name: "log_level", 46 | Usage: "debug, info, warn, error, fatal, panic", 47 | Value: "debug", 48 | } 49 | ) 50 | 51 | func main() { 52 | app := cli.NewApp() 53 | app.Name = "dummy" 54 | app.Usage = "Dummy Socket Client for Babble" 55 | app.Flags = []cli.Flag{ 56 | NameFlag, 57 | ProxyAddressFlag, 58 | ClientAddressFlag, 59 | LogLevelFlag, 60 | } 61 | app.Action = run 62 | app.Run(os.Args) 63 | } 64 | 65 | func run(c *cli.Context) error { 66 | logger := logrus.New() 67 | logger.Level = logLevel(c.String(LogLevelFlag.Name)) 68 | 69 | name := c.String(NameFlag.Name) 70 | proxyAddress := c.String(ProxyAddressFlag.Name) 71 | clientAddress := c.String(ClientAddressFlag.Name) 72 | 73 | logger.WithFields(logrus.Fields{ 74 | "name": name, 75 | "proxy_addr": proxyAddress, 76 | "client_addr": clientAddress, 77 | }).Debug("RUN") 78 | 79 | client, err := proxy.NewDummySocketClient(clientAddress, proxyAddress, logger) 80 | if err != nil { 81 | return err 82 | } 83 | 84 | scanner := bufio.NewScanner(os.Stdin) 85 | var text string 86 | for text != "q" { // break the loop if text == "q" 87 | fmt.Print("Enter your text: ") 88 | scanner.Scan() 89 | text = scanner.Text() 90 | if text != "q" { 91 | message := fmt.Sprintf("%s: %s", name, text) 92 | _, err := client.SubmitTx([]byte(message)) 93 | if err != nil { 94 | fmt.Printf("Error in SubmitTx: %v\n", err) 95 | } 96 | } 97 | } 98 | 99 | return nil 100 | } 101 | 102 | func logLevel(l string) logrus.Level { 103 | switch l { 104 | case "debug": 105 | return logrus.DebugLevel 106 | case "info": 107 | return logrus.InfoLevel 108 | case "warn": 109 | return logrus.WarnLevel 110 | case "error": 111 | return logrus.ErrorLevel 112 | case "fatal": 113 | return logrus.FatalLevel 114 | case "panic": 115 | return logrus.PanicLevel 116 | default: 117 | return logrus.DebugLevel 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /cmd/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Mosaic Networks Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package main 17 | 18 | import ( 19 | "fmt" 20 | "os" 21 | "os/user" 22 | "path/filepath" 23 | "runtime" 24 | "time" 25 | 26 | _ "net/http/pprof" 27 | 28 | "github.com/Sirupsen/logrus" 29 | "gopkg.in/urfave/cli.v1" 30 | 31 | "github.com/babbleio/babble/crypto" 32 | "github.com/babbleio/babble/net" 33 | "github.com/babbleio/babble/node" 34 | "github.com/babbleio/babble/proxy" 35 | aproxy "github.com/babbleio/babble/proxy/app" 36 | "github.com/babbleio/babble/service" 37 | ) 38 | 39 | var ( 40 | DataDirFlag = cli.StringFlag{ 41 | Name: "datadir", 42 | Usage: "Directory for the configuration", 43 | Value: defaultDataDir(), 44 | } 45 | NodeAddressFlag = cli.StringFlag{ 46 | Name: "node_addr", 47 | Usage: "IP:Port to bind Babble", 48 | Value: "127.0.0.1:1337", 49 | } 50 | NoClientFlag = cli.BoolFlag{ 51 | Name: "no_client", 52 | Usage: "Run Babble with dummy in-memory App client", 53 | } 54 | ProxyAddressFlag = cli.StringFlag{ 55 | Name: "proxy_addr", 56 | Usage: "IP:Port to bind Proxy Server", 57 | Value: "127.0.0.1:1338", 58 | } 59 | ClientAddressFlag = cli.StringFlag{ 60 | Name: "client_addr", 61 | Usage: "IP:Port of Client App", 62 | Value: "127.0.0.1:1339", 63 | } 64 | ServiceAddressFlag = cli.StringFlag{ 65 | Name: "service_addr", 66 | Usage: "IP:Port of HTTP Service", 67 | Value: "127.0.0.1:80", 68 | } 69 | LogLevelFlag = cli.StringFlag{ 70 | Name: "log_level", 71 | Usage: "debug, info, warn, error, fatal, panic", 72 | Value: "debug", 73 | } 74 | HeartbeatFlag = cli.IntFlag{ 75 | Name: "heartbeat", 76 | Usage: "Heartbeat timer milliseconds (time between gossips)", 77 | Value: 1000, 78 | } 79 | MaxPoolFlag = cli.IntFlag{ 80 | Name: "max_pool", 81 | Usage: "Max number of pooled connections", 82 | Value: 2, 83 | } 84 | TcpTimeoutFlag = cli.IntFlag{ 85 | Name: "tcp_timeout", 86 | Usage: "TCP timeout milliseconds", 87 | Value: 1000, 88 | } 89 | CacheSizeFlag = cli.IntFlag{ 90 | Name: "cache_size", 91 | Usage: "Number of items in LRU caches", 92 | Value: 500, 93 | } 94 | ) 95 | 96 | func main() { 97 | app := cli.NewApp() 98 | app.Name = "babble" 99 | app.Usage = "hashgraph consensus" 100 | app.Commands = []cli.Command{ 101 | { 102 | Name: "keygen", 103 | Usage: "Dump new key pair", 104 | Action: keygen, 105 | }, 106 | { 107 | Name: "run", 108 | Usage: "Run node", 109 | Action: run, 110 | Flags: []cli.Flag{ 111 | DataDirFlag, 112 | NodeAddressFlag, 113 | NoClientFlag, 114 | ProxyAddressFlag, 115 | ClientAddressFlag, 116 | ServiceAddressFlag, 117 | LogLevelFlag, 118 | HeartbeatFlag, 119 | MaxPoolFlag, 120 | TcpTimeoutFlag, 121 | CacheSizeFlag, 122 | }, 123 | }, 124 | } 125 | app.Run(os.Args) 126 | } 127 | 128 | func keygen(c *cli.Context) error { 129 | pemDump, err := crypto.GeneratePemKey() 130 | if err != nil { 131 | fmt.Println("Error generating PemDump") 132 | os.Exit(2) 133 | } 134 | 135 | fmt.Println("PublicKey:") 136 | fmt.Println(pemDump.PublicKey) 137 | fmt.Println("PrivateKey:") 138 | fmt.Println(pemDump.PrivateKey) 139 | 140 | return nil 141 | } 142 | 143 | func run(c *cli.Context) error { 144 | logger := logrus.New() 145 | logger.Level = logLevel(c.String(LogLevelFlag.Name)) 146 | 147 | datadir := c.String(DataDirFlag.Name) 148 | addr := c.String(NodeAddressFlag.Name) 149 | noclient := c.Bool(NoClientFlag.Name) 150 | proxyAddress := c.String(ProxyAddressFlag.Name) 151 | clientAddress := c.String(ClientAddressFlag.Name) 152 | serviceAddress := c.String(ServiceAddressFlag.Name) 153 | heartbeat := c.Int(HeartbeatFlag.Name) 154 | maxPool := c.Int(MaxPoolFlag.Name) 155 | tcpTimeout := c.Int(TcpTimeoutFlag.Name) 156 | cacheSize := c.Int(CacheSizeFlag.Name) 157 | logger.WithFields(logrus.Fields{ 158 | "datadir": datadir, 159 | "node_addr": addr, 160 | "no_client": noclient, 161 | "proxy_addr": proxyAddress, 162 | "client_addr": clientAddress, 163 | "service_addr": serviceAddress, 164 | "heartbeat": heartbeat, 165 | "max_pool": maxPool, 166 | "tcp_timeout": tcpTimeout, 167 | "cache_size": cacheSize, 168 | }).Debug("RUN") 169 | 170 | conf := node.NewConfig(time.Duration(heartbeat)*time.Millisecond, 171 | time.Duration(tcpTimeout)*time.Millisecond, 172 | cacheSize, logger) 173 | 174 | // Create the PEM key 175 | pemKey := crypto.NewPemKey(datadir) 176 | 177 | // Try a read 178 | key, err := pemKey.ReadKey() 179 | if err != nil { 180 | return err 181 | } 182 | 183 | // Create the peer store 184 | store := net.NewJSONPeers(datadir) 185 | 186 | // Try a read 187 | peers, err := store.Peers() 188 | if err != nil { 189 | return err 190 | } 191 | 192 | trans, err := net.NewTCPTransport(addr, 193 | nil, maxPool, conf.TCPTimeout, logger) 194 | if err != nil { 195 | return err 196 | } 197 | 198 | var prox proxy.AppProxy 199 | if noclient { 200 | prox = aproxy.NewInmemAppProxy(logger) 201 | } else { 202 | prox = aproxy.NewSocketAppProxy(clientAddress, proxyAddress, 203 | conf.TCPTimeout, logger) 204 | } 205 | 206 | node := node.NewNode(conf, key, peers, trans, prox) 207 | node.Init() 208 | 209 | serviceServer := service.NewService(serviceAddress, &node, logger) 210 | go serviceServer.Serve() 211 | 212 | node.Run(true) 213 | 214 | return nil 215 | } 216 | 217 | func defaultDataDir() string { 218 | // Try to place the data folder in the user's home dir 219 | home := homeDir() 220 | if home != "" { 221 | if runtime.GOOS == "darwin" { 222 | return filepath.Join(home, "Library", "BABBLE") 223 | } else if runtime.GOOS == "windows" { 224 | return filepath.Join(home, "AppData", "Roaming", "BABBLE") 225 | } else { 226 | return filepath.Join(home, ".babble") 227 | } 228 | } 229 | // As we cannot guess a stable location, return empty and handle later 230 | return "" 231 | } 232 | 233 | func homeDir() string { 234 | if home := os.Getenv("HOME"); home != "" { 235 | return home 236 | } 237 | if usr, err := user.Current(); err == nil { 238 | return usr.HomeDir 239 | } 240 | return "" 241 | } 242 | 243 | func logLevel(l string) logrus.Level { 244 | switch l { 245 | case "debug": 246 | return logrus.DebugLevel 247 | case "info": 248 | return logrus.InfoLevel 249 | case "warn": 250 | return logrus.WarnLevel 251 | case "error": 252 | return logrus.ErrorLevel 253 | case "fatal": 254 | return logrus.FatalLevel 255 | case "panic": 256 | return logrus.PanicLevel 257 | default: 258 | return logrus.DebugLevel 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /common/future.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Mosaic Networks Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package common 17 | 18 | // Future is used to represent an action that may occur in the future. 19 | type Future interface { 20 | // Error blocks until the future arrives and then 21 | // returns the error status of the future. 22 | // This may be called any number of times - all 23 | // calls will return the same value. 24 | // Note that it is not OK to call this method 25 | // twice concurrently on the same Future instance. 26 | Error() error 27 | } 28 | -------------------------------------------------------------------------------- /common/lru.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Mosaic Networks Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package common 17 | 18 | //TAKEN FROM HASHICORP LRU 19 | 20 | import "container/list" 21 | 22 | // EvictCallback is used to get a callback when a cache entry is evicted 23 | type EvictCallback func(key interface{}, value interface{}) 24 | 25 | // LRU implements a non-thread safe fixed size LRU cache 26 | type LRU struct { 27 | size int 28 | evictList *list.List 29 | items map[interface{}]*list.Element 30 | onEvict EvictCallback 31 | } 32 | 33 | // entry is used to hold a value in the evictList 34 | type entry struct { 35 | key interface{} 36 | value interface{} 37 | } 38 | 39 | // NewLRU constructs an LRU of the given size 40 | func NewLRU(size int, onEvict EvictCallback) *LRU { 41 | c := &LRU{ 42 | size: size, 43 | evictList: list.New(), 44 | items: make(map[interface{}]*list.Element), 45 | onEvict: onEvict, 46 | } 47 | return c 48 | } 49 | 50 | // Purge is used to completely clear the cache 51 | func (c *LRU) Purge() { 52 | for k, v := range c.items { 53 | if c.onEvict != nil { 54 | c.onEvict(k, v.Value.(*entry).value) 55 | } 56 | delete(c.items, k) 57 | } 58 | c.evictList.Init() 59 | } 60 | 61 | // Add adds a value to the cache. Returns true if an eviction occurred. 62 | func (c *LRU) Add(key, value interface{}) bool { 63 | // Check for existing item 64 | if ent, ok := c.items[key]; ok { 65 | c.evictList.MoveToFront(ent) 66 | ent.Value.(*entry).value = value 67 | return false 68 | } 69 | 70 | // Add new item 71 | ent := &entry{key, value} 72 | entry := c.evictList.PushFront(ent) 73 | c.items[key] = entry 74 | 75 | evict := c.evictList.Len() > c.size 76 | // Verify size not exceeded 77 | if evict { 78 | c.removeOldest() 79 | } 80 | return evict 81 | } 82 | 83 | // Get looks up a key's value from the cache. 84 | func (c *LRU) Get(key interface{}) (value interface{}, ok bool) { 85 | if ent, ok := c.items[key]; ok { 86 | c.evictList.MoveToFront(ent) 87 | return ent.Value.(*entry).value, true 88 | } 89 | return 90 | } 91 | 92 | // Check if a key is in the cache, without updating the recent-ness 93 | // or deleting it for being stale. 94 | func (c *LRU) Contains(key interface{}) (ok bool) { 95 | _, ok = c.items[key] 96 | return ok 97 | } 98 | 99 | // Returns the key value (or undefined if not found) without updating 100 | // the "recently used"-ness of the key. 101 | func (c *LRU) Peek(key interface{}) (value interface{}, ok bool) { 102 | if ent, ok := c.items[key]; ok { 103 | return ent.Value.(*entry).value, true 104 | } 105 | return nil, ok 106 | } 107 | 108 | // Remove removes the provided key from the cache, returning if the 109 | // key was contained. 110 | func (c *LRU) Remove(key interface{}) bool { 111 | if ent, ok := c.items[key]; ok { 112 | c.removeElement(ent) 113 | return true 114 | } 115 | return false 116 | } 117 | 118 | // RemoveOldest removes the oldest item from the cache. 119 | func (c *LRU) RemoveOldest() (interface{}, interface{}, bool) { 120 | ent := c.evictList.Back() 121 | if ent != nil { 122 | c.removeElement(ent) 123 | kv := ent.Value.(*entry) 124 | return kv.key, kv.value, true 125 | } 126 | return nil, nil, false 127 | } 128 | 129 | // GetOldest returns the oldest entry 130 | func (c *LRU) GetOldest() (interface{}, interface{}, bool) { 131 | ent := c.evictList.Back() 132 | if ent != nil { 133 | kv := ent.Value.(*entry) 134 | return kv.key, kv.value, true 135 | } 136 | return nil, nil, false 137 | } 138 | 139 | // Keys returns a slice of the keys in the cache, from oldest to newest. 140 | func (c *LRU) Keys() []interface{} { 141 | keys := make([]interface{}, len(c.items)) 142 | i := 0 143 | for ent := c.evictList.Back(); ent != nil; ent = ent.Prev() { 144 | keys[i] = ent.Value.(*entry).key 145 | i++ 146 | } 147 | return keys 148 | } 149 | 150 | // Len returns the number of items in the cache. 151 | func (c *LRU) Len() int { 152 | return c.evictList.Len() 153 | } 154 | 155 | // removeOldest removes the oldest item from the cache. 156 | func (c *LRU) removeOldest() { 157 | ent := c.evictList.Back() 158 | if ent != nil { 159 | c.removeElement(ent) 160 | } 161 | } 162 | 163 | // removeElement is used to remove a given list element from the cache 164 | func (c *LRU) removeElement(e *list.Element) { 165 | c.evictList.Remove(e) 166 | kv := e.Value.(*entry) 167 | delete(c.items, kv.key) 168 | if c.onEvict != nil { 169 | c.onEvict(kv.key, kv.value) 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /common/lru_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Mosaic Networks Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package common 17 | 18 | import "testing" 19 | 20 | func TestLRU(t *testing.T) { 21 | evictCounter := 0 22 | onEvicted := func(k interface{}, v interface{}) { 23 | if k != v { 24 | t.Fatalf("Evict values not equal (%v!=%v)", k, v) 25 | } 26 | evictCounter += 1 27 | } 28 | l := NewLRU(128, onEvicted) 29 | 30 | for i := 0; i < 256; i++ { 31 | l.Add(i, i) 32 | } 33 | if l.Len() != 128 { 34 | t.Fatalf("bad len: %v", l.Len()) 35 | } 36 | 37 | if evictCounter != 128 { 38 | t.Fatalf("bad evict count: %v", evictCounter) 39 | } 40 | 41 | for i, k := range l.Keys() { 42 | if v, ok := l.Get(k); !ok || v != k || v != i+128 { 43 | t.Fatalf("bad key: %v", k) 44 | } 45 | } 46 | for i := 0; i < 128; i++ { 47 | _, ok := l.Get(i) 48 | if ok { 49 | t.Fatalf("should be evicted") 50 | } 51 | } 52 | for i := 128; i < 256; i++ { 53 | _, ok := l.Get(i) 54 | if !ok { 55 | t.Fatalf("should not be evicted") 56 | } 57 | } 58 | for i := 128; i < 192; i++ { 59 | ok := l.Remove(i) 60 | if !ok { 61 | t.Fatalf("should be contained") 62 | } 63 | ok = l.Remove(i) 64 | if ok { 65 | t.Fatalf("should not be contained") 66 | } 67 | _, ok = l.Get(i) 68 | if ok { 69 | t.Fatalf("should be deleted") 70 | } 71 | } 72 | 73 | l.Get(192) // expect 192 to be last key in l.Keys() 74 | 75 | for i, k := range l.Keys() { 76 | if (i < 63 && k != i+193) || (i == 63 && k != 192) { 77 | t.Fatalf("out of order key: %v", k) 78 | } 79 | } 80 | 81 | l.Purge() 82 | if l.Len() != 0 { 83 | t.Fatalf("bad len: %v", l.Len()) 84 | } 85 | if _, ok := l.Get(200); ok { 86 | t.Fatalf("should contain nothing") 87 | } 88 | } 89 | 90 | func TestLRU_GetOldest_RemoveOldest(t *testing.T) { 91 | l := NewLRU(128, nil) 92 | 93 | for i := 0; i < 256; i++ { 94 | l.Add(i, i) 95 | } 96 | k, _, ok := l.GetOldest() 97 | if !ok { 98 | t.Fatalf("missing") 99 | } 100 | if k.(int) != 128 { 101 | t.Fatalf("bad: %v", k) 102 | } 103 | 104 | k, _, ok = l.RemoveOldest() 105 | if !ok { 106 | t.Fatalf("missing") 107 | } 108 | if k.(int) != 128 { 109 | t.Fatalf("bad: %v", k) 110 | } 111 | 112 | k, _, ok = l.RemoveOldest() 113 | if !ok { 114 | t.Fatalf("missing") 115 | } 116 | if k.(int) != 129 { 117 | t.Fatalf("bad: %v", k) 118 | } 119 | } 120 | 121 | // Test that Add returns true/false if an eviction occurred 122 | func TestLRU_Add(t *testing.T) { 123 | evictCounter := 0 124 | onEvicted := func(k interface{}, v interface{}) { 125 | evictCounter += 1 126 | } 127 | 128 | l := NewLRU(1, onEvicted) 129 | 130 | if l.Add(1, 1) == true || evictCounter != 0 { 131 | t.Errorf("should not have an eviction") 132 | } 133 | if l.Add(2, 2) == false || evictCounter != 1 { 134 | t.Errorf("should have an eviction") 135 | } 136 | } 137 | 138 | // Test that Contains doesn't update recent-ness 139 | func TestLRU_Contains(t *testing.T) { 140 | l := NewLRU(2, nil) 141 | 142 | l.Add(1, 1) 143 | l.Add(2, 2) 144 | if !l.Contains(1) { 145 | t.Errorf("1 should be contained") 146 | } 147 | 148 | l.Add(3, 3) 149 | if l.Contains(1) { 150 | t.Errorf("Contains should not have updated recent-ness of 1") 151 | } 152 | } 153 | 154 | // Test that Peek doesn't update recent-ness 155 | func TestLRU_Peek(t *testing.T) { 156 | l := NewLRU(2, nil) 157 | 158 | l.Add(1, 1) 159 | l.Add(2, 2) 160 | if v, ok := l.Peek(1); !ok || v != 1 { 161 | t.Errorf("1 should be set to 1: %v, %v", v, ok) 162 | } 163 | 164 | l.Add(3, 3) 165 | if l.Contains(1) { 166 | t.Errorf("should not have updated recent-ness of 1") 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /common/rolling_list.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Mosaic Networks Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package common 17 | 18 | import "errors" 19 | 20 | var ( 21 | ErrKeyNotFound = errors.New("not found") 22 | ErrTooLate = errors.New("too late") 23 | ) 24 | 25 | type RollingList struct { 26 | size int 27 | tot int 28 | items []interface{} 29 | } 30 | 31 | func NewRollingList(size int) *RollingList { 32 | return &RollingList{ 33 | size: size, 34 | items: make([]interface{}, 0, 2*size), 35 | } 36 | } 37 | 38 | func (r *RollingList) Get() (lastWindow []interface{}, tot int) { 39 | return r.items, r.tot 40 | } 41 | 42 | func (r *RollingList) GetItem(index int) (interface{}, error) { 43 | items := len(r.items) 44 | oldestCached := r.tot - items 45 | if index < oldestCached { 46 | return nil, ErrTooLate 47 | } 48 | findex := index - oldestCached 49 | if findex >= items { 50 | return nil, ErrKeyNotFound 51 | } 52 | return r.items[findex], nil 53 | } 54 | 55 | func (r *RollingList) Add(item interface{}) { 56 | if len(r.items) >= 2*r.size { 57 | r.Roll() 58 | } 59 | r.items = append(r.items, item) 60 | r.tot++ 61 | } 62 | 63 | func (r *RollingList) Roll() { 64 | newList := make([]interface{}, 0, 2*r.size) 65 | newList = append(newList, r.items[r.size:]...) 66 | r.items = newList 67 | } 68 | -------------------------------------------------------------------------------- /common/rolling_list_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Mosaic Networks Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package common 17 | 18 | import ( 19 | "fmt" 20 | "testing" 21 | ) 22 | 23 | func TestRollingWindow(t *testing.T) { 24 | size := 10 25 | testSize := 3 * size 26 | rollingList := NewRollingList(size) 27 | items := []string{} 28 | for i := 0; i < testSize; i++ { 29 | item := fmt.Sprintf("item%d", i) 30 | rollingList.Add(item) 31 | items = append(items, item) 32 | } 33 | cached, ts := rollingList.Get() 34 | 35 | if ts != testSize { 36 | t.Fatalf("tot should be %d, not %d", testSize, ts) 37 | } 38 | 39 | start := (testSize / (2 * size)) * (size) 40 | count := testSize - start 41 | 42 | for i := 0; i < count; i++ { 43 | if cached[i] != items[start+i] { 44 | t.Fatalf("cached[%d] should be %s, not %s", i, items[start+i], cached[i]) 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /common/test_logger.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Mosaic Networks Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package common 17 | 18 | import ( 19 | "testing" 20 | 21 | "github.com/Sirupsen/logrus" 22 | ) 23 | 24 | // This can be used as the destination for a logger and it'll 25 | // map them into calls to testing.T.Log, so that you only see 26 | // the logging for failed tests. 27 | type testLoggerAdapter struct { 28 | t *testing.T 29 | prefix string 30 | } 31 | 32 | func (a *testLoggerAdapter) Write(d []byte) (int, error) { 33 | if d[len(d)-1] == '\n' { 34 | d = d[:len(d)-1] 35 | } 36 | if a.prefix != "" { 37 | l := a.prefix + ": " + string(d) 38 | a.t.Log(l) 39 | return len(l), nil 40 | } 41 | a.t.Log(string(d)) 42 | return len(d), nil 43 | } 44 | 45 | func NewTestLogger(t *testing.T) *logrus.Logger { 46 | logger := logrus.New() 47 | logger.Out = &testLoggerAdapter{t: t} 48 | logger.Level = logrus.DebugLevel 49 | return logger 50 | } 51 | 52 | type benchmarkLoggerAdapter struct { 53 | b *testing.B 54 | prefix string 55 | } 56 | 57 | func (b *benchmarkLoggerAdapter) Write(d []byte) (int, error) { 58 | if d[len(d)-1] == '\n' { 59 | d = d[:len(d)-1] 60 | } 61 | if b.prefix != "" { 62 | l := b.prefix + ": " + string(d) 63 | b.b.Log(l) 64 | return len(l), nil 65 | } 66 | 67 | b.b.Log(string(d)) 68 | return len(d), nil 69 | } 70 | 71 | func NewBenchmarkLogger(b *testing.B) *logrus.Logger { 72 | logger := logrus.New() 73 | logger.Out = &benchmarkLoggerAdapter{b: b} 74 | logger.Level = logrus.DebugLevel 75 | return logger 76 | } 77 | -------------------------------------------------------------------------------- /crypto/crypto_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Mosaic Networks Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package crypto 17 | 18 | import ( 19 | "io/ioutil" 20 | "os" 21 | "reflect" 22 | "testing" 23 | ) 24 | 25 | func TestPem(t *testing.T) { 26 | // Create a test dir 27 | dir, err := ioutil.TempDir("", "babble") 28 | if err != nil { 29 | t.Fatalf("err: %v ", err) 30 | } 31 | defer os.RemoveAll(dir) 32 | 33 | // Create the PEM key 34 | pemKey := NewPemKey(dir) 35 | 36 | // Try a read, should get nothing 37 | key, err := pemKey.ReadKey() 38 | if err != nil { 39 | t.Fatalf("err: %v", err) 40 | } 41 | if key != nil { 42 | t.Fatalf("key is not nil") 43 | } 44 | 45 | // Initialize a key 46 | key, _ = GenerateECDSAKey() 47 | if err := pemKey.WriteKey(key); err != nil { 48 | t.Fatalf("err: %v", err) 49 | } 50 | 51 | // Try a read, should get key 52 | nKey, err := pemKey.ReadKey() 53 | if err != nil { 54 | t.Fatalf("err: %v", err) 55 | } 56 | if !reflect.DeepEqual(*nKey, *key) { 57 | t.Fatalf("Keys do not match") 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /crypto/pem_key.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Mosaic Networks Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package crypto 17 | 18 | import ( 19 | "crypto/ecdsa" 20 | "crypto/x509" 21 | "encoding/pem" 22 | "fmt" 23 | "io/ioutil" 24 | "os" 25 | "path/filepath" 26 | "sync" 27 | ) 28 | 29 | const ( 30 | pemKeyPath = "priv_key.pem" 31 | ) 32 | 33 | type PemKey struct { 34 | l sync.Mutex 35 | path string 36 | } 37 | 38 | func NewPemKey(base string) *PemKey { 39 | path := filepath.Join(base, pemKeyPath) 40 | pemKey := &PemKey{ 41 | path: path, 42 | } 43 | return pemKey 44 | } 45 | 46 | func (k *PemKey) ReadKey() (*ecdsa.PrivateKey, error) { 47 | k.l.Lock() 48 | defer k.l.Unlock() 49 | 50 | // Read the file 51 | buf, err := ioutil.ReadFile(k.path) 52 | if err != nil && !os.IsNotExist(err) { 53 | return nil, err 54 | } 55 | 56 | // Check for no key 57 | if len(buf) == 0 { 58 | return nil, nil 59 | } 60 | 61 | // Decode the PEM key 62 | block, _ := pem.Decode(buf) 63 | if block == nil { 64 | return nil, fmt.Errorf("Error decoding PEM block from data") 65 | } 66 | return x509.ParseECPrivateKey(block.Bytes) 67 | } 68 | 69 | func (k *PemKey) WriteKey(key *ecdsa.PrivateKey) error { 70 | k.l.Lock() 71 | defer k.l.Unlock() 72 | 73 | b, err := x509.MarshalECPrivateKey(key) 74 | if err != nil { 75 | return err 76 | } 77 | pemBlock := &pem.Block{Type: "EC PRIVATE KEY", Bytes: b} 78 | data := pem.EncodeToMemory(pemBlock) 79 | return ioutil.WriteFile(k.path, data, 0755) 80 | } 81 | 82 | type PemDump struct { 83 | PublicKey string 84 | PrivateKey string 85 | } 86 | 87 | func GeneratePemKey() (*PemDump, error) { 88 | key, err := GenerateECDSAKey() 89 | if err != nil { 90 | return nil, err 91 | } 92 | 93 | pub := fmt.Sprintf("0x%X", FromECDSAPub(&key.PublicKey)) 94 | 95 | b, err := x509.MarshalECPrivateKey(key) 96 | if err != nil { 97 | return nil, err 98 | } 99 | pemBlock := &pem.Block{Type: "EC PRIVATE KEY", Bytes: b} 100 | data := pem.EncodeToMemory(pemBlock) 101 | 102 | pemDump := PemDump{ 103 | PublicKey: pub, 104 | PrivateKey: string(data), 105 | } 106 | 107 | return &pemDump, err 108 | } 109 | -------------------------------------------------------------------------------- /crypto/utils.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Mosaic Networks Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package crypto 17 | 18 | import ( 19 | "crypto/ecdsa" 20 | "crypto/elliptic" 21 | "crypto/rand" 22 | "crypto/sha256" 23 | "math/big" 24 | ) 25 | 26 | func SHA256(hashBytes []byte) []byte { 27 | hasher := sha256.New() 28 | hasher.Write(hashBytes) 29 | hash := hasher.Sum(nil) 30 | return hash 31 | } 32 | 33 | func GenerateECDSAKey() (*ecdsa.PrivateKey, error) { 34 | return ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 35 | } 36 | 37 | func ToECDSAPub(pub []byte) *ecdsa.PublicKey { 38 | if len(pub) == 0 { 39 | return nil 40 | } 41 | x, y := elliptic.Unmarshal(elliptic.P256(), pub) 42 | return &ecdsa.PublicKey{Curve: elliptic.P256(), X: x, Y: y} 43 | } 44 | 45 | func FromECDSAPub(pub *ecdsa.PublicKey) []byte { 46 | if pub == nil || pub.X == nil || pub.Y == nil { 47 | return nil 48 | } 49 | return elliptic.Marshal(elliptic.P256(), pub.X, pub.Y) 50 | } 51 | 52 | func Sign(priv *ecdsa.PrivateKey, hash []byte) (r, s *big.Int, err error) { 53 | return ecdsa.Sign(rand.Reader, priv, hash) 54 | } 55 | 56 | func Verify(pub *ecdsa.PublicKey, hash []byte, r, s *big.Int) bool { 57 | return ecdsa.Verify(pub, hash, r, s) 58 | } 59 | -------------------------------------------------------------------------------- /docker/babble/Dockerfile: -------------------------------------------------------------------------------- 1 | from ubuntu:trusty 2 | ADD babble /usr/local/bin 3 | EXPOSE 1337 1338 80 4 | ENV HOME=/ 5 | ENTRYPOINT ["babble"] 6 | CMD [] 7 | -------------------------------------------------------------------------------- /docker/dummy/Dockerfile: -------------------------------------------------------------------------------- 1 | from alpine 2 | ADD dummy /usr/local/bin 3 | EXPOSE 1339 4 | ENV HOME=/ 5 | ENTRYPOINT ["dummy"] 6 | CMD [] -------------------------------------------------------------------------------- /docker/dummy/dummy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpitid/babble/81345a125b20c403f35c13dc03d077b8b23d3dc7/docker/dummy/dummy -------------------------------------------------------------------------------- /docker/makefile: -------------------------------------------------------------------------------- 1 | nodes = 4 2 | txs = 1000 3 | 4 | up: build conf start watch 5 | 6 | build : 7 | ./scripts/build-images.sh 8 | 9 | conf : 10 | rm -rf conf 11 | ./scripts/build-conf.sh $(nodes) 12 | 13 | start: 14 | ./scripts/run-testnet.sh $(nodes) 15 | 16 | watch: 17 | ./scripts/watch.sh $(nodes) 18 | 19 | stop: 20 | ./scripts/stop-testnet.sh 21 | 22 | demo: 23 | ./scripts/demo.sh $(nodes) 24 | 25 | bombard: 26 | ./scripts/bombard.sh $(nodes) $(txs) 27 | 28 | .PHONY: up build conf start watch stop demo bombard 29 | -------------------------------------------------------------------------------- /docker/scripts/bombard.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -u 4 | 5 | NODES=${1:-4} 6 | COUNT=${2:-1000} 7 | 8 | 9 | for i in `seq 1 $COUNT`; do 10 | for n in `seq 1 $NODES`; do 11 | printf "Node$n Tx$i" | base64 | \ 12 | xargs printf "{\"method\":\"Babble.SubmitTx\",\"params\":[\"%s\"],\"id\":$i}" | \ 13 | nc -v 172.77.5.$n 1338 14 | done; 15 | done; 16 | 17 | -------------------------------------------------------------------------------- /docker/scripts/build-conf.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This script creates the configuration for a Babble testnet with a variable 4 | # number of nodes. It will generate crytographic key pairs and assemble a peers.json 5 | # file in the format used by Babble. The files are copied into individual folders 6 | # for each node so that these folders can be used as the datadir that Babble reads 7 | # configuration from. 8 | 9 | set -e 10 | 11 | N=${1:-4} 12 | DEST=${2:-"conf"} 13 | IPBASE=${3:-172.77.5.} 14 | PORT=${4:-1337} 15 | 16 | for i in $(seq 1 $N) 17 | do 18 | dest=$DEST/node$i 19 | mkdir -p $dest 20 | babble keygen | sed -n -e "2 w $dest/pub" -e "4,+4 w $dest/priv_key.pem" 21 | echo "$IPBASE$i:$PORT" > $dest/addr 22 | done 23 | 24 | PFILE=$DEST/peers.json 25 | echo "[" > $PFILE 26 | for i in $(seq 1 $N) 27 | do 28 | com="," 29 | if [[ $i == $N ]]; then 30 | com="" 31 | fi 32 | 33 | printf "\t{\n" >> $PFILE 34 | printf "\t\t\"NetAddr\":\"$(cat $DEST/node$i/addr)\",\n" >> $PFILE 35 | printf "\t\t\"PubKeyHex\":\"$(cat $DEST/node$i/pub)\"\n" >> $PFILE 36 | printf "\t}%s\n" $com >> $PFILE 37 | 38 | done 39 | echo "]" >> $PFILE 40 | 41 | for i in $(seq 1 $N) 42 | do 43 | dest=$DEST/node$i 44 | cp $DEST/peers.json $dest/ 45 | rm $dest/addr $dest/pub 46 | done 47 | 48 | -------------------------------------------------------------------------------- /docker/scripts/build-images.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | CGO_ENABLED=0 go build -ldflags="-s -w" -o babble/babble ../cmd/main.go 3 | CGO_ENABLED=0 go build -o dummy/dummy ../cmd/dummy_client/main.go 4 | docker build --no-cache=true -t babble babble/ 5 | docker build --no-cache=true -t dummy dummy/ -------------------------------------------------------------------------------- /docker/scripts/demo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | xterm -geometry 50x10+100+100 -bg blue -xrm 'XTerm.vt100.allowTitleOps: false' -T 'CLIENT 1 INPUT' -e 'docker attach client1' & 4 | xterm -geometry 50x10+420+100 -bg blue -xrm 'XTerm.vt100.allowTitleOps: false' -T 'CLIENT 1 MESSAGES' -e "docker exec client1 watch -n 1 cat messages.txt" & 5 | 6 | xterm -geometry 50x10+100+280 -bg blue -xrm 'XTerm.vt100.allowTitleOps: false' -T 'CLIENT 2 INPUT' -e "docker attach client2" & 7 | xterm -geometry 50x10+420+280 -bg blue -xrm 'XTerm.vt100.allowTitleOps: false' -T 'CLIENT 2 MESSAGES' -e "docker exec client2 watch -n 1 cat messages.txt" & 8 | -------------------------------------------------------------------------------- /docker/scripts/run-testnet.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eux 4 | 5 | N=${1:-4} 6 | MPWD=$(pwd) 7 | 8 | docker network create \ 9 | --driver=bridge \ 10 | --subnet=172.77.0.0/16 \ 11 | --ip-range=172.77.5.0/24 \ 12 | --gateway=172.77.5.254 \ 13 | babblenet 14 | 15 | for i in $(seq 1 $N) 16 | do 17 | docker create --name=node$i --net=babblenet --ip=172.77.5.$i babble run \ 18 | --cache_size=50000 \ 19 | --tcp_timeout=200 \ 20 | --heartbeat=10 \ 21 | --node_addr="172.77.5.$i:1337" \ 22 | --proxy_addr="172.77.5.$i:1338" \ 23 | --client_addr="172.77.5.$(($N+$i)):1339" \ 24 | --service_addr="172.77.5.$i:80" 25 | docker cp $MPWD/conf/node$i node$i:/.babble 26 | docker start node$i 27 | 28 | docker run -d --name=client$i --net=babblenet --ip=172.77.5.$(($N+$i)) -it dummy \ 29 | --name="client $i" \ 30 | --client_addr="172.77.5.$(($N+$i)):1339" \ 31 | --proxy_addr="172.77.5.$i:1338" \ 32 | --log_level="info" 33 | done -------------------------------------------------------------------------------- /docker/scripts/stop-testnet.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker ps -f name=client -f name=node -q | xargs docker rm -f 4 | docker network rm babblenet -------------------------------------------------------------------------------- /docker/scripts/watch.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | 4 | N=${1:-4} 5 | 6 | watch -n 1 ' 7 | for i in $(seq 1 '$N'); 8 | do 9 | curl -s http://172.77.5.$i:80/Stats | \ 10 | tr -d "{}\"" | \ 11 | awk -F "," '"'"'{gsub (/[,]/," "); print;}'"'"' 12 | done; 13 | ' 14 | -------------------------------------------------------------------------------- /glide.lock: -------------------------------------------------------------------------------- 1 | hash: 176cbfe00c8c12b2269ebe3db9fd2aaa54226d7b8e7003f6c88842b01afbe8a6 2 | updated: 2017-02-20T06:57:13.757812022Z 3 | imports: 4 | - name: github.com/Sirupsen/logrus 5 | version: c078b1e43f58d563c74cebe63c85789e76ddb627 6 | - name: golang.org/x/sys 7 | version: d75a52659825e75fff6158388dddc6a5b04f9ba5 8 | subpackages: 9 | - unix 10 | - name: gopkg.in/urfave/cli.v1 11 | version: 0bdeddeeb0f650497d603c4ad7b20cfe685682f6 12 | testImports: [] 13 | -------------------------------------------------------------------------------- /glide.yaml: -------------------------------------------------------------------------------- 1 | package: github.com/babbleio/babble 2 | import: 3 | - package: github.com/Sirupsen/logrus 4 | - package: gopkg.in/urfave/cli.v1 5 | -------------------------------------------------------------------------------- /hashgraph/caches.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Mosaic Networks Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package hashgraph 17 | 18 | import "github.com/babbleio/babble/common" 19 | 20 | type Key struct { 21 | x string 22 | y string 23 | } 24 | 25 | //++++++++++++++++++++++++++++++++++++++++++++++++ 26 | //PARTICIPANT EVENTS CACHE 27 | type ParticipantEventsCache struct { 28 | size int 29 | participants map[string]int //[public key] => id 30 | participantEvents map[string]*common.RollingList 31 | } 32 | 33 | func NewParticipantEventsCache(size int, participants map[string]int) *ParticipantEventsCache { 34 | items := make(map[string]*common.RollingList) 35 | for pk, _ := range participants { 36 | items[pk] = common.NewRollingList(size) 37 | } 38 | return &ParticipantEventsCache{ 39 | size: size, 40 | participants: participants, 41 | participantEvents: items, 42 | } 43 | } 44 | 45 | func (pec *ParticipantEventsCache) Get(participant string, skip int) ([]string, error) { 46 | pe, ok := pec.participantEvents[participant] 47 | if !ok { 48 | return []string{}, ErrKeyNotFound 49 | } 50 | 51 | cached, tot := pe.Get() 52 | 53 | if skip >= tot { 54 | return []string{}, nil 55 | } 56 | 57 | oldestCached := tot - len(cached) 58 | if skip < oldestCached { 59 | //XXX TODO 60 | //LOAD REST FROM FILE 61 | return []string{}, ErrTooLate 62 | } 63 | 64 | //index of 'skipped' in RollingList 65 | start := skip - oldestCached 66 | 67 | if start >= len(cached) { 68 | return []string{}, nil 69 | } 70 | 71 | res := []string{} 72 | for k := start; k < len(cached); k++ { 73 | res = append(res, cached[k].(string)) 74 | } 75 | return res, nil 76 | } 77 | 78 | func (pec *ParticipantEventsCache) GetItem(participant string, index int) (string, error) { 79 | res, err := pec.participantEvents[participant].GetItem(index) 80 | if err != nil { 81 | return "", err 82 | } 83 | return res.(string), nil 84 | } 85 | 86 | func (pec *ParticipantEventsCache) GetLast(participant string) (string, error) { 87 | pe, ok := pec.participantEvents[participant] 88 | if !ok { 89 | return "", ErrKeyNotFound 90 | } 91 | cached, _ := pe.Get() 92 | if len(cached) == 0 { 93 | return "", nil 94 | } 95 | last := cached[len(cached)-1] 96 | return last.(string), nil 97 | } 98 | 99 | func (pec *ParticipantEventsCache) Add(participant string, hash string) { 100 | pe, ok := pec.participantEvents[participant] 101 | if !ok { 102 | pe = common.NewRollingList(pec.size) 103 | pec.participantEvents[participant] = pe 104 | } 105 | pe.Add(hash) 106 | } 107 | 108 | func (pec *ParticipantEventsCache) Known() map[int]int { 109 | known := make(map[int]int) 110 | for p, evs := range pec.participantEvents { 111 | _, tot := evs.Get() 112 | known[pec.participants[p]] = tot 113 | } 114 | return known 115 | } 116 | -------------------------------------------------------------------------------- /hashgraph/caches_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Mosaic Networks Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package hashgraph 17 | 18 | import "testing" 19 | import "fmt" 20 | import "reflect" 21 | 22 | func TestParticipantEventsCache(t *testing.T) { 23 | size := 10 24 | testSize := 25 25 | participants := map[string]int{ 26 | "alice": 0, 27 | "bob": 1, 28 | "charlie": 2, 29 | } 30 | pec := NewParticipantEventsCache(size, participants) 31 | 32 | items := make(map[string][]string) 33 | for pk := range participants { 34 | items[pk] = []string{} 35 | } 36 | 37 | for i := 0; i < testSize; i++ { 38 | for pk := range participants { 39 | item := fmt.Sprintf("%s%d", pk, i) 40 | 41 | pec.Add(pk, item) 42 | 43 | pitems := items[pk] 44 | pitems = append(pitems, item) 45 | items[pk] = pitems 46 | } 47 | } 48 | 49 | known := pec.Known() 50 | for p, k := range known { 51 | if k != testSize { 52 | t.Errorf("Known[%s] should be %d, not %d", p, testSize, k) 53 | } 54 | } 55 | 56 | for pk := range participants { 57 | if _, err := pec.Get(pk, 0); err != nil && err != ErrTooLate { 58 | t.Fatalf("Skipping 0 elements should return ErrNotFatal") 59 | } 60 | 61 | skip := 10 62 | expected := items[pk][skip:] 63 | cached, err := pec.Get(pk, skip) 64 | if err != nil { 65 | t.Fatal(err) 66 | } 67 | if !reflect.DeepEqual(expected, cached) { 68 | t.Fatalf("expected and cached not equal") 69 | } 70 | 71 | skip2 := 15 72 | expected2 := items[pk][skip2:] 73 | cached2, err := pec.Get(pk, skip2) 74 | if err != nil { 75 | t.Fatal(err) 76 | } 77 | if !reflect.DeepEqual(expected2, cached2) { 78 | t.Fatalf("expected and cached not equal") 79 | } 80 | 81 | skip3 := 27 82 | expected3 := []string{} 83 | cached3, err := pec.Get(pk, skip3) 84 | if err != nil { 85 | t.Fatal(err) 86 | } 87 | if !reflect.DeepEqual(expected3, cached3) { 88 | t.Fatalf("expected and cached not equal") 89 | } 90 | } 91 | } 92 | 93 | func TestParticipantEventsCacheEdge(t *testing.T) { 94 | size := 10 95 | testSize := 11 96 | participants := map[string]int{ 97 | "alice": 0, 98 | "bob": 1, 99 | "charlie": 2, 100 | } 101 | pec := NewParticipantEventsCache(size, participants) 102 | 103 | items := make(map[string][]string) 104 | for pk := range participants { 105 | items[pk] = []string{} 106 | } 107 | 108 | for i := 0; i < testSize; i++ { 109 | for pk := range participants { 110 | item := fmt.Sprintf("%s%d", pk, i) 111 | 112 | pec.Add(pk, item) 113 | 114 | pitems := items[pk] 115 | pitems = append(pitems, item) 116 | items[pk] = pitems 117 | } 118 | } 119 | 120 | for pk := range participants { 121 | skip := size 122 | expected := items[pk][skip:] 123 | cached, err := pec.Get(pk, skip) 124 | if err != nil { 125 | t.Fatal(err) 126 | } 127 | if !reflect.DeepEqual(expected, cached) { 128 | t.Fatalf("expected and cached not equal") 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /hashgraph/consensus_sorter.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Mosaic Networks Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package hashgraph 17 | 18 | import "math/big" 19 | 20 | type ConsensusSorter struct { 21 | a []Event 22 | r map[int]RoundInfo 23 | cache map[int]*big.Int 24 | } 25 | 26 | func NewConsensusSorter(events []Event) ConsensusSorter { 27 | return ConsensusSorter{ 28 | a: events, 29 | r: make(map[int]RoundInfo), 30 | cache: make(map[int]*big.Int), 31 | } 32 | } 33 | 34 | func (b ConsensusSorter) Len() int { return len(b.a) } 35 | func (b ConsensusSorter) Swap(i, j int) { b.a[i], b.a[j] = b.a[j], b.a[i] } 36 | func (b ConsensusSorter) Less(i, j int) bool { 37 | irr, jrr := -1, -1 38 | if b.a[i].roundReceived != nil { 39 | irr = *b.a[i].roundReceived 40 | } 41 | if b.a[j].roundReceived != nil { 42 | jrr = *b.a[j].roundReceived 43 | } 44 | if irr != jrr { 45 | return irr < jrr 46 | } 47 | 48 | if b.a[i].consensusTimestamp != b.a[j].consensusTimestamp { 49 | return b.a[i].consensusTimestamp.Sub(b.a[j].consensusTimestamp) < 0 50 | } 51 | 52 | w := b.GetPseudoRandomNumber(*b.a[i].roundReceived) 53 | 54 | wsi := new(big.Int) 55 | wsi = wsi.Xor(b.a[i].S, w) 56 | wsj := new(big.Int) 57 | wsj = wsj.Xor(b.a[j].S, w) 58 | return wsi.Cmp(wsj) < 0 59 | } 60 | func (b ConsensusSorter) GetPseudoRandomNumber(round int) *big.Int { 61 | if ps, ok := b.cache[round]; ok { 62 | return ps 63 | } 64 | rd := b.r[round] 65 | ps := rd.PseudoRandomNumber() 66 | b.cache[round] = ps 67 | return ps 68 | } 69 | -------------------------------------------------------------------------------- /hashgraph/event.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Mosaic Networks Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package hashgraph 17 | 18 | import ( 19 | "bytes" 20 | "crypto/ecdsa" 21 | "encoding/gob" 22 | "fmt" 23 | "math/big" 24 | "time" 25 | 26 | "github.com/babbleio/babble/crypto" 27 | ) 28 | 29 | type EventBody struct { 30 | Transactions [][]byte //the payload 31 | Parents []string //hashes of the event's parents, self-parent first 32 | Creator []byte //creator's public key 33 | Timestamp time.Time //creator's claimed timestamp of the event's creation 34 | Index int //index in the sequence of events created by Creator 35 | 36 | //wire 37 | //It is cheaper to send ints then hashes over the wire 38 | selfParentIndex int 39 | otherParentCreatorID int 40 | otherParentIndex int 41 | creatorID int 42 | } 43 | 44 | //gob encoding of body only 45 | func (e *EventBody) Marshal() ([]byte, error) { 46 | var b bytes.Buffer 47 | enc := gob.NewEncoder(&b) //will write to b 48 | if err := enc.Encode(e); err != nil { 49 | return nil, err 50 | } 51 | return b.Bytes(), nil 52 | } 53 | 54 | func (e *EventBody) Unmarshal(data []byte) error { 55 | b := bytes.NewBuffer(data) 56 | dec := gob.NewDecoder(b) //will read from b 57 | return dec.Decode(e) 58 | } 59 | 60 | func (e *EventBody) Hash() ([]byte, error) { 61 | hashBytes, err := e.Marshal() 62 | if err != nil { 63 | return nil, err 64 | } 65 | return crypto.SHA256(hashBytes), nil 66 | } 67 | 68 | type EventCoordinates struct { 69 | hash string 70 | index int 71 | } 72 | 73 | type Event struct { 74 | Body EventBody 75 | R, S *big.Int //creator's digital signature of body 76 | 77 | topologicalIndex int 78 | 79 | roundReceived *int 80 | consensusTimestamp time.Time 81 | 82 | lastAncestors []EventCoordinates //[participant fake id] => last ancestor 83 | firstDescendants []EventCoordinates //[participant fake id] => first descendant 84 | 85 | creator string 86 | hash []byte 87 | hex string 88 | } 89 | 90 | func NewEvent(transactions [][]byte, 91 | parents []string, 92 | creator []byte, 93 | index int) Event { 94 | 95 | body := EventBody{ 96 | Transactions: transactions, 97 | Parents: parents, 98 | Creator: creator, 99 | Timestamp: time.Now(), 100 | Index: index, 101 | } 102 | return Event{ 103 | Body: body, 104 | } 105 | } 106 | 107 | func (e *Event) Creator() string { 108 | if e.creator == "" { 109 | e.creator = fmt.Sprintf("0x%X", e.Body.Creator) 110 | } 111 | return e.creator 112 | } 113 | 114 | func (e *Event) SelfParent() string { 115 | return e.Body.Parents[0] 116 | } 117 | 118 | func (e *Event) OtherParent() string { 119 | return e.Body.Parents[1] 120 | } 121 | 122 | func (e *Event) Transactions() [][]byte { 123 | return e.Body.Transactions 124 | } 125 | 126 | func (e *Event) Index() int { 127 | return e.Body.Index 128 | } 129 | 130 | //ecdsa sig 131 | func (e *Event) Sign(privKey *ecdsa.PrivateKey) error { 132 | signBytes, err := e.Body.Hash() 133 | if err != nil { 134 | return err 135 | } 136 | e.R, e.S, err = crypto.Sign(privKey, signBytes) 137 | return err 138 | } 139 | 140 | func (e *Event) Verify() (bool, error) { 141 | pubBytes := e.Body.Creator 142 | pubKey := crypto.ToECDSAPub(pubBytes) 143 | 144 | signBytes, err := e.Body.Hash() 145 | if err != nil { 146 | return false, err 147 | } 148 | 149 | return crypto.Verify(pubKey, signBytes, e.R, e.S), nil 150 | } 151 | 152 | //gob encoding of body and signature 153 | func (e *Event) Marshal() ([]byte, error) { 154 | var b bytes.Buffer 155 | enc := gob.NewEncoder(&b) 156 | if err := enc.Encode(e); err != nil { 157 | return nil, err 158 | } 159 | return b.Bytes(), nil 160 | } 161 | 162 | func (e *Event) Unmarshal(data []byte) error { 163 | b := bytes.NewBuffer(data) 164 | dec := gob.NewDecoder(b) //will read from b 165 | return dec.Decode(e) 166 | } 167 | 168 | //sha256 hash of body and signature 169 | func (e *Event) Hash() ([]byte, error) { 170 | if len(e.hash) == 0 { 171 | hashBytes, err := e.Marshal() 172 | if err != nil { 173 | return nil, err 174 | } 175 | e.hash = crypto.SHA256(hashBytes) 176 | } 177 | return e.hash, nil 178 | } 179 | 180 | func (e *Event) Hex() string { 181 | if e.hex == "" { 182 | hash, _ := e.Hash() 183 | e.hex = fmt.Sprintf("0x%X", hash) 184 | } 185 | return e.hex 186 | } 187 | 188 | func (e *Event) SetRoundReceived(rr int) { 189 | if e.roundReceived == nil { 190 | e.roundReceived = new(int) 191 | } 192 | *e.roundReceived = rr 193 | } 194 | 195 | func (e *Event) SetWireInfo(selfParentIndex, 196 | otherParentCreatorID, 197 | otherParentIndex, 198 | creatorID int) { 199 | e.Body.selfParentIndex = selfParentIndex 200 | e.Body.otherParentCreatorID = otherParentCreatorID 201 | e.Body.otherParentIndex = otherParentIndex 202 | e.Body.creatorID = creatorID 203 | } 204 | 205 | func (e *Event) ToWire() WireEvent { 206 | return WireEvent{ 207 | Body: WireBody{ 208 | Transactions: e.Body.Transactions, 209 | SelfParentIndex: e.Body.selfParentIndex, 210 | OtherParentCreatorID: e.Body.otherParentCreatorID, 211 | OtherParentIndex: e.Body.otherParentIndex, 212 | CreatorID: e.Body.creatorID, 213 | Timestamp: e.Body.Timestamp, 214 | Index: e.Body.Index, 215 | }, 216 | R: e.R, 217 | S: e.S, 218 | } 219 | } 220 | 221 | //Sorting 222 | 223 | // ByTimestamp implements sort.Interface for []Event based on 224 | // the timestamp field. 225 | type ByTimestamp []Event 226 | 227 | func (a ByTimestamp) Len() int { return len(a) } 228 | func (a ByTimestamp) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 229 | func (a ByTimestamp) Less(i, j int) bool { return a[i].Body.Timestamp.Sub(a[j].Body.Timestamp) < 0 } 230 | 231 | // ByTopologicalOrder implements sort.Interface for []Event based on 232 | // the topologicalIndex field. 233 | type ByTopologicalOrder []Event 234 | 235 | func (a ByTopologicalOrder) Len() int { return len(a) } 236 | func (a ByTopologicalOrder) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 237 | func (a ByTopologicalOrder) Less(i, j int) bool { 238 | return a[i].topologicalIndex < a[j].topologicalIndex 239 | } 240 | 241 | //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 242 | // WireEvent 243 | 244 | type WireBody struct { 245 | Transactions [][]byte 246 | 247 | SelfParentIndex int 248 | OtherParentCreatorID int 249 | OtherParentIndex int 250 | CreatorID int 251 | 252 | Timestamp time.Time 253 | Index int 254 | } 255 | 256 | type WireEvent struct { 257 | Body WireBody 258 | R, S *big.Int 259 | } 260 | -------------------------------------------------------------------------------- /hashgraph/event_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Mosaic Networks Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package hashgraph 17 | 18 | import ( 19 | "reflect" 20 | "testing" 21 | "time" 22 | 23 | "github.com/babbleio/babble/crypto" 24 | ) 25 | 26 | func createDummyEventBody() EventBody { 27 | body := EventBody{} 28 | body.Transactions = [][]byte{[]byte("abc"), []byte("def")} 29 | body.Parents = []string{"self", "other"} 30 | body.Creator = []byte("public key") 31 | body.Timestamp = time.Now() 32 | return body 33 | } 34 | 35 | func TestMarshallBody(t *testing.T) { 36 | body := createDummyEventBody() 37 | 38 | raw, err := body.Marshal() 39 | if err != nil { 40 | t.Fatalf("Error marshalling EventBody: %s", err) 41 | } 42 | 43 | newBody := new(EventBody) 44 | if err := newBody.Unmarshal(raw); err != nil { 45 | t.Fatalf("Error unmarshalling EventBody: %s", err) 46 | } 47 | 48 | if !reflect.DeepEqual(body.Transactions, newBody.Transactions) { 49 | t.Fatalf("Payloads do not match. Expected %#v, got %#v", body.Transactions, newBody.Transactions) 50 | } 51 | if !reflect.DeepEqual(body.Parents, newBody.Parents) { 52 | t.Fatalf("Parents do not match. Expected %#v, got %#v", body.Parents, newBody.Parents) 53 | } 54 | if !reflect.DeepEqual(body.Creator, newBody.Creator) { 55 | t.Fatalf("Creators do not match. Expected %#v, got %#v", body.Creator, newBody.Creator) 56 | } 57 | if body.Timestamp != newBody.Timestamp { 58 | t.Fatalf("Timestamps do not match. Expected %#v, got %#v", body.Timestamp, newBody.Timestamp) 59 | } 60 | 61 | } 62 | 63 | func TestSignEvent(t *testing.T) { 64 | privateKey, _ := crypto.GenerateECDSAKey() 65 | publicKeyBytes := crypto.FromECDSAPub(&privateKey.PublicKey) 66 | 67 | body := createDummyEventBody() 68 | body.Creator = publicKeyBytes 69 | 70 | event := Event{Body: body} 71 | if err := event.Sign(privateKey); err != nil { 72 | t.Fatalf("Error signing Event: %s", err) 73 | } 74 | 75 | res, err := event.Verify() 76 | if err != nil { 77 | t.Fatalf("Error verifying signature: %s", err) 78 | } 79 | if !res { 80 | t.Fatalf("Verify returned false") 81 | } 82 | } 83 | 84 | func TestMarshallEvent(t *testing.T) { 85 | privateKey, _ := crypto.GenerateECDSAKey() 86 | publicKeyBytes := crypto.FromECDSAPub(&privateKey.PublicKey) 87 | 88 | body := createDummyEventBody() 89 | body.Creator = publicKeyBytes 90 | 91 | event := Event{Body: body} 92 | if err := event.Sign(privateKey); err != nil { 93 | t.Fatalf("Error signing Event: %s", err) 94 | } 95 | 96 | raw, err := event.Marshal() 97 | if err != nil { 98 | t.Fatalf("Error marshalling Event: %s", err) 99 | } 100 | 101 | newEvent := new(Event) 102 | if err := newEvent.Unmarshal(raw); err != nil { 103 | t.Fatalf("Error unmarshalling Event: %s", err) 104 | } 105 | 106 | if !reflect.DeepEqual(*newEvent, event) { 107 | t.Fatalf("Events are not deeply equal") 108 | } 109 | } 110 | 111 | func TestWireEvent(t *testing.T) { 112 | privateKey, _ := crypto.GenerateECDSAKey() 113 | publicKeyBytes := crypto.FromECDSAPub(&privateKey.PublicKey) 114 | 115 | body := createDummyEventBody() 116 | body.Creator = publicKeyBytes 117 | 118 | event := Event{Body: body} 119 | if err := event.Sign(privateKey); err != nil { 120 | t.Fatalf("Error signing Event: %s", err) 121 | } 122 | 123 | event.SetWireInfo(1, 66, 2, 67) 124 | 125 | expectedWireEvent := WireEvent{ 126 | Body: WireBody{ 127 | Transactions: event.Body.Transactions, 128 | SelfParentIndex: 1, 129 | OtherParentCreatorID: 66, 130 | OtherParentIndex: 2, 131 | CreatorID: 67, 132 | Timestamp: event.Body.Timestamp, 133 | Index: event.Body.Index, 134 | }, 135 | R: event.R, 136 | S: event.S, 137 | } 138 | 139 | wireEvent := event.ToWire() 140 | 141 | if !reflect.DeepEqual(expectedWireEvent, wireEvent) { 142 | t.Fatalf("WireEvent should be %#v, not %#v", expectedWireEvent, wireEvent) 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /hashgraph/inmem_store.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Mosaic Networks Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package hashgraph 17 | 18 | import "github.com/babbleio/babble/common" 19 | 20 | type InmemStore struct { 21 | cacheSize int 22 | eventCache *common.LRU 23 | roundCache *common.LRU 24 | consensusCache *common.RollingList 25 | participantEventsCache *ParticipantEventsCache 26 | } 27 | 28 | func NewInmemStore(participants map[string]int, cacheSize int) *InmemStore { 29 | return &InmemStore{ 30 | cacheSize: cacheSize, 31 | eventCache: common.NewLRU(cacheSize, nil), 32 | roundCache: common.NewLRU(cacheSize, nil), 33 | consensusCache: common.NewRollingList(cacheSize), 34 | participantEventsCache: NewParticipantEventsCache(cacheSize, participants), 35 | } 36 | } 37 | 38 | func (s *InmemStore) CacheSize() int { 39 | return s.cacheSize 40 | } 41 | 42 | func (s *InmemStore) GetEvent(key string) (Event, error) { 43 | res, ok := s.eventCache.Get(key) 44 | if !ok { 45 | return Event{}, ErrKeyNotFound 46 | } 47 | 48 | return res.(Event), nil 49 | } 50 | 51 | func (s *InmemStore) SetEvent(event Event) error { 52 | key := event.Hex() 53 | _, err := s.GetEvent(key) 54 | if err != nil && err != ErrKeyNotFound { 55 | return err 56 | } 57 | if err == ErrKeyNotFound { 58 | if err := s.addParticpantEvent(event.Creator(), key); err != nil { 59 | return err 60 | } 61 | } 62 | s.eventCache.Add(key, event) 63 | 64 | return nil 65 | } 66 | 67 | func (s *InmemStore) ParticipantEvents(participant string, skip int) ([]string, error) { 68 | return s.participantEventsCache.Get(participant, skip) 69 | } 70 | 71 | func (s *InmemStore) ParticipantEvent(particant string, index int) (string, error) { 72 | return s.participantEventsCache.GetItem(particant, index) 73 | } 74 | 75 | func (s *InmemStore) LastFrom(participant string) (string, error) { 76 | return s.participantEventsCache.GetLast(participant) 77 | } 78 | 79 | func (s *InmemStore) addParticpantEvent(participant string, hash string) error { 80 | s.participantEventsCache.Add(participant, hash) 81 | return nil 82 | } 83 | 84 | func (s *InmemStore) Known() map[int]int { 85 | return s.participantEventsCache.Known() 86 | } 87 | 88 | func (s *InmemStore) ConsensusEvents() []string { 89 | lastWindow, _ := s.consensusCache.Get() 90 | res := []string{} 91 | for _, item := range lastWindow { 92 | res = append(res, item.(string)) 93 | } 94 | return res 95 | } 96 | 97 | func (s *InmemStore) ConsensusEventsCount() int { 98 | _, tot := s.consensusCache.Get() 99 | return tot 100 | } 101 | 102 | func (s *InmemStore) AddConsensusEvent(key string) error { 103 | s.consensusCache.Add(key) 104 | return nil 105 | } 106 | 107 | func (s *InmemStore) GetRound(r int) (RoundInfo, error) { 108 | res, ok := s.roundCache.Get(r) 109 | if !ok { 110 | return *NewRoundInfo(), ErrKeyNotFound 111 | } 112 | return res.(RoundInfo), nil 113 | } 114 | 115 | func (s *InmemStore) SetRound(r int, round RoundInfo) error { 116 | s.roundCache.Add(r, round) 117 | return nil 118 | } 119 | 120 | func (s *InmemStore) Rounds() int { 121 | return s.roundCache.Len() 122 | } 123 | 124 | func (s *InmemStore) RoundWitnesses(r int) []string { 125 | round, err := s.GetRound(r) 126 | if err != nil { 127 | return []string{} 128 | } 129 | return round.Witnesses() 130 | } 131 | 132 | func (s *InmemStore) RoundEvents(r int) int { 133 | round, err := s.GetRound(r) 134 | if err != nil { 135 | return 0 136 | } 137 | return len(round.Events) 138 | } 139 | 140 | func (s *InmemStore) Close() error { 141 | return nil 142 | } 143 | -------------------------------------------------------------------------------- /hashgraph/inmem_store_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Mosaic Networks Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package hashgraph 17 | 18 | import ( 19 | "fmt" 20 | "reflect" 21 | "testing" 22 | 23 | "github.com/babbleio/babble/crypto" 24 | ) 25 | 26 | type pub struct { 27 | id int 28 | pubKey []byte 29 | hex string 30 | } 31 | 32 | func initInmemStore(cacheSize int) (*InmemStore, []pub) { 33 | n := 3 34 | participantPubs := []pub{} 35 | participants := make(map[string]int) 36 | for i := 0; i < n; i++ { 37 | key, _ := crypto.GenerateECDSAKey() 38 | pubKey := crypto.FromECDSAPub(&key.PublicKey) 39 | participantPubs = append(participantPubs, 40 | pub{i, pubKey, fmt.Sprintf("0x%X", pubKey)}) 41 | participants[fmt.Sprintf("0x%X", pubKey)] = i 42 | } 43 | 44 | store := NewInmemStore(participants, cacheSize) 45 | return store, participantPubs 46 | } 47 | 48 | func TestInmemEvents(t *testing.T) { 49 | cacheSize := 100 50 | testSize := 15 51 | store, participants := initInmemStore(cacheSize) 52 | 53 | events := make(map[string][]Event) 54 | for _, p := range participants { 55 | items := []Event{} 56 | for k := 0; k < testSize; k++ { 57 | event := NewEvent([][]byte{[]byte(fmt.Sprintf("%s_%d", p.hex[:5], k))}, 58 | []string{"", ""}, p.pubKey, k) 59 | _ = event.Hex() //just to set private variables 60 | items = append(items, event) 61 | err := store.SetEvent(event) 62 | if err != nil { 63 | t.Fatal(err) 64 | } 65 | } 66 | events[p.hex] = items 67 | 68 | } 69 | 70 | for p, evs := range events { 71 | for k, ev := range evs { 72 | rev, err := store.GetEvent(ev.Hex()) 73 | if err != nil { 74 | t.Fatal(err) 75 | } 76 | if !reflect.DeepEqual(ev.Body, rev.Body) { 77 | t.Fatalf("events[%s][%d] should be %#v, not %#v", p, k, ev, rev) 78 | } 79 | } 80 | } 81 | 82 | skip := 0 83 | for _, p := range participants { 84 | pEvents, err := store.ParticipantEvents(p.hex, skip) 85 | if err != nil { 86 | t.Fatal(err) 87 | } 88 | if l := len(pEvents); l != testSize { 89 | t.Fatalf("%s should have %d events, not %d", p.hex, testSize, l) 90 | } 91 | 92 | expectedEvents := events[p.hex][skip:] 93 | for k, e := range expectedEvents { 94 | if e.Hex() != pEvents[k] { 95 | t.Fatalf("ParticipantEvents[%s][%d] should be %s, not %s", 96 | p.hex, k, e.Hex(), pEvents[k]) 97 | } 98 | } 99 | } 100 | 101 | expectedKnown := make(map[int]int) 102 | for _, p := range participants { 103 | expectedKnown[p.id] = testSize 104 | } 105 | known := store.Known() 106 | if !reflect.DeepEqual(expectedKnown, known) { 107 | t.Fatalf("Incorrect Known. Got %#v, expected %#v", known, expectedKnown) 108 | } 109 | 110 | for _, p := range participants { 111 | evs := events[p.hex] 112 | for _, ev := range evs { 113 | if err := store.AddConsensusEvent(ev.Hex()); err != nil { 114 | t.Fatal(err) 115 | } 116 | } 117 | 118 | } 119 | } 120 | 121 | func TestInmemRounds(t *testing.T) { 122 | store, participants := initInmemStore(10) 123 | 124 | round := NewRoundInfo() 125 | events := make(map[string]Event) 126 | for _, p := range participants { 127 | event := NewEvent([][]byte{}, []string{"", ""}, p.pubKey, 0) 128 | events[p.hex] = event 129 | round.AddEvent(event.Hex(), true) 130 | } 131 | 132 | if err := store.SetRound(0, *round); err != nil { 133 | t.Fatal(err) 134 | } 135 | 136 | if c := store.Rounds(); c != 1 { 137 | t.Fatalf("Store should count 1 round, not %d", c) 138 | } 139 | 140 | storedRound, err := store.GetRound(0) 141 | if err != nil { 142 | t.Fatal(err) 143 | } 144 | 145 | if !reflect.DeepEqual(*round, storedRound) { 146 | t.Fatalf("Round and StoredRound do not match") 147 | } 148 | 149 | witnesses := store.RoundWitnesses(0) 150 | expectedWitnesses := round.Witnesses() 151 | if len(witnesses) != len(expectedWitnesses) { 152 | t.Fatalf("There should be %d witnesses, not %d", len(expectedWitnesses), len(witnesses)) 153 | } 154 | for _, w := range expectedWitnesses { 155 | if !contains(witnesses, w) { 156 | t.Fatalf("Witnesses should contain %s", w) 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /hashgraph/roundInfo.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Mosaic Networks Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package hashgraph 17 | 18 | import ( 19 | "bytes" 20 | "encoding/gob" 21 | "math/big" 22 | ) 23 | 24 | type Trilean int 25 | 26 | const ( 27 | Undefined Trilean = iota 28 | True 29 | False 30 | ) 31 | 32 | var trileans = []string{"Undefined", "True", "False"} 33 | 34 | func (t Trilean) String() string { 35 | return trileans[t] 36 | } 37 | 38 | type RoundEvent struct { 39 | Witness bool 40 | Famous Trilean 41 | } 42 | 43 | type RoundInfo struct { 44 | Events map[string]RoundEvent 45 | } 46 | 47 | func NewRoundInfo() *RoundInfo { 48 | return &RoundInfo{ 49 | Events: make(map[string]RoundEvent), 50 | } 51 | } 52 | 53 | func (r *RoundInfo) AddEvent(x string, witness bool) { 54 | _, ok := r.Events[x] 55 | if !ok { 56 | r.Events[x] = RoundEvent{ 57 | Witness: witness, 58 | } 59 | } 60 | } 61 | 62 | func (r *RoundInfo) SetFame(x string, f bool) { 63 | e, ok := r.Events[x] 64 | if !ok { 65 | e = RoundEvent{ 66 | Witness: true, 67 | } 68 | } 69 | if f { 70 | e.Famous = True 71 | } else { 72 | e.Famous = False 73 | } 74 | r.Events[x] = e 75 | } 76 | 77 | //return true if no witnesses' fame is left undefined 78 | func (r *RoundInfo) WitnessesDecided() bool { 79 | for _, e := range r.Events { 80 | if e.Witness && e.Famous == Undefined { 81 | return false 82 | } 83 | } 84 | return true 85 | } 86 | 87 | //return witnesses 88 | func (r *RoundInfo) Witnesses() []string { 89 | res := []string{} 90 | for x, e := range r.Events { 91 | if e.Witness { 92 | res = append(res, x) 93 | } 94 | } 95 | return res 96 | } 97 | 98 | //return famous witnesses 99 | func (r *RoundInfo) FamousWitnesses() []string { 100 | res := []string{} 101 | for x, e := range r.Events { 102 | if e.Witness && e.Famous == True { 103 | res = append(res, x) 104 | } 105 | } 106 | return res 107 | } 108 | 109 | func (r *RoundInfo) PseudoRandomNumber() *big.Int { 110 | res := new(big.Int) 111 | for x, e := range r.Events { 112 | if e.Witness && e.Famous == True { 113 | s, _ := new(big.Int).SetString(x, 16) 114 | res = res.Xor(res, s) 115 | } 116 | } 117 | return res 118 | } 119 | 120 | func (r *RoundInfo) Marshal() ([]byte, error) { 121 | var b bytes.Buffer 122 | enc := gob.NewEncoder(&b) 123 | if err := enc.Encode(r); err != nil { 124 | return nil, err 125 | } 126 | return b.Bytes(), nil 127 | } 128 | 129 | func (r *RoundInfo) Unmarshal(data []byte) error { 130 | b := bytes.NewBuffer(data) 131 | dec := gob.NewDecoder(b) //will read from b 132 | return dec.Decode(r) 133 | } 134 | -------------------------------------------------------------------------------- /hashgraph/store.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Mosaic Networks Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package hashgraph 17 | 18 | import "errors" 19 | 20 | var ( 21 | ErrKeyNotFound = errors.New("not found") 22 | ErrTooLate = errors.New("too late") 23 | ) 24 | 25 | type Store interface { 26 | CacheSize() int 27 | GetEvent(string) (Event, error) 28 | SetEvent(Event) error 29 | ParticipantEvents(string, int) ([]string, error) 30 | ParticipantEvent(string, int) (string, error) 31 | LastFrom(string) (string, error) 32 | Known() map[int]int 33 | ConsensusEvents() []string 34 | ConsensusEventsCount() int 35 | AddConsensusEvent(string) error 36 | GetRound(int) (RoundInfo, error) 37 | SetRound(int, RoundInfo) error 38 | Rounds() int 39 | RoundWitnesses(int) []string 40 | RoundEvents(int) int 41 | } 42 | -------------------------------------------------------------------------------- /img/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpitid/babble/81345a125b20c403f35c13dc03d077b8b23d3dc7/img/demo.png -------------------------------------------------------------------------------- /net/commands.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Mosaic Networks Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package net 17 | 18 | import "github.com/babbleio/babble/hashgraph" 19 | 20 | type SyncRequest struct { 21 | From string 22 | Known map[int]int 23 | } 24 | 25 | type SyncResponse struct { 26 | From string 27 | Head string 28 | Events []hashgraph.WireEvent 29 | } 30 | -------------------------------------------------------------------------------- /net/inmem_transport.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Mosaic Networks Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package net 17 | 18 | import ( 19 | "crypto/rand" 20 | "fmt" 21 | "io" 22 | "sync" 23 | "time" 24 | ) 25 | 26 | // NewInmemAddr returns a new in-memory addr with 27 | // a randomly generate UUID as the ID. 28 | func NewInmemAddr() string { 29 | return generateUUID() 30 | } 31 | 32 | // generateUUID is used to generate a random UUID. 33 | func generateUUID() string { 34 | buf := make([]byte, 16) 35 | if _, err := rand.Read(buf); err != nil { 36 | panic(fmt.Errorf("failed to read random bytes: %v", err)) 37 | } 38 | 39 | return fmt.Sprintf("%08x-%04x-%04x-%04x-%12x", 40 | buf[0:4], 41 | buf[4:6], 42 | buf[6:8], 43 | buf[8:10], 44 | buf[10:16]) 45 | } 46 | 47 | // InmemTransport Implements the Transport interface, to allow babble to be 48 | // tested in-memory without going over a network. 49 | type InmemTransport struct { 50 | sync.RWMutex 51 | consumerCh chan RPC 52 | localAddr string 53 | peers map[string]*InmemTransport 54 | timeout time.Duration 55 | } 56 | 57 | // NewInmemTransport is used to initialize a new transport 58 | // and generates a random local address if none is specified 59 | func NewInmemTransport(addr string) (string, *InmemTransport) { 60 | if addr == "" { 61 | addr = NewInmemAddr() 62 | } 63 | trans := &InmemTransport{ 64 | consumerCh: make(chan RPC, 16), 65 | localAddr: addr, 66 | peers: make(map[string]*InmemTransport), 67 | timeout: 50 * time.Millisecond, 68 | } 69 | return addr, trans 70 | } 71 | 72 | // Consumer implements the Transport interface. 73 | func (i *InmemTransport) Consumer() <-chan RPC { 74 | return i.consumerCh 75 | } 76 | 77 | // LocalAddr implements the Transport interface. 78 | func (i *InmemTransport) LocalAddr() string { 79 | return i.localAddr 80 | } 81 | 82 | // Sync implements the Transport interface. 83 | func (i *InmemTransport) Sync(target string, args *SyncRequest, resp *SyncResponse) error { 84 | rpcResp, err := i.makeRPC(target, args, nil, i.timeout) 85 | if err != nil { 86 | return err 87 | } 88 | 89 | // Copy the result back 90 | out := rpcResp.Response.(*SyncResponse) 91 | *resp = *out 92 | return nil 93 | } 94 | 95 | func (i *InmemTransport) makeRPC(target string, args interface{}, r io.Reader, timeout time.Duration) (rpcResp RPCResponse, err error) { 96 | i.RLock() 97 | peer, ok := i.peers[target] 98 | i.RUnlock() 99 | 100 | if !ok { 101 | err = fmt.Errorf("failed to connect to peer: %v", target) 102 | return 103 | } 104 | 105 | // Send the RPC over 106 | respCh := make(chan RPCResponse) 107 | peer.consumerCh <- RPC{ 108 | Command: args, 109 | Reader: r, 110 | RespChan: respCh, 111 | } 112 | 113 | // Wait for a response 114 | select { 115 | case rpcResp = <-respCh: 116 | if rpcResp.Error != nil { 117 | err = rpcResp.Error 118 | } 119 | case <-time.After(timeout): 120 | err = fmt.Errorf("command timed out") 121 | } 122 | return 123 | } 124 | 125 | // Connect is used to connect this transport to another transport for 126 | // a given peer name. This allows for local routing. 127 | func (i *InmemTransport) Connect(peer string, t Transport) { 128 | trans := t.(*InmemTransport) 129 | i.Lock() 130 | defer i.Unlock() 131 | i.peers[peer] = trans 132 | } 133 | 134 | // Disconnect is used to remove the ability to route to a given peer. 135 | func (i *InmemTransport) Disconnect(peer string) { 136 | i.Lock() 137 | defer i.Unlock() 138 | delete(i.peers, peer) 139 | } 140 | 141 | // DisconnectAll is used to remove all routes to peers. 142 | func (i *InmemTransport) DisconnectAll() { 143 | i.Lock() 144 | defer i.Unlock() 145 | i.peers = make(map[string]*InmemTransport) 146 | } 147 | 148 | // Close is used to permanently disable the transport 149 | func (i *InmemTransport) Close() error { 150 | i.DisconnectAll() 151 | return nil 152 | } 153 | -------------------------------------------------------------------------------- /net/inmem_transport_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Mosaic Networks Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package net 17 | 18 | import ( 19 | "testing" 20 | ) 21 | 22 | func TestInmemTransportImpl(t *testing.T) { 23 | var inm interface{} = &InmemTransport{} 24 | if _, ok := inm.(Transport); !ok { 25 | t.Fatalf("InmemTransport is not a Transport") 26 | } 27 | if _, ok := inm.(LoopbackTransport); !ok { 28 | t.Fatalf("InmemTransport is not a Loopback Transport") 29 | } 30 | if _, ok := inm.(WithPeers); !ok { 31 | t.Fatalf("InmemTransport is not a WithPeers Transport") 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /net/net_transport.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Mosaic Networks Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package net 17 | 18 | import ( 19 | "bufio" 20 | "encoding/gob" 21 | "errors" 22 | "fmt" 23 | "io" 24 | "net" 25 | "sync" 26 | "time" 27 | 28 | "github.com/Sirupsen/logrus" 29 | ) 30 | 31 | const ( 32 | rpcSync uint8 = iota 33 | 34 | // DefaultTimeoutScale is the default TimeoutScale in a NetworkTransport. 35 | DefaultTimeoutScale = 256 * 1024 // 256KB 36 | ) 37 | 38 | var ( 39 | // ErrTransportShutdown is returned when operations on a transport are 40 | // invoked after it's been terminated. 41 | ErrTransportShutdown = errors.New("transport shutdown") 42 | 43 | // ErrPipelineShutdown is returned when the pipeline is closed. 44 | ErrPipelineShutdown = errors.New("append pipeline closed") 45 | ) 46 | 47 | /* 48 | 49 | NetworkTransport provides a network based transport that can be 50 | used to communicate with babble on remote machines. It requires 51 | an underlying stream layer to provide a stream abstraction, which can 52 | be simple TCP, TLS, etc. 53 | 54 | This transport is very simple and lightweight. Each RPC request is 55 | framed by sending a byte that indicates the message type, followed 56 | by the gob encoded request. 57 | 58 | The response is an error string followed by the response object, 59 | both are encoded using gob. 60 | */ 61 | type NetworkTransport struct { 62 | connPool map[string][]*netConn 63 | connPoolLock sync.Mutex 64 | 65 | consumeCh chan RPC 66 | 67 | maxPool int 68 | 69 | logger *logrus.Logger 70 | 71 | shutdown bool 72 | shutdownCh chan struct{} 73 | shutdownLock sync.Mutex 74 | 75 | stream StreamLayer 76 | 77 | timeout time.Duration 78 | } 79 | 80 | // StreamLayer is used with the NetworkTransport to provide 81 | // the low level stream abstraction. 82 | type StreamLayer interface { 83 | net.Listener 84 | 85 | // Dial is used to create a new outgoing connection 86 | Dial(address string, timeout time.Duration) (net.Conn, error) 87 | } 88 | 89 | type netConn struct { 90 | target string 91 | conn net.Conn 92 | r *bufio.Reader 93 | w *bufio.Writer 94 | dec *gob.Decoder 95 | enc *gob.Encoder 96 | } 97 | 98 | func (n *netConn) Release() error { 99 | return n.conn.Close() 100 | } 101 | 102 | // NewNetworkTransport creates a new network transport with the given dialer 103 | // and listener. The maxPool controls how many connections we will pool. The 104 | // timeout is used to apply I/O deadlines. 105 | func NewNetworkTransport( 106 | stream StreamLayer, 107 | maxPool int, 108 | timeout time.Duration, 109 | logger *logrus.Logger, 110 | ) *NetworkTransport { 111 | if logger == nil { 112 | logger = logrus.New() 113 | logger.Level = logrus.DebugLevel 114 | } 115 | trans := &NetworkTransport{ 116 | connPool: make(map[string][]*netConn), 117 | consumeCh: make(chan RPC), 118 | logger: logger, 119 | maxPool: maxPool, 120 | shutdownCh: make(chan struct{}), 121 | stream: stream, 122 | timeout: timeout, 123 | } 124 | go trans.listen() 125 | return trans 126 | } 127 | 128 | // Close is used to stop the network transport. 129 | func (n *NetworkTransport) Close() error { 130 | n.shutdownLock.Lock() 131 | defer n.shutdownLock.Unlock() 132 | 133 | if !n.shutdown { 134 | close(n.shutdownCh) 135 | n.stream.Close() 136 | n.shutdown = true 137 | } 138 | return nil 139 | } 140 | 141 | // Consumer implements the Transport interface. 142 | func (n *NetworkTransport) Consumer() <-chan RPC { 143 | return n.consumeCh 144 | } 145 | 146 | // LocalAddr implements the Transport interface. 147 | func (n *NetworkTransport) LocalAddr() string { 148 | return n.stream.Addr().String() 149 | } 150 | 151 | // IsShutdown is used to check if the transport is shutdown. 152 | func (n *NetworkTransport) IsShutdown() bool { 153 | select { 154 | case <-n.shutdownCh: 155 | return true 156 | default: 157 | return false 158 | } 159 | } 160 | 161 | // getPooledConn is used to grab a pooled connection. 162 | func (n *NetworkTransport) getPooledConn(target string) *netConn { 163 | n.connPoolLock.Lock() 164 | defer n.connPoolLock.Unlock() 165 | 166 | conns, ok := n.connPool[target] 167 | if !ok || len(conns) == 0 { 168 | return nil 169 | } 170 | 171 | var conn *netConn 172 | num := len(conns) 173 | conn, conns[num-1] = conns[num-1], nil 174 | n.connPool[target] = conns[:num-1] 175 | return conn 176 | } 177 | 178 | // getConn is used to get a connection from the pool. 179 | func (n *NetworkTransport) getConn(target string, timeout time.Duration) (*netConn, error) { 180 | // Check for a pooled conn 181 | if conn := n.getPooledConn(target); conn != nil { 182 | return conn, nil 183 | } 184 | 185 | // Dial a new connection 186 | conn, err := n.stream.Dial(target, timeout) 187 | if err != nil { 188 | return nil, err 189 | } 190 | 191 | // Wrap the conn 192 | netConn := &netConn{ 193 | target: target, 194 | conn: conn, 195 | r: bufio.NewReader(conn), 196 | w: bufio.NewWriter(conn), 197 | } 198 | // Setup encoder/decoders 199 | netConn.dec = gob.NewDecoder(netConn.r) 200 | netConn.enc = gob.NewEncoder(netConn.w) 201 | 202 | // Done 203 | return netConn, nil 204 | } 205 | 206 | // returnConn returns a connection back to the pool. 207 | func (n *NetworkTransport) returnConn(conn *netConn) { 208 | n.connPoolLock.Lock() 209 | defer n.connPoolLock.Unlock() 210 | 211 | key := conn.target 212 | conns, _ := n.connPool[key] 213 | 214 | if !n.IsShutdown() && len(conns) < n.maxPool { 215 | n.connPool[key] = append(conns, conn) 216 | } else { 217 | conn.Release() 218 | } 219 | } 220 | 221 | // Sync implements the Transport interface. 222 | func (n *NetworkTransport) Sync(target string, args *SyncRequest, resp *SyncResponse) error { 223 | return n.genericRPC(target, rpcSync, args, resp) 224 | } 225 | 226 | // genericRPC handles a simple request/response RPC. 227 | func (n *NetworkTransport) genericRPC(target string, rpcType uint8, args interface{}, resp interface{}) error { 228 | // Get a conn 229 | conn, err := n.getConn(target, n.timeout) 230 | if err != nil { 231 | return err 232 | } 233 | 234 | // Set a deadline 235 | if n.timeout > 0 { 236 | conn.conn.SetDeadline(time.Now().Add(n.timeout)) 237 | } 238 | 239 | // Send the RPC 240 | if err = sendRPC(conn, rpcType, args); err != nil { 241 | return err 242 | } 243 | 244 | // Decode the response 245 | canReturn, err := decodeResponse(conn, resp) 246 | if canReturn { 247 | n.returnConn(conn) 248 | } 249 | return err 250 | } 251 | 252 | // listen is used to handling incoming connections. 253 | func (n *NetworkTransport) listen() { 254 | for { 255 | // Accept incoming connections 256 | conn, err := n.stream.Accept() 257 | if err != nil { 258 | if n.IsShutdown() { 259 | return 260 | } 261 | n.logger.WithField("error", err).Error("Failed to accept connection") 262 | continue 263 | } 264 | n.logger.WithFields(logrus.Fields{ 265 | "node": conn.LocalAddr(), 266 | "from": conn.RemoteAddr(), 267 | }).Debug("accepted connection") 268 | 269 | // Handle the connection in dedicated routine 270 | go n.handleConn(conn) 271 | } 272 | } 273 | 274 | // handleConn is used to handle an inbound connection for its lifespan. 275 | func (n *NetworkTransport) handleConn(conn net.Conn) { 276 | defer conn.Close() 277 | r := bufio.NewReader(conn) 278 | w := bufio.NewWriter(conn) 279 | dec := gob.NewDecoder(r) 280 | enc := gob.NewEncoder(w) 281 | 282 | for { 283 | if err := n.handleCommand(r, dec, enc); err != nil { 284 | if err != io.EOF { 285 | n.logger.WithField("error", err).Error("Failed to decode incoming command") 286 | } 287 | return 288 | } 289 | if err := w.Flush(); err != nil { 290 | n.logger.WithField("error", err).Error("Failed to flush response") 291 | return 292 | } 293 | } 294 | } 295 | 296 | // handleCommand is used to decode and dispatch a single command. 297 | func (n *NetworkTransport) handleCommand(r *bufio.Reader, dec *gob.Decoder, enc *gob.Encoder) error { 298 | // Get the rpc type 299 | rpcType, err := r.ReadByte() 300 | if err != nil { 301 | return err 302 | } 303 | 304 | // Create the RPC object 305 | respCh := make(chan RPCResponse, 1) 306 | rpc := RPC{ 307 | RespChan: respCh, 308 | } 309 | 310 | // Decode the command 311 | switch rpcType { 312 | case rpcSync: 313 | var req SyncRequest 314 | if err := dec.Decode(&req); err != nil { 315 | return err 316 | } 317 | rpc.Command = &req 318 | 319 | default: 320 | return fmt.Errorf("unknown rpc type %d", rpcType) 321 | } 322 | 323 | // Dispatch the RPC 324 | select { 325 | case n.consumeCh <- rpc: 326 | case <-n.shutdownCh: 327 | return ErrTransportShutdown 328 | } 329 | 330 | // Wait for response 331 | select { 332 | case resp := <-respCh: 333 | // Send the error first 334 | respErr := "" 335 | if resp.Error != nil { 336 | respErr = resp.Error.Error() 337 | } 338 | if err := enc.Encode(respErr); err != nil { 339 | return err 340 | } 341 | 342 | // Send the response 343 | if err := enc.Encode(resp.Response); err != nil { 344 | return err 345 | } 346 | case <-n.shutdownCh: 347 | return ErrTransportShutdown 348 | } 349 | return nil 350 | } 351 | 352 | // decodeResponse is used to decode an RPC response and reports whether 353 | // the connection can be reused. 354 | func decodeResponse(conn *netConn, resp interface{}) (bool, error) { 355 | // Decode the error if any 356 | var rpcError string 357 | if err := conn.dec.Decode(&rpcError); err != nil { 358 | conn.Release() 359 | return false, err 360 | } 361 | 362 | // Decode the response 363 | if err := conn.dec.Decode(resp); err != nil { 364 | conn.Release() 365 | return false, err 366 | } 367 | 368 | // Format an error if any 369 | if rpcError != "" { 370 | return true, fmt.Errorf(rpcError) 371 | } 372 | return true, nil 373 | } 374 | 375 | // sendRPC is used to encode and send the RPC. 376 | func sendRPC(conn *netConn, rpcType uint8, args interface{}) error { 377 | // Write the request type 378 | if err := conn.w.WriteByte(rpcType); err != nil { 379 | conn.Release() 380 | return err 381 | } 382 | 383 | // Send the request 384 | if err := conn.enc.Encode(args); err != nil { 385 | conn.Release() 386 | return err 387 | } 388 | 389 | // Flush 390 | if err := conn.w.Flush(); err != nil { 391 | conn.Release() 392 | return err 393 | } 394 | return nil 395 | } 396 | -------------------------------------------------------------------------------- /net/net_transport_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Mosaic Networks Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package net 17 | 18 | import ( 19 | "reflect" 20 | "sync" 21 | "testing" 22 | "time" 23 | 24 | "github.com/babbleio/babble/common" 25 | "github.com/babbleio/babble/hashgraph" 26 | ) 27 | 28 | func TestNetworkTransport_StartStop(t *testing.T) { 29 | trans, err := NewTCPTransport("127.0.0.1:0", nil, 2, time.Second, common.NewTestLogger(t)) 30 | if err != nil { 31 | t.Fatalf("err: %v", err) 32 | } 33 | trans.Close() 34 | } 35 | 36 | func TestNetworkTransport_Sync(t *testing.T) { 37 | // Transport 1 is consumer 38 | trans1, err := NewTCPTransport("127.0.0.1:0", nil, 2, time.Second, common.NewTestLogger(t)) 39 | if err != nil { 40 | t.Fatalf("err: %v", err) 41 | } 42 | defer trans1.Close() 43 | rpcCh := trans1.Consumer() 44 | 45 | // Make the RPC request 46 | args := SyncRequest{ 47 | From: "A", 48 | Known: map[int]int{ 49 | 0: 1, 50 | 1: 2, 51 | 2: 3, 52 | }, 53 | } 54 | resp := SyncResponse{ 55 | From: "B", 56 | Head: "head", 57 | Events: []hashgraph.WireEvent{ 58 | hashgraph.WireEvent{ 59 | Body: hashgraph.WireBody{ 60 | Transactions: [][]byte(nil), 61 | SelfParentIndex: 1, 62 | OtherParentCreatorID: 10, 63 | OtherParentIndex: 0, 64 | CreatorID: 9, 65 | }, 66 | }, 67 | }, 68 | } 69 | 70 | // Listen for a request 71 | go func() { 72 | select { 73 | case rpc := <-rpcCh: 74 | // Verify the command 75 | req := rpc.Command.(*SyncRequest) 76 | if !reflect.DeepEqual(req, &args) { 77 | t.Fatalf("command mismatch: %#v %#v", *req, args) 78 | } 79 | 80 | rpc.Respond(&resp, nil) 81 | 82 | case <-time.After(200 * time.Millisecond): 83 | t.Fatalf("timeout") 84 | } 85 | }() 86 | 87 | // Transport 2 makes outbound request 88 | trans2, err := NewTCPTransport("127.0.0.1:0", nil, 2, time.Second, common.NewTestLogger(t)) 89 | if err != nil { 90 | t.Fatalf("err: %v", err) 91 | } 92 | defer trans2.Close() 93 | 94 | var out SyncResponse 95 | if err := trans2.Sync(trans1.LocalAddr(), &args, &out); err != nil { 96 | t.Fatalf("err: %v", err) 97 | } 98 | 99 | // Verify the response 100 | if !reflect.DeepEqual(resp, out) { 101 | t.Fatalf("command mismatch: %#v %#v", resp, out) 102 | } 103 | } 104 | 105 | func TestNetworkTransport_PooledConn(t *testing.T) { 106 | // Transport 1 is consumer 107 | trans1, err := NewTCPTransport("127.0.0.1:0", nil, 2, time.Second, common.NewTestLogger(t)) 108 | if err != nil { 109 | t.Fatalf("err: %v", err) 110 | } 111 | defer trans1.Close() 112 | rpcCh := trans1.Consumer() 113 | 114 | // Make the RPC request 115 | args := SyncRequest{ 116 | From: "A", 117 | Known: map[int]int{ 118 | 0: 1, 119 | 1: 2, 120 | 2: 3, 121 | }, 122 | } 123 | resp := SyncResponse{ 124 | From: "B", 125 | Head: "head", 126 | Events: []hashgraph.WireEvent{ 127 | hashgraph.WireEvent{ 128 | Body: hashgraph.WireBody{ 129 | Transactions: [][]byte(nil), 130 | SelfParentIndex: 1, 131 | OtherParentCreatorID: 10, 132 | OtherParentIndex: 0, 133 | CreatorID: 9, 134 | }, 135 | }, 136 | }, 137 | } 138 | 139 | // Listen for a request 140 | go func() { 141 | for { 142 | select { 143 | case rpc := <-rpcCh: 144 | // Verify the command 145 | req := rpc.Command.(*SyncRequest) 146 | if !reflect.DeepEqual(req, &args) { 147 | t.Fatalf("command mismatch: %#v %#v", *req, args) 148 | } 149 | rpc.Respond(&resp, nil) 150 | 151 | case <-time.After(200 * time.Millisecond): 152 | return 153 | } 154 | } 155 | }() 156 | 157 | // Transport 2 makes outbound request, 3 conn pool 158 | trans2, err := NewTCPTransport("127.0.0.1:0", nil, 3, time.Second, common.NewTestLogger(t)) 159 | if err != nil { 160 | t.Fatalf("err: %v", err) 161 | } 162 | defer trans2.Close() 163 | 164 | // Create wait group 165 | wg := &sync.WaitGroup{} 166 | wg.Add(5) 167 | 168 | appendFunc := func() { 169 | defer wg.Done() 170 | var out SyncResponse 171 | if err := trans2.Sync(trans1.LocalAddr(), &args, &out); err != nil { 172 | t.Fatalf("err: %v", err) 173 | } 174 | 175 | // Verify the response 176 | if !reflect.DeepEqual(resp, out) { 177 | t.Fatalf("command mismatch: %#v %#v", resp, out) 178 | } 179 | } 180 | 181 | // Try to do parallel appends, should stress the conn pool 182 | for i := 0; i < 5; i++ { 183 | go appendFunc() 184 | } 185 | 186 | // Wait for the routines to finish 187 | wg.Wait() 188 | 189 | // Check the conn pool size 190 | addr := trans1.LocalAddr() 191 | if len(trans2.connPool[addr]) != 3 { 192 | t.Fatalf("Expected 2 pooled conns!") 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /net/peer.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Mosaic Networks Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package net 17 | 18 | import ( 19 | "bytes" 20 | "encoding/hex" 21 | "encoding/json" 22 | "io/ioutil" 23 | "os" 24 | "path/filepath" 25 | "sync" 26 | ) 27 | 28 | const ( 29 | jsonPeerPath = "peers.json" 30 | ) 31 | 32 | type Peer struct { 33 | NetAddr string 34 | PubKeyHex string 35 | } 36 | 37 | func (p *Peer) PubKeyBytes() ([]byte, error) { 38 | return hex.DecodeString(p.PubKeyHex[2:]) 39 | } 40 | 41 | // PeerStore provides an interface for persistent storage and 42 | // retrieval of peers. 43 | type PeerStore interface { 44 | // Peers returns the list of known peers. 45 | Peers() ([]Peer, error) 46 | 47 | // SetPeers sets the list of known peers. This is invoked when a peer is 48 | // added or removed. 49 | SetPeers([]Peer) error 50 | } 51 | 52 | // StaticPeers is used to provide a static list of peers. 53 | type StaticPeers struct { 54 | StaticPeers []Peer 55 | l sync.Mutex 56 | } 57 | 58 | // Peers implements the PeerStore interface. 59 | func (s *StaticPeers) Peers() ([]Peer, error) { 60 | s.l.Lock() 61 | peers := s.StaticPeers 62 | s.l.Unlock() 63 | return peers, nil 64 | } 65 | 66 | // SetPeers implements the PeerStore interface. 67 | func (s *StaticPeers) SetPeers(p []Peer) error { 68 | s.l.Lock() 69 | s.StaticPeers = p 70 | s.l.Unlock() 71 | return nil 72 | } 73 | 74 | // JSONPeers is used to provide peer persistence on disk in the form 75 | // of a JSON file. This allows human operators to manipulate the file. 76 | type JSONPeers struct { 77 | l sync.Mutex 78 | path string 79 | } 80 | 81 | // NewJSONPeers creates a new JSONPeers store. 82 | func NewJSONPeers(base string) *JSONPeers { 83 | path := filepath.Join(base, jsonPeerPath) 84 | store := &JSONPeers{ 85 | path: path, 86 | } 87 | return store 88 | } 89 | 90 | // Peers implements the PeerStore interface. 91 | func (j *JSONPeers) Peers() ([]Peer, error) { 92 | j.l.Lock() 93 | defer j.l.Unlock() 94 | 95 | // Read the file 96 | buf, err := ioutil.ReadFile(j.path) 97 | if err != nil && !os.IsNotExist(err) { 98 | return nil, err 99 | } 100 | 101 | // Check for no peers 102 | if len(buf) == 0 { 103 | return nil, nil 104 | } 105 | 106 | // Decode the peers 107 | var peerSet []Peer 108 | dec := json.NewDecoder(bytes.NewReader(buf)) 109 | if err := dec.Decode(&peerSet); err != nil { 110 | return nil, err 111 | } 112 | 113 | return peerSet, nil 114 | } 115 | 116 | // SetPeers implements the PeerStore interface. 117 | func (j *JSONPeers) SetPeers(peers []Peer) error { 118 | j.l.Lock() 119 | defer j.l.Unlock() 120 | 121 | var buf bytes.Buffer 122 | enc := json.NewEncoder(&buf) 123 | if err := enc.Encode(peers); err != nil { 124 | return err 125 | } 126 | 127 | // Write out as JSON 128 | return ioutil.WriteFile(j.path, buf.Bytes(), 0755) 129 | } 130 | 131 | // ExcludePeer is used to exclude a single peer from a list of peers. 132 | func ExcludePeer(peers []Peer, peer string) (int, []Peer) { 133 | index := -1 134 | otherPeers := make([]Peer, 0, len(peers)) 135 | for i, p := range peers { 136 | if p.NetAddr != peer { 137 | otherPeers = append(otherPeers, p) 138 | } else { 139 | index = i 140 | } 141 | } 142 | return index, otherPeers 143 | } 144 | 145 | //Sorting 146 | 147 | // ByPubKey implements sort.Interface for []Peer based on 148 | // the PubKeyHex field. 149 | type ByPubKey []Peer 150 | 151 | func (a ByPubKey) Len() int { return len(a) } 152 | func (a ByPubKey) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 153 | func (a ByPubKey) Less(i, j int) bool { 154 | ai := a[i].PubKeyHex 155 | aj := a[j].PubKeyHex 156 | return ai < aj 157 | } 158 | -------------------------------------------------------------------------------- /net/peer_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Mosaic Networks Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package net 17 | 18 | import ( 19 | "fmt" 20 | "io/ioutil" 21 | "os" 22 | "testing" 23 | 24 | "crypto/ecdsa" 25 | 26 | "reflect" 27 | 28 | scrypto "github.com/babbleio/babble/crypto" 29 | ) 30 | 31 | func TestJSONPeers(t *testing.T) { 32 | // Create a test dir 33 | dir, err := ioutil.TempDir("", "babble") 34 | if err != nil { 35 | t.Fatalf("err: %v ", err) 36 | } 37 | defer os.RemoveAll(dir) 38 | 39 | // Create the store 40 | store := NewJSONPeers(dir) 41 | 42 | // Try a read, should get nothing 43 | peers, err := store.Peers() 44 | if err != nil { 45 | t.Fatalf("err: %v", err) 46 | } 47 | if len(peers) != 0 { 48 | t.Fatalf("peers: %v", peers) 49 | } 50 | 51 | keys := []*ecdsa.PrivateKey{} 52 | newPeers := []Peer{} 53 | for i := 0; i < 3; i++ { 54 | key, _ := scrypto.GenerateECDSAKey() 55 | peer := Peer{ 56 | NetAddr: fmt.Sprintf("addr%d", i), 57 | PubKeyHex: fmt.Sprintf("0x%X", scrypto.FromECDSAPub(&key.PublicKey)), 58 | } 59 | keys = append(keys, key) 60 | newPeers = append(newPeers, peer) 61 | } 62 | 63 | if err := store.SetPeers(newPeers); err != nil { 64 | t.Fatalf("err: %v", err) 65 | } 66 | 67 | // Try a read, should peers 68 | peers, err = store.Peers() 69 | if err != nil { 70 | t.Fatalf("err: %v", err) 71 | } 72 | if len(peers) != 3 { 73 | t.Fatalf("peers: %v", peers) 74 | } 75 | 76 | for i := 0; i < 3; i++ { 77 | if peers[i].NetAddr != newPeers[i].NetAddr { 78 | t.Fatalf("peers[%d] NetAddr should be %s, not %s", i, 79 | newPeers[i].NetAddr, peers[i].NetAddr) 80 | } 81 | if peers[i].PubKeyHex != newPeers[i].PubKeyHex { 82 | t.Fatalf("peers[%d] PubKeyHex should be %s, not %s", i, 83 | newPeers[i].PubKeyHex, peers[i].PubKeyHex) 84 | } 85 | pubKeyBytes, err := peers[i].PubKeyBytes() 86 | if err != nil { 87 | t.Fatal(err) 88 | } 89 | pubKey := scrypto.ToECDSAPub(pubKeyBytes) 90 | if !reflect.DeepEqual(*pubKey, keys[i].PublicKey) { 91 | t.Fatalf("peers[%d] PublicKey not parsed correctly", i) 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /net/tcp_transport.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Mosaic Networks Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package net 17 | 18 | import ( 19 | "errors" 20 | "net" 21 | "time" 22 | 23 | "github.com/Sirupsen/logrus" 24 | ) 25 | 26 | var ( 27 | errNotAdvertisable = errors.New("local bind address is not advertisable") 28 | errNotTCP = errors.New("local address is not a TCP address") 29 | ) 30 | 31 | // TCPStreamLayer implements StreamLayer interface for plain TCP. 32 | type TCPStreamLayer struct { 33 | advertise net.Addr 34 | listener *net.TCPListener 35 | } 36 | 37 | // Dial implements the StreamLayer interface. 38 | func (t *TCPStreamLayer) Dial(address string, timeout time.Duration) (net.Conn, error) { 39 | return net.DialTimeout("tcp", address, timeout) 40 | } 41 | 42 | // Accept implements the net.Listener interface. 43 | func (t *TCPStreamLayer) Accept() (c net.Conn, err error) { 44 | return t.listener.Accept() 45 | } 46 | 47 | // Close implements the net.Listener interface. 48 | func (t *TCPStreamLayer) Close() (err error) { 49 | return t.listener.Close() 50 | } 51 | 52 | // Addr implements the net.Listener interface. 53 | func (t *TCPStreamLayer) Addr() net.Addr { 54 | // Use an advertise addr if provided 55 | if t.advertise != nil { 56 | return t.advertise 57 | } 58 | return t.listener.Addr() 59 | } 60 | 61 | // NewTCPTransport returns a NetworkTransport that is built on top of 62 | // a TCP streaming transport layer, with log output going to the supplied Logger 63 | func NewTCPTransport( 64 | bindAddr string, 65 | advertise net.Addr, 66 | maxPool int, 67 | timeout time.Duration, 68 | logger *logrus.Logger, 69 | ) (*NetworkTransport, error) { 70 | return newTCPTransport(bindAddr, advertise, maxPool, timeout, func(stream StreamLayer) *NetworkTransport { 71 | return NewNetworkTransport(stream, maxPool, timeout, logger) 72 | }) 73 | } 74 | 75 | func newTCPTransport(bindAddr string, 76 | advertise net.Addr, 77 | maxPool int, 78 | timeout time.Duration, 79 | transportCreator func(stream StreamLayer) *NetworkTransport) (*NetworkTransport, error) { 80 | // Try to bind 81 | list, err := net.Listen("tcp", bindAddr) 82 | if err != nil { 83 | return nil, err 84 | } 85 | 86 | // Create stream 87 | stream := &TCPStreamLayer{ 88 | advertise: advertise, 89 | listener: list.(*net.TCPListener), 90 | } 91 | 92 | // Verify that we have a usable advertise address 93 | addr, ok := stream.Addr().(*net.TCPAddr) 94 | if !ok { 95 | list.Close() 96 | return nil, errNotTCP 97 | } 98 | if addr.IP.IsUnspecified() { 99 | list.Close() 100 | return nil, errNotAdvertisable 101 | } 102 | 103 | // Create the network transport 104 | trans := transportCreator(stream) 105 | return trans, nil 106 | } 107 | -------------------------------------------------------------------------------- /net/tcp_transport_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Mosaic Networks Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package net 17 | 18 | import ( 19 | "net" 20 | "testing" 21 | 22 | "github.com/babbleio/babble/common" 23 | ) 24 | 25 | func TestTCPTransport_BadAddr(t *testing.T) { 26 | _, err := NewTCPTransport("0.0.0.0:0", nil, 1, 0, common.NewTestLogger(t)) 27 | if err != errNotAdvertisable { 28 | t.Fatalf("err: %v", err) 29 | } 30 | } 31 | 32 | func TestTCPTransport_WithAdvertise(t *testing.T) { 33 | addr := &net.TCPAddr{IP: []byte{127, 0, 0, 1}, Port: 12345} 34 | trans, err := NewTCPTransport("0.0.0.0:0", addr, 1, 0, common.NewTestLogger(t)) 35 | if err != nil { 36 | t.Fatalf("err: %v", err) 37 | } 38 | if trans.LocalAddr() != "127.0.0.1:12345" { 39 | t.Fatalf("bad: %v", trans.LocalAddr()) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /net/transport.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Mosaic Networks Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package net 17 | 18 | import "io" 19 | 20 | // RPCResponse captures both a response and a potential error. 21 | type RPCResponse struct { 22 | Response interface{} 23 | Error error 24 | } 25 | 26 | // RPC has a command, and provides a response mechanism. 27 | type RPC struct { 28 | Command interface{} 29 | Reader io.Reader 30 | RespChan chan<- RPCResponse 31 | } 32 | 33 | // Respond is used to respond with a response, error or both 34 | func (r *RPC) Respond(resp interface{}, err error) { 35 | r.RespChan <- RPCResponse{resp, err} 36 | } 37 | 38 | // Transport provides an interface for network transports 39 | // to allow a node to communicate with other nodes. 40 | type Transport interface { 41 | // Consumer returns a channel that can be used to 42 | // consume and respond to RPC requests. 43 | Consumer() <-chan RPC 44 | 45 | // LocalAddr is used to return our local address to distinguish from our peers. 46 | LocalAddr() string 47 | 48 | // Sync sends the appropriate RPC to the target node. 49 | Sync(target string, args *SyncRequest, resp *SyncResponse) error 50 | 51 | // Close permanently closes a transport, stopping 52 | // any associated goroutines and freeing other resources. 53 | Close() error 54 | } 55 | 56 | // WithPeers is an interface that a transport may provide which allows for connection and 57 | // disconnection. 58 | // "Connect" is likely to be nil. 59 | type WithPeers interface { 60 | Connect(peer string, t Transport) // Connect a peer 61 | Disconnect(peer string) // Disconnect a given peer 62 | DisconnectAll() // Disconnect all peers, possibly to reconnect them later 63 | } 64 | 65 | // LoopbackTransport is an interface that provides a loopback transport suitable for testing 66 | // e.g. InmemTransport. It's there so we don't have to rewrite tests. 67 | type LoopbackTransport interface { 68 | Transport // Embedded transport reference 69 | WithPeers // Embedded peer management 70 | } 71 | -------------------------------------------------------------------------------- /net/transport_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Mosaic Networks Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package net 17 | 18 | import ( 19 | "reflect" 20 | "testing" 21 | "time" 22 | 23 | "github.com/babbleio/babble/hashgraph" 24 | ) 25 | 26 | const ( 27 | TTInmem = iota 28 | 29 | // NOTE: must be last 30 | numTestTransports 31 | ) 32 | 33 | func NewTestTransport(ttype int, addr string) (string, LoopbackTransport) { 34 | switch ttype { 35 | case TTInmem: 36 | addr, lt := NewInmemTransport(addr) 37 | return addr, lt 38 | default: 39 | panic("Unknown transport type") 40 | } 41 | } 42 | 43 | func TestTransport_StartStop(t *testing.T) { 44 | for ttype := 0; ttype < numTestTransports; ttype++ { 45 | _, trans := NewTestTransport(ttype, "") 46 | if err := trans.Close(); err != nil { 47 | t.Fatalf("err: %v", err) 48 | } 49 | } 50 | } 51 | 52 | func TestTransport_Sync(t *testing.T) { 53 | for ttype := 0; ttype < numTestTransports; ttype++ { 54 | addr1, trans1 := NewTestTransport(ttype, "") 55 | defer trans1.Close() 56 | rpcCh := trans1.Consumer() 57 | 58 | // Make the RPC request 59 | args := SyncRequest{ 60 | From: "A", 61 | Known: map[int]int{ 62 | 0: 1, 63 | 1: 2, 64 | 2: 3, 65 | }, 66 | } 67 | resp := SyncResponse{ 68 | From: "B", 69 | Head: "head", 70 | Events: []hashgraph.WireEvent{ 71 | hashgraph.WireEvent{ 72 | Body: hashgraph.WireBody{ 73 | Transactions: [][]byte(nil), 74 | SelfParentIndex: 1, 75 | OtherParentCreatorID: 10, 76 | OtherParentIndex: 0, 77 | CreatorID: 9, 78 | }, 79 | }, 80 | }, 81 | } 82 | 83 | // Listen for a request 84 | go func() { 85 | select { 86 | case rpc := <-rpcCh: 87 | // Verify the command 88 | req := rpc.Command.(*SyncRequest) 89 | if !reflect.DeepEqual(req, &args) { 90 | t.Fatalf("command mismatch: %#v %#v", *req, args) 91 | } 92 | rpc.Respond(&resp, nil) 93 | 94 | case <-time.After(200 * time.Millisecond): 95 | t.Fatalf("timeout") 96 | } 97 | }() 98 | 99 | // Transport 2 makes outbound request 100 | addr2, trans2 := NewTestTransport(ttype, "") 101 | defer trans2.Close() 102 | 103 | trans1.Connect(addr2, trans2) 104 | trans2.Connect(addr1, trans1) 105 | 106 | var out SyncResponse 107 | if err := trans2.Sync(trans1.LocalAddr(), &args, &out); err != nil { 108 | t.Fatalf("err: %v", err) 109 | } 110 | 111 | // Verify the response 112 | if !reflect.DeepEqual(resp, out) { 113 | t.Fatalf("command mismatch: %#v %#v", resp, out) 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /node/config.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Mosaic Networks Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package node 17 | 18 | import ( 19 | "testing" 20 | "time" 21 | 22 | "github.com/babbleio/babble/common" 23 | "github.com/Sirupsen/logrus" 24 | ) 25 | 26 | type Config struct { 27 | HeartbeatTimeout time.Duration 28 | TCPTimeout time.Duration 29 | CacheSize int 30 | Logger *logrus.Logger 31 | } 32 | 33 | func NewConfig(heartbeat time.Duration, timeout time.Duration, cacheSize int, logger *logrus.Logger) *Config { 34 | return &Config{ 35 | HeartbeatTimeout: heartbeat, 36 | TCPTimeout: timeout, 37 | CacheSize: cacheSize, 38 | Logger: logger, 39 | } 40 | } 41 | 42 | func DefaultConfig() *Config { 43 | logger := logrus.New() 44 | logger.Level = logrus.DebugLevel 45 | return &Config{ 46 | HeartbeatTimeout: 1000 * time.Millisecond, 47 | TCPTimeout: 1000 * time.Millisecond, 48 | CacheSize: 500, 49 | Logger: logger, 50 | } 51 | } 52 | 53 | func TestConfig(t *testing.T) *Config { 54 | config := DefaultConfig() 55 | config.Logger = common.NewTestLogger(t) 56 | return config 57 | } 58 | -------------------------------------------------------------------------------- /node/core.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Mosaic Networks Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package node 17 | 18 | import ( 19 | "crypto/ecdsa" 20 | "fmt" 21 | "sort" 22 | "time" 23 | 24 | "github.com/Sirupsen/logrus" 25 | 26 | "github.com/babbleio/babble/crypto" 27 | hg "github.com/babbleio/babble/hashgraph" 28 | ) 29 | 30 | type Core struct { 31 | id int 32 | key *ecdsa.PrivateKey 33 | hg hg.Hashgraph 34 | 35 | participants map[string]int //[PubKey] => id 36 | reverseParticipants map[int]string //[id] => PubKey 37 | Head string 38 | Seq int 39 | 40 | logger *logrus.Logger 41 | } 42 | 43 | func NewCore( 44 | id int, 45 | key *ecdsa.PrivateKey, 46 | participants map[string]int, 47 | store hg.Store, 48 | commitCh chan []hg.Event, 49 | logger *logrus.Logger) Core { 50 | if logger == nil { 51 | logger = logrus.New() 52 | logger.Level = logrus.DebugLevel 53 | } 54 | 55 | reverseParticipants := make(map[int]string) 56 | for pk, id := range participants { 57 | reverseParticipants[id] = pk 58 | } 59 | 60 | core := Core{ 61 | id: id, 62 | key: key, 63 | hg: hg.NewHashgraph(participants, store, commitCh, logger), 64 | participants: participants, 65 | reverseParticipants: reverseParticipants, 66 | logger: logger, 67 | } 68 | return core 69 | } 70 | 71 | func (c *Core) ID() int { 72 | return c.id 73 | } 74 | 75 | func (c *Core) PubKey() []byte { 76 | return crypto.FromECDSAPub(&c.key.PublicKey) 77 | } 78 | 79 | func (c *Core) Init() error { 80 | initialEvent := hg.NewEvent([][]byte(nil), 81 | []string{"", ""}, 82 | c.PubKey(), 83 | c.Seq) 84 | return c.SignAndInsertSelfEvent(initialEvent) 85 | } 86 | 87 | func (c *Core) SignAndInsertSelfEvent(event hg.Event) error { 88 | if err := event.Sign(c.key); err != nil { 89 | return err 90 | } 91 | if err := c.InsertEvent(event); err != nil { 92 | return err 93 | } 94 | c.Head = event.Hex() 95 | c.Seq++ 96 | return nil 97 | } 98 | 99 | func (c *Core) InsertEvent(event hg.Event) error { 100 | return c.hg.InsertEvent(event) 101 | } 102 | 103 | func (c *Core) Known() map[int]int { 104 | return c.hg.Known() 105 | } 106 | 107 | //returns events that c knowns about that are not in 'known', along with c's head 108 | func (c *Core) Diff(known map[int]int) (head string, events []hg.Event, err error) { 109 | head = c.Head 110 | 111 | unknown := []hg.Event{} 112 | //known represents the number of events known for every participant 113 | //compare this to our view of events and fill unknown with events that we know of 114 | // and the other doesnt 115 | for id, ct := range known { 116 | pk := c.reverseParticipants[id] 117 | participantEvents, err := c.hg.Store.ParticipantEvents(pk, ct) 118 | if err != nil { 119 | return "", []hg.Event{}, err 120 | } 121 | for _, e := range participantEvents { 122 | ev, err := c.hg.Store.GetEvent(e) 123 | if err != nil { 124 | return "", []hg.Event{}, err 125 | } 126 | unknown = append(unknown, ev) 127 | } 128 | } 129 | sort.Sort(hg.ByTopologicalOrder(unknown)) 130 | 131 | return head, unknown, nil 132 | } 133 | 134 | func (c *Core) Sync(otherHead string, unknown []hg.WireEvent, payload [][]byte) error { 135 | 136 | //add unknown events 137 | for _, we := range unknown { 138 | ev, err := c.hg.ReadWireInfo(we) 139 | if err != nil { 140 | return err 141 | } 142 | if err := c.InsertEvent(*ev); err != nil { 143 | return err 144 | } 145 | } 146 | 147 | //create new event with self head and other head 148 | newHead := hg.NewEvent(payload, 149 | []string{c.Head, otherHead}, 150 | c.PubKey(), c.Seq) 151 | 152 | if err := c.SignAndInsertSelfEvent(newHead); err != nil { 153 | return fmt.Errorf("Error inserting new head: %s", err) 154 | } 155 | 156 | return nil 157 | } 158 | 159 | func (c *Core) FromWire(wireEvents []hg.WireEvent) ([]hg.Event, error) { 160 | events := make([]hg.Event, len(wireEvents), len(wireEvents)) 161 | for i, w := range wireEvents { 162 | ev, err := c.hg.ReadWireInfo(w) 163 | if err != nil { 164 | return nil, err 165 | } 166 | events[i] = *ev 167 | } 168 | return events, nil 169 | } 170 | 171 | func (c *Core) ToWire(events []hg.Event) ([]hg.WireEvent, error) { 172 | wireEvents := make([]hg.WireEvent, len(events), len(events)) 173 | for i, e := range events { 174 | wireEvents[i] = e.ToWire() 175 | } 176 | return wireEvents, nil 177 | } 178 | 179 | func (c *Core) RunConsensus() error { 180 | start := time.Now() 181 | err := c.hg.DivideRounds() 182 | c.logger.WithField("duration", time.Since(start).Nanoseconds()).Debug("DivideRounds()") 183 | if err != nil { 184 | return err 185 | } 186 | 187 | start = time.Now() 188 | err = c.hg.DecideFame() 189 | c.logger.WithField("duration", time.Since(start).Nanoseconds()).Debug("DecideFame()") 190 | if err != nil { 191 | return err 192 | } 193 | 194 | start = time.Now() 195 | err = c.hg.FindOrder() 196 | c.logger.WithField("duration", time.Since(start).Nanoseconds()).Debug("FindOrder()") 197 | if err != nil { 198 | return err 199 | } 200 | 201 | return nil 202 | } 203 | 204 | func (c *Core) GetHead() (hg.Event, error) { 205 | return c.hg.Store.GetEvent(c.Head) 206 | } 207 | 208 | func (c *Core) GetEvent(hash string) (hg.Event, error) { 209 | return c.hg.Store.GetEvent(hash) 210 | } 211 | 212 | func (c *Core) GetEventTransactions(hash string) ([][]byte, error) { 213 | var txs [][]byte 214 | ex, err := c.GetEvent(hash) 215 | if err != nil { 216 | return txs, err 217 | } 218 | txs = ex.Transactions() 219 | return txs, nil 220 | } 221 | 222 | func (c *Core) GetConsensusEvents() []string { 223 | return c.hg.ConsensusEvents() 224 | } 225 | 226 | func (c *Core) GetConsensusEventsCount() int { 227 | return c.hg.Store.ConsensusEventsCount() 228 | } 229 | 230 | func (c *Core) GetUndeterminedEvents() []string { 231 | return c.hg.UndeterminedEvents 232 | } 233 | 234 | func (c *Core) GetConsensusTransactions() ([][]byte, error) { 235 | txs := [][]byte{} 236 | for _, e := range c.GetConsensusEvents() { 237 | eTxs, err := c.GetEventTransactions(e) 238 | if err != nil { 239 | return txs, fmt.Errorf("Consensus event not found: %s", e) 240 | } 241 | txs = append(txs, eTxs...) 242 | } 243 | return txs, nil 244 | } 245 | 246 | func (c *Core) GetLastConsensusRoundIndex() *int { 247 | return c.hg.LastConsensusRound 248 | } 249 | 250 | func (c *Core) GetConsensusTransactionsCount() int { 251 | return c.hg.ConsensusTransactions 252 | } 253 | 254 | func (c *Core) GetLastCommitedRoundEventsCount() int { 255 | return c.hg.LastCommitedRoundEvents 256 | } 257 | -------------------------------------------------------------------------------- /node/core_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Mosaic Networks Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package node 17 | 18 | import ( 19 | "crypto/ecdsa" 20 | "fmt" 21 | "testing" 22 | 23 | "github.com/babbleio/babble/common" 24 | "github.com/babbleio/babble/crypto" 25 | hg "github.com/babbleio/babble/hashgraph" 26 | ) 27 | 28 | func TestInit(t *testing.T) { 29 | key, _ := crypto.GenerateECDSAKey() 30 | participants := map[string]int{ 31 | fmt.Sprintf("0x%X", crypto.FromECDSAPub(&key.PublicKey)): 0, 32 | } 33 | core := NewCore(0, key, participants, hg.NewInmemStore(participants, 10), nil, common.NewTestLogger(t)) 34 | if err := core.Init(); err != nil { 35 | t.Fatalf("Init returned and error: %s", err) 36 | } 37 | } 38 | 39 | func initCores(t *testing.T) ([]Core, []*ecdsa.PrivateKey, map[string]string) { 40 | n := 3 41 | cacheSize := 1000 42 | 43 | cores := []Core{} 44 | index := make(map[string]string) 45 | 46 | participantKeys := []*ecdsa.PrivateKey{} 47 | participants := make(map[string]int) 48 | for i := 0; i < n; i++ { 49 | key, _ := crypto.GenerateECDSAKey() 50 | participantKeys = append(participantKeys, key) 51 | participants[fmt.Sprintf("0x%X", crypto.FromECDSAPub(&key.PublicKey))] = i 52 | } 53 | 54 | for i := 0; i < n; i++ { 55 | core := NewCore(i, participantKeys[i], participants, 56 | hg.NewInmemStore(participants, cacheSize), nil, common.NewTestLogger(t)) 57 | core.Init() 58 | cores = append(cores, core) 59 | index[fmt.Sprintf("e%d", i)] = core.Head 60 | } 61 | 62 | return cores, participantKeys, index 63 | } 64 | 65 | /* 66 | | e12 | 67 | | | \ | 68 | | | e20 69 | | | / | 70 | | / | 71 | | / | | 72 | e01 | | 73 | | \ | | 74 | e0 e1 e2 75 | 0 1 2 76 | */ 77 | func initHashgraph(cores []Core, keys []*ecdsa.PrivateKey, index map[string]string, participant int) { 78 | for i := 0; i < len(cores); i++ { 79 | if i != participant { 80 | event, _ := cores[i].GetEvent(index[fmt.Sprintf("e%d", i)]) 81 | if err := cores[participant].InsertEvent(event); err != nil { 82 | fmt.Printf("error inserting %s: %s\n", getName(index, event.Hex()), err) 83 | } 84 | } 85 | } 86 | 87 | event01 := hg.NewEvent([][]byte{}, 88 | []string{index["e0"], index["e1"]}, //e0 and e1 89 | cores[0].PubKey(), 1) 90 | if err := insertEvent(cores, keys, index, event01, "e01", participant, 0); err != nil { 91 | fmt.Printf("error inserting e01: %s\n", err) 92 | } 93 | 94 | event20 := hg.NewEvent([][]byte{}, 95 | []string{index["e2"], index["e01"]}, //e2 and e01 96 | cores[2].PubKey(), 1) 97 | if err := insertEvent(cores, keys, index, event20, "e20", participant, 2); err != nil { 98 | fmt.Printf("error inserting e20: %s\n", err) 99 | } 100 | 101 | event12 := hg.NewEvent([][]byte{}, 102 | []string{index["e1"], index["e20"]}, //e1 and e20 103 | cores[1].PubKey(), 1) 104 | if err := insertEvent(cores, keys, index, event12, "e12", participant, 1); err != nil { 105 | fmt.Printf("error inserting e12: %s\n", err) 106 | } 107 | } 108 | 109 | func insertEvent(cores []Core, keys []*ecdsa.PrivateKey, index map[string]string, 110 | event hg.Event, name string, particant int, creator int) error { 111 | 112 | if particant == creator { 113 | if err := cores[particant].SignAndInsertSelfEvent(event); err != nil { 114 | return err 115 | } 116 | //event is not signed because passed by value 117 | index[name] = cores[particant].Head 118 | } else { 119 | event.Sign(keys[creator]) 120 | if err := cores[particant].InsertEvent(event); err != nil { 121 | return err 122 | } 123 | index[name] = event.Hex() 124 | } 125 | return nil 126 | } 127 | 128 | func TestDiff(t *testing.T) { 129 | cores, keys, index := initCores(t) 130 | 131 | initHashgraph(cores, keys, index, 0) 132 | 133 | /* 134 | P0 knows 135 | 136 | | e12 | 137 | | | \ | 138 | | | e20 139 | | | / | 140 | | / | 141 | | / | | 142 | e01 | | P1 knows 143 | | \ | | 144 | e0 e1 e2 | e1 | 145 | 0 1 2 0 1 2 146 | */ 147 | 148 | knownBy1 := cores[1].Known() 149 | head, unknownBy1, err := cores[0].Diff(knownBy1) 150 | if err != nil { 151 | t.Fatal(err) 152 | } 153 | if head != index["e01"] { 154 | t.Fatalf("head of core 0 should be e01") 155 | } 156 | 157 | if l := len(unknownBy1); l != 5 { 158 | t.Fatalf("length of unknown should be 5, not %d", l) 159 | } 160 | 161 | expectedOrder := []string{"e0", "e2", "e01", "e20", "e12"} 162 | for i, e := range unknownBy1 { 163 | if name := getName(index, e.Hex()); name != expectedOrder[i] { 164 | t.Fatalf("element %d should be %s, not %s", i, expectedOrder[i], name) 165 | } 166 | } 167 | 168 | } 169 | 170 | func TestSync(t *testing.T) { 171 | cores, _, index := initCores(t) 172 | 173 | /* 174 | core 0 core 1 core 2 175 | 176 | e0 | | | e1 | | | e2 177 | 0 1 2 0 1 2 0 1 2 178 | */ 179 | 180 | //core 1 is going to tell core 0 everything it knows 181 | if err := synchronizeCores(cores, 1, 0, [][]byte{}); err != nil { 182 | t.Fatal(err) 183 | } 184 | 185 | /* 186 | core 0 core 1 core 2 187 | 188 | e01 | | 189 | | \ | | 190 | e0 e1 | | e1 | | | e2 191 | 0 1 2 0 1 2 0 1 2 192 | */ 193 | 194 | knownBy0 := cores[0].Known() 195 | if k := knownBy0[cores[0].ID()]; k != 2 { 196 | t.Fatalf("core 0 should have 2 events for core 0, not %d", k) 197 | } 198 | if k := knownBy0[cores[1].ID()]; k != 1 { 199 | t.Fatalf("core 0 should have 1 events for core 1, not %d", k) 200 | } 201 | if k := knownBy0[cores[2].ID()]; k != 0 { 202 | t.Fatalf("core 0 should have 0 events for core 2, not %d", k) 203 | } 204 | core0Head, _ := cores[0].GetHead() 205 | if core0Head.SelfParent() != index["e0"] { 206 | t.Fatalf("core 0 head self-parent should be e0") 207 | } 208 | if core0Head.OtherParent() != index["e1"] { 209 | t.Fatalf("core 0 head other-parent should be e1") 210 | } 211 | index["e01"] = core0Head.Hex() 212 | 213 | //core 0 is going to tell core 2 everything it knows 214 | if err := synchronizeCores(cores, 0, 2, [][]byte{}); err != nil { 215 | t.Fatal(err) 216 | } 217 | 218 | /* 219 | 220 | core 0 core 1 core 2 221 | 222 | | | e20 223 | | | / | 224 | | / | 225 | | / | | 226 | e01 | | e01 | | 227 | | \ | | | \ | | 228 | e0 e1 | | e1 | e0 e1 e2 229 | 0 1 2 0 1 2 0 1 2 230 | */ 231 | 232 | knownBy2 := cores[2].Known() 233 | if k := knownBy2[cores[0].ID()]; k != 2 { 234 | t.Fatalf("core 2 should have 2 events for core 0, not %d", k) 235 | } 236 | if k := knownBy2[cores[1].ID()]; k != 1 { 237 | t.Fatalf("core 2 should have 1 events for core 1, not %d", k) 238 | } 239 | if k := knownBy2[cores[2].ID()]; k != 2 { 240 | t.Fatalf("core 2 should have 2 events for core 2, not %d", k) 241 | } 242 | core2Head, _ := cores[2].GetHead() 243 | if core2Head.SelfParent() != index["e2"] { 244 | t.Fatalf("core 2 head self-parent should be e2") 245 | } 246 | if core2Head.OtherParent() != index["e01"] { 247 | t.Fatalf("core 2 head other-parent should be e01") 248 | } 249 | index["e20"] = core2Head.Hex() 250 | 251 | //core 2 is going to tell core 1 everything it knows 252 | if err := synchronizeCores(cores, 2, 1, [][]byte{}); err != nil { 253 | t.Fatal(err) 254 | } 255 | 256 | /* 257 | 258 | core 0 core 1 core 2 259 | 260 | | e12 | 261 | | | \ | 262 | | | e20 | | e20 263 | | | / | | | / | 264 | | / | | / | 265 | | / | | | / | | 266 | e01 | | e01 | | e01 | | 267 | | \ | | | \ | | | \ | | 268 | e0 e1 | e0 e1 e2 e0 e1 e2 269 | 0 1 2 0 1 2 0 1 2 270 | */ 271 | 272 | knownBy1 := cores[1].Known() 273 | if k := knownBy1[cores[0].ID()]; k != 2 { 274 | t.Fatalf("core 1 should have 2 events for core 0, not %d", k) 275 | } 276 | if k := knownBy1[cores[1].ID()]; k != 2 { 277 | t.Fatalf("core 1 should have 2 events for core 1, not %d", k) 278 | } 279 | if k := knownBy1[cores[2].ID()]; k != 2 { 280 | t.Fatalf("core 1 should have 2 events for core 2, not %d", k) 281 | } 282 | core1Head, _ := cores[1].GetHead() 283 | if core1Head.SelfParent() != index["e1"] { 284 | t.Fatalf("core 1 head self-parent should be e1") 285 | } 286 | if core1Head.OtherParent() != index["e20"] { 287 | t.Fatalf("core 1 head other-parent should be e20") 288 | } 289 | index["e12"] = core1Head.Hex() 290 | 291 | } 292 | 293 | /* 294 | h0 | h2 295 | | \ | / | 296 | | h1 | 297 | | /| | 298 | g02 | | 299 | | \ | | 300 | | \ | 301 | | | \ | 302 | | | g21 303 | | | / | 304 | | g10 | 305 | | / | | 306 | g0 | g2 307 | | \ | / | 308 | | g1 | 309 | | /| | 310 | f02 | | 311 | | \ | | 312 | | \ | 313 | | | \ | 314 | | | f21 315 | | | / | 316 | | f10 | 317 | | / | | 318 | f0 | f2 319 | | \ | / | 320 | | f1 | 321 | | /| | 322 | e02 | | 323 | | \ | | 324 | | \ | 325 | | | \ | 326 | | | e21 327 | | | / | 328 | | e10 | 329 | | / | | 330 | e0 e1 e2 331 | 0 1 2 332 | */ 333 | type play struct { 334 | from int 335 | to int 336 | payload [][]byte 337 | } 338 | 339 | func TestConsensus(t *testing.T) { 340 | cores, _, _ := initCores(t) 341 | 342 | playbook := []play{ 343 | play{from: 0, to: 1, payload: [][]byte{[]byte("e10")}}, 344 | play{from: 1, to: 2, payload: [][]byte{[]byte("e21")}}, 345 | play{from: 2, to: 0, payload: [][]byte{[]byte("e02")}}, 346 | play{from: 0, to: 1, payload: [][]byte{[]byte("f1")}}, 347 | play{from: 1, to: 0, payload: [][]byte{[]byte("f0")}}, 348 | play{from: 1, to: 2, payload: [][]byte{[]byte("f2")}}, 349 | 350 | play{from: 0, to: 1, payload: [][]byte{[]byte("f10")}}, 351 | play{from: 1, to: 2, payload: [][]byte{[]byte("f21")}}, 352 | play{from: 2, to: 0, payload: [][]byte{[]byte("f02")}}, 353 | play{from: 0, to: 1, payload: [][]byte{[]byte("g1")}}, 354 | play{from: 1, to: 0, payload: [][]byte{[]byte("g0")}}, 355 | play{from: 1, to: 2, payload: [][]byte{[]byte("g2")}}, 356 | 357 | play{from: 0, to: 1, payload: [][]byte{[]byte("g10")}}, 358 | play{from: 1, to: 2, payload: [][]byte{[]byte("g21")}}, 359 | play{from: 2, to: 0, payload: [][]byte{[]byte("g02")}}, 360 | play{from: 0, to: 1, payload: [][]byte{[]byte("h1")}}, 361 | play{from: 1, to: 0, payload: [][]byte{[]byte("h0")}}, 362 | play{from: 1, to: 2, payload: [][]byte{[]byte("h2")}}, 363 | } 364 | 365 | for _, play := range playbook { 366 | if err := syncAndRunConsensus(cores, play.from, play.to, play.payload); err != nil { 367 | t.Fatal(err) 368 | } 369 | } 370 | 371 | if l := len(cores[0].GetConsensusEvents()); l != 6 { 372 | t.Fatalf("length of consensus should be 6 not %d", l) 373 | } 374 | 375 | core0Consensus := cores[0].GetConsensusEvents() 376 | core1Consensus := cores[1].GetConsensusEvents() 377 | core2Consensus := cores[2].GetConsensusEvents() 378 | 379 | for i, e := range core0Consensus { 380 | if core1Consensus[i] != e { 381 | t.Fatalf("core 1 consensus[%d] does not match core 0's", i) 382 | } 383 | if core2Consensus[i] != e { 384 | t.Fatalf("core 2 consensus[%d] does not match core 0's", i) 385 | } 386 | } 387 | } 388 | 389 | func synchronizeCores(cores []Core, from int, to int, payload [][]byte) error { 390 | knownByTo := cores[to].Known() 391 | toHead, unknownByTo, err := cores[from].Diff(knownByTo) 392 | if err != nil { 393 | return err 394 | } 395 | 396 | unknownWire, err := cores[from].ToWire(unknownByTo) 397 | if err != nil { 398 | return err 399 | } 400 | 401 | return cores[to].Sync(toHead, unknownWire, payload) 402 | } 403 | 404 | func syncAndRunConsensus(cores []Core, from int, to int, payload [][]byte) error { 405 | if err := synchronizeCores(cores, from, to, payload); err != nil { 406 | return err 407 | } 408 | cores[to].RunConsensus() 409 | return nil 410 | } 411 | 412 | func getName(index map[string]string, hash string) string { 413 | for name, h := range index { 414 | if h == hash { 415 | return name 416 | } 417 | } 418 | return "" 419 | } 420 | -------------------------------------------------------------------------------- /node/node.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Mosaic Networks Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package node 17 | 18 | import ( 19 | "crypto/ecdsa" 20 | "fmt" 21 | "math/rand" 22 | "sort" 23 | "sync" 24 | "time" 25 | 26 | "github.com/Sirupsen/logrus" 27 | 28 | "strconv" 29 | 30 | hg "github.com/babbleio/babble/hashgraph" 31 | "github.com/babbleio/babble/net" 32 | "github.com/babbleio/babble/proxy" 33 | ) 34 | 35 | type Node struct { 36 | conf *Config 37 | logger *logrus.Entry 38 | 39 | id int 40 | core *Core 41 | coreLock sync.Mutex 42 | 43 | localAddr string 44 | 45 | peerSelector PeerSelector 46 | selectorLock sync.Mutex 47 | 48 | trans net.Transport 49 | netCh <-chan net.RPC 50 | 51 | proxy proxy.AppProxy 52 | submitCh chan []byte 53 | 54 | commitCh chan []hg.Event 55 | 56 | // Shutdown channel to exit, protected to prevent concurrent exits 57 | shutdown bool 58 | shutdownCh chan struct{} 59 | shutdownLock sync.Mutex 60 | 61 | transactionPool [][]byte 62 | 63 | start time.Time 64 | syncRequests int 65 | syncErrors int 66 | } 67 | 68 | func NewNode(conf *Config, key *ecdsa.PrivateKey, participants []net.Peer, trans net.Transport, proxy proxy.AppProxy) Node { 69 | localAddr := trans.LocalAddr() 70 | 71 | sort.Sort(net.ByPubKey(participants)) 72 | pmap := make(map[string]int) 73 | var id int 74 | for i, p := range participants { 75 | pmap[p.PubKeyHex] = i 76 | if p.NetAddr == localAddr { 77 | id = i 78 | } 79 | } 80 | 81 | store := hg.NewInmemStore(pmap, conf.CacheSize) 82 | commitCh := make(chan []hg.Event, 20) 83 | core := NewCore(id, key, pmap, store, commitCh, conf.Logger) 84 | 85 | peerSelector := NewRandomPeerSelector(participants, localAddr) 86 | 87 | node := Node{ 88 | id: id, 89 | conf: conf, 90 | core: &core, 91 | localAddr: localAddr, 92 | logger: conf.Logger.WithField("node", localAddr), 93 | peerSelector: peerSelector, 94 | trans: trans, 95 | netCh: trans.Consumer(), 96 | proxy: proxy, 97 | submitCh: proxy.SubmitCh(), 98 | commitCh: commitCh, 99 | shutdownCh: make(chan struct{}), 100 | transactionPool: [][]byte{}, 101 | } 102 | return node 103 | } 104 | 105 | func (n *Node) Init() error { 106 | peerAddresses := []string{} 107 | for _, p := range n.peerSelector.Peers() { 108 | peerAddresses = append(peerAddresses, p.NetAddr) 109 | } 110 | n.logger.WithField("peers", peerAddresses).Debug("Init Node") 111 | return n.core.Init() 112 | } 113 | 114 | func (n *Node) RunAsync(gossip bool) { 115 | n.logger.Debug("runasync") 116 | go n.Run(gossip) 117 | } 118 | 119 | func (n *Node) Run(gossip bool) { 120 | n.start = time.Now() 121 | heartbeatTimer := randomTimeout(n.conf.HeartbeatTimeout) 122 | for { 123 | select { 124 | case rpc := <-n.netCh: 125 | n.logger.Debug("Processing RPC") 126 | n.processRPC(rpc) 127 | case <-heartbeatTimer: 128 | if gossip { 129 | n.logger.Debug("Time to gossip!") 130 | peer := n.peerSelector.Next() 131 | go n.gossip(peer.NetAddr) 132 | } 133 | heartbeatTimer = randomTimeout(n.conf.HeartbeatTimeout) 134 | case t := <-n.submitCh: 135 | n.logger.Debug("Adding Transaction") 136 | n.transactionPool = append(n.transactionPool, t) 137 | case events := <-n.commitCh: 138 | n.logger.WithField("events", len(events)).Debug("Committing Events") 139 | if err := n.Commit(events); err != nil { 140 | n.logger.WithField("error", err).Error("Committing Event") 141 | } 142 | case <-n.shutdownCh: 143 | return 144 | default: 145 | } 146 | } 147 | } 148 | 149 | func (n *Node) processRPC(rpc net.RPC) { 150 | switch cmd := rpc.Command.(type) { 151 | case *net.SyncRequest: 152 | n.logger.WithField("from", cmd.From).Debug("Processing SyncRequest") 153 | n.processSyncRequest(rpc, cmd) 154 | default: 155 | n.logger.WithField("cmd", rpc.Command).Error("Unexpected RPC command") 156 | rpc.Respond(nil, fmt.Errorf("unexpected command")) 157 | } 158 | } 159 | 160 | func (n *Node) processSyncRequest(rpc net.RPC, cmd *net.SyncRequest) { 161 | n.logger.WithFields(logrus.Fields{ 162 | "from": cmd.From, 163 | "known": cmd.Known, 164 | }).Debug("SyncRequest") 165 | 166 | start := time.Now() 167 | n.coreLock.Lock() 168 | head, diff, err := n.core.Diff(cmd.Known) 169 | n.coreLock.Unlock() 170 | elapsed := time.Since(start) 171 | n.logger.WithField("duration", elapsed.Nanoseconds()).Debug("Diff()") 172 | 173 | if err != nil { 174 | n.logger.WithField("error", err).Error("Calculating Diff") 175 | return 176 | } 177 | 178 | wireEvents, err := n.core.ToWire(diff) 179 | if err != nil { 180 | n.logger.WithField("error", err).Debug("Converting to WireEvent") 181 | return 182 | } 183 | 184 | n.logger.WithField("events", len(diff)).Debug("Responding to Sync Request") 185 | resp := &net.SyncResponse{ 186 | From: n.localAddr, 187 | Head: head, 188 | Events: wireEvents, 189 | } 190 | rpc.Respond(resp, err) 191 | } 192 | 193 | func (n *Node) gossip(peerAddr string) error { 194 | 195 | n.coreLock.Lock() 196 | known := n.core.Known() 197 | n.coreLock.Unlock() 198 | 199 | start := time.Now() 200 | resp, err := n.requestSync(peerAddr, known) 201 | elapsed := time.Since(start) 202 | n.logger.WithField("duration", elapsed.Nanoseconds()).Debug("requestSync()") 203 | if err != nil { 204 | n.logger.WithField("error", err).Error("requestSync()") 205 | return err 206 | } 207 | 208 | err = n.processSyncResponse(resp) 209 | if err != nil { 210 | n.logger.WithField("error", err).Error("processSyncResponse()") 211 | return err 212 | } 213 | 214 | n.selectorLock.Lock() 215 | n.peerSelector.UpdateLast(peerAddr) 216 | n.selectorLock.Unlock() 217 | 218 | n.logStats() 219 | 220 | return nil 221 | 222 | } 223 | 224 | func (n *Node) requestSync(target string, known map[int]int) (net.SyncResponse, error) { 225 | args := net.SyncRequest{ 226 | From: n.localAddr, 227 | Known: known, 228 | } 229 | 230 | var out net.SyncResponse 231 | err := n.trans.Sync(target, &args, &out) 232 | 233 | return out, err 234 | } 235 | 236 | func (n *Node) processSyncResponse(resp net.SyncResponse) error { 237 | n.coreLock.Lock() 238 | defer n.coreLock.Unlock() 239 | 240 | n.logger.WithField("events", fmt.Sprintf("%#v", resp.Events)).Debug("SyncResponse") 241 | 242 | start := time.Now() 243 | 244 | err := n.core.Sync(resp.Head, resp.Events, n.transactionPool) 245 | elapsed := time.Since(start) 246 | n.logger.WithField("duration", elapsed.Nanoseconds()).Debug("Processed Sync()") 247 | if err != nil { 248 | return err 249 | } 250 | 251 | n.transactionPool = [][]byte{} 252 | start = time.Now() 253 | err = n.core.RunConsensus() 254 | elapsed = time.Since(start) 255 | n.logger.WithField("duration", elapsed.Nanoseconds()).Debug("Processed RunConsensus()") 256 | if err != nil { 257 | return err 258 | } 259 | 260 | return nil 261 | } 262 | 263 | func (n *Node) Commit(events []hg.Event) error { 264 | for _, ev := range events { 265 | for _, tx := range ev.Transactions() { 266 | if err := n.proxy.CommitTx(tx); err != nil { 267 | return err 268 | } 269 | } 270 | } 271 | return nil 272 | } 273 | 274 | func (n *Node) Shutdown() { 275 | n.shutdownLock.Lock() 276 | defer n.shutdownLock.Unlock() 277 | 278 | if !n.shutdown { 279 | n.logger.Debug("Shutdown") 280 | close(n.shutdownCh) 281 | n.shutdown = true 282 | } 283 | } 284 | 285 | func (n *Node) GetStats() map[string]string { 286 | toString := func(i *int) string { 287 | if i == nil { 288 | return "nil" 289 | } 290 | return strconv.Itoa(*i) 291 | } 292 | 293 | timeElapsed := time.Since(n.start) 294 | 295 | consensusEvents := n.core.GetConsensusEventsCount() 296 | consensusEventsPerSecond := float64(consensusEvents) / timeElapsed.Seconds() 297 | 298 | lastConsensusRound := n.core.GetLastConsensusRoundIndex() 299 | var consensusRoundsPerSecond float64 300 | if lastConsensusRound != nil { 301 | consensusRoundsPerSecond = float64(*lastConsensusRound) / timeElapsed.Seconds() 302 | } 303 | 304 | s := map[string]string{ 305 | "last_consensus_round": toString(lastConsensusRound), 306 | "consensus_events": strconv.Itoa(consensusEvents), 307 | "consensus_transactions": strconv.Itoa(n.core.GetConsensusTransactionsCount()), 308 | "undetermined_events": strconv.Itoa(len(n.core.GetUndeterminedEvents())), 309 | "transaction_pool": strconv.Itoa(len(n.transactionPool)), 310 | "num_peers": strconv.Itoa(len(n.peerSelector.Peers())), 311 | "sync_rate": strconv.FormatFloat(n.SyncRate(), 'f', 2, 64), 312 | "events_per_second": strconv.FormatFloat(consensusEventsPerSecond, 'f', 2, 64), 313 | "rounds_per_second": strconv.FormatFloat(consensusRoundsPerSecond, 'f', 2, 64), 314 | "round_events": strconv.Itoa(n.core.GetLastCommitedRoundEventsCount()), 315 | "id": strconv.Itoa(n.id), 316 | } 317 | return s 318 | } 319 | 320 | func (n *Node) logStats() { 321 | stats := n.GetStats() 322 | n.logger.WithFields(logrus.Fields{ 323 | "last_consensus_round": stats["last_consensus_round"], 324 | "consensus_events": stats["consensus_events"], 325 | "consensus_transactions": stats["consensus_transactions"], 326 | "undetermined_events": stats["undetermined_events"], 327 | "transaction_pool": stats["transaction_pool"], 328 | "num_peers": stats["num_peers"], 329 | "sync_rate": stats["sync_rate"], 330 | "events/s": stats["events_per_second"], 331 | "rounds/s": stats["rounds_per_second"], 332 | "round_events": stats["round_events"], 333 | "id": stats["id"], 334 | }).Debug("Stats") 335 | } 336 | 337 | func (n *Node) SyncRate() float64 { 338 | var syncErrorRate float64 339 | if n.syncRequests != 0 { 340 | syncErrorRate = float64(n.syncErrors) / float64(n.syncRequests) 341 | } 342 | return 1 - syncErrorRate 343 | } 344 | 345 | func randomTimeout(minVal time.Duration) <-chan time.Time { 346 | if minVal == 0 { 347 | return nil 348 | } 349 | extra := (time.Duration(rand.Int63()) % minVal) 350 | return time.After(minVal + extra) 351 | } 352 | -------------------------------------------------------------------------------- /node/node_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Mosaic Networks Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package node 17 | 18 | import ( 19 | "crypto/ecdsa" 20 | "fmt" 21 | "math/rand" 22 | "reflect" 23 | "sort" 24 | "testing" 25 | "time" 26 | 27 | "github.com/Sirupsen/logrus" 28 | "github.com/babbleio/babble/common" 29 | "github.com/babbleio/babble/crypto" 30 | "github.com/babbleio/babble/net" 31 | aproxy "github.com/babbleio/babble/proxy/app" 32 | ) 33 | 34 | func initPeers() ([]*ecdsa.PrivateKey, []net.Peer) { 35 | keys := []*ecdsa.PrivateKey{} 36 | peers := []net.Peer{} 37 | 38 | n := 3 39 | for i := 0; i < n; i++ { 40 | key, _ := crypto.GenerateECDSAKey() 41 | keys = append(keys, key) 42 | peers = append(peers, net.Peer{ 43 | NetAddr: fmt.Sprintf("127.0.0.1:999%d", i), 44 | PubKeyHex: fmt.Sprintf("0x%X", crypto.FromECDSAPub(&keys[i].PublicKey)), 45 | }) 46 | } 47 | sort.Sort(net.ByPubKey(peers)) 48 | return keys, peers 49 | } 50 | 51 | func TestProcessSync(t *testing.T) { 52 | keys, peers := initPeers() 53 | testLogger := common.NewTestLogger(t) 54 | 55 | peer0Trans, err := net.NewTCPTransport(peers[0].NetAddr, nil, 2, time.Second, testLogger) 56 | if err != nil { 57 | t.Fatalf("err: %v", err) 58 | } 59 | defer peer0Trans.Close() 60 | 61 | node0 := NewNode(TestConfig(t), keys[0], peers, peer0Trans, aproxy.NewInmemAppProxy(testLogger)) 62 | node0.Init() 63 | 64 | node0.RunAsync(false) 65 | 66 | peer1Trans, err := net.NewTCPTransport(peers[1].NetAddr, nil, 2, time.Second, testLogger) 67 | if err != nil { 68 | t.Fatalf("err: %v", err) 69 | } 70 | defer peer1Trans.Close() 71 | 72 | node1 := NewNode(TestConfig(t), keys[1], peers, peer1Trans, aproxy.NewInmemAppProxy(testLogger)) 73 | node1.Init() 74 | 75 | node1.RunAsync(false) 76 | 77 | node0Known := node0.core.Known() 78 | 79 | head, unknown, err := node1.core.Diff(node0Known) 80 | if err != nil { 81 | t.Fatal(err) 82 | } 83 | 84 | unknownWire, err := node1.core.ToWire(unknown) 85 | if err != nil { 86 | t.Fatal(err) 87 | } 88 | 89 | args := net.SyncRequest{ 90 | From: node0.localAddr, 91 | Known: node0Known, 92 | } 93 | expectedResp := net.SyncResponse{ 94 | From: node1.localAddr, 95 | Head: head, 96 | Events: unknownWire, 97 | } 98 | 99 | var out net.SyncResponse 100 | if err := peer0Trans.Sync(peers[1].NetAddr, &args, &out); err != nil { 101 | t.Fatalf("err: %v", err) 102 | } 103 | 104 | // Verify the response 105 | if expectedResp.From != out.From { 106 | t.Fatalf("SyncResponse.From should be %s, not %s", expectedResp.From, out.From) 107 | } 108 | 109 | if expectedResp.Head != out.Head { 110 | t.Fatalf("SyncResponse.Head should be %s, not %s", expectedResp.Head, out.Head) 111 | } 112 | 113 | if l := len(out.Events); l != len(expectedResp.Events) { 114 | t.Fatalf("SyncResponse.Events should contain %d items, not %d", 115 | len(expectedResp.Events), l) 116 | } 117 | 118 | for i, e := range expectedResp.Events { 119 | ex := out.Events[i] 120 | if !reflect.DeepEqual(e.Body, ex.Body) { 121 | t.Fatalf("SyncResponse.Events[%d] should be %v, not %v", i, e.Body, 122 | ex.Body) 123 | } 124 | } 125 | 126 | node0.Shutdown() 127 | node1.Shutdown() 128 | } 129 | 130 | func TestAddTransaction(t *testing.T) { 131 | keys, peers := initPeers() 132 | testLogger := common.NewTestLogger(t) 133 | 134 | peer0Trans, err := net.NewTCPTransport(peers[0].NetAddr, nil, 2, time.Second, common.NewTestLogger(t)) 135 | if err != nil { 136 | t.Fatalf("err: %v", err) 137 | } 138 | defer peer0Trans.Close() 139 | peer0Proxy := aproxy.NewInmemAppProxy(testLogger) 140 | 141 | node0 := NewNode(TestConfig(t), keys[0], peers, peer0Trans, peer0Proxy) 142 | node0.Init() 143 | 144 | node0.RunAsync(false) 145 | 146 | peer1Trans, err := net.NewTCPTransport(peers[1].NetAddr, nil, 2, time.Second, common.NewTestLogger(t)) 147 | if err != nil { 148 | t.Fatalf("err: %v", err) 149 | } 150 | defer peer1Trans.Close() 151 | peer1Proxy := aproxy.NewInmemAppProxy(testLogger) 152 | 153 | node1 := NewNode(TestConfig(t), keys[1], peers, peer1Trans, peer1Proxy) 154 | node1.Init() 155 | 156 | node1.RunAsync(false) 157 | 158 | message := "Hello World!" 159 | peer0Proxy.SubmitTx([]byte(message)) 160 | 161 | node0Known := node0.core.Known() 162 | args := net.SyncRequest{ 163 | From: node0.localAddr, 164 | Known: node0Known, 165 | } 166 | 167 | var out net.SyncResponse 168 | if err := peer0Trans.Sync(peers[1].NetAddr, &args, &out); err != nil { 169 | t.Fatal(err) 170 | } 171 | 172 | if err := node0.processSyncResponse(out); err != nil { 173 | t.Fatal(err) 174 | } 175 | 176 | if l := len(node0.transactionPool); l > 0 { 177 | t.Fatalf("node0's transactionPool should have 0 elements, not %d\n", l) 178 | } 179 | 180 | node0Head, _ := node0.core.GetHead() 181 | if l := len(node0Head.Transactions()); l != 1 { 182 | t.Fatalf("node0's Head should have 1 element, not %d\n", l) 183 | } 184 | 185 | if m := string(node0Head.Transactions()[0]); m != message { 186 | t.Fatalf("Transaction message should be '%s' not, not %s\n", message, m) 187 | } 188 | 189 | node0.Shutdown() 190 | node1.Shutdown() 191 | } 192 | 193 | func initNodes(logger *logrus.Logger) ([]*ecdsa.PrivateKey, []*Node) { 194 | conf := NewConfig(5*time.Millisecond, time.Second, 1000, logger) 195 | 196 | keys, peers := initPeers() 197 | nodes := []*Node{} 198 | proxies := []*aproxy.InmemAppProxy{} 199 | for i := 0; i < len(peers); i++ { 200 | trans, err := net.NewTCPTransport(peers[i].NetAddr, 201 | nil, 2, time.Second, logger) 202 | if err != nil { 203 | logger.Printf(err.Error()) 204 | } 205 | prox := aproxy.NewInmemAppProxy(logger) 206 | node := NewNode(conf, keys[i], peers, trans, prox) 207 | node.Init() 208 | nodes = append(nodes, &node) 209 | proxies = append(proxies, prox) 210 | } 211 | return keys, nodes 212 | } 213 | 214 | func runNodes(nodes []*Node, gossip bool) { 215 | for _, n := range nodes { 216 | node := n 217 | go func() { 218 | node.Run(gossip) 219 | }() 220 | } 221 | } 222 | 223 | func shutdownNodes(nodes []*Node) { 224 | for _, n := range nodes { 225 | n.Shutdown() 226 | n.trans.Close() 227 | } 228 | } 229 | 230 | func getCommittedTransactions(n *Node) ([][]byte, error) { 231 | InmemAppProxy, ok := n.proxy.(*aproxy.InmemAppProxy) 232 | if !ok { 233 | return nil, fmt.Errorf("Error casting to InmemProp") 234 | } 235 | res := InmemAppProxy.GetCommittedTransactions() 236 | return res, nil 237 | } 238 | 239 | /* 240 | h0 | h2 241 | | \ | / | 242 | | h1 | 243 | | /| | 244 | g02 | | 245 | | \ | | 246 | | \ | 247 | | | \ | 248 | | | g21 249 | | | / | 250 | | g10 | 251 | | / | | 252 | g0 | g2 253 | | \ | / | 254 | | g1 | 255 | | /| | 256 | f02 | | 257 | | \ | | 258 | | \ | 259 | | | \ | 260 | | | f21 261 | | | / | 262 | | f10 | 263 | | / | | 264 | f0 | f2 265 | | \ | / | 266 | | f1 | 267 | | /| | 268 | e02 | | 269 | | \ | | 270 | | \ | 271 | | | \ | 272 | | | e21 273 | | | / | 274 | | e10 | 275 | | / | | 276 | e0 e1 e2 277 | 0 1 2 278 | */ 279 | func TestTransactionOrdering(t *testing.T) { 280 | logger := common.NewTestLogger(t) 281 | _, nodes := initNodes(logger) 282 | runNodes(nodes, false) 283 | 284 | playbook := []play{ 285 | play{to: 0, from: 1, payload: [][]byte{[]byte("e10")}}, 286 | play{to: 1, from: 2, payload: [][]byte{[]byte("e21")}}, 287 | play{to: 2, from: 0, payload: [][]byte{[]byte("e02")}}, 288 | play{to: 0, from: 1, payload: [][]byte{[]byte("f1")}}, 289 | play{to: 1, from: 0, payload: [][]byte{[]byte("f0")}}, 290 | play{to: 1, from: 2, payload: [][]byte{[]byte("f2")}}, 291 | 292 | play{to: 0, from: 1, payload: [][]byte{[]byte("f10")}}, 293 | play{to: 1, from: 2, payload: [][]byte{[]byte("f21")}}, 294 | play{to: 2, from: 0, payload: [][]byte{[]byte("f02")}}, 295 | play{to: 0, from: 1, payload: [][]byte{[]byte("g1")}}, 296 | play{to: 1, from: 0, payload: [][]byte{[]byte("g0")}}, 297 | play{to: 1, from: 2, payload: [][]byte{[]byte("g2")}}, 298 | 299 | play{to: 0, from: 1, payload: [][]byte{[]byte("g10")}}, 300 | play{to: 1, from: 2, payload: [][]byte{[]byte("g21")}}, 301 | play{to: 2, from: 0, payload: [][]byte{[]byte("g02")}}, 302 | play{to: 0, from: 1, payload: [][]byte{[]byte("h1")}}, 303 | play{to: 1, from: 0, payload: [][]byte{[]byte("h0")}}, 304 | play{to: 1, from: 2, payload: [][]byte{[]byte("h2")}}, 305 | } 306 | 307 | for k, play := range playbook { 308 | if err := synchronizeNodes(nodes[play.from], nodes[play.to], play.payload); err != nil { 309 | t.Fatalf("play %d: %s", k, err) 310 | } 311 | } 312 | shutdownNodes(nodes) 313 | 314 | expectedConsTransactions := [][]byte{ 315 | []byte("e10"), 316 | []byte("e21"), 317 | []byte("e02"), 318 | } 319 | for i, n := range nodes { 320 | consTransactions, err := n.core.GetConsensusTransactions() 321 | if err != nil { 322 | t.Fatal(err) 323 | } 324 | if len(consTransactions) != len(expectedConsTransactions) { 325 | t.Fatalf("node %d ConsensusTransactions should contain %d items, not %d", 326 | i, len(expectedConsTransactions), len(consTransactions)) 327 | } 328 | for j, et := range expectedConsTransactions { 329 | if at := string(consTransactions[j]); at != string(et) { 330 | t.Fatalf("node[%d].ConsensusTransactions[%d] should be %s, not %s", i, j, string(et), at) 331 | } 332 | } 333 | } 334 | } 335 | 336 | func TestStats(t *testing.T) { 337 | logger := common.NewTestLogger(t) 338 | _, nodes := initNodes(logger) 339 | runNodes(nodes, false) 340 | 341 | playbook := []play{ 342 | play{to: 0, from: 1, payload: [][]byte{[]byte("e10")}}, 343 | play{to: 1, from: 2, payload: [][]byte{[]byte("e21")}}, 344 | play{to: 2, from: 0, payload: [][]byte{[]byte("e02")}}, 345 | play{to: 0, from: 1, payload: [][]byte{[]byte("f1")}}, 346 | play{to: 1, from: 0, payload: [][]byte{[]byte("f0")}}, 347 | play{to: 1, from: 2, payload: [][]byte{[]byte("f2")}}, 348 | 349 | play{to: 0, from: 1, payload: [][]byte{[]byte("f10")}}, 350 | play{to: 1, from: 2, payload: [][]byte{[]byte("f21")}}, 351 | play{to: 2, from: 0, payload: [][]byte{[]byte("f02")}}, 352 | play{to: 0, from: 1, payload: [][]byte{[]byte("g1")}}, 353 | play{to: 1, from: 0, payload: [][]byte{[]byte("g0")}}, 354 | play{to: 1, from: 2, payload: [][]byte{[]byte("g2")}}, 355 | 356 | play{to: 0, from: 1, payload: [][]byte{[]byte("g10")}}, 357 | play{to: 1, from: 2, payload: [][]byte{[]byte("g21")}}, 358 | play{to: 2, from: 0, payload: [][]byte{[]byte("g02")}}, 359 | play{to: 0, from: 1, payload: [][]byte{[]byte("h1")}}, 360 | play{to: 1, from: 0, payload: [][]byte{[]byte("h0")}}, 361 | play{to: 1, from: 2, payload: [][]byte{[]byte("h2")}}, 362 | } 363 | 364 | for _, play := range playbook { 365 | if err := synchronizeNodes(nodes[play.from], nodes[play.to], play.payload); err != nil { 366 | t.Fatal(err) 367 | } 368 | } 369 | shutdownNodes(nodes) 370 | 371 | stats := nodes[0].GetStats() 372 | 373 | expectedStats := map[string]string{ 374 | "last_consensus_round": "1", 375 | "consensus_events": "6", 376 | "consensus_transactions": "3", 377 | "undetermined_events": "14", 378 | "transaction_pool": "0", 379 | "num_peers": "2", 380 | "sync_rate": "1.00", 381 | } 382 | 383 | t.Logf("%#v", stats) 384 | 385 | for k, v := range expectedStats { 386 | if stats[k] != v { 387 | t.Fatalf("Stats[%s] should be %#v, not %#v", k, v, stats[k]) 388 | } 389 | } 390 | 391 | } 392 | 393 | func synchronizeNodes(from *Node, to *Node, payload [][]byte) error { 394 | fromProxy, ok := from.proxy.(*aproxy.InmemAppProxy) 395 | if !ok { 396 | return fmt.Errorf("Error casting to InmemAppProxy") 397 | } 398 | for _, t := range payload { 399 | fromProxy.SubmitTx(t) 400 | } 401 | 402 | return from.gossip(to.localAddr) 403 | } 404 | 405 | func TestGossip(t *testing.T) { 406 | logger := common.NewTestLogger(t) 407 | _, nodes := initNodes(logger) 408 | 409 | gossip(nodes, 100) 410 | 411 | consEvents := [][]string{} 412 | consTransactions := [][][]byte{} 413 | for _, n := range nodes { 414 | consEvents = append(consEvents, n.core.GetConsensusEvents()) 415 | nodeTxs, err := getCommittedTransactions(n) 416 | if err != nil { 417 | t.Fatal(err) 418 | } 419 | consTransactions = append(consTransactions, nodeTxs) 420 | } 421 | 422 | minE := len(consEvents[0]) 423 | minT := len(consTransactions[0]) 424 | for k := 1; k < len(nodes); k++ { 425 | if len(consEvents[k]) < minE { 426 | minE = len(consEvents[k]) 427 | } 428 | if len(consTransactions[k]) < minT { 429 | minT = len(consTransactions[k]) 430 | } 431 | } 432 | 433 | t.Logf("min consensus events: %d", minE) 434 | for i, e := range consEvents[0][0:minE] { 435 | for j := range nodes[1:len(nodes)] { 436 | if consEvents[j][i] != e { 437 | t.Fatalf("nodes[%d].Consensus[%d] and nodes[0].Consensus[%d] are not equal", j, i, i) 438 | } 439 | } 440 | } 441 | 442 | t.Logf("min consensus transactions: %d", minT) 443 | for i, tx := range consTransactions[0][:minT] { 444 | for k := range nodes[1:len(nodes)] { 445 | if ot := string(consTransactions[k][i]); ot != string(tx) { 446 | t.Fatalf("nodes[%d].ConsensusTransactions[%d] should be '%s' not '%s'", k, i, string(tx), ot) 447 | } 448 | } 449 | } 450 | } 451 | 452 | func gossip(nodes []*Node, target int) { 453 | runNodes(nodes, true) 454 | quit := make(chan int) 455 | makeRandomTransactions(nodes, quit) 456 | 457 | //wait until all nodes have at least 'target' consensus events 458 | for { 459 | time.Sleep(10 * time.Millisecond) 460 | done := true 461 | for _, n := range nodes { 462 | ce := n.core.GetConsensusEventsCount() 463 | if ce < target { 464 | done = false 465 | break 466 | } 467 | } 468 | if done { 469 | break 470 | } 471 | } 472 | 473 | close(quit) 474 | shutdownNodes(nodes) 475 | } 476 | 477 | func submitTransaction(n *Node, tx []byte) error { 478 | prox, ok := n.proxy.(*aproxy.InmemAppProxy) 479 | if !ok { 480 | return fmt.Errorf("Error casting to InmemProp") 481 | } 482 | prox.SubmitTx([]byte(tx)) 483 | return nil 484 | } 485 | 486 | func makeRandomTransactions(nodes []*Node, quit chan int) { 487 | go func() { 488 | seq := make(map[int]int) 489 | for { 490 | select { 491 | case <-quit: 492 | return 493 | default: 494 | n := rand.Intn(len(nodes)) 495 | node := nodes[n] 496 | submitTransaction(node, []byte(fmt.Sprintf("node%d transaction %d", n, seq[n]))) 497 | seq[n] = seq[n] + 1 498 | time.Sleep(3 * time.Millisecond) 499 | } 500 | } 501 | }() 502 | } 503 | 504 | func BenchmarkGossip(b *testing.B) { 505 | logger := common.NewBenchmarkLogger(b) 506 | for n := 0; n < b.N; n++ { 507 | _, nodes := initNodes(logger) 508 | gossip(nodes, 5) 509 | } 510 | } 511 | -------------------------------------------------------------------------------- /node/peer_selector.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Mosaic Networks Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package node 17 | 18 | import ( 19 | "math/rand" 20 | 21 | "github.com/babbleio/babble/net" 22 | ) 23 | 24 | type PeerSelector interface { 25 | Peers() []net.Peer 26 | UpdateLast(peer string) 27 | Next() net.Peer 28 | } 29 | 30 | //+++++++++++++++++++++++++++++++++++++++ 31 | //RANDOM 32 | 33 | type RandomPeerSelector struct { 34 | peers []net.Peer 35 | last string 36 | } 37 | 38 | func NewRandomPeerSelector(participants []net.Peer, localAddr string) *RandomPeerSelector { 39 | _, peers := net.ExcludePeer(participants, localAddr) 40 | return &RandomPeerSelector{ 41 | peers: peers, 42 | } 43 | } 44 | 45 | func (ps *RandomPeerSelector) Peers() []net.Peer { 46 | return ps.peers 47 | } 48 | 49 | func (ps *RandomPeerSelector) UpdateLast(peer string) { 50 | ps.last = peer 51 | } 52 | 53 | func (ps *RandomPeerSelector) Next() net.Peer { 54 | selectablePeers := ps.peers 55 | if len(selectablePeers) > 1 { 56 | _, selectablePeers = net.ExcludePeer(selectablePeers, ps.last) 57 | } 58 | i := rand.Intn(len(selectablePeers)) 59 | peer := selectablePeers[i] 60 | return peer 61 | } 62 | -------------------------------------------------------------------------------- /proxy/app/inmem_app_proxy.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Mosaic Networks Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package app 17 | 18 | import "github.com/Sirupsen/logrus" 19 | 20 | //InmemProxy is used for testing 21 | type InmemAppProxy struct { 22 | submitCh chan []byte 23 | commitedTxs [][]byte 24 | logger *logrus.Logger 25 | } 26 | 27 | func NewInmemAppProxy(logger *logrus.Logger) *InmemAppProxy { 28 | if logger == nil { 29 | logger = logrus.New() 30 | logger.Level = logrus.DebugLevel 31 | } 32 | return &InmemAppProxy{ 33 | submitCh: make(chan []byte), 34 | commitedTxs: [][]byte{}, 35 | logger: logger, 36 | } 37 | } 38 | 39 | func (p *InmemAppProxy) SubmitCh() chan []byte { 40 | return p.submitCh 41 | } 42 | 43 | func (p *InmemAppProxy) CommitTx(tx []byte) error { 44 | p.logger.WithField("tx", tx).Debug("InmemProxy CommitTx") 45 | p.commitedTxs = append(p.commitedTxs, tx) 46 | return nil 47 | } 48 | 49 | //------------------------------------------------------- 50 | //Implement AppProxy Interface 51 | 52 | func (p *InmemAppProxy) SubmitTx(tx []byte) { 53 | p.submitCh <- tx 54 | } 55 | 56 | func (p *InmemAppProxy) GetCommittedTransactions() [][]byte { 57 | return p.commitedTxs 58 | } 59 | -------------------------------------------------------------------------------- /proxy/app/socket_app_proxy.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Mosaic Networks Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package app 17 | 18 | import ( 19 | "time" 20 | 21 | "fmt" 22 | 23 | "github.com/Sirupsen/logrus" 24 | ) 25 | 26 | type SocketAppProxy struct { 27 | clientAddress string 28 | bindAddress string 29 | 30 | client *SocketAppProxyClient 31 | server *SocketAppProxyServer 32 | 33 | logger *logrus.Logger 34 | } 35 | 36 | func NewSocketAppProxy(clientAddr string, bindAddr string, timeout time.Duration, logger *logrus.Logger) *SocketAppProxy { 37 | if logger == nil { 38 | logger = logrus.New() 39 | logger.Level = logrus.DebugLevel 40 | } 41 | 42 | client := NewSocketAppProxyClient(clientAddr, timeout, logger) 43 | server := NewSocketAppProxyServer(bindAddr, logger) 44 | 45 | proxy := &SocketAppProxy{ 46 | clientAddress: clientAddr, 47 | bindAddress: bindAddr, 48 | client: client, 49 | server: server, 50 | logger: logger, 51 | } 52 | go proxy.server.listen() 53 | 54 | return proxy 55 | } 56 | 57 | //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 58 | //Implement AppProxy Interface 59 | 60 | func (p *SocketAppProxy) SubmitCh() chan []byte { 61 | return p.server.submitCh 62 | } 63 | 64 | func (p *SocketAppProxy) CommitTx(tx []byte) error { 65 | ack, err := p.client.CommitTx(tx) 66 | if err != nil { 67 | return err 68 | } 69 | if !*ack { 70 | return fmt.Errorf("App returned false to CommitTx") 71 | } 72 | return nil 73 | } 74 | -------------------------------------------------------------------------------- /proxy/app/socket_app_proxy_client.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Mosaic Networks Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package app 17 | 18 | import ( 19 | "net" 20 | "net/rpc" 21 | "net/rpc/jsonrpc" 22 | "time" 23 | 24 | "github.com/Sirupsen/logrus" 25 | ) 26 | 27 | type SocketAppProxyClient struct { 28 | clientAddr string 29 | timeout time.Duration 30 | logger *logrus.Logger 31 | } 32 | 33 | func NewSocketAppProxyClient(clientAddr string, timeout time.Duration, logger *logrus.Logger) *SocketAppProxyClient { 34 | return &SocketAppProxyClient{ 35 | clientAddr: clientAddr, 36 | timeout: timeout, 37 | logger: logger, 38 | } 39 | } 40 | 41 | func (p *SocketAppProxyClient) getConnection() (*rpc.Client, error) { 42 | conn, err := net.DialTimeout("tcp", p.clientAddr, p.timeout) 43 | if err != nil { 44 | return nil, err 45 | } 46 | return jsonrpc.NewClient(conn), nil 47 | } 48 | 49 | func (p *SocketAppProxyClient) CommitTx(tx []byte) (*bool, error) { 50 | rpcConn, err := p.getConnection() 51 | if err != nil { 52 | return nil, err 53 | } 54 | var ack bool 55 | err = rpcConn.Call("State.CommitTx", tx, &ack) 56 | if err != nil { 57 | return nil, err 58 | } 59 | return &ack, nil 60 | } 61 | -------------------------------------------------------------------------------- /proxy/app/socket_app_proxy_server.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Mosaic Networks Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package app 17 | 18 | import ( 19 | "net" 20 | "net/rpc" 21 | "net/rpc/jsonrpc" 22 | 23 | "github.com/Sirupsen/logrus" 24 | ) 25 | 26 | type SocketAppProxyServer struct { 27 | netListener *net.Listener 28 | rpcServer *rpc.Server 29 | submitCh chan []byte 30 | logger *logrus.Logger 31 | } 32 | 33 | func NewSocketAppProxyServer(bindAddress string, logger *logrus.Logger) *SocketAppProxyServer { 34 | server := &SocketAppProxyServer{ 35 | submitCh: make(chan []byte), 36 | logger: logger, 37 | } 38 | server.register(bindAddress) 39 | return server 40 | } 41 | 42 | func (p *SocketAppProxyServer) register(bindAddress string) { 43 | rpcServer := rpc.NewServer() 44 | rpcServer.RegisterName("Babble", p) 45 | p.rpcServer = rpcServer 46 | 47 | l, err := net.Listen("tcp", bindAddress) 48 | if err != nil { 49 | p.logger.WithField("error", err).Error("Failed to listen") 50 | } 51 | p.netListener = &l 52 | } 53 | 54 | func (p *SocketAppProxyServer) listen() { 55 | for { 56 | conn, err := (*p.netListener).Accept() 57 | if err != nil { 58 | p.logger.WithField("error", err).Error("Failed to accept") 59 | } 60 | 61 | go (*p.rpcServer).ServeCodec(jsonrpc.NewServerCodec(conn)) 62 | } 63 | } 64 | 65 | func (p *SocketAppProxyServer) SubmitTx(tx []byte, ack *bool) error { 66 | p.logger.Debug("SubmitTx") 67 | p.submitCh <- tx 68 | *ack = true 69 | return nil 70 | } 71 | -------------------------------------------------------------------------------- /proxy/babble/socket_babble_proxy.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Mosaic Networks Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package babble 17 | 18 | import ( 19 | "fmt" 20 | "time" 21 | ) 22 | 23 | type SocketBabbleProxy struct { 24 | nodeAddress string 25 | bindAddress string 26 | 27 | client *SocketBabbleProxyClient 28 | server *SocketBabbleProxyServer 29 | } 30 | 31 | func NewSocketBabbleProxy(nodeAddr string, bindAddr string, timeout time.Duration) (*SocketBabbleProxy, error) { 32 | client := NewSocketBabbleProxyClient(nodeAddr, timeout) 33 | server, err := NewSocketBabbleProxyServer(bindAddr) 34 | if err != nil { 35 | return nil, err 36 | } 37 | 38 | proxy := &SocketBabbleProxy{ 39 | nodeAddress: nodeAddr, 40 | bindAddress: bindAddr, 41 | client: client, 42 | server: server, 43 | } 44 | go proxy.server.listen() 45 | 46 | return proxy, nil 47 | } 48 | 49 | //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 50 | //Implement BabbleProxy interface 51 | 52 | func (p *SocketBabbleProxy) CommitCh() chan []byte { 53 | return p.server.commitCh 54 | } 55 | 56 | func (p *SocketBabbleProxy) SubmitTx(tx []byte) error { 57 | ack, err := p.client.SubmitTx(tx) 58 | if err != nil { 59 | return err 60 | } 61 | if !*ack { 62 | return fmt.Errorf("Failed to deliver transaction to Babble") 63 | } 64 | return nil 65 | } 66 | -------------------------------------------------------------------------------- /proxy/babble/socket_babble_proxy_client.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Mosaic Networks Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package babble 17 | 18 | import ( 19 | "net" 20 | "net/rpc" 21 | "net/rpc/jsonrpc" 22 | "time" 23 | ) 24 | 25 | type SocketBabbleProxyClient struct { 26 | nodeAddr string 27 | timeout time.Duration 28 | } 29 | 30 | func NewSocketBabbleProxyClient(nodeAddr string, timeout time.Duration) *SocketBabbleProxyClient { 31 | return &SocketBabbleProxyClient{ 32 | nodeAddr: nodeAddr, 33 | timeout: timeout, 34 | } 35 | } 36 | 37 | func (p *SocketBabbleProxyClient) getConnection() (*rpc.Client, error) { 38 | conn, err := net.DialTimeout("tcp", p.nodeAddr, p.timeout) 39 | if err != nil { 40 | return nil, err 41 | } 42 | return jsonrpc.NewClient(conn), nil 43 | } 44 | 45 | func (p *SocketBabbleProxyClient) SubmitTx(tx []byte) (*bool, error) { 46 | rpcConn, err := p.getConnection() 47 | if err != nil { 48 | return nil, err 49 | } 50 | var ack bool 51 | err = rpcConn.Call("Babble.SubmitTx", tx, &ack) 52 | if err != nil { 53 | return nil, err 54 | } 55 | return &ack, nil 56 | } 57 | -------------------------------------------------------------------------------- /proxy/babble/socket_babble_proxy_server.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Mosaic Networks Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package babble 17 | 18 | import ( 19 | "net" 20 | "net/rpc" 21 | "net/rpc/jsonrpc" 22 | ) 23 | 24 | type SocketBabbleProxyServer struct { 25 | netListener *net.Listener 26 | rpcServer *rpc.Server 27 | commitCh chan []byte 28 | } 29 | 30 | func NewSocketBabbleProxyServer(bindAddress string) (*SocketBabbleProxyServer, error) { 31 | server := &SocketBabbleProxyServer{ 32 | commitCh: make(chan []byte), 33 | } 34 | 35 | if err := server.register(bindAddress); err != nil { 36 | return nil, err 37 | } 38 | 39 | return server, nil 40 | } 41 | 42 | func (p *SocketBabbleProxyServer) register(bindAddress string) error { 43 | rpcServer := rpc.NewServer() 44 | rpcServer.RegisterName("State", p) 45 | p.rpcServer = rpcServer 46 | 47 | l, err := net.Listen("tcp", bindAddress) 48 | if err != nil { 49 | return err 50 | } 51 | 52 | p.netListener = &l 53 | 54 | return nil 55 | } 56 | 57 | func (p *SocketBabbleProxyServer) listen() error { 58 | for { 59 | conn, err := (*p.netListener).Accept() 60 | if err != nil { 61 | return err 62 | } 63 | 64 | go (*p.rpcServer).ServeCodec(jsonrpc.NewServerCodec(conn)) 65 | } 66 | return nil 67 | } 68 | 69 | func (p *SocketBabbleProxyServer) CommitTx(tx []byte, ack *bool) error { 70 | p.commitCh <- tx 71 | *ack = true 72 | return nil 73 | } 74 | -------------------------------------------------------------------------------- /proxy/dummy.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Mosaic Networks Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package proxy 17 | 18 | import ( 19 | "fmt" 20 | "os" 21 | 22 | "time" 23 | 24 | "github.com/Sirupsen/logrus" 25 | bproxy "github.com/babbleio/babble/proxy/babble" 26 | ) 27 | 28 | type State struct { 29 | logger *logrus.Logger 30 | } 31 | 32 | func (a *State) CommitTx(tx []byte) error { 33 | a.logger.WithField("Tx", string(tx)).Debug("CommitTx") 34 | a.writeMessage(tx) 35 | return nil 36 | } 37 | 38 | func (a *State) writeMessage(tx []byte) { 39 | file, err := a.getFile() 40 | if err != nil { 41 | a.logger.Error(err) 42 | } 43 | defer file.Close() 44 | 45 | // write some text to file 46 | _, err = file.WriteString(fmt.Sprintf("%s\n", string(tx))) 47 | if err != nil { 48 | a.logger.Error(err) 49 | } 50 | err = file.Sync() 51 | if err != nil { 52 | a.logger.Error(err) 53 | } 54 | } 55 | 56 | func (a *State) getFile() (*os.File, error) { 57 | path := "messages.txt" 58 | return os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0666) 59 | } 60 | 61 | //------------------------------------------------------ 62 | 63 | type DummySocketClient struct { 64 | state *State 65 | babbleProxy *bproxy.SocketBabbleProxy 66 | logger *logrus.Logger 67 | } 68 | 69 | func NewDummySocketClient(clientAddr string, nodeAddr string, logger *logrus.Logger) (*DummySocketClient, error) { 70 | 71 | babbleProxy, err := bproxy.NewSocketBabbleProxy(nodeAddr, clientAddr, 1*time.Second) 72 | if err != nil { 73 | return nil, err 74 | } 75 | 76 | client := &DummySocketClient{ 77 | state: &State{logger: logger}, 78 | babbleProxy: babbleProxy, 79 | logger: logger, 80 | } 81 | 82 | go client.Run() 83 | 84 | return client, nil 85 | } 86 | 87 | func (c *DummySocketClient) Run() { 88 | for { 89 | select { 90 | case tx := <-c.babbleProxy.CommitCh(): 91 | c.logger.Debug("CommitTx") 92 | c.state.CommitTx(tx) 93 | default: 94 | } 95 | } 96 | } 97 | 98 | func (c *DummySocketClient) SubmitTx(tx []byte) error { 99 | return c.babbleProxy.SubmitTx(tx) 100 | } 101 | -------------------------------------------------------------------------------- /proxy/proxy.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Mosaic Networks Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package proxy 17 | 18 | type AppProxy interface { 19 | SubmitCh() chan []byte 20 | CommitTx(tx []byte) error 21 | } 22 | 23 | type BabbleProxy interface { 24 | CommitCh() chan []byte 25 | SubmitTx(tx []byte) error 26 | } 27 | -------------------------------------------------------------------------------- /proxy/socket_proxy_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Mosaic Networks Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package proxy 17 | 18 | import ( 19 | "reflect" 20 | "testing" 21 | "time" 22 | 23 | "github.com/babbleio/babble/common" 24 | aproxy "github.com/babbleio/babble/proxy/app" 25 | ) 26 | 27 | func TestSokcetProxyServer(t *testing.T) { 28 | clientAddr := "127.0.0.1:9990" 29 | proxyAddr := "127.0.0.1:9991" 30 | proxy := aproxy.NewSocketAppProxy(clientAddr, proxyAddr, 1*time.Second, common.NewTestLogger(t)) 31 | submitCh := proxy.SubmitCh() 32 | 33 | tx := []byte("the test transaction") 34 | 35 | // Listen for a request 36 | go func() { 37 | select { 38 | case st := <-submitCh: 39 | // Verify the command 40 | if !reflect.DeepEqual(st, tx) { 41 | t.Fatalf("tx mismatch: %#v %#v", tx, st) 42 | } 43 | case <-time.After(200 * time.Millisecond): 44 | t.Fatalf("timeout") 45 | } 46 | }() 47 | 48 | // now client part connecting to RPC service 49 | // and calling methods 50 | dummyClient, err := NewDummySocketClient(clientAddr, proxyAddr, common.NewTestLogger(t)) 51 | if err != nil { 52 | t.Fatal(err) 53 | } 54 | err = dummyClient.SubmitTx(tx) 55 | if err != nil { 56 | t.Fatal(err) 57 | } 58 | } 59 | 60 | func TestSocketProxyClient(t *testing.T) { 61 | clientAddr := "127.0.0.1:9992" 62 | proxyAddr := "127.0.0.1:9993" 63 | proxy := aproxy.NewSocketAppProxy(clientAddr, proxyAddr, 1*time.Second, common.NewTestLogger(t)) 64 | 65 | dummyClient, err := NewDummySocketClient(clientAddr, proxyAddr, common.NewTestLogger(t)) 66 | if err != nil { 67 | t.Fatal(err) 68 | } 69 | clientCh := dummyClient.babbleProxy.CommitCh() 70 | 71 | tx := []byte("the test transaction") 72 | 73 | // Listen for a request 74 | go func() { 75 | select { 76 | case st := <-clientCh: 77 | if !reflect.DeepEqual(st, tx) { 78 | t.Fatalf("tx mismatch: %#v %#v", tx, st) 79 | } 80 | case <-time.After(200 * time.Millisecond): 81 | t.Fatalf("timeout") 82 | } 83 | }() 84 | 85 | err = proxy.CommitTx(tx) 86 | if err != nil { 87 | t.Fatal(err) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /service/service.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2017 Mosaic Networks Ltd 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package service 17 | 18 | import ( 19 | "encoding/json" 20 | "net/http" 21 | 22 | "github.com/babbleio/babble/node" 23 | "github.com/Sirupsen/logrus" 24 | ) 25 | 26 | type Service struct { 27 | bindAddress string 28 | node *node.Node 29 | logger *logrus.Logger 30 | } 31 | 32 | func NewService(bindAddress string, node *node.Node, logger *logrus.Logger) *Service { 33 | service := Service{ 34 | bindAddress: bindAddress, 35 | node: node, 36 | logger: logger, 37 | } 38 | 39 | http.HandleFunc("/Stats", service.GetStats) 40 | 41 | return &service 42 | } 43 | 44 | func (s *Service) Serve() { 45 | s.logger.WithField("bind_address", s.bindAddress).Debug("Service serving") 46 | err := http.ListenAndServe(s.bindAddress, nil) 47 | if err != nil { 48 | s.logger.WithField("error", err).Error("Service failed") 49 | } 50 | } 51 | 52 | func (s *Service) GetStats(w http.ResponseWriter, r *http.Request) { 53 | s.logger.Debug("Stats request") 54 | stats := s.node.GetStats() 55 | 56 | w.Header().Set("Content-Type", "application/json") 57 | json.NewEncoder(w).Encode(stats) 58 | } 59 | -------------------------------------------------------------------------------- /terraform/example.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | access_key = "${var.access_key}" 3 | secret_key = "${var.secret_key}" 4 | region = "eu-west-2" 5 | } 6 | 7 | resource "aws_subnet" "babblenet" { 8 | vpc_id = "${var.vpc}" 9 | cidr_block = "10.0.1.0/24" 10 | map_public_ip_on_launch="true" 11 | 12 | tags { 13 | Name = "Testnet" 14 | } 15 | } 16 | 17 | resource "aws_security_group" "babblesec" { 18 | name = "babblesec" 19 | description = "Babble internal traffic + maintenance." 20 | 21 | vpc_id = "${var.vpc}" 22 | 23 | // These are for internal traffic 24 | ingress { 25 | from_port = 0 26 | to_port = 65535 27 | protocol = "tcp" 28 | self = true 29 | } 30 | 31 | ingress { 32 | from_port = 0 33 | to_port = 65535 34 | protocol = "udp" 35 | self = true 36 | } 37 | 38 | // These are for maintenance 39 | ingress { 40 | from_port = 22 41 | to_port = 22 42 | protocol = "tcp" 43 | cidr_blocks = ["0.0.0.0/0"] 44 | } 45 | 46 | ingress { 47 | from_port = 8080 48 | to_port = 8080 49 | protocol = "tcp" 50 | cidr_blocks = ["0.0.0.0/0"] 51 | } 52 | 53 | ingress { 54 | from_port = 1338 55 | to_port = 1338 56 | protocol = "tcp" 57 | cidr_blocks = ["0.0.0.0/0"] 58 | } 59 | 60 | ingress { 61 | from_port = 1339 62 | to_port = 1339 63 | protocol = "tcp" 64 | cidr_blocks = ["0.0.0.0/0"] 65 | } 66 | 67 | ingress { 68 | from_port = -1 69 | to_port = -1 70 | protocol = "icmp" 71 | cidr_blocks = ["0.0.0.0/0"] 72 | } 73 | 74 | // This is for outbound internet access 75 | egress { 76 | from_port = 0 77 | to_port = 0 78 | protocol = "-1" 79 | cidr_blocks = ["0.0.0.0/0"] 80 | } 81 | } 82 | 83 | resource "aws_instance" "server" { 84 | count = "${var.servers}" 85 | 86 | //custom ami with ubuntu + babble + dummy 87 | ami = "ami-cb4254af" 88 | instance_type = "t2.micro" 89 | 90 | subnet_id = "${aws_subnet.babblenet.id}" 91 | vpc_security_group_ids = ["${aws_security_group.babblesec.id}"] 92 | private_ip = "10.0.1.${10+count.index}" 93 | 94 | key_name = "${var.key_name}" 95 | connection { 96 | user = "ubuntu" 97 | private_key = "${file("${var.key_path}")}" 98 | } 99 | 100 | provisioner "file" { 101 | source = "conf/node${count.index +1}" 102 | destination = "babble_conf" 103 | } 104 | 105 | provisioner "local-exec" { 106 | command = "echo ${self.private_ip} ${self.public_ip} >> ips.dat" 107 | } 108 | 109 | #Instance tags 110 | tags { 111 | Name = "node${count.index}" 112 | } 113 | } 114 | 115 | output "public_addresses" { 116 | value = ["${aws_instance.server.*.public_ip}"] 117 | } -------------------------------------------------------------------------------- /terraform/makefile: -------------------------------------------------------------------------------- 1 | 2 | nodes = 4 3 | txs = 1000 4 | 5 | up: conf create start 6 | 7 | conf : 8 | rm -rf conf 9 | ./scripts/build-conf.sh $(nodes) 10 | 11 | create : 12 | rm -f ips.dat 13 | terraform apply -var-file=secret.tfvars -var "servers=$(nodes)" 14 | 15 | start: 16 | ifdef index 17 | awk 'FNR==$(index) {system("./scripts/remote-run.sh "$$0" "$$1"")}' ips.dat 18 | else 19 | awk '{system("./scripts/remote-run.sh "$$0" "$$1"")}' ips.dat 20 | endif 21 | 22 | watch : 23 | ./scripts/watch.sh 24 | 25 | bombard: 26 | ./scripts/bombard.sh $(txs) 27 | 28 | stop: 29 | ifdef index 30 | awk 'FNR==$(index) {system("./scripts/remote-kill.sh "$$0" "$$1"")}' ips.dat 31 | else 32 | awk '{system("./scripts/remote-kill.sh "$$0" "$$1"")}' ips.dat 33 | endif 34 | 35 | destroy : 36 | terraform destroy -var-file=secret.tfvars 37 | 38 | .PHONY: up conf create start watch bombard stop destroy 39 | -------------------------------------------------------------------------------- /terraform/scripts/bombard.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -u 4 | 5 | COUNT=${1:-1000} 6 | 7 | privs=($(cat ips.dat | awk '{ print $1 }')) 8 | pubs=($(cat ips.dat | awk '{ print $2 }')) 9 | 10 | 11 | 12 | for i in `seq 1 $COUNT`; do 13 | for n in "${!privs[@]}"; do 14 | printf "${privs[$n]} Tx$i" | base64 | \ 15 | xargs printf "{\"method\":\"Babble.SubmitTx\",\"params\":[\"%s\"],\"id\":$i}" | \ 16 | nc -v ${pubs[$n]} 1338 17 | done; 18 | done; 19 | 20 | -------------------------------------------------------------------------------- /terraform/scripts/build-conf.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This script creates the configuration for a Babble testnet with a variable 4 | # number of nodes. It will generate crytographic key pairs and assemble a peers.json 5 | # file in the format used by Babble. The files are copied into individual folders 6 | # for each node so that these folders can be used as the datadir that Babble reads 7 | # configuration from. 8 | 9 | set -e 10 | 11 | N=${1:-4} 12 | DEST=${2:-"conf"} 13 | IPBASE=${3:-10.0.1.} 14 | PORT=${4:-1337} 15 | 16 | for i in $(seq 1 $N) 17 | do 18 | dest=$DEST/node$i 19 | mkdir -p $dest 20 | babble keygen | sed -n -e "2 w $dest/pub" -e "4,+4 w $dest/priv_key.pem" 21 | echo "$IPBASE$((9 +i)):$PORT" > $dest/addr 22 | done 23 | 24 | PFILE=$DEST/peers.json 25 | echo "[" > $PFILE 26 | for i in $(seq 1 $N) 27 | do 28 | com="," 29 | if [[ $i == $N ]]; then 30 | com="" 31 | fi 32 | 33 | printf "\t{\n" >> $PFILE 34 | printf "\t\t\"NetAddr\":\"$(cat $DEST/node$i/addr)\",\n" >> $PFILE 35 | printf "\t\t\"PubKeyHex\":\"$(cat $DEST/node$i/pub)\"\n" >> $PFILE 36 | printf "\t}%s\n" $com >> $PFILE 37 | 38 | done 39 | echo "]" >> $PFILE 40 | 41 | for i in $(seq 1 $N) 42 | do 43 | dest=$DEST/node$i 44 | cp $DEST/peers.json $dest/ 45 | rm $dest/addr $dest/pub 46 | done 47 | 48 | -------------------------------------------------------------------------------- /terraform/scripts/remote-kill.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eux 4 | 5 | private_ip=${1} 6 | public_ip=${2} 7 | 8 | ssh -q -i babble.pem -o "UserKnownHostsFile /dev/null" -o "StrictHostKeyChecking=no" \ 9 | ubuntu@$public_ip "killall -9 babble dummy" -------------------------------------------------------------------------------- /terraform/scripts/remote-run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eux 4 | echo $0 5 | 6 | private_ip=${1} 7 | public_ip=${2} 8 | 9 | ssh -q -i babble.pem -o "UserKnownHostsFile /dev/null" -o "StrictHostKeyChecking=no" \ 10 | ubuntu@$public_ip <<-EOF 11 | nohup /home/ubuntu/bin/babble run \ 12 | --datadir=/home/ubuntu/babble_conf \ 13 | --cache_size=10000 \ 14 | --tcp_timeout=500 \ 15 | --heartbeat=50 \ 16 | --node_addr=$private_ip:1337 \ 17 | --proxy_addr=0.0.0.0:1338 \ 18 | --client_addr=$private_ip:1339 \ 19 | --service_addr=0.0.0.0:8080 > logs 2>&1 & 20 | 21 | nohup /home/ubuntu/bin/dummy \ 22 | --name=$private_ip \ 23 | --client_addr=$private_ip:1339\ 24 | --proxy_addr=0.0.0.0:1338 \ 25 | --log_level="info" < /dev/null > 2>&1 & 26 | EOF -------------------------------------------------------------------------------- /terraform/scripts/watch.sh: -------------------------------------------------------------------------------- 1 | 2 | #!/bin/bash 3 | 4 | watch -n 1 ' 5 | cat ips.dat | \ 6 | awk '"'"'{print $2}'"'"' | \ 7 | xargs -I % curl -s http://%:8080/Stats |\ 8 | tr -d "{}\"" | \ 9 | awk -F "," '"'"'{gsub (/[,]/," "); print;}'"'"' 10 | ' 11 | 12 | 13 | -------------------------------------------------------------------------------- /terraform/variables.tf: -------------------------------------------------------------------------------- 1 | variable "access_key" {} 2 | variable "secret_key" {} 3 | 4 | variable "vpc" { 5 | default = "vpc-7951fb10" 6 | } 7 | 8 | variable "ig" { 9 | default = "igw-2d03de44" 10 | } 11 | 12 | variable "servers" { 13 | default = 4 14 | } 15 | 16 | variable "key_name" { 17 | description = "SSH key name in your AWS account for AWS instances." 18 | } 19 | 20 | variable "key_path" { 21 | description = "Path to the private key specified by key_name." 22 | } --------------------------------------------------------------------------------