mirror of
https://github.com/tendermint/tendermint.git
synced 2026-01-07 13:55:17 +00:00
light cli: add feature flags and save providers (#5148)
This commit is contained in:
@@ -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
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
libs/math/fraction_test.go
Normal file
68
libs/math/fraction_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user