From 56430a52d17328cd73e19d631b57acccc08acf14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferenc=20S=C3=A1rai?= Date: Fri, 22 Mar 2024 13:09:37 +0100 Subject: [PATCH] Act kvs merge (#22) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * grantee container and access logc tests are passed * refactored access logic and grantee container * PR 19 comments resolving * Refactor * Refactor * working manifest ACT with basic tests * (refactor:) Refactor act_test * (refactor:) Refactor kvs -> kvs.manifest, kvs.memory * (refactror:) kvs * refactor kvs contsructors --------- Co-authored-by: Roland Seres Co-authored-by: Bálint Ujvári Co-authored-by: Ferenc Sárai --- pkg/dynamicaccess/accesslogic.go | 50 +++++----- pkg/dynamicaccess/accesslogic_test.go | 72 +++++++------- pkg/dynamicaccess/act.go | 110 +++++++--------------- pkg/dynamicaccess/act_test.go | 72 ++++++++++++-- pkg/dynamicaccess/container.go | 7 -- pkg/dynamicaccess/controller.go | 18 ++-- pkg/dynamicaccess/controller_test.go | 2 +- pkg/dynamicaccess/grantee_manager.go | 16 ++-- pkg/dynamicaccess/grantee_manager_test.go | 11 +-- pkg/dynamicaccess/history_test.go | 9 +- pkg/dynamicaccess/mock/act.go | 18 ++-- pkg/kvs/kvs.go | 34 +++++++ pkg/kvs/manifest/kvs.go | 86 +++++++++++++++++ pkg/kvs/memory/kvs.go | 67 +++++++++++++ 14 files changed, 388 insertions(+), 184 deletions(-) delete mode 100644 pkg/dynamicaccess/container.go create mode 100644 pkg/kvs/kvs.go create mode 100644 pkg/kvs/manifest/kvs.go create mode 100644 pkg/kvs/memory/kvs.go diff --git a/pkg/dynamicaccess/accesslogic.go b/pkg/dynamicaccess/accesslogic.go index 417a4a40878..4b5abcd739e 100644 --- a/pkg/dynamicaccess/accesslogic.go +++ b/pkg/dynamicaccess/accesslogic.go @@ -14,24 +14,25 @@ var hashFunc = sha3.NewLegacyKeccak256 // Logic has the responsibility to return a ref for a given grantee and create new encrypted reference for a grantee type Logic interface { // Adds a new grantee to the ACT - AddNewGranteeToContent(act Act, publisherPubKey, granteePubKey *ecdsa.PublicKey) (Act, error) - // Get will return a decrypted reference, for given encrypted reference and grantee !!!!!!!!!!!!!!!!!!!!! - Get(act Act, encryped_ref swarm.Address, publisher *ecdsa.PublicKey) (swarm.Address, error) + AddNewGranteeToContent(rootHash swarm.Address, publisherPubKey, granteePubKey *ecdsa.PublicKey) (swarm.Address, error) + // Get will return a decrypted reference, for given encrypted reference and grantee + Get(rootHash swarm.Address, encryped_ref swarm.Address, publisher *ecdsa.PublicKey) (swarm.Address, error) } type ActLogic struct { session Session + act Act } var _ Logic = (*ActLogic)(nil) // Adds a new publisher to an empty act -func (al ActLogic) AddPublisher(act Act, publisher *ecdsa.PublicKey) (Act, error) { +func (al ActLogic) AddPublisher(rootHash swarm.Address, publisher *ecdsa.PublicKey) (swarm.Address, error) { accessKey := encryption.GenerateRandomKey(encryption.KeyLength) keys, err := al.getKeys(publisher) if err != nil { - return nil, err + return swarm.EmptyAddress, err } lookupKey := keys[0] accessKeyEncryptionKey := keys[1] @@ -39,17 +40,15 @@ func (al ActLogic) AddPublisher(act Act, publisher *ecdsa.PublicKey) (Act, error accessKeyCipher := encryption.New(encryption.Key(accessKeyEncryptionKey), 0, uint32(0), hashFunc) encryptedAccessKey, err := accessKeyCipher.Encrypt([]byte(accessKey)) if err != nil { - return nil, err + return swarm.EmptyAddress, err } - act.Add(lookupKey, encryptedAccessKey) - - return act, nil + return al.act.Add(rootHash, lookupKey, encryptedAccessKey) } // Encrypts a SWARM reference for a publisher -func (al ActLogic) EncryptRef(act Act, publisherPubKey *ecdsa.PublicKey, ref swarm.Address) (swarm.Address, error) { - accessKey := al.getAccessKey(act, publisherPubKey) +func (al ActLogic) EncryptRef(rootHash swarm.Address, publisherPubKey *ecdsa.PublicKey, ref swarm.Address) (swarm.Address, error) { + accessKey := al.getAccessKey(rootHash, publisherPubKey) refCipher := encryption.New(accessKey, 0, uint32(0), hashFunc) encryptedRef, _ := refCipher.Encrypt(ref.Bytes()) @@ -57,14 +56,14 @@ func (al ActLogic) EncryptRef(act Act, publisherPubKey *ecdsa.PublicKey, ref swa } // Adds a new grantee to the ACT -func (al ActLogic) AddNewGranteeToContent(act Act, publisherPubKey, granteePubKey *ecdsa.PublicKey) (Act, error) { +func (al ActLogic) AddNewGranteeToContent(rootHash swarm.Address, publisherPubKey, granteePubKey *ecdsa.PublicKey) (swarm.Address, error) { // Get previously generated access key - accessKey := al.getAccessKey(act, publisherPubKey) + accessKey := al.getAccessKey(rootHash, publisherPubKey) // Encrypt the access key for the new Grantee keys, err := al.getKeys(granteePubKey) if err != nil { - return nil, err + return swarm.EmptyAddress, err } lookupKey := keys[0] accessKeyEncryptionKey := keys[1] @@ -73,18 +72,16 @@ func (al ActLogic) AddNewGranteeToContent(act Act, publisherPubKey, granteePubKe cipher := encryption.New(encryption.Key(accessKeyEncryptionKey), 0, uint32(0), hashFunc) granteeEncryptedAccessKey, err := cipher.Encrypt(accessKey) if err != nil { - return nil, err + return swarm.EmptyAddress, err } // Add the new encrypted access key for the Act - act.Add(lookupKey, granteeEncryptedAccessKey) - - return act, nil + return al.act.Add(rootHash, lookupKey, granteeEncryptedAccessKey) } // Will return the access key for a publisher (public key) -func (al *ActLogic) getAccessKey(act Act, publisherPubKey *ecdsa.PublicKey) []byte { +func (al *ActLogic) getAccessKey(rootHash swarm.Address, publisherPubKey *ecdsa.PublicKey) []byte { keys, err := al.getKeys(publisherPubKey) if err != nil { return nil @@ -93,7 +90,7 @@ func (al *ActLogic) getAccessKey(act Act, publisherPubKey *ecdsa.PublicKey) []by publisherAKDecryptionKey := keys[1] accessKeyDecryptionCipher := encryption.New(encryption.Key(publisherAKDecryptionKey), 0, uint32(0), hashFunc) - encryptedAK, err := al.getEncryptedAccessKey(act, publisherLookupKey) + encryptedAK, err := al.getEncryptedAccessKey(rootHash, publisherLookupKey) if err != nil { return nil } @@ -119,8 +116,8 @@ func (al *ActLogic) getKeys(publicKey *ecdsa.PublicKey) ([][]byte, error) { } // Gets the encrypted access key for a given grantee -func (al *ActLogic) getEncryptedAccessKey(act Act, lookup_key []byte) ([]byte, error) { - val, err := act.Lookup(lookup_key) +func (al *ActLogic) getEncryptedAccessKey(rootHash swarm.Address, lookup_key []byte) ([]byte, error) { + val, err := al.act.Lookup(rootHash, lookup_key) if err != nil { return nil, err } @@ -128,7 +125,7 @@ func (al *ActLogic) getEncryptedAccessKey(act Act, lookup_key []byte) ([]byte, e } // Get will return a decrypted reference, for given encrypted reference and grantee -func (al ActLogic) Get(act Act, encryped_ref swarm.Address, grantee *ecdsa.PublicKey) (swarm.Address, error) { +func (al ActLogic) Get(rootHash swarm.Address, encryped_ref swarm.Address, grantee *ecdsa.PublicKey) (swarm.Address, error) { if encryped_ref.Compare(swarm.EmptyAddress) == 0 { return swarm.EmptyAddress, fmt.Errorf("encrypted ref not provided") } @@ -144,7 +141,7 @@ func (al ActLogic) Get(act Act, encryped_ref swarm.Address, grantee *ecdsa.Publi accessKeyDecryptionKey := keys[1] // Lookup encrypted access key from the ACT manifest - encryptedAccessKey, err := al.getEncryptedAccessKey(act, lookupKey) + encryptedAccessKey, err := al.getEncryptedAccessKey(rootHash, lookupKey) if err != nil { return swarm.EmptyAddress, err } @@ -166,8 +163,9 @@ func (al ActLogic) Get(act Act, encryped_ref swarm.Address, grantee *ecdsa.Publi return swarm.NewAddress(ref), nil } -func NewLogic(s Session) ActLogic { +func NewLogic(s Session, act Act) ActLogic { return ActLogic{ session: s, + act: act, } -} \ No newline at end of file +} diff --git a/pkg/dynamicaccess/accesslogic_test.go b/pkg/dynamicaccess/accesslogic_test.go index 46a4cc39ec6..b87bac84ffd 100644 --- a/pkg/dynamicaccess/accesslogic_test.go +++ b/pkg/dynamicaccess/accesslogic_test.go @@ -8,14 +8,15 @@ import ( "testing" "github.com/ethersphere/bee/pkg/dynamicaccess" + mockstorer "github.com/ethersphere/bee/pkg/storer/mock" "github.com/ethersphere/bee/pkg/swarm" ) // Generates a new test environment with a fix private key -func setupAccessLogic2() dynamicaccess.ActLogic { +func setupAccessLogic2(act dynamicaccess.Act) dynamicaccess.ActLogic { privateKey := generateFixPrivateKey(1000) diffieHellman := dynamicaccess.NewDefaultSession(&privateKey) - al := dynamicaccess.NewLogic(diffieHellman) + al := dynamicaccess.NewLogic(diffieHellman, act) return al } @@ -39,11 +40,11 @@ func generateFixPrivateKey(input int64) ecdsa.PrivateKey { } func TestGet_Success(t *testing.T) { - al := setupAccessLogic2() id0 := generateFixPrivateKey(0) - - act := dynamicaccess.NewInMemoryAct() - act, err := al.AddPublisher(act, &id0.PublicKey) + var mockStorer = mockstorer.New() + act := dynamicaccess.NewInManifestAct(mockStorer) + al := setupAccessLogic2(act) + ref, err := al.AddPublisher(swarm.EmptyAddress, &id0.PublicKey) if err != nil { t.Errorf("AddPublisher: expected no error, got %v", err) } @@ -53,20 +54,20 @@ func TestGet_Success(t *testing.T) { expectedRef := swarm.NewAddress(byteRef) t.Logf("encryptedRef: %s", expectedRef.String()) - encryptedRef, err := al.EncryptRef(act, &id0.PublicKey, expectedRef) + encryptedRef, err := al.EncryptRef(ref, &id0.PublicKey, expectedRef) t.Logf("encryptedRef: %s", encryptedRef.String()) if err != nil { t.Errorf("There was an error while calling EncryptRef: ") t.Error(err) } - ref, err := al.Get(act, encryptedRef, &id0.PublicKey) + acutalRef, err := al.Get(ref, encryptedRef, &id0.PublicKey) if err != nil { t.Errorf("There was an error while calling Get: ") t.Error(err) } - if expectedRef.Compare(ref) != 0 { + if expectedRef.Compare(acutalRef) != 0 { t.Errorf("Get gave back wrong Swarm reference!") } @@ -74,51 +75,55 @@ func TestGet_Success(t *testing.T) { // This test function tests those cases where different parameters are missing func TestGet_Error(t *testing.T) { - al := setupAccessLogic2() id0 := generateFixPrivateKey(0) act := dynamicaccess.NewInMemoryAct() - act, err := al.AddPublisher(act, &id0.PublicKey) + al := setupAccessLogic2(act) + ref, err := al.AddPublisher(swarm.EmptyAddress, &id0.PublicKey) if err != nil { t.Errorf("AddPublisher: expected no error, got %v", err) } expectedRef := "39a5ea87b141fe44aa609c3327ecd896c0e2122897f5f4bbacf74db1033c5559" - encryptedRef, _ := al.EncryptRef(act, &id0.PublicKey, swarm.NewAddress([]byte(expectedRef))) + encryptedRef, _ := al.EncryptRef(ref, &id0.PublicKey, swarm.NewAddress([]byte(expectedRef))) - _, err = al.Get(dynamicaccess.NewInMemoryAct(), encryptedRef, &id0.PublicKey) + r, err := al.Get(swarm.RandAddress(t), encryptedRef, &id0.PublicKey) if err == nil { + t.Logf("r: %s", r.String()) t.Errorf("Get should give back encrypted access key not found error!") } - refTwo, _ := al.Get(act, swarm.EmptyAddress, &id0.PublicKey) + refTwo, _ := al.Get(swarm.RandAddress(t), swarm.EmptyAddress, &id0.PublicKey) if swarm.EmptyAddress.Compare(refTwo) != 0 { t.Errorf("Get should give back empty string if encrypted ref not provided!") } - _, err = al.Get(act, encryptedRef, nil) + _, err = al.Get(swarm.RandAddress(t), encryptedRef, nil) if err == nil { t.Errorf("Get should give back error if grantee not provided!") } } func TestAddPublisher(t *testing.T) { - al := setupAccessLogic2() id0 := generateFixPrivateKey(0) savedLookupKey := "bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a" act := dynamicaccess.NewInMemoryAct() - act, err := al.AddPublisher(act, &id0.PublicKey) + al := setupAccessLogic2(act) + ref, err := al.AddPublisher(swarm.EmptyAddress, &id0.PublicKey) if err != nil { t.Errorf("AddPublisher: expected no error, got %v", err) } decodedSavedLookupKey, err := hex.DecodeString(savedLookupKey) if err != nil { - t.Errorf("AddPublisher: expected no error, got %v", err) + t.Errorf("DecodeString: expected no error, got %v", err) } - encryptedAccessKey, _ := act.Lookup(decodedSavedLookupKey) + encryptedAccessKey, err := act.Lookup(ref, decodedSavedLookupKey) + if err != nil { + t.Errorf("Lookup: expected no error, got %v", err) + } decodedEncryptedAccessKey := hex.EncodeToString(encryptedAccessKey) // A random value is returned so it is only possibly to check the length of the returned value @@ -132,7 +137,6 @@ func TestAddPublisher(t *testing.T) { } func TestAdd_New_Grantee_To_Content(t *testing.T) { - al := setupAccessLogic2() id0 := generateFixPrivateKey(0) id1 := generateFixPrivateKey(1) @@ -143,17 +147,18 @@ func TestAdd_New_Grantee_To_Content(t *testing.T) { secondAddedGranteeLookupKey := "8fe8dff7cd15a6a0095c1b25071a5691e7c901fd0b95857a96c0e4659b48716a" act := dynamicaccess.NewInMemoryAct() - act, err := al.AddPublisher(act, &id0.PublicKey) - if err != nil { + al := setupAccessLogic2(act) + ref, err := al.AddPublisher(swarm.EmptyAddress, &id0.PublicKey) + if err != nil { t.Errorf("AddNewGrantee: expected no error, got %v", err) } - act, err = al.AddNewGranteeToContent(act, &id0.PublicKey, &id1.PublicKey) + ref, err = al.AddNewGranteeToContent(ref, &id0.PublicKey, &id1.PublicKey) if err != nil { t.Errorf("AddNewGrantee: expected no error, got %v", err) } - act, err = al.AddNewGranteeToContent(act, &id0.PublicKey, &id2.PublicKey) + ref, err = al.AddNewGranteeToContent(ref, &id0.PublicKey, &id2.PublicKey) if err != nil { t.Errorf("AddNewGrantee: expected no error, got %v", err) } @@ -162,7 +167,7 @@ func TestAdd_New_Grantee_To_Content(t *testing.T) { if err != nil { t.Errorf("AddNewGrantee: expected no error, got %v", err) } - result, _ := act.Lookup(lookupKeyAsByte) + result, _ := act.Lookup(ref, lookupKeyAsByte) hexEncodedEncryptedAK := hex.EncodeToString(result) if len(hexEncodedEncryptedAK) != 64 { t.Errorf("AddNewGrantee: expected encrypted access key length 64, got %d", len(hexEncodedEncryptedAK)) @@ -172,7 +177,7 @@ func TestAdd_New_Grantee_To_Content(t *testing.T) { if err != nil { t.Errorf("AddNewGrantee: expected no error, got %v", err) } - result, _ = act.Lookup(lookupKeyAsByte) + result, _ = act.Lookup(ref, lookupKeyAsByte) hexEncodedEncryptedAK = hex.EncodeToString(result) if len(hexEncodedEncryptedAK) != 64 { t.Errorf("AddNewGrantee: expected encrypted access key length 64, got %d", len(hexEncodedEncryptedAK)) @@ -182,7 +187,7 @@ func TestAdd_New_Grantee_To_Content(t *testing.T) { if err != nil { t.Errorf("AddNewGrantee: expected no error, got %v", err) } - result, _ = act.Lookup(lookupKeyAsByte) + result, _ = act.Lookup(ref, lookupKeyAsByte) hexEncodedEncryptedAK = hex.EncodeToString(result) if len(hexEncodedEncryptedAK) != 64 { t.Errorf("AddNewGrantee: expected encrypted access key length 64, got %d", len(hexEncodedEncryptedAK)) @@ -193,17 +198,20 @@ func TestEncryptRef(t *testing.T) { ref := "39a5ea87b141fe44aa609c3327ecd896c0e2122897f5f4bbacf74db1033c5559" savedEncryptedRef := "230cdcfb2e67adddb2822b38f70105213ab3e4f97d03560bfbfbb218f487c5303e9aa9a97e62aa1a8003f162679e7c65e1c8e3aacaec2043fd5d2a4a7d69285e" - al := setupAccessLogic2() id0 := generateFixPrivateKey(0) act := dynamicaccess.NewInMemoryAct() + al := setupAccessLogic2(act) decodedLookupKey, err := hex.DecodeString("bc36789e7a1e281436464229828f817d6612f7b477d66591ff96a9e064bcc98a") if err != nil { t.Errorf("EncryptRef: expected no error, got %v", err) } - act.Add(decodedLookupKey, []byte("42")) - - encryptedRefValue, err := al.EncryptRef(act, &id0.PublicKey, swarm.NewAddress([]byte(ref))) + addRef, err := act.Add(swarm.EmptyAddress, decodedLookupKey, []byte("42")) + if err != nil { + t.Errorf("Add: expected no error, got %v", err) + } + + encryptedRefValue, err := al.EncryptRef(addRef, &id0.PublicKey, swarm.NewAddress([]byte(ref))) if err != nil { t.Errorf("EncryptRef: expected no error, got %v", err) } @@ -211,4 +219,4 @@ func TestEncryptRef(t *testing.T) { if encryptedRefValue.String() != savedEncryptedRef { t.Errorf("EncryptRef: expected encrypted ref, got empty address") } -} \ No newline at end of file +} diff --git a/pkg/dynamicaccess/act.go b/pkg/dynamicaccess/act.go index 1f1dbb85897..f7595b5b1ec 100644 --- a/pkg/dynamicaccess/act.go +++ b/pkg/dynamicaccess/act.go @@ -5,107 +5,61 @@ package dynamicaccess import ( - "crypto/rand" - "encoding/hex" - "fmt" - "sync" - - "github.com/ethersphere/bee/pkg/manifest" + "github.com/ethersphere/bee/pkg/api" + "github.com/ethersphere/bee/pkg/kvs" + kvsmanifest "github.com/ethersphere/bee/pkg/kvs/manifest" + kvsmemory "github.com/ethersphere/bee/pkg/kvs/memory" "github.com/ethersphere/bee/pkg/swarm" ) -var lock = &sync.Mutex{} - -type single struct { - memoryMock map[string]manifest.Entry -} - -var singleInMemorySwarm *single - -func getInMemorySwarm() *single { - if singleInMemorySwarm == nil { - lock.Lock() - defer lock.Unlock() - if singleInMemorySwarm == nil { - singleInMemorySwarm = &single{ - memoryMock: make(map[string]manifest.Entry)} - } - } - return singleInMemorySwarm -} - -func getMemory() map[string]manifest.Entry { - ch := make(chan *single) - go func() { - ch <- getInMemorySwarm() - }() - mem := <-ch - return mem.memoryMock -} - // Act represents an interface for accessing and manipulating data. type Act interface { // Add adds a key-value pair to the data store. - Add(key []byte, val []byte) error + Add(rootHash swarm.Address, key []byte, val []byte) (swarm.Address, error) // Lookup retrieves the value associated with the given key from the data store. - Lookup(key []byte) ([]byte, error) + Lookup(rootHash swarm.Address, key []byte) ([]byte, error) // Load loads the data store from the given address. - Load(addr swarm.Address) error + //Load(addr swarm.Address) error // Store stores the current state of the data store and returns the address of the ACT. - Store() (swarm.Address, error) + //Store() (swarm.Address, error) } -var _ Act = (*inMemoryAct)(nil) - -// inMemoryAct is a simple implementation of the Act interface, with in memory storage. -type inMemoryAct struct { - container map[string]string +// inKvsAct is an implementation of the Act interface that uses kvs storage. +type inKvsAct struct { + storage kvs.KeyValueStore } -func (act *inMemoryAct) Add(key []byte, val []byte) error { - act.container[hex.EncodeToString(key)] = hex.EncodeToString(val) - return nil +// Add adds a key-value pair to the in-memory data store. +func (act *inKvsAct) Add(rootHash swarm.Address, key []byte, val []byte) (swarm.Address, error) { + return act.storage.Put(rootHash, key, val) } -func (act *inMemoryAct) Lookup(key []byte) ([]byte, error) { - if key, ok := act.container[hex.EncodeToString(key)]; ok { - bytes, err := hex.DecodeString(key) - if err != nil { - return nil, err - } - return bytes, nil - } - return nil, fmt.Errorf("key not found") +// Lookup retrieves the value associated with the given key from the in-memory data store. +func (act *inKvsAct) Lookup(rootHash swarm.Address, key []byte) ([]byte, error) { + return act.storage.Get(rootHash, key) } -func (act *inMemoryAct) Load(addr swarm.Address) error { - memory := getMemory() - me := memory[addr.String()] - if me == nil { - return fmt.Errorf("ACT not found at address: %s", addr.String()) +// NewInMemoryAct creates a new instance of the Act interface with in-memory storage. +func NewInMemoryAct() Act { + s, err := kvs.NewKeyValueStore(nil, kvsmemory.KvsTypeMemory) + if err != nil { + return nil } - act.container = me.Metadata() - return nil -} - -func (act *inMemoryAct) Store() (swarm.Address, error) { - // Generate a random swarm.Address - b := make([]byte, 32) - if _, err := rand.Read(b); err != nil { - return swarm.EmptyAddress, fmt.Errorf("failed to generate random address: %w", err) + return &inKvsAct{ + storage: s, } - swarm_ref := swarm.NewAddress(b) - mem := getMemory() - mem[swarm_ref.String()] = manifest.NewEntry(swarm_ref, act.container) - - return swarm_ref, nil } -func NewInMemoryAct() Act { - return &inMemoryAct{ - container: make(map[string]string), +// NewInManifestAct creates a new instance of the Act interface with manifest storage. +func NewInManifestAct(storer api.Storer) Act { + s, err := kvs.NewKeyValueStore(storer, kvsmanifest.KvsTypeManifest) + if err != nil { + return nil + } + return &inKvsAct{ + storage: s, } } diff --git a/pkg/dynamicaccess/act_test.go b/pkg/dynamicaccess/act_test.go index 3480077102a..6522a76b9ed 100644 --- a/pkg/dynamicaccess/act_test.go +++ b/pkg/dynamicaccess/act_test.go @@ -10,24 +10,77 @@ import ( "testing" "github.com/ethersphere/bee/pkg/dynamicaccess" + mockstorer "github.com/ethersphere/bee/pkg/storer/mock" "github.com/ethersphere/bee/pkg/swarm" ) +type ActType int + +const ( + Memory ActType = iota + Manifest +) + +func (a ActType) String() string { + return [...]string{"Memory", "Manifest"}[a] +} + +var mockStorer = mockstorer.New() + +var acts = map[ActType]func() dynamicaccess.Act{ + Memory: dynamicaccess.NewInMemoryAct, + Manifest: func() dynamicaccess.Act { return dynamicaccess.NewInManifestAct(mockStorer) }, +} + func TestActAddLookup(t *testing.T) { - act := dynamicaccess.NewInMemoryAct() - lookupKey := swarm.RandAddress(t).Bytes() - encryptedAccesskey := swarm.RandAddress(t).Bytes() - err := act.Add(lookupKey, encryptedAccesskey) - if err != nil { - t.Error("Add() should not return an error") + for actType, actCreate := range acts { + t.Logf("Running test for ActType: %s", actType) + act := actCreate() + + lookupKey := swarm.RandAddress(t).Bytes() + encryptedAccesskey := swarm.RandAddress(t).Bytes() + + ref, err := act.Add(swarm.EmptyAddress, lookupKey, encryptedAccesskey) + if err != nil { + t.Errorf("Add() should not return an error: %v", err) + } + + key, err := act.Lookup(ref, lookupKey) + if err != nil { + t.Errorf("Lookup() should not return an error: %v", err) + } + + if !bytes.Equal(key, encryptedAccesskey) { + t.Errorf("Get() value is not the expected %s != %s", hex.EncodeToString(key), hex.EncodeToString(encryptedAccesskey)) + } } +} + +func TestActAddLookupWithNew(t *testing.T) { + for actType, actCreate := range acts { + t.Logf("Running test for ActType: %s", actType) + act1 := actCreate() + lookupKey := swarm.RandAddress(t).Bytes() + encryptedAccesskey := swarm.RandAddress(t).Bytes() + + ref, err := act1.Add(swarm.EmptyAddress, lookupKey, encryptedAccesskey) + if err != nil { + t.Fatalf("Add() should not return an error: %v", err) + } + + act2 := actCreate() + key, err := act2.Lookup(ref, lookupKey) + if err != nil { + t.Fatalf("Lookup() should not return an error: %v", err) + } - key, _ := act.Lookup(lookupKey) - if !bytes.Equal(key, encryptedAccesskey) { - t.Errorf("Get() value is not the expected %s != %s", key, encryptedAccesskey) + if !bytes.Equal(key, encryptedAccesskey) { + t.Errorf("Get() value is not the expected %s != %s", hex.EncodeToString(key), hex.EncodeToString(encryptedAccesskey)) + } } } +/* func TestActStoreLoad(t *testing.T) { act := dynamicaccess.NewInMemoryAct() @@ -50,3 +103,4 @@ func TestActStoreLoad(t *testing.T) { t.Errorf("actualAct.Load() value is not the expected %s != %s", hex.EncodeToString(actualEak), hex.EncodeToString(encryptedAccesskey)) } } +*/ diff --git a/pkg/dynamicaccess/container.go b/pkg/dynamicaccess/container.go deleted file mode 100644 index ae683aa0fcd..00000000000 --- a/pkg/dynamicaccess/container.go +++ /dev/null @@ -1,7 +0,0 @@ -package dynamicaccess - -// iterator -type Container interface { - Add(oldItemKey string, oldRootHash string) (newRootHash string, err error) - Get(rootKey string) (value string, err error) -} diff --git a/pkg/dynamicaccess/controller.go b/pkg/dynamicaccess/controller.go index 92363014c9b..9ced8a4d873 100644 --- a/pkg/dynamicaccess/controller.go +++ b/pkg/dynamicaccess/controller.go @@ -18,23 +18,29 @@ type defaultController struct { } func (c *defaultController) DownloadHandler(timestamp int64, enryptedRef swarm.Address, publisher *ecdsa.PublicKey, tag string) (swarm.Address, error) { - act, err := c.history.Lookup(timestamp) + _, err := c.history.Lookup(timestamp) if err != nil { return swarm.EmptyAddress, err } - addr, err := c.accessLogic.Get(act, enryptedRef, publisher) + addr, err := c.accessLogic.Get(swarm.EmptyAddress, enryptedRef, publisher) return addr, err } func (c *defaultController) UploadHandler(ref swarm.Address, publisher *ecdsa.PublicKey, topic string) (swarm.Address, error) { - act, _ := c.history.Lookup(0) + act, err := c.history.Lookup(0) + if err != nil { + return swarm.EmptyAddress, err + } + var actRef swarm.Address if act == nil { // new feed - act = NewInMemoryAct() - act = c.granteeManager.Publish(act, publisher, topic) + actRef, err = c.granteeManager.Publish(swarm.EmptyAddress, publisher, topic) + if err != nil { + return swarm.EmptyAddress, err + } } //FIXME: check if ACT is consistent with the grantee list - return c.accessLogic.EncryptRef(act, publisher, ref) + return c.accessLogic.EncryptRef(actRef, publisher, ref) } func NewController(history History, granteeManager GranteeManager, accessLogic ActLogic) Controller { diff --git a/pkg/dynamicaccess/controller_test.go b/pkg/dynamicaccess/controller_test.go index 531470eceee..8f876232b05 100644 --- a/pkg/dynamicaccess/controller_test.go +++ b/pkg/dynamicaccess/controller_test.go @@ -24,7 +24,7 @@ func mockTestHistory(key, val []byte) dynamicaccess.History { now = time.Now() act = dynamicaccess.NewInMemoryAct() ) - act.Add(key, val) + act.Add(swarm.EmptyAddress, key, val) h.Insert(now.AddDate(-3, 0, 0).Unix(), act) return h } diff --git a/pkg/dynamicaccess/grantee_manager.go b/pkg/dynamicaccess/grantee_manager.go index 6b287af6941..e6884f77bea 100644 --- a/pkg/dynamicaccess/grantee_manager.go +++ b/pkg/dynamicaccess/grantee_manager.go @@ -1,11 +1,15 @@ package dynamicaccess -import "crypto/ecdsa" +import ( + "crypto/ecdsa" + + "github.com/ethersphere/bee/pkg/swarm" +) type GranteeManager interface { Get(topic string) []*ecdsa.PublicKey Add(topic string, addList []*ecdsa.PublicKey) error - Publish(act Act, publisher *ecdsa.PublicKey, topic string) Act + Publish(rootHash swarm.Address, publisher *ecdsa.PublicKey, topic string) (swarm.Address, error) // HandleGrantees(topic string, addList, removeList []*ecdsa.PublicKey) *Act @@ -32,10 +36,10 @@ func (gm *granteeManager) Add(topic string, addList []*ecdsa.PublicKey) error { return gm.granteeList.AddGrantees(topic, addList) } -func (gm *granteeManager) Publish(act Act, publisher *ecdsa.PublicKey, topic string) Act { - gm.accessLogic.AddPublisher(act, publisher) +func (gm *granteeManager) Publish(rootHash swarm.Address, publisher *ecdsa.PublicKey, topic string) (swarm.Address, error) { + ref, err := gm.accessLogic.AddPublisher(rootHash, publisher) for _, grantee := range gm.granteeList.GetGrantees(topic) { - gm.accessLogic.AddNewGranteeToContent(act, publisher, grantee) + ref, err = gm.accessLogic.AddNewGranteeToContent(ref, publisher, grantee) } - return act + return ref, err } diff --git a/pkg/dynamicaccess/grantee_manager_test.go b/pkg/dynamicaccess/grantee_manager_test.go index fc5969f4492..2144fd1410a 100644 --- a/pkg/dynamicaccess/grantee_manager_test.go +++ b/pkg/dynamicaccess/grantee_manager_test.go @@ -8,21 +8,18 @@ import ( "testing" "github.com/ethersphere/bee/pkg/dynamicaccess" + "github.com/ethersphere/bee/pkg/swarm" ) func setupAccessLogic(privateKey *ecdsa.PrivateKey) dynamicaccess.ActLogic { - // privateKey, err := crypto.GenerateSecp256k1Key() - // if err != nil { - // errors.New("error creating private key") - // } + act := dynamicaccess.NewInMemoryAct() si := dynamicaccess.NewDefaultSession(privateKey) - al := dynamicaccess.NewLogic(si) + al := dynamicaccess.NewLogic(si, act) return al } func TestAdd(t *testing.T) { - act := dynamicaccess.NewInMemoryAct() m := dynamicaccess.NewGranteeManager(setupAccessLogic(getPrivateKey())) pub, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) @@ -36,6 +33,6 @@ func TestAdd(t *testing.T) { if err != nil { t.Errorf("Add() returned an error") } - m.Publish(act, &pub.PublicKey, "topic") + m.Publish(swarm.EmptyAddress, &pub.PublicKey, "topic") fmt.Println("") } diff --git a/pkg/dynamicaccess/history_test.go b/pkg/dynamicaccess/history_test.go index a58aa44ecaa..e501e075697 100644 --- a/pkg/dynamicaccess/history_test.go +++ b/pkg/dynamicaccess/history_test.go @@ -7,6 +7,7 @@ import ( "github.com/ethersphere/bee/pkg/dynamicaccess" "github.com/ethersphere/bee/pkg/dynamicaccess/mock" + "github.com/ethersphere/bee/pkg/swarm" "github.com/stretchr/testify/assert" ) @@ -32,7 +33,7 @@ func TestHistoryLookup(t *testing.T) { for _, tt := range tests { t.Run("", func(t *testing.T) { actAt, _ := h.Lookup(tt.input) - output, _ := actAt.Lookup([]byte("key1")) + output, _ := actAt.Lookup(swarm.EmptyAddress, []byte("key1")) assert.Equal(t, output, hex.EncodeToString([]byte(tt.expected))) }) } @@ -46,9 +47,9 @@ func prepareTestHistory() dynamicaccess.History { act2 = dynamicaccess.NewInMemoryAct() act3 = dynamicaccess.NewInMemoryAct() ) - act1.Add([]byte("key1"), []byte("value1")) - act2.Add([]byte("key1"), []byte("value2")) - act3.Add([]byte("key1"), []byte("value3")) + act1.Add(swarm.EmptyAddress, []byte("key1"), []byte("value1")) + act2.Add(swarm.EmptyAddress, []byte("key1"), []byte("value2")) + act3.Add(swarm.EmptyAddress, []byte("key1"), []byte("value3")) h.Insert(now.AddDate(-3, 0, 0).Unix(), act1) h.Insert(now.AddDate(-2, 0, 0).Unix(), act2) diff --git a/pkg/dynamicaccess/mock/act.go b/pkg/dynamicaccess/mock/act.go index a915f38f123..721a4778829 100644 --- a/pkg/dynamicaccess/mock/act.go +++ b/pkg/dynamicaccess/mock/act.go @@ -10,26 +10,26 @@ import ( ) type ActMock struct { - AddFunc func(key []byte, val []byte) error - LookupFunc func(key []byte) ([]byte, error) + AddFunc func(root swarm.Address, key []byte, val []byte) (swarm.Address, error) + LookupFunc func(root swarm.Address, key []byte) ([]byte, error) LoadFunc func(addr swarm.Address) error StoreFunc func() (swarm.Address, error) } var _ dynamicaccess.Act = (*ActMock)(nil) -func (act *ActMock) Add(key []byte, val []byte) error { +func (act *ActMock) Add(root swarm.Address, key []byte, val []byte) (swarm.Address, error) { if act.AddFunc == nil { - return nil + return swarm.EmptyAddress, nil } - return act.AddFunc(key, val) + return act.AddFunc(root, key, val) } -func (act *ActMock) Lookup(key []byte) ([]byte, error) { +func (act *ActMock) Lookup(root swarm.Address, key []byte) ([]byte, error) { if act.LookupFunc == nil { return make([]byte, 0), nil } - return act.LookupFunc(key) + return act.LookupFunc(root, key) } func (act *ActMock) Load(addr swarm.Address) error { @@ -46,7 +46,9 @@ func (act *ActMock) Store() (swarm.Address, error) { return act.StoreFunc() } -func NewActMock(addFunc func(key []byte, val []byte) error, getFunc func(key []byte) ([]byte, error)) dynamicaccess.Act { +func NewActMock( + addFunc func(swarm.Address, []byte, []byte) (swarm.Address, error), + getFunc func(swarm.Address, []byte) ([]byte, error)) dynamicaccess.Act { return &ActMock{ AddFunc: addFunc, LookupFunc: getFunc, diff --git a/pkg/kvs/kvs.go b/pkg/kvs/kvs.go new file mode 100644 index 00000000000..65c2a0be8b9 --- /dev/null +++ b/pkg/kvs/kvs.go @@ -0,0 +1,34 @@ +package kvs + +import ( + "errors" + + "github.com/ethersphere/bee/pkg/api" + "github.com/ethersphere/bee/pkg/kvs/manifest" + "github.com/ethersphere/bee/pkg/kvs/memory" + "github.com/ethersphere/bee/pkg/swarm" +) + +var ErrInvalidKvsType = errors.New("kvs: invalid type") + +type KeyValueStore interface { + Get(rootHash swarm.Address, key []byte) ([]byte, error) + Put(rootHash swarm.Address, key, value []byte) (swarm.Address, error) +} + +// func NewDefaultKeyValueStore(storer api.Storer) (KeyValueStore, error) { +// return NewKeyValueStore(storer, memory.KvsTypeMemory) +// } + +func NewKeyValueStore(storer api.Storer, kvsType string) (KeyValueStore, error) { + switch kvsType { + case "": + return memory.NewMemoryKeyValueStore() + case memory.KvsTypeMemory: + return memory.NewMemoryKeyValueStore() + case manifest.KvsTypeManifest: + return manifest.NewManifestKeyValueStore(storer) + default: + return nil, ErrInvalidKvsType + } +} diff --git a/pkg/kvs/manifest/kvs.go b/pkg/kvs/manifest/kvs.go new file mode 100644 index 00000000000..19ca983d54a --- /dev/null +++ b/pkg/kvs/manifest/kvs.go @@ -0,0 +1,86 @@ +package manifest + +import ( + "context" + "encoding/hex" + + "github.com/ethersphere/bee/pkg/api" + "github.com/ethersphere/bee/pkg/file/loadsave" + "github.com/ethersphere/bee/pkg/file/pipeline" + "github.com/ethersphere/bee/pkg/file/pipeline/builder" + "github.com/ethersphere/bee/pkg/file/redundancy" + "github.com/ethersphere/bee/pkg/manifest" + "github.com/ethersphere/bee/pkg/storage" + "github.com/ethersphere/bee/pkg/swarm" +) + +const ( + // KvsTypeManifest represents + KvsTypeManifest = "Manifest" +) + +type ManifestKeyValueStore interface { + Get(rootHash swarm.Address, key []byte) ([]byte, error) + Put(rootHash swarm.Address, key, value []byte) (swarm.Address, error) +} + +type manifestKeyValueStore struct { + storer api.Storer +} + +// TODO: pass context as dep. +func (m *manifestKeyValueStore) Get(rootHash swarm.Address, key []byte) ([]byte, error) { + ls := loadsave.NewReadonly(m.storer.ChunkStore()) + // existing manif + manif, err := manifest.NewSimpleManifestReference(rootHash, ls) + if err != nil { + return nil, err + } + entry, err := manif.Lookup(context.Background(), hex.EncodeToString(key)) + if err != nil { + return nil, err + } + ref := entry.Reference() + return ref.Bytes(), nil +} + +func (m *manifestKeyValueStore) Put(rootHash swarm.Address, key []byte, value []byte) (swarm.Address, error) { + factory := requestPipelineFactory(context.Background(), m.storer.Cache(), false, redundancy.NONE) + ls := loadsave.New(m.storer.ChunkStore(), m.storer.Cache(), factory) + // existing manif + manif, err := manifest.NewSimpleManifestReference(rootHash, ls) + if err != nil { + // new manif + manif, err = manifest.NewSimpleManifest(ls) + if err != nil { + return swarm.EmptyAddress, err + } + } + err = manif.Add(context.Background(), hex.EncodeToString(key), manifest.NewEntry(swarm.NewAddress(value), map[string]string{})) + if err != nil { + return swarm.EmptyAddress, err + } + manifRef, err := manif.Store(context.Background()) + if err != nil { + return swarm.EmptyAddress, err + } + + putter := m.storer.DirectUpload() + err = putter.Done(manifRef) + if err != nil { + return swarm.EmptyAddress, err + } + return manifRef, nil +} + +func NewManifestKeyValueStore(storer api.Storer) (ManifestKeyValueStore, error) { + return &manifestKeyValueStore{ + storer: storer, + }, nil +} + +func requestPipelineFactory(ctx context.Context, s storage.Putter, encrypt bool, rLevel redundancy.Level) func() pipeline.Interface { + return func() pipeline.Interface { + return builder.NewPipelineBuilder(ctx, s, encrypt, rLevel) + } +} diff --git a/pkg/kvs/memory/kvs.go b/pkg/kvs/memory/kvs.go new file mode 100644 index 00000000000..3a1abc69c0e --- /dev/null +++ b/pkg/kvs/memory/kvs.go @@ -0,0 +1,67 @@ +package memory + +import ( + "encoding/hex" + "sync" + + "github.com/ethersphere/bee/pkg/swarm" +) + +const ( + // KvsTypeMemory represents + KvsTypeMemory = "Memory" +) + +type MemoryKeyValueStore interface { + Get(rootHash swarm.Address, key []byte) ([]byte, error) + Put(rootHash swarm.Address, key, value []byte) (swarm.Address, error) +} + +var lock = &sync.Mutex{} + +type single struct { + // TODO string -> []byte ? + memoryMock map[string][]byte +} + +var singleInMemorySwarm *single + +func getInMemorySwarm() *single { + if singleInMemorySwarm == nil { + lock.Lock() + defer lock.Unlock() + if singleInMemorySwarm == nil { + singleInMemorySwarm = &single{ + memoryMock: make(map[string][]byte)} + } + } + return singleInMemorySwarm +} + +func getMemory() map[string][]byte { + ch := make(chan *single) + go func() { + ch <- getInMemorySwarm() + }() + mem := <-ch + return mem.memoryMock +} + +type memoryKeyValueStore struct { +} + +func (m *memoryKeyValueStore) Get(rootHash swarm.Address, key []byte) ([]byte, error) { + mem := getMemory() + val := mem[hex.EncodeToString(key)] + return val, nil +} + +func (m *memoryKeyValueStore) Put(rootHash swarm.Address, key []byte, value []byte) (swarm.Address, error) { + mem := getMemory() + mem[hex.EncodeToString(key)] = value + return swarm.EmptyAddress, nil +} + +func NewMemoryKeyValueStore() (MemoryKeyValueStore, error) { + return &memoryKeyValueStore{}, nil +}