Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
PhSteel committed Mar 28, 2022
2 parents c304d8c + 387b500 commit e75a2be
Show file tree
Hide file tree
Showing 9 changed files with 96 additions and 28 deletions.
6 changes: 1 addition & 5 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
VERSION := $(shell echo $(shell git describe --tags) | sed 's/^v//')
SDKVERSION := $(shell go list -m -u -f '{{.Version}}' github.com/cosmos/cosmos-sdk)
TMVERSION := $(shell go list -m -u -f '{{.Version}}' github.com/tendermint/tendermint)
COMMIT := $(shell git log -1 --format='%H')

all: install

LD_FLAGS = -X github.com/strangelove-ventures/horcrux/cmd/horcrux/cmd.Version=$(VERSION) \
-X github.com/strangelove-ventures/horcrux/cmd/horcrux/cmd.Commit=$(COMMIT) \
-X github.com/strangelove-ventures/horcrux/cmd/horcrux/cmd.SDKVersion=$(SDKVERSION) \
-X github.com/strangelove-ventures/horcrux/cmd/horcrux/cmd.TMVersion=$(TMVERSION)
-X github.com/strangelove-ventures/horcrux/cmd/horcrux/cmd.Commit=$(COMMIT)

BUILD_FLAGS := -ldflags '$(LD_FLAGS)'

Expand Down
Binary file added cmd/.DS_Store
Binary file not shown.
Binary file added cmd/horcrux/.DS_Store
Binary file not shown.
84 changes: 71 additions & 13 deletions cmd/horcrux/cmd/cosigner.go
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
package cmd

import (
"encoding/hex"
"encoding/json"
"fmt"
"log"
"os"
"path"
"strings"
"time"

"github.com/rcommodum/horcrux/signer/localthreshold"
"github.com/rcommodum/horcrux/signer/raft"

"github.com/rcommodum/horcrux/signer"
"github.com/cosmos/cosmos-sdk/types/bech32"
"github.com/spf13/cobra"
"github.com/strangelove-ventures/horcrux/signer"
tmlog "github.com/tendermint/tendermint/libs/log"
tmService "github.com/tendermint/tendermint/libs/service"
"github.com/tendermint/tendermint/types"
)

func init() {
cosignerCmd.AddCommand(StartCosignerCmd())
cosignerCmd.AddCommand(AddressCmd())
rootCmd.AddCommand(cosignerCmd)
}

Expand All @@ -27,6 +29,62 @@ var cosignerCmd = &cobra.Command{
Short: "Threshold mpc signer for TM based nodes",
}

type AddressCmdOutput struct {
HexAddress string
ValConsAddress string
}

func AddressCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "address",
Short: "Get public key hex address and valcons address",
Args: cobra.RangeArgs(0, 1),
RunE: func(cmd *cobra.Command, args []string) (err error) {
err = validateCosignerConfig(config)
if err != nil {
return
}

var privValKeyFile string
if config.PrivValKeyFile == "" {
privValKeyFile = path.Join(config.HomeDir, "share.json")
} else {
privValKeyFile = config.PrivValKeyFile
}

key, err := signer.LoadCosignerKey(privValKeyFile)
if err != nil {
return fmt.Errorf("error reading cosigner key: %s", err)
}

pubKey := key.PubKey.Address()

output := AddressCmdOutput{
HexAddress: strings.ToUpper(hex.EncodeToString(pubKey)),
}

if len(args) == 1 {
bech32ValConsAddress, err := bech32.ConvertAndEncode(args[0], pubKey)
if err != nil {
return err
}
output.ValConsAddress = bech32ValConsAddress
}

jsonOut, err := json.Marshal(output)
if err != nil {
return err
}

fmt.Println(string(jsonOut))

return nil
},
}

return cmd
}

func StartCosignerCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "start",
Expand Down Expand Up @@ -74,31 +132,31 @@ func StartCosignerCmd() *cobra.Command {

var val types.PrivValidator

key, err := localthreshold.LoadCosignerKey(cfg.PrivValKeyFile)
key, err := signer.LoadCosignerKey(cfg.PrivValKeyFile)
if err != nil {
return fmt.Errorf("error reading cosigner key: %s", err)
}

// ok to auto initialize on disk since the cosigner share is the one that actually
// protects against double sign - this exists as a cache for the final signature
stateFile := path.Join(cfg.PrivValStateDir, fmt.Sprintf("%s_priv_validator_state.json", chainID))
signState, err := localthreshold.LoadOrCreateSignState(stateFile)
signState, err := signer.LoadOrCreateSignState(stateFile)
if err != nil {
panic(err)
}

// state for our cosigner share
// Not automatically initialized on disk to avoid double sign risk
shareStateFile := path.Join(cfg.PrivValStateDir, fmt.Sprintf("%s_share_sign_state.json", chainID))
shareSignState, err := localthreshold.LoadSignState(shareStateFile)
shareSignState, err := signer.LoadSignState(shareStateFile)
if err != nil {
panic(err)
}

cosigners := []localthreshold.Cosigner{}
cosigners := []signer.Cosigner{}

// add ourselves as a peer so localcosigner can handle GetEphSecPart requests
peers := []localthreshold.CosignerPeer{{
peers := []signer.CosignerPeer{{
ID: key.ID,
PublicKey: key.RSAKey.PublicKey,
}}
Expand All @@ -112,14 +170,14 @@ func StartCosignerCmd() *cobra.Command {
}

pubKey := key.CosignerKeys[cosignerConfig.ID-1]
peers = append(peers, localthreshold.CosignerPeer{
peers = append(peers, signer.CosignerPeer{
ID: cosigner.GetID(),
PublicKey: *pubKey,
})
}

total := len(cfg.Cosigners) + 1
localCosignerConfig := localthreshold.LocalCosignerConfig{
localCosignerConfig := signer.LocalCosignerConfig{
CosignerKey: key,
SignState: &shareSignState,
RsaKey: key.RSAKey,
Expand All @@ -129,7 +187,7 @@ func StartCosignerCmd() *cobra.Command {
Threshold: uint8(cfg.CosignerThreshold),
}

localCosigner := localthreshold.NewLocalCosigner(localCosignerConfig)
localCosigner := signer.NewLocalCosigner(localCosignerConfig)

timeout, _ := time.ParseDuration(config.CosignerConfig.Timeout)

Expand All @@ -142,7 +200,7 @@ func StartCosignerCmd() *cobra.Command {
nodeID := fmt.Sprint(key.ID)

// Start RAFT store listener
raftStore := raft.NewRaftStore(nodeID,
raftStore := signer.NewRaftStore(nodeID,
raftDir, cfg.ListenAddress, timeout, logger, localCosigner, cosigners)
if err := raftStore.Start(); err != nil {
log.Fatalf("Error starting raft store: %v\n", err)
Expand Down
13 changes: 11 additions & 2 deletions cmd/horcrux/cmd/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"encoding/json"
"fmt"
"runtime"
dbg "runtime/debug"

"github.com/spf13/cobra"
)
Expand Down Expand Up @@ -48,12 +49,20 @@ type Info struct {
}

func NewInfo() Info {
bi, _ := dbg.ReadBuildInfo()

dependencyVersions := map[string]string{}

for _, dep := range bi.Deps {
dependencyVersions[dep.Path] = dep.Version
}

return Info{
Version: Version,
GitCommit: Commit,
GoVersion: fmt.Sprintf("%s %s/%s", runtime.Version(), runtime.GOOS, runtime.GOARCH),
CosmosSdkVersion: SDKVersion,
TendermintVersion: TMVersion,
CosmosSdkVersion: dependencyVersions["github.com/cosmos/cosmos-sdk"],
TendermintVersion: dependencyVersions["github.com/tendermint/tendermint"],
}
}

Expand Down
6 changes: 6 additions & 0 deletions docs/migrating.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,3 +200,9 @@ Common failure modes:
### 7. CONGRATS!

You now can sleep much better at night because you are much less likely to have a down validator wake you up in the middle of the night. You have also completed a stressful migration on a production system. Go run around outside screaming, pet your dog, eat a nice meal, hug your kids/significant other, etc... and enjoy the rest of your day!

### 8. Administration Commands

`horcrux elect` - Elect a new cluster leader. Pass an optional argument with the intended leader ID to elect that cosigner as the new leader, e.g. `horcrux elect 3` to elect cosigner with `ID: 3` as leader

`horcrux cosigner address` - Get the public key address as both hex and optionally the validator consensus bech32 address. To retrieve the valcons bech32 address, pass an optional argument with the chain's bech32 valcons prefix, e.g. `horcrux cosigner address cosmosvalcons`
2 changes: 1 addition & 1 deletion docs/signing.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Horcrux is designed with performance in mind, so it will sign and return the ful

The [Raft](https://raft.github.io/) protocol, specifically the [hashicorp/raft](https://github.com/hashicorp/raft) golang implementation, is used in the Horcrux cluster for the purposes of leader election and high watermark consensus to provide fault tolerance and double sign avoidance.

Each block sign request (votes and proposals) from any connected sentry node(s), made to any signer node, is proxied through the raft leader. This ensures that there is a single node that manages the overall threshold signing flow for any given block. It also ensures that even though each connected sentry will make requests for every block, the only request that will be acted upon is the one which first reaches the signer node that is currently the elected leader. This enables a High Evailability (HA) validator with multiple sentry nodes and multiple signer nodes, enabling the validator to continue signing blocks even in the case of outages on signer and sentry nodes.
Each block sign request (votes and proposals) from any connected sentry node(s), made to any signer node, is proxied through the raft leader. This ensures that there is a single node that manages the overall threshold signing flow for any given block. It also ensures that even though each connected sentry will make requests for every block, the only request that will be acted upon is the one which first reaches the signer node that is currently the elected leader. This enables a High Availability (HA) validator with multiple sentry nodes and multiple signer nodes, enabling the validator to continue signing blocks even in the case of outages on signer and sentry nodes.

### Fault tolerance
- For the sentry nodes, the cluster needs at least one sentry that is in sync with the chain and connected to a signer node that is up and participating in the raft cluster. E.g. if the signer cluster is operational, for a 3 sentry configuration, 2 sentries can have failures and the validator will continue signing blocks.
Expand Down
8 changes: 2 additions & 6 deletions goreleaser.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,12 @@ env:
# Require use of Go modules.
- GO111MODULE=on

# TODO: figure out how to embed the sdk and tendermint versions in here
# https://goreleaser.com/customization/env/ set the env on the go releaser job
builds:
- id: "horcrux"
main: ./cmd/horcrux/main.go
ldflags:
- -X "github.com/strangelove-ventures/horcrux/version.Version={{ .Version }}"
- -X "github.com/strangelove-ventures/horcrux/version.Commit={{ .Commit }}"
- -X "github.com/strangelove-ventures/horcrux/version.SDKVersion=v0.43.2"
- -X "github.com/strangelove-ventures/horcrux/version.TMVersion=v0.34.8"
- -X "github.com/strangelove-ventures/horcrux/cmd/horcrux/cmd.Version={{ .Version }}"
- -X "github.com/strangelove-ventures/horcrux/cmd/horcrux/cmd.Commit={{ .Commit }}"
goos:
- darwin
- linux
Expand Down
5 changes: 4 additions & 1 deletion test/node_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,10 @@ func (tn *TestNode) CliContext() client.Context {
// MakeTestNodes creates the test node objects required for bootstrapping tests
func MakeTestNodes(count int, home, chainid string, chainType *ChainType,
pool *dockertest.Pool, t *testing.T) (out TestNodes) {
err := pool.Client.PullImage(docker.PullImageOptions{Repository: chainType.Repository}, docker.AuthConfiguration{})
err := pool.Client.PullImage(docker.PullImageOptions{
Repository: chainType.Repository,
Tag: chainType.Version,
}, docker.AuthConfiguration{})
if err != nil {
t.Logf("Error pulling image: %v", err)
}
Expand Down

0 comments on commit e75a2be

Please sign in to comment.