Skip to content

Commit

Permalink
feat(appchains): Introduce module + register AVS (imua-xyz#173)
Browse files Browse the repository at this point in the history
* feat(appchain): scaffold coordinator

* feat(appchain): scaffold subscriber

* feat(epochs): create number + id structure

* feat(appchain): add coordinator params

* fix(epochs): invalidate negative duration

* fix(avs): stateful validation of staking assets

* refactor(dogfood): carve `sortByPower` out

* fix(dogfood): avoid duplicate validation

x/avs handles the assetID validation

* fix(dogfood): replace with max unbonding duration

* doc(app): add comment about slashing frac storage

* feat(appchain): allow sub chain creation

- Anyone can create a subchain via a simple transaction. The genesis
  file is available at the end of the current epoch.
- If no operators opt in by that time, it is dropped.
- If the state is available, it is queried via CLI and added to the
  genesis file of the subchain.
- The genesis file, with data from other modules, starts the chain once
  enough vote power is live

* chore(lint)

* fix(test): make avs test pass

The AVS module now validates the assetIDs passed to it

* fix(appchain): remove superfluous test

* fix(epochs): update expected test message

negative duration is also not supported

* chore(lint)

* chore(lint): proto lint

* chore: run proto gen

* fix(appchain): remove floating point arithmetic

* fix(appchain): remove wrong comment

* doc(appchain): add comment about params expect

* fix(appchain): remove superfluous fmt

* feat(appchain): report invalid json

* fix(utils): remove SliceStable and use Slice

* doc(appchain): add more logging

* chore(lint)

* fix(ci): pin the golang lint version

* chore(lint): remove unused timestamp proto

* fix(doc): update field comments after review

- The subscriber unbonding duration has no bounds
- The advantage of reusing an already established channel was explicitly
  listed out

* refactor(appchain): option pattern for Subscriber

* fix(appchain-coord): remove unnecessary panic

* fix(appchain): optimize capacity of val set

* fix(appchain): option-pattern not on pointers

Since the rest of the codebase uses direct objects and not pointers,
making it pointers here would be too cumbersome.

* refactor(dogfood,operator): carve out wrapped key

The wrapped consensus key object has utility in the subscriber module as
well, and, may have some utility in the coordinator module. Hence, it is
better located in a `types` subpackage.

* fix: respond to AI comments

* fix(coordinator): correct epochs calc

* fix: build and tests

- The `github.com/ExocoreNetwork/exocore/types` package is a bit finicky
  to deal with, and hence, the wrapped key has been moved to its own
  subpackage within that.
- Some files that were copied word-to-word in our codebase were never
  being used but instead referred from the parent codebase. This
  resulted in test errors about duplicate `proto` registration. Hence,
  these files have been removed.

* fix(appchain): respond to review comments
  • Loading branch information
MaxMustermann2 authored Oct 9, 2024
1 parent 28ddae2 commit e290743
Show file tree
Hide file tree
Showing 117 changed files with 9,570 additions and 2,001 deletions.
2 changes: 2 additions & 0 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,8 @@ func NewExocoreApp(
// the validator signature rate and informs the staking keeper to perform the requisite
// slashing. all its other operations are offloaded to Exocore keepers via the dogfood or
// the operator module.
// NOTE: the slashing keeper stores the parameters (slash rate) for the dogfood
// keeper, since all slashing (for this chain) begins within this keeper.
app.SlashingKeeper = slashingkeeper.NewKeeper(
appCodec, app.LegacyAmino(), keys[slashingtypes.StoreKey],
app.StakingKeeper, authAddrString,
Expand Down
4 changes: 2 additions & 2 deletions precompiles/avs/avs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ func (suite *AVSManagerPrecompileSuite) TestRegisterAVS() {
sdk.AccAddress(utiltx.GenerateAddress().Bytes()).String(),
sdk.AccAddress(utiltx.GenerateAddress().Bytes()).String(),
}
assetID := []string{"11", "22", "33"}
assetID := suite.AssetIDs
minStakeAmount, taskAddr := uint64(3), "0xDF907c29719154eb9872f021d21CAE6E5025d7aB"
avsUnbondingPeriod, minSelfDelegation := uint64(3), uint64(3)
epochIdentifier := epochstypes.DayEpochID
Expand Down Expand Up @@ -317,7 +317,7 @@ func (suite *AVSManagerPrecompileSuite) TestUpdateAVS() {
sdk.AccAddress(utiltx.GenerateAddress().Bytes()).String(),
sdk.AccAddress(utiltx.GenerateAddress().Bytes()).String(),
}
assetID := []string{"11", "22", "33"}
assetID := suite.AssetIDs
minStakeAmount, taskAddr := uint64(3), "0x3e108c058e8066DA635321Dc3018294cA82ddEdf"
avsUnbondingPeriod, minSelfDelegation := uint64(3), uint64(3)
epochIdentifier := epochstypes.DayEpochID
Expand Down
18 changes: 14 additions & 4 deletions proto/buf.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ deps:
- remote: buf.build
owner: cosmos
repository: cosmos-proto
commit: 1935555c206d4afb9e94615dfd0fad31
digest: shake256:c74d91a3ac7ae07d579e90eee33abf9b29664047ac8816500cf22c081fec0d72d62c89ce0bebafc1f6fec7aa5315be72606717740ca95007248425102c365377
commit: 04467658e59e44bbb22fe568206e1f70
digest: shake256:73a640bd60e0c523b0f8237ff34eab67c45a38b64bbbde1d80224819d272dbf316ac183526bd245f994af6608b025f5130483d0133c5edd385531326b5990466
- remote: buf.build
owner: cosmos
repository: cosmos-sdk
Expand All @@ -16,8 +16,18 @@ deps:
repository: gogo-proto
commit: 88ef6483f90f478fb938c37dde52ece3
digest: shake256:89c45df2aa11e0cff97b0d695436713db3d993d76792e9f8dc1ae90e6ab9a9bec55503d48ceedd6b86069ab07d3041b32001b2bfe0227fa725dd515ff381e5ba
- remote: buf.build
owner: cosmos
repository: ibc
commit: fbb44f5ad3194450af479a615fa715d9
digest: shake256:3fbf41c96089017ebf3b5143f78de0d531f604cb11da1bc98b2104eb6dd295b8a49f5f35c60b8389ba50bfa08959da905109324099e75ece9afd8e4087b14019
- remote: buf.build
owner: cosmos
repository: ics23
commit: 55085f7c710a45f58fa09947208eb70b
digest: shake256:9bf0bc495b5a11c88d163d39ef521bc4b00bc1374a05758c91d82821bdc61f09e8c2c51dda8452529bf80137f34d852561eacbe9550a59015d51cecb0dacb628
- remote: buf.build
owner: googleapis
repository: googleapis
commit: e874a0be2bf140a5a4c7d4122c635823
digest: shake256:4fe3036b4d706f6ee2b13c730bd04777f021dfd02ed27e6e40480acfe664a7548238312ee0727fd77648a38d227e296d43f4a38a34cdd46068156211016d9657
commit: 8bc2c51e08c447cd8886cdea48a73e14
digest: shake256:a969155953a5cedc5b2df5b42c368f2bc66ff8ce1804bc96e0f14ff2ee8a893687963058909df844d1643cdbc98ff099d2daa6bc9f9f5b8886c49afdc60e19af
2 changes: 2 additions & 0 deletions proto/buf.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ version: v1
name: buf.build/evmos/evmos
deps:
- buf.build/cosmos/cosmos-sdk:v0.47.0
- buf.build/cosmos/ibc:fbb44f5ad3194450af479a615fa715d9
- buf.build/cosmos/cosmos-proto
- buf.build/cosmos/gogo-proto
- buf.build/cosmos/ics23:b1abd8678aab07165efd453c96796a179eb3131f
- buf.build/googleapis/googleapis
lint:
use:
Expand Down
107 changes: 107 additions & 0 deletions proto/exocore/appchain/common/v1/common.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
syntax = "proto3";

package exocore.appchain.common.v1;

import "amino/amino.proto";
import "gogoproto/gogo.proto";
import "google/protobuf/duration.proto";
import "ibc/lightclients/tendermint/v1/tendermint.proto";
import "tendermint/abci/types.proto";

option go_package = "github.com/ExocoreNetwork/exocore/x/appchain/common/types";

// This file contains all of the types shared within the coordinator module
// and each of the subscriber modules. These types (or parts thereof) are stored
// within the module states but not sent over the wire.

// Params defines the parameters for the subscriber module. TODO(mm): These must be deny listed
// for edits via governance on the subscriber chain to prevent the subcriber participants
// from unilaterally alterting parameters (like reward %) for their own benefit.
message SubscriberParams {
// Reward related params

// coordinator_fee_pool_addr_str is the address of the fee pool on the coordinator.
string coordinator_fee_pool_addr_str = 1;
// distribution_transmission_channel is the channel name used to transmit
// the rewards from the subscriber to the coordinator. It is used in the event
// that a channel between coordinator and subscriber exists prior to the
// provision of security from Exocore to the appchain. Until a changeover
// process is implemented, it is currently unused. (TODO). The advantage
// of reusing a channel that was already in place is that the coin denomination
// which contains a hash of the channel name will remain unchanged.
string distribution_transmission_channel = 2;
// blocks_per_distribution_transmission is the number of blocks after which the minted
// reward is sent to the coordinator.
int64 blocks_per_distribution_transmission = 3;
// subscriber_redistribution_fraction is the %age of the rewards that the subscriber
// should send out. For example, "0.75" means 75% of the rewards are sent out.
string subscriber_redistribution_fraction = 4;
// reward_denom is the denomination of the reward. For now, this is not
// distributed but rather simply tracked.
string reward_denom = 5;

// IBC related params

// ibc_timeout_period is the timeout period used for IBC packets (excluding transfers)
// Such a timeout is enforced by IBC itself and not by either of the chains.
google.protobuf.Duration ibc_timeout_period = 6
[ (gogoproto.nullable) = false, (gogoproto.stdduration) = true,
(gogoproto.customname) = "IBCTimeoutPeriod" ];
// transfer_timeout_period is the timeout period used for IBC transfers.
google.protobuf.Duration transfer_timeout_period = 7
[ (gogoproto.nullable) = false, (gogoproto.stdduration) = true ];

// Params relevant to chain operation
// unbonding_duration is the subscriber chain's unbonding duration.
// for now, we don't support the subscriber chain using x/epochs as a unit of time; however,
// when we do, this duration should be the best approximation of that mechanism, with
// 1 epoch added to account for the current epoch. (TODO)
google.protobuf.Duration unbonding_period = 8
[ (gogoproto.nullable) = false, (gogoproto.stdduration) = true ];
// HistoricalEntries is the number of historical entries to persist in the
// historical stats module. It is the same as that defined in the staking module,
// however, we use the signed version so that negative values can be caught.
int64 historical_entries = 9;

// These are params related to the slashing module. Requests are received
// from the subscriber and slashed according to these params. Since signing
// can only be tracked by the subscriber chain, we do not have any parameters
// here that can be used to configure the signing window and the number of
// blocks that should be signed in it. Conversely, the subscriber chain
// does not do anything with these parameters (even though they are shared)
// since slashing is done by the coordinator chain.
// Operators should refer to the genesis file of the subscriber chain to
// check their comfort with these values before onboarding the chain.

// slash_fraction_downtime is the fraction of the stake that is slashed when a
// validator is found to be offline.
string slash_fraction_downtime = 15;
// downtime_jail_duration is the duration of the jail period for a validator
// after they have been found to be offline for too long.
google.protobuf.Duration downtime_jail_duration = 16
[(gogoproto.nullable) = false, (amino.dont_omitempty) = true, (gogoproto.stdduration) = true];
// slash_fraction_double_sign is the fraction of the stake that is slashed
// when a validator is found to have double signed.
string slash_fraction_double_sign = 17;
}

// SubscriberGenesisState is the genesis state of a subscriber at the time of
// it being provisioned by Exocore, as stored in the coordinator module.
message SubscriberGenesisState {
// params is the parameters of the subscriber module, as generated.
SubscriberParams params = 1 [(gogoproto.nullable) = false];
// coordinator is the coordinator information for the subscriber.
CoordinatorInfo coordinator = 2 [ (gogoproto.nullable) = false ];
}

// CoordinatorInfo is the information about the coordinator chain that is
// stored within the subscriber chain's subscriber module.
message CoordinatorInfo {
// client_state is the client state of the coordinator chain.
ibc.lightclients.tendermint.v1.ClientState client_state = 1;
// consensus_state is the consensus state of the coordinator chain.
ibc.lightclients.tendermint.v1.ConsensusState consensus_state = 2;
// initial_val_set is the initial validator set of the coordinator chain.
repeated .tendermint.abci.ValidatorUpdate initial_val_set = 3
[ (gogoproto.nullable) = false ];
}
23 changes: 23 additions & 0 deletions proto/exocore/appchain/coordinator/v1/coordinator.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
syntax = "proto3";

package exocore.appchain.coordinator.v1;

import "exocore/appchain/coordinator/v1/tx.proto";
import "gogoproto/gogo.proto";

option go_package = "github.com/ExocoreNetwork/exocore/x/appchain/coordinator/types";

// PendingSubscriberChainRequests is a helper structure to store a list of
// subscriber chain requests that are pending activation.
message PendingSubscriberChainRequests {
// list is the list of subscriber chain requests that are pending activation.
repeated .exocore.appchain.coordinator.v1.RegisterSubscriberChainRequest list = 1
[(gogoproto.nullable) = false];
}

// ChainIDs is a helper structure to store a list of chain IDs.
message ChainIDs {
// list is the list of chain IDs.
repeated string list = 1;
}

14 changes: 14 additions & 0 deletions proto/exocore/appchain/coordinator/v1/genesis.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
syntax = "proto3";

package exocore.appchain.coordinator.v1;

import "exocore/appchain/coordinator/v1/params.proto";
import "gogoproto/gogo.proto";

option go_package = "github.com/ExocoreNetwork/exocore/x/appchain/coordinator/types";

// GenesisState is the genesis state for the appchain coordinator module.
message GenesisState {
// Params is the parameters for the appchain coordinator module.
Params params = 1 [(gogoproto.nullable) = false];
}
34 changes: 34 additions & 0 deletions proto/exocore/appchain/coordinator/v1/params.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
syntax = "proto3";

package exocore.appchain.coordinator.v1;

import "exocore/epochs/v1/epochs.proto";
import "gogoproto/gogo.proto";
import "google/protobuf/duration.proto";
import "ibc/lightclients/tendermint/v1/tendermint.proto";

option go_package = "github.com/ExocoreNetwork/exocore/x/appchain/coordinator/types";

// Params is the parameters for the appchain coordinator module.
message Params {
// template_client is the IBC template client.
ibc.lightclients.tendermint.v1.ClientState template_client = 1;
// trusting_period_fraction is the multiplier applied on the subscriber's
// unbonding duration to determine the IBC trusting period.
string trusting_period_fraction = 2;
// ibc_timeout_period is the timeout period for IBC packets. While our
// system is largely created with epochs as a unit of time (and not
// standard durations), this is an exception since it is used directly
// by the IBC codebase.
google.protobuf.Duration ibc_timeout_period = 3
[ (gogoproto.nullable) = false, (gogoproto.stdduration) = true,
(gogoproto.customname) = "IBCTimeoutPeriod" ];
// init_timeout_period is the period within which the subscriber chain
// must make a connection with the coordinator, after being spawned.
exocore.epochs.v1.Epoch init_timeout_period = 4
[ (gogoproto.nullable) = false ];
// vsc_timeout_period is the period within which the subscriber chain
// must respond to a VSC request, after it is sent.
exocore.epochs.v1.Epoch vsc_timeout_period = 5
[ (gogoproto.nullable) = false, (gogoproto.customname) = "VSCTimeoutPeriod" ];
}
49 changes: 49 additions & 0 deletions proto/exocore/appchain/coordinator/v1/query.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
syntax = "proto3";

package exocore.appchain.coordinator.v1;

import "exocore/appchain/common/v1/common.proto";
import "exocore/appchain/coordinator/v1/params.proto";
import "gogoproto/gogo.proto";
import "google/api/annotations.proto";

option go_package = "github.com/ExocoreNetwork/exocore/x/appchain/coordinator/types";

// Query defines the gRPC querier service.
service Query {
// QueryParams returns the appchain coordinator module parameters.
rpc QueryParams(QueryParamsRequest) returns (QueryParamsResponse) {
option (google.api.http) = {
get: "/appchain/coordinator/params"
};
}
// QuerySubscriberGenesis returns the genesis state for a subscriber chain.
rpc QuerySubscriberGenesis(QuerySubscriberGenesisRequest) returns (QuerySubscriberGenesisResponse) {
option (google.api.http) = {
get: "/exocore/appchain/coordinator/v1/subscriber_genesis/{chain}"
};
}
}

// QueryParamsRequest is the request type for the Query.QueryParams RPC method.
message QueryParamsRequest {}

// QueryParamsResponse is the response type for the Query.QueryParams RPC method.
message QueryParamsResponse {
// params is the parameters for the appchain coordinator module.
Params params = 1 [(gogoproto.nullable) = false];
}

// QuerySubscriberGenesisRequest is the request type for the Query.QuerySubscriberGenesis RPC method.
message QuerySubscriberGenesisRequest {
// chain is the chain ID of the subscriber chain. we intentionally don't use ChainID so that
// the query can work (it does not support custom names).
string chain = 1;
}

// QuerySubscriberGenesisResponse is the response type for the Query.QuerySubscriberGenesis RPC method.
message QuerySubscriberGenesisResponse {
// subscriber_genesis is the genesis state for the subscriber chain.
exocore.appchain.common.v1.SubscriberGenesisState subscriber_genesis = 1
[ (gogoproto.nullable) = false ];
}
57 changes: 57 additions & 0 deletions proto/exocore/appchain/coordinator/v1/tx.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
syntax = "proto3";

package exocore.appchain.coordinator.v1;

import "cosmos/msg/v1/msg.proto";
import "cosmos_proto/cosmos.proto";
import "exocore/appchain/common/v1/common.proto";
import "gogoproto/gogo.proto";
import "google/api/annotations.proto";

option go_package = "github.com/ExocoreNetwork/exocore/x/appchain/coordinator/types";

// Msg defines the Msg service.
service Msg {
option (cosmos.msg.v1.service) = true;
// RegisterSubscriberChain registers a subscriber chain with the coordinator.
// By default, it is activated at the next epoch.
rpc RegisterSubscriberChain(
RegisterSubscriberChainRequest
) returns (RegisterSubscriberChainResponse) {
option (google.api.http).post = "/exocore/appchain/coordinator/v1/tx/RegisterSubscriberChain";
}
}

// RegisterSubscriberChainRequest is the request type for the
// RegisterSubscriberChain message.
message RegisterSubscriberChainRequest {
option (cosmos.msg.v1.signer) = "FromAddress";
// from_address is the address of the transaction signer. any transactions
// originating from this address may be used to edit the chain. at some point
// in the future this will be offloaded to the governance module on the
// subscriber chain. (TODO)
string from_address = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];
// chain_id is the unique identifier for the chain, serving as the primary key.
string chain_id = 2 [(gogoproto.customname) = "ChainID"];
// epoch_identifier specifies the unit of epoch (week, hour, day). It must be
// registered in the x/epochs module.
// This epoch is the identifier used by the coordinator to send validator set
// updates to the subscriber at the end of each epoch. The subscriber chain's
// genesis is made available at the end of the current epoch
// (marked by this identifier).
string epoch_identifier = 3;
// asset_ids lists the IDs of assets accepted by the subscriber chain.
repeated string asset_ids = 4 [(gogoproto.customname) = "AssetIDs"];
// min_self_delegation_usd is the minimum self-delegation in USD required to
// be a validator on the chain.
uint64 min_self_delegation_usd = 5;
// max_validators is the maximum number of validators allowed on the chain.
uint32 max_validators = 6;
// subscriber_params are the parameters used by the subscriber module
// on the subscriber chain.
exocore.appchain.common.v1.SubscriberParams subscriber_params = 7 [(gogoproto.nullable) = false];
}

// RegisterSubscriberChainResponse defines the response structure for executing a
// RegisterSubscriberChain message.
message RegisterSubscriberChainResponse {}
14 changes: 14 additions & 0 deletions proto/exocore/appchain/subscriber/v1/genesis.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
syntax = "proto3";

package exocore.appchain.subscriber.v1;

import "exocore/appchain/common/v1/common.proto";
import "gogoproto/gogo.proto";

option go_package = "github.com/ExocoreNetwork/exocore/x/appchain/subscriber/types";

// GenesisState is the genesis state for the appchain subscriber module.
message GenesisState {
// Params is the parameters for the appchain subscriber module.
exocore.appchain.common.v1.SubscriberParams params = 1 [(gogoproto.nullable) = false];
}
Loading

0 comments on commit e290743

Please sign in to comment.