mirror of
https://github.com/tendermint/tendermint.git
synced 2026-07-30 20:12:52 +00:00
light: return light client status on rpc /status (#7536)
*light: rpc /status returns status of light client ; code refactoring light: moved lightClientInfo into light.go, renamed String to ID test/e2e: Return light client trusted height instead of SyncInfo trusted height test/e2e/start.go: Not waiting for light client to catch up in tests. Removed querying of syncInfo in start if the node is a light node * light: Removed call to primary /status. Added trustedPeriod to light info * light/provider: added ID function to return IP of primary and witnesses * light/provider/http/http_test: renamed String() to ID()
This commit is contained in:
@@ -13,6 +13,7 @@ import (
|
||||
tmmath "github.com/tendermint/tendermint/libs/math"
|
||||
"github.com/tendermint/tendermint/light/provider"
|
||||
"github.com/tendermint/tendermint/light/store"
|
||||
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
@@ -1146,3 +1147,29 @@ func (c *Client) providerShouldBeRemoved(err error) bool {
|
||||
errors.As(err, &provider.ErrBadLightBlock{}) ||
|
||||
errors.Is(err, provider.ErrConnectionClosed)
|
||||
}
|
||||
|
||||
func (c *Client) Status(ctx context.Context) *types.LightClientInfo {
|
||||
chunks := make([]string, len(c.witnesses))
|
||||
|
||||
// If primary is in witness list we do not want to count it twice in the number of peers
|
||||
primaryNotInWitnessList := 1
|
||||
for i, val := range c.witnesses {
|
||||
chunks[i] = val.ID()
|
||||
if chunks[i] == c.primary.ID() {
|
||||
primaryNotInWitnessList = 0
|
||||
}
|
||||
}
|
||||
|
||||
return &types.LightClientInfo{
|
||||
PrimaryID: c.primary.ID(),
|
||||
WitnessesID: chunks,
|
||||
NumPeers: len(chunks) + primaryNotInWitnessList,
|
||||
LastTrustedHeight: c.latestTrustedBlock.Height,
|
||||
LastTrustedHash: c.latestTrustedBlock.Hash(),
|
||||
LatestBlockTime: c.latestTrustedBlock.Time,
|
||||
TrustingPeriod: c.trustingPeriod.String(),
|
||||
// The caller of /status can deduce this from the two variables above
|
||||
// Having a boolean flag improves readbility
|
||||
TrustedBlockExpired: HeaderExpired(c.latestTrustedBlock.SignedHeader, c.trustingPeriod, time.Now()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +61,10 @@ func (impl *providerBenchmarkImpl) ReportEvidence(_ context.Context, _ types.Evi
|
||||
return errors.New("not implemented")
|
||||
}
|
||||
|
||||
// provierBenchmarkImpl does not have an ID iteself.
|
||||
// Thus we return a sample string
|
||||
func (impl *providerBenchmarkImpl) ID() string { return "ip-not-defined.com" }
|
||||
|
||||
func BenchmarkSequence(b *testing.B) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
@@ -168,4 +172,5 @@ func BenchmarkBackwards(b *testing.B) {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -167,3 +167,93 @@ func waitForBlock(ctx context.Context, p provider.Provider, height int64) (*type
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientStatusRPC(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
conf, err := rpctest.CreateConfig(t.Name())
|
||||
require.NoError(t, err)
|
||||
|
||||
// Start a test application
|
||||
app := kvstore.NewApplication()
|
||||
|
||||
_, closer, err := rpctest.StartTendermint(ctx, conf, app, rpctest.SuppressStdout)
|
||||
require.NoError(t, err)
|
||||
defer func() { require.NoError(t, closer(ctx)) }()
|
||||
|
||||
dbDir, err := os.MkdirTemp("", "light-client-test-status-example")
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() { os.RemoveAll(dbDir) })
|
||||
|
||||
chainID := conf.ChainID()
|
||||
|
||||
primary, err := httpp.New(chainID, conf.RPC.ListenAddress)
|
||||
require.NoError(t, err)
|
||||
|
||||
// give Tendermint time to generate some blocks
|
||||
block, err := waitForBlock(ctx, primary, 2)
|
||||
require.NoError(t, err)
|
||||
|
||||
db, err := dbm.NewGoLevelDB("light-client-db", dbDir)
|
||||
require.NoError(t, err)
|
||||
|
||||
// In order to not create a full testnet to verify whether we get the correct IPs
|
||||
// if we have more than one witness, we add the primary multiple times
|
||||
// TODO This should be buggy behavior, we should not be allowed to add the same nodes as witnesses
|
||||
witnesses := []provider.Provider{primary, primary, primary}
|
||||
|
||||
c, err := light.NewClient(ctx,
|
||||
chainID,
|
||||
light.TrustOptions{
|
||||
Period: 504 * time.Hour, // 21 days
|
||||
Height: 2,
|
||||
Hash: block.Hash(),
|
||||
},
|
||||
primary,
|
||||
witnesses,
|
||||
dbs.New(db),
|
||||
light.Logger(log.TestingLogger()),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
defer func() { require.NoError(t, c.Cleanup()) }()
|
||||
|
||||
lightStatus := c.Status(ctx)
|
||||
|
||||
// Verify primary IP
|
||||
require.True(t, lightStatus.PrimaryID == primary.ID())
|
||||
|
||||
// Verify IPs of witnesses
|
||||
require.ElementsMatch(t, mapProviderArrayToIP(witnesses), lightStatus.WitnessesID)
|
||||
|
||||
// Verify that number of peers is equal to number of witnesses (+ 1 if the primary is not a witness)
|
||||
require.Equal(t, len(witnesses)+1*primaryNotInWitnessList(witnesses, primary), lightStatus.NumPeers)
|
||||
|
||||
// Verify that the last trusted hash returned matches the stored hash of the trusted
|
||||
// block at the last trusted height.
|
||||
blockAtTrustedHeight, err := c.TrustedLightBlock(lightStatus.LastTrustedHeight)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.EqualValues(t, lightStatus.LastTrustedHash, blockAtTrustedHeight.Hash())
|
||||
|
||||
}
|
||||
|
||||
// Extract the IP address of all the providers within an array
|
||||
func mapProviderArrayToIP(el []provider.Provider) []string {
|
||||
ips := make([]string, len(el))
|
||||
for i, v := range el {
|
||||
ips[i] = v.ID()
|
||||
}
|
||||
return ips
|
||||
}
|
||||
|
||||
// If the primary is not in the witness list, we will return 1
|
||||
// Otherwise, return 0
|
||||
func primaryNotInWitnessList(witnesses []provider.Provider, primary provider.Provider) int {
|
||||
for _, el := range witnesses {
|
||||
if el == primary {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
@@ -100,7 +100,8 @@ func NewWithClientAndOptions(chainID string, client rpcclient.RemoteClient, opti
|
||||
}
|
||||
}
|
||||
|
||||
func (p *http) String() string {
|
||||
// Identifies the provider with an IP in string format
|
||||
func (p *http) ID() string {
|
||||
return fmt.Sprintf("http{%s}", p.client.Remote())
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ package http_test
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -22,15 +21,15 @@ import (
|
||||
func TestNewProvider(t *testing.T) {
|
||||
c, err := lighthttp.New("chain-test", "192.168.0.1:26657")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, fmt.Sprintf("%s", c), "http{http://192.168.0.1:26657}")
|
||||
require.Equal(t, c.ID(), "http{http://192.168.0.1:26657}")
|
||||
|
||||
c, err = lighthttp.New("chain-test", "http://153.200.0.1:26657")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, fmt.Sprintf("%s", c), "http{http://153.200.0.1:26657}")
|
||||
require.Equal(t, c.ID(), "http{http://153.200.0.1:26657}")
|
||||
|
||||
c, err = lighthttp.New("chain-test", "153.200.0.1")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, fmt.Sprintf("%s", c), "http{http://153.200.0.1}")
|
||||
require.Equal(t, c.ID(), "http{http://153.200.0.1}")
|
||||
}
|
||||
|
||||
func TestProvider(t *testing.T) {
|
||||
|
||||
@@ -15,6 +15,20 @@ type Provider struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// ID provides a mock function with given fields:
|
||||
func (_m *Provider) ID() string {
|
||||
ret := _m.Called()
|
||||
|
||||
var r0 string
|
||||
if rf, ok := ret.Get(0).(func() string); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// LightBlock provides a mock function with given fields: ctx, height
|
||||
func (_m *Provider) LightBlock(ctx context.Context, height int64) (*types.LightBlock, error) {
|
||||
ret := _m.Called(ctx, height)
|
||||
|
||||
@@ -25,4 +25,8 @@ type Provider interface {
|
||||
|
||||
// ReportEvidence reports an evidence of misbehavior.
|
||||
ReportEvidence(context.Context, types.Evidence) error
|
||||
|
||||
// Returns the ID of a provider. For RPC providers it returns the IP address of the client
|
||||
// For p2p providers it returns a combination of NodeID and IP address
|
||||
ID() string
|
||||
}
|
||||
|
||||
+12
-1
@@ -32,6 +32,7 @@ type LightClient interface {
|
||||
Update(ctx context.Context, now time.Time) (*types.LightBlock, error)
|
||||
VerifyLightBlockAtHeight(ctx context.Context, height int64, now time.Time) (*types.LightBlock, error)
|
||||
TrustedLightBlock(height int64) (*types.LightBlock, error)
|
||||
Status(ctx context.Context) *types.LightClientInfo
|
||||
}
|
||||
|
||||
var _ rpcclient.Client = (*Client)(nil)
|
||||
@@ -124,8 +125,18 @@ func (c *Client) OnStop() {
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the status of the light client. Previously this was querying the primary connected to the client
|
||||
// As a consequence of this change, running /status on the light client will return nil for SyncInfo, NodeInfo
|
||||
// and ValdiatorInfo.
|
||||
func (c *Client) Status(ctx context.Context) (*coretypes.ResultStatus, error) {
|
||||
return c.next.Status(ctx)
|
||||
lightClientInfo := c.lc.Status(ctx)
|
||||
|
||||
return &coretypes.ResultStatus{
|
||||
NodeInfo: types.NodeInfo{},
|
||||
SyncInfo: coretypes.SyncInfo{},
|
||||
ValidatorInfo: coretypes.ValidatorInfo{},
|
||||
LightClientInfo: *lightClientInfo,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Client) ABCIInfo(ctx context.Context) (*coretypes.ResultABCIInfo, error) {
|
||||
|
||||
@@ -31,6 +31,22 @@ func (_m *LightClient) ChainID() string {
|
||||
return r0
|
||||
}
|
||||
|
||||
// Status provides a mock function with given fields: ctx
|
||||
func (_m *LightClient) Status(ctx context.Context) *types.LightClientInfo {
|
||||
ret := _m.Called(ctx)
|
||||
|
||||
var r0 *types.LightClientInfo
|
||||
if rf, ok := ret.Get(0).(func(context.Context) *types.LightClientInfo); ok {
|
||||
r0 = rf(ctx)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*types.LightClientInfo)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// TrustedLightBlock provides a mock function with given fields: height
|
||||
func (_m *LightClient) TrustedLightBlock(height int64) (*types.LightBlock, error) {
|
||||
ret := _m.Called(height)
|
||||
|
||||
Reference in New Issue
Block a user