From c924de965083610ba9a7c7f44244108b3a91e1ae Mon Sep 17 00:00:00 2001 From: Uday Patil Date: Thu, 3 Nov 2022 10:46:37 -0400 Subject: [PATCH 1/4] Revert "Revert "Rebase 2.0.0beta Parallelization onto master" (#357)" This reverts commit 977c1d1b9e97ac01cf8634323d6ea77489466ada. --- Makefile | 54 +- aclmapping/bank/mappings.go | 91 +++ aclmapping/bank/mappings_test.go | 158 +++++ aclmapping/dependency_generator.go | 27 + aclmapping/dex/mappings.go | 34 + aclmapping/oracle/mappings.go | 69 ++ aclmapping/oracle/mappings_test.go | 143 ++++ aclmapping/staking/mappings.go | 653 ++++++++++++++++++ aclmapping/staking/mappings_test.go | 342 +++++++++ aclmapping/tokenfactory/mappings.go | 200 ++++++ aclmapping/tokenfactory/mappings_test.go | 234 +++++++ aclmapping/utils/identifier_templates.go | 40 ++ aclmapping/utils/resource_type.go | 81 +++ aclmapping/utils/test_utils.go | 9 + aclmapping/wasm/mappings.go | 53 ++ app/abci.go | 13 +- app/ante.go | 75 +- app/ante_test.go | 209 ++++++ app/antedecorators/depdecorators/signers.go | 34 + app/antedecorators/gas.go | 53 ++ app/antedecorators/gas_test.go | 77 +++ app/antedecorators/gasless.go | 21 + app/antedecorators/gasless_test.go | 10 +- app/antedecorators/traced_test.go | 14 +- app/app.go | 353 ++++++++-- app/apptesting/test_suite.go | 3 +- app/params/config.go | 2 +- app/test_helpers.go | 2 + cmd/seid/cmd/root.go | 6 +- go.mod | 13 +- go.sum | 22 +- loadtest/config.json | 126 ++-- loadtest/contracts/deploy_ten_contracts.sh | 6 +- loadtest/loadtest_client.go | 199 ++++++ loadtest/main.go | 295 ++------ loadtest/scripts/metrics.py | 3 + loadtest/scripts/populate_genesis_accounts.py | 39 +- loadtest/sign.go | 107 ++- loadtest/tx.go | 17 +- loadtest/types.go | 129 ++++ scripts/old_initialize_local.sh | 66 +- store/testutils.go | 2 +- testutil/network/network.go | 1 + utils/metrics/labels.go | 8 + utils/metrics/metrics_util.go | 60 ++ wasmbinding/encoder.go | 43 ++ wasmbinding/message_plugin.go | 229 +++--- wasmbinding/query_plugin.go | 163 +++++ wasmbinding/test/message_handler_test.go | 141 ++++ wasmbinding/test/query_test.go | 503 ++++++++++++++ wasmbinding/utils.go | 14 + wasmbinding/wasm.go | 21 +- x/dex/keeper/epoch.go | 2 +- x/dex/module_test.go | 2 +- x/mint/types/expected_keepers.go | 2 + x/oracle/ante.go | 22 +- x/oracle/keeper/test_utils.go | 0 x/oracle/simulation/operations.go | 4 +- x/tokenfactory/keeper/admins_test.go | 26 + x/tokenfactory/keeper/bankactions.go | 14 +- x/tokenfactory/types/expected_keepers.go | 6 + 61 files changed, 4721 insertions(+), 624 deletions(-) create mode 100644 aclmapping/bank/mappings.go create mode 100644 aclmapping/bank/mappings_test.go create mode 100644 aclmapping/dependency_generator.go create mode 100644 aclmapping/dex/mappings.go create mode 100644 aclmapping/oracle/mappings.go create mode 100644 aclmapping/oracle/mappings_test.go create mode 100644 aclmapping/staking/mappings.go create mode 100644 aclmapping/staking/mappings_test.go create mode 100644 aclmapping/tokenfactory/mappings.go create mode 100644 aclmapping/tokenfactory/mappings_test.go create mode 100644 aclmapping/utils/identifier_templates.go create mode 100644 aclmapping/utils/resource_type.go create mode 100644 aclmapping/utils/test_utils.go create mode 100644 aclmapping/wasm/mappings.go create mode 100644 app/ante_test.go create mode 100644 app/antedecorators/depdecorators/signers.go create mode 100644 app/antedecorators/gas.go create mode 100644 app/antedecorators/gas_test.go create mode 100644 loadtest/loadtest_client.go create mode 100644 loadtest/types.go mode change 100644 => 100755 scripts/old_initialize_local.sh create mode 100644 utils/metrics/labels.go create mode 100644 wasmbinding/encoder.go create mode 100644 wasmbinding/test/message_handler_test.go create mode 100644 wasmbinding/utils.go mode change 100755 => 100644 x/oracle/keeper/test_utils.go diff --git a/Makefile b/Makefile index c43b83294c..8a82afa15e 100644 --- a/Makefile +++ b/Makefile @@ -10,26 +10,26 @@ export GO111MODULE = on LEDGER_ENABLED ?= true build_tags = netgo ifeq ($(LEDGER_ENABLED),true) - ifeq ($(OS),Windows_NT) - GCCEXE = $(shell where gcc.exe 2> NUL) - ifeq ($(GCCEXE),) - $(error gcc.exe not installed for ledger support, please install or set LEDGER_ENABLED=false) - else - build_tags += ledger - endif - else - UNAME_S = $(shell uname -s) - ifeq ($(UNAME_S),OpenBSD) - $(warning OpenBSD detected, disabling ledger support (https://github.com/cosmos/cosmos-sdk/issues/1988)) - else - GCC = $(shell command -v gcc 2> /dev/null) - ifeq ($(GCC),) - $(error gcc not installed for ledger support, please install or set LEDGER_ENABLED=false) - else - build_tags += ledger - endif - endif - endif + ifeq ($(OS),Windows_NT) + GCCEXE = $(shell where gcc.exe 2> NUL) + ifeq ($(GCCEXE),) + $(error gcc.exe not installed for ledger support, please install or set LEDGER_ENABLED=false) + else + build_tags += ledger + endif + else + UNAME_S = $(shell uname -s) + ifeq ($(UNAME_S),OpenBSD) + $(warning OpenBSD detected, disabling ledger support (https://github.com/cosmos/cosmos-sdk/issues/1988)) + else + GCC = $(shell command -v gcc 2> /dev/null) + ifeq ($(GCC),) + $(error gcc not installed for ledger support, please install or set LEDGER_ENABLED=false) + else + build_tags += ledger + endif + endif + endif endif build_tags += $(BUILD_TAGS) @@ -43,13 +43,13 @@ build_tags_comma_sep := $(subst $(whitespace),$(comma),$(build_tags)) # process linker flags ldflags = -X github.com/cosmos/cosmos-sdk/version.Name=sei \ - -X github.com/cosmos/cosmos-sdk/version.ServerName=seid \ - -X github.com/cosmos/cosmos-sdk/version.Version=$(VERSION) \ - -X github.com/cosmos/cosmos-sdk/version.Commit=$(COMMIT) \ - -X "github.com/cosmos/cosmos-sdk/version.BuildTags=$(build_tags_comma_sep)" + -X github.com/cosmos/cosmos-sdk/version.ServerName=seid \ + -X github.com/cosmos/cosmos-sdk/version.Version=$(VERSION) \ + -X github.com/cosmos/cosmos-sdk/version.Commit=$(COMMIT) \ + -X "github.com/cosmos/cosmos-sdk/version.BuildTags=$(build_tags_comma_sep)" ifeq ($(LINK_STATICALLY),true) - ldflags += -linkmode=external -extldflags "-Wl,-z,muldefs -static" + ldflags += -linkmode=external -extldflags "-Wl,-z,muldefs -static" endif ldflags += $(LDFLAGS) ldflags := $(strip $(ldflags)) @@ -64,6 +64,10 @@ all: lint install install: go.sum go install $(BUILD_FLAGS) ./cmd/seid +loadtest: go.sum + go build $(BUILD_FLAGS) -o ./build/loadtest ./loadtest/ + + go.sum: go.mod @echo "--> Ensure dependencies have not been modified" @go mod verify diff --git a/aclmapping/bank/mappings.go b/aclmapping/bank/mappings.go new file mode 100644 index 0000000000..47989a1823 --- /dev/null +++ b/aclmapping/bank/mappings.go @@ -0,0 +1,91 @@ +package aclbankmapping + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkacltypes "github.com/cosmos/cosmos-sdk/types/accesscontrol" + aclkeeper "github.com/cosmos/cosmos-sdk/x/accesscontrol/keeper" + acltypes "github.com/cosmos/cosmos-sdk/x/accesscontrol/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + utils "github.com/sei-protocol/sei-chain/aclmapping/utils" +) + +var ErrorInvalidMsgType = fmt.Errorf("invalid message received for bank module") + +func GetBankDepedencyGenerator() aclkeeper.DependencyGeneratorMap { + dependencyGeneratorMap := make(aclkeeper.DependencyGeneratorMap) + + // dex place orders + placeOrdersKey := acltypes.GenerateMessageKey(&banktypes.MsgSend{}) + dependencyGeneratorMap[placeOrdersKey] = MsgSendDependencyGenerator + + return dependencyGeneratorMap +} + +func MsgSendDependencyGenerator(keeper aclkeeper.Keeper, ctx sdk.Context, msg sdk.Msg) ([]sdkacltypes.AccessOperation, error) { + msgSend, ok := msg.(*banktypes.MsgSend) + if !ok { + return []sdkacltypes.AccessOperation{}, ErrorInvalidMsgType + } + fromAddrIdentifier := string(banktypes.CreateAccountBalancesPrefixFromBech32(msgSend.FromAddress)) + toAddrIdentifier := string(banktypes.CreateAccountBalancesPrefixFromBech32(msgSend.ToAddress)) + + accessOperations := []sdkacltypes.AccessOperation{ + // MsgSend also checks if the coin denom is enabled, but the information is from the params. + // Changing the param would require a gov proposal, which is synchrounos by default + + // Checks balance of sender + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_BANK_BALANCES, + IdentifierTemplate: fromAddrIdentifier, + }, + // Reduce the amount from the sender's balance + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_BANK_BALANCES, + IdentifierTemplate: fromAddrIdentifier, + }, + + // Checks balance for receiver + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_BANK_BALANCES, + IdentifierTemplate: toAddrIdentifier, + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_BANK_BALANCES, + IdentifierTemplate: toAddrIdentifier, + }, + + // Tries to create the reciever's account if it doesn't exist + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_AUTH_ADDRESS_STORE, + IdentifierTemplate: string(authtypes.CreateAddressStoreKeyFromBech32(msgSend.ToAddress)), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_AUTH_ADDRESS_STORE, + IdentifierTemplate: string(authtypes.CreateAddressStoreKeyFromBech32(msgSend.ToAddress)), + }, + + // Gets Account Info for the sender + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_AUTH_ADDRESS_STORE, + IdentifierTemplate: string(authtypes.CreateAddressStoreKeyFromBech32(msgSend.FromAddress)), + }, + + { + ResourceType: sdkacltypes.ResourceType_ANY, + AccessType: sdkacltypes.AccessType_COMMIT, + IdentifierTemplate: utils.DefaultIDTemplate, + }, + } + + return accessOperations, nil +} diff --git a/aclmapping/bank/mappings_test.go b/aclmapping/bank/mappings_test.go new file mode 100644 index 0000000000..9800592f0b --- /dev/null +++ b/aclmapping/bank/mappings_test.go @@ -0,0 +1,158 @@ +package aclbankmapping + +import ( + "testing" + + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + "github.com/cosmos/cosmos-sdk/simapp" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkacltypes "github.com/cosmos/cosmos-sdk/types/accesscontrol" + acltypes "github.com/cosmos/cosmos-sdk/x/accesscontrol/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/cosmos/cosmos-sdk/x/bank" + "github.com/cosmos/cosmos-sdk/x/bank/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + aclutils "github.com/sei-protocol/sei-chain/aclmapping/utils" + oracletypes "github.com/sei-protocol/sei-chain/x/oracle/types" + "github.com/stretchr/testify/require" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" +) + + +func cacheTxContext(ctx sdk.Context) (sdk.Context, sdk.CacheMultiStore) { + ms := ctx.MultiStore() + msCache := ms.CacheMultiStore() + return ctx.WithMultiStore(msCache), msCache +} + +func TestMsgBankSendAclOps(t *testing.T) { + priv1 := secp256k1.GenPrivKey() + addr1 := sdk.AccAddress(priv1.PubKey().Address()) + priv2 := secp256k1.GenPrivKey() + addr2 := sdk.AccAddress(priv2.PubKey().Address()) + coins := sdk.Coins{sdk.NewInt64Coin("foocoin", 10)} + + tests := []struct { + name string + expectedError error + msg *types.MsgSend + dynamicDep bool + }{ + { + name: "default send", + msg: types.NewMsgSend(addr1, addr2, coins), + expectedError: nil, + dynamicDep: true, + }, + { + name: "dont check synchronous", + msg: types.NewMsgSend(addr1, addr2, coins), + expectedError: nil, + dynamicDep: false, + }, + } + + acc1 := &authtypes.BaseAccount{ + Address: addr1.String(), + } + acc2 := &authtypes.BaseAccount{ + Address: addr2.String(), + } + accs := authtypes.GenesisAccounts{acc1, acc2} + balances := []types.Balance{ + { + Address: addr1.String(), + Coins: coins, + }, + { + Address: addr2.String(), + Coins: coins, + }, + } + + app := simapp.SetupWithGenesisAccounts(accs, balances...) + ctx := app.BaseApp.NewContext(false, tmproto.Header{}) + + handler := bank.NewHandler(app.BankKeeper) + msgValidator := sdkacltypes.NewMsgValidator(aclutils.StoreKeyToResourceTypePrefixMap) + ctx = ctx.WithMsgValidator(msgValidator) + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + handlerCtx, cms := cacheTxContext(ctx) + + _, err := handler(handlerCtx, tc.msg) + + depdenencies , _ := MsgSendDependencyGenerator(app.AccessControlKeeper, handlerCtx, tc.msg) + + if !tc.dynamicDep { + depdenencies = sdkacltypes.SynchronousAccessOps() + } + + if tc.expectedError != nil { + require.EqualError(t, err, tc.expectedError.Error()) + } else { + require.NoError(t, err) + } + missing := handlerCtx.MsgValidator().ValidateAccessOperations(depdenencies, cms.GetEvents()) + require.Empty(t, missing) + }) + } +} + +func TestGeneratorInvalidMessageTypes(t *testing.T) { + accs := authtypes.GenesisAccounts{} + balances := []types.Balance{} + + app := simapp.SetupWithGenesisAccounts(accs, balances...) + ctx := app.BaseApp.NewContext(false, tmproto.Header{}) + + oracleVote := oracletypes.MsgAggregateExchangeRateVote{ + ExchangeRates: "1usei", + Feeder: "test", + Validator: "validator", + } + + _, err := MsgSendDependencyGenerator(app.AccessControlKeeper, ctx, &oracleVote) + require.Error(t, err) +} + +func TestMsgBeginBankSendGenerator(t *testing.T) { + priv1 := secp256k1.GenPrivKey() + addr1 := sdk.AccAddress(priv1.PubKey().Address()) + priv2 := secp256k1.GenPrivKey() + addr2 := sdk.AccAddress(priv2.PubKey().Address()) + coins := sdk.Coins{sdk.NewInt64Coin("foocoin", 10)} + + acc1 := &authtypes.BaseAccount{ + Address: addr1.String(), + } + acc2 := &authtypes.BaseAccount{ + Address: addr2.String(), + } + accs := authtypes.GenesisAccounts{acc1, acc2} + balances := []types.Balance{ + { + Address: addr1.String(), + Coins: coins, + }, + { + Address: addr2.String(), + Coins: coins, + }, + } + + app := simapp.SetupWithGenesisAccounts(accs, balances...) + ctx := app.BaseApp.NewContext(false, tmproto.Header{}) + + sendMsg := banktypes.MsgSend{ + FromAddress: addr1.String(), + ToAddress: addr2.String(), + Amount: coins, + } + + accessOps, err := MsgSendDependencyGenerator(app.AccessControlKeeper, ctx, &sendMsg) + require.NoError(t, err) + err = acltypes.ValidateAccessOps(accessOps) + require.NoError(t, err) +} diff --git a/aclmapping/dependency_generator.go b/aclmapping/dependency_generator.go new file mode 100644 index 0000000000..3b276cdaf4 --- /dev/null +++ b/aclmapping/dependency_generator.go @@ -0,0 +1,27 @@ +package aclmapping + +import ( + aclkeeper "github.com/cosmos/cosmos-sdk/x/accesscontrol/keeper" + aclbankmapping "github.com/sei-protocol/sei-chain/aclmapping/bank" + acldexmapping "github.com/sei-protocol/sei-chain/aclmapping/dex" + acltokenfactorymapping "github.com/sei-protocol/sei-chain/aclmapping/tokenfactory" + aclwasmmapping "github.com/sei-protocol/sei-chain/aclmapping/wasm" +) + +type CustomDependencyGenerator struct{} + +func NewCustomDependencyGenerator() CustomDependencyGenerator { + return CustomDependencyGenerator{} +} + +func (customDepGen CustomDependencyGenerator) GetCustomDependencyGenerators() aclkeeper.DependencyGeneratorMap { + dependencyGeneratorMap := make(aclkeeper.DependencyGeneratorMap) + wasmDependencyGenerators := aclwasmmapping.NewWasmDependencyGenerator() + + dependencyGeneratorMap = dependencyGeneratorMap.Merge(acldexmapping.GetDexDependencyGenerators()) + dependencyGeneratorMap = dependencyGeneratorMap.Merge(aclbankmapping.GetBankDepedencyGenerator()) + dependencyGeneratorMap = dependencyGeneratorMap.Merge(acltokenfactorymapping.GetTokenFactoryDependencyGenerators()) + dependencyGeneratorMap = dependencyGeneratorMap.Merge(wasmDependencyGenerators.GetWasmDependencyGenerators()) + + return dependencyGeneratorMap +} diff --git a/aclmapping/dex/mappings.go b/aclmapping/dex/mappings.go new file mode 100644 index 0000000000..58156eb64e --- /dev/null +++ b/aclmapping/dex/mappings.go @@ -0,0 +1,34 @@ +package acldexmapping + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkacltypes "github.com/cosmos/cosmos-sdk/types/accesscontrol" + aclkeeper "github.com/cosmos/cosmos-sdk/x/accesscontrol/keeper" + acltypes "github.com/cosmos/cosmos-sdk/x/accesscontrol/types" + dexmoduletypes "github.com/sei-protocol/sei-chain/x/dex/types" +) + +var ErrPlaceOrdersGenerator = fmt.Errorf("invalid message received for type DexPlaceOrders") + +func GetDexDependencyGenerators() aclkeeper.DependencyGeneratorMap { + dependencyGeneratorMap := make(aclkeeper.DependencyGeneratorMap) + + // dex place orders + placeOrdersKey := acltypes.GenerateMessageKey(&dexmoduletypes.MsgPlaceOrders{}) + dependencyGeneratorMap[placeOrdersKey] = DexPlaceOrdersDependencyGenerator + + return dependencyGeneratorMap +} + +func DexPlaceOrdersDependencyGenerator(keeper aclkeeper.Keeper, ctx sdk.Context, msg sdk.Msg) ([]sdkacltypes.AccessOperation, error) { + placeOrdersMsg, ok := msg.(*dexmoduletypes.MsgPlaceOrders) + if !ok { + return []sdkacltypes.AccessOperation{}, ErrPlaceOrdersGenerator + } + // TODO: This is not final, JUST AN EXAMPLE + return []sdkacltypes.AccessOperation{ + {AccessType: sdkacltypes.AccessType_WRITE, ResourceType: sdkacltypes.ResourceType_KV, IdentifierTemplate: placeOrdersMsg.ContractAddr}, + }, nil +} diff --git a/aclmapping/oracle/mappings.go b/aclmapping/oracle/mappings.go new file mode 100644 index 0000000000..778bc5bf6b --- /dev/null +++ b/aclmapping/oracle/mappings.go @@ -0,0 +1,69 @@ +package acloraclemapping + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkacltypes "github.com/cosmos/cosmos-sdk/types/accesscontrol" + aclkeeper "github.com/cosmos/cosmos-sdk/x/accesscontrol/keeper" + acltypes "github.com/cosmos/cosmos-sdk/x/accesscontrol/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + utils "github.com/sei-protocol/sei-chain/aclmapping/utils" + oracletypes "github.com/sei-protocol/sei-chain/x/oracle/types" +) + +var ErrorInvalidMsgType = fmt.Errorf("invalid message received for oracle module") + +func GetOracleDependencyGenerator() aclkeeper.DependencyGeneratorMap { + dependencyGeneratorMap := make(aclkeeper.DependencyGeneratorMap) + + // vote + voteKey := acltypes.GenerateMessageKey(&oracletypes.MsgAggregateExchangeRateVote{}) + dependencyGeneratorMap[voteKey] = MsgVoteDependencyGenerator + + return dependencyGeneratorMap +} + +func MsgVoteDependencyGenerator(keeper aclkeeper.Keeper, ctx sdk.Context, msg sdk.Msg) ([]sdkacltypes.AccessOperation, error) { + msgVote, ok := msg.(*oracletypes.MsgAggregateExchangeRateVote) + if !ok { + return []sdkacltypes.AccessOperation{}, ErrorInvalidMsgType + } + valAddr, _ := sdk.ValAddressFromBech32(msgVote.Validator) + + accessOperations := []sdkacltypes.AccessOperation{ + // validate feeder + // read feeder delegation for val addr - READ + { + ResourceType: sdkacltypes.ResourceType_KV_ORACLE_FEEDERS, + AccessType: sdkacltypes.AccessType_READ, + IdentifierTemplate: string(oracletypes.GetFeederDelegationKey(valAddr)), + }, + // read validator from staking - READ + // validator is bonded check - READ + // (both covered by below) + { + ResourceType: sdkacltypes.ResourceType_KV_STAKING_VALIDATOR, + AccessType: sdkacltypes.AccessType_READ, + IdentifierTemplate: string(stakingtypes.GetValidatorKey(valAddr)), + }, + + // get vote target (for all exchange rate tuples) -> blanket read on that prefix - READ + { + ResourceType: sdkacltypes.ResourceType_KV_ORACLE_VOTE_TARGETS, + AccessType: sdkacltypes.AccessType_READ, + IdentifierTemplate: utils.DefaultIDTemplate, + }, + + // set exchange rate vote - WRITE + { + ResourceType: sdkacltypes.ResourceType_KV_ORACLE_AGGREGATE_VOTES, + AccessType: sdkacltypes.AccessType_WRITE, + IdentifierTemplate: string(oracletypes.GetAggregateExchangeRateVoteKey(valAddr)), + }, + + // Last Operation should always be a commit + *acltypes.CommitAccessOp(), + } + return accessOperations, nil +} diff --git a/aclmapping/oracle/mappings_test.go b/aclmapping/oracle/mappings_test.go new file mode 100644 index 0000000000..0e3246475c --- /dev/null +++ b/aclmapping/oracle/mappings_test.go @@ -0,0 +1,143 @@ +package acloraclemapping_test + +import ( + "fmt" + "testing" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkacltypes "github.com/cosmos/cosmos-sdk/types/accesscontrol" + oracleacl "github.com/sei-protocol/sei-chain/aclmapping/oracle" + aclutils "github.com/sei-protocol/sei-chain/aclmapping/utils" + utils "github.com/sei-protocol/sei-chain/aclmapping/utils" + "github.com/sei-protocol/sei-chain/app/apptesting" + oraclekeeper "github.com/sei-protocol/sei-chain/x/oracle/keeper" + oracletypes "github.com/sei-protocol/sei-chain/x/oracle/types" + + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + acltypes "github.com/cosmos/cosmos-sdk/x/accesscontrol/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/sei-protocol/sei-chain/app" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +type KeeperTestSuite struct { + apptesting.KeeperTestHelper + + queryClient oracletypes.QueryClient + msgServer oracletypes.MsgServer + // defaultDenom is on the suite, as it depends on the creator test address. + defaultDenom string + defaultExchangeRate string + initalBalance sdk.Coins + + validator sdk.ValAddress +} + +func TestKeeperTestSuite(t *testing.T) { + suite.Run(t, new(KeeperTestSuite)) +} + +// Runs before each test case +func (suite *KeeperTestSuite) SetupTest() { + suite.Setup() +} + +// Explicitly only run once during setup +func (suite *KeeperTestSuite) PrepareTest() { + suite.defaultDenom = "usei" + suite.defaultExchangeRate = fmt.Sprintf("%dusei", sdk.NewDec(1700)) + + suite.initalBalance = sdk.Coins{sdk.NewInt64Coin(suite.defaultDenom, 100000000000)} + suite.FundAcc(suite.TestAccs[0], suite.initalBalance) + + suite.queryClient = oracletypes.NewQueryClient(suite.QueryHelper) + suite.msgServer = oraclekeeper.NewMsgServerImpl(suite.App.OracleKeeper) + + // testInput := oraclekeeper.CreateTestInput(suite.T()) + // suite.Ctx = testInput.Ctx + + msgValidator := sdkacltypes.NewMsgValidator(aclutils.StoreKeyToResourceTypePrefixMap) + suite.Ctx = suite.Ctx.WithMsgValidator(msgValidator) + suite.Ctx = suite.Ctx.WithBlockHeight(1) + suite.validator = suite.SetupValidator(stakingtypes.Bonded) + + suite.App.OracleKeeper.SetFeederDelegation(suite.Ctx, suite.validator, suite.TestAccs[0]) + suite.App.OracleKeeper.SetVoteTarget(suite.Ctx, suite.defaultDenom) +} + +func (suite *KeeperTestSuite) TestMsgBurnDependencies() { + suite.PrepareTest() + tests := []struct { + name string + expectedError error + msg *oracletypes.MsgAggregateExchangeRateVote + dynamicDep bool + }{ + { + name: "default vote", + msg: &oracletypes.MsgAggregateExchangeRateVote{ + ExchangeRates: suite.defaultExchangeRate, + Feeder: suite.TestAccs[0].String(), + Validator: suite.validator.String(), + }, + expectedError: nil, + dynamicDep: true, + }, + { + name: "dont check synchronous", + msg: &oracletypes.MsgAggregateExchangeRateVote{ + ExchangeRates: suite.defaultExchangeRate, + Feeder: suite.TestAccs[0].String(), + Validator: suite.validator.String(), + }, + expectedError: nil, + dynamicDep: false, + }, + } + for _, tc := range tests { + suite.Run(fmt.Sprintf("Test Case: %s", tc.name), func() { + handlerCtx, cms := utils.CacheTxContext(suite.Ctx) + _, err := suite.msgServer.AggregateExchangeRateVote( + sdk.WrapSDKContext(handlerCtx), + tc.msg, + ) + depdenencies , _ := oracleacl.MsgVoteDependencyGenerator( + suite.App.AccessControlKeeper, + handlerCtx, + tc.msg, + ) + + if !tc.dynamicDep { + depdenencies = sdkacltypes.SynchronousAccessOps() + } + + if tc.expectedError != nil { + suite.Require().EqualError(err, tc.expectedError.Error()) + } else { + suite.Require().NoError(err) + } + + missing := handlerCtx.MsgValidator().ValidateAccessOperations(depdenencies, cms.GetEvents()) + suite.Require().Empty(missing) + }) + } +} +func TestMsgVoteDependencyGenerator(t *testing.T) { + tm := time.Now().UTC() + valPub := secp256k1.GenPrivKey().PubKey() + + testWrapper := app.NewTestWrapper(t, tm, valPub) + + oracleVote := oracletypes.MsgAggregateExchangeRateVote{ + ExchangeRates: "1usei", + Feeder: "test", + Validator: "validator", + } + + accessOps, err := oracleacl.MsgVoteDependencyGenerator(testWrapper.App.AccessControlKeeper, testWrapper.Ctx, &oracleVote) + require.NoError(t, err) + err = acltypes.ValidateAccessOps(accessOps) + require.NoError(t, err) +} diff --git a/aclmapping/staking/mappings.go b/aclmapping/staking/mappings.go new file mode 100644 index 0000000000..70a280f1c8 --- /dev/null +++ b/aclmapping/staking/mappings.go @@ -0,0 +1,653 @@ +package aclstakingmapping + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkacltypes "github.com/cosmos/cosmos-sdk/types/accesscontrol" + aclkeeper "github.com/cosmos/cosmos-sdk/x/accesscontrol/keeper" + acltypes "github.com/cosmos/cosmos-sdk/x/accesscontrol/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + distributiontypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" +) + +var ErrorInvalidMsgType = fmt.Errorf("invalid message received for staking module") + +func GetStakingDependencyGenerator() aclkeeper.DependencyGeneratorMap { + dependencyGeneratorMap := make(aclkeeper.DependencyGeneratorMap) + + delegateKey := acltypes.GenerateMessageKey(&stakingtypes.MsgDelegate{}) + undelegateKey := acltypes.GenerateMessageKey(&stakingtypes.MsgUndelegate{}) + beginRedelegateKey := acltypes.GenerateMessageKey(&stakingtypes.MsgBeginRedelegate{}) + dependencyGeneratorMap[delegateKey] = MsgDelegateDependencyGenerator + dependencyGeneratorMap[undelegateKey] = MsgUndelegateDependencyGenerator + dependencyGeneratorMap[beginRedelegateKey] = MsgBeginRedelegateDependencyGenerator + + return dependencyGeneratorMap +} + +func MsgDelegateDependencyGenerator(keeper aclkeeper.Keeper, ctx sdk.Context, msg sdk.Msg) ([]sdkacltypes.AccessOperation, error) { + msgDelegate, ok := msg.(*stakingtypes.MsgDelegate) + if !ok { + return []sdkacltypes.AccessOperation{}, ErrorInvalidMsgType + } + + bondedModuleAdr := keeper.AccountKeeper.GetModuleAddress(stakingtypes.BondedPoolName) + notBondedModuleAdr := keeper.AccountKeeper.GetModuleAddress(stakingtypes.NotBondedPoolName) + + delegateAddr, _ := sdk.AccAddressFromBech32(msgDelegate.DelegatorAddress) + validatorAddr, _ := sdk.ValAddressFromBech32(msgDelegate.ValidatorAddress) + + validator, _ := keeper.StakingKeeper.GetValidator(ctx, validatorAddr) + // validatorCons, _ := validator.GetConsAddr() + // validatorAddrCons := string(stakingtypes.GetValidatorByConsAddrKey(validatorCons)) + // validatorOperatorAddr := validator.GetOperator().String() + + delegationKey := string(stakingtypes.GetDelegationKey(delegateAddr, validatorAddr)) + validatorKey := string(stakingtypes.GetValidatorKey(validatorAddr)) + delegatorBalanceKey := string(banktypes.CreateAccountBalancesPrefixFromBech32(msgDelegate.DelegatorAddress)) + validatorBalanceKey := string(banktypes.CreateAccountBalancesPrefixFromBech32(msgDelegate.ValidatorAddress)) + // validatorOperatorBalanceKey := string(banktypes.CreateAccountBalancesPrefixFromBech32(validatorOperatorAddr)) + + accessOperations := []sdkacltypes.AccessOperation{ + // Treat Delegations and Undelegations to have the same ACL since they are highly coupled, no point in finer granularization + + // Get delegation/redelegations and error checking + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_DELEGATION, + IdentifierTemplate: delegationKey, + }, + // Update/delete delegation and update redelegation + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_DELEGATION, + IdentifierTemplate: delegationKey, + }, + + // Check Unbonding + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_VALIDATORS_BY_POWER, + IdentifierTemplate: string(stakingtypes.GetValidatorsByPowerIndexKey(validator, keeper.StakingKeeper.PowerReduction(ctx))), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_VALIDATORS_BY_POWER, + IdentifierTemplate: string(stakingtypes.GetValidatorsByPowerIndexKey(validator, keeper.StakingKeeper.PowerReduction(ctx))), + }, + + // Before Unbond Distribution Hook + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_DELEGATOR_STARTING_INFO, + IdentifierTemplate: string(distributiontypes.GetDelegatorStartingInfoKey(validatorAddr, delegateAddr)), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_DELEGATOR_STARTING_INFO, + IdentifierTemplate: string(distributiontypes.GetDelegatorStartingInfoKey(validatorAddr, delegateAddr)), + }, + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_VAL_CURRENT_REWARDS, + IdentifierTemplate: string(distributiontypes.GetValidatorCurrentRewardsKey(validatorAddr)), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_VAL_CURRENT_REWARDS, + IdentifierTemplate: string(distributiontypes.GetValidatorCurrentRewardsKey(validatorAddr)), + }, + + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_OUTSTANDING_REWARDS, + IdentifierTemplate: string(distributiontypes.GetValidatorOutstandingRewardsKey(validatorAddr)), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_OUTSTANDING_REWARDS, + IdentifierTemplate: string(distributiontypes.GetValidatorOutstandingRewardsKey(validatorAddr)), + }, + + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_FEE_POOL, + IdentifierTemplate: string(distributiontypes.FeePoolKey), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_FEE_POOL, + IdentifierTemplate: string(distributiontypes.FeePoolKey), + }, + + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_VAL_HISTORICAL_REWARDS, + IdentifierTemplate: string(distributiontypes.GetValidatorHistoricalRewardsPrefix(validatorAddr)), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_VAL_HISTORICAL_REWARDS, + IdentifierTemplate: string(distributiontypes.GetValidatorHistoricalRewardsPrefix(validatorAddr)), + }, + + // Gets Module Account information + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_AUTH_ADDRESS_STORE, + IdentifierTemplate: string(authtypes.AddressStoreKey(bondedModuleAdr)), + }, + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_AUTH_ADDRESS_STORE, + IdentifierTemplate: string(authtypes.AddressStoreKey(notBondedModuleAdr)), + }, + + // Get Delegator Acc Info + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_AUTH_ADDRESS_STORE, + IdentifierTemplate: string(authtypes.AddressStoreKey(delegateAddr)), + }, + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_AUTH_ADDRESS_STORE, + IdentifierTemplate: string(authtypes.AddressStoreKey(delegateAddr)), + }, + + // Update the delegator and validator account balances + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_BANK_BALANCES, + IdentifierTemplate: delegatorBalanceKey, + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_BANK_BALANCES, + IdentifierTemplate: delegatorBalanceKey, + }, + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_BANK_BALANCES, + IdentifierTemplate: validatorBalanceKey, + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_BANK_BALANCES, + IdentifierTemplate: validatorBalanceKey, + }, + + // Checks if the validators exchange rate is valid + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_VALIDATOR, + IdentifierTemplate: validatorKey, + }, + // Update validator shares and power index + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_VALIDATOR, + IdentifierTemplate: validatorKey, + }, + + // Last Operation should always be a commit + *acltypes.CommitAccessOp(), + } + return accessOperations, nil +} + +func MsgUndelegateDependencyGenerator(keeper aclkeeper.Keeper, ctx sdk.Context, msg sdk.Msg) ([]sdkacltypes.AccessOperation, error) { + msgUndelegate, ok := msg.(*stakingtypes.MsgUndelegate) + if !ok { + return []sdkacltypes.AccessOperation{}, ErrorInvalidMsgType + } + bondedModuleAdr := keeper.AccountKeeper.GetModuleAddress(stakingtypes.BondedPoolName) + notBondedModuleAdr := keeper.AccountKeeper.GetModuleAddress(stakingtypes.NotBondedPoolName) + + delegateAddr, _ := sdk.AccAddressFromBech32(msgUndelegate.DelegatorAddress) + validatorAddr, _ := sdk.ValAddressFromBech32(msgUndelegate.ValidatorAddress) + + validator, _ := keeper.StakingKeeper.GetValidator(ctx, validatorAddr) + validatorCons, _ := validator.GetConsAddr() + validatorAddrCons := string(stakingtypes.GetValidatorByConsAddrKey(validatorCons)) + + delegationKey := string(stakingtypes.GetDelegationKey(delegateAddr, validatorAddr)) + validatorKey := string(stakingtypes.GetValidatorKey(validatorAddr)) + delegatorBalanceKey := string(banktypes.CreateAccountBalancesPrefixFromBech32(msgUndelegate.DelegatorAddress)) + validatorBalanceKey := string(banktypes.CreateAccountBalancesPrefixFromBech32(msgUndelegate.ValidatorAddress)) + + accessOperations := []sdkacltypes.AccessOperation{ + // Treat Delegations and Undelegations to have the same ACL since they are highly coupled, no point in finer granularization + + // Get delegation/redelegations and error checking + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_DELEGATION, + IdentifierTemplate: delegationKey, + }, + // Update/delete delegation and update redelegation + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_DELEGATION, + IdentifierTemplate: delegationKey, + }, + + // Check Unbonding + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_UNBONDING_DELEGATION, + IdentifierTemplate: string(stakingtypes.GetUBDKey(delegateAddr, validatorAddr)), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_UNBONDING_DELEGATION, + IdentifierTemplate: string(stakingtypes.GetUBDKey(delegateAddr, validatorAddr)), + }, + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_UNBONDING_DELEGATION_VAL, + IdentifierTemplate: string(stakingtypes.GetUBDsByValIndexKey(validatorAddr)), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_UNBONDING_DELEGATION_VAL, + IdentifierTemplate: string(stakingtypes.GetUBDsByValIndexKey(validatorAddr)), + }, + + // Testing + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_UNBONDING_DELEGATION, + IdentifierTemplate: delegationKey, + }, + + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_UNBONDING, + IdentifierTemplate: string(stakingtypes.UnbondingQueueKey), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_UNBONDING, + IdentifierTemplate: string(stakingtypes.UnbondingQueueKey), + }, + + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_VALIDATORS_CON_ADDR, + IdentifierTemplate: validatorAddrCons, + }, + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_VALIDATORS_CON_ADDR, + IdentifierTemplate: string(stakingtypes.GetUBDsByValIndexKey(validatorAddr)), + }, + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_VALIDATORS_BY_POWER, + IdentifierTemplate: string(stakingtypes.GetValidatorsByPowerIndexKey(validator, keeper.StakingKeeper.PowerReduction(ctx))), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_VALIDATORS_BY_POWER, + IdentifierTemplate: string(stakingtypes.GetValidatorsByPowerIndexKey(validator, keeper.StakingKeeper.PowerReduction(ctx))), + }, + + // Before Unbond Distribution Hook + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_DELEGATOR_STARTING_INFO, + IdentifierTemplate: string(distributiontypes.GetDelegatorStartingInfoKey(validatorAddr, delegateAddr)), + }, + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_VAL_CURRENT_REWARDS, + IdentifierTemplate: string(distributiontypes.GetValidatorCurrentRewardsKey(validatorAddr)), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_VAL_CURRENT_REWARDS, + IdentifierTemplate: string(distributiontypes.GetValidatorCurrentRewardsKey(validatorAddr)), + }, + + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_OUTSTANDING_REWARDS, + IdentifierTemplate: string(distributiontypes.GetValidatorOutstandingRewardsKey(validatorAddr)), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_OUTSTANDING_REWARDS, + IdentifierTemplate: string(distributiontypes.GetValidatorOutstandingRewardsKey(validatorAddr)), + }, + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_FEE_POOL, + IdentifierTemplate: string(distributiontypes.FeePoolKey), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_FEE_POOL, + IdentifierTemplate: string(distributiontypes.FeePoolKey), + }, + + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_VAL_HISTORICAL_REWARDS, + IdentifierTemplate: string(distributiontypes.GetValidatorHistoricalRewardsPrefix(validatorAddr)), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_VAL_HISTORICAL_REWARDS, + IdentifierTemplate: string(distributiontypes.GetValidatorHistoricalRewardsPrefix(validatorAddr)), + }, + + // Gets Module Account information + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_AUTH_ADDRESS_STORE, + IdentifierTemplate: string(authtypes.AddressStoreKey(bondedModuleAdr)), + }, + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_AUTH_ADDRESS_STORE, + IdentifierTemplate: string(authtypes.AddressStoreKey(notBondedModuleAdr)), + }, + + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_DELEGATOR_STARTING_INFO, + IdentifierTemplate: string(distributiontypes.GetDelegatorStartingInfoKey(validatorAddr, delegateAddr)), + }, + + // Update the delegator and validator account balances + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_BANK_BALANCES, + IdentifierTemplate: delegatorBalanceKey, + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_BANK_BALANCES, + IdentifierTemplate: delegatorBalanceKey, + }, + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_BANK_BALANCES, + IdentifierTemplate: validatorBalanceKey, + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_BANK_BALANCES, + IdentifierTemplate: validatorBalanceKey, + }, + + // Checks if the validators exchange rate is valid + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_VALIDATOR, + IdentifierTemplate: validatorKey, + }, + // Update validator shares and power index + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_VALIDATOR, + IdentifierTemplate: validatorKey, + }, + + // Last Operation should always be a commit + *acltypes.CommitAccessOp(), + } + + return accessOperations, nil +} + +func MsgBeginRedelegateDependencyGenerator(keeper aclkeeper.Keeper, ctx sdk.Context, msg sdk.Msg) ([]sdkacltypes.AccessOperation, error) { + msgBeingRedelegate, ok := msg.(*stakingtypes.MsgBeginRedelegate) + if !ok { + return []sdkacltypes.AccessOperation{}, ErrorInvalidMsgType + } + bondedModuleAdr := keeper.AccountKeeper.GetModuleAddress(stakingtypes.BondedPoolName) + notBondedModuleAdr := keeper.AccountKeeper.GetModuleAddress(stakingtypes.NotBondedPoolName) + + delegateAddr, _ := sdk.AccAddressFromBech32(msgBeingRedelegate.DelegatorAddress) + srcValidatorAddr, _ := sdk.ValAddressFromBech32(msgBeingRedelegate.ValidatorSrcAddress) + dstValidatorAddr, _ := sdk.ValAddressFromBech32(msgBeingRedelegate.ValidatorDstAddress) + + srcValidator, _ := keeper.StakingKeeper.GetValidator(ctx, srcValidatorAddr) + dstValidator, _ := keeper.StakingKeeper.GetValidator(ctx, dstValidatorAddr) + + srcDelegationKey := string(stakingtypes.GetDelegationKey(delegateAddr, srcValidatorAddr)) + dstDelegationKey := string(stakingtypes.GetDelegationKey(delegateAddr, dstValidatorAddr)) + + srcValidatorKey := string(stakingtypes.GetValidatorKey(srcValidatorAddr)) + dstValidatorKey := string(stakingtypes.GetValidatorKey(dstValidatorAddr)) + + dstValidatorBalanceKey := string(banktypes.CreateAccountBalancesPrefixFromBech32(msgBeingRedelegate.ValidatorDstAddress)) + + accessOperations := []sdkacltypes.AccessOperation{ + // Treat Delegations and Undelegations to have the same ACL since they are highly coupled, no point in finer granularization + + // Get delegation/redelegations and error checking + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_DELEGATION, + IdentifierTemplate: srcDelegationKey, + }, + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_DELEGATION, + IdentifierTemplate: dstDelegationKey, + }, + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_REDELEGATION, + IdentifierTemplate: string(stakingtypes.GetREDsKey(delegateAddr)), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_REDELEGATION, + IdentifierTemplate: string(stakingtypes.GetREDsKey(delegateAddr)), + }, + + // Update/delete delegation and update redelegation + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_DELEGATION, + IdentifierTemplate: dstDelegationKey, + }, + + // Check Unbonding + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_VALIDATORS_BY_POWER, + IdentifierTemplate: string(stakingtypes.GetValidatorsByPowerIndexKey(srcValidator, keeper.StakingKeeper.PowerReduction(ctx))), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_VALIDATORS_BY_POWER, + IdentifierTemplate: string(stakingtypes.GetValidatorsByPowerIndexKey(srcValidator, keeper.StakingKeeper.PowerReduction(ctx))), + }, + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_VALIDATORS_BY_POWER, + IdentifierTemplate: string(stakingtypes.GetValidatorsByPowerIndexKey(dstValidator, keeper.StakingKeeper.PowerReduction(ctx))), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_VALIDATORS_BY_POWER, + IdentifierTemplate: string(stakingtypes.GetValidatorsByPowerIndexKey(dstValidator, keeper.StakingKeeper.PowerReduction(ctx))), + }, + + // Before Unbond Distribution Hook + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_DELEGATOR_STARTING_INFO, + IdentifierTemplate: string(distributiontypes.GetDelegatorStartingInfoKey(srcValidatorAddr, delegateAddr)), + }, + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_DELEGATOR_STARTING_INFO, + IdentifierTemplate: string(distributiontypes.GetDelegatorStartingInfoKey(dstValidatorAddr, delegateAddr)), + }, + + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_VAL_CURRENT_REWARDS, + IdentifierTemplate: string(distributiontypes.GetValidatorCurrentRewardsKey(srcValidatorAddr)), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_VAL_CURRENT_REWARDS, + IdentifierTemplate: string(distributiontypes.GetValidatorCurrentRewardsKey(srcValidatorAddr)), + }, + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_VAL_CURRENT_REWARDS, + IdentifierTemplate: string(distributiontypes.GetValidatorCurrentRewardsKey(dstValidatorAddr)), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_VAL_CURRENT_REWARDS, + IdentifierTemplate: string(distributiontypes.GetValidatorCurrentRewardsKey(dstValidatorAddr)), + }, + + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_OUTSTANDING_REWARDS, + IdentifierTemplate: string(distributiontypes.GetValidatorOutstandingRewardsKey(srcValidatorAddr)), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_OUTSTANDING_REWARDS, + IdentifierTemplate: string(distributiontypes.GetValidatorOutstandingRewardsKey(srcValidatorAddr)), + }, + + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_FEE_POOL, + IdentifierTemplate: string(distributiontypes.FeePoolKey), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_FEE_POOL, + IdentifierTemplate: string(distributiontypes.FeePoolKey), + }, + + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_VAL_HISTORICAL_REWARDS, + IdentifierTemplate: string(distributiontypes.GetValidatorHistoricalRewardsPrefix(srcValidatorAddr)), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_VAL_HISTORICAL_REWARDS, + IdentifierTemplate: string(distributiontypes.GetValidatorHistoricalRewardsPrefix(srcValidatorAddr)), + }, + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_VAL_HISTORICAL_REWARDS, + IdentifierTemplate: string(distributiontypes.GetValidatorHistoricalRewardsPrefix(dstValidatorAddr)), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_VAL_HISTORICAL_REWARDS, + IdentifierTemplate: string(distributiontypes.GetValidatorHistoricalRewardsPrefix(dstValidatorAddr)), + }, + + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_REDELEGATION_QUEUE, + IdentifierTemplate: string(stakingtypes.RedelegationQueueKey), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_REDELEGATION_QUEUE, + IdentifierTemplate: string(stakingtypes.RedelegationQueueKey), + }, + + // Gets Module Account information + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_AUTH_ADDRESS_STORE, + IdentifierTemplate: string(authtypes.AddressStoreKey(bondedModuleAdr)), + }, + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_AUTH_ADDRESS_STORE, + IdentifierTemplate: string(authtypes.AddressStoreKey(notBondedModuleAdr)), + }, + + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_DELEGATOR_STARTING_INFO, + IdentifierTemplate: string(distributiontypes.GetDelegatorStartingInfoKey(srcValidatorAddr, delegateAddr)), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_DISTRIBUTION_DELEGATOR_STARTING_INFO, + IdentifierTemplate: string(distributiontypes.GetDelegatorStartingInfoKey(dstValidatorAddr, delegateAddr)), + }, + + // Update the delegator and validator account balances + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_BANK_BALANCES, + IdentifierTemplate: dstValidatorBalanceKey, + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_BANK_BALANCES, + IdentifierTemplate: dstValidatorBalanceKey, + }, + + // Checks if the validators exchange rate is valid + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_REDELEGATION_VAL_SRC, + IdentifierTemplate: string(stakingtypes.GetREDByValSrcIndexKey( + delegateAddr, + srcValidatorAddr, + dstValidatorAddr, + )), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_REDELEGATION_VAL_DST, + IdentifierTemplate: string(stakingtypes.GetREDByValDstIndexKey( + delegateAddr, + srcValidatorAddr, + dstValidatorAddr, + )), + }, + + // Checks if the validators exchange rate is valid + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_VALIDATOR, + IdentifierTemplate: srcValidatorKey, + }, + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_VALIDATOR, + IdentifierTemplate: dstValidatorKey, + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_VALIDATOR, + IdentifierTemplate: srcValidatorKey, + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_STAKING_VALIDATOR, + IdentifierTemplate: dstValidatorKey, + }, + + // Last Operation should always be a commit + *acltypes.CommitAccessOp(), + } + return accessOperations, nil +} diff --git a/aclmapping/staking/mappings_test.go b/aclmapping/staking/mappings_test.go new file mode 100644 index 0000000000..e1f9f64e74 --- /dev/null +++ b/aclmapping/staking/mappings_test.go @@ -0,0 +1,342 @@ +package aclstakingmapping_test + +import ( + "fmt" + "testing" + "time" + + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkacltypes "github.com/cosmos/cosmos-sdk/types/accesscontrol" + "github.com/cosmos/cosmos-sdk/x/staking/keeper" + stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" + "github.com/cosmos/cosmos-sdk/x/staking/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + stakingacl "github.com/sei-protocol/sei-chain/aclmapping/staking" + aclutils "github.com/sei-protocol/sei-chain/aclmapping/utils" + "github.com/sei-protocol/sei-chain/app/apptesting" + oracletypes "github.com/sei-protocol/sei-chain/x/oracle/types" + + acltypes "github.com/cosmos/cosmos-sdk/x/accesscontrol/types" + "github.com/sei-protocol/sei-chain/app" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +type KeeperTestSuite struct { + apptesting.KeeperTestHelper + + queryClient stakingtypes.QueryClient + msgServer stakingtypes.MsgServer + // defaultDenom is on the suite, as it depends on the creator test address. + defaultDenom string + defaultExchangeRate string + initalBalance sdk.Coins + + validator sdk.ValAddress + newValidator sdk.ValAddress + delegateMsg *types.MsgDelegate + undelegateMsg *types.MsgUndelegate + redelegateMsg *types.MsgBeginRedelegate +} + +func TestKeeperTestSuite(t *testing.T) { + suite.Run(t, new(KeeperTestSuite)) +} + +// Runs before each test case +func (suite *KeeperTestSuite) SetupTest() { + suite.Setup() +} + +// Explicitly only run once during setup +func (suite *KeeperTestSuite) PrepareTest() { + suite.defaultDenom = "usei" + suite.defaultExchangeRate = fmt.Sprintf("%dusei", sdk.NewDec(1700)) + + suite.initalBalance = sdk.Coins{sdk.NewInt64Coin(suite.defaultDenom, 100000000000)} + suite.initalBalance = sdk.Coins{sdk.NewInt64Coin("stake", 100000000000)} + suite.FundAcc(suite.TestAccs[0], suite.initalBalance) + + suite.queryClient = stakingtypes.NewQueryClient(suite.QueryHelper) + suite.msgServer = stakingkeeper.NewMsgServerImpl(suite.App.StakingKeeper) + + msgValidator := sdkacltypes.NewMsgValidator(aclutils.StoreKeyToResourceTypePrefixMap) + suite.Ctx = suite.Ctx.WithMsgValidator(msgValidator) + + suite.Ctx = suite.Ctx.WithBlockHeight(10) + suite.Ctx = suite.Ctx.WithBlockTime(time.Unix(333, 0)) + suite.validator = suite.SetupValidator(stakingtypes.Bonded) + suite.newValidator = suite.SetupValidator(stakingtypes.Unbonded) + + notBondedPool := suite.App.StakingKeeper.GetNotBondedPool(suite.Ctx) + suite.App.AccountKeeper.SetModuleAccount(suite.Ctx, notBondedPool) + + validator, _ := suite.App.StakingKeeper.GetValidator(suite.Ctx, suite.validator) + suite.App.StakingKeeper.SetValidatorByConsAddr(suite.Ctx, validator) + + newValidator, _ := suite.App.StakingKeeper.GetValidator(suite.Ctx, suite.newValidator) + suite.App.StakingKeeper.SetValidatorByConsAddr(suite.Ctx, newValidator) + + valTokens := suite.App.StakingKeeper.TokensFromConsensusPower(suite.Ctx, 10) + validator, issuedShares := validator.AddTokensFromDel(valTokens) + validator = keeper.TestingUpdateValidator(suite.App.StakingKeeper, suite.Ctx, validator, true) + + newValTokens := suite.App.StakingKeeper.TokensFromConsensusPower(suite.Ctx, 10) + newValidator, newIssuedShares := newValidator.AddTokensFromDel(newValTokens) + newValidator = keeper.TestingUpdateValidator(suite.App.StakingKeeper, suite.Ctx, validator, true) + + val0AccAddr := sdk.AccAddress(suite.validator) + val1AccAddr := sdk.AccAddress(suite.newValidator) + + val0selfDelegation := types.NewDelegation(val0AccAddr, suite.validator, issuedShares) + val1SelfDelegation := types.NewDelegation(val1AccAddr, suite.validator, newIssuedShares) + + suite.App.StakingKeeper.SetDelegation(suite.Ctx, val0selfDelegation) + suite.App.StakingKeeper.SetDelegation(suite.Ctx, val1SelfDelegation) + + bondedPool := suite.App.StakingKeeper.GetBondedPool(suite.Ctx) + suite.App.AccountKeeper.SetModuleAccount(suite.Ctx, bondedPool) + + suite.delegateMsg = &stakingtypes.MsgDelegate{ + Amount: sdk.NewInt64Coin("stake", 10), + ValidatorAddress: suite.validator.String(), + DelegatorAddress: suite.TestAccs[0].String(), + } + suite.undelegateMsg = &stakingtypes.MsgUndelegate{ + Amount: sdk.NewInt64Coin("stake", 10), + ValidatorAddress: suite.validator.String(), + DelegatorAddress: suite.TestAccs[0].String(), + } + suite.redelegateMsg = &stakingtypes.MsgBeginRedelegate{ + Amount: sdk.NewInt64Coin("stake", 10), + ValidatorSrcAddress: suite.validator.String(), + ValidatorDstAddress: suite.newValidator.String(), + DelegatorAddress: suite.TestAccs[0].String(), + } + + _, err := suite.msgServer.Delegate( + sdk.WrapSDKContext(suite.Ctx), + suite.delegateMsg, + ) + if err != nil { + panic(err) + } +} + + +func (suite *KeeperTestSuite) TestMsgUndelegateDependencies() { + suite.PrepareTest() + tests := []struct { + name string + expectedError error + msg *stakingtypes.MsgUndelegate + dynamicDep bool + }{ + { + name: "default vote", + msg: suite.undelegateMsg, + expectedError: nil, + dynamicDep: true, + }, + { + name: "dont check synchronous", + msg: suite.undelegateMsg, + expectedError: nil, + dynamicDep: false, + }, + } + for _, tc := range tests { + suite.Run(fmt.Sprintf("Test Case: %s", tc.name), func() { + + handlerCtx, cms := aclutils.CacheTxContext(suite.Ctx) + _, err := suite.msgServer.Undelegate( + sdk.WrapSDKContext(handlerCtx), + tc.msg, + ) + depdenencies , _ := stakingacl.MsgUndelegateDependencyGenerator( + suite.App.AccessControlKeeper, + handlerCtx, + tc.msg, + ) + + if !tc.dynamicDep { + depdenencies = sdkacltypes.SynchronousAccessOps() + } + + if tc.expectedError != nil { + suite.Require().EqualError(err, tc.expectedError.Error()) + } else { + suite.Require().NoError(err) + } + + missing := handlerCtx.MsgValidator().ValidateAccessOperations(depdenencies, cms.GetEvents()) + suite.Require().Empty(missing) + }) + } +} + +func (suite *KeeperTestSuite) TestMsgRedelegateDependencies() { + suite.PrepareTest() + tests := []struct { + name string + expectedError error + msg *stakingtypes.MsgBeginRedelegate + dynamicDep bool + }{ + { + name: "default vote", + msg: suite.redelegateMsg, + expectedError: nil, + dynamicDep: true, + }, + // { + // name: "dont check synchronous", + // msg: suite.redelegateMsg, + // expectedError: nil, + // dynamicDep: false, + // }, + } + for _, tc := range tests { + suite.Run(fmt.Sprintf("Test Case: %s", tc.name), func() { + + handlerCtx, cms := aclutils.CacheTxContext(suite.Ctx) + _, err := suite.msgServer.BeginRedelegate( + sdk.WrapSDKContext(handlerCtx), + tc.msg, + ) + depdenencies , _ := stakingacl.MsgBeginRedelegateDependencyGenerator( + suite.App.AccessControlKeeper, + handlerCtx, + tc.msg, + ) + + if !tc.dynamicDep { + depdenencies = sdkacltypes.SynchronousAccessOps() + } + + if tc.expectedError != nil { + suite.Require().EqualError(err, tc.expectedError.Error()) + } else { + suite.Require().NoError(err) + } + + missing := handlerCtx.MsgValidator().ValidateAccessOperations(depdenencies, cms.GetEvents()) + suite.Require().Empty(missing) + }) + } +} + + +func (suite *KeeperTestSuite) TestMsgDelegateDependencies() { + suite.PrepareTest() + tests := []struct { + name string + expectedError error + msg *stakingtypes.MsgDelegate + dynamicDep bool + }{ + { + name: "default vote", + msg: suite.delegateMsg, + expectedError: nil, + dynamicDep: true, + }, + { + name: "dont check synchronous", + msg: suite.delegateMsg, + expectedError: nil, + dynamicDep: false, + }, + } + for _, tc := range tests { + suite.Run(fmt.Sprintf("Test Case: %s", tc.name), func() { + handlerCtx, cms := aclutils.CacheTxContext(suite.Ctx) + _, err := suite.msgServer.Delegate( + sdk.WrapSDKContext(handlerCtx), + tc.msg, + ) + depdenencies , _ := stakingacl.MsgDelegateDependencyGenerator( + suite.App.AccessControlKeeper, + handlerCtx, + tc.msg, + ) + + if !tc.dynamicDep { + depdenencies = sdkacltypes.SynchronousAccessOps() + } + + if tc.expectedError != nil { + suite.Require().EqualError(err, tc.expectedError.Error()) + } else { + suite.Require().NoError(err) + } + + missing := handlerCtx.MsgValidator().ValidateAccessOperations(depdenencies, cms.GetEvents()) + suite.Require().Empty(missing) + }) + } +} + +func TestGeneratorInvalidMessageTypes(t *testing.T) { + tm := time.Now().UTC() + valPub := secp256k1.GenPrivKey().PubKey() + testWrapper := app.NewTestWrapper(t, tm, valPub) + + stakingDelegate := stakingtypes.MsgDelegate{ + DelegatorAddress: "delegator", + ValidatorAddress: "validator", + Amount: sdk.Coin{Denom: "usei", Amount: sdk.NewInt(5)}, + } + oracleVote := oracletypes.MsgAggregateExchangeRateVote{ + ExchangeRates: "1usei", + Feeder: "test", + Validator: "validator", + } + + _, err := stakingacl.MsgUndelegateDependencyGenerator(testWrapper.App.AccessControlKeeper, testWrapper.Ctx, &oracleVote) + require.Error(t, err) + _, err = stakingacl.MsgUndelegateDependencyGenerator(testWrapper.App.AccessControlKeeper, testWrapper.Ctx, &stakingDelegate) + require.Error(t, err) + _, err = stakingacl.MsgUndelegateDependencyGenerator(testWrapper.App.AccessControlKeeper, testWrapper.Ctx, &stakingDelegate) + require.Error(t, err) + +} + +func (suite *KeeperTestSuite) TestMsgDelegateGenerator() { + suite.PrepareTest() + stakingDelegate := suite.delegateMsg + + accessOps, err := stakingacl.MsgDelegateDependencyGenerator( + suite.App.AccessControlKeeper, + suite.Ctx, + stakingDelegate, + ) + require.NoError(suite.T(), err) + err = acltypes.ValidateAccessOps(accessOps) + require.NoError(suite.T(), err) +} + +func (suite *KeeperTestSuite) TestMsgUndelegateGenerator() { + suite.PrepareTest() + accessOps, err := stakingacl.MsgUndelegateDependencyGenerator( + suite.App.AccessControlKeeper, + suite.Ctx, + suite.undelegateMsg, + ) + require.NoError(suite.T(), err) + err = acltypes.ValidateAccessOps(accessOps) + require.NoError(suite.T(), err) +} + +func (suite *KeeperTestSuite) TestMsgBeginRedelegateGenerator() { + suite.PrepareTest() + accessOps, err := stakingacl.MsgBeginRedelegateDependencyGenerator( + suite.App.AccessControlKeeper, + suite.Ctx, + suite.redelegateMsg, + ) + require.NoError(suite.T(), err) + err = acltypes.ValidateAccessOps(accessOps) + require.NoError(suite.T(), err) +} diff --git a/aclmapping/tokenfactory/mappings.go b/aclmapping/tokenfactory/mappings.go new file mode 100644 index 0000000000..1a48909742 --- /dev/null +++ b/aclmapping/tokenfactory/mappings.go @@ -0,0 +1,200 @@ +package aclTokenFactorymapping + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkacltypes "github.com/cosmos/cosmos-sdk/types/accesscontrol" + aclkeeper "github.com/cosmos/cosmos-sdk/x/accesscontrol/keeper" + acltypes "github.com/cosmos/cosmos-sdk/x/accesscontrol/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + tfktypes "github.com/sei-protocol/sei-chain/x/tokenfactory/types" +) + +var ErrInvalidMessageType = fmt.Errorf("invalid message received for TokenFactory Module") + +func GetTokenFactoryDependencyGenerators() aclkeeper.DependencyGeneratorMap { + dependencyGeneratorMap := make(aclkeeper.DependencyGeneratorMap) + MintMsgKey := acltypes.GenerateMessageKey(&tfktypes.MsgMint{}) + dependencyGeneratorMap[MintMsgKey] = TokenFactoryMintDependencyGenerator + + BurnMsgKey := acltypes.GenerateMessageKey(&tfktypes.MsgBurn{}) + dependencyGeneratorMap[BurnMsgKey] = TokenFactoryBurnDependencyGenerator + + return dependencyGeneratorMap +} + +func TokenFactoryMintDependencyGenerator(keeper aclkeeper.Keeper, ctx sdk.Context, msg sdk.Msg) ([]sdkacltypes.AccessOperation, error) { + mintMsg, ok := msg.(*tfktypes.MsgMint) + if !ok { + return []sdkacltypes.AccessOperation{}, ErrInvalidMessageType + } + moduleAdr := keeper.AccountKeeper.GetModuleAddress(tfktypes.ModuleName) + denom := mintMsg.GetAmount().Denom + + denomMetaDataKey := append([]byte(tfktypes.DenomAuthorityMetadataKey), []byte(denom)...) + tokenfactoryDenomKey := tfktypes.GetDenomPrefixStore(denom) + bankDenomMetaDataKey := banktypes.DenomMetadataKey(denom) + supplyKey := string(append(banktypes.SupplyKey, []byte(denom)...)) + + return []sdkacltypes.AccessOperation{ + // Reads denom data From BankKeeper + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_BANK_DENOM, + IdentifierTemplate: string(bankDenomMetaDataKey), + }, + + // Gets Authoritity data related to the denom + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_TOKENFACTORY_METADATA, + IdentifierTemplate: string(denomMetaDataKey), + }, + + // Gets Authoritity data related to the denom + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_TOKENFACTORY_DENOM, + IdentifierTemplate: string(tokenfactoryDenomKey), + }, + + // Gets Module Account information + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_AUTH_ADDRESS_STORE, + IdentifierTemplate: string(authtypes.AddressStoreKey(moduleAdr)), + }, + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_BANK_BALANCES, + IdentifierTemplate: string(banktypes.CreateAccountBalancesPrefix(moduleAdr)), + }, + + // Deposit into Sender's Bank Balance + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_BANK_BALANCES, + IdentifierTemplate: string(banktypes.CreateAccountBalancesPrefixFromBech32(mintMsg.GetSender())), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_BANK_BALANCES, + IdentifierTemplate: string(banktypes.CreateAccountBalancesPrefixFromBech32(mintMsg.GetSender())), + }, + + // Read and update supply after burn + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_BANK_SUPPLY, + IdentifierTemplate: supplyKey, + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_BANK_SUPPLY, + IdentifierTemplate: supplyKey, + }, + + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_AUTH_ADDRESS_STORE, + IdentifierTemplate: string(authtypes.CreateAddressStoreKeyFromBech32(mintMsg.GetSender())), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_AUTH_ADDRESS_STORE, + IdentifierTemplate: string(authtypes.CreateAddressStoreKeyFromBech32(mintMsg.GetSender())), + }, + + // Coins removed from Module account (Deferred) + + // Last Operation should always be a commit + *acltypes.CommitAccessOp(), + }, nil +} + +func TokenFactoryBurnDependencyGenerator(keeper aclkeeper.Keeper, ctx sdk.Context, msg sdk.Msg) ([]sdkacltypes.AccessOperation, error) { + burnMsg, ok := msg.(*tfktypes.MsgBurn) + if !ok { + return []sdkacltypes.AccessOperation{}, ErrInvalidMessageType + } + + moduleAdr := keeper.AccountKeeper.GetModuleAddress(tfktypes.ModuleName) + denom := burnMsg.GetAmount().Denom + + denomMetaDataKey := append([]byte(tfktypes.DenomAuthorityMetadataKey), []byte(denom)...) + tokenfactoryDenomKey := tfktypes.GetDenomPrefixStore(denom) + bankDenomMetaDataKey := banktypes.DenomMetadataKey(denom) + supplyKey := string(append(banktypes.SupplyKey, []byte(denom)...)) + return []sdkacltypes.AccessOperation{ + // Reads denom data From BankKeeper + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_BANK_DENOM, + IdentifierTemplate: string(bankDenomMetaDataKey), + }, + + // Gets Authoritity data related to the denom + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_TOKENFACTORY_METADATA, + IdentifierTemplate: string(denomMetaDataKey), + }, + + // Gets Authoritity data related to the denom + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_TOKENFACTORY_DENOM, + IdentifierTemplate: string(tokenfactoryDenomKey), + }, + + // Gets Module Account Balance + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_AUTH_ADDRESS_STORE, + IdentifierTemplate: string(authtypes.AddressStoreKey(moduleAdr)), + }, + + // Sends from Sender to Module account (deferred deposit) + // Checks balance for receiver + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_BANK_BALANCES, + IdentifierTemplate: string(banktypes.CreateAccountBalancesPrefixFromBech32(burnMsg.GetSender())), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_BANK_BALANCES, + IdentifierTemplate: string(banktypes.CreateAccountBalancesPrefixFromBech32(burnMsg.GetSender())), + }, + + // Read and update supply after burn + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_BANK_SUPPLY, + IdentifierTemplate: supplyKey, + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_BANK_SUPPLY, + IdentifierTemplate: supplyKey, + }, + + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_AUTH_ADDRESS_STORE, + IdentifierTemplate: string(authtypes.CreateAddressStoreKeyFromBech32(burnMsg.GetSender())), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_AUTH_ADDRESS_STORE, + IdentifierTemplate: string(authtypes.CreateAddressStoreKeyFromBech32(burnMsg.GetSender())), + }, + + // Coins removed from Module account (Deferred) + + // Last Operation should always be a commit + *acltypes.CommitAccessOp(), + }, nil +} diff --git a/aclmapping/tokenfactory/mappings_test.go b/aclmapping/tokenfactory/mappings_test.go new file mode 100644 index 0000000000..70792a373d --- /dev/null +++ b/aclmapping/tokenfactory/mappings_test.go @@ -0,0 +1,234 @@ +package aclTokenFactorymapping_test + +import ( + "fmt" + "testing" + + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + "github.com/cosmos/cosmos-sdk/simapp" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkacltypes "github.com/cosmos/cosmos-sdk/types/accesscontrol" + acltypes "github.com/cosmos/cosmos-sdk/x/accesscontrol/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + tkfactory "github.com/sei-protocol/sei-chain/aclmapping/tokenfactory" + aclutils "github.com/sei-protocol/sei-chain/aclmapping/utils" + "github.com/sei-protocol/sei-chain/app/apptesting" + oracletypes "github.com/sei-protocol/sei-chain/x/oracle/types" + tokenfactorykeeper "github.com/sei-protocol/sei-chain/x/tokenfactory/keeper" + "github.com/sei-protocol/sei-chain/x/tokenfactory/types" + tokenfactorytypes "github.com/sei-protocol/sei-chain/x/tokenfactory/types" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" +) +type KeeperTestSuite struct { + apptesting.KeeperTestHelper + + queryClient tokenfactorytypes.QueryClient + msgServer tokenfactorytypes.MsgServer + // defaultDenom is on the suite, as it depends on the creator test address. + defaultDenom string + testDenom string + initalBalance sdk.Coins +} + +func TestKeeperTestSuite(t *testing.T) { + suite.Run(t, new(KeeperTestSuite)) +} + +// Runs before each test case +func (suite *KeeperTestSuite) SetupTest() { + suite.Setup() +} + +// Explicitly only run once during setup +func (suite *KeeperTestSuite) PrepareTest() { + suite.defaultDenom = "usei" + suite.testDenom = "foocoins" + + suite.initalBalance = sdk.Coins{sdk.NewInt64Coin(suite.defaultDenom, 100000000000)} + suite.FundAcc(suite.TestAccs[0], suite.initalBalance) + + suite.SetupTokenFactory() + suite.queryClient = tokenfactorytypes.NewQueryClient(suite.QueryHelper) + suite.msgServer = tokenfactorykeeper.NewMsgServerImpl(suite.App.TokenFactoryKeeper) + + res, err := suite.msgServer.CreateDenom( + sdk.WrapSDKContext(suite.Ctx), + types.NewMsgCreateDenom(suite.TestAccs[0].String(), suite.testDenom), + ) + suite.App.BankKeeper.WriteDeferredOperations(suite.Ctx) + + if err != nil { + panic(err) + } + suite.testDenom = res.GetNewTokenDenom() + + _, err = suite.msgServer.Mint( + sdk.WrapSDKContext(suite.Ctx), + types.NewMsgMint(suite.TestAccs[0].String(), sdk.NewInt64Coin(suite.testDenom, 1000000)), + ) + if err != nil { + panic(err) + } + suite.App.BankKeeper.WriteDeferredOperations(suite.Ctx) + + msgValidator := sdkacltypes.NewMsgValidator(aclutils.StoreKeyToResourceTypePrefixMap) + suite.Ctx = suite.Ctx.WithMsgValidator(msgValidator) +} + +func cacheTxContext(ctx sdk.Context) (sdk.Context, sdk.CacheMultiStore) { + ms := ctx.MultiStore() + msCache := ms.CacheMultiStore() + return ctx.WithMultiStore(msCache), msCache +} + +func (suite *KeeperTestSuite) TestMsgBurnDependencies() { + suite.PrepareTest() + + burnAmount := sdk.NewInt64Coin(suite.testDenom, 10) + addr1 := suite.TestAccs[0].String() + tests := []struct { + name string + expectedError error + msg *tokenfactorytypes.MsgBurn + dynamicDep bool + }{ + { + name: "default burn", + msg: tokenfactorytypes.NewMsgBurn(addr1, burnAmount), + expectedError: nil, + dynamicDep: true, + }, + { + name: "dont check synchronous", + msg: tokenfactorytypes.NewMsgBurn(addr1, burnAmount), + expectedError: nil, + dynamicDep: false, + }, + } + for _, tc := range tests { + suite.Run(fmt.Sprintf("Test Case: %s", tc.name), func() { + handlerCtx, cms := cacheTxContext(suite.Ctx) + _, err := suite.msgServer.Burn( + sdk.WrapSDKContext(handlerCtx), + tc.msg, + ) + suite.App.BankKeeper.WriteDeferredOperations(suite.Ctx) + + depdenencies , _ := tkfactory.TokenFactoryBurnDependencyGenerator( + suite.App.AccessControlKeeper, + handlerCtx, + tc.msg, + ) + + if !tc.dynamicDep { + depdenencies = sdkacltypes.SynchronousAccessOps() + } + + if tc.expectedError != nil { + suite.Require().EqualError(err, tc.expectedError.Error()) + } else { + suite.Require().NoError(err) + } + + missing := handlerCtx.MsgValidator().ValidateAccessOperations(depdenencies, cms.GetEvents()) + suite.Require().Empty(missing) + }) + } +} + +func (suite *KeeperTestSuite) TestMsgMintDependencies() { + suite.PrepareTest() + + burnAmount := sdk.NewInt64Coin(suite.testDenom, 10) + addr1 := suite.TestAccs[0].String() + tests := []struct { + name string + expectedError error + msg *tokenfactorytypes.MsgMint + dynamicDep bool + }{ + { + name: "default mint", + msg: tokenfactorytypes.NewMsgMint(addr1, burnAmount), + expectedError: nil, + dynamicDep: true, + }, + { + name: "dont check synchronous", + msg: tokenfactorytypes.NewMsgMint(addr1, burnAmount), + expectedError: nil, + dynamicDep: false, + }, + } + for _, tc := range tests { + suite.Run(fmt.Sprintf("Test Case: %s", tc.name), func() { + handlerCtx, cms := cacheTxContext(suite.Ctx) + _, err := suite.msgServer.Mint( + sdk.WrapSDKContext(handlerCtx), + tc.msg, + ) + suite.App.BankKeeper.WriteDeferredOperations(handlerCtx) + + depdenencies , _ := tkfactory.TokenFactoryMintDependencyGenerator( + suite.App.AccessControlKeeper, + handlerCtx, + tc.msg, + ) + + if !tc.dynamicDep { + depdenencies = sdkacltypes.SynchronousAccessOps() + } + + if tc.expectedError != nil { + suite.Require().EqualError(err, tc.expectedError.Error()) + } else { + suite.Require().NoError(err) + } + + missing := handlerCtx.MsgValidator().ValidateAccessOperations(depdenencies, cms.GetEvents()) + suite.Require().Empty(missing) + }) + } +} + +func TestGeneratorInvalidMessageTypes(t *testing.T) { + accs := authtypes.GenesisAccounts{} + balances := []banktypes.Balance{} + + app := simapp.SetupWithGenesisAccounts(accs, balances...) + ctx := app.BaseApp.NewContext(false, tmproto.Header{}) + + oracleVote := oracletypes.MsgAggregateExchangeRateVote{ + ExchangeRates: "1usei", + Feeder: "test", + Validator: "validator", + } + + _, err := tkfactory.TokenFactoryBurnDependencyGenerator(app.AccessControlKeeper, ctx, &oracleVote) + require.Error(t, err) +} + +func TestMsgBeginBurnDepedencyGenerator(t *testing.T) { + priv1 := secp256k1.GenPrivKey() + addr1 := sdk.AccAddress(priv1.PubKey().Address()) + + accs := authtypes.GenesisAccounts{} + balances := []banktypes.Balance{} + + app := simapp.SetupWithGenesisAccounts(accs, balances...) + ctx := app.BaseApp.NewContext(false, tmproto.Header{}) + + sendMsg := tokenfactorytypes.MsgBurn{ + Sender: addr1.String(), + Amount: sdk.NewInt64Coin("usei", 10), + } + + accessOps, err := tkfactory.TokenFactoryBurnDependencyGenerator(app.AccessControlKeeper, ctx, &sendMsg) + require.NoError(t, err) + err = acltypes.ValidateAccessOps(accessOps) + require.NoError(t, err) +} diff --git a/aclmapping/utils/identifier_templates.go b/aclmapping/utils/identifier_templates.go new file mode 100644 index 0000000000..ba79cecc2a --- /dev/null +++ b/aclmapping/utils/identifier_templates.go @@ -0,0 +1,40 @@ +package utils + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkacltypes "github.com/cosmos/cosmos-sdk/types/accesscontrol" +) + +const ( + ACCOUNT = "acc" + BANK = "bank" + AUTH = "auth" + STAKING = "staking" + TOKENFACTORY = "tokenfactory" + DefaultIDTemplate = "*" +) + +func GetIdentifierTemplatePerModule(module string, identifier string) string { + return fmt.Sprintf("%s/%s", module, identifier) +} + +func GetPrefixedIdentifierTemplatePerModule(module string, identifier string, prefix string) string { + return fmt.Sprintf("%s/%s/%s", module, prefix, identifier) +} + +func GetOracleReadAccessOpsForValAndFeeder(feederAddr sdk.Address, valAddr sdk.Address) []sdkacltypes.AccessOperation { + return []sdkacltypes.AccessOperation{ + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_ORACLE, + IdentifierTemplate: feederAddr.String(), + }, + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_ORACLE, + IdentifierTemplate: valAddr.String(), + }, + } +} diff --git a/aclmapping/utils/resource_type.go b/aclmapping/utils/resource_type.go new file mode 100644 index 0000000000..cb0fe3f2cd --- /dev/null +++ b/aclmapping/utils/resource_type.go @@ -0,0 +1,81 @@ +package utils + +import ( + aclsdktypes "github.com/cosmos/cosmos-sdk/types/accesscontrol" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + distributiontypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + dextypes "github.com/sei-protocol/sei-chain/x/dex/types" + epochtypes "github.com/sei-protocol/sei-chain/x/epoch/types" + oracletypes "github.com/sei-protocol/sei-chain/x/oracle/types" + tokenfactorytypes "github.com/sei-protocol/sei-chain/x/tokenfactory/types" +) + +var StoreKeyToResourceTypePrefixMap = aclsdktypes.StoreKeyToResourceTypePrefixMap{ + aclsdktypes.ParentNodeKey: { + aclsdktypes.ResourceType_ANY: aclsdktypes.EmptyPrefix, + aclsdktypes.ResourceType_KV: aclsdktypes.EmptyPrefix, + aclsdktypes.ResourceType_Mem: aclsdktypes.EmptyPrefix, + }, + dextypes.StoreKey: { + aclsdktypes.ResourceType_KV_DEX: aclsdktypes.EmptyPrefix, + aclsdktypes.ResourceType_DexMem: aclsdktypes.EmptyPrefix, + }, + banktypes.StoreKey: { + aclsdktypes.ResourceType_KV_BANK: aclsdktypes.EmptyPrefix, + aclsdktypes.ResourceType_KV_BANK_BALANCES: banktypes.BalancesPrefix, + aclsdktypes.ResourceType_KV_BANK_SUPPLY: banktypes.SupplyKey, + aclsdktypes.ResourceType_KV_BANK_DENOM: banktypes.DenomMetadataPrefix, + }, + authtypes.StoreKey: { + aclsdktypes.ResourceType_KV_AUTH: aclsdktypes.EmptyPrefix, + aclsdktypes.ResourceType_KV_AUTH_ADDRESS_STORE: authtypes.AddressStoreKeyPrefix, + }, + distributiontypes.StoreKey: { + aclsdktypes.ResourceType_KV_DISTRIBUTION: aclsdktypes.EmptyPrefix, + aclsdktypes.ResourceType_KV_DISTRIBUTION_FEE_POOL: distributiontypes.FeePoolKey, + aclsdktypes.ResourceType_KV_DISTRIBUTION_PROPOSER_KEY: distributiontypes.ProposerKey, + aclsdktypes.ResourceType_KV_DISTRIBUTION_OUTSTANDING_REWARDS: distributiontypes.ValidatorOutstandingRewardsPrefix, + aclsdktypes.ResourceType_KV_DISTRIBUTION_DELEGATOR_WITHDRAW_ADDR: distributiontypes.DelegatorWithdrawAddrPrefix, + aclsdktypes.ResourceType_KV_DISTRIBUTION_DELEGATOR_STARTING_INFO: distributiontypes.DelegatorStartingInfoPrefix, + aclsdktypes.ResourceType_KV_DISTRIBUTION_VAL_HISTORICAL_REWARDS: distributiontypes.ValidatorHistoricalRewardsPrefix, + aclsdktypes.ResourceType_KV_DISTRIBUTION_VAL_CURRENT_REWARDS: distributiontypes.ValidatorCurrentRewardsPrefix, + aclsdktypes.ResourceType_KV_DISTRIBUTION_VAL_ACCUM_COMMISSION: distributiontypes.ValidatorAccumulatedCommissionPrefix, + aclsdktypes.ResourceType_KV_DISTRIBUTION_SLASH_EVENT: distributiontypes.ValidatorSlashEventPrefix, + }, + oracletypes.StoreKey: { + aclsdktypes.ResourceType_KV_ORACLE: aclsdktypes.EmptyPrefix, + aclsdktypes.ResourceType_KV_ORACLE_VOTE_TARGETS: oracletypes.VoteTargetKey, + aclsdktypes.ResourceType_KV_ORACLE_AGGREGATE_VOTES: oracletypes.AggregateExchangeRateVoteKey, + aclsdktypes.ResourceType_KV_ORACLE_FEEDERS: oracletypes.FeederDelegationKey, + }, + stakingtypes.StoreKey: { + aclsdktypes.ResourceType_KV_STAKING: aclsdktypes.EmptyPrefix, + aclsdktypes.ResourceType_KV_STAKING_VALIDATION_POWER: stakingtypes.LastValidatorPowerKey, + aclsdktypes.ResourceType_KV_STAKING_TOTAL_POWER: stakingtypes.LastTotalPowerKey, + aclsdktypes.ResourceType_KV_STAKING_VALIDATOR: stakingtypes.ValidatorsKey, + aclsdktypes.ResourceType_KV_STAKING_VALIDATORS_CON_ADDR: stakingtypes.ValidatorsByConsAddrKey, + aclsdktypes.ResourceType_KV_STAKING_VALIDATORS_BY_POWER: stakingtypes.ValidatorsByPowerIndexKey, + aclsdktypes.ResourceType_KV_STAKING_DELEGATION: stakingtypes.DelegationKey, + aclsdktypes.ResourceType_KV_STAKING_UNBONDING_DELEGATION: stakingtypes.UnbondingDelegationKey, + aclsdktypes.ResourceType_KV_STAKING_UNBONDING_DELEGATION_VAL: stakingtypes.UnbondingDelegationByValIndexKey, + aclsdktypes.ResourceType_KV_STAKING_REDELEGATION: stakingtypes.RedelegationKey, + aclsdktypes.ResourceType_KV_STAKING_REDELEGATION_VAL_SRC: stakingtypes.RedelegationByValSrcIndexKey, + aclsdktypes.ResourceType_KV_STAKING_REDELEGATION_VAL_DST: stakingtypes.RedelegationByValDstIndexKey, + aclsdktypes.ResourceType_KV_STAKING_UNBONDING: stakingtypes.UnbondingQueueKey, + aclsdktypes.ResourceType_KV_STAKING_REDELEGATION_QUEUE: stakingtypes.RedelegationQueueKey, + aclsdktypes.ResourceType_KV_STAKING_VALIDATOR_QUEUE: stakingtypes.ValidatorQueueKey, + aclsdktypes.ResourceType_KV_STAKING_HISTORICAL_INFO: stakingtypes.HistoricalInfoKey, + }, + tokenfactorytypes.StoreKey: { + aclsdktypes.ResourceType_KV_TOKENFACTORY: aclsdktypes.EmptyPrefix, + aclsdktypes.ResourceType_KV_TOKENFACTORY_DENOM: []byte(tokenfactorytypes.DenomsPrefixKey), + aclsdktypes.ResourceType_KV_TOKENFACTORY_METADATA: []byte(tokenfactorytypes.DenomAuthorityMetadataKey), + aclsdktypes.ResourceType_KV_TOKENFACTORY_ADMIN: []byte(tokenfactorytypes.AdminPrefixKey), + aclsdktypes.ResourceType_KV_TOKENFACTORY_CREATOR: []byte(tokenfactorytypes.AdminPrefixKey), + }, + epochtypes.StoreKey: { + aclsdktypes.ResourceType_KV_EPOCH: aclsdktypes.EmptyPrefix, + }, +} diff --git a/aclmapping/utils/test_utils.go b/aclmapping/utils/test_utils.go new file mode 100644 index 0000000000..5b39275708 --- /dev/null +++ b/aclmapping/utils/test_utils.go @@ -0,0 +1,9 @@ +package utils + +import sdk "github.com/cosmos/cosmos-sdk/types" + +func CacheTxContext(ctx sdk.Context) (sdk.Context, sdk.CacheMultiStore) { + ms := ctx.MultiStore() + msCache := ms.CacheMultiStore() + return ctx.WithMultiStore(msCache), msCache +} diff --git a/aclmapping/wasm/mappings.go b/aclmapping/wasm/mappings.go new file mode 100644 index 0000000000..1559a41cc6 --- /dev/null +++ b/aclmapping/wasm/mappings.go @@ -0,0 +1,53 @@ +package aclwasmmapping + +import ( + "fmt" + + wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkacltypes "github.com/cosmos/cosmos-sdk/types/accesscontrol" + aclkeeper "github.com/cosmos/cosmos-sdk/x/accesscontrol/keeper" + acltypes "github.com/cosmos/cosmos-sdk/x/accesscontrol/types" + "github.com/sei-protocol/sei-chain/utils" +) + +var ( + ErrInvalidWasmExecuteMessage = fmt.Errorf("invalid message received for type WasmExecuteContract") + ErrInvalidWasmFunction = fmt.Errorf("unable to identify wasm function") + ErrWasmFunctionDependenciesDisabled = fmt.Errorf("wasm function dependency mapping disabled") +) + +type WasmDependencyGenerator struct{} + +func NewWasmDependencyGenerator() WasmDependencyGenerator { + return WasmDependencyGenerator{} +} + +func (wasmDepGen WasmDependencyGenerator) GetWasmDependencyGenerators() aclkeeper.DependencyGeneratorMap { + dependencyGeneratorMap := make(aclkeeper.DependencyGeneratorMap) + + // wasm execute + executeContractKey := acltypes.GenerateMessageKey(&wasmtypes.MsgExecuteContract{}) + dependencyGeneratorMap[executeContractKey] = wasmDepGen.WasmExecuteContractGenerator + + return dependencyGeneratorMap +} + +func (wasmDepGen WasmDependencyGenerator) WasmExecuteContractGenerator(keeper aclkeeper.Keeper, ctx sdk.Context, msg sdk.Msg) ([]sdkacltypes.AccessOperation, error) { + executeContractMsg, ok := msg.(*wasmtypes.MsgExecuteContract) + if !ok { + return []sdkacltypes.AccessOperation{}, ErrInvalidWasmExecuteMessage + } + contractAddr, err := sdk.AccAddressFromBech32(executeContractMsg.Contract) + if err != nil { + return []sdkacltypes.AccessOperation{}, err + } + wasmDependencyMapping, err := keeper.GetWasmDependencyMapping(ctx, contractAddr, executeContractMsg.Msg, true) + if err != nil { + return []sdkacltypes.AccessOperation{}, err + } + if !wasmDependencyMapping.Enabled { + return []sdkacltypes.AccessOperation{}, ErrWasmFunctionDependenciesDisabled + } + return utils.Map(wasmDependencyMapping.AccessOps, func(op sdkacltypes.AccessOperationWithSelector) sdkacltypes.AccessOperation { return *op.Operation }), nil +} diff --git a/app/abci.go b/app/abci.go index 35b2a84ae5..bcaf0de3d6 100644 --- a/app/abci.go +++ b/app/abci.go @@ -2,8 +2,10 @@ package app import ( "context" + "time" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/sei-protocol/sei-chain/utils/metrics" abci "github.com/tendermint/tendermint/abci/types" "go.opentelemetry.io/otel/attribute" ) @@ -31,11 +33,12 @@ func (app *App) CheckTx(ctx context.Context, req *abci.RequestCheckTx) (*abci.Re } func (app *App) DeliverTx(ctx sdk.Context, req abci.RequestDeliverTx) abci.ResponseDeliverTx { - // tracectx, span := (*app.tracingInfo.Tracer).Start(app.tracingInfo.TracerContext, "DeliverTx") - // oldCtx := app.tracingInfo.TracerContext - // app.tracingInfo.TracerContext = tracectx - // defer span.End() - // defer func() { app.tracingInfo.TracerContext = oldCtx }() + defer metrics.MeasureDeliverTxDuration(time.Now()) + tracectx, span := (*app.tracingInfo.Tracer).Start(app.tracingInfo.TracerContext, "DeliverTx") + oldCtx := app.tracingInfo.TracerContext + app.tracingInfo.TracerContext = tracectx + defer span.End() + defer func() { app.tracingInfo.TracerContext = oldCtx }() return app.BaseApp.DeliverTx(ctx, req) } diff --git a/app/ante.go b/app/ante.go index 74e28d85cc..194c9c85d6 100644 --- a/app/ante.go +++ b/app/ante.go @@ -5,10 +5,12 @@ import ( wasmTypes "github.com/CosmWasm/wasmd/x/wasm/types" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + aclkeeper "github.com/cosmos/cosmos-sdk/x/accesscontrol/keeper" "github.com/cosmos/cosmos-sdk/x/auth/ante" ibcante "github.com/cosmos/ibc-go/v3/modules/core/ante" ibckeeper "github.com/cosmos/ibc-go/v3/modules/core/keeper" "github.com/sei-protocol/sei-chain/app/antedecorators" + "github.com/sei-protocol/sei-chain/app/antedecorators/depdecorators" "github.com/sei-protocol/sei-chain/utils/tracing" "github.com/sei-protocol/sei-chain/x/dex" dexkeeper "github.com/sei-protocol/sei-chain/x/dex/keeper" @@ -22,40 +24,41 @@ import ( type HandlerOptions struct { ante.HandlerOptions - IBCKeeper *ibckeeper.Keeper - WasmConfig *wasmTypes.WasmConfig - OracleKeeper *oraclekeeper.Keeper - DexKeeper *dexkeeper.Keeper - NitroKeeper *nitrokeeper.Keeper - TXCounterStoreKey sdk.StoreKey + IBCKeeper *ibckeeper.Keeper + WasmConfig *wasmTypes.WasmConfig + OracleKeeper *oraclekeeper.Keeper + DexKeeper *dexkeeper.Keeper + NitroKeeper *nitrokeeper.Keeper + AccessControlKeeper *aclkeeper.Keeper + TXCounterStoreKey sdk.StoreKey TracingInfo *tracing.Info } -func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) { +func NewAnteHandlerAndDepGenerator(options HandlerOptions) (sdk.AnteHandler, sdk.AnteDepGenerator, error) { if options.AccountKeeper == nil { - return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "account keeper is required for AnteHandler") + return nil, nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "account keeper is required for AnteHandler") } if options.BankKeeper == nil { - return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "bank keeper is required for AnteHandler") + return nil, nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "bank keeper is required for AnteHandler") } if options.SignModeHandler == nil { - return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "sign mode handler is required for ante builder") + return nil, nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "sign mode handler is required for ante builder") } if options.WasmConfig == nil { - return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "wasm config is required for ante builder") - } - if options.TXCounterStoreKey == nil { - return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "tx counter key is required for ante builder") + return nil, nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "wasm config is required for ante builder") } if options.OracleKeeper == nil { - return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "oracle keeper is required for ante builder") + return nil, nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "oracle keeper is required for ante builder") } if options.NitroKeeper == nil { - return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "nitro keeper is required for ante builder") + return nil, nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "nitro keeper is required for ante builder") + } + if options.AccessControlKeeper == nil { + return nil, nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "accesscontrol keeper is required for ante builder") } if options.TracingInfo == nil { - return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "tracing info is required for ante builder") + return nil, nil, sdkerrors.Wrap(sdkerrors.ErrLogic, "tracing info is required for ante builder") } sigGasConsumer := options.SigGasConsumer @@ -71,29 +74,31 @@ func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) { // sigVerifyDecorator = ante.NewBatchSigVerificationDecorator(options.BatchVerifier, sequentialVerifyDecorator) // } - anteDecorators := []sdk.AnteDecorator{ - ante.NewSetUpContextDecorator(), // outermost AnteDecorator. SetUpContext must be called first + anteDecorators := []sdk.AnteFullDecorator{ + sdk.DefaultWrappedAnteDecorator(ante.NewSetUpContextDecorator(antedecorators.GetGasMeterSetter(*options.AccessControlKeeper))), // outermost AnteDecorator. SetUpContext must be called first // TODO: have dex antehandler separate, and then call the individual antehandlers FROM the gasless antehandler decorator wrapper - wasmkeeper.NewLimitSimulationGasDecorator(options.WasmConfig.SimulationGasLimit), // after setup context to enforce limits early - wasmkeeper.NewCountTXDecorator(options.TXCounterStoreKey), - ante.NewRejectExtensionOptionsDecorator(), - oracle.NewSpammingPreventionDecorator(*options.OracleKeeper), - ante.NewValidateBasicDecorator(), - ante.NewTxTimeoutHeightDecorator(), - ante.NewValidateMemoDecorator(options.AccountKeeper), + sdk.DefaultWrappedAnteDecorator(antedecorators.NewGaslessDecorator([]sdk.AnteDecorator{}, *options.OracleKeeper, *options.NitroKeeper)), + sdk.DefaultWrappedAnteDecorator(wasmkeeper.NewLimitSimulationGasDecorator(options.WasmConfig.SimulationGasLimit)), // after setup context to enforce limits early + sdk.DefaultWrappedAnteDecorator(ante.NewRejectExtensionOptionsDecorator()), + sdk.DefaultWrappedAnteDecorator(oracle.NewSpammingPreventionDecorator(*options.OracleKeeper)), + sdk.DefaultWrappedAnteDecorator(ante.NewValidateBasicDecorator()), + sdk.DefaultWrappedAnteDecorator(ante.NewTxTimeoutHeightDecorator()), + sdk.DefaultWrappedAnteDecorator(ante.NewValidateMemoDecorator(options.AccountKeeper)), ante.NewConsumeGasForTxSizeDecorator(options.AccountKeeper), ante.NewDeductFeeDecorator(options.AccountKeeper, options.BankKeeper, options.FeegrantKeeper, options.TxFeeChecker), // PriorityDecorator must be called after DeductFeeDecorator which sets tx priority based on tx fees - antedecorators.NewPriorityDecorator(), + sdk.DefaultWrappedAnteDecorator(antedecorators.NewPriorityDecorator()), // SetPubKeyDecorator must be called before all signature verification decorators - ante.NewSetPubKeyDecorator(options.AccountKeeper), - ante.NewValidateSigCountDecorator(options.AccountKeeper), - ante.NewSigGasConsumeDecorator(options.AccountKeeper, sigGasConsumer), - sequentialVerifyDecorator, - ante.NewIncrementSequenceDecorator(options.AccountKeeper), - ibcante.NewAnteDecorator(options.IBCKeeper), - dex.NewTickSizeMultipleDecorator(*options.DexKeeper), + sdk.CustomDepWrappedAnteDecorator(ante.NewSetPubKeyDecorator(options.AccountKeeper), depdecorators.SignerDepDecorator{ReadOnly: false}), + sdk.DefaultWrappedAnteDecorator(ante.NewValidateSigCountDecorator(options.AccountKeeper)), + sdk.CustomDepWrappedAnteDecorator(ante.NewSigGasConsumeDecorator(options.AccountKeeper, sigGasConsumer), depdecorators.SignerDepDecorator{ReadOnly: true}), + sdk.CustomDepWrappedAnteDecorator(sequentialVerifyDecorator, depdecorators.SignerDepDecorator{ReadOnly: true}), + sdk.CustomDepWrappedAnteDecorator(ante.NewIncrementSequenceDecorator(options.AccountKeeper), depdecorators.SignerDepDecorator{ReadOnly: false}), + sdk.DefaultWrappedAnteDecorator(ibcante.NewAnteDecorator(options.IBCKeeper)), + sdk.DefaultWrappedAnteDecorator(dex.NewTickSizeMultipleDecorator(*options.DexKeeper)), } - return sdk.ChainAnteDecorators(anteDecorators...), nil + anteHandler, anteDepGenerator := sdk.ChainAnteDecorators(anteDecorators...) + + return anteHandler, anteDepGenerator, nil } diff --git a/app/ante_test.go b/app/ante_test.go new file mode 100644 index 0000000000..ef2b364e4c --- /dev/null +++ b/app/ante_test.go @@ -0,0 +1,209 @@ +package app_test + +import ( + "context" + "testing" + + wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/tx" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/cosmos/cosmos-sdk/simapp" + "github.com/cosmos/cosmos-sdk/testutil/testdata" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkacltypes "github.com/cosmos/cosmos-sdk/types/accesscontrol" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + acltypes "github.com/cosmos/cosmos-sdk/x/accesscontrol/types" + + "github.com/cosmos/cosmos-sdk/x/auth/ante" + xauthsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" + aclutils "github.com/sei-protocol/sei-chain/aclmapping/utils" + app "github.com/sei-protocol/sei-chain/app" + "github.com/sei-protocol/sei-chain/app/apptesting" + "github.com/sei-protocol/sei-chain/utils/tracing" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + "go.opentelemetry.io/otel" +) + +// AnteTestSuite is a test suite to be used with ante handler tests. +type AnteTestSuite struct { + apptesting.KeeperTestHelper + + anteHandler sdk.AnteHandler + anteDepGenerator sdk.AnteDepGenerator + clientCtx client.Context + txBuilder client.TxBuilder + testAcc sdk.AccAddress + testAccPriv cryptotypes.PrivKey +} + +func TestKeeperTestSuite(t *testing.T) { + suite.Run(t, new(AnteTestSuite)) +} + +// SetupTest setups a new test, with new app, context, and anteHandler. +func (suite *AnteTestSuite) SetupTest(isCheckTx bool) { + suite.Setup() + + // keys and addresses + suite.testAccPriv, _, suite.testAcc = testdata.KeyTestPubAddr() + initalBalance := sdk.Coins{sdk.NewInt64Coin("atom", 100000000000)} + suite.FundAcc(suite.testAcc, initalBalance) + + suite.Ctx = suite.Ctx.WithBlockHeight(1) + + msgValidator := sdkacltypes.NewMsgValidator(aclutils.StoreKeyToResourceTypePrefixMap) + suite.Ctx = suite.Ctx.WithMsgValidator(msgValidator) + + // Set up TxConfig. + encodingConfig := simapp.MakeTestEncodingConfig() + // We're using TestMsg encoding in some tests, so register it here. + encodingConfig.Amino.RegisterConcrete(&testdata.TestMsg{}, "testdata.TestMsg", nil) + testdata.RegisterInterfaces(encodingConfig.InterfaceRegistry) + + suite.clientCtx = client.Context{}. + WithTxConfig(encodingConfig.TxConfig) + + wasmConfig := wasmtypes.DefaultWasmConfig() + defaultTracer, _ := tracing.DefaultTracerProvider() + otel.SetTracerProvider(defaultTracer) + tr := defaultTracer.Tracer("component-main") + + antehandler, anteDepGenerator, err := app.NewAnteHandlerAndDepGenerator( + app.HandlerOptions{ + HandlerOptions: ante.HandlerOptions{ + AccountKeeper: suite.App.AccountKeeper, + BankKeeper: suite.App.BankKeeper, + FeegrantKeeper: suite.App.FeeGrantKeeper, + SignModeHandler: suite.clientCtx.TxConfig.SignModeHandler(), + SigGasConsumer: ante.DefaultSigVerificationGasConsumer, + // BatchVerifier: app.batchVerifier, + }, + IBCKeeper: suite.App.IBCKeeper, + WasmConfig: &wasmConfig, + OracleKeeper: &suite.App.OracleKeeper, + DexKeeper: &suite.App.DexKeeper, + NitroKeeper: &suite.App.NitroKeeper, + AccessControlKeeper: &suite.App.AccessControlKeeper, + TracingInfo: &tracing.Info{ + Tracer: &tr, + TracerContext: context.Background(), + }, + }, + ) + + suite.Require().NoError(err) + suite.anteHandler = antehandler + suite.anteDepGenerator = anteDepGenerator +} + +func (suite *AnteTestSuite) AnteHandlerValidateAccessOp(acessOps []sdkacltypes.AccessOperation) error { + for _, accessOp := range acessOps { + err := acltypes.ValidateAccessOp(accessOp) + if err != nil { + return err + } + } + return nil +} + +// CreateTestTx is a helper function to create a tx given multiple inputs. +func (suite *AnteTestSuite) CreateTestTx(privs []cryptotypes.PrivKey, accNums []uint64, accSeqs []uint64, chainID string) (xauthsigning.Tx, error) { + // First round: we gather all the signer infos. We use the "set empty + // signature" hack to do that. + var sigsV2 []signing.SignatureV2 + for i, priv := range privs { + sigV2 := signing.SignatureV2{ + PubKey: priv.PubKey(), + Data: &signing.SingleSignatureData{ + SignMode: suite.clientCtx.TxConfig.SignModeHandler().DefaultMode(), + Signature: nil, + }, + Sequence: accSeqs[i], + } + + sigsV2 = append(sigsV2, sigV2) + } + err := suite.txBuilder.SetSignatures(sigsV2...) + if err != nil { + return nil, err + } + + // Second round: all signer infos are set, so each signer can sign. + sigsV2 = []signing.SignatureV2{} + for i, priv := range privs { + signerData := xauthsigning.SignerData{ + ChainID: chainID, + AccountNumber: accNums[i], + Sequence: accSeqs[i], + } + sigV2, err := tx.SignWithPrivKey( + suite.clientCtx.TxConfig.SignModeHandler().DefaultMode(), signerData, + suite.txBuilder, priv, suite.clientCtx.TxConfig, accSeqs[i]) + if err != nil { + return nil, err + } + + sigsV2 = append(sigsV2, sigV2) + } + err = suite.txBuilder.SetSignatures(sigsV2...) + if err != nil { + return nil, err + } + + return suite.txBuilder.GetTx(), nil +} + +func (suite *AnteTestSuite) TestValidateDepedencies() { + suite.SetupTest(true) // setup + suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() + + // msg and signatures + msg := testdata.NewTestMsg(suite.testAcc) + feeAmount := testdata.NewTestFeeAmount() + gasLimit := testdata.NewTestGasLimit() + suite.Require().NoError(suite.txBuilder.SetMsgs(msg)) + suite.txBuilder.SetFeeAmount(feeAmount) + suite.txBuilder.SetGasLimit(gasLimit) + + privs, accNums, accSeqs := []cryptotypes.PrivKey{}, []uint64{}, []uint64{} + invalidTx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.Ctx.ChainID()) + suite.Require().NoError(err) + + _, err = suite.anteHandler(suite.Ctx, invalidTx, false) + + suite.Require().NotNil(err, "Did not error on invalid tx") + + privs, accNums, accSeqs = []cryptotypes.PrivKey{suite.testAccPriv}, []uint64{8}, []uint64{0} + + handlerCtx, cms := aclutils.CacheTxContext(suite.Ctx) + validTx, err := suite.CreateTestTx(privs, accNums, accSeqs, suite.Ctx.ChainID()) + + suite.Require().NoError(err) + depdenencies, _ := suite.anteDepGenerator([]sdkacltypes.AccessOperation{}, validTx) + _, err = suite.anteHandler(handlerCtx, validTx, false) + suite.Require().Nil(err, "ValidateBasicDecorator returned error on valid tx. err: %v", err) + err = suite.AnteHandlerValidateAccessOp(depdenencies) + + require.NoError(suite.T(), err) + + missing := handlerCtx.MsgValidator().ValidateAccessOperations(depdenencies, cms.GetEvents()) + suite.Require().Empty(missing) + + // test decorator skips on recheck + suite.Ctx = suite.Ctx.WithIsReCheckTx(true) + + // decorator should skip processing invalidTx on recheck and thus return nil-error + handlerCtx, cms = aclutils.CacheTxContext(suite.Ctx) + depdenencies, _ = suite.anteDepGenerator([]sdkacltypes.AccessOperation{}, invalidTx) + _, err = suite.anteHandler(handlerCtx, invalidTx, false) + missing = handlerCtx.MsgValidator().ValidateAccessOperations(depdenencies, cms.GetEvents()) + + err = suite.AnteHandlerValidateAccessOp(depdenencies) + require.NoError(suite.T(), err) + + suite.Require().Empty(missing) + + suite.Require().Nil(err, "ValidateBasicDecorator ran on ReCheck") +} diff --git a/app/antedecorators/depdecorators/signers.go b/app/antedecorators/depdecorators/signers.go new file mode 100644 index 0000000000..9d8043c3b8 --- /dev/null +++ b/app/antedecorators/depdecorators/signers.go @@ -0,0 +1,34 @@ +package depdecorators + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + sdkacltypes "github.com/cosmos/cosmos-sdk/types/accesscontrol" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" +) + +type SignerDepDecorator struct { + ReadOnly bool +} + +func (d SignerDepDecorator) AnteDeps(txDeps []sdkacltypes.AccessOperation, tx sdk.Tx, next sdk.AnteDepGenerator) (newTxDeps []sdkacltypes.AccessOperation, err error) { + sigTx, ok := tx.(authsigning.SigVerifiableTx) + if !ok { + return txDeps, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "invalid tx type") + } + var accessType sdkacltypes.AccessType + if d.ReadOnly { + accessType = sdkacltypes.AccessType_READ + } else { + accessType = sdkacltypes.AccessType_WRITE + } + for _, signer := range sigTx.GetSigners() { + txDeps = append(txDeps, sdkacltypes.AccessOperation{ + AccessType: accessType, + ResourceType: sdkacltypes.ResourceType_KV_AUTH_ADDRESS_STORE, + IdentifierTemplate: string(authtypes.AddressStoreKey(signer)), + }) + } + return next(txDeps, tx) +} diff --git a/app/antedecorators/gas.go b/app/antedecorators/gas.go new file mode 100644 index 0000000000..d5be80fd12 --- /dev/null +++ b/app/antedecorators/gas.go @@ -0,0 +1,53 @@ +package antedecorators + +import ( + wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" + "github.com/cosmos/cosmos-sdk/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkacltypes "github.com/cosmos/cosmos-sdk/types/accesscontrol" + aclkeeper "github.com/cosmos/cosmos-sdk/x/accesscontrol/keeper" +) + +const ( + GasMultiplierNumerator uint64 = 1 + DefaultGasMultiplierDenominator uint64 = 1 + WasmCorrectDependencyDiscountDenominator uint64 = 2 +) + +func GetGasMeterSetter(aclkeeper aclkeeper.Keeper) func(bool, sdk.Context, uint64, sdk.Tx) sdk.Context { + return func(simulate bool, ctx sdk.Context, gasLimit uint64, tx sdk.Tx) sdk.Context { + if simulate || ctx.BlockHeight() == 0 { + return ctx.WithGasMeter(sdk.NewInfiniteGasMeter()) + } + + denominator := uint64(1) + for _, msg := range tx.GetMsgs() { + candidateDenominator := getMessageMultiplierDenominator(ctx, msg, aclkeeper) + if candidateDenominator > denominator { + denominator = candidateDenominator + } + } + return ctx.WithGasMeter(types.NewMultiplierGasMeter(gasLimit, DefaultGasMultiplierDenominator, denominator)) + } +} + +func getMessageMultiplierDenominator(ctx sdk.Context, msg sdk.Msg, aclkeeper aclkeeper.Keeper) uint64 { + if wasmExecuteMsg, ok := msg.(*wasmtypes.MsgExecuteContract); ok { + addr, err := sdk.AccAddressFromBech32(wasmExecuteMsg.Contract) + if err != nil { + return DefaultGasMultiplierDenominator + } + mapping, err := aclkeeper.GetWasmDependencyMapping(ctx, addr, []byte{}, false) + if err != nil { + return DefaultGasMultiplierDenominator + } + // only give gas discount if none of the dependency (except COMMIT) has id "*" + for _, op := range mapping.AccessOps { + if op.Operation.AccessType != sdkacltypes.AccessType_COMMIT && op.Operation.IdentifierTemplate == "*" { + return DefaultGasMultiplierDenominator + } + } + return WasmCorrectDependencyDiscountDenominator + } + return DefaultGasMultiplierDenominator +} diff --git a/app/antedecorators/gas_test.go b/app/antedecorators/gas_test.go new file mode 100644 index 0000000000..27baf0b972 --- /dev/null +++ b/app/antedecorators/gas_test.go @@ -0,0 +1,77 @@ +package antedecorators_test + +import ( + "testing" + + wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/accesscontrol" + acltypes "github.com/cosmos/cosmos-sdk/x/accesscontrol/types" + "github.com/sei-protocol/sei-chain/app" + "github.com/sei-protocol/sei-chain/app/antedecorators" + "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/proto/tendermint/types" +) + +type TestTx struct { + msgs []sdk.Msg +} + +func (t TestTx) GetMsgs() []sdk.Msg { + return t.msgs +} + +func (t TestTx) ValidateBasic() error { + return nil +} + +func TestMultiplierGasSetter(t *testing.T) { + app := app.Setup(false) + contractAddr, err := sdk.AccAddressFromBech32("sei1y3pxq5dp900czh0mkudhjdqjq5m8cpmmps8yjw") + require.NoError(t, err) + ctx := app.NewContext(false, types.Header{}).WithBlockHeight(2) + testMsg := wasmtypes.MsgExecuteContract{ + Contract: "sei1y3pxq5dp900czh0mkudhjdqjq5m8cpmmps8yjw", + Msg: []byte("{}"), + } + testTx := TestTx{msgs: []sdk.Msg{&testMsg}} + // discounted mapping + app.AccessControlKeeper.SetWasmDependencyMapping(ctx, contractAddr, accesscontrol.WasmDependencyMapping{ + Enabled: true, + AccessOps: []accesscontrol.AccessOperationWithSelector{ + { + Operation: &accesscontrol.AccessOperation{ + AccessType: accesscontrol.AccessType_READ, + ResourceType: accesscontrol.ResourceType_KV, + IdentifierTemplate: "something", + }, + }, + { + Operation: acltypes.CommitAccessOp(), + }, + }, + }) + gasMeterSetter := antedecorators.GetGasMeterSetter(app.AccessControlKeeper) + ctxWithGasMeter := gasMeterSetter(false, ctx, 1000, testTx) + ctxWithGasMeter.GasMeter().ConsumeGas(2, "") + require.Equal(t, uint64(1), ctxWithGasMeter.GasMeter().GasConsumed()) + // not discounted mapping + app.AccessControlKeeper.SetWasmDependencyMapping(ctx, contractAddr, accesscontrol.WasmDependencyMapping{ + Enabled: true, + AccessOps: []accesscontrol.AccessOperationWithSelector{ + { + Operation: &accesscontrol.AccessOperation{ + AccessType: accesscontrol.AccessType_READ, + ResourceType: accesscontrol.ResourceType_KV, + IdentifierTemplate: "*", + }, + }, + { + Operation: acltypes.CommitAccessOp(), + }, + }, + }) + ctxWithGasMeter = gasMeterSetter(false, ctx, 1000, testTx) + ctxWithGasMeter.GasMeter().ConsumeGas(2, "") + require.Equal(t, uint64(2), ctxWithGasMeter.GasMeter().GasConsumed()) +} diff --git a/app/antedecorators/gasless.go b/app/antedecorators/gasless.go index ea9d39999e..17591d0069 100644 --- a/app/antedecorators/gasless.go +++ b/app/antedecorators/gasless.go @@ -4,6 +4,8 @@ import ( "bytes" sdk "github.com/cosmos/cosmos-sdk/types" + sdkacltypes "github.com/cosmos/cosmos-sdk/types/accesscontrol" + aclutils "github.com/sei-protocol/sei-chain/aclmapping/utils" dextypes "github.com/sei-protocol/sei-chain/x/dex/types" nitrokeeper "github.com/sei-protocol/sei-chain/x/nitro/keeper" nitrotypes "github.com/sei-protocol/sei-chain/x/nitro/types" @@ -44,6 +46,25 @@ func (gd GaslessDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, return next(ctx, tx, simulate) } +func (gd GaslessDecorator) AnteDeps(txDeps []sdkacltypes.AccessOperation, tx sdk.Tx, next sdk.AnteDepGenerator) (newTxDeps []sdkacltypes.AccessOperation, err error) { + deps := []sdkacltypes.AccessOperation{} + for _, msg := range tx.GetMsgs() { + // Error checking will be handled in AnteHandler + switch m := msg.(type) { + case *oracletypes.MsgAggregateExchangeRateVote: + feederAddr, _ := sdk.AccAddressFromBech32(m.Feeder) + valAddr, _ := sdk.ValAddressFromBech32(m.Validator) + deps = append(deps, aclutils.GetOracleReadAccessOpsForValAndFeeder(feederAddr, valAddr)...) + // TODO: add tx gasless deps for nitro for nitrokeeper read + // TODO: we also need to add READs for Validator + bonded check + default: + continue + } + } + + return next(append(txDeps, deps...), tx) +} + func isTxGasless(tx sdk.Tx, ctx sdk.Context, oracleKeeper oraclekeeper.Keeper, nitroKeeper nitrokeeper.Keeper) bool { if len(tx.GetMsgs()) == 0 { // empty TX shouldn't be gasless diff --git a/app/antedecorators/gasless_test.go b/app/antedecorators/gasless_test.go index 439588bb5b..7fb68025bc 100644 --- a/app/antedecorators/gasless_test.go +++ b/app/antedecorators/gasless_test.go @@ -46,12 +46,12 @@ func (tx FakeTx) ValidateBasic() error { func TestGaslessDecorator(t *testing.T) { output = "" - anteDecorators := []sdk.AnteDecorator{ - FakeAnteDecoratorOne{}, - antedecorators.NewGaslessDecorator([]sdk.AnteDecorator{FakeAnteDecoratorTwo{}}, oraclekeeper.Keeper{}, nitrokeeper.Keeper{}), - FakeAnteDecoratorThree{}, + anteDecorators := []sdk.AnteFullDecorator{ + sdk.DefaultWrappedAnteDecorator(FakeAnteDecoratorOne{}), + sdk.DefaultWrappedAnteDecorator(antedecorators.NewGaslessDecorator([]sdk.AnteDecorator{FakeAnteDecoratorTwo{}}, oraclekeeper.Keeper{}, nitrokeeper.Keeper{})), + sdk.DefaultWrappedAnteDecorator(FakeAnteDecoratorThree{}), } - chainedHandler := sdk.ChainAnteDecorators(anteDecorators...) + chainedHandler, _ := sdk.ChainAnteDecorators(anteDecorators...) chainedHandler(sdk.Context{}, FakeTx{}, false) require.Equal(t, "onetwothree", output) } diff --git a/app/antedecorators/traced_test.go b/app/antedecorators/traced_test.go index 4207e5e7d8..efbca5cc44 100644 --- a/app/antedecorators/traced_test.go +++ b/app/antedecorators/traced_test.go @@ -11,15 +11,15 @@ import ( func TestTracedDecorator(t *testing.T) { output = "" - anteDecorators := []sdk.AnteDecorator{ - FakeAnteDecoratorOne{}, - FakeAnteDecoratorTwo{}, - FakeAnteDecoratorThree{}, + anteDecorators := []sdk.AnteFullDecorator{ + sdk.DefaultWrappedAnteDecorator(FakeAnteDecoratorOne{}), + sdk.DefaultWrappedAnteDecorator(FakeAnteDecoratorTwo{}), + sdk.DefaultWrappedAnteDecorator(FakeAnteDecoratorThree{}), } - tracedDecorators := utils.Map(anteDecorators, func(d sdk.AnteDecorator) sdk.AnteDecorator { - return antedecorators.NewTracedAnteDecorator(d, nil) + tracedDecorators := utils.Map(anteDecorators, func(d sdk.AnteFullDecorator) sdk.AnteFullDecorator { + return sdk.DefaultWrappedAnteDecorator(antedecorators.NewTracedAnteDecorator(d, nil)) }) - chainedHandler := sdk.ChainAnteDecorators(tracedDecorators...) + chainedHandler, _ := sdk.ChainAnteDecorators(tracedDecorators...) chainedHandler(sdk.Context{}, FakeTx{}, false) require.Equal(t, "onetwothree", output) } diff --git a/app/app.go b/app/app.go index 3c9f462ce7..40162301a9 100644 --- a/app/app.go +++ b/app/app.go @@ -9,10 +9,12 @@ import ( "os" "path/filepath" "strings" + "sync" "time" storetypes "github.com/cosmos/cosmos-sdk/store/types" + "github.com/sei-protocol/sei-chain/aclmapping" appparams "github.com/sei-protocol/sei-chain/app/params" "github.com/sei-protocol/sei-chain/utils" "github.com/sei-protocol/sei-chain/wasmbinding" @@ -29,10 +31,18 @@ import ( servertypes "github.com/cosmos/cosmos-sdk/server/types" "github.com/cosmos/cosmos-sdk/simapp" sdk "github.com/cosmos/cosmos-sdk/types" + sdkacltypes "github.com/cosmos/cosmos-sdk/types/accesscontrol" "github.com/cosmos/cosmos-sdk/types/module" "github.com/cosmos/cosmos-sdk/version" "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/auth/ante" + + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + aclmodule "github.com/cosmos/cosmos-sdk/x/accesscontrol" + aclclient "github.com/cosmos/cosmos-sdk/x/accesscontrol/client" + aclconstants "github.com/cosmos/cosmos-sdk/x/accesscontrol/constants" + aclkeeper "github.com/cosmos/cosmos-sdk/x/accesscontrol/keeper" + acltypes "github.com/cosmos/cosmos-sdk/x/accesscontrol/types" authrest "github.com/cosmos/cosmos-sdk/x/auth/client/rest" authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" authsims "github.com/cosmos/cosmos-sdk/x/auth/simulation" @@ -100,6 +110,7 @@ import ( tmproto "github.com/tendermint/tendermint/proto/tendermint/types" dbm "github.com/tendermint/tm-db" + "github.com/sei-protocol/sei-chain/utils/metrics" "github.com/sei-protocol/sei-chain/utils/tracing" dexmodule "github.com/sei-protocol/sei-chain/x/dex" @@ -131,8 +142,6 @@ import ( wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" "go.opentelemetry.io/otel" - - "github.com/sei-protocol/sei-chain/utils/metrics" ) // this line is used by starport scaffolding # stargate/wasm/app/enabledProposals @@ -144,6 +153,8 @@ func getGovProposalHandlers() []govclient.ProposalHandler { upgradeclient.CancelProposalHandler, ibcclientclient.UpdateClientProposalHandler, ibcclientclient.UpgradeProposalHandler, + aclclient.ResourceDependencyProposalHandler, + aclclient.WasmDependencyProposalHandler, // this line is used by starport scaffolding # stargate/app/govProposalHandler ) @@ -158,6 +169,7 @@ var ( // non-dependant module elements, such as codec registration // and genesis verification. ModuleBasics = module.NewBasicManager( + aclmodule.AppModuleBasic{}, auth.AppModuleBasic{}, genutil.AppModuleBasic{}, bank.AppModuleBasic{}, @@ -186,6 +198,7 @@ var ( // module account permissions maccPerms = map[string][]string{ + acltypes.ModuleName: nil, authtypes.FeeCollectorName: nil, distrtypes.ModuleName: nil, minttypes.ModuleName: {authtypes.Minter}, @@ -222,6 +235,8 @@ var ( // Boolean to only emit seid version and git commit metric once per chain initialization EmittedSeidVersionMetric = false + // EmptyAclmOpts defines a type alias for a list of wasm options. + EmptyACLOpts []aclkeeper.Option ) var ( @@ -278,23 +293,24 @@ type App struct { memKeys map[string]*sdk.MemoryStoreKey // keepers - AccountKeeper authkeeper.AccountKeeper - BankKeeper bankkeeper.Keeper - CapabilityKeeper *capabilitykeeper.Keeper - StakingKeeper stakingkeeper.Keeper - SlashingKeeper slashingkeeper.Keeper - MintKeeper mintkeeper.Keeper - DistrKeeper distrkeeper.Keeper - GovKeeper govkeeper.Keeper - CrisisKeeper crisiskeeper.Keeper - UpgradeKeeper upgradekeeper.Keeper - ParamsKeeper paramskeeper.Keeper - IBCKeeper *ibckeeper.Keeper // IBC Keeper must be a pointer in the app, so we can SetRouter on it correctly - EvidenceKeeper evidencekeeper.Keeper - TransferKeeper ibctransferkeeper.Keeper - FeeGrantKeeper feegrantkeeper.Keeper - WasmKeeper wasm.Keeper - OracleKeeper oraclekeeper.Keeper + AccessControlKeeper aclkeeper.Keeper + AccountKeeper authkeeper.AccountKeeper + BankKeeper bankkeeper.Keeper + CapabilityKeeper *capabilitykeeper.Keeper + StakingKeeper stakingkeeper.Keeper + SlashingKeeper slashingkeeper.Keeper + MintKeeper mintkeeper.Keeper + DistrKeeper distrkeeper.Keeper + GovKeeper govkeeper.Keeper + CrisisKeeper crisiskeeper.Keeper + UpgradeKeeper upgradekeeper.Keeper + ParamsKeeper paramskeeper.Keeper + IBCKeeper *ibckeeper.Keeper // IBC Keeper must be a pointer in the app, so we can SetRouter on it correctly + EvidenceKeeper evidencekeeper.Keeper + TransferKeeper ibctransferkeeper.Keeper + FeeGrantKeeper feegrantkeeper.Keeper + WasmKeeper wasm.Keeper + OracleKeeper oraclekeeper.Keeper // make scoped keepers public for test purposes ScopedIBCKeeper capabilitykeeper.ScopedKeeper @@ -321,7 +337,7 @@ type App struct { optimisticProcessingInfo *OptimisticProcessingInfo // batchVerifier *ante.SR25519BatchVerifier - // txDecoder sdk.TxDecoder + txDecoder sdk.TxDecoder } // New returns a reference to an initialized blockchain app @@ -337,6 +353,7 @@ func New( enabledProposals []wasm.ProposalType, appOpts servertypes.AppOptions, wasmOpts []wasm.Option, + aclOpts []aclkeeper.Option, baseAppOptions ...func(*baseapp.BaseApp), ) *App { appCodec := encodingConfig.Marshaler @@ -349,7 +366,7 @@ func New( bApp.SetInterfaceRegistry(interfaceRegistry) keys := sdk.NewKVStoreKeys( - authtypes.StoreKey, banktypes.StoreKey, stakingtypes.StoreKey, + acltypes.StoreKey, authtypes.StoreKey, banktypes.StoreKey, stakingtypes.StoreKey, minttypes.StoreKey, distrtypes.StoreKey, slashingtypes.StoreKey, govtypes.StoreKey, paramstypes.StoreKey, ibchost.StoreKey, upgradetypes.StoreKey, feegrant.StoreKey, evidencetypes.StoreKey, ibctransfertypes.StoreKey, capabilitytypes.StoreKey, oracletypes.StoreKey, wasm.StoreKey, @@ -381,7 +398,7 @@ func New( Tracer: &tr, TracerContext: context.Background(), }, - // txDecoder: encodingConfig.TxConfig.TxDecoder(), + txDecoder: encodingConfig.TxConfig.TxDecoder(), } app.ParamsKeeper = initParamsKeeper(appCodec, cdc, keys[paramstypes.StoreKey], tkeys[paramstypes.TStoreKey]) @@ -501,10 +518,37 @@ func New( app.keys[nitrotypes.StoreKey], app.GetSubspace(nitrotypes.ModuleName), ) + + customDependencyGenerators := aclmapping.NewCustomDependencyGenerator() + aclOpts = append(aclOpts, aclkeeper.WithDependencyGeneratorMappings(customDependencyGenerators.GetCustomDependencyGenerators())) + app.AccessControlKeeper = aclkeeper.NewKeeper( + appCodec, + app.keys[acltypes.StoreKey], + app.GetSubspace(acltypes.ModuleName), + app.AccountKeeper, + app.StakingKeeper, + aclOpts..., + ) // The last arguments can contain custom message handlers, and custom query handlers, // if we want to allow any custom callbacks supportedFeatures := "iterator,staking,stargate,sei" - wasmOpts = append(wasmbinding.RegisterCustomPlugins(&app.OracleKeeper, &app.DexKeeper, &app.EpochKeeper, &app.TokenFactoryKeeper, &app.AccountKeeper, app.MsgServiceRouter()), wasmOpts...) + wasmOpts = append( + wasmbinding.RegisterCustomPlugins( + &app.OracleKeeper, + &app.DexKeeper, + &app.EpochKeeper, + &app.TokenFactoryKeeper, + &app.AccountKeeper, + app.MsgServiceRouter(), + app.IBCKeeper.ChannelKeeper, + scopedWasmKeeper, + app.BankKeeper, + appCodec, + app.TransferKeeper, + app.AccessControlKeeper, + ), + wasmOpts..., + ) app.WasmKeeper = wasm.NewKeeper( appCodec, keys[wasm.StoreKey], @@ -536,7 +580,8 @@ func New( AddRoute(upgradetypes.RouterKey, upgrade.NewSoftwareUpgradeProposalHandler(app.UpgradeKeeper)). AddRoute(ibcclienttypes.RouterKey, ibcclient.NewClientProposalHandler(app.IBCKeeper.ClientKeeper)). AddRoute(dexmoduletypes.RouterKey, dexmodule.NewProposalHandler(app.DexKeeper)). - AddRoute(tokenfactorytypes.RouterKey, tokenfactorymodule.NewProposalHandler(app.TokenFactoryKeeper)) + AddRoute(tokenfactorytypes.RouterKey, tokenfactorymodule.NewProposalHandler(app.TokenFactoryKeeper)). + AddRoute(acltypes.ModuleName, aclmodule.NewProposalHandler(app.AccessControlKeeper)) if len(enabledProposals) != 0 { govRouter.AddRoute(wasm.RouterKey, wasm.NewWasmProposalHandler(app.WasmKeeper, enabledProposals)) } @@ -569,6 +614,7 @@ func New( app.AccountKeeper, app.StakingKeeper, app.BaseApp.DeliverTx, encodingConfig.TxConfig, ), + aclmodule.NewAppModule(appCodec, app.AccessControlKeeper), auth.NewAppModule(appCodec, app.AccountKeeper, nil), vesting.NewAppModule(app.AccountKeeper, app.BankKeeper), bank.NewAppModule(appCodec, app.BankKeeper, app.AccountKeeper), @@ -622,6 +668,7 @@ func New( wasm.ModuleName, tokenfactorytypes.ModuleName, nitrotypes.ModuleName, + acltypes.ModuleName, ) app.mm.SetOrderEndBlockers( @@ -648,6 +695,7 @@ func New( wasm.ModuleName, tokenfactorytypes.ModuleName, nitrotypes.ModuleName, + acltypes.ModuleName, ) // NOTE: The genutils module must occur after staking so that pools are @@ -679,6 +727,7 @@ func New( dexmoduletypes.ModuleName, nitrotypes.ModuleName, wasm.ModuleName, + acltypes.ModuleName, // this line is used by starport scaffolding # stargate/app/initGenesis ) @@ -727,7 +776,7 @@ func New( signModeHandler := encodingConfig.TxConfig.SignModeHandler() // app.batchVerifier = ante.NewSR25519BatchVerifier(app.AccountKeeper, signModeHandler) - anteHandler, err := NewAnteHandler( + anteHandler, anteDepGenerator, err := NewAnteHandlerAndDepGenerator( HandlerOptions{ HandlerOptions: ante.HandlerOptions{ AccountKeeper: app.AccountKeeper, @@ -737,13 +786,14 @@ func New( SigGasConsumer: ante.DefaultSigVerificationGasConsumer, // BatchVerifier: app.batchVerifier, }, - IBCKeeper: app.IBCKeeper, - TXCounterStoreKey: keys[wasm.StoreKey], - WasmConfig: &wasmConfig, - OracleKeeper: &app.OracleKeeper, - DexKeeper: &app.DexKeeper, - NitroKeeper: &app.NitroKeeper, - TracingInfo: app.tracingInfo, + IBCKeeper: app.IBCKeeper, + TXCounterStoreKey: keys[wasm.StoreKey], + WasmConfig: &wasmConfig, + OracleKeeper: &app.OracleKeeper, + DexKeeper: &app.DexKeeper, + NitroKeeper: &app.NitroKeeper, + TracingInfo: app.tracingInfo, + AccessControlKeeper: &app.AccessControlKeeper, }, ) if err != nil { @@ -751,6 +801,7 @@ func New( } app.SetAnteHandler(anteHandler) + app.SetAnteDepGenerator(anteDepGenerator) app.SetEndBlocker(app.EndBlocker) app.SetPrepareProposalHandler(app.PrepareProposalHandler) app.SetProcessProposalHandler(app.ProcessProposalHandler) @@ -818,6 +869,15 @@ func (app *App) SetStoreUpgradeHandlers() { // configure store loader that checks if version == upgradeHeight and applies store upgrades app.SetStoreLoader(upgradetypes.UpgradeStoreLoader(upgradeInfo.Height, &storeUpgrades)) } + + if upgradeInfo.Name == "2.0.0beta" && !app.UpgradeKeeper.IsSkipHeight(upgradeInfo.Height) { + storeUpgrades := storetypes.StoreUpgrades{ + Added: []string{acltypes.StoreKey}, + } + + // configure store loader that checks if version == upgradeHeight and applies store upgrades + app.SetStoreLoader(upgradetypes.UpgradeStoreLoader(upgradeInfo.Height, &storeUpgrades)) + } } // AppName returns the name of the App @@ -916,9 +976,151 @@ func (app *App) FinalizeBlocker(ctx sdk.Context, req *abci.RequestFinalizeBlock) return &resp, nil } +func (app *App) DeliverTxWithResult(ctx sdk.Context, tx []byte) *abci.ExecTxResult { + deliverTxResp := app.DeliverTx(ctx, abci.RequestDeliverTx{ + Tx: tx, + }) + return &abci.ExecTxResult{ + Code: deliverTxResp.Code, + Data: deliverTxResp.Data, + Log: deliverTxResp.Log, + Info: deliverTxResp.Info, + GasWanted: deliverTxResp.GasWanted, + GasUsed: deliverTxResp.GasUsed, + Events: deliverTxResp.Events, + Codespace: deliverTxResp.Codespace, + } +} + +func (app *App) ProcessBlockSynchronous(ctx sdk.Context, txs [][]byte) []*abci.ExecTxResult { + defer metrics.BlockProcessLatency(time.Now(), metrics.SYNCHRONOUS) + + txResults := []*abci.ExecTxResult{} + for _, tx := range txs { + txResults = append(txResults, app.DeliverTxWithResult(ctx, tx)) + metrics.IncrTxProcessTypeCounter(metrics.SYNCHRONOUS) + } + return txResults +} + +// Returns a mapping of the accessOperation to the channels +func getChannelsFromSignalMapping(signalMapping acltypes.MessageCompletionSignalMapping) sdkacltypes.MessageAccessOpsChannelMapping { + channelsMapping := make(sdkacltypes.MessageAccessOpsChannelMapping) + for messageIndex, accessOperationsToSignal := range signalMapping { + for accessOperation, completionSignals := range accessOperationsToSignal { + var channels []chan interface{} + channelsMapping[messageIndex] = make(sdkacltypes.AccessOpsChannelMapping) + for _, completionSignal := range completionSignals { + channels = append(channels, completionSignal.Channel) + } + channelsMapping[messageIndex][accessOperation] = channels + } + } + return channelsMapping +} + +type ChannelResult struct { + txIndex int + result *abci.ExecTxResult +} + +// cacheContext returns a new context based off of the provided context with +// a branched multi-store. +func (app *App) CacheContext(ctx sdk.Context) (sdk.Context, sdk.CacheMultiStore) { + ms := ctx.MultiStore() + msCache := ms.CacheMultiStore() + return ctx.WithMultiStore(msCache), msCache +} + +func (app *App) ProcessTxConcurrent( + ctx sdk.Context, + txIndex int, + txBytes []byte, + wg *sync.WaitGroup, + resultChan chan<- ChannelResult, + txCompletionSignalingMap acltypes.MessageCompletionSignalMapping, + txBlockingSignalsMap acltypes.MessageCompletionSignalMapping, + txMsgAccessOpMapping acltypes.MsgIndexToAccessOpMapping, +) { + defer wg.Done() + // Store the Channels in the Context Object for each transaction + ctx = ctx.WithTxBlockingChannels(getChannelsFromSignalMapping(txBlockingSignalsMap)) + ctx = ctx.WithTxCompletionChannels(getChannelsFromSignalMapping(txCompletionSignalingMap)) + ctx = ctx.WithTxMsgAccessOps(txMsgAccessOpMapping) + + // Deliver the transaction and store the result in the channel + + resultChan <- ChannelResult{txIndex, app.DeliverTxWithResult(ctx, txBytes)} + metrics.IncrTxProcessTypeCounter(metrics.CONCURRENT) +} + +func (app *App) ProcessBlockConcurrent( + ctx sdk.Context, + txs [][]byte, + completionSignalingMap map[int]acltypes.MessageCompletionSignalMapping, + blockingSignalsMap map[int]acltypes.MessageCompletionSignalMapping, + txMsgAccessOpMapping map[int]acltypes.MsgIndexToAccessOpMapping, +) ([]*abci.ExecTxResult, bool) { + defer metrics.BlockProcessLatency(time.Now(), metrics.CONCURRENT) + + txResults := []*abci.ExecTxResult{} + // If there's no transactions then return empty results + if len(txs) == 0 { + return txResults, true + } + + var waitGroup sync.WaitGroup + resultChan := make(chan ChannelResult) + // For each transaction, start goroutine and deliver TX + for txIndex, txBytes := range txs { + waitGroup.Add(1) + go app.ProcessTxConcurrent( + ctx, + txIndex, + txBytes, + &waitGroup, + resultChan, + completionSignalingMap[txIndex], + blockingSignalsMap[txIndex], + txMsgAccessOpMapping[txIndex], + ) + } + + // Do not call waitGroup.Wait() synchronously as it blocks on channel reads + // until all the messages are read. This closes the channel once + // results are all read and prevent any further writes. + go func() { + waitGroup.Wait() + close(resultChan) + }() + + // Gather Results and store it based on txIndex and read results from channel + // Concurrent results may be in different order than the original txIndex + txResultsMap := map[int]*abci.ExecTxResult{} + for result := range resultChan { + txResultsMap[result.txIndex] = result.result + } + + // Gather Results and store in array based on txIndex to preserve ordering + for txIndex := range txs { + txResults = append(txResults, txResultsMap[txIndex]) + } + + ok := true + for i, result := range txResults { + if result.GetCode() == sdkerrors.ErrInvalidConcurrencyExecution.ABCICode() { + ctx.Logger().Error(fmt.Sprintf("Invalid concurrent execution of deliverTx index=%d", i)) + metrics.IncrFailedConcurrentDeliverTxCounter() + ok = false + } + } + + return txResults, ok +} + func (app *App) ProcessBlock(ctx sdk.Context, txs [][]byte, req BlockProcessRequest, lastCommit abci.CommitInfo) ([]abci.Event, []*abci.ExecTxResult, abci.ResponseEndBlock, error) { goCtx := app.decorateContextWithDexMemState(ctx.Context()) - ctx = ctx.WithContext(goCtx) + ctx = ctx.WithContext(goCtx).WithContextMemCache(sdk.NewContextMemCache()) events := []abci.Event{} beginBlockReq := abci.RequestBeginBlock{ @@ -957,31 +1159,83 @@ func (app *App) ProcessBlock(ctx sdk.Context, txs [][]byte, req BlockProcessRequ // } // app.batchVerifier.VerifyTxs(ctx, typedTxs) - txResults := []*abci.ExecTxResult{} - for _, tx := range txs { - // ctx = ctx.WithContext(context.WithValue(ctx.Context(), ante.ContextKeyTxIndexKey, i)) - deliverTxResp := app.DeliverTx(ctx, abci.RequestDeliverTx{ - Tx: tx, - }) - txResults = append(txResults, &abci.ExecTxResult{ - Code: deliverTxResp.Code, - Data: deliverTxResp.Data, - Log: deliverTxResp.Log, - Info: deliverTxResp.Info, - GasWanted: deliverTxResp.GasWanted, - GasUsed: deliverTxResp.GasUsed, - Events: deliverTxResp.Events, - Codespace: deliverTxResp.Codespace, - }) + dependencyDag, err := app.AccessControlKeeper.BuildDependencyDag(ctx, app.txDecoder, app.GetAnteDepGenerator(), txs) + + var txResults []*abci.ExecTxResult + + switch err { + case nil: + // Only run concurrently if no error + // Branch off the current context and pass a cached context to the concurrent delivered TXs that are shared. + // runTx will write to this ephermeral CacheMultiStore, after the process block is done, Write() is called on this + // CacheMultiStore where it writes the data to the parent store (DeliverState) in sorted Key order to maintain + // deterministic ordering between validators in the case of concurrent deliverTXs + processBlockCtx, processBlockCache := app.CacheContext(ctx) + concurrentResults, ok := app.ProcessBlockConcurrent( + processBlockCtx, + txs, + dependencyDag.CompletionSignalingMap, + dependencyDag.BlockingSignalsMap, + dependencyDag.TxMsgAccessOpMapping, + ) + if ok { + ctx.Logger().Info("Concurrent Execution succeeded, proceeding to commit block") + txResults = concurrentResults + // Write the results back to the concurrent contexts - if concurrent execution fails, + // this should not be called and the state is rolled back and retried with synchronous execution + processBlockCache.Write() + } else { + ctx.Logger().Error("Concurrent Execution failed, retrying with Synchronous") + txResults = app.ProcessBlockSynchronous(ctx, txs) + } + + // Write the results back to the concurrent contexts + processBlockCache.Write() + case acltypes.ErrGovMsgInBlock: + ctx.Logger().Info(fmt.Sprintf("Gov msg found while building DAG, processing synchronously: %s", err)) + txResults = app.ProcessBlockSynchronous(ctx, txs) + metrics.IncrDagBuildErrorCounter(metrics.GovMsgInBlock) + default: + ctx.Logger().Error(fmt.Sprintf("Error while building DAG, processing synchronously: %s", err)) + txResults = app.ProcessBlockSynchronous(ctx, txs) + metrics.IncrDagBuildErrorCounter(metrics.FailedToBuild) } + + // Finalize all Bank Module Transfers here so that events are included + lazyWriteEvents := app.BankKeeper.WriteDeferredDepositsToModuleAccounts(ctx) + events = append(events, lazyWriteEvents...) + + ctx = app.enrichContextWithTxResults(ctx, txResults) endBlockResp := app.EndBlock(ctx, abci.RequestEndBlock{ Height: req.GetHeight(), }) + events = append(events, endBlockResp.Events...) return events, txResults, endBlockResp, nil } +func (app *App) enrichContextWithTxResults(ctx sdk.Context, txResults []*abci.ExecTxResult) sdk.Context { + wasmContractsWithIncorrectDependencies := []sdk.AccAddress{} + for _, txResult := range txResults { + if txResult.Codespace == acltypes.ModuleName && txResult.Code == 2 { + for _, event := range txResult.Events { + if event.Type == wasmbinding.EventTypeWasmContractWithIncorrectDependency { + for _, attr := range event.Attributes { + if attr.Key == wasmbinding.AttributeKeyWasmContractAddress { + addr, err := sdk.AccAddressFromBech32(attr.Value) + if err == nil { + wasmContractsWithIncorrectDependencies = append(wasmContractsWithIncorrectDependencies, addr) + } + } + } + } + } + } + } + return ctx.WithContext(context.WithValue(ctx.Context(), aclconstants.BadWasmDependencyAddressesKey, wasmContractsWithIncorrectDependencies)) +} + func (app *App) getFinalizeBlockResponse(appHash []byte, events []abci.Event, txResults []*abci.ExecTxResult, endBlockResp abci.ResponseEndBlock) abci.ResponseFinalizeBlock { return abci.ResponseFinalizeBlock{ Events: events, @@ -1118,6 +1372,7 @@ func GetMaccPerms() map[string][]string { func initParamsKeeper(appCodec codec.BinaryCodec, legacyAmino *codec.LegacyAmino, key, tkey sdk.StoreKey) paramskeeper.Keeper { paramsKeeper := paramskeeper.NewKeeper(appCodec, legacyAmino, key, tkey) + paramsKeeper.Subspace(acltypes.ModuleName) paramsKeeper.Subspace(authtypes.ModuleName) paramsKeeper.Subspace(banktypes.ModuleName) paramsKeeper.Subspace(stakingtypes.ModuleName) diff --git a/app/apptesting/test_suite.go b/app/apptesting/test_suite.go index 860d6192c3..01c71943d3 100644 --- a/app/apptesting/test_suite.go +++ b/app/apptesting/test_suite.go @@ -143,7 +143,8 @@ func (s *KeeperTestHelper) BuildTx( txBuilder client.TxBuilder, msgs []sdk.Msg, sigV2 signing.SignatureV2, - memo string, txFee sdk.Coins, + memo string, + txFee sdk.Coins, gasLimit uint64, ) authsigning.Tx { err := txBuilder.SetMsgs(msgs[0]) diff --git a/app/params/config.go b/app/params/config.go index 07a0c81595..1b6e8c7fe2 100644 --- a/app/params/config.go +++ b/app/params/config.go @@ -21,7 +21,7 @@ const ( Bech32PrefixAccAddr = "sei" ) -var UnsafeBypassCommitTimeoutOverride bool = true +var UnsafeBypassCommitTimeoutOverride = true var ( // Bech32PrefixAccPub defines the Bech32 prefix of an account's public key. diff --git a/app/test_helpers.go b/app/test_helpers.go index 814b3b8604..406c9744fc 100644 --- a/app/test_helpers.go +++ b/app/test_helpers.go @@ -140,6 +140,7 @@ func Setup(isCheckTx bool) *App { wasm.EnableAllProposals, &cosmostestutil.TestAppOpts{}, EmptyWasmOpts, + EmptyACLOpts, ) if !isCheckTx { genesisState := NewDefaultGenesisState(cdc) @@ -183,6 +184,7 @@ func SetupTestingAppWithLevelDb(isCheckTx bool) (*App, func()) { wasm.EnableAllProposals, &cosmostestutil.TestAppOpts{}, EmptyWasmOpts, + EmptyACLOpts, ) if !isCheckTx { genesisState := NewDefaultGenesisState(cdc) diff --git a/cmd/seid/cmd/root.go b/cmd/seid/cmd/root.go index ba67485460..cc1bd28d9c 100644 --- a/cmd/seid/cmd/root.go +++ b/cmd/seid/cmd/root.go @@ -21,6 +21,7 @@ import ( "github.com/cosmos/cosmos-sdk/snapshots" "github.com/cosmos/cosmos-sdk/store" sdk "github.com/cosmos/cosmos-sdk/types" + aclkeeper "github.com/cosmos/cosmos-sdk/x/accesscontrol/keeper" authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" "github.com/cosmos/cosmos-sdk/x/auth/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" @@ -249,6 +250,7 @@ func newApp( wasm.EnableAllProposals, appOpts, []wasm.Option{}, + []aclkeeper.Option{}, baseapp.SetPruning(pruningOpts), baseapp.SetMinGasPrices(cast.ToString(appOpts.Get(server.FlagMinGasPrices))), baseapp.SetMinRetainBlocks(cast.ToUint64(appOpts.Get(server.FlagMinRetainBlocks))), @@ -284,12 +286,12 @@ func appExport( } if height != -1 { - exportableApp = app.New(logger, db, traceStore, false, map[int64]bool{}, cast.ToString(appOpts.Get(flags.FlagHome)), uint(1), encCfg, app.GetWasmEnabledProposals(), appOpts, app.EmptyWasmOpts) + exportableApp = app.New(logger, db, traceStore, false, map[int64]bool{}, cast.ToString(appOpts.Get(flags.FlagHome)), uint(1), encCfg, app.GetWasmEnabledProposals(), appOpts, app.EmptyWasmOpts, app.EmptyACLOpts) if err := exportableApp.LoadHeight(height); err != nil { return servertypes.ExportedApp{}, err } } else { - exportableApp = app.New(logger, db, traceStore, true, map[int64]bool{}, cast.ToString(appOpts.Get(flags.FlagHome)), uint(1), encCfg, app.GetWasmEnabledProposals(), appOpts, app.EmptyWasmOpts) + exportableApp = app.New(logger, db, traceStore, true, map[int64]bool{}, cast.ToString(appOpts.Get(flags.FlagHome)), uint(1), encCfg, app.GetWasmEnabledProposals(), appOpts, app.EmptyWasmOpts, app.EmptyACLOpts) } return exportableApp.ExportAppStateAndValidators(forZeroHeight, jailAllowedAddrs) diff --git a/go.mod b/go.mod index 35a40c2c79..60e28f34b7 100644 --- a/go.mod +++ b/go.mod @@ -24,8 +24,8 @@ require ( go.opentelemetry.io/otel/exporters/jaeger v1.9.0 go.opentelemetry.io/otel/sdk v1.9.0 go.opentelemetry.io/otel/trace v1.9.0 - google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a - google.golang.org/grpc v1.50.1 + google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc + google.golang.org/grpc v1.48.0 google.golang.org/protobuf v1.28.1 gopkg.in/yaml.v2 v2.4.0 ) @@ -48,6 +48,7 @@ require ( github.com/creachadair/taskgroup v0.3.2 // indirect github.com/danieljoos/wincred v1.0.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/deckarep/golang-set v1.8.0 // indirect github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect github.com/dgraph-io/badger/v3 v3.2103.2 // indirect github.com/dgraph-io/ristretto v0.1.0 // indirect @@ -90,7 +91,7 @@ require ( github.com/lib/pq v1.10.6 // indirect github.com/libp2p/go-buffer-pool v0.0.2 // indirect github.com/magiconair/properties v1.8.6 // indirect - github.com/mattn/go-isatty v0.0.14 // indirect + github.com/mattn/go-isatty v0.0.16 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect @@ -108,6 +109,7 @@ require ( github.com/rs/cors v1.8.2 // indirect github.com/rs/zerolog v1.26.1 // indirect github.com/sasha-s/go-deadlock v0.3.1 // indirect + github.com/savaki/jq v0.0.0-20161209013833-0e6baecebbf8 // indirect github.com/spf13/afero v1.8.2 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect @@ -117,13 +119,14 @@ require ( github.com/tendermint/btcd v0.1.1 // indirect github.com/tendermint/crypto v0.0.0-20191022145703-50d29ede1e15 // indirect github.com/tendermint/go-amino v0.16.0 // indirect + github.com/yourbasic/graph v0.0.0-20210606180040-8ecfec1c2869 // indirect github.com/zondax/hid v0.9.0 // indirect go.etcd.io/bbolt v1.3.6 // indirect go.opencensus.io v0.23.0 // indirect golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect golang.org/x/net v0.0.0-20220909164309-bea034e7d591 // indirect golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 // indirect - golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect + golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 // indirect golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 // indirect golang.org/x/text v0.3.8 // indirect gopkg.in/ini.v1 v1.66.4 // indirect @@ -132,7 +135,7 @@ require ( ) replace ( - github.com/cosmos/cosmos-sdk => github.com/sei-protocol/sei-cosmos v0.1.195 + github.com/cosmos/cosmos-sdk => github.com/sei-protocol/sei-cosmos v0.1.249 github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 github.com/keybase/go-keychain => github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 github.com/tendermint/tendermint => github.com/sei-protocol/sei-tendermint v0.1.59 diff --git a/go.sum b/go.sum index df954c8d3f..05b2d736e9 100644 --- a/go.sum +++ b/go.sum @@ -291,6 +291,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= +github.com/deckarep/golang-set v1.8.0 h1:sk9/l/KqpunDwP7pSjUg0keiOOLEnOBHzykLrsPppp4= +github.com/deckarep/golang-set v1.8.0/go.mod h1:5nI87KwE7wgsBU1F4GKAw2Qod7p5kyS383rP6+o6qqo= github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= github.com/denis-tingaikin/go-header v0.4.3/go.mod h1:0wOCWuN71D5qIgE2nz9KrKmuYBAC2Mra5RassOIQ2/c= github.com/denisenkom/go-mssqldb v0.12.0/go.mod h1:iiK0YP1ZeepvmBQk/QpLEhhTNJgfzrpArPY/aFvc9yU= @@ -811,8 +813,9 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= @@ -1095,12 +1098,14 @@ github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0 github.com/sanposhiho/wastedassign/v2 v2.0.6/go.mod h1:KyZ0MWTwxxBmfwn33zh3k1dmsbF2ud9pAAGfoLfjhtI= github.com/sasha-s/go-deadlock v0.3.1 h1:sqv7fDNShgjcaxkO0JNcOAlr8B9+cV5Ey/OB71efZx0= github.com/sasha-s/go-deadlock v0.3.1/go.mod h1:F73l+cr82YSh10GxyRI6qZiCgK64VaZjwesgfQ1/iLM= +github.com/savaki/jq v0.0.0-20161209013833-0e6baecebbf8 h1:ajJQhvqPSQFJJ4aV5mDAMx8F7iFi6Dxfo6y62wymLNs= +github.com/savaki/jq v0.0.0-20161209013833-0e6baecebbf8/go.mod h1:Nw/CCOXNyF5JDd6UpYxBwG5WWZ2FOJ/d5QnXL4KQ6vY= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= github.com/securego/gosec/v2 v2.11.0/go.mod h1:SX8bptShuG8reGC0XS09+a4H2BoWSJi+fscA+Pulbpo= github.com/segmentio/fasthash v1.0.3/go.mod h1:waKX8l2N8yckOgmSsXJi7x1ZfdKZ4x7KRMzBtS3oedY= -github.com/sei-protocol/sei-cosmos v0.1.195 h1:WfHp+1x6DRH2jl936ogDLo443EtSvbNY62pZ7msVY9s= -github.com/sei-protocol/sei-cosmos v0.1.195/go.mod h1:bNUxufOqMG4cRokrw2Q30rQ1rOwJYjHTWsQoH+UZTFY= +github.com/sei-protocol/sei-cosmos v0.1.249 h1:beHuyOOGxDewo1G7l067OPO56BKyfVi1boDiuS8jNfw= +github.com/sei-protocol/sei-cosmos v0.1.249/go.mod h1:KPV8lFdD2Ki/M2wZTpfX3LCcuMAZnmcUzYJycjbmOYM= github.com/sei-protocol/sei-tendermint v0.1.59 h1:POGL60PumMQHF4EzAHzvkGfDnodQJLHpl65LuiwSO/Y= github.com/sei-protocol/sei-tendermint v0.1.59/go.mod h1:Olwbjyagrpoxj5DAUhHxMTWDVEfQ3FYdpypaJ3+6Hs8= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= @@ -1252,6 +1257,8 @@ github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1: github.com/yagipy/maintidx v1.0.0/go.mod h1:0qNf/I/CCZXSMhsRsrEPDZ+DkekpKLXAJfsTACwgXLk= github.com/ybbus/jsonrpc v2.1.2+incompatible/go.mod h1:XJrh1eMSzdIYFbM08flv0wp5G35eRniyeGut1z+LSiE= github.com/yeya24/promlinter v0.2.0/go.mod h1:u54lkmBOZrpEbQQ6gox2zWKKLKu2SGe+2KOiextY+IA= +github.com/yourbasic/graph v0.0.0-20210606180040-8ecfec1c2869 h1:7v7L5lsfw4w8iqBBXETukHo4IPltmD+mWoLRYUmeGN8= +github.com/yourbasic/graph v0.0.0-20210606180040-8ecfec1c2869/go.mod h1:Rfzr+sqaDreiCaoQbFCu3sTXxeFq/9kXRuyOoSlGQHE= github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= @@ -1631,8 +1638,9 @@ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg= -golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 h1:v6hYoSR9T5oet+pMXwUWkbiVqx/63mlHjefrHmxwfeY= +golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1908,8 +1916,8 @@ google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a h1:GH6UPn3ixhWcKDhpnEC55S75cerLPdpp3hrhfKYjZgw= -google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= +google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc h1:Nf+EdcTLHR8qDNN/KfkQL0u0ssxt9OhbaWCl5C0ucEI= +google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= google.golang.org/grpc v1.33.2 h1:EQyQC3sa8M+p6Ulc8yy9SWSS2GVwyRc83gAbG8lrl4o= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= diff --git a/loadtest/config.json b/loadtest/config.json index 277844036e..ddbc7b6805 100644 --- a/loadtest/config.json +++ b/loadtest/config.json @@ -1,63 +1,71 @@ { - "batch_size": 1, - "chain_id": "sei-loadtest-testnet", - "orders_per_block": 2, - "rounds": 5, - "price_distribution": { - "min": "45", - "max": "55", - "number_of_distinct_values": 20 - }, - "quantity_distribution": { - "min": "1", - "max": "21", - "number_of_distinct_values": 20 - }, - "dex_message_type_distribution": { - "limit_order_percentage": "0.2", - "market_order_percentage": "0.8" - }, - "message_type": "bank,dex,staking,tokenfactory", - "contract_distribution": [ - { - "contract_address":"sei14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sh9m79m", - "percentage":"0.1" + "msgs_per_tx": 10, + "chain_id": "sei-loadtest-testnet", + "txs_per_block": 400, + "rounds": 5, + "price_distribution": { + "min": "45", + "max": "55", + "number_of_distinct_values": 20 }, - { - "contract_address":"sei1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrqms7u8a", - "percentage":"0.1" + "quantity_distribution": { + "min": "1", + "max": "21", + "number_of_distinct_values": 20 }, - { - "contract_address":"sei17p9rzwnnfxcjp32un9ug7yhhzgtkhvl9jfksztgw5uh69wac2pgsrtqewe", - "percentage":"0.1" + "message_type_distribution": { + "dex": { + "limit_order_percentage": "0.2", + "market_order_percentage": "0.8" + }, + "staking": { + "delegate_percentage": "0.5", + "undelegate_percentage": "0.25", + "begin_redelegate_percentage": "0.25" + } }, - { - "contract_address":"sei1yw4xvtc43me9scqfr2jr2gzvcxd3a9y4eq7gaukreugw2yd2f8tsy4qgdm", - "percentage":"0.1" - }, - { - "contract_address":"sei1hulx7cgvpfcvg83wk5h96sedqgn72n026w6nl47uht554xhvj9nstdktte", - "percentage":"0.1" - }, - { - "contract_address":"sei1fzm6gzyccl8jvdv3qq6hp9vs6ylaruervs4m06c7k0ntzn2f8faq8un0p6", - "percentage":"0.1" - }, - { - "contract_address":"sei1wl59k23zngj34l7d42y9yltask7rjlnxgccawc7ltrknp6n52fpsj6ctln", - "percentage":"0.1" - }, - { - "contract_address":"sei182nff4ttmvshn6yjlqj5czapfcav9434l2qzz8aahf5pxnyd33ts2pdy3l", - "percentage":"0.1" - }, - { - "contract_address":"sei1k8re7jwz6rnnwrktnejdwkwnncte7ek7gt29gvnl3sdrg9mtnqksw4tqd9", - "percentage":"0.1" - }, - { - "contract_address":"sei1nwnejwsdpqktusvh8qhxe5arsznjd5asdwutmaz9n5qcpl3dcmhs9eeuca", - "percentage":"0.1" - } - ] -} \ No newline at end of file + "message_type": "bank,dex,staking,tokenfactory", + "run_oracle": true, + "contract_distribution": [ + { + "contract_address":"sei14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sh9m79m", + "percentage":"0.1" + }, + { + "contract_address":"sei1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrqms7u8a", + "percentage":"0.1" + }, + { + "contract_address":"sei17p9rzwnnfxcjp32un9ug7yhhzgtkhvl9jfksztgw5uh69wac2pgsrtqewe", + "percentage":"0.1" + }, + { + "contract_address":"sei1yw4xvtc43me9scqfr2jr2gzvcxd3a9y4eq7gaukreugw2yd2f8tsy4qgdm", + "percentage":"0.1" + }, + { + "contract_address":"sei1hulx7cgvpfcvg83wk5h96sedqgn72n026w6nl47uht554xhvj9nstdktte", + "percentage":"0.1" + }, + { + "contract_address":"sei1fzm6gzyccl8jvdv3qq6hp9vs6ylaruervs4m06c7k0ntzn2f8faq8un0p6", + "percentage":"0.1" + }, + { + "contract_address":"sei1wl59k23zngj34l7d42y9yltask7rjlnxgccawc7ltrknp6n52fpsj6ctln", + "percentage":"0.1" + }, + { + "contract_address":"sei182nff4ttmvshn6yjlqj5czapfcav9434l2qzz8aahf5pxnyd33ts2pdy3l", + "percentage":"0.1" + }, + { + "contract_address":"sei1k8re7jwz6rnnwrktnejdwkwnncte7ek7gt29gvnl3sdrg9mtnqksw4tqd9", + "percentage":"0.1" + }, + { + "contract_address":"sei1nwnejwsdpqktusvh8qhxe5arsznjd5asdwutmaz9n5qcpl3dcmhs9eeuca", + "percentage":"0.1" + } + ] +} diff --git a/loadtest/contracts/deploy_ten_contracts.sh b/loadtest/contracts/deploy_ten_contracts.sh index 6c0dcb78a0..3d65af5208 100644 --- a/loadtest/contracts/deploy_ten_contracts.sh +++ b/loadtest/contracts/deploy_ten_contracts.sh @@ -15,17 +15,17 @@ echo cd $seihome/loadtest/contracts/mars && cargo build && docker run --rm -v "$(pwd)":/code \ --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ - cosmwasm/rust-optimizer:0.12.5 + cosmwasm/rust-optimizer:0.12.6 cd $seihome/loadtest/contracts/saturn && cargo build && docker run --rm -v "$(pwd)":/code \ --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ - cosmwasm/rust-optimizer:0.12.5 + cosmwasm/rust-optimizer:0.12.6 cd $seihome/loadtest/contracts/venus && cargo build && docker run --rm -v "$(pwd)":/code \ --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ - cosmwasm/rust-optimizer:0.12.5 + cosmwasm/rust-optimizer:0.12.6 # Deploy all contracts echo "Deploying contracts..." diff --git a/loadtest/loadtest_client.go b/loadtest/loadtest_client.go new file mode 100644 index 0000000000..64e2dbcac5 --- /dev/null +++ b/loadtest/loadtest_client.go @@ -0,0 +1,199 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "os" + "path/filepath" + "strings" + "sync" + "time" + + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + typestx "github.com/cosmos/cosmos-sdk/types/tx" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "google.golang.org/grpc" +) + +type LoadTestClient struct { + LoadTestConfig Config + TestConfig EncodingConfig + TxClient typestx.ServiceClient + TxHashFile *os.File + SignerClient *SignerClient + ChainID string + TxHashList []string + TxHashListMutex *sync.Mutex + GrpcConn *grpc.ClientConn + StakingQueryClient stakingtypes.QueryClient + //// Staking specific variables + Validators []stakingtypes.Validator + // DelegationMap is a map of delegator -> validator -> delegated amount + DelegationMap map[string]map[string]int + //// Tokenfactory specific variables + TokenFactoryDenomOwner map[string]string +} + +func NewLoadTestClient() *LoadTestClient { + grpcConn, _ := grpc.Dial( + "127.0.0.1:9090", + grpc.WithInsecure(), + ) + TxClient := typestx.NewServiceClient(grpcConn) + + config := Config{} + pwd, _ := os.Getwd() + file, _ := os.ReadFile(pwd + "/loadtest/config.json") + if err := json.Unmarshal(file, &config); err != nil { + panic(err) + } + + // setup output files + userHomeDir, _ := os.UserHomeDir() + _ = os.Mkdir(filepath.Join(userHomeDir, "outputs"), os.ModePerm) + filename := filepath.Join(userHomeDir, "outputs", "test_tx_hash") + _ = os.Remove(filename) + outputFile, _ := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644) + + return &LoadTestClient{ + LoadTestConfig: config, + TestConfig: TestConfig, + TxClient: TxClient, + TxHashFile: outputFile, + SignerClient: NewSignerClient(), + ChainID: config.ChainID, + TxHashList: []string{}, + TxHashListMutex: &sync.Mutex{}, + GrpcConn: grpcConn, + StakingQueryClient: stakingtypes.NewQueryClient(grpcConn), + DelegationMap: map[string]map[string]int{}, + TokenFactoryDenomOwner: map[string]string{}, + } +} + +func (c *LoadTestClient) SetValidators() { + if strings.Contains(c.LoadTestConfig.MessageType, "staking") { + if resp, err := c.StakingQueryClient.Validators(context.Background(), &stakingtypes.QueryValidatorsRequest{}); err != nil { + panic(err) + } else { + c.Validators = resp.Validators + } + } +} + +func (c *LoadTestClient) Close() { + c.GrpcConn.Close() +} + +func (c *LoadTestClient) AppendTxHash(txHash string) { + c.TxHashListMutex.Lock() + defer c.TxHashListMutex.Unlock() + + c.TxHashList = append(c.TxHashList, txHash) +} + +func (c *LoadTestClient) WriteTxHashToFile() { + file := c.TxHashFile + for _, txHash := range c.TxHashList { + txHashLine := fmt.Sprintf("%s\n", txHash) + if _, err := file.WriteString(txHashLine); err != nil { + panic(err) + } + } +} + +func (c *LoadTestClient) BuildTxs() (workgroups []*sync.WaitGroup, sendersList [][]func() string) { + config := c.LoadTestConfig + numberOfAccounts := config.TxsPerBlock / config.MsgsPerTx * 2 // * 2 because we need two sets of accounts + activeAccounts := []int{} + inactiveAccounts := []int{} + + for i := 0; i < int(numberOfAccounts); i++ { + if i%2 == 0 { + activeAccounts = append(activeAccounts, i) + } else { + inactiveAccounts = append(inactiveAccounts, i) + } + } + + valKeys := c.SignerClient.GetValKeys() + + for i := 0; i < int(config.Rounds); i++ { + fmt.Printf("Preparing %d-th round\n", i) + + wg := &sync.WaitGroup{} + var senders []func() string + workgroups = append(workgroups, wg) + + for j, account := range activeAccounts { + key := c.SignerClient.GetKey(uint64(account)) + + msg, failureExpected := c.generateMessage(config, key, config.MsgsPerTx) + txBuilder := TestConfig.TxConfig.NewTxBuilder() + _ = txBuilder.SetMsgs(msg) + seqDelta := uint64(i / 2) + mode := typestx.BroadcastMode_BROADCAST_MODE_SYNC + if j == len(activeAccounts)-1 { + mode = typestx.BroadcastMode_BROADCAST_MODE_BLOCK + } + // Note: There is a potential race condition here with seqnos + // in which a later seqno is delievered before an earlier seqno + // In practice, we haven't run into this issue so we'll leave this + // as is. + sender := SendTx(key, &txBuilder, mode, seqDelta, failureExpected, *c) + wg.Add(1) + senders = append(senders, func() string { + defer wg.Done() + return sender() + }) + } + + senders = append(senders, c.GenerateOracleSenders(i, config, valKeys, wg)...) + + sendersList = append(sendersList, senders) + inactiveAccounts, activeAccounts = activeAccounts, inactiveAccounts + } + + return workgroups, sendersList +} + +func (c *LoadTestClient) GenerateOracleSenders(i int, config Config, valKeys []cryptotypes.PrivKey, waitGroup *sync.WaitGroup) []func() string { + senders := []func() string{} + if config.RunOracle && i%2 == 0 { + for _, valKey := range valKeys { + // generate oracle tx + msg := generateOracleMessage(valKey) + txBuilder := TestConfig.TxConfig.NewTxBuilder() + _ = txBuilder.SetMsgs(msg) + seqDelta := uint64(i / 2) + mode := typestx.BroadcastMode_BROADCAST_MODE_SYNC + sender := SendTx(valKey, &txBuilder, mode, seqDelta, false, *c) + waitGroup.Add(1) + senders = append(senders, func() string { + defer waitGroup.Done() + return sender() + }) + } + } + return senders +} + +func (c *LoadTestClient) SendTxs(workgroups []*sync.WaitGroup, sendersList [][]func() string) { + lastHeight := getLastHeight() + for i := 0; i < int(c.LoadTestConfig.Rounds); i++ { + newHeight := getLastHeight() + for newHeight == lastHeight { + time.Sleep(10 * time.Millisecond) + newHeight = getLastHeight() + } + fmt.Printf("Sending %d-th block\n", i) + senders := sendersList[i] + wg := workgroups[i] + for _, sender := range senders { + go sender() + } + wg.Wait() + lastHeight = newHeight + } +} diff --git a/loadtest/main.go b/loadtest/main.go index 6d77980e13..b453e43ba2 100644 --- a/loadtest/main.go +++ b/loadtest/main.go @@ -1,145 +1,32 @@ package main import ( - "context" "encoding/json" - "flag" "fmt" "math/rand" - "os" "os/exec" - "path/filepath" "strconv" "strings" - "sync" "time" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" tokenfactorytypes "github.com/sei-protocol/sei-chain/x/tokenfactory/types" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/cosmos/cosmos-sdk/std" + "github.com/cosmos/cosmos-sdk/x/auth/tx" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" - "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/codec/types" - "github.com/cosmos/cosmos-sdk/std" sdk "github.com/cosmos/cosmos-sdk/types" - typestx "github.com/cosmos/cosmos-sdk/types/tx" - "github.com/cosmos/cosmos-sdk/x/auth/tx" "github.com/sei-protocol/sei-chain/app" - "github.com/sei-protocol/sei-chain/utils" dextypes "github.com/sei-protocol/sei-chain/x/dex/types" - "google.golang.org/grpc" + oracletypes "github.com/sei-protocol/sei-chain/x/oracle/types" ) -const ( - Bank string = "bank" - FailureBankMalformed string = "failure_bank_malformed" - FailureBankInvalid string = "failure_bank_invalid" - FailureDexMalformed string = "failure_dex_malformed" - FailureDexInvalid string = "failure_dex_invalid" - Dex string = "dex" - Staking string = "staking" - Tokenfactory string = "tokenfactory" - Limit string = "limit" - Market string = "market" -) - -type EncodingConfig struct { - InterfaceRegistry types.InterfaceRegistry - // NOTE: this field will be renamed to Codec - Marshaler codec.Codec - TxConfig client.TxConfig - Amino *codec.LegacyAmino -} - -type Config struct { - BatchSize uint64 `json:"batch_size"` - ChainID string `json:"chain_id"` - OrdersPerBlock uint64 `json:"orders_per_block"` - Rounds uint64 `json:"rounds"` - MessageType string `json:"message_type"` - PriceDistr NumericDistribution `json:"price_distribution"` - QuantityDistr NumericDistribution `json:"quantity_distribution"` - DexMsgTypeDistr MsgTypeDistribution `json:"dex_message_type_distribution"` - ContractDistr ContractDistributions `json:"contract_distribution"` - Constant bool `json:"constant"` - ConstLoadInterval int64 `json:"const_load_interval"` -} - -type NumericDistribution struct { - Min sdk.Dec `json:"min"` - Max sdk.Dec `json:"max"` - NumDistinct int64 `json:"number_of_distinct_values"` -} - -func (d *NumericDistribution) Sample() sdk.Dec { - steps := sdk.NewDec(rand.Int63n(d.NumDistinct)) - return d.Min.Add(d.Max.Sub(d.Min).QuoInt64(d.NumDistinct).Mul(steps)) -} - -// Invalid numeric distribution sample -func (d *NumericDistribution) InvalidSample() sdk.Dec { - steps := sdk.NewDec(rand.Int63n(d.NumDistinct)) - if rand.Float64() < 0.5 { - return d.Min.Add(d.Max.Sub(d.Min).QuoInt64(d.NumDistinct).Mul(steps)) - } - return d.Max.Add(d.Max.Sub(d.Min).QuoInt64(d.NumDistinct).Mul(steps)) -} - -type MsgTypeDistribution struct { - LimitOrderPct sdk.Dec `json:"limit_order_percentage"` - MarketOrderPct sdk.Dec `json:"market_order_percentage"` -} - -func (d *MsgTypeDistribution) Sample() string { - if !d.LimitOrderPct.Add(d.MarketOrderPct).Equal(sdk.OneDec()) { - panic("Distribution percentages must add up to 1") - } - randNum := sdk.MustNewDecFromStr(fmt.Sprintf("%f", rand.Float64())) - if randNum.LT(d.LimitOrderPct) { - return Limit - } - return "market" -} - -type ContractDistributions []ContractDistribution - -func (d *ContractDistributions) Sample() string { - if !utils.Reduce(*d, func(i ContractDistribution, o sdk.Dec) sdk.Dec { return o.Add(i.Percentage) }, sdk.ZeroDec()).Equal(sdk.OneDec()) { - panic("Distribution percentages must add up to 1") - } - randNum := sdk.MustNewDecFromStr(fmt.Sprintf("%f", rand.Float64())) - cumPct := sdk.ZeroDec() - for _, dist := range *d { - cumPct = cumPct.Add(dist.Percentage) - if randNum.LTE(cumPct) { - return dist.ContractAddr - } - } - panic("this should never be triggered") -} - -type ContractDistribution struct { - ContractAddr string `json:"contract_address"` - Percentage sdk.Dec `json:"percentage"` -} - -var ( - TestConfig EncodingConfig - TxClient typestx.ServiceClient - StakingQueryClient stakingtypes.QueryClient - TxHashFile *os.File - ChainID string - //// Staking specific variables - Validators []stakingtypes.Validator - // DelegationMap is a map of delegator -> validator -> delegated amount - DelegationMap map[string]map[string]int - //// Tokenfactory specific variables - TokenFactoryDenomOwner map[string]string -) +var TestConfig EncodingConfig const ( VortexData = "{\"position_effect\":\"Open\",\"leverage\":\"1\"}" @@ -164,112 +51,31 @@ func init() { app.ModuleBasics.RegisterInterfaces(TestConfig.InterfaceRegistry) } -func run(config Config) { - ChainID = config.ChainID - grpcConn, _ := grpc.Dial( - "127.0.0.1:9090", - grpc.WithInsecure(), - ) - defer grpcConn.Close() - TxClient = typestx.NewServiceClient(grpcConn) - StakingQueryClient = stakingtypes.NewQueryClient(grpcConn) - createOutputFiles() - var mu sync.Mutex - batchSize := config.BatchSize - if config.OrdersPerBlock < batchSize { - panic("Must have more orders per block than batch size") - } - setValidators(config) - DelegationMap = map[string]map[string]int{} - TokenFactoryDenomOwner = map[string]string{} - numberOfAccounts := config.OrdersPerBlock / batchSize * 2 // * 2 because we need two sets of accounts - activeAccounts := []int{} - inactiveAccounts := []int{} - for i := 0; i < int(numberOfAccounts); i++ { - if i%2 == 0 { - activeAccounts = append(activeAccounts, i) - } else { - inactiveAccounts = append(inactiveAccounts, i) - } +func run() { + client := NewLoadTestClient() + client.SetValidators() + config := client.LoadTestConfig + + defer client.Close() + + if config.TxsPerBlock < config.MsgsPerTx { + panic("Must have more TxsPerBlock than MsgsPerTx") } - wgs := []*sync.WaitGroup{} - sendersList := [][]func() string{} configString, _ := json.Marshal(config) - fmt.Printf("Running with \n %s \ns", string(configString)) + fmt.Printf("Running with \n %s \n", string(configString)) fmt.Printf("%s - Starting block prepare\n", time.Now().Format("2006-01-02T15:04:05")) - for i := 0; i < int(config.Rounds); i++ { - fmt.Printf("Preparing %d-th round\n", i) - wg := &sync.WaitGroup{} - var senders []func() string - wgs = append(wgs, wg) - for j, account := range activeAccounts { - key := GetKey(uint64(account)) - - msg, failureExpected := generateMessage(config, key, batchSize) - txBuilder := TestConfig.TxConfig.NewTxBuilder() - _ = txBuilder.SetMsgs(msg) - seqDelta := uint64(i / 2) - mode := typestx.BroadcastMode_BROADCAST_MODE_SYNC - if j == len(activeAccounts)-1 { - mode = typestx.BroadcastMode_BROADCAST_MODE_BLOCK - } - // Note: There is a potential race condition here with seqnos - // in which a later seqno is delievered before an earlier seqno - // In practice, we haven't run into this issue so we'll leave this - // as is. - sender := SendTx(key, &txBuilder, mode, seqDelta, &mu, failureExpected) - wg.Add(1) - senders = append(senders, func() string { - defer wg.Done() - return sender() - }) - } - sendersList = append(sendersList, senders) + workgroups, sendersList := client.BuildTxs() - inactiveAccounts, activeAccounts = activeAccounts, inactiveAccounts - } + client.SendTxs(workgroups, sendersList) - lastHeight := getLastHeight() - for i := 0; i < int(config.Rounds); i++ { - newHeight := getLastHeight() - for newHeight == lastHeight { - time.Sleep(10 * time.Millisecond) - newHeight = getLastHeight() - } - fmt.Printf("Sending %d-th block\n", i) - senders := sendersList[i] - wg := wgs[i] - for _, sender := range senders { - go sender() - } - wg.Wait() - lastHeight = newHeight - } + // Records the resulting TxHash to file + client.WriteTxHashToFile() fmt.Printf("%s - Finished\n", time.Now().Format("2006-01-02T15:04:05")) } -func createOutputFiles() { - userHomeDir, _ := os.UserHomeDir() - _ = os.Mkdir(filepath.Join(userHomeDir, "outputs"), os.ModePerm) - filename := filepath.Join(userHomeDir, "outputs", "test_tx_hash") - _ = os.Remove(filename) - file, _ := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o644) - TxHashFile = file -} - -func setValidators(config Config) { - if strings.Contains(config.MessageType, "staking") { - if resp, err := StakingQueryClient.Validators(context.Background(), &stakingtypes.QueryValidatorsRequest{}); err != nil { - panic(err) - } else { - Validators = resp.Validators - } - } -} - -func generateMessage(config Config, key cryptotypes.PrivKey, batchSize uint64) (sdk.Msg, bool) { +func (c *LoadTestClient) generateMessage(config Config, key cryptotypes.PrivKey, msgPerTx uint64) (sdk.Msg, bool) { var msg sdk.Msg messageTypes := strings.Split(config.MessageType, ",") rand.Seed(time.Now().UnixNano()) @@ -289,7 +95,7 @@ func generateMessage(config Config, key cryptotypes.PrivKey, batchSize uint64) ( price := config.PriceDistr.Sample() quantity := config.QuantityDistr.Sample() contract := config.ContractDistr.Sample() - orderPlacements := generateDexOrderPlacements(config, key, batchSize, price, quantity) + orderPlacements := generateDexOrderPlacements(config, key, msgPerTx, price, quantity) amount, err := sdk.ParseCoinsNormalized(fmt.Sprintf("%d%s", price.Mul(quantity).Ceil().RoundInt64(), "usei")) if err != nil { panic(err) @@ -301,23 +107,24 @@ func generateMessage(config Config, key cryptotypes.PrivKey, batchSize uint64) ( Funds: amount, } case Staking: + delegatorAddr := sdk.AccAddress(key.PubKey().Address()).String() - chosenValidator := Validators[rand.Intn(len(Validators))].OperatorAddress + chosenValidator := c.Validators[rand.Intn(len(c.Validators))].OperatorAddress // Randomly pick someone to redelegate / unbond from srcAddr := "" - for k := range DelegationMap[delegatorAddr] { + for k := range c.DelegationMap[delegatorAddr] { if k == chosenValidator { continue } srcAddr = k break } - msg = generateStakingMsg(delegatorAddr, chosenValidator, srcAddr) + msg = c.generateStakingMsg(delegatorAddr, chosenValidator, srcAddr) case Tokenfactory: denomCreatorAddr := sdk.AccAddress(key.PubKey().Address()).String() // No denoms, let's mint randNum := rand.Float64() - denom, ok := TokenFactoryDenomOwner[denomCreatorAddr] + denom, ok := c.TokenFactoryDenomOwner[denomCreatorAddr] switch { case !ok || randNum <= 0.33: subDenom := fmt.Sprintf("tokenfactory-created-denom-%d", time.Now().UnixMilli()) @@ -326,7 +133,7 @@ func generateMessage(config Config, key cryptotypes.PrivKey, batchSize uint64) ( Sender: denomCreatorAddr, Subdenom: subDenom, } - TokenFactoryDenomOwner[denomCreatorAddr] = denom + c.TokenFactoryDenomOwner[denomCreatorAddr] = denom case randNum <= 0.66: msg = &tokenfactorytypes.MsgMint{ Sender: denomCreatorAddr, @@ -368,7 +175,7 @@ func generateMessage(config Config, key cryptotypes.PrivKey, batchSize uint64) ( price := config.PriceDistr.InvalidSample() quantity := config.QuantityDistr.InvalidSample() contract := config.ContractDistr.Sample() - orderPlacements := generateDexOrderPlacements(config, key, batchSize, price, quantity) + orderPlacements := generateDexOrderPlacements(config, key, msgPerTx, price, quantity) amount, err := sdk.ParseCoinsNormalized(fmt.Sprintf("%d%s", price.Mul(quantity).Ceil().RoundInt64(), "usei")) if err != nil { panic(err) @@ -383,7 +190,7 @@ func generateMessage(config Config, key cryptotypes.PrivKey, batchSize uint64) ( price := config.PriceDistr.Sample() quantity := config.QuantityDistr.Sample() contract := config.ContractDistr.Sample() - orderPlacements := generateDexOrderPlacements(config, key, batchSize, price, quantity) + orderPlacements := generateDexOrderPlacements(config, key, msgPerTx, price, quantity) var amountUsei int64 if rand.Float64() < 0.5 { amountUsei = 10000 * price.Mul(quantity).Ceil().RoundInt64() @@ -417,7 +224,7 @@ func generateDexOrderPlacements(config Config, key cryptotypes.PrivKey, batchSiz if config.MessageType == "failure_bank_malformed" { orderType = -1 } else { - dexMsgType := config.DexMsgTypeDistr.Sample() + dexMsgType := config.MsgTypeDistr.SampleDexMsgs() switch dexMsgType { case Limit: orderType = dextypes.OrderType_LIMIT @@ -451,29 +258,39 @@ func generateDexOrderPlacements(config Config, key cryptotypes.PrivKey, batchSiz return orderPlacements } -func generateStakingMsg(delegatorAddr string, chosenValidator string, srcAddr string) sdk.Msg { +func generateOracleMessage(key cryptotypes.PrivKey) sdk.Msg { + valAddr := sdk.ValAddress(key.PubKey().Address()).String() + addr := sdk.AccAddress(key.PubKey().Address()).String() + msg := &oracletypes.MsgAggregateExchangeRateVote{ + ExchangeRates: "1usei,2uatom", + Feeder: addr, + Validator: valAddr, + } + return msg +} + +func (c *LoadTestClient) generateStakingMsg(delegatorAddr string, chosenValidator string, srcAddr string) sdk.Msg { // Randomly unbond, redelegate or delegate // However, if there are no delegations, do so first var msg sdk.Msg - randNum := rand.Float64() - if _, ok := DelegationMap[delegatorAddr]; !ok || randNum <= 0.33 || srcAddr == "" { + msgType := c.LoadTestConfig.MsgTypeDistr.SampleStakingMsgs() + if _, ok := c.DelegationMap[delegatorAddr]; !ok || msgType == "delegate" || srcAddr == "" { msg = &stakingtypes.MsgDelegate{ DelegatorAddress: delegatorAddr, ValidatorAddress: chosenValidator, Amount: sdk.Coin{Denom: "usei", Amount: sdk.NewInt(1)}, } - DelegationMap[delegatorAddr] = map[string]int{} - DelegationMap[delegatorAddr][chosenValidator] = 1 + c.DelegationMap[delegatorAddr] = map[string]int{} + c.DelegationMap[delegatorAddr][chosenValidator] = 1 } else { - - if randNum <= 0.66 { + if msgType == "redelegate" { msg = &stakingtypes.MsgBeginRedelegate{ DelegatorAddress: delegatorAddr, ValidatorSrcAddress: srcAddr, ValidatorDstAddress: chosenValidator, Amount: sdk.Coin{Denom: "usei", Amount: sdk.NewInt(1)}, } - DelegationMap[delegatorAddr][chosenValidator]++ + c.DelegationMap[delegatorAddr][chosenValidator]++ } else { msg = &stakingtypes.MsgUndelegate{ DelegatorAddress: delegatorAddr, @@ -482,9 +299,9 @@ func generateStakingMsg(delegatorAddr string, chosenValidator string, srcAddr st } } // Update delegation map - DelegationMap[delegatorAddr][srcAddr]-- - if DelegationMap[delegatorAddr][srcAddr] == 0 { - delete(DelegationMap, delegatorAddr) + c.DelegationMap[delegatorAddr][srcAddr]-- + if c.DelegationMap[delegatorAddr][srcAddr] == 0 { + delete(c.DelegationMap, delegatorAddr) } } return msg @@ -507,15 +324,5 @@ func getLastHeight() int { } func main() { - flag.Parse() - config := Config{} - pwd, _ := os.Getwd() - - fileName := "/loadtest/config.json" - file, _ := os.ReadFile(pwd + fileName) - if err := json.Unmarshal(file, &config); err != nil { - panic(err) - } - - run(config) + run() } diff --git a/loadtest/scripts/metrics.py b/loadtest/scripts/metrics.py index 55074b6b30..d59c90e0de 100644 --- a/loadtest/scripts/metrics.py +++ b/loadtest/scripts/metrics.py @@ -41,17 +41,20 @@ def get_metrics(): for height in all_heights: block_info_list.append(get_block_info(height)) # Skip first and last block since it may have high deviation if we start it at the end of the block + skip_edge_blocks = block_info_list[1:-1] total_duration = skip_edge_blocks[-1]["timestamp"] - skip_edge_blocks[0]["timestamp"] average_block_time = total_duration.total_seconds() / (len(skip_edge_blocks) - 1) total_txs_num = sum([block["number_of_txs"] for block in skip_edge_blocks]) average_txs_num = total_txs_num / len(skip_edge_blocks) + return { "Summary (excl. edge block)": { "average_block_time": average_block_time, "average_throughput_per_block": average_txs_num, "average_throughput_per_sec": average_txs_num / average_block_time, "number_of_full_blocks": len(skip_edge_blocks), + "full_blocks": all_heights[1:-1], "total_txs_num": total_txs_num, }, "Detail (incl. edge blocks)": { diff --git a/loadtest/scripts/populate_genesis_accounts.py b/loadtest/scripts/populate_genesis_accounts.py index 100d1b35b6..7255829242 100644 --- a/loadtest/scripts/populate_genesis_accounts.py +++ b/loadtest/scripts/populate_genesis_accounts.py @@ -5,11 +5,14 @@ import threading import time +from pathlib import Path + PARALLEISM=32 # Global Variable used for accounts # Does not need to be thread safe, each thread should only be writing to its own index global_accounts_mapping = {} +home_path = os.path.expanduser('~') def add_key(account_name, local=False): if local: @@ -34,7 +37,6 @@ def add_account(account_name, address, mnemonic, local=False): else: add_account_cmd = f"printf '12345678\n' | ~/go/bin/seid add-genesis-account {address} 1000000000usei" - home_path = os.path.expanduser('~') filename = f"{home_path}/test_accounts/{account_name}.json" os.makedirs(os.path.dirname(filename), exist_ok=True) with open(filename, 'w') as f: @@ -91,6 +93,28 @@ def create_genesis_account(account_index, account_name, local=False): } } + if retry_counter >= 1000: + exit(-1) + + global_accounts_mapping[account_index] = { + "balance": { + "address": address, + "coins": [ + { + "denom": "usei", + "amount": "1000000000" + } + ] + }, + "account": { + "@type": "/cosmos.auth.v1beta1.BaseAccount", + "address": address, + "pub_key": None, + "account_number": "0", + "sequence": "0" + } + } + def bulk_create_genesis_accounts(number_of_accounts, start_idx, is_local=False): for i in range(start_idx, start_idx + number_of_accounts): @@ -98,14 +122,14 @@ def bulk_create_genesis_accounts(number_of_accounts, start_idx, is_local=False): print(f"Created account {i}") -def read_genesis_file(): - with open("/root/.sei/config/genesis.json", 'r') as f: +def read_genesis_file(genesis_json_file_path): + with open(genesis_json_file_path, 'r') as f: return json.load(f) -def write_genesis_file(data): +def write_genesis_file(genesis_json_file_path, data): print("Writing results to genesis file") - with open("/root/.sei/config/genesis.json", 'w') as f: + with open(genesis_json_file_path, 'w') as f: json.dump(data, f, indent=4) @@ -116,7 +140,8 @@ def main(): if len(args) > 1 and args[1] == "loc": is_local = True - genesis_file = read_genesis_file() + genesis_json_file_path = f"{home_path}/.sei/config/genesis.json" + genesis_file = read_genesis_file(genesis_json_file_path) num_threads = number_of_accounts // PARALLEISM threads = [] @@ -145,7 +170,7 @@ def main(): print(f'Created {num_accounts_created} accounts') assert num_accounts_created >= number_of_accounts - write_genesis_file(genesis_file) + write_genesis_file(genesis_json_file_path, genesis_file) if __name__ == "__main__": main() diff --git a/loadtest/sign.go b/loadtest/sign.go index 0ebaccd94e..882b5f1f5c 100644 --- a/loadtest/sign.go +++ b/loadtest/sign.go @@ -6,12 +6,16 @@ import ( "io" "os" "path/filepath" + "sync" "time" "github.com/cosmos/cosmos-sdk/client" clienttx "github.com/cosmos/cosmos-sdk/client/tx" + "github.com/cosmos/cosmos-sdk/codec/legacy" + "github.com/cosmos/cosmos-sdk/crypto" "github.com/cosmos/cosmos-sdk/crypto/hd" "github.com/cosmos/cosmos-sdk/crypto/keyring" + "github.com/cosmos/cosmos-sdk/crypto/keys/sr25519" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/tx/signing" @@ -26,7 +30,43 @@ type AccountInfo struct { Mnemonic string `json:"mnemonic"` } -func GetKey(accountIdx uint64) cryptotypes.PrivKey { +type SignerInfo struct { + AccountNumber uint64 + SequenceNumber uint64 + mutex *sync.Mutex +} + +func NewSignerInfo(accountNumber uint64, sequenceNumber uint64) *SignerInfo { + return &SignerInfo{ + AccountNumber: accountNumber, + SequenceNumber: sequenceNumber, + mutex: &sync.Mutex{}, + } +} + +func (si *SignerInfo) IncrementAccountNumber() { + si.mutex.Lock() + defer si.mutex.Unlock() + si.AccountNumber++ +} + +type SignerClient struct { + CachedAccountSeqNum *sync.Map + CachedAccountKey *sync.Map +} + +func NewSignerClient() *SignerClient { + return &SignerClient{ + CachedAccountSeqNum: &sync.Map{}, + CachedAccountKey: &sync.Map{}, + } +} + +func (sc *SignerClient) GetKey(accountIdx uint64) cryptotypes.PrivKey { + if val, ok := sc.CachedAccountKey.Load(accountIdx); ok { + privKey := val.(cryptotypes.PrivKey) + return privKey + } userHomeDir, _ := os.UserHomeDir() accountKeyFilePath := filepath.Join(userHomeDir, "test_accounts", fmt.Sprintf("ta%d.json", accountIdx)) jsonFile, err := os.Open(accountKeyFilePath) @@ -48,12 +88,56 @@ func GetKey(accountIdx uint64) cryptotypes.PrivKey { algo, _ := keyring.NewSigningAlgoFromString(algoStr, keyringAlgos) hdpath := hd.CreateHDPath(sdk.GetConfig().GetCoinType(), 0, 0).String() derivedPriv, _ := algo.Derive()(accountInfo.Mnemonic, "", hdpath) - return algo.Generate()(derivedPriv) + privKey := algo.Generate()(derivedPriv) + + // Cache this so we don't need to regenerate it + sc.CachedAccountKey.Store(accountIdx, privKey) + return privKey +} + +func (sc *SignerClient) GetValKeys() []cryptotypes.PrivKey { + valKeys := []cryptotypes.PrivKey{} + userHomeDir, _ := os.UserHomeDir() + valKeysFilePath := filepath.Join(userHomeDir, "exported_keys") + files, _ := os.ReadDir(valKeysFilePath) + for _, fn := range files { + // we dont expect subdirectories, so we can just handle files + valKeyFile := filepath.Join(valKeysFilePath, fn.Name()) + privKeyBz, err := os.ReadFile(valKeyFile) + if err != nil { + panic(err) + } + + privKeyBytes, algo, err := crypto.UnarmorDecryptPrivKey(string(privKeyBz), "12345678") + if err != nil { + panic(err) + } + + var privKey cryptotypes.PrivKey + if algo == string(hd.Sr25519Type) { + typedKey := &sr25519.PrivKey{} + if err := typedKey.UnmarshalJSON(privKeyBytes); err != nil { + panic(err) + } + privKey = typedKey + } else { + privKey, err = legacy.PrivKeyFromBytes(privKeyBytes) + if err != nil { + panic(err) + } + } + + valKeys = append(valKeys, privKey) + } + return valKeys } -func SignTx(txBuilder *client.TxBuilder, privKey cryptotypes.PrivKey, seqDelta uint64) { +func (sc *SignerClient) SignTx(chainID string, txBuilder *client.TxBuilder, privKey cryptotypes.PrivKey, seqDelta uint64) { var sigsV2 []signing.SignatureV2 - accountNum, seqNum := GetAccountNumberSequenceNumber(privKey) + signerInfo := sc.GetAccountNumberSequenceNumber(privKey) + accountNum := signerInfo.AccountNumber + seqNum := signerInfo.SequenceNumber + seqNum += seqDelta sigV2 := signing.SignatureV2{ PubKey: privKey.PubKey(), @@ -67,7 +151,7 @@ func SignTx(txBuilder *client.TxBuilder, privKey cryptotypes.PrivKey, seqDelta u _ = (*txBuilder).SetSignatures(sigsV2...) sigsV2 = []signing.SignatureV2{} signerData := xauthsigning.SignerData{ - ChainID: ChainID, + ChainID: chainID, AccountNumber: accountNum, Sequence: seqNum, } @@ -83,7 +167,13 @@ func SignTx(txBuilder *client.TxBuilder, privKey cryptotypes.PrivKey, seqDelta u _ = (*txBuilder).SetSignatures(sigsV2...) } -func GetAccountNumberSequenceNumber(privKey cryptotypes.PrivKey) (uint64, uint64) { +func (sc *SignerClient) GetAccountNumberSequenceNumber(privKey cryptotypes.PrivKey) SignerInfo { + if val, ok := sc.CachedAccountSeqNum.Load(privKey); ok { + signerinfo := val.(SignerInfo) + signerinfo.IncrementAccountNumber() + return signerinfo + } + hexAccount := privKey.PubKey().Address() address, err := sdk.AccAddressFromHex(hexAccount.String()) if err != nil { @@ -110,5 +200,8 @@ func GetAccountNumberSequenceNumber(privKey cryptotypes.PrivKey) (uint64, uint64 panic(err) } } - return account, seq + + signerInfo := *NewSignerInfo(account, seq) + sc.CachedAccountSeqNum.Store(privKey, signerInfo) + return signerInfo } diff --git a/loadtest/tx.go b/loadtest/tx.go index 10af6ec221..5b0b8698f4 100644 --- a/loadtest/tx.go +++ b/loadtest/tx.go @@ -3,7 +3,6 @@ package main import ( "context" "fmt" - "sync" "time" "github.com/cosmos/cosmos-sdk/client" @@ -18,17 +17,17 @@ func SendTx( txBuilder *client.TxBuilder, mode typestx.BroadcastMode, seqDelta uint64, - mu *sync.Mutex, failureExpected bool, -) func() string { + loadtestClient LoadTestClient, +) func() string { // TODO: do we need to return string? (*txBuilder).SetGasLimit(200000000) (*txBuilder).SetFeeAmount([]sdk.Coin{ sdk.NewCoin("usei", sdk.NewInt(10000000)), }) - SignTx(txBuilder, key, seqDelta) + loadtestClient.SignerClient.SignTx(loadtestClient.ChainID, txBuilder, key, seqDelta) txBytes, _ := TestConfig.TxConfig.TxEncoder()((*txBuilder).GetTx()) return func() string { - grpcRes, err := TxClient.BroadcastTx( + grpcRes, err := loadtestClient.TxClient.BroadcastTx( context.Background(), &typestx.BroadcastTxRequest{ Mode: mode, @@ -46,7 +45,7 @@ func SendTx( // retry after a second until either succeed or fail for some other reason fmt.Printf("Mempool full\n") time.Sleep(1 * time.Second) - grpcRes, err = TxClient.BroadcastTx( + grpcRes, err = loadtestClient.TxClient.BroadcastTx( context.Background(), &typestx.BroadcastTxRequest{ Mode: mode, @@ -64,11 +63,7 @@ func SendTx( if grpcRes.TxResponse.Code != 0 { fmt.Printf("Error: %d, %s\n", grpcRes.TxResponse.Code, grpcRes.TxResponse.RawLog) } else { - mu.Lock() - defer mu.Unlock() - if _, err := TxHashFile.WriteString(fmt.Sprintf("%s\n", grpcRes.TxResponse.TxHash)); err != nil { - panic(err) - } + loadtestClient.AppendTxHash(grpcRes.TxResponse.TxHash) return grpcRes.TxResponse.TxHash } return "" diff --git a/loadtest/types.go b/loadtest/types.go new file mode 100644 index 0000000000..70946a3574 --- /dev/null +++ b/loadtest/types.go @@ -0,0 +1,129 @@ +package main + +import ( + "fmt" + "math/rand" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/sei-protocol/sei-chain/utils" +) + +const ( + Bank string = "bank" + FailureBankMalformed string = "failure_bank_malformed" + FailureBankInvalid string = "failure_bank_invalid" + FailureDexMalformed string = "failure_dex_malformed" + FailureDexInvalid string = "failure_dex_invalid" + Dex string = "dex" + Staking string = "staking" + Tokenfactory string = "tokenfactory" + Limit string = "limit" + Market string = "market" +) + +type Config struct { + ChainID string `json:"chain_id"` + TxsPerBlock uint64 `json:"txs_per_block"` + MsgsPerTx uint64 `json:"msgs_per_tx"` + Rounds uint64 `json:"rounds"` + MessageType string `json:"message_type"` + RunOracle bool `json:"run_oracle"` + PriceDistr NumericDistribution `json:"price_distribution"` + QuantityDistr NumericDistribution `json:"quantity_distribution"` + MsgTypeDistr MsgTypeDistribution `json:"message_type_distribution"` + ContractDistr ContractDistributions `json:"contract_distribution"` + Constant bool `json:"constant"` + ConstLoadInterval int64 `json:"const_load_interval"` +} + +type EncodingConfig struct { + InterfaceRegistry types.InterfaceRegistry + // NOTE: this field will be renamed to Codec + Marshaler codec.Codec + TxConfig client.TxConfig + Amino *codec.LegacyAmino +} + +type NumericDistribution struct { + Min sdk.Dec `json:"min"` + Max sdk.Dec `json:"max"` + NumDistinct int64 `json:"number_of_distinct_values"` +} + +func (d *NumericDistribution) Sample() sdk.Dec { + steps := sdk.NewDec(rand.Int63n(d.NumDistinct)) + return d.Min.Add(d.Max.Sub(d.Min).QuoInt64(d.NumDistinct).Mul(steps)) +} + +// Invalid numeric distribution sample +func (d *NumericDistribution) InvalidSample() sdk.Dec { + steps := sdk.NewDec(rand.Int63n(d.NumDistinct)) + if rand.Float64() < 0.5 { + return d.Min.Add(d.Max.Sub(d.Min).QuoInt64(d.NumDistinct).Mul(steps)) + } + return d.Max.Add(d.Max.Sub(d.Min).QuoInt64(d.NumDistinct).Mul(steps)) +} + +type DexMsgTypeDistribution struct { + LimitOrderPct sdk.Dec `json:"limit_order_percentage"` + MarketOrderPct sdk.Dec `json:"market_order_percentage"` +} + +type StakingMsgTypeDistribution struct { + DelegatePct sdk.Dec `json:"delegate_percentage"` + UndelegatePct sdk.Dec `json:"undelegate_percentage"` + BeginRedelegatePct sdk.Dec `json:"begin_redelegate_percentage"` +} +type MsgTypeDistribution struct { + Dex DexMsgTypeDistribution `json:"dex"` + Staking StakingMsgTypeDistribution `json:"staking"` +} + +func (d *MsgTypeDistribution) SampleDexMsgs() string { + if !d.Dex.LimitOrderPct.Add(d.Dex.MarketOrderPct).Equal(sdk.OneDec()) { + panic("Distribution percentages must add up to 1") + } + randNum := sdk.MustNewDecFromStr(fmt.Sprintf("%f", rand.Float64())) + if randNum.LT(d.Dex.LimitOrderPct) { + return Limit + } + return Market +} + +func (d *MsgTypeDistribution) SampleStakingMsgs() string { + if !d.Staking.DelegatePct.Add(d.Staking.UndelegatePct).Add(d.Staking.BeginRedelegatePct).Equal(sdk.OneDec()) { + panic("Distribution percentages must add up to 1") + } + randNum := sdk.MustNewDecFromStr(fmt.Sprintf("%f", rand.Float64())) + if randNum.LT(d.Staking.DelegatePct) { + return "delegate" + } else if randNum.LT(d.Staking.DelegatePct.Add(d.Staking.UndelegatePct)) { + return "undelegate" + } + return "begin_redelegate" +} + +type ContractDistributions []ContractDistribution + +func (d *ContractDistributions) Sample() string { + if !utils.Reduce(*d, func(i ContractDistribution, o sdk.Dec) sdk.Dec { return o.Add(i.Percentage) }, sdk.ZeroDec()).Equal(sdk.OneDec()) { + panic("Distribution percentages must add up to 1") + } + randNum := sdk.MustNewDecFromStr(fmt.Sprintf("%f", rand.Float64())) + cumPct := sdk.ZeroDec() + for _, dist := range *d { + cumPct = cumPct.Add(dist.Percentage) + if randNum.LTE(cumPct) { + return dist.ContractAddr + } + } + panic("this should never be triggered") +} + +type ContractDistribution struct { + ContractAddr string `json:"contract_address"` + Percentage sdk.Dec `json:"percentage"` +} diff --git a/scripts/old_initialize_local.sh b/scripts/old_initialize_local.sh old mode 100644 new mode 100755 index a5d2352308..5047163db0 --- a/scripts/old_initialize_local.sh +++ b/scripts/old_initialize_local.sh @@ -1,48 +1,44 @@ -#!/bin/bash -echo -n OS Password: -read -s password -echo -echo -n Key Name: -read keyname -echo -echo -n Number of Test Accounts: -read numtestaccount -echo -docker stop jaeger -docker rm jaeger -docker run -d --name jaeger \ - -e COLLECTOR_ZIPKIN_HOST_PORT=:9411 \ - -p 5775:5775/udp \ - -p 6831:6831/udp \ - -p 6832:6832/udp \ - -p 5778:5778 \ - -p 16686:16686 \ - -p 14250:14250 \ - -p 14268:14268 \ - -p 14269:14269 \ - -p 9411:9411 \ - jaegertracing/all-in-one:1.33 +#!/bin/bash + +keyname=admin +#docker stop jaeger +#docker rm jaeger +#docker run -d --name jaeger \ +# -e COLLECTOR_ZIPKIN_HOST_PORT=:9411 \ +# -p 5775:5775/udp \ +# -p 6831:6831/udp \ +# -p 6832:6832/udp \ +# -p 5778:5778 \ +# -p 16686:16686 \ +# -p 14250:14250 \ +# -p 14268:14268 \ +# -p 14269:14269 \ +# -p 9411:9411 \ +# jaegertracing/all-in-one:1.33 +rm -rf ~/.sei echo "Building..." make install -echo $password | sudo -S rm -r ~/.sei/ -echo $password | sudo -S rm -r ~/test_accounts/ +#echo $password | sudo -S rm -r ~/.sei/ +#echo $password | sudo -S rm -r ~/test_accounts/ ~/go/bin/seid init demo --chain-id sei-chain -yes | ~/go/bin/seid keys add $keyname -yes | ~/go/bin/seid keys add faucet -printf '12345678\n' | ~/go/bin/seid add-genesis-account $(~/go/bin/seid keys show $keyname -a) 100000000000000000000usei,100000000000000000000uusdc,100000000000000000000uatom -printf '12345678\n' | ~/go/bin/seid add-genesis-account $(~/go/bin/seid keys show faucet -a) 100000000000000000000usei,100000000000000000000uusdc,100000000000000000000uatom -python3 ./loadtest/scripts/populate_genesis_accounts.py $numtestaccount loc -printf '12345678\n' | ~/go/bin/seid gentx $keyname 70000000000000000000usei --chain-id sei-chain -sed -i 's/mode = "full"/mode = "validator"/g' $HOME/.sei/config/config.toml -sed -i 's/indexer = \["null"\]/indexer = \["kv"\]/g' $HOME/.sei/config/config.toml +~/go/bin/seid keys add $keyname --keyring-backend test +#yes | ~/go/bin/seid keys add faucet +~/go/bin/seid add-genesis-account $(~/go/bin/seid keys show $keyname -a --keyring-backend test) 100000000000000000000usei,100000000000000000000uusdc,100000000000000000000uatom +~/go/bin/seid gentx $keyname 70000000000000000000usei --chain-id sei-chain --keyring-backend test +sed -i '' 's/mode = "full"/mode = "validator"/g' $HOME/.sei/config/config.toml +sed -i '' 's/indexer = \["null"\]/indexer = \["kv"\]/g' $HOME/.sei/config/config.toml KEY=$(jq '.pub_key' ~/.sei/config/priv_validator_key.json -c) jq '.validators = [{}]' ~/.sei/config/genesis.json > ~/.sei/config/tmp_genesis.json jq '.validators[0] += {"power":"70000000000000"}' ~/.sei/config/tmp_genesis.json > ~/.sei/config/tmp_genesis_2.json jq '.validators[0] += {"pub_key":'$KEY'}' ~/.sei/config/tmp_genesis_2.json > ~/.sei/config/tmp_genesis_3.json mv ~/.sei/config/tmp_genesis_3.json ~/.sei/config/genesis.json && rm ~/.sei/config/tmp_genesis.json && rm ~/.sei/config/tmp_genesis_2.json + +echo "Creating Accounts" +python3 loadtest/scripts/populate_genesis_accounts.py 50 loc + ~/go/bin/seid collect-gentxs cat ~/.sei/config/genesis.json | jq '.app_state["crisis"]["constant_fee"]["denom"]="usei"' > ~/.sei/config/tmp_genesis.json && mv ~/.sei/config/tmp_genesis.json ~/.sei/config/genesis.json cat ~/.sei/config/genesis.json | jq '.app_state["gov"]["deposit_params"]["min_deposit"][0]["denom"]="usei"' > ~/.sei/config/tmp_genesis.json && mv ~/.sei/config/tmp_genesis.json ~/.sei/config/genesis.json @@ -81,4 +77,4 @@ else fi # start the chain with log tracing -GORACE="log_path=/tmp/race/seid_race" ~/go/bin/seid start --trace --chain-id sei-chain \ No newline at end of file +GORACE="log_path=/tmp/race/seid_race" ~/go/bin/seid start --trace --chain-id sei-chain diff --git a/store/testutils.go b/store/testutils.go index 7a710bc745..86b0aa59bb 100644 --- a/store/testutils.go +++ b/store/testutils.go @@ -11,7 +11,7 @@ import ( func NewTestKVStore() types.KVStore { mem := dbadapter.Store{DB: dbm.NewMemDB()} - return cachekv.NewStore(mem) + return cachekv.NewStore(mem, storetypes.NewKVStoreKey("test")) } func NewTestCacheMultiStore(stores map[types.StoreKey]types.CacheWrapper) types.CacheMultiStore { diff --git a/testutil/network/network.go b/testutil/network/network.go index 84a7f7427e..67c394b375 100644 --- a/testutil/network/network.go +++ b/testutil/network/network.go @@ -60,6 +60,7 @@ func DefaultConfig() network.Config { wasm.EnableAllProposals, &simapp.EmptyAppOptions{}, nil, + app.EmptyACLOpts, baseapp.SetPruning(storetypes.NewPruningOptionsFromString(val.AppConfig.Pruning)), baseapp.SetMinGasPrices(val.AppConfig.MinGasPrices), ) diff --git a/utils/metrics/labels.go b/utils/metrics/labels.go new file mode 100644 index 0000000000..282c97d060 --- /dev/null +++ b/utils/metrics/labels.go @@ -0,0 +1,8 @@ +package metrics + +const ( + CONCURRENT = "concurrent" + SYNCHRONOUS = "synchronous" + GovMsgInBlock = "gov-msg-in-block" + FailedToBuild = "failed-to-build" +) diff --git a/utils/metrics/metrics_util.go b/utils/metrics/metrics_util.go index 6bd198ee7c..ec5ed3a0cb 100644 --- a/utils/metrics/metrics_util.go +++ b/utils/metrics/metrics_util.go @@ -44,3 +44,63 @@ func GaugeSeidVersionAndCommit(version string, commit string) { []metrics.Label{telemetry.NewLabel("seid_version", version), telemetry.NewLabel("commit", commit)}, ) } + +// sei_tx_process_type_count +func IncrTxProcessTypeCounter(processType string) { + metrics.IncrCounterWithLabels( + []string{"sei", "tx", "process", "type"}, + 1, + []metrics.Label{telemetry.NewLabel("type", processType)}, + ) +} + +// Measures the time taken to process a block by the process type +// Metric Names: +// +// sei_process_block_miliseconds +// sei_process_block_miliseconds_count +// sei_process_block_miliseconds_sum +func BlockProcessLatency(start time.Time, processType string) { + metrics.MeasureSinceWithLabels( + []string{"sei", "process", "block", "milliseconds"}, + start.UTC(), + []metrics.Label{telemetry.NewLabel("type", processType)}, + ) +} + +// Measures the time taken to execute a sudo msg +// Metric Names: +// +// sei_tx_process_type_count +func IncrDagBuildErrorCounter(reason string) { + metrics.IncrCounterWithLabels( + []string{"sei", "dag", "build", "error"}, + 1, + []metrics.Label{telemetry.NewLabel("reason", reason)}, + ) +} + +// Counts the number of concurrent transactions that failed +// Metric Names: +// +// sei_tx_concurrent_delivertx_error +func IncrFailedConcurrentDeliverTxCounter() { + metrics.IncrCounterWithLabels( + []string{"sei", "tx", "concurrent", "delievertx", "error"}, + 1, + []metrics.Label{}, + ) +} + +// Measures the time taken to execute a sudo msg +// Metric Names: +// +// sei_deliver_tx_duration_miliseconds +// sei_deliver_tx_duration_miliseconds_count +// sei_deliver_tx_duration_miliseconds_sum +func MeasureDeliverTxDuration(start time.Time) { + metrics.MeasureSince( + []string{"sei", "deliver", "tx", "milliseconds"}, + start.UTC(), + ) +} diff --git a/wasmbinding/encoder.go b/wasmbinding/encoder.go new file mode 100644 index 0000000000..b26971ebdf --- /dev/null +++ b/wasmbinding/encoder.go @@ -0,0 +1,43 @@ +package wasmbinding + +import ( + "encoding/json" + + wasmvmtypes "github.com/CosmWasm/wasmvm/types" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + dexwasm "github.com/sei-protocol/sei-chain/x/dex/client/wasm" + tokenfactorywasm "github.com/sei-protocol/sei-chain/x/tokenfactory/client/wasm" +) + +type SeiWasmMessage struct { + PlaceOrders json.RawMessage `json:"place_orders,omitempty"` + CancelOrders json.RawMessage `json:"cancel_orders,omitempty"` + CreateDenom json.RawMessage `json:"create_denom,omitempty"` + MintTokens json.RawMessage `json:"mint_tokens,omitempty"` + BurnTokens json.RawMessage `json:"burn_tokens,omitempty"` + ChangeAdmin json.RawMessage `json:"change_admin,omitempty"` +} + +func CustomEncoder(sender sdk.AccAddress, msg json.RawMessage) ([]sdk.Msg, error) { + var parsedMessage SeiWasmMessage + if err := json.Unmarshal(msg, &parsedMessage); err != nil { + return []sdk.Msg{}, sdkerrors.Wrap(err, "Error parsing Sei Wasm Message") + } + switch { + case parsedMessage.PlaceOrders != nil: + return dexwasm.EncodeDexPlaceOrders(parsedMessage.PlaceOrders, sender) + case parsedMessage.CancelOrders != nil: + return dexwasm.EncodeDexCancelOrders(parsedMessage.CancelOrders, sender) + case parsedMessage.CreateDenom != nil: + return tokenfactorywasm.EncodeTokenFactoryCreateDenom(parsedMessage.CreateDenom, sender) + case parsedMessage.MintTokens != nil: + return tokenfactorywasm.EncodeTokenFactoryMint(parsedMessage.MintTokens, sender) + case parsedMessage.BurnTokens != nil: + return tokenfactorywasm.EncodeTokenFactoryBurn(parsedMessage.BurnTokens, sender) + case parsedMessage.ChangeAdmin != nil: + return tokenfactorywasm.EncodeTokenFactoryChangeAdmin(parsedMessage.ChangeAdmin, sender) + default: + return []sdk.Msg{}, wasmvmtypes.UnsupportedRequest{Kind: "Unknown Sei Wasm Message"} + } +} diff --git a/wasmbinding/message_plugin.go b/wasmbinding/message_plugin.go index bdc16ed355..5ea56529e1 100644 --- a/wasmbinding/message_plugin.go +++ b/wasmbinding/message_plugin.go @@ -1,129 +1,160 @@ package wasmbinding import ( - "encoding/json" - wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" + wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" wasmvmtypes "github.com/CosmWasm/wasmvm/types" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" - "github.com/sei-protocol/sei-chain/wasmbinding/bindings" - dexwasm "github.com/sei-protocol/sei-chain/x/dex/client/wasm" - tokenfactorywasm "github.com/sei-protocol/sei-chain/x/tokenfactory/client/wasm" + sdkacltypes "github.com/cosmos/cosmos-sdk/types/accesscontrol" + "github.com/cosmos/cosmos-sdk/x/accesscontrol" + aclkeeper "github.com/cosmos/cosmos-sdk/x/accesscontrol/keeper" + acltypes "github.com/cosmos/cosmos-sdk/x/accesscontrol/types" + "github.com/sei-protocol/sei-chain/utils" ) -// CustomMessageDecorator returns decorator for custom CosmWasm bindings messages -func CustomMessageDecorator( +// forked from wasm +func CustomMessageHandler( router wasmkeeper.MessageRouter, - accountKeeper *authkeeper.AccountKeeper, -) func(wasmkeeper.Messenger) wasmkeeper.Messenger { - return func(old wasmkeeper.Messenger) wasmkeeper.Messenger { - return &CustomMessenger{ - router: router, - wrapped: old, - accountKeeper: accountKeeper, - } - } + channelKeeper wasmtypes.ChannelKeeper, + capabilityKeeper wasmtypes.CapabilityKeeper, + bankKeeper wasmtypes.Burner, + unpacker codectypes.AnyUnpacker, + portSource wasmtypes.ICS20TransferPortSource, + aclKeeper aclkeeper.Keeper, +) wasmkeeper.Messenger { + encoders := wasmkeeper.DefaultEncoders(unpacker, portSource) + encoders = encoders.Merge( + &wasmkeeper.MessageEncoders{ + Custom: CustomEncoder, + }) + return wasmkeeper.NewMessageHandlerChain( + NewSDKMessageDependencyDecorator(wasmkeeper.NewSDKMessageHandler(router, encoders), aclKeeper, encoders), + wasmkeeper.NewIBCRawPacketHandler(channelKeeper, capabilityKeeper), + wasmkeeper.NewBurnCoinMessageHandler(bankKeeper), + ) } -type CustomMessenger struct { - router wasmkeeper.MessageRouter - wrapped wasmkeeper.Messenger - accountKeeper *authkeeper.AccountKeeper +// SDKMessageHandler can handles messages that can be encoded into sdk.Message types and routed. +type SDKMessageDependencyDecorator struct { + wrapped wasmkeeper.Messenger + aclKeeper aclkeeper.Keeper + encoders wasmkeeper.MessageEncoders } -type SeiWasmMessage struct { - PlaceOrders json.RawMessage `json:"place_orders,omitempty"` - CancelOrders json.RawMessage `json:"cancel_orders,omitempty"` - CreateDenom json.RawMessage `json:"create_denom,omitempty"` - MintTokens json.RawMessage `json:"mint_tokens,omitempty"` - BurnTokens json.RawMessage `json:"burn_tokens,omitempty"` - ChangeAdmin json.RawMessage `json:"change_admin,omitempty"` -} - -var _ wasmkeeper.Messenger = &CustomMessenger{} - -// DispatchMsg executes on the bindingMsgs -func (m *CustomMessenger) DispatchMsg(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) ([]sdk.Event, [][]byte, error) { - if msg.Custom != nil { - return m.DispatchCustomMsg(ctx, contractAddr, contractIBCPortID, msg) +func NewSDKMessageDependencyDecorator(handler wasmkeeper.Messenger, aclKeeper aclkeeper.Keeper, encoders wasmkeeper.MessageEncoders) SDKMessageDependencyDecorator { + return SDKMessageDependencyDecorator{ + wrapped: handler, + aclKeeper: aclKeeper, + encoders: encoders, } - return m.wrapped.DispatchMsg(ctx, contractAddr, contractIBCPortID, msg) } -// DispatchCustomMsg function is forked from wasmd. sdk.Msg will be validated and routed to the corresponding module msg server in this function. -func (m *CustomMessenger) DispatchCustomMsg( - ctx sdk.Context, - contractAddr sdk.AccAddress, - contractIBCPortID string, - msg wasmvmtypes.CosmosMsg, -) (events []sdk.Event, data [][]byte, err error) { - var parsedMessage SeiWasmMessage - if err := json.Unmarshal(msg.Custom, &parsedMessage); err != nil { - return nil, nil, bindings.ErrParsingSeiWasmMsg +func BuildWasmDependencyLookupMap(accessOps []sdkacltypes.AccessOperation) map[acltypes.ResourceAccess]map[string]struct{} { + lookupMap := make(map[acltypes.ResourceAccess]map[string]struct{}) + for _, accessOp := range accessOps { + resourceAccess := acltypes.ResourceAccess{ + ResourceType: accessOp.ResourceType, + AccessType: accessOp.AccessType, + } + if _, ok := lookupMap[resourceAccess]; !ok { + // we haven't added any identifiers for this resource type, so lets initialize the nested map (set) + lookupMap[resourceAccess] = make(map[string]struct{}) + } + lookupMap[resourceAccess][accessOp.IdentifierTemplate] = struct{}{} } + return lookupMap +} - var sdkMsgs []sdk.Msg - switch { - case parsedMessage.PlaceOrders != nil: - sdkMsgs, err = dexwasm.EncodeDexPlaceOrders(parsedMessage.PlaceOrders, contractAddr) - case parsedMessage.CancelOrders != nil: - sdkMsgs, err = dexwasm.EncodeDexCancelOrders(parsedMessage.CancelOrders, contractAddr) - case parsedMessage.CreateDenom != nil: - sdkMsgs, err = tokenfactorywasm.EncodeTokenFactoryCreateDenom(parsedMessage.CreateDenom, contractAddr) - case parsedMessage.MintTokens != nil: - sdkMsgs, err = tokenfactorywasm.EncodeTokenFactoryMint(parsedMessage.MintTokens, contractAddr) - case parsedMessage.BurnTokens != nil: - sdkMsgs, err = tokenfactorywasm.EncodeTokenFactoryBurn(parsedMessage.BurnTokens, contractAddr) - case parsedMessage.ChangeAdmin != nil: - sdkMsgs, err = tokenfactorywasm.EncodeTokenFactoryChangeAdmin(parsedMessage.ChangeAdmin, contractAddr) - default: - sdkMsgs, err = []sdk.Msg{}, wasmvmtypes.UnsupportedRequest{Kind: "Unknown Sei Wasm Message"} +func GenerateAllowedResourceAccess(resource sdkacltypes.ResourceType, access sdkacltypes.AccessType) []acltypes.ResourceAccess { + // by default, write, and unknown are ok + accesses := []acltypes.ResourceAccess{ + { + ResourceType: resource, + AccessType: sdkacltypes.AccessType_WRITE, + }, + { + ResourceType: resource, + AccessType: sdkacltypes.AccessType_UNKNOWN, + }, } - if err != nil { - return nil, nil, err + if access == sdkacltypes.AccessType_READ { + accesses = append(accesses, acltypes.ResourceAccess{ + ResourceType: resource, + AccessType: access, + }) } + return accesses +} - for _, sdkMsg := range sdkMsgs { - res, err := m.handleSdkMessage(ctx, contractAddr, sdkMsg) - if err != nil { - return nil, nil, err +func AreDependenciesFulfilled(lookupMap map[acltypes.ResourceAccess]map[string]struct{}, accessOp sdkacltypes.AccessOperation) bool { + currResourceAccesses := GenerateAllowedResourceAccess(accessOp.ResourceType, accessOp.AccessType) + for _, currResourceAccess := range currResourceAccesses { + if identifierMap, ok := lookupMap[currResourceAccess]; ok { + if _, ok := identifierMap[accessOp.IdentifierTemplate]; ok { + // we found a proper listed dependency, we can go to the next access op + return true + } } - // append data - data = append(data, res.Data) - // append events - sdkEvents := make([]sdk.Event, len(res.Events)) - for i := range res.Events { - sdkEvents[i] = sdk.Event(res.Events[i]) + } + + // what about parent resources + parentResources := accessOp.ResourceType.GetParentResources() + // for each of the parent resources, we need at least one to be defined in the wasmDependencies + for _, parentResource := range parentResources { + // make parent resource access with same access type + parentResourceAccesses := GenerateAllowedResourceAccess(parentResource, accessOp.AccessType) + // for each of the parent resources, we check to see if its in the lookup map (identifier doesnt matter bc parent) + for _, parentResourceAccess := range parentResourceAccesses { + if _, parentResourcePresent := lookupMap[parentResourceAccess]; parentResourcePresent { + // we can continue to the next access op + return true + } } - events = append(events, sdkEvents...) } - return events, data, nil + return false } -// This function is forked from wasmd. sdk.Msg will be validated and routed to the corresponding module msg server in this function. -func (m *CustomMessenger) handleSdkMessage(ctx sdk.Context, contractAddr sdk.Address, msg sdk.Msg) (*sdk.Result, error) { - if err := msg.ValidateBasic(); err != nil { - return nil, err +func (decorator SDKMessageDependencyDecorator) DispatchMsg(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) { + sdkMsgs, err := decorator.encoders.Encode(ctx, contractAddr, contractIBCPortID, msg) + if err != nil { + return nil, nil, err } - // make sure this account can send it - for _, acct := range msg.GetSigners() { - if !acct.Equals(contractAddr) { - return nil, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "contract doesn't have permission") - } + // get the dependencies for the contract to validate against + // TODO: we need to carry wasmDependency in ctx instead of loading again here since here has no access to original msg payload + // which is required for populating id correctly. + wasmDependency, err := decorator.aclKeeper.GetWasmDependencyMapping(ctx, contractAddr, []byte{}, false) + // If no mapping exists, or mapping is disabled, this message would behave as blocking for all resources + if err == aclkeeper.ErrWasmDependencyMappingNotFound { + // no mapping, we can just continue + return decorator.wrapped.DispatchMsg(ctx, contractAddr, contractIBCPortID, msg) } - - // find the handler and execute it - if handler := m.router.Handler(msg); handler != nil { - // ADR 031 request type routing - msgResult, err := handler(ctx, msg) - return msgResult, err + if err != nil { + return nil, nil, err + } + if !wasmDependency.Enabled { + // if not enabled, just move on + // TODO: confirm that this is ok, is there ever a case where we should still verify dependencies for a disabled dependency? IDTS + return decorator.wrapped.DispatchMsg(ctx, contractAddr, contractIBCPortID, msg) + } + // convert wasm dependency to a map of resource access and identifier we can look up in + lookupMap := BuildWasmDependencyLookupMap( + utils.Map(wasmDependency.AccessOps, func(op sdkacltypes.AccessOperationWithSelector) sdkacltypes.AccessOperation { return *op.Operation }), + ) + // wasm dependency enabled, we need to validate the message dependencies + for _, msg := range sdkMsgs { + accessOps := decorator.aclKeeper.GetMessageDependencies(ctx, msg) + // go through each access op, and check if there is a completion signal for it OR a parent + for _, accessOp := range accessOps { + // first check for our specific resource access AND identifier template + depsFulfilled := AreDependenciesFulfilled(lookupMap, accessOp) + if !depsFulfilled { + emitIncorrectDependencyWasmEvent(ctx, contractAddr.String()) + return nil, nil, accesscontrol.ErrUnexpectedWasmDependency + } + } } - // legacy sdk.Msg routing - // Assuming that the app developer has migrated all their Msgs to - // proto messages and has registered all `Msg services`, then this - // path should never be called, because all those Msgs should be - // registered within the `msgServiceRouter` already. - return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "can't route message %+v", msg) + // we've gone through all of the messages + // and verified their dependencies with the declared dependencies in the wasm contract dependencies, we can process it now + return decorator.wrapped.DispatchMsg(ctx, contractAddr, contractIBCPortID, msg) } diff --git a/wasmbinding/query_plugin.go b/wasmbinding/query_plugin.go index f1dff2c603..db94e3162c 100644 --- a/wasmbinding/query_plugin.go +++ b/wasmbinding/query_plugin.go @@ -3,9 +3,14 @@ package wasmbinding import ( "encoding/json" + wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" wasmvmtypes "github.com/CosmWasm/wasmvm/types" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/accesscontrol" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + acl "github.com/cosmos/cosmos-sdk/x/accesscontrol" + aclkeeper "github.com/cosmos/cosmos-sdk/x/accesscontrol/keeper" + "github.com/sei-protocol/sei-chain/utils" ) const ( @@ -42,3 +47,161 @@ func CustomQuerier(qp *QueryPlugin) func(ctx sdk.Context, request json.RawMessag } } } + +type CustomQueryHandler struct { + QueryPlugins wasmkeeper.QueryPlugins + aclKeeper aclkeeper.Keeper +} + +func (queryHandler CustomQueryHandler) HandleQuery(ctx sdk.Context, caller sdk.AccAddress, request wasmvmtypes.QueryRequest) ([]byte, error) { + // TODO: we need to carry wasmDependency in ctx instead of loading again here since here has no access to original msg payload + // which is required for populating id correctly. + wasmDependency, err := queryHandler.aclKeeper.GetWasmDependencyMapping(ctx, caller, []byte{}, false) + // If no mapping exists, or mapping is disabled, this message would behave as blocking for all resources + needToCheckDependencies := true + if err == aclkeeper.ErrWasmDependencyMappingNotFound { + // no mapping, we can just continue + needToCheckDependencies = false + } + if err != nil { + return nil, err + } + if !wasmDependency.Enabled { + needToCheckDependencies = false + } + lookupMap := BuildWasmDependencyLookupMap( + utils.Map(wasmDependency.AccessOps, func(op accesscontrol.AccessOperationWithSelector) accesscontrol.AccessOperation { return *op.Operation }), + ) + if request.Bank != nil { + // check for BANK resource type + accessOp := accesscontrol.AccessOperation{ + ResourceType: accesscontrol.ResourceType_KV_BANK, + AccessType: accesscontrol.AccessType_READ, + // TODO: should IdentifierTemplate be based on the actual request? + IdentifierTemplate: "*", + } + if needToCheckDependencies { + if !AreDependenciesFulfilled(lookupMap, accessOp) { + emitIncorrectDependencyWasmEvent(ctx, caller.String()) + return nil, acl.ErrUnexpectedWasmDependency + } + } + return queryHandler.QueryPlugins.Bank(ctx, request.Bank) + + } + if request.Custom != nil { + // TODO: specially break down the custom + var contractQuery SeiQueryWrapper + if err := json.Unmarshal(request.Custom, &contractQuery); err != nil { + return nil, sdkerrors.Wrap(err, "Error parsing request data") + } + resourceType := accesscontrol.ResourceType_ANY + switch contractQuery.Route { + case OracleRoute: + resourceType = accesscontrol.ResourceType_KV_ORACLE + case DexRoute: + resourceType = accesscontrol.ResourceType_KV_DEX + case EpochRoute: + resourceType = accesscontrol.ResourceType_KV_EPOCH + case TokenFactoryRoute: + resourceType = accesscontrol.ResourceType_KV // TODO: change this to tokenfactory when rebasing a newer sei cosmos version with the enum + } + accessOp := accesscontrol.AccessOperation{ + ResourceType: resourceType, + AccessType: accesscontrol.AccessType_READ, + IdentifierTemplate: "*", + } + if needToCheckDependencies { + if !AreDependenciesFulfilled(lookupMap, accessOp) { + emitIncorrectDependencyWasmEvent(ctx, caller.String()) + return nil, acl.ErrUnexpectedWasmDependency + } + } + return queryHandler.QueryPlugins.Custom(ctx, request.Custom) + } + if request.IBC != nil { + // check for ANY resource type + // TODO: do we need a special resource type for IBC? + accessOp := accesscontrol.AccessOperation{ + ResourceType: accesscontrol.ResourceType_ANY, + AccessType: accesscontrol.AccessType_READ, + IdentifierTemplate: "*", + } + if needToCheckDependencies { + if !AreDependenciesFulfilled(lookupMap, accessOp) { + emitIncorrectDependencyWasmEvent(ctx, caller.String()) + return nil, acl.ErrUnexpectedWasmDependency + } + } + return queryHandler.QueryPlugins.IBC(ctx, caller, request.IBC) + } + if request.Staking != nil { + // check for STAKING resource type + accessOp := accesscontrol.AccessOperation{ + ResourceType: accesscontrol.ResourceType_KV_STAKING, + AccessType: accesscontrol.AccessType_READ, + IdentifierTemplate: "*", + } + if needToCheckDependencies { + if !AreDependenciesFulfilled(lookupMap, accessOp) { + emitIncorrectDependencyWasmEvent(ctx, caller.String()) + return nil, acl.ErrUnexpectedWasmDependency + } + } + return queryHandler.QueryPlugins.Staking(ctx, request.Staking) + } + if request.Stargate != nil { + // check for ANY resource type + // TODO: determine what Stargate dependency granularity looks like + accessOp := accesscontrol.AccessOperation{ + ResourceType: accesscontrol.ResourceType_ANY, + AccessType: accesscontrol.AccessType_READ, + IdentifierTemplate: "*", + } + if needToCheckDependencies { + if !AreDependenciesFulfilled(lookupMap, accessOp) { + emitIncorrectDependencyWasmEvent(ctx, caller.String()) + return nil, acl.ErrUnexpectedWasmDependency + } + } + return queryHandler.QueryPlugins.Stargate(ctx, request.Stargate) + } + if request.Wasm != nil { + // check for WASM resource type + accessOp := accesscontrol.AccessOperation{ + ResourceType: accesscontrol.ResourceType_KV_WASM, + AccessType: accesscontrol.AccessType_READ, + IdentifierTemplate: "*", + } + if needToCheckDependencies { + if !AreDependenciesFulfilled(lookupMap, accessOp) { + emitIncorrectDependencyWasmEvent(ctx, caller.String()) + return nil, acl.ErrUnexpectedWasmDependency + } + } + return queryHandler.QueryPlugins.Wasm(ctx, request.Wasm) + } + return nil, wasmvmtypes.Unknown{} +} + +func NewCustomQueryHandler(queryPlugins wasmkeeper.QueryPlugins, aclKeeper aclkeeper.Keeper) wasmkeeper.WasmVMQueryHandler { + return CustomQueryHandler{ + QueryPlugins: queryPlugins, + aclKeeper: aclKeeper, + } +} + +func CustomQueryHandlerDecorator(aclKeeper aclkeeper.Keeper, customQueryPlugin QueryPlugin) func(wasmkeeper.WasmVMQueryHandler) wasmkeeper.WasmVMQueryHandler { + // validate stuff, otherwise use default handler + return func(old wasmkeeper.WasmVMQueryHandler) wasmkeeper.WasmVMQueryHandler { + queryPlugins, ok := old.(wasmkeeper.QueryPlugins) + if !ok { + panic("Invalid query plugins") + } + + queryPlugins = queryPlugins.Merge(&wasmkeeper.QueryPlugins{ + Custom: CustomQuerier(&customQueryPlugin), + }) + return NewCustomQueryHandler(queryPlugins, aclKeeper) + } +} diff --git a/wasmbinding/test/message_handler_test.go b/wasmbinding/test/message_handler_test.go new file mode 100644 index 0000000000..d72bc219d9 --- /dev/null +++ b/wasmbinding/test/message_handler_test.go @@ -0,0 +1,141 @@ +package wasmbinding + +import ( + "encoding/json" + "testing" + + wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" + wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" + wasmvmtypes "github.com/CosmWasm/wasmvm/types" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkacltypes "github.com/cosmos/cosmos-sdk/types/accesscontrol" + "github.com/cosmos/cosmos-sdk/x/accesscontrol" + acltypes "github.com/cosmos/cosmos-sdk/x/accesscontrol/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/sei-protocol/sei-chain/app" + "github.com/sei-protocol/sei-chain/wasmbinding" + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/proto/tendermint/types" +) + +type MockMessenger struct{} + +func (m MockMessenger) DispatchMsg(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) { + return []sdk.Event{{ + Type: "test", + Attributes: []abci.EventAttribute{}, + }}, nil, nil +} + +func TestMessageHandlerDependencyDecorator(t *testing.T) { + app := app.Setup(false) + contractAddr, err := sdk.AccAddressFromBech32("sei1y3pxq5dp900czh0mkudhjdqjq5m8cpmmps8yjw") + require.NoError(t, err) + defaultEncoders := wasmkeeper.DefaultEncoders(app.AppCodec(), app.TransferKeeper) + dependencyDecorator := wasmbinding.NewSDKMessageDependencyDecorator(MockMessenger{}, app.AccessControlKeeper, defaultEncoders) + testContext := app.NewContext(false, types.Header{}) + + // setup bank send message with aclkeeper + app.AccessControlKeeper.SetResourceDependencyMapping(testContext, sdkacltypes.MessageDependencyMapping{ + MessageKey: string(acltypes.GenerateMessageKey(&banktypes.MsgSend{})), + AccessOps: []sdkacltypes.AccessOperation{ + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV, + IdentifierTemplate: "*", + }, + *acltypes.CommitAccessOp(), + }, + DynamicEnabled: false, + }) + + // setup the wasm contract's dependency mapping + app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, sdkacltypes.WasmDependencyMapping{ + Enabled: true, + AccessOps: []sdkacltypes.AccessOperationWithSelector{ + { + Operation: &sdkacltypes.AccessOperation{ + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_ANY, + IdentifierTemplate: "*", + }, + }, { + Operation: acltypes.CommitAccessOp(), + }, + }, + }) + + events, _, _ := dependencyDecorator.DispatchMsg(testContext, contractAddr, "test", wasmvmtypes.CosmosMsg{ + Bank: &wasmvmtypes.BankMsg{ + Send: &wasmvmtypes.SendMsg{ + ToAddress: "sdfasdf", + Amount: []wasmvmtypes.Coin{ + { + Denom: "usei", + Amount: "12345", + }, + }, + }, + }, + }) + // we should have received the test event + require.Equal(t, []sdk.Event{ + { + Type: "test", + Attributes: []abci.EventAttribute{}, + }, + }, events) + + app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, sdkacltypes.WasmDependencyMapping{ + Enabled: true, + AccessOps: []sdkacltypes.AccessOperationWithSelector{ + { + Operation: &sdkacltypes.AccessOperation{ + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV, + IdentifierTemplate: "otherIdentifier", + }, + }, { + Operation: acltypes.CommitAccessOp(), + }, + }, + }) + + _, _, err = dependencyDecorator.DispatchMsg(testContext, contractAddr, "test", wasmvmtypes.CosmosMsg{ + Bank: &wasmvmtypes.BankMsg{ + Send: &wasmvmtypes.SendMsg{ + ToAddress: "sdfasdf", + Amount: []wasmvmtypes.Coin{ + { + Denom: "usei", + Amount: "12345", + }, + }, + }, + }, + }) + // we expect an error now + require.Error(t, accesscontrol.ErrUnexpectedWasmDependency, err) + + // reenable wasm mapping that's correct + app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, sdkacltypes.WasmDependencyMapping{ + Enabled: true, + AccessOps: []sdkacltypes.AccessOperationWithSelector{ + { + Operation: &sdkacltypes.AccessOperation{ + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV, + IdentifierTemplate: "*", + }, + }, { + Operation: acltypes.CommitAccessOp(), + }, + }, + }) + // lets try with a message that wont decode properly + _, _, err = dependencyDecorator.DispatchMsg(testContext, contractAddr, "test", wasmvmtypes.CosmosMsg{ + Custom: json.RawMessage{}, + }) + require.Error(t, wasmtypes.ErrUnknownMsg, err) +} diff --git a/wasmbinding/test/query_test.go b/wasmbinding/test/query_test.go index 924652deed..21c7dfc18c 100644 --- a/wasmbinding/test/query_test.go +++ b/wasmbinding/test/query_test.go @@ -6,8 +6,13 @@ import ( "testing" "time" + wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" + wasmvmtypes "github.com/CosmWasm/wasmvm/types" "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/accesscontrol" + acl "github.com/cosmos/cosmos-sdk/x/accesscontrol" + acltypes "github.com/cosmos/cosmos-sdk/x/accesscontrol/types" "github.com/sei-protocol/sei-chain/app" keepertest "github.com/sei-protocol/sei-chain/testutil/keeper" "github.com/sei-protocol/sei-chain/wasmbinding" @@ -27,6 +32,7 @@ import ( tokenfactorybinding "github.com/sei-protocol/sei-chain/x/tokenfactory/client/wasm/bindings" tokenfactorytypes "github.com/sei-protocol/sei-chain/x/tokenfactory/types" "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/proto/tendermint/types" ) func SetupWasmbindingTest(t *testing.T) (*app.TestWrapper, func(ctx sdk.Context, request json.RawMessage) ([]byte, error)) { @@ -409,3 +415,500 @@ func TestWasmGetCreatorInDenomFeeWhitelist(t *testing.T) { require.NoError(t, err) require.Equal(t, tokenfactorytypes.QueryCreatorInDenomFeeWhitelistResponse{Whitelisted: true}, parsedRes2) } + +func MockQueryPlugins() wasmkeeper.QueryPlugins { + return wasmkeeper.QueryPlugins{ + Bank: func(ctx sdk.Context, request *wasmvmtypes.BankQuery) ([]byte, error) { return []byte{}, nil }, + IBC: func(ctx sdk.Context, caller sdk.AccAddress, request *wasmvmtypes.IBCQuery) ([]byte, error) { + return []byte{}, nil + }, + Custom: func(ctx sdk.Context, request json.RawMessage) ([]byte, error) { + println("test") + return []byte{}, nil + }, + Stargate: func(ctx sdk.Context, request *wasmvmtypes.StargateQuery) ([]byte, error) { return []byte{}, nil }, + Staking: func(ctx sdk.Context, request *wasmvmtypes.StakingQuery) ([]byte, error) { return []byte{}, nil }, + Wasm: func(ctx sdk.Context, request *wasmvmtypes.WasmQuery) ([]byte, error) { return []byte{}, nil }, + } +} + +func TestQueryHandlerDependencyDecoratorBank(t *testing.T) { + app := app.Setup(false) + contractAddr, err := sdk.AccAddressFromBech32("sei1y3pxq5dp900czh0mkudhjdqjq5m8cpmmps8yjw") + require.NoError(t, err) + queryDecorator := wasmbinding.NewCustomQueryHandler(MockQueryPlugins(), app.AccessControlKeeper) + testContext := app.NewContext(false, types.Header{}) + + // setup the wasm contract's dependency mapping + err = app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, accesscontrol.WasmDependencyMapping{ + Enabled: true, + AccessOps: []accesscontrol.AccessOperationWithSelector{ + { + Operation: &accesscontrol.AccessOperation{ + AccessType: accesscontrol.AccessType_READ, + ResourceType: accesscontrol.ResourceType_KV_BANK, + IdentifierTemplate: "*", + }, + }, { + Operation: acltypes.CommitAccessOp(), + }, + }, + }) + require.NoError(t, err) + + _, err = queryDecorator.HandleQuery(testContext, contractAddr, wasmvmtypes.QueryRequest{ + Bank: &wasmvmtypes.BankQuery{}, + }) + require.NoError(t, err) + + err = app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, accesscontrol.WasmDependencyMapping{ + Enabled: true, + AccessOps: []accesscontrol.AccessOperationWithSelector{ + { + Operation: &accesscontrol.AccessOperation{ + AccessType: accesscontrol.AccessType_WRITE, + ResourceType: accesscontrol.ResourceType_KV_DEX, + IdentifierTemplate: "*", + }, + }, { + Operation: acltypes.CommitAccessOp(), + }, + }, + }) + require.NoError(t, err) + + _, err = queryDecorator.HandleQuery(testContext, contractAddr, wasmvmtypes.QueryRequest{ + Bank: &wasmvmtypes.BankQuery{}, + }) + require.Error(t, acl.ErrUnexpectedWasmDependency, err) +} + +func TestQueryHandlerDependencyDecoratorIBC(t *testing.T) { + app := app.Setup(false) + contractAddr, err := sdk.AccAddressFromBech32("sei1y3pxq5dp900czh0mkudhjdqjq5m8cpmmps8yjw") + require.NoError(t, err) + queryDecorator := wasmbinding.NewCustomQueryHandler(MockQueryPlugins(), app.AccessControlKeeper) + testContext := app.NewContext(false, types.Header{}) + + // setup the wasm contract's dependency mapping + err = app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, accesscontrol.WasmDependencyMapping{ + Enabled: true, + AccessOps: []accesscontrol.AccessOperationWithSelector{ + { + Operation: &accesscontrol.AccessOperation{ + AccessType: accesscontrol.AccessType_READ, + ResourceType: accesscontrol.ResourceType_ANY, + IdentifierTemplate: "*", + }, + }, { + Operation: acltypes.CommitAccessOp(), + }, + }, + }) + require.NoError(t, err) + + _, err = queryDecorator.HandleQuery(testContext, contractAddr, wasmvmtypes.QueryRequest{ + IBC: &wasmvmtypes.IBCQuery{}, + }) + require.NoError(t, err) + + err = app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, accesscontrol.WasmDependencyMapping{ + Enabled: true, + AccessOps: []accesscontrol.AccessOperationWithSelector{ + { + Operation: &accesscontrol.AccessOperation{ + AccessType: accesscontrol.AccessType_WRITE, + ResourceType: accesscontrol.ResourceType_KV, + IdentifierTemplate: "*", + }, + }, { + Operation: acltypes.CommitAccessOp(), + }, + }, + }) + require.NoError(t, err) + + _, err = queryDecorator.HandleQuery(testContext, contractAddr, wasmvmtypes.QueryRequest{ + IBC: &wasmvmtypes.IBCQuery{}, + }) + require.Error(t, acl.ErrUnexpectedWasmDependency, err) +} + +func TestQueryHandlerDependencyDecoratorStaking(t *testing.T) { + app := app.Setup(false) + contractAddr, err := sdk.AccAddressFromBech32("sei1y3pxq5dp900czh0mkudhjdqjq5m8cpmmps8yjw") + require.NoError(t, err) + queryDecorator := wasmbinding.NewCustomQueryHandler(MockQueryPlugins(), app.AccessControlKeeper) + testContext := app.NewContext(false, types.Header{}) + + // setup the wasm contract's dependency mapping + err = app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, accesscontrol.WasmDependencyMapping{ + Enabled: true, + AccessOps: []accesscontrol.AccessOperationWithSelector{ + { + Operation: &accesscontrol.AccessOperation{ + AccessType: accesscontrol.AccessType_READ, + ResourceType: accesscontrol.ResourceType_KV_STAKING, + IdentifierTemplate: "*", + }, + }, { + Operation: acltypes.CommitAccessOp(), + }, + }, + }) + require.NoError(t, err) + + _, err = queryDecorator.HandleQuery(testContext, contractAddr, wasmvmtypes.QueryRequest{ + Staking: &wasmvmtypes.StakingQuery{}, + }) + require.NoError(t, err) + + err = app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, accesscontrol.WasmDependencyMapping{ + Enabled: true, + AccessOps: []accesscontrol.AccessOperationWithSelector{ + { + Operation: &accesscontrol.AccessOperation{ + AccessType: accesscontrol.AccessType_WRITE, + ResourceType: accesscontrol.ResourceType_KV_DEX, + IdentifierTemplate: "*", + }, + }, { + Operation: acltypes.CommitAccessOp(), + }, + }, + }) + require.NoError(t, err) + + _, err = queryDecorator.HandleQuery(testContext, contractAddr, wasmvmtypes.QueryRequest{ + Staking: &wasmvmtypes.StakingQuery{}, + }) + require.Error(t, acl.ErrUnexpectedWasmDependency, err) +} + +func TestQueryHandlerDependencyDecoratorStargate(t *testing.T) { + app := app.Setup(false) + contractAddr, err := sdk.AccAddressFromBech32("sei1y3pxq5dp900czh0mkudhjdqjq5m8cpmmps8yjw") + require.NoError(t, err) + queryDecorator := wasmbinding.NewCustomQueryHandler(MockQueryPlugins(), app.AccessControlKeeper) + testContext := app.NewContext(false, types.Header{}) + + // setup the wasm contract's dependency mapping + err = app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, accesscontrol.WasmDependencyMapping{ + Enabled: true, + AccessOps: []accesscontrol.AccessOperationWithSelector{ + { + Operation: &accesscontrol.AccessOperation{ + AccessType: accesscontrol.AccessType_READ, + ResourceType: accesscontrol.ResourceType_ANY, + IdentifierTemplate: "*", + }, + }, { + Operation: acltypes.CommitAccessOp(), + }, + }, + }) + require.NoError(t, err) + + _, err = queryDecorator.HandleQuery(testContext, contractAddr, wasmvmtypes.QueryRequest{ + Stargate: &wasmvmtypes.StargateQuery{}, + }) + require.NoError(t, err) + + err = app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, accesscontrol.WasmDependencyMapping{ + Enabled: true, + AccessOps: []accesscontrol.AccessOperationWithSelector{ + { + Operation: &accesscontrol.AccessOperation{ + AccessType: accesscontrol.AccessType_WRITE, + ResourceType: accesscontrol.ResourceType_KV, + IdentifierTemplate: "*", + }, + }, { + Operation: acltypes.CommitAccessOp(), + }, + }, + }) + require.NoError(t, err) + + _, err = queryDecorator.HandleQuery(testContext, contractAddr, wasmvmtypes.QueryRequest{ + Stargate: &wasmvmtypes.StargateQuery{}, + }) + require.Error(t, acl.ErrUnexpectedWasmDependency, err) +} + +func TestQueryHandlerDependencyDecoratorWasm(t *testing.T) { + app := app.Setup(false) + contractAddr, err := sdk.AccAddressFromBech32("sei1y3pxq5dp900czh0mkudhjdqjq5m8cpmmps8yjw") + require.NoError(t, err) + queryDecorator := wasmbinding.NewCustomQueryHandler(MockQueryPlugins(), app.AccessControlKeeper) + testContext := app.NewContext(false, types.Header{}) + + // setup the wasm contract's dependency mapping + err = app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, accesscontrol.WasmDependencyMapping{ + Enabled: true, + AccessOps: []accesscontrol.AccessOperationWithSelector{ + { + Operation: &accesscontrol.AccessOperation{ + AccessType: accesscontrol.AccessType_READ, + ResourceType: accesscontrol.ResourceType_KV_WASM, + IdentifierTemplate: "*", + }, + }, { + Operation: acltypes.CommitAccessOp(), + }, + }, + }) + require.NoError(t, err) + + _, err = queryDecorator.HandleQuery(testContext, contractAddr, wasmvmtypes.QueryRequest{ + Wasm: &wasmvmtypes.WasmQuery{}, + }) + require.NoError(t, err) + + err = app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, accesscontrol.WasmDependencyMapping{ + Enabled: true, + AccessOps: []accesscontrol.AccessOperationWithSelector{ + { + Operation: &accesscontrol.AccessOperation{ + AccessType: accesscontrol.AccessType_WRITE, + ResourceType: accesscontrol.ResourceType_KV_DEX, + IdentifierTemplate: "*", + }, + }, { + Operation: acltypes.CommitAccessOp(), + }, + }, + }) + require.NoError(t, err) + + _, err = queryDecorator.HandleQuery(testContext, contractAddr, wasmvmtypes.QueryRequest{ + Wasm: &wasmvmtypes.WasmQuery{}, + }) + require.Error(t, acl.ErrUnexpectedWasmDependency, err) +} + +func TestQueryHandlerDependencyDecoratorDex(t *testing.T) { + app := app.Setup(false) + contractAddr, err := sdk.AccAddressFromBech32("sei1y3pxq5dp900czh0mkudhjdqjq5m8cpmmps8yjw") + require.NoError(t, err) + queryDecorator := wasmbinding.NewCustomQueryHandler(MockQueryPlugins(), app.AccessControlKeeper) + testContext := app.NewContext(false, types.Header{}) + + // setup the wasm contract's dependency mapping + err = app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, accesscontrol.WasmDependencyMapping{ + Enabled: true, + AccessOps: []accesscontrol.AccessOperationWithSelector{ + { + Operation: &accesscontrol.AccessOperation{ + AccessType: accesscontrol.AccessType_READ, + ResourceType: accesscontrol.ResourceType_KV_DEX, + IdentifierTemplate: "*", + }, + }, { + Operation: acltypes.CommitAccessOp(), + }, + }, + }) + require.NoError(t, err) + + customQuery, err := json.Marshal(wasmbinding.SeiQueryWrapper{ + Route: wasmbinding.DexRoute, + }) + require.NoError(t, err) + _, err = queryDecorator.HandleQuery(testContext, contractAddr, wasmvmtypes.QueryRequest{ + Custom: customQuery, + }) + require.NoError(t, err) + + err = app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, accesscontrol.WasmDependencyMapping{ + Enabled: true, + AccessOps: []accesscontrol.AccessOperationWithSelector{ + { + Operation: &accesscontrol.AccessOperation{ + AccessType: accesscontrol.AccessType_READ, + ResourceType: accesscontrol.ResourceType_KV_ORACLE, + IdentifierTemplate: "*", + }, + }, { + Operation: acltypes.CommitAccessOp(), + }, + }, + }) + require.NoError(t, err) + + _, err = queryDecorator.HandleQuery(testContext, contractAddr, wasmvmtypes.QueryRequest{ + Custom: customQuery, + }) + require.Error(t, acl.ErrUnexpectedWasmDependency, err) +} + +func TestQueryHandlerDependencyDecoratorOracle(t *testing.T) { + app := app.Setup(false) + contractAddr, err := sdk.AccAddressFromBech32("sei1y3pxq5dp900czh0mkudhjdqjq5m8cpmmps8yjw") + require.NoError(t, err) + queryDecorator := wasmbinding.NewCustomQueryHandler(MockQueryPlugins(), app.AccessControlKeeper) + testContext := app.NewContext(false, types.Header{}) + + // setup the wasm contract's dependency mapping + err = app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, accesscontrol.WasmDependencyMapping{ + Enabled: true, + AccessOps: []accesscontrol.AccessOperationWithSelector{ + { + Operation: &accesscontrol.AccessOperation{ + AccessType: accesscontrol.AccessType_READ, + ResourceType: accesscontrol.ResourceType_KV_ORACLE, + IdentifierTemplate: "*", + }, + }, { + Operation: acltypes.CommitAccessOp(), + }, + }, + }) + require.NoError(t, err) + + customQuery, err := json.Marshal(wasmbinding.SeiQueryWrapper{ + Route: wasmbinding.OracleRoute, + }) + require.NoError(t, err) + _, err = queryDecorator.HandleQuery(testContext, contractAddr, wasmvmtypes.QueryRequest{ + Custom: customQuery, + }) + require.NoError(t, err) + + err = app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, accesscontrol.WasmDependencyMapping{ + Enabled: true, + AccessOps: []accesscontrol.AccessOperationWithSelector{ + { + Operation: &accesscontrol.AccessOperation{ + AccessType: accesscontrol.AccessType_READ, + ResourceType: accesscontrol.ResourceType_KV_BANK, + IdentifierTemplate: "*", + }, + }, { + Operation: acltypes.CommitAccessOp(), + }, + }, + }) + require.NoError(t, err) + + _, err = queryDecorator.HandleQuery(testContext, contractAddr, wasmvmtypes.QueryRequest{ + Custom: customQuery, + }) + require.Error(t, acl.ErrUnexpectedWasmDependency, err) +} + +func TestQueryHandlerDependencyDecoratorEpoch(t *testing.T) { + app := app.Setup(false) + contractAddr, err := sdk.AccAddressFromBech32("sei1y3pxq5dp900czh0mkudhjdqjq5m8cpmmps8yjw") + require.NoError(t, err) + queryDecorator := wasmbinding.NewCustomQueryHandler(MockQueryPlugins(), app.AccessControlKeeper) + testContext := app.NewContext(false, types.Header{}) + + // setup the wasm contract's dependency mapping + err = app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, accesscontrol.WasmDependencyMapping{ + Enabled: true, + AccessOps: []accesscontrol.AccessOperationWithSelector{ + { + Operation: &accesscontrol.AccessOperation{ + AccessType: accesscontrol.AccessType_READ, + ResourceType: accesscontrol.ResourceType_KV_EPOCH, + IdentifierTemplate: "*", + }, + }, { + Operation: acltypes.CommitAccessOp(), + }, + }, + }) + require.NoError(t, err) + + customQuery, err := json.Marshal(wasmbinding.SeiQueryWrapper{ + Route: wasmbinding.EpochRoute, + }) + require.NoError(t, err) + _, err = queryDecorator.HandleQuery(testContext, contractAddr, wasmvmtypes.QueryRequest{ + Custom: customQuery, + }) + require.NoError(t, err) + + err = app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, accesscontrol.WasmDependencyMapping{ + Enabled: true, + AccessOps: []accesscontrol.AccessOperationWithSelector{ + { + Operation: &accesscontrol.AccessOperation{ + AccessType: accesscontrol.AccessType_READ, + ResourceType: accesscontrol.ResourceType_KV_BANK, + IdentifierTemplate: "*", + }, + }, { + Operation: acltypes.CommitAccessOp(), + }, + }, + }) + require.NoError(t, err) + + _, err = queryDecorator.HandleQuery(testContext, contractAddr, wasmvmtypes.QueryRequest{ + Custom: customQuery, + }) + require.Error(t, acl.ErrUnexpectedWasmDependency, err) +} + +func TestQueryHandlerDependencyDecoratorTokenFactory(t *testing.T) { + app := app.Setup(false) + contractAddr, err := sdk.AccAddressFromBech32("sei1y3pxq5dp900czh0mkudhjdqjq5m8cpmmps8yjw") + require.NoError(t, err) + queryDecorator := wasmbinding.NewCustomQueryHandler(MockQueryPlugins(), app.AccessControlKeeper) + testContext := app.NewContext(false, types.Header{}) + + // setup the wasm contract's dependency mapping + err = app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, accesscontrol.WasmDependencyMapping{ + Enabled: true, + AccessOps: []accesscontrol.AccessOperationWithSelector{ + { + Operation: &accesscontrol.AccessOperation{ + AccessType: accesscontrol.AccessType_READ, + ResourceType: accesscontrol.ResourceType_KV, + IdentifierTemplate: "*", + }, + SelectorType: accesscontrol.AccessOperationSelectorType_NONE, + }, + { + Operation: acltypes.CommitAccessOp(), + SelectorType: accesscontrol.AccessOperationSelectorType_NONE, + }, + }, + }) + require.NoError(t, err) + + customQuery, err := json.Marshal(wasmbinding.SeiQueryWrapper{ + Route: wasmbinding.TokenFactoryRoute, + }) + require.NoError(t, err) + _, err = queryDecorator.HandleQuery(testContext, contractAddr, wasmvmtypes.QueryRequest{ + Custom: customQuery, + }) + require.NoError(t, err) + + err = app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, accesscontrol.WasmDependencyMapping{ + Enabled: true, + AccessOps: []accesscontrol.AccessOperationWithSelector{ + { + Operation: &accesscontrol.AccessOperation{ + AccessType: accesscontrol.AccessType_READ, + ResourceType: accesscontrol.ResourceType_Mem, + IdentifierTemplate: "*", + }, + SelectorType: accesscontrol.AccessOperationSelectorType_NONE, + }, + { + Operation: acltypes.CommitAccessOp(), + SelectorType: accesscontrol.AccessOperationSelectorType_NONE, + }, + }, + }) + require.NoError(t, err) + + _, err = queryDecorator.HandleQuery(testContext, contractAddr, wasmvmtypes.QueryRequest{ + Custom: customQuery, + }) + require.Error(t, acl.ErrUnexpectedWasmDependency, err) +} diff --git a/wasmbinding/utils.go b/wasmbinding/utils.go new file mode 100644 index 0000000000..b07702e978 --- /dev/null +++ b/wasmbinding/utils.go @@ -0,0 +1,14 @@ +package wasmbinding + +import sdk "github.com/cosmos/cosmos-sdk/types" + +const ( + EventTypeWasmContractWithIncorrectDependency = "wasm-incorrect-dep" + AttributeKeyWasmContractAddress = "contract-addr" +) + +func emitIncorrectDependencyWasmEvent(ctx sdk.Context, contractAddr string) { + ctx.EventManager().EmitEvent( + sdk.NewEvent(EventTypeWasmContractWithIncorrectDependency, sdk.NewAttribute(AttributeKeyWasmContractAddress, contractAddr)), + ) +} diff --git a/wasmbinding/wasm.go b/wasmbinding/wasm.go index 99baf4f180..5d13ae9206 100644 --- a/wasmbinding/wasm.go +++ b/wasmbinding/wasm.go @@ -3,6 +3,9 @@ package wasmbinding import ( "github.com/CosmWasm/wasmd/x/wasm" wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" + wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + aclkeeper "github.com/cosmos/cosmos-sdk/x/accesscontrol/keeper" authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" dexwasm "github.com/sei-protocol/sei-chain/x/dex/client/wasm" dexkeeper "github.com/sei-protocol/sei-chain/x/dex/keeper" @@ -21,6 +24,12 @@ func RegisterCustomPlugins( tokenfactory *tokenfactorykeeper.Keeper, accountKeeper *authkeeper.AccountKeeper, router wasmkeeper.MessageRouter, + channelKeeper wasmtypes.ChannelKeeper, + capabilityKeeper wasmtypes.CapabilityKeeper, + bankKeeper wasmtypes.Burner, + unpacker codectypes.AnyUnpacker, + portSource wasmtypes.ICS20TransferPortSource, + aclKeeper aclkeeper.Keeper, ) []wasmkeeper.Option { dexHandler := dexwasm.NewDexWasmQueryHandler(dex) oracleHandler := oraclewasm.NewOracleWasmQueryHandler(oracle) @@ -28,15 +37,13 @@ func RegisterCustomPlugins( tokenfactoryHandler := tokenfactorywasm.NewTokenFactoryWasmQueryHandler(tokenfactory) wasmQueryPlugin := NewQueryPlugin(oracleHandler, dexHandler, epochHandler, tokenfactoryHandler) - queryPluginOpt := wasmkeeper.WithQueryPlugins(&wasmkeeper.QueryPlugins{ - Custom: CustomQuerier(wasmQueryPlugin), - }) - messengerDecoratorOpt := wasmkeeper.WithMessageHandlerDecorator( - CustomMessageDecorator(router, accountKeeper), + queryOpt := wasmkeeper.WithQueryHandlerDecorator(CustomQueryHandlerDecorator(aclKeeper, *wasmQueryPlugin)) + messengerHandlerOpt := wasmkeeper.WithMessageHandler( + CustomMessageHandler(router, channelKeeper, capabilityKeeper, bankKeeper, unpacker, portSource, aclKeeper), ) return []wasm.Option{ - queryPluginOpt, - messengerDecoratorOpt, + queryOpt, + messengerHandlerOpt, } } diff --git a/x/dex/keeper/epoch.go b/x/dex/keeper/epoch.go index d9ca566b7d..33f8012af4 100644 --- a/x/dex/keeper/epoch.go +++ b/x/dex/keeper/epoch.go @@ -14,6 +14,7 @@ func (k Keeper) SetEpoch(ctx sdk.Context, epoch uint64) { bz := make([]byte, 8) binary.BigEndian.PutUint64(bz, epoch) store.Set([]byte(EpochKey), bz) + ctx.Logger().Info(fmt.Sprintf("Current epoch %d", epoch)) } func (k Keeper) IsNewEpoch(ctx sdk.Context) (bool, uint64) { @@ -21,6 +22,5 @@ func (k Keeper) IsNewEpoch(ctx sdk.Context) (bool, uint64) { b := store.Get([]byte(EpochKey)) lastEpoch := binary.BigEndian.Uint64(b) currentEpoch := k.EpochKeeper.GetEpoch(ctx).CurrentEpoch - ctx.Logger().Info(fmt.Sprintf("Current epoch %d", currentEpoch)) return currentEpoch > lastEpoch, currentEpoch } diff --git a/x/dex/module_test.go b/x/dex/module_test.go index e431d8ee6f..dd196ceb91 100644 --- a/x/dex/module_test.go +++ b/x/dex/module_test.go @@ -185,7 +185,7 @@ func TestEndBlockLimitOrder(t *testing.T) { bankkeeper := testApp.BankKeeper bankkeeper.MintCoins(ctx, minttypes.ModuleName, amounts) bankkeeper.SendCoinsFromModuleToAccount(ctx, minttypes.ModuleName, testAccount, amounts) - wasm, err := ioutil.ReadFile("./testdata/clearing_house.wasm") + wasm, err := ioutil.ReadFile("./testdata/mars.wasm") if err != nil { panic(err) } diff --git a/x/mint/types/expected_keepers.go b/x/mint/types/expected_keepers.go index 4ce2e3a31e..da49ab11b2 100644 --- a/x/mint/types/expected_keepers.go +++ b/x/mint/types/expected_keepers.go @@ -27,6 +27,8 @@ type BankKeeper interface { SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error SendCoinsFromModuleToModule(ctx sdk.Context, senderModule, recipientModule string, amt sdk.Coins) error MintCoins(ctx sdk.Context, name string, amt sdk.Coins) error + DeferredMintCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error + DeferredBurnCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error } // EpochKeeper defines the contract needed to be fulfilled for epoch keepers diff --git a/x/oracle/ante.go b/x/oracle/ante.go index 8cd469fda5..2b0a903de3 100644 --- a/x/oracle/ante.go +++ b/x/oracle/ante.go @@ -4,8 +4,10 @@ import ( "sync" sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + sdkacltypes "github.com/cosmos/cosmos-sdk/types/accesscontrol" + aclutils "github.com/sei-protocol/sei-chain/aclmapping/utils" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/sei-protocol/sei-chain/x/oracle/keeper" "github.com/sei-protocol/sei-chain/x/oracle/types" ) @@ -45,6 +47,24 @@ func (spd SpammingPreventionDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, si return next(ctx, tx, simulate) } +func (spd SpammingPreventionDecorator) AnteDeps(txDeps []sdkacltypes.AccessOperation, tx sdk.Tx, next sdk.AnteDepGenerator) (newTxDeps []sdkacltypes.AccessOperation, err error) { + deps := []sdkacltypes.AccessOperation{} + for _, msg := range tx.GetMsgs() { + // Error checking will be handled in AnteHandler + switch m := msg.(type) { + case *types.MsgAggregateExchangeRateVote: + feederAddr, _ := sdk.AccAddressFromBech32(m.Feeder) + valAddr, _ := sdk.ValAddressFromBech32(m.Validator) + deps = append(deps, aclutils.GetOracleReadAccessOpsForValAndFeeder(feederAddr, valAddr)...) + // TODO: we also need to add READs for Validator + bonded check + default: + continue + } + } + + return next(append(txDeps, deps...), tx) +} + // CheckOracleSpamming check whether the msgs are spamming purpose or not func (spd SpammingPreventionDecorator) CheckOracleSpamming(ctx sdk.Context, msgs []sdk.Msg) error { spd.mu.Lock() diff --git a/x/oracle/keeper/test_utils.go b/x/oracle/keeper/test_utils.go old mode 100755 new mode 100644 diff --git a/x/oracle/simulation/operations.go b/x/oracle/simulation/operations.go index 3988c4b164..2028e73625 100644 --- a/x/oracle/simulation/operations.go +++ b/x/oracle/simulation/operations.go @@ -65,7 +65,7 @@ func WeightedOperations( } // SimulateMsgAggregateExchangeRateVote generates a MsgAggregateExchangeRateVote with random values. -//nolint: funlen +// nolint: funlen func SimulateMsgAggregateExchangeRateVote(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Keeper) simtypes.Operation { return func( r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string, @@ -122,7 +122,7 @@ func SimulateMsgAggregateExchangeRateVote(ak types.AccountKeeper, bk types.BankK } // SimulateMsgDelegateFeedConsent generates a MsgDelegateFeedConsent with random values. -//nolint: funlen +// nolint: funlen func SimulateMsgDelegateFeedConsent(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Keeper) simtypes.Operation { return func( r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string, diff --git a/x/tokenfactory/keeper/admins_test.go b/x/tokenfactory/keeper/admins_test.go index 414519f320..0c3f3632c7 100644 --- a/x/tokenfactory/keeper/admins_test.go +++ b/x/tokenfactory/keeper/admins_test.go @@ -17,11 +17,14 @@ func (suite *KeeperTestSuite) TestAdminMsgs() { queryRes, err := suite.queryClient.DenomAuthorityMetadata(suite.Ctx.Context(), &types.QueryDenomAuthorityMetadataRequest{ Denom: suite.defaultDenom, }) + suite.App.BankKeeper.WriteDeferredOperations(suite.Ctx) suite.Require().NoError(err) suite.Require().Equal(suite.TestAccs[0].String(), queryRes.AuthorityMetadata.Admin) // Test minting to admins own account _, err = suite.msgServer.Mint(sdk.WrapSDKContext(suite.Ctx), types.NewMsgMint(suite.TestAccs[0].String(), sdk.NewInt64Coin(suite.defaultDenom, 10))) + suite.App.BankKeeper.WriteDeferredOperations(suite.Ctx) + addr0bal += 10 suite.Require().NoError(err) suite.Require().True(suite.App.BankKeeper.GetBalance(suite.Ctx, suite.TestAccs[0], suite.defaultDenom).Amount.Int64() == addr0bal, suite.App.BankKeeper.GetBalance(suite.Ctx, suite.TestAccs[0], suite.defaultDenom)) @@ -34,12 +37,16 @@ func (suite *KeeperTestSuite) TestAdminMsgs() { // Test burning from own account _, err = suite.msgServer.Burn(sdk.WrapSDKContext(suite.Ctx), types.NewMsgBurn(suite.TestAccs[0].String(), sdk.NewInt64Coin(suite.defaultDenom, 5))) + suite.App.BankKeeper.WriteDeferredOperations(suite.Ctx) + addr0bal -= 5 suite.Require().NoError(err) suite.Require().True(suite.App.BankKeeper.GetBalance(suite.Ctx, suite.TestAccs[1], suite.defaultDenom).Amount.Int64() == addr1bal) // Test Change Admin _, err = suite.msgServer.ChangeAdmin(sdk.WrapSDKContext(suite.Ctx), types.NewMsgChangeAdmin(suite.TestAccs[0].String(), suite.defaultDenom, suite.TestAccs[1].String())) + suite.App.BankKeeper.WriteDeferredOperations(suite.Ctx) + queryRes, err = suite.queryClient.DenomAuthorityMetadata(suite.Ctx.Context(), &types.QueryDenomAuthorityMetadataRequest{ Denom: suite.defaultDenom, }) @@ -48,16 +55,22 @@ func (suite *KeeperTestSuite) TestAdminMsgs() { // Make sure old admin can no longer do actions _, err = suite.msgServer.Burn(sdk.WrapSDKContext(suite.Ctx), types.NewMsgBurn(suite.TestAccs[0].String(), sdk.NewInt64Coin(suite.defaultDenom, 5))) + suite.App.BankKeeper.WriteDeferredOperations(suite.Ctx) + suite.Require().Error(err) // Make sure the new admin works _, err = suite.msgServer.Mint(sdk.WrapSDKContext(suite.Ctx), types.NewMsgMint(suite.TestAccs[1].String(), sdk.NewInt64Coin(suite.defaultDenom, 5))) + suite.App.BankKeeper.WriteDeferredOperations(suite.Ctx) + addr1bal += 5 suite.Require().NoError(err) suite.Require().True(suite.App.BankKeeper.GetBalance(suite.Ctx, suite.TestAccs[1], suite.defaultDenom).Amount.Int64() == addr1bal) // Try setting admin to empty _, err = suite.msgServer.ChangeAdmin(sdk.WrapSDKContext(suite.Ctx), types.NewMsgChangeAdmin(suite.TestAccs[1].String(), suite.defaultDenom, "")) + suite.App.BankKeeper.WriteDeferredOperations(suite.Ctx) + suite.Require().NoError(err) queryRes, err = suite.queryClient.DenomAuthorityMetadata(suite.Ctx.Context(), &types.QueryDenomAuthorityMetadataRequest{ Denom: suite.defaultDenom, @@ -108,6 +121,8 @@ func (suite *KeeperTestSuite) TestMintDenom() { suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { // Test minting to admins own account _, err := suite.msgServer.Mint(sdk.WrapSDKContext(suite.Ctx), types.NewMsgMint(tc.admin, sdk.NewInt64Coin(tc.mintDenom, 10))) + suite.App.BankKeeper.WriteDeferredOperations(suite.Ctx) + if tc.valid { addr0bal += 10 suite.Require().NoError(err) @@ -127,6 +142,8 @@ func (suite *KeeperTestSuite) TestBurnDenom() { // mint 10 default token for testAcc[0] suite.msgServer.Mint(sdk.WrapSDKContext(suite.Ctx), types.NewMsgMint(suite.TestAccs[0].String(), sdk.NewInt64Coin(suite.defaultDenom, 10))) + suite.App.BankKeeper.WriteDeferredOperations(suite.Ctx) + addr0bal += 10 for _, tc := range []struct { @@ -168,6 +185,8 @@ func (suite *KeeperTestSuite) TestBurnDenom() { suite.Run(fmt.Sprintf("Case %s", tc.desc), func() { // Test minting to admins own account _, err := suite.msgServer.Burn(sdk.WrapSDKContext(suite.Ctx), types.NewMsgBurn(tc.admin, sdk.NewInt64Coin(tc.burnDenom, 10))) + suite.App.BankKeeper.WriteDeferredOperations(suite.Ctx) + if tc.valid { addr0bal -= 10 suite.Require().NoError(err) @@ -228,14 +247,19 @@ func (suite *KeeperTestSuite) TestChangeAdminDenom() { // Create a denom and mint res, err := suite.msgServer.CreateDenom(sdk.WrapSDKContext(suite.Ctx), types.NewMsgCreateDenom(suite.TestAccs[0].String(), "bitcoin")) + suite.App.BankKeeper.WriteDeferredOperations(suite.Ctx) suite.Require().NoError(err) testDenom := res.GetNewTokenDenom() _, err = suite.msgServer.Mint(sdk.WrapSDKContext(suite.Ctx), types.NewMsgMint(suite.TestAccs[0].String(), sdk.NewInt64Coin(testDenom, 10))) + suite.App.BankKeeper.WriteDeferredOperations(suite.Ctx) + suite.Require().NoError(err) _, err = suite.msgServer.ChangeAdmin(sdk.WrapSDKContext(suite.Ctx), tc.msgChangeAdmin(testDenom)) + suite.App.BankKeeper.WriteDeferredOperations(suite.Ctx) + if tc.expectedChangeAdminPass { suite.Require().NoError(err) } else { @@ -258,6 +282,8 @@ func (suite *KeeperTestSuite) TestChangeAdminDenom() { // we test mint to test if admin authority is performed properly after admin change. if tc.msgMint != nil { _, err := suite.msgServer.Mint(sdk.WrapSDKContext(suite.Ctx), tc.msgMint(testDenom)) + suite.App.BankKeeper.WriteDeferredOperations(suite.Ctx) + if tc.expectedMintPass { suite.Require().NoError(err) } else { diff --git a/x/tokenfactory/keeper/bankactions.go b/x/tokenfactory/keeper/bankactions.go index 853c517ef8..a052db180a 100644 --- a/x/tokenfactory/keeper/bankactions.go +++ b/x/tokenfactory/keeper/bankactions.go @@ -1,6 +1,8 @@ package keeper import ( + "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/sei-protocol/sei-chain/x/tokenfactory/types" @@ -13,7 +15,8 @@ func (k Keeper) mintTo(ctx sdk.Context, amount sdk.Coin, mintTo string) error { return err } - err = k.bankKeeper.MintCoins(ctx, types.ModuleName, sdk.NewCoins(amount)) + ctx.Logger().Info(fmt.Sprintf("Minting amount=%s for module=%s", amount.String(), types.ModuleName)) + err = k.bankKeeper.DeferredMintCoins(ctx, types.ModuleName, sdk.NewCoins(amount)) if err != nil { return err } @@ -23,7 +26,8 @@ func (k Keeper) mintTo(ctx sdk.Context, amount sdk.Coin, mintTo string) error { return err } - return k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, + ctx.Logger().Info(fmt.Sprintf("Sending Minted amount=%s to addr=%s", amount.String(), addr.String())) + return k.bankKeeper.DeferredSendCoinsFromModuleToAccount(ctx, types.ModuleName, addr, sdk.NewCoins(amount)) } @@ -40,7 +44,8 @@ func (k Keeper) burnFrom(ctx sdk.Context, amount sdk.Coin, burnFrom string) erro return err } - err = k.bankKeeper.SendCoinsFromAccountToModule(ctx, + ctx.Logger().Info(fmt.Sprintf("Sending amount=%s to module=%s from account=%s", amount.String(), types.ModuleName, addr.String())) + err = k.bankKeeper.DeferredSendCoinsFromAccountToModule(ctx, addr, types.ModuleName, sdk.NewCoins(amount)) @@ -48,7 +53,8 @@ func (k Keeper) burnFrom(ctx sdk.Context, amount sdk.Coin, burnFrom string) erro return err } - return k.bankKeeper.BurnCoins(ctx, types.ModuleName, sdk.NewCoins(amount)) + ctx.Logger().Info(fmt.Sprintf("Burning amount=%s from module=%s", amount.String(), types.ModuleName)) + return k.bankKeeper.DeferredBurnCoins(ctx, types.ModuleName, sdk.NewCoins(amount)) } // func (k Keeper) forceTransfer(ctx sdk.Context, amount sdk.Coin, fromAddr string, toAddr string) error { diff --git a/x/tokenfactory/types/expected_keepers.go b/x/tokenfactory/types/expected_keepers.go index 1f750fd8d3..3b4e3c9e0a 100644 --- a/x/tokenfactory/types/expected_keepers.go +++ b/x/tokenfactory/types/expected_keepers.go @@ -15,6 +15,12 @@ type BankKeeper interface { SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error + + DeferredSendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amount sdk.Coins) error + DeferredSendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error + DeferredMintCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error + DeferredBurnCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error + DelegateCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error UndelegateCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error MintCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error From 22d5f55248c59638351c20e70c336d901c258ef1 Mon Sep 17 00:00:00 2001 From: Brandon Weng <18161326+BrandonWeng@users.noreply.github.com> Date: Wed, 2 Nov 2022 09:57:40 -0400 Subject: [PATCH 2/4] Add ACL Mapping for Dex Send and Cancel orders (#360) * Implement DEX parallelization * Remove print * sd * check out loadtest to 2.0.0beta * fix Co-authored-by: Eric Zhu --- aclmapping/dex/mappings.go | 95 +++++++- aclmapping/dex/mappings_test.go | 294 +++++++++++++++++++++++ aclmapping/utils/identifier_templates.go | 1 + aclmapping/utils/resource_type.go | 43 +++- aclmapping/utils/resource_type_test.go | 29 +++ app/app.go | 14 +- app/app_test.go | 47 ++++ go.mod | 4 +- go.sum | 8 +- 9 files changed, 509 insertions(+), 26 deletions(-) create mode 100644 aclmapping/dex/mappings_test.go create mode 100644 aclmapping/utils/resource_type_test.go diff --git a/aclmapping/dex/mappings.go b/aclmapping/dex/mappings.go index 58156eb64e..775f785f5a 100644 --- a/aclmapping/dex/mappings.go +++ b/aclmapping/dex/mappings.go @@ -7,28 +7,107 @@ import ( sdkacltypes "github.com/cosmos/cosmos-sdk/types/accesscontrol" aclkeeper "github.com/cosmos/cosmos-sdk/x/accesscontrol/keeper" acltypes "github.com/cosmos/cosmos-sdk/x/accesscontrol/types" - dexmoduletypes "github.com/sei-protocol/sei-chain/x/dex/types" + dextypes "github.com/sei-protocol/sei-chain/x/dex/types" ) -var ErrPlaceOrdersGenerator = fmt.Errorf("invalid message received for type DexPlaceOrders") +var ErrPlaceOrdersGenerator = fmt.Errorf("invalid message received for dex module") func GetDexDependencyGenerators() aclkeeper.DependencyGeneratorMap { dependencyGeneratorMap := make(aclkeeper.DependencyGeneratorMap) // dex place orders - placeOrdersKey := acltypes.GenerateMessageKey(&dexmoduletypes.MsgPlaceOrders{}) + placeOrdersKey := acltypes.GenerateMessageKey(&dextypes.MsgPlaceOrders{}) + cancelOrdersKey := acltypes.GenerateMessageKey(&dextypes.MsgCancelOrders{}) dependencyGeneratorMap[placeOrdersKey] = DexPlaceOrdersDependencyGenerator + dependencyGeneratorMap[cancelOrdersKey] = DexCancelOrdersDependencyGenerator return dependencyGeneratorMap } +func GetDexMemReadWrite(contract string) []sdkacltypes.AccessOperation { + if contract == "" { + return []sdkacltypes.AccessOperation{} + } + + return []sdkacltypes.AccessOperation{ + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_DexMem, + IdentifierTemplate: contract, + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_DexMem, + IdentifierTemplate: contract, + }, + } +} + +func GetLongShortOrderBookOps(contractAddr string, priceDenom string, assetDenom string) []sdkacltypes.AccessOperation { + return []sdkacltypes.AccessOperation{ + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_DEX_CONTRACT_LONGBOOK, + IdentifierTemplate: string(dextypes.OrderBookPrefix(true, contractAddr, priceDenom, assetDenom)), + }, + + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_DEX_CONTRACT_SHORTBOOK, + IdentifierTemplate: string(dextypes.OrderBookPrefix(false, contractAddr, priceDenom, assetDenom)), + }, + } +} + func DexPlaceOrdersDependencyGenerator(keeper aclkeeper.Keeper, ctx sdk.Context, msg sdk.Msg) ([]sdkacltypes.AccessOperation, error) { - placeOrdersMsg, ok := msg.(*dexmoduletypes.MsgPlaceOrders) + placeOrdersMsg, ok := msg.(*dextypes.MsgPlaceOrders) if !ok { return []sdkacltypes.AccessOperation{}, ErrPlaceOrdersGenerator } - // TODO: This is not final, JUST AN EXAMPLE - return []sdkacltypes.AccessOperation{ - {AccessType: sdkacltypes.AccessType_WRITE, ResourceType: sdkacltypes.ResourceType_KV, IdentifierTemplate: placeOrdersMsg.ContractAddr}, - }, nil + + contractAddr := placeOrdersMsg.ContractAddr + + aclOps := GetDexMemReadWrite(contractAddr) + + aclOps = append(aclOps, []sdkacltypes.AccessOperation{ + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_DEX_NEXT_ORDER_ID, + IdentifierTemplate: string(dextypes.NextOrderIDPrefix(contractAddr)), + }, + { + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV_DEX_NEXT_ORDER_ID, + IdentifierTemplate: string(dextypes.NextOrderIDPrefix(contractAddr)), + }, + { + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV_DEX_TICK_SIZE, + IdentifierTemplate: string(dextypes.TickSizeKeyPrefix(contractAddr)), + }, + }...) + + // Last Operation should always be a commit + aclOps = append(aclOps, *acltypes.CommitAccessOp()) + return aclOps, nil +} + +func DexCancelOrdersDependencyGenerator(keeper aclkeeper.Keeper, ctx sdk.Context, msg sdk.Msg) ([]sdkacltypes.AccessOperation, error) { + cancelOrdersMsg, ok := msg.(*dextypes.MsgCancelOrders) + if !ok { + return []sdkacltypes.AccessOperation{}, ErrPlaceOrdersGenerator + } + contractAddr := cancelOrdersMsg.ContractAddr + + aclOps := GetDexMemReadWrite(contractAddr) + + for _, order := range cancelOrdersMsg.GetCancellations() { + priceDenom := order.GetPriceDenom() + assetDenom := order.GetAssetDenom() + aclOps = append(aclOps, GetLongShortOrderBookOps(contractAddr, priceDenom, assetDenom)...) + } + + // Last Operation should always be a commit + aclOps = append(aclOps, *acltypes.CommitAccessOp()) + return aclOps, nil } diff --git a/aclmapping/dex/mappings_test.go b/aclmapping/dex/mappings_test.go new file mode 100644 index 0000000000..906fd01b2e --- /dev/null +++ b/aclmapping/dex/mappings_test.go @@ -0,0 +1,294 @@ +package acldexmapping_test + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkacltypes "github.com/cosmos/cosmos-sdk/types/accesscontrol" + acltypes "github.com/cosmos/cosmos-sdk/x/accesscontrol/types" + "github.com/k0kubun/pp/v3" + dexacl "github.com/sei-protocol/sei-chain/aclmapping/dex" + aclutils "github.com/sei-protocol/sei-chain/aclmapping/utils" + "github.com/sei-protocol/sei-chain/app" + "github.com/sei-protocol/sei-chain/app/apptesting" + keepertest "github.com/sei-protocol/sei-chain/testutil/keeper" + dexcache "github.com/sei-protocol/sei-chain/x/dex/cache" + dexmsgserver "github.com/sei-protocol/sei-chain/x/dex/keeper/msgserver" + "github.com/sei-protocol/sei-chain/x/dex/types" + dextypes "github.com/sei-protocol/sei-chain/x/dex/types" + dexutils "github.com/sei-protocol/sei-chain/x/dex/utils" + oracletypes "github.com/sei-protocol/sei-chain/x/oracle/types" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +type KeeperTestSuite struct { + apptesting.KeeperTestHelper + + queryClient dextypes.QueryClient + msgServer dextypes.MsgServer + defaultDenom string + defaultExchangeRate string + initialBalance sdk.Coins + creator string + contract string + + msgPlaceOrders *dextypes.MsgPlaceOrders + msgCancelOrders *dextypes.MsgCancelOrders +} + +func TestKeeperTestSuite(t *testing.T) { + suite.Run(t, new(KeeperTestSuite)) +} + +// Runs before each test case +func (suite *KeeperTestSuite) SetupTest() { + suite.Setup() +} + +// Explicitly only run once during setup +func (suite *KeeperTestSuite) PrepareTest() { + suite.defaultDenom = "usei" + suite.defaultExchangeRate = fmt.Sprintf("%dusei", sdk.NewDec(1700)) + + suite.initialBalance = sdk.Coins{sdk.NewInt64Coin(suite.defaultDenom, 100000000000)} + suite.initialBalance = sdk.Coins{sdk.NewInt64Coin("stake", 100000000000)} + suite.FundAcc(suite.TestAccs[0], suite.initialBalance) + + suite.queryClient = dextypes.NewQueryClient(suite.QueryHelper) + suite.msgServer = dexmsgserver.NewMsgServerImpl(suite.App.DexKeeper) + + msgValidator := sdkacltypes.NewMsgValidator(aclutils.StoreKeyToResourceTypePrefixMap) + suite.Ctx = suite.Ctx.WithMsgValidator(msgValidator) + + suite.Ctx = suite.Ctx.WithBlockHeight(10) + suite.Ctx = suite.Ctx.WithBlockTime(time.Unix(333, 0)) + + suite.creator = suite.TestAccs[0].String() + suite.contract = "sei14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sh9m79m" + + suite.App.DexKeeper.AddRegisteredPair(suite.Ctx, suite.contract, keepertest.TestPair) + suite.App.DexKeeper.SetTickSizeForPair(suite.Ctx, suite.contract, keepertest.TestPair, *keepertest.TestPair.Ticksize) + + suite.msgPlaceOrders = &types.MsgPlaceOrders{ + Creator: suite.creator, + ContractAddr: suite.contract, + Orders: []*types.Order{ + { + Price: sdk.MustNewDecFromStr("10"), + Quantity: sdk.MustNewDecFromStr("10"), + Data: "", + PositionDirection: types.PositionDirection_LONG, + OrderType: types.OrderType_LIMIT, + PriceDenom: keepertest.TestPriceDenom, + AssetDenom: keepertest.TestAssetDenom, + }, + { + Price: sdk.MustNewDecFromStr("20"), + Quantity: sdk.MustNewDecFromStr("5"), + Data: "", + PositionDirection: types.PositionDirection_SHORT, + OrderType: types.OrderType_MARKET, + PriceDenom: keepertest.TestPriceDenom, + AssetDenom: keepertest.TestAssetDenom, + }, + }, + } + + suite.msgCancelOrders = &types.MsgCancelOrders{ + Creator: suite.creator, + ContractAddr: suite.contract, + Cancellations: []*types.Cancellation{ + { + Id: 1, + Price: sdk.MustNewDecFromStr("10"), + Creator: suite.creator, + PositionDirection: types.PositionDirection_LONG, + PriceDenom: keepertest.TestPriceDenom, + AssetDenom: keepertest.TestAssetDenom, + }, + { + Id: 2, + Creator: suite.creator, + Price: sdk.MustNewDecFromStr("20"), + PositionDirection: types.PositionDirection_SHORT, + PriceDenom: keepertest.TestPriceDenom, + AssetDenom: keepertest.TestAssetDenom, + }, + }, + } +} + + +func (suite *KeeperTestSuite) TestMsgPlaceOrder() { + suite.PrepareTest() + tests := []struct { + name string + expectedError error + msg *dextypes.MsgPlaceOrders + dynamicDep bool + }{ + { + name: "default place order", + msg: suite.msgPlaceOrders, + expectedError: nil, + dynamicDep: true, + }, + { + name: "dont check synchronous", + msg: suite.msgPlaceOrders, + expectedError: nil, + dynamicDep: false, + }, + } + for _, tc := range tests { + suite.Run(fmt.Sprintf("Test Case: %s", tc.name), func() { + goCtx := context.WithValue(suite.Ctx.Context(), dexutils.DexMemStateContextKey, dexcache.NewMemState()) + suite.Ctx = suite.Ctx.WithContext(goCtx) + + handlerCtx, cms := aclutils.CacheTxContext(suite.Ctx) + _, err := suite.msgServer.PlaceOrders( + sdk.WrapSDKContext(handlerCtx), + tc.msg, + ) + + depdenencies , _ := dexacl.DexPlaceOrdersDependencyGenerator( + suite.App.AccessControlKeeper, + handlerCtx, + tc.msg, + ) + + if !tc.dynamicDep { + depdenencies = sdkacltypes.SynchronousAccessOps() + } + + if tc.expectedError != nil { + suite.Require().EqualError(err, tc.expectedError.Error()) + } else { + suite.Require().NoError(err) + } + + missing := handlerCtx.MsgValidator().ValidateAccessOperations(depdenencies, cms.GetEvents()) + pp.Default.SetColoringEnabled(false) + + suite.Require().Empty(missing) + }) + } +} + +func (suite *KeeperTestSuite) TestMsgCancelOrder() { + suite.PrepareTest() + tests := []struct { + name string + expectedError error + msg *dextypes.MsgCancelOrders + dynamicDep bool + }{ + { + name: "default cancel order", + msg: suite.msgCancelOrders, + expectedError: nil, + dynamicDep: true, + }, + { + name: "dont check synchronous", + msg: suite.msgCancelOrders, + expectedError: nil, + dynamicDep: false, + }, + } + for _, tc := range tests { + suite.Run(fmt.Sprintf("Test Case: %s", tc.name), func() { + goCtx := context.WithValue(suite.Ctx.Context(), dexutils.DexMemStateContextKey, dexcache.NewMemState()) + suite.Ctx = suite.Ctx.WithContext(goCtx) + + _, err := suite.msgServer.PlaceOrders( + sdk.WrapSDKContext(suite.Ctx), + suite.msgPlaceOrders, + ) + + handlerCtx, cms := aclutils.CacheTxContext(suite.Ctx) + _, err = suite.msgServer.CancelOrders( + sdk.WrapSDKContext(handlerCtx), + tc.msg, + ) + + depdenencies , _ := dexacl.DexCancelOrdersDependencyGenerator( + suite.App.AccessControlKeeper, + handlerCtx, + tc.msg, + ) + + if !tc.dynamicDep { + depdenencies = sdkacltypes.SynchronousAccessOps() + } + + if tc.expectedError != nil { + suite.Require().EqualError(err, tc.expectedError.Error()) + } else { + suite.Require().NoError(err) + } + + missing := handlerCtx.MsgValidator().ValidateAccessOperations(depdenencies, cms.GetEvents()) + suite.Require().Empty(missing) + }) + } +} + + + +func TestGeneratorInvalidMessageTypes(t *testing.T) { + tm := time.Now().UTC() + valPub := secp256k1.GenPrivKey().PubKey() + testWrapper := app.NewTestWrapper(t, tm, valPub) + + oracleVote := oracletypes.MsgAggregateExchangeRateVote{ + ExchangeRates: "1usei", + Feeder: "test", + Validator: "validator", + } + + _, err := dexacl.DexPlaceOrdersDependencyGenerator( + testWrapper.App.AccessControlKeeper, + testWrapper.Ctx, + &oracleVote, + ) + require.Error(t, err) + + _, err = dexacl.DexCancelOrdersDependencyGenerator( + testWrapper.App.AccessControlKeeper, + testWrapper.Ctx, + &oracleVote, + ) + require.Error(t, err) +} + +func (suite *KeeperTestSuite) TestMsgPlaceOrderGenerator() { + suite.PrepareTest() + + accessOps, err := dexacl.DexPlaceOrdersDependencyGenerator( + suite.App.AccessControlKeeper, + suite.Ctx, + suite.msgPlaceOrders, + ) + require.NoError(suite.T(), err) + err = acltypes.ValidateAccessOps(accessOps) + require.NoError(suite.T(), err) +} + +func (suite *KeeperTestSuite) TestMsgCancelOrderGenerator() { + suite.PrepareTest() + accessOps, err := dexacl.DexCancelOrdersDependencyGenerator( + suite.App.AccessControlKeeper, + suite.Ctx, + suite.msgCancelOrders, + ) + require.NoError(suite.T(), err) + err = acltypes.ValidateAccessOps(accessOps) + require.NoError(suite.T(), err) +} + diff --git a/aclmapping/utils/identifier_templates.go b/aclmapping/utils/identifier_templates.go index ba79cecc2a..effbc00445 100644 --- a/aclmapping/utils/identifier_templates.go +++ b/aclmapping/utils/identifier_templates.go @@ -13,6 +13,7 @@ const ( AUTH = "auth" STAKING = "staking" TOKENFACTORY = "tokenfactory" + DEX = "dex" DefaultIDTemplate = "*" ) diff --git a/aclmapping/utils/resource_type.go b/aclmapping/utils/resource_type.go index cb0fe3f2cd..7e90975e59 100644 --- a/aclmapping/utils/resource_type.go +++ b/aclmapping/utils/resource_type.go @@ -14,13 +14,35 @@ import ( var StoreKeyToResourceTypePrefixMap = aclsdktypes.StoreKeyToResourceTypePrefixMap{ aclsdktypes.ParentNodeKey: { - aclsdktypes.ResourceType_ANY: aclsdktypes.EmptyPrefix, - aclsdktypes.ResourceType_KV: aclsdktypes.EmptyPrefix, - aclsdktypes.ResourceType_Mem: aclsdktypes.EmptyPrefix, + aclsdktypes.ResourceType_ANY: aclsdktypes.EmptyPrefix, + aclsdktypes.ResourceType_KV: aclsdktypes.EmptyPrefix, + aclsdktypes.ResourceType_Mem: aclsdktypes.EmptyPrefix, + aclsdktypes.ResourceType_KV_WASM: aclsdktypes.EmptyPrefix, }, dextypes.StoreKey: { - aclsdktypes.ResourceType_KV_DEX: aclsdktypes.EmptyPrefix, - aclsdktypes.ResourceType_DexMem: aclsdktypes.EmptyPrefix, + aclsdktypes.ResourceType_KV_DEX: aclsdktypes.EmptyPrefix, + aclsdktypes.ResourceType_DexMem: aclsdktypes.EmptyPrefix, + aclsdktypes.ResourceType_KV_DEX_CONTRACT_LONGBOOK: dextypes.KeyPrefix(dextypes.LongBookKey), + aclsdktypes.ResourceType_KV_DEX_CONTRACT_SHORTBOOK: dextypes.KeyPrefix(dextypes.ShortBookKey), + // pricedenom and assetdenoms are the prefixes + aclsdktypes.ResourceType_KV_DEX_PAIR_PREFIX: aclsdktypes.EmptyPrefix, + aclsdktypes.ResourceType_KV_DEX_TWAP: dextypes.KeyPrefix(dextypes.TwapKey), + aclsdktypes.ResourceType_KV_DEX_PRICE: dextypes.KeyPrefix(dextypes.PriceKey), + aclsdktypes.ResourceType_KV_DEX_SETTLEMENT_ENTRY: dextypes.KeyPrefix(dextypes.SettlementEntryKey), + aclsdktypes.ResourceType_KV_DEX_REGISTERED_PAIR: dextypes.KeyPrefix(dextypes.RegisteredPairKey), + aclsdktypes.ResourceType_KV_DEX_TICK_SIZE: dextypes.KeyPrefix(dextypes.TickSizeKey), + aclsdktypes.ResourceType_KV_DEX_ORDER: dextypes.KeyPrefix(dextypes.OrderKey), + aclsdktypes.ResourceType_KV_DEX_CANCEL: dextypes.KeyPrefix(dextypes.CancelKey), + aclsdktypes.ResourceType_KV_DEX_ACCOUNT_ACTIVE_ORDERS: dextypes.KeyPrefix(dextypes.AccountActiveOrdersKey), + aclsdktypes.ResourceType_KV_DEX_REGISTERED_PAIR_COUNT: dextypes.KeyPrefix(dextypes.RegisteredPairCount), + aclsdktypes.ResourceType_KV_DEX_ASSET_LIST: dextypes.KeyPrefix(dextypes.AssetListKey), + aclsdktypes.ResourceType_KV_DEX_NEXT_ORDER_ID: dextypes.KeyPrefix(dextypes.NextOrderIDKey), + aclsdktypes.ResourceType_KV_DEX_NEXT_SETTLEMENT_ID: dextypes.KeyPrefix(dextypes.NextSettlementIDKey), + aclsdktypes.ResourceType_KV_DEX_MATCH_RESULT: dextypes.KeyPrefix(dextypes.MatchResultKey), + aclsdktypes.ResourceType_KV_DEX_ORDER_BOOK: dextypes.KeyPrefix(dextypes.NextOrderIDKey), + // SETTLEMENT keys are prefixed with account and order id + aclsdktypes.ResourceType_KV_DEX_SETTLEMENT_ORDER_ID: aclsdktypes.EmptyPrefix, + aclsdktypes.ResourceType_KV_DEX_SETTLEMENT: aclsdktypes.EmptyPrefix, }, banktypes.StoreKey: { aclsdktypes.ResourceType_KV_BANK: aclsdktypes.EmptyPrefix, @@ -45,10 +67,13 @@ var StoreKeyToResourceTypePrefixMap = aclsdktypes.StoreKeyToResourceTypePrefixMa aclsdktypes.ResourceType_KV_DISTRIBUTION_SLASH_EVENT: distributiontypes.ValidatorSlashEventPrefix, }, oracletypes.StoreKey: { - aclsdktypes.ResourceType_KV_ORACLE: aclsdktypes.EmptyPrefix, - aclsdktypes.ResourceType_KV_ORACLE_VOTE_TARGETS: oracletypes.VoteTargetKey, - aclsdktypes.ResourceType_KV_ORACLE_AGGREGATE_VOTES: oracletypes.AggregateExchangeRateVoteKey, - aclsdktypes.ResourceType_KV_ORACLE_FEEDERS: oracletypes.FeederDelegationKey, + aclsdktypes.ResourceType_KV_ORACLE: aclsdktypes.EmptyPrefix, + aclsdktypes.ResourceType_KV_ORACLE_VOTE_TARGETS: oracletypes.VoteTargetKey, + aclsdktypes.ResourceType_KV_ORACLE_AGGREGATE_VOTES: oracletypes.AggregateExchangeRateVoteKey, + aclsdktypes.ResourceType_KV_ORACLE_FEEDERS: oracletypes.FeederDelegationKey, + aclsdktypes.ResourceType_KV_ORACLE_PRICE_SNAPSHOT: oracletypes.PriceSnapshotKey, + aclsdktypes.ResourceType_KV_ORACLE_EXCHANGE_RATE: oracletypes.ExchangeRateKey, + aclsdktypes.ResourceType_KV_ORACLE_VOTE_PENALTY_COUNTER: oracletypes.VotePenaltyCounterKey, }, stakingtypes.StoreKey: { aclsdktypes.ResourceType_KV_STAKING: aclsdktypes.EmptyPrefix, diff --git a/aclmapping/utils/resource_type_test.go b/aclmapping/utils/resource_type_test.go new file mode 100644 index 0000000000..fffe91a44e --- /dev/null +++ b/aclmapping/utils/resource_type_test.go @@ -0,0 +1,29 @@ +package utils_test + +import ( + "fmt" + "testing" + + sdkacltypes "github.com/cosmos/cosmos-sdk/types/accesscontrol" + aclutils "github.com/sei-protocol/sei-chain/aclmapping/utils" +) + +func TestAllResourcesInTree(t *testing.T) { + storeKeyToResourceMap := aclutils.StoreKeyToResourceTypePrefixMap + resourceTree := sdkacltypes.ResourceTree + + storeKeyAllResourceTypes := make(map[sdkacltypes.ResourceType]bool) + for _, resourceTypeToPrefix := range storeKeyToResourceMap { + for resourceType := range resourceTypeToPrefix { + storeKeyAllResourceTypes[resourceType] = true + } + } + + for resourceType := range resourceTree { + if _, ok := storeKeyAllResourceTypes[resourceType]; !ok { + panic(fmt.Sprintf("Missing resourceType=%s in the storekey to resource type prefix mapping", resourceType)) + } + } + + +} diff --git a/app/app.go b/app/app.go index 40162301a9..4e29bb4ec5 100644 --- a/app/app.go +++ b/app/app.go @@ -15,6 +15,7 @@ import ( storetypes "github.com/cosmos/cosmos-sdk/store/types" "github.com/sei-protocol/sei-chain/aclmapping" + aclutils "github.com/sei-protocol/sei-chain/aclmapping/utils" appparams "github.com/sei-protocol/sei-chain/app/params" "github.com/sei-protocol/sei-chain/utils" "github.com/sei-protocol/sei-chain/wasmbinding" @@ -1004,12 +1005,12 @@ func (app *App) ProcessBlockSynchronous(ctx sdk.Context, txs [][]byte) []*abci.E } // Returns a mapping of the accessOperation to the channels -func getChannelsFromSignalMapping(signalMapping acltypes.MessageCompletionSignalMapping) sdkacltypes.MessageAccessOpsChannelMapping { +func GetChannelsFromSignalMapping(signalMapping acltypes.MessageCompletionSignalMapping) sdkacltypes.MessageAccessOpsChannelMapping { channelsMapping := make(sdkacltypes.MessageAccessOpsChannelMapping) for messageIndex, accessOperationsToSignal := range signalMapping { + channelsMapping[messageIndex] = make(sdkacltypes.AccessOpsChannelMapping) for accessOperation, completionSignals := range accessOperationsToSignal { var channels []chan interface{} - channelsMapping[messageIndex] = make(sdkacltypes.AccessOpsChannelMapping) for _, completionSignal := range completionSignals { channels = append(channels, completionSignal.Channel) } @@ -1044,12 +1045,14 @@ func (app *App) ProcessTxConcurrent( ) { defer wg.Done() // Store the Channels in the Context Object for each transaction - ctx = ctx.WithTxBlockingChannels(getChannelsFromSignalMapping(txBlockingSignalsMap)) - ctx = ctx.WithTxCompletionChannels(getChannelsFromSignalMapping(txCompletionSignalingMap)) + ctx = ctx.WithTxCompletionChannels(GetChannelsFromSignalMapping(txCompletionSignalingMap)) + ctx = ctx.WithTxBlockingChannels(GetChannelsFromSignalMapping(txBlockingSignalsMap)) ctx = ctx.WithTxMsgAccessOps(txMsgAccessOpMapping) + ctx = ctx.WithMsgValidator( + sdkacltypes.NewMsgValidator(aclutils.StoreKeyToResourceTypePrefixMap), + ) // Deliver the transaction and store the result in the channel - resultChan <- ChannelResult{txIndex, app.DeliverTxWithResult(ctx, txBytes)} metrics.IncrTxProcessTypeCounter(metrics.CONCURRENT) } @@ -1179,7 +1182,6 @@ func (app *App) ProcessBlock(ctx sdk.Context, txs [][]byte, req BlockProcessRequ dependencyDag.TxMsgAccessOpMapping, ) if ok { - ctx.Logger().Info("Concurrent Execution succeeded, proceeding to commit block") txResults = concurrentResults // Write the results back to the concurrent contexts - if concurrent execution fails, // this should not be called and the state is rolled back and retried with synchronous execution diff --git a/app/app_test.go b/app/app_test.go index 5b8eb2c5d1..706ba6868a 100644 --- a/app/app_test.go +++ b/app/app_test.go @@ -6,6 +6,9 @@ import ( "time" "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + sdkacltypes "github.com/cosmos/cosmos-sdk/types/accesscontrol" + acltypes "github.com/cosmos/cosmos-sdk/x/accesscontrol/types" + "github.com/k0kubun/pp/v3" "github.com/sei-protocol/sei-chain/app" "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" @@ -29,3 +32,47 @@ func TestEmptyBlockIdempotency(t *testing.T) { require.Equal(t, len(referenceData), len(data)) } } + +func TestGetChannelsFromSignalMapping(t *testing.T) { + dag := acltypes.NewDag() + commit := *acltypes.CommitAccessOp() + writeA := sdkacltypes.AccessOperation{ + AccessType: sdkacltypes.AccessType_WRITE, + ResourceType: sdkacltypes.ResourceType_KV, + IdentifierTemplate: "ResourceA", + } + readA := sdkacltypes.AccessOperation{ + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_KV, + IdentifierTemplate: "ResourceA", + } + readAll := sdkacltypes.AccessOperation{ + AccessType: sdkacltypes.AccessType_READ, + ResourceType: sdkacltypes.ResourceType_ANY, + IdentifierTemplate: "*", + } + + dag.AddNodeBuildDependency(0, 0, writeA) + dag.AddNodeBuildDependency(0, 0, commit) + dag.AddNodeBuildDependency(1, 0, readAll) + dag.AddNodeBuildDependency(1, 0, commit) + dag.AddNodeBuildDependency(2, 0, writeA) + dag.AddNodeBuildDependency(2, 0, commit) + dag.AddNodeBuildDependency(3, 0, writeA) + dag.AddNodeBuildDependency(3, 0, commit) + + dag.AddNodeBuildDependency(0, 1, writeA) + dag.AddNodeBuildDependency(0, 1, commit) + dag.AddNodeBuildDependency(1, 1, readA) + dag.AddNodeBuildDependency(1, 1, commit) + + completionSignalsMap, blockingSignalsMap := dag.CompletionSignalingMap, dag.BlockingSignalsMap + + pp.Default.SetColoringEnabled(false) + + resultCompletionSignalsMap := app.GetChannelsFromSignalMapping(completionSignalsMap[0]) + resultBlockingSignalsMap := app.GetChannelsFromSignalMapping(blockingSignalsMap[1]) + + require.True(t, len(resultCompletionSignalsMap) > 1) + require.True(t, len(resultBlockingSignalsMap) > 1) +} diff --git a/go.mod b/go.mod index 60e28f34b7..6feef32797 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( github.com/golang/protobuf v1.5.2 github.com/gorilla/mux v1.8.0 github.com/grpc-ecosystem/grpc-gateway v1.16.0 + github.com/k0kubun/pp/v3 v3.2.0 github.com/pkg/errors v0.9.1 github.com/regen-network/cosmos-proto v0.3.1 github.com/spf13/cast v1.5.0 @@ -91,6 +92,7 @@ require ( github.com/lib/pq v1.10.6 // indirect github.com/libp2p/go-buffer-pool v0.0.2 // indirect github.com/magiconair/properties v1.8.6 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.16 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect @@ -135,7 +137,7 @@ require ( ) replace ( - github.com/cosmos/cosmos-sdk => github.com/sei-protocol/sei-cosmos v0.1.249 + github.com/cosmos/cosmos-sdk => github.com/sei-protocol/sei-cosmos v0.1.267 github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 github.com/keybase/go-keychain => github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 github.com/tendermint/tendermint => github.com/sei-protocol/sei-tendermint v0.1.59 diff --git a/go.sum b/go.sum index 05b2d736e9..4b7fd914b2 100644 --- a/go.sum +++ b/go.sum @@ -738,6 +738,8 @@ github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8 github.com/julz/importas v0.1.0/go.mod h1:oSFU2R4XK/P7kNBrnL/FEQlDGN1/6WoxXEjSSXO0DV0= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= +github.com/k0kubun/pp/v3 v3.2.0 h1:h33hNTZ9nVFNP3u2Fsgz8JXiF5JINoZfFq4SvKJwNcs= +github.com/k0kubun/pp/v3 v3.2.0/go.mod h1:ODtJQbQcIRfAD3N+theGCV1m/CBxweERz2dapdz1EwA= github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/errcheck v1.6.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= @@ -804,6 +806,8 @@ github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= @@ -1104,8 +1108,8 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= github.com/securego/gosec/v2 v2.11.0/go.mod h1:SX8bptShuG8reGC0XS09+a4H2BoWSJi+fscA+Pulbpo= github.com/segmentio/fasthash v1.0.3/go.mod h1:waKX8l2N8yckOgmSsXJi7x1ZfdKZ4x7KRMzBtS3oedY= -github.com/sei-protocol/sei-cosmos v0.1.249 h1:beHuyOOGxDewo1G7l067OPO56BKyfVi1boDiuS8jNfw= -github.com/sei-protocol/sei-cosmos v0.1.249/go.mod h1:KPV8lFdD2Ki/M2wZTpfX3LCcuMAZnmcUzYJycjbmOYM= +github.com/sei-protocol/sei-cosmos v0.1.267 h1:B+glrt6ydiKNvtuyUSVes9V7ReKVqx4vyqxTW4KzAaU= +github.com/sei-protocol/sei-cosmos v0.1.267/go.mod h1:o4+r4xXqBLvskNeg5eCmFH+K1Rqd08UPMvnzWI+OxRo= github.com/sei-protocol/sei-tendermint v0.1.59 h1:POGL60PumMQHF4EzAHzvkGfDnodQJLHpl65LuiwSO/Y= github.com/sei-protocol/sei-tendermint v0.1.59/go.mod h1:Olwbjyagrpoxj5DAUhHxMTWDVEfQ3FYdpypaJ3+6Hs8= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= From f9589fa4672e182da6e0620119d50ad3ecf05b0a Mon Sep 17 00:00:00 2001 From: Brandon Weng <18161326+BrandonWeng@users.noreply.github.com> Date: Wed, 2 Nov 2022 10:09:35 -0400 Subject: [PATCH 3/4] bump version --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 6feef32797..52cdae5065 100644 --- a/go.mod +++ b/go.mod @@ -137,7 +137,7 @@ require ( ) replace ( - github.com/cosmos/cosmos-sdk => github.com/sei-protocol/sei-cosmos v0.1.267 + github.com/cosmos/cosmos-sdk => github.com/sei-protocol/sei-cosmos v0.1.268 github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 github.com/keybase/go-keychain => github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 github.com/tendermint/tendermint => github.com/sei-protocol/sei-tendermint v0.1.59 diff --git a/go.sum b/go.sum index 4b7fd914b2..85d1c1d4b4 100644 --- a/go.sum +++ b/go.sum @@ -1108,8 +1108,8 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= github.com/securego/gosec/v2 v2.11.0/go.mod h1:SX8bptShuG8reGC0XS09+a4H2BoWSJi+fscA+Pulbpo= github.com/segmentio/fasthash v1.0.3/go.mod h1:waKX8l2N8yckOgmSsXJi7x1ZfdKZ4x7KRMzBtS3oedY= -github.com/sei-protocol/sei-cosmos v0.1.267 h1:B+glrt6ydiKNvtuyUSVes9V7ReKVqx4vyqxTW4KzAaU= -github.com/sei-protocol/sei-cosmos v0.1.267/go.mod h1:o4+r4xXqBLvskNeg5eCmFH+K1Rqd08UPMvnzWI+OxRo= +github.com/sei-protocol/sei-cosmos v0.1.268 h1:v1WlauXbjnILJf3y+N9YuEnM0xqN+LKnkQ97KGCkY7M= +github.com/sei-protocol/sei-cosmos v0.1.268/go.mod h1:o4+r4xXqBLvskNeg5eCmFH+K1Rqd08UPMvnzWI+OxRo= github.com/sei-protocol/sei-tendermint v0.1.59 h1:POGL60PumMQHF4EzAHzvkGfDnodQJLHpl65LuiwSO/Y= github.com/sei-protocol/sei-tendermint v0.1.59/go.mod h1:Olwbjyagrpoxj5DAUhHxMTWDVEfQ3FYdpypaJ3+6Hs8= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= From 0689a4542a3bdaf6dffe8b518bd60ee80c1fddfe Mon Sep 17 00:00:00 2001 From: Brandon Weng <18161326+BrandonWeng@users.noreply.github.com> Date: Wed, 2 Nov 2022 11:59:04 -0400 Subject: [PATCH 4/4] Small Refactor for loadtest script (#362) * sample * more money * lint --- loadtest/main.go | 20 ++++++++++++-------- x/oracle/simulation/operations.go | 4 ++-- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/loadtest/main.go b/loadtest/main.go index b453e43ba2..bf512a2f87 100644 --- a/loadtest/main.go +++ b/loadtest/main.go @@ -137,7 +137,7 @@ func (c *LoadTestClient) generateMessage(config Config, key cryptotypes.PrivKey, case randNum <= 0.66: msg = &tokenfactorytypes.MsgMint{ Sender: denomCreatorAddr, - Amount: sdk.Coin{Denom: denom, Amount: sdk.NewInt(1000000)}, + Amount: sdk.Coin{Denom: denom, Amount: sdk.NewInt(10000000000000)}, } default: msg = &tokenfactorytypes.MsgBurn{ @@ -218,22 +218,26 @@ func (c *LoadTestClient) generateMessage(config Config, key cryptotypes.PrivKey, return msg, false } -func generateDexOrderPlacements(config Config, key cryptotypes.PrivKey, batchSize uint64, price sdk.Dec, quantity sdk.Dec) []*dextypes.Order { - orderPlacements := []*dextypes.Order{} - var orderType dextypes.OrderType +func sampleDexOrderType(config Config) (orderType dextypes.OrderType) { if config.MessageType == "failure_bank_malformed" { orderType = -1 } else { - dexMsgType := config.MsgTypeDistr.SampleDexMsgs() - switch dexMsgType { + msgType := config.MsgTypeDistr.SampleDexMsgs() + switch msgType { case Limit: orderType = dextypes.OrderType_LIMIT case Market: orderType = dextypes.OrderType_MARKET default: - panic(fmt.Sprintf("Unknown message type %s\n", dexMsgType)) + panic(fmt.Sprintf("Unknown message type %s\n", msgType)) } } + return orderType +} + +func generateDexOrderPlacements(config Config, key cryptotypes.PrivKey, msgPerTx uint64, price sdk.Dec, quantity sdk.Dec) (orderPlacements []*dextypes.Order) { + orderType := sampleDexOrderType(config) + var direction dextypes.PositionDirection if rand.Float64() < 0.5 { direction = dextypes.PositionDirection_LONG @@ -242,7 +246,7 @@ func generateDexOrderPlacements(config Config, key cryptotypes.PrivKey, batchSiz } contract := config.ContractDistr.Sample() - for j := 0; j < int(batchSize); j++ { + for j := 0; j < int(msgPerTx); j++ { orderPlacements = append(orderPlacements, &dextypes.Order{ Account: sdk.AccAddress(key.PubKey().Address()).String(), ContractAddr: contract, diff --git a/x/oracle/simulation/operations.go b/x/oracle/simulation/operations.go index 2028e73625..3988c4b164 100644 --- a/x/oracle/simulation/operations.go +++ b/x/oracle/simulation/operations.go @@ -65,7 +65,7 @@ func WeightedOperations( } // SimulateMsgAggregateExchangeRateVote generates a MsgAggregateExchangeRateVote with random values. -// nolint: funlen +//nolint: funlen func SimulateMsgAggregateExchangeRateVote(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Keeper) simtypes.Operation { return func( r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string, @@ -122,7 +122,7 @@ func SimulateMsgAggregateExchangeRateVote(ak types.AccountKeeper, bk types.BankK } // SimulateMsgDelegateFeedConsent generates a MsgDelegateFeedConsent with random values. -// nolint: funlen +//nolint: funlen func SimulateMsgDelegateFeedConsent(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Keeper) simtypes.Operation { return func( r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string,