From 562bb78e7e7d234774ae9490cc87023e7e867a2d Mon Sep 17 00:00:00 2001 From: Sandra Vrtikapa Date: Wed, 19 Feb 2020 16:02:37 -0500 Subject: [PATCH] feat: Support delete document Update to latest sidetree-core and create test for delete document. Closes #130 Signed-off-by: Sandra Vrtikapa --- cmd/chaincode/doc/doc.go | 55 ++++++++++++- cmd/chaincode/doc/doc_test.go | 77 ++++++++++++++++-- cmd/chaincode/doc/logger.go | 48 ----------- cmd/chaincode/doc/logger_test.go | 20 ----- cmd/chaincode/txn/logger.go | 48 ----------- cmd/chaincode/txn/logger_test.go | 20 ----- cmd/chaincode/txn/txn.go | 3 +- cmd/peer/go.sum | 4 +- go.mod | 2 +- go.sum | 4 +- pkg/context/store/client.go | 28 +++++-- pkg/context/store/client_test.go | 20 ++++- pkg/httpserver/server_test.go | 6 +- pkg/observer/monitor/operationstore.go | 4 +- pkg/observer/monitor/operationstore_test.go | 12 +-- pkg/observer/observer.go | 2 +- pkg/observer/observer_test.go | 2 +- test/bddtests/did_sidetree_steps.go | 90 +++++++++++++++++---- test/bddtests/features/did-sidetree.feature | 14 ++++ test/bddtests/go.mod | 2 +- test/bddtests/go.sum | 4 +- 21 files changed, 276 insertions(+), 189 deletions(-) delete mode 100644 cmd/chaincode/doc/logger.go delete mode 100644 cmd/chaincode/doc/logger_test.go delete mode 100644 cmd/chaincode/txn/logger.go delete mode 100644 cmd/chaincode/txn/logger_test.go diff --git a/cmd/chaincode/doc/doc.go b/cmd/chaincode/doc/doc.go index 4222758..934b8a1 100644 --- a/cmd/chaincode/doc/doc.go +++ b/cmd/chaincode/doc/doc.go @@ -10,14 +10,16 @@ import ( "encoding/json" "fmt" "runtime/debug" + "sort" "github.com/hyperledger/fabric-chaincode-go/shim" pb "github.com/hyperledger/fabric-protos-go/peer" + "github.com/hyperledger/fabric/common/flogging" ccapi "github.com/hyperledger/fabric/extensions/chaincode/api" "github.com/trustbloc/sidetree-fabric/cmd/chaincode/cas" ) -var logger = NewLogger("doc") +var logger = flogging.MustGetLogger("doc") const ( ccVersion = "v1" @@ -191,9 +193,15 @@ func (cc *DocumentCC) queryByID(stub shim.ChaincodeStubInterface, args [][]byte) } } - // TODO: sort documents by blockchain time (block number, tx number within block, operation index within batch) + // sort documents by blockchain time (block number, tx number within block) + sorted, err := sortChronologically(operations) + if err != nil { + errMsg := fmt.Sprintf("failed to sort operations chronologically for id[%s]: %s", ID, err.Error()) + logger.Errorf("[txID %s] %s", txID, errMsg) + return shim.Error(errMsg) + } - payload, err := json.Marshal(operations) + payload, err := json.Marshal(sorted) if err != nil { errMsg := fmt.Sprintf("failed to marshal documents: %s", err.Error()) logger.Errorf("[txID %s] %s", txID, errMsg) @@ -203,6 +211,47 @@ func (cc *DocumentCC) queryByID(stub shim.ChaincodeStubInterface, args [][]byte) return shim.Success(payload) } +func sortChronologically(operations [][]byte) ([][]byte, error) { + if len(operations) <= 1 { + return operations, nil + } + + ops := make([]*operation, len(operations)) + for index, bytes := range operations { + op := &operation{} + err := json.Unmarshal(bytes, op) + if err != nil { + return nil, err + } + op.Index = index + ops[index] = op + } + + sort.Slice(ops, func(i, j int) bool { + if ops[i].TransactionTime == ops[j].TransactionTime { + return ops[i].TransactionNumber < ops[j].TransactionNumber + } + return ops[i].TransactionTime < ops[j].TransactionTime + }) + + sorted := make([][]byte, len(operations)) + for i, o := range ops { + sorted[i] = operations[o.Index] + } + + return sorted, nil +} + +type operation struct { + Index int + + // The logical blockchain time that this operation was anchored on the blockchain - corresponds to block number + TransactionTime uint64 `json:"transactionTime"` + + // The transaction number of the transaction this operation was batched within - corresponds to tx number within block + TransactionNumber uint64 `json:"transactionNumber"` +} + func (m funcMap) String() string { str := "" i := 0 diff --git a/cmd/chaincode/doc/doc_test.go b/cmd/chaincode/doc/doc_test.go index 09df9f9..b5e97d3 100644 --- a/cmd/chaincode/doc/doc_test.go +++ b/cmd/chaincode/doc/doc_test.go @@ -9,6 +9,7 @@ package doc import ( "crypto" "encoding/base64" + "encoding/json" "fmt" "testing" @@ -183,6 +184,66 @@ func TestQueryError(t *testing.T) { assert.Contains(t, err.Error(), testErr.Error()) } +func TestQuery_SortError(t *testing.T) { + stub := prepareStub() + + testPayload := getOperationBytes(getCreateOperation()) + + address, err := invoke(stub, [][]byte{[]byte(write), testPayload}) + assert.Nil(t, err) + assert.NotNil(t, address) + + address, err = invoke(stub, [][]byte{[]byte(write), []byte("invalid json")}) + assert.Nil(t, err) + assert.NotNil(t, address) + + payload, err := invoke(stub, [][]byte{[]byte(queryByID), []byte(testID)}) + assert.NotNil(t, err) + assert.Contains(t, err.Error(), "unexpected end of JSON input") + assert.Nil(t, payload) +} + +func TestSort(t *testing.T) { + var operations [][]byte + + delete := &testOperation{ID: testID, Type: "delete", TransactionTime: 2, TransactionNumber: 1} + update := &testOperation{ID: testID, Type: "update", TransactionTime: 1, TransactionNumber: 7} + create := &testOperation{ID: testID, Type: "create", TransactionTime: 1, TransactionNumber: 1} + + operations = append(operations, getOperationBytes(delete)) + operations = append(operations, getOperationBytes(update)) + operations = append(operations, getOperationBytes(create)) + + result, err := sortChronologically(operations) + require.NoError(t, err) + + var first testOperation + err = json.Unmarshal(result[0], &first) + require.NoError(t, err) + require.Equal(t, create.Type, first.Type) + + var second testOperation + err = json.Unmarshal(result[1], &second) + require.NoError(t, err) + require.Equal(t, update.Type, second.Type) + + var third testOperation + err = json.Unmarshal(result[2], &third) + require.NoError(t, err) + require.Equal(t, delete.Type, third.Type) +} + +func TestSortError(t *testing.T) { + var operations [][]byte + operations = append(operations, []byte("invalid json")) + operations = append(operations, []byte("invalid json")) + + result, err := sortChronologically(operations) + require.Error(t, err) + require.Contains(t, err.Error(), "invalid character") + require.Nil(t, result) +} + func TestWarmup(t *testing.T) { stub := prepareStub() @@ -254,7 +315,7 @@ func invoke(stub *mocks.MockStub, args [][]byte) ([]byte, error) { return res.Payload, nil } -func getOperationBytes(op *Operation) []byte { +func getOperationBytes(op *testOperation) []byte { bytes, err := docutil.MarshalCanonical(op) if err != nil { @@ -264,12 +325,14 @@ func getOperationBytes(op *Operation) []byte { return bytes } -func getCreateOperation() *Operation { - return &Operation{ID: testID, Type: "create"} +func getCreateOperation() *testOperation { + return &testOperation{ID: testID, Type: "create", TransactionTime: 1, TransactionNumber: 1} } -// Operation defines sample operation -type Operation struct { - Type string `json:"type"` - ID string `json:"id"` +// testOperation defines sample operation with smaallest subset of information +type testOperation struct { + Type string `json:"type"` + ID string `json:"id"` + TransactionTime uint64 `json:"transactionTime"` + TransactionNumber uint64 `json:"transactionNumber"` } diff --git a/cmd/chaincode/doc/logger.go b/cmd/chaincode/doc/logger.go deleted file mode 100644 index c15b1b5..0000000 --- a/cmd/chaincode/doc/logger.go +++ /dev/null @@ -1,48 +0,0 @@ -/* -Copyright SecureKey Technologies Inc. All Rights Reserved. - -SPDX-License-Identifier: Apache-2.0 -*/ - -package doc - -import ( - "fmt" -) - -// Logger is a simple implementation of a logger that uses fmt.Printf -// TODO: This should be replaced by a real logger -type Logger struct { - module string -} - -// NewLogger returns a new logger -func NewLogger(module string) *Logger { - return &Logger{ - module: module, - } -} - -// Debugf prints a log -func (l *Logger) Debugf(format string, args ...interface{}) { - l.printf("DEBUG", format, args...) -} - -// Infof prints a log -func (l *Logger) Infof(format string, args ...interface{}) { - l.printf("INFO", format, args...) -} - -// Warnf prints a log -func (l *Logger) Warnf(format string, args ...interface{}) { - l.printf("WARN", format, args...) -} - -// Errorf prints a log -func (l *Logger) Errorf(format string, args ...interface{}) { - l.printf("ERROR", format, args...) -} - -func (l *Logger) printf(level, format string, args ...interface{}) { - fmt.Printf(fmt.Sprintf("[%s] -> %s %s\n", l.module, level, format), args...) -} diff --git a/cmd/chaincode/doc/logger_test.go b/cmd/chaincode/doc/logger_test.go deleted file mode 100644 index ffc8ded..0000000 --- a/cmd/chaincode/doc/logger_test.go +++ /dev/null @@ -1,20 +0,0 @@ -/* -Copyright SecureKey Technologies Inc. All Rights Reserved. - -SPDX-License-Identifier: Apache-2.0 -*/ - -package doc - -import ( - "testing" -) - -func TestLogger(t *testing.T) { - l := NewLogger("test") - arg := "some arg" - l.Debugf("Some message: [%s]", arg) - l.Infof("Some message: [%s]", arg) - l.Warnf("Some message: [%s]", arg) - l.Errorf("Some message: [%s]", arg) -} diff --git a/cmd/chaincode/txn/logger.go b/cmd/chaincode/txn/logger.go deleted file mode 100644 index 709ab6f..0000000 --- a/cmd/chaincode/txn/logger.go +++ /dev/null @@ -1,48 +0,0 @@ -/* -Copyright SecureKey Technologies Inc. All Rights Reserved. - -SPDX-License-Identifier: Apache-2.0 -*/ - -package txn - -import ( - "fmt" -) - -// Logger is a simple implementation of a logger that uses fmt.Printf -// TODO: This should be replaced by a real logger -type Logger struct { - module string -} - -// NewLogger returns a new logger -func NewLogger(module string) *Logger { - return &Logger{ - module: module, - } -} - -// Debugf prints a log -func (l *Logger) Debugf(format string, args ...interface{}) { - l.printf("DEBUG", format, args...) -} - -// Infof prints a log -func (l *Logger) Infof(format string, args ...interface{}) { - l.printf("INFO", format, args...) -} - -// Warnf prints a log -func (l *Logger) Warnf(format string, args ...interface{}) { - l.printf("WARN", format, args...) -} - -// Errorf prints a log -func (l *Logger) Errorf(format string, args ...interface{}) { - l.printf("ERROR", format, args...) -} - -func (l *Logger) printf(level, format string, args ...interface{}) { - fmt.Printf(fmt.Sprintf("[%s] -> %s %s\n", l.module, level, format), args...) -} diff --git a/cmd/chaincode/txn/logger_test.go b/cmd/chaincode/txn/logger_test.go deleted file mode 100644 index 97e136b..0000000 --- a/cmd/chaincode/txn/logger_test.go +++ /dev/null @@ -1,20 +0,0 @@ -/* -Copyright SecureKey Technologies Inc. All Rights Reserved. - -SPDX-License-Identifier: Apache-2.0 -*/ - -package txn - -import ( - "testing" -) - -func TestLogger(t *testing.T) { - l := NewLogger("test") - arg := "some arg" - l.Debugf("Some message: [%s]", arg) - l.Infof("Some message: [%s]", arg) - l.Warnf("Some message: [%s]", arg) - l.Errorf("Some message: [%s]", arg) -} diff --git a/cmd/chaincode/txn/txn.go b/cmd/chaincode/txn/txn.go index 39106bf..687d78e 100644 --- a/cmd/chaincode/txn/txn.go +++ b/cmd/chaincode/txn/txn.go @@ -12,11 +12,12 @@ import ( "github.com/hyperledger/fabric-chaincode-go/shim" pb "github.com/hyperledger/fabric-protos-go/peer" + "github.com/hyperledger/fabric/common/flogging" ccapi "github.com/hyperledger/fabric/extensions/chaincode/api" "github.com/trustbloc/sidetree-fabric/cmd/chaincode/cas" ) -var logger = NewLogger("sidetreetxncc") +var logger = flogging.MustGetLogger("sidetreetxncc") const ( ccVersion = "v1" diff --git a/cmd/peer/go.sum b/cmd/peer/go.sum index 5698c40..8df4583 100644 --- a/cmd/peer/go.sum +++ b/cmd/peer/go.sum @@ -346,8 +346,8 @@ github.com/trustbloc/fabric-peer-ext/mod/peer v0.0.0-20200218213141-fc25d209285c github.com/trustbloc/fabric-peer-ext/mod/peer v0.0.0-20200218213141-fc25d209285c/go.mod h1:ot55KksVO/lnRDVVNDF14AtdS7CSs//NE20WU6x/eiw= github.com/trustbloc/fabric-protos-go-ext v0.1.2-0.20200205170340-c69bba6d7b81 h1:iant8lATTlHYUWVdL3llKZuGgZUHBL4q7JUkLmcBQXk= github.com/trustbloc/fabric-protos-go-ext v0.1.2-0.20200205170340-c69bba6d7b81/go.mod h1:xVYTjK4DtZRBxZ2D9aE4y6AbLaPwue2o/criQyQbVD0= -github.com/trustbloc/sidetree-core-go v0.1.2-0.20200214144924-3e7aa7825416 h1:t9FawNEHc0R1PTYdDhWd/Q643KAN9IpJZMdlu2FlBxM= -github.com/trustbloc/sidetree-core-go v0.1.2-0.20200214144924-3e7aa7825416/go.mod h1:qJ7oOPveEqrxTsO4KJsSHUM/Yivym221Dvd+IUq1V1U= +github.com/trustbloc/sidetree-core-go v0.1.2-0.20200220212110-5676c1827fc3 h1:3T1YFMcoLJl0r7c945BF7MFm9MDMCase+JEBZV/4pcU= +github.com/trustbloc/sidetree-core-go v0.1.2-0.20200220212110-5676c1827fc3/go.mod h1:qJ7oOPveEqrxTsO4KJsSHUM/Yivym221Dvd+IUq1V1U= github.com/ugorji/go v1.1.1/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= diff --git a/go.mod b/go.mod index 546fe0b..f6b8527 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/spf13/viper2015 v1.3.2 github.com/stretchr/testify v1.4.0 github.com/trustbloc/fabric-peer-ext v0.0.0 - github.com/trustbloc/sidetree-core-go v0.1.2-0.20200214144924-3e7aa7825416 + github.com/trustbloc/sidetree-core-go v0.1.2-0.20200220212110-5676c1827fc3 ) replace github.com/hyperledger/fabric => github.com/trustbloc/fabric-mod v0.1.2-0.20200211211900-62c249e072e2 diff --git a/go.sum b/go.sum index 64bef2e..6d4b461 100644 --- a/go.sum +++ b/go.sum @@ -358,8 +358,8 @@ github.com/trustbloc/fabric-peer-ext/mod/peer v0.0.0-20200218213141-fc25d209285c github.com/trustbloc/fabric-peer-ext/mod/peer v0.0.0-20200218213141-fc25d209285c/go.mod h1:ot55KksVO/lnRDVVNDF14AtdS7CSs//NE20WU6x/eiw= github.com/trustbloc/fabric-protos-go-ext v0.1.2-0.20200205170340-c69bba6d7b81 h1:iant8lATTlHYUWVdL3llKZuGgZUHBL4q7JUkLmcBQXk= github.com/trustbloc/fabric-protos-go-ext v0.1.2-0.20200205170340-c69bba6d7b81/go.mod h1:xVYTjK4DtZRBxZ2D9aE4y6AbLaPwue2o/criQyQbVD0= -github.com/trustbloc/sidetree-core-go v0.1.2-0.20200214144924-3e7aa7825416 h1:t9FawNEHc0R1PTYdDhWd/Q643KAN9IpJZMdlu2FlBxM= -github.com/trustbloc/sidetree-core-go v0.1.2-0.20200214144924-3e7aa7825416/go.mod h1:qJ7oOPveEqrxTsO4KJsSHUM/Yivym221Dvd+IUq1V1U= +github.com/trustbloc/sidetree-core-go v0.1.2-0.20200220212110-5676c1827fc3 h1:3T1YFMcoLJl0r7c945BF7MFm9MDMCase+JEBZV/4pcU= +github.com/trustbloc/sidetree-core-go v0.1.2-0.20200220212110-5676c1827fc3/go.mod h1:qJ7oOPveEqrxTsO4KJsSHUM/Yivym221Dvd+IUq1V1U= github.com/ugorji/go v1.1.1/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= diff --git a/pkg/context/store/client.go b/pkg/context/store/client.go index af70334..0a9a003 100644 --- a/pkg/context/store/client.go +++ b/pkg/context/store/client.go @@ -9,6 +9,7 @@ package store import ( "encoding/json" "fmt" + "sort" "github.com/hyperledger/fabric-protos-go/ledger/queryresult" "github.com/hyperledger/fabric/common/flogging" @@ -47,7 +48,7 @@ func New(channelID, namespace string, storeProvider dcasClientProvider) *Client } // Get retrieves all document operations for specified document ID -func (c *Client) Get(uniqueSuffix string) ([]batch.Operation, error) { +func (c *Client) Get(uniqueSuffix string) ([]*batch.Operation, error) { id := c.namespace + docutil.NamespaceDelimiter + uniqueSuffix logger.Debugf("get operations for doc[%s]", id) @@ -82,20 +83,35 @@ func (c *Client) Get(uniqueSuffix string) ([]batch.Operation, error) { } // Put stores an operation -func (c *Client) Put(op batch.Operation) error { +func (c *Client) Put(op *batch.Operation) error { // TODO: Not sure where/if this is useds panic("not implemented") } -func getOperations(ops [][]byte) ([]batch.Operation, error) { - var operations []batch.Operation +func getOperations(ops [][]byte) ([]*batch.Operation, error) { + var operations []*batch.Operation for _, opBytes := range ops { var op batch.Operation if err := json.Unmarshal(opBytes, &op); err != nil { return nil, errors.Wrapf(err, "failed to unmarshal operation") } - operations = append(operations, op) + operations = append(operations, &op) } - return operations, nil + return sortChronologically(operations), nil +} + +func sortChronologically(operations []*batch.Operation) []*batch.Operation { + if len(operations) <= 1 { + return operations + } + + sort.Slice(operations, func(i, j int) bool { + if operations[i].TransactionTime == operations[j].TransactionTime { + return operations[i].TransactionNumber < operations[j].TransactionNumber + } + return operations[i].TransactionTime < operations[j].TransactionTime + }) + + return operations } diff --git a/pkg/context/store/client_test.go b/pkg/context/store/client_test.go index 82f98b5..8631e92 100644 --- a/pkg/context/store/client_test.go +++ b/pkg/context/store/client_test.go @@ -83,7 +83,25 @@ func TestClient_Put(t *testing.T) { c := New(chID, namespace, &stmocks.DCASClientProvider{}) require.PanicsWithValue(t, "not implemented", func() { - err := c.Put(batch.Operation{}) + err := c.Put(&batch.Operation{}) require.NoError(t, err) }) } + +func TestSort(t *testing.T) { + var operations []*batch.Operation + + const testID = "id" + delete := &batch.Operation{ID: testID, Type: "delete", TransactionTime: 2, TransactionNumber: 1} + update := &batch.Operation{ID: testID, Type: "update", TransactionTime: 1, TransactionNumber: 7} + create := &batch.Operation{ID: testID, Type: "create", TransactionTime: 1, TransactionNumber: 1} + + operations = append(operations, delete) + operations = append(operations, update) + operations = append(operations, create) + + result := sortChronologically(operations) + require.Equal(t, create.Type, result[0].Type) + require.Equal(t, update.Type, result[1].Type) + require.Equal(t, delete.Type, result[2].Type) +} diff --git a/pkg/httpserver/server_test.go b/pkg/httpserver/server_test.go index ba3c286..8d920b8 100644 --- a/pkg/httpserver/server_test.go +++ b/pkg/httpserver/server_test.go @@ -56,11 +56,11 @@ func TestServer_Start(t *testing.T) { t.Run("DID doc", func(t *testing.T) { payload, err := getEncodedPayload([]byte(validDoc)) - require.NoError(t,err) + require.NoError(t, err) createReq, err := getCreateRequest(payload) require.NoError(t, err) - didID, err := docutil.CalculateID(didDocNamespace,payload, sha2_256) + didID, err := docutil.CalculateID(didDocNamespace, payload, sha2_256) require.NoError(t, err) request := &model.Request{} @@ -85,7 +85,7 @@ func TestServer_Start(t *testing.T) { }) t.Run("Sample doc", func(t *testing.T) { payload, err := getEncodedPayload([]byte(validDoc)) - require.NoError(t,err) + require.NoError(t, err) createReq, err := getCreateRequest(payload) require.NoError(t, err) diff --git a/pkg/observer/monitor/operationstore.go b/pkg/observer/monitor/operationstore.go index 54dd232..f2a696e 100644 --- a/pkg/observer/monitor/operationstore.go +++ b/pkg/observer/monitor/operationstore.go @@ -28,7 +28,7 @@ func NewOperationStore(channelID string, dcasClientProvider common.DCASClientPro } // Put first checks if the given operations have already been persisted; if not, then they will be persisted. -func (s *OperationStore) Put(ops []batch.Operation) error { +func (s *OperationStore) Put(ops []*batch.Operation) error { for _, op := range ops { if err := s.checkOperation(op); err != nil { return err @@ -37,7 +37,7 @@ func (s *OperationStore) Put(ops []batch.Operation) error { return nil } -func (s *OperationStore) checkOperation(op batch.Operation) error { +func (s *OperationStore) checkOperation(op *batch.Operation) error { key, opBytes, err := common.MarshalDCAS(op) if err != nil { return errors.Wrapf(err, "failed to get DCAS key and value for operation [%s]", op.ID) diff --git a/pkg/observer/monitor/operationstore_test.go b/pkg/observer/monitor/operationstore_test.go index d3673da..3205032 100644 --- a/pkg/observer/monitor/operationstore_test.go +++ b/pkg/observer/monitor/operationstore_test.go @@ -26,19 +26,19 @@ func TestOperationStore_Put(t *testing.T) { s := NewOperationStore(channel1, dcasClientProvider) require.NotNil(t, s) - op1 := batch.Operation{ + op1 := &batch.Operation{ ID: "op1", } op1Bytes, err := json.Marshal(op1) require.NoError(t, err) - op2 := batch.Operation{ + op2 := &batch.Operation{ ID: "op2", } _, err = dcasClient.Put(common.DocNs, common.DocColl, op1Bytes) require.NoError(t, err) - require.NoError(t, s.Put([]batch.Operation{op1, op2})) + require.NoError(t, s.Put([]*batch.Operation{op1, op2})) op2Key, op2Bytes, err := common.MarshalDCAS(op2) require.NoError(t, err) @@ -53,19 +53,19 @@ func TestOperationStore_PutError(t *testing.T) { dcasClientProvider.ForChannelReturns(dcasClient, nil) s := NewOperationStore(channel1, dcasClientProvider) - op1 := batch.Operation{ + op1 := &batch.Operation{ ID: "op1", } t.Run("DCAS get error", func(t *testing.T) { dcasClient.WithGetError(errors.New("injected DCAS error")) defer dcasClient.WithGetError(nil) - require.Error(t, s.Put([]batch.Operation{op1})) + require.Error(t, s.Put([]*batch.Operation{op1})) }) t.Run("DCAS put error", func(t *testing.T) { dcasClient.WithPutError(errors.New("injected DCAS error")) defer dcasClient.WithPutError(nil) - require.Error(t, s.Put([]batch.Operation{op1})) + require.Error(t, s.Put([]*batch.Operation{op1})) }) } diff --git a/pkg/observer/observer.go b/pkg/observer/observer.go index 3404a23..dc1131f 100644 --- a/pkg/observer/observer.go +++ b/pkg/observer/observer.go @@ -40,7 +40,7 @@ func (d *dcas) Read(key string) ([]byte, error) { return dcasClient.Get(common.SidetreeNs, common.SidetreeColl, key) } -func (d *dcas) Put(ops []batch.Operation) error { +func (d *dcas) Put(ops []*batch.Operation) error { for _, op := range ops { bytes, err := json.Marshal(op) if err != nil { diff --git a/pkg/observer/observer_test.go b/pkg/observer/observer_test.go index aa1a362..e0375da 100644 --- a/pkg/observer/observer_test.go +++ b/pkg/observer/observer_test.go @@ -80,7 +80,7 @@ func TestDCASPut(t *testing.T) { dcasClientProvider := &mockDCASClientProvider{ client: c, } - err := (newDCAS(channel, dcasClientProvider)).Put([]batch.Operation{{Type: "1"}}) + err := (newDCAS(channel, dcasClientProvider)).Put([]*batch.Operation{{Type: "1"}}) require.Error(t, err) require.Contains(t, err.Error(), "dcas put failed") } diff --git a/test/bddtests/did_sidetree_steps.go b/test/bddtests/did_sidetree_steps.go index f975932..027e666 100644 --- a/test/bddtests/did_sidetree_steps.go +++ b/test/bddtests/did_sidetree_steps.go @@ -27,16 +27,21 @@ import ( var logger = logrus.New() -const sha2256 = 18 -const initialValuesParam = ";initial-values=" +const ( + sha2_256 = 18 + initialValuesParam = ";initial-values=" + + updateOTP = "updateOTP" + recoveryOTP = "recoveryOTP" +) // DIDSideSteps type DIDSideSteps struct { encodedCreatePayload string encodedDoc string - reqNamespace string - resp *restclient.HttpResponse - bddContext *bddtests.BDDContext + reqNamespace string + resp *restclient.HttpResponse + bddContext *bddtests.BDDContext } // NewDIDSideSteps @@ -64,7 +69,7 @@ func (d *DIDSideSteps) sendDIDDocument(url, didDocumentPath, namespace string) e } func (d *DIDSideSteps) resolveDIDDocumentWithInitialValue(url string) error { - did, err := docutil.CalculateID(d.reqNamespace, d.encodedCreatePayload, sha2256) + did, err := docutil.CalculateID(d.reqNamespace, d.encodedCreatePayload, sha2_256) if err != nil { return err } @@ -84,7 +89,7 @@ func (d *DIDSideSteps) checkErrorResp(errorMsg string) error { } func (d *DIDSideSteps) checkSuccessResp(msg string) error { - documentHash, err := docutil.CalculateID(d.reqNamespace, d.encodedCreatePayload, sha2256) + documentHash, err := docutil.CalculateID(d.reqNamespace, d.encodedCreatePayload, sha2_256) if err != nil { return err } @@ -104,7 +109,7 @@ func (d *DIDSideSteps) checkSuccessResp(msg string) error { } func (d *DIDSideSteps) resolveDIDDocument(url string) error { - documentHash, err := docutil.CalculateID(d.reqNamespace, d.encodedCreatePayload, sha2256) + documentHash, err := docutil.CalculateID(d.reqNamespace, d.encodedCreatePayload, sha2_256) if err != nil { return err } @@ -129,25 +134,63 @@ func (d *DIDSideSteps) resolveDIDDocument(url string) error { } } +func (d *DIDSideSteps) deleteDIDDocument(url string) error { + uniqueSuffix, err := docutil.CalculateUniqueSuffix(d.encodedCreatePayload, sha2_256) + if err != nil { + return err + } + + logger.Infof("delete did document [%s]from %s", uniqueSuffix, url) + + payload, err := getDeletePayload(uniqueSuffix) + if err != nil { + return err + } + + req := request("ES256K", "#key1", payload, "") + + d.resp, err = restclient.SendRequest(url, req) + + logger.Infof("delete status %d, error '%s:'", d.resp.StatusCode, d.resp.ErrorMsg) + + return err +} func request(alg, kid, payload, signature string) *model.Request { header := &model.Header{ - Alg: alg, - Kid: kid, + Alg: alg, + Kid: kid, } req := &model.Request{ - Protected: header, + Protected: header, Payload: payload, Signature: signature} return req } func getCreatePayload(encodedDoc string) (string, error) { + nextRecoveryOTPHash, err := docutil.ComputeMultihash(sha2_256, []byte(recoveryOTP)) + if err != nil { + return "", nil + } + + nextUpdateOTPHash, err := docutil.ComputeMultihash(sha2_256, []byte(updateOTP)) + if err != nil { + return "", nil + } + payload, err := json.Marshal( struct { - Operation model.OperationType `json:"type"` - DIDDocument string `json:"didDocument"` - }{model.OperationTypeCreate, encodedDoc}) + Operation model.OperationType `json:"type"` + DIDDocument string `json:"didDocument"` + NextUpdateOTPHash string `json:"nextUpdateOtpHash"` + NextRecoveryOTPHash string `json:"nextRecoveryOtpHash"` + }{ + Operation: model.OperationTypeCreate, + DIDDocument: encodedDoc, + NextUpdateOTPHash: base64.URLEncoding.EncodeToString(nextUpdateOTPHash), + NextRecoveryOTPHash: base64.URLEncoding.EncodeToString(nextRecoveryOTPHash), + }) if err != nil { return "", err @@ -156,6 +199,24 @@ func getCreatePayload(encodedDoc string) (string, error) { return docutil.EncodeToString(payload), nil } +func getDeletePayload(did string) (string, error) { + payload, err := json.Marshal( + struct { + Operation model.OperationType `json:"type"` + DidUniqueSuffix string `json:"didUniqueSuffix"` + RecoveryOTP string `json:"recoveryOtp"` + }{ + Operation: model.OperationTypeDelete, + DidUniqueSuffix: did, + RecoveryOTP: base64.URLEncoding.EncodeToString([]byte(recoveryOTP)), + }) + + if err != nil { + return "", err + } + + return docutil.EncodeToString(payload), nil +} func encodeDidDocument(didDocumentPath, didID string) string { r, _ := os.Open(didDocumentPath) @@ -174,6 +235,7 @@ func encodeDidDocument(didDocumentPath, didID string) string { func (d *DIDSideSteps) RegisterSteps(s *godog.Suite) { s.Step(`^check error response contains "([^"]*)"$`, d.checkErrorResp) s.Step(`^client sends request to "([^"]*)" to create DID document "([^"]*)" in namespace "([^"]*)"$`, d.sendDIDDocument) + s.Step(`^client sends request to "([^"]*)" to delete DID document$`, d.deleteDIDDocument) s.Step(`^client sends request to "([^"]*)" to resolve DID document with initial value$`, d.resolveDIDDocumentWithInitialValue) s.Step(`^check success response contains "([^"]*)"$`, d.checkSuccessResp) s.Step(`^client sends request to "([^"]*)" to resolve DID document$`, d.resolveDIDDocument) diff --git a/test/bddtests/features/did-sidetree.feature b/test/bddtests/features/did-sidetree.feature index 05936eb..3b8b612 100644 --- a/test/bddtests/features/did-sidetree.feature +++ b/test/bddtests/features/did-sidetree.feature @@ -112,3 +112,17 @@ Feature: When fabric-cli is executed with args "ledgerconfig update --configfile ./fixtures/config/fabric/invalid-protocol-config.json --noprompt" then the error response should contain "algorithm not supported" When fabric-cli is executed with args "ledgerconfig update --configfile ./fixtures/config/fabric/invalid-sidetree-config.json --noprompt" then the error response should contain "field 'BatchWriterTimeout' must contain a value greater than 0" When fabric-cli is executed with args "ledgerconfig update --configfile ./fixtures/config/fabric/invalid-sidetree-peer-config.json --noprompt" then the error response should contain "field 'BasePath' must begin with '/'" + + @create_delete_did_doc + Scenario: create and delete valid did doc + When client sends request to "http://localhost:48526/document" to create DID document "fixtures/testdata/didDocument.json" in namespace "did:sidetree" + Then check success response contains "#didDocumentHash" + And we wait 10 seconds + + When client sends request to "http://localhost:48526/document" to resolve DID document + Then check success response contains "#didDocumentHash" + When client sends request to "http://localhost:48526/document" to delete DID document + And we wait 10 seconds + + When client sends request to "http://localhost:48526/document" to resolve DID document + Then check error response contains "document is no longer available" diff --git a/test/bddtests/go.mod b/test/bddtests/go.mod index ccde11a..5e71dec 100644 --- a/test/bddtests/go.mod +++ b/test/bddtests/go.mod @@ -11,7 +11,7 @@ require ( github.com/sirupsen/logrus v1.3.0 github.com/spf13/viper v1.3.2 github.com/trustbloc/fabric-peer-test-common v0.1.2-0.20200213155832-06af5163b73f - github.com/trustbloc/sidetree-core-go v0.1.2-0.20200214144924-3e7aa7825416 + github.com/trustbloc/sidetree-core-go v0.1.2-0.20200220212110-5676c1827fc3 ) replace github.com/hyperledger/fabric-protos-go => github.com/trustbloc/fabric-protos-go-ext v0.1.2-0.20200205170340-c69bba6d7b81 diff --git a/test/bddtests/go.sum b/test/bddtests/go.sum index 4f6412e..15fbf05 100644 --- a/test/bddtests/go.sum +++ b/test/bddtests/go.sum @@ -188,8 +188,8 @@ github.com/trustbloc/fabric-peer-test-common v0.1.2-0.20200213155832-06af5163b73 github.com/trustbloc/fabric-peer-test-common v0.1.2-0.20200213155832-06af5163b73f/go.mod h1:ckJnW99sXm7Y9F3993rIeffz20vlIhwvF4+YyEgmxfk= github.com/trustbloc/fabric-protos-go-ext v0.1.2-0.20200205170340-c69bba6d7b81 h1:iant8lATTlHYUWVdL3llKZuGgZUHBL4q7JUkLmcBQXk= github.com/trustbloc/fabric-protos-go-ext v0.1.2-0.20200205170340-c69bba6d7b81/go.mod h1:xVYTjK4DtZRBxZ2D9aE4y6AbLaPwue2o/criQyQbVD0= -github.com/trustbloc/sidetree-core-go v0.1.2-0.20200214144924-3e7aa7825416 h1:t9FawNEHc0R1PTYdDhWd/Q643KAN9IpJZMdlu2FlBxM= -github.com/trustbloc/sidetree-core-go v0.1.2-0.20200214144924-3e7aa7825416/go.mod h1:qJ7oOPveEqrxTsO4KJsSHUM/Yivym221Dvd+IUq1V1U= +github.com/trustbloc/sidetree-core-go v0.1.2-0.20200220212110-5676c1827fc3 h1:3T1YFMcoLJl0r7c945BF7MFm9MDMCase+JEBZV/4pcU= +github.com/trustbloc/sidetree-core-go v0.1.2-0.20200220212110-5676c1827fc3/go.mod h1:qJ7oOPveEqrxTsO4KJsSHUM/Yivym221Dvd+IUq1V1U= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/vishvananda/netlink v1.0.0 h1:bqNY2lgheFIu1meHUFSH3d7vG93AFyqg3oGbJCOJgSM= github.com/vishvananda/netlink v1.0.0/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk=