Skip to content

Commit

Permalink
[FAB-6355] Fix GetPrivateData() error on CouchDB
Browse files Browse the repository at this point in the history
The problem was that the retrieve of the public
hash from couch state db fails due to a URL
encoding issue of a plus sign in the hashed key.

Keys were already URL encoded using golang URL
encoding, but the encoding skips plus signs in
the path component.  CouchDB unencodes the plus
sign as a space.

The fix is to explicitly URL encode plus character
when interacting with CouchDB.

Also add unit test to verify the fix and test
all other URL special characters.

Also clarified the error message received when
private version does not match public hash.

Change-Id: I9b99d3446542e9eae0196158270205d3ea09db9f
Signed-off-by: David Enyeart <enyeart@us.ibm.com>
  • Loading branch information
denyeart committed Sep 29, 2017
1 parent dbe3c78 commit 41714c2
Show file tree
Hide file tree
Showing 3 changed files with 29 additions and 4 deletions.
2 changes: 1 addition & 1 deletion core/ledger/kvledger/txmgmt/txmgr/lockbasedtxmgr/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ func (h *queryHelper) getPrivateData(ns, coll, key string) ([]byte, error) {
}
if !version.AreSame(hashVersion, ver) {
return nil, &txmgr.ErrPvtdataNotAvailable{Msg: fmt.Sprintf(
"The available copy of the private data is not latest. The latest version = %#v, available version = %#v",
"Private data matching public hash version is not available. Public hash version = %#v, Private data version = %#v",
hashVersion, ver)}
}
if h.rwsetBuilder != nil {
Expand Down
9 changes: 6 additions & 3 deletions core/ledger/util/couchdb/couchdb.go
Original file line number Diff line number Diff line change
Expand Up @@ -1407,13 +1407,16 @@ func IsJSON(s string) bool {
return json.Unmarshal([]byte(s), &js) == nil
}

// encodePathElement uses Golang for encoding and in addition, replaces a '/' by %2F.
// Otherwise, in the regular encoding, a '/' is treated as a path separator in the url
// encodePathElement uses Golang for url path encoding, additionally:
// '/' is replaced by %2F, otherwise path encoding will treat as path separator and ignore it
// '+' is replaced by %2B, otherwise path encoding will ignore it, while CouchDB will unencode the plus as a space
// Note that all other URL special characters have been tested successfully without need for special handling
func encodePathElement(str string) string {
u := &url.URL{}
u.Path = str
encodedStr := u.String()
encodedStr := u.String() // url encode using golang url path encoding rules
encodedStr = strings.Replace(encodedStr, "/", "%2F", -1)
encodedStr = strings.Replace(encodedStr, "+", "%2B", -1)
return encodedStr
}

Expand Down
22 changes: 22 additions & 0 deletions core/ledger/util/couchdb/couchdb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1130,6 +1130,7 @@ func TestBatchBatchOperations(t *testing.T) {
byteJSON03 := []byte(`{"_id":"marble03","asset_name":"marble03","color":"green","size":"3","owner":"jerry"}`)
byteJSON04 := []byte(`{"_id":"marble04","asset_name":"marble04","color":"purple","size":"4","owner":"tom"}`)
byteJSON05 := []byte(`{"_id":"marble05","asset_name":"marble05","color":"blue","size":"5","owner":"jerry"}`)
byteJSON06 := []byte(`{"_id":"marble06#$&'()*+,/:;=?@[]","asset_name":"marble06#$&'()*+,/:;=?@[]","color":"blue","size":"6","owner":"jerry"}`)

attachment1 := &AttachmentInfo{}
attachment1.AttachmentBytes = []byte(`marble01 - test attachment`)
Expand Down Expand Up @@ -1166,6 +1167,13 @@ func TestBatchBatchOperations(t *testing.T) {
attachments5 := []*AttachmentInfo{}
attachments5 = append(attachments5, attachment5)

attachment6 := &AttachmentInfo{}
attachment6.AttachmentBytes = []byte(`marble06#$&'()*+,/:;=?@[] - test attachment`)
attachment6.ContentType = "application/octet-stream"
attachment6.Name = "data"
attachments6 := []*AttachmentInfo{}
attachments6 = append(attachments6, attachment6)

database := "testbatch"
err := cleanup(database)
testutil.AssertNoError(t, err, fmt.Sprintf("Error when trying to cleanup Error: %s", err))
Expand All @@ -1188,12 +1196,14 @@ func TestBatchBatchOperations(t *testing.T) {
value3 := &CouchDoc{JSONValue: byteJSON03, Attachments: attachments3}
value4 := &CouchDoc{JSONValue: byteJSON04, Attachments: attachments4}
value5 := &CouchDoc{JSONValue: byteJSON05, Attachments: attachments5}
value6 := &CouchDoc{JSONValue: byteJSON06, Attachments: attachments6}

batchUpdateDocs = append(batchUpdateDocs, value1)
batchUpdateDocs = append(batchUpdateDocs, value2)
batchUpdateDocs = append(batchUpdateDocs, value3)
batchUpdateDocs = append(batchUpdateDocs, value4)
batchUpdateDocs = append(batchUpdateDocs, value5)
batchUpdateDocs = append(batchUpdateDocs, value6)

batchUpdateResp, err := db.BatchUpdateDocuments(batchUpdateDocs)
testutil.AssertNoError(t, err, fmt.Sprintf("Error when attempting to update a batch of documents"))
Expand All @@ -1214,6 +1224,18 @@ func TestBatchBatchOperations(t *testing.T) {
//Verify the owner retrieved matches
testutil.AssertEquals(t, assetResp.Owner, "jerry")

//----------------------------------------------
// Test Retrieve JSON using ID with URL special characters,
// this will confirm that batch document IDs and URL IDs are consistent, even if they include special characters
dbGetResp, _, geterr = db.ReadDoc("marble06#$&'()*+,/:;=?@[]")
testutil.AssertNoError(t, geterr, fmt.Sprintf("Error when attempting read a document"))

assetResp = &Asset{}
geterr = json.Unmarshal(dbGetResp.JSONValue, &assetResp)
testutil.AssertNoError(t, geterr, fmt.Sprintf("Error when trying to retrieve a document"))
//Verify the owner retrieved matches
testutil.AssertEquals(t, assetResp.Owner, "jerry")

//----------------------------------------------
//Test retrieve binary
dbGetResp, _, geterr = db.ReadDoc("marble03")
Expand Down

0 comments on commit 41714c2

Please sign in to comment.