github.com/okex/exchain@v1.8.0/libs/tendermint/lite/proxy/wrapper.go (about) 1 package proxy 2 3 import ( 4 "context" 5 6 "github.com/okex/exchain/libs/tendermint/crypto/merkle" 7 "github.com/okex/exchain/libs/tendermint/libs/bytes" 8 "github.com/okex/exchain/libs/tendermint/lite" 9 rpcclient "github.com/okex/exchain/libs/tendermint/rpc/client" 10 ctypes "github.com/okex/exchain/libs/tendermint/rpc/core/types" 11 rpctypes "github.com/okex/exchain/libs/tendermint/rpc/jsonrpc/types" 12 ) 13 14 var _ rpcclient.Client = Wrapper{} 15 16 // Wrapper wraps a rpcclient with a Verifier and double-checks any input that is 17 // provable before passing it along. Allows you to make any rpcclient fully secure. 18 type Wrapper struct { 19 rpcclient.Client 20 cert *lite.DynamicVerifier 21 prt *merkle.ProofRuntime 22 } 23 24 // SecureClient uses a given Verifier to wrap an connection to an untrusted 25 // host and return a cryptographically secure rpc client. 26 // 27 // If it is wrapping an HTTP rpcclient, it will also wrap the websocket interface 28 func SecureClient(c rpcclient.Client, cert *lite.DynamicVerifier) Wrapper { 29 prt := defaultProofRuntime() 30 wrap := Wrapper{c, cert, prt} 31 // TODO: no longer possible as no more such interface exposed.... 32 // if we wrap http client, then we can swap out the event switch to filter 33 // if hc, ok := c.(*rpcclient.HTTP); ok { 34 // evt := hc.WSEvents.EventSwitch 35 // hc.WSEvents.EventSwitch = WrappedSwitch{evt, wrap} 36 // } 37 return wrap 38 } 39 40 // ABCIQueryWithOptions exposes all options for the ABCI query and verifies the returned proof 41 func (w Wrapper) ABCIQueryWithOptions(path string, data bytes.HexBytes, 42 opts rpcclient.ABCIQueryOptions) (*ctypes.ResultABCIQuery, error) { 43 44 res, err := GetWithProofOptions(w.prt, path, data, opts, w.Client, w.cert) 45 return res, err 46 } 47 48 // ABCIQuery uses default options for the ABCI query and verifies the returned proof 49 func (w Wrapper) ABCIQuery(path string, data bytes.HexBytes) (*ctypes.ResultABCIQuery, error) { 50 return w.ABCIQueryWithOptions(path, data, rpcclient.DefaultABCIQueryOptions) 51 } 52 53 // Tx queries for a given tx and verifies the proof if it was requested 54 func (w Wrapper) Tx(hash []byte, prove bool) (*ctypes.ResultTx, error) { 55 res, err := w.Client.Tx(hash, prove) 56 if !prove || err != nil { 57 return res, err 58 } 59 h := res.Height 60 sh, err := GetCertifiedCommit(h, w.Client, w.cert) 61 if err != nil { 62 return res, err 63 } 64 err = res.Proof.Validate(sh.DataHash, res.Height) 65 return res, err 66 } 67 68 // BlockchainInfo requests a list of headers and verifies them all... 69 // Rather expensive. 70 // 71 // TODO: optimize this if used for anything needing performance 72 func (w Wrapper) BlockchainInfo(minHeight, maxHeight int64) (*ctypes.ResultBlockchainInfo, error) { 73 r, err := w.Client.BlockchainInfo(minHeight, maxHeight) 74 if err != nil { 75 return nil, err 76 } 77 78 // go and verify every blockmeta in the result.... 79 for _, meta := range r.BlockMetas { 80 // get a checkpoint to verify from 81 res, err := w.Commit(&meta.Header.Height) 82 if err != nil { 83 return nil, err 84 } 85 sh := res.SignedHeader 86 err = ValidateBlockMeta(meta, sh) 87 if err != nil { 88 return nil, err 89 } 90 } 91 92 return r, nil 93 } 94 95 func (w Wrapper) LatestBlockNumber() (int64, error) { 96 info, err := w.BlockchainInfo(0, 0) 97 if err != nil { 98 return 0, err 99 } 100 return info.LastHeight, nil 101 } 102 103 // Block returns an entire block and verifies all signatures 104 func (w Wrapper) Block(height *int64) (*ctypes.ResultBlock, error) { 105 resBlock, err := w.Client.Block(height) 106 if err != nil { 107 return nil, err 108 } 109 // get a checkpoint to verify from 110 resCommit, err := w.Commit(height) 111 if err != nil { 112 return nil, err 113 } 114 sh := resCommit.SignedHeader 115 116 err = ValidateBlock(resBlock.Block, sh) 117 if err != nil { 118 return nil, err 119 } 120 return resBlock, nil 121 } 122 123 // Commit downloads the Commit and certifies it with the lite. 124 // 125 // This is the foundation for all other verification in this module 126 func (w Wrapper) Commit(height *int64) (*ctypes.ResultCommit, error) { 127 if height == nil { 128 resStatus, err := w.Client.Status() 129 if err != nil { 130 return nil, err 131 } 132 // NOTE: If resStatus.CatchingUp, there is a race 133 // condition where the validator set for the next height 134 // isn't available until some time after the blockstore 135 // has height h on the remote node. This isn't an issue 136 // once the node has caught up, and a syncing node likely 137 // won't have this issue esp with the implementation we 138 // have here, but we may have to address this at some 139 // point. 140 height = new(int64) 141 *height = resStatus.SyncInfo.LatestBlockHeight 142 } 143 rpcclient.WaitForHeight(w.Client, *height, nil) 144 res, err := w.Client.Commit(height) 145 // if we got it, then verify it 146 if err == nil { 147 sh := res.SignedHeader 148 err = w.cert.Verify(sh) 149 } 150 return res, err 151 } 152 153 func (w Wrapper) RegisterOpDecoder(typ string, dec merkle.OpDecoder) { 154 w.prt.RegisterOpDecoder(typ, dec) 155 } 156 157 // SubscribeWS subscribes for events using the given query and remote address as 158 // a subscriber, but does not verify responses (UNSAFE)! 159 func (w Wrapper) SubscribeWS(ctx *rpctypes.Context, query string) (*ctypes.ResultSubscribe, error) { 160 out, err := w.Client.Subscribe(context.Background(), ctx.RemoteAddr(), query) 161 if err != nil { 162 return nil, err 163 } 164 165 go func() { 166 for { 167 select { 168 case resultEvent := <-out: 169 // XXX(melekes) We should have a switch here that performs a validation 170 // depending on the event's type. 171 ctx.WSConn.TryWriteRPCResponse( 172 rpctypes.NewRPCSuccessResponse( 173 ctx.WSConn.Codec(), 174 ctx.JSONReq.ID, 175 resultEvent, 176 )) 177 case <-w.Client.Quit(): 178 return 179 } 180 } 181 }() 182 183 return &ctypes.ResultSubscribe{}, nil 184 } 185 186 // UnsubscribeWS calls original client's Unsubscribe using remote address as a 187 // subscriber. 188 func (w Wrapper) UnsubscribeWS(ctx *rpctypes.Context, query string) (*ctypes.ResultUnsubscribe, error) { 189 err := w.Client.Unsubscribe(context.Background(), ctx.RemoteAddr(), query) 190 if err != nil { 191 return nil, err 192 } 193 return &ctypes.ResultUnsubscribe{}, nil 194 } 195 196 // UnsubscribeAllWS calls original client's UnsubscribeAll using remote address 197 // as a subscriber. 198 func (w Wrapper) UnsubscribeAllWS(ctx *rpctypes.Context) (*ctypes.ResultUnsubscribe, error) { 199 err := w.Client.UnsubscribeAll(context.Background(), ctx.RemoteAddr()) 200 if err != nil { 201 return nil, err 202 } 203 return &ctypes.ResultUnsubscribe{}, nil 204 } 205 206 // // WrappedSwitch creates a websocket connection that auto-verifies any info 207 // // coming through before passing it along. 208 // // 209 // // Since the verification takes 1-2 rpc calls, this is obviously only for 210 // // relatively low-throughput situations that can tolerate a bit extra latency 211 // type WrappedSwitch struct { 212 // types.EventSwitch 213 // client rpcclient.Client 214 // } 215 216 // // FireEvent verifies any block or header returned from the eventswitch 217 // func (s WrappedSwitch) FireEvent(event string, data events.EventData) { 218 // tm, ok := data.(types.TMEventData) 219 // if !ok { 220 // fmt.Printf("bad type %#v\n", data) 221 // return 222 // } 223 224 // // check to validate it if possible, and drop if not valid 225 // switch t := tm.(type) { 226 // case types.EventDataNewBlockHeader: 227 // err := verifyHeader(s.client, t.Header) 228 // if err != nil { 229 // fmt.Printf("Invalid header: %#v\n", err) 230 // return 231 // } 232 // case types.EventDataNewBlock: 233 // err := verifyBlock(s.client, t.Block) 234 // if err != nil { 235 // fmt.Printf("Invalid block: %#v\n", err) 236 // return 237 // } 238 // // TODO: can we verify tx as well? anything else 239 // } 240 241 // // looks good, we fire it 242 // s.EventSwitch.FireEvent(event, data) 243 // } 244 245 // func verifyHeader(c rpcclient.Client, head *types.Header) error { 246 // // get a checkpoint to verify from 247 // commit, err := c.Commit(&head.Height) 248 // if err != nil { 249 // return err 250 // } 251 // check := certclient.CommitFromResult(commit) 252 // return ValidateHeader(head, check) 253 // } 254 // 255 // func verifyBlock(c rpcclient.Client, block *types.Block) error { 256 // // get a checkpoint to verify from 257 // commit, err := c.Commit(&block.Height) 258 // if err != nil { 259 // return err 260 // } 261 // check := certclient.CommitFromResult(commit) 262 // return ValidateBlock(block, check) 263 // }