/
querier.go
158 lines (137 loc) · 4.96 KB
/
querier.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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
package proof
import (
"bytes"
"fmt"
"strconv"
"github.com/celestiaorg/celestia-app/pkg/appconsts"
"github.com/celestiaorg/celestia-app/pkg/shares"
"github.com/celestiaorg/celestia-app/pkg/square"
appns "github.com/celestiaorg/celestia-app/pkg/namespace"
sdk "github.com/cosmos/cosmos-sdk/types"
abci "github.com/tendermint/tendermint/abci/types"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
"github.com/tendermint/tendermint/types"
)
const TxInclusionQueryPath = "txInclusionProof"
// Querier defines the logic performed when the ABCI client using the Query
// method with the custom prove.QueryPath. The index of the transaction being
// proved must be appended to the path. The marshalled bytes of the transaction
// proof (tmproto.ShareProof) are returned.
//
// example path for proving the third transaction in that block:
// custom/txInclusionProof/3
func QueryTxInclusionProof(_ sdk.Context, path []string, req abci.RequestQuery) ([]byte, error) {
// parse the index from the path
if len(path) != 1 {
return nil, fmt.Errorf("expected query path length: 1 actual: %d ", len(path))
}
index, err := strconv.ParseInt(path[0], 10, 64)
if err != nil {
return nil, err
}
if index < 0 {
return nil, fmt.Errorf("path[0] element: %q produced a negative value: %d", path[0], index)
}
// unmarshal the block data that is passed from the ABCI client
pbb := new(tmproto.Block)
err = pbb.Unmarshal(req.Data)
if err != nil {
return nil, fmt.Errorf("error reading block: %w", err)
}
data, err := types.DataFromProto(&pbb.Data)
if err != nil {
panic(fmt.Errorf("error from proto block: %w", err))
}
// create and marshal the tx inclusion proof, which we return in the form of []byte
shareProof, err := NewTxInclusionProof(data.Txs.ToSliceOfBytes(), uint64(index), pbb.Header.Version.App)
if err != nil {
return nil, err
}
pShareProof := shareProof.ToProto()
rawShareProof, err := pShareProof.Marshal()
if err != nil {
return nil, err
}
return rawShareProof, nil
}
const ShareInclusionQueryPath = "shareInclusionProof"
// QueryShareInclusionProof defines the logic performed when querying for the
// inclusion proofs of a set of shares to the data root. The share range should
// be appended to the path. Example path for proving the set of shares [3, 5]:
// custom/shareInclusionProof/3/5
func QueryShareInclusionProof(_ sdk.Context, path []string, req abci.RequestQuery) ([]byte, error) {
// parse the share range from the path
if len(path) != 2 {
return nil, fmt.Errorf("expected query path length: 2 actual: %d ", len(path))
}
beginShare, err := strconv.ParseInt(path[0], 10, 64)
if err != nil {
return nil, err
}
endShare, err := strconv.ParseInt(path[1], 10, 64)
if err != nil {
return nil, err
}
// unmarshal the block data that is passed from the ABCI client
pbb := new(tmproto.Block)
err = pbb.Unmarshal(req.Data)
if err != nil {
return nil, fmt.Errorf("error reading block: %w", err)
}
// construct the data square from the block data. As we don't have
// access to the application's state machine we use the upper bound
// square size instead of the square size dictated from governance
dataSquare, err := square.Construct(pbb.Data.Txs, pbb.Header.Version.App, appconsts.SquareSizeUpperBound(pbb.Header.Version.App))
if err != nil {
return nil, err
}
nID, err := ParseNamespace(dataSquare, int(beginShare), int(endShare))
if err != nil {
return nil, err
}
// create and marshal the share inclusion proof, which we return in the form of []byte
shareProof, err := NewShareInclusionProof(
dataSquare,
nID,
shares.NewRange(int(beginShare), int(endShare)),
)
if err != nil {
return nil, err
}
pShareProof := shareProof.ToProto()
rawShareProof, err := pShareProof.Marshal()
if err != nil {
return nil, err
}
return rawShareProof, nil
}
// ParseNamespace validates the share range, checks if it only contains one namespace and returns
// that namespace ID.
func ParseNamespace(rawShares []shares.Share, startShare, endShare int) (appns.Namespace, error) {
if startShare < 0 {
return appns.Namespace{}, fmt.Errorf("start share %d should be positive", startShare)
}
if endShare < 0 {
return appns.Namespace{}, fmt.Errorf("end share %d should be positive", endShare)
}
if endShare < startShare {
return appns.Namespace{}, fmt.Errorf("end share %d cannot be lower than starting share %d", endShare, startShare)
}
if endShare >= len(rawShares) {
return appns.Namespace{}, fmt.Errorf("end share %d is higher than block shares %d", endShare, len(rawShares))
}
startShareNs, err := rawShares[startShare].Namespace()
if err != nil {
return appns.Namespace{}, err
}
for i, share := range rawShares[startShare:endShare] {
ns, err := share.Namespace()
if err != nil {
return appns.Namespace{}, err
}
if !bytes.Equal(startShareNs.Bytes(), ns.Bytes()) {
return appns.Namespace{}, fmt.Errorf("shares range contain different namespaces at index %d: %v and %v ", i, startShareNs, ns)
}
}
return startShareNs, nil
}