Skip to content

Commit

Permalink
Implement Record Specification (#118)
Browse files Browse the repository at this point in the history
* [59]: Create some stuff in types/specification.go related to RecordSpecification and InputSpeciofication objects. Rename a couple existing functions in there to prevent conflict and confusion (related to Source fields).

* [59]: Fill in ValidateBasic for both RecordSpecification and InputSpecification.

* [59]: Add a MetadataAddress function for getting a contract spec address from a record spec address.

* [59]: Allow multiple responsible parties in a record spec.

* [59]: Make sure there's at least one responsible party on a record spec.

* [59]: Remove the wantErr parameter in the specification tests since it's redundant (empty want string conveys same info).

* [59]: Update migrate test due to multiple responsible parties change.

* [59]: Create unit tests for String methods on RecordSpecification and InputSpecification.

* [59]: stop outputting some actual values in the specification tests.

* [59]: Verify that the record id metadata address in the input spec source has the record prefix.

* [59]: Add unit tests for InputSpecification.ValidateBasic().

* z[59]: Add unit tests for RecordSpecification.ValidateBasic().

* [59]: Update query and tx proto to add record spec rpcs and messages.

* [59]: Remove the DeleteRecordSpecification rpc endpoint. Record spec deleting will be handled automatically during contract spec updating or deleting.

* [59]: Put the DeleteRecordSpecification endpoint back in.

* [59]: Add a name parameter to the query for record specifications (since they don't have their own uuids).

* [59]: Implement at least skeletons for the stuff recently added to the queries and transactions endpoints list.

* [59]: proto-format and regen.

* [59]: Create iterator funcs for record specs.

* [59]: Finish up RecordSpecificationsForContractSpecification in the query server.

* [59]: During the spec remove keeper methods, first check if the spec is used and short circuit as early as possible.

* [59]: Linter....

* [59]: Fix one of the tests that was set up incorrectly to falsely validate the functionality of a method that was corrected.

* [59]: Remove the record_spec_ids field from the contract specification. The specification ids for record specifications contain the uuid for the contract specification its in. Having that list along with that spec id structure was causing synchronization issues. Removing that field allows for a record spec to be added or deleted without having to update the contract spec.

* [59]: Linter.

* [59]: For the msg_server methods for specifications, pull the signing logic out of the specification keeper and into the msg_server. I feel this is a bit cleaner by making the keeper more focused on managing data in the store.

* [59]: Update unit tests with change in signer check code location.

* [59]: Move the ValidateAllOwnersAreSigners func into the main keeper.go file since it's a really generic function that could be useful in other keepers.

* [59]: In the iterators, prefer UnmarshalBinaryBare to MustUnmarshalBinaryBare since the iterators can return the error to be handled (instead of callin panic). Also call isRecordSpecUsed for all record specs in a contract spec when checking if the contract spec is used.

* [59]: Add a query for getting both the contract spec and record specs.

* [59]: Log an error when getting record specifications but one isn't found.

* [59]: Have the remove spec methods in the keeper return an error since sometimes it doesn't actually do the remove.

* [59]: Comments and formatting.

* [59]: Add some TODO notes on stuff that needs unit tests. Create a skeleton unit test file for the msg server.

* [59]: Add some TODO notes to the querier.

* [59]: Add changelog line for this ticket.

* [59]: When deleting a contract specification, first delete all its record specifications.

* [59]: Add record spec stuff to the codec.

* [59]: Add some stuff that got missed in the types/msg.go file.

* [59]: Add a record specs parameter to NewGenesisState.

* [59]: Add spec stuff to the genesis keeper.

* [59]: Write a few unit tests for the RecordSpecification stuff in the keeper. Fix the failure messages of some of the other keeper unit tests.

* [59]: write TestIterateRecordSpecsForContractSpec tests.

* [59]: Linter.

* [59]: Write TestGetRecordSpecificationsForContractSpecificationID tests.

* [59]: Write TestValidateRecordSpecUpdate tests.

* [59]: Add specification msg service calls to the handler.
  • Loading branch information
dwedul-figure authored Mar 5, 2021
1 parent cfd7d44 commit 401c6d8
Show file tree
Hide file tree
Showing 29 changed files with 5,392 additions and 1,428 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ Ref: https://keepachangelog.com/en/1.0.0/

* Truncate hashes used in metadata addresses for Record, Record Specification #132
* Add support for creating, updating, removing, finding, and iterating over `Session`s #55
* Add support for creating, updating, removing, finding, and iterating over `RecordSpecification`s #59

## [v0.1.10](https://github.com/provenance-io/provenance/releases/tag/v0.1.10) - 2021-03-04

Expand Down
57 changes: 55 additions & 2 deletions proto/provenance/metadata/v1/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,34 @@ service Query {
option (google.api.http).get = "/provenance/metadata/v1/valueownership/{address}";
}

// ScopeSpecification returns a scope specification for the given specification id
// ScopeSpecification returns a scope specification for the given specification uuid
rpc ScopeSpecification(ScopeSpecificationRequest) returns (ScopeSpecificationResponse) {
option (google.api.http).get = "/provenance/metadata/v1/scopespec/{specification_uuid}";
}

// ContractSpecification returns a contract specification for the given specification id
// ContractSpecification returns a contract specification for the given specification uuid
rpc ContractSpecification(ContractSpecificationRequest) returns (ContractSpecificationResponse) {
option (google.api.http).get = "/provenance/metadata/v1/contractspec/{specification_uuid}";
}

// ContractSpecification returns a contract specification and record specifications for the given contract
// specification uuid
rpc ContractSpecificationExtended(ContractSpecificationExtendedRequest)
returns (ContractSpecificationExtendedResponse) {
option (google.api.http).get = "/provenance/metadata/v1/contractspec/{specification_uuid}/extended";
}

// RecordSpecificationsForContractSpecification returns the record specifications for the given contract specification
// uuid
rpc RecordSpecificationsForContractSpecification(RecordSpecificationsForContractSpecificationRequest)
returns (RecordSpecificationsForContractSpecificationResponse) {
option (google.api.http).get = "/provenance/metadata/v1/contractspec/{contract_specification_uuid}/recordspecs";
}

// RecordSpecification returns a record specification for the given specification uuid
rpc RecordSpecification(RecordSpecificationRequest) returns (RecordSpecificationResponse) {
option (google.api.http).get = "/provenance/metadata/v1/recordspec/{contract_specification_uuid}/{name}";
}
}

// QueryParamsRequest is the request type for the Query/Params RPC method.
Expand Down Expand Up @@ -195,3 +214,37 @@ message ContractSpecificationRequest {
message ContractSpecificationResponse {
ContractSpecification contract_specification = 1 [(gogoproto.moretags) = "yaml:\"contract_specification\""];
}

// ContractSpecificationExtendedRequest is used for requesting a contract specification with extended data by contract
// specification uuid
message ContractSpecificationExtendedRequest {
string specification_uuid = 1 [(gogoproto.moretags) = "yaml:\"specification_uuid\""];
}

// ContractSpecificationExtendedResponse is the response to a contract specification extended request.
message ContractSpecificationExtendedResponse {
ContractSpecification contract_specification = 1 [(gogoproto.moretags) = "yaml:\"contract_specification\""];
repeated RecordSpecification record_specifications = 2 [(gogoproto.moretags) = "yaml:\"record_specifications\""];
}

// RecordSpecificationsForContractSpecificationRequest is used for requesting record specifications by contract
// specification uuid
message RecordSpecificationsForContractSpecificationRequest {
string contract_specification_uuid = 1 [(gogoproto.moretags) = "yaml:\"contract_specification_uuid\""];
}

// RecordSpecificationResponseResponse is the response to a record specification for contract specification request.
message RecordSpecificationsForContractSpecificationResponse {
repeated RecordSpecification record_specifications = 1 [(gogoproto.moretags) = "yaml:\"record_specifications\""];
}

// RecordSpecificationRequest is used for requesting a record specification by uuid
message RecordSpecificationRequest {
string contract_specification_uuid = 1 [(gogoproto.moretags) = "yaml:\"contract_specification_uuid\""];
string name = 2;
}

// RecordSpecificationResponse is the response to a record specification request.
message RecordSpecificationResponse {
RecordSpecification record_specification = 1 [(gogoproto.moretags) = "yaml:\"record_specification\""];
}
13 changes: 5 additions & 8 deletions proto/provenance/metadata/v1/specification.proto
Original file line number Diff line number Diff line change
Expand Up @@ -83,17 +83,12 @@ message ContractSpecification {
}
// name of the class/type of this contract executable
string class_name = 7 [(gogoproto.moretags) = "yaml:\"class_name\""];
// a collection of ids of checks (RecordSpecifications) that must be satisfied against a scope prior to
// allowing a record to be added under this specification
repeated bytes record_spec_ids = 8 [
(gogoproto.nullable) = false,
(gogoproto.customtype) = "MetadataAddress",
(gogoproto.moretags) = "yaml:\"record_spec_ids\""
];
}

// RecordSpecification defines the specification for a Record including allowed/required inputs/outputs
message RecordSpecification {
option (gogoproto.goproto_stringer) = false;

// unique identifier for this specification on chain
bytes specification_id = 1 [
(gogoproto.nullable) = false,
Expand All @@ -109,12 +104,14 @@ message RecordSpecification {
// Type of result for this record specification (must be RECORD or RECORD_LIST)
DefinitionType result_type = 5 [(gogoproto.moretags) = "yaml:\"result_type\""];
// Type of party responsible for this record
PartyType responsible_party = 6 [(gogoproto.moretags) = "yaml:\"responsible_party\""];
repeated PartyType responsible_parties = 6 [(gogoproto.moretags) = "yaml:\"responsible_parties\""];
}

// InputSpecification defines a name, type_name, and source reference (either on or off chain) to define an input
// parameter
message InputSpecification {
option (gogoproto.goproto_stringer) = false;

// name for this input
string name = 1;
// a type_name (typically a proto name or class_name)
Expand Down
42 changes: 40 additions & 2 deletions proto/provenance/metadata/v1/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ service Msg {
// DeleteContractSpecification deletes a contract specification
rpc DeleteContractSpecification(MsgDeleteContractSpecificationRequest)
returns (MsgDeleteContractSpecificationResponse);

// AddRecordSpecification adds a record specification
rpc AddRecordSpecification(MsgAddRecordSpecificationRequest) returns (MsgAddRecordSpecificationResponse);
// DeleteRecordSpecification deletes a record specification
rpc DeleteRecordSpecification(MsgDeleteRecordSpecificationRequest) returns (MsgDeleteRecordSpecificationResponse);
}

// MsgMemorializeContractRequest is a request from a P8e execution environment to record results of a contract
Expand Down Expand Up @@ -201,7 +206,7 @@ message MsgDeleteScopeSpecificationRequest {
option (gogoproto.stringer) = false;
option (gogoproto.goproto_getters) = false;

// Unique ID for the scope specification to delete.
// MetadataAddress for the scope specification to delete.
bytes specification_id = 1 [
(gogoproto.nullable) = false,
(gogoproto.customtype) = "MetadataAddress",
Expand Down Expand Up @@ -234,7 +239,7 @@ message MsgDeleteContractSpecificationRequest {
option (gogoproto.stringer) = false;
option (gogoproto.goproto_getters) = false;

// Unique ID for the scope specification to delete.
// MetadataAddress for the contract specification to delete.
bytes specification_id = 1 [
(gogoproto.nullable) = false,
(gogoproto.customtype) = "MetadataAddress",
Expand All @@ -245,3 +250,36 @@ message MsgDeleteContractSpecificationRequest {

// MsgDeleteContractSpecificationResponse from a delete contract specification request
message MsgDeleteContractSpecificationResponse {}

// MsgAddRecordSpecificationRequest is a request to add a record specification
message MsgAddRecordSpecificationRequest {
option (gogoproto.equal) = false;
option (gogoproto.goproto_stringer) = false;
option (gogoproto.stringer) = false;
option (gogoproto.goproto_getters) = false;

RecordSpecification specification = 1 [(gogoproto.nullable) = false];
repeated string signers = 2;
}

// MsgAddRecordSpecificationResponse from an add record specification request
message MsgAddRecordSpecificationResponse {}

// MsgDeleteRecordSpecificationRequest deletes a record specification
message MsgDeleteRecordSpecificationRequest {
option (gogoproto.equal) = false;
option (gogoproto.goproto_stringer) = false;
option (gogoproto.stringer) = false;
option (gogoproto.goproto_getters) = false;

// MetadataAddress for the record specification to delete.
bytes specification_id = 1 [
(gogoproto.nullable) = false,
(gogoproto.customtype) = "MetadataAddress",
(gogoproto.moretags) = "yaml:\"specification_id\""
];
repeated string signers = 2;
}

// MsgDeleteRecordSpecificationResponse from a delete record specification request
message MsgDeleteRecordSpecificationResponse {}
20 changes: 20 additions & 0 deletions x/metadata/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,26 @@ func NewHandler(k keeper.Keeper) sdk.Handler {
case *types.MsgAddSessionRequest:
res, err := msgServer.AddSession(sdk.WrapSDKContext(ctx), msg)
return sdk.WrapServiceResult(ctx, res, err)

case *types.MsgAddScopeSpecificationRequest:
res, err := msgServer.AddScopeSpecification(sdk.WrapSDKContext(ctx), msg)
return sdk.WrapServiceResult(ctx, res, err)
case *types.MsgDeleteScopeSpecificationRequest:
res, err := msgServer.DeleteScopeSpecification(sdk.WrapSDKContext(ctx), msg)
return sdk.WrapServiceResult(ctx, res, err)
case *types.MsgAddContractSpecificationRequest:
res, err := msgServer.AddContractSpecification(sdk.WrapSDKContext(ctx), msg)
return sdk.WrapServiceResult(ctx, res, err)
case *types.MsgDeleteContractSpecificationRequest:
res, err := msgServer.DeleteContractSpecification(sdk.WrapSDKContext(ctx), msg)
return sdk.WrapServiceResult(ctx, res, err)
case *types.MsgAddRecordSpecificationRequest:
res, err := msgServer.AddRecordSpecification(sdk.WrapSDKContext(ctx), msg)
return sdk.WrapServiceResult(ctx, res, err)
case *types.MsgDeleteRecordSpecificationRequest:
res, err := msgServer.DeleteRecordSpecification(sdk.WrapSDKContext(ctx), msg)
return sdk.WrapServiceResult(ctx, res, err)

default:
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unknown message type: %v", msg.Type())
}
Expand Down
36 changes: 33 additions & 3 deletions x/metadata/keeper/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,11 @@ func (k Keeper) InitGenesis(ctx sdk.Context, data *types.GenesisState) {
k.SetContractSpecification(ctx, s)
}
}
// TODO: data.RecordSpecifications
if data.RecordSpecifications != nil {
for _, s := range data.RecordSpecifications {
k.SetRecordSpecification(ctx, s)
}
}
}

// ExportGenesis exports the current keeper state of the metadata module.ExportGenesis
Expand All @@ -45,6 +49,9 @@ func (k Keeper) ExportGenesis(ctx sdk.Context) (data *types.GenesisState) {
scopes := make([]types.Scope, 0)
sessions := make([]types.Session, 0)
records := make([]types.Record, 0)
scopeSpecs := make([]types.ScopeSpecification, 0)
contractSpecs := make([]types.ContractSpecification, 0)
recordSpecs := make([]types.RecordSpecification, 0)

appendToScopes := func(scope types.Scope) bool {
scopes = append(scopes, scope)
Expand All @@ -61,6 +68,21 @@ func (k Keeper) ExportGenesis(ctx sdk.Context) (data *types.GenesisState) {
return false
}

appendToScopeSpecs := func(scopeSpec types.ScopeSpecification) bool {
scopeSpecs = append(scopeSpecs, scopeSpec)
return false
}

appendToContractSpecs := func(contractSpec types.ContractSpecification) bool {
contractSpecs = append(contractSpecs, contractSpec)
return false
}

appendToRecordSpecs := func(recordSpec types.RecordSpecification) bool {
recordSpecs = append(recordSpecs, recordSpec)
return false
}

if err := k.IterateScopes(ctx, appendToScopes); err != nil {
panic(err)
}
Expand All @@ -70,7 +92,15 @@ func (k Keeper) ExportGenesis(ctx sdk.Context) (data *types.GenesisState) {
if err := k.IterateRecords(ctx, types.MetadataAddress{}, appendToRecords); err != nil {
panic(err)
}
// TODO iterate over existing scope, group specifications and collect here for export
if err := k.IterateScopeSpecs(ctx, appendToScopeSpecs); err != nil {
panic(err)
}
if err := k.IterateContractSpecs(ctx, appendToContractSpecs); err != nil {
panic(err)
}
if err := k.IterateRecordSpecs(ctx, appendToRecordSpecs); err != nil {
panic(err)
}

return types.NewGenesisState(params, scopes, sessions, records, []types.ScopeSpecification{}, []types.ContractSpecification{})
return types.NewGenesisState(params, scopes, sessions, records, scopeSpecs, contractSpecs, recordSpecs)
}
27 changes: 20 additions & 7 deletions x/metadata/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,14 @@ package keeper
import (
"fmt"

"github.com/tendermint/tendermint/libs/log"

"github.com/cosmos/cosmos-sdk/codec"

"github.com/provenance-io/provenance/x/metadata/types"

cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
sdk "github.com/cosmos/cosmos-sdk/types"
authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
paramtypes "github.com/cosmos/cosmos-sdk/x/params/types"
"github.com/provenance-io/provenance/x/metadata/types"
"github.com/tendermint/tendermint/libs/log"
)

// MetadataKeeperI is the internal state api for the metadata module.
Expand Down Expand Up @@ -52,7 +49,7 @@ type MetadataKeeperI interface {
// SetScopeSpecification persists the provided scope specification
SetScopeSpecification(sdk.Context, types.ScopeSpecification)
// RemoveScopeSpecification removes a scope specification from the module kv store.
RemoveScopeSpecification(sdk.Context, types.MetadataAddress)
RemoveScopeSpecification(sdk.Context, types.MetadataAddress) error

// IterateScopeSpecs processes all scope specs using a given handler.
IterateScopeSpecs(ctx sdk.Context, handler func(specification types.ScopeSpecification) (stop bool)) error
Expand All @@ -66,12 +63,28 @@ type MetadataKeeperI interface {
// SetContractSpecification persists the provided contract specification
SetContractSpecification(sdk.Context, types.ContractSpecification)
// RemoveContractSpecification removes a contract specification from the module kv store.
RemoveContractSpecification(sdk.Context, types.MetadataAddress)
RemoveContractSpecification(sdk.Context, types.MetadataAddress) error

// IterateContractSpecs processes all contract specs using the given handler.
IterateContractSpecs(ctx sdk.Context, handler func(specification types.ContractSpecification) (stop bool)) error
// IterateContractSpecsForOwner processes all contract specs owned by an address using a given handler.
IterateContractSpecsForOwner(ctx sdk.Context, ownerAddress sdk.AccAddress, handler func(contractSpecID types.MetadataAddress) (stop bool)) error

// GetRecordSpecification returns the record specification with the given address.
GetRecordSpecification(sdk.Context, types.MetadataAddress) (types.RecordSpecification, bool)
// SetRecordSpecification persists the provided record specification
SetRecordSpecification(sdk.Context, types.RecordSpecification)
// RemoveRecordSpecification removes a record specification from the module kv store.
RemoveRecordSpecification(sdk.Context, types.MetadataAddress) error

// IterateRecordSpecs processes all record specs using a given handler.
IterateRecordSpecs(ctx sdk.Context, handler func(specification types.RecordSpecification) (stop bool)) error
// IterateRecordSpecsForOwner processes all record specs owned by an address using a given handler.
IterateRecordSpecsForOwner(ctx sdk.Context, ownerAddress sdk.AccAddress, handler func(recordSpecID types.MetadataAddress) (stop bool)) error
// IterateRecordSpecsForContractSpec processes all record specs for a contract spec using a given handler.
IterateRecordSpecsForContractSpec(ctx sdk.Context, contractSpecID types.MetadataAddress, handler func(recordSpecID types.MetadataAddress) (stop bool)) error
// GetRecordSpecificationsForContractSpecificationID returns all the record specifications associated with given contractSpecID
GetRecordSpecificationsForContractSpecificationID(ctx sdk.Context, contractSpecID types.MetadataAddress) ([]*types.RecordSpecification, error)
}

// Keeper is the concrete state-based API for the metadata module.
Expand Down
10 changes: 5 additions & 5 deletions x/metadata/keeper/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@ func (s *KeeperTestSuite) SetupTest() {
s.queryClient = queryClient
}

func TestKeeperTestSuite(t *testing.T) {
suite.Run(t, new(KeeperTestSuite))
}

func (s *KeeperTestSuite) TestMetadataScopeGetSet() {
scope, found := s.app.MetadataKeeper.GetScope(s.ctx, s.scopeID)
s.NotNil(scope)
Expand Down Expand Up @@ -558,7 +562,7 @@ func (s *KeeperTestSuite) TestMetadataValidateSessionUpdate() {
invalidNameSession := types.NewSession("invalid", s.sessionId, s.contractSpecId, parties, types.AuditFields{})

partiesInvolved := []types.PartyType{types.PartyType_PARTY_TYPE_AFFILIATE}
contractSpec := types.NewContractSpecification(s.contractSpecId, types.NewDescription("name", "desc", "url", "icon"), []string{s.user1}, partiesInvolved, &types.ContractSpecification_Hash{"hash"}, "processname", []types.MetadataAddress{})
contractSpec := types.NewContractSpecification(s.contractSpecId, types.NewDescription("name", "desc", "url", "icon"), []string{s.user1}, partiesInvolved, &types.ContractSpecification_Hash{"hash"}, "processname")
s.app.MetadataKeeper.SetContractSpecification(s.ctx, *contractSpec)

cases := map[string]struct {
Expand Down Expand Up @@ -828,7 +832,3 @@ func (s *KeeperTestSuite) TestValidateAllOwnersAreSigners() {
})
}
}

func TestKeeperTestSuite(t *testing.T) {
suite.Run(t, new(KeeperTestSuite))
}
Loading

0 comments on commit 401c6d8

Please sign in to comment.