From cb67dad18d477bbcfe2e21f5ae2a7c1a6e1e0a06 Mon Sep 17 00:00:00 2001 From: Roman Shtylman Date: Mon, 23 Mar 2020 11:03:41 -0700 Subject: [PATCH] make it so --- .gitignore | 2 + .gitlab-ci.yml | 6 ++ LICENSE.md | 55 ++++++++++ Makefile | 28 +++++ README.md | 82 +++++++++++++++ cmd/signer/main.go | 94 +++++++++++++++++ go.mod | 27 +++++ go.sum | 116 +++++++++++++++++++++ internal/signer/Config.go | 37 +++++++ internal/signer/NodeClient.go | 171 +++++++++++++++++++++++++++++++ internal/signer/PvGuard.go | 36 +++++++ internal/signer/Serialization.go | 30 ++++++ 12 files changed, 684 insertions(+) create mode 100644 .gitignore create mode 100644 .gitlab-ci.yml create mode 100644 LICENSE.md create mode 100644 Makefile create mode 100644 README.md create mode 100644 cmd/signer/main.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 internal/signer/Config.go create mode 100644 internal/signer/NodeClient.go create mode 100644 internal/signer/PvGuard.go create mode 100644 internal/signer/Serialization.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..af96791 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.vscode +build diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..ff5cd8c --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,6 @@ +image: golang:1.13 + +unit_test: + stage: test + script: + - make test diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..7a84c0d --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,55 @@ +# Blue Oak Model License + +Version 1.0.0 + +## Purpose + +This license gives everyone as much permission to work with +this software as possible, while protecting contributors +from liability. + +## Acceptance + +In order to receive this license, you must agree to its +rules. The rules of this license are both obligations +under that agreement and conditions to your license. +You must not do anything with this software that triggers +a rule that you cannot or will not follow. + +## Copyright + +Each contributor licenses you to do everything with this +software that would otherwise infringe that contributor's +copyright in it. + +## Notices + +You must ensure that everyone who gets a copy of +any part of this software from you, with or without +changes, also gets the text of this license or a link to +. + +## Excuse + +If anyone notifies you in writing that you have not +complied with [Notices](#notices), you can keep your +license by taking all practical steps to comply within 30 +days after the notice. If you do not do so, your license +ends immediately. + +## Patent + +Each contributor licenses you to do everything with this +software that would otherwise infringe any patent claims +they can license or become able to license. + +## Reliability + +No contributor can revoke this license. + +## No Liability + +***As far as the law allows, this software comes as is, +without any warranty or condition, and no contributor +will be liable to anyone for any damages related to this +software or this license, under any kind of legal claim.*** \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..fd7abec --- /dev/null +++ b/Makefile @@ -0,0 +1,28 @@ +GOLINT:=$(shell go list -f {{.Target}} golang.org/x/lint/golint) + +all: build + +build: build/signer + +build/signer: cmd/signer/main.go $(wildcard internal/**/*.go) + CGO_ENABLED=0 go build -o ./build/signer ${gobuild_flags} ./cmd/signer + +lint: tools + @$(GOLINT) -set_exit_status ./... + +test: + @go test -short ./... + +race: + @go test -race -short ./... + +msan: + @go test -msan -short ./... + +tools: + @go install golang.org/x/lint/golint + +clean: + rm -rf build + +.PHONY: all lint test race msan tools clean build diff --git a/README.md b/README.md new file mode 100644 index 0000000..bb3f2ab --- /dev/null +++ b/README.md @@ -0,0 +1,82 @@ +# Tendermint Validator + +A lightweight single key tendermint validator for sentry nodes. + +## Design + +A lightweight alternative to using a full node instance for validating blocks. The validator is able to connect to any number of sentry nodes and will sign blocks provided by the nodes. The validator maintains a watermark file to protect against double signing. + +## Pre-requisites + +Before starting, please make sure to fully understand node and validator requirements and operation for your particular network and chain. + +## Setup + +_The security of any key material is outside the scope of this guide. At a minimum we recommend performing key material steps on airgapped computers and using your audited security procedures._ + +### Setup Validator Instance + +Configure the instance with a [toml](https://github.com/toml-lang/toml) file. Below is a sample configuration. + +```toml +# Path to priv validator key json file +key_file = "/path/to/priv_validator_key.json" + +# The state directory stores watermarks for double signing protection. +# Each validator instance maintains a watermark. +state_dir = "/path/to/state/dir" + +# The network chain id for your p2p nodes +chain_id = "chain-id-here" + +# Configure any number of p2p network nodes. +# We recommend at least 2 nodes for redundancy. +[[node]] +address = "tcp://:1234" + +[[node]] +address = "tcp://:1234" +``` + +## Configure p2p network nodes + +Validators are not directly connected to the p2p network nor do they store chain and application state. They rely on nodes to receive blocks from the p2p network, make signing requests, and relay the signed blocks back to the p2p network. + +To make a node available as a relay for a validator, find the `priv_validator_laddr` (or equivalent) configuration item in your node's configuration file. Change this value, to accept connections on an IP address and port of your choosing. + +```diff + # TCP or UNIX socket address for Tendermint to listen on for + # connections from an external PrivValidator process +-priv_validator_laddr = "" ++priv_validator_laddr = "tcp://0.0.0.0:1234" +``` + +_Full configuration and operation of your tendermint node is outside the scope of this guide. You should consult your network's documentation for node configuration._ + +_We recommend hosting nodes on separate and isolated infrastructure from your validator instances._ + +## Launch validator + +Once your validator instance and node is configured, you can launch the signer. + +```bash +signer --config /path/to/config.toml +``` + +_We recommend using systemd or similar service management program as appropriate for your runtime platform._ + +## Security + +Security and management of any key material is outside the scope of this service. Always consider your own security and risk profile when dealing with sensitive keys, services, or infrastructure. + +## No Liability + +As far as the law allows, this software comes as is, +without any warranty or condition, and no contributor +will be liable to anyone for any damages related to this +software or this license, under any kind of legal claim. + +## References + +- https://docs.tendermint.com/master/tendermint-core/validators.html +- https://hub.cosmos.network/master/validators/overview.html diff --git a/cmd/signer/main.go b/cmd/signer/main.go new file mode 100644 index 0000000..4f6eb04 --- /dev/null +++ b/cmd/signer/main.go @@ -0,0 +1,94 @@ +package main + +import ( + "flag" + "fmt" + "log" + "net" + "os" + "path" + "sync" + "time" + + "tendermint-signer/internal/signer" + + cmn "github.com/tendermint/tendermint/libs/common" + tmlog "github.com/tendermint/tendermint/libs/log" + "github.com/tendermint/tendermint/privval" +) + +func fileExists(filename string) bool { + info, err := os.Stat(filename) + if os.IsNotExist(err) { + return false + } + return !info.IsDir() +} + +func main() { + logger := tmlog.NewTMLogger( + tmlog.NewSyncWriter(os.Stdout), + ).With("module", "validator") + + var configFile = flag.String("config", "", "path to configuration file") + flag.Parse() + + if *configFile == "" { + panic("--config flag is required") + } + + config, err := signer.LoadConfigFromFile(*configFile) + if err != nil { + log.Fatal(err) + } + + logger.Info( + "Tendermint Validator", + "priv-key", config.PrivValKeyFile, + "priv-state-dir", config.PrivValStateDir, + ) + + signer.InitSerialization() + + // services to stop on shutdown + var services []cmn.Service + + chainID := config.ChainID + if chainID == "" { + log.Fatal("chain_id option is required") + } + + stateFile := path.Join(config.PrivValStateDir, fmt.Sprintf("%s_priv_validator_state.json", chainID)) + + if !fileExists(stateFile) { + log.Fatalf("State file missing: %s\n", stateFile) + } + + val := privval.LoadFilePV(config.PrivValKeyFile, stateFile) + pv := &signer.PvGuard{PrivValidator: val} + + for _, node := range config.Nodes { + dialer := net.Dialer{Timeout: 30 * time.Second} + signer := signer.NewNodeClient(node.Address, logger, config.ChainID, pv, dialer) + + err := signer.Start() + if err != nil { + panic(err) + } + + services = append(services, signer) + } + + wg := sync.WaitGroup{} + wg.Add(1) + cmn.TrapSignal(logger, func() { + for _, service := range services { + err := service.Stop() + if err != nil { + panic(err) + } + } + wg.Done() + }) + wg.Wait() +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..066710f --- /dev/null +++ b/go.mod @@ -0,0 +1,27 @@ +module tendermint-signer + +require ( + github.com/BurntSushi/toml v0.3.1 + github.com/btcsuite/btcd v0.0.0-20190213025234-306aecffea32 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/fortytw2/leaktest v1.3.0 // indirect + github.com/go-kit/kit v0.9.0 // indirect + github.com/go-logfmt/logfmt v0.4.0 // indirect + github.com/go-stack/stack v1.8.0 // indirect + github.com/gogo/protobuf v1.2.1 // indirect + github.com/golang/protobuf v1.3.2 // indirect + github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf // indirect + github.com/magiconair/properties v1.8.0 // indirect + github.com/pkg/errors v0.8.1 // indirect + github.com/stretchr/testify v1.3.0 // indirect + github.com/tendermint/go-amino v0.14.1 + github.com/tendermint/tendermint v0.31.5 + golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 // indirect + golang.org/x/net v0.0.0-20190620200207-3b0461eec859 // indirect + golang.org/x/sys v0.0.0-20190422165155-953cdadca894 // indirect + golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 // indirect + google.golang.org/genproto v0.0.0-20190219182410-082222b4a5c5 // indirect + google.golang.org/grpc v1.19.0 // indirect +) + +go 1.13 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..130313a --- /dev/null +++ b/go.sum @@ -0,0 +1,116 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/btcsuite/btcd v0.0.0-20190213025234-306aecffea32 h1:qkOC5Gd33k54tobS36cXdAzJbeHaduLtnLQQwNoIi78= +github.com/btcsuite/btcd v0.0.0-20190213025234-306aecffea32/go.mod h1:DrZx5ec/dmnfpw9KyYoQyYo7d0KEvTkk/5M/vbZjAr8= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20190207003914-4c204d697803 h1:j3AgPKKZtZStM2nyhrDSLSYgT7YHrZKdSkq1OYeLjvM= +github.com/btcsuite/btcutil v0.0.0-20190207003914-4c204d697803/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495 h1:6IyqGr3fnd0tM3YxipK27TUskaOVUjU2nG45yzwcQKY= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/go-kit/kit v0.9.0 h1:wDJmvq38kDhkVxi50ni9ykkdUr1PKgqKOoi01fa0Mdk= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= +github.com/golang/mock v1.1.1 h1:G5FRp8JnTd7RQH5kemVNlMeyXQAztQ3mOWV95KxsXH8= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf h1:+RRA9JqSOZFfKrOeqr2z77+8R2RKyh8PG66dcu1V0ck= +github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/tendermint/go-amino v0.14.1 h1:o2WudxNfdLNBwMyl2dqOJxiro5rfrEaU0Ugs6offJMk= +github.com/tendermint/go-amino v0.14.1/go.mod h1:i/UKE5Uocn+argJJBb12qTZsCDBcAYMbR92AaJVmKso= +github.com/tendermint/tendermint v0.31.5 h1:vTet8tCq3B9/J9Yo11dNZ8pOB7NtSy++bVSfkP4KzR4= +github.com/tendermint/tendermint v0.31.5/go.mod h1:ymcPyWblXCplCPQjbOYbrF1fWnpslATMVqiGgWbZrlc= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3 h1:x/bBzNauLQAlE3fLku/xy92Y8QwKX5HZymrMz2IiKFc= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 h1:z99zHgr7hKfrUcX/KsoJk5FJfjTceCKIp96+biqP4To= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b h1:qMK98NmNCRVDIYFycQ5yVRkvgDUFfdP8Ip4KqmDEB7g= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190219182410-082222b4a5c5 h1:SdCO7As+ChE1iV3IjBleIIWlj8VjZWuYEUF5pjELOOQ= +google.golang.org/genproto v0.0.0-20190219182410-082222b4a5c5/go.mod h1:L3J43x8/uS+qIUoksaLKe6OS3nUKxOKuIFz1sl2/jx4= +google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= +google.golang.org/grpc v1.19.0 h1:cfg4PD8YEdSFnm7qLV4++93WcmhH2nIUhMjhdCvl3j8= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/internal/signer/Config.go b/internal/signer/Config.go new file mode 100644 index 0000000..173bf62 --- /dev/null +++ b/internal/signer/Config.go @@ -0,0 +1,37 @@ +package signer + +import ( + "os" + + "github.com/BurntSushi/toml" +) + +type NodeConfig struct { + Address string `toml:"address"` +} + +type CosignerConfig struct { + ID int `toml:"id"` + Address string `toml:"remote_address"` +} + +type Config struct { + PrivValKeyFile string `toml:"key_file"` + PrivValStateDir string `toml:"state_dir"` + ChainID string `toml:"chain_id"` + CosignerThreshold int `toml:"cosigner_threshold"` + ListenAddress string `toml:"cosigner_listen_address"` + Nodes []NodeConfig `toml:"node"` + Cosigners []CosignerConfig `toml:"cosigner"` +} + +func LoadConfigFromFile(file string) (Config, error) { + var config Config + + reader, err := os.Open(file) + if err != nil { + return config, err + } + _, err = toml.DecodeReader(reader, &config) + return config, err +} diff --git a/internal/signer/NodeClient.go b/internal/signer/NodeClient.go new file mode 100644 index 0000000..74b3f0b --- /dev/null +++ b/internal/signer/NodeClient.go @@ -0,0 +1,171 @@ +package signer + +import ( + "fmt" + "net" + "time" + + "github.com/tendermint/tendermint/crypto/ed25519" + cmn "github.com/tendermint/tendermint/libs/common" + "github.com/tendermint/tendermint/libs/log" + p2pconn "github.com/tendermint/tendermint/p2p/conn" + "github.com/tendermint/tendermint/privval" + "github.com/tendermint/tendermint/types" +) + +// NodeClient dials a node responds to signature requests using its privVal. +type NodeClient struct { + cmn.BaseService + + address string + chainID string + privKey ed25519.PrivKeyEd25519 + privVal types.PrivValidator + + dialer net.Dialer +} + +// NewNodeClient return a NodeClient that will dial using the given +// dialer and respond to any signature requests over the connection +// using the given privVal. +// +// If the connection is broken, the NodeClient will attempt to reconnect. +func NewNodeClient( + address string, + logger log.Logger, + chainID string, + privVal types.PrivValidator, + dialer net.Dialer, +) *NodeClient { + rs := &NodeClient{ + address: address, + chainID: chainID, + privVal: privVal, + dialer: dialer, + privKey: ed25519.GenPrivKey(), + } + + rs.BaseService = *cmn.NewBaseService(logger, "RemoteSigner", rs) + return rs +} + +// OnStart implements cmn.Service. +func (rs *NodeClient) OnStart() error { + go rs.loop() + return nil +} + +// main loop for NodeClient +func (rs *NodeClient) loop() { + var conn net.Conn + for { + if !rs.IsRunning() { + if conn != nil { + if err := conn.Close(); err != nil { + rs.Logger.Error("Close", "err", cmn.ErrorWrap(err, "closing listener failed")) + } + } + return + } + + for conn == nil { + proto, address := cmn.ProtocolAndAddress(rs.address) + netConn, err := rs.dialer.Dial(proto, address) + if err != nil { + rs.Logger.Error("Dialing", "err", err) + rs.Logger.Info("Retrying", "sleep (s)", 3, "address", rs.address) + time.Sleep(time.Second * 3) + continue + } + + rs.Logger.Info("Connected", "address", rs.address) + conn, err = p2pconn.MakeSecretConnection(netConn, rs.privKey) + if err != nil { + conn = nil + rs.Logger.Error("Secret Conn", "err", err) + rs.Logger.Info("Retrying", "sleep (s)", 3, "address", rs.address) + time.Sleep(time.Second * 3) + continue + } + } + + // since dialing can take time, we check running again + if !rs.IsRunning() { + if err := conn.Close(); err != nil { + rs.Logger.Error("Close", "err", cmn.ErrorWrap(err, "closing listener failed")) + } + return + } + + req, err := ReadMsg(conn) + if err != nil { + rs.Logger.Error("readMsg", "err", err) + conn.Close() + conn = nil + continue + } + + res, err := rs.handleRequest(req) + if err != nil { + // only log the error; we'll reply with an error in res + rs.Logger.Error("handleRequest", "err", err) + } + + err = WriteMsg(conn, res) + if err != nil { + rs.Logger.Error("writeMsg", "err", err) + conn.Close() + conn = nil + } + } +} + +func (rs *NodeClient) handleRequest(req privval.RemoteSignerMsg) (privval.RemoteSignerMsg, error) { + var res privval.RemoteSignerMsg + var err error + + switch typedReq := req.(type) { + case *privval.PubKeyRequest: + pubKey := rs.privVal.GetPubKey() + res = &privval.PubKeyResponse{PubKey: pubKey, Error: nil} + case *privval.SignVoteRequest: + err = rs.privVal.SignVote(rs.chainID, typedReq.Vote) + if err != nil { + rs.Logger.Error("Failed to sign vote", "address", rs.address, "error", err, "vote", typedReq.Vote) + res = &privval.SignedVoteResponse{ + Vote: nil, + Error: &privval.RemoteSignerError{ + Code: 0, + Description: err.Error(), + }, + } + } else { + rs.Logger.Info("Signed vote", "address", rs.address, "vote", typedReq.Vote) + res = &privval.SignedVoteResponse{Vote: typedReq.Vote, Error: nil} + } + case *privval.SignProposalRequest: + err = rs.privVal.SignProposal(rs.chainID, typedReq.Proposal) + if err != nil { + rs.Logger.Error("Failed to sign proposal", "address", rs.address, "error", err, "proposal", typedReq.Proposal) + res = &privval.SignedProposalResponse{ + Proposal: nil, + Error: &privval.RemoteSignerError{ + Code: 0, + Description: err.Error(), + }, + } + } else { + rs.Logger.Info("Signed proposal", "address", rs.address, "proposal", typedReq.Proposal) + res = &privval.SignedProposalResponse{ + Proposal: typedReq.Proposal, + Error: nil, + } + } + case *privval.PingRequest: + res = &privval.PingResponse{} + default: + err = fmt.Errorf("unknown msg: %v", typedReq) + } + + return res, err +} diff --git a/internal/signer/PvGuard.go b/internal/signer/PvGuard.go new file mode 100644 index 0000000..ec06904 --- /dev/null +++ b/internal/signer/PvGuard.go @@ -0,0 +1,36 @@ +package signer + +import ( + "sync" + + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/types" +) + +// PvGuard guards access to an underlying PrivValidator by using mutexes +// for each of the PrivValidator interface functions +type PvGuard struct { + PrivValidator types.PrivValidator + pvMutex sync.Mutex +} + +// GetPubKey implementes types.PrivValidator +func (pv *PvGuard) GetPubKey() crypto.PubKey { + pv.pvMutex.Lock() + defer pv.pvMutex.Unlock() + return pv.PrivValidator.GetPubKey() +} + +// SignVote implementes types.PrivValidator +func (pv *PvGuard) SignVote(chainID string, vote *types.Vote) error { + pv.pvMutex.Lock() + defer pv.pvMutex.Unlock() + return pv.PrivValidator.SignVote(chainID, vote) +} + +// SignProposal implementes types.PrivValidator +func (pv *PvGuard) SignProposal(chainID string, proposal *types.Proposal) error { + pv.pvMutex.Lock() + defer pv.pvMutex.Unlock() + return pv.PrivValidator.SignProposal(chainID, proposal) +} diff --git a/internal/signer/Serialization.go b/internal/signer/Serialization.go new file mode 100644 index 0000000..152e4cf --- /dev/null +++ b/internal/signer/Serialization.go @@ -0,0 +1,30 @@ +package signer + +import ( + "io" + + amino "github.com/tendermint/go-amino" + cryptoAmino "github.com/tendermint/tendermint/crypto/encoding/amino" + "github.com/tendermint/tendermint/privval" +) + +var codec = amino.NewCodec() + +// InitSerialization initalizes the private codec encoder/decoder +func InitSerialization() { + cryptoAmino.RegisterAmino(codec) + privval.RegisterRemoteSignerMsg(codec) +} + +// ReadMsg reads a message from an io.Reader +func ReadMsg(reader io.Reader) (msg privval.RemoteSignerMsg, err error) { + const maxRemoteSignerMsgSize = 1024 * 10 + _, err = codec.UnmarshalBinaryLengthPrefixedReader(reader, &msg, maxRemoteSignerMsgSize) + return +} + +// WriteMsg writes a message to an io.Writer +func WriteMsg(writer io.Writer, msg interface{}) (err error) { + _, err = codec.MarshalBinaryLengthPrefixedWriter(writer, msg) + return +}