Browse Source

light cli: add feature flags and save providers (#5148)

pull/5164/head
Callum Waters 4 years ago
committed by GitHub
parent
commit
cf84dcd44c
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 173 additions and 14 deletions
  1. +79
    -13
      cmd/tendermint/commands/lite.go
  2. +26
    -1
      libs/math/fraction.go
  3. +68
    -0
      libs/math/fraction_test.go

+ 79
- 13
cmd/tendermint/commands/lite.go View File

@ -1,6 +1,7 @@
package commands package commands
import ( import (
"errors"
"fmt" "fmt"
"net/http" "net/http"
"os" "os"
@ -12,6 +13,7 @@ import (
dbm "github.com/tendermint/tm-db" dbm "github.com/tendermint/tm-db"
"github.com/tendermint/tendermint/libs/log" "github.com/tendermint/tendermint/libs/log"
tmmath "github.com/tendermint/tendermint/libs/math"
tmos "github.com/tendermint/tendermint/libs/os" tmos "github.com/tendermint/tendermint/libs/os"
"github.com/tendermint/tendermint/light" "github.com/tendermint/tendermint/light"
lproxy "github.com/tendermint/tendermint/light/proxy" lproxy "github.com/tendermint/tendermint/light/proxy"
@ -31,16 +33,11 @@ All calls that can be tracked back to a block header by a proof
will be verified before passing them back to the caller. Other than will be verified before passing them back to the caller. Other than
that, it will present the same interface as a full Tendermint node. that, it will present the same interface as a full Tendermint node.
Example:
Furthermore to the chainID, a fresh instance of a light client will
need a primary RPC address, a trusted hash and height and witness RPC addresses
(if not using sequential verification). To restart the node, thereafter
only the chainID is required.
start a fresh instance:
light cosmoshub-3 -p http://52.57.29.196:26657 -w http://public-seed-node.cosmoshub.certus.one:26657
--height 962118 --hash 28B97BE9F6DE51AC69F70E0B7BFD7E5C9CD1A595B7DC31AFF27C50D4948020CD
continue from latest state:
light cosmoshub-3 -p http://52.57.29.196:26657 -w http://public-seed-node.cosmoshub.certus.one:26657
`, `,
RunE: runProxy, RunE: runProxy,
Args: cobra.ExactArgs(1), Args: cobra.ExactArgs(1),
@ -56,11 +53,16 @@ var (
home string home string
maxOpenConnections int maxOpenConnections int
sequential bool
trustingPeriod time.Duration trustingPeriod time.Duration
trustedHeight int64 trustedHeight int64
trustedHash []byte trustedHash []byte
trustLevelStr string
verbose bool verbose bool
primaryKey = []byte("primary")
witnessesKey = []byte("witnesses")
) )
func init() { func init() {
@ -77,10 +79,16 @@ func init() {
900, 900,
"Maximum number of simultaneous connections (including WebSocket).") "Maximum number of simultaneous connections (including WebSocket).")
LightCmd.Flags().DurationVar(&trustingPeriod, "trusting-period", 168*time.Hour, LightCmd.Flags().DurationVar(&trustingPeriod, "trusting-period", 168*time.Hour,
"Trusting period. Should be significantly less than the unbonding period")
"Trusting period that headers can be verified within. Should be significantly less than the unbonding period")
LightCmd.Flags().Int64Var(&trustedHeight, "height", 1, "Trusted header's height") LightCmd.Flags().Int64Var(&trustedHeight, "height", 1, "Trusted header's height")
LightCmd.Flags().BytesHexVar(&trustedHash, "hash", []byte{}, "Trusted header's hash") LightCmd.Flags().BytesHexVar(&trustedHash, "hash", []byte{}, "Trusted header's hash")
LightCmd.Flags().BoolVar(&verbose, "verbose", false, "Verbose output") LightCmd.Flags().BoolVar(&verbose, "verbose", false, "Verbose output")
LightCmd.Flags().StringVar(&trustLevelStr, "trust-level", "1/3",
"Trust level. Must be between 1/3 and 3/3",
)
LightCmd.Flags().BoolVar(&sequential, "sequential", false,
"Sequential Verification. Verify all headers sequentially as opposed to using skipping verification",
)
} }
func runProxy(cmd *cobra.Command, args []string) error { func runProxy(cmd *cobra.Command, args []string) error {
@ -97,13 +105,46 @@ func runProxy(cmd *cobra.Command, args []string) error {
chainID = args[0] chainID = args[0]
logger.Info("Creating client...", "chainID", chainID) logger.Info("Creating client...", "chainID", chainID)
witnessesAddrs := strings.Split(witnessAddrsJoined, ",")
witnessesAddrs := []string{}
if witnessAddrsJoined != "" {
witnessesAddrs = strings.Split(witnessAddrsJoined, ",")
}
db, err := dbm.NewGoLevelDB("light-client-db", home) db, err := dbm.NewGoLevelDB("light-client-db", home)
if err != nil { if err != nil {
return fmt.Errorf("can't create a db: %w", err) return fmt.Errorf("can't create a db: %w", err)
} }
if primaryAddr == "" { // check to see if we can start from an existing state
var err error
primaryAddr, witnessesAddrs, err = checkForExistingProviders(db)
if err != nil {
return fmt.Errorf("failed to retrieve primary or witness from db: %w", err)
}
if primaryAddr == "" {
return errors.New("no primary address was provided nor found. Please provide a primary (using -p)." +
" Run the command: tendermint light --help for more information")
}
} else {
err := saveProviders(db, primaryAddr, witnessAddrsJoined)
if err != nil {
logger.Error("Unable to save primary and or witness addresses", "err", err)
}
}
trustLevel, err := tmmath.ParseFraction(trustLevelStr)
if err != nil {
return fmt.Errorf("can't parse trust level: %w", err)
}
options := []light.Option{light.Logger(logger)}
if sequential {
options = append(options, light.SequentialVerification())
} else {
options = append(options, light.SkippingVerification(trustLevel))
}
var c *light.Client var c *light.Client
if trustedHeight > 0 && len(trustedHash) > 0 { // fresh installation if trustedHeight > 0 && len(trustedHash) > 0 { // fresh installation
c, err = light.NewHTTPClient( c, err = light.NewHTTPClient(
@ -116,7 +157,7 @@ func runProxy(cmd *cobra.Command, args []string) error {
primaryAddr, primaryAddr,
witnessesAddrs, witnessesAddrs,
dbs.New(db, chainID), dbs.New(db, chainID),
light.Logger(logger),
options...,
) )
} else { // continue from latest state } else { // continue from latest state
c, err = light.NewHTTPClientFromTrustedStore( c, err = light.NewHTTPClientFromTrustedStore(
@ -125,7 +166,7 @@ func runProxy(cmd *cobra.Command, args []string) error {
primaryAddr, primaryAddr,
witnessesAddrs, witnessesAddrs,
dbs.New(db, chainID), dbs.New(db, chainID),
light.Logger(logger),
options...,
) )
} }
if err != nil { if err != nil {
@ -167,3 +208,28 @@ func runProxy(cmd *cobra.Command, args []string) error {
return nil return nil
} }
func checkForExistingProviders(db dbm.DB) (string, []string, error) {
primaryBytes, err := db.Get(primaryKey)
if err != nil {
return "", []string{""}, err
}
witnessesBytes, err := db.Get(witnessesKey)
if err != nil {
return "", []string{""}, err
}
witnessesAddrs := strings.Split(string(witnessesBytes), ",")
return string(primaryBytes), witnessesAddrs, nil
}
func saveProviders(db dbm.DB, primaryAddr, witnessesAddrs string) error {
err := db.Set(primaryKey, []byte(primaryAddr))
if err != nil {
return fmt.Errorf("failed to save primary provider: %w", err)
}
err = db.Set(witnessesKey, []byte(witnessesAddrs))
if err != nil {
return fmt.Errorf("failed to save witness providers: %w", err)
}
return nil
}

+ 26
- 1
libs/math/fraction.go View File

@ -1,6 +1,11 @@
package math package math
import "fmt"
import (
"errors"
"fmt"
"strconv"
"strings"
)
// Fraction defined in terms of a numerator divided by a denominator in int64 // Fraction defined in terms of a numerator divided by a denominator in int64
// format. // format.
@ -15,3 +20,23 @@ type Fraction struct {
func (fr Fraction) String() string { func (fr Fraction) String() string {
return fmt.Sprintf("%d/%d", fr.Numerator, fr.Denominator) return fmt.Sprintf("%d/%d", fr.Numerator, fr.Denominator)
} }
// ParseFractions takes the string of a fraction as input i.e "2/3" and converts this
// to the equivalent fraction else returns an error. The format of the string must be
// one number followed by a slash (/) and then the other number.
func ParseFraction(f string) (Fraction, error) {
o := strings.SplitN(f, "/", -1)
if len(o) != 2 {
return Fraction{}, errors.New("incorrect formating: should be like \"1/3\"")
}
numerator, err := strconv.ParseInt(o[0], 10, 64)
if err != nil {
return Fraction{}, fmt.Errorf("incorrect formatting, err: %w", err)
}
denominator, err := strconv.ParseInt(o[1], 10, 64)
if err != nil {
return Fraction{}, fmt.Errorf("incorrect formatting, err: %w", err)
}
return Fraction{Numerator: numerator, Denominator: denominator}, nil
}

+ 68
- 0
libs/math/fraction_test.go View File

@ -0,0 +1,68 @@
package math
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestParseFraction(t *testing.T) {
testCases := []struct {
f string
exp Fraction
err bool
}{
{
f: "2/3",
exp: Fraction{2, 3},
err: false,
},
{
f: "15/5",
exp: Fraction{15, 5},
err: false,
},
{
f: "-1/2",
exp: Fraction{-1, 2},
err: false,
},
{
f: "1/-2",
exp: Fraction{1, -2},
err: false,
},
{
f: "2/3/4",
exp: Fraction{},
err: true,
},
{
f: "123",
exp: Fraction{},
err: true,
},
{
f: "1a2/4",
exp: Fraction{},
err: true,
},
{
f: "1/3bc4",
exp: Fraction{},
err: true,
},
}
for idx, tc := range testCases {
output, err := ParseFraction(tc.f)
if tc.err {
assert.Error(t, err, idx)
} else {
assert.NoError(t, err, idx)
}
assert.Equal(t, tc.exp, output, idx)
}
}

Loading…
Cancel
Save