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