make it so

This commit is contained in:
Roman Shtylman 2020-03-23 11:03:41 -07:00
commit cb67dad18d
No known key found for this signature in database
GPG Key ID: 72ED6A839D752787
12 changed files with 684 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
.vscode
build

6
.gitlab-ci.yml Normal file
View File

@ -0,0 +1,6 @@
image: golang:1.13
unit_test:
stage: test
script:
- make test

55
LICENSE.md Normal file
View File

@ -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
<https://blueoakcouncil.org/license/1.0.0>.
## 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.***

28
Makefile Normal file
View File

@ -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

82
README.md Normal file
View File

@ -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://<node-a ip>:1234"
[[node]]
address = "tcp://<node-b ip>: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

94
cmd/signer/main.go Normal file
View File

@ -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()
}

27
go.mod Normal file
View File

@ -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

116
go.sum Normal file
View File

@ -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=

37
internal/signer/Config.go Normal file
View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}