diff --git a/testutil/keeper/keepers_init.go b/testutil/keeper/keepers_init.go index f52ce2cbf4..1e1f6a8073 100644 --- a/testutil/keeper/keepers_init.go +++ b/testutil/keeper/keepers_init.go @@ -24,6 +24,8 @@ import ( "github.com/lavanet/lava/x/plans" planskeeper "github.com/lavanet/lava/x/plans/keeper" planstypes "github.com/lavanet/lava/x/plans/types" + projectskeeper "github.com/lavanet/lava/x/projects/keeper" + projectstypes "github.com/lavanet/lava/x/projects/types" "github.com/lavanet/lava/x/spec" speckeeper "github.com/lavanet/lava/x/spec/keeper" spectypes "github.com/lavanet/lava/x/spec/types" @@ -47,6 +49,7 @@ type Keepers struct { Plans planskeeper.Keeper Pairing pairingkeeper.Keeper Conflict conflictkeeper.Keeper + Projects projectskeeper.Keeper BankKeeper mockBankKeeper AccountKeeper mockAccountKeeper ParamsKeeper paramskeeper.Keeper @@ -58,6 +61,7 @@ type Servers struct { SpecServer spectypes.MsgServer PairingServer pairingtypes.MsgServer ConflictServer conflicttypes.MsgServer + ProjectServer projectstypes.MsgServer PlansServer planstypes.MsgServer } @@ -115,6 +119,11 @@ func InitAllKeepers(t testing.TB) (*Servers, *Keepers, context.Context) { stateStore.MountStoreWithDB(conflictStoreKey, sdk.StoreTypeIAVL, db) stateStore.MountStoreWithDB(conflictMemStoreKey, sdk.StoreTypeMemory, nil) + projectsStoreKey := sdk.NewKVStoreKey(projectstypes.StoreKey) + projectsMemStoreKey := storetypes.NewMemoryStoreKey(projectstypes.MemStoreKey) + stateStore.MountStoreWithDB(projectsStoreKey, sdk.StoreTypeIAVL, db) + stateStore.MountStoreWithDB(projectsMemStoreKey, sdk.StoreTypeMemory, nil) + require.NoError(t, stateStore.LoadLatestVersion()) paramsKeeper := paramskeeper.NewKeeper(cdc, pairingtypes.Amino, paramsStoreKey, tkey) @@ -129,6 +138,8 @@ func InitAllKeepers(t testing.TB) (*Servers, *Keepers, context.Context) { specparamsSubspace, _ := paramsKeeper.GetSubspace(spectypes.ModuleName) + projectsparamsSubspace, _ := paramsKeeper.GetSubspace(projectstypes.ModuleName) + plansparamsSubspace, _ := paramsKeeper.GetSubspace(planstypes.ModuleName) conflictparamsSubspace := paramstypes.NewSubspace(cdc, @@ -142,6 +153,7 @@ func InitAllKeepers(t testing.TB) (*Servers, *Keepers, context.Context) { ks.AccountKeeper = mockAccountKeeper{} ks.BankKeeper = mockBankKeeper{balance: make(map[string]sdk.Coins)} ks.Spec = *speckeeper.NewKeeper(cdc, specStoreKey, specMemStoreKey, specparamsSubspace) + ks.Projects = *projectskeeper.NewKeeper(cdc, projectsStoreKey, projectsMemStoreKey, projectsparamsSubspace) ks.Epochstorage = *epochstoragekeeper.NewKeeper(cdc, epochStoreKey, epochMemStoreKey, epochparamsSubspace, &ks.BankKeeper, &ks.AccountKeeper, ks.Spec) ks.Plans = *planskeeper.NewKeeper(cdc, plansStoreKey, plansMemStoreKey, plansparamsSubspace) ks.Pairing = *pairingkeeper.NewKeeper(cdc, pairingStoreKey, pairingMemStoreKey, pairingparamsSubspace, &ks.BankKeeper, &ks.AccountKeeper, ks.Spec, &ks.Epochstorage) @@ -156,6 +168,7 @@ func InitAllKeepers(t testing.TB) (*Servers, *Keepers, context.Context) { ks.Spec.SetParams(ctx, spectypes.DefaultParams()) ks.Epochstorage.SetParams(ctx, epochstoragetypes.DefaultParams()) ks.Conflict.SetParams(ctx, conflicttypes.DefaultParams()) + ks.Projects.SetParams(ctx, projectstypes.DefaultParams()) ks.Plans.SetParams(ctx, planstypes.DefaultParams()) ks.Epochstorage.PushFixatedParams(ctx, 0, 0) @@ -166,6 +179,7 @@ func InitAllKeepers(t testing.TB) (*Servers, *Keepers, context.Context) { ss.PlansServer = planskeeper.NewMsgServerImpl(ks.Plans) ss.PairingServer = pairingkeeper.NewMsgServerImpl(ks.Pairing) ss.ConflictServer = conflictkeeper.NewMsgServerImpl(ks.Conflict) + ss.ProjectServer = projectskeeper.NewMsgServerImpl(ks.Projects) core.SetEnvironment(&core.Environment{BlockStore: &ks.BlockStore}) diff --git a/x/projects/keeper/creation.go b/x/projects/keeper/creation.go index 5c700e011e..fdd47b32e5 100644 --- a/x/projects/keeper/creation.go +++ b/x/projects/keeper/creation.go @@ -8,50 +8,47 @@ import ( // add a default project to a subscription, add the subscription key as func (k Keeper) CreateDefaultProject(ctx sdk.Context, subscriptionAddress string) error { - project := types.DefaultProject(subscriptionAddress) // TODO add the CU per epoch here - var emptyProject types.Project - - _, found := k.projectsFS.FindEntry(ctx, project.Index, uint64(ctx.BlockHeight()), &emptyProject) - if found { - return utils.LavaError(ctx, ctx.Logger(), "CreateDefaultProject_already_exist", map[string]string{"subscription": subscriptionAddress}, "default project already exist for the current subscription") - } - - project.Enabled = true - // add subscription key as developer key to the default project - projectID := types.ProtoString{String_: project.Index} - k.developerKeysFS.AppendEntry(ctx, project.Index, uint64(ctx.BlockHeight()), &projectID) - - return k.projectsFS.AppendEntry(ctx, project.Index, uint64(ctx.BlockHeight()), &project) + return k.CreateProject(ctx, subscriptionAddress, types.DEFAULT_PROJECT_NAME, subscriptionAddress, true) } // add a new project to the subscription -func (k Keeper) CreateEmptyProject(ctx sdk.Context, subscriptionAddress string, projectName string, adminAddress string, enable bool) error { - project := types.CreateEmptyProject(subscriptionAddress, projectName) +func (k Keeper) CreateProject(ctx sdk.Context, subscriptionAddress string, projectName string, adminAddress string, enable bool) error { + project := types.CreateProject(subscriptionAddress, projectName) var emptyProject types.Project - _, found := k.projectsFS.FindEntry(ctx, project.Index, uint64(ctx.BlockHeight()), &emptyProject) + blockHeight := uint64(ctx.BlockHeight()) + _, found := k.projectsFS.FindEntry(ctx, project.Index, blockHeight, &emptyProject) // the project with the same name already exists if no error has returned if found { return utils.LavaError(ctx, ctx.Logger(), "CreateEmptyProject_already_exist", map[string]string{"subscription": subscriptionAddress}, "project already exist for the current subscription with the same name") } if subscriptionAddress != adminAddress { - project.ProjectKeys = append(project.ProjectKeys, types.ProjectKey{Key: adminAddress, Types: []types.ProjectKey_KEY_TYPE{types.ProjectKey_ADMIN}}) + project.AppendKey(types.ProjectKey{Key: adminAddress, Types: []types.ProjectKey_KEY_TYPE{types.ProjectKey_ADMIN}}) } + err := k.RegisterDeveloperKey(ctx, adminAddress, project.Index, blockHeight) + if err != nil { + return err + } + + project.Enabled = enable + return k.projectsFS.AppendEntry(ctx, project.Index, blockHeight, &project) +} + +func (k Keeper) RegisterDeveloperKey(ctx sdk.Context, developerKey string, projectIndex string, blockHeight uint64) error { var projectID types.ProtoString - _, found = k.developerKeysFS.FindEntry(ctx, adminAddress, uint64(ctx.BlockHeight()), &projectID) + _, found := k.developerKeysFS.FindEntry(ctx, developerKey, blockHeight, &projectID) // a developer key with this address is not registered, add it to the developer keys list - if found { - projectID.String_ = project.Index - err := k.developerKeysFS.AppendEntry(ctx, project.Index, uint64(ctx.BlockHeight()), &projectID) + if !found { + projectID.String_ = projectIndex + err := k.developerKeysFS.AppendEntry(ctx, developerKey, blockHeight, &projectID) if err != nil { return err } } - project.Enabled = enable - return k.projectsFS.AppendEntry(ctx, project.Index, uint64(ctx.BlockHeight()), &project) + return nil } // snapshot project, create a snapshot of a project and reset the cu diff --git a/x/projects/keeper/msg_server_add_project_keys.go b/x/projects/keeper/msg_server_add_project_keys.go index 19d1ab3161..d6fa526dc0 100644 --- a/x/projects/keeper/msg_server_add_project_keys.go +++ b/x/projects/keeper/msg_server_add_project_keys.go @@ -4,47 +4,15 @@ import ( "context" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/lavanet/lava/utils" "github.com/lavanet/lava/x/projects/types" ) func (k msgServer) AddProjectKeys(goCtx context.Context, msg *types.MsgAddProjectKeys) (*types.MsgAddProjectKeysResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) - projectID := msg.Project - adminKey := msg.Creator - projectKeys := msg.ProjectKeys - var project types.Project - err, found := k.projectsFS.FindEntry(ctx, projectID, uint64(ctx.BlockHeight()), &project) - if err != nil || !found { - return nil, utils.LavaError(ctx, ctx.Logger(), "AddProjectKeys_project_not_found", map[string]string{"project": projectID}, "project id not found") - } - - // check if the admin key is valid - if !project.IsKeyType(adminKey, types.ProjectKey_ADMIN) || project.Subscription != adminKey { - return nil, utils.LavaError(ctx, ctx.Logger(), "AddProjectKeys_not_admin", map[string]string{"project": projectID}, "the requesting key is not admin key") - } - - // check that those keys are unique for developers - for _, projectKey := range projectKeys { - if projectKey.IsKeyType(types.ProjectKey_DEVELOPER) { - // if the key is a developer key add it to the map of developer keys - var projectIDstring types.ProtoString - _, found := k.developerKeysFS.FindEntry(ctx, projectKey.Key, uint64(ctx.BlockHeight()), &projectIDstring) - if !found { - projectIDstring.String_ = project.Index - err = k.developerKeysFS.AppendEntry(ctx, project.Index, uint64(ctx.BlockHeight()), &projectIDstring) - if err != nil { - return nil, utils.LavaError(ctx, ctx.Logger(), "AddProjectKeys_used_key", map[string]string{"project": projectID, "developerKey": projectKey.Key, "err": err.Error()}, "the requesting key is not admin key") - } - } - } - } - - err = k.projectsFS.AppendEntry(ctx, projectID, uint64(ctx.BlockHeight()), &project) + err := k.AddKeysToProject(ctx, msg.Project, msg.Creator, msg.ProjectKeys) if err != nil { return nil, err } - - return &types.MsgAddProjectKeysResponse{}, err + return &types.MsgAddProjectKeysResponse{}, nil } diff --git a/x/projects/keeper/msg_server_set_project_policy.go b/x/projects/keeper/msg_server_set_project_policy.go index 8c0e4bcba2..5688c8dd36 100644 --- a/x/projects/keeper/msg_server_set_project_policy.go +++ b/x/projects/keeper/msg_server_set_project_policy.go @@ -20,7 +20,7 @@ func (k msgServer) SetProjectPolicy(goCtx context.Context, msg *types.MsgSetProj } // check if the admin key is valid - if !project.IsKeyType(adminKey, types.ProjectKey_ADMIN) || project.Subscription != adminKey { + if !project.HasKeyType(adminKey, types.ProjectKey_ADMIN) || project.Subscription != adminKey { return nil, utils.LavaError(ctx, ctx.Logger(), "SetProjectPolicy_not_admin", map[string]string{"project": projectID}, "the requesting key is not admin key") } diff --git a/x/projects/keeper/project.go b/x/projects/keeper/project.go index 6d8b0eb70a..9b850e5038 100644 --- a/x/projects/keeper/project.go +++ b/x/projects/keeper/project.go @@ -8,7 +8,6 @@ import ( "github.com/lavanet/lava/x/projects/types" ) -// add a default project to a subscription func (k Keeper) GetProjectForBlock(ctx sdk.Context, projectID string, blockHeight uint64) (types.Project, error) { var project types.Project @@ -49,6 +48,31 @@ func (k Keeper) GetProjectForDeveloper(ctx sdk.Context, developerKey string, blo return project, nil } +func (k Keeper) AddKeysToProject(ctx sdk.Context, projectID string, adminKey string, projectKeys []types.ProjectKey) error { + var project types.Project + err, found := k.projectsFS.FindEntry(ctx, projectID, uint64(ctx.BlockHeight()), &project) + if err != nil || !found { + return utils.LavaError(ctx, ctx.Logger(), "AddProjectKeys_project_not_found", map[string]string{"project": projectID}, "project id not found") + } + + // check if the admin key is valid + if !project.HasKeyType(adminKey, types.ProjectKey_ADMIN) && project.Subscription != adminKey { + return utils.LavaError(ctx, ctx.Logger(), "AddProjectKeys_not_admin", map[string]string{"project": projectID}, "the requesting key is not admin key") + } + + // check that those keys are unique for developers + for _, projectKey := range projectKeys { + err = k.RegisterDeveloperKey(ctx, projectKey.Key, project.Index, uint64(ctx.BlockHeight())) + if err != nil { + return err + } + + project.AppendKey(projectKey) + } + + return k.projectsFS.AppendEntry(ctx, projectID, uint64(ctx.BlockHeight()), &project) +} + func (k Keeper) ValidateDeveloperRequest(ctx sdk.Context, developerKey string, chainID string, apiName string, blockHeight uint64) (valid bool, policy types.Policy, err error) { project, err := k.GetProjectForDeveloper(ctx, developerKey, blockHeight) if err != nil { diff --git a/x/projects/keeper/project_test.go b/x/projects/keeper/project_test.go new file mode 100644 index 0000000000..2c087aced4 --- /dev/null +++ b/x/projects/keeper/project_test.go @@ -0,0 +1,131 @@ +package keeper_test + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/lavanet/lava/testutil/common" + testkeeper "github.com/lavanet/lava/testutil/keeper" + "github.com/lavanet/lava/x/projects/types" + "github.com/stretchr/testify/require" +) + +func TestCreateDefaultProject(t *testing.T) { + _, keepers, ctx := testkeeper.InitAllKeepers(t) + + subAccount := common.CreateNewAccount(ctx, *keepers, 10000) + err := keepers.Projects.CreateDefaultProject(sdk.UnwrapSDKContext(ctx), subAccount.Addr.String()) + require.Nil(t, err) + + // subscription key is a developer in the default project + response1, err := keepers.Projects.ShowDevelopersProject(ctx, &types.QueryShowDevelopersProjectRequest{Developer: subAccount.Addr.String()}) + require.Nil(t, err) + + testkeeper.AdvanceEpoch(ctx, keepers) + + response2, err := keepers.Projects.ShowProject(ctx, &types.QueryShowProjectRequest{Project: response1.Project.Index}) + require.Nil(t, err) + + require.Equal(t, response2.Project, response1.Project) +} + +func TestCreateProject(t *testing.T) { + _, keepers, ctx := testkeeper.InitAllKeepers(t) + + projectName := "mockname" + subAccount := common.CreateNewAccount(ctx, *keepers, 10000) + adminAcc := common.CreateNewAccount(ctx, *keepers, 10000) + err := keepers.Projects.CreateProject(sdk.UnwrapSDKContext(ctx), subAccount.Addr.String(), projectName, adminAcc.Addr.String(), false) + require.Nil(t, err) + + testkeeper.AdvanceEpoch(ctx, keepers) + + // create another project with the same name, should fail as this is unique + err = keepers.Projects.CreateProject(sdk.UnwrapSDKContext(ctx), subAccount.Addr.String(), projectName, adminAcc.Addr.String(), false) + require.NotNil(t, err) + + // subscription key is not a developer + response1, err := keepers.Projects.ShowDevelopersProject(ctx, &types.QueryShowDevelopersProjectRequest{Developer: subAccount.Addr.String()}) + require.NotNil(t, err) + + response1, err = keepers.Projects.ShowDevelopersProject(ctx, &types.QueryShowDevelopersProjectRequest{Developer: adminAcc.Addr.String()}) + require.Nil(t, err) + + response2, err := keepers.Projects.ShowProject(ctx, &types.QueryShowProjectRequest{Project: response1.Project.Index}) + require.Nil(t, err) + + require.Equal(t, response2.Project, response1.Project) + + require.Equal(t, len(response2.Project.ProjectKeys), 1) + require.Equal(t, response2.Project.ProjectKeys[0].Key, adminAcc.Addr.String()) + require.Equal(t, len(response2.Project.ProjectKeys[0].Types), 1) + require.Equal(t, response2.Project.ProjectKeys[0].Types[0], types.ProjectKey_ADMIN) +} + +func TestAddKeys(t *testing.T) { + servers, keepers, ctx := testkeeper.InitAllKeepers(t) + + projectName := "mockname" + subAccount := common.CreateNewAccount(ctx, *keepers, 10000) + adminAcc := common.CreateNewAccount(ctx, *keepers, 10000) + developerAcc := common.CreateNewAccount(ctx, *keepers, 10000) + err := keepers.Projects.CreateProject(sdk.UnwrapSDKContext(ctx), subAccount.Addr.String(), projectName, adminAcc.Addr.String(), false) + require.Nil(t, err) + + testkeeper.AdvanceEpoch(ctx, keepers) + + projectRes, err := keepers.Projects.ShowDevelopersProject(ctx, &types.QueryShowDevelopersProjectRequest{Developer: adminAcc.Addr.String()}) + require.Nil(t, err) + + project := projectRes.Project + pk := types.ProjectKey{Key: developerAcc.Addr.String(), Types: []types.ProjectKey_KEY_TYPE{types.ProjectKey_ADMIN}} + // try adding myself as admin, should fail + _, err = servers.ProjectServer.AddProjectKeys(ctx, &types.MsgAddProjectKeys{Creator: developerAcc.Addr.String(), Project: project.Index, ProjectKeys: []types.ProjectKey{pk}}) + require.NotNil(t, err) + + // admin key adding as developer + pk = types.ProjectKey{Key: developerAcc.Addr.String(), Types: []types.ProjectKey_KEY_TYPE{types.ProjectKey_DEVELOPER}} + _, err = servers.ProjectServer.AddProjectKeys(ctx, &types.MsgAddProjectKeys{Creator: adminAcc.Addr.String(), Project: project.Index, ProjectKeys: []types.ProjectKey{pk}}) + require.Nil(t, err) + + // developer tries to add admin + pk = types.ProjectKey{Key: developerAcc.Addr.String(), Types: []types.ProjectKey_KEY_TYPE{types.ProjectKey_ADMIN}} + _, err = servers.ProjectServer.AddProjectKeys(ctx, &types.MsgAddProjectKeys{Creator: developerAcc.Addr.String(), Project: project.Index, ProjectKeys: []types.ProjectKey{pk}}) + require.NotNil(t, err) + + // admin adding admin + pk = types.ProjectKey{Key: developerAcc.Addr.String(), Types: []types.ProjectKey_KEY_TYPE{types.ProjectKey_ADMIN}} + _, err = servers.ProjectServer.AddProjectKeys(ctx, &types.MsgAddProjectKeys{Creator: adminAcc.Addr.String(), Project: project.Index, ProjectKeys: []types.ProjectKey{pk}}) + require.Nil(t, err) + + // new admin adding another developer + developerAcc2 := common.CreateNewAccount(ctx, *keepers, 10000) + pk = types.ProjectKey{Key: developerAcc2.Addr.String(), Types: []types.ProjectKey_KEY_TYPE{types.ProjectKey_ADMIN}} + _, err = servers.ProjectServer.AddProjectKeys(ctx, &types.MsgAddProjectKeys{Creator: developerAcc.Addr.String(), Project: project.Index, ProjectKeys: []types.ProjectKey{pk}}) + require.Nil(t, err) + + // fetch project with new developer + projectRes, err = keepers.Projects.ShowDevelopersProject(ctx, &types.QueryShowDevelopersProjectRequest{Developer: developerAcc2.Addr.String()}) + require.Nil(t, err) +} + +func TestAddAdminInTwoProjects(t *testing.T) { + _, keepers, ctx := testkeeper.InitAllKeepers(t) + // he should be a developer only in the first project + projectName1 := "mockname1" + projectName2 := "mockname2" + + subAccount := common.CreateNewAccount(ctx, *keepers, 10000) + adminAcc := common.CreateNewAccount(ctx, *keepers, 10000) + err := keepers.Projects.CreateProject(sdk.UnwrapSDKContext(ctx), subAccount.Addr.String(), projectName1, adminAcc.Addr.String(), false) + require.Nil(t, err) + + err = keepers.Projects.CreateProject(sdk.UnwrapSDKContext(ctx), subAccount.Addr.String(), projectName2, adminAcc.Addr.String(), false) + require.Nil(t, err) + + testkeeper.AdvanceEpoch(ctx, keepers) + + response, err := keepers.Projects.ShowDevelopersProject(ctx, &types.QueryShowDevelopersProjectRequest{Developer: adminAcc.Addr.String()}) + require.Nil(t, err) + require.Equal(t, response.Project.Index, types.ProjectIndex(subAccount.Addr.String(), projectName1)) +} diff --git a/x/projects/types/keys.go b/x/projects/types/keys.go index 873505a3bd..9358df9fad 100644 --- a/x/projects/types/keys.go +++ b/x/projects/types/keys.go @@ -17,10 +17,10 @@ const ( MemStoreKey = "mem_projects" // prefix for the projects fixation store - ProjectsFixationPrefix = "projects-fixation" + ProjectsFixationPrefix = "prj-fs" // prefix for the projects fixation store - DeveloperKeysFixationPrefix = "developers-fixation" + DeveloperKeysFixationPrefix = "dev-fs" ) func KeyPrefix(p string) []byte { diff --git a/x/projects/types/project.go b/x/projects/types/project.go index 75ca7f3640..7d1fd2d726 100644 --- a/x/projects/types/project.go +++ b/x/projects/types/project.go @@ -6,21 +6,17 @@ func ProjectIndex(subscriptionAddress string, projectName string) string { return subscriptionAddress + "-" + projectName } -func CreateEmptyProject(subscriptionAddress string, projectName string) Project { +func CreateProject(subscriptionAddress string, projectName string) Project { return Project{ Index: ProjectIndex(subscriptionAddress, projectName), Subscription: subscriptionAddress, - Description: "Permissive default project", + Description: "", ProjectKeys: []ProjectKey{}, Policy: Policy{}, UsedCu: 0, } } -func DefaultProject(subscriptionAddress string) Project { - return CreateEmptyProject(subscriptionAddress, DEFAULT_PROJECT_NAME) -} - func (project *Project) GetKey(projectKey string) ProjectKey { for _, key := range project.ProjectKeys { if key.Key == projectKey { @@ -47,6 +43,16 @@ func (projectKey *ProjectKey) AppendKeyType(typesToAdd []ProjectKey_KEY_TYPE) { } } -func (project *Project) IsKeyType(projectKey string, keyTypeToCheck ProjectKey_KEY_TYPE) bool { +func (project *Project) AppendKey(keyToAdd ProjectKey) { + for i := 0; i < len(project.ProjectKeys); i++ { + if project.ProjectKeys[i].Key == keyToAdd.Key { + project.ProjectKeys[i].AppendKeyType(keyToAdd.Types) + return + } + } + project.ProjectKeys = append(project.ProjectKeys, keyToAdd) +} + +func (project *Project) HasKeyType(projectKey string, keyTypeToCheck ProjectKey_KEY_TYPE) bool { return project.GetKey(projectKey).IsKeyType(keyTypeToCheck) }