Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[R4R] feat(oracle) #30

Merged
merged 49 commits into from
Apr 25, 2024
Merged

Conversation

leonz789
Copy link
Contributor

@leonz789 leonz789 commented Apr 9, 2024

Description

Oracle module is used to collect external prices from sources(offchain/onchain) outside of exocorechain. And the prices access served by oracle module will be used by other modules like operator for voting power calculation.
Supersedes #6 , reconstruct the commits for easy to review.

commits:

  1. proto file definition and some ignite scalffold dependencies
  2. mainly generated files from proto definition
  3. implement keeper for state
  4. add tests for keeper
  5. add cache serves as memory storage for price collection across blocks
  6. add aggregator, this implements the main logic for price aggregation
Screenshot 2024-04-03 at 03 48 43

Copy link

@github-advanced-security github-advanced-security bot left a comment

Choose a reason for hiding this comment

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

gosec found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.

x/oracle/types/info.pb.go Dismissed Show dismissed Hide dismissed
x/oracle/types/info.pb.go Dismissed Show dismissed Hide dismissed
x/oracle/types/info.pb.go Dismissed Show dismissed Hide dismissed
x/oracle/types/info.pb.go Dismissed Show dismissed Hide dismissed
testutil/nullify/nullify.go Dismissed Show dismissed Hide dismissed
x/oracle/keeper/single.go Fixed Show fixed Hide fixed
x/oracle/keeper/aggregator/aggregator.go Fixed Show fixed Hide fixed
x/oracle/module.go Dismissed Show dismissed Hide dismissed
x/oracle/keeper/cache/caches.go Fixed Show fixed Hide fixed
Comment on lines +81 to +86
for b, recentParams := range recentParamsMap {
if b <= from && b > prev {
pTmp = common.Params(*recentParams)
agc.SetParams(&pTmp)
prev = b
}
}

Check warning

Code scanning / CodeQL

Iteration over map Warning

Iteration over map may be a possible source of non-determinism
@leonz789
Copy link
Contributor Author

leonz789 commented Apr 9, 2024

ref: #24

@leonz789 leonz789 mentioned this pull request Apr 9, 2024
x/oracle/keeper/prices.go Outdated Show resolved Hide resolved

b := store.Get(types.PricesRoundKey(roundId))
if b == nil {
return
Copy link
Contributor

Choose a reason for hiding this comment

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

return uninitialized variables

Copy link
Contributor Author

Choose a reason for hiding this comment

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

expected, found will be initialized to false by default, just return the false is expected.

x/oracle/keeper/prices.go Outdated Show resolved Hide resolved
@leonz789
Copy link
Contributor Author

leonz789 commented Apr 11, 2024

Thanks @bwhour and @MaxMustermann2 for the pr review(#24 ), here's former comments i just copied here and corresponding replies:

  1. workflows fail:
    A: working

  2. currency concern for maps:
    A: currently there's no concurrent access, will update when we do the concurrency related performance update.

  3. 'If there exists a pSource with no prices, accessing pSource.Prices[0] will trigger panic'
    A: before the message be processed, it has been checked to make sure the pSource at least has one price

  4. 'Use string concatenation (validator + strconv. Itoa(int(pSource.SourceId))) to generate the mapped key, whether this key generation rule may cause key conflicts'
    A: won't be, it's like a uri each level will not be conflict under parent scope

  5. 'list4Calculator, list4Aggregator := w.f.filtrate(msg): if the filtrate method encounters any exceptions while processing a message (e.g., the message is formatted incorrectly), the current implementation will not catch or handle those exceptions'
    A: The format check is and should be done in the first place when the message start processing(have been done in the checkmsg)

  6. 'In the updatePriceAndPower method, when new prices and weights are added to roundPrices, if any exceptions are encountered (e.g., if pw.price or pw.power is nil), the code doesn't perform error handling'
    A: same as above

  7. len(r.prices) < cap(r.prices) Its a good practice taking takes into account not exceeding the preset capacity. Is it necessary to take into account other boundary conditions, such as validation of the input parameters ?
    A: this length check is meant to make sure we don't accept values more than the limitation we set, just use the capicity as a boundary limitation

  8. 'list4Calculator, list4Aggregator := w.f.filtrate(msg): if the filtrate method encounters any exceptions while processing a message (e.g., the message is formatted incorrectly), the current implementation will not catch or handle those exceptions'
    A: Format check is/should be done in the checkmsg and antehandler

  9. 'im.PSources = append(im.PSources, item.PSources...)
    This case is If there are new PSources to be added, it will add the entire PSources array to the existing entry, not just the new SourceId. If if the new PSources array contains some new and some old (already existing in the current entry) SourceIds, then the price sources associated with the old SourceId will be added repeatedly; over time, if new PSources are added frequently, the PSources array in each entry may become very large, affecting data processing and query efficiency'
    A: the cache is just used as temporary storage, we wanna it be fast, and for the case you mentioned here, the duplicated srouceIds has been checked and ignored before put into cache.

  10. 'For collection operations (Add operations), assuming that they always succeed without checking the return value can hide problems.'
    A: not sure what this meant for.

Others:

  1. error handling and logs
    A: has added some and updated with new commits, will check further

  2. Update status to enum instead of integer
    A: will update

@leonz789 leonz789 force-pushed the develop-oracle-review branch from 6ebdc14 to ede2652 Compare April 13, 2024 14:42
x/oracle/keeper/aggregator/aggregator.go Fixed Show fixed Hide fixed
Comment on lines +170 to +172
for addr, power := range c.validators.validators {
item[addr] = power
}

Check warning

Code scanning / CodeQL

Iteration over map Warning

Iteration over map may be a possible source of non-determinism
x/oracle/keeper/aggregator/aggregator.go Fixed Show fixed Hide fixed

// String implements the Stringer interface.
func (p Params) String() string {
out, _ := yaml.Marshal(p)

Check warning

Code scanning / gosec

Returned error is not propagated up the stack. Warning

Returned error is not propagated up the stack.
if len(validatorUpdates) > 0 {
validatorList := make(map[string]*big.Int)
for _, vu := range validatorUpdates {
pubKey, _ := cryptocodec.FromTmProtoPublicKey(vu.PubKey)

Check warning

Code scanning / gosec

Returned error is not propagated up the stack. Warning

Returned error is not propagated up the stack.

// GetSigners returns the expected signers for a MsgUpdateParams message
func (msg *MsgUpdateParams) GetSigners() []sdk.AccAddress {
addr, _ := sdk.AccAddressFromBech32(msg.Authority)

Check warning

Code scanning / gosec

Returned error is not propagated up the stack. Warning

Returned error is not propagated up the stack.
x/oracle/keeper/common/types.go Dismissed Show dismissed Hide dismissed
x/oracle/keeper/common/types.go Dismissed Show dismissed Hide dismissed
x/oracle/keeper/aggregator/aggregator.go Fixed Show fixed Hide fixed
x/oracle/keeper/aggregator/aggregator.go Fixed Show fixed Hide fixed
x/oracle/client/cli/tx_create_price.go Dismissed Show dismissed Hide dismissed
Comment on lines +94 to +98
agc.FillPrice(&types.MsgCreatePrice{
Creator: msg.Validator,
FeederID: msg.FeederID,
Prices: msg.PSources,
})

Check warning

Code scanning / gosec

Errors unhandled. Warning

Errors unhandled.
@leonz789
Copy link
Contributor Author

For the workflow: Tests / test-unit-cover (pull_request)
it seems that the workflow can't handle the 'monkey patch' well, the test itself is actually passed.

@leonz789 leonz789 force-pushed the develop-oracle-review branch from ed1e350 to dc84085 Compare April 18, 2024 02:48
@ExocoreNetwork ExocoreNetwork deleted a comment from qinglin89 Apr 18, 2024
@ExocoreNetwork ExocoreNetwork deleted a comment from qinglin89 Apr 18, 2024
Copy link
Contributor

@TimmyExogenous TimmyExogenous left a comment

Choose a reason for hiding this comment

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

There is a lot of code in this PR, so didn't review it very deeply, just left some questions.

proto/exocore/oracle/info.proto Show resolved Hide resolved
proto/exocore/oracle/info.proto Show resolved Hide resolved
proto/exocore/oracle/query.proto Show resolved Hide resolved
proto/exocore/oracle/recent_params.proto Show resolved Hide resolved
proto/exocore/oracle/token_feeder.proto Show resolved Hide resolved
x/oracle/keeper/aggregator/aggregator.go Outdated Show resolved Hide resolved
x/oracle/keeper/common/types.go Outdated Show resolved Hide resolved
x/oracle/keeper/single.go Show resolved Hide resolved
x/oracle/keeper/aggregator/aggregator_aggregator.go Outdated Show resolved Hide resolved
x/oracle/keeper/aggregator/aggregator.go Outdated Show resolved Hide resolved

//TODO: userDefinedTokenFeeder
// latest block on which the validator set be updated
ValidatorUpdateBlock validator_update_block = 4;
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this start from 3?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

corrected

x/oracle/keeper/aggregator/context.go Dismissed Show dismissed Hide dismissed
x/oracle/keeper/aggregator/context.go Dismissed Show dismissed Hide dismissed
x/oracle/keeper/aggregator/context.go Dismissed Show dismissed Hide dismissed
x/oracle/keeper/aggregator/context.go Dismissed Show dismissed Hide dismissed
x/oracle/keeper/aggregator/context.go Dismissed Show dismissed Hide dismissed
x/oracle/keeper/aggregator/aggregator.go Dismissed Show dismissed Hide dismissed
Comment on lines +160 to +187
for feederID, round := range agc.rounds {
if round.status == 1 {
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 1:
expired := feeder.EndBlock > 0 && uint64(ctx.BlockHeight()) >= feeder.EndBlock
outOfWindow := uint64(ctx.BlockHeight())-round.basedBlock >= uint64(common.MaxNonce)
if expired || outOfWindow || force {
failed = append(failed, feeder.TokenID)
if expired {
delete(agc.rounds, feederID)
delete(agc.aggregators, feederID)
} else {
round.status = 2
agc.aggregators[feederID] = nil
}
}
default:
ctx.Logger().Info("mode other than 1 is not support now")
}
}
// all status: 1->2, remove its aggregator
if agc.aggregators[feederID] != nil && agc.aggregators[feederID].sealed {
agc.aggregators[feederID] = nil
}
}

Check warning

Code scanning / CodeQL

Iteration over map Warning

Iteration over map may be a possible source of non-determinism
x/oracle/keeper/aggregator/context.go Dismissed Show dismissed Hide dismissed
app/app.go Outdated
@@ -448,15 +455,12 @@ func NewExocoreApp(
operatorTypes.StoreKey,
avsManagerTypes.StoreKey,
avsTaskTypes.StoreKey,
oracleTypes.StoreKey,
)

// Add the EVM transient store key
Copy link
Contributor

Choose a reason for hiding this comment

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

Update or remove this comment.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

updated

@@ -977,6 +985,7 @@ func NewExocoreApp(
withdrawTypes.ModuleName,
rewardTypes.ModuleName,
exoslashTypes.ModuleName,
oracleTypes.ModuleName,
Copy link
Contributor

Choose a reason for hiding this comment

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

Please explain the rationale for these orders, if there is any. Or add a comment anyway if there is no particular rationale.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

updated

// nolint: staticcheck
WithKeyTable(evmtypes.ParamKeyTable())
paramsKeeper.Subspace(evmtypes.ModuleName).WithKeyTable(evmtypes.ParamKeyTable()) //nolint:staticcheck
paramsKeeper.Subspace(oracleTypes.ModuleName).WithKeyTable(oracleTypes.ParamKeyTable())
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't this also have the same nolint line ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

udpated

int32 decimal = 2;
// timestamp when the price corresponding to
string timestamp = 3;
// det_id is used for deterministic source to tell of which round from this soure the price is corresponded
Copy link
Contributor

Choose a reason for hiding this comment

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

typo: soure

Copy link
Contributor Author

Choose a reason for hiding this comment

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

corrected

_ "github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway"
_ "github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger"
_ "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2"
)
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice!

return s.size
}

func NewSet[T comparable](length int) *Set[T] {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice!

}

// RemovePrices removes a prices from the store
func (k Keeper) RemovePrices(
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we do some pruning of this data if it gets old ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, we can add the pruning feature as config in further PR.

return nil, status.Error(codes.NotFound, "not found")
}

return &types.QueryGetPricesResponse{Prices: val}, nil
Copy link
Contributor

Choose a reason for hiding this comment

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

paginate ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, i will update in a new PR with related test

@@ -0,0 +1,77 @@
package keeper
Copy link
Contributor

Choose a reason for hiding this comment

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

When is the data used? Can you explain briefly?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is used for refill the cache which stores price messages for aggregation:
since we have a window of more than one block to collect the prices from all validators, if this process is interrupted before the final aggregated price written to block, we would like to have some way to retrieve these messages. After we got the final price, the messages could be able to remove, so we just need the recent messages within the window.

@@ -0,0 +1,76 @@
package keeper
Copy link
Contributor

Choose a reason for hiding this comment

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

When is the data used? Can you explain briefly?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is like the recent_msgs, which is used to refill the cache:
the aggregation is affected by 3 elements:

  1. validatorset(: votingPower)
  2. params(: tokenFeeder)
  3. messages (: including price_with_votingPower for tokenFeeder)
    So when we do the refill, we just use these 3 data to reproduce the aggregation process.
    But i think the recent_params could be optimized with carefully check like validator_update_block so that we don't need to keep the history params, currently this is a straightforward way. I will update in a new PR

validatorPowers := make(map[string]*big.Int)
k.IterateBondedValidatorsByPower(ctx, func(_ int64, validator stakingtypes.ValidatorI) bool {
power := big.NewInt(validator.GetConsensusPower(validator.GetBondedTokens()))
// addr := string(validator.GetOperator())
Copy link
Contributor

Choose a reason for hiding this comment

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

Delete unnecessary comment

Copy link
Contributor Author

Choose a reason for hiding this comment

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

updated


// SetRecentParams set a specific recentParams in the store from its index
func (k Keeper) SetRecentParams(ctx sdk.Context, recentParams types.RecentParams) {
store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.RecentParamsKeyPrefix))
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this only used in genesis initialize?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, this is used to refill the cache

)

// CreatePrice proposes price for new round of specific tokenFeeder
func (ms msgServer) CreatePrice(goCtx context.Context, msg *types.MsgCreatePrice) (*types.MsgCreatePriceResponse, error) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Wouldn't this be called by validator?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This serves a a message service, and the message should be signed by validator

Copy link
Contributor

@mikebraver mikebraver left a comment

Choose a reason for hiding this comment

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

fix the action error then

@leonz789 leonz789 merged commit 9f1b6a8 into ExocoreNetwork:develop Apr 25, 2024
19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants