Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implementation of MsgBuyDataAccessNFT Tx #306

Merged
merged 25 commits into from
Apr 25, 2022
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions proto/panacea/datapool/v2/genesis.proto
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,11 @@ import "panacea/datapool/v2/pool.proto";

// GenesisState defines the datapool module's genesis state.
message GenesisState {
repeated panacea.datapool.v2.DataValidator data_validators = 1;
uint64 next_pool_number = 2;
repeated Pool pools = 3;
Params params = 4 [
(gogoproto.nullable) = false
];
repeated DataValidator data_validators = 1 [(gogoproto.nullable) = false];
uint64 next_pool_number = 2;
repeated Pool pools = 3 [(gogoproto.nullable) = false];
Params params = 4 [(gogoproto.nullable) = false];
repeated WhiteList white_list = 5 [(gogoproto.nullable) = false];
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also add [ (gogoproto.nullable) = false ] to some fields.

When I export genesis.json with multiple values of those fields, all the same values (I guess the last one) are stored and exported. I referred to other projects and they all used the gogoproto extension. I'm not sure of the reason, it was a workaround for now. If you have any idea, please leave some comments.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you saying this?
expected:

{
  "whiteList": [
    {
      "data_pool_code_id" : 1,
      "data_pool_nft_contract_adress": "panaceaxxxx1"
    },
    {
      "data_pool_code_id" : 2,
      "data_pool_nft_contract_adress": "panaceaxxxx2"
    }
  ]
}

acutally

{
  "whiteList": [
    {
      "data_pool_code_id" : 2,
      "data_pool_nft_contract_adress": "panaceaxxxx2"
    }
  ]
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In your example,

{
  "whiteList": [
    {
      "data_pool_code_id" : 2,
      "data_pool_nft_contract_adress": "panaceaxxxx2"
    },
    {
      "data_pool_code_id" : 2,
      "data_pool_nft_contract_adress": "panaceaxxxx2"
    }
  ]
}

The last one was duplicated..

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's interesting...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are also differences of types in GenesisState. Without gogoproto extension, pointer is added to each type.

Without [(gogoproto.nullable) = false],

type GenesisState struct {
    DataValidators []*DataValidator `protobuf:"bytes,1,rep,name=data_validators,json=dataValidators,proto3" json:"data_validators"`
    NextPoolNumber uint64           `protobuf:"varint,2,opt,name=next_pool_number,json=nextPoolNumber,proto3" json:"next_pool_number,omitempty"`
    Pools          []*Pool          `protobuf:"bytes,3,rep,name=pools,proto3" json:"pools"`
    Params         *Params          `protobuf:"bytes,4,opt,name=params,proto3" json:"params"`
}

With [(gogoproto.nullable) = false],

type GenesisState struct {
    DataValidators []DataValidator `protobuf:"bytes,1,rep,name=data_validators,json=dataValidators,proto3" json:"data_validators"`
    NextPoolNumber uint64          `protobuf:"varint,2,opt,name=next_pool_number,json=nextPoolNumber,proto3" json:"next_pool_number,omitempty"`
    Pools          []Pool          `protobuf:"bytes,3,rep,name=pools,proto3" json:"pools"`
    Params         Params          `protobuf:"bytes,4,opt,name=params,proto3" json:"params"`
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are also differences of types in GenesisState. Without gogoproto extension, pointer is added to each type.

This behavior is intended. But still, it's weird that the list contains all the same value.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

�> Is this issue still being raised? Then, I will take a look at it.

This issue is resolved now, but I don't know what's going on.

This behavior is intended.

Oh I see. I'm curious this part as well. I'll look at gogoproto extension more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just removed [(gogoproto.nullable) = false] and checked that the exported list in the genesis contains two different elements. As I know, [(gogoproto.nullable) = false] shouldn't violate the fundamental behavior of protobuf marshalling/unmarhalling.

In protobuf, the nullability is a bit tricky to handle because everything is optional by default in protobuf 3. That's why all fields are defined with pointer such as []*DataValidator or *Params. Previously, the protobuf 2 had the required keyword which sets some field not nullable, but that feature was deleted in protobuf 3: protocolbuffers/protobuf#2497.

That's why the gogoproto, which is an extension of protobuf, introduced a new feature nullable. If you really want to make each list element not nullable, then you can use [(gogoproto.nullable) = false] so that gogoproto defines the list as []Element, not []*Element. I also agree with using it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But still, it's weird that the list contains all the same value.

I guess some Go code wasn't implemented correctly at that time.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I see. I'm very thankful to you for your �detailed explanation.

I guess some Go code wasn't implemented correctly at that time.

I think it could be, because as I remember It was exported correctly when I implement ExportGenesis() of module params in #294.

}

// Params define parameters of datapool module
Expand All @@ -25,4 +24,10 @@ message Params {
];
uint64 data_pool_code_id = 2;
string data_pool_nft_contract_address = 3;
}

// WhiteList define white list of data pool
message WhiteList {
0xHansLee marked this conversation as resolved.
Show resolved Hide resolved
0xHansLee marked this conversation as resolved.
Show resolved Hide resolved
uint64 pool_id = 1;
string address = 2;
}
10 changes: 2 additions & 8 deletions types/testsuite/suite.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import (
paramstypes "github.com/cosmos/cosmos-sdk/x/params/types"
stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
"github.com/medibloc/panacea-core/v2/types/assets"
aolkeeper "github.com/medibloc/panacea-core/v2/x/aol/keeper"
aoltypes "github.com/medibloc/panacea-core/v2/x/aol/types"
burnkeeper "github.com/medibloc/panacea-core/v2/x/burn/keeper"
Expand Down Expand Up @@ -241,13 +240,8 @@ func (suite *TestSuite) SetupTest() {
)
suite.DataPoolMsgServer = datapoolkeeper.NewMsgServerImpl(suite.DataPoolKeeper)

dataPoolGenState := datapooltypes.GenesisState{
DataValidators: []*datapooltypes.DataValidator{},
NextPoolNumber: 1,
Pools: []*datapooltypes.Pool{},
Params: datapooltypes.Params{DataPoolDeposit: sdk.NewCoin(assets.MicroMedDenom, sdk.NewInt(1000000))},
}
datapool.InitGenesis(suite.Ctx, suite.DataPoolKeeper, dataPoolGenState)
dataPoolGenState := datapooltypes.DefaultGenesis()
datapool.InitGenesis(suite.Ctx, suite.DataPoolKeeper, *dataPoolGenState)
}

func (suite *TestSuite) BeforeTest(suiteName, testName string) {
Expand Down
1 change: 1 addition & 0 deletions x/datapool/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
## Deploy and instantiate NFT smart contract
## Deploy and instantiate NFT smart contract
0xHansLee marked this conversation as resolved.
Show resolved Hide resolved

### Submit proposal (1): store NFT contract
```shell
Expand Down
2 changes: 2 additions & 0 deletions x/datapool/client/cli/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,7 @@ func GetTxCmd() *cobra.Command {
cmd.AddCommand(CmdRegisterDataValidator())
cmd.AddCommand(CmdUpdateDataValidator())
cmd.AddCommand(CmdCreatePool())
cmd.AddCommand(CmdBuyDataAccessNFT())

return cmd
}
47 changes: 47 additions & 0 deletions x/datapool/client/cli/txPool.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"io/ioutil"
"strconv"
"time"

"github.com/cosmos/cosmos-sdk/client"
Expand Down Expand Up @@ -130,3 +131,49 @@ func newCreatePoolMsg(clientCtx client.Context, file string) (sdk.Msg, error) {
msg := types.NewMsgCreatePool(poolParams, clientCtx.GetFromAddress().String())
return msg, nil
}

func CmdBuyDataAccessNFT() *cobra.Command {
cmd := &cobra.Command{
Use: "buy-data-access-nft [pool ID] [round] [payment]",
Short: "buy data access NFT",
Args: cobra.ExactArgs(3),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientTxContext(cmd)
if err != nil {
return err
}

buyer := clientCtx.GetFromAddress()

poolID, err := strconv.ParseUint(args[0], 10, 64)
if err != nil {
return err
}

round, err := strconv.ParseUint(args[1], 10, 64)
if err != nil {
return err
}

payment, err := sdk.ParseCoinNormalized(args[2])
if err != nil {
return err
}

msg := &types.MsgBuyDataAccessNFT{
PoolId: poolID,
Round: round,
Payment: &payment,
Buyer: buyer.String(),
}

if err := msg.ValidateBasic(); err != nil {
return err
}
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
},
}

flags.AddTxFlagsToCmd(cmd)
return cmd
}
27 changes: 19 additions & 8 deletions x/datapool/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,22 @@ func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState)
k.SetPoolNumber(ctx, genState.NextPoolNumber)

for _, dataValidator := range genState.DataValidators {
err := k.SetDataValidator(ctx, *dataValidator)
err := k.SetDataValidator(ctx, dataValidator)
if err != nil {
panic(err)
}
}

for _, pool := range genState.Pools {
k.SetPool(ctx, pool)
k.SetPool(ctx, &pool)
}

for _, whiteList := range genState.WhiteList {
addr, err := sdk.AccAddressFromBech32(whiteList.Address)
if err != nil {
panic(err)
}
k.AddToWhiteList(ctx, whiteList.PoolId, addr)
}
// this line is used by starport scaffolding # genesis/module/init

Expand All @@ -38,21 +46,24 @@ func ExportGenesis(ctx sdk.Context, k keeper.Keeper) *types.GenesisState {
panic(err)
}

for _, val := range dataValidators {
genesis.DataValidators = append(genesis.DataValidators, &val)
}
genesis.DataValidators = append(genesis.DataValidators, dataValidators...)

pools, err := k.GetAllPools(ctx)
if err != nil {
panic(err)
}

for _, pool := range pools {
genesis.Pools = append(genesis.Pools, &pool)
}
genesis.Pools = append(genesis.Pools, pools...)

genesis.Params = k.GetParams(ctx)

whiteLists, err := k.GetAllWhiteLists(ctx)
if err != nil {
panic(err)
}

genesis.WhiteList = append(genesis.WhiteList, whiteLists...)

// this line is used by starport scaffolding # genesis/module/export

// this line is used by starport scaffolding # ibc/genesis/export
Expand Down
60 changes: 48 additions & 12 deletions x/datapool/genesis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,11 @@ import (
var (
dataVal = sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address())
curator = sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address())
buyer = sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address())
buyer2 = sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address())
NFTPrice = sdk.NewCoin(assets.MicroMedDenom, sdk.NewInt(10000000))
downloadPeriod = time.Duration(time.Second * 100000000)
poolID = uint64(1)
)

type genesisTestSuite struct {
Expand All @@ -31,81 +34,100 @@ func TestGenesisTestSuite(t *testing.T) {
}

func (suite genesisTestSuite) TestDataPoolInitGenesis() {
var dataValidators []*types.DataValidator
var dataValidators []types.DataValidator

dataValidator := makeSampleDataValidator()

dataValidators = append(dataValidators, dataValidator)

pool := makeSamplePool()

pools := []*types.Pool{pool}
pools := []types.Pool{pool}

params := types.DefaultParams()

whiteList := makeSampleWhiteList()

genState := &types.GenesisState{
DataValidators: dataValidators,
NextPoolNumber: 2,
Pools: pools,
Params: params,
WhiteList: whiteList,
}

datapool.InitGenesis(suite.Ctx, suite.DataPoolKeeper, *genState)

// check data validator
dataValidatorFromKeeper, err := suite.DataPoolKeeper.GetDataValidator(suite.Ctx, dataVal)
suite.Require().NoError(err)
suite.Require().Equal(*dataValidator, dataValidatorFromKeeper)
suite.Require().Equal(dataValidator, dataValidatorFromKeeper)

// check the next pool number
suite.Require().Equal(uint64(2), suite.DataPoolKeeper.GetNextPoolNumber(suite.Ctx))

// check pool
poolFromKeeper, err := suite.DataPoolKeeper.GetPool(suite.Ctx, uint64(1))
suite.Require().NoError(err)
suite.Require().Equal(pool, poolFromKeeper)
suite.Require().Equal(pool, *poolFromKeeper)

// check params
paramsFromKeeper := suite.DataPoolKeeper.GetParams(suite.Ctx)
suite.Require().Equal(params, paramsFromKeeper)

// check white list
whiteListFromKeeper, err := suite.DataPoolKeeper.GetAllWhiteLists(suite.Ctx)
suite.Require().NoError(err)
suite.Require().Len(whiteListFromKeeper, 2)
}

func (suite genesisTestSuite) TestDataPoolExportGenesis() {
// register data validator
dataValidator := makeSampleDataValidator()
err := suite.DataPoolKeeper.SetDataValidator(suite.Ctx, *dataValidator)
err := suite.DataPoolKeeper.SetDataValidator(suite.Ctx, dataValidator)
suite.Require().NoError(err)

// create pool
pool := makeSamplePool()
suite.DataPoolKeeper.SetPool(suite.Ctx, pool)
suite.DataPoolKeeper.SetPool(suite.Ctx, &pool)
suite.DataPoolKeeper.SetPoolNumber(suite.Ctx, uint64(2))

// set params
suite.DataPoolKeeper.SetParams(suite.Ctx, types.DefaultParams())

// set white list
whiteList := makeSampleWhiteList()
for _, list := range whiteList {
addr, err := sdk.AccAddressFromBech32(list.Address)
suite.Require().NoError(err)
suite.DataPoolKeeper.AddToWhiteList(suite.Ctx, list.PoolId, addr)
}

genesisState := datapool.ExportGenesis(suite.Ctx, suite.DataPoolKeeper)
suite.Require().Equal(uint64(2), genesisState.NextPoolNumber)
suite.Require().Len(genesisState.Pools, 1)
suite.Require().Equal(types.DefaultParams(), genesisState.Params)
suite.Require().Len(genesisState.DataValidators, 1)
suite.Require().Len(genesisState.WhiteList, 2)
suite.Require().Contains(genesisState.WhiteList, whiteList[0])
suite.Require().Contains(genesisState.WhiteList, whiteList[1])
}

func makeSampleDataValidator() *types.DataValidator {
return &types.DataValidator{
func makeSampleDataValidator() types.DataValidator {
return types.DataValidator{
Address: dataVal.String(),
Endpoint: "https://my-validator.org",
}
}

func makeSamplePool() *types.Pool {
return &types.Pool{
PoolId: 1,
func makeSamplePool() types.Pool {
return types.Pool{
PoolId: poolID,
PoolAddress: types.NewPoolAddress(uint64(1)).String(),
Round: 1,
PoolParams: makeSamplePoolParams(),
CurNumData: 0,
NumIssuedNfts: 0,
NumIssuedNfts: 1,
Status: types.PENDING,
Curator: curator.String(),
}
Expand All @@ -122,3 +144,17 @@ func makeSamplePoolParams() *types.PoolParams {
DownloadPeriod: &downloadPeriod,
}
}

func makeSampleWhiteList() []types.WhiteList {
whiteList1 := types.WhiteList{
PoolId: poolID,
Address: buyer.String(),
}

whiteList2 := types.WhiteList{
PoolId: poolID,
Address: buyer2.String(),
}

return []types.WhiteList{whiteList1, whiteList2}
}
3 changes: 3 additions & 0 deletions x/datapool/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ func NewHandler(k keeper.Keeper) sdk.Handler {
case *types.MsgCreatePool:
res, err := msgServer.CreatePool(sdk.WrapSDKContext(ctx), msg)
return sdk.WrapServiceResult(ctx, res, err)
case *types.MsgBuyDataAccessNFT:
res, err := msgServer.BuyDataAccessNFT(sdk.WrapSDKContext(ctx), msg)
return sdk.WrapServiceResult(ctx, res, err)
default:
errMsg := fmt.Sprintf("unrecognized %s message type: %T", types.ModuleName, msg)
return nil, sdkerrors.Wrap(sdkerrors.ErrUnknownRequest, errMsg)
Expand Down
14 changes: 13 additions & 1 deletion x/datapool/keeper/msg_server_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,19 @@ func (m msgServer) SellData(goCtx context.Context, msg *types.MsgSellData) (*typ
}

func (m msgServer) BuyDataAccessNFT(goCtx context.Context, msg *types.MsgBuyDataAccessNFT) (*types.MsgBuyDataAccessNFTResponse, error) {
return &types.MsgBuyDataAccessNFTResponse{}, nil
ctx := sdk.UnwrapSDKContext(goCtx)

buyer, err := sdk.AccAddressFromBech32(msg.Buyer)
if err != nil {
return nil, err
}

err = m.Keeper.BuyDataAccessNFT(ctx, buyer, msg.PoolId, msg.Round, *msg.Payment)
if err != nil {
return nil, err
}

return &types.MsgBuyDataAccessNFTResponse{PoolId: msg.PoolId, Round: msg.Round}, nil
}

func (m msgServer) RedeemDataAccessNFT(goCtx context.Context, msg *types.MsgRedeemDataAccessNFT) (*types.MsgRedeemDataAccessNFTResponse, error) {
Expand Down
Loading