From 6d44166b0f5719377ac3e664f9499ddaf5884cd9 Mon Sep 17 00:00:00 2001 From: MaxMustermann2 <82761650+MaxMustermann2@users.noreply.github.com> Date: Tue, 6 Feb 2024 16:17:27 +0000 Subject: [PATCH 01/14] feat(dogfood): add parameters The dogfooding module, as previously described, is used as the staking module for the Exocore chain. It allows operators and delegators to deposit and delegate their assets on a client chain for staking. The staked assets are then used to secure the Exocore chain. The parameters used by this module are the following: - EpochsUntilUnbonded, which represents the number of epochs after which an unbonding is effective. - EpochIdentifier, which is the identifier of the epoch duration. It should be valid according to the epoch keeper of `app.go`, and is thus constrained to the options week/day/hour. - MaxValidators, which is the maximum number of validators that will be forwarded to ABCI. - HistoricalEntries, which is the number of historical entries to persist in state. These are used by IBC. --- proto/exocore/dogfood/v1/params.proto | 12 +- x/dogfood/genesis.go | 2 +- x/dogfood/keeper/params.go | 48 ++++++- x/dogfood/keeper/query.go | 2 +- x/dogfood/types/params.go | 109 +++++++++++++-- x/dogfood/types/params.pb.go | 194 ++++++++++++++++++++++++-- 6 files changed, 339 insertions(+), 28 deletions(-) diff --git a/proto/exocore/dogfood/v1/params.proto b/proto/exocore/dogfood/v1/params.proto index 3305239a6..4d3f94a8d 100644 --- a/proto/exocore/dogfood/v1/params.proto +++ b/proto/exocore/dogfood/v1/params.proto @@ -9,5 +9,15 @@ option go_package = "github.com/ExocoreNetwork/exocore/x/dogfood/types"; // Params defines the parameters for the module. message Params { option (gogoproto.goproto_stringer) = false; - + // EpochsUntilUnbonded is the number of epochs after which an unbonding + // is released. Note that it starts from the beginning of the next epoch + // in which the unbonding request was received. At that point, the vote + // power is reduced by the amount of the unbonding operation. + uint32 epochs_until_unbonded = 1; + // EpochIdentifier is the identifier of the epoch (week, hour, day). + string epoch_identifier = 2; + // MaxValidators is the maximum number of validators. + uint32 max_validators = 3; + // HistoricalEntries is the number of historical entries to persist. + uint32 historical_entries = 4; } diff --git a/x/dogfood/genesis.go b/x/dogfood/genesis.go index 0c4077eb6..15eef3d3e 100644 --- a/x/dogfood/genesis.go +++ b/x/dogfood/genesis.go @@ -14,7 +14,7 @@ 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() - genesis.Params = k.GetParams(ctx) + genesis.Params = k.GetDogfoodParams(ctx) return genesis } diff --git a/x/dogfood/keeper/params.go b/x/dogfood/keeper/params.go index 95553df50..f2bb7af60 100644 --- a/x/dogfood/keeper/params.go +++ b/x/dogfood/keeper/params.go @@ -5,12 +5,52 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -// GetParams get all parameters as types.Params -func (k Keeper) GetParams(ctx sdk.Context) types.Params { - return types.NewParams() +// GetEpochsUntilUnbonded returns the number of epochs after which an unbonding that is made +// during the current epoch will be released. It is a parameter of the dogfood module. +func (k Keeper) GetEpochsUntilUnbonded(ctx sdk.Context) uint32 { + var epochsUntilUnbonded uint32 + k.paramstore.Get(ctx, types.KeyEpochsUntilUnbonded, &epochsUntilUnbonded) + return epochsUntilUnbonded } -// SetParams set the params +// GetEpochIdentifier returns the epoch identifier used to measure an epoch. It is a parameter +// of the dogfood module. +func (k Keeper) GetEpochIdentifier(ctx sdk.Context) string { + var epochIdentifier string + k.paramstore.Get(ctx, types.KeyEpochIdentifier, &epochIdentifier) + return epochIdentifier +} + +// GetMaxValidators returns the maximum number of validators that can be asked to validate for +// the chain. It is a parameter of the dogfood module. +func (k Keeper) GetMaxValidators(ctx sdk.Context) uint32 { + var maxValidators uint32 + k.paramstore.Get(ctx, types.KeyMaxValidators, &maxValidators) + return maxValidators +} + +// GetHistorialEntries is the number of historical info entries to persist in the store. These +// entries are used by the IBC module. The return value is a parameter of the dogfood module. +func (k Keeper) GetHistoricalEntries(ctx sdk.Context) uint32 { + var historicalEntries uint32 + k.paramstore.Get(ctx, types.KeyHistoricalEntries, &historicalEntries) + return historicalEntries +} + +// SetParams sets the params for the dogfood module. func (k Keeper) SetParams(ctx sdk.Context, params types.Params) { k.paramstore.SetParamSet(ctx, ¶ms) } + +// GetDogfoodParams returns the parameters for the dogfood module. Note that this function is +// intentionally called GetDogfoodParams and not GetParams, since the GetParams function is used +// to implement the slashingtypes.StakingKeeper interface `GetParams(sdk.Context) +// stakingtypes.Params`. +func (k Keeper) GetDogfoodParams(ctx sdk.Context) (params types.Params) { + return types.NewParams( + k.GetEpochsUntilUnbonded(ctx), + k.GetEpochIdentifier(ctx), + k.GetMaxValidators(ctx), + k.GetHistoricalEntries(ctx), + ) +} diff --git a/x/dogfood/keeper/query.go b/x/dogfood/keeper/query.go index 34ed99320..60c010b19 100644 --- a/x/dogfood/keeper/query.go +++ b/x/dogfood/keeper/query.go @@ -21,5 +21,5 @@ func (k Keeper) Params( } ctx := sdk.UnwrapSDKContext(goCtx) - return &types.QueryParamsResponse{Params: k.GetParams(ctx)}, nil + return &types.QueryParamsResponse{Params: k.GetDogfoodParams(ctx)}, nil } diff --git a/x/dogfood/types/params.go b/x/dogfood/types/params.go index 357196ad6..97fb4f583 100644 --- a/x/dogfood/types/params.go +++ b/x/dogfood/types/params.go @@ -1,38 +1,127 @@ package types import ( + "fmt" + paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "gopkg.in/yaml.v2" + + epochTypes "github.com/evmos/evmos/v14/x/epochs/types" ) var _ paramtypes.ParamSet = (*Params)(nil) -// ParamKeyTable the param key table for launch module +const ( + // DefaultEpochsUntilUnbonded is the default number of epochs after which an unbonding entry + // is released. For example, if an unbonding is requested during epoch 8, it is made + // effective at the beginning of epoch 9. The unbonding amount is released at the beginning + // of epoch 16 (9 + DefaultEpochsUntilUnbonded). + DefaultEpochsUntilUnbonded = 7 + // DefaultEpochIdentifier is the epoch identifier which is used, by default, to identify the + // epoch. Note that the options include week, day or hour. + DefaultEpochIdentifier = epochTypes.HourEpochID + // DefaultMaxValidators is the default maximum number of bonded validators. + DefaultMaxValidators = stakingtypes.DefaultMaxValidators + // DefaultHistorical entries is the number of entries of historical staking data to persist. + // Apps that don't use IBC can ignore this value by not adding the staking module to the + // application module manager's SetOrderBeginBlockers. + DefaultHistoricalEntries = stakingtypes.DefaultHistoricalEntries +) + +// Reflection based keys for params subspace. +var ( + KeyEpochsUntilUnbonded = []byte("EpochsUntilUnbonded") + KeyEpochIdentifier = []byte("EpochIdentifier") + KeyMaxValidators = []byte("MaxValidators") + KeyHistoricalEntries = []byte("HistoricalEntries") +) + +// ParamKeyTable returns a key table with the necessary registered params. func ParamKeyTable() paramtypes.KeyTable { return paramtypes.NewKeyTable().RegisterParamSet(&Params{}) } -// NewParams creates a new Params instance -func NewParams() Params { - return Params{} +// NewParams creates a new Params instance. +func NewParams( + epochsUntilUnbonded uint32, + epochIdentifier string, + maxValidators uint32, + historicalEntries uint32, +) Params { + return Params{ + EpochsUntilUnbonded: epochsUntilUnbonded, + EpochIdentifier: epochIdentifier, + MaxValidators: maxValidators, + HistoricalEntries: historicalEntries, + } } -// DefaultParams returns a default set of parameters +// DefaultParams returns a default set of parameters. func DefaultParams() Params { - return NewParams() + return NewParams( + DefaultEpochsUntilUnbonded, + DefaultEpochIdentifier, + DefaultMaxValidators, + DefaultHistoricalEntries, + ) } -// ParamSetPairs get the params.ParamSet +// ParamSetPairs implements params.ParamSet func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs { - return paramtypes.ParamSetPairs{} + return paramtypes.ParamSetPairs{ + paramtypes.NewParamSetPair( + KeyEpochsUntilUnbonded, + &p.EpochsUntilUnbonded, + ValidatePositiveUint32, + ), + paramtypes.NewParamSetPair( + KeyEpochIdentifier, + &p.EpochIdentifier, + epochTypes.ValidateEpochIdentifierInterface, + ), + paramtypes.NewParamSetPair( + KeyMaxValidators, + &p.MaxValidators, + ValidatePositiveUint32, + ), + paramtypes.NewParamSetPair( + KeyHistoricalEntries, + &p.HistoricalEntries, + ValidatePositiveUint32, + ), + } } -// Validate validates the set of params +// Validate validates the set of params. func (p Params) Validate() error { + if err := ValidatePositiveUint32(p.EpochsUntilUnbonded); err != nil { + return fmt.Errorf("epochs until unbonded: %w", err) + } + if err := epochTypes.ValidateEpochIdentifierInterface(p.EpochIdentifier); err != nil { + return fmt.Errorf("epoch identifier: %w", err) + } + if err := ValidatePositiveUint32(p.MaxValidators); err != nil { + return fmt.Errorf("max validators: %w", err) + } + if err := ValidatePositiveUint32(p.HistoricalEntries); err != nil { + return fmt.Errorf("historical entries: %w", err) + } + return nil +} + +// ValidatePositiveUint32 checks whether the supplied value is a positive uint32. +func ValidatePositiveUint32(i interface{}) error { + if val, ok := i.(uint32); !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } else if val == 0 { + return fmt.Errorf("invalid parameter value: %d", val) + } return nil } -// String implements the Stringer interface. +// String implements the Stringer interface. Ths interface is required as part of the +// proto.Message interface, which is used in the query server. func (p Params) String() string { out, _ := yaml.Marshal(p) return string(out) diff --git a/x/dogfood/types/params.pb.go b/x/dogfood/types/params.pb.go index 60a1da67d..1b35e818a 100644 --- a/x/dogfood/types/params.pb.go +++ b/x/dogfood/types/params.pb.go @@ -25,6 +25,17 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package // Params defines the parameters for the module. type Params struct { + // EpochsUntilUnbonded is the number of epochs after which an unbonding + // is released. Note that it starts from the beginning of the next epoch + // in which the unbonding request was received. At that point, the vote + // power is reduced by the amount of the unbonding operation. + EpochsUntilUnbonded uint32 `protobuf:"varint,1,opt,name=epochs_until_unbonded,json=epochsUntilUnbonded,proto3" json:"epochs_until_unbonded,omitempty"` + // EpochIdentifier is the identifier of the epoch (week, hour, day). + EpochIdentifier string `protobuf:"bytes,2,opt,name=epoch_identifier,json=epochIdentifier,proto3" json:"epoch_identifier,omitempty"` + // MaxValidators is the maximum number of validators. + MaxValidators uint32 `protobuf:"varint,3,opt,name=max_validators,json=maxValidators,proto3" json:"max_validators,omitempty"` + // HistoricalEntries is the number of historical entries to persist. + HistoricalEntries uint32 `protobuf:"varint,4,opt,name=historical_entries,json=historicalEntries,proto3" json:"historical_entries,omitempty"` } func (m *Params) Reset() { *m = Params{} } @@ -59,6 +70,34 @@ func (m *Params) XXX_DiscardUnknown() { var xxx_messageInfo_Params proto.InternalMessageInfo +func (m *Params) GetEpochsUntilUnbonded() uint32 { + if m != nil { + return m.EpochsUntilUnbonded + } + return 0 +} + +func (m *Params) GetEpochIdentifier() string { + if m != nil { + return m.EpochIdentifier + } + return "" +} + +func (m *Params) GetMaxValidators() uint32 { + if m != nil { + return m.MaxValidators + } + return 0 +} + +func (m *Params) GetHistoricalEntries() uint32 { + if m != nil { + return m.HistoricalEntries + } + return 0 +} + func init() { proto.RegisterType((*Params)(nil), "exocore.dogfood.v1.Params") } @@ -66,17 +105,26 @@ func init() { func init() { proto.RegisterFile("exocore/dogfood/v1/params.proto", fileDescriptor_e8747fb70c97d97f) } var fileDescriptor_e8747fb70c97d97f = []byte{ - // 160 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x4f, 0xad, 0xc8, 0x4f, - 0xce, 0x2f, 0x4a, 0xd5, 0x4f, 0xc9, 0x4f, 0x4f, 0xcb, 0xcf, 0x4f, 0xd1, 0x2f, 0x33, 0xd4, 0x2f, - 0x48, 0x2c, 0x4a, 0xcc, 0x2d, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x82, 0x2a, 0xd0, - 0x83, 0x2a, 0xd0, 0x2b, 0x33, 0x94, 0x12, 0x49, 0xcf, 0x4f, 0xcf, 0x07, 0x4b, 0xeb, 0x83, 0x58, - 0x10, 0x95, 0x4a, 0x7c, 0x5c, 0x6c, 0x01, 0x60, 0x9d, 0x56, 0x2c, 0x33, 0x16, 0xc8, 0x33, 0x38, - 0x79, 0x9f, 0x78, 0x24, 0xc7, 0x78, 0xe1, 0x91, 0x1c, 0xe3, 0x83, 0x47, 0x72, 0x8c, 0x13, 0x1e, - 0xcb, 0x31, 0x5c, 0x78, 0x2c, 0xc7, 0x70, 0xe3, 0xb1, 0x1c, 0x43, 0x94, 0x61, 0x7a, 0x66, 0x49, - 0x46, 0x69, 0x92, 0x5e, 0x72, 0x7e, 0xae, 0xbe, 0x2b, 0xc4, 0x78, 0xbf, 0xd4, 0x92, 0xf2, 0xfc, - 0xa2, 0x6c, 0x7d, 0x98, 0x73, 0x2a, 0xe0, 0x0e, 0x2a, 0xa9, 0x2c, 0x48, 0x2d, 0x4e, 0x62, 0x03, - 0xdb, 0x61, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0x8e, 0x0e, 0x0c, 0x9a, 0xb0, 0x00, 0x00, 0x00, + // 290 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x44, 0x90, 0x3f, 0x4b, 0xc3, 0x40, + 0x18, 0x87, 0x73, 0x5a, 0x0a, 0x1e, 0xd4, 0x3f, 0xa7, 0x42, 0x70, 0xb8, 0x16, 0x41, 0xa8, 0x83, + 0x39, 0xaa, 0x9b, 0xa3, 0xd0, 0x41, 0x04, 0x91, 0x42, 0x1d, 0x5c, 0xc2, 0x35, 0x77, 0x4d, 0x0f, + 0x9b, 0xbc, 0xe1, 0xee, 0x1a, 0xe3, 0xb7, 0x70, 0x74, 0xf4, 0xbb, 0xb8, 0x38, 0x76, 0x74, 0x94, + 0xe4, 0x8b, 0x48, 0x2f, 0x31, 0x6e, 0xc7, 0xef, 0x79, 0xee, 0x1d, 0x1e, 0xdc, 0x97, 0x05, 0x44, + 0xa0, 0x25, 0x13, 0x10, 0xcf, 0x01, 0x04, 0xcb, 0x47, 0x2c, 0xe3, 0x9a, 0x27, 0x26, 0xc8, 0x34, + 0x58, 0x20, 0xa4, 0x11, 0x82, 0x46, 0x08, 0xf2, 0xd1, 0xc9, 0x51, 0x0c, 0x31, 0x38, 0xcc, 0x36, + 0xaf, 0xda, 0x3c, 0xfd, 0x44, 0xb8, 0xfb, 0xe0, 0xbe, 0x92, 0x4b, 0x7c, 0x2c, 0x33, 0x88, 0x16, + 0x26, 0x5c, 0xa5, 0x56, 0x2d, 0xc3, 0x55, 0x3a, 0x83, 0x54, 0x48, 0xe1, 0xa3, 0x01, 0x1a, 0xf6, + 0x26, 0x87, 0x35, 0x9c, 0x6e, 0xd8, 0xb4, 0x41, 0xe4, 0x1c, 0xef, 0xbb, 0x39, 0x54, 0x42, 0xa6, + 0x56, 0xcd, 0x95, 0xd4, 0xfe, 0xd6, 0x00, 0x0d, 0x77, 0x26, 0x7b, 0x6e, 0xbf, 0x6d, 0x67, 0x72, + 0x86, 0x77, 0x13, 0x5e, 0x84, 0x39, 0x5f, 0x2a, 0xc1, 0x2d, 0x68, 0xe3, 0x6f, 0xbb, 0xbb, 0xbd, + 0x84, 0x17, 0x8f, 0xed, 0x48, 0x2e, 0x30, 0x59, 0x28, 0x63, 0x41, 0xab, 0x88, 0x2f, 0x43, 0x99, + 0x5a, 0xad, 0xa4, 0xf1, 0x3b, 0x4e, 0x3d, 0xf8, 0x27, 0xe3, 0x1a, 0x5c, 0x77, 0xde, 0x3f, 0xfa, + 0xde, 0xcd, 0xdd, 0x57, 0x49, 0xd1, 0xba, 0xa4, 0xe8, 0xa7, 0xa4, 0xe8, 0xad, 0xa2, 0xde, 0xba, + 0xa2, 0xde, 0x77, 0x45, 0xbd, 0xa7, 0x51, 0xac, 0xec, 0x62, 0x35, 0x0b, 0x22, 0x48, 0xd8, 0xb8, + 0x8e, 0x72, 0x2f, 0xed, 0x0b, 0xe8, 0x67, 0xf6, 0x17, 0xb1, 0x68, 0x33, 0xda, 0xd7, 0x4c, 0x9a, + 0x59, 0xd7, 0x95, 0xb9, 0xfa, 0x0d, 0x00, 0x00, 0xff, 0xff, 0x52, 0x3e, 0xbb, 0xe0, 0x66, 0x01, + 0x00, 0x00, } func (m *Params) Marshal() (dAtA []byte, err error) { @@ -99,6 +147,28 @@ func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.HistoricalEntries != 0 { + i = encodeVarintParams(dAtA, i, uint64(m.HistoricalEntries)) + i-- + dAtA[i] = 0x20 + } + if m.MaxValidators != 0 { + i = encodeVarintParams(dAtA, i, uint64(m.MaxValidators)) + i-- + dAtA[i] = 0x18 + } + if len(m.EpochIdentifier) > 0 { + i -= len(m.EpochIdentifier) + copy(dAtA[i:], m.EpochIdentifier) + i = encodeVarintParams(dAtA, i, uint64(len(m.EpochIdentifier))) + i-- + dAtA[i] = 0x12 + } + if m.EpochsUntilUnbonded != 0 { + i = encodeVarintParams(dAtA, i, uint64(m.EpochsUntilUnbonded)) + i-- + dAtA[i] = 0x8 + } return len(dAtA) - i, nil } @@ -119,6 +189,19 @@ func (m *Params) Size() (n int) { } var l int _ = l + if m.EpochsUntilUnbonded != 0 { + n += 1 + sovParams(uint64(m.EpochsUntilUnbonded)) + } + l = len(m.EpochIdentifier) + if l > 0 { + n += 1 + l + sovParams(uint64(l)) + } + if m.MaxValidators != 0 { + n += 1 + sovParams(uint64(m.MaxValidators)) + } + if m.HistoricalEntries != 0 { + n += 1 + sovParams(uint64(m.HistoricalEntries)) + } return n } @@ -157,6 +240,95 @@ func (m *Params) Unmarshal(dAtA []byte) error { return fmt.Errorf("proto: Params: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field EpochsUntilUnbonded", wireType) + } + m.EpochsUntilUnbonded = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.EpochsUntilUnbonded |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field EpochIdentifier", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + 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 ErrInvalidLengthParams + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthParams + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.EpochIdentifier = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field MaxValidators", wireType) + } + m.MaxValidators = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.MaxValidators |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field HistoricalEntries", wireType) + } + m.HistoricalEntries = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.HistoricalEntries |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipParams(dAtA[iNdEx:]) From cb08e29ba2fac1e19cea2377405a9da531525752 Mon Sep 17 00:00:00 2001 From: MaxMustermann2 <82761650+MaxMustermann2@users.noreply.github.com> Date: Tue, 6 Feb 2024 17:45:24 +0000 Subject: [PATCH 02/14] feat(dogfood): load genesis state for bootstrap The bootstrapping smart contract is responsible for accepting user deposits, operator registration, and delegations. These operations may flow freely until 24 hours (say) before the spawn time of Exocore, at which the contract will be locked. During this time, the genesis state of Exocore will be amended to record the initial deposits, delegations, operator registrations and public keys. The last of these will feed into the dogfooding module which will mark these operators as part of the initial validator set and allow block production. This PR includes the capability to load that state, and save the newly created operator information to disk. In addition, the staking hooks have been set up (partially) in this PR, which allow the SDK's slashing keeper to record validator signing rates for downtime slashing. --- proto/buf.lock | 10 +- proto/buf.yaml | 2 +- proto/exocore/dogfood/v1/dogfood.proto | 24 ++ proto/exocore/dogfood/v1/genesis.proto | 8 + x/dogfood/genesis.go | 8 +- x/dogfood/keeper/hooks.go | 34 ++ x/dogfood/keeper/keeper.go | 32 +- x/dogfood/keeper/validators.go | 261 +++++++++++++++ x/dogfood/module.go | 4 +- x/dogfood/types/dogfood.pb.go | 429 +++++++++++++++++++++++++ x/dogfood/types/expected_keepers.go | 19 ++ x/dogfood/types/genesis.pb.go | 98 +++++- x/dogfood/types/keys.go | 23 ++ x/dogfood/types/validator.go | 45 +++ 14 files changed, 972 insertions(+), 25 deletions(-) create mode 100644 proto/exocore/dogfood/v1/dogfood.proto create mode 100644 x/dogfood/keeper/hooks.go create mode 100644 x/dogfood/keeper/validators.go create mode 100644 x/dogfood/types/dogfood.pb.go create mode 100644 x/dogfood/types/validator.go diff --git a/proto/buf.lock b/proto/buf.lock index face468fb..d025fc2f3 100644 --- a/proto/buf.lock +++ b/proto/buf.lock @@ -5,15 +5,19 @@ deps: owner: cosmos repository: cosmos-proto commit: 1935555c206d4afb9e94615dfd0fad31 + digest: shake256:c74d91a3ac7ae07d579e90eee33abf9b29664047ac8816500cf22c081fec0d72d62c89ce0bebafc1f6fec7aa5315be72606717740ca95007248425102c365377 - remote: buf.build owner: cosmos repository: cosmos-sdk - commit: 508e19f5f37549e3a471a2a59b903c00 + commit: 954f7b05f38440fc8250134b15adec47 + digest: shake256:2ab4404fd04a7d1d52df0e2d0f2d477a3d83ffd88d876957bf3fedfd702c8e52833d65b3ce1d89a3c5adf2aab512616b0e4f51d8463f07eda9a8a3317ee3ac54 - remote: buf.build owner: cosmos repository: gogo-proto - commit: 34d970b699f84aa382f3c29773a60836 + commit: 88ef6483f90f478fb938c37dde52ece3 + digest: shake256:89c45df2aa11e0cff97b0d695436713db3d993d76792e9f8dc1ae90e6ab9a9bec55503d48ceedd6b86069ab07d3041b32001b2bfe0227fa725dd515ff381e5ba - remote: buf.build owner: googleapis repository: googleapis - commit: 783e4b5374fa488ab068d08af9658438 + commit: e874a0be2bf140a5a4c7d4122c635823 + digest: shake256:4fe3036b4d706f6ee2b13c730bd04777f021dfd02ed27e6e40480acfe664a7548238312ee0727fd77648a38d227e296d43f4a38a34cdd46068156211016d9657 diff --git a/proto/buf.yaml b/proto/buf.yaml index d7876a9fc..00eff3659 100644 --- a/proto/buf.yaml +++ b/proto/buf.yaml @@ -1,7 +1,7 @@ version: v1 name: buf.build/evmos/evmos deps: - - buf.build/cosmos/cosmos-sdk + - buf.build/cosmos/cosmos-sdk:v0.47.0 - buf.build/cosmos/cosmos-proto - buf.build/cosmos/gogo-proto - buf.build/googleapis/googleapis diff --git a/proto/exocore/dogfood/v1/dogfood.proto b/proto/exocore/dogfood/v1/dogfood.proto new file mode 100644 index 000000000..e51f4a408 --- /dev/null +++ b/proto/exocore/dogfood/v1/dogfood.proto @@ -0,0 +1,24 @@ +syntax = "proto3"; + +package exocore.dogfood.v1; + +import "gogoproto/gogo.proto"; +import "google/protobuf/any.proto"; +import "cosmos_proto/cosmos.proto"; + +option go_package = "github.com/ExocoreNetwork/exocore/x/dogfood/types"; + +// ExocoreValidator is a validator that is part of the Exocore network. It is +// used to validate and sign blocks and transactions. +message ExocoreValidator { + // The address, as derived from the consensus key. It has no relation + // with the operator's account address. + bytes address = 1; + // Last known power + int64 power = 2; + // pubkey is the consensus public key of the validator, as a Protobuf Any. + google.protobuf.Any pubkey = 3 [ + (cosmos_proto.accepts_interface) = "cosmos.crypto.PubKey", + (gogoproto.moretags) = "yaml:\"consensus_pubkey\"" + ]; +} \ No newline at end of file diff --git a/proto/exocore/dogfood/v1/genesis.proto b/proto/exocore/dogfood/v1/genesis.proto index 4cb6bca2f..02b53d840 100644 --- a/proto/exocore/dogfood/v1/genesis.proto +++ b/proto/exocore/dogfood/v1/genesis.proto @@ -3,11 +3,19 @@ syntax = "proto3"; package exocore.dogfood.v1; import "gogoproto/gogo.proto"; + +import "tendermint/abci/types.proto"; + import "exocore/dogfood/v1/params.proto"; option go_package = "github.com/ExocoreNetwork/exocore/x/dogfood/types"; // GenesisState defines the dogfood module's genesis state. message GenesisState { + // Parameters of the module. Params params = 1 [(gogoproto.nullable) = false]; + + // Validator set, stored by ExocoreValidatorKey. + repeated .tendermint.abci.ValidatorUpdate val_set = 2 + [ (gogoproto.nullable) = false ]; } diff --git a/x/dogfood/genesis.go b/x/dogfood/genesis.go index 15eef3d3e..de40ed814 100644 --- a/x/dogfood/genesis.go +++ b/x/dogfood/genesis.go @@ -3,12 +3,18 @@ package dogfood import ( "github.com/ExocoreNetwork/exocore/x/dogfood/keeper" "github.com/ExocoreNetwork/exocore/x/dogfood/types" + abci "github.com/cometbft/cometbft/abci/types" sdk "github.com/cosmos/cosmos-sdk/types" ) // InitGenesis initializes the module's state from a provided genesis state. -func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState) { +func InitGenesis( + ctx sdk.Context, + k keeper.Keeper, + genState types.GenesisState, +) []abci.ValidatorUpdate { k.SetParams(ctx, genState.Params) + return k.ApplyValidatorChanges(ctx, genState.ValSet) } // ExportGenesis returns the module's exported genesis diff --git a/x/dogfood/keeper/hooks.go b/x/dogfood/keeper/hooks.go new file mode 100644 index 000000000..cfbd66959 --- /dev/null +++ b/x/dogfood/keeper/hooks.go @@ -0,0 +1,34 @@ +package keeper + +import ( + "github.com/ExocoreNetwork/exocore/x/dogfood/types" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// interface guard +var _ types.DogfoodHooks = &MultiDogfoodHooks{} + +// MultiDogfoodHooks is a collection of DogfoodHooks. It calls the hook for each element in the +// collection one-by-one. The hook is called in the order in which the collection is created. +type MultiDogfoodHooks []types.DogfoodHooks + +// NewMultiDogfoodHooks is used to create a collective object of dogfood hooks from a list of +// the hooks. It follows the "accept interface, return concrete types" philosophy. Other modules +// may set the hooks by calling k := (*k).SetHooks(NewMultiDogfoodHooks(hookI)) +func NewMultiDogfoodHooks(hooks ...types.DogfoodHooks) MultiDogfoodHooks { + return hooks +} + +// AfterValidatorBonded is the implementation of types.DogfoodHooks for MultiDogfoodHooks. +func (hooks MultiDogfoodHooks) AfterValidatorBonded( + ctx sdk.Context, + consAddr sdk.ConsAddress, + operator sdk.ValAddress, +) error { + for _, hook := range hooks { + if err := hook.AfterValidatorBonded(ctx, consAddr, operator); err != nil { + return err + } + } + return nil +} diff --git a/x/dogfood/keeper/keeper.go b/x/dogfood/keeper/keeper.go index 5abed0dd1..b90ec9095 100644 --- a/x/dogfood/keeper/keeper.go +++ b/x/dogfood/keeper/keeper.go @@ -17,13 +17,19 @@ type ( cdc codec.BinaryCodec storeKey storetypes.StoreKey paramstore paramtypes.Subspace + + dogfoodHooks types.DogfoodHooks + + epochsKeeper types.EpochsKeeper } ) +// NewKeeper creates a new dogfood keeper. func NewKeeper( cdc codec.BinaryCodec, storeKey storetypes.StoreKey, ps paramtypes.Subspace, + epochsKeeper types.EpochsKeeper, ) *Keeper { // set KeyTable if it has not already been set if !ps.HasKeyTable() { @@ -31,12 +37,32 @@ func NewKeeper( } return &Keeper{ - cdc: cdc, - storeKey: storeKey, - paramstore: ps, + cdc: cdc, + storeKey: storeKey, + paramstore: ps, + epochsKeeper: epochsKeeper, } } +// Logger returns a logger object for use within the module. func (k Keeper) Logger(ctx sdk.Context) log.Logger { return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName)) } + +// SetHooks sets the hooks on the keeper. It intentionally has a pointer receiver so that +// changes can be saved to the object. +func (k *Keeper) SetHooks(sh types.DogfoodHooks) *Keeper { + if k.dogfoodHooks != nil { + panic("cannot set dogfood hooks twice") + } + if sh == nil { + panic("cannot set nil dogfood hooks") + } + k.dogfoodHooks = sh + return k +} + +// Hooks returns the hooks registered to the module. +func (k Keeper) Hooks() types.DogfoodHooks { + return k.dogfoodHooks +} diff --git a/x/dogfood/keeper/validators.go b/x/dogfood/keeper/validators.go new file mode 100644 index 000000000..d1b5e3e37 --- /dev/null +++ b/x/dogfood/keeper/validators.go @@ -0,0 +1,261 @@ +// This file is a duplicate of the subscriber module's validators file with minor changes. +// The function ApplyValidatorChanges can likely be carved out into a shared package. + +package keeper + +import ( + "time" + + "github.com/ExocoreNetwork/exocore/x/dogfood/types" + abci "github.com/cometbft/cometbft/abci/types" + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" +) + +// UnbondingTime returns the time duration of the unbonding period. It is part of the +// implementation of the staking keeper expected by IBC. +// It is calculated as the number of epochs until unbonded multiplied by the duration of an +// epoch. This function is used by IBC's client keeper to validate the self client, and +// nowhere else. As long as it reports a consistent value, it's fine. +func (k Keeper) UnbondingTime(ctx sdk.Context) time.Duration { + count := k.GetEpochsUntilUnbonded(ctx) + identifier := k.GetEpochIdentifier(ctx) + epoch, found := k.epochsKeeper.GetEpochInfo(ctx, identifier) + if !found { + panic("epoch info not found") + } + durationPerEpoch := epoch.Duration + return time.Duration(count) * durationPerEpoch +} + +// ApplyValidatorChanges returns the validator set as is. However, it also +// stores the validators that are added or those that are removed, and updates +// the power for the existing validators. It also allows any hooks registered +// on the keeper to be executed. +func (k Keeper) ApplyValidatorChanges( + ctx sdk.Context, + changes []abci.ValidatorUpdate, +) []abci.ValidatorUpdate { + ret := []abci.ValidatorUpdate{} + for _, change := range changes { + // convert TM pubkey to SDK pubkey + pubkey, err := cryptocodec.FromTmProtoPublicKey(change.GetPubKey()) + if err != nil { + // An error here would indicate that the validator updates + // received from other modules are invalid. + panic(err) + } + addr := pubkey.Address() + val, found := k.GetValidator(ctx, addr) + + if found { + // update or delete an existing validator + if change.Power < 1 { + k.DeleteValidator(ctx, addr) + } else { + val.Power = change.Power + k.SetValidator(ctx, val) + } + } else if change.Power > 0 { + // create a new validator - the address is just derived from the public key and has + // no correlation with the operator address on Exocore + ocVal, err := types.NewExocoreValidator(addr, change.Power, pubkey) + if err != nil { + // An error here would indicate that the validator updates + // received are invalid. + panic(err) + } + + k.SetValidator(ctx, ocVal) + err = k.Hooks().AfterValidatorBonded(ctx, sdk.ConsAddress(addr), nil) + if err != nil { + // AfterValidatorBonded is hooked by the Slashing module and should not return + // an error. If any other module were to hook it, they should also not. + panic(err) + } + } else { + // edge case: we received an update for 0 power + // but the validator is already deleted. Do not forward + // to tendermint. + continue + } + + ret = append(ret, change) + } + return ret +} + +// SetValidator stores a validator based on the pub key derived address. This +// is accessible in the genesis state via `val_set`. +func (k Keeper) SetValidator(ctx sdk.Context, validator types.ExocoreValidator) { + store := ctx.KVStore(k.storeKey) + bz := k.cdc.MustMarshal(&validator) + + store.Set(types.ExocoreValidatorKey(validator.Address), bz) +} + +// GetValidator gets a validator based on the pub key derived address. +func (k Keeper) GetValidator( + ctx sdk.Context, addr []byte, +) (validator types.ExocoreValidator, found bool) { + store := ctx.KVStore(k.storeKey) + v := store.Get(types.ExocoreValidatorKey(addr)) + if v == nil { + return + } + k.cdc.MustUnmarshal(v, &validator) + found = true + + return +} + +// DeleteValidator deletes a validator based on the pub key derived address. +func (k Keeper) DeleteValidator(ctx sdk.Context, addr []byte) { + store := ctx.KVStore(k.storeKey) + store.Delete(types.ExocoreValidatorKey(addr)) +} + +// GetAllExocoreValidators returns all validators in the store. +func (k Keeper) GetAllExocoreValidators( + ctx sdk.Context, +) (validators []types.ExocoreValidator) { + store := ctx.KVStore(k.storeKey) + iterator := sdk.KVStorePrefixIterator(store, []byte{types.ExocoreValidatorBytePrefix}) + + defer iterator.Close() + for ; iterator.Valid(); iterator.Next() { + val := types.ExocoreValidator{} + k.cdc.MustUnmarshal(iterator.Value(), &val) + validators = append(validators, val) + } + + return validators +} + +// GetHistoricalInfo gets the historical info at a given height. It is part of the +// implementation of the staking keeper expected by IBC. +func (k Keeper) GetHistoricalInfo( + ctx sdk.Context, height int64, +) (stakingtypes.HistoricalInfo, bool) { + store := ctx.KVStore(k.storeKey) + key := types.HistoricalInfoKey(height) + + value := store.Get(key) + if value == nil { + return stakingtypes.HistoricalInfo{}, false + } + + return stakingtypes.MustUnmarshalHistoricalInfo(k.cdc, value), true +} + +// SetHistoricalInfo sets the historical info at a given height. This is +// (intentionally) not exported in the genesis state. +func (k Keeper) SetHistoricalInfo( + ctx sdk.Context, height int64, hi *stakingtypes.HistoricalInfo, +) { + store := ctx.KVStore(k.storeKey) + key := types.HistoricalInfoKey(height) + value := k.cdc.MustMarshal(hi) + + store.Set(key, value) +} + +// DeleteHistoricalInfo deletes the historical info at a given height. +func (k Keeper) DeleteHistoricalInfo(ctx sdk.Context, height int64) { + store := ctx.KVStore(k.storeKey) + key := types.HistoricalInfoKey(height) + + store.Delete(key) +} + +// TrackHistoricalInfo saves the latest historical-info and deletes the oldest +// heights that are below pruning height. +func (k Keeper) TrackHistoricalInfo(ctx sdk.Context) { + numHistoricalEntries := k.GetHistoricalEntries(ctx) + + // Prune store to ensure we only have parameter-defined historical entries. + // In most cases, this will involve removing a single historical entry. + // In the rare scenario when the historical entries gets reduced to a lower value k' + // from the original value k. k - k' entries must be deleted from the store. + // Since the entries to be deleted are always in a continuous range, we can iterate + // over the historical entries starting from the most recent version to be pruned + // and then return at the first empty entry. + for i := ctx.BlockHeight() - int64(numHistoricalEntries); i >= 0; i-- { + _, found := k.GetHistoricalInfo(ctx, i) + if found { + k.DeleteHistoricalInfo(ctx, i) + } else { + break + } + } + + // if there is no need to persist historicalInfo, return. + if numHistoricalEntries == 0 { + return + } + + // Create HistoricalInfo struct + lastVals := []stakingtypes.Validator{} + for _, v := range k.GetAllExocoreValidators(ctx) { + pk, err := v.ConsPubKey() + if err != nil { + // This should never happen as the pubkey is assumed + // to be stored correctly earlier. + panic(err) + } + val, err := stakingtypes.NewValidator(nil, pk, stakingtypes.Description{}) + if err != nil { + // This should never happen as the pubkey is assumed + // to be stored correctly earlier. + panic(err) + } + + // Set validator to bonded status. + val.Status = stakingtypes.Bonded + // Compute tokens from voting power. + val.Tokens = sdk.TokensFromConsensusPower( + v.Power, + // TODO(mm) + // note that this is not super relevant for the historical info + // since IBC does not seem to use the tokens field. + sdk.NewInt(1), + ) + lastVals = append(lastVals, val) + } + + // Create historical info entry which sorts the validator set by voting power. + historicalEntry := stakingtypes.NewHistoricalInfo( + ctx.BlockHeader(), lastVals, + // TODO(mm) + // this should match the power reduction number above + // and is also thus not relevant. + sdk.NewInt(1), + ) + + // Set latest HistoricalInfo at current height. + k.SetHistoricalInfo(ctx, ctx.BlockHeight(), &historicalEntry) +} + +// MustGetCurrentValidatorsAsABCIUpdates gets all validators converted +// to the ABCI validator update type. It panics in case of failure. +func (k Keeper) MustGetCurrentValidatorsAsABCIUpdates(ctx sdk.Context) []abci.ValidatorUpdate { + vals := k.GetAllExocoreValidators(ctx) + valUpdates := make([]abci.ValidatorUpdate, 0, len(vals)) + for _, v := range vals { + pk, err := v.ConsPubKey() + if err != nil { + // This should never happen as the pubkey is assumed + // to be stored correctly earlier. + panic(err) + } + tmPK, err := cryptocodec.ToTmProtoPublicKey(pk) + if err != nil { + // This should never happen as the pubkey is assumed + // to be stored correctly earlier. + panic(err) + } + valUpdates = append(valUpdates, abci.ValidatorUpdate{PubKey: tmPK, Power: v.Power}) + } + return valUpdates +} diff --git a/x/dogfood/module.go b/x/dogfood/module.go index e8ea80431..2ac7087e3 100644 --- a/x/dogfood/module.go +++ b/x/dogfood/module.go @@ -141,9 +141,7 @@ func (am AppModule) InitGenesis( // Initialize global index to index in genesis state cdc.MustUnmarshalJSON(gs, &genState) - InitGenesis(ctx, am.keeper, genState) - - return []abci.ValidatorUpdate{} + return InitGenesis(ctx, am.keeper, genState) } // ExportGenesis returns the module's exported genesis state as raw JSON bytes. diff --git a/x/dogfood/types/dogfood.pb.go b/x/dogfood/types/dogfood.pb.go new file mode 100644 index 000000000..64cadbb85 --- /dev/null +++ b/x/dogfood/types/dogfood.pb.go @@ -0,0 +1,429 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: exocore/dogfood/v1/dogfood.proto + +package types + +import ( + fmt "fmt" + _ "github.com/cosmos/cosmos-proto" + types "github.com/cosmos/cosmos-sdk/codec/types" + _ "github.com/cosmos/gogoproto/gogoproto" + 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 + +// ExocoreValidator is a validator that is part of the Exocore network. It is +// used to validate and sign blocks and transactions. +type ExocoreValidator struct { + // The address, as derived from the consensus key. It has no relation + // with the operator's account address. + Address []byte `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + // Last known power + Power int64 `protobuf:"varint,2,opt,name=power,proto3" json:"power,omitempty"` + // pubkey is the consensus public key of the validator, as a Protobuf Any. + Pubkey *types.Any `protobuf:"bytes,3,opt,name=pubkey,proto3" json:"pubkey,omitempty" yaml:"consensus_pubkey"` +} + +func (m *ExocoreValidator) Reset() { *m = ExocoreValidator{} } +func (m *ExocoreValidator) String() string { return proto.CompactTextString(m) } +func (*ExocoreValidator) ProtoMessage() {} +func (*ExocoreValidator) Descriptor() ([]byte, []int) { + return fileDescriptor_071b9989c501c3f2, []int{0} +} +func (m *ExocoreValidator) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ExocoreValidator) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ExocoreValidator.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 *ExocoreValidator) XXX_Merge(src proto.Message) { + xxx_messageInfo_ExocoreValidator.Merge(m, src) +} +func (m *ExocoreValidator) XXX_Size() int { + return m.Size() +} +func (m *ExocoreValidator) XXX_DiscardUnknown() { + xxx_messageInfo_ExocoreValidator.DiscardUnknown(m) +} + +var xxx_messageInfo_ExocoreValidator proto.InternalMessageInfo + +func (m *ExocoreValidator) GetAddress() []byte { + if m != nil { + return m.Address + } + return nil +} + +func (m *ExocoreValidator) GetPower() int64 { + if m != nil { + return m.Power + } + return 0 +} + +func (m *ExocoreValidator) GetPubkey() *types.Any { + if m != nil { + return m.Pubkey + } + return nil +} + +func init() { + proto.RegisterType((*ExocoreValidator)(nil), "exocore.dogfood.v1.ExocoreValidator") +} + +func init() { proto.RegisterFile("exocore/dogfood/v1/dogfood.proto", fileDescriptor_071b9989c501c3f2) } + +var fileDescriptor_071b9989c501c3f2 = []byte{ + // 302 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x3c, 0x90, 0x31, 0x4e, 0xf3, 0x30, + 0x14, 0xc7, 0xeb, 0xaf, 0xfa, 0x8a, 0x14, 0x18, 0x50, 0x14, 0x89, 0xd0, 0xc1, 0x44, 0x9d, 0xba, + 0x60, 0xab, 0x74, 0x63, 0xa3, 0x12, 0x53, 0x25, 0x84, 0x3a, 0x30, 0xb0, 0x54, 0x4e, 0xe2, 0x9a, + 0xaa, 0x49, 0x5e, 0x64, 0x3b, 0x6d, 0x7d, 0x0b, 0x2e, 0xc1, 0x0d, 0x38, 0x04, 0x62, 0xea, 0xc8, + 0x84, 0x50, 0x72, 0x03, 0x4e, 0x80, 0x88, 0x63, 0xb6, 0xf7, 0xf3, 0xff, 0x3d, 0xfb, 0xe7, 0xe7, + 0x45, 0x7c, 0x0f, 0x09, 0x48, 0x4e, 0x53, 0x10, 0x2b, 0x80, 0x94, 0x6e, 0x27, 0xae, 0x24, 0xa5, + 0x04, 0x0d, 0xbe, 0xdf, 0x75, 0x10, 0x77, 0xbc, 0x9d, 0x0c, 0x03, 0x01, 0x02, 0xda, 0x98, 0xfe, + 0x56, 0xb6, 0x73, 0x78, 0x2e, 0x00, 0x44, 0xc6, 0x69, 0x4b, 0x71, 0xb5, 0xa2, 0xac, 0x30, 0x2e, + 0x4a, 0x40, 0xe5, 0xa0, 0x96, 0x76, 0xc6, 0x82, 0x8d, 0x46, 0x2f, 0xc8, 0x3b, 0xbd, 0xb5, 0x4f, + 0x3c, 0xb0, 0x6c, 0x9d, 0x32, 0x0d, 0xd2, 0x0f, 0xbd, 0x23, 0x96, 0xa6, 0x92, 0x2b, 0x15, 0xa2, + 0x08, 0x8d, 0x4f, 0x16, 0x0e, 0xfd, 0xc0, 0xfb, 0x5f, 0xc2, 0x8e, 0xcb, 0xf0, 0x5f, 0x84, 0xc6, + 0xfd, 0x85, 0x05, 0x9f, 0x79, 0x83, 0xb2, 0x8a, 0x37, 0xdc, 0x84, 0xfd, 0x08, 0x8d, 0x8f, 0xaf, + 0x02, 0x62, 0x5d, 0x88, 0x73, 0x21, 0x37, 0x85, 0x99, 0x4d, 0xbf, 0x3f, 0x2f, 0xce, 0x0c, 0xcb, + 0xb3, 0xeb, 0x51, 0x02, 0x85, 0xe2, 0x85, 0xaa, 0xd4, 0xd2, 0xce, 0x8d, 0xde, 0x5f, 0x2f, 0x83, + 0xce, 0x2b, 0x91, 0xa6, 0xd4, 0x40, 0xee, 0xab, 0x78, 0xce, 0xcd, 0xa2, 0xbb, 0x78, 0x36, 0x7f, + 0xab, 0x31, 0x3a, 0xd4, 0x18, 0x7d, 0xd5, 0x18, 0x3d, 0x37, 0xb8, 0x77, 0x68, 0x70, 0xef, 0xa3, + 0xc1, 0xbd, 0xc7, 0x89, 0x58, 0xeb, 0xa7, 0x2a, 0x26, 0x09, 0xe4, 0xb4, 0xfb, 0xc9, 0x1d, 0xd7, + 0x3b, 0x90, 0x1b, 0xea, 0xb6, 0xbb, 0xff, 0xdb, 0xaf, 0x36, 0x25, 0x57, 0xf1, 0xa0, 0xf5, 0x9a, + 0xfe, 0x04, 0x00, 0x00, 0xff, 0xff, 0x39, 0xda, 0x59, 0x49, 0x7f, 0x01, 0x00, 0x00, +} + +func (m *ExocoreValidator) 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 *ExocoreValidator) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ExocoreValidator) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Pubkey != nil { + { + size, err := m.Pubkey.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintDogfood(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + if m.Power != 0 { + i = encodeVarintDogfood(dAtA, i, uint64(m.Power)) + i-- + dAtA[i] = 0x10 + } + if len(m.Address) > 0 { + i -= len(m.Address) + copy(dAtA[i:], m.Address) + i = encodeVarintDogfood(dAtA, i, uint64(len(m.Address))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintDogfood(dAtA []byte, offset int, v uint64) int { + offset -= sovDogfood(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *ExocoreValidator) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Address) + if l > 0 { + n += 1 + l + sovDogfood(uint64(l)) + } + if m.Power != 0 { + n += 1 + sovDogfood(uint64(m.Power)) + } + if m.Pubkey != nil { + l = m.Pubkey.Size() + n += 1 + l + sovDogfood(uint64(l)) + } + return n +} + +func sovDogfood(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozDogfood(x uint64) (n int) { + return sovDogfood(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *ExocoreValidator) 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 ErrIntOverflowDogfood + } + 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: ExocoreValidator: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ExocoreValidator: 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 byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDogfood + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthDogfood + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthDogfood + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Address = append(m.Address[:0], dAtA[iNdEx:postIndex]...) + if m.Address == nil { + m.Address = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Power", wireType) + } + m.Power = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDogfood + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Power |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pubkey", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDogfood + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthDogfood + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthDogfood + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Pubkey == nil { + m.Pubkey = &types.Any{} + } + if err := m.Pubkey.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipDogfood(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthDogfood + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipDogfood(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, ErrIntOverflowDogfood + } + 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, ErrIntOverflowDogfood + } + 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, ErrIntOverflowDogfood + } + 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, ErrInvalidLengthDogfood + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupDogfood + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthDogfood + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthDogfood = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowDogfood = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupDogfood = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/dogfood/types/expected_keepers.go b/x/dogfood/types/expected_keepers.go index ab1254f4c..b498ad09b 100644 --- a/x/dogfood/types/expected_keepers.go +++ b/x/dogfood/types/expected_keepers.go @@ -1 +1,20 @@ package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + epochsTypes "github.com/evmos/evmos/v14/x/epochs/types" +) + +// EpochsKeeper represents the expected keeper interface for the epochs module. +type EpochsKeeper interface { + GetEpochInfo(sdk.Context, string) (epochsTypes.EpochInfo, bool) +} + +// DogfoodHooks represent the event hooks for dogfood module. Ideally, these should +// match those of the staking module but for now it is only a subset of them. The side effects +// of calling the other hooks are not relevant to running the chain, so they can be skipped. +type DogfoodHooks interface { + AfterValidatorBonded( + sdk.Context, sdk.ConsAddress, sdk.ValAddress, + ) error +} diff --git a/x/dogfood/types/genesis.pb.go b/x/dogfood/types/genesis.pb.go index a70ecb578..b3744468b 100644 --- a/x/dogfood/types/genesis.pb.go +++ b/x/dogfood/types/genesis.pb.go @@ -5,6 +5,7 @@ package types import ( fmt "fmt" + types "github.com/cometbft/cometbft/abci/types" _ "github.com/cosmos/gogoproto/gogoproto" proto "github.com/cosmos/gogoproto/proto" io "io" @@ -25,7 +26,10 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package // GenesisState defines the dogfood module's genesis state. type GenesisState struct { + // Parameters of the module. Params Params `protobuf:"bytes,1,opt,name=params,proto3" json:"params"` + // Validator set, stored by ExocoreValidatorKey. + ValSet []types.ValidatorUpdate `protobuf:"bytes,2,rep,name=val_set,json=valSet,proto3" json:"val_set"` } func (m *GenesisState) Reset() { *m = GenesisState{} } @@ -68,6 +72,13 @@ func (m *GenesisState) GetParams() Params { return Params{} } +func (m *GenesisState) GetValSet() []types.ValidatorUpdate { + if m != nil { + return m.ValSet + } + return nil +} + func init() { proto.RegisterType((*GenesisState)(nil), "exocore.dogfood.v1.GenesisState") } @@ -75,20 +86,25 @@ func init() { func init() { proto.RegisterFile("exocore/dogfood/v1/genesis.proto", fileDescriptor_1a9d908a27866b1b) } var fileDescriptor_1a9d908a27866b1b = []byte{ - // 202 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x48, 0xad, 0xc8, 0x4f, - 0xce, 0x2f, 0x4a, 0xd5, 0x4f, 0xc9, 0x4f, 0x4f, 0xcb, 0xcf, 0x4f, 0xd1, 0x2f, 0x33, 0xd4, 0x4f, - 0x4f, 0xcd, 0x4b, 0x2d, 0xce, 0x2c, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x82, 0xaa, - 0xd0, 0x83, 0xaa, 0xd0, 0x2b, 0x33, 0x94, 0x12, 0x49, 0xcf, 0x4f, 0xcf, 0x07, 0x4b, 0xeb, 0x83, - 0x58, 0x10, 0x95, 0x52, 0xf2, 0x58, 0xcc, 0x2a, 0x48, 0x2c, 0x4a, 0xcc, 0x85, 0x1a, 0xa5, 0xe4, - 0xc1, 0xc5, 0xe3, 0x0e, 0x31, 0x3b, 0xb8, 0x24, 0xb1, 0x24, 0x55, 0xc8, 0x82, 0x8b, 0x0d, 0x22, - 0x2f, 0xc1, 0xa8, 0xc0, 0xa8, 0xc1, 0x6d, 0x24, 0xa5, 0x87, 0x69, 0x97, 0x5e, 0x00, 0x58, 0x85, - 0x13, 0xcb, 0x89, 0x7b, 0xf2, 0x0c, 0x41, 0x50, 0xf5, 0x4e, 0xde, 0x27, 0x1e, 0xc9, 0x31, 0x5e, - 0x78, 0x24, 0xc7, 0xf8, 0xe0, 0x91, 0x1c, 0xe3, 0x84, 0xc7, 0x72, 0x0c, 0x17, 0x1e, 0xcb, 0x31, - 0xdc, 0x78, 0x2c, 0xc7, 0x10, 0x65, 0x98, 0x9e, 0x59, 0x92, 0x51, 0x9a, 0xa4, 0x97, 0x9c, 0x9f, - 0xab, 0xef, 0x0a, 0x31, 0xcd, 0x2f, 0xb5, 0xa4, 0x3c, 0xbf, 0x28, 0x5b, 0x1f, 0xe6, 0xbc, 0x0a, - 0xb8, 0x03, 0x4b, 0x2a, 0x0b, 0x52, 0x8b, 0x93, 0xd8, 0xc0, 0xae, 0x33, 0x06, 0x04, 0x00, 0x00, - 0xff, 0xff, 0xda, 0xc5, 0xe8, 0xc9, 0x0c, 0x01, 0x00, 0x00, + // 273 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x8f, 0x31, 0x4b, 0xc3, 0x40, + 0x14, 0xc7, 0x73, 0x2a, 0x15, 0x52, 0xa7, 0xe0, 0x50, 0x22, 0x5c, 0x83, 0x53, 0xa7, 0x3b, 0x52, + 0x17, 0x37, 0xa1, 0x20, 0x0e, 0x82, 0x88, 0x45, 0x07, 0x17, 0xb9, 0x24, 0xcf, 0x78, 0x98, 0xe4, + 0x85, 0xcb, 0x33, 0xd6, 0x8f, 0xe0, 0xe6, 0xc7, 0xea, 0xd8, 0xd1, 0x49, 0x24, 0xf9, 0x22, 0xd2, + 0xe4, 0xaa, 0x83, 0xdd, 0x0e, 0xee, 0xc7, 0xfb, 0xff, 0x7e, 0x6e, 0x00, 0x0b, 0x8c, 0xd1, 0x80, + 0x4c, 0x30, 0x7d, 0x44, 0x4c, 0x64, 0x1d, 0xca, 0x14, 0x0a, 0xa8, 0x74, 0x25, 0x4a, 0x83, 0x84, + 0x9e, 0x67, 0x09, 0x61, 0x09, 0x51, 0x87, 0xfe, 0x61, 0x8a, 0x29, 0x76, 0xdf, 0x72, 0xfd, 0xea, + 0x49, 0xff, 0x88, 0xa0, 0x48, 0xc0, 0xe4, 0xba, 0x20, 0xa9, 0xa2, 0x58, 0x4b, 0x7a, 0x2b, 0xc1, + 0x9e, 0xf1, 0xc7, 0x5b, 0x86, 0x4a, 0x65, 0x54, 0x6e, 0x81, 0xe3, 0x77, 0xe6, 0x1e, 0x5c, 0xf4, + 0xcb, 0x73, 0x52, 0x04, 0xde, 0xa9, 0x3b, 0xe8, 0x81, 0x11, 0x0b, 0xd8, 0x64, 0x38, 0xf5, 0xc5, + 0x7f, 0x13, 0x71, 0xdd, 0x11, 0xb3, 0xbd, 0xe5, 0xd7, 0xd8, 0xb9, 0xb1, 0xbc, 0x77, 0xe6, 0xee, + 0xd7, 0x2a, 0x7b, 0xa8, 0x80, 0x46, 0x3b, 0xc1, 0xee, 0x64, 0x38, 0x0d, 0xc4, 0x9f, 0x9a, 0x58, + 0xab, 0x89, 0x3b, 0x95, 0xe9, 0x44, 0x11, 0x9a, 0xdb, 0x32, 0x51, 0x04, 0x9b, 0x03, 0xb5, 0xca, + 0xe6, 0x40, 0xb3, 0xcb, 0x65, 0xc3, 0xd9, 0xaa, 0xe1, 0xec, 0xbb, 0xe1, 0xec, 0xa3, 0xe5, 0xce, + 0xaa, 0xe5, 0xce, 0x67, 0xcb, 0x9d, 0xfb, 0x30, 0xd5, 0xf4, 0xf4, 0x12, 0x89, 0x18, 0x73, 0x79, + 0xde, 0xeb, 0x5c, 0x01, 0xbd, 0xa2, 0x79, 0x96, 0x9b, 0xc0, 0xc5, 0x6f, 0x62, 0xd7, 0x1f, 0x0d, + 0xba, 0xbe, 0x93, 0x9f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x26, 0x56, 0x05, 0x51, 0x6b, 0x01, 0x00, + 0x00, } func (m *GenesisState) Marshal() (dAtA []byte, err error) { @@ -111,6 +127,20 @@ func (m *GenesisState) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if len(m.ValSet) > 0 { + for iNdEx := len(m.ValSet) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.ValSet[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + } { size, err := m.Params.MarshalToSizedBuffer(dAtA[:i]) if err != nil { @@ -143,6 +173,12 @@ func (m *GenesisState) Size() (n int) { _ = l l = m.Params.Size() n += 1 + l + sovGenesis(uint64(l)) + if len(m.ValSet) > 0 { + for _, e := range m.ValSet { + l = e.Size() + n += 1 + l + sovGenesis(uint64(l)) + } + } return n } @@ -214,6 +250,40 @@ func (m *GenesisState) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ValSet", 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.ValSet = append(m.ValSet, types.ValidatorUpdate{}) + if err := m.ValSet[len(m.ValSet)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenesis(dAtA[iNdEx:]) diff --git a/x/dogfood/types/keys.go b/x/dogfood/types/keys.go index 1256e87e4..f7efde563 100644 --- a/x/dogfood/types/keys.go +++ b/x/dogfood/types/keys.go @@ -1,5 +1,9 @@ package types +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + const ( // ModuleName defines the module name ModuleName = "dogfood" @@ -7,3 +11,22 @@ const ( // StoreKey defines the primary module store key StoreKey = ModuleName ) + +const ( + // ExocoreValidatorBytePrefix is the prefix for the validator store. + ExocoreValidatorBytePrefix byte = iota + 1 + + // HistoricalInfoBytePrefix is the prefix for the historical info store. + HistoricalInfoBytePrefix +) + +// ExocoreValidatorKey returns the key for the validator store. +func ExocoreValidatorKey(address sdk.AccAddress) []byte { + return append([]byte{ExocoreValidatorBytePrefix}, address.Bytes()...) +} + +// HistoricalInfoKey returns the key for the historical info store. +func HistoricalInfoKey(height int64) []byte { + bz := sdk.Uint64ToBigEndian(uint64(height)) + return append([]byte{HistoricalInfoBytePrefix}, bz...) +} diff --git a/x/dogfood/types/validator.go b/x/dogfood/types/validator.go new file mode 100644 index 000000000..dafb75065 --- /dev/null +++ b/x/dogfood/types/validator.go @@ -0,0 +1,45 @@ +package types + +import ( + errorsmod "cosmossdk.io/errors" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" +) + +// NewExocoreValidator creates an ExocoreValidator with the specified (consensus) address, vote +// power and consensus public key. If the public key is malformed, it returns an error. +func NewExocoreValidator( + address []byte, power int64, pubKey cryptotypes.PubKey, +) (ExocoreValidator, error) { + pkAny, err := codectypes.NewAnyWithValue(pubKey) + if err != nil { + return ExocoreValidator{}, errorsmod.Wrap(err, "failed to pack public key") + } + return ExocoreValidator{ + Address: address, + Power: power, + Pubkey: pkAny, + }, nil +} + +// UnpackInterfaces implements UnpackInterfacesMessage.UnpackInterfaces. +// It is required to ensure that ConsPubKey below works. +func (ecv ExocoreValidator) UnpackInterfaces(unpacker codectypes.AnyUnpacker) error { + var pk cryptotypes.PubKey + return unpacker.UnpackAny(ecv.Pubkey, &pk) +} + +// ConsPubKey returns the validator PubKey as a cryptotypes.PubKey. +func (ecv ExocoreValidator) ConsPubKey() (cryptotypes.PubKey, error) { + pk, ok := ecv.Pubkey.GetCachedValue().(cryptotypes.PubKey) + if !ok { + return nil, errorsmod.Wrapf( + sdkerrors.ErrInvalidType, + "expecting cryptotypes.PubKey, got %T", + pk, + ) + } + + return pk, nil +} From b4f4df5606d22d30ef4916019ac8e205c78375f4 Mon Sep 17 00:00:00 2001 From: MaxMustermann2 <82761650+MaxMustermann2@users.noreply.github.com> Date: Wed, 7 Feb 2024 19:01:50 +0000 Subject: [PATCH 03/14] feat(dogfood): implement operator hooks --- proto/exocore/dogfood/v1/dogfood.proto | 58 ++ x/dogfood/keeper/impl_operator_hooks.go | 107 +++ x/dogfood/keeper/keeper.go | 35 + x/dogfood/keeper/opt_out.go | 162 ++++ x/dogfood/keeper/queue.go | 45 ++ x/dogfood/keeper/unbonding.go | 58 ++ x/dogfood/types/dogfood.pb.go | 955 +++++++++++++++++++++++- x/dogfood/types/expected_keepers.go | 14 +- x/dogfood/types/keys.go | 40 + x/dogfood/types/utils.go | 31 + 10 files changed, 1477 insertions(+), 28 deletions(-) create mode 100644 x/dogfood/keeper/impl_operator_hooks.go create mode 100644 x/dogfood/keeper/opt_out.go create mode 100644 x/dogfood/keeper/queue.go create mode 100644 x/dogfood/keeper/unbonding.go create mode 100644 x/dogfood/types/utils.go diff --git a/proto/exocore/dogfood/v1/dogfood.proto b/proto/exocore/dogfood/v1/dogfood.proto index e51f4a408..1259ac803 100644 --- a/proto/exocore/dogfood/v1/dogfood.proto +++ b/proto/exocore/dogfood/v1/dogfood.proto @@ -5,6 +5,7 @@ package exocore.dogfood.v1; import "gogoproto/gogo.proto"; import "google/protobuf/any.proto"; import "cosmos_proto/cosmos.proto"; +import "tendermint/crypto/keys.proto"; option go_package = "github.com/ExocoreNetwork/exocore/x/dogfood/types"; @@ -21,4 +22,61 @@ message ExocoreValidator { (cosmos_proto.accepts_interface) = "cosmos.crypto.PubKey", (gogoproto.moretags) = "yaml:\"consensus_pubkey\"" ]; +} + +// OperationType is used to indicate the type of operation that is being +// cached by the module to create the updated validator set. +enum OperationType { + option (gogoproto.goproto_enum_prefix) = false; + // KeyOpUnspecified is used to indicate that the operation type is not specified. + // This should never be used. + OPERATION_TYPE_UNSPECIFIED = 0 [ (gogoproto.enumvalue_customname) = "KeyOpUnspecified" ]; + // KeyAddition is used to indicate that the operation is a key addition. + OPERATION_TYPE_ADDITION_OR_UPDATE = 1 [ (gogoproto.enumvalue_customname) = "KeyAdditionOrUpdate" ]; + // KeyRemoval is used to indicate that the operation is a key removal. Typically + // this is done due to key replacement mechanism and not directly. + OPERATION_TYPE_REMOVAL = 2 [ (gogoproto.enumvalue_customname) = "KeyRemoval" ]; +} + +// QueueResultType is used to indicate the result of the queue operation. +enum QueueResultType { + option (gogoproto.goproto_enum_prefix) = false; + // QueueResultUnspecified is used to indicate that the queue result type is not specified. + QUEUE_RESULT_TYPE_UNSPECIFIED = 0 [ (gogoproto.enumvalue_customname) = "QueueResultUnspecified" ]; + // QueueResultSuccess is used to indicate that the queue operation was successful. + QUEUE_RESULT_TYPE_SUCCESS = 1 [ (gogoproto.enumvalue_customname) = "QueueResultSuccess" ]; + // QueueResultExists is used to indicate that the queue operation failed because the + // operation already exists in the queue. + QUEUE_RESULT_TYPE_EXISTS = 2 [ (gogoproto.enumvalue_customname) = "QueueResultExists" ]; + // QueueResultRemoved is used to indicate that the queue operation resulted in an existing + // operation being removed from the queue. + QUEUE_RESULT_TYPE_REMOVED = 3 [ (gogoproto.enumvalue_customname) = "QueueResultRemoved" ]; +} + +// Operation is used to indicate the operation that is being cached by the module +// to create the updated validator set. +message Operation { + // OperationType is the type of the operation (addition / removal). + OperationType operation_type = 1; + // OperatorAddress is the sdk.AccAddress of the operator. + bytes operator_address = 2; + // PubKey is the public key for which the operation is being applied. + tendermint.crypto.PublicKey pub_key = 3 [(gogoproto.nullable) = false]; +} + +// Operations is a collection of Operation. +message Operations { + repeated Operation list = 1 [(gogoproto.nullable) = false]; +} + +// AccountAddresses represents a list of account addresses. It is used to store the list of +// operator addresses whose operations are maturing at an epoch. +message AccountAddresses { + repeated bytes list = 1; +} + +// ConsensusAddresses represents a list of account addresses. It is used to store the list of +// addresses (which correspond to operator public keys) to delete at the end of an epoch. +message ConsensusAddresses { + repeated bytes list = 1; } \ No newline at end of file diff --git a/x/dogfood/keeper/impl_operator_hooks.go b/x/dogfood/keeper/impl_operator_hooks.go new file mode 100644 index 000000000..e5a42d615 --- /dev/null +++ b/x/dogfood/keeper/impl_operator_hooks.go @@ -0,0 +1,107 @@ +package keeper + +import ( + "strings" + + "github.com/ExocoreNetwork/exocore/x/dogfood/types" + tmprotocrypto "github.com/cometbft/cometbft/proto/tendermint/crypto" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// OperatorHooksWrapper is the wrapper structure that implements the operator hooks for the +// dogfood keeper. +type OperatorHooksWrapper struct { + keeper *Keeper +} + +// Interface guards +var _ types.OperatorHooks = OperatorHooksWrapper{} + +func (k *Keeper) OperatorHooks() OperatorHooksWrapper { + return OperatorHooksWrapper{k} +} + +// Hooks assumptions: Assuming I is opt-in, O is opt-out and R is key replacement, these are all +// possible within the same epoch, for a fresh operator. +// I O +// I R +// I R O +// This is not possible for a fresh operator to do: +// I O R +// R I O +// R I +// For an operator that is already opted in, the list looks like follows: +// R O +// O I +// O I R +// R O I +// The impossible list looks like: +// O R +// O R I +// TODO: list out operation results for each of these, and make sure everything is covered below + +// AfterOperatorOptIn is the implementation of the operator hooks. +func (h OperatorHooksWrapper) AfterOperatorOptIn( + ctx sdk.Context, addr sdk.AccAddress, + chainID string, pubKey tmprotocrypto.PublicKey, +) { + if strings.Compare(ctx.ChainID(), chainID) == 0 { + // res == Removed, it means operator has opted back in + // res == Success, there is no additional information to store + // res == Exists, there is nothing to do + if res := h.keeper.QueueOperation( + ctx, addr, pubKey, types.KeyAdditionOrUpdate, + ); res == types.QueueResultRemoved { + // the old operation was key removal, which is now removed from the queue. + // so all of the changes that were associated with it need to be undone. + h.keeper.ClearUnbondingInformation(ctx, addr, pubKey) + } + } +} + +// AfterOperatorKeyReplacement is the implementation of the operator hooks. +func (h OperatorHooksWrapper) AfterOperatorKeyReplacement( + ctx sdk.Context, addr sdk.AccAddress, + newKey tmprotocrypto.PublicKey, oldKey tmprotocrypto.PublicKey, + chainID string, +) { + if strings.Compare(chainID, ctx.ChainID()) == 0 { + // res == Removed, it means operator has added their original key again + // res == Success, there is no additional information to store + // res == Exists, there is no nothing to do + if res := h.keeper.QueueOperation( + ctx, addr, newKey, types.KeyAdditionOrUpdate, + ); res == types.QueueResultRemoved { + // see AfterOperatorOptIn for explanation + h.keeper.ClearUnbondingInformation(ctx, addr, newKey) + } + // res == Removed, it means operator had added this key and is now removing it. + // no additional information to clear. + // res == Success, the old key should be pruned from the operator module. + // res == Exists, there is nothing to do. + if res := h.keeper.QueueOperation( + ctx, addr, oldKey, types.KeyRemoval, + ); res == types.QueueResultSuccess { + // the old key can be marked for pruning + h.keeper.SetUnbondingInformation(ctx, addr, oldKey, false) + } + } +} + +// AfterOperatorOptOutInitiated is the implementation of the operator hooks. +func (h OperatorHooksWrapper) AfterOperatorOptOutInitiated( + ctx sdk.Context, addr sdk.AccAddress, + chainID string, pubKey tmprotocrypto.PublicKey, +) { + if strings.Compare(chainID, ctx.ChainID()) == 0 { + // res == Removed means operator had opted in and is now opting out. nothing to do if + // it is within the same epoch. + // res == Success, set up pruning deadline and opt out completion deadline + // res == Exists, there is nothing to do (should never happen) + if res := h.keeper.QueueOperation( + ctx, addr, pubKey, types.KeyRemoval, + ); res == types.QueueResultSuccess { + h.keeper.SetUnbondingInformation(ctx, addr, pubKey, true) + } + } +} diff --git a/x/dogfood/keeper/keeper.go b/x/dogfood/keeper/keeper.go index b90ec9095..47e766553 100644 --- a/x/dogfood/keeper/keeper.go +++ b/x/dogfood/keeper/keeper.go @@ -66,3 +66,38 @@ func (k *Keeper) SetHooks(sh types.DogfoodHooks) *Keeper { func (k Keeper) Hooks() types.DogfoodHooks { return k.dogfoodHooks } + +// GetQueuedKeyOperations returns the list of operations that are queued for execution at the +// end of the current epoch. +func (k Keeper) GetQueuedOperations( + ctx sdk.Context, +) []types.Operation { + store := ctx.KVStore(k.storeKey) + bz := store.Get(types.QueuedOperationsKey()) + if bz == nil { + return []types.Operation{} + } + var operations types.Operations + if err := operations.Unmarshal(bz); err != nil { + // TODO(mm): any failure to unmarshal is treated as no operations or panic? + return []types.Operation{} + } + return operations.GetList() +} + +// ClearQueuedOperations clears the operations to be executed at the end of the epoch. +func (k Keeper) ClearQueuedOperations(ctx sdk.Context) { + store := ctx.KVStore(k.storeKey) + store.Delete(types.QueuedOperationsKey()) +} + +// setQueuedOperations is a private, internal function used to update the current queue of +// operations to be executed at the end of the epoch with the supplied value. +func (k Keeper) setQueuedOperations(ctx sdk.Context, operations types.Operations) { + store := ctx.KVStore(k.storeKey) + bz, err := operations.Marshal() + if err != nil { + panic(err) + } + store.Set(types.QueuedOperationsKey(), bz) +} diff --git a/x/dogfood/keeper/opt_out.go b/x/dogfood/keeper/opt_out.go new file mode 100644 index 000000000..939d747e3 --- /dev/null +++ b/x/dogfood/keeper/opt_out.go @@ -0,0 +1,162 @@ +package keeper + +import ( + "github.com/ExocoreNetwork/exocore/x/dogfood/types" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// AppendOptOutToFinish appends an operator address to the list of operator addresses that have +// opted out and will be finished at the end of the provided epoch. +func (k Keeper) AppendOptOutToFinish( + ctx sdk.Context, epoch int64, operatorAddr sdk.AccAddress, +) { + prev := k.GetOptOutsToFinish(ctx, epoch) + next := types.AccountAddresses{List: append(prev, operatorAddr)} + k.setOptOutsToFinish(ctx, epoch, next) +} + +// GetOptOutsToFinish returns the list of operator addresses that have opted out and will be +// finished at the end of the provided epoch. +func (k Keeper) GetOptOutsToFinish( + ctx sdk.Context, epoch int64, +) [][]byte { + store := ctx.KVStore(k.storeKey) + key := types.OptOutsToFinishKey(epoch) + bz := store.Get(key) + if bz == nil { + return [][]byte{} + } + var res types.AccountAddresses + if err := res.Unmarshal(bz); err != nil { + panic(err) + } + return res.GetList() +} + +// setOptOutsToFinish sets the list of operator addresses that have opted out and will be +// finished at the end of the provided epoch. +func (k Keeper) setOptOutsToFinish( + ctx sdk.Context, epoch int64, addrs types.AccountAddresses, +) { + store := ctx.KVStore(k.storeKey) + key := types.OptOutsToFinishKey(epoch) + bz, err := addrs.Marshal() + if err != nil { + panic(err) + } + store.Set(key, bz) +} + +// RemoveOptOutToFinish removes an operator address from the list of operator addresses that +// have opted out and will be finished at the end of the provided epoch. +func (k Keeper) RemoveOptOutToFinish(ctx sdk.Context, epoch int64, addr sdk.AccAddress) { + prev := k.GetOptOutsToFinish(ctx, epoch) + next := types.AccountAddresses{ + List: types.RemoveFromBytesList(prev, addr), + } + k.setOptOutsToFinish(ctx, epoch, next) +} + +// ClearOptOutsToFinish clears the list of operator addresses that have opted out and will be +// finished at the end of the provided epoch. +func (k Keeper) ClearOptOutsToFinish(ctx sdk.Context, epoch int64) { + store := ctx.KVStore(k.storeKey) + key := types.OptOutsToFinishKey(epoch) + store.Delete(key) +} + +// SetOperatorOptOutFinishEpoch sets the epoch at which an operator's opt out will be finished. +func (k Keeper) SetOperatorOptOutFinishEpoch( + ctx sdk.Context, operatorAddr sdk.AccAddress, epoch int64, +) { + store := ctx.KVStore(k.storeKey) + key := types.OperatorOptOutFinishEpochKey(operatorAddr) + bz := sdk.Uint64ToBigEndian(uint64(epoch)) + store.Set(key, bz) +} + +// GetOperatorOptOutFinishEpoch returns the epoch at which an operator's opt out will be +// finished. +func (k Keeper) GetOperatorOptOutFinishEpoch( + ctx sdk.Context, operatorAddr sdk.AccAddress, +) int64 { + store := ctx.KVStore(k.storeKey) + key := types.OperatorOptOutFinishEpochKey(operatorAddr) + bz := store.Get(key) + if bz == nil { + return -1 + } + return int64(sdk.BigEndianToUint64(bz)) +} + +// DeleteOperatorOptOutFinishEpoch deletes the epoch at which an operator's opt out will be +// finished. +func (k Keeper) DeleteOperatorOptOutFinishEpoch( + ctx sdk.Context, operatorAddr sdk.AccAddress, +) { + store := ctx.KVStore(k.storeKey) + key := types.OperatorOptOutFinishEpochKey(operatorAddr) + store.Delete(key) +} + +// AppendConsensusAddrToPrune appends a consensus address to the list of consensus addresses to +// prune at the end of the epoch. +func (k Keeper) AppendConsensusAddrToPrune( + ctx sdk.Context, epoch int64, operatorAddr sdk.ConsAddress, +) { + prev := k.GetConsensusAddrsToPrune(ctx, epoch) + next := types.ConsensusAddresses{List: append(prev, operatorAddr)} + k.setConsensusAddrsToPrune(ctx, epoch, next) +} + +// GetConsensusAddrsToPrune returns the list of consensus addresses to prune at the end of the +// epoch. +func (k Keeper) GetConsensusAddrsToPrune( + ctx sdk.Context, epoch int64, +) [][]byte { + store := ctx.KVStore(k.storeKey) + key := types.ConsensusAddrsToPruneKey(epoch) + bz := store.Get(key) + if bz == nil { + return [][]byte{} + } + var res types.ConsensusAddresses + if err := res.Unmarshal(bz); err != nil { + panic(err) + } + return res.GetList() +} + +// DeleteConsensusAddrToPrune deletes a consensus address from the list of consensus addresses +// to prune at the end of the provided epoch. +func (k Keeper) DeleteConsensusAddrToPrune( + ctx sdk.Context, epoch int64, addr sdk.ConsAddress, +) { + prev := k.GetConsensusAddrsToPrune(ctx, epoch) + next := types.ConsensusAddresses{ + List: types.RemoveFromBytesList(prev, addr.Bytes()), + } + k.setConsensusAddrsToPrune(ctx, epoch, next) +} + +// ClearConsensusAddrsToPrune clears the list of consensus addresses to prune at the end of the +// epoch. +func (k Keeper) ClearConsensusAddrsToPrune(ctx sdk.Context, epoch int64) { + store := ctx.KVStore(k.storeKey) + key := types.ConsensusAddrsToPruneKey(epoch) + store.Delete(key) +} + +// setConsensusAddrsToPrune sets the list of consensus addresses to prune at the end of the +// epoch. +func (k Keeper) setConsensusAddrsToPrune( + ctx sdk.Context, epoch int64, addrs types.ConsensusAddresses, +) { + store := ctx.KVStore(k.storeKey) + key := types.ConsensusAddrsToPruneKey(epoch) + bz, err := addrs.Marshal() + if err != nil { + panic(err) + } + store.Set(key, bz) +} diff --git a/x/dogfood/keeper/queue.go b/x/dogfood/keeper/queue.go new file mode 100644 index 000000000..591962d5f --- /dev/null +++ b/x/dogfood/keeper/queue.go @@ -0,0 +1,45 @@ +package keeper + +import ( + "github.com/ExocoreNetwork/exocore/x/dogfood/types" + tmprotocrypto "github.com/cometbft/cometbft/proto/tendermint/crypto" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// QueueOperation adds an operation to the consensus public key queue. If a similar operation +// already exists, the queue is not modified and QueueResultExists is returned. If a reverse +// operation already exists (removal + addition, or addition + removal), the old operation is +// dropped from the queue and QueueResultRemoved is returned. In the case that the operation is +// added to the queue, QueueResultSuccess is returned. +func (k Keeper) QueueOperation( + ctx sdk.Context, addr sdk.AccAddress, + key tmprotocrypto.PublicKey, operationType types.OperationType, +) types.QueueResultType { + if operationType == types.KeyOpUnspecified { + // should never happen + panic("invalid operation type") + } + currentQueue := k.GetQueuedOperations(ctx) + indexToDelete := len(currentQueue) + for i, operation := range currentQueue { + if operation.PubKey.Equal(key) { + if operation.OperationType == operationType { + return types.QueueResultExists + } else { + indexToDelete = i + break + } + } + } + ret := types.QueueResultSuccess + if indexToDelete > len(currentQueue) { + currentQueue = append(currentQueue[:indexToDelete], currentQueue[indexToDelete+1:]...) + ret = types.QueueResultRemoved + } else { + operation := types.Operation{OperationType: operationType, OperatorAddress: addr, PubKey: key} + currentQueue = append(currentQueue, operation) + } + operations := types.Operations{List: currentQueue} + k.setQueuedOperations(ctx, operations) + return ret +} diff --git a/x/dogfood/keeper/unbonding.go b/x/dogfood/keeper/unbonding.go new file mode 100644 index 000000000..4987e523a --- /dev/null +++ b/x/dogfood/keeper/unbonding.go @@ -0,0 +1,58 @@ +package keeper + +import ( + "github.com/ExocoreNetwork/exocore/x/dogfood/types" + tmprotocrypto "github.com/cometbft/cometbft/proto/tendermint/crypto" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// ClearUnbondingInformation clears all information related to an operator's opt out +// or key replacement. This is done because the operator has opted back in or has +// replaced their key (again) with the original one. +func (k Keeper) ClearUnbondingInformation( + ctx sdk.Context, addr sdk.AccAddress, pubKey tmprotocrypto.PublicKey, +) { + optOutEpoch := k.GetOperatorOptOutFinishEpoch(ctx, addr) + k.DeleteOperatorOptOutFinishEpoch(ctx, addr) + k.RemoveOptOutToFinish(ctx, optOutEpoch, addr) + consAddress, err := types.TMCryptoPublicKeyToConsAddr(pubKey) + if err != nil { + panic(err) + } + k.DeleteConsensusAddrToPrune(ctx, optOutEpoch, consAddress) +} + +// SetUnbondingInformation sets information related to an operator's opt out or key replacement. +func (k Keeper) SetUnbondingInformation( + ctx sdk.Context, addr sdk.AccAddress, pubKey tmprotocrypto.PublicKey, isOptingOut bool, +) { + unbondingCompletionEpoch := k.GetUnbondingCompletionEpoch(ctx) + k.AppendOptOutToFinish(ctx, unbondingCompletionEpoch, addr) + if isOptingOut { + k.SetOperatorOptOutFinishEpoch(ctx, addr, unbondingCompletionEpoch) + } + consAddress, err := types.TMCryptoPublicKeyToConsAddr(pubKey) + if err != nil { + panic(err) + } + k.AppendConsensusAddrToPrune(ctx, unbondingCompletionEpoch, consAddress) +} + +// GetUnbondingCompletionEpoch returns the epoch at the end of which +// an unbonding triggered in this epoch will be completed. +func (k Keeper) GetUnbondingCompletionEpoch( + ctx sdk.Context, +) int64 { + unbondingEpochs := k.GetEpochsUntilUnbonded(ctx) + epochInfo, found := k.epochsKeeper.GetEpochInfo( + ctx, k.GetEpochIdentifier(ctx), + ) + if !found { + panic("current epoch not found") + } + // if i execute the transaction at epoch 5, the vote power change + // goes into effect at the beginning of epoch 6. the information + // should be held for 7 epochs, so it should be deleted at the + // beginning of epoch 13 or the end of epoch 12. + return epochInfo.CurrentEpoch + int64(unbondingEpochs) +} diff --git a/x/dogfood/types/dogfood.pb.go b/x/dogfood/types/dogfood.pb.go index 64cadbb85..3a64b1dea 100644 --- a/x/dogfood/types/dogfood.pb.go +++ b/x/dogfood/types/dogfood.pb.go @@ -5,6 +5,7 @@ package types import ( fmt "fmt" + crypto "github.com/cometbft/cometbft/proto/tendermint/crypto" _ "github.com/cosmos/cosmos-proto" types "github.com/cosmos/cosmos-sdk/codec/types" _ "github.com/cosmos/gogoproto/gogoproto" @@ -25,6 +26,79 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package +// OperationType is used to indicate the type of operation that is being +// cached by the module to create the updated validator set. +type OperationType int32 + +const ( + // KeyOpUnspecified is used to indicate that the operation type is not specified. + // This should never be used. + KeyOpUnspecified OperationType = 0 + // KeyAddition is used to indicate that the operation is a key addition. + KeyAdditionOrUpdate OperationType = 1 + // KeyRemoval is used to indicate that the operation is a key removal. Typically + // this is done due to key replacement mechanism and not directly. + KeyRemoval OperationType = 2 +) + +var OperationType_name = map[int32]string{ + 0: "OPERATION_TYPE_UNSPECIFIED", + 1: "OPERATION_TYPE_ADDITION_OR_UPDATE", + 2: "OPERATION_TYPE_REMOVAL", +} + +var OperationType_value = map[string]int32{ + "OPERATION_TYPE_UNSPECIFIED": 0, + "OPERATION_TYPE_ADDITION_OR_UPDATE": 1, + "OPERATION_TYPE_REMOVAL": 2, +} + +func (x OperationType) String() string { + return proto.EnumName(OperationType_name, int32(x)) +} + +func (OperationType) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_071b9989c501c3f2, []int{0} +} + +// QueueResultType is used to indicate the result of the queue operation. +type QueueResultType int32 + +const ( + // QueueResultUnspecified is used to indicate that the queue result type is not specified. + QueueResultUnspecified QueueResultType = 0 + // QueueResultSuccess is used to indicate that the queue operation was successful. + QueueResultSuccess QueueResultType = 1 + // QueueResultExists is used to indicate that the queue operation failed because the + // operation already exists in the queue. + QueueResultExists QueueResultType = 2 + // QueueResultRemoved is used to indicate that the queue operation resulted in an existing + // operation being removed from the queue. + QueueResultRemoved QueueResultType = 3 +) + +var QueueResultType_name = map[int32]string{ + 0: "QUEUE_RESULT_TYPE_UNSPECIFIED", + 1: "QUEUE_RESULT_TYPE_SUCCESS", + 2: "QUEUE_RESULT_TYPE_EXISTS", + 3: "QUEUE_RESULT_TYPE_REMOVED", +} + +var QueueResultType_value = map[string]int32{ + "QUEUE_RESULT_TYPE_UNSPECIFIED": 0, + "QUEUE_RESULT_TYPE_SUCCESS": 1, + "QUEUE_RESULT_TYPE_EXISTS": 2, + "QUEUE_RESULT_TYPE_REMOVED": 3, +} + +func (x QueueResultType) String() string { + return proto.EnumName(QueueResultType_name, int32(x)) +} + +func (QueueResultType) EnumDescriptor() ([]byte, []int) { + return fileDescriptor_071b9989c501c3f2, []int{1} +} + // ExocoreValidator is a validator that is part of the Exocore network. It is // used to validate and sign blocks and transactions. type ExocoreValidator struct { @@ -91,33 +165,267 @@ func (m *ExocoreValidator) GetPubkey() *types.Any { return nil } +// Operation is used to indicate the operation that is being cached by the module +// to create the updated validator set. +type Operation struct { + // OperationType is the type of the operation (addition / removal). + OperationType OperationType `protobuf:"varint,1,opt,name=operation_type,json=operationType,proto3,enum=exocore.dogfood.v1.OperationType" json:"operation_type,omitempty"` + // OperatorAddress is the sdk.AccAddress of the operator. + OperatorAddress []byte `protobuf:"bytes,2,opt,name=operator_address,json=operatorAddress,proto3" json:"operator_address,omitempty"` + // PubKey is the public key for which the operation is being applied. + PubKey crypto.PublicKey `protobuf:"bytes,3,opt,name=pub_key,json=pubKey,proto3" json:"pub_key"` +} + +func (m *Operation) Reset() { *m = Operation{} } +func (m *Operation) String() string { return proto.CompactTextString(m) } +func (*Operation) ProtoMessage() {} +func (*Operation) Descriptor() ([]byte, []int) { + return fileDescriptor_071b9989c501c3f2, []int{1} +} +func (m *Operation) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Operation) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Operation.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 *Operation) XXX_Merge(src proto.Message) { + xxx_messageInfo_Operation.Merge(m, src) +} +func (m *Operation) XXX_Size() int { + return m.Size() +} +func (m *Operation) XXX_DiscardUnknown() { + xxx_messageInfo_Operation.DiscardUnknown(m) +} + +var xxx_messageInfo_Operation proto.InternalMessageInfo + +func (m *Operation) GetOperationType() OperationType { + if m != nil { + return m.OperationType + } + return KeyOpUnspecified +} + +func (m *Operation) GetOperatorAddress() []byte { + if m != nil { + return m.OperatorAddress + } + return nil +} + +func (m *Operation) GetPubKey() crypto.PublicKey { + if m != nil { + return m.PubKey + } + return crypto.PublicKey{} +} + +// Operations is a collection of Operation. +type Operations struct { + List []Operation `protobuf:"bytes,1,rep,name=list,proto3" json:"list"` +} + +func (m *Operations) Reset() { *m = Operations{} } +func (m *Operations) String() string { return proto.CompactTextString(m) } +func (*Operations) ProtoMessage() {} +func (*Operations) Descriptor() ([]byte, []int) { + return fileDescriptor_071b9989c501c3f2, []int{2} +} +func (m *Operations) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Operations) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Operations.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 *Operations) XXX_Merge(src proto.Message) { + xxx_messageInfo_Operations.Merge(m, src) +} +func (m *Operations) XXX_Size() int { + return m.Size() +} +func (m *Operations) XXX_DiscardUnknown() { + xxx_messageInfo_Operations.DiscardUnknown(m) +} + +var xxx_messageInfo_Operations proto.InternalMessageInfo + +func (m *Operations) GetList() []Operation { + if m != nil { + return m.List + } + return nil +} + +// AccountAddresses represents a list of account addresses. It is used to store the list of +// operator addresses whose operations are maturing at an epoch. +type AccountAddresses struct { + List [][]byte `protobuf:"bytes,1,rep,name=list,proto3" json:"list,omitempty"` +} + +func (m *AccountAddresses) Reset() { *m = AccountAddresses{} } +func (m *AccountAddresses) String() string { return proto.CompactTextString(m) } +func (*AccountAddresses) ProtoMessage() {} +func (*AccountAddresses) Descriptor() ([]byte, []int) { + return fileDescriptor_071b9989c501c3f2, []int{3} +} +func (m *AccountAddresses) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *AccountAddresses) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_AccountAddresses.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 *AccountAddresses) XXX_Merge(src proto.Message) { + xxx_messageInfo_AccountAddresses.Merge(m, src) +} +func (m *AccountAddresses) XXX_Size() int { + return m.Size() +} +func (m *AccountAddresses) XXX_DiscardUnknown() { + xxx_messageInfo_AccountAddresses.DiscardUnknown(m) +} + +var xxx_messageInfo_AccountAddresses proto.InternalMessageInfo + +func (m *AccountAddresses) GetList() [][]byte { + if m != nil { + return m.List + } + return nil +} + +// ConsensusAddresses represents a list of account addresses. It is used to store the list of +// addresses (which correspond to operator public keys) to delete at the end of an epoch. +type ConsensusAddresses struct { + List [][]byte `protobuf:"bytes,1,rep,name=list,proto3" json:"list,omitempty"` +} + +func (m *ConsensusAddresses) Reset() { *m = ConsensusAddresses{} } +func (m *ConsensusAddresses) String() string { return proto.CompactTextString(m) } +func (*ConsensusAddresses) ProtoMessage() {} +func (*ConsensusAddresses) Descriptor() ([]byte, []int) { + return fileDescriptor_071b9989c501c3f2, []int{4} +} +func (m *ConsensusAddresses) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ConsensusAddresses) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ConsensusAddresses.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 *ConsensusAddresses) XXX_Merge(src proto.Message) { + xxx_messageInfo_ConsensusAddresses.Merge(m, src) +} +func (m *ConsensusAddresses) XXX_Size() int { + return m.Size() +} +func (m *ConsensusAddresses) XXX_DiscardUnknown() { + xxx_messageInfo_ConsensusAddresses.DiscardUnknown(m) +} + +var xxx_messageInfo_ConsensusAddresses proto.InternalMessageInfo + +func (m *ConsensusAddresses) GetList() [][]byte { + if m != nil { + return m.List + } + return nil +} + func init() { + proto.RegisterEnum("exocore.dogfood.v1.OperationType", OperationType_name, OperationType_value) + proto.RegisterEnum("exocore.dogfood.v1.QueueResultType", QueueResultType_name, QueueResultType_value) proto.RegisterType((*ExocoreValidator)(nil), "exocore.dogfood.v1.ExocoreValidator") + proto.RegisterType((*Operation)(nil), "exocore.dogfood.v1.Operation") + proto.RegisterType((*Operations)(nil), "exocore.dogfood.v1.Operations") + proto.RegisterType((*AccountAddresses)(nil), "exocore.dogfood.v1.AccountAddresses") + proto.RegisterType((*ConsensusAddresses)(nil), "exocore.dogfood.v1.ConsensusAddresses") } func init() { proto.RegisterFile("exocore/dogfood/v1/dogfood.proto", fileDescriptor_071b9989c501c3f2) } var fileDescriptor_071b9989c501c3f2 = []byte{ - // 302 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x3c, 0x90, 0x31, 0x4e, 0xf3, 0x30, - 0x14, 0xc7, 0xeb, 0xaf, 0xfa, 0x8a, 0x14, 0x18, 0x50, 0x14, 0x89, 0xd0, 0xc1, 0x44, 0x9d, 0xba, - 0x60, 0xab, 0x74, 0x63, 0xa3, 0x12, 0x53, 0x25, 0x84, 0x3a, 0x30, 0xb0, 0x54, 0x4e, 0xe2, 0x9a, - 0xaa, 0x49, 0x5e, 0x64, 0x3b, 0x6d, 0x7d, 0x0b, 0x2e, 0xc1, 0x0d, 0x38, 0x04, 0x62, 0xea, 0xc8, - 0x84, 0x50, 0x72, 0x03, 0x4e, 0x80, 0x88, 0x63, 0xb6, 0xf7, 0xf3, 0xff, 0x3d, 0xfb, 0xe7, 0xe7, - 0x45, 0x7c, 0x0f, 0x09, 0x48, 0x4e, 0x53, 0x10, 0x2b, 0x80, 0x94, 0x6e, 0x27, 0xae, 0x24, 0xa5, - 0x04, 0x0d, 0xbe, 0xdf, 0x75, 0x10, 0x77, 0xbc, 0x9d, 0x0c, 0x03, 0x01, 0x02, 0xda, 0x98, 0xfe, - 0x56, 0xb6, 0x73, 0x78, 0x2e, 0x00, 0x44, 0xc6, 0x69, 0x4b, 0x71, 0xb5, 0xa2, 0xac, 0x30, 0x2e, - 0x4a, 0x40, 0xe5, 0xa0, 0x96, 0x76, 0xc6, 0x82, 0x8d, 0x46, 0x2f, 0xc8, 0x3b, 0xbd, 0xb5, 0x4f, - 0x3c, 0xb0, 0x6c, 0x9d, 0x32, 0x0d, 0xd2, 0x0f, 0xbd, 0x23, 0x96, 0xa6, 0x92, 0x2b, 0x15, 0xa2, - 0x08, 0x8d, 0x4f, 0x16, 0x0e, 0xfd, 0xc0, 0xfb, 0x5f, 0xc2, 0x8e, 0xcb, 0xf0, 0x5f, 0x84, 0xc6, - 0xfd, 0x85, 0x05, 0x9f, 0x79, 0x83, 0xb2, 0x8a, 0x37, 0xdc, 0x84, 0xfd, 0x08, 0x8d, 0x8f, 0xaf, - 0x02, 0x62, 0x5d, 0x88, 0x73, 0x21, 0x37, 0x85, 0x99, 0x4d, 0xbf, 0x3f, 0x2f, 0xce, 0x0c, 0xcb, - 0xb3, 0xeb, 0x51, 0x02, 0x85, 0xe2, 0x85, 0xaa, 0xd4, 0xd2, 0xce, 0x8d, 0xde, 0x5f, 0x2f, 0x83, - 0xce, 0x2b, 0x91, 0xa6, 0xd4, 0x40, 0xee, 0xab, 0x78, 0xce, 0xcd, 0xa2, 0xbb, 0x78, 0x36, 0x7f, - 0xab, 0x31, 0x3a, 0xd4, 0x18, 0x7d, 0xd5, 0x18, 0x3d, 0x37, 0xb8, 0x77, 0x68, 0x70, 0xef, 0xa3, - 0xc1, 0xbd, 0xc7, 0x89, 0x58, 0xeb, 0xa7, 0x2a, 0x26, 0x09, 0xe4, 0xb4, 0xfb, 0xc9, 0x1d, 0xd7, - 0x3b, 0x90, 0x1b, 0xea, 0xb6, 0xbb, 0xff, 0xdb, 0xaf, 0x36, 0x25, 0x57, 0xf1, 0xa0, 0xf5, 0x9a, - 0xfe, 0x04, 0x00, 0x00, 0xff, 0xff, 0x39, 0xda, 0x59, 0x49, 0x7f, 0x01, 0x00, 0x00, + // 705 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x94, 0xcf, 0x4e, 0xdb, 0x4a, + 0x14, 0xc6, 0xe3, 0x84, 0x0b, 0xba, 0xc3, 0x3f, 0xdf, 0xb9, 0x29, 0x04, 0x0b, 0x82, 0xc9, 0xa2, + 0x4a, 0x91, 0x6a, 0x0b, 0x68, 0x55, 0xa9, 0x55, 0x2b, 0x99, 0xc4, 0x55, 0xa3, 0x50, 0x12, 0xec, + 0x18, 0xb5, 0xdd, 0x58, 0x8e, 0x7d, 0x48, 0x2d, 0x12, 0x8f, 0xe5, 0xb1, 0x01, 0xbf, 0x41, 0x95, + 0x55, 0x5f, 0x20, 0xab, 0xaa, 0x6f, 0x50, 0xa9, 0xab, 0xee, 0x51, 0x57, 0x2c, 0xbb, 0x42, 0x15, + 0xbc, 0x41, 0x77, 0xdd, 0x55, 0xf1, 0x1f, 0x48, 0x09, 0x62, 0xe7, 0x6f, 0xce, 0xf9, 0x66, 0x7e, + 0xdf, 0x91, 0x67, 0x10, 0x0f, 0x27, 0xc4, 0x24, 0x1e, 0x88, 0x16, 0xe9, 0x1c, 0x10, 0x62, 0x89, + 0x47, 0x1b, 0xe9, 0xa7, 0xe0, 0x7a, 0xc4, 0x27, 0x18, 0x27, 0x1d, 0x42, 0xba, 0x7c, 0xb4, 0xc1, + 0xe5, 0x3b, 0xa4, 0x43, 0xa2, 0xb2, 0x38, 0xfc, 0x8a, 0x3b, 0xb9, 0xa5, 0x0e, 0x21, 0x9d, 0x2e, + 0x88, 0x91, 0x6a, 0x07, 0x07, 0xa2, 0xe1, 0x84, 0x69, 0xc9, 0x24, 0xb4, 0x47, 0xa8, 0x1e, 0x7b, + 0x62, 0x91, 0x94, 0x96, 0x7d, 0x70, 0x2c, 0xf0, 0x7a, 0xb6, 0xe3, 0x8b, 0xa6, 0x17, 0xba, 0x3e, + 0x11, 0x0f, 0x21, 0x4c, 0xaa, 0xa5, 0xcf, 0x0c, 0x62, 0xe5, 0x18, 0x60, 0xdf, 0xe8, 0xda, 0x96, + 0xe1, 0x13, 0x0f, 0x17, 0xd0, 0x94, 0x61, 0x59, 0x1e, 0x50, 0x5a, 0x60, 0x78, 0xa6, 0x3c, 0xa3, + 0xa4, 0x12, 0xe7, 0xd1, 0x3f, 0x2e, 0x39, 0x06, 0xaf, 0x90, 0xe5, 0x99, 0x72, 0x4e, 0x89, 0x05, + 0x36, 0xd0, 0xa4, 0x1b, 0xb4, 0x0f, 0x21, 0x2c, 0xe4, 0x78, 0xa6, 0x3c, 0xbd, 0x99, 0x17, 0x62, + 0x52, 0x21, 0x25, 0x15, 0x24, 0x27, 0xdc, 0xde, 0xfa, 0x75, 0xbe, 0xba, 0x18, 0x1a, 0xbd, 0xee, + 0xd3, 0x92, 0x49, 0x1c, 0x0a, 0x0e, 0x0d, 0xa8, 0x1e, 0xfb, 0x4a, 0xdf, 0xbf, 0x3c, 0xcc, 0x27, + 0xd4, 0x31, 0xa3, 0xd0, 0x0c, 0xda, 0x75, 0x08, 0x95, 0x64, 0xe3, 0xd2, 0x37, 0x06, 0xfd, 0xdb, + 0x70, 0xc1, 0x33, 0x7c, 0x9b, 0x38, 0xf8, 0x15, 0x9a, 0x23, 0xa9, 0xd0, 0xfd, 0xd0, 0x85, 0x88, + 0x73, 0x6e, 0x73, 0x4d, 0x18, 0x1f, 0xa6, 0x70, 0x65, 0x6b, 0x85, 0x2e, 0x28, 0xb3, 0x64, 0x54, + 0xe2, 0x07, 0x88, 0x8d, 0x17, 0x88, 0xa7, 0xa7, 0x99, 0xb3, 0x51, 0xe6, 0xf9, 0x74, 0x5d, 0x4a, + 0xb2, 0x3f, 0x43, 0x53, 0x6e, 0xd0, 0xd6, 0xaf, 0x63, 0x2e, 0x0b, 0xd7, 0xa3, 0x1d, 0xc1, 0xee, + 0xda, 0x66, 0x1d, 0xc2, 0xed, 0x89, 0xd3, 0xf3, 0xd5, 0x4c, 0xc4, 0x5f, 0x87, 0xb0, 0x24, 0x23, + 0x74, 0xc5, 0x41, 0xf1, 0x13, 0x34, 0xd1, 0xb5, 0xa9, 0x5f, 0x60, 0xf8, 0x5c, 0x79, 0x7a, 0x73, + 0xe5, 0x4e, 0xea, 0x64, 0xa3, 0xc8, 0x50, 0xba, 0x8f, 0x58, 0xc9, 0x34, 0x49, 0xe0, 0xf8, 0x09, + 0x15, 0x50, 0x8c, 0x47, 0x36, 0x9b, 0x49, 0xfa, 0xca, 0x08, 0x57, 0xd2, 0x19, 0xdf, 0xd9, 0xb9, + 0xfe, 0x95, 0x41, 0xb3, 0x7f, 0x4d, 0x08, 0x3f, 0x42, 0x5c, 0xa3, 0x29, 0x2b, 0x52, 0xab, 0xd6, + 0xd8, 0xd5, 0x5b, 0x6f, 0x9b, 0xb2, 0xae, 0xed, 0xaa, 0x4d, 0xb9, 0x52, 0x7b, 0x59, 0x93, 0xab, + 0x6c, 0x86, 0xcb, 0xf7, 0x07, 0x3c, 0x5b, 0x87, 0xb0, 0xe1, 0x6a, 0x0e, 0x75, 0xc1, 0xb4, 0x0f, + 0x6c, 0xb0, 0xf0, 0x0b, 0xb4, 0x76, 0xc3, 0x25, 0x55, 0xab, 0xb5, 0x48, 0x35, 0x14, 0x5d, 0x6b, + 0x56, 0xa5, 0x96, 0xcc, 0x32, 0xdc, 0x62, 0x7f, 0xc0, 0xff, 0x5f, 0x87, 0x50, 0xb2, 0x2c, 0x7b, + 0x78, 0x62, 0xc3, 0xd3, 0x5c, 0xcb, 0xf0, 0x01, 0xaf, 0xa3, 0x85, 0x1b, 0x7e, 0x45, 0x7e, 0xdd, + 0xd8, 0x97, 0x76, 0xd8, 0x2c, 0x37, 0xd7, 0x1f, 0xf0, 0x68, 0xf8, 0x37, 0x40, 0x8f, 0x1c, 0x19, + 0x5d, 0x6e, 0xe2, 0xc3, 0xa7, 0x62, 0x66, 0xfd, 0x37, 0x83, 0xe6, 0xf7, 0x02, 0x08, 0x40, 0x01, + 0x1a, 0x74, 0xfd, 0x88, 0xfd, 0x39, 0x5a, 0xd9, 0xd3, 0x64, 0x6d, 0x68, 0x56, 0xb5, 0x9d, 0xd6, + 0x6d, 0xf8, 0x5c, 0x7f, 0xc0, 0x2f, 0x8c, 0xf8, 0x46, 0x43, 0x3c, 0x46, 0x4b, 0xe3, 0x76, 0x55, + 0xab, 0x54, 0x64, 0x55, 0x65, 0x19, 0x6e, 0xa1, 0x3f, 0xe0, 0xf1, 0x88, 0x55, 0x0d, 0x4c, 0x73, + 0xf8, 0x67, 0x6c, 0xa1, 0xc2, 0xb8, 0x4d, 0x7e, 0x53, 0x53, 0x5b, 0x2a, 0x9b, 0xe5, 0xee, 0xf5, + 0x07, 0xfc, 0x7f, 0x23, 0x2e, 0xf9, 0xc4, 0xa6, 0x3e, 0xbd, 0xfd, 0xac, 0x28, 0xb3, 0x5c, 0x65, + 0x73, 0x63, 0x67, 0x45, 0xd9, 0xc1, 0x8a, 0xb3, 0x6f, 0xd7, 0x4f, 0x2f, 0x8a, 0xcc, 0xd9, 0x45, + 0x91, 0xf9, 0x79, 0x51, 0x64, 0x3e, 0x5e, 0x16, 0x33, 0x67, 0x97, 0xc5, 0xcc, 0x8f, 0xcb, 0x62, + 0xe6, 0xdd, 0x46, 0xc7, 0xf6, 0xdf, 0x07, 0x6d, 0xc1, 0x24, 0x3d, 0x31, 0xb9, 0xd8, 0xbb, 0xe0, + 0x1f, 0x13, 0xef, 0x50, 0x4c, 0x9f, 0xa2, 0x93, 0xab, 0xc7, 0x68, 0x78, 0x75, 0x68, 0x7b, 0x32, + 0xba, 0xa6, 0x5b, 0x7f, 0x02, 0x00, 0x00, 0xff, 0xff, 0xa3, 0xf3, 0x23, 0x11, 0xac, 0x04, 0x00, + 0x00, } func (m *ExocoreValidator) Marshal() (dAtA []byte, err error) { @@ -167,6 +475,152 @@ func (m *ExocoreValidator) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *Operation) 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 *Operation) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Operation) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.PubKey.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintDogfood(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + if len(m.OperatorAddress) > 0 { + i -= len(m.OperatorAddress) + copy(dAtA[i:], m.OperatorAddress) + i = encodeVarintDogfood(dAtA, i, uint64(len(m.OperatorAddress))) + i-- + dAtA[i] = 0x12 + } + if m.OperationType != 0 { + i = encodeVarintDogfood(dAtA, i, uint64(m.OperationType)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + +func (m *Operations) 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 *Operations) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Operations) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.List) > 0 { + for iNdEx := len(m.List) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.List[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintDogfood(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *AccountAddresses) 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 *AccountAddresses) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *AccountAddresses) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.List) > 0 { + for iNdEx := len(m.List) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.List[iNdEx]) + copy(dAtA[i:], m.List[iNdEx]) + i = encodeVarintDogfood(dAtA, i, uint64(len(m.List[iNdEx]))) + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *ConsensusAddresses) 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 *ConsensusAddresses) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ConsensusAddresses) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.List) > 0 { + for iNdEx := len(m.List) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.List[iNdEx]) + copy(dAtA[i:], m.List[iNdEx]) + i = encodeVarintDogfood(dAtA, i, uint64(len(m.List[iNdEx]))) + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + func encodeVarintDogfood(dAtA []byte, offset int, v uint64) int { offset -= sovDogfood(v) base := offset @@ -198,15 +652,78 @@ func (m *ExocoreValidator) Size() (n int) { return n } -func sovDogfood(x uint64) (n int) { - return (math_bits.Len64(x|1) + 6) / 7 +func (m *Operation) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.OperationType != 0 { + n += 1 + sovDogfood(uint64(m.OperationType)) + } + l = len(m.OperatorAddress) + if l > 0 { + n += 1 + l + sovDogfood(uint64(l)) + } + l = m.PubKey.Size() + n += 1 + l + sovDogfood(uint64(l)) + return n } -func sozDogfood(x uint64) (n int) { - return sovDogfood(uint64((x << 1) ^ uint64((int64(x) >> 63)))) + +func (m *Operations) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.List) > 0 { + for _, e := range m.List { + l = e.Size() + n += 1 + l + sovDogfood(uint64(l)) + } + } + return n } -func (m *ExocoreValidator) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 + +func (m *AccountAddresses) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.List) > 0 { + for _, b := range m.List { + l = len(b) + n += 1 + l + sovDogfood(uint64(l)) + } + } + return n +} + +func (m *ConsensusAddresses) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.List) > 0 { + for _, b := range m.List { + l = len(b) + n += 1 + l + sovDogfood(uint64(l)) + } + } + return n +} + +func sovDogfood(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozDogfood(x uint64) (n int) { + return sovDogfood(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *ExocoreValidator) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 for iNdEx < l { preIndex := iNdEx var wire uint64 @@ -343,6 +860,390 @@ func (m *ExocoreValidator) Unmarshal(dAtA []byte) error { } return nil } +func (m *Operation) 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 ErrIntOverflowDogfood + } + 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: Operation: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Operation: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field OperationType", wireType) + } + m.OperationType = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDogfood + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.OperationType |= OperationType(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field OperatorAddress", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDogfood + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthDogfood + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthDogfood + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.OperatorAddress = append(m.OperatorAddress[:0], dAtA[iNdEx:postIndex]...) + if m.OperatorAddress == nil { + m.OperatorAddress = []byte{} + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PubKey", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDogfood + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthDogfood + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthDogfood + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.PubKey.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipDogfood(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthDogfood + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Operations) 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 ErrIntOverflowDogfood + } + 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: Operations: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Operations: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field List", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDogfood + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthDogfood + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthDogfood + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.List = append(m.List, Operation{}) + if err := m.List[len(m.List)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipDogfood(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthDogfood + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *AccountAddresses) 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 ErrIntOverflowDogfood + } + 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: AccountAddresses: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: AccountAddresses: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field List", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDogfood + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthDogfood + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthDogfood + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.List = append(m.List, make([]byte, postIndex-iNdEx)) + copy(m.List[len(m.List)-1], dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipDogfood(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthDogfood + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ConsensusAddresses) 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 ErrIntOverflowDogfood + } + 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: ConsensusAddresses: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ConsensusAddresses: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field List", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDogfood + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthDogfood + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthDogfood + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.List = append(m.List, make([]byte, postIndex-iNdEx)) + copy(m.List[len(m.List)-1], dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipDogfood(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthDogfood + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipDogfood(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/x/dogfood/types/expected_keepers.go b/x/dogfood/types/expected_keepers.go index b498ad09b..1880294b6 100644 --- a/x/dogfood/types/expected_keepers.go +++ b/x/dogfood/types/expected_keepers.go @@ -1,6 +1,7 @@ package types import ( + tmprotocrypto "github.com/cometbft/cometbft/proto/tendermint/crypto" sdk "github.com/cosmos/cosmos-sdk/types" epochsTypes "github.com/evmos/evmos/v14/x/epochs/types" ) @@ -10,7 +11,7 @@ type EpochsKeeper interface { GetEpochInfo(sdk.Context, string) (epochsTypes.EpochInfo, bool) } -// DogfoodHooks represent the event hooks for dogfood module. Ideally, these should +// DogfoodHooks represents the event hooks for dogfood module. Ideally, these should // match those of the staking module but for now it is only a subset of them. The side effects // of calling the other hooks are not relevant to running the chain, so they can be skipped. type DogfoodHooks interface { @@ -18,3 +19,14 @@ type DogfoodHooks interface { sdk.Context, sdk.ConsAddress, sdk.ValAddress, ) error } + +// OperatorHooks is the interface for the operator module's hooks. The functions are called +// whenever an operator opts in to a Cosmos chain, opts out of a Cosmos chain, or replaces their +// public key with another one. +type OperatorHooks interface { + AfterOperatorOptIn(sdk.Context, sdk.AccAddress, string, tmprotocrypto.PublicKey) + AfterOperatorKeyReplacement( + sdk.Context, sdk.AccAddress, tmprotocrypto.PublicKey, tmprotocrypto.PublicKey, string, + ) + AfterOperatorOptOutInitiated(sdk.Context, sdk.AccAddress, string, tmprotocrypto.PublicKey) +} diff --git a/x/dogfood/types/keys.go b/x/dogfood/types/keys.go index f7efde563..b0b823c05 100644 --- a/x/dogfood/types/keys.go +++ b/x/dogfood/types/keys.go @@ -18,6 +18,21 @@ const ( // HistoricalInfoBytePrefix is the prefix for the historical info store. HistoricalInfoBytePrefix + + // QueuedOperationsByte is the byte used to store the queue of operations. + QueuedOperationsByte + + // OptOutsToFinishBytePrefix is the byte used to store the list of operator addresses whose + // opt outs are maturing at the provided epoch. + OptOutsToFinishBytePrefix + + // OperatorOptOutFinishEpochBytePrefix is the byte prefix to store the epoch at which an + // operator's opt out will mature. + OperatorOptOutFinishEpochBytePrefix + + // ConsensusAddrsToPruneBytePrefix is the byte prefix to store the list of consensus + // addresses that can be pruned from the operator module at the provided epoch. + ConsensusAddrsToPruneBytePrefix ) // ExocoreValidatorKey returns the key for the validator store. @@ -30,3 +45,28 @@ func HistoricalInfoKey(height int64) []byte { bz := sdk.Uint64ToBigEndian(uint64(height)) return append([]byte{HistoricalInfoBytePrefix}, bz...) } + +// QueuedOperationsKey returns the key for the queued operations store. +func QueuedOperationsKey() []byte { + return []byte{QueuedOperationsByte} +} + +// OptOutsToFinishKey returns the key for the operator opt out maturity store (epoch -> list of +// addresses). +func OptOutsToFinishKey(epoch int64) []byte { + return append([]byte{OptOutsToFinishBytePrefix}, sdk.Uint64ToBigEndian(uint64(epoch))...) +} + +// OperatorOptOutFinishEpochKey is the key for the operator opt out maturity store +// (sdk.AccAddress -> epoch) +func OperatorOptOutFinishEpochKey(address sdk.AccAddress) []byte { + return append([]byte{OperatorOptOutFinishEpochBytePrefix}, address.Bytes()...) +} + +// ConsensusAddrsToPruneKey is the key to lookup the list of operator consensus addresses that +// can be pruned from the operator module at the provided epoch. +func ConsensusAddrsToPruneKey(epoch int64) []byte { + return append( + []byte{ConsensusAddrsToPruneBytePrefix}, + sdk.Uint64ToBigEndian(uint64(epoch))...) +} diff --git a/x/dogfood/types/utils.go b/x/dogfood/types/utils.go new file mode 100644 index 000000000..079a21dec --- /dev/null +++ b/x/dogfood/types/utils.go @@ -0,0 +1,31 @@ +package types + +import ( + "bytes" + + sdk "github.com/cosmos/cosmos-sdk/types" + + tmprotocrypto "github.com/cometbft/cometbft/proto/tendermint/crypto" + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" +) + +// TMCryptoPublicKeyToConsAddr converts a TM public key to an SDK public key +// and returns the associated consensus address. +func TMCryptoPublicKeyToConsAddr(k tmprotocrypto.PublicKey) (sdk.ConsAddress, error) { + sdkK, err := cryptocodec.FromTmProtoPublicKey(k) + if err != nil { + return nil, err + } + return sdk.GetConsAddress(sdkK), nil +} + +// RemoveFromBytesList removes an address from a list of addresses +// or a byte slice from a list of byte slices. +func RemoveFromBytesList(list [][]byte, addr []byte) [][]byte { + for i, a := range list { + if bytes.Equal(a, addr) { + return append(list[:i], list[i+1:]...) + } + } + panic("address not found in list") +} From fdf9b9a367acd3388478faaf244bebe469af0095 Mon Sep 17 00:00:00 2001 From: MaxMustermann2 <82761650+MaxMustermann2@users.noreply.github.com> Date: Thu, 8 Feb 2024 15:33:33 +0000 Subject: [PATCH 04/14] docs(dogfood): explain the operator queue logic The operator module should restrict an operator from doing things that are logically not possible. For example, an operator that is already opted in should not be able to opt in again. Similarly, an operator who has opted out of a chain should not be able to replace the key for that chain without opting back in. Based on these assumptions, the comment maps out the list of possible operations that can happen within an epoch and is used to ensure that all of the cases result in the unbonding period being enforced accurately. --- x/dogfood/keeper/impl_operator_hooks.go | 42 +++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/x/dogfood/keeper/impl_operator_hooks.go b/x/dogfood/keeper/impl_operator_hooks.go index e5a42d615..9e0c22af2 100644 --- a/x/dogfood/keeper/impl_operator_hooks.go +++ b/x/dogfood/keeper/impl_operator_hooks.go @@ -32,13 +32,51 @@ func (k *Keeper) OperatorHooks() OperatorHooksWrapper { // R I // For an operator that is already opted in, the list looks like follows: // R O -// O I +// O I (reversing the decision to opt out) // O I R // R O I // The impossible list looks like: // O R // O R I -// TODO: list out operation results for each of these, and make sure everything is covered below +// Replacing the key with the same key is not possible, so it is not covered. +// The assumption is that these are enforced (allowed/denied) by the operator module. + +// Cases for a fresh operator: +// I + O => KeyAdditionOrUpdate + KeyRemoval => Success + Removed => covered. +// I + R => KeyAdditionOrUpdate (old) + KeyAdditionOrUpdate (new) + KeyRemoval (old) => +// Success + Success + Removed + => not considered unbonding of the old key since it +// was not yet effective => covered. +// I + R + O => KeyAdditionOrUpdate (old) + KeyAdditionOrUpdate (new) + KeyRemoval (old) + +// KeyRemoval (new) => +// Success (old) + Success (new) + Removed (old) + Removed (new) => covered + +// Cases for an operator that has already opted in: +// R + O => KeyAdditionOrUpdate (new) + KeyRemoval (old) + KeyRemoval (new) => +// Success (new) + Success (old) + Removed (new) => +// unbonding data made (old) => covered. +// O + I +// O + I case 1 => KeyRemoval (old) + KeyAdditionOrUpdate (new) => Success (old) + Success (new) +// => unbonding data made (old) => covered. +// O + I case 2 => KeyRemoval (old) + KeyAdditionOrUpdate (old) => Success (old) + Removed (old) +// => unbonding data made (old) and then cleared => covered. +// O + I + R +// O + I + R case 1 => KeyRemoval (old) + KeyAdditionOrUpdate (old) + KeyRemoval (old) + +// KeyAdditionOrUpdate (new) => Success (old) + Removed (old) + Success (old) + +// Success (new) => unbonding data old made + cleared + made => covered. +// O + I + R case 2 => +// AfterOperatorOptOut(old) => Success => unbonding data made for old +// AfterOperatorOptIn(new) => Success => no data changed +// AfterOperatorKeyReplacement(new, new2) => +// new2 operation KeyAdditionOrUpdate => Success => no data changed +// new operation KeyRemoval => Removed => no data changed +// => covered +// R + O + I => KeyAdditionOrUpdate (new) + KeyRemoval (old) + KeyRemoval (new) + +// KeyAdditionOrUpdate (X) +// Success + Success (=> unbonding data for old) + Removed (=> no data) + +// case 1 => X == new => Success => no change => covered +// case 2 => X == old => Removed => unbonding data for old removed => covered. +// case 3 => X == abc => Success => no change and unbonding data for old is not removed => +// covered. // AfterOperatorOptIn is the implementation of the operator hooks. func (h OperatorHooksWrapper) AfterOperatorOptIn( From 50d098daa4d994055905149d63c4d85e7787a5c3 Mon Sep 17 00:00:00 2001 From: MaxMustermann2 <82761650+MaxMustermann2@users.noreply.github.com> Date: Thu, 8 Feb 2024 16:14:29 +0000 Subject: [PATCH 05/14] feat(dogfood): implement delegation hooks Whenever a delegation or undelegation event happens in the delegation module, the hooks are called. For both of these events, as long as the operator is not in the process of opting out from the chain, consensus key operations are queued to be applied at the end of the current epoch. For undelegation, specifically, the `recordKey` identifier of the undelegation is used to mark within the delegation module that the undelegation should be held until released at some point in the future (as opposed to releasing it after a fixed number of blocks, which is the default behaviour). This point in the future is calculated as the earlier of the unbonding period end, or the operator's opt out period end (if the operator is opting out). --- proto/exocore/dogfood/v1/dogfood.proto | 7 + x/dogfood/keeper/impl_delegation_hooks.go | 126 +++++++++++++ x/dogfood/keeper/keeper.go | 18 +- x/dogfood/keeper/unbonding.go | 55 ++++++ x/dogfood/types/dogfood.pb.go | 219 +++++++++++++++++++--- x/dogfood/types/expected_keepers.go | 23 +++ x/dogfood/types/keys.go | 12 ++ 7 files changed, 434 insertions(+), 26 deletions(-) create mode 100644 x/dogfood/keeper/impl_delegation_hooks.go diff --git a/proto/exocore/dogfood/v1/dogfood.proto b/proto/exocore/dogfood/v1/dogfood.proto index 1259ac803..a27fc45e6 100644 --- a/proto/exocore/dogfood/v1/dogfood.proto +++ b/proto/exocore/dogfood/v1/dogfood.proto @@ -79,4 +79,11 @@ message AccountAddresses { // addresses (which correspond to operator public keys) to delete at the end of an epoch. message ConsensusAddresses { repeated bytes list = 1; +} + +// RecordKeys is a collection of record keys. This is used to store a list of +// undelegation records to mature in the delegation module at the end of the +// epoch. +message RecordKeys { + repeated bytes list = 1; } \ No newline at end of file diff --git a/x/dogfood/keeper/impl_delegation_hooks.go b/x/dogfood/keeper/impl_delegation_hooks.go new file mode 100644 index 000000000..21a8da737 --- /dev/null +++ b/x/dogfood/keeper/impl_delegation_hooks.go @@ -0,0 +1,126 @@ +package keeper + +import ( + "github.com/ExocoreNetwork/exocore/x/dogfood/types" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// DelegationHooksWrapper is the wrapper structure that implements the delegation hooks for the +// dogfood keeper. +type DelegationHooksWrapper struct { + keeper *Keeper +} + +// Interface guard +var _ types.DelegationHooks = DelegationHooksWrapper{} + +// DelegationHooks returns the delegation hooks wrapper. It follows the "accept interfaces, +// return concretes" pattern. +func (k *Keeper) DelegationHooks() DelegationHooksWrapper { + return DelegationHooksWrapper{k} +} + +// AfterDelegation is called after a delegation is made. +func (wrapper DelegationHooksWrapper) AfterDelegation( + ctx sdk.Context, operator sdk.AccAddress, +) { + found, pubKey, err := wrapper.keeper.operatorKeeper.GetOperatorConsKeyForChainId( + ctx, operator, ctx.ChainID(), + ) + if err != nil { + // the operator keeper can offer two errors: not an operator and not a chain. + // both of these should not happen here because the dogfooding genesis will + // register the chain, and the operator must be known to the delegation module + // when it calls this hook. + panic(err) + } + if found { + if !wrapper.keeper.operatorKeeper.IsOperatorOptingOutFromChainId( + ctx, operator, ctx.ChainID(), + ) { + // only queue the operation if operator is still opted into the chain. + res := wrapper.keeper.QueueOperation( + ctx, operator, pubKey, types.KeyAdditionOrUpdate, + ) + switch res { + case types.QueueResultExists: + // nothing to do because the operation is in the queue already. + case types.QueueResultRemoved: + // a KeyRemoval was in the queue which has now been cleared from the queue. + // the KeyRemoval can only be in the queue if the operator is opting out from + // the chain, or has replaced their key. if it is the former, it means that + // there is some inconsistency. if it is the latter, it means that the operator + // module just reported the old key in `GetOperatorConsKeyForChainId`, which + // should not happen. + panic("unexpected removal of operation from queue") + case types.QueueResultSuccess: + // best case, nothing to do. + case types.QueueResultUnspecified: + panic("unspecified queue result") + } + } + } +} + +// AfterUndelegationStarted is called after an undelegation is started. +func (wrapper DelegationHooksWrapper) AfterUndelegationStarted( + ctx sdk.Context, operator sdk.AccAddress, recordKey []byte, +) { + found, pubKey, err := wrapper.keeper.operatorKeeper.GetOperatorConsKeyForChainId( + ctx, operator, ctx.ChainID(), + ) + if err != nil { + panic(err) + } + if found { + // note that this is still key addition or update because undelegation does not remove + // the operator from the list. it only decreases their vote power. + if !wrapper.keeper.operatorKeeper.IsOperatorOptingOutFromChainId( + ctx, operator, ctx.ChainID(), + ) { + // only queue the operation if operator is still opted into the chain. + res := wrapper.keeper.QueueOperation( + ctx, operator, pubKey, types.KeyAdditionOrUpdate, + ) + switch res { + case types.QueueResultExists: + // nothing to do + case types.QueueResultRemoved: + // KeyRemoval + KeyAdditionOrUpdate => Removed + // KeyRemoval can happen + // 1. if the operator is opting out from the chain,which is inconsistent. + // 2. if the operator is replacing their old key, which should not be returned + // by `GetOperatorConsKeyForChainId`. + panic("unexpected removal of operation from queue") + case types.QueueResultSuccess: + // best case, nothing to do. + case types.QueueResultUnspecified: + panic("unspecified queue result") + } + } + // now handle the unbonding timeline. + wrapper.keeper.delegationKeeper.IncrementUndelegationHoldCount(ctx, recordKey) + // mark for unbonding release. + // note that we aren't supporting redelegation yet, so this undelegated amount will be + // held until the end of the unbonding period or the operator opt out period, whichever + // is first. + var unbondingCompletionEpoch int64 + if wrapper.keeper.operatorKeeper.IsOperatorOptingOutFromChainId( + ctx, operator, ctx.ChainID(), + ) { + unbondingCompletionEpoch = wrapper.keeper.GetOperatorOptOutFinishEpoch( + ctx, operator, + ) + } else { + unbondingCompletionEpoch = wrapper.keeper.GetUnbondingCompletionEpoch(ctx) + } + wrapper.keeper.AppendUndelegationToMature(ctx, unbondingCompletionEpoch, recordKey) + } +} + +// AfterUndelegationCompleted is called after an undelegation is completed. +func (DelegationHooksWrapper) AfterUndelegationCompleted( + sdk.Context, sdk.AccAddress, +) { + // no-op +} diff --git a/x/dogfood/keeper/keeper.go b/x/dogfood/keeper/keeper.go index 47e766553..8061edd8b 100644 --- a/x/dogfood/keeper/keeper.go +++ b/x/dogfood/keeper/keeper.go @@ -18,9 +18,13 @@ type ( storeKey storetypes.StoreKey paramstore paramtypes.Subspace + // internal hooks to allow other modules to subscriber to our events dogfoodHooks types.DogfoodHooks - epochsKeeper types.EpochsKeeper + // external keepers as interfaces + epochsKeeper types.EpochsKeeper + operatorKeeper types.OperatorKeeper + delegationKeeper types.DelegationKeeper } ) @@ -30,6 +34,8 @@ func NewKeeper( storeKey storetypes.StoreKey, ps paramtypes.Subspace, epochsKeeper types.EpochsKeeper, + operatorKeeper types.OperatorKeeper, + delegationKeeper types.DelegationKeeper, ) *Keeper { // set KeyTable if it has not already been set if !ps.HasKeyTable() { @@ -37,10 +43,12 @@ func NewKeeper( } return &Keeper{ - cdc: cdc, - storeKey: storeKey, - paramstore: ps, - epochsKeeper: epochsKeeper, + cdc: cdc, + storeKey: storeKey, + paramstore: ps, + epochsKeeper: epochsKeeper, + operatorKeeper: operatorKeeper, + delegationKeeper: delegationKeeper, } } diff --git a/x/dogfood/keeper/unbonding.go b/x/dogfood/keeper/unbonding.go index 4987e523a..a973754c5 100644 --- a/x/dogfood/keeper/unbonding.go +++ b/x/dogfood/keeper/unbonding.go @@ -56,3 +56,58 @@ func (k Keeper) GetUnbondingCompletionEpoch( // beginning of epoch 13 or the end of epoch 12. return epochInfo.CurrentEpoch + int64(unbondingEpochs) } + +// AppendUndelegationsToMature stores that the undelegation with recordKey should be +// released at the end of the provided epoch. +func (k Keeper) AppendUndelegationToMature( + ctx sdk.Context, epoch int64, recordKey []byte, +) { + prev := k.GetUndelegationsToMature(ctx, epoch) + next := types.RecordKeys{ + List: append(prev, recordKey), + } + k.setUndelegationsToMature(ctx, epoch, next) +} + +// GetUndelegationsToMature returns all undelegation entries that should be released +// at the end of the provided epoch. +func (k Keeper) GetUndelegationsToMature( + ctx sdk.Context, epoch int64, +) [][]byte { + store := ctx.KVStore(k.storeKey) + key := types.UnbondingReleaseMaturityKey(epoch) + bz := store.Get(key) + if bz == nil { + return [][]byte{} + } + var res types.RecordKeys + if err := res.Unmarshal(bz); err != nil { + // should never happen + panic(err) + } + return res.GetList() +} + +// ClearUndelegationsToMature is a pruning method which is called after we mature +// the undelegation entries. +func (k Keeper) ClearUndelegationsToMature( + ctx sdk.Context, epoch int64, +) { + store := ctx.KVStore(k.storeKey) + key := types.UnbondingReleaseMaturityKey(epoch) + store.Delete(key) +} + +// setUndelegationsToMature sets all undelegation entries that should be released +// at the end of the provided epoch. +func (k Keeper) setUndelegationsToMature( + ctx sdk.Context, epoch int64, recordKeys types.RecordKeys, +) { + store := ctx.KVStore(k.storeKey) + key := types.UnbondingReleaseMaturityKey(epoch) + val, err := recordKeys.Marshal() + if err != nil { + panic(err) + } + store.Set(key, val) +} diff --git a/x/dogfood/types/dogfood.pb.go b/x/dogfood/types/dogfood.pb.go index 3a64b1dea..fa4d626f1 100644 --- a/x/dogfood/types/dogfood.pb.go +++ b/x/dogfood/types/dogfood.pb.go @@ -367,6 +367,53 @@ func (m *ConsensusAddresses) GetList() [][]byte { return nil } +// RecordKeys is a collection of record keys. This is used to store a list of +// undelegation records to mature in the delegation module at the end of the +// epoch. +type RecordKeys struct { + List [][]byte `protobuf:"bytes,1,rep,name=list,proto3" json:"list,omitempty"` +} + +func (m *RecordKeys) Reset() { *m = RecordKeys{} } +func (m *RecordKeys) String() string { return proto.CompactTextString(m) } +func (*RecordKeys) ProtoMessage() {} +func (*RecordKeys) Descriptor() ([]byte, []int) { + return fileDescriptor_071b9989c501c3f2, []int{5} +} +func (m *RecordKeys) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *RecordKeys) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_RecordKeys.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 *RecordKeys) XXX_Merge(src proto.Message) { + xxx_messageInfo_RecordKeys.Merge(m, src) +} +func (m *RecordKeys) XXX_Size() int { + return m.Size() +} +func (m *RecordKeys) XXX_DiscardUnknown() { + xxx_messageInfo_RecordKeys.DiscardUnknown(m) +} + +var xxx_messageInfo_RecordKeys proto.InternalMessageInfo + +func (m *RecordKeys) GetList() [][]byte { + if m != nil { + return m.List + } + return nil +} + func init() { proto.RegisterEnum("exocore.dogfood.v1.OperationType", OperationType_name, OperationType_value) proto.RegisterEnum("exocore.dogfood.v1.QueueResultType", QueueResultType_name, QueueResultType_value) @@ -375,12 +422,13 @@ func init() { proto.RegisterType((*Operations)(nil), "exocore.dogfood.v1.Operations") proto.RegisterType((*AccountAddresses)(nil), "exocore.dogfood.v1.AccountAddresses") proto.RegisterType((*ConsensusAddresses)(nil), "exocore.dogfood.v1.ConsensusAddresses") + proto.RegisterType((*RecordKeys)(nil), "exocore.dogfood.v1.RecordKeys") } func init() { proto.RegisterFile("exocore/dogfood/v1/dogfood.proto", fileDescriptor_071b9989c501c3f2) } var fileDescriptor_071b9989c501c3f2 = []byte{ - // 705 bytes of a gzipped FileDescriptorProto + // 715 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x94, 0xcf, 0x4e, 0xdb, 0x4a, 0x14, 0xc6, 0xe3, 0x84, 0x0b, 0xba, 0xc3, 0x3f, 0xdf, 0xb9, 0x29, 0x04, 0x0b, 0x82, 0xc9, 0xa2, 0x4a, 0x91, 0x6a, 0x0b, 0x68, 0x55, 0xa9, 0x55, 0x2b, 0x99, 0xc4, 0x55, 0xa3, 0x50, 0x12, 0xec, @@ -406,26 +454,26 @@ var fileDescriptor_071b9989c501c3f2 = []byte{ 0xda, 0x66, 0x1d, 0xc2, 0xed, 0x89, 0xd3, 0xf3, 0xd5, 0x4c, 0xc4, 0x5f, 0x87, 0xb0, 0x24, 0x23, 0x74, 0xc5, 0x41, 0xf1, 0x13, 0x34, 0xd1, 0xb5, 0xa9, 0x5f, 0x60, 0xf8, 0x5c, 0x79, 0x7a, 0x73, 0xe5, 0x4e, 0xea, 0x64, 0xa3, 0xc8, 0x50, 0xba, 0x8f, 0x58, 0xc9, 0x34, 0x49, 0xe0, 0xf8, 0x09, - 0x15, 0x50, 0x8c, 0x47, 0x36, 0x9b, 0x49, 0xfa, 0xca, 0x08, 0x57, 0xd2, 0x19, 0xdf, 0xd9, 0xb9, - 0xfe, 0x95, 0x41, 0xb3, 0x7f, 0x4d, 0x08, 0x3f, 0x42, 0x5c, 0xa3, 0x29, 0x2b, 0x52, 0xab, 0xd6, - 0xd8, 0xd5, 0x5b, 0x6f, 0x9b, 0xb2, 0xae, 0xed, 0xaa, 0x4d, 0xb9, 0x52, 0x7b, 0x59, 0x93, 0xab, - 0x6c, 0x86, 0xcb, 0xf7, 0x07, 0x3c, 0x5b, 0x87, 0xb0, 0xe1, 0x6a, 0x0e, 0x75, 0xc1, 0xb4, 0x0f, - 0x6c, 0xb0, 0xf0, 0x0b, 0xb4, 0x76, 0xc3, 0x25, 0x55, 0xab, 0xb5, 0x48, 0x35, 0x14, 0x5d, 0x6b, - 0x56, 0xa5, 0x96, 0xcc, 0x32, 0xdc, 0x62, 0x7f, 0xc0, 0xff, 0x5f, 0x87, 0x50, 0xb2, 0x2c, 0x7b, - 0x78, 0x62, 0xc3, 0xd3, 0x5c, 0xcb, 0xf0, 0x01, 0xaf, 0xa3, 0x85, 0x1b, 0x7e, 0x45, 0x7e, 0xdd, - 0xd8, 0x97, 0x76, 0xd8, 0x2c, 0x37, 0xd7, 0x1f, 0xf0, 0x68, 0xf8, 0x37, 0x40, 0x8f, 0x1c, 0x19, - 0x5d, 0x6e, 0xe2, 0xc3, 0xa7, 0x62, 0x66, 0xfd, 0x37, 0x83, 0xe6, 0xf7, 0x02, 0x08, 0x40, 0x01, - 0x1a, 0x74, 0xfd, 0x88, 0xfd, 0x39, 0x5a, 0xd9, 0xd3, 0x64, 0x6d, 0x68, 0x56, 0xb5, 0x9d, 0xd6, - 0x6d, 0xf8, 0x5c, 0x7f, 0xc0, 0x2f, 0x8c, 0xf8, 0x46, 0x43, 0x3c, 0x46, 0x4b, 0xe3, 0x76, 0x55, - 0xab, 0x54, 0x64, 0x55, 0x65, 0x19, 0x6e, 0xa1, 0x3f, 0xe0, 0xf1, 0x88, 0x55, 0x0d, 0x4c, 0x73, - 0xf8, 0x67, 0x6c, 0xa1, 0xc2, 0xb8, 0x4d, 0x7e, 0x53, 0x53, 0x5b, 0x2a, 0x9b, 0xe5, 0xee, 0xf5, - 0x07, 0xfc, 0x7f, 0x23, 0x2e, 0xf9, 0xc4, 0xa6, 0x3e, 0xbd, 0xfd, 0xac, 0x28, 0xb3, 0x5c, 0x65, - 0x73, 0x63, 0x67, 0x45, 0xd9, 0xc1, 0x8a, 0xb3, 0x6f, 0xd7, 0x4f, 0x2f, 0x8a, 0xcc, 0xd9, 0x45, - 0x91, 0xf9, 0x79, 0x51, 0x64, 0x3e, 0x5e, 0x16, 0x33, 0x67, 0x97, 0xc5, 0xcc, 0x8f, 0xcb, 0x62, - 0xe6, 0xdd, 0x46, 0xc7, 0xf6, 0xdf, 0x07, 0x6d, 0xc1, 0x24, 0x3d, 0x31, 0xb9, 0xd8, 0xbb, 0xe0, - 0x1f, 0x13, 0xef, 0x50, 0x4c, 0x9f, 0xa2, 0x93, 0xab, 0xc7, 0x68, 0x78, 0x75, 0x68, 0x7b, 0x32, - 0xba, 0xa6, 0x5b, 0x7f, 0x02, 0x00, 0x00, 0xff, 0xff, 0xa3, 0xf3, 0x23, 0x11, 0xac, 0x04, 0x00, - 0x00, + 0x15, 0x50, 0x8c, 0x47, 0x36, 0x9b, 0x49, 0xfa, 0xca, 0x08, 0x57, 0xd2, 0x19, 0xdf, 0xdd, 0xc9, + 0x23, 0xa4, 0x80, 0x49, 0x3c, 0xab, 0x0e, 0xe1, 0xad, 0x1d, 0xeb, 0x5f, 0x19, 0x34, 0xfb, 0xd7, + 0x0c, 0xf1, 0x23, 0xc4, 0x35, 0x9a, 0xb2, 0x22, 0xb5, 0x6a, 0x8d, 0x5d, 0xbd, 0xf5, 0xb6, 0x29, + 0xeb, 0xda, 0xae, 0xda, 0x94, 0x2b, 0xb5, 0x97, 0x35, 0xb9, 0xca, 0x66, 0xb8, 0x7c, 0x7f, 0xc0, + 0xb3, 0x75, 0x08, 0x1b, 0xae, 0xe6, 0x50, 0x17, 0x4c, 0xfb, 0xc0, 0x06, 0x0b, 0xbf, 0x40, 0x6b, + 0x37, 0x5c, 0x52, 0xb5, 0x5a, 0x8b, 0x54, 0x43, 0xd1, 0xb5, 0x66, 0x55, 0x6a, 0xc9, 0x2c, 0xc3, + 0x2d, 0xf6, 0x07, 0xfc, 0xff, 0x75, 0x08, 0x25, 0xcb, 0xb2, 0x87, 0x27, 0x36, 0x3c, 0xcd, 0xb5, + 0x0c, 0x1f, 0xf0, 0x3a, 0x5a, 0xb8, 0xe1, 0x57, 0xe4, 0xd7, 0x8d, 0x7d, 0x69, 0x87, 0xcd, 0x72, + 0x73, 0xfd, 0x01, 0x8f, 0x86, 0xff, 0x0b, 0xf4, 0xc8, 0x91, 0xd1, 0xe5, 0x26, 0x3e, 0x7c, 0x2a, + 0x66, 0xd6, 0x7f, 0x33, 0x68, 0x7e, 0x2f, 0x80, 0x00, 0x14, 0xa0, 0x41, 0xd7, 0x8f, 0xd8, 0x9f, + 0xa3, 0x95, 0x3d, 0x4d, 0xd6, 0x86, 0x66, 0x55, 0xdb, 0x69, 0xdd, 0x86, 0xcf, 0xf5, 0x07, 0xfc, + 0xc2, 0x88, 0x6f, 0x34, 0xc4, 0x63, 0xb4, 0x34, 0x6e, 0x57, 0xb5, 0x4a, 0x45, 0x56, 0x55, 0x96, + 0xe1, 0x16, 0xfa, 0x03, 0x1e, 0x8f, 0x58, 0xd5, 0xc0, 0x34, 0x87, 0xff, 0xce, 0x16, 0x2a, 0x8c, + 0xdb, 0xe4, 0x37, 0x35, 0xb5, 0xa5, 0xb2, 0x59, 0xee, 0x5e, 0x7f, 0xc0, 0xff, 0x37, 0xe2, 0x92, + 0x4f, 0x6c, 0xea, 0xd3, 0xdb, 0xcf, 0x8a, 0x32, 0xcb, 0x55, 0x36, 0x37, 0x76, 0x56, 0x94, 0x1d, + 0xac, 0x38, 0xfb, 0x76, 0xfd, 0xf4, 0xa2, 0xc8, 0x9c, 0x5d, 0x14, 0x99, 0x9f, 0x17, 0x45, 0xe6, + 0xe3, 0x65, 0x31, 0x73, 0x76, 0x59, 0xcc, 0xfc, 0xb8, 0x2c, 0x66, 0xde, 0x6d, 0x74, 0x6c, 0xff, + 0x7d, 0xd0, 0x16, 0x4c, 0xd2, 0x13, 0x93, 0xab, 0xbf, 0x0b, 0xfe, 0x31, 0xf1, 0x0e, 0xc5, 0xf4, + 0xb1, 0x3a, 0xb9, 0x7a, 0xae, 0x86, 0x97, 0x8b, 0xb6, 0x27, 0xa3, 0x8b, 0xbc, 0xf5, 0x27, 0x00, + 0x00, 0xff, 0xff, 0xbd, 0x64, 0x30, 0xab, 0xce, 0x04, 0x00, 0x00, } func (m *ExocoreValidator) Marshal() (dAtA []byte, err error) { @@ -621,6 +669,38 @@ func (m *ConsensusAddresses) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *RecordKeys) 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 *RecordKeys) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *RecordKeys) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.List) > 0 { + for iNdEx := len(m.List) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.List[iNdEx]) + copy(dAtA[i:], m.List[iNdEx]) + i = encodeVarintDogfood(dAtA, i, uint64(len(m.List[iNdEx]))) + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + func encodeVarintDogfood(dAtA []byte, offset int, v uint64) int { offset -= sovDogfood(v) base := offset @@ -715,6 +795,21 @@ func (m *ConsensusAddresses) Size() (n int) { return n } +func (m *RecordKeys) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.List) > 0 { + for _, b := range m.List { + l = len(b) + n += 1 + l + sovDogfood(uint64(l)) + } + } + return n +} + func sovDogfood(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -1244,6 +1339,88 @@ func (m *ConsensusAddresses) Unmarshal(dAtA []byte) error { } return nil } +func (m *RecordKeys) 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 ErrIntOverflowDogfood + } + 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: RecordKeys: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: RecordKeys: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field List", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDogfood + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthDogfood + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthDogfood + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.List = append(m.List, make([]byte, postIndex-iNdEx)) + copy(m.List[len(m.List)-1], dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipDogfood(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthDogfood + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipDogfood(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/x/dogfood/types/expected_keepers.go b/x/dogfood/types/expected_keepers.go index 1880294b6..585c28b38 100644 --- a/x/dogfood/types/expected_keepers.go +++ b/x/dogfood/types/expected_keepers.go @@ -30,3 +30,26 @@ type OperatorHooks interface { ) AfterOperatorOptOutInitiated(sdk.Context, sdk.AccAddress, string, tmprotocrypto.PublicKey) } + +// DelegationHooks represent the event hooks for delegation module. +type DelegationHooks interface { + AfterDelegation(sdk.Context, sdk.AccAddress) + AfterUndelegationStarted(sdk.Context, sdk.AccAddress, []byte) + AfterUndelegationCompleted(sdk.Context, sdk.AccAddress) +} + +// OperatorKeeper represents the expected keeper interface for the operator module. +type OperatorKeeper interface { + GetOperatorConsKeyForChainId( + sdk.Context, sdk.AccAddress, string, + ) (bool, tmprotocrypto.PublicKey, error) + IsOperatorOptingOutFromChainId( + sdk.Context, sdk.AccAddress, string, + ) bool +} + +// DelegationKeeper represents the expected keeper interface for the delegation module. +type DelegationKeeper interface { + IncrementUndelegationHoldCount(sdk.Context, []byte) + DecrementUndelegationHoldCount(sdk.Context, []byte) +} diff --git a/x/dogfood/types/keys.go b/x/dogfood/types/keys.go index b0b823c05..e7e0e4aab 100644 --- a/x/dogfood/types/keys.go +++ b/x/dogfood/types/keys.go @@ -33,6 +33,10 @@ const ( // ConsensusAddrsToPruneBytePrefix is the byte prefix to store the list of consensus // addresses that can be pruned from the operator module at the provided epoch. ConsensusAddrsToPruneBytePrefix + + // UnbondingReleaseMaturityBytePrefix is the byte prefix to store the list of undelegations + // that will mature at the provided epoch. + UnbondingReleaseMaturityBytePrefix ) // ExocoreValidatorKey returns the key for the validator store. @@ -70,3 +74,11 @@ func ConsensusAddrsToPruneKey(epoch int64) []byte { []byte{ConsensusAddrsToPruneBytePrefix}, sdk.Uint64ToBigEndian(uint64(epoch))...) } + +// UnbondingReleaseMaturityKey is the key to lookup the list of undelegations that will mature +// at the provided epoch. +func UnbondingReleaseMaturityKey(epoch int64) []byte { + return append( + []byte{UnbondingReleaseMaturityBytePrefix}, + sdk.Uint64ToBigEndian(uint64(epoch))...) +} From ebc853022aec2b2c83ce09b94f89a9a50d78784c Mon Sep 17 00:00:00 2001 From: MaxMustermann2 <82761650+MaxMustermann2@users.noreply.github.com> Date: Thu, 8 Feb 2024 16:18:50 +0000 Subject: [PATCH 06/14] fix(dogfood): upd delegation hook interface --- x/dogfood/keeper/impl_delegation_hooks.go | 2 +- x/dogfood/types/expected_keepers.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/x/dogfood/keeper/impl_delegation_hooks.go b/x/dogfood/keeper/impl_delegation_hooks.go index 21a8da737..e32442853 100644 --- a/x/dogfood/keeper/impl_delegation_hooks.go +++ b/x/dogfood/keeper/impl_delegation_hooks.go @@ -120,7 +120,7 @@ func (wrapper DelegationHooksWrapper) AfterUndelegationStarted( // AfterUndelegationCompleted is called after an undelegation is completed. func (DelegationHooksWrapper) AfterUndelegationCompleted( - sdk.Context, sdk.AccAddress, + sdk.Context, sdk.AccAddress, []byte, ) { // no-op } diff --git a/x/dogfood/types/expected_keepers.go b/x/dogfood/types/expected_keepers.go index 585c28b38..f6d85f8c6 100644 --- a/x/dogfood/types/expected_keepers.go +++ b/x/dogfood/types/expected_keepers.go @@ -35,7 +35,7 @@ type OperatorHooks interface { type DelegationHooks interface { AfterDelegation(sdk.Context, sdk.AccAddress) AfterUndelegationStarted(sdk.Context, sdk.AccAddress, []byte) - AfterUndelegationCompleted(sdk.Context, sdk.AccAddress) + AfterUndelegationCompleted(sdk.Context, sdk.AccAddress, []byte) } // OperatorKeeper represents the expected keeper interface for the operator module. From 5d0a0cd4ebd94b92e3b9bb1f5f65ac8852116aac Mon Sep 17 00:00:00 2001 From: MaxMustermann2 <82761650+MaxMustermann2@users.noreply.github.com> Date: Thu, 8 Feb 2024 16:50:24 +0000 Subject: [PATCH 07/14] feat(dogfood): implement epochs hooks + end block The epochs hooks are used by the dogfood module to subscribe to the start and end times of epochs. After an epoch ends, the operations that are currently in the queue are upgraded to "pending", which are applied at the end of the block. The upgrades are also made to undelegation maturity, operator opt out, and operator key replacement data. Once the pending actions are applied, they are cleared from the queue. This branch is merged into dogfood-part5, which forms the basis of #121. --- x/dogfood/keeper/abci.go | 182 ++++++++++++++++++++++++++ x/dogfood/keeper/impl_epochs_hooks.go | 61 +++++++++ x/dogfood/keeper/keeper.go | 3 + x/dogfood/types/expected_keepers.go | 13 ++ x/dogfood/types/keys.go | 37 ++++++ 5 files changed, 296 insertions(+) create mode 100644 x/dogfood/keeper/abci.go create mode 100644 x/dogfood/keeper/impl_epochs_hooks.go diff --git a/x/dogfood/keeper/abci.go b/x/dogfood/keeper/abci.go new file mode 100644 index 000000000..043045680 --- /dev/null +++ b/x/dogfood/keeper/abci.go @@ -0,0 +1,182 @@ +package keeper + +import ( + "github.com/ExocoreNetwork/exocore/x/dogfood/types" + abci "github.com/cometbft/cometbft/abci/types" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func (k Keeper) EndBlock(ctx sdk.Context) []abci.ValidatorUpdate { + // start with undelegations + undelegations := k.GetPendingUndelegations(ctx) + for _, undelegation := range undelegations.GetList() { + k.delegationKeeper.DecrementUndelegationHoldCount(ctx, undelegation) + } + k.ClearPendingUndelegations(ctx) + // then opt outs (consensus addresses should be done after opt out) + optOuts := k.GetPendingOptOuts(ctx) + for _, addr := range optOuts.GetList() { + k.operatorKeeper.CompleteOperatorOptOutFromChainId(ctx, addr, ctx.ChainID()) + } + k.ClearPendingOptOuts(ctx) + // then consensus addresses + consensusAddrs := k.GetPendingConsensusAddrs(ctx) + for _, consensusAddr := range consensusAddrs.GetList() { + k.operatorKeeper.DeleteOperatorAddressForChainIdAndConsAddr( + ctx, ctx.ChainID(), consensusAddr, + ) + } + k.ClearPendingConsensusAddrs(ctx) + // finally, operations + operations := k.GetPendingOperations(ctx) + res := make([]abci.ValidatorUpdate, 0, len(operations.GetList())) + for _, operation := range operations.GetList() { + switch operation.OperationType { + case types.KeyAdditionOrUpdate: + power, err := k.restakingKeeper.GetOperatorAssetValue( + ctx, operation.OperatorAddress, + ) + if err != nil { + panic(err) + } + res = append(res, abci.ValidatorUpdate{ + PubKey: operation.PubKey, + Power: power, + }) + case types.KeyRemoval: + res = append(res, abci.ValidatorUpdate{ + PubKey: operation.PubKey, + Power: 0, + }) + case types.KeyOpUnspecified: + panic("unspecified operation type") + } + } + return res +} + +// SetPendingOperations sets the pending operations to be applied at the end of the block. +func (k Keeper) SetPendingOperations(ctx sdk.Context, operations types.Operations) { + store := ctx.KVStore(k.storeKey) + bz, err := operations.Marshal() + if err != nil { + panic(err) + } + store.Set(types.PendingOperationsKey(), bz) +} + +// GetPendingOperations returns the pending operations to be applied at the end of the block. +func (k Keeper) GetPendingOperations(ctx sdk.Context) types.Operations { + store := ctx.KVStore(k.storeKey) + bz := store.Get(types.PendingOperationsKey()) + if bz == nil { + return types.Operations{} + } + var operations types.Operations + if err := operations.Unmarshal(bz); err != nil { + panic(err) + } + return operations +} + +// ClearPendingOperations clears the pending operations to be applied at the end of the block. +func (k Keeper) ClearPendingOperations(ctx sdk.Context) { + store := ctx.KVStore(k.storeKey) + store.Delete(types.PendingOperationsKey()) +} + +// SetPendingOptOuts sets the pending opt-outs to be applied at the end of the block. +func (k Keeper) SetPendingOptOuts(ctx sdk.Context, addrs types.AccountAddresses) { + store := ctx.KVStore(k.storeKey) + bz, err := addrs.Marshal() + if err != nil { + panic(err) + } + store.Set(types.PendingOptOutsKey(), bz) +} + +// GetPendingOptOuts returns the pending opt-outs to be applied at the end of the block. +func (k Keeper) GetPendingOptOuts(ctx sdk.Context) types.AccountAddresses { + store := ctx.KVStore(k.storeKey) + bz := store.Get(types.PendingOptOutsKey()) + if bz == nil { + return types.AccountAddresses{} + } + var addrs types.AccountAddresses + if err := addrs.Unmarshal(bz); err != nil { + panic(err) + } + return addrs +} + +// ClearPendingOptOuts clears the pending opt-outs to be applied at the end of the block. +func (k Keeper) ClearPendingOptOuts(ctx sdk.Context) { + store := ctx.KVStore(k.storeKey) + store.Delete(types.PendingOptOutsKey()) +} + +// SetPendingConsensusAddrs sets the pending consensus addresses to be pruned at the end of the +// block. +func (k Keeper) SetPendingConsensusAddrs(ctx sdk.Context, addrs types.ConsensusAddresses) { + store := ctx.KVStore(k.storeKey) + bz, err := addrs.Marshal() + if err != nil { + panic(err) + } + store.Set(types.PendingConsensusAddrsKey(), bz) +} + +// GetPendingConsensusAddrs returns the pending consensus addresses to be pruned at the end of +// the block. +func (k Keeper) GetPendingConsensusAddrs(ctx sdk.Context) types.ConsensusAddresses { + store := ctx.KVStore(k.storeKey) + bz := store.Get(types.PendingConsensusAddrsKey()) + if bz == nil { + return types.ConsensusAddresses{} + } + var addrs types.ConsensusAddresses + if err := addrs.Unmarshal(bz); err != nil { + panic(err) + } + return addrs +} + +// ClearPendingConsensusAddrs clears the pending consensus addresses to be pruned at the end of +// the block. +func (k Keeper) ClearPendingConsensusAddrs(ctx sdk.Context) { + store := ctx.KVStore(k.storeKey) + store.Delete(types.PendingConsensusAddrsKey()) +} + +// SetPendingUndelegations sets the pending undelegations to be released at the end of the +// block. +func (k Keeper) SetPendingUndelegations(ctx sdk.Context, undelegations types.RecordKeys) { + store := ctx.KVStore(k.storeKey) + bz, err := undelegations.Marshal() + if err != nil { + panic(err) + } + store.Set(types.PendingUndelegationsKey(), bz) +} + +// GetPendingUndelegations returns the pending undelegations to be released at the end of the +// block. +func (k Keeper) GetPendingUndelegations(ctx sdk.Context) types.RecordKeys { + store := ctx.KVStore(k.storeKey) + bz := store.Get(types.PendingUndelegationsKey()) + if bz == nil { + return types.RecordKeys{} + } + var undelegations types.RecordKeys + if err := undelegations.Unmarshal(bz); err != nil { + panic(err) + } + return undelegations +} + +// ClearPendingUndelegations clears the pending undelegations to be released at the end of the +// block. +func (k Keeper) ClearPendingUndelegations(ctx sdk.Context) { + store := ctx.KVStore(k.storeKey) + store.Delete(types.PendingUndelegationsKey()) +} diff --git a/x/dogfood/keeper/impl_epochs_hooks.go b/x/dogfood/keeper/impl_epochs_hooks.go new file mode 100644 index 000000000..c68604316 --- /dev/null +++ b/x/dogfood/keeper/impl_epochs_hooks.go @@ -0,0 +1,61 @@ +package keeper + +import ( + "strings" + + "github.com/ExocoreNetwork/exocore/x/dogfood/types" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +var ( + _ = sdk.NewCoin("stake", sdk.NewInt(1)) +) + +// EpochsHooksWrapper is the wrapper structure that implements the epochs hooks for the dogfood +// keeper. +type EpochsHooksWrapper struct { + keeper *Keeper +} + +// Interface guard +var _ types.EpochsHooks = EpochsHooksWrapper{} + +// EpochsHooks returns the epochs hooks wrapper. It follows the "accept interfaces, return +// concretes" pattern. +func (k *Keeper) EpochsHooks() EpochsHooksWrapper { + return EpochsHooksWrapper{k} +} + +// AfterEpochEnd is called after an epoch ends. +func (wrapper EpochsHooksWrapper) AfterEpochEnd( + ctx sdk.Context, identifier string, epoch int64, +) { + if strings.Compare(identifier, wrapper.keeper.GetEpochIdentifier(ctx)) == 0 { + // we will upgrade all of the queued information to "pending", which will be applied at + // the end of the block. + // note that this hook is called during BeginBlock, and the "pending" operations will be + // applied within this block. however, for clarity, it is highlighted that unbonding + // takes N epochs + 1 block to complete. + operations := wrapper.keeper.GetQueuedOperations(ctx) + wrapper.keeper.SetPendingOperations(ctx, types.Operations{List: operations}) + wrapper.keeper.ClearQueuedOperations(ctx) + optOuts := wrapper.keeper.GetOptOutsToFinish(ctx, epoch) + wrapper.keeper.SetPendingOptOuts(ctx, types.AccountAddresses{List: optOuts}) + wrapper.keeper.ClearOptOutsToFinish(ctx, epoch) + consAddresses := wrapper.keeper.GetConsensusAddrsToPrune(ctx, epoch) + wrapper.keeper.SetPendingConsensusAddrs( + ctx, types.ConsensusAddresses{List: consAddresses}, + ) + wrapper.keeper.ClearConsensusAddrsToPrune(ctx, epoch) + undelegations := wrapper.keeper.GetUndelegationsToMature(ctx, epoch) + wrapper.keeper.SetPendingUndelegations(ctx, types.RecordKeys{List: undelegations}) + wrapper.keeper.ClearUndelegationsToMature(ctx, epoch) + } +} + +// BeforeEpochStart is called before an epoch starts. +func (wrapper EpochsHooksWrapper) BeforeEpochStart( + ctx sdk.Context, identifier string, epoch int64, +) { + // nothing to do +} diff --git a/x/dogfood/keeper/keeper.go b/x/dogfood/keeper/keeper.go index 8061edd8b..103d60c97 100644 --- a/x/dogfood/keeper/keeper.go +++ b/x/dogfood/keeper/keeper.go @@ -25,6 +25,7 @@ type ( epochsKeeper types.EpochsKeeper operatorKeeper types.OperatorKeeper delegationKeeper types.DelegationKeeper + restakingKeeper types.RestakingKeeper } ) @@ -36,6 +37,7 @@ func NewKeeper( epochsKeeper types.EpochsKeeper, operatorKeeper types.OperatorKeeper, delegationKeeper types.DelegationKeeper, + restakingKeeper types.RestakingKeeper, ) *Keeper { // set KeyTable if it has not already been set if !ps.HasKeyTable() { @@ -49,6 +51,7 @@ func NewKeeper( epochsKeeper: epochsKeeper, operatorKeeper: operatorKeeper, delegationKeeper: delegationKeeper, + restakingKeeper: restakingKeeper, } } diff --git a/x/dogfood/types/expected_keepers.go b/x/dogfood/types/expected_keepers.go index f6d85f8c6..9dfd255ea 100644 --- a/x/dogfood/types/expected_keepers.go +++ b/x/dogfood/types/expected_keepers.go @@ -46,6 +46,8 @@ type OperatorKeeper interface { IsOperatorOptingOutFromChainId( sdk.Context, sdk.AccAddress, string, ) bool + CompleteOperatorOptOutFromChainId(sdk.Context, sdk.AccAddress, string) + DeleteOperatorAddressForChainIdAndConsAddr(sdk.Context, string, sdk.ConsAddress) } // DelegationKeeper represents the expected keeper interface for the delegation module. @@ -53,3 +55,14 @@ type DelegationKeeper interface { IncrementUndelegationHoldCount(sdk.Context, []byte) DecrementUndelegationHoldCount(sdk.Context, []byte) } + +// EpochsHooks represents the event hooks for the epochs module. +type EpochsHooks interface { + AfterEpochEnd(sdk.Context, string, int64) + BeforeEpochStart(sdk.Context, string, int64) +} + +// RestakingKeeper represents the expected keeper interface for the restaking module. +type RestakingKeeper interface { + GetOperatorAssetValue(sdk.Context, sdk.AccAddress) (int64, error) +} diff --git a/x/dogfood/types/keys.go b/x/dogfood/types/keys.go index e7e0e4aab..40aa58676 100644 --- a/x/dogfood/types/keys.go +++ b/x/dogfood/types/keys.go @@ -37,6 +37,22 @@ const ( // UnbondingReleaseMaturityBytePrefix is the byte prefix to store the list of undelegations // that will mature at the provided epoch. UnbondingReleaseMaturityBytePrefix + + // PendingOperationsByte is the byte used to store the list of operations to be applied at + // the end of the current block. + PendingOperationsByte + + // PendingOptOutsByte is the byte used to store the list of operator addresses whose opt + // outs will be made effective at the end of the current block. + PendingOptOutsByte + + // PendingConsensusAddrsByte is the byte used to store the list of consensus addresses to be + // pruned at the end of the block. + PendingConsensusAddrsByte + + // PendingUndelegationsByte is the byte used to store the list of undelegations that will + // mature at the end of the current block. + PendingUndelegationsByte ) // ExocoreValidatorKey returns the key for the validator store. @@ -82,3 +98,24 @@ func UnbondingReleaseMaturityKey(epoch int64) []byte { []byte{UnbondingReleaseMaturityBytePrefix}, sdk.Uint64ToBigEndian(uint64(epoch))...) } + +// PendingOperationsKey returns the key for the pending operations store. +func PendingOperationsKey() []byte { + return []byte{PendingOperationsByte} +} + +// PendingOptOutsKey returns the key for the pending opt-outs store. +func PendingOptOutsKey() []byte { + return []byte{PendingOptOutsByte} +} + +// PendingConsensusAddrsByte is the byte used to store the list of consensus addresses to be +// pruned at the end of the block. +func PendingConsensusAddrsKey() []byte { + return []byte{PendingConsensusAddrsByte} +} + +// PendingUndelegationsKey returns the key for the pending undelegations store. +func PendingUndelegationsKey() []byte { + return []byte{PendingUndelegationsByte} +} From a2aa2e106c43e60d908526a928b8fa035ba7391f Mon Sep 17 00:00:00 2001 From: MaxMustermann2 <82761650+MaxMustermann2@users.noreply.github.com> Date: Thu, 8 Feb 2024 16:54:31 +0000 Subject: [PATCH 08/14] fix(dogfood): call `EndBlock` from module --- x/dogfood/module.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x/dogfood/module.go b/x/dogfood/module.go index 2ac7087e3..bfd476b9a 100644 --- a/x/dogfood/module.go +++ b/x/dogfood/module.go @@ -159,6 +159,6 @@ func (AppModule) ConsensusVersion() uint64 { return 1 } func (am AppModule) BeginBlock(_ sdk.Context, _ abci.RequestBeginBlock) {} // EndBlock contains the logic that is automatically triggered at the end of each block -func (am AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { - return []abci.ValidatorUpdate{} +func (am AppModule) EndBlock(ctx sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { + return am.keeper.EndBlock(ctx) } From cc56595c94ff9b3043b84d9ee5732d48d52529d7 Mon Sep 17 00:00:00 2001 From: MaxMustermann2 <82761650+MaxMustermann2@users.noreply.github.com> Date: Mon, 26 Feb 2024 19:24:17 +0000 Subject: [PATCH 09/14] feat(dogfood): retrieve val set from id The validator set is not bound to change at each block. It can only change at the end of an epoch, or following a slashing event. Therefore, storing the historical information for each block is redundant. Instead, we create a validator set id corresponding to each validator set, and map each height to a validator set id. The mapping and the validator set id are pruned once the historical info for a height is not required. TODO: actually store the validator set against each id, within the `EndBlock` function at the end of the epoch. --- proto/exocore/dogfood/v1/dogfood.proto | 20 + x/dogfood/genesis.go | 26 -- x/dogfood/keeper/genesis.go | 34 ++ x/dogfood/keeper/validators.go | 210 ++++++---- x/dogfood/module.go | 6 +- x/dogfood/types/dogfood.pb.go | 519 ++++++++++++++++++++++++- x/dogfood/types/keys.go | 28 +- 7 files changed, 719 insertions(+), 124 deletions(-) delete mode 100644 x/dogfood/genesis.go create mode 100644 x/dogfood/keeper/genesis.go diff --git a/proto/exocore/dogfood/v1/dogfood.proto b/proto/exocore/dogfood/v1/dogfood.proto index e51f4a408..429f893b2 100644 --- a/proto/exocore/dogfood/v1/dogfood.proto +++ b/proto/exocore/dogfood/v1/dogfood.proto @@ -4,7 +4,10 @@ package exocore.dogfood.v1; import "gogoproto/gogo.proto"; import "google/protobuf/any.proto"; +import "google/protobuf/timestamp.proto"; + import "cosmos_proto/cosmos.proto"; +import "cosmos/staking/v1beta1/staking.proto"; option go_package = "github.com/ExocoreNetwork/exocore/x/dogfood/types"; @@ -21,4 +24,21 @@ message ExocoreValidator { (cosmos_proto.accepts_interface) = "cosmos.crypto.PubKey", (gogoproto.moretags) = "yaml:\"consensus_pubkey\"" ]; +} + +// Validators is a list of validators stored according to the staking module. +message Validators { + repeated cosmos.staking.v1beta1.Validator list = 1 [(gogoproto.nullable) = false]; +} + +// HeaderSubset is a subset of the block header that is relevant to the IBC codebase. It is +// stored for each height and then converted to the `tm.Header` object after queried. It is +// pruned when the information is no longer needed according to the `HistoricalEntries` param. +message HeaderSubset { + // timestamp of the block + google.protobuf.Timestamp time = 1 [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; + // validators for the next block + bytes next_validators_hash = 2; + // state after txs from the previous block + bytes app_hash = 3; } \ No newline at end of file diff --git a/x/dogfood/genesis.go b/x/dogfood/genesis.go deleted file mode 100644 index de40ed814..000000000 --- a/x/dogfood/genesis.go +++ /dev/null @@ -1,26 +0,0 @@ -package dogfood - -import ( - "github.com/ExocoreNetwork/exocore/x/dogfood/keeper" - "github.com/ExocoreNetwork/exocore/x/dogfood/types" - abci "github.com/cometbft/cometbft/abci/types" - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// InitGenesis initializes the module's state from a provided genesis state. -func InitGenesis( - ctx sdk.Context, - k keeper.Keeper, - genState types.GenesisState, -) []abci.ValidatorUpdate { - k.SetParams(ctx, genState.Params) - return k.ApplyValidatorChanges(ctx, genState.ValSet) -} - -// ExportGenesis returns the module's exported genesis -func ExportGenesis(ctx sdk.Context, k keeper.Keeper) *types.GenesisState { - genesis := types.DefaultGenesis() - genesis.Params = k.GetDogfoodParams(ctx) - - return genesis -} diff --git a/x/dogfood/keeper/genesis.go b/x/dogfood/keeper/genesis.go new file mode 100644 index 000000000..16f51892b --- /dev/null +++ b/x/dogfood/keeper/genesis.go @@ -0,0 +1,34 @@ +package keeper + +import ( + "github.com/ExocoreNetwork/exocore/x/dogfood/types" + abci "github.com/cometbft/cometbft/abci/types" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// InitGenesis initializes the module's state from a provided genesis state. +func (k Keeper) InitGenesis( + ctx sdk.Context, + genState types.GenesisState, +) []abci.ValidatorUpdate { + k.SetParams(ctx, genState.Params) + // the `params` validator is not super useful to validate state level information + // so, it must be done here. by extension, the `InitGenesis` of the epochs module + // should be called before that of this module. + _, found := k.epochsKeeper.GetEpochInfo(ctx, genState.Params.EpochIdentifier) + if !found { + // the panic is suitable here because it is being done at genesis, when the node + // is not running. it means that the genesis file is malformed. + panic("epoch info not found") + + } + return k.ApplyValidatorChanges(ctx, genState.ValSet) +} + +// ExportGenesis returns the module's exported genesis +func (k Keeper) ExportGenesis(ctx sdk.Context) *types.GenesisState { + genesis := types.DefaultGenesis() + genesis.Params = k.GetDogfoodParams(ctx) + + return genesis +} diff --git a/x/dogfood/keeper/validators.go b/x/dogfood/keeper/validators.go index d1b5e3e37..15c5d229b 100644 --- a/x/dogfood/keeper/validators.go +++ b/x/dogfood/keeper/validators.go @@ -8,6 +8,7 @@ import ( "github.com/ExocoreNetwork/exocore/x/dogfood/types" abci "github.com/cometbft/cometbft/abci/types" + tmproto "github.com/cometbft/cometbft/proto/tendermint/types" cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" sdk "github.com/cosmos/cosmos-sdk/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" @@ -21,10 +22,8 @@ import ( func (k Keeper) UnbondingTime(ctx sdk.Context) time.Duration { count := k.GetEpochsUntilUnbonded(ctx) identifier := k.GetEpochIdentifier(ctx) - epoch, found := k.epochsKeeper.GetEpochInfo(ctx, identifier) - if !found { - panic("epoch info not found") - } + // no need to check for found, as the epoch info is validated at genesis. + epoch, _ := k.epochsKeeper.GetEpochInfo(ctx, identifier) durationPerEpoch := epoch.Duration return time.Duration(count) * durationPerEpoch } @@ -138,103 +137,111 @@ func (k Keeper) GetAllExocoreValidators( func (k Keeper) GetHistoricalInfo( ctx sdk.Context, height int64, ) (stakingtypes.HistoricalInfo, bool) { - store := ctx.KVStore(k.storeKey) - key := types.HistoricalInfoKey(height) - - value := store.Get(key) - if value == nil { + headerSubset, found := k.GetBlockHeader(ctx, height) + if !found { + // only panic in the case of an unmarshal error return stakingtypes.HistoricalInfo{}, false } - - return stakingtypes.MustUnmarshalHistoricalInfo(k.cdc, value), true + valSetID, found := k.GetValidatorSetID(ctx, height) + if !found { + // only panic in the case of an unmarshal error + return stakingtypes.HistoricalInfo{}, false + } + valSet, found := k.GetValidatorSet(ctx, valSetID) + if !found { + // only panic in the case of an unmarshal error + return stakingtypes.HistoricalInfo{}, false + } + header := tmproto.Header{ + Time: headerSubset.Time, + NextValidatorsHash: headerSubset.NextValidatorsHash, + AppHash: headerSubset.AppHash, + } + return stakingtypes.NewHistoricalInfo( + header, stakingtypes.Validators(valSet.GetList()), sdk.DefaultPowerReduction, + ), true } -// SetHistoricalInfo sets the historical info at a given height. This is +// SetValidatorSet sets the validator set at a given id. This is // (intentionally) not exported in the genesis state. -func (k Keeper) SetHistoricalInfo( - ctx sdk.Context, height int64, hi *stakingtypes.HistoricalInfo, +func (k Keeper) SetValidatorSet( + ctx sdk.Context, id uint64, vs *types.Validators, ) { store := ctx.KVStore(k.storeKey) - key := types.HistoricalInfoKey(height) - value := k.cdc.MustMarshal(hi) - + key := types.ValidatorSetKey(id) + value := k.cdc.MustMarshal(vs) store.Set(key, value) } -// DeleteHistoricalInfo deletes the historical info at a given height. -func (k Keeper) DeleteHistoricalInfo(ctx sdk.Context, height int64) { +// GetValidatorSet gets the validator set at a given id. +func (k Keeper) GetValidatorSet( + ctx sdk.Context, id uint64, +) (*types.Validators, bool) { store := ctx.KVStore(k.storeKey) - key := types.HistoricalInfoKey(height) + key := types.ValidatorSetKey(id) + if !store.Has(key) { + return nil, false + } + value := store.Get(key) + var hi types.Validators + k.cdc.MustUnmarshal(value, &hi) + return &hi, true +} +// DeleteValidatorSet deletes the validator set at a given id. +func (k Keeper) DeleteValidatorSet(ctx sdk.Context, id uint64) { + store := ctx.KVStore(k.storeKey) + key := types.ValidatorSetKey(id) store.Delete(key) } -// TrackHistoricalInfo saves the latest historical-info and deletes the oldest -// heights that are below pruning height. +// TrackHistoricalInfo saves the latest historical info and deletes the ones eligible to be +// pruned. The historical info is stored in two parts: one is the header and the other is the +// validator set. Within an epoch, the validator set will only change if there is a slashing +// event. Otherwise, it is constant. The header, however, will change at every block. Since +// the Cosmos SDK does not allow for the retrieval of a past block header, we store the header +// ourselves in this function. The validator set is stored when it changes at the end of an +// epoch or at a slashing event in the corresponding functions. func (k Keeper) TrackHistoricalInfo(ctx sdk.Context) { + // Get the number of historical entries to persist, as the number of block heights. numHistoricalEntries := k.GetHistoricalEntries(ctx) - // Prune store to ensure we only have parameter-defined historical entries. - // In most cases, this will involve removing a single historical entry. - // In the rare scenario when the historical entries gets reduced to a lower value k' - // from the original value k. k - k' entries must be deleted from the store. - // Since the entries to be deleted are always in a continuous range, we can iterate - // over the historical entries starting from the most recent version to be pruned - // and then return at the first empty entry. + // we are deleting headers, say, from, 0 to 999 at block 1999 + // for these headers, we must find the corresponding validator set ids to delete. + // they must be only deleted if no other block is using them. + lastDeletedID := uint64(0) // contract: starts from 1. for i := ctx.BlockHeight() - int64(numHistoricalEntries); i >= 0; i-- { - _, found := k.GetHistoricalInfo(ctx, i) + _, found := k.GetBlockHeader(ctx, i) if found { - k.DeleteHistoricalInfo(ctx, i) + // because they are deleted together, and saved one after the other, + // since the block header exists, so must the validator set id. + lastDeletedID, _ = k.GetValidatorSetID(ctx, i+1) + // clear both the header and the mapping + k.DeleteBlockHeader(ctx, i) + k.DeleteValidatorSetID(ctx, i) } else { break } } + // contract: TrackHistoricalInfo must be called after storing the validator set + // for the current block. + currentID, _ := k.GetValidatorSetID(ctx, ctx.BlockHeight()) + for i := lastDeletedID; i < currentID; i++ { + k.DeleteValidatorSet(ctx, i) + } // if there is no need to persist historicalInfo, return. if numHistoricalEntries == 0 { return } - // Create HistoricalInfo struct - lastVals := []stakingtypes.Validator{} - for _, v := range k.GetAllExocoreValidators(ctx) { - pk, err := v.ConsPubKey() - if err != nil { - // This should never happen as the pubkey is assumed - // to be stored correctly earlier. - panic(err) - } - val, err := stakingtypes.NewValidator(nil, pk, stakingtypes.Description{}) - if err != nil { - // This should never happen as the pubkey is assumed - // to be stored correctly earlier. - panic(err) - } + // store the header + k.StoreBlockHeader(ctx) - // Set validator to bonded status. - val.Status = stakingtypes.Bonded - // Compute tokens from voting power. - val.Tokens = sdk.TokensFromConsensusPower( - v.Power, - // TODO(mm) - // note that this is not super relevant for the historical info - // since IBC does not seem to use the tokens field. - sdk.NewInt(1), - ) - lastVals = append(lastVals, val) - } - - // Create historical info entry which sorts the validator set by voting power. - historicalEntry := stakingtypes.NewHistoricalInfo( - ctx.BlockHeader(), lastVals, - // TODO(mm) - // this should match the power reduction number above - // and is also thus not relevant. - sdk.NewInt(1), - ) - - // Set latest HistoricalInfo at current height. - k.SetHistoricalInfo(ctx, ctx.BlockHeight(), &historicalEntry) + // we have stored: + // before TrackHistoricalInfo: ValidatorSetID for height, and the validator set. + // within TrackHistoricalInfo: the header. + // this is enough information to answer the GetHistoricalInfo query. } // MustGetCurrentValidatorsAsABCIUpdates gets all validators converted @@ -259,3 +266,66 @@ func (k Keeper) MustGetCurrentValidatorsAsABCIUpdates(ctx sdk.Context) []abci.Va } return valUpdates } + +// GetValidatorSetID returns the identifier of the validator set at a given height. +// It is used to "share" the validator set entries across multiple heights within an epoch. +// Typically, the validator set should change only at the end of an epoch. However, in the +// case of a slashing occurrence, the validator set may change within an epoch. +func (k Keeper) GetValidatorSetID(ctx sdk.Context, height int64) (uint64, bool) { + store := ctx.KVStore(k.storeKey) + key := types.ValidatorSetIDKey(height) + value := store.Get(key) + if value == nil { + return 0, false + } + return sdk.BigEndianToUint64(value), true +} + +// SetValidatorSetID sets the identifier of the validator set at a given height. +func (k Keeper) SetValidatorSetID(ctx sdk.Context, height int64, id uint64) { + store := ctx.KVStore(k.storeKey) + key := types.ValidatorSetIDKey(height) + value := sdk.Uint64ToBigEndian(id) + store.Set(key, value) +} + +// DeleteValidatorSetID deletes the identifier of the validator set at a given height. +func (k Keeper) DeleteValidatorSetID(ctx sdk.Context, height int64) { + store := ctx.KVStore(k.storeKey) + key := types.ValidatorSetIDKey(height) + store.Delete(key) +} + +// GetBlockHeader returns the block header at a given height. +func (k Keeper) GetBlockHeader(ctx sdk.Context, height int64) (types.HeaderSubset, bool) { + store := ctx.KVStore(k.storeKey) + key := types.HeaderKey(height) + var header types.HeaderSubset + value := store.Get(key) + if value == nil { + return header, false + } + k.cdc.MustUnmarshal(value, &header) + return header, true +} + +// SetBlockHeader sets the block header at a given height. +func (k Keeper) DeleteBlockHeader(ctx sdk.Context, height int64) { + store := ctx.KVStore(k.storeKey) + key := types.HeaderKey(height) + store.Delete(key) +} + +// StoreBlockHeader stores the block header subset as of the current height. +func (k Keeper) StoreBlockHeader(ctx sdk.Context) { + key := types.HeaderKey(ctx.BlockHeight()) + sdkHeader := ctx.BlockHeader() + header := types.HeaderSubset{ + Time: sdkHeader.Time, + NextValidatorsHash: sdkHeader.NextValidatorsHash, + AppHash: sdkHeader.GetAppHash(), + } + store := ctx.KVStore(k.storeKey) + value := k.cdc.MustMarshal(&header) + store.Set(key, value) +} diff --git a/x/dogfood/module.go b/x/dogfood/module.go index 2ac7087e3..4be32e0a3 100644 --- a/x/dogfood/module.go +++ b/x/dogfood/module.go @@ -141,12 +141,12 @@ func (am AppModule) InitGenesis( // Initialize global index to index in genesis state cdc.MustUnmarshalJSON(gs, &genState) - return InitGenesis(ctx, am.keeper, genState) + return am.keeper.InitGenesis(ctx, genState) } // ExportGenesis returns the module's exported genesis state as raw JSON bytes. func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.RawMessage { - genState := ExportGenesis(ctx, am.keeper) + genState := am.keeper.ExportGenesis(ctx) return cdc.MustMarshalJSON(genState) } @@ -159,6 +159,6 @@ func (AppModule) ConsensusVersion() uint64 { return 1 } func (am AppModule) BeginBlock(_ sdk.Context, _ abci.RequestBeginBlock) {} // EndBlock contains the logic that is automatically triggered at the end of each block -func (am AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { +func (am AppModule) EndBlock(ctx sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { return []abci.ValidatorUpdate{} } diff --git a/x/dogfood/types/dogfood.pb.go b/x/dogfood/types/dogfood.pb.go index 64cadbb85..1e5f859ed 100644 --- a/x/dogfood/types/dogfood.pb.go +++ b/x/dogfood/types/dogfood.pb.go @@ -7,17 +7,22 @@ import ( fmt "fmt" _ "github.com/cosmos/cosmos-proto" types "github.com/cosmos/cosmos-sdk/codec/types" + types1 "github.com/cosmos/cosmos-sdk/x/staking/types" _ "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/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. @@ -91,33 +96,156 @@ func (m *ExocoreValidator) GetPubkey() *types.Any { return nil } +// Validators is a list of validators stored according to the staking module. +type Validators struct { + List []types1.Validator `protobuf:"bytes,1,rep,name=list,proto3" json:"list"` +} + +func (m *Validators) Reset() { *m = Validators{} } +func (m *Validators) String() string { return proto.CompactTextString(m) } +func (*Validators) ProtoMessage() {} +func (*Validators) Descriptor() ([]byte, []int) { + return fileDescriptor_071b9989c501c3f2, []int{1} +} +func (m *Validators) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Validators) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Validators.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 *Validators) XXX_Merge(src proto.Message) { + xxx_messageInfo_Validators.Merge(m, src) +} +func (m *Validators) XXX_Size() int { + return m.Size() +} +func (m *Validators) XXX_DiscardUnknown() { + xxx_messageInfo_Validators.DiscardUnknown(m) +} + +var xxx_messageInfo_Validators proto.InternalMessageInfo + +func (m *Validators) GetList() []types1.Validator { + if m != nil { + return m.List + } + return nil +} + +// HeaderSubset is a subset of the block header that is relevant to the IBC codebase. It is +// stored for each height and then converted to the `tm.Header` object after queried. It is +// pruned when the information is no longer needed according to the `HistoricalEntries` param. +type HeaderSubset struct { + // timestamp of the block + Time time.Time `protobuf:"bytes,1,opt,name=time,proto3,stdtime" json:"time"` + // validators for the next block + NextValidatorsHash []byte `protobuf:"bytes,2,opt,name=next_validators_hash,json=nextValidatorsHash,proto3" json:"next_validators_hash,omitempty"` + // state after txs from the previous block + AppHash []byte `protobuf:"bytes,3,opt,name=app_hash,json=appHash,proto3" json:"app_hash,omitempty"` +} + +func (m *HeaderSubset) Reset() { *m = HeaderSubset{} } +func (m *HeaderSubset) String() string { return proto.CompactTextString(m) } +func (*HeaderSubset) ProtoMessage() {} +func (*HeaderSubset) Descriptor() ([]byte, []int) { + return fileDescriptor_071b9989c501c3f2, []int{2} +} +func (m *HeaderSubset) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *HeaderSubset) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_HeaderSubset.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 *HeaderSubset) XXX_Merge(src proto.Message) { + xxx_messageInfo_HeaderSubset.Merge(m, src) +} +func (m *HeaderSubset) XXX_Size() int { + return m.Size() +} +func (m *HeaderSubset) XXX_DiscardUnknown() { + xxx_messageInfo_HeaderSubset.DiscardUnknown(m) +} + +var xxx_messageInfo_HeaderSubset proto.InternalMessageInfo + +func (m *HeaderSubset) GetTime() time.Time { + if m != nil { + return m.Time + } + return time.Time{} +} + +func (m *HeaderSubset) GetNextValidatorsHash() []byte { + if m != nil { + return m.NextValidatorsHash + } + return nil +} + +func (m *HeaderSubset) GetAppHash() []byte { + if m != nil { + return m.AppHash + } + return nil +} + func init() { proto.RegisterType((*ExocoreValidator)(nil), "exocore.dogfood.v1.ExocoreValidator") + proto.RegisterType((*Validators)(nil), "exocore.dogfood.v1.Validators") + proto.RegisterType((*HeaderSubset)(nil), "exocore.dogfood.v1.HeaderSubset") } func init() { proto.RegisterFile("exocore/dogfood/v1/dogfood.proto", fileDescriptor_071b9989c501c3f2) } var fileDescriptor_071b9989c501c3f2 = []byte{ - // 302 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x3c, 0x90, 0x31, 0x4e, 0xf3, 0x30, - 0x14, 0xc7, 0xeb, 0xaf, 0xfa, 0x8a, 0x14, 0x18, 0x50, 0x14, 0x89, 0xd0, 0xc1, 0x44, 0x9d, 0xba, - 0x60, 0xab, 0x74, 0x63, 0xa3, 0x12, 0x53, 0x25, 0x84, 0x3a, 0x30, 0xb0, 0x54, 0x4e, 0xe2, 0x9a, - 0xaa, 0x49, 0x5e, 0x64, 0x3b, 0x6d, 0x7d, 0x0b, 0x2e, 0xc1, 0x0d, 0x38, 0x04, 0x62, 0xea, 0xc8, - 0x84, 0x50, 0x72, 0x03, 0x4e, 0x80, 0x88, 0x63, 0xb6, 0xf7, 0xf3, 0xff, 0x3d, 0xfb, 0xe7, 0xe7, - 0x45, 0x7c, 0x0f, 0x09, 0x48, 0x4e, 0x53, 0x10, 0x2b, 0x80, 0x94, 0x6e, 0x27, 0xae, 0x24, 0xa5, - 0x04, 0x0d, 0xbe, 0xdf, 0x75, 0x10, 0x77, 0xbc, 0x9d, 0x0c, 0x03, 0x01, 0x02, 0xda, 0x98, 0xfe, - 0x56, 0xb6, 0x73, 0x78, 0x2e, 0x00, 0x44, 0xc6, 0x69, 0x4b, 0x71, 0xb5, 0xa2, 0xac, 0x30, 0x2e, - 0x4a, 0x40, 0xe5, 0xa0, 0x96, 0x76, 0xc6, 0x82, 0x8d, 0x46, 0x2f, 0xc8, 0x3b, 0xbd, 0xb5, 0x4f, - 0x3c, 0xb0, 0x6c, 0x9d, 0x32, 0x0d, 0xd2, 0x0f, 0xbd, 0x23, 0x96, 0xa6, 0x92, 0x2b, 0x15, 0xa2, - 0x08, 0x8d, 0x4f, 0x16, 0x0e, 0xfd, 0xc0, 0xfb, 0x5f, 0xc2, 0x8e, 0xcb, 0xf0, 0x5f, 0x84, 0xc6, - 0xfd, 0x85, 0x05, 0x9f, 0x79, 0x83, 0xb2, 0x8a, 0x37, 0xdc, 0x84, 0xfd, 0x08, 0x8d, 0x8f, 0xaf, - 0x02, 0x62, 0x5d, 0x88, 0x73, 0x21, 0x37, 0x85, 0x99, 0x4d, 0xbf, 0x3f, 0x2f, 0xce, 0x0c, 0xcb, - 0xb3, 0xeb, 0x51, 0x02, 0x85, 0xe2, 0x85, 0xaa, 0xd4, 0xd2, 0xce, 0x8d, 0xde, 0x5f, 0x2f, 0x83, - 0xce, 0x2b, 0x91, 0xa6, 0xd4, 0x40, 0xee, 0xab, 0x78, 0xce, 0xcd, 0xa2, 0xbb, 0x78, 0x36, 0x7f, - 0xab, 0x31, 0x3a, 0xd4, 0x18, 0x7d, 0xd5, 0x18, 0x3d, 0x37, 0xb8, 0x77, 0x68, 0x70, 0xef, 0xa3, - 0xc1, 0xbd, 0xc7, 0x89, 0x58, 0xeb, 0xa7, 0x2a, 0x26, 0x09, 0xe4, 0xb4, 0xfb, 0xc9, 0x1d, 0xd7, - 0x3b, 0x90, 0x1b, 0xea, 0xb6, 0xbb, 0xff, 0xdb, 0xaf, 0x36, 0x25, 0x57, 0xf1, 0xa0, 0xf5, 0x9a, - 0xfe, 0x04, 0x00, 0x00, 0xff, 0xff, 0x39, 0xda, 0x59, 0x49, 0x7f, 0x01, 0x00, 0x00, + // 454 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x64, 0x92, 0x31, 0x8e, 0xd3, 0x40, + 0x14, 0x86, 0x33, 0x24, 0x2c, 0xab, 0x49, 0x0a, 0x34, 0xb2, 0x84, 0x37, 0x85, 0x63, 0x2c, 0x8a, + 0x34, 0xd8, 0x38, 0xdb, 0x20, 0xa8, 0x88, 0x84, 0xb4, 0x68, 0x25, 0x84, 0x0c, 0xa2, 0xa0, 0x89, + 0xc6, 0xf6, 0xac, 0x63, 0x25, 0xf6, 0x1b, 0x79, 0xc6, 0xd9, 0xf8, 0x16, 0xdb, 0x70, 0x04, 0x6e, + 0xc0, 0x21, 0x56, 0x54, 0x5b, 0x52, 0x05, 0x94, 0xdc, 0x80, 0x13, 0x20, 0x7b, 0x66, 0x82, 0xc4, + 0x76, 0xf3, 0xfb, 0x7f, 0xff, 0xd3, 0xf7, 0x9e, 0x1f, 0x76, 0xd9, 0x16, 0x12, 0xa8, 0x58, 0x90, + 0x42, 0x76, 0x05, 0x90, 0x06, 0x9b, 0xd0, 0x3c, 0x7d, 0x5e, 0x81, 0x04, 0x42, 0x74, 0x85, 0x6f, + 0x3e, 0x6f, 0xc2, 0xb1, 0x95, 0x41, 0x06, 0x9d, 0x1d, 0xb4, 0x2f, 0x55, 0x39, 0x3e, 0xcb, 0x00, + 0xb2, 0x35, 0x0b, 0x3a, 0x15, 0xd7, 0x57, 0x01, 0x2d, 0x1b, 0x6d, 0x4d, 0xfe, 0xb7, 0x64, 0x5e, + 0x30, 0x21, 0x69, 0xc1, 0x4d, 0x36, 0x01, 0x51, 0x80, 0x58, 0xa8, 0xa6, 0x4a, 0x68, 0xeb, 0x99, + 0x52, 0x81, 0x90, 0x74, 0x95, 0x97, 0x59, 0xb0, 0x09, 0x63, 0x26, 0x69, 0x68, 0xb4, 0xaa, 0xf2, + 0xbe, 0x21, 0xfc, 0xf8, 0xad, 0x22, 0xfd, 0x4c, 0xd7, 0x79, 0x4a, 0x25, 0x54, 0xc4, 0xc6, 0x8f, + 0x68, 0x9a, 0x56, 0x4c, 0x08, 0x1b, 0xb9, 0x68, 0x3a, 0x8a, 0x8c, 0x24, 0x16, 0x7e, 0xc8, 0xe1, + 0x9a, 0x55, 0xf6, 0x03, 0x17, 0x4d, 0xfb, 0x91, 0x12, 0x84, 0xe2, 0x13, 0x5e, 0xc7, 0x2b, 0xd6, + 0xd8, 0x7d, 0x17, 0x4d, 0x87, 0x33, 0xcb, 0x57, 0xdc, 0xbe, 0xe1, 0xf6, 0xdf, 0x94, 0xcd, 0xfc, + 0xfc, 0xcf, 0x6e, 0xf2, 0xa4, 0xa1, 0xc5, 0xfa, 0x95, 0x97, 0x40, 0x29, 0x58, 0x29, 0x6a, 0xb1, + 0x50, 0x39, 0xef, 0xc7, 0xf7, 0xe7, 0x96, 0xa6, 0x4f, 0xaa, 0x86, 0x4b, 0xf0, 0x3f, 0xd4, 0xf1, + 0x25, 0x6b, 0x22, 0xdd, 0xd8, 0x7b, 0x87, 0xf1, 0x91, 0x4f, 0x90, 0xd7, 0x78, 0xb0, 0xce, 0x85, + 0xb4, 0x91, 0xdb, 0x9f, 0x0e, 0x67, 0x4f, 0x7d, 0x1d, 0x35, 0xa3, 0xe9, 0x51, 0xfd, 0x63, 0x62, + 0x3e, 0xb8, 0xdd, 0x4d, 0x7a, 0x51, 0x17, 0xf2, 0xbe, 0x22, 0x3c, 0xba, 0x60, 0x34, 0x65, 0xd5, + 0xc7, 0x3a, 0x16, 0x4c, 0x92, 0x97, 0x78, 0xd0, 0xee, 0xb5, 0x9b, 0x75, 0x38, 0x1b, 0xdf, 0x83, + 0xff, 0x64, 0x96, 0x3e, 0x3f, 0x6d, 0xdb, 0xdc, 0xfc, 0x9a, 0xa0, 0xa8, 0x4b, 0x90, 0x17, 0xd8, + 0x2a, 0xd9, 0x56, 0x2e, 0x36, 0x47, 0xb4, 0xc5, 0x92, 0x8a, 0x65, 0xb7, 0x9d, 0x51, 0x44, 0x5a, + 0xef, 0x1f, 0xf5, 0x05, 0x15, 0x4b, 0x72, 0x86, 0x4f, 0x29, 0xe7, 0xaa, 0xaa, 0xaf, 0x77, 0xcb, + 0x79, 0x6b, 0xcd, 0x2f, 0x6f, 0xf7, 0x0e, 0xba, 0xdb, 0x3b, 0xe8, 0xf7, 0xde, 0x41, 0x37, 0x07, + 0xa7, 0x77, 0x77, 0x70, 0x7a, 0x3f, 0x0f, 0x4e, 0xef, 0x4b, 0x98, 0xe5, 0x72, 0x59, 0xc7, 0x7e, + 0x02, 0x45, 0xa0, 0x7f, 0xd6, 0x7b, 0x26, 0xaf, 0xa1, 0x5a, 0x05, 0xe6, 0x0e, 0xb7, 0xc7, 0x4b, + 0x94, 0x0d, 0x67, 0x22, 0x3e, 0xe9, 0xe8, 0xcf, 0xff, 0x06, 0x00, 0x00, 0xff, 0xff, 0x07, 0xfc, + 0xed, 0x55, 0xa9, 0x02, 0x00, 0x00, } func (m *ExocoreValidator) Marshal() (dAtA []byte, err error) { @@ -167,6 +295,88 @@ func (m *ExocoreValidator) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *Validators) 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 *Validators) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Validators) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.List) > 0 { + for iNdEx := len(m.List) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.List[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintDogfood(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *HeaderSubset) 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 *HeaderSubset) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *HeaderSubset) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.AppHash) > 0 { + i -= len(m.AppHash) + copy(dAtA[i:], m.AppHash) + i = encodeVarintDogfood(dAtA, i, uint64(len(m.AppHash))) + i-- + dAtA[i] = 0x1a + } + if len(m.NextValidatorsHash) > 0 { + i -= len(m.NextValidatorsHash) + copy(dAtA[i:], m.NextValidatorsHash) + i = encodeVarintDogfood(dAtA, i, uint64(len(m.NextValidatorsHash))) + i-- + dAtA[i] = 0x12 + } + n2, err2 := github_com_cosmos_gogoproto_types.StdTimeMarshalTo(m.Time, dAtA[i-github_com_cosmos_gogoproto_types.SizeOfStdTime(m.Time):]) + if err2 != nil { + return 0, err2 + } + i -= n2 + i = encodeVarintDogfood(dAtA, i, uint64(n2)) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + func encodeVarintDogfood(dAtA []byte, offset int, v uint64) int { offset -= sovDogfood(v) base := offset @@ -198,6 +408,40 @@ func (m *ExocoreValidator) Size() (n int) { return n } +func (m *Validators) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.List) > 0 { + for _, e := range m.List { + l = e.Size() + n += 1 + l + sovDogfood(uint64(l)) + } + } + return n +} + +func (m *HeaderSubset) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = github_com_cosmos_gogoproto_types.SizeOfStdTime(m.Time) + n += 1 + l + sovDogfood(uint64(l)) + l = len(m.NextValidatorsHash) + if l > 0 { + n += 1 + l + sovDogfood(uint64(l)) + } + l = len(m.AppHash) + if l > 0 { + n += 1 + l + sovDogfood(uint64(l)) + } + return n +} + func sovDogfood(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -343,6 +587,241 @@ func (m *ExocoreValidator) Unmarshal(dAtA []byte) error { } return nil } +func (m *Validators) 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 ErrIntOverflowDogfood + } + 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: Validators: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Validators: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field List", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDogfood + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthDogfood + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthDogfood + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.List = append(m.List, types1.Validator{}) + if err := m.List[len(m.List)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipDogfood(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthDogfood + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *HeaderSubset) 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 ErrIntOverflowDogfood + } + 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: HeaderSubset: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: HeaderSubset: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Time", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDogfood + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthDogfood + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthDogfood + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := github_com_cosmos_gogoproto_types.StdTimeUnmarshal(&m.Time, dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NextValidatorsHash", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDogfood + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthDogfood + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthDogfood + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.NextValidatorsHash = append(m.NextValidatorsHash[:0], dAtA[iNdEx:postIndex]...) + if m.NextValidatorsHash == nil { + m.NextValidatorsHash = []byte{} + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppHash", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDogfood + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthDogfood + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthDogfood + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppHash = append(m.AppHash[:0], dAtA[iNdEx:postIndex]...) + if m.AppHash == nil { + m.AppHash = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipDogfood(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthDogfood + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipDogfood(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/x/dogfood/types/keys.go b/x/dogfood/types/keys.go index f7efde563..e3875dc57 100644 --- a/x/dogfood/types/keys.go +++ b/x/dogfood/types/keys.go @@ -16,8 +16,14 @@ const ( // ExocoreValidatorBytePrefix is the prefix for the validator store. ExocoreValidatorBytePrefix byte = iota + 1 - // HistoricalInfoBytePrefix is the prefix for the historical info store. - HistoricalInfoBytePrefix + // ValidatorSetBytePrefix is the prefix for the historical validator set store. + ValidatorSetBytePrefix + + // ValidatorSetIDBytePrefix is the prefix for the validator set id store. + ValidatorSetIDBytePrefix + + // HeaderBytePrefix is the prefix for the header store. + HeaderBytePrefix ) // ExocoreValidatorKey returns the key for the validator store. @@ -25,8 +31,20 @@ func ExocoreValidatorKey(address sdk.AccAddress) []byte { return append([]byte{ExocoreValidatorBytePrefix}, address.Bytes()...) } -// HistoricalInfoKey returns the key for the historical info store. -func HistoricalInfoKey(height int64) []byte { +// ValidatorSetKey returns the key for the historical validator set store. +func ValidatorSetKey(id uint64) []byte { + bz := sdk.Uint64ToBigEndian(id) + return append([]byte{ValidatorSetBytePrefix}, bz...) +} + +// ValidatorSetIDKey returns the key for the validator set id store. +func ValidatorSetIDKey(height int64) []byte { + bz := sdk.Uint64ToBigEndian(uint64(height)) + return append([]byte{ValidatorSetIDBytePrefix}, bz...) +} + +// HeaderKey returns the key for the header store. +func HeaderKey(height int64) []byte { bz := sdk.Uint64ToBigEndian(uint64(height)) - return append([]byte{HistoricalInfoBytePrefix}, bz...) + return append([]byte{HeaderBytePrefix}, bz...) } From 3447309b9f3c1342bf4e2d1e3fa26dcace13ddbe Mon Sep 17 00:00:00 2001 From: MaxMustermann2 <82761650+MaxMustermann2@users.noreply.github.com> Date: Mon, 26 Feb 2024 19:35:44 +0000 Subject: [PATCH 10/14] fix(dogfood): remove old key then add new --- x/dogfood/keeper/impl_operator_hooks.go | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/x/dogfood/keeper/impl_operator_hooks.go b/x/dogfood/keeper/impl_operator_hooks.go index 9e0c22af2..3599d6565 100644 --- a/x/dogfood/keeper/impl_operator_hooks.go +++ b/x/dogfood/keeper/impl_operator_hooks.go @@ -104,15 +104,7 @@ func (h OperatorHooksWrapper) AfterOperatorKeyReplacement( chainID string, ) { if strings.Compare(chainID, ctx.ChainID()) == 0 { - // res == Removed, it means operator has added their original key again - // res == Success, there is no additional information to store - // res == Exists, there is no nothing to do - if res := h.keeper.QueueOperation( - ctx, addr, newKey, types.KeyAdditionOrUpdate, - ); res == types.QueueResultRemoved { - // see AfterOperatorOptIn for explanation - h.keeper.ClearUnbondingInformation(ctx, addr, newKey) - } + // remove the old key // res == Removed, it means operator had added this key and is now removing it. // no additional information to clear. // res == Success, the old key should be pruned from the operator module. @@ -123,6 +115,16 @@ func (h OperatorHooksWrapper) AfterOperatorKeyReplacement( // the old key can be marked for pruning h.keeper.SetUnbondingInformation(ctx, addr, oldKey, false) } + // add the new key + // res == Removed, it means operator has added their original key again + // res == Success, there is no additional information to store + // res == Exists, there is no nothing to do + if res := h.keeper.QueueOperation( + ctx, addr, newKey, types.KeyAdditionOrUpdate, + ); res == types.QueueResultRemoved { + // see AfterOperatorOptIn for explanation + h.keeper.ClearUnbondingInformation(ctx, addr, newKey) + } } } From fe987cef101286d3c8e205b821700c188aee3394 Mon Sep 17 00:00:00 2001 From: MaxMustermann2 <82761650+MaxMustermann2@users.noreply.github.com> Date: Mon, 26 Feb 2024 19:41:30 +0000 Subject: [PATCH 11/14] fix(dogfood): RecordKeys > UndelegationRecordKeys The delegation module calls the records as undelegation records, and within itself stores the identifier for the record as the record key. The dogfood module should follow the same naming convention and call them undelegation record keys for clarity. --- proto/exocore/dogfood/v1/dogfood.proto | 7 +- x/dogfood/keeper/unbonding.go | 8 +- x/dogfood/types/dogfood.pb.go | 157 ++++++++++++------------- 3 files changed, 85 insertions(+), 87 deletions(-) diff --git a/proto/exocore/dogfood/v1/dogfood.proto b/proto/exocore/dogfood/v1/dogfood.proto index 587e5522f..21c5b350b 100644 --- a/proto/exocore/dogfood/v1/dogfood.proto +++ b/proto/exocore/dogfood/v1/dogfood.proto @@ -84,10 +84,9 @@ message ConsensusAddresses { repeated bytes list = 1; } -// RecordKeys is a collection of record keys. This is used to store a list of -// undelegation records to mature in the delegation module at the end of the -// epoch. -message RecordKeys { +// UndelegationRecordKeys is a collection of undelegation record keys. This is used to store a +// list of undelegation records to mature in the delegation module at the end of the epoch. +message UndelegationRecordKeys { repeated bytes list = 1; } diff --git a/x/dogfood/keeper/unbonding.go b/x/dogfood/keeper/unbonding.go index a973754c5..9e588e63a 100644 --- a/x/dogfood/keeper/unbonding.go +++ b/x/dogfood/keeper/unbonding.go @@ -63,7 +63,7 @@ func (k Keeper) AppendUndelegationToMature( ctx sdk.Context, epoch int64, recordKey []byte, ) { prev := k.GetUndelegationsToMature(ctx, epoch) - next := types.RecordKeys{ + next := types.UndelegationRecordKeys{ List: append(prev, recordKey), } k.setUndelegationsToMature(ctx, epoch, next) @@ -80,7 +80,7 @@ func (k Keeper) GetUndelegationsToMature( if bz == nil { return [][]byte{} } - var res types.RecordKeys + var res types.UndelegationRecordKeys if err := res.Unmarshal(bz); err != nil { // should never happen panic(err) @@ -101,11 +101,11 @@ func (k Keeper) ClearUndelegationsToMature( // setUndelegationsToMature sets all undelegation entries that should be released // at the end of the provided epoch. func (k Keeper) setUndelegationsToMature( - ctx sdk.Context, epoch int64, recordKeys types.RecordKeys, + ctx sdk.Context, epoch int64, undelegationRecords types.UndelegationRecordKeys, ) { store := ctx.KVStore(k.storeKey) key := types.UnbondingReleaseMaturityKey(epoch) - val, err := recordKeys.Marshal() + val, err := undelegationRecords.Marshal() if err != nil { panic(err) } diff --git a/x/dogfood/types/dogfood.pb.go b/x/dogfood/types/dogfood.pb.go index 0b5879f77..fc3868a19 100644 --- a/x/dogfood/types/dogfood.pb.go +++ b/x/dogfood/types/dogfood.pb.go @@ -372,25 +372,24 @@ func (m *ConsensusAddresses) GetList() [][]byte { return nil } -// RecordKeys is a collection of record keys. This is used to store a list of -// undelegation records to mature in the delegation module at the end of the -// epoch. -type RecordKeys struct { +// UndelegationRecordKeys is a collection of undelegation record keys. This is used to store a +// list of undelegation records to mature in the delegation module at the end of the epoch. +type UndelegationRecordKeys struct { List [][]byte `protobuf:"bytes,1,rep,name=list,proto3" json:"list,omitempty"` } -func (m *RecordKeys) Reset() { *m = RecordKeys{} } -func (m *RecordKeys) String() string { return proto.CompactTextString(m) } -func (*RecordKeys) ProtoMessage() {} -func (*RecordKeys) Descriptor() ([]byte, []int) { +func (m *UndelegationRecordKeys) Reset() { *m = UndelegationRecordKeys{} } +func (m *UndelegationRecordKeys) String() string { return proto.CompactTextString(m) } +func (*UndelegationRecordKeys) ProtoMessage() {} +func (*UndelegationRecordKeys) Descriptor() ([]byte, []int) { return fileDescriptor_071b9989c501c3f2, []int{5} } -func (m *RecordKeys) XXX_Unmarshal(b []byte) error { +func (m *UndelegationRecordKeys) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) } -func (m *RecordKeys) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { +func (m *UndelegationRecordKeys) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { if deterministic { - return xxx_messageInfo_RecordKeys.Marshal(b, m, deterministic) + return xxx_messageInfo_UndelegationRecordKeys.Marshal(b, m, deterministic) } else { b = b[:cap(b)] n, err := m.MarshalToSizedBuffer(b) @@ -400,19 +399,19 @@ func (m *RecordKeys) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return b[:n], nil } } -func (m *RecordKeys) XXX_Merge(src proto.Message) { - xxx_messageInfo_RecordKeys.Merge(m, src) +func (m *UndelegationRecordKeys) XXX_Merge(src proto.Message) { + xxx_messageInfo_UndelegationRecordKeys.Merge(m, src) } -func (m *RecordKeys) XXX_Size() int { +func (m *UndelegationRecordKeys) XXX_Size() int { return m.Size() } -func (m *RecordKeys) XXX_DiscardUnknown() { - xxx_messageInfo_RecordKeys.DiscardUnknown(m) +func (m *UndelegationRecordKeys) XXX_DiscardUnknown() { + xxx_messageInfo_UndelegationRecordKeys.DiscardUnknown(m) } -var xxx_messageInfo_RecordKeys proto.InternalMessageInfo +var xxx_messageInfo_UndelegationRecordKeys proto.InternalMessageInfo -func (m *RecordKeys) GetList() [][]byte { +func (m *UndelegationRecordKeys) GetList() [][]byte { if m != nil { return m.List } @@ -538,7 +537,7 @@ func init() { proto.RegisterType((*Operations)(nil), "exocore.dogfood.v1.Operations") proto.RegisterType((*AccountAddresses)(nil), "exocore.dogfood.v1.AccountAddresses") proto.RegisterType((*ConsensusAddresses)(nil), "exocore.dogfood.v1.ConsensusAddresses") - proto.RegisterType((*RecordKeys)(nil), "exocore.dogfood.v1.RecordKeys") + proto.RegisterType((*UndelegationRecordKeys)(nil), "exocore.dogfood.v1.UndelegationRecordKeys") proto.RegisterType((*Validators)(nil), "exocore.dogfood.v1.Validators") proto.RegisterType((*HeaderSubset)(nil), "exocore.dogfood.v1.HeaderSubset") } @@ -546,61 +545,61 @@ func init() { func init() { proto.RegisterFile("exocore/dogfood/v1/dogfood.proto", fileDescriptor_071b9989c501c3f2) } var fileDescriptor_071b9989c501c3f2 = []byte{ - // 849 bytes of a gzipped FileDescriptorProto + // 857 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x54, 0xcf, 0x6f, 0xe3, 0x44, 0x14, 0x8e, 0x9b, 0xb2, 0xbb, 0x4c, 0xbb, 0x5d, 0x33, 0x84, 0x6e, 0x6a, 0xed, 0xa6, 0x6e, 0x84, - 0x50, 0xa8, 0x84, 0x4d, 0x5a, 0x10, 0x08, 0x04, 0x52, 0xda, 0x18, 0x35, 0xca, 0xd2, 0x64, 0xed, - 0x64, 0x05, 0x5c, 0xac, 0x89, 0xfd, 0x9a, 0x5a, 0x4d, 0x3c, 0x23, 0xcf, 0x38, 0x5b, 0xff, 0x07, - 0x28, 0xa7, 0xbd, 0x70, 0xcc, 0x09, 0xf1, 0x1f, 0x20, 0x71, 0xe2, 0xbe, 0xe2, 0xb4, 0x47, 0x4e, - 0x0b, 0x6a, 0xff, 0x03, 0x6e, 0xdc, 0x56, 0xfe, 0x95, 0x66, 0x9b, 0xaa, 0x37, 0xbf, 0x79, 0xef, - 0x7b, 0xf3, 0x7d, 0x7e, 0xdf, 0x3c, 0xa4, 0xc2, 0x39, 0x75, 0x68, 0x00, 0xba, 0x4b, 0x87, 0x27, - 0x94, 0xba, 0xfa, 0xa4, 0x9e, 0x7f, 0x6a, 0x2c, 0xa0, 0x82, 0x62, 0x9c, 0x55, 0x68, 0xf9, 0xf1, - 0xa4, 0xae, 0x94, 0x86, 0x74, 0x48, 0x93, 0xb4, 0x1e, 0x7f, 0xa5, 0x95, 0xca, 0xd6, 0x90, 0xd2, - 0xe1, 0x08, 0xf4, 0x24, 0x1a, 0x84, 0x27, 0x3a, 0xf1, 0xa3, 0x2c, 0xb5, 0x7d, 0x3d, 0x25, 0xbc, - 0x31, 0x70, 0x41, 0xc6, 0x2c, 0xc7, 0x3a, 0x94, 0x8f, 0x29, 0xb7, 0xd3, 0xa6, 0x69, 0x90, 0xa5, - 0x1e, 0x09, 0xf0, 0x5d, 0x08, 0xc6, 0x9e, 0x2f, 0x74, 0x27, 0x88, 0x98, 0xa0, 0xfa, 0x19, 0x44, - 0x79, 0xf6, 0xc3, 0xb4, 0x56, 0xe7, 0x82, 0x9c, 0x79, 0xfe, 0x50, 0x9f, 0xd4, 0x07, 0x20, 0x48, - 0x3d, 0x8f, 0xd3, 0xaa, 0xea, 0x6f, 0x12, 0x92, 0x8d, 0x54, 0xc7, 0x33, 0x32, 0xf2, 0x5c, 0x22, - 0x68, 0x80, 0xcb, 0xe8, 0x2e, 0x71, 0xdd, 0x00, 0x38, 0x2f, 0x4b, 0xaa, 0x54, 0x5b, 0x37, 0xf3, - 0x10, 0x97, 0xd0, 0x3b, 0x8c, 0x3e, 0x87, 0xa0, 0xbc, 0xa2, 0x4a, 0xb5, 0xa2, 0x99, 0x06, 0x98, - 0xa0, 0x3b, 0x2c, 0x1c, 0x9c, 0x41, 0x54, 0x2e, 0xaa, 0x52, 0x6d, 0x6d, 0xaf, 0xa4, 0xa5, 0xaa, - 0xb4, 0x5c, 0x95, 0xd6, 0xf0, 0xa3, 0x83, 0xfd, 0xff, 0x5e, 0x6f, 0x3f, 0x8c, 0xc8, 0x78, 0xf4, - 0x55, 0xd5, 0xa1, 0x3e, 0x07, 0x9f, 0x87, 0xdc, 0x4e, 0x71, 0xd5, 0xbf, 0x7e, 0xff, 0xa4, 0x94, - 0x69, 0x4b, 0x95, 0x68, 0xdd, 0x70, 0xd0, 0x86, 0xc8, 0xcc, 0x1a, 0x57, 0xff, 0x94, 0xd0, 0xbb, - 0x1d, 0x06, 0x01, 0x11, 0x1e, 0xf5, 0xf1, 0x11, 0xda, 0xa0, 0x79, 0x60, 0x8b, 0x88, 0x41, 0xc2, - 0x73, 0x63, 0x6f, 0x47, 0x5b, 0x9e, 0x89, 0x36, 0x87, 0xf5, 0x22, 0x06, 0xe6, 0x7d, 0xba, 0x18, - 0xe2, 0x8f, 0x91, 0x9c, 0x1e, 0xd0, 0xc0, 0xce, 0x35, 0xaf, 0x24, 0x9a, 0x1f, 0xe4, 0xe7, 0x8d, - 0x4c, 0xfb, 0xd7, 0xe8, 0x2e, 0x0b, 0x07, 0xf6, 0x95, 0xcc, 0x47, 0xda, 0xd5, 0x00, 0x16, 0x68, - 0x8f, 0x3c, 0xa7, 0x0d, 0xd1, 0xc1, 0xea, 0xcb, 0xd7, 0xdb, 0x85, 0x84, 0x7f, 0x1b, 0xa2, 0xaa, - 0x81, 0xd0, 0x9c, 0x07, 0xc7, 0x5f, 0xa0, 0xd5, 0x91, 0xc7, 0x45, 0x59, 0x52, 0x8b, 0xb5, 0xb5, - 0xbd, 0xc7, 0xb7, 0xb2, 0xce, 0x1a, 0x25, 0x80, 0xea, 0x47, 0x48, 0x6e, 0x38, 0x0e, 0x0d, 0x7d, - 0x91, 0xb1, 0x02, 0x8e, 0xf1, 0x42, 0xb3, 0xf5, 0xac, 0xae, 0x86, 0xf0, 0x61, 0xfe, 0x8f, 0x6f, - 0xaf, 0x54, 0x11, 0x32, 0xc1, 0xa1, 0x81, 0xdb, 0x86, 0xe8, 0xe6, 0x8a, 0x16, 0x42, 0x73, 0x6b, - 0xc4, 0x7f, 0x61, 0x91, 0xfa, 0x8e, 0x96, 0x4d, 0x2d, 0x77, 0x55, 0xe6, 0x32, 0x6d, 0x8e, 0x78, - 0x8b, 0xfe, 0x2f, 0x12, 0x5a, 0x3f, 0x02, 0xe2, 0x42, 0x60, 0x85, 0x03, 0x0e, 0x02, 0x7f, 0x89, - 0x56, 0x63, 0xc3, 0x27, 0xe3, 0x5b, 0xdb, 0x53, 0x96, 0x7c, 0xd3, 0xcb, 0x5f, 0xc3, 0xc1, 0xbd, - 0xb8, 0xcd, 0x8b, 0x7f, 0xb6, 0x25, 0x33, 0x41, 0xe0, 0x4f, 0x51, 0xc9, 0x87, 0x73, 0x61, 0x4f, - 0xe6, 0xd4, 0xec, 0x53, 0xc2, 0x4f, 0xb3, 0xe1, 0xe1, 0x38, 0x77, 0xc5, 0xfa, 0x88, 0xf0, 0x53, - 0xbc, 0x85, 0xee, 0x11, 0xc6, 0xd2, 0xaa, 0x62, 0x66, 0x6b, 0xc6, 0xe2, 0xd4, 0xee, 0x1f, 0x12, - 0xba, 0xff, 0x96, 0x4d, 0xf0, 0x67, 0x48, 0xe9, 0x74, 0x0d, 0xb3, 0xd1, 0x6b, 0x75, 0x8e, 0xed, - 0xde, 0x8f, 0x5d, 0xc3, 0xee, 0x1f, 0x5b, 0x5d, 0xe3, 0xb0, 0xf5, 0x5d, 0xcb, 0x68, 0xca, 0x05, - 0xa5, 0x34, 0x9d, 0xa9, 0x72, 0x1b, 0xa2, 0x0e, 0xeb, 0xfb, 0x9c, 0x81, 0xe3, 0x9d, 0x78, 0xe0, - 0xe2, 0x6f, 0xd1, 0xce, 0x35, 0x54, 0xa3, 0xd9, 0x6c, 0x25, 0x51, 0xc7, 0xb4, 0xfb, 0xdd, 0x66, - 0xa3, 0x67, 0xc8, 0x92, 0xf2, 0x70, 0x3a, 0x53, 0xdf, 0x6f, 0x43, 0xd4, 0x70, 0x5d, 0x2f, 0xbe, - 0xb1, 0x13, 0xf4, 0x99, 0x4b, 0x04, 0xe0, 0x5d, 0xb4, 0x79, 0x0d, 0x6f, 0x1a, 0xdf, 0x77, 0x9e, - 0x35, 0x9e, 0xc8, 0x2b, 0xca, 0xc6, 0x74, 0xa6, 0xa2, 0xf8, 0x49, 0xc0, 0x98, 0x4e, 0xc8, 0x48, - 0x59, 0xfd, 0xf9, 0xd7, 0x4a, 0x61, 0xf7, 0x7f, 0x09, 0x3d, 0x78, 0x1a, 0x42, 0x08, 0x26, 0xf0, - 0x70, 0x24, 0x12, 0xee, 0xdf, 0xa0, 0xc7, 0x4f, 0xfb, 0x46, 0x3f, 0x06, 0x5b, 0xfd, 0x27, 0xbd, - 0x9b, 0xe8, 0x2b, 0xd3, 0x99, 0xba, 0xb9, 0x80, 0x5b, 0x14, 0xf1, 0x39, 0xda, 0x5a, 0x86, 0x5b, - 0xfd, 0xc3, 0x43, 0xc3, 0xb2, 0x64, 0x49, 0xd9, 0x9c, 0xce, 0x54, 0xbc, 0x00, 0xb5, 0x42, 0xc7, - 0x89, 0x9f, 0xc7, 0x3e, 0x2a, 0x2f, 0xc3, 0x8c, 0x1f, 0x5a, 0x56, 0xcf, 0x92, 0x57, 0x94, 0x0f, - 0xa6, 0x33, 0xf5, 0xbd, 0x05, 0x94, 0x71, 0xee, 0x71, 0xc1, 0x6f, 0xbe, 0x2b, 0xd1, 0x6c, 0x34, - 0xe5, 0xe2, 0xd2, 0x5d, 0x89, 0x76, 0x70, 0x53, 0xed, 0x07, 0xed, 0x97, 0x17, 0x15, 0xe9, 0xd5, - 0x45, 0x45, 0xfa, 0xf7, 0xa2, 0x22, 0xbd, 0xb8, 0xac, 0x14, 0x5e, 0x5d, 0x56, 0x0a, 0x7f, 0x5f, - 0x56, 0x0a, 0x3f, 0xd5, 0x87, 0x9e, 0x38, 0x0d, 0x07, 0x9a, 0x43, 0xc7, 0x7a, 0xb6, 0xdd, 0x8e, - 0x41, 0x3c, 0xa7, 0xc1, 0x99, 0x9e, 0xaf, 0xf5, 0xf3, 0xf9, 0x62, 0x8f, 0xf7, 0x07, 0x1f, 0xdc, - 0x49, 0x3c, 0xb7, 0xff, 0x26, 0x00, 0x00, 0xff, 0xff, 0x2a, 0xf4, 0x2e, 0x60, 0xf8, 0x05, 0x00, - 0x00, + 0x50, 0xa8, 0xc0, 0x26, 0x2d, 0x08, 0x04, 0x02, 0x29, 0x6d, 0x8c, 0x1a, 0x65, 0x69, 0xb2, 0x76, + 0xbc, 0x02, 0x2e, 0x96, 0x63, 0xbf, 0xa6, 0x56, 0x13, 0xcf, 0xc8, 0x33, 0xce, 0xd6, 0xff, 0x01, + 0xca, 0x69, 0x2f, 0x1c, 0x73, 0x42, 0xfc, 0x07, 0x48, 0x9c, 0xb8, 0xaf, 0x38, 0xed, 0x91, 0xd3, + 0x82, 0xda, 0xff, 0x80, 0x1b, 0x37, 0xe4, 0x5f, 0xa9, 0xb7, 0xa9, 0x7a, 0xf3, 0x9b, 0xf7, 0xbe, + 0x37, 0xdf, 0xe7, 0xf7, 0xcd, 0x43, 0x32, 0x9c, 0x13, 0x87, 0x04, 0xa0, 0xba, 0x64, 0x74, 0x42, + 0x88, 0xab, 0x4e, 0x9b, 0xf9, 0xa7, 0x42, 0x03, 0xc2, 0x09, 0xc6, 0x59, 0x85, 0x92, 0x1f, 0x4f, + 0x9b, 0x52, 0x65, 0x44, 0x46, 0x24, 0x49, 0xab, 0xf1, 0x57, 0x5a, 0x29, 0x6d, 0x8d, 0x08, 0x19, + 0x8d, 0x41, 0x4d, 0xa2, 0x61, 0x78, 0xa2, 0xda, 0x7e, 0x94, 0xa5, 0xb6, 0xaf, 0xa7, 0xb8, 0x37, + 0x01, 0xc6, 0xed, 0x09, 0xcd, 0xb1, 0x0e, 0x61, 0x13, 0xc2, 0xac, 0xb4, 0x69, 0x1a, 0x64, 0xa9, + 0x47, 0x1c, 0x7c, 0x17, 0x82, 0x89, 0xe7, 0x73, 0xd5, 0x09, 0x22, 0xca, 0x89, 0x7a, 0x06, 0x51, + 0x9e, 0x7d, 0x3f, 0xad, 0x55, 0x19, 0xb7, 0xcf, 0x3c, 0x7f, 0xa4, 0x4e, 0x9b, 0x43, 0xe0, 0x76, + 0x33, 0x8f, 0xd3, 0xaa, 0xfa, 0xaf, 0x02, 0x12, 0xb5, 0x54, 0xc7, 0x33, 0x7b, 0xec, 0xb9, 0x36, + 0x27, 0x01, 0xae, 0xa2, 0xbb, 0xb6, 0xeb, 0x06, 0xc0, 0x58, 0x55, 0x90, 0x85, 0xc6, 0xba, 0x9e, + 0x87, 0xb8, 0x82, 0xde, 0xa2, 0xe4, 0x39, 0x04, 0xd5, 0x15, 0x59, 0x68, 0x94, 0xf5, 0x34, 0xc0, + 0x36, 0xba, 0x43, 0xc3, 0xe1, 0x19, 0x44, 0xd5, 0xb2, 0x2c, 0x34, 0xd6, 0xf6, 0x2a, 0x4a, 0xaa, + 0x4a, 0xc9, 0x55, 0x29, 0x2d, 0x3f, 0x3a, 0xd8, 0xff, 0xf7, 0xf5, 0xf6, 0xc3, 0xc8, 0x9e, 0x8c, + 0xbf, 0xac, 0x3b, 0xc4, 0x67, 0xe0, 0xb3, 0x90, 0x59, 0x29, 0xae, 0xfe, 0xe7, 0x6f, 0x1f, 0x57, + 0x32, 0x6d, 0xa9, 0x12, 0xa5, 0x1f, 0x0e, 0xbb, 0x10, 0xe9, 0x59, 0xe3, 0xfa, 0x1f, 0x02, 0x7a, + 0xbb, 0x47, 0x21, 0xb0, 0xb9, 0x47, 0x7c, 0x7c, 0x84, 0x36, 0x48, 0x1e, 0x58, 0x3c, 0xa2, 0x90, + 0xf0, 0xdc, 0xd8, 0xdb, 0x51, 0x96, 0x67, 0xa2, 0x2c, 0x60, 0x83, 0x88, 0x82, 0x7e, 0x9f, 0x14, + 0x43, 0xfc, 0x21, 0x12, 0xd3, 0x03, 0x12, 0x58, 0xb9, 0xe6, 0x95, 0x44, 0xf3, 0x83, 0xfc, 0xbc, + 0x95, 0x69, 0xff, 0x0a, 0xdd, 0xa5, 0xe1, 0xd0, 0xba, 0x92, 0xf9, 0x48, 0xb9, 0x1a, 0x40, 0x81, + 0xf6, 0xd8, 0x73, 0xba, 0x10, 0x1d, 0xac, 0xbe, 0x7c, 0xbd, 0x5d, 0x4a, 0xf8, 0x77, 0x21, 0xaa, + 0x6b, 0x08, 0x2d, 0x78, 0x30, 0xfc, 0x39, 0x5a, 0x1d, 0x7b, 0x8c, 0x57, 0x05, 0xb9, 0xdc, 0x58, + 0xdb, 0x7b, 0x7c, 0x2b, 0xeb, 0xac, 0x51, 0x02, 0xa8, 0x7f, 0x80, 0xc4, 0x96, 0xe3, 0x90, 0xd0, + 0xe7, 0x19, 0x2b, 0x60, 0x18, 0x17, 0x9a, 0xad, 0x67, 0x75, 0x0d, 0x84, 0x0f, 0xf3, 0x7f, 0x7c, + 0x7b, 0xe5, 0x47, 0x68, 0xd3, 0xf4, 0x5d, 0x18, 0xc3, 0x28, 0xb9, 0x4d, 0x07, 0x87, 0x04, 0x6e, + 0x17, 0xa2, 0x9b, 0xab, 0x3b, 0x08, 0x2d, 0x6c, 0x12, 0xff, 0x91, 0xa2, 0x8c, 0x1d, 0x25, 0x9b, + 0x60, 0xee, 0xb0, 0xcc, 0x71, 0xca, 0x02, 0xf1, 0x86, 0x94, 0x9f, 0x05, 0xb4, 0x7e, 0x04, 0xb6, + 0x0b, 0x81, 0x11, 0x0e, 0x19, 0x70, 0xfc, 0x05, 0x5a, 0x8d, 0xcd, 0x9f, 0x8c, 0x72, 0x6d, 0x4f, + 0x5a, 0xf2, 0xd0, 0x20, 0x7f, 0x19, 0x07, 0xf7, 0xe2, 0x36, 0x2f, 0xfe, 0xde, 0x16, 0xf4, 0x04, + 0x81, 0x3f, 0x41, 0x15, 0x1f, 0xce, 0xb9, 0x35, 0x5d, 0x50, 0xb3, 0x4e, 0x6d, 0x76, 0x9a, 0x0d, + 0x12, 0xc7, 0xb9, 0x2b, 0xd6, 0x47, 0x36, 0x3b, 0xc5, 0x5b, 0xe8, 0x9e, 0x4d, 0x69, 0x5a, 0x55, + 0xce, 0x2c, 0x4e, 0x69, 0x9c, 0xda, 0xfd, 0x5d, 0x40, 0xf7, 0xdf, 0xb0, 0x0c, 0xfe, 0x14, 0x49, + 0xbd, 0xbe, 0xa6, 0xb7, 0x06, 0x9d, 0xde, 0xb1, 0x35, 0xf8, 0xa1, 0xaf, 0x59, 0xe6, 0xb1, 0xd1, + 0xd7, 0x0e, 0x3b, 0xdf, 0x76, 0xb4, 0xb6, 0x58, 0x92, 0x2a, 0xb3, 0xb9, 0x2c, 0x76, 0x21, 0xea, + 0x51, 0xd3, 0x67, 0x14, 0x1c, 0xef, 0xc4, 0x03, 0x17, 0x7f, 0x83, 0x76, 0xae, 0xa1, 0x5a, 0xed, + 0x76, 0x27, 0x89, 0x7a, 0xba, 0x65, 0xf6, 0xdb, 0xad, 0x81, 0x26, 0x0a, 0xd2, 0xc3, 0xd9, 0x5c, + 0x7e, 0xb7, 0x0b, 0x51, 0xcb, 0x75, 0xbd, 0xf8, 0xc6, 0x5e, 0x60, 0x52, 0xd7, 0xe6, 0x80, 0x77, + 0xd1, 0xe6, 0x35, 0xbc, 0xae, 0x7d, 0xd7, 0x7b, 0xd6, 0x7a, 0x22, 0xae, 0x48, 0x1b, 0xb3, 0xb9, + 0x8c, 0xe2, 0xe7, 0x01, 0x13, 0x32, 0xb5, 0xc7, 0xd2, 0xea, 0x4f, 0xbf, 0xd4, 0x4a, 0xbb, 0xff, + 0x09, 0xe8, 0xc1, 0xd3, 0x10, 0x42, 0xd0, 0x81, 0x85, 0x63, 0x9e, 0x70, 0xff, 0x1a, 0x3d, 0x7e, + 0x6a, 0x6a, 0x66, 0x0c, 0x36, 0xcc, 0x27, 0x83, 0x9b, 0xe8, 0x4b, 0xb3, 0xb9, 0xbc, 0x59, 0xc0, + 0x15, 0x45, 0x7c, 0x86, 0xb6, 0x96, 0xe1, 0x86, 0x79, 0x78, 0xa8, 0x19, 0x86, 0x28, 0x48, 0x9b, + 0xb3, 0xb9, 0x8c, 0x0b, 0x50, 0x23, 0x74, 0x9c, 0xf8, 0xa9, 0xec, 0xa3, 0xea, 0x32, 0x4c, 0xfb, + 0xbe, 0x63, 0x0c, 0x0c, 0x71, 0x45, 0x7a, 0x6f, 0x36, 0x97, 0xdf, 0x29, 0xa0, 0xb4, 0x73, 0x8f, + 0x71, 0x76, 0xf3, 0x5d, 0x89, 0x66, 0xad, 0x2d, 0x96, 0x97, 0xee, 0x4a, 0xb4, 0x83, 0x9b, 0x6a, + 0x3f, 0xe8, 0xbe, 0xbc, 0xa8, 0x09, 0xaf, 0x2e, 0x6a, 0xc2, 0x3f, 0x17, 0x35, 0xe1, 0xc5, 0x65, + 0xad, 0xf4, 0xea, 0xb2, 0x56, 0xfa, 0xeb, 0xb2, 0x56, 0xfa, 0xb1, 0x39, 0xf2, 0xf8, 0x69, 0x38, + 0x54, 0x1c, 0x32, 0x51, 0xb3, 0x4d, 0x77, 0x0c, 0xfc, 0x39, 0x09, 0xce, 0xd4, 0x7c, 0xc5, 0x9f, + 0x2f, 0x96, 0x7c, 0xbc, 0x4b, 0xd8, 0xf0, 0x4e, 0xe2, 0xb9, 0xfd, 0xff, 0x03, 0x00, 0x00, 0xff, + 0xff, 0x41, 0xe1, 0x03, 0x2c, 0x04, 0x06, 0x00, 0x00, } func (m *ExocoreValidator) Marshal() (dAtA []byte, err error) { @@ -796,7 +795,7 @@ func (m *ConsensusAddresses) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } -func (m *RecordKeys) Marshal() (dAtA []byte, err error) { +func (m *UndelegationRecordKeys) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalToSizedBuffer(dAtA[:size]) @@ -806,12 +805,12 @@ func (m *RecordKeys) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *RecordKeys) MarshalTo(dAtA []byte) (int, error) { +func (m *UndelegationRecordKeys) MarshalTo(dAtA []byte) (int, error) { size := m.Size() return m.MarshalToSizedBuffer(dAtA[:size]) } -func (m *RecordKeys) MarshalToSizedBuffer(dAtA []byte) (int, error) { +func (m *UndelegationRecordKeys) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) _ = i var l int @@ -1004,7 +1003,7 @@ func (m *ConsensusAddresses) Size() (n int) { return n } -func (m *RecordKeys) Size() (n int) { +func (m *UndelegationRecordKeys) Size() (n int) { if m == nil { return 0 } @@ -1582,7 +1581,7 @@ func (m *ConsensusAddresses) Unmarshal(dAtA []byte) error { } return nil } -func (m *RecordKeys) Unmarshal(dAtA []byte) error { +func (m *UndelegationRecordKeys) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -1605,10 +1604,10 @@ func (m *RecordKeys) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: RecordKeys: wiretype end group for non-group") + return fmt.Errorf("proto: UndelegationRecordKeys: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: RecordKeys: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: UndelegationRecordKeys: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: From 061ec1365075f6ec5064d242c60826f96a1a8c8b Mon Sep 17 00:00:00 2001 From: MaxMustermann2 <82761650+MaxMustermann2@users.noreply.github.com> Date: Mon, 26 Feb 2024 19:55:05 +0000 Subject: [PATCH 12/14] doc(dogfood): update comments in EndBlock ...and refactor getters / setters to `pending.go`. --- x/dogfood/keeper/abci.go | 136 ++---------------------------------- x/dogfood/keeper/pending.go | 132 ++++++++++++++++++++++++++++++++++ 2 files changed, 138 insertions(+), 130 deletions(-) create mode 100644 x/dogfood/keeper/pending.go diff --git a/x/dogfood/keeper/abci.go b/x/dogfood/keeper/abci.go index 4b6fcb36c..01750e969 100644 --- a/x/dogfood/keeper/abci.go +++ b/x/dogfood/keeper/abci.go @@ -7,19 +7,21 @@ import ( ) func (k Keeper) EndBlock(ctx sdk.Context) []abci.ValidatorUpdate { - // start with undelegations + // start with clearing the hold on the undelegations. undelegations := k.GetPendingUndelegations(ctx) for _, undelegation := range undelegations.GetList() { k.delegationKeeper.DecrementUndelegationHoldCount(ctx, undelegation) } k.ClearPendingUndelegations(ctx) - // then opt outs (consensus addresses should be done after opt out) + // then, let the operator module know that the opt out has finished. optOuts := k.GetPendingOptOuts(ctx) for _, addr := range optOuts.GetList() { k.operatorKeeper.CompleteOperatorOptOutFromChainId(ctx, addr, ctx.ChainID()) } k.ClearPendingOptOuts(ctx) - // then consensus addresses + // for slashing, the operator module is required to store a mapping of chain id + cons addr + // to operator address. this information can now be pruned, since the opt out is considered + // complete. consensusAddrs := k.GetPendingConsensusAddrs(ctx) for _, consensusAddr := range consensusAddrs.GetList() { k.operatorKeeper.DeleteOperatorAddressForChainIdAndConsAddr( @@ -27,7 +29,7 @@ func (k Keeper) EndBlock(ctx sdk.Context) []abci.ValidatorUpdate { ) } k.ClearPendingConsensusAddrs(ctx) - // finally, operations + // finally, perform the actual operations of vote power changes. operations := k.GetPendingOperations(ctx) res := make([]abci.ValidatorUpdate, 0, len(operations.GetList())) for _, operation := range operations.GetList() { @@ -54,129 +56,3 @@ func (k Keeper) EndBlock(ctx sdk.Context) []abci.ValidatorUpdate { } return res } - -// SetPendingOperations sets the pending operations to be applied at the end of the block. -func (k Keeper) SetPendingOperations(ctx sdk.Context, operations types.Operations) { - store := ctx.KVStore(k.storeKey) - bz, err := operations.Marshal() - if err != nil { - panic(err) - } - store.Set(types.PendingOperationsKey(), bz) -} - -// GetPendingOperations returns the pending operations to be applied at the end of the block. -func (k Keeper) GetPendingOperations(ctx sdk.Context) types.Operations { - store := ctx.KVStore(k.storeKey) - bz := store.Get(types.PendingOperationsKey()) - if bz == nil { - return types.Operations{} - } - var operations types.Operations - if err := operations.Unmarshal(bz); err != nil { - panic(err) - } - return operations -} - -// ClearPendingOperations clears the pending operations to be applied at the end of the block. -func (k Keeper) ClearPendingOperations(ctx sdk.Context) { - store := ctx.KVStore(k.storeKey) - store.Delete(types.PendingOperationsKey()) -} - -// SetPendingOptOuts sets the pending opt-outs to be applied at the end of the block. -func (k Keeper) SetPendingOptOuts(ctx sdk.Context, addrs types.AccountAddresses) { - store := ctx.KVStore(k.storeKey) - bz, err := addrs.Marshal() - if err != nil { - panic(err) - } - store.Set(types.PendingOptOutsKey(), bz) -} - -// GetPendingOptOuts returns the pending opt-outs to be applied at the end of the block. -func (k Keeper) GetPendingOptOuts(ctx sdk.Context) types.AccountAddresses { - store := ctx.KVStore(k.storeKey) - bz := store.Get(types.PendingOptOutsKey()) - if bz == nil { - return types.AccountAddresses{} - } - var addrs types.AccountAddresses - if err := addrs.Unmarshal(bz); err != nil { - panic(err) - } - return addrs -} - -// ClearPendingOptOuts clears the pending opt-outs to be applied at the end of the block. -func (k Keeper) ClearPendingOptOuts(ctx sdk.Context) { - store := ctx.KVStore(k.storeKey) - store.Delete(types.PendingOptOutsKey()) -} - -// SetPendingConsensusAddrs sets the pending consensus addresses to be pruned at the end of the -// block. -func (k Keeper) SetPendingConsensusAddrs(ctx sdk.Context, addrs types.ConsensusAddresses) { - store := ctx.KVStore(k.storeKey) - bz, err := addrs.Marshal() - if err != nil { - panic(err) - } - store.Set(types.PendingConsensusAddrsKey(), bz) -} - -// GetPendingConsensusAddrs returns the pending consensus addresses to be pruned at the end of -// the block. -func (k Keeper) GetPendingConsensusAddrs(ctx sdk.Context) types.ConsensusAddresses { - store := ctx.KVStore(k.storeKey) - bz := store.Get(types.PendingConsensusAddrsKey()) - if bz == nil { - return types.ConsensusAddresses{} - } - var addrs types.ConsensusAddresses - if err := addrs.Unmarshal(bz); err != nil { - panic(err) - } - return addrs -} - -// ClearPendingConsensusAddrs clears the pending consensus addresses to be pruned at the end of -// the block. -func (k Keeper) ClearPendingConsensusAddrs(ctx sdk.Context) { - store := ctx.KVStore(k.storeKey) - store.Delete(types.PendingConsensusAddrsKey()) -} - -// SetPendingUndelegations sets the pending undelegations to be released at the end of the -// block. -func (k Keeper) SetPendingUndelegations(ctx sdk.Context, undelegations types.UndelegationRecordKeys) { - store := ctx.KVStore(k.storeKey) - bz, err := undelegations.Marshal() - if err != nil { - panic(err) - } - store.Set(types.PendingUndelegationsKey(), bz) -} - -// GetPendingUndelegations returns the pending undelegations to be released at the end of the -// block. -func (k Keeper) GetPendingUndelegations(ctx sdk.Context) types.UndelegationRecordKeys { - store := ctx.KVStore(k.storeKey) - bz := store.Get(types.PendingUndelegationsKey()) - if bz == nil { - return types.UndelegationRecordKeys{} - } - var undelegations types.UndelegationRecordKeys - if err := undelegations.Unmarshal(bz); err != nil { - panic(err) - } - return undelegations -} - -// ClearPendingUndelegations clears the pending undelegations to be released at the end of the -// block. -func (k Keeper) ClearPendingUndelegations(ctx sdk.Context) { - store := ctx.KVStore(k.storeKey) - store.Delete(types.PendingUndelegationsKey()) -} diff --git a/x/dogfood/keeper/pending.go b/x/dogfood/keeper/pending.go new file mode 100644 index 000000000..7d68779f8 --- /dev/null +++ b/x/dogfood/keeper/pending.go @@ -0,0 +1,132 @@ +package keeper + +import ( + "github.com/ExocoreNetwork/exocore/x/dogfood/types" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// SetPendingOperations sets the pending operations to be applied at the end of the block. +func (k Keeper) SetPendingOperations(ctx sdk.Context, operations types.Operations) { + store := ctx.KVStore(k.storeKey) + bz, err := operations.Marshal() + if err != nil { + panic(err) + } + store.Set(types.PendingOperationsKey(), bz) +} + +// GetPendingOperations returns the pending operations to be applied at the end of the block. +func (k Keeper) GetPendingOperations(ctx sdk.Context) types.Operations { + store := ctx.KVStore(k.storeKey) + bz := store.Get(types.PendingOperationsKey()) + if bz == nil { + return types.Operations{} + } + var operations types.Operations + if err := operations.Unmarshal(bz); err != nil { + panic(err) + } + return operations +} + +// ClearPendingOperations clears the pending operations to be applied at the end of the block. +func (k Keeper) ClearPendingOperations(ctx sdk.Context) { + store := ctx.KVStore(k.storeKey) + store.Delete(types.PendingOperationsKey()) +} + +// SetPendingOptOuts sets the pending opt-outs to be applied at the end of the block. +func (k Keeper) SetPendingOptOuts(ctx sdk.Context, addrs types.AccountAddresses) { + store := ctx.KVStore(k.storeKey) + bz, err := addrs.Marshal() + if err != nil { + panic(err) + } + store.Set(types.PendingOptOutsKey(), bz) +} + +// GetPendingOptOuts returns the pending opt-outs to be applied at the end of the block. +func (k Keeper) GetPendingOptOuts(ctx sdk.Context) types.AccountAddresses { + store := ctx.KVStore(k.storeKey) + bz := store.Get(types.PendingOptOutsKey()) + if bz == nil { + return types.AccountAddresses{} + } + var addrs types.AccountAddresses + if err := addrs.Unmarshal(bz); err != nil { + panic(err) + } + return addrs +} + +// ClearPendingOptOuts clears the pending opt-outs to be applied at the end of the block. +func (k Keeper) ClearPendingOptOuts(ctx sdk.Context) { + store := ctx.KVStore(k.storeKey) + store.Delete(types.PendingOptOutsKey()) +} + +// SetPendingConsensusAddrs sets the pending consensus addresses to be pruned at the end of the +// block. +func (k Keeper) SetPendingConsensusAddrs(ctx sdk.Context, addrs types.ConsensusAddresses) { + store := ctx.KVStore(k.storeKey) + bz, err := addrs.Marshal() + if err != nil { + panic(err) + } + store.Set(types.PendingConsensusAddrsKey(), bz) +} + +// GetPendingConsensusAddrs returns the pending consensus addresses to be pruned at the end of +// the block. +func (k Keeper) GetPendingConsensusAddrs(ctx sdk.Context) types.ConsensusAddresses { + store := ctx.KVStore(k.storeKey) + bz := store.Get(types.PendingConsensusAddrsKey()) + if bz == nil { + return types.ConsensusAddresses{} + } + var addrs types.ConsensusAddresses + if err := addrs.Unmarshal(bz); err != nil { + panic(err) + } + return addrs +} + +// ClearPendingConsensusAddrs clears the pending consensus addresses to be pruned at the end of +// the block. +func (k Keeper) ClearPendingConsensusAddrs(ctx sdk.Context) { + store := ctx.KVStore(k.storeKey) + store.Delete(types.PendingConsensusAddrsKey()) +} + +// SetPendingUndelegations sets the pending undelegations to be released at the end of the +// block. +func (k Keeper) SetPendingUndelegations(ctx sdk.Context, undelegations types.UndelegationRecordKeys) { + store := ctx.KVStore(k.storeKey) + bz, err := undelegations.Marshal() + if err != nil { + panic(err) + } + store.Set(types.PendingUndelegationsKey(), bz) +} + +// GetPendingUndelegations returns the pending undelegations to be released at the end of the +// block. +func (k Keeper) GetPendingUndelegations(ctx sdk.Context) types.UndelegationRecordKeys { + store := ctx.KVStore(k.storeKey) + bz := store.Get(types.PendingUndelegationsKey()) + if bz == nil { + return types.UndelegationRecordKeys{} + } + var undelegations types.UndelegationRecordKeys + if err := undelegations.Unmarshal(bz); err != nil { + panic(err) + } + return undelegations +} + +// ClearPendingUndelegations clears the pending undelegations to be released at the end of the +// block. +func (k Keeper) ClearPendingUndelegations(ctx sdk.Context) { + store := ctx.KVStore(k.storeKey) + store.Delete(types.PendingUndelegationsKey()) +} From ba3b5d89763e179c6976ad525a8c19bc812ac6d1 Mon Sep 17 00:00:00 2001 From: MaxMustermann2 <82761650+MaxMustermann2@users.noreply.github.com> Date: Mon, 26 Feb 2024 20:16:08 +0000 Subject: [PATCH 13/14] fix(dogfood): clear validator set only if unused --- x/dogfood/keeper/validators.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/x/dogfood/keeper/validators.go b/x/dogfood/keeper/validators.go index 15c5d229b..d53a88d58 100644 --- a/x/dogfood/keeper/validators.go +++ b/x/dogfood/keeper/validators.go @@ -223,9 +223,9 @@ func (k Keeper) TrackHistoricalInfo(ctx sdk.Context) { break } } - // contract: TrackHistoricalInfo must be called after storing the validator set - // for the current block. - currentID, _ := k.GetValidatorSetID(ctx, ctx.BlockHeight()) + currentID, _ := k.GetValidatorSetID( + ctx, ctx.BlockHeight()-int64(numHistoricalEntries)+1, + ) for i := lastDeletedID; i < currentID; i++ { k.DeleteValidatorSet(ctx, i) } From 8e9dec2d034cbac50e4434b431dfcde3a5fe3c8d Mon Sep 17 00:00:00 2001 From: Max <82761650+MaxMustermann2@users.noreply.github.com> Date: Wed, 28 Feb 2024 12:32:40 +0530 Subject: [PATCH 14/14] feat(dogfood): implement sdk staking interface (#123) * feat(dogfood): implement sdk staking interface The interface required by IBC was already implemented in `validators.go`. This PR takes that a step further and implements the interfaces required to use the dogfood module as a drop-in replacement for the SDK's staking module. The interfaces implemented are those expected by the slashing, evidence and `genutil` modules. An explanation has been provided above each function to explain why and where it is called, and if its implementation is really necessary. There are still some TODOs left in this PR that depend on the overall slashing / operator design. This branch is merged into dogfood-part6, which forms the basis of #122. * fix(dogfood): store val set id + val set at genesis, the val set id starts with 1. each time the validator set changes, the val set id of the current block is retrieved. for the next block, the val set is is stored as this id + 1. * fix(dogfood): increment val set id correctly in the case of genesis, we should not use height + 1 as the key in the mapping; rather, it should be height. in all other cases, the mapping key is height + 1. the value is the val set id in all cases. --- x/dogfood/keeper/abci.go | 11 +- x/dogfood/keeper/genesis.go | 3 +- x/dogfood/keeper/impl_sdk.go | 171 ++++++++++++++++++++++++++++ x/dogfood/keeper/keeper.go | 3 + x/dogfood/keeper/validators.go | 35 +++++- x/dogfood/types/expected_keepers.go | 15 +++ x/dogfood/types/keys.go | 3 + 7 files changed, 233 insertions(+), 8 deletions(-) create mode 100644 x/dogfood/keeper/impl_sdk.go diff --git a/x/dogfood/keeper/abci.go b/x/dogfood/keeper/abci.go index 01750e969..30dd3950b 100644 --- a/x/dogfood/keeper/abci.go +++ b/x/dogfood/keeper/abci.go @@ -31,6 +31,13 @@ func (k Keeper) EndBlock(ctx sdk.Context) []abci.ValidatorUpdate { k.ClearPendingConsensusAddrs(ctx) // finally, perform the actual operations of vote power changes. operations := k.GetPendingOperations(ctx) + id, _ := k.GetValidatorSetID(ctx, ctx.BlockHeight()) + if len(operations.GetList()) == 0 { + // there is no validator set change, so we just increment the block height + // and retain the same val set id mapping. + k.SetValidatorSetID(ctx, ctx.BlockHeight()+1, id) + return []abci.ValidatorUpdate{} + } res := make([]abci.ValidatorUpdate, 0, len(operations.GetList())) for _, operation := range operations.GetList() { switch operation.OperationType { @@ -54,5 +61,7 @@ func (k Keeper) EndBlock(ctx sdk.Context) []abci.ValidatorUpdate { panic("unspecified operation type") } } - return res + // call via wrapper function so that validator info is stored. + // the id is incremented by 1 for the next block. + return k.ApplyValidatorChanges(ctx, res, id+1, false) } diff --git a/x/dogfood/keeper/genesis.go b/x/dogfood/keeper/genesis.go index 16f51892b..e0544924d 100644 --- a/x/dogfood/keeper/genesis.go +++ b/x/dogfood/keeper/genesis.go @@ -20,9 +20,8 @@ func (k Keeper) InitGenesis( // the panic is suitable here because it is being done at genesis, when the node // is not running. it means that the genesis file is malformed. panic("epoch info not found") - } - return k.ApplyValidatorChanges(ctx, genState.ValSet) + return k.ApplyValidatorChanges(ctx, genState.ValSet, types.InitialValidatorSetID, true) } // ExportGenesis returns the module's exported genesis diff --git a/x/dogfood/keeper/impl_sdk.go b/x/dogfood/keeper/impl_sdk.go new file mode 100644 index 000000000..547f526e7 --- /dev/null +++ b/x/dogfood/keeper/impl_sdk.go @@ -0,0 +1,171 @@ +package keeper + +import ( + "cosmossdk.io/math" + abci "github.com/cometbft/cometbft/abci/types" + sdk "github.com/cosmos/cosmos-sdk/types" + evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" + genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" + slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" +) + +// interface guards +var _ slashingtypes.StakingKeeper = Keeper{} +var _ evidencetypes.StakingKeeper = Keeper{} +var _ genutiltypes.StakingKeeper = Keeper{} +var _ clienttypes.StakingKeeper = Keeper{} // implemented in `validators.go` + +// GetParams is an implementation of the staking interface expected by the SDK's evidence +// module. The module does not use it, but it is part of the interface. +func (k Keeper) GetParams(ctx sdk.Context) stakingtypes.Params { + return stakingtypes.Params{} +} + +// IterateValidators is an implementation of the staking interface expected by the SDK's +// slashing module. The slashing module uses it for two purposes: once at genesis to +// store a mapping of pub key to cons address (which is done by our operator module), +// and then during the invariants check to ensure that the total delegated amount +// matches that of each validator. Ideally, this invariant should be implemented +// by the delegation and/or deposit module(s) instead. +func (k Keeper) IterateValidators(sdk.Context, + func(index int64, validator stakingtypes.ValidatorI) (stop bool)) { + // no op +} + +// Validator is an implementation of the staking interface expected by the SDK's +// slashing module. The slashing module uses it to obtain a validator's information at +// its addition to the list of validators, and then to unjail a validator. The former +// is used to create the pub key to cons address mapping, which we do in the operator module. +// The latter should also be implemented in the operator module, or maybe the slashing module +// depending upon the finalized design. We don't need to implement this function here because +// we are not calling the AfterValidatorCreated hook in our module, so this will never be +// reached. +func (k Keeper) Validator(ctx sdk.Context, addr sdk.ValAddress) stakingtypes.ValidatorI { + panic("unimplemented on this keeper") +} + +// ValidatorByConsAddr is an implementation of the staking interface expected by the SDK's +// slashing and evidence modules. +// The slashing module calls this function when it observes downtime. The only requirement on +// the returned value is that it isn't nil, and the jailed status is accurately set (to prevent +// re-jailing of the same operator). +// The evidence module calls this function when it handles equivocation evidence. The returned +// value must not be nil and must not have an UNBONDED validator status (the default is +// unspecified), or evidence will reject it. +func (k Keeper) ValidatorByConsAddr( + ctx sdk.Context, + addr sdk.ConsAddress, +) stakingtypes.ValidatorI { + found, accAddr := k.operatorKeeper.GetOperatorAddressForChainIdAndConsAddr( + ctx, ctx.ChainID(), addr, + ) + if !found { + // replicate the behavior of the SDK's staking module + return nil + } + return stakingtypes.Validator{ + Jailed: k.operatorKeeper.IsOperatorJailedForChainId(ctx, accAddr, ctx.ChainID()), + } +} + +// Slash is an implementation of the staking interface expected by the SDK's slashing module. +// It forwards the call to SlashWithInfractionReason with Infraction_INFRACTION_UNSPECIFIED. +// It is not called within the slashing module, but is part of the interface. +func (k Keeper) Slash( + ctx sdk.Context, addr sdk.ConsAddress, + infractionHeight, power int64, + slashFactor sdk.Dec, +) math.Int { + return k.SlashWithInfractionReason( + ctx, addr, infractionHeight, power, + slashFactor, stakingtypes.Infraction_INFRACTION_UNSPECIFIED, + ) +} + +// SlashWithInfractionReason is an implementation of the staking interface expected by the +// SDK's slashing module. It is called when the slashing module observes an infraction +// of either downtime or equivocation (which is via the evidence module). +func (k Keeper) SlashWithInfractionReason( + ctx sdk.Context, addr sdk.ConsAddress, infractionHeight, power int64, + slashFactor sdk.Dec, infraction stakingtypes.Infraction, +) math.Int { + found, accAddress := k.operatorKeeper.GetOperatorAddressForChainIdAndConsAddr( + ctx, ctx.ChainID(), addr, + ) + if !found { + // TODO(mm): already slashed and removed from the set? + return math.NewInt(0) + } + // TODO(mm): add list of assets to be slashed (and not just all of them). + // based on yet to be finalized slashing design. + return k.slashingKeeper.SlashWithInfractionReason( + ctx, accAddress, infractionHeight, + power, slashFactor, infraction, + ) +} + +// Jail is an implementation of the staking interface expected by the SDK's slashing module. +// It delegates the call to the operator module. Alternatively, this may be handled +// by the slashing module depending upon the design decisions. +func (k Keeper) Jail(ctx sdk.Context, addr sdk.ConsAddress) { + k.operatorKeeper.Jail(ctx, addr, ctx.ChainID()) + // TODO(mm) + // once the operator module jails someone, a hook should be triggered + // and the validator removed from the set. same for unjailing. +} + +// Unjail is an implementation of the staking interface expected by the SDK's slashing module. +// The function is called by the slashing module only when it receives a request from the +// operator to do so. TODO(mm): We need to use the SDK's slashing module to allow for downtime +// slashing but somehow we need to prevent its Unjail function from being called by anyone. +func (k Keeper) Unjail(sdk.Context, sdk.ConsAddress) { + panic("unimplemented on this keeper") +} + +// Delegation is an implementation of the staking interface expected by the SDK's slashing +// module. The slashing module uses it to obtain the delegation information of a validator +// before unjailing it. If the slashing module's unjail function is never called, this +// function will never be called either. +func (k Keeper) Delegation( + sdk.Context, sdk.AccAddress, sdk.ValAddress, +) stakingtypes.DelegationI { + panic("unimplemented on this keeper") +} + +// MaxValidators is an implementation of the staking interface expected by the SDK's slashing +// module. It is not called within the slashing module, but is part of the interface. +// It returns the maximum number of validators allowed in the network. +func (k Keeper) MaxValidators(ctx sdk.Context) uint32 { + return k.GetMaxValidators(ctx) +} + +// GetAllValidators is an implementation of the staking interface expected by the SDK's +// slashing module. It is not called within the slashing module, but is part of the interface. +func (k Keeper) GetAllValidators(ctx sdk.Context) (validators []stakingtypes.Validator) { + return []stakingtypes.Validator{} +} + +// IsValidatorJailed is an implementation of the staking interface expected by the SDK's +// slashing module. It is called by the slashing module to record validator signatures +// for downtime tracking. We delegate the call to the operator keeper. +func (k Keeper) IsValidatorJailed(ctx sdk.Context, addr sdk.ConsAddress) bool { + found, accAddr := k.operatorKeeper.GetOperatorAddressForChainIdAndConsAddr( + ctx, ctx.ChainID(), addr, + ) + if !found { + // replicate the behavior of the SDK's staking module + return false + } + return k.operatorKeeper.IsOperatorJailedForChainId(ctx, accAddr, ctx.ChainID()) +} + +// ApplyAndReturnValidatorSetUpdates is an implementation of the staking interface expected +// by the SDK's genutil module. It is used in the gentx command, which we do not need to +// support. So this function does nothing. +func (k Keeper) ApplyAndReturnValidatorSetUpdates( + sdk.Context, +) (updates []abci.ValidatorUpdate, err error) { + return +} diff --git a/x/dogfood/keeper/keeper.go b/x/dogfood/keeper/keeper.go index 103d60c97..e85f961d0 100644 --- a/x/dogfood/keeper/keeper.go +++ b/x/dogfood/keeper/keeper.go @@ -26,6 +26,7 @@ type ( operatorKeeper types.OperatorKeeper delegationKeeper types.DelegationKeeper restakingKeeper types.RestakingKeeper + slashingKeeper types.SlashingKeeper } ) @@ -38,6 +39,7 @@ func NewKeeper( operatorKeeper types.OperatorKeeper, delegationKeeper types.DelegationKeeper, restakingKeeper types.RestakingKeeper, + slashingKeeper types.SlashingKeeper, ) *Keeper { // set KeyTable if it has not already been set if !ps.HasKeyTable() { @@ -52,6 +54,7 @@ func NewKeeper( operatorKeeper: operatorKeeper, delegationKeeper: delegationKeeper, restakingKeeper: restakingKeeper, + slashingKeeper: slashingKeeper, } } diff --git a/x/dogfood/keeper/validators.go b/x/dogfood/keeper/validators.go index d53a88d58..c41b7ba22 100644 --- a/x/dogfood/keeper/validators.go +++ b/x/dogfood/keeper/validators.go @@ -31,10 +31,10 @@ func (k Keeper) UnbondingTime(ctx sdk.Context) time.Duration { // ApplyValidatorChanges returns the validator set as is. However, it also // stores the validators that are added or those that are removed, and updates // the power for the existing validators. It also allows any hooks registered -// on the keeper to be executed. +// on the keeper to be executed. Lastly, it stores the validator set against the +// provided validator set id. func (k Keeper) ApplyValidatorChanges( - ctx sdk.Context, - changes []abci.ValidatorUpdate, + ctx sdk.Context, changes []abci.ValidatorUpdate, valSetID uint64, genesis bool, ) []abci.ValidatorUpdate { ret := []abci.ValidatorUpdate{} for _, change := range changes { @@ -42,7 +42,7 @@ func (k Keeper) ApplyValidatorChanges( pubkey, err := cryptocodec.FromTmProtoPublicKey(change.GetPubKey()) if err != nil { // An error here would indicate that the validator updates - // received from other modules are invalid. + // received from other modules (or genesis) are invalid. panic(err) } addr := pubkey.Address() @@ -79,9 +79,34 @@ func (k Keeper) ApplyValidatorChanges( // to tendermint. continue } - ret = append(ret, change) } + + // store the validator set against the provided validator set id + lastVals := types.Validators{} + for _, v := range k.GetAllExocoreValidators(ctx) { + pubkey, err := v.ConsPubKey() + if err != nil { + panic(err) + } + val, err := stakingtypes.NewValidator(nil, pubkey, stakingtypes.Description{}) + if err != nil { + panic(err) + } + // Set validator to bonded status + val.Status = stakingtypes.Bonded + // Compute tokens from voting power + val.Tokens = sdk.TokensFromConsensusPower(v.Power, sdk.DefaultPowerReduction) + lastVals.List = append(lastVals.GetList(), val) + } + k.SetValidatorSet(ctx, valSetID, &lastVals) + if !genesis { + // the val set change is effective as of the next block, so height + 1. + k.SetValidatorSetID(ctx, ctx.BlockHeight()+1, valSetID) + } else { + // the val set change is effective immediately. + k.SetValidatorSetID(ctx, ctx.BlockHeight(), valSetID) + } return ret } diff --git a/x/dogfood/types/expected_keepers.go b/x/dogfood/types/expected_keepers.go index 9dfd255ea..06ca74df4 100644 --- a/x/dogfood/types/expected_keepers.go +++ b/x/dogfood/types/expected_keepers.go @@ -1,8 +1,10 @@ package types import ( + "cosmossdk.io/math" tmprotocrypto "github.com/cometbft/cometbft/proto/tendermint/crypto" sdk "github.com/cosmos/cosmos-sdk/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" epochsTypes "github.com/evmos/evmos/v14/x/epochs/types" ) @@ -48,6 +50,11 @@ type OperatorKeeper interface { ) bool CompleteOperatorOptOutFromChainId(sdk.Context, sdk.AccAddress, string) DeleteOperatorAddressForChainIdAndConsAddr(sdk.Context, string, sdk.ConsAddress) + GetOperatorAddressForChainIdAndConsAddr( + sdk.Context, string, sdk.ConsAddress, + ) (bool, sdk.AccAddress) + IsOperatorJailedForChainId(sdk.Context, sdk.AccAddress, string) bool + Jail(sdk.Context, sdk.ConsAddress, string) } // DelegationKeeper represents the expected keeper interface for the delegation module. @@ -66,3 +73,11 @@ type EpochsHooks interface { type RestakingKeeper interface { GetOperatorAssetValue(sdk.Context, sdk.AccAddress) (int64, error) } + +// SlashingKeeper represents the expected keeper interface for the (exo-)slashing module. +type SlashingKeeper interface { + SlashWithInfractionReason( + sdk.Context, sdk.AccAddress, int64, + int64, sdk.Dec, stakingtypes.Infraction, + ) math.Int +} diff --git a/x/dogfood/types/keys.go b/x/dogfood/types/keys.go index 15e3fda9f..6397e1796 100644 --- a/x/dogfood/types/keys.go +++ b/x/dogfood/types/keys.go @@ -12,6 +12,9 @@ const ( StoreKey = ModuleName ) +// InitialValidatorSetID is the initial validator set id. +const InitialValidatorSetID = uint64(1) + const ( // ExocoreValidatorBytePrefix is the prefix for the validator store. ExocoreValidatorBytePrefix byte = iota + 1