From 052bf5e603956e37e2975253392f581862395993 Mon Sep 17 00:00:00 2001 From: leonz789 Date: Tue, 9 Apr 2024 22:40:33 +0800 Subject: [PATCH] test(oracle):add test cases, events, logs --- testutil/utils.go | 2 +- x/oracle/keeper/aggregator/aggregator.go | 34 ++---- .../aggregator/aggregator_aggregator.go | 2 - .../aggregator/aggregator_calculator.go | 3 - x/oracle/keeper/aggregator/worker.go | 2 - x/oracle/keeper/cache/caches.go | 2 - x/oracle/keeper/common/types.go | 5 - x/oracle/keeper/keeper_suite_test.go | 35 ++++-- x/oracle/keeper/msg_server_create_price.go | 40 +++++-- .../keeper/msg_server_create_price_test.go | 1 - x/oracle/keeper/msg_server_test.go | 102 ++++++++++++++++++ x/oracle/keeper/single.go | 19 ++-- x/oracle/module.go | 28 ++++- x/oracle/types/errors.go | 4 +- x/oracle/types/events.go | 16 +++ 15 files changed, 222 insertions(+), 73 deletions(-) create mode 100644 x/oracle/types/events.go diff --git a/testutil/utils.go b/testutil/utils.go index 1a3874c52..46e6f8ff0 100644 --- a/testutil/utils.go +++ b/testutil/utils.go @@ -70,7 +70,7 @@ func (suite *BaseTestSuite) SetupTest() { suite.DoSetupTest() } -// SetupWithGenesisValSet initializes a new EvmosApp with a validator set and genesis accounts +// SetupWithGenesisValSet initializes a new ExocoreApp with a validator set and genesis accounts // that also act as delegators. For simplicity, each validator is bonded with a delegation // of one consensus engine unit (10^6) in the default token of the simapp from first genesis // account. A Nop logger is set in SimApp. diff --git a/x/oracle/keeper/aggregator/aggregator.go b/x/oracle/keeper/aggregator/aggregator.go index 95fbd4dfb..6673f7604 100644 --- a/x/oracle/keeper/aggregator/aggregator.go +++ b/x/oracle/keeper/aggregator/aggregator.go @@ -11,12 +11,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -type cacheItemM struct { - feederId int32 - pSources []*types.PriceWithSource - validator string -} - type priceItemKV struct { TokenId int32 PriceTR types.PriceWithTimeAndRound @@ -53,6 +47,7 @@ func (agc *AggregatorContext) sanityCheck(msg *types.MsgCreatePrice) error { //TODO: check the msgCreatePrice's Decimal is correct with params setting //TODO: check len(price.prices)>0, len(price.prices._range_eachPriceWithSource.Prices)>0, at least has one source, and for each source has at least one price //TODO: check for each source, at most maxDetId count price (now in filter, ->anteHandler) + if agc.validatorsPower[msg.Creator] == nil { return errors.New("signer is not validator") } @@ -72,20 +67,18 @@ func (agc *AggregatorContext) sanityCheck(msg *types.MsgCreatePrice) error { } //check with params is coressponding source is deteministic if agc.params.IsDeterministicSource(pSource.SourceId) { - for _, pDetId := range pSource.Prices { + for _, pDetID := range pSource.Prices { //TODO: verify the format of DetId is correct, since this is string, and we will make consensus with validator's power, so it's ok not to verify the format //just make sure the DetId won't mess up with NS's placeholder id, the limitation of maximum count one validator can submit will be check by filter - if len(pDetId.DetId) == 0 { + if len(pDetID.DetId) == 0 { //deterministic must have specified deterministicId return errors.New("ds should have roundid") } //DS's price value will go through consensus process, so it's safe to skip the check here } - } else { //sanity check: NS submit only one price with detId=="" - if len(pSource.Prices) > 1 || len(pSource.Prices[0].DetId) > 0 { - return errors.New("ns should not have roundid") - } + } else if len(pSource.Prices) > 1 || len(pSource.Prices[0].DetId) > 0 { + return errors.New("ns should not have roundid") } } return nil @@ -115,7 +108,6 @@ func (agc *AggregatorContext) checkMsg(msg *types.MsgCreatePrice) error { } func (agc *AggregatorContext) FillPrice(msg *types.MsgCreatePrice) (*priceItemKV, *cache.CacheItemM, error) { - // fmt.Println("debug agc fillprice") feederWorker := agc.aggregators[msg.FeederId] //worker initialzed here reduce workload for Endblocker if feederWorker == nil { @@ -124,7 +116,7 @@ func (agc *AggregatorContext) FillPrice(msg *types.MsgCreatePrice) (*priceItemKV } if feederWorker.sealed { - return nil, nil, errors.New("") + return nil, nil, types.ErrPriceProposalIgnored.Wrap("price aggregation for this round has sealed") } if listFilled := feederWorker.do(msg); listFilled != nil { @@ -142,18 +134,16 @@ func (agc *AggregatorContext) FillPrice(msg *types.MsgCreatePrice) (*priceItemKV return nil, &cache.CacheItemM{msg.FeederId, listFilled, msg.Creator}, nil } - return nil, nil, errors.New("") + //return nil, nil, errors.New("no valid price proposal to add for aggregation") + return nil, nil, types.ErrPriceProposalIgnored } // NewCreatePrice receives msgCreatePrice message, and goes process: filter->aggregator, filter->calculator->aggregator // non-deterministic data will goes directly into aggregator, and deterministic data will goes into calculator first to get consensus on the deterministic id. func (agc *AggregatorContext) NewCreatePrice(ctx sdk.Context, msg *types.MsgCreatePrice) (*priceItemKV, *cache.CacheItemM, error) { - // fmt.Println("debug agc.newcreateprice") if err := agc.checkMsg(msg); err != nil { - // fmt.Println("debug agc.newcreateprice.error", err) - return nil, nil, err + return nil, nil, types.ErrInvalidMsg.Wrap(err.Error()) } - // fmt.Println("debug before agc.fillprice") return agc.FillPrice(msg) } @@ -176,7 +166,6 @@ func (agc *AggregatorContext) SealRound(ctx sdk.Context, force bool) (success [] expired := feeder.EndBlock > 0 && ctx.BlockHeight() >= feeder.EndBlock outOfWindow := uint64(ctx.BlockHeight())-round.basedBlock >= uint64(common.MaxNonce) if expired || outOfWindow || force { - //TODO: WRITE TO KVSTORE with previous round data for this round failed = append(failed, feeder.TokenId) if expired { delete(agc.rounds, feederId) @@ -206,21 +195,16 @@ func (agc *AggregatorContext) PrepareRound(ctx sdk.Context, block uint64) { block = uint64(ctx.BlockHeight()) } - // fmt.Println("debug agc.prepareround, height:", block) for feederId, feeder := range agc.params.GetTokenFeeders() { if feederId == 0 { continue } - // fmt.Println("debug agc.prepareround, feederId:", feederId) if (feeder.EndBlock > 0 && uint64(feeder.EndBlock) <= block) || uint64(feeder.StartBaseBlock) > block { - // fmt.Println("debug agc.prepareround 2, feederId:", feederId, feeder.StartBaseBlock, block) //this feeder is inactive continue } - // fmt.Println("debug agc.prepareround 3, feederId:", feederId) - delta := (block - uint64(feeder.StartBaseBlock)) left := delta % uint64(feeder.Interval) count := delta / uint64(feeder.Interval) diff --git a/x/oracle/keeper/aggregator/aggregator_aggregator.go b/x/oracle/keeper/aggregator/aggregator_aggregator.go index 5dccf33d6..18d65e6d1 100644 --- a/x/oracle/keeper/aggregator/aggregator_aggregator.go +++ b/x/oracle/keeper/aggregator/aggregator_aggregator.go @@ -140,7 +140,6 @@ func (agg *aggregator) aggregate() *big.Int { if agg.finalPrice != nil { return agg.finalPrice } - // fmt.Printf("debug aggregator.aggregate(), reportPower:%s, totalPower:%s\n", agg.reportPower.String(), agg.totalPower.String()) //TODO: implemetn different MODE for definition of consensus, //currently: use rule_1+MODE_1: {rule:specified source:`chainlink`, MODE: asap when power exceeds the threshold} //1. check OVA threshold @@ -148,7 +147,6 @@ func (agg *aggregator) aggregate() *big.Int { if common.ExceedsThreshold(agg.reportPower, agg.totalPower) { //TODO: this is kind of a mock way to suite V1, need update to check with params.rule //check if IVA all reached consensus - // fmt.Printf("debug aggregator.aggregate() len(dsPrice):%d\n", len(agg.dsPrices)) if len(agg.dsPrices) > 0 { validatorPrices := make([]*big.Int, 0, len(agg.reports)) //do the aggregation to find out the 'final price' diff --git a/x/oracle/keeper/aggregator/aggregator_calculator.go b/x/oracle/keeper/aggregator/aggregator_calculator.go index cc3fa5d2b..94fc5974f 100644 --- a/x/oracle/keeper/aggregator/aggregator_calculator.go +++ b/x/oracle/keeper/aggregator/aggregator_calculator.go @@ -128,7 +128,6 @@ func (c *calculator) getOrNewSourceId(sourceId int32) *roundPricesList { // fillPrice called upon new MsgCreatPrice arrived, to trigger the calculation to get to consensus on the same roundID_of_deterministic_source // v1 use mode1, TODO: switch modes func (c *calculator) fillPrice(pSources []*types.PriceWithSource, validator string, power *big.Int) (confirmedRounds []*confirmedPrice) { - // fmt.Println("debug calculator.fillPrice, calculator.ds[1]", c.deterministicSource[1], pSources) for _, pSource := range pSources { rounds := c.getOrNewSourceId(pSource.SourceId) if rounds.hasConfirmedDetId() { @@ -145,7 +144,6 @@ func (c *calculator) fillPrice(pSources []*types.PriceWithSource, validator stri roundPrice, _ := new(big.Int).SetString(pDetId.Price, 10) - // fmt.Printf("debug calculator.fillPrice before updatePriceAndPower. power%s, price%s\n", power.String(), roundPrice.String()) updated, confirmed := round.updatePriceAndPower(&priceAndPower{roundPrice, power}, c.totalPower) if updated && confirmed { //sourceId, detId, price @@ -155,7 +153,6 @@ func (c *calculator) fillPrice(pSources []*types.PriceWithSource, validator stri } } } - // fmt.Println("debug calculator.fillPrice, after calculator.ds[1]", c.deterministicSource[1]) return } diff --git a/x/oracle/keeper/aggregator/worker.go b/x/oracle/keeper/aggregator/worker.go index a30124412..6d3f4776b 100644 --- a/x/oracle/keeper/aggregator/worker.go +++ b/x/oracle/keeper/aggregator/worker.go @@ -27,9 +27,7 @@ func (w *worker) do(msg *types.MsgCreatePrice) []*types.PriceWithSource { list4Calculator, list4Aggregator := w.f.filtrate(msg) if list4Aggregator != nil { w.a.fillPrice(list4Aggregator, validator, power) - // fmt.Printf("debug worker.do after fill aggregator\n") if confirmedRounds := w.c.fillPrice(list4Calculator, validator, power); confirmedRounds != nil { - // fmt.Printf("debug worker.do after fill calculator and have confirmedRounds\n") w.a.confirmDSPrice(confirmedRounds) } } diff --git a/x/oracle/keeper/cache/caches.go b/x/oracle/keeper/cache/caches.go index df8ae2867..92181e1bc 100644 --- a/x/oracle/keeper/cache/caches.go +++ b/x/oracle/keeper/cache/caches.go @@ -171,7 +171,6 @@ func (c *Cache) GetCache(i any) bool { item[addr] = power } case CacheItemP: - //fmt.Println("debug ", c.params.params) if item == nil { return false } @@ -180,7 +179,6 @@ func (c *Cache) GetCache(i any) bool { if item == nil { return false } - // fmt.Println("debug getCacheM", c.msg) tmp := make([]*CacheItemM, 0, len(c.msg)) for _, msgs := range c.msg { tmp = append(tmp, msgs...) diff --git a/x/oracle/keeper/common/types.go b/x/oracle/keeper/common/types.go index 66db474ba..2e3ca7f47 100644 --- a/x/oracle/keeper/common/types.go +++ b/x/oracle/keeper/common/types.go @@ -74,10 +74,8 @@ func (p Params) CheckRules(feederId int32, prices []*types.PriceWithSource) (boo if source.Valid { notFound = true for _, p := range prices { - // fmt.Println("debug rules check _0, p.sourceid", p.SourceId) if p.SourceId == int32(sId) { notFound = false - // fmt.Println("debug rules match _0") break } } @@ -88,10 +86,8 @@ func (p Params) CheckRules(feederId int32, prices []*types.PriceWithSource) (boo for _, source := range rule.SourceIds { notFound = true for _, p := range prices { - // fmt.Println("debug rules check, p.sourceid", p.SourceId) if p.SourceId == source { notFound = false - // fmt.Println("debug rules match") break } } @@ -99,7 +95,6 @@ func (p Params) CheckRules(feederId int32, prices []*types.PriceWithSource) (boo } } if notFound { - // fmt.Println("debug rules check, rule.sources", rule.SourceIds) return false, errors.New("price source not match with rule") } } diff --git a/x/oracle/keeper/keeper_suite_test.go b/x/oracle/keeper/keeper_suite_test.go index 19935f0e1..80a436d2f 100644 --- a/x/oracle/keeper/keeper_suite_test.go +++ b/x/oracle/keeper/keeper_suite_test.go @@ -4,20 +4,27 @@ import ( "context" "testing" + "github.com/ExocoreNetwork/exocore/testutil" "github.com/ExocoreNetwork/exocore/x/oracle/keeper" "github.com/ExocoreNetwork/exocore/x/oracle/types" sdk "github.com/cosmos/cosmos-sdk/types" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "github.com/stretchr/testify/suite" gomock "go.uber.org/mock/gomock" ) type KeeperSuite struct { - t *testing.T - k keeper.Keeper - ctx sdk.Context - ms types.MsgServer - ctrl *gomock.Controller + testutil.BaseTestSuite + + t *testing.T + k keeper.Keeper + ctx sdk.Context + ms types.MsgServer + ctrl *gomock.Controller + valAddr1 sdk.ValAddress + valAddr2 sdk.ValAddress } var ks *KeeperSuite @@ -29,13 +36,23 @@ func TestKeeper(t *testing.T) { ks.ctx = sdk.UnwrapSDKContext(ctxW) ks.t = t + suite.Run(t, ks) + RegisterFailHandler(Fail) RunSpecs(t, "Keeper Suite") } -func (k *KeeperSuite) Reset() { +func (suite *KeeperSuite) Reset() { var ctxW context.Context - k.ms, ctxW, k.k = setupMsgServer(k.t) - k.ctx = sdk.UnwrapSDKContext(ctxW) - k.ctrl = gomock.NewController(k.t) + suite.ms, ctxW, suite.k = setupMsgServer(suite.t) + suite.ctx = sdk.UnwrapSDKContext(ctxW) + suite.ctrl = gomock.NewController(suite.t) +} + +func (suite *KeeperSuite) SetupTest() { + suite.DoSetupTest() + suite.valAddr1, _ = sdk.ValAddressFromBech32(suite.Validators[0].OperatorAddress) + suite.valAddr2, _ = sdk.ValAddressFromBech32(suite.Validators[1].OperatorAddress) + keeper.ResetAggregatorContext() + keeper.ResetCache() } diff --git a/x/oracle/keeper/msg_server_create_price.go b/x/oracle/keeper/msg_server_create_price.go index ab6ba5a13..33a8977c8 100644 --- a/x/oracle/keeper/msg_server_create_price.go +++ b/x/oracle/keeper/msg_server_create_price.go @@ -2,26 +2,46 @@ package keeper import ( "context" + "strconv" "github.com/ExocoreNetwork/exocore/x/oracle/types" sdk "github.com/cosmos/cosmos-sdk/types" ) +// CreatePrice proposes price for new round of specific tokenFeeder func (k msgServer) CreatePrice(goCtx context.Context, msg *types.MsgCreatePrice) (*types.MsgCreatePriceResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) - /** - 1. aggregator.rInfo.Tokenid->status == 0(1 ignore and return) - 2. basedBlock is valid [roundInfo.basedBlock, *+5], each base only allow for one submit each validator, window for submition is 5 blocks while every validator only allowed to submit at most 3 transactions each round - 3. check the rule fulfilled(sources check), check the decimal of the 1st mathc the params' definition(among prices the decimal had been checked in ante stage), timestamp:later than previous block's timestamp, [not future than now(+1s), this is checked in anteHandler], timestamp verification is not necessary - **/ - - //newItem, caches, _ := k.GetAggregatorContext(ctx, k.Keeper).NewCreatePrice(ctx, msg) - newItem, caches, _ := GetAggregatorContext(ctx, k.Keeper).NewCreatePrice(ctx, msg) - // fmt.Println("debug after NewCreatePrice", newItem, caches) + + newItem, caches, err := GetAggregatorContext(ctx, k.Keeper).NewCreatePrice(ctx, msg) + if err != nil { + return nil, err + } + + logger := k.Keeper.Logger(ctx) + logger.Info("add price proposal for aggregation", "feederID", msg.FeederId, "basedBlock", msg.BasedBlock, "proposer", msg.Creator) + + ctx.EventManager().EmitEvent(sdk.NewEvent( + types.EventTypeCreatePrice, + sdk.NewAttribute(types.AttributeKeyFeederID, strconv.Itoa(int(msg.FeederId))), + sdk.NewAttribute(types.AttributeKeyBasedBlock, strconv.FormatInt(int64(msg.BasedBlock), 10)), + sdk.NewAttribute(types.AttributeKeyProposer, msg.Creator), + ), + ) + if caches != nil { if newItem != nil { k.AppendPriceTR(ctx, newItem.TokenId, newItem.PriceTR) - //TODO: move related caches + + logger.Info("final price aggregation done", "feederID", msg.FeederId, "roundID", newItem.PriceTR.RoundId, "price", newItem.PriceTR.Price) + + ctx.EventManager().EmitEvent(sdk.NewEvent( + types.EventTypeCreatePrice, + sdk.NewAttribute(types.AttributeKeyFeederID, strconv.Itoa(int(msg.FeederId))), + sdk.NewAttribute(types.AttributeKeyRoundID, strconv.FormatInt(int64(newItem.PriceTR.RoundId), 10)), + sdk.NewAttribute(types.AttributeKeyFinalPrice, newItem.PriceTR.Price), + sdk.NewAttribute(types.AttributeKeyPriceUpdated, types.AttributeValuePriceUpdatedSuccess), + ), + ) cs.RemoveCache(caches) } else { cs.AddCache(caches) diff --git a/x/oracle/keeper/msg_server_create_price_test.go b/x/oracle/keeper/msg_server_create_price_test.go index e1ac601af..eecc89784 100644 --- a/x/oracle/keeper/msg_server_create_price_test.go +++ b/x/oracle/keeper/msg_server_create_price_test.go @@ -113,7 +113,6 @@ var _ = Describe("MsgCreatePrice", func() { c.GetCache(&iRes) Expect(len(iRes)).Should(Equal(0)) prices := ks.k.GetAllPrices(sdk.UnwrapSDKContext(ks.ctx)) - //fmt.Println("GetAllPrices", prices[0]) Expect(prices[0]).Should(BeEquivalentTo(types.Prices{ TokenId: 1, NextRountId: 2, diff --git a/x/oracle/keeper/msg_server_test.go b/x/oracle/keeper/msg_server_test.go index b13973b15..2f03e2651 100644 --- a/x/oracle/keeper/msg_server_test.go +++ b/x/oracle/keeper/msg_server_test.go @@ -6,11 +6,14 @@ import ( keepertest "github.com/ExocoreNetwork/exocore/testutil/keeper" "github.com/ExocoreNetwork/exocore/x/oracle/keeper" + "github.com/ExocoreNetwork/exocore/x/oracle/keeper/testdata" "github.com/ExocoreNetwork/exocore/x/oracle/types" sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" // "github.com/cosmos/ibc-go/testing/mock" + // "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -25,3 +28,102 @@ func TestMsgServer(t *testing.T) { require.NotNil(t, ms) require.NotNil(t, ctx) } + +func (suite *KeeperSuite) TestCreatePriceSingleBlock() { + router := suite.App.MsgServiceRouter() + oServer := router.Handler(&types.MsgCreatePrice{}) + require.EqualValues(suite.T(), 2, suite.Ctx.BlockHeight()) + oServer(suite.Ctx, &types.MsgCreatePrice{ + Creator: suite.valAddr1.String(), + Nonce: 1, + FeederId: 1, + Prices: testdata.PS1, + BasedBlock: 1, + }) + oServer(suite.Ctx, &types.MsgCreatePrice{ + Creator: suite.valAddr2.String(), + Nonce: 1, + FeederId: 1, + Prices: testdata.PS2, + BasedBlock: 1, + }) + prices, found := suite.App.OracleKeeper.GetPrices(suite.Ctx, 1) + if suite.Equal(true, found, "final price should be returned") { + suite.EqualValues(prices.TokenId, 1, "final price has tokenId equals to 1") + suite.Equal(2, len(prices.PriceList), "length of price list should be 2 including the 0 index with an empty element as placeholder") + suite.Exactly(types.Prices{ + TokenId: 1, + NextRountId: 2, + PriceList: []*types.PriceWithTimeAndRound{ + {}, + { + Price: testdata.PTD2.Price, + Decimal: 18, + //since timestamp is filled with realtime, so we use the value from result to fill the expected value here + Timestamp: prices.PriceList[1].Timestamp, + RoundId: 1, + }, + }, + }, prices) + } + + //run the endblock to seal and prepare for next block + suite.NextBlock() + require.EqualValues(suite.T(), 3, suite.Ctx.BlockHeight()) + _, err := oServer(suite.Ctx, &types.MsgCreatePrice{ + Creator: suite.valAddr1.String(), + Nonce: 1, + FeederId: 1, + Prices: testdata.PS1, + BasedBlock: 1, + }) + codespace, code, log := sdkerrors.ABCIInfo(err, false) + suite.Equal(codespace, types.ModuleName) + suite.EqualValues(code, 1) + suite.Equal(log, err.Error()) +} + +func (suite *KeeperSuite) TestCreatePriceTwoBlock() { + router := suite.App.MsgServiceRouter() + oServer := router.Handler(&types.MsgCreatePrice{}) + res, _ := oServer(suite.Ctx, &types.MsgCreatePrice{ + Creator: suite.valAddr1.String(), + Nonce: 1, + FeederId: 1, + Prices: testdata.PS1, + BasedBlock: 1, + }) + proposerAttribute, _ := res.GetEvents().GetAttributes(types.AttributeKeyProposer) + proposer := proposerAttribute[0].Value + suite.Equal(suite.valAddr1.String(), proposer) + _, found := suite.App.OracleKeeper.GetPrices(suite.Ctx, 1) + require.Equal(suite.T(), false, found) + if suite.Equal(false, found) { + //run the endblock to seal and prepare for next block + suite.NextBlock() + oServer(suite.Ctx, &types.MsgCreatePrice{ + Creator: suite.valAddr2.String(), + Nonce: 1, + FeederId: 1, + Prices: testdata.PS3, + BasedBlock: 1, + }) + prices, found := suite.App.OracleKeeper.GetPrices(suite.Ctx, 1) + if suite.Equal(true, found) { + suite.Exactly(types.Prices{ + TokenId: 1, + NextRountId: 2, + PriceList: []*types.PriceWithTimeAndRound{ + {}, + { + Price: testdata.PTD1.Price, + Decimal: 18, + //since timestamp is filled with realtime, so we use the value from result to fill the expected value here + Timestamp: prices.PriceList[1].Timestamp, + RoundId: 1, + }, + }, + }, prices) + } + } +} diff --git a/x/oracle/keeper/single.go b/x/oracle/keeper/single.go index 4082b6068..db0a4adf8 100644 --- a/x/oracle/keeper/single.go +++ b/x/oracle/keeper/single.go @@ -23,6 +23,7 @@ func GetCaches() *cache.Cache { return 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 { if agc != nil { return agc @@ -38,7 +39,6 @@ func GetAggregatorContext(ctx sdk.Context, k Keeper) *aggregator.AggregatorConte return agc } -// func recacheAggregatorContext(ctx sdk.Context, agc *aggregator.AggregatorContext, k common.KeeperOracle, c *cache.Cache) bool { func recacheAggregatorContext(ctx sdk.Context, agc *aggregator.AggregatorContext, k Keeper, c *cache.Cache) bool { from := uint64(ctx.BlockHeight()) - common.MaxNonce @@ -57,7 +57,7 @@ func recacheAggregatorContext(ctx sdk.Context, agc *aggregator.AggregatorContext totalPower := big.NewInt(0) validatorPowers := make(map[string]*big.Int) - k.IterateBondedValidatorsByPower(ctx, func(index int64, validator stakingtypes.ValidatorI) bool { + k.IterateBondedValidatorsByPower(ctx, func(_ int64, validator stakingtypes.ValidatorI) bool { power := big.NewInt(validator.GetConsensusPower(validator.GetBondedTokens())) addr := string(validator.GetOperator()) validatorPowers[addr] = power @@ -65,7 +65,6 @@ func recacheAggregatorContext(ctx sdk.Context, agc *aggregator.AggregatorContext return false }) agc.SetValidatorPowers(validatorPowers) - // agc.SetTotalPower(totalPower) //TODO: test only if k.GetLastTotalPower(ctx).Cmp(totalPower) != 0 { panic("something wrong when get validatorsPower from staking module") @@ -78,15 +77,11 @@ func recacheAggregatorContext(ctx sdk.Context, agc *aggregator.AggregatorContext var pTmp common.Params for ; from < to; from++ { //fill params + prev := uint64(0) for b, recentParams := range recentParamsMap { - prev := uint64(0) if b <= from && b > prev { pTmp = common.Params(*recentParams) agc.SetParams(&pTmp) - if prev > 0 { - //TODO: safe delete - delete(recentParamsMap, prev) - } prev = b } } @@ -148,3 +143,11 @@ func initAggregatorContext(ctx sdk.Context, agc *aggregator.AggregatorContext, k agc.PrepareRound(ctx, uint64(ctx.BlockHeight())-1) return nil } + +func ResetAggregatorContext() { + agc = nil +} + +func ResetCache() { + cs = nil +} diff --git a/x/oracle/module.go b/x/oracle/module.go index 2b6b2aec3..165205034 100644 --- a/x/oracle/module.go +++ b/x/oracle/module.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "math/big" + "strconv" // this line is used by starport scaffolding # 1 @@ -160,40 +161,59 @@ func (am AppModule) EndBlock(ctx sdk.Context, _ abci.RequestEndBlock) []abci.Val forceSeal := false agc := keeper.GetAggregatorContext(ctx, am.keeper) + logger := am.keeper.Logger(ctx) if len(validatorUpdates) > 0 { - //validatorUpdates := am.keeper.GetValidatorUpdates(ctx) validatorList := make(map[string]*big.Int) for _, vu := range validatorUpdates { pubKey, _ := cryptocodec.FromTmProtoPublicKey(vu.PubKey) validator, _ := am.keeper.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(pubKey)) validatorList[validator.OperatorAddress] = big.NewInt(vu.Power) } - // cs.AddCache(validatorList, am.keeper) validatorPowers := make(map[string]*big.Int) cs.GetCache(cache.CacheItemV(validatorPowers)) //update validatorPowerList in aggregatorContext - // keeper.GetAggregatorContext(ctx, am.keeper).SetValidatorPowers(validatorPowers) - // agc := keeper.GetAggregatorContext(ctx, am.keeper) agc.SetValidatorPowers(validatorPowers) //TODO: seal all alive round since validatorSet changed here forceSeal = true + logger.Info("validator set changed, force seal all active rounds") } //TODO: for v1 use mode==1, just check the failed feeders _, failed := agc.SealRound(ctx, forceSeal) //append new round with previous price for fail-seal token for _, tokenId := range failed { + event := sdk.NewEvent( + types.EventTypeCreatePrice, + sdk.NewAttribute(types.AttributeKeyTokenID, strconv.Itoa(int(tokenId))), + sdk.NewAttribute(types.AttributeKeyPriceUpdated, types.AttributeValuePriceUpdatedFail), + ) + logInfo := fmt.Sprintf("add new round with previous price under fail aggregation, tokenID:%d", tokenId) if pTR, ok := am.keeper.GetPriceTRLatest(ctx, tokenId); ok { pTR.RoundId++ am.keeper.AppendPriceTR(ctx, tokenId, pTR) + logger.Info("add new round with previous price under fail aggregation", "tokenID", tokenId, "roundID", pTR.RoundId) + logInfo += fmt.Sprintf(", roundID:%d, price:%s", pTR.RoundId, pTR.Price) + event.AppendAttributes( + sdk.NewAttribute(types.AttributeKeyRoundID, strconv.Itoa(int(pTR.RoundId))), + sdk.NewAttribute(types.AttributeKeyFinalPrice, pTR.Price), + ) } else { nextRoundId := am.keeper.GetNextRoundId(ctx, tokenId) am.keeper.AppendPriceTR(ctx, tokenId, types.PriceWithTimeAndRound{ RoundId: nextRoundId, }) + logInfo += fmt.Sprintf(", roundID:%d, price:-", nextRoundId) + event.AppendAttributes( + sdk.NewAttribute(types.AttributeKeyRoundID, strconv.Itoa(int(nextRoundId))), + sdk.NewAttribute(types.AttributeKeyFinalPrice, "-"), + ) } + logger.Info(logInfo) + ctx.EventManager().EmitEvent(event) } + //TODO: emit events for success sealed rounds(could ignore for v1) + logger.Info("prepare for next oracle round of each tokenFeeder") agc.PrepareRound(ctx, 0) cs.CommitCache(ctx, true, am.keeper) diff --git a/x/oracle/types/errors.go b/x/oracle/types/errors.go index 6d57a417b..ce36be548 100644 --- a/x/oracle/types/errors.go +++ b/x/oracle/types/errors.go @@ -8,5 +8,7 @@ import ( // x/oracle module sentinel errors var ( - ErrSample = sdkerrors.Register(ModuleName, 1100, "sample error") + ErrSample = sdkerrors.Register(ModuleName, 1100, "sample error") + ErrInvalidMsg = sdkerrors.Register(ModuleName, 1, "invalid input create price") + ErrPriceProposalIgnored = sdkerrors.Register(ModuleName, 2, "price proposal ignored") ) diff --git a/x/oracle/types/events.go b/x/oracle/types/events.go new file mode 100644 index 000000000..4a7ae578e --- /dev/null +++ b/x/oracle/types/events.go @@ -0,0 +1,16 @@ +package types + +const ( + EventTypeCreatePrice = "create_price" + + AttributeKeyFeederID = "feeder_id" + AttributeKeyTokenID = "token_id" + AttributeKeyBasedBlock = "based_block" + AttributeKeyRoundID = "round_id" + AttributeKeyProposer = "proposer" + AttributeKeyFinalPrice = "final_price" + AttributeKeyPriceUpdated = "price_update" + + AttributeValuePriceUpdatedSuccess = "success" + AttributeValuePriceUpdatedFail = "fail" +)