{"id":15914075,"url":"https://github.com/bbengfort/consensus","last_synced_at":"2025-04-03T03:26:01.372Z","repository":{"id":69222720,"uuid":"146429393","full_name":"bbengfort/consensus","owner":"bbengfort","description":"Consensus communication stubs with gRPC streaming","archived":false,"fork":false,"pushed_at":"2018-08-29T12:31:03.000Z","size":2863,"stargazers_count":1,"open_issues_count":0,"forks_count":2,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-02-08T17:27:24.282Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bbengfort.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-08-28T10:14:55.000Z","updated_at":"2019-09-24T07:28:28.000Z","dependencies_parsed_at":"2023-04-09T04:33:29.858Z","dependency_job_id":null,"html_url":"https://github.com/bbengfort/consensus","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bbengfort%2Fconsensus","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bbengfort%2Fconsensus/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bbengfort%2Fconsensus/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bbengfort%2Fconsensus/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bbengfort","download_url":"https://codeload.github.com/bbengfort/consensus/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246929947,"owners_count":20856518,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-06T17:01:13.221Z","updated_at":"2025-04-03T03:26:01.349Z","avatar_url":"https://github.com/bbengfort.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Consensus Communication\n\n**Consensus communication stubs with gRPC streaming.**\n\n## RPC and Communication\n\nCommunication between replicas involves messages sent as [protocol buffers](https://developers.google.com/protocol-buffers/docs/proto3) over [gRPC](https://grpc.io/docs/quickstart/go.html) HTTP connections. The `github.com/bbengfort/consensus/pb` package implements several message types and a service in `.proto` files.\n\n\u003e **NOTE**: only edit the `pb/*.proto` files, the `pb/*.pb.go` are generated using the `make protobuf` command, which will overwrite any changes made. If you need to add Go to the `pb` package, please do so in `pb/*.go` files.\n\nRPCs between replicas and clients is defined by two gRPC services:\n\n- **Propose**: a unary RPC that allows clients to propose commands and values to any replica to be committed by the quorum.\n- **Dispatch**: a bidirectional streaming RPC that allows replicas to send generic messages to each other.\n\nIn order to enable communication, a replica/server must implement the `pb.ConsensusServer` interface, defined as follows:\n\n```go\ntype ConsensusServer interface {\n\tPropose(ctx context.Context, req *ProposeRequest) (*ProposeReply, error)\n\tDispatch(stream Consensus_DispatchServer) error\n}\n```\n\nThe `Dispatch` method will receive `*pb.PeerRequest` messages, a wrapper message defined in `pb/envelope.proto`. The wrapper message specifies the type of message, and can contain _one_ wrapped message. To read a message from the stream, you would check it's type, then use the appropriate `Get` method:\n\n```go\nimport (\n\t\"fmt\"\n\n\t\"github.com/bbengfort/consensus/pb\"\n)\n\nfunc ExampleParseBeacon(req *pb.PeerRequest) (*pb.BeaconRequest, error) {\n\tif req.GetType() == pb.Type_BEACON {\n\t\treturn req.GetBeacon(), nil\n\t}\n\n\treturn nil, fmt.Errorf(\"message with type %d is not a beacon request\", req.GetType())\n}\n```\n\nA `Dispatch` method therefore simply handle each incoming message with its own method based on it's type then send a reply, which may look like:\n\n```go\nfunc (r *Replica) Dispatch(stream pb.Consensus_DispatchServer) (err error) {\n\t// Receive each message from the stream, handle it, then send the reply.\n\tfor {\n\t\tvar req *pb.PeerRequest\n\t\tif req, err = stream.Recv(); err != nil {\n\t\t\tif err == io.EOF {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\treturn err\n\t\t}\n\n\t\t// Handle the specific event type and get a reply\n\t\tvar rep *pb.PeerReply\n\t\tswitch req.GetType() {\n\t\tcase pb.Type_BEACON:\n\t\t\trep, err = r.Beacon(req)\n\t\tcase pb.Type_PREPARE:\n\t\t\trep, err = r.Prepare(req)\n\t\t}\n\n\t\t// Exit if there is an error handling the message\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// Send the reply on the stream and handle the next message\n\t\tif err = stream.Send(rep); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t}\n}\n```\n\nTo create a beacon reply to send on the stream, you have to assign the message to the correct type with a `pb.PeerReply_[Type]` wrapper as follows (this is also how you might create `pb.PeerRequest` messages as well):\n\n```go\nimport (\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/bbengfort/consensus/pb\"\n)\n\nfunc ExampleCreateBeaconReply() *pb.PeerReply {\n\thostname, _ := os.Hostname()\n\n\treq := \u0026pb.PeerReply{\n\t\tType:   pb.Type_BEACON,\n\t\tSender: hostname,\n\t\tMessage: \u0026pb.PeerReply_Beacon{\n\t\t\tBeacon: \u0026pb.BeaconReply{\n\t\t\t\tTimestamp: time.Now().Format(time.RFC3339Nano),\n\t\t\t},\n\t\t},\n\t}\n\n\treturn req\n}\n```\n\n\u003e **NOTE**: the message types defined in `pb/*.proto` are based on the protocol buffers defined in the [original implementation of ePaxos](https://github.com/efficient/epaxos). They are meant to be an example only, and can be modified and recompiled as needed.\n\nAdding a new message type to the `Dispatch` streaming RPC is fairly straight forward; create the new message request and reply types in a `pb/.proto` file, then import it in `pb/envelope.proto`. In that same file, add the type to the `Type` enumeration, then the appropriate fields to the `PeerRequest` and `PeerReply` message `oneof` field. Rebuild the protocol buffers as follows:\n\n```\n$ make protobuf\n```\n\nNote that to run this command, [Protocol Buffers v3](https://grpc.io/docs/quickstart/go.html#install-protocol-buffers-v3) must be installed on your system (this is not a go dependency).\n\n## Metrics\n\nA note on metrics, there is a `Metrics` data structure on the `Replica` that keeps track of the number of requests and unique clients. Metrics are updated in the `Propose` method in `server.go`. Note that this is completely optional and can be ignored. Metrics can also be dumped to disk, appended to a [JSON lines](http://jsonlines.org/) file as follows:\n\n```go\nreplica.Metrics.Dump(\"path/to/metrics.json\")\n```\n\nIf you'd like to know more about benchmarking the quorum, please let me know!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbbengfort%2Fconsensus","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbbengfort%2Fconsensus","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbbengfort%2Fconsensus/lists"}