github.com/devwanda/aphelion-staking@v0.33.9/lite2/rpc/client.go (about) 1 package rpc 2 3 import ( 4 "bytes" 5 "context" 6 "errors" 7 "fmt" 8 "strings" 9 "time" 10 11 "github.com/devwanda/aphelion-staking/crypto/merkle" 12 tmbytes "github.com/devwanda/aphelion-staking/libs/bytes" 13 service "github.com/devwanda/aphelion-staking/libs/service" 14 lite "github.com/devwanda/aphelion-staking/lite2" 15 rpcclient "github.com/devwanda/aphelion-staking/rpc/client" 16 ctypes "github.com/devwanda/aphelion-staking/rpc/core/types" 17 rpctypes "github.com/devwanda/aphelion-staking/rpc/jsonrpc/types" 18 "github.com/devwanda/aphelion-staking/types" 19 ) 20 21 var errNegOrZeroHeight = errors.New("negative or zero height") 22 23 // Client is an RPC client, which uses lite#Client to verify data (if it can be 24 // proved!). 25 type Client struct { 26 service.BaseService 27 28 next rpcclient.Client 29 lc *lite.Client 30 prt *merkle.ProofRuntime 31 } 32 33 var _ rpcclient.Client = (*Client)(nil) 34 35 // NewClient returns a new client. 36 func NewClient(next rpcclient.Client, lc *lite.Client) *Client { 37 c := &Client{ 38 next: next, 39 lc: lc, 40 prt: defaultProofRuntime(), 41 } 42 c.BaseService = *service.NewBaseService(nil, "Client", c) 43 return c 44 } 45 46 func (c *Client) OnStart() error { 47 if !c.next.IsRunning() { 48 return c.next.Start() 49 } 50 return nil 51 } 52 53 func (c *Client) OnStop() { 54 if c.next.IsRunning() { 55 c.next.Stop() 56 } 57 } 58 59 func (c *Client) Status() (*ctypes.ResultStatus, error) { 60 return c.next.Status() 61 } 62 63 func (c *Client) ABCIInfo() (*ctypes.ResultABCIInfo, error) { 64 return c.next.ABCIInfo() 65 } 66 67 func (c *Client) ABCIQuery(path string, data tmbytes.HexBytes) (*ctypes.ResultABCIQuery, error) { 68 return c.ABCIQueryWithOptions(path, data, rpcclient.DefaultABCIQueryOptions) 69 } 70 71 // GetWithProofOptions is useful if you want full access to the ABCIQueryOptions. 72 // XXX Usage of path? It's not used, and sometimes it's /, sometimes /key, sometimes /store. 73 func (c *Client) ABCIQueryWithOptions(path string, data tmbytes.HexBytes, 74 opts rpcclient.ABCIQueryOptions) (*ctypes.ResultABCIQuery, error) { 75 76 res, err := c.next.ABCIQueryWithOptions(path, data, opts) 77 if err != nil { 78 return nil, err 79 } 80 resp := res.Response 81 82 // Validate the response. 83 if resp.IsErr() { 84 return nil, fmt.Errorf("err response code: %v", resp.Code) 85 } 86 if len(resp.Key) == 0 || resp.Proof == nil { 87 return nil, errors.New("empty tree") 88 } 89 if resp.Height <= 0 { 90 return nil, errNegOrZeroHeight 91 } 92 93 // Update the light client if we're behind. 94 // NOTE: AppHash for height H is in header H+1. 95 h, err := c.updateLiteClientIfNeededTo(resp.Height + 1) 96 if err != nil { 97 return nil, err 98 } 99 100 // Validate the value proof against the trusted header. 101 if resp.Value != nil { 102 // Value exists 103 // XXX How do we encode the key into a string... 104 storeName, err := parseQueryStorePath(path) 105 if err != nil { 106 return nil, err 107 } 108 kp := merkle.KeyPath{} 109 kp = kp.AppendKey([]byte(storeName), merkle.KeyEncodingURL) 110 kp = kp.AppendKey(resp.Key, merkle.KeyEncodingURL) 111 err = c.prt.VerifyValue(resp.Proof, h.AppHash, kp.String(), resp.Value) 112 if err != nil { 113 return nil, fmt.Errorf("verify value proof: %w", err) 114 } 115 return &ctypes.ResultABCIQuery{Response: resp}, nil 116 } 117 118 // OR validate the ansence proof against the trusted header. 119 // XXX How do we encode the key into a string... 120 err = c.prt.VerifyAbsence(resp.Proof, h.AppHash, string(resp.Key)) 121 if err != nil { 122 return nil, fmt.Errorf("verify absence proof: %w", err) 123 } 124 return &ctypes.ResultABCIQuery{Response: resp}, nil 125 } 126 127 func (c *Client) BroadcastTxCommit(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) { 128 return c.next.BroadcastTxCommit(tx) 129 } 130 131 func (c *Client) BroadcastTxAsync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) { 132 return c.next.BroadcastTxAsync(tx) 133 } 134 135 func (c *Client) BroadcastTxSync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) { 136 return c.next.BroadcastTxSync(tx) 137 } 138 139 func (c *Client) UnconfirmedTxs(limit int) (*ctypes.ResultUnconfirmedTxs, error) { 140 return c.next.UnconfirmedTxs(limit) 141 } 142 143 func (c *Client) NumUnconfirmedTxs() (*ctypes.ResultUnconfirmedTxs, error) { 144 return c.next.NumUnconfirmedTxs() 145 } 146 147 func (c *Client) NetInfo() (*ctypes.ResultNetInfo, error) { 148 return c.next.NetInfo() 149 } 150 151 func (c *Client) DumpConsensusState() (*ctypes.ResultDumpConsensusState, error) { 152 return c.next.DumpConsensusState() 153 } 154 155 func (c *Client) ConsensusState() (*ctypes.ResultConsensusState, error) { 156 return c.next.ConsensusState() 157 } 158 159 func (c *Client) ConsensusParams(height *int64) (*ctypes.ResultConsensusParams, error) { 160 res, err := c.next.ConsensusParams(height) 161 if err != nil { 162 return nil, err 163 } 164 165 // Validate res. 166 if err := res.ConsensusParams.Validate(); err != nil { 167 return nil, err 168 } 169 if res.BlockHeight <= 0 { 170 return nil, errNegOrZeroHeight 171 } 172 173 // Update the light client if we're behind. 174 h, err := c.updateLiteClientIfNeededTo(res.BlockHeight) 175 if err != nil { 176 return nil, err 177 } 178 179 // Verify hash. 180 if cH, tH := res.ConsensusParams.Hash(), h.ConsensusHash; !bytes.Equal(cH, tH) { 181 return nil, fmt.Errorf("params hash %X does not match trusted hash %X", 182 cH, tH) 183 } 184 185 return res, nil 186 } 187 188 func (c *Client) Health() (*ctypes.ResultHealth, error) { 189 return c.next.Health() 190 } 191 192 // BlockchainInfo calls rpcclient#BlockchainInfo and then verifies every header 193 // returned. 194 func (c *Client) BlockchainInfo(minHeight, maxHeight int64) (*ctypes.ResultBlockchainInfo, error) { 195 res, err := c.next.BlockchainInfo(minHeight, maxHeight) 196 if err != nil { 197 return nil, err 198 } 199 200 // Validate res. 201 for i, meta := range res.BlockMetas { 202 if meta == nil { 203 return nil, fmt.Errorf("nil block meta %d", i) 204 } 205 if err := meta.ValidateBasic(); err != nil { 206 return nil, fmt.Errorf("invalid block meta %d: %w", i, err) 207 } 208 } 209 210 // Update the light client if we're behind. 211 if len(res.BlockMetas) > 0 { 212 lastHeight := res.BlockMetas[len(res.BlockMetas)-1].Header.Height 213 if _, err := c.updateLiteClientIfNeededTo(lastHeight); err != nil { 214 return nil, err 215 } 216 } 217 218 // Verify each of the BlockMetas. 219 for _, meta := range res.BlockMetas { 220 h, err := c.lc.TrustedHeader(meta.Header.Height) 221 if err != nil { 222 return nil, fmt.Errorf("trusted header %d: %w", meta.Header.Height, err) 223 } 224 if bmH, tH := meta.Header.Hash(), h.Hash(); !bytes.Equal(bmH, tH) { 225 return nil, fmt.Errorf("block meta header %X does not match with trusted header %X", 226 bmH, tH) 227 } 228 } 229 230 return res, nil 231 } 232 233 func (c *Client) Genesis() (*ctypes.ResultGenesis, error) { 234 return c.next.Genesis() 235 } 236 237 // Block calls rpcclient#Block and then verifies the result. 238 func (c *Client) Block(height *int64) (*ctypes.ResultBlock, error) { 239 res, err := c.next.Block(height) 240 if err != nil { 241 return nil, err 242 } 243 244 // Validate res. 245 if err := res.BlockID.ValidateBasic(); err != nil { 246 return nil, err 247 } 248 if err := res.Block.ValidateBasic(); err != nil { 249 return nil, err 250 } 251 if bmH, bH := res.BlockID.Hash, res.Block.Hash(); !bytes.Equal(bmH, bH) { 252 return nil, fmt.Errorf("blockID %X does not match with block %X", 253 bmH, bH) 254 } 255 256 // Update the light client if we're behind. 257 h, err := c.updateLiteClientIfNeededTo(res.Block.Height) 258 if err != nil { 259 return nil, err 260 } 261 262 // Verify block. 263 if bH, tH := res.Block.Hash(), h.Hash(); !bytes.Equal(bH, tH) { 264 return nil, fmt.Errorf("block header %X does not match with trusted header %X", 265 bH, tH) 266 } 267 268 return res, nil 269 } 270 271 func (c *Client) BlockResults(height *int64) (*ctypes.ResultBlockResults, error) { 272 res, err := c.next.BlockResults(height) 273 if err != nil { 274 return nil, err 275 } 276 277 // Validate res. 278 if res.Height <= 0 { 279 return nil, errNegOrZeroHeight 280 } 281 282 // Update the light client if we're behind. 283 h, err := c.updateLiteClientIfNeededTo(res.Height + 1) 284 if err != nil { 285 return nil, err 286 } 287 288 // Verify block results. 289 results := types.NewResults(res.TxsResults) 290 if rH, tH := results.Hash(), h.LastResultsHash; !bytes.Equal(rH, tH) { 291 return nil, fmt.Errorf("last results %X does not match with trusted last results %X", 292 rH, tH) 293 } 294 295 return res, nil 296 } 297 298 func (c *Client) Commit(height *int64) (*ctypes.ResultCommit, error) { 299 res, err := c.next.Commit(height) 300 if err != nil { 301 return nil, err 302 } 303 304 // Validate res. 305 if err := res.SignedHeader.ValidateBasic(c.lc.ChainID()); err != nil { 306 return nil, err 307 } 308 if res.Height <= 0 { 309 return nil, errNegOrZeroHeight 310 } 311 312 // Update the light client if we're behind. 313 h, err := c.updateLiteClientIfNeededTo(res.Height) 314 if err != nil { 315 return nil, err 316 } 317 318 // Verify commit. 319 if rH, tH := res.Hash(), h.Hash(); !bytes.Equal(rH, tH) { 320 return nil, fmt.Errorf("header %X does not match with trusted header %X", 321 rH, tH) 322 } 323 324 return res, nil 325 } 326 327 // Tx calls rpcclient#Tx method and then verifies the proof if such was 328 // requested. 329 func (c *Client) Tx(hash []byte, prove bool) (*ctypes.ResultTx, error) { 330 res, err := c.next.Tx(hash, prove) 331 if err != nil || !prove { 332 return res, err 333 } 334 335 // Validate res. 336 if res.Height <= 0 { 337 return nil, errNegOrZeroHeight 338 } 339 340 // Update the light client if we're behind. 341 h, err := c.updateLiteClientIfNeededTo(res.Height) 342 if err != nil { 343 return nil, err 344 } 345 346 // Validate the proof. 347 return res, res.Proof.Validate(h.DataHash) 348 } 349 350 func (c *Client) TxSearch(query string, prove bool, page, perPage int, orderBy string) ( 351 *ctypes.ResultTxSearch, error) { 352 return c.next.TxSearch(query, prove, page, perPage, orderBy) 353 } 354 355 // Validators fetches and verifies validators. 356 // 357 // WARNING: only full validator sets are verified (when length of validators is 358 // less than +perPage+. +perPage+ default is 30, max is 100). 359 func (c *Client) Validators(height *int64, page, perPage int) (*ctypes.ResultValidators, error) { 360 res, err := c.next.Validators(height, page, perPage) 361 if err != nil { 362 return nil, err 363 } 364 365 // Validate res. 366 if res.BlockHeight <= 0 { 367 return nil, errNegOrZeroHeight 368 } 369 370 // Update the light client if we're behind. 371 h, err := c.updateLiteClientIfNeededTo(res.BlockHeight) 372 if err != nil { 373 return nil, err 374 } 375 376 // Verify validators. 377 if res.Count <= res.Total { 378 if rH, tH := types.NewValidatorSet(res.Validators).Hash(), h.ValidatorsHash; !bytes.Equal(rH, tH) { 379 return nil, fmt.Errorf("validators %X does not match with trusted validators %X", 380 rH, tH) 381 } 382 } 383 384 return res, nil 385 } 386 387 func (c *Client) BroadcastEvidence(ev types.Evidence) (*ctypes.ResultBroadcastEvidence, error) { 388 return c.next.BroadcastEvidence(ev) 389 } 390 391 func (c *Client) Subscribe(ctx context.Context, subscriber, query string, 392 outCapacity ...int) (out <-chan ctypes.ResultEvent, err error) { 393 return c.next.Subscribe(ctx, subscriber, query, outCapacity...) 394 } 395 396 func (c *Client) Unsubscribe(ctx context.Context, subscriber, query string) error { 397 return c.next.Unsubscribe(ctx, subscriber, query) 398 } 399 400 func (c *Client) UnsubscribeAll(ctx context.Context, subscriber string) error { 401 return c.next.UnsubscribeAll(ctx, subscriber) 402 } 403 404 func (c *Client) updateLiteClientIfNeededTo(height int64) (*types.SignedHeader, error) { 405 h, err := c.lc.VerifyHeaderAtHeight(height, time.Now()) 406 if err != nil { 407 return nil, fmt.Errorf("failed to update light client to %d: %w", height, err) 408 } 409 return h, nil 410 } 411 412 func (c *Client) RegisterOpDecoder(typ string, dec merkle.OpDecoder) { 413 c.prt.RegisterOpDecoder(typ, dec) 414 } 415 416 // SubscribeWS subscribes for events using the given query and remote address as 417 // a subscriber, but does not verify responses (UNSAFE)! 418 // TODO: verify data 419 func (c *Client) SubscribeWS(ctx *rpctypes.Context, query string) (*ctypes.ResultSubscribe, error) { 420 out, err := c.next.Subscribe(context.Background(), ctx.RemoteAddr(), query) 421 if err != nil { 422 return nil, err 423 } 424 425 go func() { 426 for { 427 select { 428 case resultEvent := <-out: 429 // We should have a switch here that performs a validation 430 // depending on the event's type. 431 ctx.WSConn.TryWriteRPCResponse( 432 rpctypes.NewRPCSuccessResponse( 433 ctx.WSConn.Codec(), 434 rpctypes.JSONRPCStringID(fmt.Sprintf("%v#event", ctx.JSONReq.ID)), 435 resultEvent, 436 )) 437 case <-c.Quit(): 438 return 439 } 440 } 441 }() 442 443 return &ctypes.ResultSubscribe{}, nil 444 } 445 446 // UnsubscribeWS calls original client's Unsubscribe using remote address as a 447 // subscriber. 448 func (c *Client) UnsubscribeWS(ctx *rpctypes.Context, query string) (*ctypes.ResultUnsubscribe, error) { 449 err := c.next.Unsubscribe(context.Background(), ctx.RemoteAddr(), query) 450 if err != nil { 451 return nil, err 452 } 453 return &ctypes.ResultUnsubscribe{}, nil 454 } 455 456 // UnsubscribeAllWS calls original client's UnsubscribeAll using remote address 457 // as a subscriber. 458 func (c *Client) UnsubscribeAllWS(ctx *rpctypes.Context) (*ctypes.ResultUnsubscribe, error) { 459 err := c.next.UnsubscribeAll(context.Background(), ctx.RemoteAddr()) 460 if err != nil { 461 return nil, err 462 } 463 return &ctypes.ResultUnsubscribe{}, nil 464 } 465 466 func parseQueryStorePath(path string) (storeName string, err error) { 467 if !strings.HasPrefix(path, "/") { 468 return "", errors.New("expected path to start with /") 469 } 470 471 paths := strings.SplitN(path[1:], "/", 3) 472 switch { 473 case len(paths) != 3: 474 return "", errors.New("expected format like /store/<storeName>/key") 475 case paths[0] != "store": 476 return "", errors.New("expected format like /store/<storeName>/key") 477 case paths[2] != "key": 478 return "", errors.New("expected format like /store/<storeName>/key") 479 } 480 481 return paths[1], nil 482 }