Skip to content

Commit 6acf858

Browse files
authored
Merge pull request #6217 from oasisprotocol/peternose/trivial/refactor-light-client
go/consensus/cometbft/light/client: Refactor light client
2 parents c265093 + 419a0e9 commit 6acf858

3 files changed

Lines changed: 101 additions & 85 deletions

File tree

.changelog/6216.trivial.md

Whitespace-only changes.

go/consensus/cometbft/full/statesync.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ type stateProvider struct {
2525
// Implements cmtstatesync.StateProvider.
2626
func (sp *stateProvider) AppHash(ctx context.Context, height uint64) ([]byte, error) {
2727
// We have to fetch the next height, which contains the app hash for the previous height.
28-
lb, err := sp.lightClient.GetVerifiedLightBlock(ctx, int64(height+1))
28+
lb, err := sp.lightClient.VerifyLightBlockAt(ctx, int64(height+1))
2929
if err != nil {
3030
return nil, err
3131
}
@@ -34,7 +34,7 @@ func (sp *stateProvider) AppHash(ctx context.Context, height uint64) ([]byte, er
3434

3535
// Implements cmtstatesync.StateProvider.
3636
func (sp *stateProvider) Commit(ctx context.Context, height uint64) (*cmttypes.Commit, error) {
37-
lb, err := sp.lightClient.GetVerifiedLightBlock(ctx, int64(height))
37+
lb, err := sp.lightClient.VerifyLightBlockAt(ctx, int64(height))
3838
if err != nil {
3939
return nil, err
4040
}
@@ -59,15 +59,15 @@ func (sp *stateProvider) State(ctx context.Context, height uint64) (cmtstate.Sta
5959
//
6060
// We need to fetch the NextValidators from height+2 because if the application changed
6161
// the validator set at the snapshot height then this only takes effect at height+2.
62-
lastLightBlock, err := sp.lightClient.GetVerifiedLightBlock(ctx, int64(height))
62+
lastLightBlock, err := sp.lightClient.VerifyLightBlockAt(ctx, int64(height))
6363
if err != nil {
6464
return cmtstate.State{}, err
6565
}
66-
curLightBlock, err := sp.lightClient.GetVerifiedLightBlock(ctx, int64(height)+1)
66+
curLightBlock, err := sp.lightClient.VerifyLightBlockAt(ctx, int64(height)+1)
6767
if err != nil {
6868
return cmtstate.State{}, err
6969
}
70-
nextLightBlock, err := sp.lightClient.GetVerifiedLightBlock(ctx, int64(height)+2)
70+
nextLightBlock, err := sp.lightClient.VerifyLightBlockAt(ctx, int64(height)+2)
7171
if err != nil {
7272
return cmtstate.State{}, err
7373
}
@@ -82,14 +82,14 @@ func (sp *stateProvider) State(ctx context.Context, height uint64) (cmtstate.Sta
8282
state.LastHeightValidatorsChanged = nextLightBlock.Height
8383

8484
// Fetch consensus parameters with light client verification.
85-
params, err := sp.lightClient.GetVerifiedParameters(ctx, nextLightBlock.Height)
85+
params, err := sp.lightClient.VerifyParametersAt(ctx, nextLightBlock.Height)
8686
if err != nil {
8787
return cmtstate.State{}, fmt.Errorf("failed to fetch consensus parameters for height %d: %w",
8888
nextLightBlock.Height,
8989
err,
9090
)
9191
}
92-
state.ConsensusParams = cmttypes.ConsensusParamsFromProto(*params)
92+
state.ConsensusParams = *params
9393

9494
return state, nil
9595
}

go/consensus/cometbft/light/client.go

Lines changed: 94 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bytes"
55
"context"
66
"fmt"
7+
"sync"
78
"time"
89

910
dbm "github.com/cometbft/cometbft-db"
@@ -31,133 +32,148 @@ type Config struct {
3132
// Client is a CometBFT consensus light client that talks with remote oasis-nodes that are using
3233
// the CometBFT consensus backend and verifies responses.
3334
type Client struct {
35+
mu sync.Mutex
36+
3437
providers []*Provider
3538

3639
// lightClient is a wrapped CometBFT light client used for verifying headers.
3740
lightClient *lazyClient
3841
}
3942

40-
func tryProviders[R any](
41-
ctx context.Context,
42-
providers []*Provider,
43-
fn func(*Provider) (R, rpc.PeerFeedback, error),
44-
) (R, rpc.PeerFeedback, error) {
45-
var (
46-
result R
47-
err error
48-
)
49-
for _, provider := range providers {
50-
if ctx.Err() != nil {
51-
return result, nil, ctx.Err()
52-
}
43+
// NewClient creates an internal and non-persistent light client.
44+
//
45+
// This client is instantiated from the provided (obtained out of bound) trusted block
46+
// and is used internally for CometBFT's state sync protocol.
47+
func NewClient(ctx context.Context, chainContext string, p2p rpc.P2P, cfg Config) (*Client, error) {
48+
pool := NewProviderPool(ctx, chainContext, p2p)
49+
providers := make([]*Provider, 0, numWitnesses+1)
50+
for range numWitnesses + 1 {
51+
providers = append(providers, pool.NewProvider())
52+
}
5353

54-
var pf rpc.PeerFeedback
55-
result, pf, err = fn(provider)
56-
if err == nil {
57-
return result, pf, nil
58-
}
54+
primary := providers[0]
55+
witnesses := make([]cmtlightprovider.Provider, 0, numWitnesses)
56+
for _, provider := range providers[1:] {
57+
witnesses = append(witnesses, provider)
5958
}
6059

61-
// Trigger primary provider refresh if everything fails.
62-
providers[0].RefreshPeer()
60+
lightClient, err := newLazyClient(
61+
cfg.GenesisDocument.ChainID,
62+
cfg.TrustOptions,
63+
primary,
64+
witnesses,
65+
cmtlightdb.New(dbm.NewMemDB(), ""),
66+
cmtlight.MaxRetryAttempts(lcMaxRetryAttempts), // TODO: Make this configurable.
67+
cmtlight.Logger(common.NewLogAdapter(!config.GlobalConfig.Consensus.LogDebug)),
68+
cmtlight.DisableProviderRemoval(),
69+
)
70+
if err != nil {
71+
return nil, fmt.Errorf("failed to create light client: %w", err)
72+
}
6373

64-
return result, nil, err
74+
return &Client{
75+
providers: providers,
76+
lightClient: lightClient,
77+
}, nil
6578
}
6679

67-
// GetLightBlock queries peers for a specific light block.
68-
func (c *Client) GetLightBlock(ctx context.Context, height int64) (*consensus.LightBlock, rpc.PeerFeedback, error) {
69-
return tryProviders(ctx, c.providers, func(p *Provider) (*consensus.LightBlock, rpc.PeerFeedback, error) {
70-
return p.GetLightBlock(ctx, height)
71-
})
80+
// LastTrustedHeight returns the last trusted height.
81+
func (c *Client) LastTrustedHeight() (int64, error) {
82+
height, err := c.lightClient.LastTrustedHeight()
83+
if err != nil {
84+
return 0, err
85+
}
86+
if height == -1 {
87+
return 0, fmt.Errorf("no trusted headers")
88+
}
89+
return height, nil
7290
}
7391

74-
// GetParameters queries peers for consensus parameters for a specific height.
75-
func (c *Client) GetParameters(ctx context.Context, height int64) (*consensus.Parameters, rpc.PeerFeedback, error) {
76-
return tryProviders(ctx, c.providers, func(p *Provider) (*consensus.Parameters, rpc.PeerFeedback, error) {
77-
return p.GetParameters(ctx, height)
78-
})
92+
// VerifyHeader verifies the given header.
93+
func (c *Client) VerifyHeader(ctx context.Context, header *cmttypes.Header) error {
94+
c.mu.Lock()
95+
defer c.mu.Unlock()
96+
return c.lightClient.VerifyHeader(ctx, header, time.Now())
7997
}
8098

81-
// GetVerifiedLightBlock returns a verified light block.
82-
func (c *Client) GetVerifiedLightBlock(ctx context.Context, height int64) (*cmttypes.LightBlock, error) {
99+
// VerifyLightBlockAt returns a verified light block at the given height.
100+
func (c *Client) VerifyLightBlockAt(ctx context.Context, height int64) (*cmttypes.LightBlock, error) {
101+
c.mu.Lock()
102+
defer c.mu.Unlock()
83103
return c.lightClient.VerifyLightBlockAtHeight(ctx, height, time.Now())
84104
}
85105

86-
// GetVerifiedParameters returns verified consensus parameters.
87-
func (c *Client) GetVerifiedParameters(ctx context.Context, height int64) (*cmtproto.ConsensusParams, error) {
88-
p, pf, err := c.GetParameters(ctx, height)
106+
// VerifyParametersAt returns a verified consensus parameters at the given height.
107+
func (c *Client) VerifyParametersAt(ctx context.Context, height int64) (*cmttypes.ConsensusParams, error) {
108+
params, pf, err := c.getParameters(ctx, height)
89109
if err != nil {
90110
return nil, err
91111
}
92-
if p.Height <= 0 {
112+
if params.Height <= 0 {
93113
pf.RecordBadPeer()
94-
return nil, fmt.Errorf("malformed height in response: %d", p.Height)
114+
return nil, fmt.Errorf("malformed height in response: %d", params.Height)
95115
}
96116

97117
// Decode CometBFT-specific parameters.
98-
var paramsPB cmtproto.ConsensusParams
99-
if err = paramsPB.Unmarshal(p.Meta); err != nil {
118+
var proto cmtproto.ConsensusParams
119+
if err = proto.Unmarshal(params.Meta); err != nil {
100120
pf.RecordBadPeer()
101121
return nil, fmt.Errorf("malformed parameters: %w", err)
102122
}
103-
params := cmttypes.ConsensusParamsFromProto(paramsPB)
104-
if err = params.ValidateBasic(); err != nil {
123+
cmtparams := cmttypes.ConsensusParamsFromProto(proto)
124+
if err = cmtparams.ValidateBasic(); err != nil {
105125
pf.RecordBadPeer()
106126
return nil, fmt.Errorf("malformed parameters: %w", err)
107127
}
108128

109129
// Fetch the header from the light client.
110-
l, err := c.lightClient.VerifyLightBlockAtHeight(ctx, p.Height, time.Now())
130+
l, err := c.lightClient.VerifyLightBlockAtHeight(ctx, params.Height, time.Now())
111131
if err != nil {
112132
pf.RecordBadPeer()
113-
return nil, fmt.Errorf("failed to fetch header %d from light client: %w", p.Height, err)
133+
return nil, fmt.Errorf("failed to fetch header %d from light client: %w", params.Height, err)
114134
}
115135

116136
// Verify hash.
117-
if localHash := params.Hash(); !bytes.Equal(localHash, l.ConsensusHash) {
137+
if hash := cmtparams.Hash(); !bytes.Equal(hash, l.ConsensusHash) {
118138
pf.RecordBadPeer()
119139
return nil, fmt.Errorf("mismatched parameters hash (expected: %X got: %X)",
120140
l.ConsensusHash,
121-
localHash,
141+
hash,
122142
)
123143
}
124144

125-
return &paramsPB, nil
145+
return &cmtparams, nil
126146
}
127147

128-
// NewClient creates an internal and non-persistent light client.
129-
//
130-
// This client is instantiated from the provided (obtained out of bound) trusted block
131-
// and is used internally for CometBFT's state sync protocol.
132-
func NewClient(ctx context.Context, chainContext string, p2p rpc.P2P, cfg Config) (*Client, error) {
133-
pool := NewProviderPool(ctx, chainContext, p2p)
134-
providers := make([]*Provider, 0, numWitnesses+1)
135-
for range numWitnesses + 1 {
136-
providers = append(providers, pool.NewProvider())
137-
}
138-
139-
primary := providers[0]
140-
witnesses := make([]cmtlightprovider.Provider, 0, numWitnesses)
141-
for _, provider := range providers[1:] {
142-
witnesses = append(witnesses, provider)
143-
}
148+
func (c *Client) getParameters(ctx context.Context, height int64) (*consensus.Parameters, rpc.PeerFeedback, error) {
149+
return tryProviders(ctx, c.providers, func(p *Provider) (*consensus.Parameters, rpc.PeerFeedback, error) {
150+
return p.GetParameters(ctx, height)
151+
})
152+
}
144153

145-
lightClient, err := newLazyClient(
146-
cfg.GenesisDocument.ChainID,
147-
cfg.TrustOptions,
148-
primary,
149-
witnesses,
150-
cmtlightdb.New(dbm.NewMemDB(), ""),
151-
cmtlight.MaxRetryAttempts(lcMaxRetryAttempts), // TODO: Make this configurable.
152-
cmtlight.Logger(common.NewLogAdapter(!config.GlobalConfig.Consensus.LogDebug)),
153-
cmtlight.DisableProviderRemoval(),
154+
func tryProviders[R any](
155+
ctx context.Context,
156+
providers []*Provider,
157+
fn func(*Provider) (R, rpc.PeerFeedback, error),
158+
) (R, rpc.PeerFeedback, error) {
159+
var (
160+
result R
161+
err error
154162
)
155-
if err != nil {
156-
return nil, fmt.Errorf("failed to create light client: %w", err)
163+
for _, provider := range providers {
164+
if ctx.Err() != nil {
165+
return result, nil, ctx.Err()
166+
}
167+
168+
var pf rpc.PeerFeedback
169+
result, pf, err = fn(provider)
170+
if err == nil {
171+
return result, pf, nil
172+
}
157173
}
158174

159-
return &Client{
160-
providers: providers,
161-
lightClient: lightClient,
162-
}, nil
175+
// Trigger primary provider refresh if everything fails.
176+
providers[0].RefreshPeer()
177+
178+
return result, nil, err
163179
}

0 commit comments

Comments
 (0)