Skip to content

Commit 0a69c3b

Browse files
committed
Revert REST API to no base64
The REST/JSON API is more natural to use when taking strings, rather than when taking base64 encoded data. This change also restores the previous API behavior, which suffered compatibility breakage previously. JSON by default unmarshals []byte by using base64 encoding. However, we want to override this by using strings instead. We fix this by providing a custom UnmarshalJSON method for ChaincodeInput, which converts the string-based REST/JSON input to the []byte-based current ChaincodeInput structure. Change-Id: I1cfef1447ee5680d3c8bb2b36d3853badbd7ef13 Signed-off-by: Bradley Gorman <[email protected]> Signed-off-by: Gabor Hosszu <[email protected]>
1 parent 1ffde44 commit 0a69c3b

File tree

7 files changed

+62
-36
lines changed

7 files changed

+62
-36
lines changed

bddtests/steps/peer_basic_impl.py

+13-8
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ def deployChainCodeToContainer(context, chaincode, containerName):
240240

241241
def createChaincodeSpec(context, chaincode):
242242
chaincode = validateChaincodeDictionary(chaincode)
243-
args = to_bytes(prepend(chaincode["constructor"], chaincode["args"]))
243+
args = prepend(chaincode["constructor"], chaincode["args"])
244244
# Create a ChaincodeSpec structure
245245
chaincodeSpec = {
246246
"type": getChaincodeTypeValue(chaincode["language"]),
@@ -366,17 +366,18 @@ def invokeChaincode(context, devopsFunc, functionName, containerName, idGenAlg=N
366366
if 'table' in context:
367367
# There is ctor arguments
368368
args = context.table[0].cells
369-
args = to_bytes(prepend(functionName, args))
369+
args = prepend(functionName, args)
370370
for idx, attr in enumerate(attributes):
371371
attributes[idx] = attr.strip()
372372

373-
context.chaincodeSpec['ctorMsg']['args'] = args
374373
context.chaincodeSpec['attributes'] = attributes
375374

376375
#If idGenAlg is passed then, we still using the deprecated devops API because this parameter can't be passed in the new API.
377376
if idGenAlg != None:
377+
context.chaincodeSpec['ctorMsg']['args'] = to_bytes(args)
378378
invokeUsingDevopsService(context, devopsFunc, functionName, containerName, idGenAlg)
379379
else:
380+
context.chaincodeSpec['ctorMsg']['args'] = args
380381
invokeUsingChaincodeService(context, devopsFunc, functionName, containerName)
381382

382383
def invokeUsingChaincodeService(context, devopsFunc, functionName, containerName):
@@ -424,7 +425,7 @@ def invokeMasterChaincode(context, devopsFunc, chaincodeName, functionName, cont
424425
args = []
425426
if 'table' in context:
426427
args = context.table[0].cells
427-
args = to_bytes(prepend(functionName, args))
428+
args = prepend(functionName, args)
428429
typeGolang = 1
429430
chaincodeSpec = {
430431
"type": typeGolang,
@@ -646,7 +647,7 @@ def step_impl(context, chaincodeName, functionName):
646647
if 'table' in context:
647648
# There is ctor arguments
648649
args = context.table[0].cells
649-
args = to_bytes(prepend(functionName, args))
650+
args = prepend(functionName, args)
650651
context.chaincodeSpec['ctorMsg']['args'] = args #context.table[0].cells if ('table' in context) else []
651652
# Invoke the POST
652653
chaincodeOpPayload = createChaincodeOpPayload("query", context.chaincodeSpec)
@@ -678,7 +679,7 @@ def query_common(context, chaincodeName, functionName, value, failOnError):
678679
containerDataList = bdd_test_util.getContainerDataValuesFromContext(context, aliases, lambda containerData: containerData)
679680

680681
# Update the chaincodeSpec ctorMsg for invoke
681-
context.chaincodeSpec['ctorMsg']['args'] = to_bytes([functionName, value])
682+
context.chaincodeSpec['ctorMsg']['args'] = [functionName, value]
682683
# Invoke the POST
683684
# Make deep copy of chaincodeSpec as we will be changing the SecurityContext per call.
684685
chaincodeOpPayload = createChaincodeOpPayload("query", copy.deepcopy(context.chaincodeSpec))
@@ -824,8 +825,12 @@ def to_bytes(strlist):
824825

825826
def prepend(elem, l):
826827
if l is None or l == "":
827-
return [elem]
828-
return [elem] + l
828+
tail = []
829+
else:
830+
tail = l
831+
if elem is None:
832+
return tail
833+
return [elem] + tail
829834

830835
@given(u'I do nothing')
831836
def step_impl(context):

core/rest/rest_api.go

+4-3
Original file line numberDiff line numberDiff line change
@@ -1297,10 +1297,10 @@ func (s *ServerOpenchainREST) ProcessChaincode(rw web.ResponseWriter, req *web.R
12971297
}
12981298

12991299
// Extract the ChaincodeSpec from the params field
1300-
deploySpec := requestPayload.Params
1300+
ccSpec := requestPayload.Params
13011301

13021302
// Process the chaincode deployment request and record the result
1303-
result = s.processChaincodeDeploy(deploySpec)
1303+
result = s.processChaincodeDeploy(ccSpec)
13041304
} else {
13051305

13061306
//
@@ -1310,7 +1310,8 @@ func (s *ServerOpenchainREST) ProcessChaincode(rw web.ResponseWriter, req *web.R
13101310
// Because chaincode invocation/query requests require a ChaincodeInvocationSpec
13111311
// message instead of a ChaincodeSpec message, we must initialize it here
13121312
// before proceeding.
1313-
invokequeryPayload := &pb.ChaincodeInvocationSpec{ChaincodeSpec: requestPayload.Params}
1313+
ccSpec := requestPayload.Params
1314+
invokequeryPayload := &pb.ChaincodeInvocationSpec{ChaincodeSpec: ccSpec}
13141315

13151316
// Payload params field must contain a ChaincodeSpec message
13161317
if invokequeryPayload.ChaincodeSpec == nil {

core/rest/rest_api_test.go

+12-13
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ package rest
1818

1919
import (
2020
"bytes"
21-
"encoding/base64"
2221
"encoding/json"
2322
"fmt"
2423
"io/ioutil"
@@ -34,10 +33,10 @@ import (
3433
"github.com/hyperledger/fabric/protos"
3534
)
3635

37-
var failb64 string = base64.StdEncoding.EncodeToString([]byte("fail"))
38-
var initb64 string = base64.StdEncoding.EncodeToString([]byte("Init"))
39-
var change_ownerb64 string = base64.StdEncoding.EncodeToString([]byte("change_owner"))
40-
var get_ownerb64 string = base64.StdEncoding.EncodeToString([]byte("get_owner"))
36+
var fail_func string = "fail"
37+
var init_func string = "Init"
38+
var change_owner_func string = "change_owner"
39+
var get_owner_func string = "get_owner"
4140

4241
func performHTTPGet(t *testing.T, url string) []byte {
4342
response, err := http.Get(url)
@@ -594,7 +593,7 @@ func TestServerOpenchainREST_API_Chaincode_Deploy(t *testing.T) {
594593
},
595594
"ctorMsg": {
596595
"args": ["` +
597-
initb64 +
596+
init_func +
598597
`"]
599598
},
600599
"secureContext": "myuser"
@@ -621,7 +620,7 @@ func TestServerOpenchainREST_API_Chaincode_Deploy(t *testing.T) {
621620
},
622621
"ctorMsg": {
623622
"args": ["` +
624-
initb64 +
623+
init_func +
625624
`"]
626625
}
627626
}
@@ -644,7 +643,7 @@ func TestServerOpenchainREST_API_Chaincode_Deploy(t *testing.T) {
644643
},
645644
"ctorMsg": {
646645
"args": ["` +
647-
initb64 +
646+
init_func +
648647
`"]
649648
},
650649
"secureContext": "myuser"
@@ -691,7 +690,7 @@ func TestServerOpenchainREST_API_Chaincode_Invoke(t *testing.T) {
691690
performHTTPPost(t, httpServer.URL+"/registrar", []byte(`{"enrollId":"myuser","enrollSecret":"password"}`))
692691

693692
// Test invoke with "fail" function
694-
httpResponse, body = performHTTPPost(t, httpServer.URL+"/chaincode", []byte(`{"jsonrpc":"2.0","ID":123,"method":"invoke","params":{"type":1,"chaincodeID":{"name":"dummy"},"ctorMsg":{"args":["`+failb64+`"]},"secureContext":"myuser"}}`))
693+
httpResponse, body = performHTTPPost(t, httpServer.URL+"/chaincode", []byte(`{"jsonrpc":"2.0","ID":123,"method":"invoke","params":{"type":1,"chaincodeID":{"name":"dummy"},"ctorMsg":{"Function":"`+fail_func+`","args":[]},"secureContext":"myuser"}}`))
695694
if httpResponse.StatusCode != http.StatusOK {
696695
t.Errorf("Expected an HTTP status code %#v but got %#v", http.StatusOK, httpResponse.StatusCode)
697696
}
@@ -701,7 +700,7 @@ func TestServerOpenchainREST_API_Chaincode_Invoke(t *testing.T) {
701700
}
702701

703702
// Test invoke with "change_owner" function
704-
httpResponse, body = performHTTPPost(t, httpServer.URL+"/chaincode", []byte(`{"jsonrpc":"2.0","ID":123,"method":"invoke","params":{"type":1,"chaincodeID":{"name":"dummy"},"ctorMsg":{"args":["`+change_ownerb64+`"]},"secureContext":"myuser"}}`))
703+
httpResponse, body = performHTTPPost(t, httpServer.URL+"/chaincode", []byte(`{"jsonrpc":"2.0","ID":123,"method":"invoke","params":{"type":1,"chaincodeID":{"name":"dummy"},"ctorMsg":{"Function":"`+change_owner_func+`","args":[]},"secureContext":"myuser"}}`))
705704
if httpResponse.StatusCode != http.StatusOK {
706705
t.Errorf("Expected an HTTP status code %#v but got %#v", http.StatusOK, httpResponse.StatusCode)
707706
}
@@ -742,7 +741,7 @@ func TestServerOpenchainREST_API_Chaincode_Query(t *testing.T) {
742741
performHTTPPost(t, httpServer.URL+"/registrar", []byte(`{"enrollId":"myuser","enrollSecret":"password"}`))
743742

744743
// Test query with non-existing chaincode name
745-
httpResponse, body = performHTTPPost(t, httpServer.URL+"/chaincode", []byte(`{"jsonrpc":"2.0","ID":123,"method":"query","params":{"type":1,"chaincodeID":{"name":"non-existing"},"ctorMsg":{"args":["`+initb64+`"]},"secureContext":"myuser"}}`))
744+
httpResponse, body = performHTTPPost(t, httpServer.URL+"/chaincode", []byte(`{"jsonrpc":"2.0","ID":123,"method":"query","params":{"type":1,"chaincodeID":{"name":"non-existing"},"ctorMsg":{"Function":"`+init_func+`","args":[]},"secureContext":"myuser"}}`))
746745
if httpResponse.StatusCode != http.StatusOK {
747746
t.Errorf("Expected an HTTP status code %#v but got %#v", http.StatusOK, httpResponse.StatusCode)
748747
}
@@ -752,7 +751,7 @@ func TestServerOpenchainREST_API_Chaincode_Query(t *testing.T) {
752751
}
753752

754753
// Test query with fail function
755-
httpResponse, body = performHTTPPost(t, httpServer.URL+"/chaincode", []byte(`{"jsonrpc":"2.0","ID":123,"method":"query","params":{"type":1,"chaincodeID":{"name":"dummy"},"ctorMsg":{"args":["`+failb64+`"]},"secureContext":"myuser"}}`))
754+
httpResponse, body = performHTTPPost(t, httpServer.URL+"/chaincode", []byte(`{"jsonrpc":"2.0","ID":123,"method":"query","params":{"type":1,"chaincodeID":{"name":"dummy"},"ctorMsg":{"Function":"`+fail_func+`","args":[]},"secureContext":"myuser"}}`))
756755
if httpResponse.StatusCode != http.StatusOK {
757756
t.Errorf("Expected an HTTP status code %#v but got %#v", http.StatusOK, httpResponse.StatusCode)
758757
}
@@ -765,7 +764,7 @@ func TestServerOpenchainREST_API_Chaincode_Query(t *testing.T) {
765764
}
766765

767766
// Test query with get_owner function
768-
httpResponse, body = performHTTPPost(t, httpServer.URL+"/chaincode", []byte(`{"jsonrpc":"2.0","ID":123,"method":"query","params":{"type":1,"chaincodeID":{"name":"dummy"},"ctorMsg":{"args":["`+get_ownerb64+`"]},"secureContext":"myuser"}}`))
767+
httpResponse, body = performHTTPPost(t, httpServer.URL+"/chaincode", []byte(`{"jsonrpc":"2.0","ID":123,"method":"query","params":{"type":1,"chaincodeID":{"name":"dummy"},"ctorMsg":{"Function":"`+get_owner_func+`","args":[]},"secureContext":"myuser"}}`))
769768
if httpResponse.StatusCode != http.StatusOK {
770769
t.Errorf("Expected an HTTP status code %#v but got %#v", http.StatusOK, httpResponse.StatusCode)
771770
}

peer/chaincode/common.go

+7-12
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import (
2525
"strings"
2626

2727
"github.com/hyperledger/fabric/core"
28-
u "github.com/hyperledger/fabric/core/util"
2928
"github.com/hyperledger/fabric/peer/common"
3029
"github.com/hyperledger/fabric/peer/util"
3130
pb "github.com/hyperledger/fabric/protos"
@@ -34,22 +33,17 @@ import (
3433
"golang.org/x/net/context"
3534
)
3635

37-
type container struct {
38-
Args []string
39-
}
40-
4136
func getChaincodeSpecification(cmd *cobra.Command) (*pb.ChaincodeSpec, error) {
4237
spec := &pb.ChaincodeSpec{}
4338
if err := checkChaincodeCmdParams(cmd); err != nil {
4439
return spec, err
4540
}
4641

4742
// Build the spec
48-
inputc := container{}
49-
if err := json.Unmarshal([]byte(chaincodeCtorJSON), &inputc); err != nil {
43+
input := &pb.ChaincodeInput{}
44+
if err := json.Unmarshal([]byte(chaincodeCtorJSON), &input); err != nil {
5045
return spec, fmt.Errorf("Chaincode argument error: %s", err)
5146
}
52-
input := &pb.ChaincodeInput{Args: u.ToChaincodeArgs(inputc.Args...)}
5347

5448
var attributes []string
5549
if err := json.Unmarshal([]byte(chaincodeAttributesJSON), &attributes); err != nil {
@@ -193,18 +187,19 @@ func checkChaincodeCmdParams(cmd *cobra.Command) error {
193187
return fmt.Errorf("Chaincode argument error: %s", err)
194188
}
195189
m := f.(map[string]interface{})
196-
if len(m) != 1 {
197-
return fmt.Errorf("Non-empty JSON chaincode parameters must contain exactly 1 key: 'Args'")
190+
if len(m) != 2 {
191+
return fmt.Errorf("Non-empty JSON chaincode parameters must contain exactly 2 keys: 'Function' and 'Args'")
198192
}
199193
for k := range m {
200194
switch strings.ToLower(k) {
201195
case "args":
196+
case "function":
202197
default:
203-
return fmt.Errorf("Illegal chaincode key '%s' - must be only 'Args'", k)
198+
return fmt.Errorf("Illegal chaincode key '%s' - must be 'Function' or 'Args'", k)
204199
}
205200
}
206201
} else {
207-
return errors.New("Empty JSON chaincode parameters must contain exactly 1 key: 'Args'")
202+
return errors.New("Empty JSON chaincode parameters must contain exactly 2 keys: 'Function' and 'Args'")
208203
}
209204

210205
if chaincodeAttributesJSON != "[]" {

protos/chaincode.pb.go

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

protos/chaincode.proto

+2
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ message ChaincodeID {
4646
}
4747

4848
// Carries the chaincode function and its arguments.
49+
// UnmarshalJSON in transaction.go converts the string-based REST/JSON input to
50+
// the []byte-based current ChaincodeInput structure.
4951
message ChaincodeInput {
5052
repeated bytes args = 1;
5153
}

protos/transaction.go

+22
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package protos
1818

1919
import (
20+
"encoding/json"
2021
"fmt"
2122

2223
"github.com/golang/protobuf/proto"
@@ -111,3 +112,24 @@ func NewChaincodeExecute(chaincodeInvocationSpec *ChaincodeInvocationSpec, uuid
111112
transaction.Payload = data
112113
return transaction, nil
113114
}
115+
116+
type strArgs struct {
117+
Function string
118+
Args []string
119+
}
120+
121+
// UnmarshalJSON converts the string-based REST/JSON input to
122+
// the []byte-based current ChaincodeInput structure.
123+
func (c *ChaincodeInput) UnmarshalJSON(b []byte) error {
124+
sa := &strArgs{}
125+
err := json.Unmarshal(b, sa)
126+
if err != nil {
127+
return err
128+
}
129+
allArgs := sa.Args
130+
if sa.Function != "" {
131+
allArgs = append([]string{sa.Function}, sa.Args...)
132+
}
133+
c.Args = util.ToChaincodeArgs(allArgs...)
134+
return nil
135+
}

0 commit comments

Comments
 (0)