Integrate go-nitro

- Add nitro server and x/nitro module
    - wire go-nitro p2p through cometbft

- Add distsig server, currently WIP
    - integrate DKG and DSS schemes into ABCI methods

- Remove deprecated features
    - crisis module
    - module invariants

- Update to use newer SDK patterns
    - upgrade sdk to v0.53.x
    - custom address codec
    - expand use of depinject
    - migrate e2e tests to system tests
    - use depinject to set up integration tests
    - change reserved protobuf field name `cerc.registry.v1.Record.type`

- Revise & add documentation
    - TransferCoinsToModuleAccount: clarify function

- Update init.sh script
This commit is contained in:
roysc committed 2025-09-21 11:44:44 +08:00
1 parent a8ebc0aceb
commit 5aa2594073
179 files changed
+15631 -3058

No files matched your search

@@ -3,7 +3,6 @@ package keeper_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
integrationTest "git.vdb.to/cerc-io/laconicd/tests/integration"
@@ -19,7 +18,7 @@ type KeeperTestSuite struct {
func (kts *KeeperTestSuite) SetupTest() {
err := kts.TestFixture.Setup()
assert.Nil(kts.T(), err)
kts.Require().NoError(err)
qr := kts.App.QueryHelper()
kts.queryClient = types.NewQueryClient(qr)
@@ -30,7 +30,7 @@ func (kts *KeeperTestSuite) TestGrpcQueryParams() {
for _, test := range testCases {
kts.Run(fmt.Sprintf("Case %s", test.msg), func() {
resp, err := kts.queryClient.Params(context.Background(), test.req)
kts.Require().Nil(err)
kts.Require().NoError(err)
kts.Require().Equal(*(resp.Params), types.DefaultParams())
})
}
@@ -59,14 +59,14 @@ func (kts *KeeperTestSuite) TestGrpcGetAuction() {
var expectedAuction types.Auction
if test.createAuction {
auction, _, err := kts.createAuctionAndCommitBid(false)
kts.Require().Nil(err)
kts.Require().NoError(err)
test.req.Id = auction.Id
expectedAuction = *auction
}
resp, err := kts.queryClient.GetAuction(context.Background(), test.req)
if test.createAuction {
kts.Require().Nil(err)
kts.Require().NoError(err)
kts.Require().NotNil(resp.GetAuction())
kts.Require().EqualExportedValues(expectedAuction, *(resp.GetAuction()))
} else {
@@ -103,7 +103,7 @@ func (kts *KeeperTestSuite) TestGrpcGetAllAuctions() {
kts.Run(fmt.Sprintf("Case %s", test.msg), func() {
if test.createAuctions {
_, _, err := kts.createAuctionAndCommitBid(false)
kts.Require().Nil(err)
kts.Require().NoError(err)
}
resp, _ := kts.queryClient.Auctions(context.Background(), test.req)
@@ -153,7 +153,7 @@ func (kts *KeeperTestSuite) TestGrpcGetBids() {
resp, err := kts.queryClient.GetBids(context.Background(), test.req)
if test.createAuction {
kts.Require().Nil(err)
kts.Require().NoError(err)
kts.Require().Equal(test.bidCount, len(resp.GetBids()))
} else {
kts.Require().NotNil(err)
+1 -2
View File
@@ -3,7 +3,6 @@ package keeper_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
integrationTest "git.vdb.to/cerc-io/laconicd/tests/integration"
@@ -19,7 +18,7 @@ type KeeperTestSuite struct {
func (kts *KeeperTestSuite) SetupTest() {
err := kts.TestFixture.Setup()
assert.Nil(kts.T(), err)
kts.Require().NoError(err)
qr := kts.App.QueryHelper()
kts.queryClient = types.NewQueryClient(qr)
@@ -26,7 +26,7 @@ func (kts *KeeperTestSuite) TestGrpcQueryParams() {
for _, test := range testCases {
kts.Run(fmt.Sprintf("Case %s", test.msg), func() {
resp, err := kts.queryClient.Params(context.Background(), test.req)
kts.Require().Nil(err)
kts.Require().NoError(err)
kts.Require().Equal(*(resp.Params), types.DefaultParams())
})
}
@@ -101,7 +101,7 @@ func (kts *KeeperTestSuite) TestGrpcQueryBondByBondId() {
}
resp, err := kts.queryClient.GetBondById(context.Background(), test.req)
if !test.errResponse {
kts.Require().Nil(err)
kts.Require().NoError(err)
kts.Require().NotNil(resp.GetBond())
kts.Require().Equal(test.req.Id, resp.GetBond().GetId())
} else {
@@ -148,7 +148,7 @@ func (kts *KeeperTestSuite) TestGrpcGetBondsByOwner() {
}
resp, err := kts.queryClient.GetBondsByOwner(context.Background(), test.req)
if !test.errResponse {
kts.Require().Nil(err)
kts.Require().NoError(err)
kts.Require().NotNil(resp.GetBonds())
kts.Require().Equal(test.noOfBonds, len(resp.GetBonds()))
} else {
@@ -184,7 +184,7 @@ func (kts *KeeperTestSuite) TestGrpcGetModuleBalance() {
}
resp, err := kts.queryClient.GetBondModuleBalance(context.Background(), test.req)
if !test.errResponse {
kts.Require().Nil(err)
kts.Require().NoError(err)
kts.Require().NotNil(resp.GetBalance())
kts.Require().Equal(resp.GetBalance(), sdk.NewCoins(sdk.NewCoin(params.CoinUnit, math.NewInt(10))))
} else {
+152 -98
View File
@@ -3,17 +3,20 @@ package integration_test
import (
"context"
runtimev1alpha1 "cosmossdk.io/api/cosmos/app/runtime/v1alpha1"
appv1alpha1 "cosmossdk.io/api/cosmos/app/v1alpha1"
authmodulev1 "cosmossdk.io/api/cosmos/auth/module/v1"
bankmodulev1 "cosmossdk.io/api/cosmos/bank/module/v1"
"cosmossdk.io/core/appconfig"
"cosmossdk.io/core/appmodule"
"cosmossdk.io/depinject"
"cosmossdk.io/log"
storetypes "cosmossdk.io/store/types"
cmtprototypes "github.com/cometbft/cometbft/proto/tendermint/types"
"github.com/cosmos/cosmos-sdk/codec"
addresscodec "github.com/cosmos/cosmos-sdk/codec/address"
"github.com/cosmos/cosmos-sdk/runtime"
"github.com/cosmos/cosmos-sdk/testutil/integration"
sdk "github.com/cosmos/cosmos-sdk/types"
moduletestutil "github.com/cosmos/cosmos-sdk/types/module/testutil"
"github.com/cosmos/cosmos-sdk/x/auth"
authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper"
authsims "github.com/cosmos/cosmos-sdk/x/auth/simulation"
@@ -21,16 +24,22 @@ import (
"github.com/cosmos/cosmos-sdk/x/bank"
bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
minttypes "github.com/cosmos/cosmos-sdk/x/mint/types"
auctionmodulev1 "git.vdb.to/cerc-io/laconicd/api/cerc/auction/module/v1"
bondmodulev1 "git.vdb.to/cerc-io/laconicd/api/cerc/bond/module/v1"
registrymodulev1 "git.vdb.to/cerc-io/laconicd/api/cerc/registry/module/v1"
"git.vdb.to/cerc-io/laconicd/app/params"
auctionTypes "git.vdb.to/cerc-io/laconicd/x/auction"
"git.vdb.to/cerc-io/laconicd/server"
"git.vdb.to/cerc-io/laconicd/utils"
auctiontypes "git.vdb.to/cerc-io/laconicd/x/auction"
auctionkeeper "git.vdb.to/cerc-io/laconicd/x/auction/keeper"
auctionmodule "git.vdb.to/cerc-io/laconicd/x/auction/module"
bondTypes "git.vdb.to/cerc-io/laconicd/x/bond"
bondtypes "git.vdb.to/cerc-io/laconicd/x/bond"
bondkeeper "git.vdb.to/cerc-io/laconicd/x/bond/keeper"
bondmodule "git.vdb.to/cerc-io/laconicd/x/bond/module"
registryTypes "git.vdb.to/cerc-io/laconicd/x/registry"
registrytypes "git.vdb.to/cerc-io/laconicd/x/registry"
registrykeeper "git.vdb.to/cerc-io/laconicd/x/registry/keeper"
registrymodule "git.vdb.to/cerc-io/laconicd/x/registry/module"
)
@@ -51,108 +60,136 @@ type TestFixture struct {
}
func (tf *TestFixture) Setup() error {
keys := storetypes.NewKVStoreKeys(
authtypes.StoreKey, banktypes.StoreKey, auctionTypes.StoreKey, bondTypes.StoreKey, registryTypes.StoreKey,
logger := log.NewNopLogger()
authority := authtypes.NewModuleAddress(govtypes.ModuleName)
moduleAccPerms := []*authmodulev1.ModuleAccountPermission{
{Account: minttypes.ModuleName, Permissions: []string{authtypes.Minter}},
{Account: auctiontypes.ModuleName},
{Account: auctiontypes.AuctionBurnModuleAccountName},
{Account: bondtypes.ModuleName},
{Account: registrytypes.ModuleName},
{Account: registrytypes.RecordRentModuleAccountName},
{Account: registrytypes.AuthorityRentModuleAccountName},
}
moduleConfigs := []*appv1alpha1.ModuleConfig{
{
Name: runtime.ModuleName,
Config: appconfig.WrapAny(&runtimev1alpha1.Module{
AppName: "TestApp",
BeginBlockers: []string{
authtypes.ModuleName,
banktypes.ModuleName,
auctiontypes.ModuleName,
bondtypes.ModuleName,
registrytypes.ModuleName,
},
EndBlockers: []string{
auctiontypes.ModuleName,
bondtypes.ModuleName,
registrytypes.ModuleName,
banktypes.ModuleName,
authtypes.ModuleName,
},
InitGenesis: []string{
authtypes.ModuleName,
banktypes.ModuleName,
auctiontypes.ModuleName,
bondtypes.ModuleName,
registrytypes.ModuleName,
},
}),
},
{
Name: authtypes.ModuleName,
Config: appconfig.WrapAny(&authmodulev1.Module{
Bech32Prefix: params.Bech32PrefixAccAddr,
ModuleAccountPermissions: moduleAccPerms,
EnableUnorderedTransactions: true,
}),
},
{
Name: banktypes.ModuleName,
Config: appconfig.WrapAny(&bankmodulev1.Module{}),
},
{
Name: auctiontypes.ModuleName,
Config: appconfig.WrapAny(&auctionmodulev1.Module{}),
},
{
Name: bondtypes.ModuleName,
Config: appconfig.WrapAny(&bondmodulev1.Module{}),
},
{
Name: registrytypes.ModuleName,
Config: appconfig.WrapAny(&registrymodulev1.Module{}),
},
}
storeKeys := StoreKeyExtractor{}
appConfig := depinject.Configs(
appconfig.Compose(&appv1alpha1.Config{
Modules: moduleConfigs,
}),
depinject.Supply(
logger,
authority,
utils.NewAddressCodec(),
),
depinject.Provide(
server.NewGasService,
),
storeKeys.config(),
)
cdc := moduletestutil.MakeTestEncodingConfig(
auth.AppModuleBasic{},
auctionmodule.AppModule{},
bondmodule.AppModule{},
registrymodule.AppModule{},
).Codec
logger := log.NewNopLogger() // Use log.NewTestLogger(kts.T()) for help with debugging
cms := integration.CreateMultiStore(keys, logger)
var (
appBuilder *runtime.AppBuilder
cdc codec.Codec
accountKeeper authkeeper.AccountKeeper
bankKeeper bankkeeper.Keeper
auctionKeeper *auctionkeeper.Keeper
bondKeeper *bondkeeper.Keeper
registryKeeper registrykeeper.Keeper
)
if err := depinject.Inject(
appConfig,
&appBuilder,
&cdc,
&accountKeeper,
&bankKeeper,
&auctionKeeper,
&bondKeeper,
&registryKeeper,
); err != nil {
return err
}
cms := integration.CreateMultiStore(storeKeys, logger)
newCtx := sdk.NewContext(cms, cmtprototypes.Header{}, true, logger)
authority := authtypes.NewModuleAddress("gov")
maccPerms := map[string][]string{
minttypes.ModuleName: {authtypes.Minter},
auctionTypes.ModuleName: {},
auctionTypes.AuctionBurnModuleAccountName: {},
bondTypes.ModuleName: {},
registryTypes.ModuleName: {},
registryTypes.RecordRentModuleAccountName: {},
registryTypes.AuthorityRentModuleAccountName: {},
modules := map[string]appmodule.AppModule{
authtypes.ModuleName: auth.NewAppModule(cdc, accountKeeper, authsims.RandomGenesisAccounts, nil),
banktypes.ModuleName: bank.NewAppModule(cdc, bankKeeper, accountKeeper, nil),
auctiontypes.ModuleName: auctionmodule.NewAppModule(cdc, auctionKeeper),
bondtypes.ModuleName: bondmodule.NewAppModule(cdc, bondKeeper),
registrytypes.ModuleName: registrymodule.NewAppModule(cdc, registryKeeper),
}
accountKeeper := authkeeper.NewAccountKeeper(
cdc,
runtime.NewKVStoreService(keys[authtypes.StoreKey]),
authtypes.ProtoBaseAccount,
maccPerms,
addresscodec.NewBech32Codec(params.Bech32PrefixAccAddr),
params.Bech32PrefixAccAddr,
authority.String(),
)
blockedAddresses := map[string]bool{
accountKeeper.GetAuthority(): false,
}
bankKeeper := bankkeeper.NewBaseKeeper(
cdc,
runtime.NewKVStoreService(keys[banktypes.StoreKey]),
accountKeeper,
blockedAddresses,
authority.String(),
log.NewNopLogger(),
)
auctionKeeper := auctionkeeper.NewKeeper(cdc, runtime.NewKVStoreService(keys[auctionTypes.StoreKey]), accountKeeper, bankKeeper, authority.String())
bondKeeper := bondkeeper.NewKeeper(cdc, runtime.NewKVStoreService(keys[bondTypes.StoreKey]), accountKeeper, bankKeeper, authority.String())
registryKeeper := registrykeeper.NewKeeper(
cdc,
runtime.NewKVStoreService(keys[registryTypes.StoreKey]),
accountKeeper,
bankKeeper,
bondKeeper,
auctionKeeper,
authority.String(),
)
authModule := auth.NewAppModule(cdc, accountKeeper, authsims.RandomGenesisAccounts, nil)
bankModule := bank.NewAppModule(cdc, bankKeeper, accountKeeper, nil)
auctionModule := auctionmodule.NewAppModule(cdc, auctionKeeper)
bondModule := bondmodule.NewAppModule(cdc, bondKeeper)
registryModule := registrymodule.NewAppModule(cdc, registryKeeper)
integrationApp := integration.NewIntegrationApp(newCtx, logger, keys, cdc, map[string]appmodule.AppModule{
authtypes.ModuleName: authModule,
banktypes.ModuleName: bankModule,
auctionTypes.ModuleName: auctionModule,
bondTypes.ModuleName: bondModule,
registryTypes.ModuleName: registryModule,
})
integrationApp := integration.NewIntegrationApp(newCtx, logger, storeKeys, cdc, modules)
sdkCtx := sdk.UnwrapSDKContext(integrationApp.Context())
// Register MsgServer and QueryServer
auctionTypes.RegisterMsgServer(integrationApp.MsgServiceRouter(), auctionkeeper.NewMsgServerImpl(auctionKeeper))
auctionTypes.RegisterQueryServer(integrationApp.QueryHelper(), auctionkeeper.NewQueryServerImpl(auctionKeeper))
auctiontypes.RegisterMsgServer(integrationApp.MsgServiceRouter(), auctionkeeper.NewMsgServerImpl(auctionKeeper))
auctiontypes.RegisterQueryServer(integrationApp.QueryHelper(), auctionkeeper.NewQueryServerImpl(auctionKeeper))
bondtypes.RegisterMsgServer(integrationApp.MsgServiceRouter(), bondkeeper.NewMsgServerImpl(bondKeeper))
bondtypes.RegisterQueryServer(integrationApp.QueryHelper(), bondkeeper.NewQueryServerImpl(bondKeeper))
registrytypes.RegisterMsgServer(integrationApp.MsgServiceRouter(), registrykeeper.NewMsgServerImpl(registryKeeper))
registrytypes.RegisterQueryServer(integrationApp.QueryHelper(), registrykeeper.NewQueryServerImpl(registryKeeper))
bondTypes.RegisterMsgServer(integrationApp.MsgServiceRouter(), bondkeeper.NewMsgServerImpl(bondKeeper))
bondTypes.RegisterQueryServer(integrationApp.QueryHelper(), bondkeeper.NewQueryServerImpl(bondKeeper))
auctionKeeper.Params.Set(sdkCtx, auctiontypes.DefaultParams())
bondKeeper.Params.Set(sdkCtx, bondtypes.DefaultParams())
registryKeeper.Params.Set(sdkCtx, registrytypes.DefaultParams())
registryTypes.RegisterMsgServer(integrationApp.MsgServiceRouter(), registrykeeper.NewMsgServerImpl(registryKeeper))
registryTypes.RegisterQueryServer(integrationApp.QueryHelper(), registrykeeper.NewQueryServerImpl(registryKeeper))
// set default params
if err := auctionKeeper.Params.Set(sdkCtx, auctionTypes.DefaultParams()); err != nil {
return err
}
if err := bondKeeper.Params.Set(sdkCtx, bondTypes.DefaultParams()); err != nil {
return err
}
if err := registryKeeper.Params.Set(sdkCtx, registryTypes.DefaultParams()); err != nil {
return err
}
tf.App = integrationApp
tf.SdkCtx, tf.cdc, tf.keys = sdkCtx, cdc, keys
tf.App, tf.SdkCtx, tf.cdc, tf.keys = integrationApp, sdkCtx, cdc, storeKeys
tf.AccountKeeper, tf.BankKeeper = accountKeeper, bankKeeper
tf.AuctionKeeper, tf.BondKeeper, tf.RegistryKeeper = auctionKeeper, bondKeeper, registryKeeper
@@ -164,3 +201,20 @@ type BondDenomProvider struct{}
func (bdp BondDenomProvider) BondDenom(ctx context.Context) (string, error) {
return params.CoinUnit, nil
}
type StoreKeyExtractor map[string]*storetypes.KVStoreKey
func (ske *StoreKeyExtractor) ExtractKey(mkey depinject.ModuleKey, storeKey *storetypes.KVStoreKey) {
(*ske)[mkey.Name()] = storeKey
}
func (ske *StoreKeyExtractor) config() depinject.Config {
return depinject.Configs(
depinject.Supply(ske),
depinject.InvokeInModule(banktypes.ModuleName, (*StoreKeyExtractor).ExtractKey),
depinject.InvokeInModule(authtypes.ModuleName, (*StoreKeyExtractor).ExtractKey),
depinject.InvokeInModule(bondtypes.ModuleName, (*StoreKeyExtractor).ExtractKey),
depinject.InvokeInModule(auctiontypes.ModuleName, (*StoreKeyExtractor).ExtractKey),
depinject.InvokeInModule(registrytypes.ModuleName, (*StoreKeyExtractor).ExtractKey),
)
}
@@ -6,7 +6,6 @@ import (
"cosmossdk.io/math"
simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
"git.vdb.to/cerc-io/laconicd/app/params"
@@ -27,18 +26,18 @@ type KeeperTestSuite struct {
func (kts *KeeperTestSuite) SetupTest() {
err := kts.TestFixture.Setup()
assert.Nil(kts.T(), err)
kts.Require().NoError(err)
// set default params
err = kts.RegistryKeeper.Params.Set(kts.SdkCtx, types.DefaultParams())
assert.Nil(kts.T(), err)
kts.Require().NoError(err)
qr := kts.App.QueryHelper()
kts.queryClient = types.NewQueryClient(qr)
// Create a bond
bond, err := kts.createBond()
assert.Nil(kts.T(), err)
kts.Require().NoError(err)
kts.bond = *bond
}
+260
View File
@@ -0,0 +1,260 @@
package system
import (
"fmt"
"github.com/cosmos/cosmos-sdk/testutil"
"github.com/tidwall/gjson"
"git.vdb.to/cerc-io/laconicd/x/auction"
)
const (
randomAuctionId = "randomAuctionId"
randomBidderAddress = "randomBidderAddress"
randomOwnerAddress = "randomOwnerAddress"
)
func (s *auctionSuite) TestGRPCQueryParams() {
reqURL := s.endpointURL("auction/v1/params")
s.Run("valid request to get auction params", func() {
require := s.Require()
resp, err := testutil.GetRequest(reqURL)
require.NoError(err)
var params auction.QueryParamsResponse
require.NoError(s.codec.UnmarshalJSON(resp, &params), string(resp))
require.Equal(auction.DefaultParams(), *params.GetParams())
})
}
func (s *auctionSuite) TestGRPCGetAllAuctions() {
reqURL := s.endpointURL("auction/v1/auctions")
s.createAuction()
testCases := []struct {
name string
url string
errmsg string
}{
{
"invalid request",
reqURL + "-asdasd",
"Not Implemented",
},
{
"valid request",
reqURL,
"",
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
require := s.Require()
resp, err := testutil.GetRequest(tc.url)
require.NoError(err)
if tc.errmsg != "" {
message := gjson.Get(string(resp), "message").String()
require.Contains(message, tc.errmsg, tc.url)
} else {
var auctions auction.QueryAuctionsResponse
require.NoError(s.codec.UnmarshalJSON(resp, &auctions), string(resp))
require.NotEmpty(auctions.Auctions.Auctions)
}
})
}
}
func (s *auctionSuite) TestGRPCGetAuction() {
reqURL := s.endpointURL("auction/v1/auctions/%s")
auctionId := s.createAuction()
s.createBid(auctionId)
testCases := []struct {
name string
url string
exists bool
}{
{
"nonexistent auction",
fmt.Sprintf(reqURL, randomAuctionId),
false,
},
{
"valid request",
fmt.Sprintf(reqURL, auctionId),
true,
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
require := s.Require()
resp, err := testutil.GetRequest(tc.url)
require.NoError(err)
if tc.exists {
var auction auction.QueryGetAuctionResponse
require.NoError(s.codec.UnmarshalJSON(resp, &auction), string(resp))
require.Equal(auctionId, auction.Auction.Id)
} else {
message := gjson.Get(string(resp), "message").String()
require.Contains(message, "not found", tc.url)
}
})
}
}
func (s *auctionSuite) TestGRPCGetBids() {
reqURL := s.endpointURL("auction/v1/bids/%s")
auctionId := s.createAuction()
s.createBid(auctionId)
testCases := []struct {
name string
url string
exists bool
}{
{
"nonexistent auction",
fmt.Sprintf(reqURL, randomAuctionId),
false,
},
{
"valid request",
fmt.Sprintf(reqURL, auctionId),
true,
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
require := s.Require()
resp, err := testutil.GetRequest(tc.url)
require.NoError(err)
var bids auction.QueryGetBidsResponse
require.NoError(s.codec.UnmarshalJSON(resp, &bids), string(resp))
if tc.exists {
require.Equal(auctionId, bids.Bids[0].AuctionId)
} else {
require.Empty(bids.Bids)
}
})
}
}
func (s *auctionSuite) TestGRPCGetBid() {
reqURL := s.endpointURL("auction/v1/bids/%s/%s")
bidderAddress := s.cli().GetKeyAddr(s.bidderAccount)
auctionId := s.createAuction()
s.createBid(auctionId)
testCases := []struct {
name string
url string
exists bool
}{
{
"nonexistent auction",
fmt.Sprintf(reqURL, randomAuctionId, bidderAddress),
false,
},
{
"nonexistent bidder",
fmt.Sprintf(reqURL, auctionId, randomBidderAddress),
false,
},
{
"valid request",
fmt.Sprintf(reqURL, auctionId, bidderAddress),
true,
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
require := s.Require()
resp, err := testutil.GetRequest(tc.url)
require.NoError(err)
if tc.exists {
var bid auction.QueryGetBidResponse
require.NoError(s.codec.UnmarshalJSON(resp, &bid), string(resp))
require.Equal(auctionId, bid.Bid.AuctionId)
} else {
message := gjson.Get(string(resp), "message").String()
require.Contains(message, "not found", tc.url)
}
})
}
}
func (s *auctionSuite) TestGRPCGetAuctionsByOwner() {
reqURL := s.endpointURL("auction/v1/by-owner/%s")
ownerAddress := s.cli().GetKeyAddr(s.ownerAccount)
auctionId := s.createAuction()
s.createBid(auctionId)
testCases := []struct {
name string
url string
exists bool
}{
{
"nonexistent owner",
fmt.Sprintf(reqURL, randomOwnerAddress),
false,
},
{
"valid request",
fmt.Sprintf(reqURL, ownerAddress),
true,
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
require := s.Require()
resp, err := testutil.GetRequest(tc.url)
require.NoError(err)
var auctions auction.QueryAuctionsResponse
require.NoError(s.codec.UnmarshalJSON(resp, &auctions), string(resp))
if tc.exists {
require.Equal(auctionId, auctions.Auctions.Auctions[0].Id)
} else {
require.Empty(auctions.Auctions.Auctions)
}
})
}
}
func (s *auctionSuite) TestGRPCQueryBalance() {
reqURL := s.endpointURL("auction/v1/balance")
s.Run("valid request", func() {
require := s.Require()
auctionId := s.createAuction()
s.createBid(auctionId)
resp, err := testutil.GetRequest(reqURL)
require.NoError(err)
var response auction.QueryGetAuctionModuleBalanceResponse
require.NoError(s.codec.UnmarshalJSON(resp, &response), string(resp))
require.NotEmpty(response.GetBalance())
})
}
+161
View File
@@ -0,0 +1,161 @@
package system
import (
"fmt"
"os"
"path/filepath"
"testing"
"git.vdb.to/cerc-io/laconicd/x/auction"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/stretchr/testify/suite"
)
const (
sampleCommitTime = "90s"
sampleRevealTime = "5s"
placeholderAuctionId = "placeholder_auction_id"
)
type auctionSuite struct {
testSuite
ownerAccount string
bidderAccount string
defaultAuctionId string
}
func TestAuction(t *testing.T) {
suite.Run(t, new(auctionSuite))
}
func (s *auctionSuite) SetupTest() {
s.resetChain()
s.ownerAccount = s.account(0)
s.bidderAccount = s.account(1)
Sut.StartChain(s.T())
}
func (s *auctionSuite) TearDownTest() {
s.cleanupBidFiles()
}
func (s *auctionSuite) TestQueryList() {
sr := s.Require()
testCases := []struct {
name string
createAuction bool
}{
{
"when no auctions exist",
false,
},
{
"after creating an auction",
true,
},
}
for _, test := range testCases {
s.Run(test.name, func() {
if test.createAuction {
s.createAuction()
}
out := s.cli().CustomQuery("q", "auction", "list")
var response auction.QueryAuctionsResponse
sr.NoError(s.codec.UnmarshalJSON([]byte(out), &response), out)
if test.createAuction {
sr.NotEmpty(response.Auctions.Auctions)
}
})
}
}
func (s *auctionSuite) TestTxCommitBid() {
auctionId := s.createAuction()
testCases := []struct {
name string
args []string
err bool
}{
{
"with missing args",
[]string{auctionId},
true,
},
{
"with zero bid",
[]string{auctionId, ("0" + s.bondDenom)},
true,
},
{
"with invalid auction",
[]string{"fake", ("200" + s.bondDenom)},
true,
},
{
"with valid args",
[]string{auctionId, ("200" + s.bondDenom)},
false,
},
}
for _, test := range testCases {
s.Run(test.name, func() {
cmd := []string{
"tx", "auction", "commit-bid",
}
cmd = append(cmd, test.args...)
s.runTx(cmd, test.err)
})
}
}
func (s *auctionSuite) createAuction() string {
require := s.Require()
cmd := []string{
"tx", "auction", "create",
sampleCommitTime,
sampleRevealTime,
fmt.Sprintf("10%s", s.bondDenom),
fmt.Sprintf("10%s", s.bondDenom),
"--kind", auction.AuctionKindVickrey,
"--minimum-bid", fmt.Sprintf("100%s", s.bondDenom),
"--max-price", fmt.Sprintf("0%s", s.bondDenom),
"--num-providers", "0",
}
s.runTx(cmd, false)
out := s.cli().CustomQuery("q", "auction", "list")
var response auction.QueryAuctionsResponse
require.NoError(s.codec.UnmarshalJSON([]byte(out), &response), out)
return response.Auctions.Auctions[0].Id
}
func (s *auctionSuite) createBid(auctionId string) {
cmd := []string{
"tx", "auction", "commit-bid",
auctionId, fmt.Sprintf("200%s", s.bondDenom),
fmt.Sprintf("--%s=%s", flags.FlagFrom, s.bidderAccount),
}
s.runTx(cmd, false)
}
func (s *auctionSuite) cleanupBidFiles() {
matches, err := filepath.Glob(fmt.Sprintf("%s-*.json", s.bidderAccount))
if err != nil {
s.T().Errorf("Error matching bidder files: %v\n", err)
}
for _, match := range matches {
err := os.Remove(match)
if err != nil {
s.T().Errorf("Error removing bidder file: %v\n", err)
}
}
}
+168
View File
@@ -0,0 +1,168 @@
package system
import (
"fmt"
"github.com/stretchr/testify/require"
"github.com/tidwall/gjson"
"github.com/cosmos/cosmos-sdk/testutil"
"git.vdb.to/cerc-io/laconicd/x/bond"
)
func (s *bondSuite) TestGRPCGetParams() {
reqURL := s.endpointURL("bond/v1/params")
var params bond.QueryParamsResponse
resp, err := testutil.GetRequest(reqURL)
require.NoError(s.T(), err)
require.NoError(s.T(), s.codec.UnmarshalJSON(resp, &params))
require.Equal(s.T(), bond.DefaultParams().MaxBondAmount, params.GetParams().MaxBondAmount)
}
func (s *bondSuite) TestGRPCGetBond() {
reqURL := s.endpointURL("bond/v1/bonds")
testcases := []struct {
name string
url string
errmsg string
prerun func()
}{
{
"invalid request with headers",
reqURL + "asdasdas",
"Not Implemented",
func() {},
},
{
"valid request",
reqURL,
"",
func() { s.createBond() },
},
}
for _, tc := range testcases {
s.Run(tc.name, func() {
require := s.Require()
tc.prerun()
resp, err := testutil.GetRequest(tc.url)
require.NoError(err)
if tc.errmsg != "" {
message := gjson.Get(string(resp), "message").String()
require.Contains(message, tc.errmsg, string(resp))
} else {
var bonds bond.QueryBondsResponse
require.NoError(s.codec.UnmarshalJSON(resp, &bonds), string(resp))
require.NotEmpty(bonds.GetBonds())
}
})
}
}
func (s *bondSuite) TestGRPCGetBondsByOwner() {
reqURL := s.endpointURL("bond/v1/by-owner/%s")
accountAddress := s.cli().GetKeyAddr(s.account(0))
testcases := []struct {
name string
url string
err bool
prerun func()
}{
{
"empty list",
fmt.Sprintf(reqURL, "asdasd"),
true,
func() {},
},
{
"valid request",
fmt.Sprintf(reqURL, accountAddress),
false,
func() { s.createBond() },
},
}
for _, tc := range testcases {
s.Run(tc.name, func() {
require := s.Require()
tc.prerun()
resp, err := testutil.GetRequest(tc.url)
require.NoError(err)
var bonds bond.QueryGetBondsByOwnerResponse
require.NoError(s.codec.UnmarshalJSON(resp, &bonds))
if tc.err {
require.Empty(bonds.GetBonds())
} else {
bondsList := bonds.GetBonds()
require.NotEmpty(bondsList)
require.Equal(accountAddress, bondsList[0].GetOwner())
}
})
}
}
func (s *bondSuite) TestGRPCGetBondById() {
reqURL := s.endpointURL("bond/v1/bonds/%s")
testcases := []struct {
name string
url string
err bool
prerun func() string
}{
{
"invalid request",
reqURL,
true,
func() string { return "asdadad" },
},
{
"valid request",
reqURL,
false,
func() string { return s.createBond() },
},
}
for _, tc := range testcases {
s.Run(tc.name, func() {
require := s.Require()
bondId := tc.prerun()
tc.url = fmt.Sprintf(reqURL, bondId)
var bond bond.QueryGetBondByIdResponse
resp, err := testutil.GetRequest(tc.url)
require.NoError(err)
if tc.err {
require.Error(s.codec.UnmarshalJSON(resp, &bond))
} else {
require.NoError(s.codec.UnmarshalJSON(resp, &bond))
require.Equal(bondId, bond.GetBond().GetId())
}
})
}
}
func (s *bondSuite) TestGRPCGetBondModuleBalance() {
reqURL := s.endpointURL("bond/v1/balance")
s.createBond()
require := s.Require()
resp, err := testutil.GetRequest(reqURL)
require.NoError(err)
var response bond.QueryGetBondModuleBalanceResponse
require.NoError(s.codec.UnmarshalJSON(resp, &response))
require.False(response.GetBalance().IsZero())
}
+97
View File
@@ -0,0 +1,97 @@
package system
import (
"encoding/json"
"fmt"
"testing"
"git.vdb.to/cerc-io/laconicd/x/bond"
"github.com/stretchr/testify/suite"
"github.com/tidwall/gjson"
)
type bondSuite struct {
testSuite
}
func TestBonds(t *testing.T) {
suite.Run(t, new(bondSuite))
}
func (s *bondSuite) SetupTest() {
s.resetChain()
Sut.StartChain(s.T())
}
func (s *bondSuite) TestQueryList() {
testCases := []struct {
name string
args []string
prerun func()
}{
{
"create and get bond lists",
nil,
func() { s.createBond() },
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
tc.prerun()
out := s.cli().CustomQuery("q", "bond", "list")
s.Require().NotEmpty(gjson.Get(out, "bonds").Array(), out)
})
}
}
func (s *bondSuite) TestTxCreateBond() {
testCases := []struct {
name string
args []string
err bool
}{
{
"without deposit",
[]string{},
true,
},
{
"create bond",
[]string{
fmt.Sprintf("10%s", s.bondDenom),
},
false,
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
cmd := append([]string{"tx", "bond", "create"}, tc.args...)
s.runTx(cmd, tc.err)
})
}
}
func (s *bondSuite) createBond() string {
return createBond(&s.testSuite)
}
// free function for use in other suites
func createBond(s *testSuite) string {
require := s.Require()
cli := s.cli()
cmd := []string{
"tx", "bond", "create", ("1000000" + s.bondDenom),
}
s.runTx(cmd, false)
raw := cli.CustomQuery("q", "bond", "list")
var queryResponse bond.QueryBondsResponse
require.NoError(json.Unmarshal([]byte(raw), &queryResponse))
bonds := queryResponse.GetBonds()
require.NotEmpty(bonds)
return bonds[0].GetId()
}
+55
View File
@@ -0,0 +1,55 @@
package system
import (
"flag"
"os"
"testing"
"time"
"cosmossdk.io/systemtests"
)
var (
Sut *systemtests.SystemUnderTest
verbose bool
nodesCount = flag.Int("nodes", 4, "number of nodes in the cluster")
blockTime = 3 * time.Second // expected time between blocks
// txTimeout = 10 * time.Second // timeout for transactions to be included in a block
txTimeout = 10 * time.Minute // timeout for transactions to be included in a block
workDir string
)
func TestMain(m *testing.M) {
// sdk systemtests expects this to be set for v2 server/runtime
os.Setenv("COSMOS_BUILD_OPTIONS", "v2")
flag.BoolVar(&verbose, "verbose", false, "verbose output")
flag.Parse()
dir, err := os.Getwd()
if err != nil {
panic(err)
}
workDir = dir
if verbose {
println("Work dir: ", workDir)
}
Sut = systemtests.NewSystemUnderTest("laconicd", verbose, *nodesCount, blockTime)
// Sut.SetTestnetInitializer(initer)
Sut.SetupChain() // setup chain and keyring
// run tests
exitCode := m.Run()
// postprocess
Sut.StopChain()
if verbose || exitCode != 0 {
Sut.PrintBuffer()
}
os.Exit(exitCode)
}
+452
View File
@@ -0,0 +1,452 @@
package system
import (
"encoding/json"
"math/big"
"os"
"os/signal"
"syscall"
"testing"
nitrocrypto "github.com/statechannels/go-nitro/crypto"
chainutils "github.com/statechannels/go-nitro/node/chain"
"github.com/statechannels/go-nitro/node/query"
"github.com/statechannels/go-nitro/node_test"
nitrotypes "github.com/statechannels/go-nitro/types"
"github.com/stretchr/testify/suite"
"github.com/tidwall/gjson"
systest "cosmossdk.io/systemtests"
)
var (
ethChainPort = "8546"
ethChainUrl = "ws://127.0.0.1:8546"
)
type nitroSuite struct {
testSuite
tokenDenom string
ethChainOpts *node_test.LocalChainOptions
alice, bob nitrocrypto.Credential
intermediaries []nitrocrypto.Credential
}
func TestNitro(t *testing.T) {
suite.Run(t, new(nitroSuite))
}
func (s *nitroSuite) SetupSuite() {
s.testSuite.SetupSuite()
s.tokenDenom = "eth"
// Set up Ethereum test chain
// TODO: use anvil for better self-containment
ethChainOpts := node_test.ExternalHardhatChainOptions(s.T())
ethChainOpts.ChainUrl = ethChainUrl
aliceKey := ethChainOpts.ChainPks[0]
bobKey := ethChainOpts.ChainPks[1]
s.ethChainOpts = ethChainOpts
ethChain, err := node_test.ConnectLocalChain(ethChainOpts, nitrotypes.BytesFromHex(aliceKey))
s.Require().NoError(err)
s.T().Cleanup(func() { s.NoError(ethChain.Close()) })
s.alice = nitrocrypto.NewSimpleCredential(nitrotypes.BytesFromHex(aliceKey))
s.bob = nitrocrypto.NewSimpleCredential(nitrotypes.BytesFromHex(bobKey))
for _, pk := range ethChainOpts.ChainPks {
creds := nitrocrypto.NewSimpleCredential(nitrotypes.BytesFromHex(pk))
s.intermediaries = append(s.intermediaries, creds)
}
s.configureEthChain()
// TODO when using intermediaries, set more validator node pks here
s.setValidatorNitroKeys(s.ethChainOpts.ChainPks[1])
}
func (s *nitroSuite) SetupTest() {
s.resetChain()
s.setClientKeys(s.ethChainOpts.ChainPks[0])
Sut.StartChain(s.T())
}
func (s *nitroSuite) SetupSubTest() { s.SetupTest() }
func (s *nitroSuite) configureEthChain() {
cas, err := chainutils.LoadEnvContractAddresses()
s.Require().NoError(err)
cmds := [][]string{
{"config", "set", "app", "nitro.eth-na-address", "'" + cas.NaAddress.String() + "'"},
{"config", "set", "app", "nitro.eth-vpa-address", "'" + cas.VpaAddress.String() + "'"},
{"config", "set", "app", "nitro.eth-ca-address", "'" + cas.CaAddress.String() + "'"},
{"config", "set", "app", "nitro.eth-url", ethChainUrl},
}
_ = Sut.ForEachNodeExecAndWait(s.T(), cmds...)
for _, cmd := range cmds {
cmd = append(cmd, "--home", s.clientHome())
_ = systest.MustRunShellCmd(s.T(), Sut.ExecBinary(), cmd...)
}
}
func (s *nitroSuite) TestTxOpenChannel() {
testCases := []struct {
name string
args []string
err bool
}{
{
"missing args",
[]string{},
true,
},
{
"missing amount",
[]string{s.bob.AsParticipant().String()},
true,
},
{
"valid request",
[]string{s.bob.AsParticipant().String(), "10" + s.tokenDenom},
false,
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
cmd := append([]string{"tx", "nitro", "open-channel"}, tc.args...)
s.runTx(cmd, tc.err)
})
}
}
func (s *nitroSuite) TestTxCloseChannel() {
testCases := []struct {
name string
args []string
err bool
setup func() []string
}{
{
"missing args",
[]string{},
true,
nil,
},
{
"invalid channel",
[]string{"invalid-channel-id"},
true,
nil,
},
{
"valid channel",
nil,
false,
func() []string { return s.setupLedgerChannel("10") },
},
// {
// "valid channel with challenge",
// []string{"--challenge"},
// false,
// func() []string { return s.setupLedgerChannel("10") },
// },
}
for _, tc := range testCases {
s.Run(tc.name, func() {
cmd := []string{"tx", "nitro", "close-channel"}
if tc.setup != nil {
cmd = append(cmd, tc.setup()...)
}
cmd = append(cmd, tc.args...)
s.runTx(cmd, tc.err)
})
}
}
func (s *nitroSuite) TestTxOpenPaymentChannel() {
testCases := []struct {
name string
args []string
err bool
setup func() []string
}{
{
"missing args",
[]string{},
true,
nil,
},
{
"missing amount",
[]string{s.bob.AsParticipant().String()},
true,
nil,
},
{
"zero hops",
[]string{s.bob.AsParticipant().String(), "10" + s.tokenDenom},
false,
func() []string { s.setupLedgerChannel("20"); return nil },
},
// { // TODO open n-hop ledger channels
// "one hop",
// []string{"10" + s.tokenDenom, s.bob.AsParticipant().String(), s.intermediaries[0].String()},
// false,
// setupLedgerChannelForPaymentChannel,
// },
// { // TODO use a different counterparty, cannot have duplicate channels
// "direct channel with challenge duration",
// []string{"10" + s.tokenDenom, s.bob.AsParticipant().String(), "--challenge-duration", "100"},
// false,
// setupLedgerChannelForPaymentChannel,
// },
}
for _, tc := range testCases {
s.Run(tc.name, func() {
cmd := []string{"tx", "nitro", "open-payment-channel"}
if tc.setup != nil {
tc.setup()
}
cmd = append(cmd, tc.args...)
s.runTx(cmd, tc.err)
})
}
}
func (s *nitroSuite) TestTxClosePaymentChannel() {
testCases := []struct {
name string
args []string
err bool
setup func() []string
}{
{
"missing args",
[]string{},
true,
nil,
},
// { // TODO use different counterparty
// "invalid channel ID",
// []string{"invalid-channel-id"},
// true,
// setupLedgerChannel,
// },
{
"zero hops",
nil,
false,
func() []string {
s.setupLedgerChannel("20")
return s.setupPaymentChannel("10")
},
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
cmd := []string{"tx", "nitro", "close-payment-channel"}
if tc.setup != nil {
cmd = append(cmd, tc.setup()...)
}
cmd = append(cmd, tc.args...)
s.runTx(cmd, tc.err)
})
}
}
func (s *nitroSuite) TestTxPay() {
testCases := []struct {
name string
args []string
error bool
setup func() []string
paid *big.Int
}{
{
name: "missing args",
error: true,
},
{
name: "invalid channel ID",
args: []string{"invalid-channel-id", "10" + s.tokenDenom},
error: true,
},
{
name: "invalid amount format",
args: []string{"0x0102030000000000000000000000000000000000000000000000000000000000", "invalid-amount"},
error: true,
},
{
name: "nonexistent channel",
args: []string{"0x0102030000000000000000000000000000000000000000000000000000000000", "5" + s.tokenDenom},
error: true,
},
{
name: "valid payment",
args: []string{"10" + s.tokenDenom},
setup: func() []string {
s.setupLedgerChannel("100")
return s.setupPaymentChannel("50")
},
paid: big.NewInt(10),
},
// {
// "insufficient funds",
// []string{"10" + s.tokenDenom},
// false,
// func() []string {
// s.setupLedgerChannel("100")
// return s.setupPaymentChannel("50")
// },
// },
}
for _, tc := range testCases {
s.Run(tc.name, func() {
var channelId string
cmd := []string{"tx", "nitro", "pay"}
if tc.setup != nil {
setupArgs := tc.setup()
cmd = append(cmd, setupArgs...)
if !tc.error && len(setupArgs) > 0 {
channelId = setupArgs[0]
}
}
cmd = append(cmd, tc.args...)
s.runTx(cmd, tc.error)
// Check balance change for successful payments
if channelId != "" {
balance := s.getChannelBalance(channelId)
s.Require().Equal(tc.paid, balance.PaidSoFar.ToInt())
}
})
}
}
func (s *nitroSuite) TestFullPaymentFlow() {
s.Run("complete payment lifecycle", func() {
// Open ledger and payment channels
ledgerChannel := s.setupLedgerChannel("100")[0]
paymentChannel := s.setupPaymentChannel("50")[0]
// Verify initial balance
s.Require().Equal("0", s.getChannelBalance(paymentChannel).PaidSoFar.ToInt().String())
// Make payments: 5 + 10 + 15 = 30
s.runTx([]string{"tx", "nitro", "pay", paymentChannel, "5" + s.tokenDenom}, false)
s.runTx([]string{"tx", "nitro", "pay", paymentChannel, "10" + s.tokenDenom}, false)
s.runTx([]string{"tx", "nitro", "pay", paymentChannel, "15" + s.tokenDenom}, false)
// Verify final balance
finalBalance := s.getChannelBalance(paymentChannel)
s.Require().Equal("30", finalBalance.PaidSoFar.ToInt().String())
s.Require().Equal("20", finalBalance.RemainingFunds.ToInt().String())
// Close channels in correct order
s.runTx([]string{"tx", "nitro", "close-payment-channel", paymentChannel}, false)
s.runTx([]string{"tx", "nitro", "close-channel", ledgerChannel}, false)
})
s.Run("insufficient funds scenario", func() {
s.setupLedgerChannel("20")
paymentChannel := s.setupPaymentChannel("10")[0]
s.runTx([]string{"tx", "nitro", "pay", paymentChannel, "5" + s.tokenDenom}, false)
s.runTx([]string{"tx", "nitro", "pay", paymentChannel, "10" + s.tokenDenom}, true) // insufficient funds
s.runTx([]string{"tx", "nitro", "pay", paymentChannel, "5" + s.tokenDenom}, false)
// Verify all funds used
balance := s.getChannelBalance(paymentChannel)
s.Require().Equal("10", balance.PaidSoFar.ToInt().String())
s.Require().Equal("0", balance.RemainingFunds.ToInt().String())
})
s.Run("invalid closure order", func() {
// Open both channels
ledgerChannel := s.setupLedgerChannel("50")[0]
paymentChannel := s.setupPaymentChannel("30")[0]
// Make a payment
s.runTx([]string{"tx", "nitro", "pay", paymentChannel, "10" + s.tokenDenom}, false)
// Try to close ledger channel before payment channel - should fail
s.runTx([]string{"tx", "nitro", "close-channel", ledgerChannel}, true)
// Correct order should work
s.runTx([]string{"tx", "nitro", "close-payment-channel", paymentChannel}, false)
s.runTx([]string{"tx", "nitro", "close-channel", ledgerChannel}, false)
})
}
// setupLedgerChannel creates a ledger channel and returns the channel ID as args
func (s *nitroSuite) setupLedgerChannel(amount string) []string {
openCmd := []string{"tx", "nitro", "open-channel", s.bob.AsParticipant().String(), amount + s.tokenDenom}
txhash := s.runTx(openCmd, false)
return []string{s.getChannelIdFromTx(txhash)}
}
// setupPaymentChannel creates a ledger channel and payment channel, returns payment channel ID as args
func (s *nitroSuite) setupPaymentChannel(amount string) []string {
openPcCmd := []string{"tx", "nitro", "open-payment-channel", s.bob.AsParticipant().String(), amount + s.tokenDenom}
txhash := s.runTx(openPcCmd, false)
return []string{s.getChannelIdFromTx(txhash)}
}
// getChannelIdFromTx opens a channel and extracts the channel ID from the transaction result
func (s *nitroSuite) getChannelIdFromTx(txHash string) string {
txDetails := s.cli().CustomQuery("query", "tx", txHash)
// find channel_id in any event's attributes
channelId := gjson.Get(txDetails, "events.#.attributes.#(key==\"channel_id\").value|0")
s.Require().True(channelId.Exists(), "channel_id not found in transaction events")
return channelId.String()
}
func (s *nitroSuite) getChannelBalance(channelId string) query.PaymentChannelBalance {
channelInfo := s.cli().CustomQuery(
s.withNitroQueryFlags("q", "nitro", "get-channel", "--payment", channelId)...,
)
var info query.PaymentChannelInfo
err := json.Unmarshal([]byte(channelInfo), &info)
s.Require().NoError(err, "could not parse PaymentChannelInfo: %s", channelInfo)
return info.Balance
}
func (s *nitroSuite) withNitroQueryFlags(cmd ...string) []string {
return append(cmd, "--home", s.clientHome())
}
func (s *nitroSuite) Test_Hang() {
hang := os.Getenv("HANG")
if hang == "" {
s.T().Skip("Placeholder to run the validator nodes and hang for manual testing")
}
if hang == "ledger" || hang == "payment" {
s.setupLedgerChannel("20")
}
if hang == "payment" {
s.setupPaymentChannel("10")
}
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
<-c
}
+389
View File
@@ -0,0 +1,389 @@
package system
import (
"fmt"
"github.com/cosmos/cosmos-sdk/testutil"
"github.com/tidwall/gjson"
"git.vdb.to/cerc-io/laconicd/x/registry"
)
const badPath = "/asdasd"
func (s *registrySuite) TestGRPCQueryParams() {
reqURL := s.endpointURL("registry/v1/params")
testCases := []struct {
name string
url string
errmsg string
}{
{
"invalid request",
reqURL + badPath,
"Not Implemented",
},
{
"valid request",
reqURL,
"",
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
require := s.Require()
resp, err := testutil.GetRequest(tc.url)
require.NoError(err)
if tc.errmsg != "" {
message := gjson.Get(string(resp), "message").String()
require.Contains(message, tc.errmsg, tc.url)
} else {
var response registry.QueryParamsResponse
require.NoError(s.codec.UnmarshalJSON(resp, &response), string(resp))
params := registry.DefaultParams()
s.updateParams(&params)
require.Equal(params.String(), response.GetParams().String())
}
})
}
}
func (s *registrySuite) TestGRPCQueryWhoIs() {
reqURL := s.endpointURL("registry/v1/whois/%s")
authorityName := "QueryWhoIS"
testCases := []struct {
name string
url string
errmsg string
prerun func(string)
}{
{
"invalid url",
reqURL + badPath,
"Not Implemented",
func(string) {},
},
{
"valid request",
reqURL,
"",
func(name string) { s.reserveName(name) },
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
require := s.Require()
tc.prerun(authorityName)
url := fmt.Sprintf(tc.url, authorityName)
resp, err := testutil.GetRequest(url)
require.NoError(err)
if tc.errmsg != "" {
message := gjson.Get(string(resp), "message").String()
require.Contains(message, tc.errmsg, tc.url)
} else {
var response registry.QueryWhoisResponse
require.NoError(s.codec.UnmarshalJSON(resp, &response), string(resp))
require.Equal(registry.AuthorityActive, response.GetNameAuthority().Status)
}
})
}
}
func (s *registrySuite) TestGRPCQueryLookup() {
reqURL := s.endpointURL("registry/v1/lookup")
authorityName := "QueryLookUp"
s.createNameRecord(authorityName)
testCases := []struct {
name string
url string
errmsg string
}{
{
"invalid url",
reqURL + badPath,
"Not Implemented",
},
{
"nonexistent LRN",
fmt.Sprintf(reqURL+"?lrn=lrn://%s/", "nonexistent"),
"not found",
},
{
"valid request",
fmt.Sprintf(reqURL+"?lrn=lrn://%s/", authorityName),
"",
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
require := s.Require()
resp, err := testutil.GetRequest(tc.url)
require.NoError(err)
if tc.errmsg != "" {
message := gjson.Get(string(resp), "message").String()
require.Contains(message, tc.errmsg, tc.url)
} else {
var response registry.QueryLookupLrnResponse
require.NoError(s.codec.UnmarshalJSON(resp, &response), string(resp))
require.NotEmpty(response.Name.Latest.Id)
}
})
}
}
func (s *registrySuite) TestGRPCQueryListRecords() {
reqURL := s.endpointURL("registry/v1/records")
bondId := s.bondId
testCases := []struct {
name string
url string
errmsg string
prerun func(string)
}{
{
"invalid url",
reqURL + badPath,
"not found",
func(string) {},
},
{
"valid request",
reqURL,
"",
func(id string) { s.createRecord(id) },
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
require := s.Require()
tc.prerun(bondId)
resp, err := testutil.GetRequest(tc.url)
require.NoError(err)
if tc.errmsg != "" {
message := gjson.Get(string(resp), "message").String()
require.Contains(message, tc.errmsg, tc.url)
} else {
var response registry.QueryRecordsResponse
require.NoError(s.codec.UnmarshalJSON(resp, &response), string(resp))
require.NotEmpty(response.GetRecords())
require.Equal(bondId, response.GetRecords()[0].GetBondId())
}
})
}
}
func (s *registrySuite) TestGRPCQueryGetRecordById() {
reqURL := s.endpointURL("registry/v1/records/%s")
bondId := s.bondId
testCases := []struct {
name string
url string
errmsg string
prerun func(string) string
}{
{
"invalid url",
reqURL + badPath,
"not found",
func(string) string { return "" },
},
{
"valid request",
reqURL,
"",
func(id string) string {
require := s.Require()
// create a record and get the id
s.createRecord(id)
out := s.cli().CustomQuery("q", "registry", "list")
var response registry.QueryRecordsResponse
require.NoError(s.codec.UnmarshalJSON([]byte(out), &response), out)
require.NotEmpty(response.GetRecords())
return response.GetRecords()[0].Id
},
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
require := s.Require()
recordId := tc.prerun(bondId)
url := fmt.Sprintf(tc.url, recordId)
resp, err := testutil.GetRequest(url)
require.NoError(err)
if tc.errmsg != "" {
message := gjson.Get(string(resp), "message").String()
require.Contains(message, tc.errmsg, tc.url)
} else {
var response registry.QueryGetRecordResponse
require.NoError(s.codec.UnmarshalJSON(resp, &response), string(resp))
record := response.GetRecord()
require.NotEmpty(record.GetId())
require.Equal(recordId, record.GetId())
}
})
}
}
func (s *registrySuite) TestGRPCQueryGetRecordByBondId() {
reqURL := s.endpointURL("registry/v1/records-by-bond-id/%s")
bondId := s.bondId
testCases := []struct {
name string
url string
errmsg string
prerun func(string)
}{
{
"invalid url",
reqURL + badPath,
"Not Implemented",
func(string) {},
},
{
"valid request",
reqURL,
"",
func(id string) { s.createRecord(id) },
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
require := s.Require()
tc.prerun(bondId)
url := fmt.Sprintf(tc.url, bondId)
resp, err := testutil.GetRequest(url)
require.NoError(err)
if tc.errmsg != "" {
message := gjson.Get(string(resp), "message").String()
require.Contains(message, tc.errmsg, tc.url)
} else {
var response registry.QueryGetRecordsByBondIdResponse
require.NoError(s.codec.UnmarshalJSON(resp, &response), string(resp))
records := response.GetRecords()
require.NotEmpty(records)
require.Equal(bondId, records[0].GetBondId())
}
})
}
}
func (s *registrySuite) TestGRPCQueryGetRegistryModuleBalance() {
reqURL := s.endpointURL("registry/v1/balance")
bondId := s.bondId
testCases := []struct {
name string
url string
errmsg string
prerun func(string)
}{
{
"invalid url",
reqURL + badPath,
"Not Implemented",
func(string) {},
},
{
"valid request",
reqURL,
"",
func(id string) { s.createRecord(id) },
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
require := s.Require()
tc.prerun(bondId)
resp, err := testutil.GetRequest(tc.url)
require.NoError(err)
if tc.errmsg != "" {
message := gjson.Get(string(resp), "message").String()
require.Contains(message, tc.errmsg, tc.url)
} else {
var response registry.QueryGetRegistryModuleBalanceResponse
require.NoError(s.codec.UnmarshalJSON(resp, &response), string(resp))
require.NotEmpty(response.GetBalances())
}
})
}
}
func (s *registrySuite) TestGRPCQueryNamesList() {
reqURL := s.endpointURL("registry/v1/names")
testCases := []struct {
name string
url string
errmsg string
prerun func(string)
}{
{
"invalid url",
reqURL + badPath,
"Not Implemented",
func(string) {},
},
{
"valid request",
reqURL,
"",
func(name string) { s.createNameRecord(name) },
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
require := s.Require()
tc.prerun("ListNameRecords")
resp, err := testutil.GetRequest(tc.url)
require.NoError(err)
if tc.errmsg != "" {
message := gjson.Get(string(resp), "message").String()
require.Contains(message, tc.errmsg, tc.url)
} else {
var response registry.QueryNameRecordsResponse
require.NoError(s.codec.UnmarshalJSON(resp, &response), string(resp))
require.NotEmpty(response.GetNames())
}
})
}
}
+157
View File
@@ -0,0 +1,157 @@
package system
import (
"fmt"
"os"
"path/filepath"
"testing"
"time"
"github.com/stretchr/testify/suite"
"github.com/tidwall/gjson"
"github.com/tidwall/sjson"
// accounts "github.com/cosmos/cosmos-sdk/x/accounts/v1"
"cosmossdk.io/math"
systest "cosmossdk.io/systemtests"
sdk "github.com/cosmos/cosmos-sdk/types"
// nitro "git.vdb.to/cerc-io/laconicd/x/nitro/v1"
"git.vdb.to/cerc-io/laconicd/x/registry"
)
var recordFilePath = "../data/examples/service_provider_example.yml"
func init() {
var err error
if recordFilePath, err = filepath.Abs(recordFilePath); err != nil {
panic(err)
}
if _, err = os.Stat(recordFilePath); err != nil {
panic(err)
}
}
type registrySuite struct {
testSuite
accountAddress string
bondId string
}
func TestRegistry(t *testing.T) {
suite.Run(t, new(registrySuite))
}
func (s *registrySuite) SetupTest() {
s.resetChain()
Sut.ModifyGenesisJSON(s.T(), UpdateGenesisRegistry(s))
s.accountAddress = s.cli().GetKeyAddr(s.account(0))
Sut.StartChain(s.T())
s.bondId = createBond(&s.testSuite)
}
func (s *registrySuite) TestTxSetRecord() {
testCases := []struct {
name string
args []string
err bool
}{
{
"request with invalid payload file arg",
[]string{"bad-file", s.bondId},
true,
},
{
"success",
[]string{recordFilePath, s.bondId},
false,
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
cmd := []string{"tx", "registry", "set"}
cmd = append(cmd, tc.args...)
s.runTx(cmd, tc.err)
})
}
}
func (s *testSuite) updateParams(params *registry.Params) {
params.RecordRent = sdk.NewCoin(s.bondDenom, math.NewInt(1000))
params.RecordRentDuration = 10 * time.Second
params.AuthorityRent = sdk.NewCoin(s.bondDenom, math.NewInt(1000))
params.AuthorityGracePeriod = 10 * time.Second
params.AuthorityAuctionCommitFee = sdk.NewCoin(s.bondDenom, math.NewInt(100))
params.AuthorityAuctionRevealFee = sdk.NewCoin(s.bondDenom, math.NewInt(100))
params.AuthorityAuctionMinimumBid = sdk.NewCoin(s.bondDenom, math.NewInt(500))
}
func UpdateGenesisRegistry(s *registrySuite) systest.GenesisMutator {
require := s.Require()
return func(genesis []byte) []byte {
var regState registry.GenesisState
raw := gjson.Get(string(genesis), "app_state.registry").String()
require.NoError(s.codec.UnmarshalJSON([]byte(raw), &regState))
s.updateParams(&regState.Params)
regStateBz, err := s.codec.MarshalJSON(&regState)
require.NoError(err)
genesis, err = sjson.SetRawBytes(genesis, "app_state.registry", regStateBz)
require.NoError(err)
// // add consensus-controlled module account(s)
// bondinitmsg, err := codectypes.NewAnyWithValue(&nitro.MsgInitAccount{})
// require.NoError(s.T(), err)
// var accountsState accounts.GenesisState
// raw = gjson.Get(string(genesis), "app_state.accounts").String()
// require.NoError(s.T(), cdc.UnmarshalJSON([]byte(raw), &accountsState))
// accountsState.InitAccountMsgs = []*accounts.MsgInit{
// {
// Sender: accountAddress,
// AccountType: "nitro",
// Message: bondinitmsg,
// },
// }
// accountsStateBz, err := cdc.MarshalJSON(&accountsState)
// require.NoError(s.T(), err)
// genesis, err = sjson.SetRawBytes(genesis, "app_state.accounts", accountsStateBz)
// require.NoError(s.T(), err)
return genesis
}
}
func (s *registrySuite) reserveName(authorityName string) {
cmd := []string{
"tx", "registry", "reserve-authority", authorityName, s.accountAddress,
}
s.runTx(cmd, false)
}
func (s *registrySuite) createNameRecord(authorityName string) {
for _, cmd := range [][]string{
// reserve name authority
{"tx", "registry", "reserve-authority", authorityName, s.accountAddress},
// add bond-id to name authority
{"tx", "registry", "authority-bond", authorityName, s.bondId},
// create actual name record
{"tx", "registry", "set-name", fmt.Sprintf("lrn://%s/", authorityName), "test_hello_cid"},
} {
s.runTx(cmd, false)
}
}
func (s *registrySuite) createRecord(bondId string) {
s.T().Helper()
cmd := []string{
"tx", "registry", "set", recordFilePath, bondId,
}
s.runTx(cmd, false)
}
+170
View File
@@ -0,0 +1,170 @@
package system
import (
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
"github.com/tidwall/gjson"
systest "cosmossdk.io/systemtests"
"github.com/cosmos/cosmos-sdk/codec"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"git.vdb.to/cerc-io/laconicd/app/params"
)
type testSuite struct {
suite.Suite
codec codec.Codec
accountNamePrefix string
fees string
bondDenom string
clientEthKey string
validatorEthKey string
}
func (s *testSuite) SetupSuite() {
s.codec = codec.NewProtoCodec(codectypes.NewInterfaceRegistry())
s.accountNamePrefix = "node" // from testnet (--node-dir-prefix)
s.fees = "1" + params.CoinUnit
s.bondDenom = sdk.DefaultBondDenom
s.validatorEthKey = "validator-eth-key"
s.clientEthKey = "client-eth-key"
// configure the validators and client
commonInitCommands := [][]string{
{"config", "set", "client", "chain-id", "testing"},
{"config", "set", "client", "keyring-backend", "test"},
}
// disable nitro on validators by default
Sut.ForEachNodeExecAndWait(s.T(), append(commonInitCommands,
[]string{"config", "set", "app", "nitro.enable", "false"},
)...)
clientInitCommands := append(commonInitCommands,
[]string{"config", "set", "app", "nitro.eth-key", s.clientEthKey},
)
for _, cmd := range clientInitCommands {
cmd = append(cmd, "--home", s.clientHome())
_ = systest.MustRunShellCmd(s.T(), Sut.ExecBinary(), cmd...)
}
}
func (s *testSuite) setValidatorNitroKeys(keys ...string) {
Sut.WithEachNodeHome(func(i int, home string) {
var cmds [][]string
// TODO: complete and enable distsig
// []string{"config", "set", "app", "distsig.enable", "true"},
// []string{"config", "set", "app", "distsig.longterm-key", s.validatorNitroKey},
// []string{"config", "set", "app", "nitro.use-distsig", "true"},
if i < len(keys) {
// configure validator as a payment channel (individual) counterparty
cmds = [][]string{
{"config", "set", "app", "nitro.enable", "true"},
{"keys", "import-hex", s.validatorEthKey, keys[i]},
{"config", "set", "app", "nitro.eth-key", s.validatorEthKey},
}
}
for _, cmd := range cmds {
systest.MustRunShellCmd(s.T(), Sut.ExecBinary(), append(cmd, "--home", home)...)
}
})
}
// note: keys have to be created after ResetChain, which clears the keyring
func (s *testSuite) setClientKeys(ethKey string) {
commands := [][]string{
{"keys", "import-hex", s.clientEthKey, ethKey},
}
for _, cmd := range commands {
cmd = append(cmd, "--home", s.clientHome())
_ = systest.MustRunShellCmd(s.T(), Sut.ExecBinary(), cmd...)
}
}
func (s *testSuite) resetChain() {
Sut.ResetChain(s.T())
// clear the nitro data dirs
Sut.WithEachNodeHome(func(i int, home string) {
_ = os.RemoveAll(filepath.Join(home, "nitro"))
})
_ = os.RemoveAll(filepath.Join(s.clientHome(), "nitro"))
}
func (s *testSuite) account(i int) string {
return fmt.Sprintf("%s%d", s.accountNamePrefix, i)
}
// the home dir systemtests uses to run client commands
func (s *testSuite) clientHome() string {
return filepath.Join(workDir, "testnet")
}
func (s *testSuite) endpointURL(endpoint string) string {
return fmt.Sprintf("%s/cerc/%s", Sut.APIAddress(), endpoint)
}
func (s *testSuite) cli() *systest.CLIWrapper {
cli := systest.NewCLIWrapper(s.T(), Sut, verbose).
WithRunStderr(os.Stderr)
return &cli
}
func (s *testSuite) assertWithStderr(asserter systest.RunErrorAssert) systest.RunErrorAssert {
return func(t assert.TestingT, err error, msgAndArgs ...any) bool {
if asserter(t, err, msgAndArgs...) {
return true
}
var exitError *exec.ExitError
if errors.As(err, &exitError) {
if stderr := exitError.Stderr; len(stderr) > 0 {
defer t.Errorf("stderr: %s", stderr)
}
}
return false
}
}
func (s *testSuite) runTx(cmd []string, err bool) string {
s.T().Helper()
cli := s.cli()
asserter := assert.NoError
if err {
asserter = assert.Error
}
// don't use Run(), it will clobber our custom --home
out := cli.
WithRunErrorMatcher(s.assertWithStderr(asserter)).
RunCommandWithArgs(s.withTxFlags(cmd)...)
if err {
return ""
}
txResult, committed := cli.AwaitTxCommitted(out, txTimeout)
s.Require().True(committed)
systest.RequireTxSuccess(s.T(), txResult)
txHash := gjson.Get(out, "txhash")
s.Require().True(txHash.Exists(), "txhash not found in output: %s", out)
return txHash.String()
}
func (s *testSuite) withTxFlags(cmd []string) []string {
cmd = s.cli().WithTXFlags(cmd...)
return append(cmd, []string{
"--from", s.account(0),
"--fees", s.fees,
}...)
}