querier.go 8.68 KB
Newer Older
Binh Q. Nguyen's avatar
Binh Q. Nguyen committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*
Copyright IBM Corp. 2017 All Rights Reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

		 http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

17
package qscc
Binh Q. Nguyen's avatar
Binh Q. Nguyen committed
18
19
20
21
22
23
24
25
26

import (
	"bytes"
	"fmt"
	"strconv"

	"github.com/op/go-logging"
	"github.com/spf13/viper"

27
	commonledger "github.com/hyperledger/fabric/common/ledger"
Binh Q. Nguyen's avatar
Binh Q. Nguyen committed
28
29
30
	"github.com/hyperledger/fabric/core/chaincode/shim"
	"github.com/hyperledger/fabric/core/ledger"
	"github.com/hyperledger/fabric/core/peer"
31
	pb "github.com/hyperledger/fabric/protos/peer"
Binh Q. Nguyen's avatar
Binh Q. Nguyen committed
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
	"github.com/hyperledger/fabric/protos/utils"
)

// LedgerQuerier implements the ledger query functions, including:
// - GetChainInfo returns BlockchainInfo
// - GetBlockByNumber returns a block
// - GetBlockByHash returns a block
// - GetTransactionByID returns a transaction
// - GetQueryResult returns result of a freeform query
type LedgerQuerier struct {
}

var qscclogger = logging.MustGetLogger("qscc")

// These are function names from Invoke first parameter
const (
	GetChainInfo       string = "GetChainInfo"
	GetBlockByNumber   string = "GetBlockByNumber"
	GetBlockByHash     string = "GetBlockByHash"
	GetTransactionByID string = "GetTransactionByID"
	GetQueryResult     string = "GetQueryResult"
)

// 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.
58
func (e *LedgerQuerier) Init(stub shim.ChaincodeStubInterface) pb.Response {
Binh Q. Nguyen's avatar
Binh Q. Nguyen committed
59
60
	qscclogger.Info("Init QSCC")

61
	return shim.Success(nil)
Binh Q. Nguyen's avatar
Binh Q. Nguyen committed
62
63
64
65
66
67
68
69
70
71
72
73
74
75
}

// 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]
// # GetQueryResult: Return the result of executing the specified native
// query string in args[2]. Note that this only works if plugged in database
// supports it. The result is a JSON array in a byte array. Note that error
// may be returned together with a valid partial result as error might occur
// during accummulating records from the ledger
76
func (e *LedgerQuerier) Invoke(stub shim.ChaincodeStubInterface) pb.Response {
Binh Q. Nguyen's avatar
Binh Q. Nguyen committed
77
78
79
	args := stub.GetArgs()

	if len(args) < 2 {
80
		return shim.Error(fmt.Sprintf("Incorrect number of arguments, %d", len(args)))
Binh Q. Nguyen's avatar
Binh Q. Nguyen committed
81
82
83
84
85
	}
	fname := string(args[0])
	cid := string(args[1])

	if fname != GetChainInfo && len(args) < 3 {
86
		return shim.Error(fmt.Sprintf("missing 3rd argument for %s", fname))
Binh Q. Nguyen's avatar
Binh Q. Nguyen committed
87
88
89
90
	}

	targetLedger := peer.GetLedger(cid)
	if targetLedger == nil {
91
		return shim.Error(fmt.Sprintf("Invalid chain ID, %s", cid))
Binh Q. Nguyen's avatar
Binh Q. Nguyen committed
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
	}
	if qscclogger.IsEnabledFor(logging.DEBUG) {
		qscclogger.Debugf("Invoke function: %s on chain: %s", fname, cid)
	}

	// TODO: Handle ACL

	switch fname {
	case GetQueryResult:
		return getQueryResult(targetLedger, args[2])
	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)
	}

112
	return shim.Error(fmt.Sprintf("Requested function %s not found.", fname))
Binh Q. Nguyen's avatar
Binh Q. Nguyen committed
113
114
115
}

// Execute the specified query string
116
func getQueryResult(vledger ledger.PeerLedger, query []byte) (res pb.Response) {
Binh Q. Nguyen's avatar
Binh Q. Nguyen committed
117
	if query == nil {
118
		return shim.Error("Query string must not be nil.")
Binh Q. Nguyen's avatar
Binh Q. Nguyen committed
119
120
121
	}
	qstring := string(query)
	var qexe ledger.QueryExecutor
122
	var ri commonledger.ResultsIterator
123
	var err error
Binh Q. Nguyen's avatar
Binh Q. Nguyen committed
124
125
126
127
128
129
130
131
132
133
134

	// We install a recover() to gain control in 2 cases
	// 1) bytes.Buffer panics, which happens when out of memory
	// This is a safety measure beyond the config limit variable
	// 2) plugin db driver might panic
	// We recover by stopping the query and return the panic error.
	defer func() {
		if panicValue := recover(); panicValue != nil {
			if qscclogger.IsEnabledFor(logging.DEBUG) {
				qscclogger.Debugf("Recovering panic: %s", panicValue)
			}
135
			res = shim.Error(fmt.Sprintf("Error recovery: %s", panicValue))
Binh Q. Nguyen's avatar
Binh Q. Nguyen committed
136
137
138
139
		}
	}()

	if qexe, err = vledger.NewQueryExecutor(); err != nil {
140
		return shim.Error(err.Error())
Binh Q. Nguyen's avatar
Binh Q. Nguyen committed
141
142
	}
	if ri, err = qexe.ExecuteQuery(qstring); err != nil {
143
		return shim.Error(err.Error())
Binh Q. Nguyen's avatar
Binh Q. Nguyen committed
144
145
146
147
148
149
150
151
152
	}
	defer ri.Close()

	limit := viper.GetInt("ledger.state.couchDBConfig.queryLimit")

	// buffer is a JSON array containing QueryRecords
	var buffer bytes.Buffer
	buffer.WriteString("[")

153
	var qresult commonledger.QueryResult
154
	bArrayMemberAlreadyWritten := false
Binh Q. Nguyen's avatar
Binh Q. Nguyen committed
155
156
157
	qresult, err = ri.Next()
	for r := 0; qresult != nil && err == nil && r < limit; r++ {
		if qr, ok := qresult.(*ledger.QueryRecord); ok {
158
159
160
161
			// Add a comma before array members, suppress it for the first array member
			if bArrayMemberAlreadyWritten == true {
				buffer.WriteString(",")
			}
Binh Q. Nguyen's avatar
Binh Q. Nguyen committed
162
			collectRecord(&buffer, qr)
163
			bArrayMemberAlreadyWritten = true
Binh Q. Nguyen's avatar
Binh Q. Nguyen committed
164
165
166
167
168
169
170
		}
		qresult, err = ri.Next()
	}

	buffer.WriteString("]")

	// Return what we have accummulated
171
172
	ret := buffer.Bytes()
	return shim.Success(ret)
Binh Q. Nguyen's avatar
Binh Q. Nguyen committed
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
}

// Append QueryRecord into buffer as a JSON record of the form {namespace, key, record}
// type QueryRecord struct {
// 	Namespace string
// 	Key       string
// 	Record    []byte
// }
func collectRecord(buffer *bytes.Buffer, rec *ledger.QueryRecord) {
	buffer.WriteString("{\"Namespace\":")
	buffer.WriteString("\"")
	buffer.WriteString(rec.Namespace)
	buffer.WriteString("\"")

	buffer.WriteString(", \"Key\":")
	buffer.WriteString("\"")
	buffer.WriteString(rec.Key)
	buffer.WriteString("\"")

	buffer.WriteString(", \"Record\":")
	// Record is a JSON object, so we write as-is
	buffer.WriteString(string(rec.Record))
	buffer.WriteString("}")
}

198
func getTransactionByID(vledger ledger.PeerLedger, tid []byte) pb.Response {
Binh Q. Nguyen's avatar
Binh Q. Nguyen committed
199
	if tid == nil {
200
		return shim.Error("Transaction ID must not be nil.")
Binh Q. Nguyen's avatar
Binh Q. Nguyen committed
201
	}
202
	txEnvelope, err := vledger.GetTransactionByID(string(tid))
Binh Q. Nguyen's avatar
Binh Q. Nguyen committed
203
	if err != nil {
204
		return shim.Error(fmt.Sprintf("Failed to get transaction with id %s, error %s", string(tid), err))
Binh Q. Nguyen's avatar
Binh Q. Nguyen committed
205
	}
206
207
	// TODO In the returned transaction, need to replace binary simulation results with a proto
	//  structure including write set, so that clients know what this transaction wrote
Binh Q. Nguyen's avatar
Binh Q. Nguyen committed
208

209
	bytes, err := utils.Marshal(txEnvelope)
210
211
212
213
214
	if err != nil {
		return shim.Error(err.Error())
	}

	return shim.Success(bytes)
Binh Q. Nguyen's avatar
Binh Q. Nguyen committed
215
216
}

217
func getBlockByNumber(vledger ledger.PeerLedger, number []byte) pb.Response {
Binh Q. Nguyen's avatar
Binh Q. Nguyen committed
218
	if number == nil {
219
		return shim.Error("Block number must not be nil.")
Binh Q. Nguyen's avatar
Binh Q. Nguyen committed
220
221
222
	}
	bnum, err := strconv.ParseUint(string(number), 10, 64)
	if err != nil {
223
		return shim.Error(fmt.Sprintf("Failed to parse block number with error %s", err))
Binh Q. Nguyen's avatar
Binh Q. Nguyen committed
224
225
226
	}
	block, err := vledger.GetBlockByNumber(bnum)
	if err != nil {
227
		return shim.Error(fmt.Sprintf("Failed to get block number %d, error %s", bnum, err))
Binh Q. Nguyen's avatar
Binh Q. Nguyen committed
228
229
	}
	// TODO: consider trim block content before returning
230
231
232
	//  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
Binh Q. Nguyen's avatar
Binh Q. Nguyen committed
233

234
235
236
237
238
239
	bytes, err := utils.Marshal(block)
	if err != nil {
		return shim.Error(err.Error())
	}

	return shim.Success(bytes)
Binh Q. Nguyen's avatar
Binh Q. Nguyen committed
240
241
}

242
func getBlockByHash(vledger ledger.PeerLedger, hash []byte) pb.Response {
Binh Q. Nguyen's avatar
Binh Q. Nguyen committed
243
	if hash == nil {
244
		return shim.Error("Block hash must not be nil.")
Binh Q. Nguyen's avatar
Binh Q. Nguyen committed
245
246
247
	}
	block, err := vledger.GetBlockByHash(hash)
	if err != nil {
248
		return shim.Error(fmt.Sprintf("Failed to get block hash %s, error %s", string(hash), err))
Binh Q. Nguyen's avatar
Binh Q. Nguyen committed
249
250
	}
	// TODO: consider trim block content before returning
251
252
253
	//  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
Binh Q. Nguyen's avatar
Binh Q. Nguyen committed
254

255
256
257
258
259
260
	bytes, err := utils.Marshal(block)
	if err != nil {
		return shim.Error(err.Error())
	}

	return shim.Success(bytes)
Binh Q. Nguyen's avatar
Binh Q. Nguyen committed
261
262
}

263
func getChainInfo(vledger ledger.PeerLedger) pb.Response {
Binh Q. Nguyen's avatar
Binh Q. Nguyen committed
264
265
	binfo, err := vledger.GetBlockchainInfo()
	if err != nil {
266
		return shim.Error(fmt.Sprintf("Failed to get block info with error %s", err))
Binh Q. Nguyen's avatar
Binh Q. Nguyen committed
267
	}
268
269
270
271
272
273
	bytes, err := utils.Marshal(binfo)
	if err != nil {
		return shim.Error(err.Error())
	}

	return shim.Success(bytes)
Binh Q. Nguyen's avatar
Binh Q. Nguyen committed
274
}