From 5d1a7d9fe99aac5e9e5e54cb4fd40f464b0d6daf Mon Sep 17 00:00:00 2001 From: Callum Waters Date: Thu, 30 Nov 2023 17:30:10 +0100 Subject: [PATCH 1/9] introduce a TryUpgrade message as a crank message for upgrading the network --- proto/celestia/upgrade/v1/tx.proto | 12 + x/upgrade/keeper.go | 27 ++- x/upgrade/module.go | 7 +- x/upgrade/tally_test.go | 6 +- x/upgrade/types/msgs.go | 24 +- x/upgrade/types/tx.pb.go | 373 +++++++++++++++++++++++++++-- x/upgrade/types/tx.pb.gw.go | 83 +++++++ 7 files changed, 492 insertions(+), 40 deletions(-) diff --git a/proto/celestia/upgrade/v1/tx.proto b/proto/celestia/upgrade/v1/tx.proto index 9d8666a7fd..df5c6273ef 100644 --- a/proto/celestia/upgrade/v1/tx.proto +++ b/proto/celestia/upgrade/v1/tx.proto @@ -11,6 +11,12 @@ service Msg { rpc SignalVersion(MsgSignalVersion) returns (MsgSignalVersionResponse) { option (google.api.http).post = "/upgrade/v1/signal"; } + + // TryUpgrade tallies all the votes and if a quorum is reached, it will + // trigger an upgrade for the following height + rpc TryUpgrade(MsgTryUpgrade) returns (MsgTryUpgradeResponse) { + option (google.api.http).post = "/upgrade/v1/upgrade"; + } } // MsgSignalVersion signals for an upgrade @@ -22,3 +28,9 @@ message MsgSignalVersion { // MsgSignalVersionResponse describes the response returned after the submission // of a SignalVersion message MsgSignalVersionResponse {} + +// MsgTryUpgrade tries to upgrade the chain +message MsgTryUpgrade { string signer = 1; } + +// MsgTryUpgradeResponse describes the response returned after the submission +message MsgTryUpgradeResponse {} diff --git a/x/upgrade/keeper.go b/x/upgrade/keeper.go index 483f68ee66..1834014012 100644 --- a/x/upgrade/keeper.go +++ b/x/upgrade/keeper.go @@ -13,7 +13,7 @@ import ( // Keeper implements the MsgServer and QueryServer interfaces var ( - _ types.MsgServer = Keeper{} + _ types.MsgServer = &Keeper{} _ types.QueryServer = Keeper{} defaultSignalTheshold = Fraction{Numerator: 5, Denominator: 6} @@ -93,6 +93,20 @@ func (k Keeper) SignalVersion(ctx context.Context, req *types.MsgSignalVersion) return &types.MsgSignalVersionResponse{}, nil } +// TryUpgrade is a method required by the MsgServer interface +// It tallies the voting power that has voted on each version. +// If one version has quorum, it is set as the quorum version +// which the application can use as signal to upgrade to that version. +func (k *Keeper) TryUpgrade(ctx context.Context, _ *types.MsgTryUpgrade) (*types.MsgTryUpgradeResponse, error) { + sdkCtx := sdk.UnwrapSDKContext(ctx) + threshold := k.GetVotingPowerThreshold(sdkCtx) + hasQuorum, version := k.TallyVotingPower(sdkCtx, threshold.Int64()) + if hasQuorum { + k.quorumVersion = version + } + return &types.MsgTryUpgradeResponse{}, nil +} + // VersionTally is a method required by the QueryServer interface func (k Keeper) VersionTally(ctx context.Context, req *types.QueryVersionTallyRequest) (*types.QueryVersionTallyResponse, error) { sdkCtx := sdk.UnwrapSDKContext(ctx) @@ -130,17 +144,6 @@ func (k Keeper) DeleteValidatorVersion(ctx sdk.Context, valAddress sdk.ValAddres store.Delete(valAddress) } -// EndBlock is called at the end of every block. It tallies the voting power that has -// voted on each version. If one version has quorum, it is set as the quorum version -// which the application can use as signal to upgrade to that version. -func (k *Keeper) EndBlock(ctx sdk.Context) { - threshold := k.GetVotingPowerThreshold(ctx) - hasQuorum, version := k.TallyVotingPower(ctx, threshold.Int64()) - if hasQuorum { - k.quorumVersion = version - } -} - // TallyVotingPower tallies the voting power for each version and returns true if // any version has reached the quorum in voting power func (k Keeper) TallyVotingPower(ctx sdk.Context, threshold int64) (bool, uint64) { diff --git a/x/upgrade/module.go b/x/upgrade/module.go index a69c54dd24..0b876a2d0e 100644 --- a/x/upgrade/module.go +++ b/x/upgrade/module.go @@ -96,7 +96,7 @@ func (am AppModule) LegacyQuerierHandler(_ *codec.LegacyAmino) sdk.Querier { // RegisterServices registers module services. func (am AppModule) RegisterServices(cfg module.Configurator) { - types.RegisterMsgServer(cfg.MsgServer(), am.keeper) + types.RegisterMsgServer(cfg.MsgServer(), &am.keeper) types.RegisterQueryServer(cfg.QueryServer(), am.keeper) } @@ -122,8 +122,3 @@ func (am AppModule) ExportGenesis(_ sdk.Context, cdc codec.JSONCodec) json.RawMe // ConsensusVersion implements AppModule/ConsensusVersion. func (AppModule) ConsensusVersion() uint64 { return consensusVersion } - -// EndBlock is used by the Endblocker -func (am AppModule) EndBlock(ctx sdk.Context, _ abci.RequestEndBlock) { - am.keeper.EndBlock(ctx) -} diff --git a/x/upgrade/tally_test.go b/x/upgrade/tally_test.go index 994e74043f..2608a2239f 100644 --- a/x/upgrade/tally_test.go +++ b/x/upgrade/tally_test.go @@ -96,7 +96,8 @@ func TestTallyingLogic(t *testing.T) { require.EqualValues(t, 100, res.ThresholdPower) require.EqualValues(t, 120, res.TotalVotingPower) - upgradeKeeper.EndBlock(ctx) + _, err = upgradeKeeper.TryUpgrade(goCtx, &types.MsgTryUpgrade{}) + require.NoError(t, err) shouldUpgrade, version := upgradeKeeper.ShouldUpgrade() require.False(t, shouldUpgrade) require.Equal(t, uint64(0), version) @@ -108,7 +109,8 @@ func TestTallyingLogic(t *testing.T) { }) require.NoError(t, err) - upgradeKeeper.EndBlock(ctx) + _, err = upgradeKeeper.TryUpgrade(goCtx, &types.MsgTryUpgrade{}) + require.NoError(t, err) shouldUpgrade, version = upgradeKeeper.ShouldUpgrade() require.True(t, shouldUpgrade) require.Equal(t, uint64(2), version) diff --git a/x/upgrade/types/msgs.go b/x/upgrade/types/msgs.go index d308ab9f74..7b4db72781 100644 --- a/x/upgrade/types/msgs.go +++ b/x/upgrade/types/msgs.go @@ -15,7 +15,10 @@ const ( QuerierRoute = ModuleName ) -var _ sdk.Msg = &MsgSignalVersion{} +var ( + _ sdk.Msg = &MsgSignalVersion{} + _ sdk.Msg = &MsgTryUpgrade{} +) func NewMsgSignalVersion(valAddress sdk.ValAddress, version uint64) *MsgSignalVersion { return &MsgSignalVersion{ @@ -36,3 +39,22 @@ func (msg *MsgSignalVersion) ValidateBasic() error { _, err := sdk.ValAddressFromBech32(msg.ValidatorAddress) return err } + +func NewMsgTryUpgrade(signer sdk.AccAddress) *MsgTryUpgrade { + return &MsgTryUpgrade{ + Signer: signer.String(), + } +} + +func (msg *MsgTryUpgrade) GetSigners() []sdk.AccAddress { + valAddr, err := sdk.ValAddressFromBech32(msg.Signer) + if err != nil { + panic(err) + } + return []sdk.AccAddress{sdk.AccAddress(valAddr)} +} + +func (msg *MsgTryUpgrade) ValidateBasic() error { + _, err := sdk.ValAddressFromBech32(msg.Signer) + return err +} diff --git a/x/upgrade/types/tx.pb.go b/x/upgrade/types/tx.pb.go index fd15cd30ee..3b2ac5b9c6 100644 --- a/x/upgrade/types/tx.pb.go +++ b/x/upgrade/types/tx.pb.go @@ -119,33 +119,121 @@ func (m *MsgSignalVersionResponse) XXX_DiscardUnknown() { var xxx_messageInfo_MsgSignalVersionResponse proto.InternalMessageInfo +// MsgTryUpgrade tries to upgrade the chain +type MsgTryUpgrade struct { + Signer string `protobuf:"bytes,1,opt,name=signer,proto3" json:"signer,omitempty"` +} + +func (m *MsgTryUpgrade) Reset() { *m = MsgTryUpgrade{} } +func (m *MsgTryUpgrade) String() string { return proto.CompactTextString(m) } +func (*MsgTryUpgrade) ProtoMessage() {} +func (*MsgTryUpgrade) Descriptor() ([]byte, []int) { + return fileDescriptor_ee2a0c754324bd13, []int{2} +} +func (m *MsgTryUpgrade) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgTryUpgrade) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgTryUpgrade.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 *MsgTryUpgrade) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgTryUpgrade.Merge(m, src) +} +func (m *MsgTryUpgrade) XXX_Size() int { + return m.Size() +} +func (m *MsgTryUpgrade) XXX_DiscardUnknown() { + xxx_messageInfo_MsgTryUpgrade.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgTryUpgrade proto.InternalMessageInfo + +func (m *MsgTryUpgrade) GetSigner() string { + if m != nil { + return m.Signer + } + return "" +} + +// MsgTryUpgradeResponse describes the response returned after the submission +type MsgTryUpgradeResponse struct { +} + +func (m *MsgTryUpgradeResponse) Reset() { *m = MsgTryUpgradeResponse{} } +func (m *MsgTryUpgradeResponse) String() string { return proto.CompactTextString(m) } +func (*MsgTryUpgradeResponse) ProtoMessage() {} +func (*MsgTryUpgradeResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_ee2a0c754324bd13, []int{3} +} +func (m *MsgTryUpgradeResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgTryUpgradeResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgTryUpgradeResponse.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 *MsgTryUpgradeResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgTryUpgradeResponse.Merge(m, src) +} +func (m *MsgTryUpgradeResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgTryUpgradeResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgTryUpgradeResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgTryUpgradeResponse proto.InternalMessageInfo + func init() { proto.RegisterType((*MsgSignalVersion)(nil), "celestia.upgrade.v1.MsgSignalVersion") proto.RegisterType((*MsgSignalVersionResponse)(nil), "celestia.upgrade.v1.MsgSignalVersionResponse") + proto.RegisterType((*MsgTryUpgrade)(nil), "celestia.upgrade.v1.MsgTryUpgrade") + proto.RegisterType((*MsgTryUpgradeResponse)(nil), "celestia.upgrade.v1.MsgTryUpgradeResponse") } func init() { proto.RegisterFile("celestia/upgrade/v1/tx.proto", fileDescriptor_ee2a0c754324bd13) } var fileDescriptor_ee2a0c754324bd13 = []byte{ - // 283 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x50, 0x4f, 0x4b, 0xc3, 0x30, - 0x14, 0x6f, 0xa6, 0x28, 0x06, 0x84, 0x59, 0x3d, 0x94, 0x32, 0xc2, 0x28, 0x08, 0x03, 0x59, 0xc2, - 0xdc, 0x27, 0xd0, 0x7b, 0x2f, 0x15, 0x04, 0xbd, 0x48, 0xb6, 0x86, 0x18, 0xa8, 0x49, 0xc8, 0xcb, - 0xca, 0x3c, 0xea, 0xcd, 0x9b, 0xe0, 0x97, 0xf2, 0x38, 0xf0, 0xe2, 0x51, 0x5a, 0x3f, 0x88, 0xb0, - 0xda, 0xa2, 0xc3, 0x83, 0xb7, 0xf7, 0xde, 0xef, 0x1f, 0xef, 0x87, 0x07, 0x73, 0x51, 0x08, 0xf0, - 0x8a, 0xb3, 0x85, 0x95, 0x8e, 0xe7, 0x82, 0x95, 0x13, 0xe6, 0x97, 0xd4, 0x3a, 0xe3, 0x4d, 0x78, - 0xd8, 0xa2, 0xf4, 0x1b, 0xa5, 0xe5, 0x24, 0x1e, 0x48, 0x63, 0x64, 0x21, 0x18, 0xb7, 0x8a, 0x71, - 0xad, 0x8d, 0xe7, 0x5e, 0x19, 0x0d, 0x8d, 0x24, 0xb9, 0xc2, 0xfd, 0x14, 0xe4, 0x85, 0x92, 0x9a, - 0x17, 0x97, 0xc2, 0x81, 0x32, 0x3a, 0x3c, 0xc1, 0x07, 0x25, 0x2f, 0x54, 0xce, 0xbd, 0x71, 0x37, - 0x3c, 0xcf, 0x9d, 0x00, 0x88, 0xd0, 0x10, 0x8d, 0xf6, 0xb2, 0x7e, 0x07, 0x9c, 0x35, 0xf7, 0x30, - 0xc2, 0xbb, 0x65, 0xa3, 0x8b, 0x7a, 0x43, 0x34, 0xda, 0xce, 0xda, 0x35, 0x89, 0x71, 0xb4, 0x69, - 0x9d, 0x09, 0xb0, 0x46, 0x83, 0x38, 0x7d, 0x42, 0x78, 0x2b, 0x05, 0x19, 0x3e, 0x20, 0xbc, 0xff, - 0x3b, 0xfc, 0x98, 0xfe, 0xf1, 0x04, 0xdd, 0x34, 0x8a, 0xc7, 0xff, 0xa2, 0xb5, 0x79, 0x49, 0xfc, - 0xf8, 0xf6, 0xf9, 0xd2, 0x3b, 0x4a, 0xc2, 0x9f, 0xbd, 0xc1, 0x9a, 0x7a, 0x9e, 0xbe, 0x56, 0x04, - 0xad, 0x2a, 0x82, 0x3e, 0x2a, 0x82, 0x9e, 0x6b, 0x12, 0xac, 0x6a, 0x12, 0xbc, 0xd7, 0x24, 0xb8, - 0x9e, 0x4a, 0xe5, 0x6f, 0x17, 0x33, 0x3a, 0x37, 0x77, 0xac, 0x8d, 0x33, 0x4e, 0x76, 0xf3, 0x98, - 0x5b, 0xcb, 0x96, 0x9d, 0xa5, 0xbf, 0xb7, 0x02, 0x66, 0x3b, 0xeb, 0x62, 0xa7, 0x5f, 0x01, 0x00, - 0x00, 0xff, 0xff, 0x7d, 0x45, 0x22, 0x3c, 0xab, 0x01, 0x00, 0x00, + // 343 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x52, 0xcd, 0x4a, 0x33, 0x31, + 0x14, 0xed, 0xf4, 0xfb, 0xa8, 0x18, 0x28, 0xd4, 0xd4, 0xea, 0x30, 0x96, 0xa1, 0x04, 0xc4, 0xa2, + 0x74, 0x42, 0xed, 0x13, 0xe8, 0x7e, 0x36, 0xf5, 0x07, 0x74, 0x23, 0x69, 0x27, 0xc4, 0xc0, 0x98, + 0x84, 0x24, 0x1d, 0xda, 0xa5, 0xe2, 0x03, 0x08, 0xbe, 0x94, 0xcb, 0x82, 0x1b, 0x97, 0xd2, 0xfa, + 0x20, 0x42, 0xe7, 0xc7, 0xb6, 0x28, 0xba, 0xbb, 0x37, 0xe7, 0xdc, 0x73, 0x4e, 0x2e, 0x17, 0x34, + 0x87, 0x34, 0xa6, 0xc6, 0x72, 0x82, 0x47, 0x8a, 0x69, 0x12, 0x51, 0x9c, 0x74, 0xb1, 0x1d, 0x07, + 0x4a, 0x4b, 0x2b, 0x61, 0x3d, 0x47, 0x83, 0x0c, 0x0d, 0x92, 0xae, 0xd7, 0x64, 0x52, 0xb2, 0x98, + 0x62, 0xa2, 0x38, 0x26, 0x42, 0x48, 0x4b, 0x2c, 0x97, 0xc2, 0xa4, 0x23, 0xe8, 0x0a, 0xd4, 0x42, + 0xc3, 0xce, 0x38, 0x13, 0x24, 0xbe, 0xa4, 0xda, 0x70, 0x29, 0xe0, 0x11, 0xd8, 0x4a, 0x48, 0xcc, + 0x23, 0x62, 0xa5, 0xbe, 0x21, 0x51, 0xa4, 0xa9, 0x31, 0xae, 0xd3, 0x72, 0xda, 0x9b, 0xfd, 0x5a, + 0x01, 0x9c, 0xa4, 0xef, 0xd0, 0x05, 0x1b, 0x49, 0x3a, 0xe7, 0x96, 0x5b, 0x4e, 0xfb, 0x7f, 0x3f, + 0x6f, 0x91, 0x07, 0xdc, 0x75, 0xe9, 0x3e, 0x35, 0x4a, 0x0a, 0x43, 0xd1, 0x01, 0xa8, 0x86, 0x86, + 0x9d, 0xeb, 0xc9, 0x45, 0x1a, 0x14, 0xee, 0x80, 0x8a, 0xe1, 0x4c, 0x50, 0x9d, 0x19, 0x65, 0x1d, + 0xda, 0x05, 0x8d, 0x15, 0x62, 0xae, 0x70, 0xfc, 0x58, 0x06, 0xff, 0x42, 0xc3, 0xe0, 0xbd, 0x03, + 0xaa, 0xab, 0xf1, 0xf7, 0x83, 0x6f, 0xd6, 0x10, 0xac, 0x47, 0xf1, 0x3a, 0x7f, 0xa2, 0x15, 0x89, + 0xbd, 0x87, 0xd7, 0x8f, 0xe7, 0xf2, 0x36, 0x82, 0xcb, 0x9b, 0x37, 0x0b, 0x2a, 0x9c, 0x00, 0xb0, + 0xf4, 0x15, 0xf4, 0x93, 0xf0, 0x17, 0xc7, 0x3b, 0xfc, 0x9d, 0x53, 0x38, 0xef, 0x2d, 0x9c, 0x1b, + 0xa8, 0xbe, 0xec, 0x9c, 0x95, 0xa7, 0xe1, 0xcb, 0xcc, 0x77, 0xa6, 0x33, 0xdf, 0x79, 0x9f, 0xf9, + 0xce, 0xd3, 0xdc, 0x2f, 0x4d, 0xe7, 0x7e, 0xe9, 0x6d, 0xee, 0x97, 0xae, 0x7b, 0x8c, 0xdb, 0xdb, + 0xd1, 0x20, 0x18, 0xca, 0x3b, 0x9c, 0x9b, 0x49, 0xcd, 0x8a, 0xba, 0x43, 0x94, 0xc2, 0xe3, 0x42, + 0xd3, 0x4e, 0x14, 0x35, 0x83, 0xca, 0xe2, 0x2a, 0x7a, 0x9f, 0x01, 0x00, 0x00, 0xff, 0xff, 0xe5, + 0xec, 0x2f, 0xdc, 0x68, 0x02, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -162,6 +250,9 @@ const _ = grpc.SupportPackageIsVersion4 type MsgClient interface { // SignalVersion allows the validator to signal for an upgrade SignalVersion(ctx context.Context, in *MsgSignalVersion, opts ...grpc.CallOption) (*MsgSignalVersionResponse, error) + // TryUpgrade tallies all the votes and if a quorum is reached, it will + // trigger an upgrade for the following height + TryUpgrade(ctx context.Context, in *MsgTryUpgrade, opts ...grpc.CallOption) (*MsgTryUpgradeResponse, error) } type msgClient struct { @@ -181,10 +272,22 @@ func (c *msgClient) SignalVersion(ctx context.Context, in *MsgSignalVersion, opt return out, nil } +func (c *msgClient) TryUpgrade(ctx context.Context, in *MsgTryUpgrade, opts ...grpc.CallOption) (*MsgTryUpgradeResponse, error) { + out := new(MsgTryUpgradeResponse) + err := c.cc.Invoke(ctx, "/celestia.upgrade.v1.Msg/TryUpgrade", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // MsgServer is the server API for Msg service. type MsgServer interface { // SignalVersion allows the validator to signal for an upgrade SignalVersion(context.Context, *MsgSignalVersion) (*MsgSignalVersionResponse, error) + // TryUpgrade tallies all the votes and if a quorum is reached, it will + // trigger an upgrade for the following height + TryUpgrade(context.Context, *MsgTryUpgrade) (*MsgTryUpgradeResponse, error) } // UnimplementedMsgServer can be embedded to have forward compatible implementations. @@ -194,6 +297,9 @@ type UnimplementedMsgServer struct { func (*UnimplementedMsgServer) SignalVersion(ctx context.Context, req *MsgSignalVersion) (*MsgSignalVersionResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method SignalVersion not implemented") } +func (*UnimplementedMsgServer) TryUpgrade(ctx context.Context, req *MsgTryUpgrade) (*MsgTryUpgradeResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method TryUpgrade not implemented") +} func RegisterMsgServer(s grpc1.Server, srv MsgServer) { s.RegisterService(&_Msg_serviceDesc, srv) @@ -217,6 +323,24 @@ func _Msg_SignalVersion_Handler(srv interface{}, ctx context.Context, dec func(i return interceptor(ctx, in, info, handler) } +func _Msg_TryUpgrade_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgTryUpgrade) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).TryUpgrade(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/celestia.upgrade.v1.Msg/TryUpgrade", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).TryUpgrade(ctx, req.(*MsgTryUpgrade)) + } + return interceptor(ctx, in, info, handler) +} + var _Msg_serviceDesc = grpc.ServiceDesc{ ServiceName: "celestia.upgrade.v1.Msg", HandlerType: (*MsgServer)(nil), @@ -225,6 +349,10 @@ var _Msg_serviceDesc = grpc.ServiceDesc{ MethodName: "SignalVersion", Handler: _Msg_SignalVersion_Handler, }, + { + MethodName: "TryUpgrade", + Handler: _Msg_TryUpgrade_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "celestia/upgrade/v1/tx.proto", @@ -288,6 +416,59 @@ func (m *MsgSignalVersionResponse) MarshalToSizedBuffer(dAtA []byte) (int, error return len(dAtA) - i, nil } +func (m *MsgTryUpgrade) 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 *MsgTryUpgrade) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgTryUpgrade) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Signer) > 0 { + i -= len(m.Signer) + copy(dAtA[i:], m.Signer) + i = encodeVarintTx(dAtA, i, uint64(len(m.Signer))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgTryUpgradeResponse) 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 *MsgTryUpgradeResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgTryUpgradeResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + func encodeVarintTx(dAtA []byte, offset int, v uint64) int { offset -= sovTx(v) base := offset @@ -324,6 +505,28 @@ func (m *MsgSignalVersionResponse) Size() (n int) { return n } +func (m *MsgTryUpgrade) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Signer) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgTryUpgradeResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + func sovTx(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -481,6 +684,138 @@ func (m *MsgSignalVersionResponse) Unmarshal(dAtA []byte) error { } return nil } +func (m *MsgTryUpgrade) 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 ErrIntOverflowTx + } + 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: MsgTryUpgrade: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgTryUpgrade: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Signer", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + 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 ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Signer = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgTryUpgradeResponse) 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 ErrIntOverflowTx + } + 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: MsgTryUpgradeResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgTryUpgradeResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipTx(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/x/upgrade/types/tx.pb.gw.go b/x/upgrade/types/tx.pb.gw.go index 429e98cf87..463c324c64 100644 --- a/x/upgrade/types/tx.pb.gw.go +++ b/x/upgrade/types/tx.pb.gw.go @@ -69,6 +69,42 @@ func local_request_Msg_SignalVersion_0(ctx context.Context, marshaler runtime.Ma } +var ( + filter_Msg_TryUpgrade_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} +) + +func request_Msg_TryUpgrade_0(ctx context.Context, marshaler runtime.Marshaler, client MsgClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq MsgTryUpgrade + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Msg_TryUpgrade_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.TryUpgrade(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Msg_TryUpgrade_0(ctx context.Context, marshaler runtime.Marshaler, server MsgServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq MsgTryUpgrade + var metadata runtime.ServerMetadata + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Msg_TryUpgrade_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.TryUpgrade(ctx, &protoReq) + return msg, metadata, err + +} + // RegisterMsgHandlerServer registers the http handlers for service Msg to "mux". // UnaryRPC :call MsgServer directly. // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. @@ -98,6 +134,29 @@ func RegisterMsgHandlerServer(ctx context.Context, mux *runtime.ServeMux, server }) + mux.Handle("POST", pattern_Msg_TryUpgrade_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Msg_TryUpgrade_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Msg_TryUpgrade_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } @@ -159,13 +218,37 @@ func RegisterMsgHandlerClient(ctx context.Context, mux *runtime.ServeMux, client }) + mux.Handle("POST", pattern_Msg_TryUpgrade_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Msg_TryUpgrade_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Msg_TryUpgrade_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + return nil } var ( pattern_Msg_SignalVersion_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"upgrade", "v1", "signal"}, "", runtime.AssumeColonVerbOpt(false))) + + pattern_Msg_TryUpgrade_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 0}, []string{"upgrade", "v1"}, "", runtime.AssumeColonVerbOpt(false))) ) var ( forward_Msg_SignalVersion_0 = runtime.ForwardResponseMessage + + forward_Msg_TryUpgrade_0 = runtime.ForwardResponseMessage ) From d85b60624d0d0603d34dc40cec54a76a24e0209a Mon Sep 17 00:00:00 2001 From: Callum Waters Date: Thu, 30 Nov 2023 18:04:51 +0100 Subject: [PATCH 2/9] Use AccAddress instead of ValAddress --- x/upgrade/types/msgs.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x/upgrade/types/msgs.go b/x/upgrade/types/msgs.go index 7b4db72781..c0f70c69fc 100644 --- a/x/upgrade/types/msgs.go +++ b/x/upgrade/types/msgs.go @@ -47,7 +47,7 @@ func NewMsgTryUpgrade(signer sdk.AccAddress) *MsgTryUpgrade { } func (msg *MsgTryUpgrade) GetSigners() []sdk.AccAddress { - valAddr, err := sdk.ValAddressFromBech32(msg.Signer) + valAddr, err := sdk.AccAddressFromBech32(msg.Signer) if err != nil { panic(err) } @@ -55,6 +55,6 @@ func (msg *MsgTryUpgrade) GetSigners() []sdk.AccAddress { } func (msg *MsgTryUpgrade) ValidateBasic() error { - _, err := sdk.ValAddressFromBech32(msg.Signer) + _, err := sdk.AccAddressFromBech32(msg.Signer) return err } From 874aaa01304c6b952519fed8fb123459291f5358 Mon Sep 17 00:00:00 2001 From: Callum Waters Date: Thu, 30 Nov 2023 18:33:24 +0100 Subject: [PATCH 3/9] feat: reset tally after quorum has reached --- app/app.go | 1 + x/upgrade/keeper.go | 28 ++++++++++++++++++++++------ x/upgrade/tally_test.go | 10 ++++++++++ 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/app/app.go b/app/app.go index 1676818832..7835064e79 100644 --- a/app/app.go +++ b/app/app.go @@ -587,6 +587,7 @@ func (app *App) EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) abci.Respo // Version changes must be increasing. Downgrades are not permitted if version > app.AppVersion(ctx) { app.SetAppVersion(ctx, version) + app.UpgradeKeeper.ResetTally(ctx, version) } } return res diff --git a/x/upgrade/keeper.go b/x/upgrade/keeper.go index 1834014012..7cce3c3058 100644 --- a/x/upgrade/keeper.go +++ b/x/upgrade/keeper.go @@ -76,12 +76,6 @@ func (k Keeper) SignalVersion(ctx context.Context, req *types.MsgSignalVersion) return nil, err } - // check that the validator exists - _, found := k.stakingKeeper.GetValidator(sdkCtx, valAddr) - if !found { - return nil, stakingtypes.ErrNoValidatorFound - } - // the signalled version must be either the current version (for cancelling an upgrade) // or the very next version (for accepting an upgrade) currentVersion := sdkCtx.BlockHeader().Version.App @@ -89,6 +83,12 @@ func (k Keeper) SignalVersion(ctx context.Context, req *types.MsgSignalVersion) return nil, types.ErrInvalidVersion } + // check that the validator exists + _, found := k.stakingKeeper.GetValidator(sdkCtx, valAddr) + if !found { + return nil, stakingtypes.ErrNoValidatorFound + } + k.SetValidatorVersion(sdkCtx, valAddr, req.Version) return &types.MsgSignalVersionResponse{}, nil } @@ -201,6 +201,22 @@ func (k *Keeper) ShouldUpgrade() (bool, uint64) { return k.quorumVersion != 0, k.quorumVersion } +// ResetTally resets the tally after a version change. It iterates over the store, +// and deletes any versions that are less than the provided version. It also +// resets the quorumVersion to 0. +func (k *Keeper) ResetTally(ctx sdk.Context, version uint64) { + store := ctx.KVStore(k.storeKey) + iterator := store.Iterator(nil, nil) + defer iterator.Close() + for ; iterator.Valid(); iterator.Next() { + v := VersionFromBytes(iterator.Value()) + if v <= version { + store.Delete(iterator.Key()) + } + } + k.quorumVersion = 0 +} + func VersionToBytes(version uint64) []byte { return binary.BigEndian.AppendUint64(nil, version) } diff --git a/x/upgrade/tally_test.go b/x/upgrade/tally_test.go index 2608a2239f..9f1a25aab1 100644 --- a/x/upgrade/tally_test.go +++ b/x/upgrade/tally_test.go @@ -155,6 +155,16 @@ func TestTallyingLogic(t *testing.T) { Version: 2, }) require.Error(t, err) + + // resetting the tally should clear other votes + upgradeKeeper.ResetTally(ctx, 2) + res, err = upgradeKeeper.VersionTally(goCtx, &types.QueryVersionTallyRequest{ + Version: 2, + }) + require.NoError(t, err) + require.EqualValues(t, 0, res.VotingPower) + require.EqualValues(t, 99, res.ThresholdPower) + require.EqualValues(t, 119, res.TotalVotingPower) } func setup(t *testing.T) (upgrade.Keeper, sdk.Context, *mockStakingKeeper) { From 8482daf903aed07e1382974770f1d19f31f49c04 Mon Sep 17 00:00:00 2001 From: Callum Waters Date: Wed, 6 Dec 2023 17:09:46 +0100 Subject: [PATCH 4/9] add integration tests and cli tests --- app/app.go | 2 +- test/util/test_app.go | 4 ++ x/upgrade/cli/cli_test.go | 40 ++++++++++++++++ x/upgrade/cli/query.go | 58 +++++++++++++++++++++++ x/upgrade/cli/tx.go | 88 +++++++++++++++++++++++++++++++++++ x/upgrade/integration_test.go | 57 +++++++++++++++++++++++ x/upgrade/keeper.go | 1 - x/upgrade/module.go | 2 +- x/upgrade/tally_test.go | 12 +++++ 9 files changed, 261 insertions(+), 3 deletions(-) create mode 100644 x/upgrade/cli/cli_test.go create mode 100644 x/upgrade/cli/query.go create mode 100644 x/upgrade/cli/tx.go create mode 100644 x/upgrade/integration_test.go diff --git a/app/app.go b/app/app.go index 7835064e79..42c3b04084 100644 --- a/app/app.go +++ b/app/app.go @@ -336,7 +336,7 @@ func New( ) app.FeeGrantKeeper = feegrantkeeper.NewKeeper(appCodec, keys[feegrant.StoreKey], app.AccountKeeper) - app.UpgradeKeeper = upgrade.NewKeeper(keys[upgradetypes.StoreKey], upgradeHeight, app.StakingKeeper) + app.UpgradeKeeper = upgrade.NewKeeper(keys[upgradetypes.StoreKey], upgradeHeight, stakingKeeper) app.BlobstreamKeeper = *bsmodulekeeper.NewKeeper( appCodec, diff --git a/test/util/test_app.go b/test/util/test_app.go index 56e805fd67..5fbd47c8de 100644 --- a/test/util/test_app.go +++ b/test/util/test_app.go @@ -25,6 +25,7 @@ import ( abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/libs/log" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + tmversion "github.com/tendermint/tendermint/proto/tendermint/version" tmtypes "github.com/tendermint/tendermint/types" dbm "github.com/tendermint/tm-db" @@ -106,6 +107,9 @@ func SetupTestAppWithGenesisValSet(cparams *tmproto.ConsensusParams, genAccounts AppHash: testApp.LastCommitID().Hash, ValidatorsHash: valSet.Hash(), NextValidatorsHash: valSet.Hash(), + Version: tmversion.Consensus{ + App: cparams.Version.AppVersion, + }, }}) return testApp, kr diff --git a/x/upgrade/cli/cli_test.go b/x/upgrade/cli/cli_test.go new file mode 100644 index 0000000000..74291590e6 --- /dev/null +++ b/x/upgrade/cli/cli_test.go @@ -0,0 +1,40 @@ +package cli_test + +import ( + "testing" + + "github.com/celestiaorg/celestia-app/test/util/testnode" + "github.com/celestiaorg/celestia-app/x/upgrade/cli" + testutil "github.com/cosmos/cosmos-sdk/testutil/cli" + "github.com/stretchr/testify/suite" +) + +func TestCLITestSuite(t *testing.T) { + if testing.Short() { + t.Skip("skipping upgrade CLI test in short mode") + } + suite.Run(t, new(CLITestSuite)) +} + +type CLITestSuite struct { + suite.Suite + + ctx testnode.Context +} + +func (s *CLITestSuite) SetupSuite() { + s.T().Log("setting up integration test suite") + ctx, _, _ := testnode.NewNetwork(s.T(), testnode.DefaultConfig()) + s.ctx = ctx + _, err := s.ctx.WaitForHeight(1) + s.Require().NoError(err) +} + +func (s *CLITestSuite) TestCmdQueryTally() { + cmd := cli.CmdQueryTally() + output, err := testutil.ExecTestCLICmd(s.ctx.Context, cmd, []string{"1"}) + s.Require().NoError(err) + s.Require().Contains(output.String(), "voting_power") + s.Require().Contains(output.String(), "threshold_power") + s.Require().Contains(output.String(), "total_voting_power") +} diff --git a/x/upgrade/cli/query.go b/x/upgrade/cli/query.go new file mode 100644 index 0000000000..6af83c8785 --- /dev/null +++ b/x/upgrade/cli/query.go @@ -0,0 +1,58 @@ +package cli + +import ( + "fmt" + "strconv" + + "github.com/celestiaorg/celestia-app/x/upgrade/types" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/spf13/cobra" +) + +// GetQueryCmd returns the CLI query commands for this module +func GetQueryCmd() *cobra.Command { + // Group blob queries under a subcommand + cmd := &cobra.Command{ + Use: types.ModuleName, + Short: fmt.Sprintf("Querying commands for the %s module", types.ModuleName), + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + cmd.AddCommand(CmdQueryTally()) + + return cmd +} + +func CmdQueryTally() *cobra.Command { + cmd := &cobra.Command{ + Use: "tally [version]", + Short: "Query the tally of signal votes for a given version", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientQueryContext(cmd) + if err != nil { + return err + } + + version, err := strconv.ParseUint(args[0], 10, 64) + if err != nil { + return err + } + + upgradeQueryClient := types.NewQueryClient(clientCtx) + + resp, err := upgradeQueryClient.VersionTally(cmd.Context(), &types.QueryVersionTallyRequest{Version: version}) + if err != nil { + return err + } + + return clientCtx.PrintProto(resp) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + return cmd +} diff --git a/x/upgrade/cli/tx.go b/x/upgrade/cli/tx.go new file mode 100644 index 0000000000..4e6fb4e6dd --- /dev/null +++ b/x/upgrade/cli/tx.go @@ -0,0 +1,88 @@ +package cli + +import ( + "fmt" + "strconv" + + "github.com/celestiaorg/celestia-app/x/upgrade/types" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/client/tx" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/spf13/cobra" +) + +// GetTxCmd returns the transaction commands for this module +func GetTxCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: types.ModuleName, + Short: fmt.Sprintf("%s transactions subcommands", types.ModuleName), + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + cmd.AddCommand(CmdSignalVersion()) + cmd.AddCommand(CmdTryUpgrade()) + + return cmd +} + +func CmdSignalVersion() *cobra.Command { + cmd := &cobra.Command{ + Use: "signal [version]", + Short: "Signal a software upgrade for the specified version", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + version, err := strconv.ParseUint(args[0], 10, 64) + if err != nil { + return err + } + + addr := clientCtx.GetFromAddress().Bytes() + valAddr := sdk.ValAddress(addr) + + msg := types.NewMsgSignalVersion(valAddr, version) + if err := msg.ValidateBasic(); err != nil { + return err + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + return cmd +} + +func CmdTryUpgrade() *cobra.Command { + cmd := &cobra.Command{ + Use: "try-upgrade", + Short: "Try to perform a software upgrade", + Long: `This command will submit a TryUpgrade message to tally all +the signal votes. If a quorum has been reached, the network will upgrade +to the signalled version at the following height. +`, + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + msg := types.NewMsgTryUpgrade(clientCtx.GetFromAddress()) + if err := msg.ValidateBasic(); err != nil { + return err + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + return cmd +} diff --git a/x/upgrade/integration_test.go b/x/upgrade/integration_test.go new file mode 100644 index 0000000000..1f61b9db69 --- /dev/null +++ b/x/upgrade/integration_test.go @@ -0,0 +1,57 @@ +package upgrade_test + +import ( + "testing" + + "github.com/celestiaorg/celestia-app/app" + testutil "github.com/celestiaorg/celestia-app/test/util" + "github.com/celestiaorg/celestia-app/x/upgrade/types" + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" + tmlog "github.com/tendermint/tendermint/libs/log" + tmtypes "github.com/tendermint/tendermint/proto/tendermint/types" + tmversion "github.com/tendermint/tendermint/proto/tendermint/version" +) + +func TestUpgradeIntegration(t *testing.T) { + app, _ := testutil.SetupTestAppWithGenesisValSet(app.DefaultConsensusParams()) + ctx := sdk.NewContext(app.CommitMultiStore(), tmtypes.Header{ + Version: tmversion.Consensus{ + App: 1, + }, + }, false, tmlog.NewNopLogger()) + goCtx := sdk.WrapSDKContext(ctx) + ctx = sdk.UnwrapSDKContext(goCtx) + + res, err := app.UpgradeKeeper.VersionTally(goCtx, &types.QueryVersionTallyRequest{ + Version: 2, + }) + require.NoError(t, err) + require.EqualValues(t, 0, res.VotingPower) + + validators := app.StakingKeeper.GetAllValidators(ctx) + valAddr, err := sdk.ValAddressFromBech32(validators[0].OperatorAddress) + require.NoError(t, err) + + _, err = app.UpgradeKeeper.SignalVersion(ctx, &types.MsgSignalVersion{ + ValidatorAddress: valAddr.String(), + Version: 2, + }) + require.NoError(t, err) + + res, err = app.UpgradeKeeper.VersionTally(goCtx, &types.QueryVersionTallyRequest{ + Version: 2, + }) + require.NoError(t, err) + require.EqualValues(t, 1, res.VotingPower) + require.EqualValues(t, 0, res.ThresholdPower) + require.EqualValues(t, 1, res.TotalVotingPower) + + _, err = app.UpgradeKeeper.TryUpgrade(ctx, nil) + require.NoError(t, err) + + shouldUpgrade, version := app.UpgradeKeeper.ShouldUpgrade() + require.True(t, shouldUpgrade) + require.EqualValues(t, 2, version) +} diff --git a/x/upgrade/keeper.go b/x/upgrade/keeper.go index 7cce3c3058..00d8afb36c 100644 --- a/x/upgrade/keeper.go +++ b/x/upgrade/keeper.go @@ -123,7 +123,6 @@ func (k Keeper) VersionTally(ctx context.Context, req *types.QueryVersionTallyRe currentVotingPower = currentVotingPower.AddRaw(power) } } - threshold := k.GetVotingPowerThreshold(sdkCtx) return &types.QueryVersionTallyResponse{ VotingPower: currentVotingPower.Uint64(), diff --git a/x/upgrade/module.go b/x/upgrade/module.go index 0b876a2d0e..2fb99353e6 100644 --- a/x/upgrade/module.go +++ b/x/upgrade/module.go @@ -8,13 +8,13 @@ import ( "github.com/spf13/cobra" abci "github.com/tendermint/tendermint/abci/types" + "github.com/celestiaorg/celestia-app/x/upgrade/cli" "github.com/celestiaorg/celestia-app/x/upgrade/types" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" - "github.com/cosmos/cosmos-sdk/x/upgrade/client/cli" ) func init() { diff --git a/x/upgrade/tally_test.go b/x/upgrade/tally_test.go index 9f1a25aab1..474c4c2fbc 100644 --- a/x/upgrade/tally_test.go +++ b/x/upgrade/tally_test.go @@ -167,6 +167,18 @@ func TestTallyingLogic(t *testing.T) { require.EqualValues(t, 119, res.TotalVotingPower) } +func TestEmptyStore(t *testing.T) { + upgradeKeeper, ctx, _ := setup(t) + goCtx := sdk.WrapSDKContext(ctx) + + res, err := upgradeKeeper.VersionTally(goCtx, &types.QueryVersionTallyRequest{ + Version: 2, + }) + require.NoError(t, err) + require.EqualValues(t, 0, res.VotingPower) + require.EqualValues(t, 120, res.TotalVotingPower) +} + func setup(t *testing.T) (upgrade.Keeper, sdk.Context, *mockStakingKeeper) { upgradeStore := sdk.NewKVStoreKey(types.StoreKey) db := tmdb.NewMemDB() From 6f6c72a11bd5838048eb3d750401ef3670bb1604 Mon Sep 17 00:00:00 2001 From: Callum Waters Date: Thu, 7 Dec 2023 11:33:04 +0100 Subject: [PATCH 5/9] Apply suggestions from code review Co-authored-by: Rootul P --- x/upgrade/cli/query.go | 3 +-- x/upgrade/cli/tx.go | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/x/upgrade/cli/query.go b/x/upgrade/cli/query.go index 6af83c8785..bf0c8a7a0a 100644 --- a/x/upgrade/cli/query.go +++ b/x/upgrade/cli/query.go @@ -12,7 +12,6 @@ import ( // GetQueryCmd returns the CLI query commands for this module func GetQueryCmd() *cobra.Command { - // Group blob queries under a subcommand cmd := &cobra.Command{ Use: types.ModuleName, Short: fmt.Sprintf("Querying commands for the %s module", types.ModuleName), @@ -28,7 +27,7 @@ func GetQueryCmd() *cobra.Command { func CmdQueryTally() *cobra.Command { cmd := &cobra.Command{ - Use: "tally [version]", + Use: "tally version", Short: "Query the tally of signal votes for a given version", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { diff --git a/x/upgrade/cli/tx.go b/x/upgrade/cli/tx.go index 4e6fb4e6dd..6af7fbb1b0 100644 --- a/x/upgrade/cli/tx.go +++ b/x/upgrade/cli/tx.go @@ -30,7 +30,7 @@ func GetTxCmd() *cobra.Command { func CmdSignalVersion() *cobra.Command { cmd := &cobra.Command{ - Use: "signal [version]", + Use: "signal version", Short: "Signal a software upgrade for the specified version", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { From c15ff0bf00b466fdafd2959ba12eef1c7548697e Mon Sep 17 00:00:00 2001 From: Callum Waters Date: Thu, 7 Dec 2023 11:48:03 +0100 Subject: [PATCH 6/9] add some comments to the tests --- x/upgrade/integration_test.go | 3 +++ x/upgrade/tally_test.go | 20 ++++++++++++++++---- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/x/upgrade/integration_test.go b/x/upgrade/integration_test.go index 1f61b9db69..1ef940e5f5 100644 --- a/x/upgrade/integration_test.go +++ b/x/upgrade/integration_test.go @@ -14,6 +14,9 @@ import ( tmversion "github.com/tendermint/tendermint/proto/tendermint/version" ) +// TestUpgradeIntegration uses the real application including the upgrade keeper (and staking keeper). It +// simulates an upgrade scenario with a single validator which signals for the version change, checks the quorum +// has been reached and then calls TryUpgrade, asserting that the upgrade module returns the new app version func TestUpgradeIntegration(t *testing.T) { app, _ := testutil.SetupTestAppWithGenesisValSet(app.DefaultConsensusParams()) ctx := sdk.NewContext(app.CommitMultiStore(), tmtypes.Header{ diff --git a/x/upgrade/tally_test.go b/x/upgrade/tally_test.go index 474c4c2fbc..c172180bd2 100644 --- a/x/upgrade/tally_test.go +++ b/x/upgrade/tally_test.go @@ -139,6 +139,7 @@ func TestTallyingLogic(t *testing.T) { // remove one of the validators from the set delete(mockStakingKeeper.validators, testutil.ValAddrs[1].String()) + // the validator had 1 voting power, so we deduct it from the total mockStakingKeeper.totalVotingPower-- res, err = upgradeKeeper.VersionTally(goCtx, &types.QueryVersionTallyRequest{ @@ -176,6 +177,7 @@ func TestEmptyStore(t *testing.T) { }) require.NoError(t, err) require.EqualValues(t, 0, res.VotingPower) + // 120 is the summation in voting power of the four validators require.EqualValues(t, 120, res.TotalVotingPower) } @@ -191,15 +193,14 @@ func setup(t *testing.T) (upgrade.Keeper, sdk.Context, *mockStakingKeeper) { App: 1, }, }, false, log.NewNopLogger()) - mockStakingKeeper := &mockStakingKeeper{ - totalVotingPower: 120, - validators: map[string]int64{ + mockStakingKeeper := newMockStakingKeeper( + map[string]int64{ testutil.ValAddrs[0].String(): 40, testutil.ValAddrs[1].String(): 1, testutil.ValAddrs[2].String(): 60, testutil.ValAddrs[3].String(): 19, }, - } + ) upgradeKeeper := upgrade.NewKeeper(upgradeStore, 0, mockStakingKeeper) return upgradeKeeper, mockCtx, mockStakingKeeper @@ -212,6 +213,17 @@ type mockStakingKeeper struct { validators map[string]int64 } +func newMockStakingKeeper(validators map[string]int64) *mockStakingKeeper { + totalVotingPower := int64(0) + for _, power := range validators { + totalVotingPower += power + } + return &mockStakingKeeper{ + totalVotingPower: totalVotingPower, + validators: validators, + } +} + func (m *mockStakingKeeper) GetLastTotalPower(_ sdk.Context) math.Int { return math.NewInt(m.totalVotingPower) } From 03aa6981fda22647c6e5dcb2e3746eccd540978b Mon Sep 17 00:00:00 2001 From: Callum Waters Date: Thu, 7 Dec 2023 12:27:26 +0100 Subject: [PATCH 7/9] theshold is greater than equal rather than strictly greater --- x/upgrade/integration_test.go | 2 +- x/upgrade/keeper.go | 10 +++++++--- x/upgrade/tally_test.go | 33 +++++++++++++++++++++++++-------- 3 files changed, 33 insertions(+), 12 deletions(-) diff --git a/x/upgrade/integration_test.go b/x/upgrade/integration_test.go index 1ef940e5f5..dcb52331b0 100644 --- a/x/upgrade/integration_test.go +++ b/x/upgrade/integration_test.go @@ -48,7 +48,7 @@ func TestUpgradeIntegration(t *testing.T) { }) require.NoError(t, err) require.EqualValues(t, 1, res.VotingPower) - require.EqualValues(t, 0, res.ThresholdPower) + require.EqualValues(t, 1, res.ThresholdPower) require.EqualValues(t, 1, res.TotalVotingPower) _, err = app.UpgradeKeeper.TryUpgrade(ctx, nil) diff --git a/x/upgrade/keeper.go b/x/upgrade/keeper.go index 00d8afb36c..e230153008 100644 --- a/x/upgrade/keeper.go +++ b/x/upgrade/keeper.go @@ -169,7 +169,7 @@ func (k Keeper) TallyVotingPower(ctx sdk.Context, threshold int64) (bool, uint64 } else { output[version] += power } - if output[version] > threshold { + if output[version] >= threshold { return true, version } } @@ -179,10 +179,14 @@ func (k Keeper) TallyVotingPower(ctx sdk.Context, threshold int64) (bool, uint64 // GetVotingPowerThreshold returns the voting power threshold required to // upgrade to a new version. func (k Keeper) GetVotingPowerThreshold(ctx sdk.Context) sdkmath.Int { - // contract: totalVotingPower should not exceed MaxUit64 + // contract: totalVotingPower should not exceed MaxUint64 totalVotingPower := k.stakingKeeper.GetLastTotalPower(ctx) thresholdFraction := SignalThreshold(ctx.BlockHeader().Version.App) - return totalVotingPower.MulRaw(thresholdFraction.Numerator).QuoRaw(thresholdFraction.Denominator) + product := totalVotingPower.MulRaw(thresholdFraction.Numerator) + if product.ModRaw(thresholdFraction.Denominator).IsZero() { + return product.QuoRaw(thresholdFraction.Denominator) + } + return product.QuoRaw(thresholdFraction.Denominator).AddRaw(1) } // ShouldUpgradeToV2 returns true if the current height is one before diff --git a/x/upgrade/tally_test.go b/x/upgrade/tally_test.go index c172180bd2..329beffcab 100644 --- a/x/upgrade/tally_test.go +++ b/x/upgrade/tally_test.go @@ -92,7 +92,7 @@ func TestTallyingLogic(t *testing.T) { Version: 2, }) require.NoError(t, err) - require.EqualValues(t, 100, res.VotingPower) + require.EqualValues(t, 99, res.VotingPower) require.EqualValues(t, 100, res.ThresholdPower) require.EqualValues(t, 120, res.TotalVotingPower) @@ -133,7 +133,7 @@ func TestTallyingLogic(t *testing.T) { Version: 2, }) require.NoError(t, err) - require.EqualValues(t, 61, res.VotingPower) + require.EqualValues(t, 60, res.VotingPower) require.EqualValues(t, 100, res.ThresholdPower) require.EqualValues(t, 120, res.TotalVotingPower) @@ -146,8 +146,8 @@ func TestTallyingLogic(t *testing.T) { Version: 2, }) require.NoError(t, err) - require.EqualValues(t, 60, res.VotingPower) - require.EqualValues(t, 99, res.ThresholdPower) + require.EqualValues(t, 59, res.VotingPower) + require.EqualValues(t, 100, res.ThresholdPower) require.EqualValues(t, 119, res.TotalVotingPower) // That validator should not be able to signal a version @@ -164,8 +164,6 @@ func TestTallyingLogic(t *testing.T) { }) require.NoError(t, err) require.EqualValues(t, 0, res.VotingPower) - require.EqualValues(t, 99, res.ThresholdPower) - require.EqualValues(t, 119, res.TotalVotingPower) } func TestEmptyStore(t *testing.T) { @@ -181,6 +179,25 @@ func TestEmptyStore(t *testing.T) { require.EqualValues(t, 120, res.TotalVotingPower) } +func TestThresholdVotingPower(t *testing.T) { + upgradeKeeper, ctx, mockStakingKeeper := setup(t) + + for _, tc := range []struct { + total int64 + threshold int64 + }{ + {total: 1, threshold: 1}, + {total: 2, threshold: 2}, + {total: 3, threshold: 3}, + {total: 6, threshold: 5}, + {total: 59, threshold: 50}, + } { + mockStakingKeeper.totalVotingPower = tc.total + threshold := upgradeKeeper.GetVotingPowerThreshold(ctx) + require.EqualValues(t, tc.threshold, threshold.Int64()) + } +} + func setup(t *testing.T) (upgrade.Keeper, sdk.Context, *mockStakingKeeper) { upgradeStore := sdk.NewKVStoreKey(types.StoreKey) db := tmdb.NewMemDB() @@ -197,8 +214,8 @@ func setup(t *testing.T) (upgrade.Keeper, sdk.Context, *mockStakingKeeper) { map[string]int64{ testutil.ValAddrs[0].String(): 40, testutil.ValAddrs[1].String(): 1, - testutil.ValAddrs[2].String(): 60, - testutil.ValAddrs[3].String(): 19, + testutil.ValAddrs[2].String(): 59, + testutil.ValAddrs[3].String(): 20, }, ) From d3487b36d643615acf48b250f718568e6719dd22 Mon Sep 17 00:00:00 2001 From: Callum Waters Date: Thu, 7 Dec 2023 12:55:51 +0100 Subject: [PATCH 8/9] add tests to the sdk integration test suite --- app/test/std_sdk_test.go | 38 +++++++++++++++++++++++++++++++++--- test/util/genesis/genesis.go | 10 +--------- test/util/testnode/config.go | 5 ----- 3 files changed, 36 insertions(+), 17 deletions(-) diff --git a/app/test/std_sdk_test.go b/app/test/std_sdk_test.go index 9add7bbdfc..2456d0f650 100644 --- a/app/test/std_sdk_test.go +++ b/app/test/std_sdk_test.go @@ -11,6 +11,7 @@ import ( "github.com/celestiaorg/celestia-app/test/util/blobfactory" "github.com/celestiaorg/celestia-app/test/util/testfactory" "github.com/celestiaorg/celestia-app/test/util/testnode" + upgrade "github.com/celestiaorg/celestia-app/x/upgrade/types" "github.com/cosmos/cosmos-sdk/crypto/hd" "github.com/cosmos/cosmos-sdk/crypto/keyring" "github.com/cosmos/cosmos-sdk/testutil/mock" @@ -40,6 +41,7 @@ type StandardSDKIntegrationTestSuite struct { suite.Suite accounts []string + cfg *testnode.Config cctx testnode.Context ecfg encoding.Config @@ -56,11 +58,10 @@ func (s *StandardSDKIntegrationTestSuite) SetupSuite() { accounts[i] = tmrand.Str(9) } - cfg := testnode.DefaultConfig().WithFundedAccounts(accounts...) - cctx, _, _ := testnode.NewNetwork(t, cfg) + s.cfg = testnode.DefaultConfig().WithFundedAccounts(accounts...) + s.cctx, _, _ = testnode.NewNetwork(t, s.cfg) s.accounts = accounts s.ecfg = encoding.MakeConfig(app.ModuleEncodingRegisters...) - s.cctx = cctx } func (s *StandardSDKIntegrationTestSuite) unusedAccount() string { @@ -71,6 +72,18 @@ func (s *StandardSDKIntegrationTestSuite) unusedAccount() string { return acc } +func (s *StandardSDKIntegrationTestSuite) getValidatorName() string { + return s.cfg.Genesis.Validators()[0].Name +} + +func (s *StandardSDKIntegrationTestSuite) getValidatorAccount() sdk.ValAddress { + record, err := s.cfg.Genesis.Keyring().Key(s.getValidatorName()) + s.Require().NoError(err) + address, err := record.GetAddress() + s.Require().NoError(err) + return sdk.ValAddress(address) +} + func (s *StandardSDKIntegrationTestSuite) TestStandardSDK() { t := s.T() type test struct { @@ -274,6 +287,25 @@ func (s *StandardSDKIntegrationTestSuite) TestStandardSDK() { }, expectedCode: abci.CodeTypeOK, }, + { + name: "try to upgrade the network version", + msgFunc: func() (msgs []sdk.Msg, signer string) { + account := s.unusedAccount() + addr := testfactory.GetAddress(s.cctx.Keyring, account) + msg := upgrade.NewMsgTryUpgrade(addr) + return []sdk.Msg{msg}, account + }, + expectedCode: abci.CodeTypeOK, + }, + { + name: "signal a version change", + msgFunc: func() (msgs []sdk.Msg, signer string) { + valAccount := s.getValidatorAccount() + msg := upgrade.NewMsgSignalVersion(valAccount, 2) + return []sdk.Msg{msg}, s.getValidatorName() + }, + expectedCode: abci.CodeTypeOK, + }, } // sign and submit the transactions diff --git a/test/util/genesis/genesis.go b/test/util/genesis/genesis.go index 560d20291d..f748da45f5 100644 --- a/test/util/genesis/genesis.go +++ b/test/util/genesis/genesis.go @@ -7,7 +7,6 @@ import ( "github.com/celestiaorg/celestia-app/app" "github.com/celestiaorg/celestia-app/app/encoding" - "github.com/celestiaorg/celestia-app/pkg/appconsts" "github.com/cosmos/cosmos-sdk/crypto/hd" "github.com/cosmos/cosmos-sdk/crypto/keyring" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" @@ -50,7 +49,7 @@ func NewDefaultGenesis() *Genesis { ecfg := encoding.MakeConfig(app.ModuleBasics) g := &Genesis{ ecfg: ecfg, - ConsensusParams: DefaultConsensusParams(), + ConsensusParams: app.DefaultConsensusParams(), ChainID: tmrand.Str(6), GenesisTime: time.Now(), kr: keyring.NewInMemory(ecfg.Codec), @@ -203,10 +202,3 @@ func (g *Genesis) Validator(i int) (Validator, bool) { } return Validator{}, false } - -func DefaultConsensusParams() *tmproto.ConsensusParams { - cparams := coretypes.DefaultConsensusParams() - cparams.Block.TimeIotaMs = 1 - cparams.Block.MaxBytes = appconsts.DefaultMaxBytes - return cparams -} diff --git a/test/util/testnode/config.go b/test/util/testnode/config.go index 0f8f1f4504..f2e3fc341f 100644 --- a/test/util/testnode/config.go +++ b/test/util/testnode/config.go @@ -9,7 +9,6 @@ import ( srvconfig "github.com/cosmos/cosmos-sdk/server/config" srvtypes "github.com/cosmos/cosmos-sdk/server/types" tmconfig "github.com/tendermint/tendermint/config" - tmrand "github.com/tendermint/tendermint/libs/rand" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" "github.com/tendermint/tendermint/types" ) @@ -114,10 +113,6 @@ func DefaultConfig() *Config { return cfg. WithGenesis( genesis.NewDefaultGenesis(). - WithChainID(tmrand.Str(6)). - WithGenesisTime(time.Now()). - WithConsensusParams(DefaultConsensusParams()). - WithModifiers(). WithValidators(genesis.NewDefaultValidator(DefaultValidatorAccountName)), ). WithTendermintConfig(DefaultTendermintConfig()). From 2e29003d9339a2d0adf5586200d3504a4baf0520 Mon Sep 17 00:00:00 2001 From: Callum Waters Date: Thu, 7 Dec 2023 22:53:53 +0100 Subject: [PATCH 9/9] fix tests --- app/test/integration_test.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/test/integration_test.go b/app/test/integration_test.go index ba2d1ab516..bfb2b2574d 100644 --- a/app/test/integration_test.go +++ b/app/test/integration_test.go @@ -21,7 +21,6 @@ import ( "github.com/celestiaorg/celestia-app/app" "github.com/celestiaorg/celestia-app/app/encoding" "github.com/celestiaorg/celestia-app/pkg/appconsts" - v2 "github.com/celestiaorg/celestia-app/pkg/appconsts/v2" "github.com/celestiaorg/celestia-app/pkg/blob" "github.com/celestiaorg/celestia-app/pkg/da" appns "github.com/celestiaorg/celestia-app/pkg/namespace" @@ -169,7 +168,7 @@ func (s *IntegrationTestSuite) TestMaxBlockSize() { require.LessOrEqual(t, size, uint64(appconsts.DefaultGovMaxSquareSize)) require.GreaterOrEqual(t, size, uint64(appconsts.MinSquareSize)) - require.EqualValues(t, v2.Version, blockRes.Block.Header.Version.App) + require.EqualValues(t, app.DefaultInitialVersion, blockRes.Block.Header.Version.App) sizes = append(sizes, size) ExtendBlockTest(t, blockRes.Block) @@ -238,7 +237,7 @@ func (s *IntegrationTestSuite) TestShareInclusionProof() { blockRes, err := node.Block(context.Background(), &txResp.Height) require.NoError(t, err) - require.EqualValues(t, v2.Version, blockRes.Block.Header.Version.App) + require.EqualValues(t, app.DefaultInitialVersion, blockRes.Block.Header.Version.App) _, isBlobTx := coretypes.UnmarshalBlobTx(blockRes.Block.Txs[txResp.Index]) require.True(t, isBlobTx)