diff --git a/app/ante/cosmos/sigverify.go b/app/ante/cosmos/sigverify.go index c6984f308..3d91e1cdd 100644 --- a/app/ante/cosmos/sigverify.go +++ b/app/ante/cosmos/sigverify.go @@ -70,8 +70,7 @@ func (spkd SetPubKeyDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate b return ctx, err } for i, pk := range pubKeys { - // addrFromPubk, err := sdk.AccAddressFromBech32(sdk.AccAddress(pk).String()) - if !bytes.Equal(signers[i], pk.Address()) { + if !simulate && !bytes.Equal(signers[i], pk.Address()) { return ctx, sdkerrors.ErrInvalidPubKey.Wrapf("pubKey does not match signer address %s with signer index: %d", signers[i], i) } } diff --git a/app/app.go b/app/app.go index f84763c36..1bc932d13 100644 --- a/app/app.go +++ b/app/app.go @@ -588,6 +588,7 @@ func NewExocoreApp( appCodec, keys[oracleTypes.StoreKey], memKeys[oracleTypes.MemStoreKey], app.GetSubspace(oracleTypes.ModuleName), app.StakingKeeper, &app.DelegationKeeper, &app.AssetsKeeper, authAddrString, + &app.SlashingKeeper, ) // the SDK slashing module is used to slash validators in the case of downtime. it tracks diff --git a/crypto/algo_ed25519.go b/crypto/algo_ed25519.go new file mode 100644 index 000000000..0b0a8ba8d --- /dev/null +++ b/crypto/algo_ed25519.go @@ -0,0 +1,50 @@ +package crypto + +import ( + cryptoed25519 "crypto/ed25519" + + "github.com/cosmos/cosmos-sdk/crypto/hd" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" + + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + evmoshd "github.com/evmos/evmos/v16/crypto/hd" +) + +var ( + SupportedAlgorithms = keyring.SigningAlgoList{evmoshd.EthSecp256k1, hd.Secp256k1, Ed25519} + Ed25519 = ed25519Algo{} +) + +type ed25519Algo struct{} + +func (e ed25519Algo) Name() hd.PubKeyType { + return hd.Ed25519Type +} + +// Derive derives and returns the ed25519 private key +// for ed25519, this is mainly used for test, we don't actually generate ed25519 from the path defined from slip0010, we ignore the path, and retrieve seed from mnemonic directly, then use that seed as secret to generate keys through ed25519 +func (e ed25519Algo) Derive() hd.DeriveFn { + return func(mnemonic, _, _ string) ([]byte, error) { + // seed, err := bip39.NewSeedWithErrorChecking(mnemonic, bip39Passphrase) + // don't do transfer to be compatible with the way used in x/genutil/utils.go.InitializeNodeValidatorFilesFromMnemonic + seed := []byte(mnemonic) + return ed25519.GenPrivKeyFromSecret(seed).Bytes(), nil + } +} + +// Generate will be used to import privateKey from hex through keyring, so we just return the bz as privateKey instead of seed +func (e ed25519Algo) Generate() hd.GenerateFn { + return func(bz []byte) cryptotypes.PrivKey { + return &ed25519.PrivKey{ + Key: cryptoed25519.PrivateKey(bz), + } + } +} + +// Ed25519Option this option is mainly used for test +func Ed25519Option() keyring.Option { + return func(options *keyring.Options) { + options.SupportedAlgos = SupportedAlgorithms + } +} diff --git a/go.mod b/go.mod index 6c0fa830a..34be7cc96 100644 --- a/go.mod +++ b/go.mod @@ -263,6 +263,7 @@ replace ( github.com/99designs/keyring => github.com/cosmos/keyring v1.2.0 // use Cosmos-SDK fork to enable Ledger functionality github.com/cosmos/cosmos-sdk => github.com/evmos/cosmos-sdk v0.47.5-evmos.2 + //fix cosmos-sdk error github.com/cosmos/gogoproto => github.com/cosmos/gogoproto v1.4.10 // use Evmos geth fork diff --git a/precompiles/assets/assets_test.go b/precompiles/assets/assets_test.go index e42083492..29af74c51 100644 --- a/precompiles/assets/assets_test.go +++ b/precompiles/assets/assets_test.go @@ -1,7 +1,6 @@ package assets_test import ( - "fmt" "math/big" "strings" @@ -316,7 +315,6 @@ func (s *AssetsPrecompileSuite) TestRunWithdrawPrincipal() { OpAmount: depositAmount, } err := s.App.AssetsKeeper.PerformDepositOrWithdraw(s.Ctx, params) - fmt.Println("Debug---", assetAddress, len(assetAddress)) s.Require().NoError(err) } diff --git a/proto/exocore/oracle/v1/genesis.proto b/proto/exocore/oracle/v1/genesis.proto index 512db1f02..45b725f30 100644 --- a/proto/exocore/oracle/v1/genesis.proto +++ b/proto/exocore/oracle/v1/genesis.proto @@ -9,6 +9,7 @@ import "exocore/oracle/v1/params.proto"; import "exocore/oracle/v1/prices.proto"; import "exocore/oracle/v1/recent_msg.proto"; import "exocore/oracle/v1/recent_params.proto"; +import "exocore/oracle/v1/slashing.proto"; import "exocore/oracle/v1/validator_update_block.proto"; import "gogoproto/gogo.proto"; @@ -18,10 +19,12 @@ option go_package = "github.com/ExocoreNetwork/exocore/x/oracle/types"; message GenesisState { // module params Params params = 1 [(gogoproto.nullable) = false]; - // prices of all tokens + + // prices of all tokens including NST repeated Prices prices_list = 2 [(gogoproto.nullable) = false]; //TODO: userDefinedTokenFeeder + // information for memory-cache recovery // latest block on which the validator set be updated ValidatorUpdateBlock validator_update_block = 3; // index for the cached recent params @@ -32,10 +35,18 @@ message GenesisState { repeated RecentMsg recent_msg_list = 6[(gogoproto.nullable) = false]; // cached recent params repeated RecentParams recent_params_list = 7[(gogoproto.nullable) = false]; + + // information for NST related // stakerInfos for each nst token repeated StakerInfosAssets staker_infos_assets = 8[(gogoproto.nullable) = false]; // stakerList for each nst token repeated StakerListAssets staker_list_assets = 9[(gogoproto.nullable) = false]; + + // information for slashing history + // ValidatorReportInfo records all the validatorReportInfos + repeated ValidatorReportInfo validator_report_infos = 10[(gogoproto.nullable)=false]; + // ValidatorMissedRounds records missedRounds for all validators seen + repeated ValidatorMissedRounds validator_missed_rounds = 11[(gogoproto.nullable)=false]; } // stakerInfosAssets bond stakerinfos to their related assets id @@ -53,3 +64,19 @@ message StakerListAssets { // stakerList StakerList staker_list = 2; } + +// ValidatorMissedRounds record missed rounds indexes for a validator which consAddr corresponding to the address +message ValidatorMissedRounds { + // address of validator + string address = 1; + // missed_rounds tells how many rounds this validtor had missed for current windo + repeated MissedRound missed_rounds = 2; +} + +// MissedRound records if round with index is missed +message MissedRound { + // index of the round in current window + int64 index = 1; + // if this round is missed + bool missed = 2; +} diff --git a/proto/exocore/oracle/v1/params.proto b/proto/exocore/oracle/v1/params.proto index 1407a062d..b3041689e 100644 --- a/proto/exocore/oracle/v1/params.proto +++ b/proto/exocore/oracle/v1/params.proto @@ -1,10 +1,13 @@ syntax = "proto3"; package exocore.oracle.v1; +import "amino/amino.proto"; +import "cosmos_proto/cosmos.proto"; import "exocore/oracle/v1/info.proto"; import "exocore/oracle/v1/token_feeder.proto"; import "gogoproto/gogo.proto"; - +import "google/protobuf/duration.proto"; +import "google/protobuf/timestamp.proto"; option go_package = "github.com/ExocoreNetwork/exocore/x/oracle/types"; // Params defines the parameters for the module. @@ -32,6 +35,9 @@ message Params { int32 max_det_id = 10; // for each token, only keep max_size_prices round of prices int32 max_size_prices = 11; + + // slashing defines the slashing related params + SlashingParams slashing = 12; } // ConsensusMode defines the consensus mode for the prices. @@ -42,4 +48,35 @@ enum ConsensusMode { // CONSENSUS_MODE_ASAP defines the mode to get final price immediately when the voting power // exceeds the threshold. CONSENSUS_MODE_ASAP = 1 [(gogoproto.enumvalue_customname) = "ConsensusModeASAP"]; -} \ No newline at end of file +} + +// slashing related params +message SlashingParams { + // reported_rounds_window defines how many rounds included in one window for performance review of missing report + int64 reported_rounds_window = 1; + // min_reported_perwindow defines at least how many rounds should be reported, this is a percentage of window + bytes min_reported_per_window = 2 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.nullable) = false, + (amino.dont_omitempty) = true + ]; + // oracle_miss_jail_duraion defines the duration one validator should be jailed for missing reporting price + google.protobuf.Duration oracle_miss_jail_duration = 3 + [(gogoproto.nullable) = false, (amino.dont_omitempty) = true, (gogoproto.stdduration) = true]; + // oracle_malicious_jail_duration defines the duratin one validator should be jailed for malicious behavior + google.protobuf.Duration oracle_malicious_jail_duration =4 + [(gogoproto.nullable) = false, (amino.dont_omitempty) = true, (gogoproto.stdduration) = true]; + // slash_fraction_miss defines the fraction one validator should be punished for msissing reporting price + bytes slash_fraction_miss = 5 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.nullable) = false, + (amino.dont_omitempty) = true + ]; + // slash_fraction_miss defines the fractino one validator should be punished for maliciours behavior + bytes slash_fraction_malicious = 6 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.nullable) = false, + (amino.dont_omitempty) = true + ]; + +} diff --git a/proto/exocore/oracle/v1/slashing.proto b/proto/exocore/oracle/v1/slashing.proto new file mode 100644 index 000000000..a74d076a9 --- /dev/null +++ b/proto/exocore/oracle/v1/slashing.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; + +package exocore.oracle.v1; + +option go_package = "github.com/ExocoreNetwork/exocore/x/oracle/types"; + +// ValidatorReportInfo represents the information to describe the miss status of a validator reporting prices +message ValidatorReportInfo { + // address of the validtor + string address = 1; + // start_height for the performance round of the configured window of rounds + int64 start_height = 2; + // index_offset track the offset of current window + int64 index_offset = 3; + // missed_rounds_counter counts the number of missed rounds for this window + int64 missed_rounds_counter = 4; +} diff --git a/tests/e2e/bank/bank_test.go b/tests/e2e/bank/bank_test.go new file mode 100644 index 000000000..773cf5681 --- /dev/null +++ b/tests/e2e/bank/bank_test.go @@ -0,0 +1,16 @@ +package bank + +import ( + "testing" + + "github.com/ExocoreNetwork/exocore/testutil/network" + "github.com/stretchr/testify/suite" +) + +func TestE2ETestSuite(t *testing.T) { + cfg := network.DefaultConfig() + cfg.NumValidators = 1 + cfg.CleanupDir = true + cfg.EnableTMLogging = false + suite.Run(t, NewE2ETestSuite(cfg)) +} diff --git a/tests/e2e/bank/query.go b/tests/e2e/bank/query.go new file mode 100644 index 000000000..78c8abd65 --- /dev/null +++ b/tests/e2e/bank/query.go @@ -0,0 +1,13 @@ +package bank + +import ( + "github.com/ExocoreNetwork/exocore/tests/e2e" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func (s *E2ETestSuite) TestQeuryBalance() { + res, err := e2e.QueryNativeCoinBalance(s.network.Validators[0].Address, s.network) + s.Require().NoError(err) + s.Require().Equal(sdk.NewCoin(s.network.Config.NativeDenom, s.network.Config.AccountTokens), *res.Balance) + s.Require().Equal(e2e.NewNativeCoin(s.network.Config.AccountTokens, s.network), *res.Balance) +} diff --git a/tests/e2e/bank/suite.go b/tests/e2e/bank/suite.go new file mode 100644 index 000000000..1566f26fd --- /dev/null +++ b/tests/e2e/bank/suite.go @@ -0,0 +1,26 @@ +package bank + +import ( + "github.com/ExocoreNetwork/exocore/testutil/network" + "github.com/stretchr/testify/suite" +) + +type E2ETestSuite struct { + suite.Suite + + cfg network.Config + network *network.Network +} + +func NewE2ETestSuite(cfg network.Config) *E2ETestSuite { + return &E2ETestSuite{cfg: cfg} +} + +func (s *E2ETestSuite) SetupSuite() { + s.T().Log("setting up e2e test suite") + var err error + s.network, err = network.New(s.T(), s.T().TempDir(), s.cfg) + s.Require().NoError(err) + _, err = s.network.WaitForHeight(2) + s.Require().NoError(err) +} diff --git a/tests/e2e/bank/test.result b/tests/e2e/bank/test.result new file mode 100644 index 000000000..e69de29bb diff --git a/tests/e2e/bank/tx.go b/tests/e2e/bank/tx.go new file mode 100644 index 000000000..801301a29 --- /dev/null +++ b/tests/e2e/bank/tx.go @@ -0,0 +1,47 @@ +package bank + +import ( + sdkmath "cosmossdk.io/math" + "github.com/ExocoreNetwork/exocore/tests/e2e" + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" +) + +func (s *E2ETestSuite) TestSendCoin() { + kr := s.network.Validators[0].ClientCtx.Keyring + // generate a new account with ethsecp256k1 to recieve/send native coins (hua) + toAddr, err := e2e.GenerateAccAddress(kr, "user1") + s.Require().NoError(err) + // generate sendCoin msg + fromAddr := s.network.Validators[0].Address + msg := banktypes.NewMsgSend(fromAddr, toAddr, sdk.NewCoins(sdk.NewCoin(s.network.Config.NativeDenom, sdkmath.NewInt(2000000)))) + + // send sendCoinMsg + err = s.network.SendTx([]sdk.Msg{msg}, s.network.Validators[0].ClientCtx.FromName, kr) + s.Require().NoError(err) + + // wait to next block for tx to be included + err = s.network.WaitForNextBlock() + s.Require().NoError(err) + + // check user1's balance + res, err := e2e.QueryNativeCoinBalance(toAddr, s.network) + s.Require().NoError(err) + s.Require().Equal(e2e.NewNativeCoin(sdkmath.NewInt(2000000), s.network), *res.Balance) + + toAddr2, _ := e2e.GenerateAccAddress(kr, "user2") + + msg = banktypes.NewMsgSend(toAddr, toAddr2, sdk.NewCoins(sdk.NewCoin(s.network.Config.NativeDenom, sdkmath.NewInt(100)))) + // send sendCoinMsg + err = s.network.SendTx([]sdk.Msg{msg}, "user1", kr) + s.Require().NoError(err) + + // wait to next block for tx to be included + err = s.network.WaitForNextBlock() + s.Require().NoError(err) + + // check user2's balance + res, err = e2e.QueryNativeCoinBalance(toAddr2, s.network) + s.Require().NoError(err) + s.Require().Equal(e2e.NewNativeCoin(sdkmath.NewInt(100), s.network), *res.Balance) +} diff --git a/tests/e2e/basesuite.go b/tests/e2e/basesuite.go new file mode 100644 index 000000000..aa19de429 --- /dev/null +++ b/tests/e2e/basesuite.go @@ -0,0 +1,26 @@ +package e2e + +import ( + "github.com/ExocoreNetwork/exocore/testutil/network" + "github.com/stretchr/testify/suite" +) + +type BaseSuite struct { + suite.Suite + + Cfg network.Config + Network *network.Network +} + +func NewBaseSuite(cfg network.Config) *BaseSuite { + return &BaseSuite{Cfg: cfg} +} + +func (s *BaseSuite) SetupSuite() { + s.T().Log("setting up e2e test suite") + var err error + s.Network, err = network.New(s.T(), s.T().TempDir(), s.Cfg) + s.Require().NoError(err) + _, err = s.Network.WaitForHeight(2) + s.Require().NoError(err) +} diff --git a/tests/e2e/oracle/create_price.go b/tests/e2e/oracle/create_price.go new file mode 100644 index 000000000..075d26252 --- /dev/null +++ b/tests/e2e/oracle/create_price.go @@ -0,0 +1,175 @@ +package oracle + +import ( + "context" + "time" + + oracletypes "github.com/ExocoreNetwork/exocore/x/oracle/types" + sdk "github.com/cosmos/cosmos-sdk/types" + slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +const layout = "2006-01-02 15:04:05" + +/* + cases: + we need more than 2/3 power, so that at least 3 out of 4 validators power should be enough + 1. block_1_1: v1 sendPrice{p1}, [no round_1 price after block_1_1 committed], block_1_2:v2&v3 sendPrice{p1}, [got round_1 price{p1} after block_1_2 committed] + 2. block_2_1: v3 sendPrice{p2}, block_2_2: v1 sendPrice{p2}, [no round_2 price after block_2_2 committed], block_2_3:nothing, [got round_2 price{p1} equals to round_1 after block_2_3 committed] + 3. block_3_1: v1 sendPrice{p1}, block_3_2: v2&v3 sendPrice{p2}, block_3_3: v3 sendPrice{p2}, [got final price{p2} after block_3_3 committed] + 4. block_4_1: v1&v2&v3 sendPrice{p1}, [got round_4 price{p1} after block_4_1 committed]] + + --- nonce: +*/ + +func (s *E2ETestSuite) TestCreatePriceLST() { + kr0 := s.network.Validators[0].ClientCtx.Keyring + creator0 := sdk.AccAddress(s.network.Validators[0].PubKey.Address()) + + kr1 := s.network.Validators[1].ClientCtx.Keyring + creator1 := sdk.AccAddress(s.network.Validators[1].PubKey.Address()) + + kr2 := s.network.Validators[2].ClientCtx.Keyring + creator2 := sdk.AccAddress(s.network.Validators[2].PubKey.Address()) + + // kr3 := s.network.Validators[2].ClientCtx.Keyring + // creator3 := sdk.AccAddress(s.network.Validators[2].PubKey.Address()) + + priceTest1R1 := price1.updateTimestamp() + priceTimeDetID1R1 := priceTest1R1.getPriceTimeDetID("9") + priceSource1R1 := oracletypes.PriceSource{ + SourceID: 1, + Prices: []*oracletypes.PriceTimeDetID{ + &priceTimeDetID1R1, + }, + } + + // case_1. update price to p1 {reporter: v0, v1, v2. miss:v3} + s.moveToAndCheck(10) + // send create-price from validator-0 + msg0 := oracletypes.NewMsgCreatePrice(creator0.String(), 1, []*oracletypes.PriceSource{&priceSource1R1}, 10, 1) + err := s.network.SendTxOracleCreateprice([]sdk.Msg{msg0}, "valconskey0", kr0) + s.Require().NoError(err) + + // query final price + _, err = s.network.QueryOracle().LatestPrice(context.Background(), &oracletypes.QueryGetLatestPriceRequest{TokenId: 1}) + errStatus, _ := status.FromError(err) + s.Require().Equal(codes.NotFound, errStatus.Code()) + + s.moveToAndCheck(11) + // send create-price from validator-1 + msg1 := oracletypes.NewMsgCreatePrice(creator1.String(), 1, []*oracletypes.PriceSource{&priceSource1R1}, 10, 1) + err = s.network.SendTxOracleCreateprice([]sdk.Msg{msg1}, "valconskey1", kr1) + s.Require().NoError(err) + + // send create-price from validator-2 + msg2 := oracletypes.NewMsgCreatePrice(creator2.String(), 1, []*oracletypes.PriceSource{&priceSource1R1}, 10, 1) + err = s.network.SendTxOracleCreateprice([]sdk.Msg{msg2}, "valconskey2", kr2) + s.Require().NoError(err) + + s.moveToAndCheck(13) + // query final price + res, err := s.network.QueryOracle().LatestPrice(context.Background(), &oracletypes.QueryGetLatestPriceRequest{TokenId: 1}) + s.Require().NoError(err) + s.Require().Equal(priceTest1R1.getPriceTimeRound(1), res.Price) + + // case_2. failed to update price to p2, keep p1 + // timestamp need to be updated + priceTest2R2 := price2.updateTimestamp() + priceTimeDetID2R2 := priceTest2R2.getPriceTimeDetID("10") + priceSource2R2 := oracletypes.PriceSource{ + SourceID: 1, + Prices: []*oracletypes.PriceTimeDetID{ + &priceTimeDetID2R2, + }, + } + msg0 = oracletypes.NewMsgCreatePrice(creator0.String(), 1, []*oracletypes.PriceSource{&priceSource2R2}, 20, 1) + msg2 = oracletypes.NewMsgCreatePrice(creator2.String(), 1, []*oracletypes.PriceSource{&priceSource2R2}, 20, 1) + + s.moveToAndCheck(20) + // send price{p2} from validator-2 + err = s.network.SendTxOracleCreateprice([]sdk.Msg{msg2}, "valconskey2", kr2) + s.Require().NoError(err) + s.moveToAndCheck(21) + // send price{p2} from validator-0 + err = s.network.SendTxOracleCreateprice([]sdk.Msg{msg0}, "valconskey0", kr0) + s.Require().NoError(err) + s.moveToAndCheck(24) + res, err = s.network.QueryOracle().LatestPrice(context.Background(), &oracletypes.QueryGetLatestPriceRequest{TokenId: 1}) + s.Require().NoError(err) + // price update fail, round 2 still have price{p1} + s.Require().Equal(priceTest1R1.getPriceTimeRound(2), res.Price) + + // case_3. update price to p2{reporter:v0,v1,v2, miss:v3}, v3 should be slash for now(require 1 report at least, but got 0) + // update timestamp + priceTest2R3 := price2.updateTimestamp() + priceTimeDetID2R3 := priceTest2R3.getPriceTimeDetID("11") + priceSource2R3 := oracletypes.PriceSource{ + SourceID: 1, + Prices: []*oracletypes.PriceTimeDetID{ + &priceTimeDetID2R3, + }, + } + + msg0 = oracletypes.NewMsgCreatePrice(creator0.String(), 1, []*oracletypes.PriceSource{&priceSource2R3}, 30, 1) + msg1 = oracletypes.NewMsgCreatePrice(creator1.String(), 1, []*oracletypes.PriceSource{&priceSource2R3}, 30, 1) + msg2 = oracletypes.NewMsgCreatePrice(creator2.String(), 1, []*oracletypes.PriceSource{&priceSource2R3}, 30, 1) + s.moveToAndCheck(30) + // send price{p2} from validator-0 + err = s.network.SendTxOracleCreateprice([]sdk.Msg{msg0}, "valconskey0", kr0) + s.Require().NoError(err) + s.moveToAndCheck(31) + // send price{p2} from validator-1 + err = s.network.SendTxOracleCreateprice([]sdk.Msg{msg1}, "valconskey1", kr1) + s.Require().NoError(err) + + // send price{p2} from validator-2 + err = s.network.SendTxOracleCreateprice([]sdk.Msg{msg2}, "valconskey2", kr2) + s.Require().NoError(err) + + s.moveToAndCheck(33) + res, err = s.network.QueryOracle().LatestPrice(context.Background(), &oracletypes.QueryGetLatestPriceRequest{TokenId: 1}) + s.Require().NoError(err) + // price updated, round 3 has price{p2} + s.Require().Equal(priceTest2R3.getPriceTimeRound(3), res.Price) + // case_slahsing: validator 3 is jailed + resSigningInfo, err := s.network.QuerySlashing().SigningInfo(context.Background(), &slashingtypes.QuerySigningInfoRequest{ConsAddress: sdk.ConsAddress(s.network.Validators[3].PubKey.Address()).String()}) + s.Require().NoError(err) + s.Require().True(true, resSigningInfo.ValSigningInfo.JailedUntil.After(time.Now())) + + // case_4. update price to p1{reporter:v0,v1,v2, miss:v3} + s.moveToAndCheck(40) + priceTest1R4, priceSource1R4 := price1.generateRealTimeStructs("12", 1) + msg0 = oracletypes.NewMsgCreatePrice(creator0.String(), 1, []*oracletypes.PriceSource{&priceSource1R4}, 40, 1) + msg1 = oracletypes.NewMsgCreatePrice(creator1.String(), 1, []*oracletypes.PriceSource{&priceSource1R4}, 40, 1) + msg2 = oracletypes.NewMsgCreatePrice(creator2.String(), 1, []*oracletypes.PriceSource{&priceSource1R4}, 40, 1) + err = s.network.SendTxOracleCreateprice([]sdk.Msg{msg0}, "valconskey0", kr0) + s.Require().NoError(err) + err = s.network.SendTxOracleCreateprice([]sdk.Msg{msg1}, "valconskey1", kr1) + s.Require().NoError(err) + err = s.network.SendTxOracleCreateprice([]sdk.Msg{msg2}, "valconskey2", kr2) + s.Require().NoError(err) + s.moveToAndCheck(42) + res, err = s.network.QueryOracle().LatestPrice(context.Background(), &oracletypes.QueryGetLatestPriceRequest{TokenId: 1}) + s.Require().NoError(err) + // price updated, round 4 has price{p1} + s.Require().Equal(priceTest1R4.getPriceTimeRound(4), res.Price) +} + +func (s *E2ETestSuite) TestCreatePriceNST() { + +} + +func (s *E2ETestSuite) moveToAndCheck(height int64) { + _, err := s.network.WaitForHeightWithTimeout(height, 30*time.Second) + s.Require().NoError(err) +} + +func (s *E2ETestSuite) moveNAndCheck(n int64) { + for i := int64(0); i < n; i++ { + err := s.network.WaitForNextBlock() + s.Require().NoError(err) + } +} diff --git a/tests/e2e/oracle/data.go b/tests/e2e/oracle/data.go new file mode 100644 index 000000000..f3fbb5aeb --- /dev/null +++ b/tests/e2e/oracle/data.go @@ -0,0 +1,60 @@ +package oracle + +import ( + "time" + + oracletypes "github.com/ExocoreNetwork/exocore/x/oracle/types" +) + +type priceTime struct { + Price string + Decimal int32 + Timestamp string +} + +func (p priceTime) getPriceTimeDetID(detID string) oracletypes.PriceTimeDetID { + return oracletypes.PriceTimeDetID{ + Price: p.Price, + Decimal: p.Decimal, + Timestamp: p.Timestamp, + DetID: detID, + } +} +func (p priceTime) getPriceTimeRound(roundID uint64) oracletypes.PriceTimeRound { + return oracletypes.PriceTimeRound{ + Price: p.Price, + Decimal: p.Decimal, + Timestamp: p.Timestamp, + RoundID: roundID, + } +} + +func (p priceTime) updateTimestamp() priceTime { + t := time.Now().UTC().Format(layout) + p.Timestamp = t + return p +} + +func (p priceTime) generateRealTimeStructs(detID string, sourceID uint64) (priceTime, oracletypes.PriceSource) { + retP := p.updateTimestamp() + pTimeDetID := retP.getPriceTimeDetID(detID) + return retP, oracletypes.PriceSource{ + SourceID: 1, + Prices: []*oracletypes.PriceTimeDetID{ + &pTimeDetID, + }, + } +} + +var ( + price1 = priceTime{ + Price: "199999", + Decimal: 18, + Timestamp: time.Now().UTC().Format(layout), + } + price2 = priceTime{ + Price: "299999", + Decimal: 18, + Timestamp: time.Now().UTC().Format(layout), + } +) diff --git a/tests/e2e/oracle/oracle_test.go b/tests/e2e/oracle/oracle_test.go new file mode 100644 index 000000000..cbe84803f --- /dev/null +++ b/tests/e2e/oracle/oracle_test.go @@ -0,0 +1,16 @@ +package oracle + +import ( + "testing" + + "github.com/ExocoreNetwork/exocore/testutil/network" + "github.com/stretchr/testify/suite" +) + +func TestE2ESuite(t *testing.T) { + cfg := network.DefaultConfig() + cfg.NumValidators = 4 + cfg.CleanupDir = true + cfg.EnableTMLogging = true + suite.Run(t, NewE2ETestSuite(cfg)) +} diff --git a/tests/e2e/oracle/suite.go b/tests/e2e/oracle/suite.go new file mode 100644 index 000000000..238b55121 --- /dev/null +++ b/tests/e2e/oracle/suite.go @@ -0,0 +1,26 @@ +package oracle + +import ( + "github.com/ExocoreNetwork/exocore/testutil/network" + "github.com/stretchr/testify/suite" +) + +type E2ETestSuite struct { + suite.Suite + + cfg network.Config + network *network.Network +} + +func NewE2ETestSuite(cfg network.Config) *E2ETestSuite { + return &E2ETestSuite{cfg: cfg} +} + +func (s *E2ETestSuite) SetupSuite() { + s.T().Log("setting up e2e test suite") + var err error + s.network, err = network.New(s.T(), s.T().TempDir(), s.cfg) + s.Require().NoError(err) + _, err = s.network.WaitForHeight(2) + s.Require().NoError(err) +} diff --git a/tests/e2e/util.go b/tests/e2e/util.go new file mode 100644 index 000000000..777e584ad --- /dev/null +++ b/tests/e2e/util.go @@ -0,0 +1,37 @@ +package e2e + +import ( + "context" + + sdkmath "cosmossdk.io/math" + "github.com/ExocoreNetwork/exocore/testutil/network" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/evmos/evmos/v16/crypto/hd" +) + +// func (s *E2ETestSuite) queryNativeCoinBalance(address sdk.AccAddress, n *network.Network) (*banktypes.QueryBalanceResponse, error) { +func QueryNativeCoinBalance(address sdk.AccAddress, n *network.Network) (*banktypes.QueryBalanceResponse, error) { + return n.QueryBank().Balance(context.Background(), &banktypes.QueryBalanceRequest{ + Address: address.String(), + // Denom: s.network.Config.NativeDenom, + Denom: n.Config.NativeDenom, + }) +} + +// func (s *E2ETestSuite) newNativeCoin(amount sdkmath.Int, n *network.Network) sdk.Coin { +func NewNativeCoin(amount sdkmath.Int, n *network.Network) sdk.Coin { + // return sdk.NewCoin(s.network.Config.NativeDenom, amount) + return sdk.NewCoin(n.Config.NativeDenom, amount) +} + +func GenerateAccAddress(kr keyring.Keyring, name string) (sdk.AccAddress, error) { + // generate a new account with ethsecp256k1 + r, _, err := kr.NewMnemonic(name, keyring.English, sdk.GetConfig().GetFullBIP44Path(), "", hd.EthSecp256k1) + if err != nil { + return nil, err + } + addr, _ := r.GetAddress() + return addr, nil +} diff --git a/testutil/keeper/oracle.go b/testutil/keeper/oracle.go index 4308d54df..646eb86f9 100644 --- a/testutil/keeper/oracle.go +++ b/testutil/keeper/oracle.go @@ -21,6 +21,7 @@ import ( assetskeeper "github.com/ExocoreNetwork/exocore/x/assets/keeper" delegationkeeper "github.com/ExocoreNetwork/exocore/x/delegation/keeper" dogfoodkeeper "github.com/ExocoreNetwork/exocore/x/dogfood/keeper" + slashingkeeper "github.com/cosmos/cosmos-sdk/x/slashing/keeper" "github.com/stretchr/testify/require" ) @@ -52,6 +53,7 @@ func OracleKeeper(t testing.TB) (*keeper.Keeper, sdk.Context) { delegationkeeper.Keeper{}, assetskeeper.Keeper{}, authtypes.NewModuleAddress(govtypes.ModuleName).String(), + slashingkeeper.Keeper{}, ) ctx := sdk.NewContext(stateStore, tmproto.Header{ diff --git a/testutil/network/genesis_data.go b/testutil/network/genesis_data.go new file mode 100644 index 000000000..108d7d2aa --- /dev/null +++ b/testutil/network/genesis_data.go @@ -0,0 +1,69 @@ +package network + +import ( + sdkmath "cosmossdk.io/math" + assetstypes "github.com/ExocoreNetwork/exocore/x/assets/types" + delegationtypes "github.com/ExocoreNetwork/exocore/x/delegation/types" + dogfoodtypes "github.com/ExocoreNetwork/exocore/x/dogfood/types" + operatortypes "github.com/ExocoreNetwork/exocore/x/operator/types" + oracletypes "github.com/ExocoreNetwork/exocore/x/oracle/types" +) + +var ( + // DefaultGenStateAssets only includes two assets, one for ETH and the other for NST ETH + // For the contract address of asset-ETH we filled with the address of USDT, that's ok for test + // we bond both tokens to the price of ETH in oracle module + DefaultGenStateAssets = assetstypes.GenesisState{ + Params: assetstypes.Params{ + ExocoreLzAppAddress: "0x3e108c058e8066da635321dc3018294ca82ddedf", + ExocoreLzAppEventTopic: assetstypes.DefaultParams().ExocoreLzAppEventTopic, + }, + ClientChains: []assetstypes.ClientChainInfo{ + { + Name: "Example EVM chain", + MetaInfo: "Example EVM chain metaInfo", + LayerZeroChainID: 101, + AddressLength: 20, + }, + }, + Tokens: []assetstypes.StakingAssetInfo{ + { + // for test this token will be registered on ETH in oracle module + AssetBasicInfo: assetstypes.AssetInfo{ + Name: "ETH", + MetaInfo: "Ethereum native token", + // the address is of USDT_Etheruem, but that's ok + Address: "0xdac17f958d2ee523a2206206994597c13d831ec7", + LayerZeroChainID: 101, + }, + StakingTotalAmount: sdkmath.NewInt(5000), + }, + { + AssetBasicInfo: assetstypes.AssetInfo{ + Name: "NST ETH", + MetaInfo: "native restaking ETH", + Address: "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", + LayerZeroChainID: 101, + }, + StakingTotalAmount: sdkmath.NewInt(5000), + }, + }, + } + + DefaultGenStateOperator = operatortypes.GenesisState{} + // DefaultGenStateOperator = *operatortypes.DefaultGenesis() + + DefaultGenStateDelegation = delegationtypes.GenesisState{} + // DefaultGenStateDelegation = *delegationtypes.DefaultGenesis() + + DefaultGenStateDogfood = *dogfoodtypes.DefaultGenesis() + + DefaultGenStateOracle = *oracletypes.DefaultGenesis() +) + +func init() { + // bond assetsIDs of ETH, NSTETH to ETH price + DefaultGenStateOracle.Params.Tokens[1].AssetID = "0xdac17f958d2ee523a2206206994597c13d831ec7_0x65,0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee_0x65" + // set ETH tokenfeeder's 'StartBaseBlock' to 10 + DefaultGenStateOracle.Params.TokenFeeders[1].StartBaseBlock = 10 +} diff --git a/testutil/network/network.go b/testutil/network/network.go index 327e90c55..6610b2098 100644 --- a/testutil/network/network.go +++ b/testutil/network/network.go @@ -10,12 +10,14 @@ import ( "net/url" "os" "path/filepath" + "sort" "strings" "sync" "testing" "time" "cosmossdk.io/math" + sdkmath "cosmossdk.io/math" dbm "github.com/cometbft/cometbft-db" tmcfg "github.com/cometbft/cometbft/config" tmflags "github.com/cometbft/cometbft/libs/cli/flags" @@ -33,7 +35,6 @@ import ( "github.com/ExocoreNetwork/exocore/app" "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/client/tx" "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" "github.com/cosmos/cosmos-sdk/crypto/keyring" @@ -49,9 +50,13 @@ import ( authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" "github.com/cosmos/cosmos-sdk/x/genutil" - stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + + exocorecrypto "github.com/ExocoreNetwork/exocore/crypto" + cosmoshd "github.com/cosmos/cosmos-sdk/crypto/hd" "github.com/evmos/evmos/v16/crypto/hd" + // ekeyring "github.com/evmos/evmos/v16/crypto/keyring" + "github.com/evmos/evmos/v16/encoding" "github.com/evmos/evmos/v16/server/config" evmostypes "github.com/evmos/evmos/v16/types" @@ -77,22 +82,23 @@ type Config struct { AppConstructor AppConstructor // the ABCI application constructor GenesisState simapp.GenesisState // custom gensis state to provide TimeoutCommit time.Duration // the consensus commitment timeout - AccountTokens math.Int // the amount of unique validator tokens (e.g. 1000node0) - StakingTokens math.Int // the amount of tokens each validator has available to stake - BondedTokens math.Int // the amount of tokens each validator stakes - NumValidators int // the total number of validators to create and bond - ChainID string // the network chain-id - BondDenom string // the staking bond denomination - MinGasPrices string // the minimum gas prices each validator will accept - PruningStrategy string // the pruning strategy each validator will have - SigningAlgo string // signing algorithm for keys - RPCAddress string // RPC listen address (including port) - JSONRPCAddress string // JSON-RPC listen address (including port) - APIAddress string // REST API listen address (including port) - GRPCAddress string // GRPC server listen address (including port) - EnableTMLogging bool // enable Tendermint logging to STDOUT - CleanupDir bool // remove base temporary directory during cleanup - PrintMnemonic bool // print the mnemonic of first validator as log output for testing + AccountTokens math.Int // the amount of system tokens(e.g. 1000hua) + DepositedTokens math.Int + StakingTokens math.Int // the amount of tokens each validator stakes for every supporte asset + NumValidators int // the total number of validators to create and bond + ChainID string // the network chain-id + // BondDenom string // the staking bond denomination + NativeDenom string // denomination for native token + MinGasPrices string // the minimum gas prices each validator will accept + PruningStrategy string // the pruning strategy each validator will have + SigningAlgo string // signing algorithm for keys + RPCAddress string // RPC listen address (including port) + JSONRPCAddress string // JSON-RPC listen address (including port) + APIAddress string // REST API listen address (including port) + GRPCAddress string // GRPC server listen address (including port) + EnableTMLogging bool // enable Tendermint logging to STDOUT + CleanupDir bool // remove base temporary directory during cleanup + PrintMnemonic bool // print the mnemonic of first validator as log output for testing } // DefaultConfig returns a sane default configuration suitable for nearly all @@ -108,19 +114,22 @@ func DefaultConfig() Config { AccountRetriever: authtypes.AccountRetriever{}, AppConstructor: NewAppConstructor(encCfg, chainID), GenesisState: app.ModuleBasics.DefaultGenesis(encCfg.Codec), - TimeoutCommit: 3 * time.Second, + TimeoutCommit: 2 * time.Second, ChainID: chainID, NumValidators: 4, - BondDenom: "hua", - MinGasPrices: fmt.Sprintf("0.000006%s", evmostypes.AttoEvmos), - AccountTokens: sdk.TokensFromConsensusPower(1000000000000000000, evmostypes.PowerReduction), - StakingTokens: sdk.TokensFromConsensusPower(500000000000000000, evmostypes.PowerReduction), - BondedTokens: sdk.TokensFromConsensusPower(100000000000000000, evmostypes.PowerReduction), - PruningStrategy: pruningtypes.PruningOptionNothing, - CleanupDir: true, - SigningAlgo: string(hd.EthSecp256k1Type), - KeyringOptions: []keyring.Option{hd.EthSecp256k1Option()}, - PrintMnemonic: false, + // BondDenom: "hua", + NativeDenom: "hua", + // MinGasPrices: fmt.Sprintf("0.000006%s", "exo"), + MinGasPrices: "10hua", + AccountTokens: sdk.TokensFromConsensusPower(1000, evmostypes.PowerReduction), + DepositedTokens: sdk.TokensFromConsensusPower(500, evmostypes.PowerReduction), + StakingTokens: sdk.TokensFromConsensusPower(200, evmostypes.PowerReduction), + PruningStrategy: pruningtypes.PruningOptionNothing, + CleanupDir: true, + SigningAlgo: string(hd.EthSecp256k1Type), + // KeyringOptions: []keyring.Option{hd.EthSecp256k1Option()}, + KeyringOptions: []keyring.Option{exocorecrypto.Ed25519Option()}, + PrintMnemonic: false, } } @@ -235,6 +244,7 @@ func New(l Logger, baseDir string, cfg Config) (*Network, error) { monikers := make([]string, cfg.NumValidators) nodeIDs := make([]string, cfg.NumValidators) valPubKeys := make([]cryptotypes.PubKey, cfg.NumValidators) + addressesIPs := make([]string, cfg.NumValidators) var ( genAccounts []authtypes.GenesisAccount @@ -247,6 +257,7 @@ func New(l Logger, baseDir string, cfg Config) (*Network, error) { // generate private keys, node IDs, and initial transactions for i := 0; i < cfg.NumValidators; i++ { appCfg := config.DefaultConfig() + appCfg.MinGasPrices = cfg.MinGasPrices appCfg.Pruning = cfg.PruningStrategy appCfg.MinGasPrices = cfg.MinGasPrices appCfg.API.Enable = true @@ -334,9 +345,8 @@ func New(l Logger, baseDir string, cfg Config) (*Network, error) { ctx.Logger = logger nodeDirName := fmt.Sprintf("node%d", i) - nodeDir := filepath.Join(network.BaseDir, nodeDirName, "evmosd") - clientDir := filepath.Join(network.BaseDir, nodeDirName, "evmoscli") - gentxsDir := filepath.Join(network.BaseDir, "gentxs") + nodeDir := filepath.Join(network.BaseDir, nodeDirName, "exocored") + clientDir := filepath.Join(network.BaseDir, nodeDirName, "exocorecli") err := os.MkdirAll(filepath.Join(nodeDir, "config"), 0o750) if err != nil { @@ -366,20 +376,35 @@ func New(l Logger, baseDir string, cfg Config) (*Network, error) { tmCfg.P2P.AddrBookStrict = false tmCfg.P2P.AllowDuplicateIP = true - nodeID, pubKey, err := genutil.InitializeNodeValidatorFiles(tmCfg) + kb, err := keyring.New(sdk.KeyringServiceName(), keyring.BackendTest, clientDir, buf, cfg.Codec, cfg.KeyringOptions...) if err != nil { return nil, err } - nodeIDs[i] = nodeID - valPubKeys[i] = pubKey - kb, err := keyring.New(sdk.KeyringServiceName(), keyring.BackendTest, clientDir, buf, cfg.Codec, cfg.KeyringOptions...) + keyringAlgos, _ := kb.SupportedAlgorithms() + algo, err := keyring.NewSigningAlgoFromString(string(cosmoshd.Ed25519Type), keyringAlgos) + if err != nil { + return nil, err + } + _, mnemonic, err := kb.NewMnemonic(fmt.Sprintf("valconskey%d", i), keyring.English, sdk.GetConfig().GetBech32AccountPubPrefix(), keyring.DefaultBIP39Passphrase, algo) if err != nil { return nil, err } - keyringAlgos, _ := kb.SupportedAlgorithms() - algo, err := keyring.NewSigningAlgoFromString(cfg.SigningAlgo, keyringAlgos) + // initialize nodekey, consensus_priv_key under nodeDir + nodeID, pubKey, err := genutil.InitializeNodeValidatorFilesFromMnemonic(tmCfg, mnemonic) + if err != nil { + return nil, err + } + p2pURL, err := url.Parse(p2pAddr) + if err != nil { + return nil, err + } + nodeIDs[i] = nodeID + addressesIPs[i] = fmt.Sprintf("%s@%s:%s", nodeIDs[i], p2pURL.Hostname(), p2pURL.Port()) + valPubKeys[i] = pubKey + + algo, err = keyring.NewSigningAlgoFromString(cfg.SigningAlgo, keyringAlgos) if err != nil { return nil, err } @@ -401,15 +426,16 @@ func New(l Logger, baseDir string, cfg Config) (*Network, error) { return nil, err } - // save private key seed words + // save private key seed words of account used as validator err = WriteFile(fmt.Sprintf("%v.json", "key_seed"), clientDir, infoBz) if err != nil { return nil, err } balances := sdk.NewCoins( - sdk.NewCoin(fmt.Sprintf("%stoken", nodeDirName), cfg.AccountTokens), - sdk.NewCoin(cfg.BondDenom, cfg.StakingTokens), + // sdk.NewCoin(fmt.Sprintf("%stoken", nodeDirName), cfg.AccountTokens), + // sdk.NewCoin(cfg.BondDenom, cfg.DepositedTokens), + sdk.NewCoin(cfg.NativeDenom, cfg.AccountTokens), ) genFiles = append(genFiles, tmCfg.GenesisFile()) @@ -419,60 +445,7 @@ func New(l Logger, baseDir string, cfg Config) (*Network, error) { CodeHash: common.BytesToHash(evmtypes.EmptyCodeHash).Hex(), }) - commission, err := sdk.NewDecFromStr("0.5") - if err != nil { - return nil, err - } - - createValMsg, err := stakingtypes.NewMsgCreateValidator( - sdk.ValAddress(addr), - valPubKeys[i], - sdk.NewCoin(cfg.BondDenom, cfg.BondedTokens), - stakingtypes.NewDescription(nodeDirName, "", "", "", ""), - stakingtypes.NewCommissionRates(commission, sdk.OneDec(), sdk.OneDec()), - sdk.OneInt(), - ) - if err != nil { - return nil, err - } - - p2pURL, err := url.Parse(p2pAddr) - if err != nil { - return nil, err - } - - memo := fmt.Sprintf("%s@%s:%s", nodeIDs[i], p2pURL.Hostname(), p2pURL.Port()) - fee := sdk.NewCoins(sdk.NewCoin(cfg.BondDenom, sdk.NewInt(0))) - txBuilder := cfg.TxConfig.NewTxBuilder() - err = txBuilder.SetMsgs(createValMsg) - if err != nil { - return nil, err - } - txBuilder.SetFeeAmount(fee) // Arbitrary fee - txBuilder.SetGasLimit(1000000) // Need at least 100386 - txBuilder.SetMemo(memo) - - txFactory := tx.Factory{} - txFactory = txFactory. - WithChainID(cfg.ChainID). - WithMemo(memo). - WithKeybase(kb). - WithTxConfig(cfg.TxConfig) - - if err := tx.Sign(txFactory, nodeDirName, txBuilder, true); err != nil { - return nil, err - } - - txBz, err := cfg.TxConfig.TxJSONEncoder()(txBuilder.GetTx()) - if err != nil { - return nil, err - } - - if err := WriteFile(fmt.Sprintf("%v.json", nodeDirName), gentxsDir, txBz); err != nil { - return nil, err - } - - customAppTemplate, _ := config.AppConfig(evmostypes.AttoEvmos) + customAppTemplate, _ := config.AppConfig("hua") srvconfig.SetConfigTemplate(customAppTemplate) srvconfig.WriteConfigFile(filepath.Join(nodeDir, "config/app.toml"), appCfg) @@ -492,7 +465,9 @@ func New(l Logger, baseDir string, cfg Config) (*Network, error) { WithCodec(cfg.Codec). WithLegacyAmino(cfg.LegacyAmino). WithTxConfig(cfg.TxConfig). - WithAccountRetriever(cfg.AccountRetriever) + WithAccountRetriever(cfg.AccountRetriever). + WithFromName(nodeDirName). + WithFrom(addr.String()) network.Validators[i] = &Validator{ AppConfig: appCfg, @@ -510,11 +485,19 @@ func New(l Logger, baseDir string, cfg Config) (*Network, error) { } } - err := initGenFiles(cfg, genAccounts, genBalances, genFiles) - if err != nil { - return nil, err + sort.Strings(addressesIPs) + // set persistentPeers for tendermintConfig and write into configFile for each valdiator + for i := 0; i < cfg.NumValidators; i++ { + cfgTmp := network.Validators[i].Ctx.Config + IPs := make([]string, 0, cfg.NumValidators-1) + IPs = append(IPs, addressesIPs[0:i]...) + IPs = append(IPs, addressesIPs[i+1:]...) + cfgTmp.P2P.PersistentPeers = strings.Join(IPs, ",") + tmcfg.WriteConfigFile(filepath.Join(cfgTmp.RootDir, "config", "config.toml"), cfgTmp) } - err = collectGenFiles(cfg, network.Validators, network.BaseDir) + + commissionRate, _ := sdkmath.LegacyNewDecFromStr("0.5") + err := initGenFiles(cfg, genAccounts, genBalances, genFiles, network.Validators, commissionRate) if err != nil { return nil, err } diff --git a/testutil/network/network_test.go b/testutil/network/network_test.go index 00cb4d89a..687e83608 100644 --- a/testutil/network/network_test.go +++ b/testutil/network/network_test.go @@ -1,9 +1,7 @@ -//go:build norace -// +build norace - package network_test import ( + "context" "fmt" "testing" "time" @@ -15,6 +13,9 @@ import ( "github.com/evmos/evmos/v16/server/config" exocorenetwork "github.com/ExocoreNetwork/exocore/testutil/network" + + sdk "github.com/cosmos/cosmos-sdk/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" ) type IntegrationTestSuite struct { @@ -29,13 +30,15 @@ func (s *IntegrationTestSuite) SetupSuite() { var err error cfg := exocorenetwork.DefaultConfig() cfg.JSONRPCAddress = config.DefaultJSONRPCAddress - cfg.NumValidators = 1 + cfg.NumValidators = 3 + cfg.CleanupDir = true + cfg.EnableTMLogging = false s.network, err = network.New(s.T(), s.T().TempDir(), cfg) s.Require().NoError(err) s.Require().NotNil(s.network) - _, err = s.network.WaitForHeight(2) + _, err = s.network.WaitForHeightWithTimeout(2, 300*time.Second) s.Require().NoError(err) if s.network.Validators[0].JSONRPCClient == nil { @@ -57,6 +60,14 @@ func (s *IntegrationTestSuite) TestNetwork_Liveness() { latestHeight, err := s.network.LatestHeight() s.Require().NoError(err, "latest height failed") s.Require().GreaterOrEqual(latestHeight, h) + + res, err := s.network.QueryBank().Balance(context.Background(), &banktypes.QueryBalanceRequest{ + Address: s.network.Validators[0].Address.String(), + Denom: s.network.Config.NativeDenom, + }) + s.Require().NoError(err, "query for validator's balance fail") + + s.Require().Equal(sdk.NewCoin(s.network.Config.NativeDenom, s.network.Config.AccountTokens), *res.Balance) } func TestIntegrationTestSuite(t *testing.T) { diff --git a/testutil/network/query.go b/testutil/network/query.go new file mode 100644 index 000000000..2c8566cac --- /dev/null +++ b/testutil/network/query.go @@ -0,0 +1,40 @@ +package network + +import ( + assetstypes "github.com/ExocoreNetwork/exocore/x/assets/types" + avstypes "github.com/ExocoreNetwork/exocore/x/avs/types" + dogfoodtypes "github.com/ExocoreNetwork/exocore/x/dogfood/types" + operatortypes "github.com/ExocoreNetwork/exocore/x/operator/types" + oracletypes "github.com/ExocoreNetwork/exocore/x/oracle/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" +) + +func (n *Network) QueryOracle() oracletypes.QueryClient { + return oracletypes.NewQueryClient(n.Validators[0].ClientCtx) +} + +func (n *Network) QueryBank() banktypes.QueryClient { + return banktypes.NewQueryClient(n.Validators[0].ClientCtx) + +} + +func (n *Network) QueryAssets() assetstypes.QueryClient { + return assetstypes.NewQueryClient(n.Validators[0].ClientCtx) +} + +func (n *Network) QueryOperator() operatortypes.QueryClient { + return operatortypes.NewQueryClient(n.Validators[0].ClientCtx) +} + +func (n *Network) QueryAVS() avstypes.QueryClient { + return avstypes.NewQueryClient(n.Validators[0].ClientCtx) +} + +func (n *Network) QueryDogfood() dogfoodtypes.QueryClient { + return dogfoodtypes.NewQueryClient(n.Validators[0].ClientCtx) +} + +func (n *Network) QuerySlashing() slashingtypes.QueryClient { + return slashingtypes.NewQueryClient(n.Validators[0].ClientCtx) +} diff --git a/testutil/network/tx.go b/testutil/network/tx.go new file mode 100644 index 000000000..372823653 --- /dev/null +++ b/testutil/network/tx.go @@ -0,0 +1,89 @@ +package network + +import ( + "context" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + + "github.com/cosmos/cosmos-sdk/client/tx" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + sdk "github.com/cosmos/cosmos-sdk/types" + txtypes "github.com/cosmos/cosmos-sdk/types/tx" + "github.com/cosmos/cosmos-sdk/types/tx/signing" +) + +// SendTx construct and sign that tx with input msgs +func (n *Network) SendTx(msgs []sdk.Msg, keyName string, keyring keyring.Keyring) error { + txf, ctx, err := generateTxf(n.Validators[0].ClientCtx, keyName, keyring, 1.5, n.Config.MinGasPrices) + if err != nil { + return err + } + return tx.BroadcastTx(ctx, txf, msgs...) +} + +// SendTxOracleCreatePrice consturct and sign that tx with input msgs, it's different from SendTx, since when we use ed25519 for oracle senario, we allowed that signer is an unexists account, this implementation skip the 'accoutn exists' related checks +// Also, if you want to sign some normal message (not oracle-create-price) with ed25519, just use SendTx is fine, we support ed25519 signing in keyring +func (n *Network) SendTxOracleCreateprice(msgs []sdk.Msg, keyName string, keyring keyring.Keyring) error { + txf, ctx, err := generateTxf(n.Validators[0].ClientCtx, keyName, keyring, 1.5, n.Config.MinGasPrices) + if err != nil { + return err + } + // calculate gas + txBytes, err := txf.BuildSimTx(msgs...) + if err != nil { + return err + } + txClient := txtypes.NewServiceClient(ctx) + simRes, err := txClient.Simulate(context.Background(), &txtypes.SimulateRequest{ + TxBytes: txBytes, + }) + if err != nil { + return err + } + gasAdjusted := uint64(txf.GasAdjustment() * float64(simRes.GasInfo.GasUsed)) + txf.WithGas(gasAdjusted) + transaction, err := txf.BuildUnsignedTx(msgs...) + if err != nil { + return err + } + if err := tx.Sign(txf, ctx.GetFromName(), transaction, true); err != nil { + return err + } + txBytes, err = ctx.TxConfig.TxEncoder()(transaction.GetTx()) + if err != nil { + return err + } + res, err := ctx.BroadcastTx(txBytes) + if err != nil { + return err + } + return ctx.PrintProto(res) +} + +func generateTxf(ctx client.Context, keyName string, kr keyring.Keyring, adjustment float64, minGasPrice string) (tx.Factory, client.Context, error) { + var txf tx.Factory + record, err := kr.Key(keyName) + if err != nil { + return txf, ctx, err + } + acc, err := record.GetAddress() + if err != nil { + return txf, ctx, err + } + ctx.FromAddress = acc + ctx.FromName = keyName + ctx.SkipConfirm = true + txf = tx.Factory{}. + WithChainID(ctx.ChainID). + WithKeybase(kr). + WithTxConfig(ctx.TxConfig). + WithSignMode(signing.SignMode_SIGN_MODE_DIRECT). + WithGasAdjustment(adjustment). + WithAccountRetriever(ctx.AccountRetriever). + WithGasPrices(minGasPrice). + WithSimulateAndExecute(true) + + ctx.BroadcastMode = flags.BroadcastSync + return txf, ctx, nil +} diff --git a/testutil/network/tx_internal.go b/testutil/network/tx_internal.go new file mode 100644 index 000000000..14eb876dc --- /dev/null +++ b/testutil/network/tx_internal.go @@ -0,0 +1,89 @@ +package network + +import ( + "context" + "fmt" + + sdkmath "cosmossdk.io/math" + ctypes "github.com/cometbft/cometbft/rpc/core/types" + "github.com/cosmos/cosmos-sdk/client/tx" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + sdk "github.com/cosmos/cosmos-sdk/types" + sdktx "github.com/cosmos/cosmos-sdk/types/tx" +) + +func (n *Network) SendTxInternal(msgs []sdk.Msg, keyName string, kr keyring.Keyring) (*ctypes.ResultBroadcastTx, error) { + signedTxBytes, err := n.genSignedTxInternal(msgs, keyName, kr) + if err != nil { + return nil, err + } + // txClient.BroadcastTx() + return n.Validators[0].RPCClient.BroadcastTxSync(context.Background(), signedTxBytes) +} + +// GenSignedTx construct a tx with input messages and sign it privatekey stored in keyring +func (n *Network) genSignedTxInternal(msgs []sdk.Msg, keyName string, kr keyring.Keyring) ([]byte, error) { + val := n.Validators[0] + ctx := val.ClientCtx + + // record, err := ctx.Keyring.Key(keyName) + record, err := kr.Key(keyName) + if err != nil { + return nil, err + } + acc, err := record.GetAddress() + if err != nil { + return nil, err + } + txConfig := ctx.TxConfig + txBuilder := txConfig.NewTxBuilder() + txClient := sdktx.NewServiceClient(ctx) + if err = txBuilder.SetMsgs(msgs...); err != nil { + return nil, err + } + txBytes, _ := txConfig.TxEncoder()(txBuilder.GetTx()) + // simulate tx to get estimate gasLimit + gasInfo, err := txClient.Simulate(context.Background(), &sdktx.SimulateRequest{ + TxBytes: txBytes, + }) + fmt.Println("debug--err", err) + + // adjustment + gasLimit := uint64(float64(gasInfo.GasInfo.GasUsed) * 1.5) + gasLimitInt := sdkmath.NewIntFromUint64(gasLimit) + l := len(n.Config.MinGasPrices) - len(n.Config.NativeDenom) + fee := sdkmath.ZeroInt() + if l > 0 { + minGasPriceStr := n.Config.MinGasPrices[:l] + minGasPrice, _ := sdkmath.NewIntFromString(minGasPriceStr) + if err != nil { + return nil, err + } + fee = gasLimitInt.Mul(minGasPrice) + } + + txBuilder.SetGasLimit(gasLimit) + txBuilder.SetFeeAmount(sdk.Coins{sdk.NewCoin(n.Config.NativeDenom, fee)}) + num, seq, err := ctx.AccountRetriever.GetAccountNumberSequence(ctx, acc) + if err != nil { + return nil, err + } + txf := tx.Factory{}. + WithChainID(ctx.ChainID). + // WithKeybase(ctx.Keyring). + WithKeybase(kr). + WithTxConfig(txConfig). + WithAccountNumber(num). + WithSequence(seq) + + if err := tx.Sign(txf, keyName, txBuilder, true); err != nil { + return nil, err + } + + signedTx := txBuilder.GetTx() + if txBytes, err = txConfig.TxEncoder()(signedTx); err != nil { + return nil, err + } + + return txBytes, nil +} diff --git a/testutil/network/util.go b/testutil/network/util.go index d9f868d2e..1645f9cd7 100644 --- a/testutil/network/util.go +++ b/testutil/network/util.go @@ -6,6 +6,7 @@ import ( "path/filepath" "time" + sdkmath "cosmossdk.io/math" tmos "github.com/cometbft/cometbft/libs/os" "github.com/cometbft/cometbft/node" "github.com/cometbft/cometbft/p2p" @@ -13,22 +14,32 @@ import ( "github.com/cometbft/cometbft/proxy" "github.com/cometbft/cometbft/rpc/client/local" "github.com/cometbft/cometbft/types" - tmtime "github.com/cometbft/cometbft/types/time" + sdk "github.com/cosmos/cosmos-sdk/types" + sdktypes "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/ethclient" + assetstypes "github.com/ExocoreNetwork/exocore/x/assets/types" + avstypes "github.com/ExocoreNetwork/exocore/x/avs/types" + delegationtypes "github.com/ExocoreNetwork/exocore/x/delegation/types" + dogfoodtypes "github.com/ExocoreNetwork/exocore/x/dogfood/types" + operatortypes "github.com/ExocoreNetwork/exocore/x/operator/types" + oracletypes "github.com/ExocoreNetwork/exocore/x/oracle/types" "github.com/cosmos/cosmos-sdk/server/api" servergrpc "github.com/cosmos/cosmos-sdk/server/grpc" srvtypes "github.com/cosmos/cosmos-sdk/server/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" crisistypes "github.com/cosmos/cosmos-sdk/x/crisis/types" - "github.com/cosmos/cosmos-sdk/x/genutil" - genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" govv1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + cmttime "github.com/cometbft/cometbft/types/time" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/evmos/evmos/v16/server" + evmostypes "github.com/evmos/evmos/v16/types" evmtypes "github.com/evmos/evmos/v16/x/evm/types" + feemarkettypes "github.com/evmos/evmos/v16/x/feemarket/types" ) func startInProcess(cfg Config, val *Validator) error { @@ -145,42 +156,7 @@ func startInProcess(cfg Config, val *Validator) error { return nil } -func collectGenFiles(cfg Config, vals []*Validator, outputDir string) error { - genTime := tmtime.Now() - - for i := 0; i < cfg.NumValidators; i++ { - tmCfg := vals[i].Ctx.Config - - nodeDir := filepath.Join(outputDir, vals[i].Moniker, "evmosd") - gentxsDir := filepath.Join(outputDir, "gentxs") - - tmCfg.Moniker = vals[i].Moniker - tmCfg.SetRoot(nodeDir) - - initCfg := genutiltypes.NewInitConfig(cfg.ChainID, gentxsDir, vals[i].NodeID, vals[i].PubKey) - - genFile := tmCfg.GenesisFile() - genDoc, err := types.GenesisDocFromFile(genFile) - if err != nil { - return err - } - - appState, err := genutil.GenAppStateFromConfig(cfg.Codec, cfg.TxConfig, - tmCfg, initCfg, *genDoc, banktypes.GenesisBalancesIterator{}, genutiltypes.DefaultMessageValidator) - if err != nil { - return err - } - - // overwrite each validator's genesis file to have a canonical genesis time - if err := genutil.ExportGenesisFileWithTime(genFile, cfg.ChainID, nil, appState, genTime); err != nil { - return err - } - } - - return nil -} - -func initGenFiles(cfg Config, genAccounts []authtypes.GenesisAccount, genBalances []banktypes.Balance, genFiles []string) error { +func initGenFiles(cfg Config, genAccounts []authtypes.GenesisAccount, genBalances []banktypes.Balance, genFiles []string, validators []*Validator, commissionRate sdkmath.LegacyDec) error { // set the accounts in the genesis state var authGenState authtypes.GenesisState cfg.Codec.MustUnmarshalJSON(cfg.GenesisState[authtypes.ModuleName], &authGenState) @@ -193,29 +169,85 @@ func initGenFiles(cfg Config, genAccounts []authtypes.GenesisAccount, genBalance authGenState.Accounts = append(authGenState.Accounts, accounts...) cfg.GenesisState[authtypes.ModuleName] = cfg.Codec.MustMarshalJSON(&authGenState) + var feemarketGenState feemarkettypes.GenesisState + cfg.Codec.MustUnmarshalJSON(cfg.GenesisState[feemarkettypes.ModuleName], &feemarketGenState) + l := len(cfg.MinGasPrices) - len(cfg.NativeDenom) + minGasPrice := sdkmath.ZeroInt() + if l > 0 { + minGasPriceStr := cfg.MinGasPrices[:l] + minGasPrice, _ = sdkmath.NewIntFromString(minGasPriceStr) + if err != nil { + return err + } + } + feemarketGenState.Params.BaseFee = minGasPrice + cfg.GenesisState[feemarkettypes.ModuleName] = cfg.Codec.MustMarshalJSON(&feemarketGenState) + // set the balances in the genesis state var bankGenState banktypes.GenesisState bankGenState.Balances = genBalances + bankGenState.Params.DefaultSendEnabled = true cfg.GenesisState[banktypes.ModuleName] = cfg.Codec.MustMarshalJSON(&bankGenState) var govGenState govv1.GenesisState cfg.Codec.MustUnmarshalJSON(cfg.GenesisState[govtypes.ModuleName], &govGenState) - govGenState.Params.MinDeposit[0].Denom = cfg.BondDenom + govGenState.Params.MinDeposit[0].Denom = cfg.NativeDenom cfg.GenesisState[govtypes.ModuleName] = cfg.Codec.MustMarshalJSON(&govGenState) var crisisGenState crisistypes.GenesisState cfg.Codec.MustUnmarshalJSON(cfg.GenesisState[crisistypes.ModuleName], &crisisGenState) - crisisGenState.ConstantFee.Denom = cfg.BondDenom + crisisGenState.ConstantFee.Denom = cfg.NativeDenom cfg.GenesisState[crisistypes.ModuleName] = cfg.Codec.MustMarshalJSON(&crisisGenState) var evmGenState evmtypes.GenesisState cfg.Codec.MustUnmarshalJSON(cfg.GenesisState[evmtypes.ModuleName], &evmGenState) - evmGenState.Params.EvmDenom = cfg.BondDenom + evmGenState.Params.EvmDenom = cfg.NativeDenom cfg.GenesisState[evmtypes.ModuleName] = cfg.Codec.MustMarshalJSON(&evmGenState) + // set validators related modules: assets, operator, dogfood + operatorAccAddresses := make([]sdk.AccAddress, 0, len(validators)) + consPubKeys := make([]string, 0, len(validators)) + for _, validator := range validators { + operatorAccAddresses = append(operatorAccAddresses, validator.Address) + // the bytes in vmmostype, tmproto, tmcryptointerface are actually the same, we skip the conversion in test scenario + consPubKeys = append(consPubKeys, hexutil.Encode(validator.PubKey.Bytes())) + } + + assetsGenState, err := NewGenStateAssets(operatorAccAddresses, cfg.DepositedTokens, cfg.StakingTokens) + if err != nil { + return err + } + cfg.GenesisState[assetstypes.ModuleName] = cfg.Codec.MustMarshalJSON(&assetsGenState) + + avsAddrStr := avstypes.GenerateAVSAddr(avstypes.ChainIDWithoutRevision(cfg.ChainID)) + operatorGenState, err := NewGenStateOperator(operatorAccAddresses, consPubKeys, commissionRate, cfg.ChainID, []string{avsAddrStr}, cfg.StakingTokens, assetsGenState) + if err != nil { + return err + } + cfg.GenesisState[operatortypes.ModuleName] = cfg.Codec.MustMarshalJSON(&operatorGenState) + + dogfoodGenState, err := NewGenStateDogfood(consPubKeys, cfg.StakingTokens, assetsGenState) + if err != nil { + return err + } + cfg.GenesisState[dogfoodtypes.ModuleName] = cfg.Codec.MustMarshalJSON(&dogfoodGenState) + + delegationGenState, err := NewGenStateDelegation(operatorAccAddresses, cfg.StakingTokens, assetsGenState) + if err != nil { + return err + } + cfg.GenesisState[delegationtypes.ModuleName] = cfg.Codec.MustMarshalJSON(&delegationGenState) + + // set oracle genesis statse + oracleGenState, err := NewGenStateOracle() + if err != nil { + return err + } + cfg.GenesisState[oracletypes.ModuleName] = cfg.Codec.MustMarshalJSON(&oracleGenState) + appGenStateJSON, err := json.MarshalIndent(cfg.GenesisState, "", " ") if err != nil { return err @@ -228,7 +260,15 @@ func initGenFiles(cfg Config, genAccounts []authtypes.GenesisAccount, genBalance } // generate empty genesis files for each validator and save + gTime := cmttime.Now() for i := 0; i < cfg.NumValidators; i++ { + if genDoc.InitialHeight == 0 { + genDoc.InitialHeight = 1 + } + genDoc.GenesisTime = gTime + if err := genDoc.ValidateAndComplete(); err != nil { + return err + } if err := genDoc.SaveAs(genFiles[i]); err != nil { return err } @@ -247,3 +287,202 @@ func WriteFile(name string, dir string, contents []byte) error { return tmos.WriteFile(file, contents, 0o644) } + +// The NewGenState.. is mainlly used for validatorset related config + +// set deposits and operator_assets for assets genesisState +func NewGenStateAssets(operatorAccAddresses []sdktypes.AccAddress, depositAmount, stakingAmount sdkmath.Int) (assetstypes.GenesisState, error) { + if stakingAmount.GT(depositAmount) { + return DefaultGenStateAssets, fmt.Errorf("stakingAmount %v should be less than depositAmount %v", stakingAmount, depositAmount) + } + n := len(operatorAccAddresses) + nInt := sdkmath.NewInt(int64(n)) + totalDepositAmount := depositAmount.Mul(nInt) + depositsByStakers := make([]assetstypes.DepositsByStaker, 0, len(DefaultGenStateAssets.Tokens)*n) + operatorsAssets := make([]assetstypes.AssetsByOperator, 0, n) + nAssets := len(DefaultGenStateAssets.Tokens) + for i := 0; i < nAssets; i++ { + DefaultGenStateAssets.Tokens[i].StakingTotalAmount = totalDepositAmount + } + for _, operatorAccAddress := range operatorAccAddresses { + // use the same address []byte for operator(exo..) and staker(0x...), both derived from the same pubkey and since evmos use ethsecp256k1, this address is generated from keccak-256(.) instead of ripemd160(sha256(.)) + stakerAddrStr := hexutil.Encode(operatorAccAddress) + depositsByAssets := make([]assetstypes.DepositByAsset, 0, nAssets) + assetsStates := make([]assetstypes.AssetByID, 0, nAssets) + stakerID := "" + assetID := "" + for _, asset := range DefaultGenStateAssets.Tokens { + stakerID, assetID = assetstypes.GetStakerIDAndAssetIDFromStr(asset.AssetBasicInfo.LayerZeroChainID, stakerAddrStr, asset.AssetBasicInfo.Address) + depositsByAssets = append(depositsByAssets, assetstypes.DepositByAsset{ + AssetID: assetID, + Info: assetstypes.StakerAssetInfo{ + TotalDepositAmount: depositAmount, + WithdrawableAmount: depositAmount.Sub(stakingAmount), + PendingUndelegationAmount: sdkmath.ZeroInt(), + }, + }) + assetsStates = append(assetsStates, assetstypes.AssetByID{ + AssetID: assetID, + Info: assetstypes.OperatorAssetInfo{ + TotalAmount: stakingAmount, + PendingUndelegationAmount: sdkmath.ZeroInt(), + TotalShare: sdkmath.LegacyNewDecFromInt(stakingAmount), + // only take self delegation for genesis state + OperatorShare: sdkmath.LegacyNewDecFromInt(stakingAmount), + }, + }) + } + depositsByStakers = append(depositsByStakers, assetstypes.DepositsByStaker{ + StakerID: stakerID, + Deposits: depositsByAssets, + }) + operatorsAssets = append(operatorsAssets, assetstypes.AssetsByOperator{ + Operator: operatorAccAddress.String(), + AssetsState: assetsStates, + }) + } + + DefaultGenStateAssets.Deposits = depositsByStakers + DefaultGenStateAssets.OperatorAssets = operatorsAssets + + return DefaultGenStateAssets, nil +} + +// stakingAmount, each operator opt in evry AVS with stakingAmount of every assets +// each avs suppport all assets +// each operator opts in every avs +// each operator deposited and self staked all assets with: (depsitAmount, stakingAmount) +// initial price for every asset is 1 USD +func NewGenStateOperator(operatorAccAddresses []sdktypes.AccAddress, consPubKeys []string, commissionRate sdkmath.LegacyDec, chainID string, optedAVSAddresses []string, stakingAmount sdkmath.Int, genStateAssets assetstypes.GenesisState) (operatortypes.GenesisState, error) { + // total stakingAmount one operator holds among all assets + stakingAmount = stakingAmount.Mul(sdkmath.NewInt(int64(len(genStateAssets.Tokens)))) + if len(operatorAccAddresses) != len(consPubKeys) { + return DefaultGenStateOperator, fmt.Errorf("length of operatorAccAddresses %d should be equal to length of consPubKeys %d", len(operatorAccAddresses), len(consPubKeys)) + } + n := len(operatorAccAddresses) + totalStakingAmount := stakingAmount.Mul(sdkmath.NewInt(int64(n))) + for i, operatorAccAddress := range operatorAccAddresses { + // operators + DefaultGenStateOperator.Operators = append(DefaultGenStateOperator.Operators, operatortypes.OperatorDetail{ + OperatorAddress: operatorAccAddress.String(), + OperatorInfo: operatortypes.OperatorInfo{ + EarningsAddr: operatorAccAddress.String(), + OperatorMetaInfo: fmt.Sprintf("operator_%d", i), + Commission: stakingtypes.Commission{ + CommissionRates: stakingtypes.CommissionRates{ + Rate: commissionRate, + MaxRate: commissionRate.Mul(sdkmath.LegacyNewDec(2)), + MaxChangeRate: sdkmath.LegacyNewDecWithPrec(1, 1), + }, + }, + }, + }) + // operator_records + DefaultGenStateOperator.OperatorRecords = append(DefaultGenStateOperator.OperatorRecords, operatortypes.OperatorConsKeyRecord{ + OperatorAddress: operatorAccAddress.String(), + Chains: []operatortypes.ChainDetails{ + { + ChainID: avstypes.ChainIDWithoutRevision(chainID), + ConsensusKey: consPubKeys[i], + }, + }, + }) + // OptStates + for _, AVSAddress := range optedAVSAddresses { + DefaultGenStateOperator.OptStates = append(DefaultGenStateOperator.OptStates, operatortypes.OptedState{ + Key: operatorAccAddress.String() + "/" + AVSAddress, + OptInfo: operatortypes.OptedInfo{ + OptedInHeight: 1, + OptedOutHeight: 18446744073709551615, + Jailed: false, + }, + }) + // OperatorUSDValues + // the price unit of assets is 1 not decimal 18 + stakingValue := sdktypes.TokensToConsensusPower(stakingAmount, evmostypes.PowerReduction) + DefaultGenStateOperator.OperatorUSDValues = append(DefaultGenStateOperator.OperatorUSDValues, operatortypes.OperatorUSDValue{ + Key: AVSAddress + "/" + operatorAccAddress.String(), + OptedUSDValue: operatortypes.OperatorOptedUSDValue{ + SelfUSDValue: sdkmath.LegacyNewDec(stakingValue), + TotalUSDValue: sdkmath.LegacyNewDec(stakingValue), + ActiveUSDValue: sdkmath.LegacyNewDec(stakingValue), + }, + }) + } + } + // AVSUSDValues + for _, AVSAddress := range optedAVSAddresses { + DefaultGenStateOperator.AVSUSDValues = append(DefaultGenStateOperator.AVSUSDValues, operatortypes.AVSUSDValue{ + AVSAddr: AVSAddress, + Value: operatortypes.DecValueField{ + // the price unit of assets is 1 not decimal 18 + Amount: sdkmath.LegacyNewDec(sdktypes.TokensToConsensusPower(totalStakingAmount, evmostypes.PowerReduction)), + }, + }) + } + return DefaultGenStateOperator, nil +} + +// NewGenStateDogfood generates dogfood genesis state from default +// stakingAmount is the amount each operator have for every single asset defined in assets module, so for a single operator the total stakingAmount they have is stakingAmount*count(assets) +// assets genesis state is required as input argument to provide assets information. It should be called with NewGenStateAssets to update default assets genesis state for test +func NewGenStateDogfood(consPubKeys []string, stakingAmount sdkmath.Int, genStateAssets assetstypes.GenesisState) (dogfoodtypes.GenesisState, error) { + power := sdktypes.TokensToConsensusPower(stakingAmount.Mul(sdkmath.NewInt(int64(len(genStateAssets.Tokens)))), evmostypes.PowerReduction) + DefaultGenStateDogfood.Params.EpochIdentifier = "minute" + DefaultGenStateDogfood.Params.EpochsUntilUnbonded = 5 + DefaultGenStateDogfood.Params.MinSelfDelegation = sdkmath.NewInt(100) + assetIDs := make(map[string]bool) + for _, assetID := range DefaultGenStateDogfood.Params.AssetIDs { + assetIDs[assetID] = true + } + for _, asset := range genStateAssets.Tokens { + _, assetID := assetstypes.GetStakerIDAndAssetIDFromStr(asset.AssetBasicInfo.LayerZeroChainID, "", asset.AssetBasicInfo.Address) + if assetIDs[assetID] { + continue + } + DefaultGenStateDogfood.Params.AssetIDs = append(DefaultGenStateDogfood.Params.AssetIDs, assetID) + } + for _, consPubKey := range consPubKeys { + DefaultGenStateDogfood.ValSet = append(DefaultGenStateDogfood.ValSet, dogfoodtypes.GenesisValidator{ + PublicKey: consPubKey, + Power: power, + }) + } + DefaultGenStateDogfood.LastTotalPower = sdkmath.NewInt(power * int64(len(consPubKeys))) + return DefaultGenStateDogfood, nil +} + +func NewGenStateDelegation(operatorAccAddresses []sdk.AccAddress, stakingAmount sdkmath.Int, genStateAssets assetstypes.GenesisState) (delegationtypes.GenesisState, error) { + for _, operator := range operatorAccAddresses { + stakerIDsLinked := make(map[string]bool) + for _, asset := range genStateAssets.Tokens { + stakerID, assetID := assetstypes.GetStakerIDAndAssetIDFromStr(asset.AssetBasicInfo.LayerZeroChainID, hexutil.Encode(operator), asset.AssetBasicInfo.Address) + if !stakerIDsLinked[stakerID] { + DefaultGenStateDelegation.Associations = append(DefaultGenStateDelegation.Associations, delegationtypes.StakerToOperator{ + StakerID: stakerID, + Operator: operator.String(), + }) + stakerIDsLinked[stakerID] = true + } + DefaultGenStateDelegation.DelegationStates = append(DefaultGenStateDelegation.DelegationStates, delegationtypes.DelegationStates{ + Key: stakerID + "/" + assetID + "/" + operator.String(), + States: delegationtypes.DelegationAmounts{ + UndelegatableShare: sdkmath.LegacyNewDecFromInt(stakingAmount), + WaitUndelegationAmount: sdkmath.ZeroInt(), + }, + }) + DefaultGenStateDelegation.StakersByOperator = append(DefaultGenStateDelegation.StakersByOperator, delegationtypes.StakersByOperator{ + Key: operator.String() + "/" + assetID, + Stakers: []string{ + stakerID, + }, + }) + } + } + return DefaultGenStateDelegation, nil +} + +func NewGenStateOracle() (oracletypes.GenesisState, error) { + DefaultGenStateOracle.Params.Slashing.ReportedRoundsWindow = 2 + return DefaultGenStateOracle, nil +} diff --git a/x/delegation/keeper/abci.go b/x/delegation/keeper/abci.go index 47ca02748..d89be5616 100644 --- a/x/delegation/keeper/abci.go +++ b/x/delegation/keeper/abci.go @@ -116,6 +116,7 @@ func (k *Keeper) EndBlock( continue } + // TODO: the field IsPending in types.UndelegationRecord is useless since when a record is completed it will be removed, so the record is either existing&pending or unexist&completed, and the IsPending is not used nowhere(like slashFromUndelegation doesn't check this field either), good to remove this field. And types.UndelegationRecord is actually PendingUndelegationRecord // delete the Undelegation records that have been complemented err = k.DeleteUndelegationRecord(cc, record) if err != nil { diff --git a/x/delegation/keeper/delegation_state.go b/x/delegation/keeper/delegation_state.go index 340d585dd..29ac28491 100644 --- a/x/delegation/keeper/delegation_state.go +++ b/x/delegation/keeper/delegation_state.go @@ -303,6 +303,7 @@ func (k *Keeper) SetStakerShareToZero(ctx sdk.Context, operator, assetID string, singleStateKey := assetstype.GetJoinedStoreKey(stakerID, assetID, operator) value := store.Get(singleStateKey) if value != nil { + // TODO: check if pendingUndelegation==0 => just delete this item instead of update share to zero, otherwise this item will be left in the storage forever with zero value delegationState := delegationtype.DelegationAmounts{} k.cdc.MustUnmarshal(value, &delegationState) delegationState.UndelegatableShare = sdkmath.LegacyNewDec(0) diff --git a/x/operator/keeper/slash.go b/x/operator/keeper/slash.go index 1f1c6a58d..f5be6d8e6 100644 --- a/x/operator/keeper/slash.go +++ b/x/operator/keeper/slash.go @@ -23,7 +23,7 @@ func GetSlashIDForDogfood(infraction stakingtypes.Infraction, infractionHeight i return strings.Join([]string{hexutil.EncodeUint64(uint64(infraction)), hexutil.EncodeUint64(uint64(infractionHeight))}, utils.DelimiterForID) } -// SlashFromUndelegation executes the slash from an undelegation +// SlashFromUndelegation executes the slash from an undelegation, reduce the .ActualCompletedAmount from undelegationRecords func SlashFromUndelegation(undelegation *delegationtype.UndelegationRecord, slashProportion sdkmath.LegacyDec) *types.SlashFromUndelegation { if undelegation.ActualCompletedAmount.IsZero() { return nil @@ -136,6 +136,7 @@ func (k *Keeper) SlashAssets(ctx sdk.Context, parameter *types.SlashInputInfo) ( state.OperatorShare = sdkmath.LegacyNewDec(0) } state.TotalAmount = remainingAmount + // TODO: check if pendingUndelegation also zero => delete this item, and this operator should be opted out if all aasets falls to 0 since the miniself is not satisfied then. executionInfo.SlashAssetsPool = append(executionInfo.SlashAssetsPool, types.SlashFromAssetsPool{ AssetID: assetID, Amount: slashAmount, diff --git a/x/operator/keeper/usd_value.go b/x/operator/keeper/usd_value.go index fc1fdb5f2..a826aa8bf 100644 --- a/x/operator/keeper/usd_value.go +++ b/x/operator/keeper/usd_value.go @@ -348,7 +348,6 @@ func (k *Keeper) CalculateUSDValueForOperator( } // iterate all assets owned by the operator to calculate its voting power opFuncToIterateAssets := func(assetID string, state *assetstype.OperatorAssetInfo) error { - // var price operatortypes.Price var price oracletype.Price var decimal uint32 if isForSlash { diff --git a/x/oracle/genesis.go b/x/oracle/genesis.go index 084caa580..8c4223acc 100644 --- a/x/oracle/genesis.go +++ b/x/oracle/genesis.go @@ -40,6 +40,16 @@ func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState) for _, elem := range genState.StakerInfosAssets { k.SetStakerInfos(ctx, elem.AssetId, elem.StakerInfos) } + // set validatorReportInfos + for _, elem := range genState.ValidatorReportInfos { + k.SetValidatorReportInfo(ctx, elem.Address, elem) + } + // set vlidatorMissedRounds + for _, elem := range genState.ValidatorMissedRounds { + for _, missedRound := range elem.MissedRounds { + k.SetValidatorMissedRoundBitArray(ctx, elem.Address, uint64(missedRound.Index), missedRound.Missed) + } + } // this line is used by starport scaffolding # genesis/module/init k.SetParams(ctx, genState.Params) } @@ -47,9 +57,13 @@ func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState) // ExportGenesis returns the module's exported genesis func ExportGenesis(ctx sdk.Context, k keeper.Keeper) *types.GenesisState { genesis := types.DefaultGenesis() + // params genesis.Params = k.GetParams(ctx) + // priceList genesis.PricesList = k.GetAllPrices(ctx) + + // cache recovery related, used by agc // Get all validatorUpdateBlock validatorUpdateBlock, found := k.GetValidatorUpdateBlock(ctx) if found { @@ -67,9 +81,25 @@ func ExportGenesis(ctx sdk.Context, k keeper.Keeper) *types.GenesisState { } genesis.RecentMsgList = k.GetAllRecentMsg(ctx) genesis.RecentParamsList = k.GetAllRecentParams(ctx) - // TODO: export stakerListAssets, and stakerInfosAssets + + // NST related genesis.StakerInfosAssets = k.GetAllStakerInfosAssets(ctx) genesis.StakerListAssets = k.GetAllStakerListAssets(ctx) + + // slashing related + reportInfos := make([]types.ValidatorReportInfo, 0) + validatorMissedRounds := make([]types.ValidatorMissedRounds, 0) + k.IterateValidatorReportInfos(ctx, func(validator string, reportInfo types.ValidatorReportInfo) bool { + reportInfos = append(reportInfos, reportInfo) + missedRounds := k.GetValidatorMissedRounds(ctx, validator) + validatorMissedRounds = append(validatorMissedRounds, types.ValidatorMissedRounds{ + Address: validator, + MissedRounds: missedRounds, + }) + return false + }) + genesis.ValidatorReportInfos = reportInfos + genesis.ValidatorMissedRounds = validatorMissedRounds // this line is used by starport scaffolding # genesis/module/export return genesis diff --git a/x/oracle/keeper/aggregator/aggregator.go b/x/oracle/keeper/aggregator/aggregator.go index 48e7778cd..8ebd84492 100644 --- a/x/oracle/keeper/aggregator/aggregator.go +++ b/x/oracle/keeper/aggregator/aggregator.go @@ -2,6 +2,7 @@ package aggregator import ( "math/big" + "sort" "github.com/ExocoreNetwork/exocore/x/oracle/keeper/common" "github.com/ExocoreNetwork/exocore/x/oracle/types" @@ -16,7 +17,7 @@ type priceWithTimeAndRound struct { type reportPrice struct { validator string - // final price, set to -1 as initial + // final price, nil as initial stands for no aggregated price reached yet price *big.Int // sourceId->priceWithTimeAndRound prices map[uint64]*priceWithTimeAndRound @@ -50,9 +51,9 @@ type aggregator struct { func (agg *aggregator) copy4CheckTx() *aggregator { ret := &aggregator{ - finalPrice: big.NewInt(0).Set(agg.finalPrice), - reportPower: big.NewInt(0).Set(agg.reportPower), - totalPower: big.NewInt(0).Set(agg.totalPower), + finalPrice: copyBigInt(agg.finalPrice), + reportPower: copyBigInt(agg.reportPower), + totalPower: copyBigInt(agg.totalPower), reports: make([]*reportPrice, 0, len(agg.reports)), dsPrices: make(map[uint64]string), @@ -62,13 +63,13 @@ func (agg *aggregator) copy4CheckTx() *aggregator { } for _, report := range agg.reports { rTmp := *report - rTmp.price = big.NewInt(0).Set(report.price) - rTmp.power = big.NewInt(0).Set(report.power) + rTmp.price = copyBigInt(report.price) + rTmp.power = copyBigInt(report.power) for k, v := range report.prices { // prices are information submitted by validators, these data will not change under deterministic sources, but with non-deterministic sources they might be overwrite by later prices tmpV := *v - tmpV.price = big.NewInt(0).Set(v.price) + tmpV.price = copyBigInt(v.price) rTmp.prices[k] = &tmpV } @@ -124,6 +125,7 @@ func (agg *aggregator) fillPrice(pSources []*types.PriceSource, validator string pTR.price = new(big.Int).Set(priceTmp.price) pTR.detRoundID = priceTmp.detRoundID pTR.timestamp = priceTmp.timestamp + break } } } @@ -151,7 +153,7 @@ func (agg *aggregator) confirmDSPrice(confirmedRounds []*confirmedPrice) { price.detRoundID = priceSourceRound.detID price.timestamp = priceSourceRound.timestamp price.price = priceSourceRound.price - } // else TODO: panice in V1 + } // else TODO: panic in V1 } } } @@ -191,6 +193,37 @@ func (agg *aggregator) aggregate() *big.Int { return agg.finalPrice } +// TODO: this only suites for DS. check source type for extension +// GetFinaPriceListForFeederIDs retrieve final price info as an array ordered by sourceID asc +func (agg *aggregator) getFinalPriceList(feederID uint64) []*types.AggFinalPrice { + sourceIDs := make([]uint64, 0, len(agg.dsPrices)) + for sID := range agg.dsPrices { + sourceIDs = append(sourceIDs, sID) + } + sort.Slice(sourceIDs, func(i, j int) bool { + return sourceIDs[i] < sourceIDs[j] + }) + ret := make([]*types.AggFinalPrice, 0, len(sourceIDs)) + for _, sID := range sourceIDs { + for _, report := range agg.reports { + price := report.prices[sID] + if price == nil || price.detRoundID != agg.dsPrices[sID] { + // the DetID mismatch should not happen + continue + } + ret = append(ret, &types.AggFinalPrice{ + FeederID: feederID, + SourceID: sID, + DetID: price.detRoundID, + Price: price.price.String(), + }) + // {feederID,soruceID} has been found, skip other reports + break + } + } + return ret +} + func newAggregator(validatorSetLength int, totalPower *big.Int) *aggregator { return &aggregator{ reports: make([]*reportPrice, 0, validatorSetLength), diff --git a/x/oracle/keeper/aggregator/calculator.go b/x/oracle/keeper/aggregator/calculator.go index 9ee504d25..068725ca2 100644 --- a/x/oracle/keeper/aggregator/calculator.go +++ b/x/oracle/keeper/aggregator/calculator.go @@ -52,7 +52,6 @@ func (r *roundPrices) updatePriceAndPower(pw *priceAndPower, totalPower *big.Int updated = true if common.ExceedsThreshold(pw.power, totalPower) { r.price = pw.price - // r.confirmed = true confirmed = true } } @@ -75,14 +74,14 @@ func (r *roundPricesList) copy4CheckTx() *roundPricesList { for _, v := range r.roundPricesList { tmpRP := &roundPrices{ detID: v.detID, - price: big.NewInt(0).Set(v.price), + price: copyBigInt(v.price), prices: make([]*priceAndPower, 0, len(v.prices)), timestamp: v.timestamp, } for _, pNP := range v.prices { tmpPNP := *pNP // power will be modified during execution - tmpPNP.power = big.NewInt(0).Set(pNP.power) + tmpPNP.power = copyBigInt(pNP.power) tmpRP.prices = append(tmpRP.prices, &tmpPNP) } diff --git a/x/oracle/keeper/aggregator/context.go b/x/oracle/keeper/aggregator/context.go index 591f653d5..be0a38efb 100644 --- a/x/oracle/keeper/aggregator/context.go +++ b/x/oracle/keeper/aggregator/context.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "math/big" + "sort" "github.com/ExocoreNetwork/exocore/x/oracle/keeper/cache" "github.com/ExocoreNetwork/exocore/x/oracle/keeper/common" @@ -160,10 +161,16 @@ func (agc *AggregatorContext) FillPrice(msg *types.MsgCreatePrice) (*PriceItemKV } if feederWorker.sealed { + if _, list4Aggregator := feederWorker.filtrate(msg); list4Aggregator != nil { + // record this message for performance evaluation(used for slashing) + feederWorker.recordMessage(msg.Creator, msg.FeederID, list4Aggregator) + } return nil, nil, types.ErrPriceProposalIgnored.Wrap("price aggregation for this round has sealed") } if listFilled := feederWorker.do(msg); listFilled != nil { + // record this message for performance evaluation(used for slashing) + feederWorker.recordMessage(msg.Creator, msg.FeederID, listFilled) if finalPrice := feederWorker.aggregate(); finalPrice != nil { agc.rounds[msg.FeederID].status = roundStatusClosed feederWorker.seal() @@ -196,26 +203,46 @@ func (agc *AggregatorContext) NewCreatePrice(_ sdk.Context, msg *types.MsgCreate // including possible aggregation and state update // when validatorSet update, set force to true, to seal all alive round // returns: 1st successful sealed, need to be written to KVStore, 2nd: failed sealed tokenID, use previous price to write to KVStore -func (agc *AggregatorContext) SealRound(ctx sdk.Context, force bool) (success []*PriceItemKV, failed []uint64, sealed []uint64) { - for feederID, round := range agc.rounds { +func (agc *AggregatorContext) SealRound(ctx sdk.Context, force bool) (success []*PriceItemKV, failed []uint64, sealed []uint64, windowClosed []uint64) { + feederIDs := make([]uint64, 0, len(agc.rounds)) + for fID := range agc.rounds { + feederIDs = append(feederIDs, fID) + } + sort.Slice(feederIDs, func(i, j int) bool { + return feederIDs[i] < feederIDs[j] + }) + height := uint64(ctx.BlockHeight()) + // make sure feederIDs are accessed in order to calculate the indexOffset for slashing + windowClosedMap := make(map[uint64]bool) + for _, feederID := range feederIDs { + if agc.windowEnd(feederID, height) { + windowClosed = append(windowClosed, feederID) + windowClosedMap[feederID] = true + } + round := agc.rounds[feederID] if round.status == roundStatusOpen { feeder := agc.params.GetTokenFeeder(feederID) // TODO: for mode=1, we don't do aggregate() here, since if it donesn't success in the transaction execution stage, it won't success here // but it's not always the same for other modes, switch modes switch common.Mode { case types.ConsensusModeASAP: - expired := feeder.EndBlock > 0 && uint64(ctx.BlockHeight()) >= feeder.EndBlock - outOfWindow := uint64(ctx.BlockHeight())-round.basedBlock >= uint64(common.MaxNonce) + offset := height - round.basedBlock + expired := feeder.EndBlock > 0 && height >= feeder.EndBlock + outOfWindow := offset >= uint64(common.MaxNonce) + + // an open round reach its end of window, increase offsetIndex for active valdiator and chech the performance(missing/malicious) + if expired || outOfWindow || force { failed = append(failed, feeder.TokenID) - if expired { - delete(agc.rounds, feederID) - } else { + if !expired { round.status = roundStatusClosed } // TODO: optimize operformance sealed = append(sealed, feederID) - delete(agc.aggregators, feederID) + if !windowClosedMap[feederID] { + // this should be clear after performanceReview + agc.RemoveWorker(feederID) + } } default: ctx.Logger().Info("mode other than 1 is not support now") @@ -223,11 +250,10 @@ func (agc *AggregatorContext) SealRound(ctx sdk.Context, force bool) (success [] } // all status: 1->2, remove its aggregator if agc.aggregators[feederID] != nil && agc.aggregators[feederID].sealed { - delete(agc.aggregators, feederID) sealed = append(sealed, feederID) } } - return success, failed, sealed + return success, failed, sealed, windowClosed } // PrepareEndBlock is called at EndBlock stage, to prepare the roundInfo for the next block(of input block) @@ -279,9 +305,10 @@ func (agc *AggregatorContext) PrepareRoundEndBlock(block uint64) (newRoundFeeder // set nonce for corresponding feederID for new roud start newRoundFeederIDs = append(newRoundFeederIDs, feederIDUint64) // drop previous worker - delete(agc.aggregators, feederIDUint64) + agc.RemoveWorker(feederIDUint64) } else if round.status == roundStatusOpen && left >= uint64(common.MaxNonce) { // this shouldn't happen, if do sealround properly before prepareRound, basically for test only + // TODO: print error log here round.status = roundStatusClosed // TODO: just modify the status here, since sealRound should do all the related seal actions already when parepare invoked } @@ -335,6 +362,54 @@ func (agc *AggregatorContext) GetParamsMaxSizePrices() uint64 { return uint64(agc.params.MaxSizePrices) } +// GetFinalPriceListForFeederIDs get final price list for required feederIDs in format []{feederID, sourceID, detID, price} with asc of {feederID, sourceID} +// feederIDs is required to be ordered asc +func (agc *AggregatorContext) GetFinalPriceListForFeederIDs(feederIDs []uint64) []*types.AggFinalPrice { + ret := make([]*types.AggFinalPrice, 0, len(feederIDs)) + for _, feederID := range feederIDs { + feederWorker := agc.aggregators[feederID] + if feederWorker != nil { + if pList := feederWorker.getFinalPriceList(feederID); len(pList) > 0 { + ret = append(ret, pList...) + } + } + } + return ret +} + +// PerformanceReview compare results to decide whether the validator is effective, honest +func (agc *AggregatorContext) PerformanceReview(ctx sdk.Context, finalPrice *types.AggFinalPrice, validator string) (exist, matched bool) { + feederWorker := agc.aggregators[finalPrice.FeederID] + if feederWorker == nil { + // Log unexpected nil feederWorker for debugging + ctx.Logger().Error( + "unexpected nil feederWorker in PerformanceReview", + "feederID", finalPrice.FeederID, + "validator", validator, + ) + // Treat validator as effective & honest to avoid unfair penalties + exist = true + matched = true + return + } + exist, matched = feederWorker.check(validator, finalPrice.FeederID, finalPrice.SourceID, finalPrice.Price, finalPrice.DetID) + return +} + +func (agc AggregatorContext) windowEnd(feederID, height uint64) bool { + feeder := agc.params.TokenFeeders[feederID] + if (feeder.EndBlock > 0 && feeder.EndBlock <= height) || feeder.StartBaseBlock > height { + return false + } + delta := height - feeder.StartBaseBlock + left := delta % feeder.Interval + return left == uint64(common.MaxNonce) +} + +func (agc *AggregatorContext) RemoveWorker(feederID uint64) { + delete(agc.aggregators, feederID) +} + // NewAggregatorContext returns a new instance of AggregatorContext func NewAggregatorContext() *AggregatorContext { return &AggregatorContext{ diff --git a/x/oracle/keeper/aggregator/util.go b/x/oracle/keeper/aggregator/util.go new file mode 100644 index 000000000..98fd7bd00 --- /dev/null +++ b/x/oracle/keeper/aggregator/util.go @@ -0,0 +1,10 @@ +package aggregator + +import "math/big" + +func copyBigInt(i *big.Int) *big.Int { + if i == nil { + return nil + } + return big.NewInt(0).Set(i) +} diff --git a/x/oracle/keeper/aggregator/worker.go b/x/oracle/keeper/aggregator/worker.go index bbf7dcf2f..e4a09c3d8 100644 --- a/x/oracle/keeper/aggregator/worker.go +++ b/x/oracle/keeper/aggregator/worker.go @@ -20,14 +20,75 @@ type worker struct { // when enough data(exceeds threshold) collected, aggregate to conduct the final price a *aggregator ctx *AggregatorContext + // TODO: move outside into context through .ctx + records recordMsg } -func (w *worker) do(msg *types.MsgCreatePrice) []*types.PriceSource { - accAddress, _ := sdk.AccAddressFromBech32(msg.Creator) +// recordKey used to retrieve messages from records to evaluate that if a validator report proper price for a specific feederID+sourceID +type recordKey struct { + validator string + feederID uint64 + sourceID uint64 +} + +// recordMsg define wrap the map for fast access to validator's message info +type recordMsg map[recordKey][]*types.PriceTimeDetID + +func newRecordMsg() recordMsg { + return make(map[recordKey][]*types.PriceTimeDetID) +} + +func (r recordMsg) get(validator string, feederID, sourceID uint64) []*types.PriceTimeDetID { + v := r[recordKey{validator, feederID, sourceID}] + return v +} + +func (r recordMsg) check(validator string, feederID, sourceID uint64, price, detID string) (exist, matched bool) { + prices := r.get(validator, feederID, sourceID) + for _, p := range prices { + if p.DetID == detID { + exist = true + if p.Price == price { + matched = true + return + } + } + } + return +} + +func (r recordMsg) set(creator string, feederID uint64, priceSources []*types.PriceSource) { + accAddress, _ := sdk.AccAddressFromBech32(creator) validator := sdk.ConsAddress(accAddress).String() - power := w.ctx.validatorsPower[validator] + for _, price := range priceSources { + r[recordKey{validator, feederID, price.SourceID}] = price.Prices + } +} + +// GetFinalPriceList relies requirement to aggregator inside them to get final price list +// []{feederID, sourceID, detID, price} in asc order of {soruceID} +func (w *worker) getFinalPriceList(feederID uint64) []*types.AggFinalPrice { + return w.a.getFinalPriceList(feederID) +} + +func (w *worker) filtrate(msg *types.MsgCreatePrice) (list4Calculator []*types.PriceSource, list4Aggregator []*types.PriceSource) { + return w.f.filtrate(msg) +} + +func (w *worker) recordMessage(creator string, feederID uint64, priceSources []*types.PriceSource) { + w.records.set(creator, feederID, priceSources) +} + +func (w *worker) check(validator string, feederID, sourceID uint64, price, detID string) (exist, matched bool) { + return w.records.check(validator, feederID, sourceID, price, detID) +} + +func (w *worker) do(msg *types.MsgCreatePrice) []*types.PriceSource { list4Calculator, list4Aggregator := w.f.filtrate(msg) if list4Aggregator != nil { + accAddress, _ := sdk.AccAddressFromBech32(msg.Creator) + validator := sdk.ConsAddress(accAddress).String() + power := w.ctx.validatorsPower[validator] w.a.fillPrice(list4Aggregator, validator, power) if confirmedRounds := w.c.fillPrice(list4Calculator, validator, power); confirmedRounds != nil { w.a.confirmDSPrice(confirmedRounds) @@ -49,7 +110,8 @@ func (w *worker) seal() { w.price = w.a.aggregate().String() w.f = nil w.c = nil - w.a = nil + // aggregator is kept for performance evaluation + // w.a = nil } // newWorker new a instance for a tokenFeeder's specific round @@ -60,5 +122,6 @@ func newWorker(feederID uint64, agc *AggregatorContext) *worker { a: newAggregator(len(agc.validatorsPower), agc.totalPower), decimal: agc.params.GetTokenInfo(feederID).Decimal, ctx: agc, + records: newRecordMsg(), } } diff --git a/x/oracle/keeper/common/expected_keepers.go b/x/oracle/keeper/common/expected_keepers.go index 6e68f8695..f668d139b 100644 --- a/x/oracle/keeper/common/expected_keepers.go +++ b/x/oracle/keeper/common/expected_keepers.go @@ -7,7 +7,7 @@ import ( "github.com/ExocoreNetwork/exocore/x/oracle/types" abci "github.com/cometbft/cometbft/abci/types" sdk "github.com/cosmos/cosmos-sdk/types" - stakingTypes "github.com/cosmos/cosmos-sdk/x/staking/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" ) type Price struct { @@ -53,9 +53,12 @@ var _ KeeperDogfood = dogfoodkeeper.Keeper{} type KeeperDogfood = interface { GetLastTotalPower(ctx sdk.Context) sdkmath.Int - IterateBondedValidatorsByPower(ctx sdk.Context, fn func(index int64, validator stakingTypes.ValidatorI) (stop bool)) + IterateBondedValidatorsByPower(ctx sdk.Context, fn func(index int64, validator stakingtypes.ValidatorI) (stop bool)) GetValidatorUpdates(ctx sdk.Context) []abci.ValidatorUpdate - GetValidatorByConsAddr(ctx sdk.Context, consAddr sdk.ConsAddress) (validator stakingTypes.Validator, found bool) + GetValidatorByConsAddr(ctx sdk.Context, consAddr sdk.ConsAddress) (validator stakingtypes.Validator, found bool) GetAllExocoreValidators(ctx sdk.Context) (validators []dogfoodtypes.ExocoreValidator) + ValidatorByConsAddr(ctx sdk.Context, addr sdk.ConsAddress) stakingtypes.ValidatorI + SlashWithInfractionReason(ctx sdk.Context, addr sdk.ConsAddress, infractionHeight, power int64, slashFactor sdk.Dec, infraction stakingtypes.Infraction) sdkmath.Int + Jail(ctx sdk.Context, addr sdk.ConsAddress) } diff --git a/x/oracle/keeper/keeper.go b/x/oracle/keeper/keeper.go index 8ec37b5f9..6738b8b37 100644 --- a/x/oracle/keeper/keeper.go +++ b/x/oracle/keeper/keeper.go @@ -11,11 +11,20 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" + "github.com/ExocoreNetwork/exocore/x/oracle/keeper/aggregator" + "github.com/ExocoreNetwork/exocore/x/oracle/keeper/cache" "github.com/ExocoreNetwork/exocore/x/oracle/keeper/common" "github.com/ExocoreNetwork/exocore/x/oracle/types" ) type ( + memoryStore struct { + cs *cache.Cache + agc *aggregator.AggregatorContext + agcCheckTx *aggregator.AggregatorContext + updatedFeederIDs []string + } + Keeper struct { cdc codec.BinaryCodec storeKey storetypes.StoreKey @@ -25,6 +34,9 @@ type ( common.KeeperDogfood delegationKeeper types.DelegationKeeper assetsKeeper types.AssetsKeeper + types.SlashingKeeper + // wrap all four memory cache into one pointer to track them among cpoies of Keeper (msgServer, module) + memStore *memoryStore } ) @@ -39,6 +51,7 @@ func NewKeeper( delegationKeeper types.DelegationKeeper, assetsKeeper types.AssetsKeeper, authority string, + slashingKeeper types.SlashingKeeper, ) Keeper { // ensure authority is a valid bech32 address if _, err := sdk.AccAddressFromBech32(authority); err != nil { @@ -58,6 +71,8 @@ func NewKeeper( delegationKeeper: delegationKeeper, assetsKeeper: assetsKeeper, authority: authority, + SlashingKeeper: slashingKeeper, + memStore: new(memoryStore), } } diff --git a/x/oracle/keeper/keeper_suite_test.go b/x/oracle/keeper/keeper_suite_test.go index 983c84a6b..3bc01c6b0 100644 --- a/x/oracle/keeper/keeper_suite_test.go +++ b/x/oracle/keeper/keeper_suite_test.go @@ -67,17 +67,19 @@ func TestKeeper(t *testing.T) { suite.Run(t, ks) - resetSingle() + resetSingle(ks.App.OracleKeeper) RegisterFailHandler(Fail) RunSpecs(t, "Keeper Suite") } func (suite *KeeperSuite) Reset() { - var ctxW context.Context - suite.ms, ctxW, suite.k = setupMsgServer(suite.t) - suite.ctx = sdk.UnwrapSDKContext(ctxW) + p4Test := types.DefaultParams() + p4Test.TokenFeeders[1].StartBaseBlock = 1 + suite.k.SetParams(suite.ctx, p4Test) + suite.ctx = suite.ctx.WithBlockHeight(12) + suite.ctrl = gomock.NewController(suite.t) - resetSingle() + resetSingle(suite.App.OracleKeeper) } func (suite *KeeperSuite) SetupTest() { @@ -99,10 +101,19 @@ func (suite *KeeperSuite) SetupTest() { validators := suite.ValSet.Validators suite.valAddr1, _ = sdk.ValAddressFromBech32(sdk.ValAddress(validators[0].Address).String()) suite.valAddr2, _ = sdk.ValAddressFromBech32(sdk.ValAddress(validators[1].Address).String()) - resetSingle() + resetSingle(suite.App.OracleKeeper) + + suite.k = suite.App.OracleKeeper + suite.ms = keeper.NewMsgServerImpl(suite.App.OracleKeeper) + suite.ctx = suite.Ctx + // Initialize params + p4Test := types.DefaultParams() + p4Test.TokenFeeders[1].StartBaseBlock = 1 + suite.k.SetParams(suite.ctx, p4Test) + suite.ctx = suite.ctx.WithBlockHeight(12) } -func resetSingle() { - keeper.ResetAggregatorContext() - keeper.ResetCache() +func resetSingle(k keeper.Keeper) { + k.ResetAggregatorContext() + k.ResetCache() } diff --git a/x/oracle/keeper/msg_server_create_price.go b/x/oracle/keeper/msg_server_create_price.go index 7eca28a82..9a7062126 100644 --- a/x/oracle/keeper/msg_server_create_price.go +++ b/x/oracle/keeper/msg_server_create_price.go @@ -30,7 +30,7 @@ func (ms msgServer) CreatePrice(goCtx context.Context, msg *types.MsgCreatePrice return nil, types.ErrPriceProposalFormatInvalid.Wrap(err.Error()) } - agc := GetAggregatorContext(ctx, ms.Keeper) + agc := ms.Keeper.GetAggregatorContext(ctx) newItem, caches, err := agc.NewCreatePrice(ctx, msg) if err != nil { logger.Info("price proposal failed", "error", err, "height", ctx.BlockHeight(), "feederID", msg.FeederID) @@ -70,11 +70,11 @@ func (ms msgServer) CreatePrice(goCtx context.Context, msg *types.MsgCreatePrice sdk.NewAttribute(types.AttributeKeyPriceUpdated, types.AttributeValuePriceUpdatedSuccess)), ) if !ctx.IsCheckTx() { - cs.RemoveCache(caches) + ms.Keeper.GetCaches().RemoveCache(caches) } - AppendUpdatedFeederIDs(msg.FeederID) + ms.Keeper.AppendUpdatedFeederIDs(msg.FeederID) } else if !ctx.IsCheckTx() { - cs.AddCache(caches) + ms.Keeper.GetCaches().AddCache(caches) } return &types.MsgCreatePriceResponse{}, nil diff --git a/x/oracle/keeper/msg_server_create_price_test.go b/x/oracle/keeper/msg_server_create_price_test.go index 211fe907a..3b5b3121f 100644 --- a/x/oracle/keeper/msg_server_create_price_test.go +++ b/x/oracle/keeper/msg_server_create_price_test.go @@ -6,7 +6,6 @@ import ( math "cosmossdk.io/math" dogfoodkeeper "github.com/ExocoreNetwork/exocore/x/dogfood/keeper" dogfoodtypes "github.com/ExocoreNetwork/exocore/x/dogfood/types" - "github.com/ExocoreNetwork/exocore/x/oracle/keeper" "github.com/ExocoreNetwork/exocore/x/oracle/keeper/cache" "github.com/ExocoreNetwork/exocore/x/oracle/keeper/testdata" "github.com/ExocoreNetwork/exocore/x/oracle/types" @@ -45,7 +44,7 @@ var _ = Describe("MsgCreatePrice", func() { } }) - Expect(ks.ctx.BlockHeight()).To(Equal(int64(2))) + Expect(ks.ctx.BlockHeight()).To(Equal(int64(12))) }) AfterEach(func() { @@ -61,11 +60,12 @@ var _ = Describe("MsgCreatePrice", func() { Creator: ks.mockConsAddr1.String(), FeederID: 1, Prices: testdata.PS1, - BasedBlock: 1, + BasedBlock: 11, Nonce: 1, }) - c = keeper.GetCaches() + c = ks.App.OracleKeeper.GetCaches() + // c = ks.ms.Keeper.GetCaches() var pRes cache.ItemP c.GetCache(&pRes) p4Test := types.DefaultParams() @@ -82,7 +82,7 @@ var _ = Describe("MsgCreatePrice", func() { Creator: ks.mockConsAddr2.String(), FeederID: 1, Prices: testdata.PS2, - BasedBlock: 1, + BasedBlock: 11, Nonce: 1, }, ) @@ -94,7 +94,7 @@ var _ = Describe("MsgCreatePrice", func() { Creator: ks.mockConsAddr3.String(), FeederID: 1, Prices: testdata.PS4, - BasedBlock: 1, + BasedBlock: 11, Nonce: 1, }, ) @@ -103,13 +103,19 @@ var _ = Describe("MsgCreatePrice", func() { prices := ks.k.GetAllPrices(sdk.UnwrapSDKContext(ks.ctx)) Expect(prices[0]).Should(BeEquivalentTo(types.Prices{ TokenID: 1, - NextRoundID: 2, + NextRoundID: 3, PriceList: []*types.PriceTimeRound{ + { + Price: "1", + Decimal: 0, + Timestamp: "", + RoundID: 1, + }, { Price: testdata.PTD2.Price, Decimal: testdata.PTD2.Decimal, - Timestamp: prices[0].PriceList[0].Timestamp, - RoundID: 1, + Timestamp: prices[0].PriceList[1].Timestamp, + RoundID: 2, }, }, })) diff --git a/x/oracle/keeper/msg_server_update_params.go b/x/oracle/keeper/msg_server_update_params.go index 400dd7de8..87f35f2c4 100644 --- a/x/oracle/keeper/msg_server_update_params.go +++ b/x/oracle/keeper/msg_server_update_params.go @@ -66,7 +66,7 @@ func (ms msgServer) UpdateParams(goCtx context.Context, msg *types.MsgUpdatePara } // set updated new params ms.SetParams(ctx, p) - _ = GetAggregatorContext(ctx, ms.Keeper) - cs.AddCache(cache.ItemP(p)) + _ = ms.Keeper.GetAggregatorContext(ctx) + ms.Keeper.GetCaches().AddCache(cache.ItemP(p)) return &types.MsgUpdateParamsResponse{}, nil } diff --git a/x/oracle/keeper/msg_server_update_params_test.go b/x/oracle/keeper/msg_server_update_params_test.go index f4a09062c..6792d02e7 100644 --- a/x/oracle/keeper/msg_server_update_params_test.go +++ b/x/oracle/keeper/msg_server_update_params_test.go @@ -166,6 +166,7 @@ var _ = Describe("MsgUpdateParams", Ordered, func() { Context("update TokenFeeders", func() { It("update StartBaseBlock for TokenFeeder", func() { + ks.ctx = ks.ctx.WithBlockHeight(2) p := defaultParams p.TokenFeeders[1].StartBaseBlock = 10 ks.k.SetParams(ks.ctx, p) diff --git a/x/oracle/keeper/nonce.go b/x/oracle/keeper/nonce.go index dd2bc16be..16c6fb28d 100644 --- a/x/oracle/keeper/nonce.go +++ b/x/oracle/keeper/nonce.go @@ -2,6 +2,7 @@ package keeper import ( "errors" + "fmt" "github.com/ExocoreNetwork/exocore/x/oracle/keeper/common" "github.com/ExocoreNetwork/exocore/x/oracle/types" @@ -127,7 +128,9 @@ func (k Keeper) CheckAndIncreaseNonce(ctx sdk.Context, validator string, feederI } return 0, errors.New("feeder not found") } - return 0, errors.New("validator not found") + validatorset := k.GetAggregatorContext(ctx).GetValidators() + ctx.Logger().Debug("current validatorset:%v", validatorset) + return 0, fmt.Errorf("validator for the consKey which signed the create-price tx is not included in active validator set, signer consAddr:%s", validator) } // internal usage for avoiding duplicated 'NewStore' diff --git a/x/oracle/keeper/params.go b/x/oracle/keeper/params.go index 18db1ea69..9f7ccff5a 100644 --- a/x/oracle/keeper/params.go +++ b/x/oracle/keeper/params.go @@ -74,8 +74,8 @@ func (k Keeper) RegisterNewTokenAndSetTokenFeeder(ctx sdk.Context, oInfo *types. t.AssetID = strings.Join([]string{t.AssetID, oInfo.AssetID}, ",") k.SetParams(ctx, p) if !ctx.IsCheckTx() { - _ = GetAggregatorContext(ctx, k) - cs.AddCache(cache.ItemP(p)) + _ = k.GetAggregatorContext(ctx) + k.GetCaches().AddCache(cache.ItemP(p)) } // there should have been existing tokenFeeder running(currently we register tokens from assets-module and with infinite endBlock) return nil @@ -108,8 +108,8 @@ func (k Keeper) RegisterNewTokenAndSetTokenFeeder(ctx sdk.Context, oInfo *types. // skip cache update if this is not deliverTx // for normal cosmostx, checkTx will skip actual message exucution and do anteHandler only, but from ethc.callContract the message will be executed without anteHandler check as checkTx mode. if !ctx.IsCheckTx() { - _ = GetAggregatorContext(ctx, k) - cs.AddCache(cache.ItemP(p)) + _ = k.GetAggregatorContext(ctx) + k.GetCaches().AddCache(cache.ItemP(p)) } return nil } diff --git a/x/oracle/keeper/prices.go b/x/oracle/keeper/prices.go index 309054245..1c5e33a06 100644 --- a/x/oracle/keeper/prices.go +++ b/x/oracle/keeper/prices.go @@ -57,8 +57,8 @@ func (k Keeper) GetSpecifiedAssetsPrice(ctx sdk.Context, assetID string) (types. var p types.Params // get params from cache if exists - if agc != nil { - p = agc.GetParams() + if k.memStore.agc != nil { + p = k.memStore.agc.GetParams() } else { p = k.GetParams(ctx) } @@ -91,8 +91,8 @@ func (k Keeper) GetSpecifiedAssetsPrice(ctx sdk.Context, assetID string) (types. func (k Keeper) GetMultipleAssetsPrices(ctx sdk.Context, assets map[string]interface{}) (prices map[string]types.Price, err error) { var p types.Params // get params from cache if exists - if agc != nil { - p = agc.GetParams() + if k.memStore.agc != nil { + p = k.memStore.agc.GetParams() } else { p = k.GetParams(ctx) } @@ -201,7 +201,7 @@ func (k Keeper) AppendPriceTR(ctx sdk.Context, tokenID uint64, priceTR types.Pri store := k.getPriceTRStore(ctx, tokenID) b := k.cdc.MustMarshal(&priceTR) store.Set(types.PricesRoundKey(nextRoundID), b) - if expiredRoundID := nextRoundID - agc.GetParamsMaxSizePrices(); expiredRoundID > 0 { + if expiredRoundID := nextRoundID - k.memStore.agc.GetParamsMaxSizePrices(); expiredRoundID > 0 { store.Delete(types.PricesRoundKey(expiredRoundID)) } roundID := k.IncreaseNextRoundID(ctx, tokenID) @@ -210,8 +210,8 @@ func (k Keeper) AppendPriceTR(ctx sdk.Context, tokenID uint64, priceTR types.Pri // TODO: set hooks as a genral approach var p types.Params // get params from cache if exists - if agc != nil { - p = agc.GetParams() + if k.memStore.agc != nil { + p = k.memStore.agc.GetParams() } else { p = k.GetParams(ctx) } diff --git a/x/oracle/keeper/single.go b/x/oracle/keeper/single.go index c1f60e652..cf665cff3 100644 --- a/x/oracle/keeper/single.go +++ b/x/oracle/keeper/single.go @@ -11,58 +11,52 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -var updatedFeederIDs []string - -var cs *cache.Cache - -var agc, agcCheckTx *aggregator.AggregatorContext - -func GetCaches() *cache.Cache { - if cs != nil { - return cs +func (k *Keeper) GetCaches() *cache.Cache { + if k.memStore.cs != nil { + return k.memStore.cs } - cs = cache.NewCache() - return cs + k.memStore.cs = cache.NewCache() + return k.memStore.cs } // GetAggregatorContext returns singleton aggregatorContext used to calculate final price for each round of each tokenFeeder -func GetAggregatorContext(ctx sdk.Context, k Keeper) *aggregator.AggregatorContext { +func (k *Keeper) GetAggregatorContext(ctx sdk.Context) *aggregator.AggregatorContext { if ctx.IsCheckTx() { - if agcCheckTx != nil { - return agcCheckTx + if k.memStore.agcCheckTx != nil { + return k.memStore.agcCheckTx } - if agc == nil { - c := GetCaches() + if k.memStore.agc == nil { + c := k.GetCaches() c.ResetCaches() - agcCheckTx = aggregator.NewAggregatorContext() - if ok := recacheAggregatorContext(ctx, agcCheckTx, k, c); !ok { + k.memStore.agcCheckTx = aggregator.NewAggregatorContext() + if ok := k.recacheAggregatorContext(ctx, k.memStore.agcCheckTx, c); !ok { // this is the very first time oracle has been started, fill relalted info as initialization - initAggregatorContext(ctx, agcCheckTx, k, c) + initAggregatorContext(ctx, k.memStore.agcCheckTx, k, c) } - return agcCheckTx + return k.memStore.agcCheckTx } - agcCheckTx = agc.Copy4CheckTx() - return agcCheckTx + k.memStore.agcCheckTx = k.memStore.agc.Copy4CheckTx() + return k.memStore.agcCheckTx } - if agc != nil { - return agc + if k.memStore.agc != nil { + return k.memStore.agc } - c := GetCaches() + c := k.GetCaches() c.ResetCaches() - agc = aggregator.NewAggregatorContext() - if ok := recacheAggregatorContext(ctx, agc, k, c); !ok { + k.memStore.agc = aggregator.NewAggregatorContext() + if ok := k.recacheAggregatorContext(ctx, k.memStore.agc, c); !ok { // this is the very first time oracle has been started, fill relalted info as initialization - initAggregatorContext(ctx, agc, k, c) + initAggregatorContext(ctx, k.memStore.agc, k, c) } else { // this is when a node restart and use the persistent state to refill cache, we don't need to commit these data again c.SkipCommit() } - return agc + return k.memStore.agc } -func recacheAggregatorContext(ctx sdk.Context, agc *aggregator.AggregatorContext, k Keeper, c *cache.Cache) bool { +func (k Keeper) recacheAggregatorContext(ctx sdk.Context, agc *aggregator.AggregatorContext, c *cache.Cache) bool { logger := k.Logger(ctx) from := ctx.BlockHeight() - int64(common.MaxNonce) + 1 to := ctx.BlockHeight() @@ -163,7 +157,7 @@ func recacheAggregatorContext(ctx sdk.Context, agc *aggregator.AggregatorContext return true } -func initAggregatorContext(ctx sdk.Context, agc *aggregator.AggregatorContext, k common.KeeperOracle, c *cache.Cache) { +func initAggregatorContext(ctx sdk.Context, agc *aggregator.AggregatorContext, k *Keeper, c *cache.Cache) { ctx.Logger().Info("initAggregatorContext", "height", ctx.BlockHeight()) // set params p := k.GetParams(ctx) @@ -187,16 +181,16 @@ func initAggregatorContext(ctx sdk.Context, agc *aggregator.AggregatorContext, k agc.PrepareRoundEndBlock(uint64(ctx.BlockHeight()) - 1) } -func ResetAggregatorContext() { - agc = nil +func (k *Keeper) ResetAggregatorContext() { + k.memStore.agc = nil } -func ResetCache() { - cs = nil +func (k *Keeper) ResetCache() { + k.memStore.cs = nil } -func ResetAggregatorContextCheckTx() { - agcCheckTx = nil +func (k *Keeper) ResetAggregatorContextCheckTx() { + k.memStore.agcCheckTx = nil } func setCommonParams(p *types.Params) { @@ -207,16 +201,16 @@ func setCommonParams(p *types.Params) { common.Mode = p.Mode } -func ResetUpdatedFeederIDs() { - if updatedFeederIDs != nil { - updatedFeederIDs = nil +func (k *Keeper) ResetUpdatedFeederIDs() { + if k.memStore.updatedFeederIDs != nil { + k.memStore.updatedFeederIDs = nil } } -func GetUpdatedFeederIDs() []string { - return updatedFeederIDs +func (k Keeper) GetUpdatedFeederIDs() []string { + return k.memStore.updatedFeederIDs } -func AppendUpdatedFeederIDs(id uint64) { - updatedFeederIDs = append(updatedFeederIDs, strconv.FormatUint(id, 10)) +func (k *Keeper) AppendUpdatedFeederIDs(id uint64) { + k.memStore.updatedFeederIDs = append(k.memStore.updatedFeederIDs, strconv.FormatUint(id, 10)) } diff --git a/x/oracle/keeper/slashing.go b/x/oracle/keeper/slashing.go new file mode 100644 index 000000000..7c75f0c19 --- /dev/null +++ b/x/oracle/keeper/slashing.go @@ -0,0 +1,153 @@ +package keeper + +import ( + "encoding/binary" + "fmt" + "time" + + "github.com/ExocoreNetwork/exocore/x/oracle/types" + "github.com/cosmos/cosmos-sdk/store/prefix" + sdk "github.com/cosmos/cosmos-sdk/types" + gogotypes "github.com/cosmos/gogoproto/types" +) + +// InitValidatorReportInfo creates a new item for a first-seen validator to track their performance +func (k Keeper) InitValidatorReportInfo(ctx sdk.Context, validator string, height int64) { + store := ctx.KVStore(k.storeKey) + key := types.SlashingValidatorReportInfoKey(validator) + if !store.Has(key) { + // set the record for validator to tracking performance of oracle service + reportInfo := &types.ValidatorReportInfo{ + Address: validator, + StartHeight: height, + } + bz := k.cdc.MustMarshal(reportInfo) + store.Set(key, bz) + } +} + +// SetValidatorReportInfo sets the reporting info for a validator +func (k Keeper) SetValidatorReportInfo(ctx sdk.Context, validator string, info types.ValidatorReportInfo) { + store := ctx.KVStore(k.storeKey) + bz := k.cdc.MustMarshal(&info) + store.Set(types.SlashingValidatorReportInfoKey(validator), bz) +} + +// GetValidatorReportInfo returns the ValidatorReportInfo for a specific validator +func (k Keeper) GetValidatorReportInfo(ctx sdk.Context, validator string) (info types.ValidatorReportInfo, found bool) { + store := ctx.KVStore(k.storeKey) + bz := store.Get(types.SlashingValidatorReportInfoKey(validator)) + if bz == nil { + return + } + k.cdc.MustUnmarshal(bz, &info) + found = true + return +} + +// SetValidatorMissedRoundBitArray sets the bit that checks if the validator has +// missed a round to report price in the current window +func (k Keeper) SetValidatorMissedRoundBitArray(ctx sdk.Context, validator string, index uint64, missed bool) { + store := ctx.KVStore(k.storeKey) + bz := k.cdc.MustMarshal(&gogotypes.BoolValue{Value: missed}) + store.Set(types.SlashingMissedBitArrayKey(validator, index), bz) +} + +// GetValidatorMissedBlocks returns array of missed rounds for given validator +func (k Keeper) GetValidatorMissedRoundBitArray(ctx sdk.Context, validator string, index uint64) bool { + store := ctx.KVStore(k.storeKey) + bz := store.Get(types.SlashingMissedBitArrayKey(validator, index)) + if bz == nil { + return false + } + var missed gogotypes.BoolValue + k.cdc.MustUnmarshal(bz, &missed) + return missed.Value +} + +// GetReportedRoundsWindow returns the sliding window size for reporting slashing +func (k Keeper) GetReportedRoundsWindow(ctx sdk.Context) int64 { + return k.GetParams(ctx).Slashing.ReportedRoundsWindow +} + +// GetSlashFractionMiss fraction of power slashed for missed rounds +func (k Keeper) GetSlashFractionMiss(ctx sdk.Context) (res sdk.Dec) { + return k.GetParams(ctx).Slashing.SlashFractionMiss +} + +// GetSlashFractionMalicious fraction returns the fraction of power slashed for malicious behavior +func (k Keeper) GetSlashFractionMalicious(ctx sdk.Context) (res sdk.Dec) { + return k.GetParams(ctx).Slashing.SlashFractionMalicious +} + +// GetMinReportedPerWindow minimum blocks repored prices per window +func (k Keeper) GetMinReportedPerWindow(ctx sdk.Context) int64 { + params := k.GetParams(ctx) + reportedRoundsWindow := k.GetReportedRoundsWindow(ctx) + + // NOTE: RoundInt64 will never panic as minReportedPerWindow is + // less than 1. + return params.Slashing.MinReportedPerWindow.MulInt64(reportedRoundsWindow).RoundInt64() +} + +// GetMissJailDuration returns the jail duration for a validator who misses reports +func (k Keeper) GetMissJailDuration(ctx sdk.Context) (res time.Duration) { + return k.GetParams(ctx).Slashing.OracleMissJailDuration +} + +// GetMaliciousJailDuration returns the jail duration for malicious validator behavior +func (k Keeper) GetMaliciousJailDuration(ctx sdk.Context) (res time.Duration) { + return k.GetParams(ctx).Slashing.OracleMaliciousJailDuration +} + +// IterateValidatorReportedInfos iterates over the stored reportInfo +// and performs a callback function +func (k Keeper) IterateValidatorReportInfos(ctx sdk.Context, handler func(address string, reportInfo types.ValidatorReportInfo) (stop bool)) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), types.ValidatorReportInfoPrefix) + iterator := sdk.KVStorePrefixIterator(store, []byte{}) + for ; iterator.Valid(); iterator.Next() { + address := string(iterator.Key()) + var info types.ValidatorReportInfo + k.cdc.MustUnmarshal(iterator.Value(), &info) + if handler(address, info) { + break + } + } + iterator.Close() +} + +// IterateValidatorMissedRoundBitArrray iterates all missed rounds in one performance window of rounds +func (k Keeper) IterateValidatorMissedRoundBitArray(ctx sdk.Context, validator string, handler func(index int64, missed bool) (stop bool)) { + store := prefix.NewStore(ctx.KVStore(k.storeKey), types.SlashingMissedBitArrayPrefix(validator)) + iterator := sdk.KVStorePrefixIterator(store, []byte{}) + defer iterator.Close() + for ; iterator.Valid(); iterator.Next() { + index := binary.BigEndian.Uint64(iterator.Key()) + var missed gogotypes.BoolValue + if err := k.cdc.Unmarshal(iterator.Value(), &missed); err != nil { + panic(fmt.Sprintf("failed to unmarshal missed round: %v", err)) + } + if handler(int64(index), missed.Value) { + break + } + } +} + +func (k Keeper) GetValidatorMissedRounds(ctx sdk.Context, address string) []*types.MissedRound { + missedRounds := []*types.MissedRound{} + k.IterateValidatorMissedRoundBitArray(ctx, address, func(index int64, missed bool) (stop bool) { + missedRounds = append(missedRounds, types.NewMissedRound(index, missed)) + return false + }) + return missedRounds +} + +// ClearValidatorMissedBlockBitArray deletes every instance of ValidatorMissedBlockBitArray in the store +func (k Keeper) ClearValidatorMissedRoundBitArray(ctx sdk.Context, validator string) { + store := ctx.KVStore(k.storeKey) + iterator := sdk.KVStorePrefixIterator(store, types.SlashingMissedBitArrayPrefix(validator)) + defer iterator.Close() + for ; iterator.Valid(); iterator.Next() { + store.Delete(iterator.Key()) + } +} diff --git a/x/oracle/module.go b/x/oracle/module.go index 8e089d0b3..e76f141c4 100644 --- a/x/oracle/module.go +++ b/x/oracle/module.go @@ -24,6 +24,7 @@ import ( cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" ) var ( @@ -155,25 +156,34 @@ func (AppModule) ConsensusVersion() uint64 { return 1 } func (am AppModule) BeginBlock(ctx sdk.Context, _ abci.RequestBeginBlock) { // init caches and aggregatorContext for node restart // TODO: try better way to init caches and aggregatorContext than beginBlock - once.Do(func() { - _ = keeper.GetCaches() - _ = keeper.GetAggregatorContext(ctx, am.keeper) - }) + _ = am.keeper.GetCaches() + agc := am.keeper.GetAggregatorContext(ctx) + validatorPowers := agc.GetValidatorPowers() + // set validatorReportInfo to track performance + for validator := range validatorPowers { + am.keeper.InitValidatorReportInfo(ctx, validator, ctx.BlockHeight()) + } } // EndBlock contains the logic that is automatically triggered at the end of each block func (am AppModule) EndBlock(ctx sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { - cs := keeper.GetCaches() + cs := am.keeper.GetCaches() validatorUpdates := am.keeper.GetValidatorUpdates(ctx) forceSeal := false - agc := keeper.GetAggregatorContext(ctx, am.keeper) + agc := am.keeper.GetAggregatorContext(ctx) logger := am.keeper.Logger(ctx) + height := ctx.BlockHeight() if len(validatorUpdates) > 0 { validatorList := make(map[string]*big.Int) for _, vu := range validatorUpdates { pubKey, _ := cryptocodec.FromTmProtoPublicKey(vu.PubKey) - validatorList[sdk.ConsAddress(pubKey.Address()).String()] = big.NewInt(vu.Power) + validatorStr := sdk.ConsAddress(pubKey.Address()).String() + validatorList[validatorStr] = big.NewInt(vu.Power) + // add possible new added validator info for slashing tracking + if vu.Power > 0 { + am.keeper.InitValidatorReportInfo(ctx, validatorStr, height) + } } // update validator set information in cache cs.AddCache(cache.ItemV(validatorList)) @@ -183,21 +193,171 @@ func (am AppModule) EndBlock(ctx sdk.Context, _ abci.RequestEndBlock) []abci.Val agc.SetValidatorPowers(validatorPowers) // TODO: seal all alive round since validatorSet changed here forceSeal = true - logger.Info("validator set changed, force seal all active rounds", "height", ctx.BlockHeight()) + logger.Info("validator set changed, force seal all active rounds", "height", height) } // TODO: for v1 use mode==1, just check the failed feeders - _, failed, sealed := agc.SealRound(ctx, forceSeal) + _, failed, sealed, windowClosed := agc.SealRound(ctx, forceSeal) + defer func() { + for _, feederID := range windowClosed { + agc.RemoveWorker(feederID) + } + }() + // update&check slashing info + validatorPowers := agc.GetValidatorPowers() + for validator, power := range validatorPowers { + reportedInfo, found := am.keeper.GetValidatorReportInfo(ctx, validator) + if !found { + logger.Error(fmt.Sprintf("Expected report info for validator %s but not found", validator)) + continue + } + // TODO: for the round calculation, now only sourceID=1 is used so {feederID, sourceID} have only one value for each feederID which corresponding to one round. + // But when we came to multiple sources, we should consider the round corresponding to feedeerID instead of {feederID, sourceID} + for _, finalPrice := range agc.GetFinalPriceListForFeederIDs(windowClosed) { + exist, matched := agc.PerformanceReview(ctx, finalPrice, validator) + if exist && !matched { + // TODO: malicious price, just slash&jail immediately + logger.Info( + "confirmed malicious price", + "validator", validator, + "infraction_height", height, + "infraction_time", ctx.BlockTime(), + "feederID", finalPrice.FeederID, + "detID", finalPrice.DetID, + "sourceID", finalPrice.SourceID, + "finalPrice", finalPrice.Price, + ) + consAddr, err := sdk.ConsAddressFromBech32(validator) + if err != nil { + panic("invalid consAddr string") + } + + operator := am.keeper.ValidatorByConsAddr(ctx, consAddr) + if operator != nil && !operator.IsJailed() { + coinsBurned := am.keeper.SlashWithInfractionReason(ctx, consAddr, height, power.Int64(), am.keeper.GetSlashFractionMalicious(ctx), stakingtypes.Infraction_INFRACTION_UNSPECIFIED) + ctx.EventManager().EmitEvent( + sdk.NewEvent( + types.EventTypeOracleSlash, + sdk.NewAttribute(types.AttributeKeyValidatorKey, validator), + sdk.NewAttribute(types.AttributeKeyPower, fmt.Sprintf("%d", power)), + sdk.NewAttribute(types.AttributeKeyReason, types.AttributeValueMaliciousReportPrice), + sdk.NewAttribute(types.AttributeKeyJailed, validator), + sdk.NewAttribute(types.AttributeKeyBurnedCoins, coinsBurned.String()), + ), + ) + am.keeper.Jail(ctx, consAddr) + jailUntil := ctx.BlockHeader().Time.Add(am.keeper.GetMaliciousJailDuration(ctx)) + am.keeper.JailUntil(ctx, consAddr, jailUntil) + reportedInfo.MissedRoundsCounter = 0 + reportedInfo.IndexOffset = 0 + am.keeper.ClearValidatorMissedRoundBitArray(ctx, validator) + } + continue + } + + index := uint64(reportedInfo.IndexOffset % am.keeper.GetReportedRoundsWindow(ctx)) + reportedInfo.IndexOffset++ + // Update reported round bit array & counter + // This counter just tracks the sum of the bit array + // That way we avoid needing to read/write the whole array each time + previous := am.keeper.GetValidatorMissedRoundBitArray(ctx, validator, index) + missed := !exist + switch { + case !previous && missed: + // Array value has changed from not missed to missed, increment counter + am.keeper.SetValidatorMissedRoundBitArray(ctx, validator, index, true) + reportedInfo.MissedRoundsCounter++ + case previous && !missed: + // Array value has changed from missed to not missed, decrement counter + am.keeper.SetValidatorMissedRoundBitArray(ctx, validator, index, false) + reportedInfo.MissedRoundsCounter-- + default: + // Array value at this index has not changed, no need to update counter + } + minReportedPerWindow := am.keeper.GetMinReportedPerWindow(ctx) + + if missed { + ctx.EventManager().EmitEvent( + sdk.NewEvent( + types.EventTypeOracleLiveness, + sdk.NewAttribute(types.AttributeKeyValidatorKey, validator), + sdk.NewAttribute(types.AttributeKeyMissedRounds, fmt.Sprintf("%d", reportedInfo.MissedRoundsCounter)), + sdk.NewAttribute(types.AttributeKeyHeight, fmt.Sprintf("%d", height)), + ), + ) + + logger.Debug( + "absent validator", + "height", ctx.BlockHeight(), + "validator", validator, + "missed", reportedInfo.MissedRoundsCounter, + "threshold", minReportedPerWindow, + ) + } + + minHeight := reportedInfo.StartHeight + am.keeper.GetReportedRoundsWindow(ctx) + maxMissed := am.keeper.GetReportedRoundsWindow(ctx) - minReportedPerWindow + // if we are past the minimum height and the validator has missed too many rounds reporting prices, punish them + if height > minHeight && reportedInfo.MissedRoundsCounter > maxMissed { + consAddr, err := sdk.ConsAddressFromBech32(validator) + if err != nil { + panic("invalid consAddr string") + } + operator := am.keeper.ValidatorByConsAddr(ctx, consAddr) + if operator != nil && !operator.IsJailed() { + // missing rounds confirmed: slash and jail the validator + coinsBurned := am.keeper.SlashWithInfractionReason(ctx, consAddr, height, power.Int64(), am.keeper.GetSlashFractionMiss(ctx), stakingtypes.Infraction_INFRACTION_UNSPECIFIED) + ctx.EventManager().EmitEvent( + sdk.NewEvent( + types.EventTypeOracleSlash, + sdk.NewAttribute(types.AttributeKeyValidatorKey, validator), + sdk.NewAttribute(types.AttributeKeyPower, fmt.Sprintf("%d", power)), + sdk.NewAttribute(types.AttributeKeyReason, types.AttributeValueMissingReportPrice), + sdk.NewAttribute(types.AttributeKeyJailed, validator), + sdk.NewAttribute(types.AttributeKeyBurnedCoins, coinsBurned.String()), + ), + ) + am.keeper.Jail(ctx, consAddr) + jailUntil := ctx.BlockHeader().Time.Add(am.keeper.GetMissJailDuration(ctx)) + am.keeper.JailUntil(ctx, consAddr, jailUntil) + + // We need to reset the counter & array so that the validator won't be immediately slashed for miss report info upon rebonding. + reportedInfo.MissedRoundsCounter = 0 + reportedInfo.IndexOffset = 0 + am.keeper.ClearValidatorMissedRoundBitArray(ctx, validator) + + logger.Info( + "slashing and jailing validator due to liveness fault", + "height", height, + "validator", consAddr.String(), + "min_height", minHeight, + "threshold", minReportedPerWindow, + "slashed", am.keeper.GetSlashFractionMiss(ctx).String(), + "jailed_until", jailUntil, + ) + } else { + // validator was (a) not found or (b) already jailed so we do not slash + logger.Info( + "validator would have been slashed for too many missed repoerting price, but was either not found in store or already jailed", + "validator", validator, + ) + } + } + // Set the updated reportInfo + am.keeper.SetValidatorReportInfo(ctx, validator, reportedInfo) + } + } + for _, feederID := range sealed { am.keeper.RemoveNonceWithFeederIDForValidators(ctx, feederID, agc.GetValidators()) } - // append new round with previous price for fail-seal token + // append new round with previous price for fail-sealed token for _, tokenID := range failed { prevPrice, nextRoundID := am.keeper.GrowRoundID(ctx, tokenID) logger.Info("add new round with previous price under fail aggregation", "tokenID", tokenID, "roundID", nextRoundID, "price", prevPrice) } - keeper.ResetAggregatorContextCheckTx() + am.keeper.ResetAggregatorContextCheckTx() if _, _, paramsUpdated := cs.CommitCache(ctx, false, am.keeper); paramsUpdated { var p cache.ItemP @@ -210,14 +370,14 @@ func (am AppModule) EndBlock(ctx sdk.Context, _ abci.RequestEndBlock) []abci.Val )) } - if feederIDs := keeper.GetUpdatedFeederIDs(); len(feederIDs) > 0 { + if feederIDs := am.keeper.GetUpdatedFeederIDs(); len(feederIDs) > 0 { feederIDsStr := strings.Join(feederIDs, "_") ctx.EventManager().EmitEvent(sdk.NewEvent( types.EventTypeCreatePrice, sdk.NewAttribute(types.AttributeKeyPriceUpdated, types.AttributeValuePriceUpdatedSuccess), sdk.NewAttribute(types.AttributeKeyFeederIDs, feederIDsStr), )) - keeper.ResetUpdatedFeederIDs() + am.keeper.ResetUpdatedFeederIDs() } newRoundFeederIDs := agc.PrepareRoundEndBlock(uint64(ctx.BlockHeight())) diff --git a/x/oracle/types/events.go b/x/oracle/types/events.go index f8c61132f..3a0d42c2b 100644 --- a/x/oracle/types/events.go +++ b/x/oracle/types/events.go @@ -1,7 +1,9 @@ package types const ( - EventTypeCreatePrice = "create_price" + EventTypeCreatePrice = "create_price" + EventTypeOracleLiveness = "oracle_liveness" + EventTypeOracleSlash = "oracle_slash" AttributeKeyFeederID = "feeder_id" AttributeKeyTokenID = "token_id" @@ -14,10 +16,19 @@ const ( AttributeKeyFeederIDs = "feeder_ids" AttributeKeyNativeTokenUpdate = "native_token_update" AttributeKeyNativeTokenChange = "native_token_change" + AttributeKeyValidatorKey = "validator_key" + AttributeKeyMissedRounds = "missed_rounds" + AttributeKeyHeight = "height" + AttributeKeyPower = "power" + AttributeKeyReason = "reason" + AttributeKeyJailed = "jailed" + AttributeKeyBurnedCoins = "burned_coins" AttributeValuePriceUpdatedSuccess = "success" AttributeValueParamsUpdatedSuccess = "success" AttributeValueNativeTokenUpdate = "update" AttributeValueNativeTokenDeposit = "deposit" AttributeValueNativeTokenWithdraw = "withdraw" + AttributeValueMissingReportPrice = "missing_report_price" + AttributeValueMaliciousReportPrice = "malicious_report_price" ) diff --git a/x/oracle/types/expected_keepers.go b/x/oracle/types/expected_keepers.go index ee9c09b0f..d19ad53d0 100644 --- a/x/oracle/types/expected_keepers.go +++ b/x/oracle/types/expected_keepers.go @@ -1,6 +1,8 @@ package types import ( + time "time" + sdkmath "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth/types" @@ -26,3 +28,7 @@ type DelegationKeeper interface { type AssetsKeeper interface { GetAssetsDecimal(ctx sdk.Context, assets map[string]interface{}) (decimals map[string]uint32, err error) } + +type SlashingKeeper interface { + JailUntil(sdk.Context, sdk.ConsAddress, time.Time) +} diff --git a/x/oracle/types/genesis.go b/x/oracle/types/genesis.go index d1768b13e..46f4a0d57 100644 --- a/x/oracle/types/genesis.go +++ b/x/oracle/types/genesis.go @@ -35,6 +35,13 @@ func NewGenesisState(p Params) *GenesisState { } } +func NewMissedRound(index int64, missed bool) *MissedRound { + return &MissedRound{ + Index: index, + Missed: missed, + } +} + // Validate performs basic genesis state validation returning an error upon any // failure. func (gs GenesisState) Validate() error { diff --git a/x/oracle/types/genesis.pb.go b/x/oracle/types/genesis.pb.go index dbd1c437f..d17864e30 100644 --- a/x/oracle/types/genesis.pb.go +++ b/x/oracle/types/genesis.pb.go @@ -27,9 +27,10 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package type GenesisState struct { // module params Params Params `protobuf:"bytes,1,opt,name=params,proto3" json:"params"` - // prices of all tokens + // prices of all tokens including NST PricesList []Prices `protobuf:"bytes,2,rep,name=prices_list,json=pricesList,proto3" json:"prices_list"` //TODO: userDefinedTokenFeeder + // information for memory-cache recovery // latest block on which the validator set be updated ValidatorUpdateBlock *ValidatorUpdateBlock `protobuf:"bytes,3,opt,name=validator_update_block,json=validatorUpdateBlock,proto3" json:"validator_update_block,omitempty"` // index for the cached recent params @@ -40,10 +41,16 @@ type GenesisState struct { RecentMsgList []RecentMsg `protobuf:"bytes,6,rep,name=recent_msg_list,json=recentMsgList,proto3" json:"recent_msg_list"` // cached recent params RecentParamsList []RecentParams `protobuf:"bytes,7,rep,name=recent_params_list,json=recentParamsList,proto3" json:"recent_params_list"` + // information for NST related // stakerInfos for each nst token StakerInfosAssets []StakerInfosAssets `protobuf:"bytes,8,rep,name=staker_infos_assets,json=stakerInfosAssets,proto3" json:"staker_infos_assets"` // stakerList for each nst token StakerListAssets []StakerListAssets `protobuf:"bytes,9,rep,name=staker_list_assets,json=stakerListAssets,proto3" json:"staker_list_assets"` + // information for slashing history + // ValidatorReportInfo records all the validatorReportInfos + ValidatorReportInfos []ValidatorReportInfo `protobuf:"bytes,10,rep,name=validator_report_infos,json=validatorReportInfos,proto3" json:"validator_report_infos"` + // ValidatorMissedRounds records missedRounds for all validators seen + ValidatorMissedRounds []ValidatorMissedRounds `protobuf:"bytes,11,rep,name=validator_missed_rounds,json=validatorMissedRounds,proto3" json:"validator_missed_rounds"` } func (m *GenesisState) Reset() { *m = GenesisState{} } @@ -142,6 +149,20 @@ func (m *GenesisState) GetStakerListAssets() []StakerListAssets { return nil } +func (m *GenesisState) GetValidatorReportInfos() []ValidatorReportInfo { + if m != nil { + return m.ValidatorReportInfos + } + return nil +} + +func (m *GenesisState) GetValidatorMissedRounds() []ValidatorMissedRounds { + if m != nil { + return m.ValidatorMissedRounds + } + return nil +} + // stakerInfosAssets bond stakerinfos to their related assets id type StakerInfosAssets struct { // asset_id tells the assetid which the stakerInfos belong to @@ -252,51 +273,172 @@ func (m *StakerListAssets) GetStakerList() *StakerList { return nil } +// ValidatorMissedRounds record missed rounds indexes for a validator which consAddr corresponding to the address +type ValidatorMissedRounds struct { + // address of validator + Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + // missed_rounds tells how many rounds this validtor had missed for current windo + MissedRounds []*MissedRound `protobuf:"bytes,2,rep,name=missed_rounds,json=missedRounds,proto3" json:"missed_rounds,omitempty"` +} + +func (m *ValidatorMissedRounds) Reset() { *m = ValidatorMissedRounds{} } +func (m *ValidatorMissedRounds) String() string { return proto.CompactTextString(m) } +func (*ValidatorMissedRounds) ProtoMessage() {} +func (*ValidatorMissedRounds) Descriptor() ([]byte, []int) { + return fileDescriptor_6b68ac5b0c7f4305, []int{3} +} +func (m *ValidatorMissedRounds) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ValidatorMissedRounds) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ValidatorMissedRounds.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ValidatorMissedRounds) XXX_Merge(src proto.Message) { + xxx_messageInfo_ValidatorMissedRounds.Merge(m, src) +} +func (m *ValidatorMissedRounds) XXX_Size() int { + return m.Size() +} +func (m *ValidatorMissedRounds) XXX_DiscardUnknown() { + xxx_messageInfo_ValidatorMissedRounds.DiscardUnknown(m) +} + +var xxx_messageInfo_ValidatorMissedRounds proto.InternalMessageInfo + +func (m *ValidatorMissedRounds) GetAddress() string { + if m != nil { + return m.Address + } + return "" +} + +func (m *ValidatorMissedRounds) GetMissedRounds() []*MissedRound { + if m != nil { + return m.MissedRounds + } + return nil +} + +// MissedRound records if round with index is missed +type MissedRound struct { + // index of the round in current window + Index int64 `protobuf:"varint,1,opt,name=index,proto3" json:"index,omitempty"` + // if this round is missed + Missed bool `protobuf:"varint,2,opt,name=missed,proto3" json:"missed,omitempty"` +} + +func (m *MissedRound) Reset() { *m = MissedRound{} } +func (m *MissedRound) String() string { return proto.CompactTextString(m) } +func (*MissedRound) ProtoMessage() {} +func (*MissedRound) Descriptor() ([]byte, []int) { + return fileDescriptor_6b68ac5b0c7f4305, []int{4} +} +func (m *MissedRound) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MissedRound) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MissedRound.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MissedRound) XXX_Merge(src proto.Message) { + xxx_messageInfo_MissedRound.Merge(m, src) +} +func (m *MissedRound) XXX_Size() int { + return m.Size() +} +func (m *MissedRound) XXX_DiscardUnknown() { + xxx_messageInfo_MissedRound.DiscardUnknown(m) +} + +var xxx_messageInfo_MissedRound proto.InternalMessageInfo + +func (m *MissedRound) GetIndex() int64 { + if m != nil { + return m.Index + } + return 0 +} + +func (m *MissedRound) GetMissed() bool { + if m != nil { + return m.Missed + } + return false +} + func init() { proto.RegisterType((*GenesisState)(nil), "exocore.oracle.v1.GenesisState") proto.RegisterType((*StakerInfosAssets)(nil), "exocore.oracle.v1.StakerInfosAssets") proto.RegisterType((*StakerListAssets)(nil), "exocore.oracle.v1.StakerListAssets") + proto.RegisterType((*ValidatorMissedRounds)(nil), "exocore.oracle.v1.ValidatorMissedRounds") + proto.RegisterType((*MissedRound)(nil), "exocore.oracle.v1.MissedRound") } func init() { proto.RegisterFile("exocore/oracle/v1/genesis.proto", fileDescriptor_6b68ac5b0c7f4305) } var fileDescriptor_6b68ac5b0c7f4305 = []byte{ - // 555 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x94, 0x5b, 0x6f, 0xd3, 0x30, - 0x14, 0x80, 0xdb, 0xdd, 0xe7, 0x0e, 0x68, 0xbd, 0x09, 0x65, 0x15, 0x4b, 0x47, 0x19, 0x62, 0x12, - 0x52, 0xc2, 0xe0, 0x81, 0x37, 0x34, 0x2a, 0x21, 0xd4, 0x71, 0x11, 0x4a, 0xb9, 0x48, 0x93, 0x50, - 0xe4, 0xa6, 0x26, 0x58, 0x6d, 0xe3, 0xca, 0xf6, 0x4a, 0xf9, 0x17, 0xfc, 0xac, 0x3d, 0xee, 0x91, - 0x27, 0x04, 0xed, 0x1f, 0x41, 0x39, 0x76, 0x58, 0x73, 0x69, 0x79, 0x4b, 0x8e, 0xbf, 0xf3, 0xf9, - 0x9c, 0x63, 0xcb, 0xa8, 0x41, 0x27, 0x3c, 0xe0, 0x82, 0xba, 0x5c, 0x90, 0x60, 0x40, 0xdd, 0xf1, - 0x89, 0x1b, 0xd2, 0x88, 0x4a, 0x26, 0x9d, 0x91, 0xe0, 0x8a, 0xe3, 0x9a, 0x01, 0x1c, 0x0d, 0x38, - 0xe3, 0x93, 0xfa, 0x71, 0x3e, 0x87, 0x45, 0x3d, 0x3a, 0xf1, 0x05, 0x0d, 0x68, 0xa4, 0xfc, 0xa1, - 0x0c, 0x75, 0x72, 0xfd, 0xe1, 0x7f, 0xc8, 0x11, 0x11, 0x64, 0x68, 0x76, 0xaa, 0x1f, 0xe5, 0xe1, - 0x88, 0x28, 0x36, 0xa6, 0xbe, 0xe2, 0x7d, 0x1a, 0x19, 0xca, 0xce, 0x53, 0x29, 0x4b, 0xd1, 0xba, - 0x60, 0x01, 0x4d, 0xd6, 0x9b, 0xf9, 0xf5, 0x5c, 0xd9, 0xf7, 0x17, 0x32, 0xa9, 0xad, 0x9c, 0x3c, - 0x36, 0x26, 0x03, 0xd6, 0x23, 0x8a, 0x0b, 0xff, 0x62, 0xd4, 0x23, 0x8a, 0xfa, 0xdd, 0x01, 0x0f, - 0xfa, 0x86, 0xdf, 0x0b, 0x79, 0xc8, 0xe1, 0xd3, 0x8d, 0xbf, 0x74, 0xb4, 0xf9, 0x67, 0x1d, 0xed, - 0xbc, 0xd4, 0x23, 0xef, 0x28, 0xa2, 0x28, 0x7e, 0x8a, 0x36, 0xf4, 0x36, 0x56, 0xf9, 0xb0, 0x7c, - 0x5c, 0x79, 0xbc, 0xef, 0xe4, 0x8e, 0xc0, 0x79, 0x07, 0x40, 0x6b, 0xed, 0xf2, 0x57, 0xa3, 0xe4, - 0x19, 0x1c, 0x9f, 0xa2, 0x8a, 0x6e, 0xd5, 0x1f, 0x30, 0xa9, 0xac, 0x95, 0xc3, 0xd5, 0x45, 0xd9, - 0x40, 0x99, 0x6c, 0xa4, 0x73, 0x5e, 0x33, 0xa9, 0xf0, 0x67, 0x74, 0xbb, 0xb8, 0x03, 0x6b, 0x15, - 0x4a, 0x79, 0x50, 0x20, 0xfb, 0x98, 0x24, 0x7c, 0x00, 0xbe, 0x15, 0xe3, 0xde, 0xde, 0xb8, 0x20, - 0x8a, 0xdf, 0xa3, 0xdd, 0x82, 0xe3, 0xb7, 0xd6, 0xc0, 0x7d, 0x54, 0xe0, 0x6e, 0xc7, 0xb4, 0x07, - 0xb0, 0xee, 0xd8, 0xab, 0xb1, 0x6c, 0x08, 0xbf, 0x42, 0xd5, 0xec, 0xf5, 0xb3, 0xd6, 0x41, 0x79, - 0x77, 0xb9, 0xf2, 0x8d, 0x0c, 0xbd, 0x9b, 0x2c, 0xf5, 0x8f, 0xcf, 0xd0, 0xad, 0x6b, 0x8d, 0x9e, - 0xe3, 0x06, 0xcc, 0xf1, 0x4e, 0x81, 0xeb, 0x5f, 0x9a, 0x19, 0xe5, 0x0d, 0x91, 0x04, 0x60, 0x9a, - 0x1d, 0x84, 0x53, 0x8d, 0x6a, 0xdd, 0x26, 0xe8, 0x1a, 0x0b, 0x75, 0xa9, 0xa3, 0xad, 0x8a, 0xb9, - 0x18, 0x48, 0xcf, 0xd1, 0xae, 0x54, 0xa4, 0x4f, 0x85, 0xcf, 0xa2, 0x2f, 0x5c, 0xfa, 0x44, 0x4a, - 0xaa, 0xa4, 0xb5, 0x05, 0xd6, 0xa2, 0x19, 0x76, 0x80, 0x6e, 0xc7, 0xf0, 0x73, 0x60, 0x8d, 0xba, - 0x26, 0xb3, 0x0b, 0xf8, 0x13, 0xc2, 0xc6, 0x1d, 0x57, 0x9a, 0xa8, 0xb7, 0x41, 0x7d, 0x6f, 0xa1, - 0x3a, 0x2e, 0x2b, 0x65, 0xae, 0xca, 0x4c, 0xbc, 0x39, 0x42, 0xb5, 0x5c, 0x19, 0x78, 0x1f, 0x6d, - 0xc1, 0x0e, 0x3e, 0xeb, 0xc1, 0x4d, 0xdf, 0xf6, 0x36, 0xe1, 0xbf, 0xdd, 0xc3, 0xa7, 0x68, 0x67, - 0xbe, 0x49, 0x73, 0x95, 0x0f, 0x96, 0x76, 0xe7, 0x55, 0xe6, 0x1a, 0x6a, 0x0e, 0x51, 0x35, 0x5b, - 0xdd, 0xb2, 0x0d, 0x9f, 0xa1, 0xca, 0x5c, 0xe7, 0xd6, 0x0a, 0x5c, 0x9f, 0x83, 0xa5, 0x2d, 0x7b, - 0xe8, 0xba, 0xcd, 0xd6, 0xd9, 0xe5, 0xd4, 0x2e, 0x5f, 0x4d, 0xed, 0xf2, 0xef, 0xa9, 0x5d, 0xfe, - 0x31, 0xb3, 0x4b, 0x57, 0x33, 0xbb, 0xf4, 0x73, 0x66, 0x97, 0xce, 0x1f, 0x85, 0x4c, 0x7d, 0xbd, - 0xe8, 0x3a, 0x01, 0x1f, 0xba, 0x2f, 0xb4, 0xee, 0x2d, 0x55, 0xdf, 0xb8, 0xe8, 0xbb, 0xc9, 0xf3, - 0x31, 0x49, 0x1e, 0x10, 0xf5, 0x7d, 0x44, 0x65, 0x77, 0x03, 0xde, 0x85, 0x27, 0x7f, 0x03, 0x00, - 0x00, 0xff, 0xff, 0x17, 0x12, 0x12, 0xc9, 0x9b, 0x05, 0x00, 0x00, + // 691 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x94, 0xdd, 0x6e, 0xd3, 0x30, + 0x14, 0xc7, 0xdb, 0x7d, 0xb4, 0xdb, 0xe9, 0x06, 0xad, 0xf7, 0x41, 0x36, 0xb1, 0x6c, 0x94, 0x01, + 0x93, 0x90, 0x52, 0x06, 0x17, 0x5c, 0x20, 0xa1, 0x31, 0x84, 0xd0, 0x06, 0x43, 0x28, 0xe3, 0x43, + 0x9a, 0x84, 0xa2, 0xb4, 0xf1, 0x3a, 0xab, 0x6d, 0x5c, 0xd9, 0x5e, 0x18, 0x6f, 0xc1, 0x8b, 0xf0, + 0x1e, 0xbb, 0xdc, 0x25, 0x57, 0x08, 0xad, 0x2f, 0x82, 0x72, 0x9c, 0xac, 0x49, 0x93, 0x96, 0xbb, + 0xd8, 0xe7, 0x7f, 0x7e, 0xe7, 0xf8, 0x7f, 0xec, 0xc0, 0x26, 0xbd, 0xe0, 0x2d, 0x2e, 0x68, 0x83, + 0x0b, 0xb7, 0xd5, 0xa5, 0x8d, 0x60, 0xb7, 0xd1, 0xa6, 0x3e, 0x95, 0x4c, 0x5a, 0x7d, 0xc1, 0x15, + 0x27, 0xb5, 0x48, 0x60, 0x69, 0x81, 0x15, 0xec, 0xae, 0xef, 0x64, 0x73, 0x98, 0xef, 0xd1, 0x0b, + 0x47, 0xd0, 0x16, 0xf5, 0x95, 0xd3, 0x93, 0x6d, 0x9d, 0xbc, 0xfe, 0xf8, 0x3f, 0xca, 0xbe, 0x2b, + 0xdc, 0x5e, 0x54, 0x69, 0x7d, 0x3b, 0x2b, 0xf6, 0x5d, 0xc5, 0x02, 0xea, 0x28, 0xde, 0xa1, 0x7e, + 0xa4, 0x32, 0xb3, 0xaa, 0x14, 0x25, 0x2f, 0x2e, 0x58, 0x8b, 0xc6, 0xf1, 0x7a, 0x36, 0x9e, 0x69, + 0xfb, 0xc1, 0x58, 0x4d, 0xaa, 0xd4, 0x56, 0x56, 0x26, 0xbb, 0xae, 0x3c, 0x63, 0x7e, 0x0c, 0xb2, + 0xb2, 0x8a, 0xc0, 0xed, 0x32, 0xcf, 0x55, 0x5c, 0x38, 0xe7, 0x7d, 0xcf, 0x55, 0xd4, 0x69, 0x76, + 0x79, 0xab, 0x13, 0xe9, 0x97, 0xdb, 0xbc, 0xcd, 0xf1, 0xb3, 0x11, 0x7e, 0xe9, 0xdd, 0xfa, 0xaf, + 0x32, 0x2c, 0xbc, 0xd5, 0x43, 0x39, 0x56, 0xae, 0xa2, 0xe4, 0x39, 0x94, 0x74, 0x23, 0x46, 0x71, + 0xab, 0xb8, 0x53, 0x79, 0xba, 0x66, 0x65, 0x86, 0x64, 0x7d, 0x44, 0xc1, 0xfe, 0xcc, 0xe5, 0x9f, + 0xcd, 0x82, 0x1d, 0xc9, 0xc9, 0x1e, 0x54, 0xb4, 0x19, 0x4e, 0x97, 0x49, 0x65, 0x4c, 0x6d, 0x4d, + 0x8f, 0xcb, 0x46, 0x55, 0x94, 0x0d, 0x3a, 0xe7, 0x3d, 0x93, 0x8a, 0x7c, 0x83, 0xd5, 0xfc, 0x13, + 0x18, 0xd3, 0xd8, 0xca, 0xa3, 0x1c, 0xd8, 0x97, 0x38, 0xe1, 0x33, 0xea, 0xf7, 0x43, 0xb9, 0xbd, + 0x1c, 0xe4, 0xec, 0x92, 0x4f, 0xb0, 0x94, 0x73, 0x41, 0x8c, 0x19, 0x64, 0x6f, 0xe7, 0xb0, 0x0f, + 0x42, 0xb5, 0x8d, 0x62, 0x7d, 0x62, 0xbb, 0xc6, 0x46, 0xb7, 0xc8, 0x3b, 0xa8, 0x8e, 0x5e, 0x50, + 0x63, 0x16, 0x91, 0xf7, 0x26, 0x23, 0x8f, 0x64, 0xdb, 0xbe, 0xc5, 0x52, 0x6b, 0x72, 0x08, 0xb7, + 0x87, 0x18, 0xed, 0x63, 0x09, 0x7d, 0xbc, 0x9b, 0xc3, 0xba, 0x49, 0x8b, 0xac, 0x5c, 0x14, 0xf1, + 0x06, 0xba, 0x79, 0x0c, 0x24, 0x75, 0x50, 0x8d, 0x2b, 0x23, 0x6e, 0x73, 0x2c, 0x2e, 0x35, 0xda, + 0xaa, 0x48, 0xec, 0x21, 0xf4, 0x04, 0x96, 0xa4, 0x72, 0x3b, 0x54, 0x38, 0xcc, 0x3f, 0xe5, 0xd2, + 0x71, 0xa5, 0xa4, 0x4a, 0x1a, 0x73, 0x48, 0xcd, 0xf3, 0xf0, 0x18, 0xd5, 0x07, 0xa1, 0xf8, 0x15, + 0x6a, 0x23, 0x74, 0x4d, 0x8e, 0x06, 0xc8, 0x57, 0x20, 0x11, 0x3b, 0xec, 0x34, 0x46, 0xcf, 0x23, + 0xfa, 0xfe, 0x58, 0x74, 0xd8, 0x56, 0x8a, 0x5c, 0x95, 0x23, 0xfb, 0xa4, 0x99, 0xbc, 0x57, 0x82, + 0xf6, 0xb9, 0x50, 0xba, 0x7d, 0x03, 0x10, 0xfe, 0x70, 0xd2, 0xbd, 0xb2, 0x51, 0x1f, 0xf6, 0x19, + 0xf1, 0x87, 0x97, 0x6b, 0x18, 0x92, 0xe4, 0x14, 0xee, 0x0c, 0x6b, 0xf4, 0x98, 0x94, 0xd4, 0x73, + 0x04, 0x3f, 0xf7, 0x3d, 0x69, 0x54, 0xb0, 0xc8, 0xce, 0xa4, 0x22, 0x47, 0x98, 0x60, 0xa3, 0x3e, + 0x2a, 0xb3, 0x12, 0xe4, 0x05, 0xeb, 0x7d, 0xa8, 0x65, 0x2c, 0x25, 0x6b, 0x30, 0x87, 0x6e, 0x39, + 0xcc, 0xc3, 0x57, 0x3b, 0x6f, 0x97, 0x71, 0x7d, 0xe0, 0x91, 0x3d, 0x58, 0x48, 0x0e, 0x2c, 0x7a, + 0x96, 0x1b, 0x13, 0x27, 0x65, 0x57, 0x12, 0xc3, 0xa9, 0xf7, 0xa0, 0x3a, 0xea, 0xf4, 0xa4, 0x82, + 0x2f, 0xa1, 0x92, 0x98, 0xa2, 0x31, 0x85, 0x4f, 0x61, 0x63, 0xe2, 0xf8, 0x6c, 0x18, 0x8e, 0xac, + 0x1e, 0xc0, 0x4a, 0xae, 0x2d, 0xc4, 0x80, 0xb2, 0xeb, 0x79, 0x82, 0x4a, 0x79, 0x53, 0x52, 0x2f, + 0xc9, 0x6b, 0x58, 0x4c, 0x3b, 0xae, 0x0f, 0x69, 0xe6, 0x14, 0x4d, 0x10, 0xed, 0x85, 0x5e, 0xd2, + 0xd8, 0x17, 0x50, 0x49, 0x04, 0xc9, 0x32, 0xcc, 0xe2, 0xdb, 0xc4, 0x5a, 0xd3, 0xb6, 0x5e, 0x90, + 0x55, 0x28, 0xe9, 0x24, 0x3c, 0xd7, 0x9c, 0x1d, 0xad, 0xf6, 0x0f, 0x2f, 0xaf, 0xcd, 0xe2, 0xd5, + 0xb5, 0x59, 0xfc, 0x7b, 0x6d, 0x16, 0x7f, 0x0e, 0xcc, 0xc2, 0xd5, 0xc0, 0x2c, 0xfc, 0x1e, 0x98, + 0x85, 0x93, 0x27, 0x6d, 0xa6, 0xce, 0xce, 0x9b, 0x56, 0x8b, 0xf7, 0x1a, 0x6f, 0x74, 0x3b, 0x1f, + 0xa8, 0xfa, 0xce, 0x45, 0xa7, 0x11, 0xff, 0xbf, 0x2f, 0xe2, 0x3f, 0xb8, 0xfa, 0xd1, 0xa7, 0xb2, + 0x59, 0xc2, 0x1f, 0xf3, 0xb3, 0x7f, 0x01, 0x00, 0x00, 0xff, 0xff, 0xa7, 0xe5, 0x58, 0x7c, 0x3e, + 0x07, 0x00, 0x00, } func (m *GenesisState) Marshal() (dAtA []byte, err error) { @@ -319,6 +461,34 @@ func (m *GenesisState) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if len(m.ValidatorMissedRounds) > 0 { + for iNdEx := len(m.ValidatorMissedRounds) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.ValidatorMissedRounds[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x5a + } + } + if len(m.ValidatorReportInfos) > 0 { + for iNdEx := len(m.ValidatorReportInfos) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.ValidatorReportInfos[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x52 + } + } if len(m.StakerListAssets) > 0 { for iNdEx := len(m.StakerListAssets) - 1; iNdEx >= 0; iNdEx-- { { @@ -524,6 +694,88 @@ func (m *StakerListAssets) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *ValidatorMissedRounds) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ValidatorMissedRounds) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ValidatorMissedRounds) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.MissedRounds) > 0 { + for iNdEx := len(m.MissedRounds) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.MissedRounds[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } + if len(m.Address) > 0 { + i -= len(m.Address) + copy(dAtA[i:], m.Address) + i = encodeVarintGenesis(dAtA, i, uint64(len(m.Address))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MissedRound) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MissedRound) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MissedRound) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Missed { + i-- + if m.Missed { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x10 + } + if m.Index != 0 { + i = encodeVarintGenesis(dAtA, i, uint64(m.Index)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + func encodeVarintGenesis(dAtA []byte, offset int, v uint64) int { offset -= sovGenesis(v) base := offset @@ -585,6 +837,18 @@ func (m *GenesisState) Size() (n int) { n += 1 + l + sovGenesis(uint64(l)) } } + if len(m.ValidatorReportInfos) > 0 { + for _, e := range m.ValidatorReportInfos { + l = e.Size() + n += 1 + l + sovGenesis(uint64(l)) + } + } + if len(m.ValidatorMissedRounds) > 0 { + for _, e := range m.ValidatorMissedRounds { + l = e.Size() + n += 1 + l + sovGenesis(uint64(l)) + } + } return n } @@ -624,6 +888,40 @@ func (m *StakerListAssets) Size() (n int) { return n } +func (m *ValidatorMissedRounds) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Address) + if l > 0 { + n += 1 + l + sovGenesis(uint64(l)) + } + if len(m.MissedRounds) > 0 { + for _, e := range m.MissedRounds { + l = e.Size() + n += 1 + l + sovGenesis(uint64(l)) + } + } + return n +} + +func (m *MissedRound) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Index != 0 { + n += 1 + sovGenesis(uint64(m.Index)) + } + if m.Missed { + n += 2 + } + return n +} + func sovGenesis(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -970,6 +1268,74 @@ func (m *GenesisState) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 10: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ValidatorReportInfos", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ValidatorReportInfos = append(m.ValidatorReportInfos, ValidatorReportInfo{}) + if err := m.ValidatorReportInfos[len(m.ValidatorReportInfos)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ValidatorMissedRounds", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ValidatorMissedRounds = append(m.ValidatorMissedRounds, ValidatorMissedRounds{}) + if err := m.ValidatorMissedRounds[len(m.ValidatorMissedRounds)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenesis(dAtA[iNdEx:]) @@ -1225,6 +1591,211 @@ func (m *StakerListAssets) Unmarshal(dAtA []byte) error { } return nil } +func (m *ValidatorMissedRounds) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ValidatorMissedRounds: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ValidatorMissedRounds: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Address", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Address = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field MissedRounds", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.MissedRounds = append(m.MissedRounds, &MissedRound{}) + if err := m.MissedRounds[len(m.MissedRounds)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenesis(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenesis + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MissedRound) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MissedRound: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MissedRound: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Index", wireType) + } + m.Index = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Index |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Missed", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Missed = bool(v != 0) + default: + iNdEx = preIndex + skippy, err := skipGenesis(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenesis + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipGenesis(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/x/oracle/types/genesis_test.go b/x/oracle/types/genesis_test.go index 76a7843fc..bf73106b9 100644 --- a/x/oracle/types/genesis_test.go +++ b/x/oracle/types/genesis_test.go @@ -2,7 +2,9 @@ package types_test import ( "testing" + "time" + sdkmath "cosmossdk.io/math" "github.com/ExocoreNetwork/exocore/x/oracle/types" "github.com/stretchr/testify/require" ) @@ -55,6 +57,14 @@ func TestGenesisState_Validate(t *testing.T) { Mode: types.ConsensusModeASAP, MaxDetId: 5, MaxSizePrices: 100, + Slashing: &types.SlashingParams{ + ReportedRoundsWindow: 100, + MinReportedPerWindow: sdkmath.LegacyNewDec(1).Quo(sdkmath.LegacyNewDec(2)), + OracleMissJailDuration: 600 * time.Second, + OracleMaliciousJailDuration: 30 * 24 * time.Hour, + SlashFractionMiss: sdkmath.LegacyNewDec(1).Quo(sdkmath.LegacyNewDec(20)), + SlashFractionMalicious: sdkmath.LegacyNewDec(1).Quo(sdkmath.LegacyNewDec(10)), + }, }, // this line is used by starport scaffolding # types/genesis/validField }, @@ -151,7 +161,7 @@ func TestGenesisState_Validate(t *testing.T) { valid: false, }, { - desc: "valid", + desc: "invalid", genState: &types.GenesisState{ StakerListAssets: []types.StakerListAssets{ { diff --git a/x/oracle/types/key_slashing.go b/x/oracle/types/key_slashing.go new file mode 100644 index 000000000..ec5f13ee2 --- /dev/null +++ b/x/oracle/types/key_slashing.go @@ -0,0 +1,20 @@ +package types + +var ( + SlashingPrefix = []byte("Slashing/") + ValidatorReportInfoPrefix = append(SlashingPrefix, []byte("validator/value/")...) + MissedBitArrayPrefix = append(SlashingPrefix, []byte("missed/value/")...) +) + +func SlashingValidatorReportInfoKey(validator string) []byte { + return append(ValidatorReportInfoPrefix, []byte(validator)...) +} + +func SlashingMissedBitArrayPrefix(validator string) []byte { + key := append([]byte(validator), DelimiterForCombinedKey) + return append(MissedBitArrayPrefix, key...) +} + +func SlashingMissedBitArrayKey(validator string, index uint64) []byte { + return append(SlashingMissedBitArrayPrefix(validator), Uint64Bytes(index)...) +} diff --git a/x/oracle/types/params.go b/x/oracle/types/params.go index 71b148610..2cb7fbcd6 100644 --- a/x/oracle/types/params.go +++ b/x/oracle/types/params.go @@ -3,7 +3,9 @@ package types import ( "errors" "strings" + "time" + sdkmath "cosmossdk.io/math" paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" "gopkg.in/yaml.v2" ) @@ -14,6 +16,7 @@ var ( KeySources = []byte("Sources") KeyRules = []byte("Rules") KeyTokenFeeders = []byte("TokenFeeders") + oneDec = sdkmath.LegacyNewDec(1) ) var _ paramtypes.ParamSet = (*Params)(nil) @@ -103,6 +106,14 @@ func DefaultParams() Params { Mode: ConsensusModeASAP, MaxDetId: 5, MaxSizePrices: 100, + Slashing: &SlashingParams{ + ReportedRoundsWindow: 30, + MinReportedPerWindow: sdkmath.LegacyNewDec(1).Quo(sdkmath.LegacyNewDec(2)), + OracleMissJailDuration: 600 * time.Second, + OracleMaliciousJailDuration: 30 * 24 * time.Hour, + SlashFractionMiss: sdkmath.LegacyNewDec(1).Quo(sdkmath.LegacyNewDec(20)), + SlashFractionMalicious: sdkmath.LegacyNewDec(1).Quo(sdkmath.LegacyNewDec(10)), + }, } } @@ -129,6 +140,31 @@ func (p Params) Validate() error { return ErrInvalidParams.Wrapf("invalid maxNonce/maxDetID/Threshold/Mode/MaxSizePrices: %d, %d, %d, %d, %d, %d", p.MaxNonce, p.MaxDetId, p.ThresholdA, p.ThresholdB, p.Mode, p.MaxSizePrices) } + slashing := p.Slashing + + if slashing == nil { + return ErrInvalidParams.Wrap("slashing params must not be nil") + } + + if slashing.ReportedRoundsWindow < 1 { + return ErrInvalidParams.Wrapf("ReportedRoundsWindow must be at least 1, got %d", slashing.ReportedRoundsWindow) + } + if slashing.MinReportedPerWindow.GT(oneDec) || !slashing.MinReportedPerWindow.IsPositive() { + return ErrInvalidParams.Wrapf("MinReportedPerWindow must be in (0, 1], got %v", slashing.MinReportedPerWindow) + } + if slashing.SlashFractionMiss.GT(oneDec) || !slashing.SlashFractionMiss.IsPositive() { + return ErrInvalidParams.Wrapf("SlashFractionMiss must be in (0, 1], got %v", slashing.SlashFractionMiss) + } + if slashing.SlashFractionMalicious.GT(oneDec) || !slashing.SlashFractionMalicious.IsPositive() { + return ErrInvalidParams.Wrapf("SlashFractionMalicious must be in (0, 1], got %v", slashing.SlashFractionMalicious) + } + if slashing.OracleMissJailDuration <= 0 { + return ErrInvalidParams.Wrapf("OracleMissJailDuration must be positive, got %v", slashing.OracleMissJailDuration) + } + if slashing.OracleMaliciousJailDuration <= 0 { + return ErrInvalidParams.Wrapf("OracleMaliciousJailDuration must be positive, got %v", slashing.OracleMaliciousJailDuration) + } + // validate tokenFeeders feeders := make(map[uint64]*TokenFeeder) for fID, feeder := range p.TokenFeeders { diff --git a/x/oracle/types/params.pb.go b/x/oracle/types/params.pb.go index ced645ef2..e1a20d74b 100644 --- a/x/oracle/types/params.pb.go +++ b/x/oracle/types/params.pb.go @@ -5,17 +5,25 @@ package types import ( fmt "fmt" + _ "github.com/cosmos/cosmos-proto" + github_com_cosmos_cosmos_sdk_types "github.com/cosmos/cosmos-sdk/types" + _ "github.com/cosmos/cosmos-sdk/types/tx/amino" _ "github.com/cosmos/gogoproto/gogoproto" proto "github.com/cosmos/gogoproto/proto" + github_com_cosmos_gogoproto_types "github.com/cosmos/gogoproto/types" + _ "google.golang.org/protobuf/types/known/durationpb" + _ "google.golang.org/protobuf/types/known/timestamppb" io "io" math "math" math_bits "math/bits" + time "time" ) // Reference imports to suppress errors if they are not otherwise used. var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf +var _ = time.Kitchen // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. @@ -76,6 +84,8 @@ type Params struct { MaxDetId int32 `protobuf:"varint,10,opt,name=max_det_id,json=maxDetId,proto3" json:"max_det_id,omitempty"` // for each token, only keep max_size_prices round of prices MaxSizePrices int32 `protobuf:"varint,11,opt,name=max_size_prices,json=maxSizePrices,proto3" json:"max_size_prices,omitempty"` + // slashing defines the slashing related params + Slashing *SlashingParams `protobuf:"bytes,12,opt,name=slashing,proto3" json:"slashing,omitempty"` } func (m *Params) Reset() { *m = Params{} } @@ -187,47 +197,144 @@ func (m *Params) GetMaxSizePrices() int32 { return 0 } +func (m *Params) GetSlashing() *SlashingParams { + if m != nil { + return m.Slashing + } + return nil +} + +// slashing related params +type SlashingParams struct { + // reported_rounds_window defines how many rounds included in one window for performance review of missing report + ReportedRoundsWindow int64 `protobuf:"varint,1,opt,name=reported_rounds_window,json=reportedRoundsWindow,proto3" json:"reported_rounds_window,omitempty"` + // min_reported_perwindow defines at least how many rounds should be reported, this is a percentage of window + MinReportedPerWindow github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,2,opt,name=min_reported_per_window,json=minReportedPerWindow,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"min_reported_per_window"` + // oracle_miss_jail_duraion defines the duration one validator should be jailed for missing reporting price + OracleMissJailDuration time.Duration `protobuf:"bytes,3,opt,name=oracle_miss_jail_duration,json=oracleMissJailDuration,proto3,stdduration" json:"oracle_miss_jail_duration"` + // oracle_malicious_jail_duration defines the duratin one validator should be jailed for malicious behavior + OracleMaliciousJailDuration time.Duration `protobuf:"bytes,4,opt,name=oracle_malicious_jail_duration,json=oracleMaliciousJailDuration,proto3,stdduration" json:"oracle_malicious_jail_duration"` + // slash_fraction_miss defines the fraction one validator should be punished for msissing reporting price + SlashFractionMiss github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,5,opt,name=slash_fraction_miss,json=slashFractionMiss,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"slash_fraction_miss"` + // slash_fraction_miss defines the fractino one validator should be punished for maliciours behavior + SlashFractionMalicious github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,6,opt,name=slash_fraction_malicious,json=slashFractionMalicious,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"slash_fraction_malicious"` +} + +func (m *SlashingParams) Reset() { *m = SlashingParams{} } +func (m *SlashingParams) String() string { return proto.CompactTextString(m) } +func (*SlashingParams) ProtoMessage() {} +func (*SlashingParams) Descriptor() ([]byte, []int) { + return fileDescriptor_72f39bba4594b794, []int{1} +} +func (m *SlashingParams) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *SlashingParams) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_SlashingParams.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *SlashingParams) XXX_Merge(src proto.Message) { + xxx_messageInfo_SlashingParams.Merge(m, src) +} +func (m *SlashingParams) XXX_Size() int { + return m.Size() +} +func (m *SlashingParams) XXX_DiscardUnknown() { + xxx_messageInfo_SlashingParams.DiscardUnknown(m) +} + +var xxx_messageInfo_SlashingParams proto.InternalMessageInfo + +func (m *SlashingParams) GetReportedRoundsWindow() int64 { + if m != nil { + return m.ReportedRoundsWindow + } + return 0 +} + +func (m *SlashingParams) GetOracleMissJailDuration() time.Duration { + if m != nil { + return m.OracleMissJailDuration + } + return 0 +} + +func (m *SlashingParams) GetOracleMaliciousJailDuration() time.Duration { + if m != nil { + return m.OracleMaliciousJailDuration + } + return 0 +} + func init() { proto.RegisterEnum("exocore.oracle.v1.ConsensusMode", ConsensusMode_name, ConsensusMode_value) proto.RegisterType((*Params)(nil), "exocore.oracle.v1.Params") + proto.RegisterType((*SlashingParams)(nil), "exocore.oracle.v1.SlashingParams") } func init() { proto.RegisterFile("exocore/oracle/v1/params.proto", fileDescriptor_72f39bba4594b794) } var fileDescriptor_72f39bba4594b794 = []byte{ - // 511 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x92, 0xbf, 0x6f, 0xd3, 0x40, - 0x14, 0xc7, 0x6d, 0xf2, 0xa3, 0xed, 0x85, 0x40, 0x7b, 0x80, 0x74, 0x98, 0xe0, 0x5a, 0x08, 0xa1, - 0x88, 0xc1, 0x6e, 0x1b, 0x26, 0xc4, 0x92, 0x26, 0xa9, 0x14, 0xa4, 0xa6, 0x91, 0x4d, 0x16, 0x16, - 0xcb, 0xb1, 0x5f, 0x12, 0xab, 0xb1, 0x2f, 0xba, 0x73, 0x8a, 0xe9, 0xca, 0x82, 0x3a, 0x31, 0xb2, - 0x54, 0x42, 0xe2, 0x9f, 0x61, 0xec, 0xc8, 0x88, 0x92, 0x7f, 0x83, 0x01, 0xf9, 0xec, 0x94, 0xa6, - 0x49, 0xb7, 0xbb, 0xf7, 0xfd, 0x7c, 0xf4, 0x9e, 0x9e, 0x1e, 0x52, 0x21, 0xa6, 0x2e, 0x65, 0x60, - 0x50, 0xe6, 0xb8, 0x63, 0x30, 0xce, 0xf6, 0x8d, 0x89, 0xc3, 0x9c, 0x80, 0xeb, 0x13, 0x46, 0x23, - 0x8a, 0x77, 0xb2, 0x5c, 0x4f, 0x73, 0xfd, 0x6c, 0x5f, 0xa9, 0xac, 0x2a, 0x7e, 0x38, 0xa0, 0xa9, - 0xa0, 0xbc, 0x5c, 0x4d, 0x23, 0x7a, 0x0a, 0xa1, 0x3d, 0x00, 0xf0, 0x80, 0x65, 0xd4, 0xe3, 0x21, - 0x1d, 0x52, 0xf1, 0x34, 0x92, 0x57, 0x5a, 0x7d, 0xf1, 0x37, 0x87, 0x8a, 0x5d, 0xd1, 0x1d, 0xef, - 0xa1, 0xa2, 0x3b, 0x72, 0xfc, 0x90, 0x13, 0x59, 0xcb, 0x55, 0x4b, 0x07, 0x44, 0x5f, 0x19, 0x44, - 0x6f, 0x24, 0x80, 0x99, 0x71, 0x89, 0x21, 0x1a, 0x71, 0x72, 0xef, 0x4e, 0xe3, 0x43, 0x02, 0x98, - 0x19, 0x87, 0x6b, 0x68, 0x83, 0xd3, 0x29, 0x73, 0x81, 0x93, 0x9c, 0x50, 0x9e, 0xae, 0x51, 0x2c, - 0x41, 0x98, 0x0b, 0x12, 0xd7, 0x50, 0x81, 0x4d, 0xc7, 0xc0, 0x49, 0x5e, 0x28, 0xcf, 0xd7, 0x28, - 0xe6, 0x74, 0x0c, 0x99, 0x96, 0xb2, 0xb8, 0x81, 0xca, 0x37, 0x97, 0xc0, 0x49, 0x41, 0xc8, 0xea, - 0x5d, 0x23, 0x1e, 0x09, 0xcc, 0xbc, 0x1f, 0xfd, 0xff, 0x70, 0xfc, 0x0c, 0x6d, 0x05, 0x4e, 0x6c, - 0x87, 0x34, 0x74, 0x81, 0x14, 0x35, 0xb9, 0x5a, 0x30, 0x37, 0x03, 0x27, 0xee, 0x24, 0x7f, 0xbc, - 0x8b, 0x4a, 0xd1, 0x88, 0x01, 0x1f, 0xd1, 0xb1, 0x67, 0x3b, 0x64, 0x43, 0xc4, 0xe8, 0xba, 0x54, - 0x5f, 0x06, 0xfa, 0x64, 0xf3, 0x16, 0x70, 0x88, 0xdf, 0xa0, 0x7c, 0x40, 0x3d, 0x20, 0x5b, 0x9a, - 0x5c, 0x7d, 0x70, 0xa0, 0xad, 0xdb, 0x37, 0x0d, 0x39, 0x84, 0x7c, 0xca, 0x8f, 0xa9, 0x07, 0xa6, - 0xa0, 0x71, 0x05, 0xa1, 0x64, 0x28, 0x0f, 0x22, 0xdb, 0xf7, 0x08, 0xba, 0x9e, 0xaa, 0x09, 0x51, - 0xdb, 0xc3, 0xaf, 0xd0, 0xc3, 0x24, 0xe5, 0xfe, 0x39, 0xd8, 0x13, 0xe6, 0x27, 0x9b, 0x2e, 0x09, - 0xa4, 0x1c, 0x38, 0xb1, 0xe5, 0x9f, 0x43, 0x57, 0x14, 0xdf, 0xe6, 0xbf, 0xff, 0xd8, 0x95, 0x5e, - 0x7f, 0x91, 0x51, 0x79, 0xa9, 0x07, 0x7e, 0x87, 0x94, 0xc6, 0x49, 0xc7, 0x6a, 0x75, 0xac, 0x9e, - 0x65, 0x1f, 0x9f, 0x34, 0x5b, 0x76, 0xaf, 0x63, 0x75, 0x5b, 0x8d, 0xf6, 0x51, 0xbb, 0xd5, 0xdc, - 0x96, 0x94, 0xca, 0xc5, 0xa5, 0x46, 0x96, 0x94, 0x5e, 0xc8, 0x27, 0xe0, 0xfa, 0x03, 0x1f, 0x3c, - 0xac, 0xa3, 0x47, 0xb7, 0xec, 0xba, 0x55, 0xef, 0x6e, 0xcb, 0xca, 0x93, 0x8b, 0x4b, 0x6d, 0x67, - 0x49, 0x4b, 0x02, 0x25, 0xff, 0xf5, 0xa7, 0x2a, 0x1d, 0xbe, 0xff, 0x35, 0x53, 0xe5, 0xab, 0x99, - 0x2a, 0xff, 0x99, 0xa9, 0xf2, 0xb7, 0xb9, 0x2a, 0x5d, 0xcd, 0x55, 0xe9, 0xf7, 0x5c, 0x95, 0x3e, - 0xee, 0x0d, 0xfd, 0x68, 0x34, 0xed, 0xeb, 0x2e, 0x0d, 0x8c, 0x56, 0xba, 0x9d, 0x0e, 0x44, 0x9f, - 0x28, 0x3b, 0x35, 0x16, 0x47, 0x1f, 0x2f, 0xce, 0x3e, 0xfa, 0x3c, 0x01, 0xde, 0x2f, 0x8a, 0xbb, - 0xae, 0xfd, 0x0b, 0x00, 0x00, 0xff, 0xff, 0xf5, 0xbe, 0x10, 0x4f, 0x66, 0x03, 0x00, 0x00, + // 812 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x54, 0xbf, 0x6f, 0xdb, 0x46, + 0x14, 0x16, 0xa3, 0x1f, 0x71, 0x4e, 0x56, 0x1a, 0x5d, 0x5c, 0xf7, 0xa4, 0xb8, 0x14, 0x1b, 0x14, + 0x81, 0x10, 0xa0, 0x64, 0x62, 0x65, 0x2a, 0xda, 0xc1, 0xfa, 0x61, 0xc0, 0x01, 0xac, 0x08, 0x54, + 0x8d, 0x02, 0x5d, 0x88, 0x13, 0x79, 0x92, 0x2e, 0x26, 0x79, 0xc4, 0x1d, 0x19, 0xab, 0x59, 0xbb, + 0x14, 0x99, 0x3a, 0x66, 0x09, 0x50, 0xa0, 0x4b, 0xc7, 0xee, 0xfd, 0x07, 0x32, 0x66, 0x2c, 0x32, + 0xa4, 0x85, 0x3d, 0xf4, 0xdf, 0x28, 0x78, 0x3c, 0xaa, 0x91, 0xac, 0x0c, 0xf1, 0x22, 0xf1, 0xdd, + 0xf7, 0x7d, 0xef, 0x7b, 0xef, 0x1d, 0x1f, 0x81, 0x4e, 0x16, 0xcc, 0x65, 0x9c, 0x58, 0x8c, 0x63, + 0xd7, 0x27, 0xd6, 0xb3, 0x87, 0x56, 0x84, 0x39, 0x0e, 0x84, 0x19, 0x71, 0x16, 0x33, 0x58, 0x57, + 0xb8, 0x99, 0xe1, 0xe6, 0xb3, 0x87, 0xcd, 0x3a, 0x0e, 0x68, 0xc8, 0x2c, 0xf9, 0x9b, 0xb1, 0x9a, + 0x0d, 0x97, 0x89, 0x80, 0x09, 0x47, 0x46, 0x56, 0x16, 0x28, 0x68, 0xef, 0xb2, 0x01, 0x0d, 0xa7, + 0xb9, 0xf0, 0xcb, 0xcb, 0x68, 0xcc, 0x4e, 0x49, 0xe8, 0x4c, 0x09, 0xf1, 0x08, 0x57, 0xac, 0x9d, + 0x19, 0x9b, 0xb1, 0x2c, 0x77, 0xfa, 0xa4, 0x4e, 0xf5, 0x19, 0x63, 0x33, 0x9f, 0x58, 0x32, 0x9a, + 0x24, 0x53, 0xcb, 0x4b, 0x38, 0x8e, 0x29, 0x0b, 0x15, 0xde, 0x5a, 0xc7, 0x63, 0x1a, 0x10, 0x11, + 0xe3, 0x20, 0xca, 0x08, 0x77, 0xff, 0x2c, 0x81, 0xca, 0x48, 0x36, 0x0b, 0x1f, 0x80, 0x8a, 0x3b, + 0xc7, 0x34, 0x14, 0x48, 0x33, 0x8a, 0xed, 0xea, 0x3e, 0x32, 0x2f, 0xf5, 0x6d, 0xf6, 0x52, 0x82, + 0xad, 0x78, 0xa9, 0x42, 0x56, 0x2a, 0xd0, 0xb5, 0x0f, 0x2a, 0xbe, 0x4b, 0x09, 0xb6, 0xe2, 0xc1, + 0x0e, 0xb8, 0x2e, 0x58, 0xc2, 0x5d, 0x22, 0x50, 0x51, 0x4a, 0x1a, 0x1b, 0x24, 0x63, 0xc9, 0xb0, + 0x73, 0x26, 0xec, 0x80, 0x32, 0x4f, 0x7c, 0x22, 0x50, 0x49, 0x4a, 0x3e, 0xdf, 0x20, 0xb1, 0x13, + 0x9f, 0x28, 0x59, 0xc6, 0x85, 0x3d, 0x50, 0x7b, 0x7f, 0x8a, 0x02, 0x95, 0xa5, 0x58, 0xff, 0x50, + 0x89, 0x87, 0x92, 0x66, 0x6f, 0xc7, 0xff, 0x07, 0x02, 0xde, 0x01, 0x37, 0x02, 0xbc, 0x70, 0x42, + 0x16, 0xba, 0x04, 0x55, 0x0c, 0xad, 0x5d, 0xb6, 0xb7, 0x02, 0xbc, 0x18, 0xa6, 0x31, 0x6c, 0x81, + 0x6a, 0x3c, 0xe7, 0x44, 0xcc, 0x99, 0xef, 0x39, 0x18, 0x5d, 0x97, 0x30, 0x58, 0x1e, 0x1d, 0xac, + 0x12, 0x26, 0x68, 0x6b, 0x8d, 0xd0, 0x85, 0x8f, 0x40, 0x29, 0x60, 0x1e, 0x41, 0x37, 0x0c, 0xad, + 0x7d, 0x73, 0xdf, 0xd8, 0x34, 0x6f, 0x16, 0x0a, 0x12, 0x8a, 0x44, 0x1c, 0x33, 0x8f, 0xd8, 0x92, + 0x0d, 0xf7, 0x00, 0x48, 0x8b, 0xf2, 0x48, 0xec, 0x50, 0x0f, 0x81, 0x65, 0x55, 0x7d, 0x12, 0x1f, + 0x79, 0xf0, 0x1e, 0xf8, 0x24, 0x45, 0x05, 0x7d, 0x4e, 0x9c, 0x88, 0xd3, 0x74, 0xd2, 0x55, 0x49, + 0xa9, 0x05, 0x78, 0x31, 0xa6, 0xcf, 0xc9, 0x48, 0x1e, 0xc2, 0x6f, 0xc1, 0x96, 0xf0, 0xb1, 0x98, + 0xd3, 0x70, 0x86, 0xb6, 0x0d, 0xad, 0x5d, 0xdd, 0xff, 0x62, 0xd3, 0x55, 0x28, 0x4a, 0xf6, 0x8a, + 0xd8, 0x4b, 0xc9, 0xd7, 0xa5, 0x97, 0xbf, 0xb6, 0x0a, 0x77, 0xdf, 0x96, 0xc0, 0xcd, 0x55, 0x0a, + 0x7c, 0x04, 0x76, 0x39, 0x89, 0x18, 0x8f, 0x89, 0xe7, 0x70, 0x96, 0x84, 0x9e, 0x70, 0xce, 0x68, + 0xe8, 0xb1, 0x33, 0xa4, 0x19, 0x5a, 0xbb, 0x68, 0xef, 0xe4, 0xa8, 0x2d, 0xc1, 0xef, 0x25, 0x06, + 0x9f, 0x82, 0xcf, 0x02, 0x1a, 0x3a, 0x4b, 0x65, 0x44, 0x78, 0x2e, 0xbb, 0x66, 0x68, 0xed, 0xed, + 0x6e, 0xe7, 0xf5, 0xbb, 0x56, 0xe1, 0xed, 0xbb, 0xd6, 0xbd, 0x19, 0x8d, 0xe7, 0xc9, 0xc4, 0x74, + 0x59, 0xa0, 0x76, 0x4c, 0xfd, 0x7d, 0x25, 0xbc, 0x53, 0x2b, 0xfe, 0x31, 0x22, 0xc2, 0xec, 0x13, + 0xf7, 0xf7, 0x7f, 0xff, 0xb8, 0xaf, 0xd9, 0x3b, 0x01, 0x0d, 0x6d, 0x95, 0x72, 0x44, 0xb8, 0xf2, + 0x72, 0x41, 0x23, 0x6b, 0xd0, 0x09, 0xa8, 0x10, 0xce, 0x53, 0x4c, 0x7d, 0x27, 0x5f, 0x1b, 0x54, + 0x94, 0xa3, 0x68, 0x98, 0xd9, 0xde, 0x98, 0xf9, 0xde, 0x98, 0x7d, 0x45, 0xe8, 0xd6, 0xd2, 0x42, + 0x5e, 0xfe, 0xdd, 0xd2, 0x32, 0x8b, 0xdd, 0x2c, 0xd5, 0x31, 0x15, 0xe2, 0x31, 0xa6, 0x7e, 0x4e, + 0x83, 0x01, 0xd0, 0x73, 0x13, 0xec, 0x53, 0x97, 0xb2, 0x64, 0xdd, 0xa9, 0xf4, 0x91, 0x4e, 0x77, + 0x94, 0x53, 0x9e, 0x6e, 0xc5, 0xce, 0x05, 0xb7, 0xe5, 0xd5, 0x38, 0x53, 0x8e, 0xdd, 0xf4, 0x44, + 0xf6, 0x86, 0xca, 0x57, 0x9f, 0x5d, 0x5d, 0xe6, 0x3b, 0x54, 0xe9, 0xd2, 0xfe, 0x60, 0x00, 0xd0, + 0xba, 0x49, 0x5e, 0x8c, 0x5c, 0x8e, 0x2b, 0x3a, 0xed, 0xae, 0x3a, 0xe5, 0x29, 0xef, 0xff, 0xa4, + 0x81, 0xda, 0xca, 0xfb, 0x0f, 0xbf, 0x01, 0xcd, 0xde, 0x93, 0xe1, 0x78, 0x30, 0x1c, 0x9f, 0x8c, + 0x9d, 0xe3, 0x27, 0xfd, 0x81, 0x73, 0x32, 0x1c, 0x8f, 0x06, 0xbd, 0xa3, 0xc3, 0xa3, 0x41, 0xff, + 0x56, 0xa1, 0xb9, 0xf7, 0xe2, 0x95, 0x81, 0x56, 0x24, 0x27, 0xa1, 0x88, 0x88, 0x4b, 0xa7, 0x94, + 0x78, 0xd0, 0x04, 0xb7, 0xd7, 0xd4, 0x07, 0xe3, 0x83, 0xd1, 0x2d, 0xad, 0xf9, 0xe9, 0x8b, 0x57, + 0x46, 0x7d, 0x45, 0x96, 0x02, 0xcd, 0xd2, 0xcf, 0xbf, 0xe9, 0x85, 0xee, 0xe3, 0xd7, 0xe7, 0xba, + 0xf6, 0xe6, 0x5c, 0xd7, 0xfe, 0x39, 0xd7, 0xb5, 0x5f, 0x2e, 0xf4, 0xc2, 0x9b, 0x0b, 0xbd, 0xf0, + 0xd7, 0x85, 0x5e, 0xf8, 0xe1, 0xc1, 0x7b, 0x4d, 0x0e, 0xb2, 0xcd, 0x19, 0x92, 0xf8, 0x8c, 0xf1, + 0x53, 0x2b, 0xff, 0xa2, 0x2f, 0xf2, 0x6f, 0xba, 0x6c, 0x79, 0x52, 0x91, 0x97, 0xdc, 0xf9, 0x2f, + 0x00, 0x00, 0xff, 0xff, 0x67, 0xc3, 0xdd, 0xf6, 0x71, 0x06, 0x00, 0x00, } func (m *Params) Marshal() (dAtA []byte, err error) { @@ -250,6 +357,18 @@ func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.Slashing != nil { + { + size, err := m.Slashing.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintParams(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x62 + } if m.MaxSizePrices != 0 { i = encodeVarintParams(dAtA, i, uint64(m.MaxSizePrices)) i-- @@ -353,6 +472,80 @@ func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *SlashingParams) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *SlashingParams) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *SlashingParams) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size := m.SlashFractionMalicious.Size() + i -= size + if _, err := m.SlashFractionMalicious.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintParams(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x32 + { + size := m.SlashFractionMiss.Size() + i -= size + if _, err := m.SlashFractionMiss.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintParams(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x2a + n2, err2 := github_com_cosmos_gogoproto_types.StdDurationMarshalTo(m.OracleMaliciousJailDuration, dAtA[i-github_com_cosmos_gogoproto_types.SizeOfStdDuration(m.OracleMaliciousJailDuration):]) + if err2 != nil { + return 0, err2 + } + i -= n2 + i = encodeVarintParams(dAtA, i, uint64(n2)) + i-- + dAtA[i] = 0x22 + n3, err3 := github_com_cosmos_gogoproto_types.StdDurationMarshalTo(m.OracleMissJailDuration, dAtA[i-github_com_cosmos_gogoproto_types.SizeOfStdDuration(m.OracleMissJailDuration):]) + if err3 != nil { + return 0, err3 + } + i -= n3 + i = encodeVarintParams(dAtA, i, uint64(n3)) + i-- + dAtA[i] = 0x1a + { + size := m.MinReportedPerWindow.Size() + i -= size + if _, err := m.MinReportedPerWindow.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintParams(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + if m.ReportedRoundsWindow != 0 { + i = encodeVarintParams(dAtA, i, uint64(m.ReportedRoundsWindow)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + func encodeVarintParams(dAtA []byte, offset int, v uint64) int { offset -= sovParams(v) base := offset @@ -418,6 +611,32 @@ func (m *Params) Size() (n int) { if m.MaxSizePrices != 0 { n += 1 + sovParams(uint64(m.MaxSizePrices)) } + if m.Slashing != nil { + l = m.Slashing.Size() + n += 1 + l + sovParams(uint64(l)) + } + return n +} + +func (m *SlashingParams) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.ReportedRoundsWindow != 0 { + n += 1 + sovParams(uint64(m.ReportedRoundsWindow)) + } + l = m.MinReportedPerWindow.Size() + n += 1 + l + sovParams(uint64(l)) + l = github_com_cosmos_gogoproto_types.SizeOfStdDuration(m.OracleMissJailDuration) + n += 1 + l + sovParams(uint64(l)) + l = github_com_cosmos_gogoproto_types.SizeOfStdDuration(m.OracleMaliciousJailDuration) + n += 1 + l + sovParams(uint64(l)) + l = m.SlashFractionMiss.Size() + n += 1 + l + sovParams(uint64(l)) + l = m.SlashFractionMalicious.Size() + n += 1 + l + sovParams(uint64(l)) return n } @@ -740,6 +959,276 @@ func (m *Params) Unmarshal(dAtA []byte) error { break } } + case 12: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Slashing", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthParams + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthParams + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Slashing == nil { + m.Slashing = &SlashingParams{} + } + if err := m.Slashing.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipParams(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthParams + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *SlashingParams) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: SlashingParams: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: SlashingParams: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ReportedRoundsWindow", wireType) + } + m.ReportedRoundsWindow = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ReportedRoundsWindow |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field MinReportedPerWindow", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthParams + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthParams + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.MinReportedPerWindow.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field OracleMissJailDuration", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthParams + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthParams + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := github_com_cosmos_gogoproto_types.StdDurationUnmarshal(&m.OracleMissJailDuration, dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field OracleMaliciousJailDuration", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthParams + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthParams + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := github_com_cosmos_gogoproto_types.StdDurationUnmarshal(&m.OracleMaliciousJailDuration, dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SlashFractionMiss", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthParams + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthParams + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.SlashFractionMiss.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SlashFractionMalicious", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthParams + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthParams + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.SlashFractionMalicious.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipParams(dAtA[iNdEx:]) diff --git a/x/oracle/types/slashing.pb.go b/x/oracle/types/slashing.pb.go new file mode 100644 index 000000000..31dfc7395 --- /dev/null +++ b/x/oracle/types/slashing.pb.go @@ -0,0 +1,431 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: exocore/oracle/v1/slashing.proto + +package types + +import ( + fmt "fmt" + proto "github.com/cosmos/gogoproto/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// ValidatorReportInfo represents the information to describe the miss status of a validator reporting prices +type ValidatorReportInfo struct { + // address of the validtor + Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + // start_height for the performance round of the configured window of rounds + StartHeight int64 `protobuf:"varint,2,opt,name=start_height,json=startHeight,proto3" json:"start_height,omitempty"` + // index_offset track the offset of current window + IndexOffset int64 `protobuf:"varint,3,opt,name=index_offset,json=indexOffset,proto3" json:"index_offset,omitempty"` + // missed_rounds_counter counts the number of missed rounds for this window + MissedRoundsCounter int64 `protobuf:"varint,4,opt,name=missed_rounds_counter,json=missedRoundsCounter,proto3" json:"missed_rounds_counter,omitempty"` +} + +func (m *ValidatorReportInfo) Reset() { *m = ValidatorReportInfo{} } +func (m *ValidatorReportInfo) String() string { return proto.CompactTextString(m) } +func (*ValidatorReportInfo) ProtoMessage() {} +func (*ValidatorReportInfo) Descriptor() ([]byte, []int) { + return fileDescriptor_27165252f41a09d2, []int{0} +} +func (m *ValidatorReportInfo) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ValidatorReportInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ValidatorReportInfo.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ValidatorReportInfo) XXX_Merge(src proto.Message) { + xxx_messageInfo_ValidatorReportInfo.Merge(m, src) +} +func (m *ValidatorReportInfo) XXX_Size() int { + return m.Size() +} +func (m *ValidatorReportInfo) XXX_DiscardUnknown() { + xxx_messageInfo_ValidatorReportInfo.DiscardUnknown(m) +} + +var xxx_messageInfo_ValidatorReportInfo proto.InternalMessageInfo + +func (m *ValidatorReportInfo) GetAddress() string { + if m != nil { + return m.Address + } + return "" +} + +func (m *ValidatorReportInfo) GetStartHeight() int64 { + if m != nil { + return m.StartHeight + } + return 0 +} + +func (m *ValidatorReportInfo) GetIndexOffset() int64 { + if m != nil { + return m.IndexOffset + } + return 0 +} + +func (m *ValidatorReportInfo) GetMissedRoundsCounter() int64 { + if m != nil { + return m.MissedRoundsCounter + } + return 0 +} + +func init() { + proto.RegisterType((*ValidatorReportInfo)(nil), "exocore.oracle.v1.ValidatorReportInfo") +} + +func init() { proto.RegisterFile("exocore/oracle/v1/slashing.proto", fileDescriptor_27165252f41a09d2) } + +var fileDescriptor_27165252f41a09d2 = []byte{ + // 260 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x34, 0x90, 0x31, 0x4e, 0x84, 0x40, + 0x14, 0x86, 0x19, 0xd7, 0x68, 0x44, 0x1b, 0xd9, 0x98, 0x50, 0x4d, 0xd0, 0x6a, 0x2b, 0x70, 0xf5, + 0x06, 0x1a, 0x13, 0xb5, 0xd0, 0x84, 0xc2, 0xc2, 0x86, 0xcc, 0x32, 0x0f, 0x98, 0xc8, 0xf2, 0xc8, + 0xcc, 0x63, 0xc5, 0x5b, 0x78, 0x0d, 0x6f, 0x62, 0xb9, 0xa5, 0xa5, 0x81, 0x8b, 0x18, 0x67, 0x96, + 0xf2, 0x7d, 0xff, 0xd7, 0xbc, 0xcf, 0x8f, 0xa0, 0xc7, 0x1c, 0x35, 0x24, 0xa8, 0x45, 0x5e, 0x43, + 0xb2, 0x59, 0x26, 0xa6, 0x16, 0xa6, 0x52, 0x4d, 0x19, 0xb7, 0x1a, 0x09, 0x83, 0xd3, 0x9d, 0x11, + 0x3b, 0x23, 0xde, 0x2c, 0x2f, 0xbe, 0x98, 0x3f, 0x7f, 0x11, 0xb5, 0x92, 0x82, 0x50, 0xa7, 0xd0, + 0xa2, 0xa6, 0x87, 0xa6, 0xc0, 0x20, 0xf4, 0x0f, 0x85, 0x94, 0x1a, 0x8c, 0x09, 0x59, 0xc4, 0x16, + 0x47, 0xe9, 0x74, 0x06, 0xe7, 0xfe, 0x89, 0x21, 0xa1, 0x29, 0xab, 0x40, 0x95, 0x15, 0x85, 0x7b, + 0x11, 0x5b, 0xcc, 0xd2, 0x63, 0xcb, 0xee, 0x2d, 0xfa, 0x57, 0x54, 0x23, 0xa1, 0xcf, 0xb0, 0x28, + 0x0c, 0x50, 0x38, 0x73, 0x8a, 0x65, 0xcf, 0x16, 0x05, 0x57, 0xfe, 0xd9, 0x5a, 0x19, 0x03, 0x32, + 0xd3, 0xd8, 0x35, 0xd2, 0x64, 0x39, 0x76, 0x0d, 0x81, 0x0e, 0xf7, 0xad, 0x3b, 0x77, 0x63, 0x6a, + 0xb7, 0x5b, 0x37, 0xdd, 0x3c, 0x7e, 0x0f, 0x9c, 0x6d, 0x07, 0xce, 0x7e, 0x07, 0xce, 0x3e, 0x47, + 0xee, 0x6d, 0x47, 0xee, 0xfd, 0x8c, 0xdc, 0x7b, 0xbd, 0x2c, 0x15, 0x55, 0xdd, 0x2a, 0xce, 0x71, + 0x9d, 0xdc, 0xb9, 0x1f, 0x9f, 0x80, 0xde, 0x51, 0xbf, 0x25, 0x53, 0x94, 0x7e, 0xca, 0x42, 0x1f, + 0x2d, 0x98, 0xd5, 0x81, 0x2d, 0x72, 0xfd, 0x17, 0x00, 0x00, 0xff, 0xff, 0xfc, 0xb6, 0xd0, 0xba, + 0x35, 0x01, 0x00, 0x00, +} + +func (m *ValidatorReportInfo) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ValidatorReportInfo) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ValidatorReportInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.MissedRoundsCounter != 0 { + i = encodeVarintSlashing(dAtA, i, uint64(m.MissedRoundsCounter)) + i-- + dAtA[i] = 0x20 + } + if m.IndexOffset != 0 { + i = encodeVarintSlashing(dAtA, i, uint64(m.IndexOffset)) + i-- + dAtA[i] = 0x18 + } + if m.StartHeight != 0 { + i = encodeVarintSlashing(dAtA, i, uint64(m.StartHeight)) + i-- + dAtA[i] = 0x10 + } + if len(m.Address) > 0 { + i -= len(m.Address) + copy(dAtA[i:], m.Address) + i = encodeVarintSlashing(dAtA, i, uint64(len(m.Address))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintSlashing(dAtA []byte, offset int, v uint64) int { + offset -= sovSlashing(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *ValidatorReportInfo) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Address) + if l > 0 { + n += 1 + l + sovSlashing(uint64(l)) + } + if m.StartHeight != 0 { + n += 1 + sovSlashing(uint64(m.StartHeight)) + } + if m.IndexOffset != 0 { + n += 1 + sovSlashing(uint64(m.IndexOffset)) + } + if m.MissedRoundsCounter != 0 { + n += 1 + sovSlashing(uint64(m.MissedRoundsCounter)) + } + return n +} + +func sovSlashing(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozSlashing(x uint64) (n int) { + return sovSlashing(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *ValidatorReportInfo) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSlashing + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ValidatorReportInfo: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ValidatorReportInfo: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Address", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSlashing + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthSlashing + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthSlashing + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Address = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field StartHeight", wireType) + } + m.StartHeight = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSlashing + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.StartHeight |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field IndexOffset", wireType) + } + m.IndexOffset = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSlashing + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.IndexOffset |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field MissedRoundsCounter", wireType) + } + m.MissedRoundsCounter = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSlashing + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.MissedRoundsCounter |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipSlashing(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthSlashing + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipSlashing(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowSlashing + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowSlashing + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowSlashing + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthSlashing + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupSlashing + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthSlashing + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthSlashing = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowSlashing = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupSlashing = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/oracle/types/types.go b/x/oracle/types/types.go index 18135f518..09365b51a 100644 --- a/x/oracle/types/types.go +++ b/x/oracle/types/types.go @@ -36,11 +36,20 @@ type Price struct { Decimal uint8 } +type AggFinalPrice struct { + FeederID uint64 + SourceID uint64 + DetID string + Price string +} + const ( DefaultPriceValue = 1 DefaultPriceDecimal = 0 ) +var DelimiterForCombinedKey = byte('/') + func Uint64Bytes(value uint64) []byte { valueBytes := make([]byte, 8) binary.BigEndian.PutUint64(valueBytes, value)