|
4 | 4 | "bytes" |
5 | 5 | "context" |
6 | 6 | "fmt" |
| 7 | + "sync" |
7 | 8 | "time" |
8 | 9 |
|
9 | 10 | dbm "github.com/cometbft/cometbft-db" |
@@ -31,133 +32,148 @@ type Config struct { |
31 | 32 | // Client is a CometBFT consensus light client that talks with remote oasis-nodes that are using |
32 | 33 | // the CometBFT consensus backend and verifies responses. |
33 | 34 | type Client struct { |
| 35 | + mu sync.Mutex |
| 36 | + |
34 | 37 | providers []*Provider |
35 | 38 |
|
36 | 39 | // lightClient is a wrapped CometBFT light client used for verifying headers. |
37 | 40 | lightClient *lazyClient |
38 | 41 | } |
39 | 42 |
|
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 | + } |
53 | 53 |
|
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) |
59 | 58 | } |
60 | 59 |
|
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 | + } |
63 | 73 |
|
64 | | - return result, nil, err |
| 74 | + return &Client{ |
| 75 | + providers: providers, |
| 76 | + lightClient: lightClient, |
| 77 | + }, nil |
65 | 78 | } |
66 | 79 |
|
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 |
72 | 90 | } |
73 | 91 |
|
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()) |
79 | 97 | } |
80 | 98 |
|
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() |
83 | 103 | return c.lightClient.VerifyLightBlockAtHeight(ctx, height, time.Now()) |
84 | 104 | } |
85 | 105 |
|
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) |
89 | 109 | if err != nil { |
90 | 110 | return nil, err |
91 | 111 | } |
92 | | - if p.Height <= 0 { |
| 112 | + if params.Height <= 0 { |
93 | 113 | 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) |
95 | 115 | } |
96 | 116 |
|
97 | 117 | // 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 { |
100 | 120 | pf.RecordBadPeer() |
101 | 121 | return nil, fmt.Errorf("malformed parameters: %w", err) |
102 | 122 | } |
103 | | - params := cmttypes.ConsensusParamsFromProto(paramsPB) |
104 | | - if err = params.ValidateBasic(); err != nil { |
| 123 | + cmtparams := cmttypes.ConsensusParamsFromProto(proto) |
| 124 | + if err = cmtparams.ValidateBasic(); err != nil { |
105 | 125 | pf.RecordBadPeer() |
106 | 126 | return nil, fmt.Errorf("malformed parameters: %w", err) |
107 | 127 | } |
108 | 128 |
|
109 | 129 | // 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()) |
111 | 131 | if err != nil { |
112 | 132 | 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) |
114 | 134 | } |
115 | 135 |
|
116 | 136 | // Verify hash. |
117 | | - if localHash := params.Hash(); !bytes.Equal(localHash, l.ConsensusHash) { |
| 137 | + if hash := cmtparams.Hash(); !bytes.Equal(hash, l.ConsensusHash) { |
118 | 138 | pf.RecordBadPeer() |
119 | 139 | return nil, fmt.Errorf("mismatched parameters hash (expected: %X got: %X)", |
120 | 140 | l.ConsensusHash, |
121 | | - localHash, |
| 141 | + hash, |
122 | 142 | ) |
123 | 143 | } |
124 | 144 |
|
125 | | - return ¶msPB, nil |
| 145 | + return &cmtparams, nil |
126 | 146 | } |
127 | 147 |
|
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 | +} |
144 | 153 |
|
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 |
154 | 162 | ) |
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 | + } |
157 | 173 | } |
158 | 174 |
|
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 |
163 | 179 | } |
0 commit comments