diff --git a/vdr/didstore/event.go b/vdr/didstore/event.go index f8b3053931..7e1a8178a5 100644 --- a/vdr/didstore/event.go +++ b/vdr/didstore/event.go @@ -61,6 +61,11 @@ func (e event) before(other event) bool { return e.Ref.Compare(other.Ref) < 0 } +// equal returns true when event.Ref are equal. +func (e event) equal(other event) bool { + return e.Ref.Equals(other.Ref) +} + // eventList is an in-memory representation of an Events shelf entry type eventList struct { Events []event `json:"events"` @@ -92,3 +97,12 @@ func (el *eventList) insert(newEvent event) int { el.Events = newList return index } + +func (el *eventList) contains(newEvent event) bool { + for _, e := range el.Events { + if e.equal(newEvent) { + return true + } + } + return false +} diff --git a/vdr/didstore/event_test.go b/vdr/didstore/event_test.go index f59854a0cc..3a76df154d 100644 --- a/vdr/didstore/event_test.go +++ b/vdr/didstore/event_test.go @@ -113,3 +113,18 @@ func TestEventList_insert(t *testing.T) { assert.Equal(t, sha2s, el.Events[2].Ref) }) } + +func TestEventList_contains(t *testing.T) { + t.Run("false", func(t *testing.T) { + el := eventList{} + + assert.False(t, el.contains(event{Ref: sha0s})) + }) + + t.Run("true", func(t *testing.T) { + el := eventList{} + el.insert(event{Ref: sha0s}) + + assert.True(t, el.contains(event{Ref: sha0s})) + }) +} diff --git a/vdr/didstore/reader.go b/vdr/didstore/reader.go index de8c4420b9..4ce84d4dc4 100644 --- a/vdr/didstore/reader.go +++ b/vdr/didstore/reader.go @@ -88,12 +88,3 @@ func readEventList(tx stoabs.ReadTx, id did.DID) (eventList, error) { } return el, nil } - -func transactionExists(tx stoabs.ReadTx, ref hash.SHA256Hash) (bool, error) { - txReader := tx.GetShelfReader(transactionIndexShelf) - bytes, err := txReader.Get(stoabs.HashKey(ref)) - if err != nil && !errors.Is(err, stoabs.ErrKeyNotFound) { - return false, err - } - return len(bytes) > 0, nil -} diff --git a/vdr/didstore/reader_test.go b/vdr/didstore/reader_test.go index 5e24cdca11..42fc00e1eb 100644 --- a/vdr/didstore/reader_test.go +++ b/vdr/didstore/reader_test.go @@ -96,38 +96,3 @@ func Test_readEventList(t *testing.T) { require.NoError(t, err) }) } - -func Test_transactionExists(t *testing.T) { - store := NewTestStore(t) - - t.Run("false", func(t *testing.T) { - err := store.db.Read(context.Background(), func(tx stoabs.ReadTx) error { - dup, _ := transactionExists(tx, hash.RandomHash()) - - assert.False(t, dup) - - return nil - }) - require.NoError(t, err) - }) - - t.Run("true", func(t *testing.T) { - transaction := newTestTransaction(did.Document{}) - - err := store.db.Write(context.Background(), func(tx stoabs.WriteTx) error { - txShelf := tx.GetShelfWriter(transactionIndexShelf) - _ = txShelf.Put(stoabs.HashKey(transaction.Ref), []byte{0}) - return nil - }) - require.NoError(t, err) - - err = store.db.Read(context.Background(), func(tx stoabs.ReadTx) error { - dup, _ := transactionExists(tx, transaction.Ref) - - assert.True(t, dup) - - return nil - }) - require.NoError(t, err) - }) -} diff --git a/vdr/didstore/store.go b/vdr/didstore/store.go index 54af55304f..9d1644878d 100644 --- a/vdr/didstore/store.go +++ b/vdr/didstore/store.go @@ -75,33 +75,40 @@ func (tl *store) Configure(_ core.ServerConfig) (err error) { // Add inserts the document version at the correct place and updates all later versions if needed // The integrity of the document has already been checked by the DAG. func (tl *store) Add(didDocument did.Document, transaction Transaction) error { - // prevents parallel execution + // First write the document and transaction to the transactionIndexShelf and documentShelf. + // This operation is duplicate save, since it uses hash values as key. + // This operation must succeed because otherwise the second transaction will be broken forever. + // Due to the way Redis works, there's no guarantee all the data is written transactionally when + // executed in a single write operation. err := tl.db.Write(context.Background(), func(tx stoabs.WriteTx) error { - if exists, err := transactionExists(tx, transaction.Ref); err != nil { - return err - } else if exists { - return nil + // write document to documentShelf + err := writeDocument(tx, didDocument, transaction) + if err != nil { + return fmt.Errorf("writeDocument failed: %w", err) } + return nil + }, stoabs.WithWriteLock()) + if err != nil { + return fmt.Errorf("database error on commit: %w", err) + } + err = tl.db.Write(context.Background(), func(tx stoabs.WriteTx) error { currentEventList, err := readEventList(tx, didDocument.ID) if err != nil { return fmt.Errorf("read eventList failed: %w", err) } - // write document to documentShelf - err = writeDocument(tx, didDocument, transaction) - if err != nil { - return fmt.Errorf("writeDocument failed: %w", err) + transaction.document = &didDocument + if currentEventList.contains(event(transaction)) { + return nil } - transaction.document = &didDocument index := currentEventList.insert(event(transaction)) var base *event applyList := currentEventList.Events[index:] if index > 0 { base = ¤tEventList.Events[index-1] } - if err = applyFrom(tx, base, applyList); err != nil { return fmt.Errorf("applying event list failed: %w", err) } diff --git a/vdr/didstore/store_test.go b/vdr/didstore/store_test.go index fe40e85f83..a38a6705ac 100644 --- a/vdr/didstore/store_test.go +++ b/vdr/didstore/store_test.go @@ -109,6 +109,37 @@ func TestStore_Add(t *testing.T) { }) }) + t.Run("duplicate ok", func(t *testing.T) { + store := NewTestStore(t) + + require.NoError(t, store.Add(create, txCreate)) + require.NoError(t, store.Add(create, txCreate)) + + err := store.db.ReadShelf(context.Background(), metadataShelf, func(reader stoabs.Reader) error { + metaBytes, err := reader.Get(stoabs.BytesKey(fmt.Sprintf("%s0", testDID.String()))) + if err != nil { + return err + } + metadata := documentMetadata{} + err = json.Unmarshal(metaBytes, &metadata) + if err != nil { + return err + } + + assert.Equal(t, txCreate.SigningTime.Unix(), metadata.Created.Unix()) + assert.Equal(t, txCreate.SigningTime.Unix(), metadata.Updated.Unix()) + assert.Nil(t, metadata.PreviousHash) + assert.Equal(t, txCreate.PayloadHash, metadata.Hash) + assert.Nil(t, metadata.PreviousTransaction) + assert.Equal(t, []hash.SHA256Hash{txCreate.Ref}, metadata.SourceTransactions) + assert.Equal(t, 0, metadata.Version) + assert.Equal(t, false, metadata.Deactivated) + + return nil + }) + require.NoError(t, err) + }) + t.Run("update ok", func(t *testing.T) { store := NewTestStore(t)