-
Notifications
You must be signed in to change notification settings - Fork 8.9k
/
query.go
228 lines (186 loc) · 6.82 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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package qscc
import (
"fmt"
"strconv"
"github.com/hyperledger/fabric-chaincode-go/shim"
pb "github.com/hyperledger/fabric-protos-go/peer"
"github.com/hyperledger/fabric/common/flogging"
"github.com/hyperledger/fabric/core/aclmgmt"
"github.com/hyperledger/fabric/core/ledger"
"github.com/hyperledger/fabric/protoutil"
)
// LedgerGetter gets the PeerLedger associated with a channel.
type LedgerGetter interface {
GetLedger(cid string) ledger.PeerLedger
}
// New returns an instance of QSCC.
// Typically this is called once per peer.
func New(aclProvider aclmgmt.ACLProvider, ledgers LedgerGetter) *LedgerQuerier {
return &LedgerQuerier{
aclProvider: aclProvider,
ledgers: ledgers,
}
}
func (e *LedgerQuerier) Name() string { return "qscc" }
func (e *LedgerQuerier) Chaincode() shim.Chaincode { return e }
// LedgerQuerier implements the ledger query functions, including:
// - GetChainInfo returns BlockchainInfo
// - GetBlockByNumber returns a block
// - GetBlockByHash returns a block
// - GetTransactionByID returns a transaction
type LedgerQuerier struct {
aclProvider aclmgmt.ACLProvider
ledgers LedgerGetter
}
var qscclogger = flogging.MustGetLogger("qscc")
// These are function names from Invoke first parameter
const (
GetChainInfo string = "GetChainInfo"
GetBlockByNumber string = "GetBlockByNumber"
GetBlockByHash string = "GetBlockByHash"
GetTransactionByID string = "GetTransactionByID"
GetBlockByTxID string = "GetBlockByTxID"
)
// Init is called once per chain when the chain is created.
// This allows the chaincode to initialize any variables on the ledger prior
// to any transaction execution on the chain.
func (e *LedgerQuerier) Init(stub shim.ChaincodeStubInterface) pb.Response {
qscclogger.Info("Init QSCC")
return shim.Success(nil)
}
// Invoke is called with args[0] contains the query function name, args[1]
// contains the chain ID, which is temporary for now until it is part of stub.
// Each function requires additional parameters as described below:
// # GetChainInfo: Return a BlockchainInfo object marshalled in bytes
// # GetBlockByNumber: Return the block specified by block number in args[2]
// # GetBlockByHash: Return the block specified by block hash in args[2]
// # GetTransactionByID: Return the transaction specified by ID in args[2]
func (e *LedgerQuerier) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
args := stub.GetArgs()
if len(args) < 2 {
return shim.Error(fmt.Sprintf("Incorrect number of arguments, %d", len(args)))
}
fname := string(args[0])
cid := string(args[1])
sp, err := stub.GetSignedProposal()
if err != nil {
return shim.Error(fmt.Sprintf("Failed getting signed proposal from stub, %s: %s", cid, err))
}
name, err := protoutil.InvokedChaincodeName(sp.ProposalBytes)
if err != nil {
return shim.Error(fmt.Sprintf("Failed to identify the called chaincode: %s", err))
}
if name != e.Name() {
return shim.Error(fmt.Sprintf("Rejecting invoke of QSCC from another chaincode because of potential for deadlocks, original invocation for '%s'", name))
}
if fname != GetChainInfo && len(args) < 3 {
return shim.Error(fmt.Sprintf("missing 3rd argument for %s", fname))
}
targetLedger := e.ledgers.GetLedger(cid)
if targetLedger == nil {
return shim.Error(fmt.Sprintf("Invalid chain ID, %s", cid))
}
qscclogger.Debugf("Invoke function: %s on chain: %s", fname, cid)
// Handle ACL:
res := getACLResource(fname)
if err = e.aclProvider.CheckACL(res, cid, sp); err != nil {
return shim.Error(fmt.Sprintf("access denied for [%s][%s]: [%s]", fname, cid, err))
}
switch fname {
case GetTransactionByID:
return getTransactionByID(targetLedger, args[2])
case GetBlockByNumber:
return getBlockByNumber(targetLedger, args[2])
case GetBlockByHash:
return getBlockByHash(targetLedger, args[2])
case GetChainInfo:
return getChainInfo(targetLedger)
case GetBlockByTxID:
return getBlockByTxID(targetLedger, args[2])
}
return shim.Error(fmt.Sprintf("Requested function %s not found.", fname))
}
func getTransactionByID(vledger ledger.PeerLedger, tid []byte) pb.Response {
if tid == nil {
return shim.Error("Transaction ID must not be nil.")
}
processedTran, err := vledger.GetTransactionByID(string(tid))
if err != nil {
return shim.Error(fmt.Sprintf("Failed to get transaction with id %s, error %s", string(tid), err))
}
bytes, err := protoutil.Marshal(processedTran)
if err != nil {
return shim.Error(err.Error())
}
return shim.Success(bytes)
}
func getBlockByNumber(vledger ledger.PeerLedger, number []byte) pb.Response {
if number == nil {
return shim.Error("Block number must not be nil.")
}
bnum, err := strconv.ParseUint(string(number), 10, 64)
if err != nil {
return shim.Error(fmt.Sprintf("Failed to parse block number with error %s", err))
}
block, err := vledger.GetBlockByNumber(bnum)
if err != nil {
return shim.Error(fmt.Sprintf("Failed to get block number %d, error %s", bnum, err))
}
// TODO: consider trim block content before returning
// Specifically, trim transaction 'data' out of the transaction array Payloads
// This will preserve the transaction Payload header,
// and client can do GetTransactionByID() if they want the full transaction details
bytes, err := protoutil.Marshal(block)
if err != nil {
return shim.Error(err.Error())
}
return shim.Success(bytes)
}
func getBlockByHash(vledger ledger.PeerLedger, hash []byte) pb.Response {
if hash == nil {
return shim.Error("Block hash must not be nil.")
}
block, err := vledger.GetBlockByHash(hash)
if err != nil {
return shim.Error(fmt.Sprintf("Failed to get block hash %s, error %s", string(hash), err))
}
// TODO: consider trim block content before returning
// Specifically, trim transaction 'data' out of the transaction array Payloads
// This will preserve the transaction Payload header,
// and client can do GetTransactionByID() if they want the full transaction details
bytes, err := protoutil.Marshal(block)
if err != nil {
return shim.Error(err.Error())
}
return shim.Success(bytes)
}
func getChainInfo(vledger ledger.PeerLedger) pb.Response {
binfo, err := vledger.GetBlockchainInfo()
if err != nil {
return shim.Error(fmt.Sprintf("Failed to get block info with error %s", err))
}
bytes, err := protoutil.Marshal(binfo)
if err != nil {
return shim.Error(err.Error())
}
return shim.Success(bytes)
}
func getBlockByTxID(vledger ledger.PeerLedger, rawTxID []byte) pb.Response {
txID := string(rawTxID)
block, err := vledger.GetBlockByTxID(txID)
if err != nil {
return shim.Error(fmt.Sprintf("Failed to get block for txID %s, error %s", txID, err))
}
bytes, err := protoutil.Marshal(block)
if err != nil {
return shim.Error(err.Error())
}
return shim.Success(bytes)
}
func getACLResource(fname string) string {
return "qscc/" + fname
}