diff --git a/cmd/xrncli/main.go b/cmd/xrncli/main.go index c446aec014..5ec143fee9 100644 --- a/cmd/xrncli/main.go +++ b/cmd/xrncli/main.go @@ -29,6 +29,7 @@ import ( espclient "github.com/regen-network/regen-ledger/x/esp/client" geoclient "github.com/regen-network/regen-ledger/x/geo/client" agentclient "github.com/regen-network/regen-ledger/x/group/client" + claimclient "github.com/regen-network/regen-ledger/x/claim/client" proposalclient "github.com/regen-network/regen-ledger/x/proposal/client" upgradecli "github.com/regen-network/regen-ledger/x/upgrade/client/cli" upgraderest "github.com/regen-network/regen-ledger/x/upgrade/client/rest" @@ -37,6 +38,7 @@ import ( const ( storeAcc = "acc" + storeClaim = "claim" storeData = "data" storeAgent = "group" storeProposal = "proposal" @@ -61,6 +63,7 @@ func main() { dataclient.NewModuleClient(storeData, cdc), agentclient.NewModuleClient(storeAgent, cdc), consortiumclient.NewModuleClient(cdc), + claimclient.NewModuleClient(storeClaim, cdc), } rootCmd := &cobra.Command{ diff --git a/types/types.go b/types/types.go index 553ce662b6..5b59003370 100644 --- a/types/types.go +++ b/types/types.go @@ -80,3 +80,26 @@ func DecodeBech32DataAddress(url string) (DataAddress, error) { } return nil, fmt.Errorf("can't decode data URL") } + +// IsGraphDataAddress indicates whether the provided DataAddress points to graph +// data - which has a well-known structure conformant with the schema module - +// as opposed to "raw" data which can have any format +func IsGraphDataAddress(addr DataAddress) bool { + switch addr[0] { + case DataAddressPrefixOnChainGraph: + return true + default: + return false + } +} + +// IsRawDataAddress indicates whether the provided DataAddress points to raw +// data - i.e. data in any format - as opposed to well-structured graph data +func IsRawDataAddress(addr DataAddress) bool { + switch addr[0] { + case DataAddressPrefixOnChainGraph: + return false + default: + return false + } +} diff --git a/util/test.go b/util/test.go new file mode 100644 index 0000000000..a13f8b9275 --- /dev/null +++ b/util/test.go @@ -0,0 +1,30 @@ +package util + +import ( + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/store" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/suite" + abci "github.com/tendermint/tendermint/abci/types" + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" +) + +type TestHarness struct { + suite.Suite + Ctx sdk.Context + Cms store.CommitMultiStore + Cdc *codec.Codec + Db *dbm.MemDB + Addr1 sdk.AccAddress + Addr2 sdk.AccAddress +} + +func (s *TestHarness) Setup() { + s.Db = dbm.NewMemDB() + s.Cms = store.NewCommitMultiStore(s.Db) + s.Cdc = codec.New() + s.Ctx = sdk.NewContext(s.Cms, abci.Header{}, false, log.NewNopLogger()) + s.Addr1 = sdk.AccAddress{0, 1, 2, 3, 4, 5, 6, 7, 8} + s.Addr2 = sdk.AccAddress{1, 2, 3, 4, 5, 6, 7, 8, 9} +} diff --git a/x/claim/claim_test.go b/x/claim/claim_test.go new file mode 100644 index 0000000000..f072477b0d --- /dev/null +++ b/x/claim/claim_test.go @@ -0,0 +1,126 @@ +package claim_test + +import ( + "bytes" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/regen-network/regen-ledger/graph" + "github.com/regen-network/regen-ledger/graph/binary" + "github.com/regen-network/regen-ledger/graph/gen" + "github.com/regen-network/regen-ledger/types" + "github.com/regen-network/regen-ledger/x/claim" + "github.com/regen-network/regen-ledger/x/data" + schema_test "github.com/regen-network/regen-ledger/x/schema/test" + "github.com/stretchr/testify/suite" + "testing" +) + +type Suite struct { + schema_test.Harness + dataKeeper data.Keeper + Keeper claim.Keeper + Handler sdk.Handler +} + +func (s *Suite) SetupTest() { + s.Setup() + data.RegisterCodec(s.Cdc) + claim.RegisterCodec(s.Cdc) + dataKey := sdk.NewKVStoreKey("data") + s.dataKeeper = data.NewKeeper(dataKey, s.Harness.Keeper, s.Cdc) + claimKey := sdk.NewKVStoreKey("claim") + s.Keeper = claim.NewKeeper(claimKey, s.dataKeeper, s.Cdc) + s.Handler = claim.NewHandler(s.Keeper) + s.Cms.MountStoreWithDB(dataKey, sdk.StoreTypeIAVL, s.Db) + s.Cms.MountStoreWithDB(claimKey, sdk.StoreTypeIAVL, s.Db) + _ = s.Cms.LoadLatestVersion() + s.CreateSampleSchema() +} + +func (s *Suite) randomData() types.DataAddress { + x, ok := gen.Graph(s.Resolver).Sample() + if !ok { + panic("couldn't generate graph") + } + g := x.(graph.Graph) + buf := new(bytes.Buffer) + err := binary.SerializeGraph(s.Harness.Resolver, g, buf) + s.Require().Nil(err) + addr, err := s.dataKeeper.StoreGraph(s.Ctx, graph.Hash(g), buf.Bytes()) + s.Require().Nil(err) + return addr +} + +func (s *Suite) TestCreateClaim() { + s.T().Logf("sign a claim") + c := s.randomData() + ev0 := s.randomData() + ev1 := s.randomData() + msg := claim.MsgSignClaim{Content: c, Evidence: []types.DataAddress{ev0, ev1}, Signers: []sdk.AccAddress{s.Addr1}} + res := s.Handler(s.Ctx, msg) + s.Require().Equal(sdk.CodeOK, res.Code) + s.Require().Equal(string(res.Tags[0].Value), c.String()) + + s.T().Logf("retrieve the signatures") + sigs := s.Keeper.GetSigners(s.Ctx, c) + s.Require().True(bytes.Equal(s.Addr1, sigs[0])) + + s.T().Logf("retrieve the evidence") + ev := s.Keeper.GetEvidence(s.Ctx, c, s.Addr1) + s.requireContainsData(ev, ev0) + s.requireContainsData(ev, ev1) + + s.T().Logf("add more evidence and another signature") + ev2 := s.randomData() + err := s.Keeper.SignClaim(s.Ctx, c, []types.DataAddress{ev2}, []sdk.AccAddress{s.Addr1, s.Addr2}) + s.Require().Nil(err) + + s.T().Logf("retrieve the signatures") + sigs = s.Keeper.GetSigners(s.Ctx, c) + s.requireContainsAddr(sigs, s.Addr1) + s.requireContainsAddr(sigs, s.Addr2) + + s.T().Logf("retrieve the evidence") + ev = s.Keeper.GetEvidence(s.Ctx, c, s.Addr1) + s.requireContainsData(ev, ev0) + s.requireContainsData(ev, ev1) + s.requireContainsData(ev, ev2) + + ev = s.Keeper.GetEvidence(s.Ctx, c, s.Addr2) + s.requireContainsData(ev, ev2) +} + +func (s *Suite) TestCreateBadClaim() { + msg := claim.MsgSignClaim{Signers: []sdk.AccAddress{s.Addr1}} + err := msg.ValidateBasic() + s.Require().NotNil(err) + + msg = claim.MsgSignClaim{Content: types.GetDataAddressOnChainGraph([]byte{}), Signers: []sdk.AccAddress{s.Addr1}} + res := s.Handler(s.Ctx, msg) + s.Require().Equal(sdk.CodeUnknownRequest, res.Code) + + msg = claim.MsgSignClaim{Content: types.DataAddress([]byte{10, 2, 3, 4}), Signers: []sdk.AccAddress{s.Addr1}} + res = s.Handler(s.Ctx, msg) + s.Require().Equal(sdk.CodeUnknownRequest, res.Code) +} + +func (s *Suite) requireContainsAddr(xs []sdk.AccAddress, x sdk.AccAddress) { + for _, y := range xs { + if bytes.Equal(x, y) { + return + } + } + s.Require().FailNow("can't find address") +} + +func (s *Suite) requireContainsData(xs []types.DataAddress, x types.DataAddress) { + for _, y := range xs { + if bytes.Equal(x, y) { + return + } + } + s.Require().FailNow("can't find the data") +} + +func TestSuite(t *testing.T) { + suite.Run(t, new(Suite)) +} diff --git a/x/claim/client/cli/query.go b/x/claim/client/cli/query.go new file mode 100644 index 0000000000..0bfa4a13c3 --- /dev/null +++ b/x/claim/client/cli/query.go @@ -0,0 +1,98 @@ +package cli + +import ( + "fmt" + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/regen-network/regen-ledger/types" + "github.com/regen-network/regen-ledger/x/claim" + "github.com/spf13/cobra" + "strings" +) + +// GetSignaturesQueryCmd creates a query sub-command for the claim module using cmdName as the name of the sub-command. +func GetSignaturesQueryCmd(storeName string, cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "signatures ", + Short: "get signatures for claim", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + content, err := types.DecodeBech32DataAddress(args[0]) + if err != nil { + return err + } + + res, err := cliCtx.QueryStore(claim.KeySignatures(content), storeName) + if err != nil { + return err + } + + if len(res) == 0 { + return fmt.Errorf("no signatures for claim") + } + + var sigs []sdk.AccAddress + err = cdc.UnmarshalBinaryBare(res, &sigs) + if err != nil { + return err + } + + var signatures strings.Builder + for _, sig := range sigs { + signatures.WriteString(sig.String()) + signatures.WriteString(" ") + } + + fmt.Println(signatures) + return nil + }, + } +} + +// GetEvidenceQueryCmd creates a query sub-command for the claim module using cmdName as the name of the sub-command. +func GetEvidenceQueryCmd(storeName string, cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "evidence ", + Short: "get evidence for claim", + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + content, err := types.DecodeBech32DataAddress(args[0]) + if err != nil { + return err + } + signer, err := sdk.AccAddressFromBech32(args[1]) + if err != nil { + return err + } + + res, err := cliCtx.QueryStore(claim.KeySignatureEvidence(content, signer), storeName) + if err != nil { + return err + } + + if len(res) == 0 { + return fmt.Errorf("no evidence for claim") + } + + var evidence []types.DataAddress + err = cdc.UnmarshalBinaryBare(res, &evidence) + if err != nil { + return err + } + + var evidenceString strings.Builder + + for _, data := range evidence { + evidenceString.WriteString(data.String()) + evidenceString.WriteString(" ") + } + + fmt.Println(evidenceString) + return nil + }, + } +} + diff --git a/x/claim/client/cli/tx.go b/x/claim/client/cli/tx.go new file mode 100644 index 0000000000..63f6ff5cf5 --- /dev/null +++ b/x/claim/client/cli/tx.go @@ -0,0 +1,62 @@ +package cli + +import ( + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/client/utils" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" + "github.com/regen-network/regen-ledger/types" + "github.com/regen-network/regen-ledger/x/claim" + "github.com/spf13/cobra" +) + +// GetCmdSignClaim returns the tx claim sign command. +func GetCmdSignClaim(cdc *codec.Codec) *cobra.Command { + var evidence []string + cmd := &cobra.Command{ + Use: "sign [--evidence ] --from ", + Short: "sign a claim on the blockchain", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc).WithAccountDecoder(cdc) + + txBldr := authtxb.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc)) + + if err := cliCtx.EnsureAccountExists(); err != nil { + return err + } + + account := cliCtx.GetFromAddress() + + contentAddr, err := types.DecodeBech32DataAddress(args[0]) + if err != nil { + return err + } + + evidenceAddrs := make([]types.DataAddress, len(evidence)) + for i, bech := range evidence { + evidenceAddrs[i], err = types.DecodeBech32DataAddress(bech) + if err != nil { + return err + } + } + + msg := claim.MsgSignClaim{ + Content: contentAddr, + Signers: []sdk.AccAddress{account}, + Evidence: evidenceAddrs, + } + err = msg.ValidateBasic() + if err != nil { + return err + } + + cliCtx.PrintResponse = true + + return utils.CompleteAndBroadcastTxCLI(txBldr, cliCtx, []sdk.Msg{msg}) + }, + } + cmd.Flags().StringSliceVar(&evidence, "evidence", nil, "A comma-separated list of data addresses representing claim evidence") + return cmd +} diff --git a/x/claim/client/module_client.go b/x/claim/client/module_client.go new file mode 100644 index 0000000000..b8d0d4fbf5 --- /dev/null +++ b/x/claim/client/module_client.go @@ -0,0 +1,48 @@ +package client + +import ( + "github.com/cosmos/cosmos-sdk/client" + claimcmd "github.com/regen-network/regen-ledger/x/claim/client/cli" + "github.com/spf13/cobra" + "github.com/tendermint/go-amino" +) + +// ModuleClient exports all client functionality from this module +type ModuleClient struct { + storeKey string + cdc *amino.Codec +} + +// NewModuleClient returns a new claim client module +func NewModuleClient(storeKey string, cdc *amino.Codec) ModuleClient { + return ModuleClient{storeKey, cdc} +} + +// GetQueryCmd returns the cli query commands for this module +func (mc ModuleClient) GetQueryCmd() *cobra.Command { + queryCmd := &cobra.Command{ + Use: "claim", + Short: "Querying commands for the claim module", + } + + queryCmd.AddCommand(client.GetCommands( + claimcmd.GetSignaturesQueryCmd(mc.storeKey, mc.cdc), + claimcmd.GetEvidenceQueryCmd(mc.storeKey, mc.cdc), + )...) + + return queryCmd +} + +// GetTxCmd returns the transaction commands for this module +func (mc ModuleClient) GetTxCmd() *cobra.Command { + txCmd := &cobra.Command{ + Use: "claim", + Short: "Claim transactions subcommands", + } + + txCmd.AddCommand(client.PostCommands( + claimcmd.GetCmdSignClaim(mc.cdc), + )...) + + return txCmd +} diff --git a/x/claim/codec.go b/x/claim/codec.go new file mode 100644 index 0000000000..4a345fed39 --- /dev/null +++ b/x/claim/codec.go @@ -0,0 +1,10 @@ +package claim + +import ( + "github.com/cosmos/cosmos-sdk/codec" +) + +// RegisterCodec registers concrete types on the Amino codec +func RegisterCodec(cdc *codec.Codec) { + cdc.RegisterConcrete(MsgSignClaim{}, "claim/MsgSignClaim", nil) +} diff --git a/x/claim/handler.go b/x/claim/handler.go new file mode 100644 index 0000000000..c478ff7298 --- /dev/null +++ b/x/claim/handler.go @@ -0,0 +1,29 @@ +package claim + +import ( + "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// NewHandler returns a handler for "claim" type messages. +func NewHandler(keeper Keeper) sdk.Handler { + return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { + switch msg := msg.(type) { + case MsgSignClaim: + return handleMsgSignClaim(ctx, keeper, msg) + default: + errMsg := fmt.Sprintf("Unrecognized data Msg type: %v", msg.Type()) + return sdk.ErrUnknownRequest(errMsg).Result() + } + } +} + +func handleMsgSignClaim(ctx sdk.Context, keeper Keeper, msg MsgSignClaim) sdk.Result { + err := keeper.SignClaim(ctx, msg.Content, msg.Evidence, msg.Signers) + if err != nil { + return err.Result() + } + res := sdk.Result{} + res.Tags = res.Tags.AppendTag("claim.id", msg.Content.String()) + return res +} diff --git a/x/claim/keeper.go b/x/claim/keeper.go new file mode 100644 index 0000000000..f8c76a549c --- /dev/null +++ b/x/claim/keeper.go @@ -0,0 +1,96 @@ +package claim + +import ( + "bytes" + "fmt" + "github.com/campoy/unique" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/regen-network/regen-ledger/types" + "github.com/regen-network/regen-ledger/x/data" +) + +// Keeper is that claim module keeper +type Keeper struct { + storeKey sdk.StoreKey + dataKeeper data.Keeper + cdc *codec.Codec +} + +// NewKeeper creates a new claim Keeper +func NewKeeper(storeKey sdk.StoreKey, dataKeeper data.Keeper, cdc *codec.Codec) Keeper { + return Keeper{storeKey: storeKey, dataKeeper: dataKeeper, cdc: cdc} +} + +// KeySignatures returns a story key pointing to the signatures for the content +func KeySignatures(content types.DataAddress) []byte { + return []byte(fmt.Sprintf("%x/sigs", content)) +} + +// KeySignatureEvidence returns a story key pointing to the evidence for a given content signature +func KeySignatureEvidence(content types.DataAddress, signer sdk.AccAddress) []byte { + return []byte(fmt.Sprintf("%x/sig/%x", content, signer)) +} + +// SignClaim asserts that the signers sign the content with the provided evidence +func (keeper Keeper) SignClaim(ctx sdk.Context, content types.DataAddress, evidence []types.DataAddress, signers []sdk.AccAddress) sdk.Error { + if !types.IsGraphDataAddress(content) { + return sdk.ErrUnknownRequest("Content isn't a graph") + } + if !keeper.dataKeeper.HasData(ctx, content) { + return sdk.ErrUnknownRequest("Content data can't be found") + } + + store := ctx.KVStore(keeper.storeKey) + sigsToStore := signers + existingSigs := keeper.GetSigners(ctx, content) + if len(existingSigs) != 0 { + sigsToStore = append(sigsToStore, existingSigs...) + unique.Slice(&sigsToStore, func(i, j int) bool { + return bytes.Compare(sigsToStore[i], sigsToStore[j]) < 0 + }) + } + store.Set(KeySignatures(content), keeper.cdc.MustMarshalBinaryBare(sigsToStore)) + + for _, signer := range signers { + evidenceToStore := evidence + existingEvidence := keeper.GetEvidence(ctx, content, signer) + if len(existingEvidence) != 0 { + evidenceToStore = append(evidenceToStore, existingEvidence...) + unique.Slice(&sigsToStore, func(i, j int) bool { + return bytes.Compare(evidenceToStore[i], evidenceToStore[j]) < 0 + }) + } + store.Set(KeySignatureEvidence(content, signer), keeper.cdc.MustMarshalBinaryBare(evidenceToStore)) + } + + return nil +} + +// GetSigners gets the signers for the given content +func (keeper Keeper) GetSigners(ctx sdk.Context, content types.DataAddress) []sdk.AccAddress { + store := ctx.KVStore(keeper.storeKey) + bz := store.Get(KeySignatures(content)) + + if bz == nil { + return nil + } + + var sigs []sdk.AccAddress + keeper.cdc.MustUnmarshalBinaryBare(bz, &sigs) + return sigs +} + +// GetEvidence returns the evidence provided by the given signer for the given content +func (keeper Keeper) GetEvidence(ctx sdk.Context, content types.DataAddress, signer sdk.AccAddress) []types.DataAddress { + store := ctx.KVStore(keeper.storeKey) + bz := store.Get(KeySignatureEvidence(content, signer)) + + if bz == nil { + return nil + } + + var evidence []types.DataAddress + keeper.cdc.MustUnmarshalBinaryBare(bz, &evidence) + return evidence +} diff --git a/x/claim/msgs.go b/x/claim/msgs.go new file mode 100644 index 0000000000..48017508c0 --- /dev/null +++ b/x/claim/msgs.go @@ -0,0 +1,56 @@ +package claim + +import ( + "encoding/json" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/regen-network/regen-ledger/types" +) + +// MsgSignClaim creates a transaction which indicates that the Signers of the +// claim are stating that to the best of their knowledge, the Content of the +// claim is true, optionally linking to some Evidence that supports their +// assessment. +type MsgSignClaim struct { + // Content is the content of the claim which is being signed + // - the statement about what is being claimed as true. It must point + // to an on or off chain graph stored or tracked by the data module. + Content types.DataAddress + // Evidence is optional data which is being pointed to as evidence to the + // Content's veracity by the Signers of the Content. It can point to on or + // off chain graphs as well as raw data tracked by the data module. + Evidence []types.DataAddress + // Signers are the signers of this claim. By signing this claim they + // are asserting to the best of their knowledge the Content is true + Signers []sdk.AccAddress +} + +// Route implements Msg. +func (msg MsgSignClaim) Route() string { return "claim" } + +// Type implements Msg. +func (msg MsgSignClaim) Type() string { return "claim.sign" } + +// ValidateBasic implements Msg. +func (msg MsgSignClaim) ValidateBasic() sdk.Error { + if len(msg.Content) == 0 { + return sdk.ErrUnknownRequest("Content cannot be empty") + } + if !types.IsGraphDataAddress(msg.Content) { + return sdk.ErrUnknownRequest("Content must point to graph data") + } + return nil +} + +// GetSignBytes implements Msg. +func (msg MsgSignClaim) GetSignBytes() []byte { + b, err := json.Marshal(msg) + if err != nil { + panic(err) + } + return sdk.MustSortJSON(b) +} + +// GetSigners implements Msg. +func (msg MsgSignClaim) GetSigners() []sdk.AccAddress { + return msg.Signers +} diff --git a/x/data/codec.go b/x/data/codec.go index 919b4674d7..1ea74b50d7 100644 --- a/x/data/codec.go +++ b/x/data/codec.go @@ -6,7 +6,5 @@ import ( // RegisterCodec registers concrete types on the Amino codec func RegisterCodec(cdc *codec.Codec) { - //cdc.RegisterConcrete(MsgRegisterSchema{}, "data/RegisterSchema", nil) cdc.RegisterConcrete(MsgStoreGraph{}, "data/MsgStoreGraph", nil) - //cdc.RegisterConcrete(MsgStoreDataPointer{}, "data/MsgStoreDataPointer", nil) } diff --git a/x/data/data_test.go b/x/data/data_test.go index d4e54eadcc..474c36db82 100644 --- a/x/data/data_test.go +++ b/x/data/data_test.go @@ -3,8 +3,6 @@ package data_test import ( "bytes" "fmt" - "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/store" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/leanovate/gopter" "github.com/leanovate/gopter/prop" @@ -13,43 +11,26 @@ import ( "github.com/regen-network/regen-ledger/graph/gen" "github.com/regen-network/regen-ledger/types" "github.com/regen-network/regen-ledger/x/data" - "github.com/regen-network/regen-ledger/x/schema" schematest "github.com/regen-network/regen-ledger/x/schema/test" "github.com/stretchr/testify/suite" - abci "github.com/tendermint/tendermint/abci/types" - dbm "github.com/tendermint/tendermint/libs/db" - "github.com/tendermint/tendermint/libs/log" "testing" ) type Suite struct { - suite.Suite - SchemaKeeper schema.Keeper - Keeper data.Keeper - Handler sdk.Handler - Ctx sdk.Context - Cms store.CommitMultiStore - AnAddr sdk.AccAddress - Resolver graph.SchemaResolver + schematest.Harness + Keeper data.Keeper + Handler sdk.Handler } func (s *Suite) SetupTest() { - db := dbm.NewMemDB() - s.Cms = store.NewCommitMultiStore(db) - schemaKey := sdk.NewKVStoreKey("schema") + s.Setup() dataKey := sdk.NewKVStoreKey("data") - cdc := codec.New() - schema.RegisterCodec(cdc) - s.SchemaKeeper = schema.NewKeeper(schemaKey, cdc) - s.Keeper = data.NewKeeper(dataKey, s.SchemaKeeper, cdc) - s.Cms.MountStoreWithDB(schemaKey, sdk.StoreTypeIAVL, db) - s.Cms.MountStoreWithDB(dataKey, sdk.StoreTypeIAVL, db) - _ = s.Cms.LoadLatestVersion() - s.Ctx = sdk.NewContext(s.Cms, abci.Header{}, false, log.NewNopLogger()) - s.AnAddr = sdk.AccAddress{0, 1, 2, 3, 4, 5, 6, 7, 8} + data.RegisterCodec(s.Cdc) + s.Keeper = data.NewKeeper(dataKey, s.Harness.Keeper, s.Cdc) s.Handler = data.NewHandler(s.Keeper) - s.Resolver = schema.NewOnChainSchemaResolver(s.SchemaKeeper, s.Ctx) - schematest.CreateSampleSchema(s.Suite, s.SchemaKeeper, s.Ctx, s.AnAddr) + s.Cms.MountStoreWithDB(dataKey, sdk.StoreTypeIAVL, s.Db) + _ = s.Cms.LoadLatestVersion() + s.CreateSampleSchema() } func (s *Suite) TestStoreDataGraph() { @@ -68,7 +49,7 @@ func (s *Suite) TestStoreDataGraph() { addr := types.GetDataAddressOnChainGraph(hash) bz, err := s.Keeper.GetData(s.Ctx, addr) if bz == nil { - res := s.Handler(s.Ctx, data.MsgStoreGraph{Hash: hash, Data: buf.Bytes(), Signer: s.AnAddr}) + res := s.Handler(s.Ctx, data.MsgStoreGraph{Hash: hash, Data: buf.Bytes(), Signer: s.Addr1}) if res.Code != sdk.CodeOK { return false, fmt.Errorf("%+v", res) } @@ -80,7 +61,7 @@ func (s *Suite) TestStoreDataGraph() { } // verify can't store same graph again - res = s.Handler(s.Ctx, data.MsgStoreGraph{Hash: hash, Data: buf.Bytes(), Signer: s.AnAddr}) + res = s.Handler(s.Ctx, data.MsgStoreGraph{Hash: hash, Data: buf.Bytes(), Signer: s.Addr1}) if res.Code == sdk.CodeOK { return false, fmt.Errorf("shouldn't be able to store the same graph twice") } diff --git a/x/data/keeper.go b/x/data/keeper.go index 0202120aff..b77542110b 100644 --- a/x/data/keeper.go +++ b/x/data/keeper.go @@ -43,6 +43,12 @@ func (k Keeper) GetData(ctx sdk.Context, addr types.DataAddress) ([]byte, sdk.Er } } +// GetData indicates whether data at the specified address is stored or tracked +func (k Keeper) HasData(ctx sdk.Context, addr types.DataAddress) bool { + store := ctx.KVStore(k.dataStoreKey) + return store.Has(addr) +} + const ( gasForHashAndLookup = 100 gasPerByteStorage = 100 diff --git a/x/schema/schema_test.go b/x/schema/schema_test.go index db55fff196..0a00e5841f 100644 --- a/x/schema/schema_test.go +++ b/x/schema/schema_test.go @@ -31,7 +31,7 @@ func (s *TestSuite) TestCreatorCantBeEmpty() { func (s *TestSuite) TestNameCantBeEmpty() { s.T().Log("define property") prop1 := schema.PropertyDefinition{ - Creator: s.AnAddr, + Creator: s.Addr1, PropertyType: graph.TyBool, } _, _, err := s.Keeper.DefineProperty(s.Ctx, prop1) @@ -41,7 +41,7 @@ func (s *TestSuite) TestNameCantBeEmpty() { func (s *TestSuite) TestPropertyCanOnlyBeDefinedOnce() { s.T().Log("define property") prop1 := schema.PropertyDefinition{ - Creator: s.AnAddr, + Creator: s.Addr1, Name: "test1", PropertyType: graph.TyBool, } @@ -50,7 +50,7 @@ func (s *TestSuite) TestPropertyCanOnlyBeDefinedOnce() { s.T().Log("try to define property with same name") prop2 := schema.PropertyDefinition{ - Creator: s.AnAddr, + Creator: s.Addr1, Name: "test1", PropertyType: graph.TyInteger, } @@ -61,7 +61,7 @@ func (s *TestSuite) TestPropertyCanOnlyBeDefinedOnce() { func (s *TestSuite) TestCheckPropertyType() { s.T().Log("invalid property type should be rejected") prop1 := schema.PropertyDefinition{ - Creator: s.AnAddr, + Creator: s.Addr1, Name: "test1", PropertyType: graph.PropertyType(12345678), } @@ -74,7 +74,7 @@ func (s *TestSuite) TestCheckPropertyType() { func (s *TestSuite) TestCheckArity() { s.T().Log("invalid arity should be rejected") prop1 := schema.PropertyDefinition{ - Creator: s.AnAddr, + Creator: s.Addr1, Name: "test1", PropertyType: graph.TyObject, Arity: graph.Arity(513848), @@ -88,7 +88,7 @@ func (s *TestSuite) TestCheckArity() { func (s *TestSuite) TestCanRetrieveProperty() { s.T().Log("define property") prop := schema.PropertyDefinition{ - Creator: s.AnAddr, + Creator: s.Addr1, Name: "test1", PropertyType: graph.TyBool, } @@ -111,7 +111,7 @@ func (s *TestSuite) TestCanRetrieveProperty() { func (s *TestSuite) TestIncrementPropertyID() { s.T().Log("create one property") prop1 := schema.PropertyDefinition{ - Creator: s.AnAddr, + Creator: s.Addr1, Name: "test1", PropertyType: graph.TyBool, } @@ -121,7 +121,7 @@ func (s *TestSuite) TestIncrementPropertyID() { s.T().Log("create another property") prop2 := schema.PropertyDefinition{ - Creator: s.AnAddr, + Creator: s.Addr1, Name: "test2", PropertyType: graph.TyString, Arity: graph.UnorderedSet, @@ -142,7 +142,7 @@ func (s *TestSuite) TestPropertyNotFound() { func (s *TestSuite) TestPropertyNameRegex() { prop1 := schema.PropertyDefinition{ - Creator: s.AnAddr, + Creator: s.Addr1, Name: "TestCamelCase", PropertyType: graph.TyString, Arity: graph.OrderedSet, @@ -154,7 +154,7 @@ func (s *TestSuite) TestPropertyNameRegex() { func (s *TestSuite) TestDefinePropertyHandler() { s.T().Log("create one property") prop1 := schema.PropertyDefinition{ - Creator: s.AnAddr, + Creator: s.Addr1, Name: "test1", PropertyType: graph.TyBool, } diff --git a/x/schema/test/helpers.go b/x/schema/test/helpers.go index b06b71598a..28e9f868a1 100644 --- a/x/schema/test/helpers.go +++ b/x/schema/test/helpers.go @@ -1,44 +1,33 @@ package test import ( - "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/store" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/regen-network/regen-ledger/graph" + "github.com/regen-network/regen-ledger/util" "github.com/regen-network/regen-ledger/x/schema" "github.com/stretchr/testify/suite" - abci "github.com/tendermint/tendermint/abci/types" - dbm "github.com/tendermint/tendermint/libs/db" - "github.com/tendermint/tendermint/libs/log" ) type Harness struct { - suite.Suite + util.TestHarness Keeper schema.Keeper Handler sdk.Handler - Ctx sdk.Context - Cms store.CommitMultiStore - AnAddr sdk.AccAddress Resolver graph.SchemaResolver } func (s *Harness) Setup() { - db := dbm.NewMemDB() - s.Cms = store.NewCommitMultiStore(db) + s.TestHarness.Setup() key := sdk.NewKVStoreKey("schema") - cdc := codec.New() - schema.RegisterCodec(cdc) - s.Keeper = schema.NewKeeper(key, cdc) - s.Cms.MountStoreWithDB(key, sdk.StoreTypeIAVL, db) + schema.RegisterCodec(s.Cdc) + s.Keeper = schema.NewKeeper(key, s.Cdc) + s.Cms.MountStoreWithDB(key, sdk.StoreTypeIAVL, s.Db) _ = s.Cms.LoadLatestVersion() - s.Ctx = sdk.NewContext(s.Cms, abci.Header{}, false, log.NewNopLogger()) - s.AnAddr = sdk.AccAddress{0, 1, 2, 3, 4, 5, 6, 7, 8} s.Handler = schema.NewHandler(s.Keeper) s.Resolver = schema.NewOnChainSchemaResolver(s.Keeper, s.Ctx) } func (s *Harness) CreateSampleSchema() { - CreateSampleSchema(s.Suite, s.Keeper, s.Ctx, s.AnAddr) + CreateSampleSchema(s.Suite, s.Keeper, s.Ctx, s.Addr1) } func CreateSampleSchema(s suite.Suite, keeper schema.Keeper, ctx sdk.Context, anAddr sdk.AccAddress) {