From f572cdb5e2e8d50a5f288c3838e50d1589d03d78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierluca=20Bors=C3=B2?= Date: Fri, 4 Aug 2023 19:23:20 +0200 Subject: [PATCH 01/12] Formatting --- core/access/darc/darc.go | 12 ++++++++++-- core/ordering/cosipbft/controller/controller.go | 3 ++- test/cosidela_test.go | 3 ++- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/core/access/darc/darc.go b/core/access/darc/darc.go index b1d857869..f1fbac08a 100644 --- a/core/access/darc/darc.go +++ b/core/access/darc/darc.go @@ -31,7 +31,11 @@ func NewService(ctx serde.Context) Service { // Match implements access.Service. It returns nil if the group of identities // have access to the given credentials, otherwise a meaningful error on the // reason if it does not have access. -func (srvc Service) Match(store store.Readable, creds access.Credential, idents ...access.Identity) error { +func (srvc Service) Match( + store store.Readable, + creds access.Credential, + idents ...access.Identity, +) error { perm, err := srvc.readPermission(store, creds.GetID()) if err != nil { return xerrors.Errorf("store failed: %v", err) @@ -51,7 +55,11 @@ func (srvc Service) Match(store store.Readable, creds access.Credential, idents // Grant implements access.Service. It updates or creates the credential and // grants the access to the group of identities. -func (srvc Service) Grant(store store.Snapshot, cred access.Credential, idents ...access.Identity) error { +func (srvc Service) Grant( + store store.Snapshot, + cred access.Credential, + idents ...access.Identity, +) error { perm, err := srvc.readPermission(store, cred.GetID()) if err != nil { return xerrors.Errorf("store failed: %v", err) diff --git a/core/ordering/cosipbft/controller/controller.go b/core/ordering/cosipbft/controller/controller.go index eaad146d3..0ac53a1c7 100644 --- a/core/ordering/cosipbft/controller/controller.go +++ b/core/ordering/cosipbft/controller/controller.go @@ -178,7 +178,8 @@ func (m miniController) OnStart(flags cli.Flags, inj node.Injector) error { return xerrors.Errorf("failed to load blocks: %v", err) } - srvc, err := cosipbft.NewService(param, cosipbft.WithGenesisStore(genstore), cosipbft.WithBlockStore(blocks)) + srvc, err := cosipbft.NewService(param, cosipbft.WithGenesisStore(genstore), + cosipbft.WithBlockStore(blocks)) if err != nil { return xerrors.Errorf("service: %v", err) } diff --git a/test/cosidela_test.go b/test/cosidela_test.go index 7f50548e0..5a1dafd92 100644 --- a/test/cosidela_test.go +++ b/test/cosidela_test.go @@ -93,7 +93,8 @@ func newDelaNode(t require.TestingT, path string, port int) dela { fload := loader.NewFileLoader(filepath.Join(path, certKeyName)) - keydata, err := fload.LoadOrCreate(newCertGenerator(rand.New(rand.NewSource(0)), elliptic.P521())) + keydata, err := fload.LoadOrCreate(newCertGenerator(rand.New(rand.NewSource(0)), + elliptic.P521())) require.NoError(t, err) key, err := x509.ParseECPrivateKey(keydata) From 1481c1220b019913c30f69f831c40131789b34b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierluca=20Bors=C3=B2?= Date: Fri, 4 Aug 2023 19:22:18 +0200 Subject: [PATCH 02/12] Moved access keys to 28-bytes only --- contracts/access/controller/controller.go | 2 +- contracts/value/controller/controller.go | 2 +- core/ordering/cosipbft/controller/controller.go | 2 +- core/ordering/cosipbft/proc.go | 4 ++-- test/cosidela_test.go | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/contracts/access/controller/controller.go b/contracts/access/controller/controller.go index 4c94899af..f2ff6cec3 100644 --- a/contracts/access/controller/controller.go +++ b/contracts/access/controller/controller.go @@ -14,7 +14,7 @@ import ( "golang.org/x/xerrors" ) -var aKey = [32]byte{1} +var aKey = [28]byte{1} // newStore is the function used to create the new store. It allows us to create // a different store in the tests. diff --git a/contracts/value/controller/controller.go b/contracts/value/controller/controller.go index ee287ca5f..1f289fd19 100644 --- a/contracts/value/controller/controller.go +++ b/contracts/value/controller/controller.go @@ -10,7 +10,7 @@ import ( ) // aKey is the access key used for the value contract -var aKey = [32]byte{2} +var aKey = [28]byte{2} // miniController is a CLI initializer to register the value contract // diff --git a/core/ordering/cosipbft/controller/controller.go b/core/ordering/cosipbft/controller/controller.go index 0ac53a1c7..4d1b439c4 100644 --- a/core/ordering/cosipbft/controller/controller.go +++ b/core/ordering/cosipbft/controller/controller.go @@ -38,7 +38,7 @@ import ( const privateKeyFile = "private.key" // valueAccessKey is the access key used for the value contract. -var valueAccessKey = [32]byte{2} +var valueAccessKey = [28]byte{2} func blsSigner() encoding.BinaryMarshaler { return bls.NewSigner() diff --git a/core/ordering/cosipbft/proc.go b/core/ordering/cosipbft/proc.go index 5c24caea9..d7be899c8 100644 --- a/core/ordering/cosipbft/proc.go +++ b/core/ordering/cosipbft/proc.go @@ -29,8 +29,8 @@ import ( ) var ( - keyRoster = [32]byte{} - keyAccess = [32]byte{1} + keyRoster = [28]byte{} + keyAccess = [28]byte{1} ) // Processor processes the messages to run a collective signing PBFT consensus. diff --git a/test/cosidela_test.go b/test/cosidela_test.go index 5a1dafd92..c5b912e44 100644 --- a/test/cosidela_test.go +++ b/test/cosidela_test.go @@ -49,8 +49,8 @@ import ( const certKeyName = "cert.key" const privateKeyFile = "private.key" -var aKey = [32]byte{1} -var valueAccessKey = [32]byte{2} +var aKey = [28]byte{1} +var valueAccessKey = [28]byte{2} // cosiDela defines the interface needed to use a Dela node using cosi. type cosiDela interface { From 06127dbd011ecf5571eee1984d30e245a368c6a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierluca=20Bors=C3=B2?= Date: Fri, 4 Aug 2023 19:23:11 +0200 Subject: [PATCH 03/12] Prefixed DARC keys in KV store --- core/access/darc/darc.go | 11 +++++++++-- core/access/darc/darc_test.go | 6 +++--- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/core/access/darc/darc.go b/core/access/darc/darc.go index f1fbac08a..3b42268a0 100644 --- a/core/access/darc/darc.go +++ b/core/access/darc/darc.go @@ -76,7 +76,7 @@ func (srvc Service) Grant( return xerrors.Errorf("failed to serialize: %v", err) } - err = store.Set(cred.GetID(), value) + err = store.Set(prefixKey(cred.GetID()), value) if err != nil { return xerrors.Errorf("store failed to write: %v", err) } @@ -85,7 +85,7 @@ func (srvc Service) Grant( } func (srvc Service) readPermission(store store.Readable, key []byte) (types.Permission, error) { - value, err := store.Get(key) + value, err := store.Get(prefixKey(key)) if err != nil { return nil, xerrors.Errorf("while reading: %v", err) } @@ -101,3 +101,10 @@ func (srvc Service) readPermission(store store.Readable, key []byte) (types.Perm return perm, nil } + +// 4 bytes used to prefix DARC keys in the K/V store. +const darcPrefix = "DARC" + +func prefixKey(key []byte) []byte { + return append([]byte(darcPrefix), key...) +} diff --git a/core/access/darc/darc_test.go b/core/access/darc/darc_test.go index f00cbc186..49eaee7d7 100644 --- a/core/access/darc/darc_test.go +++ b/core/access/darc/darc_test.go @@ -27,8 +27,8 @@ func TestService_Match(t *testing.T) { data, err := perm.Serialize(testCtx) require.NoError(t, err) - store.Set([]byte{0xaa}, data) - store.Set([]byte{0xbb}, []byte{}) + store.Set(append([]byte("DARC"), 0xaa), data) + store.Set(append([]byte("DARC"), 0xbb), []byte{}) srvc := NewService(testCtx) @@ -57,7 +57,7 @@ func TestService_Match(t *testing.T) { func TestService_Grant(t *testing.T) { store := fake.NewSnapshot() - store.Set([]byte{0xbb}, []byte{}) + store.Set(append([]byte("DARC"), 0xbb), []byte{}) creds := access.NewContractCreds([]byte{0xaa}, "test", "grant") From 458c103d500610c69626f86c8f1858abceb915ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierluca=20Bors=C3=B2?= Date: Mon, 7 Aug 2023 14:38:56 +0200 Subject: [PATCH 04/12] Refactored Smart Contract's DARC keys and storage prefixes - Contracts have a hardcoded (4-bytes) access key - Access keys are also used as storage prefix --- contracts/access/access.go | 31 ++++++++------ contracts/access/access_test.go | 8 ++-- contracts/access/controller/action.go | 2 +- contracts/access/controller/controller.go | 4 +- contracts/value/controller/controller.go | 5 +-- contracts/value/value.go | 38 +++++++++-------- contracts/value/value_test.go | 12 +++--- core/execution/native/example_test.go | 4 ++ core/execution/native/native.go | 25 ++++++++++- core/execution/native/native_test.go | 41 ++++++++++++++++++- .../contracts/viewchange/viewchange.go | 41 ++++++++++++++----- .../contracts/viewchange/viewchange_test.go | 2 +- .../cosipbft/controller/controller.go | 2 +- core/ordering/cosipbft/cosipbft.go | 2 +- core/ordering/cosipbft/cosipbft_test.go | 10 ++++- core/ordering/cosipbft/proc.go | 11 ++--- core/ordering/pow/pow_test.go | 4 ++ core/validation/simple/example_test.go | 4 ++ test/cosidela_test.go | 7 +--- test/integration_test.go | 5 ++- 20 files changed, 177 insertions(+), 81 deletions(-) diff --git a/contracts/access/access.go b/contracts/access/access.go index c434758d4..8ce1b7572 100644 --- a/contracts/access/access.go +++ b/contracts/access/access.go @@ -27,6 +27,10 @@ import ( ) const ( + // ContractUID is the unique (4-bytes) identifier of the contract, it is + // used to prefix keys in the K/V store and by DARCs for access control. + ContractUID = "DARC" + // ContractName is the name of the access contract. ContractName = "go.dedis.ch/dela.Access" @@ -50,9 +54,9 @@ const ( // run on the contract. Should be one of the Command type. CmdArg = "access:command" - // credentialAllCommand defines the credential command that is allowed to + // CredentialAllCommand defines the credential command that is allowed to // perform all commands. - credentialAllCommand = "all" + CredentialAllCommand = "all" ) // Command defines a command for the command contract @@ -64,8 +68,8 @@ const ( ) // NewCreds creates new credentials for an access contract execution. -func NewCreds(id []byte) access.Credential { - return access.NewContractCreds(id, ContractName, credentialAllCommand) +func NewCreds() access.Credential { + return access.NewContractCreds([]byte(ContractUID), ContractName, CredentialAllCommand) } // RegisterContract registers the access contract to the given execution @@ -81,24 +85,20 @@ type Contract struct { // access is the access service that will be modified. access access.Service - // accessKey is the credential's ID allowed to use this smart contract - accessKey []byte - store store.Readable } // NewContract creates a new access contract -func NewContract(aKey []byte, srvc access.Service, store store.Readable) Contract { +func NewContract(srvc access.Service, store store.Readable) Contract { return Contract{ - access: srvc, - accessKey: aKey, - store: store, + access: srvc, + store: store, } } // Execute implements native.Contract func (c Contract) Execute(snap store.Snapshot, step execution.Step) error { - creds := NewCreds(c.accessKey) + creds := NewCreds() err := c.access.Match(c.store, creds, step.Current.GetIdentity()) if err != nil { @@ -123,6 +123,13 @@ func (c Contract) Execute(snap store.Snapshot, step execution.Step) error { return nil } +// UID returns the unique 4-bytes contract identifier. +// +// - implements native.Contract +func (c Contract) UID() string { + return ContractUID +} + // grant perform the GRANT command func (c Contract) grant(snap store.Snapshot, step execution.Step) error { idHex := step.Current.GetArg(GrantIDArg) diff --git a/contracts/access/access_test.go b/contracts/access/access_test.go index 45955fad1..d85ae1c36 100644 --- a/contracts/access/access_test.go +++ b/contracts/access/access_test.go @@ -16,12 +16,12 @@ import ( ) func TestExecute(t *testing.T) { - contract := NewContract([]byte{}, fakeAccess{err: fake.GetError()}, fakeStore{}) + contract := NewContract(fakeAccess{err: fake.GetError()}, fakeStore{}) err := contract.Execute(fakeStore{}, makeStep(t, CmdArg, "")) require.EqualError(t, err, "identity not authorized: fake.PublicKey ("+fake.GetError().Error()+")") - contract = NewContract([]byte{}, fakeAccess{}, fakeStore{}) + contract = NewContract(fakeAccess{}, fakeStore{}) err = contract.Execute(fakeStore{}, makeStep(t, CmdArg, "")) require.EqualError(t, err, "'access:command' not found in tx arg") @@ -44,7 +44,7 @@ func TestExecute(t *testing.T) { } func TestGrant(t *testing.T) { - contract := NewContract([]byte{}, fakeAccess{}, fakeStore{}) + contract := NewContract(fakeAccess{}, fakeStore{}) err := contract.grant(fakeStore{}, makeStep(t)) require.EqualError(t, err, "'access:grant_id' not found in tx arg") @@ -86,7 +86,7 @@ func TestGrant(t *testing.T) { IdentityArg, id)) require.NoError(t, err) - contract = NewContract([]byte{}, fakeAccess{err: fake.GetError()}, fakeStore{}) + contract = NewContract(fakeAccess{err: fake.GetError()}, fakeStore{}) err = contract.grant(fakeStore{}, makeStep(t, GrantIDArg, "deadbeef", GrantContractArg, "fake contract", GrantCommandArg, "fake command", diff --git a/contracts/access/controller/action.go b/contracts/access/controller/action.go index 49fc3d435..f6dfff34e 100644 --- a/contracts/access/controller/action.go +++ b/contracts/access/controller/action.go @@ -49,7 +49,7 @@ func (a addAction) Execute(ctx node.Context) error { return xerrors.Errorf("failed to parse identities: %v", err) } - err = asrv.Grant(accessStore, accessContract.NewCreds(aKey[:]), identities...) + err = asrv.Grant(accessStore, accessContract.NewCreds(), identities...) if err != nil { return xerrors.Errorf("failed to grant: %v", err) } diff --git a/contracts/access/controller/controller.go b/contracts/access/controller/controller.go index f2ff6cec3..bf72f59f1 100644 --- a/contracts/access/controller/controller.go +++ b/contracts/access/controller/controller.go @@ -14,8 +14,6 @@ import ( "golang.org/x/xerrors" ) -var aKey = [28]byte{1} - // newStore is the function used to create the new store. It allows us to create // a different store in the tests. var newStore = func(path string) (accessStore, error) { @@ -68,7 +66,7 @@ func (m miniController) OnStart(flags cli.Flags, inj node.Injector) error { return xerrors.Errorf("failed to create access store: %v", err) } - contract := accessContract.NewContract(aKey[:], access, accessStore) + contract := accessContract.NewContract(access, accessStore) accessContract.RegisterContract(exec, contract) inj.Inject(accessStore) diff --git a/contracts/value/controller/controller.go b/contracts/value/controller/controller.go index 1f289fd19..37d6e670c 100644 --- a/contracts/value/controller/controller.go +++ b/contracts/value/controller/controller.go @@ -9,9 +9,6 @@ import ( "golang.org/x/xerrors" ) -// aKey is the access key used for the value contract -var aKey = [28]byte{2} - // miniController is a CLI initializer to register the value contract // // - implements node.Initializer @@ -41,7 +38,7 @@ func (m miniController) OnStart(flags cli.Flags, inj node.Injector) error { return xerrors.Errorf("failed to resolve native service: %v", err) } - contract := value.NewContract(aKey[:], access) + contract := value.NewContract(access) value.RegisterContract(exec, contract) diff --git a/contracts/value/value.go b/contracts/value/value.go index 6e1a6b831..9c5c31754 100644 --- a/contracts/value/value.go +++ b/contracts/value/value.go @@ -26,6 +26,10 @@ type commands interface { } const ( + // ContractUID is the unique (4-bytes) identifier of the contract, it is + // used to prefix keys in the K/V store and by DARCs for access control. + ContractUID = "VALU" + // ContractName is the name of the contract. ContractName = "go.dedis.ch/dela.Value" @@ -41,12 +45,9 @@ const ( // run on the contract. Should be one of the Command type. CmdArg = "value:command" - // credentialAllCommand defines the credential command that is allowed to + // CredentialAllCommand defines the credential command that is allowed to // perform all commands. - credentialAllCommand = "all" - - // contractKeyPrefix is used to prefix keys in the K/V store. - contractKeyPrefix = "VALU" // intentionally 4 bytes only, not a typo! + CredentialAllCommand = "all" ) // Command defines a type of command for the value contract @@ -69,8 +70,8 @@ const ( // NewCreds creates new credentials for a value contract execution. We might // want to use in the future a separate credential for each command. -func NewCreds(id []byte) access.Credential { - return access.NewContractCreds(id, ContractName, credentialAllCommand) +func NewCreds() access.Credential { + return access.NewContractCreds([]byte(ContractUID), ContractName, CredentialAllCommand) } // RegisterContract registers the value contract to the given execution service. @@ -89,9 +90,6 @@ type Contract struct { // access is the access control service managing this smart contract access access.Service - // accessKey is the access identifier allowed to use this smart contract - accessKey []byte - // cmd provides the commands that can be executed by this smart contract cmd commands @@ -100,12 +98,11 @@ type Contract struct { } // NewContract creates a new Value contract -func NewContract(aKey []byte, srvc access.Service) Contract { +func NewContract(srvc access.Service) Contract { contract := Contract{ - index: map[string]struct{}{}, - access: srvc, - accessKey: aKey, - printer: infoLog{}, + index: map[string]struct{}{}, + access: srvc, + printer: infoLog{}, } contract.cmd = valueCommand{Contract: &contract} @@ -115,7 +112,7 @@ func NewContract(aKey []byte, srvc access.Service) Contract { // Execute implements native.Contract. It runs the appropriate command. func (c Contract) Execute(snap store.Snapshot, step execution.Step) error { - creds := NewCreds(c.accessKey) + creds := NewCreds() err := c.access.Match(snap, creds, step.Current.GetIdentity()) if err != nil { @@ -156,6 +153,13 @@ func (c Contract) Execute(snap store.Snapshot, step execution.Step) error { return nil } +// UID returns the unique 4-bytes contract identifier. +// +// - implements native.Contract +func (c Contract) UID() string { + return ContractUID +} + // valueCommand implements the commands of the value contract // // - implements commands @@ -260,5 +264,5 @@ func (h infoLog) Write(p []byte) (int, error) { } func prefix(key []byte) []byte { - return append([]byte(contractKeyPrefix), key...) + return append([]byte(ContractUID), key...) } diff --git a/contracts/value/value_test.go b/contracts/value/value_test.go index c358793cd..7027ea992 100644 --- a/contracts/value/value_test.go +++ b/contracts/value/value_test.go @@ -17,13 +17,13 @@ import ( ) func TestExecute(t *testing.T) { - contract := NewContract([]byte{}, fakeAccess{err: fake.GetError()}) + contract := NewContract(fakeAccess{err: fake.GetError()}) err := contract.Execute(fakeStore{}, makeStep(t)) require.EqualError(t, err, "identity not authorized: fake.PublicKey ("+fake.GetError().Error()+")") - contract = NewContract([]byte{}, fakeAccess{}) + contract = NewContract(fakeAccess{}) err = contract.Execute(fakeStore{}, makeStep(t)) require.EqualError(t, err, "'value:command' not found in tx arg") @@ -50,7 +50,7 @@ func TestExecute(t *testing.T) { } func TestCommand_Write(t *testing.T) { - contract := NewContract([]byte{}, fakeAccess{}) + contract := NewContract(fakeAccess{}) cmd := valueCommand{ Contract: &contract, @@ -82,7 +82,7 @@ func TestCommand_Write(t *testing.T) { } func TestCommand_Read(t *testing.T) { - contract := NewContract([]byte{}, fakeAccess{}) + contract := NewContract(fakeAccess{}) cmd := valueCommand{ Contract: &contract, @@ -110,7 +110,7 @@ func TestCommand_Read(t *testing.T) { } func TestCommand_Delete(t *testing.T) { - contract := NewContract([]byte{}, fakeAccess{}) + contract := NewContract(fakeAccess{}) cmd := valueCommand{ Contract: &contract, @@ -142,7 +142,7 @@ func TestCommand_Delete(t *testing.T) { } func TestCommand_List(t *testing.T) { - contract := NewContract([]byte{}, fakeAccess{}) + contract := NewContract(fakeAccess{}) key1 := "key1" key2 := "key2" diff --git a/core/execution/native/example_test.go b/core/execution/native/example_test.go index 5fcd47d57..87308cfe7 100644 --- a/core/execution/native/example_test.go +++ b/core/execution/native/example_test.go @@ -90,6 +90,10 @@ func (exampleContract) Execute(store store.Snapshot, step execution.Step) error return nil } +func (exampleContract) UID() string { + return "EXPL" +} + // inMemoryStore in a simple implementation of a store using an in-memory // map. // diff --git a/core/execution/native/native.go b/core/execution/native/native.go index f9b5c233e..2ffb61b19 100644 --- a/core/execution/native/native.go +++ b/core/execution/native/native.go @@ -20,6 +20,7 @@ const ( // be executed natively. type Contract interface { Execute(store.Snapshot, execution.Step) error + UID() string } // Service is an execution service for packaged applications. Those @@ -27,21 +28,41 @@ type Contract interface { // // - implements execution.Service type Service struct { - contracts map[string]Contract + contracts map[string]Contract + contractUIDs map[string]struct{} } // NewExecution returns a new native execution. The given service will be // executed for every incoming transaction. func NewExecution() *Service { return &Service{ - contracts: map[string]Contract{}, + contracts: map[string]Contract{}, + contractUIDs: map[string]struct{}{}, } } // Set stores the contract using the name as the key. A transaction can trigger // this contract by using the same name as the contract argument. func (ns *Service) Set(name string, contract Contract) { + // Check if the contract is already registered + if _, ok := ns.contracts[name]; ok { + panic(xerrors.Errorf("contract '%s' already registered", name)) + } + + uid := contract.UID() + + // UIDs are expected to be 4 bytes long, always. + if len(uid) != 4 { + panic(xerrors.Errorf("contract UID '%x' for '%s' is not 4 bytes long", uid, name)) + } + + // Check if the contract's UID is already registered + if _, ok := ns.contractUIDs[uid]; ok { + panic(xerrors.Errorf("contract UID '%x' for '%s' already registered", uid, name)) + } + ns.contracts[name] = contract + ns.contractUIDs[uid] = struct{}{} } // Execute implements execution.Service. It uses the executor to process the diff --git a/core/execution/native/native_test.go b/core/execution/native/native_test.go index e65c4306f..0a3735402 100644 --- a/core/execution/native/native_test.go +++ b/core/execution/native/native_test.go @@ -1,6 +1,7 @@ package native import ( + "fmt" "testing" "github.com/stretchr/testify/require" @@ -10,10 +11,41 @@ import ( "go.dedis.ch/dela/testing/fake" ) +func TestService_RequireUniqueContractName(t *testing.T) { + srvc := NewExecution() + srvc.Set("abc", fakeExec{uid: "abcd"}) + + require.PanicsWithError(t, "contract 'abc' already registered", func() { + srvc.Set("abc", fakeExec{uid: "badd"}) + }) +} + +func TestService_RequireUniqueContractID(t *testing.T) { + srvc := NewExecution() + srvc.Set("abc", fakeExec{uid: "abcd"}) + + err := fmt.Sprintf("contract UID '%x' for '%s' already registered", + "abcd", "bad") + + require.PanicsWithError(t, err, func() { + srvc.Set("bad", fakeExec{uid: "abcd"}) + }) +} + +func TestService_VerifyContractIDFormat(t *testing.T) { + srvc := NewExecution() + err := fmt.Sprintf("contract UID '%x' for '%s' is not 4 bytes long", + "abc", "bad") + + require.PanicsWithError(t, err, func() { + srvc.Set("bad", fakeExec{uid: "abc"}) + }) +} + func TestService_Execute(t *testing.T) { srvc := NewExecution() - srvc.Set("abc", fakeExec{}) - srvc.Set("bad", fakeExec{err: fake.GetError()}) + srvc.Set("abc", fakeExec{uid: "abcd"}) + srvc.Set("bad", fakeExec{uid: "badd", err: fake.GetError()}) step := execution.Step{} step.Current = fakeTx{contract: "abc"} @@ -37,12 +69,17 @@ func TestService_Execute(t *testing.T) { type fakeExec struct { err error + uid string } func (e fakeExec) Execute(store.Snapshot, execution.Step) error { return e.err } +func (e fakeExec) UID() string { + return e.uid +} + type fakeTx struct { txn.Transaction contract string diff --git a/core/ordering/cosipbft/contracts/viewchange/viewchange.go b/core/ordering/cosipbft/contracts/viewchange/viewchange.go index 5ac9a2cd8..0609230ed 100644 --- a/core/ordering/cosipbft/contracts/viewchange/viewchange.go +++ b/core/ordering/cosipbft/contracts/viewchange/viewchange.go @@ -18,6 +18,10 @@ import ( ) const ( + // ContractUID is the unique (4-bytes) identifier of the contract, it is + // used to prefix keys in the K/V store and by DARCs for access control. + ContractUID = "COSI" + // ContractName is the name of the contract. ContractName = "go.dedis.ch/dela.ViewChange" @@ -34,6 +38,20 @@ const ( messageUnauthorized = "unauthorized identity" ) +var ( + rosterKey [32]byte +) + +func init() { + copy(rosterKey[:4], ContractUID) +} + +// GetRosterKey returns the key used to store the roster in the K/V storage. +// It is formatted like ContractUID + roster key. +func GetRosterKey() []byte { + return rosterKey[:] +} + // RegisterContract registers the view change contract to the given execution // service. func RegisterContract(exec *native.Service, c Contract) { @@ -41,8 +59,8 @@ func RegisterContract(exec *native.Service, c Contract) { } // NewCreds creates new credentials for a view change contract execution. -func NewCreds(id []byte) access.Credential { - return access.NewContractCreds(id, ContractName, "update") +func NewCreds() access.Credential { + return access.NewContractCreds([]byte(ContractUID), ContractName, "update") } // Manager is an extension of a normal transaction manager to help creating view @@ -84,19 +102,15 @@ func (mgr Manager) Make(roster authority.Authority) (txn.Transaction, error) { // // - implements native.Contract type Contract struct { - rosterKey []byte rosterFac authority.Factory - accessKey []byte access access.Service context serde.Context } // NewContract creates a new viewchange contract. -func NewContract(rKey, aKey []byte, rFac authority.Factory, srvc access.Service) Contract { +func NewContract(rFac authority.Factory, srvc access.Service) Contract { return Contract{ - rosterKey: rKey, rosterFac: rFac, - accessKey: aKey, access: srvc, context: json.NewContext(), } @@ -121,7 +135,7 @@ func (c Contract) Execute(snap store.Snapshot, step execution.Step) error { return xerrors.New(messageArgMissing) } - currData, err := snap.Get(c.rosterKey) + currData, err := snap.Get(rosterKey[:]) if err != nil { reportErr(step.Current, xerrors.Errorf("reading store: %v", err)) @@ -148,7 +162,7 @@ func (c Contract) Execute(snap store.Snapshot, step execution.Step) error { } } - creds := NewCreds(c.accessKey) + creds := NewCreds() err = c.access.Match(snap, creds, step.Current.GetIdentity()) if err != nil { @@ -157,7 +171,7 @@ func (c Contract) Execute(snap store.Snapshot, step execution.Step) error { return xerrors.Errorf("%s: %v", messageUnauthorized, step.Current.GetIdentity()) } - err = snap.Set(c.rosterKey, step.Current.GetArg(AuthorityArg)) + err = snap.Set(rosterKey[:], step.Current.GetArg(AuthorityArg)) if err != nil { reportErr(step.Current, xerrors.Errorf("writing store: %v", err)) @@ -167,6 +181,13 @@ func (c Contract) Execute(snap store.Snapshot, step execution.Step) error { return nil } +// UID returns the unique 4-bytes contract identifier. +// +// - implements native.Contract +func (c Contract) UID() string { + return ContractUID +} + // reportErr prints a log with the actual error while the transaction will // contain a simplified explanation. func reportErr(tx txn.Transaction, err error) { diff --git a/core/ordering/cosipbft/contracts/viewchange/viewchange_test.go b/core/ordering/cosipbft/contracts/viewchange/viewchange_test.go index 1e148156d..1f718ff81 100644 --- a/core/ordering/cosipbft/contracts/viewchange/viewchange_test.go +++ b/core/ordering/cosipbft/contracts/viewchange/viewchange_test.go @@ -40,7 +40,7 @@ func TestNewTransaction(t *testing.T) { func TestContract_Execute(t *testing.T) { fac := authority.NewFactory(fake.AddressFactory{}, fake.PublicKeyFactory{}) - contract := NewContract([]byte("roster"), []byte("access"), fac, fakeAccess{}) + contract := NewContract(fac, fakeAccess{}) err := contract.Execute(fakeStore{}, makeStep(t, "[]")) require.NoError(t, err) diff --git a/core/ordering/cosipbft/controller/controller.go b/core/ordering/cosipbft/controller/controller.go index 4d1b439c4..705bb2e74 100644 --- a/core/ordering/cosipbft/controller/controller.go +++ b/core/ordering/cosipbft/controller/controller.go @@ -127,7 +127,7 @@ func (m miniController) OnStart(flags cli.Flags, inj node.Injector) error { rosterFac := authority.NewFactory(onet.GetAddressFactory(), cosi.GetPublicKeyFactory()) cosipbft.RegisterRosterContract(exec, rosterFac, access) - value.RegisterContract(exec, value.NewContract(valueAccessKey[:], access)) + value.RegisterContract(exec, value.NewContract(access)) txFac := signed.NewTransactionFactory() vs := simple.NewService(exec, txFac) diff --git a/core/ordering/cosipbft/cosipbft.go b/core/ordering/cosipbft/cosipbft.go index bcf350691..f614a5fbf 100644 --- a/core/ordering/cosipbft/cosipbft.go +++ b/core/ordering/cosipbft/cosipbft.go @@ -86,7 +86,7 @@ const ( // RegisterRosterContract registers the native smart contract to update the // roster to the given service. func RegisterRosterContract(exec *native.Service, rFac authority.Factory, srvc access.Service) { - contract := viewchange.NewContract(keyRoster[:], keyAccess[:], rFac, srvc) + contract := viewchange.NewContract(rFac, srvc) viewchange.RegisterContract(exec, contract) } diff --git a/core/ordering/cosipbft/cosipbft_test.go b/core/ordering/cosipbft/cosipbft_test.go index c274e607a..720593fed 100644 --- a/core/ordering/cosipbft/cosipbft_test.go +++ b/core/ordering/cosipbft/cosipbft_test.go @@ -43,6 +43,8 @@ import ( "go.dedis.ch/dela/testing/fake" ) +// This test is known to be VERY flaky on Windows. +// Further investigation is needed. func TestService_Scenario_Basic(t *testing.T) { nodes, ro, clean := makeAuthority(t, 5) defer clean() @@ -84,11 +86,11 @@ func TestService_Scenario_Basic(t *testing.T) { require.Equal(t, uint64(i+3), evt.Index) } - proof, err := nodes[0].service.GetProof(keyRoster[:]) + proof, err := nodes[0].service.GetProof(viewchange.GetRosterKey()) require.NoError(t, err) require.NotNil(t, proof.GetValue()) - require.Equal(t, keyRoster[:], proof.GetKey()) + require.Equal(t, viewchange.GetRosterKey(), proof.GetKey()) require.NotNil(t, proof.GetValue()) checkProof(t, proof.(Proof), nodes[0].service) @@ -935,6 +937,10 @@ func (e testExec) Execute(store.Snapshot, execution.Step) error { return e.err } +func (e testExec) UID() string { + return "TEST" +} + func makeTx(t *testing.T, nonce uint64, signer crypto.Signer) txn.Transaction { opts := []signed.TransactionOption{ signed.WithArg(native.ContractArg, []byte(testContractName)), diff --git a/core/ordering/cosipbft/proc.go b/core/ordering/cosipbft/proc.go index d7be899c8..a703c8c2f 100644 --- a/core/ordering/cosipbft/proc.go +++ b/core/ordering/cosipbft/proc.go @@ -28,11 +28,6 @@ import ( "golang.org/x/xerrors" ) -var ( - keyRoster = [28]byte{} - keyAccess = [28]byte{1} -) - // Processor processes the messages to run a collective signing PBFT consensus. // // - implements cosi.Reactor @@ -176,7 +171,7 @@ func (h *processor) getCurrentRoster() (authority.Authority, error) { } func (h *processor) readRoster(tree hashtree.Tree) (authority.Authority, error) { - data, err := tree.Get(keyRoster[:]) + data, err := tree.Get(viewchange.GetRosterKey()) if err != nil { return nil, xerrors.Errorf("read from tree: %v", err) } @@ -201,7 +196,7 @@ func (h *processor) storeGenesis(roster authority.Authority, match *types.Digest return xerrors.Errorf("failed to set access: %v", err) } - err = snap.Set(keyRoster[:], value) + err = snap.Set(viewchange.GetRosterKey(), value) if err != nil { return xerrors.Errorf("failed to store roster: %v", err) } @@ -242,7 +237,7 @@ func (h *processor) storeGenesis(roster authority.Authority, match *types.Digest } func (h *processor) makeAccess(store store.Snapshot, roster authority.Authority) error { - creds := viewchange.NewCreds(keyAccess[:]) + creds := viewchange.NewCreds() iter := roster.PublicKeyIterator() for iter.HasNext() { diff --git a/core/ordering/pow/pow_test.go b/core/ordering/pow/pow_test.go index 51628a7ea..92e4c13b3 100644 --- a/core/ordering/pow/pow_test.go +++ b/core/ordering/pow/pow_test.go @@ -169,6 +169,10 @@ func (e testExec) Execute(store store.Snapshot, step execution.Step) error { return nil } +func (e testExec) UID() string { + return "TEST" +} + type badValidation struct { validation.Service } diff --git a/core/validation/simple/example_test.go b/core/validation/simple/example_test.go index 93afd921e..1396a5588 100644 --- a/core/validation/simple/example_test.go +++ b/core/validation/simple/example_test.go @@ -86,6 +86,10 @@ func (exampleContract) Execute(store.Snapshot, execution.Step) error { return nil } +func (exampleContract) UID() string { + return "EXPL" +} + // inMemoryStore in a simple implementation of a store using an in-memory // map. // diff --git a/test/cosidela_test.go b/test/cosidela_test.go index c5b912e44..8c50b2b8c 100644 --- a/test/cosidela_test.go +++ b/test/cosidela_test.go @@ -49,9 +49,6 @@ import ( const certKeyName = "cert.key" const privateKeyFile = "private.key" -var aKey = [28]byte{1} -var valueAccessKey = [28]byte{2} - // cosiDela defines the interface needed to use a Dela node using cosi. type cosiDela interface { dela @@ -127,7 +124,7 @@ func newDelaNode(t require.TestingT, path string, port int) dela { rosterFac := authority.NewFactory(onet.GetAddressFactory(), cosi.GetPublicKeyFactory()) cosipbft.RegisterRosterContract(exec, rosterFac, accessService) - value.RegisterContract(exec, value.NewContract(valueAccessKey[:], accessService)) + value.RegisterContract(exec, value.NewContract(accessService)) txFac := signed.NewTransactionFactory() vs := simple.NewService(exec, txFac) @@ -175,7 +172,7 @@ func newDelaNode(t require.TestingT, path string, port int) dela { // access accessStore := newAccessStore() - contract := accessContract.NewContract(aKey[:], accessService, accessStore) + contract := accessContract.NewContract(accessService, accessStore) accessContract.RegisterContract(exec, contract) return cosiDelaNode{ diff --git a/test/integration_test.go b/test/integration_test.go index 3a1441eef..7981b04be 100644 --- a/test/integration_test.go +++ b/test/integration_test.go @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/require" accessContract "go.dedis.ch/dela/contracts/access" + "go.dedis.ch/dela/contracts/value" "go.dedis.ch/dela/core/txn" "go.dedis.ch/dela/core/txn/signed" "go.dedis.ch/dela/crypto/bls" @@ -63,7 +64,7 @@ func getTest[T require.TestingT](numNode, numTx int) func(t T) { require.NoError(t, err) pubKey := signer.GetPublicKey() - cred := accessContract.NewCreds(aKey[:]) + cred := accessContract.NewCreds() for _, node := range nodes { node.GetAccessService().Grant(node.(cosiDelaNode).GetAccessStore(), cred, pubKey) @@ -76,7 +77,7 @@ func getTest[T require.TestingT](numNode, numTx int) func(t T) { args := []txn.Arg{ {Key: "go.dedis.ch/dela.ContractArg", Value: []byte("go.dedis.ch/dela.Access")}, - {Key: "access:grant_id", Value: []byte(hex.EncodeToString(valueAccessKey[:]))}, + {Key: "access:grant_id", Value: []byte(hex.EncodeToString([]byte(value.ContractUID)))}, {Key: "access:grant_contract", Value: []byte("go.dedis.ch/dela.Value")}, {Key: "access:grant_command", Value: []byte("all")}, {Key: "access:identity", Value: []byte(base64.StdEncoding.EncodeToString(pubKeyBuf))}, From 19d0b6adec29be193cdf9cd85ac82807a5b89a7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierluca=20Bors=C3=B2?= Date: Mon, 7 Aug 2023 15:21:08 +0200 Subject: [PATCH 05/12] Removed unused variable --- core/ordering/cosipbft/controller/controller.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/core/ordering/cosipbft/controller/controller.go b/core/ordering/cosipbft/controller/controller.go index 705bb2e74..a81184d5f 100644 --- a/core/ordering/cosipbft/controller/controller.go +++ b/core/ordering/cosipbft/controller/controller.go @@ -37,9 +37,6 @@ import ( const privateKeyFile = "private.key" -// valueAccessKey is the access key used for the value contract. -var valueAccessKey = [28]byte{2} - func blsSigner() encoding.BinaryMarshaler { return bls.NewSigner() } From 5d2faf96177c321bf25afe2f8830a8c700445bbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierluca=20Bors=C3=B2?= Date: Mon, 7 Aug 2023 15:55:35 +0200 Subject: [PATCH 06/12] Fixed linting --- core/execution/native/native.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core/execution/native/native.go b/core/execution/native/native.go index 2ffb61b19..34ee7316d 100644 --- a/core/execution/native/native.go +++ b/core/execution/native/native.go @@ -45,7 +45,8 @@ func NewExecution() *Service { // this contract by using the same name as the contract argument. func (ns *Service) Set(name string, contract Contract) { // Check if the contract is already registered - if _, ok := ns.contracts[name]; ok { + _, found := ns.contracts[name] + if found { panic(xerrors.Errorf("contract '%s' already registered", name)) } @@ -57,7 +58,8 @@ func (ns *Service) Set(name string, contract Contract) { } // Check if the contract's UID is already registered - if _, ok := ns.contractUIDs[uid]; ok { + _, found = ns.contractUIDs[uid] + if found { panic(xerrors.Errorf("contract UID '%x' for '%s' already registered", uid, name)) } From b13dbe21a3450e6b17d7f7d19c0725cd6dbb7487 Mon Sep 17 00:00:00 2001 From: Jean Viaene Date: Fri, 11 Aug 2023 11:40:33 +0200 Subject: [PATCH 07/12] add prefixed key --- .editorconfig | 53 +---------------------- contracts/value/value.go | 21 ++++------ contracts/value/value_test.go | 64 ++++++++++++++++------------ core/access/darc/darc.go | 14 +++---- core/access/darc/darc_test.go | 39 ++++++++++------- core/store/prefixed/prefixed.go | 74 +++++++++++++++++++++++++++++++++ test/integration_test.go | 13 +++--- testing/fake/store.go | 4 +- 8 files changed, 160 insertions(+), 122 deletions(-) create mode 100644 core/store/prefixed/prefixed.go diff --git a/.editorconfig b/.editorconfig index 79d73424b..c8adcc8bc 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,52 +1 @@ -[*] -charset = utf-8 -end_of_line = crlf -indent_size = 4 -indent_style = space -insert_final_newline = false -max_line_length = 100 -tab_width = 4 -ij_continuation_indent_size = 4 -ij_formatter_off_tag = @formatter:off -ij_formatter_on_tag = @formatter:on -ij_formatter_tags_enabled = true -ij_smart_tabs = false -ij_visual_guides = 80 -ij_wrap_on_typing = false - -[.editorconfig] -ij_editorconfig_align_group_field_declarations = false -ij_editorconfig_space_after_colon = false -ij_editorconfig_space_after_comma = true -ij_editorconfig_space_before_colon = false -ij_editorconfig_space_before_comma = false -ij_editorconfig_spaces_around_assignment_operators = true - -[*.go] -indent_style = tab -ij_smart_tabs = true -ij_go_GROUP_CURRENT_PROJECT_IMPORTS = true -ij_go_add_leading_space_to_comments = true -ij_go_add_parentheses_for_single_import = false -ij_go_call_parameters_new_line_after_left_paren = false -ij_go_call_parameters_right_paren_on_new_line = false -ij_go_call_parameters_wrap = normal -ij_go_fill_paragraph_width = 80 -ij_go_group_stdlib_imports = false -ij_go_import_sorting = goimports -ij_go_keep_indents_on_empty_lines = false -ij_go_local_group_mode = project -ij_go_move_all_imports_in_one_declaration = true -ij_go_move_all_stdlib_imports_in_one_group = false -ij_go_remove_redundant_import_aliases = true -ij_go_run_go_fmt_on_reformat = true -ij_go_use_back_quotes_for_imports = false -ij_go_wrap_comp_lit = on_every_item -ij_go_wrap_comp_lit_newline_after_lbrace = true -ij_go_wrap_comp_lit_newline_before_rbrace = true -ij_go_wrap_func_params = on_every_item -ij_go_wrap_func_params_newline_after_lparen = true -ij_go_wrap_func_params_newline_before_rparen = true -ij_go_wrap_func_result = on_every_item -ij_go_wrap_func_result_newline_after_lparen = true -ij_go_wrap_func_result_newline_before_rparen = true \ No newline at end of file +[*] charset = utf-8 end_of_line = lf indent_size = 4 indent_style = space insert_final_newline = false max_line_length = 100 tab_width = 4 ij_continuation_indent_size = 4 ij_formatter_off_tag = @formatter:off ij_formatter_on_tag = @formatter:on ij_formatter_tags_enabled = true ij_smart_tabs = false ij_visual_guides = 80 ij_wrap_on_typing = false [.editorconfig] ij_editorconfig_align_group_field_declarations = false ij_editorconfig_space_after_colon = false ij_editorconfig_space_after_comma = true ij_editorconfig_space_before_colon = false ij_editorconfig_space_before_comma = false ij_editorconfig_spaces_around_assignment_operators = true [*.go] indent_style = tab ij_smart_tabs = true ij_go_GROUP_CURRENT_PROJECT_IMPORTS = true ij_go_add_leading_space_to_comments = true ij_go_add_parentheses_for_single_import = false ij_go_call_parameters_new_line_after_left_paren = false ij_go_call_parameters_right_paren_on_new_line = false ij_go_call_parameters_wrap = normal ij_go_fill_paragraph_width = 80 ij_go_group_stdlib_imports = false ij_go_import_sorting = goimports ij_go_keep_indents_on_empty_lines = false ij_go_local_group_mode = project ij_go_move_all_imports_in_one_declaration = true ij_go_move_all_stdlib_imports_in_one_group = false ij_go_remove_redundant_import_aliases = true ij_go_run_go_fmt_on_reformat = true ij_go_use_back_quotes_for_imports = false ij_go_wrap_comp_lit = on_every_item ij_go_wrap_comp_lit_newline_after_lbrace = true ij_go_wrap_comp_lit_newline_before_rbrace = true ij_go_wrap_func_params = on_every_item ij_go_wrap_func_params_newline_after_lparen = true ij_go_wrap_func_params_newline_before_rparen = true ij_go_wrap_func_result = on_every_item ij_go_wrap_func_result_newline_after_lparen = true ij_go_wrap_func_result_newline_before_rparen = true \ No newline at end of file diff --git a/contracts/value/value.go b/contracts/value/value.go index 9c5c31754..a5012b58d 100644 --- a/contracts/value/value.go +++ b/contracts/value/value.go @@ -13,6 +13,7 @@ import ( "go.dedis.ch/dela/core/execution" "go.dedis.ch/dela/core/execution/native" "go.dedis.ch/dela/core/store" + "go.dedis.ch/dela/core/store/prefixed" "golang.org/x/xerrors" ) @@ -125,6 +126,8 @@ func (c Contract) Execute(snap store.Snapshot, step execution.Step) error { return xerrors.Errorf("'%s' not found in tx arg", CmdArg) } + snap = prefixed.NewSnapshot(ContractUID, snap) + switch Command(cmd) { case CmdWrite: err := c.cmd.write(snap, step) @@ -179,8 +182,7 @@ func (c valueCommand) write(snap store.Snapshot, step execution.Step) error { return xerrors.Errorf("'%s' not found in tx arg", ValueArg) } - snapKey := prefix(key) - err := snap.Set(snapKey, value) + err := snap.Set(key, value) if err != nil { return xerrors.Errorf("failed to set value: %v", err) } @@ -189,7 +191,7 @@ func (c valueCommand) write(snap store.Snapshot, step execution.Step) error { dela.Logger.Info(). Str("contract", ContractName). - Msgf("setting value %x=%s", snapKey, value) + Msgf("setting value %x=%s", key, value) return nil } @@ -201,7 +203,7 @@ func (c valueCommand) read(snap store.Snapshot, step execution.Step) error { return xerrors.Errorf("'%s' not found in tx arg", KeyArg) } - val, err := snap.Get(prefix(key)) + val, err := snap.Get(key) if err != nil { return xerrors.Errorf("failed to get key '%s': %v", key, err) } @@ -218,8 +220,7 @@ func (c valueCommand) delete(snap store.Snapshot, step execution.Step) error { return xerrors.Errorf("'%s' not found in tx arg", KeyArg) } - snapKey := prefix(key) - err := snap.Delete(snapKey) + err := snap.Delete(key) if err != nil { return xerrors.Errorf("failed to delete key '%x': %v", key, err) } @@ -228,7 +229,7 @@ func (c valueCommand) delete(snap store.Snapshot, step execution.Step) error { dela.Logger.Info(). Str("contract", ContractName). - Msgf("deleting value %x", snapKey) + Msgf("deleting value %x", key) return nil } @@ -238,7 +239,7 @@ func (c valueCommand) list(snap store.Snapshot) error { res := []string{} for k := range c.index { - v, err := snap.Get(prefix([]byte(k))) + v, err := snap.Get([]byte(k)) if err != nil { return xerrors.Errorf("failed to get key '%s': %v", k, err) } @@ -262,7 +263,3 @@ func (h infoLog) Write(p []byte) (int, error) { return len(p), nil } - -func prefix(key []byte) []byte { - return append([]byte(ContractUID), key...) -} diff --git a/contracts/value/value_test.go b/contracts/value/value_test.go index 7027ea992..15afaa709 100644 --- a/contracts/value/value_test.go +++ b/contracts/value/value_test.go @@ -11,6 +11,7 @@ import ( "go.dedis.ch/dela/core/execution" "go.dedis.ch/dela/core/execution/native" "go.dedis.ch/dela/core/store" + "go.dedis.ch/dela/core/store/prefixed" "go.dedis.ch/dela/core/txn" "go.dedis.ch/dela/core/txn/signed" "go.dedis.ch/dela/testing/fake" @@ -56,27 +57,29 @@ func TestCommand_Write(t *testing.T) { Contract: &contract, } - err := cmd.write(fake.NewSnapshot(), makeStep(t)) + snap := prefixed.NewSnapshot(ContractUID, fake.NewSnapshot()) + err := cmd.write(snap, makeStep(t)) require.EqualError(t, err, "'value:key' not found in tx arg") - err = cmd.write(fake.NewSnapshot(), makeStep(t, KeyArg, "dummy")) + snap = prefixed.NewSnapshot(ContractUID, fake.NewSnapshot()) + err = cmd.write(snap, makeStep(t, KeyArg, "dummy")) require.EqualError(t, err, "'value:value' not found in tx arg") - err = cmd.write(fake.NewBadSnapshot(), makeStep(t, KeyArg, "dummy", ValueArg, "value")) + snap = prefixed.NewSnapshot(ContractUID, fake.NewBadSnapshot()) + err = cmd.write(snap, makeStep(t, KeyArg, "dummy", ValueArg, "value")) require.EqualError(t, err, fake.Err("failed to set value")) - snap := fake.NewSnapshot() - _, found := contract.index["dummy"] require.False(t, found) + snap = prefixed.NewSnapshot(ContractUID, fake.NewSnapshot()) err = cmd.write(snap, makeStep(t, KeyArg, "dummy", ValueArg, "value")) require.NoError(t, err) _, found = contract.index["dummy"] require.True(t, found) - res, err := snap.Get(prefix([]byte("dummy"))) + res, err := snap.Get([]byte("dummy")) require.NoError(t, err) require.Equal(t, "value", string(res)) } @@ -91,14 +94,17 @@ func TestCommand_Read(t *testing.T) { key := []byte("dummy") keyHex := hex.EncodeToString(key) - err := cmd.read(fake.NewSnapshot(), makeStep(t)) + snap := prefixed.NewSnapshot(ContractUID, fake.NewSnapshot()) + err := cmd.read(snap, makeStep(t)) require.EqualError(t, err, "'value:key' not found in tx arg") - err = cmd.read(fake.NewBadSnapshot(), makeStep(t, KeyArg, "dummy")) + badSnap := prefixed.NewSnapshot(ContractUID, fake.NewBadSnapshot()) + err = cmd.read(badSnap, makeStep(t, KeyArg, "dummy")) require.EqualError(t, err, fake.Err("failed to get key 'dummy'")) - snap := fake.NewSnapshot() - snap.Set(prefix(key), []byte("value")) + snap = prefixed.NewSnapshot(ContractUID, fake.NewSnapshot()) + err = snap.Set(key, []byte("value")) + require.NoError(t, err) buf := &bytes.Buffer{} cmd.Contract.printer = buf @@ -120,20 +126,23 @@ func TestCommand_Delete(t *testing.T) { keyHex := hex.EncodeToString(key) keyStr := string(key) - err := cmd.delete(fake.NewSnapshot(), makeStep(t)) + snap := prefixed.NewSnapshot(ContractUID, fake.NewSnapshot()) + err := cmd.delete(snap, makeStep(t)) require.EqualError(t, err, "'value:key' not found in tx arg") - err = cmd.delete(fake.NewBadSnapshot(), makeStep(t, KeyArg, keyStr)) + badSnap := prefixed.NewSnapshot(ContractUID, fake.NewBadSnapshot()) + err = cmd.delete(badSnap, makeStep(t, KeyArg, keyStr)) require.EqualError(t, err, fake.Err("failed to delete key '"+keyHex+"'")) - snap := fake.NewSnapshot() - snap.Set(prefix(key), []byte("value")) + snap = prefixed.NewSnapshot(ContractUID, fake.NewSnapshot()) + err = snap.Set(key, []byte("value")) + require.NoError(t, err) contract.index[keyStr] = struct{}{} err = cmd.delete(snap, makeStep(t, KeyArg, keyStr)) require.NoError(t, err) - res, err := snap.Get(prefix(key)) + res, err := snap.Get(key) require.Nil(t, err) require.Nil(t, res) @@ -157,16 +166,19 @@ func TestCommand_List(t *testing.T) { Contract: &contract, } - snap := fake.NewSnapshot() - snap.Set(prefix([]byte(key1)), []byte("value1")) - snap.Set(prefix([]byte(key2)), []byte("value2")) + snap := prefixed.NewSnapshot(ContractUID, fake.NewSnapshot()) + err := snap.Set([]byte(key1), []byte("value1")) + require.NoError(t, err) + err = snap.Set([]byte(key2), []byte("value2")) + require.NoError(t, err) - err := cmd.list(snap) + err = cmd.list(snap) require.NoError(t, err) require.Equal(t, fmt.Sprintf("%x=value1,%x=value2", key1, key2), buf.String()) - err = cmd.list(fake.NewBadSnapshot()) + badSnap := prefixed.NewSnapshot(ContractUID, fake.NewBadSnapshot()) + err = cmd.list(badSnap) // we can't assume an order from the map require.Regexp(t, "^failed to get key", err.Error()) } @@ -220,11 +232,11 @@ type fakeStore struct { store.Snapshot } -func (s fakeStore) Get(key []byte) ([]byte, error) { +func (s fakeStore) Get(_ []byte) ([]byte, error) { return nil, nil } -func (s fakeStore) Set(key, value []byte) error { +func (s fakeStore) Set(_, _ []byte) error { return nil } @@ -232,18 +244,18 @@ type fakeCmd struct { err error } -func (c fakeCmd) write(snap store.Snapshot, step execution.Step) error { +func (c fakeCmd) write(_ store.Snapshot, _ execution.Step) error { return c.err } -func (c fakeCmd) read(snap store.Snapshot, step execution.Step) error { +func (c fakeCmd) read(_ store.Snapshot, _ execution.Step) error { return c.err } -func (c fakeCmd) delete(snap store.Snapshot, step execution.Step) error { +func (c fakeCmd) delete(_ store.Snapshot, _ execution.Step) error { return c.err } -func (c fakeCmd) list(snap store.Snapshot) error { +func (c fakeCmd) list(_ store.Snapshot) error { return c.err } diff --git a/core/access/darc/darc.go b/core/access/darc/darc.go index 3b42268a0..66dfa7cec 100644 --- a/core/access/darc/darc.go +++ b/core/access/darc/darc.go @@ -11,6 +11,9 @@ import ( "golang.org/x/xerrors" ) +// 4 bytes used to prefix DARC keys in the K/V store. +const ContractUID = "DARC" + // Service is an implementation of an access service that will allow one to // store and verify access for a group of identities. // @@ -76,7 +79,7 @@ func (srvc Service) Grant( return xerrors.Errorf("failed to serialize: %v", err) } - err = store.Set(prefixKey(cred.GetID()), value) + err = store.Set(cred.GetID(), value) if err != nil { return xerrors.Errorf("store failed to write: %v", err) } @@ -85,7 +88,7 @@ func (srvc Service) Grant( } func (srvc Service) readPermission(store store.Readable, key []byte) (types.Permission, error) { - value, err := store.Get(prefixKey(key)) + value, err := store.Get(key) if err != nil { return nil, xerrors.Errorf("while reading: %v", err) } @@ -101,10 +104,3 @@ func (srvc Service) readPermission(store store.Readable, key []byte) (types.Perm return perm, nil } - -// 4 bytes used to prefix DARC keys in the K/V store. -const darcPrefix = "DARC" - -func prefixKey(key []byte) []byte { - return append([]byte(darcPrefix), key...) -} diff --git a/core/access/darc/darc_test.go b/core/access/darc/darc_test.go index 49eaee7d7..4ed240421 100644 --- a/core/access/darc/darc_test.go +++ b/core/access/darc/darc_test.go @@ -6,6 +6,7 @@ import ( "github.com/stretchr/testify/require" "go.dedis.ch/dela/core/access" "go.dedis.ch/dela/core/access/darc/types" + "go.dedis.ch/dela/core/store/prefixed" "go.dedis.ch/dela/crypto/bls" "go.dedis.ch/dela/serde" "go.dedis.ch/dela/serde/json" @@ -15,20 +16,22 @@ import ( var testCtx = json.NewContext() func TestService_Match(t *testing.T) { - store := fake.NewSnapshot() + store := prefixed.NewSnapshot(ContractUID, fake.NewSnapshot()) alice := bls.NewSigner() bob := bls.NewSigner() - creds := access.NewContractCreds([]byte{0xaa}, "test", "match") + creds := access.NewContractCreds([]byte{'A'}, "test", "match") perm := types.NewPermission() perm.Allow(creds.GetRule(), alice.GetPublicKey()) data, err := perm.Serialize(testCtx) require.NoError(t, err) - store.Set(append([]byte("DARC"), 0xaa), data) - store.Set(append([]byte("DARC"), 0xbb), []byte{}) + err = store.Set([]byte{'A'}, data) + require.NoError(t, err) + err = store.Set([]byte{'B'}, []byte{}) + require.NoError(t, err) srvc := NewService(testCtx) @@ -44,38 +47,41 @@ func TestService_Match(t *testing.T) { require.Regexp(t, "^permission: rule 'test:match': unauthorized: \\[bls:[[:xdigit:]]+\\]", err.Error()) - err = srvc.Match(fake.NewBadSnapshot(), creds, alice.GetPublicKey()) + badStore := prefixed.NewSnapshot(ContractUID, fake.NewBadSnapshot()) + err = srvc.Match(badStore, creds, alice.GetPublicKey()) require.EqualError(t, err, fake.Err("store failed: while reading")) - err = srvc.Match(store, access.NewContractCreds([]byte{0xcc}, "", "")) - require.EqualError(t, err, "permission 0xcc not found") + err = srvc.Match(store, access.NewContractCreds([]byte{'C'}, "", "")) + require.EqualError(t, err, "permission 0x43 not found") - err = srvc.Match(store, access.NewContractCreds([]byte{0xbb}, "", ""), alice.GetPublicKey()) + err = srvc.Match(store, access.NewContractCreds([]byte{'B'}, "", ""), alice.GetPublicKey()) require.EqualError(t, err, "store failed: permission malformed: JSON format: failed to unmarshal: unexpected end of JSON input") } func TestService_Grant(t *testing.T) { - store := fake.NewSnapshot() - store.Set(append([]byte("DARC"), 0xbb), []byte{}) + store := prefixed.NewSnapshot(ContractUID, fake.NewSnapshot()) + err := store.Set([]byte{'B'}, []byte{}) + require.NoError(t, err) - creds := access.NewContractCreds([]byte{0xaa}, "test", "grant") + creds := access.NewContractCreds([]byte{'A'}, "test", "grant") alice := bls.NewSigner() bob := bls.NewSigner() srvc := NewService(testCtx) - err := srvc.Grant(store, creds, alice.GetPublicKey()) + err = srvc.Grant(store, creds, alice.GetPublicKey()) require.NoError(t, err) err = srvc.Grant(store, creds, bob.GetPublicKey()) require.NoError(t, err) - err = srvc.Grant(fake.NewBadSnapshot(), creds) + badStore := prefixed.NewSnapshot(ContractUID, fake.NewBadSnapshot()) + err = srvc.Grant(badStore, creds) require.EqualError(t, err, fake.Err("store failed: while reading")) - err = srvc.Grant(store, access.NewContractCreds([]byte{0xbb}, "", "")) + err = srvc.Grant(store, access.NewContractCreds([]byte{'B'}, "", "")) require.EqualError(t, err, "store failed: permission malformed: JSON format: failed to unmarshal: unexpected end of JSON input") @@ -83,8 +89,9 @@ func TestService_Grant(t *testing.T) { err = srvc.Grant(store, creds, alice.GetPublicKey()) require.EqualError(t, err, fake.Err("failed to serialize")) - badStore := fake.NewSnapshot() - badStore.ErrWrite = fake.GetError() + fakeSnap := fake.NewSnapshot() + fakeSnap.ErrWrite = fake.GetError() + badStore = prefixed.NewSnapshot(ContractUID, fakeSnap) err = srvc.Grant(badStore, creds, alice.GetPublicKey()) require.EqualError(t, err, fake.Err("store failed to write")) } diff --git a/core/store/prefixed/prefixed.go b/core/store/prefixed/prefixed.go new file mode 100644 index 000000000..97d5420fd --- /dev/null +++ b/core/store/prefixed/prefixed.go @@ -0,0 +1,74 @@ +package prefixed + +import ( + "encoding/binary" + + "go.dedis.ch/dela/core/store" + "go.dedis.ch/dela/crypto" +) + +type readable struct { + store.Readable + prefix []byte +} + +type writable struct { + store.Writable + prefix []byte +} + +type snapshot struct { + *writable + *readable +} + +// NewSnapShot creates a new prefixed Snapshot. +func NewSnapshot(prefix string, snap store.Snapshot) store.Snapshot { + p := []byte(prefix) + return &snapshot{ + &writable{snap, p}, + &readable{snap, p}, + } +} + +// Get implements store.Readable +// It takes a key as input and returns a value or error status +func (s *readable) Get(key []byte) ([]byte, error) { + + k := NewPrefixedKey(s.prefix, key) + return s.Readable.Get(k) +} + +// Set implements store.Writable +// It takes a key and value as input, and returns an error status +func (s *writable) Set(key []byte, value []byte) error { + k := NewPrefixedKey(s.prefix, key) + return s.Writable.Set(k, value) +} + +// Delete implements store.Writable +// It takes a key as input and returns an error status +func (s *writable) Delete(key []byte) error { + k := NewPrefixedKey(s.prefix, key) + return s.Writable.Delete(k) +} + +// NewPrefixedKey is exported because it is used in integration tests. +// It creates a 256bit (hashed) key from a prefix and a base key. +func NewPrefixedKey(prefix, key []byte) []byte { + h := crypto.NewHashFactory(crypto.Sha256).New() + + length := []byte{0, 0} + binary.LittleEndian.PutUint16(length, uint16(len(prefix))) + + h.Write(length) + h.Write(prefix) + + length = []byte{0, 0} + binary.LittleEndian.PutUint16(length, uint16(len(key))) + + h.Write(length) + h.Write(key) + + return h.Sum(nil) +} diff --git a/test/integration_test.go b/test/integration_test.go index 7981b04be..7061d4821 100644 --- a/test/integration_test.go +++ b/test/integration_test.go @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/require" accessContract "go.dedis.ch/dela/contracts/access" "go.dedis.ch/dela/contracts/value" + "go.dedis.ch/dela/core/store/prefixed" "go.dedis.ch/dela/core/txn" "go.dedis.ch/dela/core/txn/signed" "go.dedis.ch/dela/crypto/bls" @@ -88,7 +89,7 @@ func getTest[T require.TestingT](numNode, numTx int) func(t T) { require.NoError(t, err) for i := 0; i < numTx; i++ { - key := make([]byte, 28) // only 224 bits are available + key := make([]byte, 32) // only 224 bits are available _, err = rand.Read(key) require.NoError(t, err) @@ -103,7 +104,8 @@ func getTest[T require.TestingT](numNode, numTx int) func(t T) { err = addAndWait(t, timeout, manager, nodes[0].(cosiDelaNode), args...) require.NoError(t, err) - proof, err := nodes[0].GetOrdering().GetProof(append([]byte("VALU"), key...)) + prefixedKey := prefixed.NewPrefixedKey([]byte("VALU"), key) + proof, err := nodes[0].GetOrdering().GetProof(prefixedKey) require.NoError(t, err) require.Equal(t, []byte("value1"), proof.GetValue()) } @@ -120,7 +122,8 @@ func addAndWait( node cosiDelaNode, args ...txn.Arg, ) error { - manager.Sync() + err := manager.Sync() + require.NoError(t, err) tx, err := manager.Make(args...) if err != nil { @@ -139,9 +142,9 @@ func addAndWait( for event := range events { for _, result := range event.Transactions { - tx := result.GetTransaction() + txDone := result.GetTransaction() - if bytes.Equal(tx.GetID(), tx.GetID()) { + if bytes.Equal(tx.GetID(), txDone.GetID()) { accepted, err := event.Transactions[0].GetStatus() require.Empty(t, err) diff --git a/testing/fake/store.go b/testing/fake/store.go index abc3d99fc..001c48af9 100644 --- a/testing/fake/store.go +++ b/testing/fake/store.go @@ -139,8 +139,8 @@ func (tx dbTx) GetBucketOrCreate(name []byte) (kv.Bucket, error) { return bucket, tx.err } -// GetBucketOrCreate implements store.Transaction. -func (dbTx) OnCommit(fn func()) {} +// OnCommit implements store.Transaction. +func (dbTx) OnCommit(_ func()) {} // Bucket is a fake key/value storage bucket. // From f7c34869ac55d7ba16d3a1279e5f996d6bfd40ad Mon Sep 17 00:00:00 2001 From: Jean Viaene Date: Fri, 11 Aug 2023 12:27:53 +0200 Subject: [PATCH 08/12] minor changes after review --- test/integration_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/integration_test.go b/test/integration_test.go index 7061d4821..1b53375ce 100644 --- a/test/integration_test.go +++ b/test/integration_test.go @@ -89,7 +89,7 @@ func getTest[T require.TestingT](numNode, numTx int) func(t T) { require.NoError(t, err) for i := 0; i < numTx; i++ { - key := make([]byte, 32) // only 224 bits are available + key := make([]byte, 32) _, err = rand.Read(key) require.NoError(t, err) @@ -104,7 +104,7 @@ func getTest[T require.TestingT](numNode, numTx int) func(t T) { err = addAndWait(t, timeout, manager, nodes[0].(cosiDelaNode), args...) require.NoError(t, err) - prefixedKey := prefixed.NewPrefixedKey([]byte("VALU"), key) + prefixedKey := prefixed.NewPrefixedKey([]byte(value.ContractUID), key) proof, err := nodes[0].GetOrdering().GetProof(prefixedKey) require.NoError(t, err) require.Equal(t, []byte("value1"), proof.GetValue()) From b7a74fac8c5efb6e6acf12219713c2aceb160347 Mon Sep 17 00:00:00 2001 From: Jean Viaene Date: Fri, 11 Aug 2023 13:24:08 +0200 Subject: [PATCH 09/12] tweak timings --- core/ordering/cosipbft/cosipbft_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/ordering/cosipbft/cosipbft_test.go b/core/ordering/cosipbft/cosipbft_test.go index 720593fed..28bba291b 100644 --- a/core/ordering/cosipbft/cosipbft_test.go +++ b/core/ordering/cosipbft/cosipbft_test.go @@ -64,25 +64,25 @@ func TestService_Scenario_Basic(t *testing.T) { err = nodes[0].pool.Add(makeTx(t, 0, signer)) require.NoError(t, err) - evt := waitEvent(t, events, 2*DefaultRoundTimeout) + evt := waitEvent(t, events, 3*DefaultRoundTimeout) require.Equal(t, uint64(0), evt.Index) err = nodes[1].pool.Add(makeTx(t, 1, signer)) require.NoError(t, err) - evt = waitEvent(t, events, 20*DefaultRoundTimeout) + evt = waitEvent(t, events, 10*DefaultRoundTimeout) require.Equal(t, uint64(1), evt.Index) err = nodes[1].pool.Add(makeRosterTx(t, 2, ro, signer)) require.NoError(t, err) - evt = waitEvent(t, events, 20*DefaultRoundTimeout) + evt = waitEvent(t, events, 10*DefaultRoundTimeout) require.Equal(t, uint64(2), evt.Index) for i := 0; i < 3; i++ { err = nodes[1].pool.Add(makeTx(t, uint64(i+3), signer)) require.NoError(t, err) - evt = waitEvent(t, events, 20*DefaultRoundTimeout) + evt = waitEvent(t, events, 10*DefaultRoundTimeout) require.Equal(t, uint64(i+3), evt.Index) } From 170673b10c85d18ead0a4a3286d8d965cb9d758b Mon Sep 17 00:00:00 2001 From: Jean Viaene Date: Tue, 8 Aug 2023 08:27:37 +0200 Subject: [PATCH 10/12] refactor inmemory store to better handle errors --- testing/fake/store.go | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/testing/fake/store.go b/testing/fake/store.go index 001c48af9..3404ffebd 100644 --- a/testing/fake/store.go +++ b/testing/fake/store.go @@ -3,6 +3,7 @@ package fake import ( "go.dedis.ch/dela/core/store" "go.dedis.ch/dela/core/store/kv" + "golang.org/x/xerrors" ) // InMemorySnapshot is a fake implementation of a store snapshot. @@ -36,21 +37,43 @@ func NewBadSnapshot() *InMemorySnapshot { // Get implements store.Snapshot. func (snap *InMemorySnapshot) Get(key []byte) ([]byte, error) { - return snap.values[string(key)], snap.ErrRead + if snap.ErrRead != nil { + return nil, snap.ErrRead + } + + value, found := snap.values[string(key)] + if found { + return value, nil + } + return nil, xerrors.Errorf("key not found: %s", key) } // Set implements store.Snapshot. func (snap *InMemorySnapshot) Set(key, value []byte) error { + if snap.ErrWrite != nil { + return snap.ErrWrite + } + snap.values[string(key)] = value - return snap.ErrWrite + return nil } // Delete implements store.Snapshot. func (snap *InMemorySnapshot) Delete(key []byte) error { + if snap.ErrDelete != nil { + return snap.ErrDelete + } + + _, found := snap.values[string(key)] + if !found { + // is this behaviour correct or should it be ignored ? + return xerrors.Errorf("key not found: %s", key) + } + delete(snap.values, string(key)) - return snap.ErrDelete + return nil } // InMemoryDB is a fake implementation of a key/value storage. From ad639116f3b40ff00ce8ff561ca16eb5a41ca8ca Mon Sep 17 00:00:00 2001 From: Jean Viaene Date: Tue, 8 Aug 2023 08:34:50 +0200 Subject: [PATCH 11/12] fix error returned on store delete --- core/store/store.go | 3 +++ testing/fake/store.go | 6 ------ 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/core/store/store.go b/core/store/store.go index a1e20deaf..87783159c 100644 --- a/core/store/store.go +++ b/core/store/store.go @@ -12,6 +12,9 @@ type Readable interface { type Writable interface { Set(key []byte, value []byte) error + // Delete deletes a given key. + // It can return an error if deleting from a read-only store + // No error is returned if the key does not exist. Delete(key []byte) error } diff --git a/testing/fake/store.go b/testing/fake/store.go index 3404ffebd..222f980ee 100644 --- a/testing/fake/store.go +++ b/testing/fake/store.go @@ -65,12 +65,6 @@ func (snap *InMemorySnapshot) Delete(key []byte) error { return snap.ErrDelete } - _, found := snap.values[string(key)] - if !found { - // is this behaviour correct or should it be ignored ? - return xerrors.Errorf("key not found: %s", key) - } - delete(snap.values, string(key)) return nil From bf09f67066d219bccff613980703b96cdf632372 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pierluca=20Bors=C3=B2?= Date: Mon, 14 Aug 2023 17:28:22 +0200 Subject: [PATCH 12/12] Fixed DARC logic and tests --- contracts/value/value_test.go | 2 +- core/access/darc/darc.go | 8 ++++--- core/access/darc/darc_test.go | 41 +++++++++++++++------------------ core/store/prefixed/prefixed.go | 8 ++++++- testing/fake/store.go | 3 +-- 5 files changed, 33 insertions(+), 29 deletions(-) diff --git a/contracts/value/value_test.go b/contracts/value/value_test.go index 15afaa709..aa4f9769c 100644 --- a/contracts/value/value_test.go +++ b/contracts/value/value_test.go @@ -144,7 +144,7 @@ func TestCommand_Delete(t *testing.T) { res, err := snap.Get(key) require.Nil(t, err) - require.Nil(t, res) + require.Nil(t, res) // = "key not found" _, found := contract.index[keyStr] require.False(t, found) diff --git a/core/access/darc/darc.go b/core/access/darc/darc.go index 66dfa7cec..2cc218a7a 100644 --- a/core/access/darc/darc.go +++ b/core/access/darc/darc.go @@ -4,16 +4,15 @@ package darc import ( + contract "go.dedis.ch/dela/contracts/access" "go.dedis.ch/dela/core/access" "go.dedis.ch/dela/core/access/darc/types" "go.dedis.ch/dela/core/store" + "go.dedis.ch/dela/core/store/prefixed" "go.dedis.ch/dela/serde" "golang.org/x/xerrors" ) -// 4 bytes used to prefix DARC keys in the K/V store. -const ContractUID = "DARC" - // Service is an implementation of an access service that will allow one to // store and verify access for a group of identities. // @@ -39,6 +38,7 @@ func (srvc Service) Match( creds access.Credential, idents ...access.Identity, ) error { + store = prefixed.NewReadable(contract.ContractUID, store) perm, err := srvc.readPermission(store, creds.GetID()) if err != nil { return xerrors.Errorf("store failed: %v", err) @@ -63,6 +63,8 @@ func (srvc Service) Grant( cred access.Credential, idents ...access.Identity, ) error { + store = prefixed.NewSnapshot(contract.ContractUID, store) + perm, err := srvc.readPermission(store, cred.GetID()) if err != nil { return xerrors.Errorf("store failed: %v", err) diff --git a/core/access/darc/darc_test.go b/core/access/darc/darc_test.go index 4ed240421..4ed8e6c72 100644 --- a/core/access/darc/darc_test.go +++ b/core/access/darc/darc_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/stretchr/testify/require" + contract "go.dedis.ch/dela/contracts/access" "go.dedis.ch/dela/core/access" "go.dedis.ch/dela/core/access/darc/types" "go.dedis.ch/dela/core/store/prefixed" @@ -16,22 +17,21 @@ import ( var testCtx = json.NewContext() func TestService_Match(t *testing.T) { - store := prefixed.NewSnapshot(ContractUID, fake.NewSnapshot()) + store := fake.NewSnapshot() alice := bls.NewSigner() bob := bls.NewSigner() - creds := access.NewContractCreds([]byte{'A'}, "test", "match") + creds := access.NewContractCreds([]byte{0xaa}, "test", "match") perm := types.NewPermission() perm.Allow(creds.GetRule(), alice.GetPublicKey()) data, err := perm.Serialize(testCtx) require.NoError(t, err) - err = store.Set([]byte{'A'}, data) - require.NoError(t, err) - err = store.Set([]byte{'B'}, []byte{}) - require.NoError(t, err) + prefixStore := prefixed.NewSnapshot(contract.ContractUID, store) + prefixStore.Set([]byte{0xaa}, data) + prefixStore.Set([]byte{0xbb}, []byte{}) srvc := NewService(testCtx) @@ -47,41 +47,39 @@ func TestService_Match(t *testing.T) { require.Regexp(t, "^permission: rule 'test:match': unauthorized: \\[bls:[[:xdigit:]]+\\]", err.Error()) - badStore := prefixed.NewSnapshot(ContractUID, fake.NewBadSnapshot()) - err = srvc.Match(badStore, creds, alice.GetPublicKey()) + err = srvc.Match(fake.NewBadSnapshot(), creds, alice.GetPublicKey()) require.EqualError(t, err, fake.Err("store failed: while reading")) - err = srvc.Match(store, access.NewContractCreds([]byte{'C'}, "", "")) - require.EqualError(t, err, "permission 0x43 not found") + err = srvc.Match(store, access.NewContractCreds([]byte{0xcc}, "", "")) + require.EqualError(t, err, "permission 0xcc not found") - err = srvc.Match(store, access.NewContractCreds([]byte{'B'}, "", ""), alice.GetPublicKey()) + err = srvc.Match(store, access.NewContractCreds([]byte{0xbb}, "", ""), alice.GetPublicKey()) require.EqualError(t, err, "store failed: permission malformed: JSON format: failed to unmarshal: unexpected end of JSON input") } func TestService_Grant(t *testing.T) { - store := prefixed.NewSnapshot(ContractUID, fake.NewSnapshot()) - err := store.Set([]byte{'B'}, []byte{}) - require.NoError(t, err) + store := fake.NewSnapshot() + prefixStore := prefixed.NewSnapshot(contract.ContractUID, store) + prefixStore.Set([]byte{0xbb}, []byte{}) - creds := access.NewContractCreds([]byte{'A'}, "test", "grant") + creds := access.NewContractCreds([]byte{0xaa}, "test", "grant") alice := bls.NewSigner() bob := bls.NewSigner() srvc := NewService(testCtx) - err = srvc.Grant(store, creds, alice.GetPublicKey()) + err := srvc.Grant(store, creds, alice.GetPublicKey()) require.NoError(t, err) err = srvc.Grant(store, creds, bob.GetPublicKey()) require.NoError(t, err) - badStore := prefixed.NewSnapshot(ContractUID, fake.NewBadSnapshot()) - err = srvc.Grant(badStore, creds) + err = srvc.Grant(fake.NewBadSnapshot(), creds) require.EqualError(t, err, fake.Err("store failed: while reading")) - err = srvc.Grant(store, access.NewContractCreds([]byte{'B'}, "", "")) + err = srvc.Grant(store, access.NewContractCreds([]byte{0xbb}, "", "")) require.EqualError(t, err, "store failed: permission malformed: JSON format: failed to unmarshal: unexpected end of JSON input") @@ -89,9 +87,8 @@ func TestService_Grant(t *testing.T) { err = srvc.Grant(store, creds, alice.GetPublicKey()) require.EqualError(t, err, fake.Err("failed to serialize")) - fakeSnap := fake.NewSnapshot() - fakeSnap.ErrWrite = fake.GetError() - badStore = prefixed.NewSnapshot(ContractUID, fakeSnap) + badStore := fake.NewSnapshot() + badStore.ErrWrite = fake.GetError() err = srvc.Grant(badStore, creds, alice.GetPublicKey()) require.EqualError(t, err, fake.Err("store failed to write")) } diff --git a/core/store/prefixed/prefixed.go b/core/store/prefixed/prefixed.go index 97d5420fd..bbb96c0e2 100644 --- a/core/store/prefixed/prefixed.go +++ b/core/store/prefixed/prefixed.go @@ -22,7 +22,7 @@ type snapshot struct { *readable } -// NewSnapShot creates a new prefixed Snapshot. +// NewSnapshot creates a new prefixed Snapshot. func NewSnapshot(prefix string, snap store.Snapshot) store.Snapshot { p := []byte(prefix) return &snapshot{ @@ -31,6 +31,12 @@ func NewSnapshot(prefix string, snap store.Snapshot) store.Snapshot { } } +// NewReadable creates a new prefixed Readable. +func NewReadable(prefix string, r store.Readable) store.Readable { + p := []byte(prefix) + return &readable{r, p} +} + // Get implements store.Readable // It takes a key as input and returns a value or error status func (s *readable) Get(key []byte) ([]byte, error) { diff --git a/testing/fake/store.go b/testing/fake/store.go index 222f980ee..8a33ab9cb 100644 --- a/testing/fake/store.go +++ b/testing/fake/store.go @@ -3,7 +3,6 @@ package fake import ( "go.dedis.ch/dela/core/store" "go.dedis.ch/dela/core/store/kv" - "golang.org/x/xerrors" ) // InMemorySnapshot is a fake implementation of a store snapshot. @@ -45,7 +44,7 @@ func (snap *InMemorySnapshot) Get(key []byte) ([]byte, error) { if found { return value, nil } - return nil, xerrors.Errorf("key not found: %s", key) + return nil, nil } // Set implements store.Snapshot.