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