Skip to content

Commit c14896a

Browse files
committed
Ledger query APIs
This system chaincode provides ledger structural query APIs such as GetTransactionByID, GetBlockByHash, etc. Currently the inputs require a channel name rather using the channel from the -C parameter. That will change on next iteration when we change the chaincode invocation to pass the channel name. For example, to invoke with CLI peer chaincode query -C "" -n qscc -c '{"Args":["GetTransactionByID","mychannel","txid"]}' Change-Id: I31da536f5dd72bb73b0444d8ee11b1b6093c136d Signed-off-by: Binh Q. Nguyen <[email protected]>
1 parent 9e8fb87 commit c14896a

File tree

4 files changed

+391
-4
lines changed

4 files changed

+391
-4
lines changed

core/chaincode/importsysccs.go

+11-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright IBM Corp. 2016 All Rights Reserved.
2+
Copyright IBM Corp. 2016, 2017 All Rights Reserved.
33
44
Licensed under the Apache License, Version 2.0 (the "License");
55
you may not use this file except in compliance with the License.
@@ -55,7 +55,16 @@ var systemChaincodes = []*SystemChaincode{
5555
Path: "github.com/hyperledger/fabric/core/system_chaincode/vscc",
5656
InitArgs: [][]byte{[]byte("")},
5757
Chaincode: &vscc.ValidatorOneValidSignature{},
58-
}}
58+
},
59+
{
60+
ChainlessCC: true,
61+
Enabled: true,
62+
Name: "qscc",
63+
Path: "github.com/hyperledger/fabric/core/chaincode/qscc",
64+
InitArgs: [][]byte{[]byte("")},
65+
Chaincode: &LedgerQuerier{},
66+
},
67+
}
5968

6069
//RegisterSysCCs is the hook for system chaincodes where system chaincodes are registered with the fabric
6170
//note the chaincode must still be deployed and launched like a user chaincode will be

core/chaincode/querier.go

+238
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
/*
2+
Copyright IBM Corp. 2017 All Rights Reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package chaincode
18+
19+
import (
20+
"bytes"
21+
"fmt"
22+
"strconv"
23+
24+
"github.com/op/go-logging"
25+
"github.com/spf13/viper"
26+
27+
"github.com/hyperledger/fabric/core/chaincode/shim"
28+
"github.com/hyperledger/fabric/core/ledger"
29+
"github.com/hyperledger/fabric/core/peer"
30+
"github.com/hyperledger/fabric/protos/utils"
31+
)
32+
33+
// LedgerQuerier implements the ledger query functions, including:
34+
// - GetChainInfo returns BlockchainInfo
35+
// - GetBlockByNumber returns a block
36+
// - GetBlockByHash returns a block
37+
// - GetTransactionByID returns a transaction
38+
// - GetQueryResult returns result of a freeform query
39+
type LedgerQuerier struct {
40+
}
41+
42+
var qscclogger = logging.MustGetLogger("qscc")
43+
44+
// These are function names from Invoke first parameter
45+
const (
46+
GetChainInfo string = "GetChainInfo"
47+
GetBlockByNumber string = "GetBlockByNumber"
48+
GetBlockByHash string = "GetBlockByHash"
49+
GetTransactionByID string = "GetTransactionByID"
50+
GetQueryResult string = "GetQueryResult"
51+
)
52+
53+
// Init is called once per chain when the chain is created.
54+
// This allows the chaincode to initialize any variables on the ledger prior
55+
// to any transaction execution on the chain.
56+
func (e *LedgerQuerier) Init(stub shim.ChaincodeStubInterface) ([]byte, error) {
57+
qscclogger.Info("Init QSCC")
58+
59+
return nil, nil
60+
}
61+
62+
// Invoke is called with args[0] contains the query function name, args[1]
63+
// contains the chain ID, which is temporary for now until it is part of stub.
64+
// Each function requires additional parameters as described below:
65+
// # GetChainInfo: Return a BlockchainInfo object marshalled in bytes
66+
// # GetBlockByNumber: Return the block specified by block number in args[2]
67+
// # GetBlockByHash: Return the block specified by block hash in args[2]
68+
// # GetTransactionByID: Return the transaction specified by ID in args[2]
69+
// # GetQueryResult: Return the result of executing the specified native
70+
// query string in args[2]. Note that this only works if plugged in database
71+
// supports it. The result is a JSON array in a byte array. Note that error
72+
// may be returned together with a valid partial result as error might occur
73+
// during accummulating records from the ledger
74+
func (e *LedgerQuerier) Invoke(stub shim.ChaincodeStubInterface) ([]byte, error) {
75+
args := stub.GetArgs()
76+
77+
if len(args) < 2 {
78+
return nil, fmt.Errorf("Incorrect number of arguments, %d", len(args))
79+
}
80+
fname := string(args[0])
81+
cid := string(args[1])
82+
83+
if fname != GetChainInfo && len(args) < 3 {
84+
return nil, fmt.Errorf("missing 3rd argument for %s", fname)
85+
}
86+
87+
targetLedger := peer.GetLedger(cid)
88+
if targetLedger == nil {
89+
return nil, fmt.Errorf("Invalid chain ID, %s", cid)
90+
}
91+
if qscclogger.IsEnabledFor(logging.DEBUG) {
92+
qscclogger.Debugf("Invoke function: %s on chain: %s", fname, cid)
93+
}
94+
95+
// TODO: Handle ACL
96+
97+
switch fname {
98+
case GetQueryResult:
99+
return getQueryResult(targetLedger, args[2])
100+
case GetTransactionByID:
101+
return getTransactionByID(targetLedger, args[2])
102+
case GetBlockByNumber:
103+
return getBlockByNumber(targetLedger, args[2])
104+
case GetBlockByHash:
105+
return getBlockByHash(targetLedger, args[2])
106+
case GetChainInfo:
107+
return getChainInfo(targetLedger)
108+
}
109+
110+
return nil, fmt.Errorf("Requested function %s not found.", fname)
111+
}
112+
113+
// Execute the specified query string
114+
func getQueryResult(vledger ledger.ValidatedLedger, query []byte) (ret []byte, err error) {
115+
if query == nil {
116+
return nil, fmt.Errorf("Query string must not be nil.")
117+
}
118+
qstring := string(query)
119+
var qexe ledger.QueryExecutor
120+
var ri ledger.ResultsIterator
121+
122+
// We install a recover() to gain control in 2 cases
123+
// 1) bytes.Buffer panics, which happens when out of memory
124+
// This is a safety measure beyond the config limit variable
125+
// 2) plugin db driver might panic
126+
// We recover by stopping the query and return the panic error.
127+
defer func() {
128+
if panicValue := recover(); panicValue != nil {
129+
if qscclogger.IsEnabledFor(logging.DEBUG) {
130+
qscclogger.Debugf("Recovering panic: %s", panicValue)
131+
}
132+
err = fmt.Errorf("Error recovery: %s", panicValue)
133+
}
134+
}()
135+
136+
if qexe, err = vledger.NewQueryExecutor(); err != nil {
137+
return nil, err
138+
}
139+
if ri, err = qexe.ExecuteQuery(qstring); err != nil {
140+
return nil, err
141+
}
142+
defer ri.Close()
143+
144+
limit := viper.GetInt("ledger.state.couchDBConfig.queryLimit")
145+
146+
// buffer is a JSON array containing QueryRecords
147+
var buffer bytes.Buffer
148+
buffer.WriteString("[")
149+
150+
var qresult ledger.QueryResult
151+
qresult, err = ri.Next()
152+
for r := 0; qresult != nil && err == nil && r < limit; r++ {
153+
if qr, ok := qresult.(*ledger.QueryRecord); ok {
154+
collectRecord(&buffer, qr)
155+
}
156+
qresult, err = ri.Next()
157+
}
158+
159+
buffer.WriteString("]")
160+
161+
// Return what we have accummulated
162+
ret = buffer.Bytes()
163+
return ret, err
164+
}
165+
166+
// Append QueryRecord into buffer as a JSON record of the form {namespace, key, record}
167+
// type QueryRecord struct {
168+
// Namespace string
169+
// Key string
170+
// Record []byte
171+
// }
172+
func collectRecord(buffer *bytes.Buffer, rec *ledger.QueryRecord) {
173+
buffer.WriteString("{\"Namespace\":")
174+
buffer.WriteString("\"")
175+
buffer.WriteString(rec.Namespace)
176+
buffer.WriteString("\"")
177+
178+
buffer.WriteString(", \"Key\":")
179+
buffer.WriteString("\"")
180+
buffer.WriteString(rec.Key)
181+
buffer.WriteString("\"")
182+
183+
buffer.WriteString(", \"Record\":")
184+
// Record is a JSON object, so we write as-is
185+
buffer.WriteString(string(rec.Record))
186+
buffer.WriteString("}")
187+
}
188+
189+
func getTransactionByID(vledger ledger.ValidatedLedger, tid []byte) ([]byte, error) {
190+
if tid == nil {
191+
return nil, fmt.Errorf("Transaction ID must not be nil.")
192+
}
193+
tx, err := vledger.GetTransactionByID(string(tid))
194+
if err != nil {
195+
return nil, fmt.Errorf("Failed to get transaction with id %s, error %s", string(tid), err)
196+
}
197+
// TODO: tx is *pb.Transaction, what should we return?
198+
199+
return utils.Marshal(tx)
200+
}
201+
202+
func getBlockByNumber(vledger ledger.ValidatedLedger, number []byte) ([]byte, error) {
203+
if number == nil {
204+
return nil, fmt.Errorf("Block number must not be nil.")
205+
}
206+
bnum, err := strconv.ParseUint(string(number), 10, 64)
207+
if err != nil {
208+
return nil, fmt.Errorf("Failed to parse block number with error %s", err)
209+
}
210+
block, err := vledger.GetBlockByNumber(bnum)
211+
if err != nil {
212+
return nil, fmt.Errorf("Failed to get block number %d, error %s", bnum, err)
213+
}
214+
// TODO: consider trim block content before returning
215+
216+
return utils.Marshal(block)
217+
}
218+
219+
func getBlockByHash(vledger ledger.ValidatedLedger, hash []byte) ([]byte, error) {
220+
if hash == nil {
221+
return nil, fmt.Errorf("Block hash must not be nil.")
222+
}
223+
block, err := vledger.GetBlockByHash(hash)
224+
if err != nil {
225+
return nil, fmt.Errorf("Failed to get block hash %s, error %s", string(hash), err)
226+
}
227+
// TODO: consider trim block content before returning
228+
229+
return utils.Marshal(block)
230+
}
231+
232+
func getChainInfo(vledger ledger.ValidatedLedger) ([]byte, error) {
233+
binfo, err := vledger.GetBlockchainInfo()
234+
if err != nil {
235+
return nil, fmt.Errorf("Failed to get block info with error %s", err)
236+
}
237+
return utils.Marshal(binfo)
238+
}

core/chaincode/querier_test.go

+134
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
/*
2+
Copyright IBM Corp. 2017 All Rights Reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
package chaincode
17+
18+
import (
19+
"fmt"
20+
"os"
21+
"testing"
22+
23+
"github.com/spf13/viper"
24+
25+
"github.com/hyperledger/fabric/core/chaincode/shim"
26+
"github.com/hyperledger/fabric/core/peer"
27+
)
28+
29+
func TestInit(t *testing.T) {
30+
viper.Set("peer.fileSystemPath", "/var/hyperledger/test1/")
31+
defer os.RemoveAll("/var/hyperledger/test1/")
32+
peer.MockInitialize()
33+
peer.MockCreateChain("mytestchainid1")
34+
35+
e := new(LedgerQuerier)
36+
stub := shim.NewMockStub("LedgerQuerier", e)
37+
38+
if _, err := stub.MockInit("1", nil); err != nil {
39+
fmt.Println("Init failed", err)
40+
t.FailNow()
41+
}
42+
}
43+
44+
func TestQueryGetChainInfo(t *testing.T) {
45+
viper.Set("peer.fileSystemPath", "/var/hyperledger/test2/")
46+
defer os.RemoveAll("/var/hyperledger/test2/")
47+
peer.MockInitialize()
48+
peer.MockCreateChain("mytestchainid2")
49+
50+
e := new(LedgerQuerier)
51+
stub := shim.NewMockStub("LedgerQuerier", e)
52+
53+
args := [][]byte{[]byte(GetChainInfo), []byte("mytestchainid2")}
54+
if _, err := stub.MockInvoke("1", args); err != nil {
55+
t.Fatalf("qscc GetChainInfo failed with err: %s", err)
56+
}
57+
}
58+
59+
func TestQueryGetTransactionByID(t *testing.T) {
60+
viper.Set("peer.fileSystemPath", "/var/hyperledger/test3/")
61+
defer os.RemoveAll("/var/hyperledger/test3/")
62+
peer.MockInitialize()
63+
peer.MockCreateChain("mytestchainid3")
64+
65+
e := new(LedgerQuerier)
66+
stub := shim.NewMockStub("LedgerQuerier", e)
67+
68+
args := [][]byte{[]byte(GetTransactionByID), []byte("mytestchainid3"), []byte("1")}
69+
if _, err := stub.MockInvoke("1", args); err == nil {
70+
t.Fatalf("qscc getTransactionByID should have failed with invalid txid: 1")
71+
}
72+
}
73+
74+
func TestQueryWithWrongParameters(t *testing.T) {
75+
viper.Set("peer.fileSystemPath", "/var/hyperledger/test4/")
76+
defer os.RemoveAll("/var/hyperledger/test4/")
77+
peer.MockInitialize()
78+
peer.MockCreateChain("mytestchainid4")
79+
80+
e := new(LedgerQuerier)
81+
stub := shim.NewMockStub("LedgerQuerier", e)
82+
83+
// Test with wrong number of parameters
84+
args := [][]byte{[]byte(GetTransactionByID), []byte("mytestchainid4")}
85+
if _, err := stub.MockInvoke("1", args); err == nil {
86+
t.Fatalf("qscc getTransactionByID should have failed with invalid txid: 1")
87+
}
88+
}
89+
90+
func TestQueryGetBlockByNumber(t *testing.T) {
91+
//t.Skip()
92+
viper.Set("peer.fileSystemPath", "/var/hyperledger/test5/")
93+
defer os.RemoveAll("/var/hyperledger/test5/")
94+
peer.MockInitialize()
95+
peer.MockCreateChain("mytestchainid5")
96+
97+
e := new(LedgerQuerier)
98+
stub := shim.NewMockStub("LedgerQuerier", e)
99+
100+
args := [][]byte{[]byte(GetBlockByNumber), []byte("mytestchainid5"), []byte("0")}
101+
if _, err := stub.MockInvoke("1", args); err == nil {
102+
t.Fatalf("qscc GetBlockByNumber should have failed with invalid number: 0")
103+
}
104+
}
105+
106+
func TestQueryGetBlockByHash(t *testing.T) {
107+
viper.Set("peer.fileSystemPath", "/var/hyperledger/test6/")
108+
defer os.RemoveAll("/var/hyperledger/test6/")
109+
peer.MockInitialize()
110+
peer.MockCreateChain("mytestchainid6")
111+
112+
e := new(LedgerQuerier)
113+
stub := shim.NewMockStub("LedgerQuerier", e)
114+
115+
args := [][]byte{[]byte(GetBlockByHash), []byte("mytestchainid6"), []byte("0")}
116+
if _, err := stub.MockInvoke("1", args); err == nil {
117+
t.Fatalf("qscc GetBlockByHash should have failed with invalid hash: 0")
118+
}
119+
}
120+
121+
func TestQueryGetQueryResult(t *testing.T) {
122+
viper.Set("peer.fileSystemPath", "/var/hyperledger/test7/")
123+
defer os.RemoveAll("/var/hyperledger/test7/")
124+
peer.MockInitialize()
125+
peer.MockCreateChain("mytestchainid7")
126+
127+
e := new(LedgerQuerier)
128+
stub := shim.NewMockStub("LedgerQuerier", e)
129+
qstring := "{\"selector\":{\"key\":\"value\"}}"
130+
args := [][]byte{[]byte(GetQueryResult), []byte("mytestchainid7"), []byte(qstring)}
131+
if _, err := stub.MockInvoke("1", args); err == nil {
132+
t.Fatalf("qscc GetQueryResult should have failed with invalid query: abc")
133+
}
134+
}

0 commit comments

Comments
 (0)