github.com/evdatsion/aphelion-dpos-bft@v0.32.1/lite/proxy/query.go (about) 1 package proxy 2 3 import ( 4 "fmt" 5 "strings" 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 lerr "github.com/evdatsion/aphelion-dpos-bft/lite/errors" 12 rpcclient "github.com/evdatsion/aphelion-dpos-bft/rpc/client" 13 ctypes "github.com/evdatsion/aphelion-dpos-bft/rpc/core/types" 14 "github.com/evdatsion/aphelion-dpos-bft/types" 15 ) 16 17 // GetWithProof will query the key on the given node, and verify it has 18 // a valid proof, as defined by the Verifier. 19 // 20 // If there is any error in checking, returns an error. 21 func GetWithProof(prt *merkle.ProofRuntime, key []byte, reqHeight int64, node rpcclient.Client, 22 cert lite.Verifier) ( 23 val cmn.HexBytes, height int64, proof *merkle.Proof, err error) { 24 25 if reqHeight < 0 { 26 err = cmn.NewError("Height cannot be negative") 27 return 28 } 29 30 res, err := GetWithProofOptions(prt, "/key", key, 31 rpcclient.ABCIQueryOptions{Height: int64(reqHeight), Prove: true}, 32 node, cert) 33 if err != nil { 34 return 35 } 36 37 resp := res.Response 38 val, height = resp.Value, resp.Height 39 return val, height, proof, err 40 } 41 42 // GetWithProofOptions is useful if you want full access to the ABCIQueryOptions. 43 // XXX Usage of path? It's not used, and sometimes it's /, sometimes /key, sometimes /store. 44 func GetWithProofOptions(prt *merkle.ProofRuntime, path string, key []byte, opts rpcclient.ABCIQueryOptions, 45 node rpcclient.Client, cert lite.Verifier) ( 46 *ctypes.ResultABCIQuery, error) { 47 opts.Prove = true 48 res, err := node.ABCIQueryWithOptions(path, key, opts) 49 if err != nil { 50 return nil, err 51 } 52 resp := res.Response 53 54 // Validate the response, e.g. height. 55 if resp.IsErr() { 56 err = cmn.NewError("Query error for key %d: %d", key, resp.Code) 57 return nil, err 58 } 59 60 if len(resp.Key) == 0 || resp.Proof == nil { 61 return nil, lerr.ErrEmptyTree() 62 } 63 if resp.Height == 0 { 64 return nil, cmn.NewError("Height returned is zero") 65 } 66 67 // AppHash for height H is in header H+1 68 signedHeader, err := GetCertifiedCommit(resp.Height+1, node, cert) 69 if err != nil { 70 return nil, err 71 } 72 73 // Validate the proof against the certified header to ensure data integrity. 74 if resp.Value != nil { 75 // Value exists 76 // XXX How do we encode the key into a string... 77 storeName, err := parseQueryStorePath(path) 78 if err != nil { 79 return nil, err 80 } 81 kp := merkle.KeyPath{} 82 kp = kp.AppendKey([]byte(storeName), merkle.KeyEncodingURL) 83 kp = kp.AppendKey(resp.Key, merkle.KeyEncodingURL) 84 err = prt.VerifyValue(resp.Proof, signedHeader.AppHash, kp.String(), resp.Value) 85 if err != nil { 86 return nil, cmn.ErrorWrap(err, "Couldn't verify value proof") 87 } 88 return &ctypes.ResultABCIQuery{Response: resp}, nil 89 } else { 90 // Value absent 91 // Validate the proof against the certified header to ensure data integrity. 92 // XXX How do we encode the key into a string... 93 err = prt.VerifyAbsence(resp.Proof, signedHeader.AppHash, string(resp.Key)) 94 if err != nil { 95 return nil, cmn.ErrorWrap(err, "Couldn't verify absence proof") 96 } 97 return &ctypes.ResultABCIQuery{Response: resp}, nil 98 } 99 } 100 101 func parseQueryStorePath(path string) (storeName string, err error) { 102 if !strings.HasPrefix(path, "/") { 103 return "", fmt.Errorf("expected path to start with /") 104 } 105 106 paths := strings.SplitN(path[1:], "/", 3) 107 switch { 108 case len(paths) != 3: 109 return "", fmt.Errorf("expected format like /store/<storeName>/key") 110 case paths[0] != "store": 111 return "", fmt.Errorf("expected format like /store/<storeName>/key") 112 case paths[2] != "key": 113 return "", fmt.Errorf("expected format like /store/<storeName>/key") 114 } 115 116 return paths[1], nil 117 } 118 119 // GetCertifiedCommit gets the signed header for a given height and certifies 120 // it. Returns error if unable to get a proven header. 121 func GetCertifiedCommit(h int64, client rpcclient.Client, cert lite.Verifier) (types.SignedHeader, error) { 122 123 // FIXME: cannot use cert.GetByHeight for now, as it also requires 124 // Validators and will fail on querying tendermint for non-current height. 125 // When this is supported, we should use it instead... 126 rpcclient.WaitForHeight(client, h, nil) 127 cresp, err := client.Commit(&h) 128 if err != nil { 129 return types.SignedHeader{}, err 130 } 131 132 // Validate downloaded checkpoint with our request and trust store. 133 sh := cresp.SignedHeader 134 if sh.Height != h { 135 return types.SignedHeader{}, fmt.Errorf("height mismatch: want %v got %v", 136 h, sh.Height) 137 } 138 139 if err = cert.Verify(sh); err != nil { 140 return types.SignedHeader{}, err 141 } 142 143 return sh, nil 144 }