From de840e8decb545ede9c05633c198c50495cdb053 Mon Sep 17 00:00:00 2001 From: Redouane Lakrache Date: Mon, 26 Aug 2024 20:20:01 +0200 Subject: [PATCH] [Application] Implement unbonding period (#735) ## Summary This PR adds an unbonding period when an `Application` submits an unstaking transaction. * Adds an unbondingHeight to the `Application` proto. * Resets the unbondingHeight when an `Application` re-stakes during that period. * Prevents unstaking when an `Application` is already in an unbonding period. * Adds an EndBlocker routine to unbond `Application`s that reached their unbonding height. * Deactivate the `Application`, denying it to get/construct `Session`s after the session corresponding to the unstake message ends. ## Issue `Application`s unstaking needs to be delayed at least until the accounting for the last `Session` is settled. This is to avoid `Application`s from prematurely unstaking and getting their funds back, before their stake is burned for the work requested. - #696 ## Type of change Select one or more: - [x] New feature, functionality or library - [ ] Bug fix - [ ] Code health or cleanup - [ ] Documentation - [ ] Other (specify) ## Testing **Documentation changes** (only if making doc changes) - [ ] `make docusaurus_start`; only needed if you make doc changes **Local Testing** (only if making code changes) - [x] **Unit Tests**: `make go_develop_and_test` - [x] **LocalNet E2E Tests**: `make test_e2e` - See [quickstart guide](https://dev.poktroll.com/developer_guide/quickstart) for instructions **PR Testing** (only if making code changes) - [ ] **DevNet E2E Tests**: Add the `devnet-test-e2e` label to the PR. - **THIS IS VERY EXPENSIVE**, so only do it after all the reviews are complete. - Optionally run `make trigger_ci` if you want to re-trigger tests without any code changes - If tests fail, try re-running failed tests only using the GitHub UI as shown [here](https://github.com/pokt-network/poktroll/assets/1892194/607984e9-0615-4569-9452-4c730190c1d2) ## Sanity Checklist - [x] I have tested my changes using the available tooling - [x] I have commented my code - [x] I have performed a self-review of my own code; both comments & source code - [ ] I create and reference any new tickets, if applicable - [ ] I have left TODOs throughout the codebase, if applicable ## Summary by CodeRabbit - **New Features** - Added the ability to track the end height of unstaking sessions for applications. - Introduced a new parameter to configure the number of unbonding sessions applications must wait after unstaking. - **Bug Fixes** - Enhanced operational checks in the unstaking process to improve reliability. - **Documentation** - Updated test scenarios for clarity regarding application unbonding and staking processes. - **Tests** - Expanded the test suite to cover new unbonding functionality and parameter updates. --- api/poktroll/application/types.pulsar.go | 118 ++++++++++--- api/poktroll/shared/params.pulsar.go | 113 +++++++++--- e2e/tests/init_test.go | 62 ++++++- e2e/tests/parse_params_test.go | 2 + e2e/tests/stake_app.feature | 7 +- e2e/tests/stake_supplier.feature | 2 +- e2e/tests/update_params.feature | 44 ++--- e2e/tests/update_params_test.go | 5 + proto/poktroll/application/types.proto | 13 +- proto/poktroll/shared/params.proto | 5 + testutil/keeper/application.go | 36 +++- .../keeper/msg_server_stake_application.go | 3 + .../keeper/msg_server_unstake_application.go | 31 ++-- .../msg_server_unstake_application_test.go | 167 +++++++++++++++--- x/application/keeper/unbond_applications.go | 67 +++++++ x/application/module/abci.go | 4 + x/application/types/application.go | 34 ++++ x/application/types/errors.go | 1 + x/application/types/types.pb.go | 101 +++++++---- x/session/keeper/session_hydrator.go | 10 ++ x/session/types/errors.go | 1 + x/shared/keeper/msg_server_update_param.go | 7 + .../keeper/msg_server_update_param_test.go | 104 +++++++++-- x/shared/types/genesis_test.go | 5 +- x/shared/types/message_update_param.go | 3 +- x/shared/types/params.go | 109 +++++++++--- x/shared/types/params.pb.go | 100 ++++++++--- x/shared/types/params_test.go | 35 ++++ x/tokenomics/types/tx.pb.go | 1 + 29 files changed, 954 insertions(+), 236 deletions(-) create mode 100644 x/application/keeper/unbond_applications.go create mode 100644 x/application/types/application.go diff --git a/api/poktroll/application/types.pulsar.go b/api/poktroll/application/types.pulsar.go index 89f1d355d..ac359e285 100644 --- a/api/poktroll/application/types.pulsar.go +++ b/api/poktroll/application/types.pulsar.go @@ -211,6 +211,7 @@ var ( fd_Application_service_configs protoreflect.FieldDescriptor fd_Application_delegatee_gateway_addresses protoreflect.FieldDescriptor fd_Application_pending_undelegations protoreflect.FieldDescriptor + fd_Application_unstake_session_end_height protoreflect.FieldDescriptor ) func init() { @@ -221,6 +222,7 @@ func init() { fd_Application_service_configs = md_Application.Fields().ByName("service_configs") fd_Application_delegatee_gateway_addresses = md_Application.Fields().ByName("delegatee_gateway_addresses") fd_Application_pending_undelegations = md_Application.Fields().ByName("pending_undelegations") + fd_Application_unstake_session_end_height = md_Application.Fields().ByName("unstake_session_end_height") } var _ protoreflect.Message = (*fastReflection_Application)(nil) @@ -318,6 +320,12 @@ func (x *fastReflection_Application) Range(f func(protoreflect.FieldDescriptor, return } } + if x.UnstakeSessionEndHeight != uint64(0) { + value := protoreflect.ValueOfUint64(x.UnstakeSessionEndHeight) + if !f(fd_Application_unstake_session_end_height, value) { + return + } + } } // Has reports whether a field is populated. @@ -343,6 +351,8 @@ func (x *fastReflection_Application) Has(fd protoreflect.FieldDescriptor) bool { return len(x.DelegateeGatewayAddresses) != 0 case "poktroll.application.Application.pending_undelegations": return len(x.PendingUndelegations) != 0 + case "poktroll.application.Application.unstake_session_end_height": + return x.UnstakeSessionEndHeight != uint64(0) default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: poktroll.application.Application")) @@ -369,6 +379,8 @@ func (x *fastReflection_Application) Clear(fd protoreflect.FieldDescriptor) { x.DelegateeGatewayAddresses = nil case "poktroll.application.Application.pending_undelegations": x.PendingUndelegations = nil + case "poktroll.application.Application.unstake_session_end_height": + x.UnstakeSessionEndHeight = uint64(0) default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: poktroll.application.Application")) @@ -409,6 +421,9 @@ func (x *fastReflection_Application) Get(descriptor protoreflect.FieldDescriptor } mapValue := &_Application_5_map{m: &x.PendingUndelegations} return protoreflect.ValueOfMap(mapValue) + case "poktroll.application.Application.unstake_session_end_height": + value := x.UnstakeSessionEndHeight + return protoreflect.ValueOfUint64(value) default: if descriptor.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: poktroll.application.Application")) @@ -445,6 +460,8 @@ func (x *fastReflection_Application) Set(fd protoreflect.FieldDescriptor, value mv := value.Map() cmv := mv.(*_Application_5_map) x.PendingUndelegations = *cmv.m + case "poktroll.application.Application.unstake_session_end_height": + x.UnstakeSessionEndHeight = value.Uint() default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: poktroll.application.Application")) @@ -490,6 +507,8 @@ func (x *fastReflection_Application) Mutable(fd protoreflect.FieldDescriptor) pr return protoreflect.ValueOfMap(value) case "poktroll.application.Application.address": panic(fmt.Errorf("field address of message poktroll.application.Application is not mutable")) + case "poktroll.application.Application.unstake_session_end_height": + panic(fmt.Errorf("field unstake_session_end_height of message poktroll.application.Application is not mutable")) default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: poktroll.application.Application")) @@ -517,6 +536,8 @@ func (x *fastReflection_Application) NewField(fd protoreflect.FieldDescriptor) p case "poktroll.application.Application.pending_undelegations": m := make(map[uint64]*UndelegatingGatewayList) return protoreflect.ValueOfMap(&_Application_5_map{m: &m}) + case "poktroll.application.Application.unstake_session_end_height": + return protoreflect.ValueOfUint64(uint64(0)) default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: poktroll.application.Application")) @@ -634,6 +655,9 @@ func (x *fastReflection_Application) ProtoMethods() *protoiface.Methods { } } } + if x.UnstakeSessionEndHeight != 0 { + n += 1 + runtime.Sov(uint64(x.UnstakeSessionEndHeight)) + } if x.unknownFields != nil { n += len(x.unknownFields) } @@ -663,6 +687,11 @@ func (x *fastReflection_Application) ProtoMethods() *protoiface.Methods { i -= len(x.unknownFields) copy(dAtA[i:], x.unknownFields) } + if x.UnstakeSessionEndHeight != 0 { + i = runtime.EncodeVarint(dAtA, i, uint64(x.UnstakeSessionEndHeight)) + i-- + dAtA[i] = 0x30 + } if len(x.PendingUndelegations) > 0 { MaRsHaLmAp := func(k uint64, v *UndelegatingGatewayList) (protoiface.MarshalOutput, error) { baseI := i @@ -1055,6 +1084,25 @@ func (x *fastReflection_Application) ProtoMethods() *protoiface.Methods { } x.PendingUndelegations[mapkey] = mapvalue iNdEx = postIndex + case 6: + if wireType != 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field UnstakeSessionEndHeight", wireType) + } + x.UnstakeSessionEndHeight = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow + } + if iNdEx >= l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + x.UnstakeSessionEndHeight |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := runtime.Skip(dAtA[iNdEx:]) @@ -1606,6 +1654,9 @@ type Application struct { // TODO_DOCUMENT(@red-0ne): Need to document the flow from this comment // so its clear to everyone why this is necessary; https://github.com/pokt-network/poktroll/issues/476#issuecomment-2052639906. PendingUndelegations map[uint64]*UndelegatingGatewayList `protobuf:"bytes,5,rep,name=pending_undelegations,json=pendingUndelegations,proto3" json:"pending_undelegations,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + // The end height of the session at which an application initiated its unstaking process. + // If the application did not unstake, this value will be 0. + UnstakeSessionEndHeight uint64 `protobuf:"varint,6,opt,name=unstake_session_end_height,json=unstakeSessionEndHeight,proto3" json:"unstake_session_end_height,omitempty"` } func (x *Application) Reset() { @@ -1663,6 +1714,13 @@ func (x *Application) GetPendingUndelegations() map[uint64]*UndelegatingGatewayL return nil } +func (x *Application) GetUnstakeSessionEndHeight() uint64 { + if x != nil { + return x.UnstakeSessionEndHeight + } + return 0 +} + // UndelegatingGatewayList is used as the Value of `pending_undelegations`. // It is required to store a repeated list of strings as a map value. type UndelegatingGatewayList struct { @@ -1713,7 +1771,7 @@ var file_poktroll_application_types_proto_rawDesc = []byte{ 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1d, 0x70, 0x6f, 0x6b, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x2f, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, - 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x94, 0x04, 0x0a, 0x0b, 0x41, 0x70, 0x70, + 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xd1, 0x04, 0x0a, 0x0b, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x32, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x18, 0xd2, 0xb4, 0x2d, 0x14, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x53, 0x74, 0x72, @@ -1739,33 +1797,37 @@ var file_poktroll_application_types_proto_rawDesc = []byte{ 0x6e, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x55, 0x6e, 0x64, 0x65, 0x6c, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x42, 0x04, 0xc8, 0xde, 0x1f, 0x00, 0x52, 0x14, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x55, 0x6e, 0x64, 0x65, 0x6c, 0x65, - 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x1a, 0x76, 0x0a, 0x19, 0x50, 0x65, 0x6e, 0x64, 0x69, - 0x6e, 0x67, 0x55, 0x6e, 0x64, 0x65, 0x6c, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x45, - 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x04, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x43, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x70, 0x6f, 0x6b, 0x74, 0x72, 0x6f, 0x6c, 0x6c, - 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x55, 0x6e, 0x64, - 0x65, 0x6c, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, - 0x4c, 0x69, 0x73, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, - 0x64, 0x0a, 0x17, 0x55, 0x6e, 0x64, 0x65, 0x6c, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x47, - 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x49, 0x0a, 0x11, 0x67, 0x61, - 0x74, 0x65, 0x77, 0x61, 0x79, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, - 0x02, 0x20, 0x03, 0x28, 0x09, 0x42, 0x1c, 0xc8, 0xde, 0x1f, 0x00, 0xd2, 0xb4, 0x2d, 0x14, 0x63, - 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x53, 0x74, 0x72, - 0x69, 0x6e, 0x67, 0x52, 0x10, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x41, 0x64, 0x64, 0x72, - 0x65, 0x73, 0x73, 0x65, 0x73, 0x42, 0xbe, 0x01, 0x0a, 0x18, 0x63, 0x6f, 0x6d, 0x2e, 0x70, 0x6f, - 0x6b, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x42, 0x0a, 0x54, 0x79, 0x70, 0x65, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, - 0x5a, 0x25, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x73, 0x64, 0x6b, 0x2e, 0x69, 0x6f, 0x2f, 0x61, - 0x70, 0x69, 0x2f, 0x70, 0x6f, 0x6b, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x2f, 0x61, 0x70, 0x70, 0x6c, - 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0xa2, 0x02, 0x03, 0x50, 0x41, 0x58, 0xaa, 0x02, 0x14, - 0x50, 0x6f, 0x6b, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0xca, 0x02, 0x14, 0x50, 0x6f, 0x6b, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x5c, - 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0xe2, 0x02, 0x20, 0x50, 0x6f, - 0x6b, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x5c, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, - 0x15, 0x50, 0x6f, 0x6b, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x3a, 0x3a, 0x41, 0x70, 0x70, 0x6c, 0x69, - 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x3b, 0x0a, 0x1a, 0x75, 0x6e, 0x73, 0x74, 0x61, + 0x6b, 0x65, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x6e, 0x64, 0x5f, 0x68, + 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x17, 0x75, 0x6e, 0x73, + 0x74, 0x61, 0x6b, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x45, 0x6e, 0x64, 0x48, 0x65, + 0x69, 0x67, 0x68, 0x74, 0x1a, 0x76, 0x0a, 0x19, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x55, + 0x6e, 0x64, 0x65, 0x6c, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x03, + 0x6b, 0x65, 0x79, 0x12, 0x43, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x70, 0x6f, 0x6b, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x2e, 0x61, 0x70, + 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x55, 0x6e, 0x64, 0x65, 0x6c, 0x65, + 0x67, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x47, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x4c, 0x69, 0x73, + 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x64, 0x0a, 0x17, + 0x55, 0x6e, 0x64, 0x65, 0x6c, 0x65, 0x67, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x47, 0x61, 0x74, 0x65, + 0x77, 0x61, 0x79, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x49, 0x0a, 0x11, 0x67, 0x61, 0x74, 0x65, 0x77, + 0x61, 0x79, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, + 0x28, 0x09, 0x42, 0x1c, 0xc8, 0xde, 0x1f, 0x00, 0xd2, 0xb4, 0x2d, 0x14, 0x63, 0x6f, 0x73, 0x6d, + 0x6f, 0x73, 0x2e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, + 0x52, 0x10, 0x67, 0x61, 0x74, 0x65, 0x77, 0x61, 0x79, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x65, 0x73, 0x42, 0xbe, 0x01, 0x0a, 0x18, 0x63, 0x6f, 0x6d, 0x2e, 0x70, 0x6f, 0x6b, 0x74, 0x72, + 0x6f, 0x6c, 0x6c, 0x2e, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, + 0x0a, 0x54, 0x79, 0x70, 0x65, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x25, 0x63, + 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x73, 0x64, 0x6b, 0x2e, 0x69, 0x6f, 0x2f, 0x61, 0x70, 0x69, 0x2f, + 0x70, 0x6f, 0x6b, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x2f, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0xa2, 0x02, 0x03, 0x50, 0x41, 0x58, 0xaa, 0x02, 0x14, 0x50, 0x6f, 0x6b, + 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x2e, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0xca, 0x02, 0x14, 0x50, 0x6f, 0x6b, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x5c, 0x41, 0x70, 0x70, + 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0xe2, 0x02, 0x20, 0x50, 0x6f, 0x6b, 0x74, 0x72, + 0x6f, 0x6c, 0x6c, 0x5c, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5c, + 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x15, 0x50, 0x6f, + 0x6b, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x3a, 0x3a, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/api/poktroll/shared/params.pulsar.go b/api/poktroll/shared/params.pulsar.go index 0982420ec..38e93da7d 100644 --- a/api/poktroll/shared/params.pulsar.go +++ b/api/poktroll/shared/params.pulsar.go @@ -15,14 +15,15 @@ import ( ) var ( - md_Params protoreflect.MessageDescriptor - fd_Params_num_blocks_per_session protoreflect.FieldDescriptor - fd_Params_grace_period_end_offset_blocks protoreflect.FieldDescriptor - fd_Params_claim_window_open_offset_blocks protoreflect.FieldDescriptor - fd_Params_claim_window_close_offset_blocks protoreflect.FieldDescriptor - fd_Params_proof_window_open_offset_blocks protoreflect.FieldDescriptor - fd_Params_proof_window_close_offset_blocks protoreflect.FieldDescriptor - fd_Params_supplier_unbonding_period_sessions protoreflect.FieldDescriptor + md_Params protoreflect.MessageDescriptor + fd_Params_num_blocks_per_session protoreflect.FieldDescriptor + fd_Params_grace_period_end_offset_blocks protoreflect.FieldDescriptor + fd_Params_claim_window_open_offset_blocks protoreflect.FieldDescriptor + fd_Params_claim_window_close_offset_blocks protoreflect.FieldDescriptor + fd_Params_proof_window_open_offset_blocks protoreflect.FieldDescriptor + fd_Params_proof_window_close_offset_blocks protoreflect.FieldDescriptor + fd_Params_supplier_unbonding_period_sessions protoreflect.FieldDescriptor + fd_Params_application_unbonding_period_sessions protoreflect.FieldDescriptor ) func init() { @@ -35,6 +36,7 @@ func init() { fd_Params_proof_window_open_offset_blocks = md_Params.Fields().ByName("proof_window_open_offset_blocks") fd_Params_proof_window_close_offset_blocks = md_Params.Fields().ByName("proof_window_close_offset_blocks") fd_Params_supplier_unbonding_period_sessions = md_Params.Fields().ByName("supplier_unbonding_period_sessions") + fd_Params_application_unbonding_period_sessions = md_Params.Fields().ByName("application_unbonding_period_sessions") } var _ protoreflect.Message = (*fastReflection_Params)(nil) @@ -144,6 +146,12 @@ func (x *fastReflection_Params) Range(f func(protoreflect.FieldDescriptor, proto return } } + if x.ApplicationUnbondingPeriodSessions != uint64(0) { + value := protoreflect.ValueOfUint64(x.ApplicationUnbondingPeriodSessions) + if !f(fd_Params_application_unbonding_period_sessions, value) { + return + } + } } // Has reports whether a field is populated. @@ -173,6 +181,8 @@ func (x *fastReflection_Params) Has(fd protoreflect.FieldDescriptor) bool { return x.ProofWindowCloseOffsetBlocks != uint64(0) case "poktroll.shared.Params.supplier_unbonding_period_sessions": return x.SupplierUnbondingPeriodSessions != uint64(0) + case "poktroll.shared.Params.application_unbonding_period_sessions": + return x.ApplicationUnbondingPeriodSessions != uint64(0) default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: poktroll.shared.Params")) @@ -203,6 +213,8 @@ func (x *fastReflection_Params) Clear(fd protoreflect.FieldDescriptor) { x.ProofWindowCloseOffsetBlocks = uint64(0) case "poktroll.shared.Params.supplier_unbonding_period_sessions": x.SupplierUnbondingPeriodSessions = uint64(0) + case "poktroll.shared.Params.application_unbonding_period_sessions": + x.ApplicationUnbondingPeriodSessions = uint64(0) default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: poktroll.shared.Params")) @@ -240,6 +252,9 @@ func (x *fastReflection_Params) Get(descriptor protoreflect.FieldDescriptor) pro case "poktroll.shared.Params.supplier_unbonding_period_sessions": value := x.SupplierUnbondingPeriodSessions return protoreflect.ValueOfUint64(value) + case "poktroll.shared.Params.application_unbonding_period_sessions": + value := x.ApplicationUnbondingPeriodSessions + return protoreflect.ValueOfUint64(value) default: if descriptor.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: poktroll.shared.Params")) @@ -274,6 +289,8 @@ func (x *fastReflection_Params) Set(fd protoreflect.FieldDescriptor, value proto x.ProofWindowCloseOffsetBlocks = value.Uint() case "poktroll.shared.Params.supplier_unbonding_period_sessions": x.SupplierUnbondingPeriodSessions = value.Uint() + case "poktroll.shared.Params.application_unbonding_period_sessions": + x.ApplicationUnbondingPeriodSessions = value.Uint() default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: poktroll.shared.Params")) @@ -308,6 +325,8 @@ func (x *fastReflection_Params) Mutable(fd protoreflect.FieldDescriptor) protore panic(fmt.Errorf("field proof_window_close_offset_blocks of message poktroll.shared.Params is not mutable")) case "poktroll.shared.Params.supplier_unbonding_period_sessions": panic(fmt.Errorf("field supplier_unbonding_period_sessions of message poktroll.shared.Params is not mutable")) + case "poktroll.shared.Params.application_unbonding_period_sessions": + panic(fmt.Errorf("field application_unbonding_period_sessions of message poktroll.shared.Params is not mutable")) default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: poktroll.shared.Params")) @@ -335,6 +354,8 @@ func (x *fastReflection_Params) NewField(fd protoreflect.FieldDescriptor) protor return protoreflect.ValueOfUint64(uint64(0)) case "poktroll.shared.Params.supplier_unbonding_period_sessions": return protoreflect.ValueOfUint64(uint64(0)) + case "poktroll.shared.Params.application_unbonding_period_sessions": + return protoreflect.ValueOfUint64(uint64(0)) default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: poktroll.shared.Params")) @@ -425,6 +446,9 @@ func (x *fastReflection_Params) ProtoMethods() *protoiface.Methods { if x.SupplierUnbondingPeriodSessions != 0 { n += 1 + runtime.Sov(uint64(x.SupplierUnbondingPeriodSessions)) } + if x.ApplicationUnbondingPeriodSessions != 0 { + n += 1 + runtime.Sov(uint64(x.ApplicationUnbondingPeriodSessions)) + } if x.unknownFields != nil { n += len(x.unknownFields) } @@ -454,6 +478,11 @@ func (x *fastReflection_Params) ProtoMethods() *protoiface.Methods { i -= len(x.unknownFields) copy(dAtA[i:], x.unknownFields) } + if x.ApplicationUnbondingPeriodSessions != 0 { + i = runtime.EncodeVarint(dAtA, i, uint64(x.ApplicationUnbondingPeriodSessions)) + i-- + dAtA[i] = 0x40 + } if x.SupplierUnbondingPeriodSessions != 0 { i = runtime.EncodeVarint(dAtA, i, uint64(x.SupplierUnbondingPeriodSessions)) i-- @@ -671,6 +700,25 @@ func (x *fastReflection_Params) ProtoMethods() *protoiface.Methods { break } } + case 8: + if wireType != 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field ApplicationUnbondingPeriodSessions", wireType) + } + x.ApplicationUnbondingPeriodSessions = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow + } + if iNdEx >= l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + x.ApplicationUnbondingPeriodSessions |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := runtime.Skip(dAtA[iNdEx:]) @@ -749,6 +797,11 @@ type Params struct { // On-chain business logic requires, and ensures, that the corresponding block count of the unbonding // period will exceed the end of any active claim & proof lifecycles. SupplierUnbondingPeriodSessions uint64 `protobuf:"varint,7,opt,name=supplier_unbonding_period_sessions,json=supplierUnbondingPeriodSessions,proto3" json:"supplier_unbonding_period_sessions,omitempty"` + // application_unbonding_period_sessions is the number of sessions that an application must wait after + // unstaking before their staked assets are moved to their account balance. + // On-chain business logic requires, and ensures, that the corresponding block count of the + // application unbonding period will exceed the end of its corresponding proof window close height. + ApplicationUnbondingPeriodSessions uint64 `protobuf:"varint,8,opt,name=application_unbonding_period_sessions,json=applicationUnbondingPeriodSessions,proto3" json:"application_unbonding_period_sessions,omitempty"` } func (x *Params) Reset() { @@ -820,6 +873,13 @@ func (x *Params) GetSupplierUnbondingPeriodSessions() uint64 { return 0 } +func (x *Params) GetApplicationUnbondingPeriodSessions() uint64 { + if x != nil { + return x.ApplicationUnbondingPeriodSessions + } + return 0 +} + var File_poktroll_shared_params_proto protoreflect.FileDescriptor var file_poktroll_shared_params_proto_rawDesc = []byte{ @@ -828,7 +888,7 @@ var file_poktroll_shared_params_proto_rawDesc = []byte{ 0x70, 0x6f, 0x6b, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x2e, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x1a, 0x11, 0x61, 0x6d, 0x69, 0x6e, 0x6f, 0x2f, 0x61, 0x6d, 0x69, 0x6e, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x14, 0x67, 0x6f, 0x67, 0x6f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x6f, - 0x67, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x8b, 0x06, 0x0a, 0x06, 0x50, 0x61, 0x72, + 0x67, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x89, 0x07, 0x0a, 0x06, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x12, 0x4f, 0x0a, 0x16, 0x6e, 0x75, 0x6d, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x73, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x42, 0x1a, 0xea, 0xde, 0x1f, 0x16, 0x6e, 0x75, 0x6d, 0x5f, 0x62, 0x6c, 0x6f, @@ -875,20 +935,27 @@ var file_poktroll_shared_params_proto_rawDesc = []byte{ 0x6e, 0x67, 0x5f, 0x70, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x1f, 0x73, 0x75, 0x70, 0x70, 0x6c, 0x69, 0x65, 0x72, 0x55, 0x6e, 0x62, 0x6f, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x53, 0x65, 0x73, 0x73, 0x69, - 0x6f, 0x6e, 0x73, 0x3a, 0x21, 0xe8, 0xa0, 0x1f, 0x01, 0x8a, 0xe7, 0xb0, 0x2a, 0x18, 0x70, 0x6f, - 0x6b, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x2f, 0x78, 0x2f, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x2f, - 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x42, 0xa1, 0x01, 0x0a, 0x13, 0x63, 0x6f, 0x6d, 0x2e, 0x70, - 0x6f, 0x6b, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x2e, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x42, 0x0b, - 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x20, 0x63, - 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x73, 0x64, 0x6b, 0x2e, 0x69, 0x6f, 0x2f, 0x61, 0x70, 0x69, 0x2f, - 0x70, 0x6f, 0x6b, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x2f, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0xa2, - 0x02, 0x03, 0x50, 0x53, 0x58, 0xaa, 0x02, 0x0f, 0x50, 0x6f, 0x6b, 0x74, 0x72, 0x6f, 0x6c, 0x6c, - 0x2e, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0xca, 0x02, 0x0f, 0x50, 0x6f, 0x6b, 0x74, 0x72, 0x6f, - 0x6c, 0x6c, 0x5c, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0xe2, 0x02, 0x1b, 0x50, 0x6f, 0x6b, 0x74, - 0x72, 0x6f, 0x6c, 0x6c, 0x5c, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x5c, 0x47, 0x50, 0x42, 0x4d, - 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x10, 0x50, 0x6f, 0x6b, 0x74, 0x72, 0x6f, - 0x6c, 0x6c, 0x3a, 0x3a, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x33, + 0x6f, 0x6e, 0x73, 0x12, 0x7c, 0x0a, 0x25, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x5f, 0x75, 0x6e, 0x62, 0x6f, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x65, 0x72, + 0x69, 0x6f, 0x64, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x08, 0x20, 0x01, + 0x28, 0x04, 0x42, 0x29, 0xea, 0xde, 0x1f, 0x25, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x75, 0x6e, 0x62, 0x6f, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x65, + 0x72, 0x69, 0x6f, 0x64, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x22, 0x61, + 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x6e, 0x62, 0x6f, 0x6e, 0x64, + 0x69, 0x6e, 0x67, 0x50, 0x65, 0x72, 0x69, 0x6f, 0x64, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x73, 0x3a, 0x21, 0xe8, 0xa0, 0x1f, 0x01, 0x8a, 0xe7, 0xb0, 0x2a, 0x18, 0x70, 0x6f, 0x6b, 0x74, + 0x72, 0x6f, 0x6c, 0x6c, 0x2f, 0x78, 0x2f, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x2f, 0x50, 0x61, + 0x72, 0x61, 0x6d, 0x73, 0x42, 0xa1, 0x01, 0x0a, 0x13, 0x63, 0x6f, 0x6d, 0x2e, 0x70, 0x6f, 0x6b, + 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x2e, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x42, 0x0b, 0x50, 0x61, + 0x72, 0x61, 0x6d, 0x73, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x20, 0x63, 0x6f, 0x73, + 0x6d, 0x6f, 0x73, 0x73, 0x64, 0x6b, 0x2e, 0x69, 0x6f, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x6f, + 0x6b, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x2f, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0xa2, 0x02, 0x03, + 0x50, 0x53, 0x58, 0xaa, 0x02, 0x0f, 0x50, 0x6f, 0x6b, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x2e, 0x53, + 0x68, 0x61, 0x72, 0x65, 0x64, 0xca, 0x02, 0x0f, 0x50, 0x6f, 0x6b, 0x74, 0x72, 0x6f, 0x6c, 0x6c, + 0x5c, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0xe2, 0x02, 0x1b, 0x50, 0x6f, 0x6b, 0x74, 0x72, 0x6f, + 0x6c, 0x6c, 0x5c, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, + 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x10, 0x50, 0x6f, 0x6b, 0x74, 0x72, 0x6f, 0x6c, 0x6c, + 0x3a, 0x3a, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/e2e/tests/init_test.go b/e2e/tests/init_test.go index f204a4ad5..2fcad22b7 100644 --- a/e2e/tests/init_test.go +++ b/e2e/tests/init_test.go @@ -501,7 +501,7 @@ func (s *suite) TheSupplierForAccountIsUnbonding(supplierOperatorName string) { require.True(s, supplier.IsUnbonding()) } -func (s *suite) TheUserWaitsForUnbondingPeriodToFinish(accName string) { +func (s *suite) TheUserWaitsForTheSupplierForAccountUnbondingPeriodToFinish(accName string) { _, ok := operatorAccNameToSupplierMap[accName] require.True(s, ok, "supplier %s not found", accName) @@ -509,6 +509,24 @@ func (s *suite) TheUserWaitsForUnbondingPeriodToFinish(accName string) { s.waitForBlockHeight(unbondingHeight + 1) // Add 1 to ensure the unbonding block has been committed } +func (s *suite) TheApplicationForAccountIsUnbonding(appName string) { + _, ok := accNameToAppMap[appName] + require.True(s, ok, "application %s not found", appName) + + s.waitForTxResultEvent(newEventMsgTypeMatchFn("application", "UnstakeApplication")) + + supplier := s.getApplicationInfo(appName) + require.True(s, supplier.IsUnbonding()) +} + +func (s *suite) TheUserWaitsForTheApplicationForAccountUnbondingPeriodToFinish(accName string) { + _, ok := accNameToAppMap[accName] + require.True(s, ok, "application %s not found", accName) + + unbondingHeight := s.getApplicationUnbondingHeight(accName) + s.waitForBlockHeight(unbondingHeight + 1) // Add 1 to ensure the unbonding block has been committed +} + func (s *suite) getStakedAmount(actorType, accName string) (int, bool) { s.Helper() args := []string{ @@ -724,6 +742,48 @@ func (s *suite) getSupplierUnbondingHeight(accName string) int64 { return unbondingHeight } +// getApplicationInfo returns the application information for a given application address. +func (s *suite) getApplicationInfo(appName string) *apptypes.Application { + appAddr := accNameToAddrMap[appName] + args := []string{ + "query", + "application", + "show-application", + appAddr, + "--output=json", + } + + res, err := s.pocketd.RunCommandOnHostWithRetry("", numQueryRetries, args...) + require.NoError(s, err, "error getting supplier %s", appAddr) + s.pocketd.result = res + + var resp apptypes.QueryGetApplicationResponse + responseBz := []byte(strings.TrimSpace(res.Stdout)) + s.cdc.MustUnmarshalJSON(responseBz, &resp) + return &resp.Application +} + +// getApplicationUnbondingHeight returns the height at which the application will be unbonded. +func (s *suite) getApplicationUnbondingHeight(accName string) int64 { + application := s.getApplicationInfo(accName) + + args := []string{ + "query", + "shared", + "params", + "--output=json", + } + + res, err := s.pocketd.RunCommandOnHostWithRetry("", numQueryRetries, args...) + require.NoError(s, err, "error getting shared module params") + + var resp sharedtypes.QueryParamsResponse + responseBz := []byte(strings.TrimSpace(res.Stdout)) + s.cdc.MustUnmarshalJSON(responseBz, &resp) + unbondingHeight := apptypes.GetApplicationUnbondingHeight(&resp.Params, application) + return unbondingHeight +} + // getServiceComputeUnitsPerRelay returns the compute units per relay for a given service ID func (s *suite) getServiceComputeUnitsPerRelay(serviceId string) uint64 { args := []string{ diff --git a/e2e/tests/parse_params_test.go b/e2e/tests/parse_params_test.go index 894478374..af6571fb5 100644 --- a/e2e/tests/parse_params_test.go +++ b/e2e/tests/parse_params_test.go @@ -172,6 +172,8 @@ func (s *suite) newSharedMsgUpdateParams(params paramsMap) cosmostypes.Msg { msgUpdateParams.Params.ProofWindowCloseOffsetBlocks = uint64(paramValue.value.(int64)) case sharedtypes.ParamSupplierUnbondingPeriodSessions: msgUpdateParams.Params.SupplierUnbondingPeriodSessions = uint64(paramValue.value.(int64)) + case sharedtypes.ParamApplicationUnbondingPeriodSessions: + msgUpdateParams.Params.ApplicationUnbondingPeriodSessions = uint64(paramValue.value.(int64)) default: s.Fatalf("ERROR: unexpected %q type param name %q", paramValue.typeStr, paramName) } diff --git a/e2e/tests/stake_app.feature b/e2e/tests/stake_app.feature index e0b29b03e..0ea08d7b5 100644 --- a/e2e/tests/stake_app.feature +++ b/e2e/tests/stake_app.feature @@ -1,10 +1,8 @@ Feature: Stake App Namespaces - Scenario: User can stake an Application + Scenario: User can stake an Application waiting for it to unbond Given the user has the pocketd binary installed And the user verifies the "application" for account "app2" is not staked - # Stake with 1 uPOKT more than the current stake used in genesis to make - # the transaction succeed. And the account "app2" has a balance greater than "1000070" uPOKT When the user stakes a "application" with "1000070" uPOKT for "anvil" service from the account "app2" Then the user should be able to see standard output containing "txhash:" @@ -22,6 +20,7 @@ Feature: Stake App Namespaces Then the user should be able to see standard output containing "txhash:" And the user should be able to see standard output containing "code: 0" And the pocketd binary should exit without error - And the user should wait for the "application" module "UnstakeApplication" message to be submitted + And the application for account "app2" is unbonding + When the user waits for the application for account "app2" unbonding period to finish And the user verifies the "application" for account "app2" is not staked And the account balance of "app2" should be "1000070" uPOKT "more" than before \ No newline at end of file diff --git a/e2e/tests/stake_supplier.feature b/e2e/tests/stake_supplier.feature index 5adedc42b..8a9aff15d 100644 --- a/e2e/tests/stake_supplier.feature +++ b/e2e/tests/stake_supplier.feature @@ -21,7 +21,7 @@ Feature: Stake Supplier Namespace And the user should be able to see standard output containing "code: 0" And the pocketd binary should exit without error And the supplier for account "supplier2" is unbonding - When the user waits for "supplier2" unbonding period to finish + When the user waits for the supplier for account "supplier2" unbonding period to finish Then the user verifies the "supplier" for account "supplier2" is not staked And the account balance of "supplier2" should be "1000070" uPOKT "more" than before diff --git a/e2e/tests/update_params.feature b/e2e/tests/update_params.feature index 862d08d8f..32297eaac 100644 --- a/e2e/tests/update_params.feature +++ b/e2e/tests/update_params.feature @@ -51,14 +51,15 @@ Feature: Params Namespace And all "shared" module params are set to their default values And an authz grant from the "gov" "module" account to the "pnf" "user" account for the "/poktroll.shared.MsgUpdateParams" message exists When the "pnf" account sends an authz exec message to update all "shared" module params - | name | value | type | - | num_blocks_per_session | 5 | int64 | - | grace_period_end_offset_blocks | 2 | int64 | - | claim_window_open_offset_blocks | 2 | int64 | - | claim_window_close_offset_blocks | 3 | int64 | - | proof_window_open_offset_blocks | 1 | int64 | - | proof_window_close_offset_blocks | 5 | int64 | - | supplier_unbonding_period_sessions | 5 | int64 | + | name | value | type | + | num_blocks_per_session | 5 | int64 | + | grace_period_end_offset_blocks | 2 | int64 | + | claim_window_open_offset_blocks | 2 | int64 | + | claim_window_close_offset_blocks | 3 | int64 | + | proof_window_open_offset_blocks | 1 | int64 | + | proof_window_close_offset_blocks | 5 | int64 | + | supplier_unbonding_period_sessions | 5 | int64 | + | application_unbonding_period_sessions | 5 | int64 | Then all "shared" module params should be updated # NB: If you are reading this and any module has parameters that @@ -73,19 +74,20 @@ Feature: Params Namespace Then the "" module param "" should be updated Examples: - | module | message_type | param_name | param_value | param_type | - | tokenomics | /poktroll.tokenomics.MsgUpdateParam | compute_units_to_tokens_multiplier | 68 | int64 | - | proof | /poktroll.proof.MsgUpdateParam | min_relay_difficulty_bits | 12 | int64 | - | proof | /poktroll.proof.MsgUpdateParam | proof_request_probability | 0.1 | float | - | proof | /poktroll.proof.MsgUpdateParam | proof_requirement_threshold | 100 | int64 | - | proof | /poktroll.proof.MsgUpdateParam | proof_missing_penalty | 500 | coin | - | shared | /poktroll.shared.MsgUpdateParam | num_blocks_per_session | 5 | int64 | - | shared | /poktroll.shared.MsgUpdateParam | grace_period_end_offset_blocks | 2 | int64 | - | shared | /poktroll.shared.MsgUpdateParam | claim_window_open_offset_blocks | 2 | int64 | - | shared | /poktroll.shared.MsgUpdateParam | claim_window_close_offset_blocks | 3 | int64 | - | shared | /poktroll.shared.MsgUpdateParam | proof_window_open_offset_blocks | 1 | int64 | - | shared | /poktroll.shared.MsgUpdateParam | proof_window_close_offset_blocks | 5 | int64 | - | shared | /poktroll.shared.MsgUpdateParam | supplier_unbonding_period_sessions | 5 | int64 | + | module | message_type | param_name | param_value | param_type | + | tokenomics | /poktroll.tokenomics.MsgUpdateParam | compute_units_to_tokens_multiplier | 68 | int64 | + | proof | /poktroll.proof.MsgUpdateParam | min_relay_difficulty_bits | 12 | int64 | + | proof | /poktroll.proof.MsgUpdateParam | proof_request_probability | 0.1 | float | + | proof | /poktroll.proof.MsgUpdateParam | proof_requirement_threshold | 100 | int64 | + | proof | /poktroll.proof.MsgUpdateParam | proof_missing_penalty | 500 | coin | + | shared | /poktroll.shared.MsgUpdateParam | num_blocks_per_session | 5 | int64 | + | shared | /poktroll.shared.MsgUpdateParam | grace_period_end_offset_blocks | 2 | int64 | + | shared | /poktroll.shared.MsgUpdateParam | claim_window_open_offset_blocks | 2 | int64 | + | shared | /poktroll.shared.MsgUpdateParam | claim_window_close_offset_blocks | 3 | int64 | + | shared | /poktroll.shared.MsgUpdateParam | proof_window_open_offset_blocks | 1 | int64 | + | shared | /poktroll.shared.MsgUpdateParam | proof_window_close_offset_blocks | 5 | int64 | + | shared | /poktroll.shared.MsgUpdateParam | supplier_unbonding_period_sessions | 5 | int64 | + | shared | /poktroll.shared.MsgUpdateParam | application_unbonding_period_sessions | 5 | int64 | Scenario: An unauthorized user cannot update individual module params Given the user has the pocketd binary installed diff --git a/e2e/tests/update_params_test.go b/e2e/tests/update_params_test.go index b17f40bb2..ddc4762e7 100644 --- a/e2e/tests/update_params_test.go +++ b/e2e/tests/update_params_test.go @@ -436,6 +436,11 @@ func (s *suite) assertExpectedModuleParamsUpdated(moduleName string) { params.SupplierUnbondingPeriodSessions = uint64(supplierUnbondingPeriodSessions.value.(int64)) } + applicationUnbondingPeriodSessions, ok := paramsMap[sharedtypes.ParamApplicationUnbondingPeriodSessions] + if ok { + params.ApplicationUnbondingPeriodSessions = uint64(applicationUnbondingPeriodSessions.value.(int64)) + } + assertUpdatedParams(s, []byte(res.Stdout), &sharedtypes.QueryParamsResponse{ diff --git a/proto/poktroll/application/types.proto b/proto/poktroll/application/types.proto index f2fc18f0f..e55f2e303 100644 --- a/proto/poktroll/application/types.proto +++ b/proto/poktroll/application/types.proto @@ -21,13 +21,16 @@ message Application { // TODO_BETA: Rename `delegatee_gateway_addresses` to `gateway_addresses_delegated_to`. // Ensure to rename all relevant configs, comments, variables, function names, etc as well. repeated string delegatee_gateway_addresses = 4 [(cosmos_proto.scalar) = "cosmos.AddressString", (gogoproto.nullable) = false]; // The Bech32 encoded addresses for all delegatee Gateways, in a non-nullable slice - // A map from sessionEndHeights to a list of Gateways. - // The key is the height of the last block of the session during which the - // respective undelegation was committed. - // The value is a list of gateways being undelegated from. - // TODO_DOCUMENT(@red-0ne): Need to document the flow from this comment + // A map from sessionEndHeights to a list of Gateways. + // The key is the height of the last block of the session during which the + // respective undelegation was committed. + // The value is a list of gateways being undelegated from. + // TODO_DOCUMENT(@red-0ne): Need to document the flow from this comment // so its clear to everyone why this is necessary; https://github.com/pokt-network/poktroll/issues/476#issuecomment-2052639906. map pending_undelegations = 5 [(gogoproto.nullable) = false]; + // The end height of the session at which an application initiated its unstaking process. + // If the application did not unstake, this value will be 0. + uint64 unstake_session_end_height = 6; } // UndelegatingGatewayList is used as the Value of `pending_undelegations`. diff --git a/proto/poktroll/shared/params.proto b/proto/poktroll/shared/params.proto index 9e24b959f..1feeb2941 100644 --- a/proto/poktroll/shared/params.proto +++ b/proto/poktroll/shared/params.proto @@ -35,4 +35,9 @@ message Params { // On-chain business logic requires, and ensures, that the corresponding block count of the unbonding // period will exceed the end of any active claim & proof lifecycles. uint64 supplier_unbonding_period_sessions = 7 [(gogoproto.jsontag) = "supplier_unbonding_period_sessions"]; + // application_unbonding_period_sessions is the number of sessions that an application must wait after + // unstaking before their staked assets are moved to their account balance. + // On-chain business logic requires, and ensures, that the corresponding block count of the + // application unbonding period will exceed the end of its corresponding proof window close height. + uint64 application_unbonding_period_sessions = 8 [(gogoproto.jsontag) = "application_unbonding_period_sessions"]; } \ No newline at end of file diff --git a/testutil/keeper/application.go b/testutil/keeper/application.go index 97d578a09..312472327 100644 --- a/testutil/keeper/application.go +++ b/testutil/keeper/application.go @@ -34,7 +34,16 @@ import ( // WARNING: Using this map may cause issues if running multiple tests in parallel var stakedGatewayMap = make(map[string]struct{}) -func ApplicationKeeper(t testing.TB) (keeper.Keeper, context.Context) { +// ApplicationModuleKeepers is a struct that contains the keepers needed for testing +// the application module. +type ApplicationModuleKeepers struct { + *keeper.Keeper + types.SharedKeeper +} + +// NewApplicationModuleKeepers creates a new application keeper for testing along +// with its dependencies then returns the application module keepers and context. +func NewApplicationModuleKeepers(t testing.TB) (ApplicationModuleKeepers, context.Context) { t.Helper() storeKey := storetypes.NewKVStoreKey(types.StoreKey) @@ -81,7 +90,7 @@ func ApplicationKeeper(t testing.TB) (keeper.Keeper, context.Context) { }). AnyTimes() - k := keeper.NewKeeper( + appKeeper := keeper.NewKeeper( cdc, runtime.NewKVStoreService(storeKey), log.NewNopLogger(), @@ -95,9 +104,28 @@ func ApplicationKeeper(t testing.TB) (keeper.Keeper, context.Context) { ctx := sdk.NewContext(stateStore, cmtproto.Header{}, false, log.NewNopLogger()) // Initialize params - require.NoError(t, k.SetParams(ctx, types.DefaultParams())) + require.NoError(t, appKeeper.SetParams(ctx, types.DefaultParams())) + + // Move block height to 1 to get a non zero session end height + sdkCtx := sdk.UnwrapSDKContext(ctx) + ctx = sdkCtx.WithBlockHeight(1) + + applicationModuleKeepers := ApplicationModuleKeepers{ + Keeper: &appKeeper, + SharedKeeper: mockSharedKeeper, + } + + return applicationModuleKeepers, ctx +} + +// ApplicationKeeper creates a new application keeper for testing and returns +// the keeper and context. +func ApplicationKeeper(t testing.TB) (keeper.Keeper, context.Context) { + t.Helper() + + applicationModuleKeepers, ctx := NewApplicationModuleKeepers(t) - return k, ctx + return *applicationModuleKeepers.Keeper, ctx } // AddGatewayToStakedGatewayMap adds the given gateway address to the staked diff --git a/x/application/keeper/msg_server_stake_application.go b/x/application/keeper/msg_server_stake_application.go index cb83ca787..a51f64d38 100644 --- a/x/application/keeper/msg_server_stake_application.go +++ b/x/application/keeper/msg_server_stake_application.go @@ -47,6 +47,9 @@ func (k msgServer) StakeApplication(ctx context.Context, msg *types.MsgStakeAppl return nil, err } logger.Info(fmt.Sprintf("Application is going to escrow an additional %+v coins", coinsToEscrow)) + + // If the application has initiated an unstake action, cancel it since it is staking again. + foundApp.UnstakeSessionEndHeight = types.ApplicationNotUnstaking } // Must always stake or upstake (> 0 delta) diff --git a/x/application/keeper/msg_server_unstake_application.go b/x/application/keeper/msg_server_unstake_application.go index 74f662d41..7fbdc9f58 100644 --- a/x/application/keeper/msg_server_unstake_application.go +++ b/x/application/keeper/msg_server_unstake_application.go @@ -8,6 +8,7 @@ import ( "github.com/pokt-network/poktroll/telemetry" "github.com/pokt-network/poktroll/x/application/types" + "github.com/pokt-network/poktroll/x/shared" ) // TODO(#489): Determine if an application needs an unbonding period after unstaking. @@ -25,8 +26,7 @@ func (k msgServer) UnstakeApplication( logger := k.Logger().With("method", "UnstakeApplication") logger.Info(fmt.Sprintf("About to unstake application with msg: %v", msg)) - // Check if the application already exists or not - var err error + // Check if the application already exists or not. foundApp, isAppFound := k.GetApplication(ctx, msg.Address) if !isAppFound { logger.Info(fmt.Sprintf("Application not found. Cannot unstake address %s", msg.Address)) @@ -34,23 +34,22 @@ func (k msgServer) UnstakeApplication( } logger.Info(fmt.Sprintf("Application found. Unstaking application for address %s", msg.Address)) - // Retrieve the address of the application - appAddress, err := sdk.AccAddressFromBech32(msg.Address) - if err != nil { - logger.Error(fmt.Sprintf("could not parse address %s", msg.Address)) - return nil, err + // Check if the application has already initiated the unstaking process. + if foundApp.IsUnbonding() { + logger.Warn(fmt.Sprintf("Application %s is still unbonding from previous unstaking", msg.Address)) + return nil, types.ErrAppIsUnstaking } - // Send the coins from the application pool back to the application - err = k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, appAddress, []sdk.Coin{*foundApp.Stake}) - if err != nil { - logger.Error(fmt.Sprintf("could not send %v coins from %s module to %s account due to %v", foundApp.Stake, appAddress, types.ModuleName, err)) - return nil, err - } + sdkCtx := sdk.UnwrapSDKContext(ctx) + currentHeight := sdkCtx.BlockHeight() + sharedParams := k.sharedKeeper.GetParams(sdkCtx) - // Update the Application in the store - k.RemoveApplication(ctx, appAddress.String()) - logger.Info(fmt.Sprintf("Successfully removed the application: %+v", foundApp)) + // Mark the application as unstaking by recording the height at which it should + // no longer be able to request services. + // The application MAY continue to request service until the end of the current + // session. After that, the application will be considered inactive. + foundApp.UnstakeSessionEndHeight = uint64(shared.GetSessionEndHeight(&sharedParams, currentHeight)) + k.SetApplication(ctx, foundApp) isSuccessful = true return &types.MsgUnstakeApplicationResponse{}, nil diff --git a/x/application/keeper/msg_server_unstake_application_test.go b/x/application/keeper/msg_server_unstake_application_test.go index 23f3c1e0c..cc0a7ac10 100644 --- a/x/application/keeper/msg_server_unstake_application_test.go +++ b/x/application/keeper/msg_server_unstake_application_test.go @@ -15,58 +15,135 @@ import ( ) func TestMsgServer_UnstakeApplication_Success(t *testing.T) { - k, ctx := keepertest.ApplicationKeeper(t) - srv := keeper.NewMsgServerImpl(k) + applicationModuleKeepers, ctx := keepertest.NewApplicationModuleKeepers(t) + srv := keeper.NewMsgServerImpl(*applicationModuleKeepers.Keeper) + sharedParams := applicationModuleKeepers.SharedKeeper.GetParams(ctx) // Generate an address for the application - appAddr := sample.AccAddress() + unstakingAppAddr := sample.AccAddress() // Verify that the app does not exist yet - _, isAppFound := k.GetApplication(ctx, appAddr) + _, isAppFound := applicationModuleKeepers.GetApplication(ctx, unstakingAppAddr) require.False(t, isAppFound) // Prepare the application - initialStake := sdk.NewCoin("upokt", math.NewInt(100)) - stakeMsg := &types.MsgStakeApplication{ - Address: appAddr, - Stake: &initialStake, - Services: []*sharedtypes.ApplicationServiceConfig{ - { - Service: &sharedtypes.Service{Id: "svc1"}, - }, - }, - } + initialStake := int64(100) + stakeMsg := createAppStakeMsg(unstakingAppAddr, initialStake) // Stake the application _, err := srv.StakeApplication(ctx, stakeMsg) require.NoError(t, err) // Verify that the application exists - appFound, isAppFound := k.GetApplication(ctx, appAddr) + foundApp, isAppFound := applicationModuleKeepers.GetApplication(ctx, unstakingAppAddr) + require.True(t, isAppFound) + require.Equal(t, unstakingAppAddr, foundApp.Address) + require.Equal(t, initialStake, foundApp.Stake.Amount.Int64()) + require.Len(t, foundApp.ServiceConfigs, 1) + + // Create and stake another application that will not be unstaked to assert that + // only the unstaking application is removed from the applications list when the + // unbonding period is over. + nonUnstakingAppAddr := sample.AccAddress() + stakeMsg = createAppStakeMsg(nonUnstakingAppAddr, initialStake) + _, err = srv.StakeApplication(ctx, stakeMsg) + require.NoError(t, err) + + // Verify that the non-unstaking application exists + _, isAppFound = applicationModuleKeepers.GetApplication(ctx, nonUnstakingAppAddr) require.True(t, isAppFound) - require.Equal(t, appAddr, appFound.Address) - require.Equal(t, initialStake.Amount, appFound.Stake.Amount) - require.Len(t, appFound.ServiceConfigs, 1) // Unstake the application - unstakeMsg := &types.MsgUnstakeApplication{Address: appAddr} + unstakeMsg := &types.MsgUnstakeApplication{Address: unstakingAppAddr} _, err = srv.UnstakeApplication(ctx, unstakeMsg) require.NoError(t, err) - // Make sure the app can no longer be found after unstaking - _, isAppFound = k.GetApplication(ctx, appAddr) + // Make sure the application entered the unbonding period + foundApp, isAppFound = applicationModuleKeepers.GetApplication(ctx, unstakingAppAddr) + require.True(t, isAppFound) + require.True(t, foundApp.IsUnbonding()) + + // Move block height to the end of the unbonding period + unbondingHeight := types.GetApplicationUnbondingHeight(&sharedParams, &foundApp) + ctx = keepertest.SetBlockHeight(ctx, unbondingHeight) + + // Run the endblocker to unbond applications + err = applicationModuleKeepers.EndBlockerUnbondApplications(ctx) + require.NoError(t, err) + + // Make sure the unstaking application is removed from the applications list when + // the unbonding period is over. + _, isAppFound = applicationModuleKeepers.GetApplication(ctx, unstakingAppAddr) require.False(t, isAppFound) + + // Verify that the non-unstaking application still exists. + nonUnstakingApplication, isAppFound := applicationModuleKeepers.GetApplication(ctx, nonUnstakingAppAddr) + require.True(t, isAppFound) + require.False(t, nonUnstakingApplication.IsUnbonding()) +} + +func TestMsgServer_UnstakeApplication_CancelUnbondingIfRestaked(t *testing.T) { + applicationModuleKeepers, ctx := keepertest.NewApplicationModuleKeepers(t) + srv := keeper.NewMsgServerImpl(*applicationModuleKeepers.Keeper) + sharedParams := applicationModuleKeepers.SharedKeeper.GetParams(ctx) + + // Generate an address for the application + appAddr := sample.AccAddress() + + // Stake the application + initialStake := int64(100) + stakeMsg := createAppStakeMsg(appAddr, initialStake) + _, err := srv.StakeApplication(ctx, stakeMsg) + require.NoError(t, err) + + // Verify that the application exists with no unbonding height + foundApp, isAppFound := applicationModuleKeepers.GetApplication(ctx, appAddr) + require.True(t, isAppFound) + require.False(t, foundApp.IsUnbonding()) + + // Initiate the application unstaking + unstakeMsg := &types.MsgUnstakeApplication{Address: appAddr} + _, err = srv.UnstakeApplication(ctx, unstakeMsg) + require.NoError(t, err) + + // Make sure the application entered the unbonding period + foundApp, isAppFound = applicationModuleKeepers.GetApplication(ctx, appAddr) + require.True(t, isAppFound) + require.True(t, foundApp.IsUnbonding()) + + unbondingHeight := types.GetApplicationUnbondingHeight(&sharedParams, &foundApp) + + // Stake the application again + stakeMsg = createAppStakeMsg(appAddr, initialStake+1) + _, err = srv.StakeApplication(ctx, stakeMsg) + require.NoError(t, err) + + // Make sure the application is no longer in the unbonding period + foundApp, isAppFound = applicationModuleKeepers.GetApplication(ctx, appAddr) + require.True(t, isAppFound) + require.False(t, foundApp.IsUnbonding()) + + ctx = keepertest.SetBlockHeight(ctx, int64(unbondingHeight)) + + // Run the EndBlocker, the application should not be unbonding. + err = applicationModuleKeepers.EndBlockerUnbondApplications(ctx) + require.NoError(t, err) + + // Make sure the application exists with an unbonding height of 0 + foundApp, isAppFound = applicationModuleKeepers.GetApplication(ctx, appAddr) + require.True(t, isAppFound) + require.False(t, foundApp.IsUnbonding()) } func TestMsgServer_UnstakeApplication_FailIfNotStaked(t *testing.T) { - k, ctx := keepertest.ApplicationKeeper(t) - srv := keeper.NewMsgServerImpl(k) + applicationModuleKeepers, ctx := keepertest.NewApplicationModuleKeepers(t) + srv := keeper.NewMsgServerImpl(*applicationModuleKeepers.Keeper) // Generate an address for the application appAddr := sample.AccAddress() // Verify that the app does not exist yet - _, isAppFound := k.GetApplication(ctx, appAddr) + _, isAppFound := applicationModuleKeepers.GetApplication(ctx, appAddr) require.False(t, isAppFound) // Unstake the application @@ -75,6 +152,46 @@ func TestMsgServer_UnstakeApplication_FailIfNotStaked(t *testing.T) { require.Error(t, err) require.ErrorIs(t, err, types.ErrAppNotFound) - _, isAppFound = k.GetApplication(ctx, appAddr) + _, isAppFound = applicationModuleKeepers.GetApplication(ctx, appAddr) require.False(t, isAppFound) } + +func TestMsgServer_UnstakeApplication_FailIfCurrentlyUnstaking(t *testing.T) { + applicationModuleKeepers, ctx := keepertest.NewApplicationModuleKeepers(t) + srv := keeper.NewMsgServerImpl(*applicationModuleKeepers.Keeper) + + // Generate an address for the application + appAddr := sample.AccAddress() + + // Stake the application + initialStake := int64(100) + stakeMsg := createAppStakeMsg(appAddr, initialStake) + _, err := srv.StakeApplication(ctx, stakeMsg) + require.NoError(t, err) + + // Initiate the application unstaking + unstakeMsg := &types.MsgUnstakeApplication{Address: appAddr} + _, err = srv.UnstakeApplication(ctx, unstakeMsg) + require.NoError(t, err) + + sdkCtx := sdk.UnwrapSDKContext(ctx) + ctx = keepertest.SetBlockHeight(ctx, int64(sdkCtx.BlockHeight()+1)) + + // Verify that the application cannot unstake if it is already unstaking. + _, err = srv.UnstakeApplication(ctx, unstakeMsg) + require.ErrorIs(t, err, types.ErrAppIsUnstaking) +} + +func createAppStakeMsg(appAddr string, stakeAmount int64) *types.MsgStakeApplication { + initialStake := sdk.NewCoin("upokt", math.NewInt(stakeAmount)) + + return &types.MsgStakeApplication{ + Address: appAddr, + Stake: &initialStake, + Services: []*sharedtypes.ApplicationServiceConfig{ + { + Service: &sharedtypes.Service{Id: "svc1"}, + }, + }, + } +} diff --git a/x/application/keeper/unbond_applications.go b/x/application/keeper/unbond_applications.go new file mode 100644 index 000000000..38bbe2ef9 --- /dev/null +++ b/x/application/keeper/unbond_applications.go @@ -0,0 +1,67 @@ +package keeper + +import ( + "context" + "fmt" + + cosmostypes "github.com/cosmos/cosmos-sdk/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/pokt-network/poktroll/x/application/types" +) + +// EndBlockerUnbondApplications unbonds applications whose unbonding period has elapsed. +func (k Keeper) EndBlockerUnbondApplications(ctx context.Context) error { + sdkCtx := cosmostypes.UnwrapSDKContext(ctx) + sharedParams := k.sharedKeeper.GetParams(sdkCtx) + currentHeight := sdkCtx.BlockHeight() + + // Only process unbonding applications at the end of the session. + if currentHeight != k.sharedKeeper.GetSessionEndHeight(ctx, currentHeight) { + return nil + } + + logger := k.Logger().With("method", "UnbondApplication") + + // Iterate over all applications and unbond the ones that have finished the unbonding period. + // TODO_IMPROVE: Use an index to iterate over the applications that have initiated + // the unbonding action instead of iterating over all of them. + for _, application := range k.GetAllApplications(ctx) { + // Ignore applications that have not initiated the unbonding action. + if !application.IsUnbonding() { + continue + } + + unbondingHeight := types.GetApplicationUnbondingHeight(&sharedParams, &application) + + // If the unbonding height is ahead of the current height, the application + // stays in the unbonding state. + if unbondingHeight > currentHeight { + continue + } + + // Retrieve the account address of the application. + applicationAccAddress, err := cosmostypes.AccAddressFromBech32(application.Address) + if err != nil { + logger.Error(fmt.Sprintf("could not parse address %s", application.Address)) + return err + } + + // Send the coins from the application pool back to the application + err = k.bankKeeper.SendCoinsFromModuleToAccount( + ctx, types.ModuleName, applicationAccAddress, []sdk.Coin{*application.Stake}, + ) + if err != nil { + logger.Error(fmt.Sprintf( + "could not send %v coins from module %s to account %s due to %v", + application.Stake, applicationAccAddress, types.ModuleName, err, + )) + return err + } + + // Update the Application in the store + k.RemoveApplication(ctx, applicationAccAddress.String()) + logger.Info(fmt.Sprintf("Successfully removed the application: %+v", application)) + } + + return nil +} diff --git a/x/application/module/abci.go b/x/application/module/abci.go index ed331c3fe..3f0b16f4d 100644 --- a/x/application/module/abci.go +++ b/x/application/module/abci.go @@ -16,5 +16,9 @@ func EndBlocker(ctx sdk.Context, k keeper.Keeper) error { return err } + if err := k.EndBlockerUnbondApplications(ctx); err != nil { + return err + } + return nil } diff --git a/x/application/types/application.go b/x/application/types/application.go new file mode 100644 index 000000000..b838df69a --- /dev/null +++ b/x/application/types/application.go @@ -0,0 +1,34 @@ +package types + +import sharedtypes "github.com/pokt-network/poktroll/x/shared/types" + +// ApplicationNotUnstaking is the value of `unstake_session_end_height` if the +// application is not actively in the unbonding period. +const ApplicationNotUnstaking uint64 = 0 + +// IsUnbonding returns true if the application is actively unbonding. +// It determines if the application has submitted an unstake message, in which case +// the application has its UnstakeSessionEndHeight set. +func (s *Application) IsUnbonding() bool { + return s.UnstakeSessionEndHeight != ApplicationNotUnstaking +} + +// IsActive returns whether the application is allowed to request services at the +// given query height. +// An application that has not submitted an unstake message is always active. +// An application that has submitted an unstake message is active until the end of +// the session containing the height at which unstake message was submitted. +func (s *Application) IsActive(queryHeight int64) bool { + return !s.IsUnbonding() || uint64(queryHeight) <= s.UnstakeSessionEndHeight +} + +// GetApplicationUnbondingHeight returns the session end height at which the given +// application finishes unbonding. +func GetApplicationUnbondingHeight( + sharedParams *sharedtypes.Params, + application *Application, +) int64 { + applicationUnbondingPeriodSessions := sharedParams.ApplicationUnbondingPeriodSessions * sharedParams.NumBlocksPerSession + + return int64(application.UnstakeSessionEndHeight + applicationUnbondingPeriodSessions) +} diff --git a/x/application/types/errors.go b/x/application/types/errors.go index 981a11a3c..67754829b 100644 --- a/x/application/types/errors.go +++ b/x/application/types/errors.go @@ -18,4 +18,5 @@ var ( ErrAppMaxDelegatedGateways = sdkerrors.Register(ModuleName, 1110, "maximum number of delegated gateways reached") ErrAppInvalidMaxDelegatedGateways = sdkerrors.Register(ModuleName, 1111, "invalid MaxDelegatedGateways parameter") ErrAppNotDelegated = sdkerrors.Register(ModuleName, 1112, "application not delegated to gateway") + ErrAppIsUnstaking = sdkerrors.Register(ModuleName, 1113, "application is in unbonding period") ) diff --git a/x/application/types/types.pb.go b/x/application/types/types.pb.go index 2e9c55442..08c8cfafe 100644 --- a/x/application/types/types.pb.go +++ b/x/application/types/types.pb.go @@ -41,6 +41,9 @@ type Application struct { // TODO_DOCUMENT(@red-0ne): Need to document the flow from this comment // so its clear to everyone why this is necessary; https://github.com/pokt-network/poktroll/issues/476#issuecomment-2052639906. PendingUndelegations map[uint64]UndelegatingGatewayList `protobuf:"bytes,5,rep,name=pending_undelegations,json=pendingUndelegations,proto3" json:"pending_undelegations" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + // The end height of the session at which an application initiated its unstaking process. + // If the application did not unstake, this value will be 0. + UnstakeSessionEndHeight uint64 `protobuf:"varint,6,opt,name=unstake_session_end_height,json=unstakeSessionEndHeight,proto3" json:"unstake_session_end_height,omitempty"` } func (m *Application) Reset() { *m = Application{} } @@ -111,6 +114,13 @@ func (m *Application) GetPendingUndelegations() map[uint64]UndelegatingGatewayLi return nil } +func (m *Application) GetUnstakeSessionEndHeight() uint64 { + if m != nil { + return m.UnstakeSessionEndHeight + } + return 0 +} + // UndelegatingGatewayList is used as the Value of `pending_undelegations`. // It is required to store a repeated list of strings as a map value. type UndelegatingGatewayList struct { @@ -166,37 +176,39 @@ func init() { func init() { proto.RegisterFile("poktroll/application/types.proto", fileDescriptor_1899440439257283) } var fileDescriptor_1899440439257283 = []byte{ - // 473 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x93, 0x4f, 0x8b, 0xd3, 0x40, - 0x18, 0xc6, 0x9b, 0xfe, 0x51, 0x76, 0x0a, 0xba, 0x0e, 0x15, 0xd3, 0xaa, 0x31, 0xec, 0xa9, 0x1e, - 0x3a, 0xc3, 0x56, 0x0f, 0xa2, 0xa7, 0xb6, 0x88, 0x08, 0x22, 0x92, 0xc5, 0x8b, 0x08, 0x61, 0x9a, - 0x8c, 0xb3, 0x43, 0xb3, 0x33, 0x21, 0x33, 0xcd, 0xda, 0x6f, 0xe1, 0xc1, 0x8f, 0xe2, 0x87, 0xd8, - 0xe3, 0xe2, 0xc9, 0x93, 0x48, 0xfb, 0x45, 0x24, 0x99, 0x69, 0x36, 0xd8, 0x16, 0xbc, 0x94, 0x19, - 0x9e, 0xe7, 0x79, 0xfb, 0x7b, 0xdf, 0x77, 0x02, 0xfc, 0x54, 0x2e, 0x74, 0x26, 0x93, 0x04, 0x93, - 0x34, 0x4d, 0x78, 0x44, 0x34, 0x97, 0x02, 0xeb, 0x55, 0x4a, 0x15, 0x4a, 0x33, 0xa9, 0x25, 0xec, - 0x6d, 0x1d, 0xa8, 0xe6, 0x18, 0xf4, 0x98, 0x64, 0xb2, 0x34, 0xe0, 0xe2, 0x64, 0xbc, 0x03, 0x2f, - 0x92, 0xea, 0x42, 0x2a, 0x3c, 0x27, 0x8a, 0xe2, 0xfc, 0x74, 0x4e, 0x35, 0x39, 0xc5, 0x91, 0xe4, - 0xc2, 0xea, 0x7d, 0xa3, 0x87, 0x26, 0x68, 0x2e, 0x56, 0x7a, 0x5c, 0x81, 0xa8, 0x73, 0x92, 0xd1, - 0x18, 0x2b, 0x9a, 0xe5, 0x3c, 0xa2, 0x46, 0x3e, 0xf9, 0xde, 0x06, 0xdd, 0xc9, 0xcd, 0xff, 0xc3, - 0x31, 0xb8, 0x4d, 0xe2, 0x38, 0xa3, 0x4a, 0xb9, 0x8e, 0xef, 0x0c, 0x8f, 0xa6, 0xee, 0xcf, 0x1f, - 0xa3, 0x9e, 0xad, 0x38, 0x31, 0xca, 0x99, 0xce, 0xb8, 0x60, 0xc1, 0xd6, 0x08, 0x31, 0xe8, 0x28, - 0x4d, 0x16, 0xd4, 0x6d, 0xfa, 0xce, 0xb0, 0x3b, 0xee, 0x23, 0x6b, 0x2f, 0x68, 0x91, 0xa5, 0x45, - 0x33, 0xc9, 0x45, 0x60, 0x7c, 0x30, 0x00, 0x77, 0x2d, 0x45, 0x18, 0x49, 0xf1, 0x85, 0x33, 0xe5, - 0xb6, 0xfc, 0xd6, 0xb0, 0x3b, 0x7e, 0x8a, 0xaa, 0xa1, 0x18, 0x5a, 0x54, 0x63, 0x3b, 0x33, 0x91, - 0x59, 0x99, 0x08, 0xee, 0xa8, 0xfa, 0x55, 0xc1, 0xcf, 0xe0, 0x61, 0x4c, 0x13, 0xca, 0x88, 0xa6, - 0x34, 0x2c, 0x7e, 0x2f, 0xc9, 0x2a, 0xb4, 0x84, 0x54, 0xb9, 0x6d, 0xbf, 0x35, 0x3c, 0x9a, 0x3e, - 0xba, 0xfa, 0xfd, 0xa4, 0x71, 0xb0, 0xa1, 0x7e, 0x55, 0xe0, 0x8d, 0xc9, 0x4f, 0xb6, 0x71, 0x98, - 0x83, 0xfb, 0x29, 0x15, 0x31, 0x17, 0x2c, 0x5c, 0x0a, 0x6b, 0xe3, 0x52, 0x28, 0xb7, 0x53, 0x72, - 0xbf, 0x42, 0xfb, 0x96, 0x59, 0x87, 0x47, 0x1f, 0x4c, 0xfc, 0x63, 0x3d, 0xfd, 0x5a, 0xe8, 0x6c, - 0x35, 0x6d, 0x17, 0x50, 0x41, 0x2f, 0xdd, 0x63, 0x18, 0xe4, 0xa0, 0x7f, 0x30, 0x08, 0x8f, 0x41, - 0x6b, 0x41, 0x57, 0xe5, 0x9e, 0xda, 0x41, 0x71, 0x84, 0x33, 0xd0, 0xc9, 0x49, 0xb2, 0xdc, 0x6e, - 0x62, 0xb4, 0x1f, 0xeb, 0xa6, 0x94, 0x60, 0xb6, 0xd3, 0x77, 0x5c, 0xe9, 0xc0, 0x64, 0x5f, 0x36, - 0x5f, 0x38, 0x27, 0x31, 0x78, 0x70, 0xc0, 0x05, 0xdf, 0x82, 0x7b, 0xbb, 0xe3, 0x6d, 0xfe, 0xc7, - 0x78, 0x8f, 0xd9, 0x3f, 0x53, 0x9d, 0xbe, 0xbf, 0x5a, 0x7b, 0xce, 0xf5, 0xda, 0x73, 0xfe, 0xac, - 0x3d, 0xe7, 0xdb, 0xc6, 0x6b, 0x5c, 0x6f, 0xbc, 0xc6, 0xaf, 0x8d, 0xd7, 0xf8, 0xf4, 0x9c, 0x71, - 0x7d, 0xbe, 0x9c, 0xa3, 0x48, 0x5e, 0xe0, 0xa2, 0x87, 0x91, 0xa0, 0xfa, 0x52, 0x66, 0x0b, 0x5c, - 0xbd, 0xe6, 0xaf, 0xbb, 0x1f, 0xd6, 0xfc, 0x56, 0xf9, 0xa6, 0x9f, 0xfd, 0x0d, 0x00, 0x00, 0xff, - 0xff, 0xac, 0x5c, 0x63, 0xa6, 0x7d, 0x03, 0x00, 0x00, + // 508 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x93, 0x4d, 0x8b, 0x13, 0x31, + 0x1c, 0xc6, 0x3b, 0x7d, 0x59, 0xd9, 0x14, 0x74, 0x0d, 0x95, 0x9d, 0x56, 0x1d, 0x87, 0x3d, 0xd5, + 0x43, 0x13, 0xb6, 0x7a, 0x10, 0xf7, 0xd4, 0x96, 0x45, 0x05, 0x11, 0x99, 0xe2, 0x45, 0x84, 0x21, + 0x9d, 0x89, 0x69, 0x68, 0x37, 0x19, 0x26, 0xe9, 0xac, 0xfd, 0x16, 0x7e, 0x18, 0x3f, 0xc4, 0x1e, + 0x57, 0x4f, 0x9e, 0x44, 0xda, 0x2f, 0x22, 0x33, 0x49, 0xbb, 0x83, 0xdb, 0xc2, 0x5e, 0x86, 0x09, + 0xcf, 0xef, 0x49, 0x9e, 0xe4, 0xe1, 0x0f, 0xfc, 0x44, 0xce, 0x74, 0x2a, 0xe7, 0x73, 0x4c, 0x92, + 0x64, 0xce, 0x23, 0xa2, 0xb9, 0x14, 0x58, 0x2f, 0x13, 0xaa, 0x50, 0x92, 0x4a, 0x2d, 0x61, 0x6b, + 0x43, 0xa0, 0x12, 0xd1, 0x69, 0x31, 0xc9, 0x64, 0x01, 0xe0, 0xfc, 0xcf, 0xb0, 0x1d, 0x2f, 0x92, + 0xea, 0x42, 0x2a, 0x3c, 0x21, 0x8a, 0xe2, 0xec, 0x74, 0x42, 0x35, 0x39, 0xc5, 0x91, 0xe4, 0xc2, + 0xea, 0x6d, 0xa3, 0x87, 0xc6, 0x68, 0x16, 0x56, 0x7a, 0xba, 0x0d, 0xa2, 0xa6, 0x24, 0xa5, 0x31, + 0x56, 0x34, 0xcd, 0x78, 0x44, 0x8d, 0x7c, 0xf2, 0xb3, 0x0e, 0x9a, 0x83, 0x9b, 0xf3, 0x61, 0x1f, + 0xdc, 0x23, 0x71, 0x9c, 0x52, 0xa5, 0x5c, 0xc7, 0x77, 0xba, 0x87, 0x43, 0xf7, 0xd7, 0x8f, 0x5e, + 0xcb, 0xee, 0x38, 0x30, 0xca, 0x58, 0xa7, 0x5c, 0xb0, 0x60, 0x03, 0x42, 0x0c, 0x1a, 0x4a, 0x93, + 0x19, 0x75, 0xab, 0xbe, 0xd3, 0x6d, 0xf6, 0xdb, 0xc8, 0xe2, 0x79, 0x5a, 0x64, 0xd3, 0xa2, 0x91, + 0xe4, 0x22, 0x30, 0x1c, 0x0c, 0xc0, 0x03, 0x9b, 0x22, 0x8c, 0xa4, 0xf8, 0xca, 0x99, 0x72, 0x6b, + 0x7e, 0xad, 0xdb, 0xec, 0x3f, 0x47, 0xdb, 0x47, 0x31, 0x69, 0x51, 0x29, 0xdb, 0xd8, 0x58, 0x46, + 0x85, 0x23, 0xb8, 0xaf, 0xca, 0x4b, 0x05, 0xbf, 0x80, 0xc7, 0x31, 0x9d, 0x53, 0x46, 0x34, 0xa5, + 0x61, 0xfe, 0xbd, 0x24, 0xcb, 0xd0, 0x26, 0xa4, 0xca, 0xad, 0xfb, 0xb5, 0xee, 0xe1, 0xf0, 0xc9, + 0xd5, 0x9f, 0x67, 0x95, 0xbd, 0x17, 0x6a, 0x6f, 0x37, 0x78, 0x63, 0xfc, 0x83, 0x8d, 0x1d, 0x66, + 0xe0, 0x51, 0x42, 0x45, 0xcc, 0x05, 0x0b, 0x17, 0xc2, 0x62, 0x5c, 0x0a, 0xe5, 0x36, 0x8a, 0xdc, + 0x67, 0x68, 0x57, 0x99, 0xe5, 0xf0, 0xe8, 0xa3, 0xb1, 0x7f, 0x2a, 0xbb, 0xcf, 0x85, 0x4e, 0x97, + 0xc3, 0x7a, 0x1e, 0x2a, 0x68, 0x25, 0x3b, 0x00, 0x78, 0x06, 0x3a, 0x0b, 0x51, 0x3c, 0x5a, 0xa8, + 0xa8, 0x52, 0x5c, 0x8a, 0x90, 0x8a, 0x38, 0x9c, 0x52, 0xce, 0xa6, 0xda, 0x3d, 0xf0, 0x9d, 0x6e, + 0x3d, 0x38, 0xb6, 0xc4, 0xd8, 0x00, 0xe7, 0x22, 0x7e, 0x5b, 0xc8, 0x9d, 0x0c, 0xb4, 0xf7, 0x9e, + 0x0a, 0x8f, 0x40, 0x6d, 0x46, 0x97, 0x45, 0xc9, 0xf5, 0x20, 0xff, 0x85, 0x23, 0xd0, 0xc8, 0xc8, + 0x7c, 0xb1, 0xa9, 0xb1, 0xb7, 0xfb, 0x4e, 0x37, 0x5b, 0x09, 0x66, 0x9f, 0xe9, 0x3d, 0x57, 0x3a, + 0x30, 0xde, 0xd7, 0xd5, 0x57, 0xce, 0x49, 0x0c, 0x8e, 0xf7, 0x50, 0xf0, 0x1d, 0x78, 0x78, 0xbb, + 0x9b, 0xea, 0x1d, 0xba, 0x39, 0x62, 0xff, 0x55, 0x32, 0xfc, 0x70, 0xb5, 0xf2, 0x9c, 0xeb, 0x95, + 0xe7, 0xfc, 0x5d, 0x79, 0xce, 0xf7, 0xb5, 0x57, 0xb9, 0x5e, 0x7b, 0x95, 0xdf, 0x6b, 0xaf, 0xf2, + 0xf9, 0x25, 0xe3, 0x7a, 0xba, 0x98, 0xa0, 0x48, 0x5e, 0xe0, 0xfc, 0x0e, 0x3d, 0x41, 0xf5, 0xa5, + 0x4c, 0x67, 0x78, 0x3b, 0x0a, 0xdf, 0x6e, 0x4f, 0xe5, 0xe4, 0xa0, 0x18, 0x88, 0x17, 0xff, 0x02, + 0x00, 0x00, 0xff, 0xff, 0x6b, 0x46, 0x81, 0x7e, 0xba, 0x03, 0x00, 0x00, } func (m *Application) Marshal() (dAtA []byte, err error) { @@ -219,6 +231,11 @@ func (m *Application) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.UnstakeSessionEndHeight != 0 { + i = encodeVarintTypes(dAtA, i, uint64(m.UnstakeSessionEndHeight)) + i-- + dAtA[i] = 0x30 + } if len(m.PendingUndelegations) > 0 { for k := range m.PendingUndelegations { v := m.PendingUndelegations[k] @@ -364,6 +381,9 @@ func (m *Application) Size() (n int) { n += mapEntrySize + 1 + sovTypes(uint64(mapEntrySize)) } } + if m.UnstakeSessionEndHeight != 0 { + n += 1 + sovTypes(uint64(m.UnstakeSessionEndHeight)) + } return n } @@ -666,6 +686,25 @@ func (m *Application) Unmarshal(dAtA []byte) error { } m.PendingUndelegations[mapkey] = *mapvalue iNdEx = postIndex + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field UnstakeSessionEndHeight", wireType) + } + m.UnstakeSessionEndHeight = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.UnstakeSessionEndHeight |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipTypes(dAtA[iNdEx:]) diff --git a/x/session/keeper/session_hydrator.go b/x/session/keeper/session_hydrator.go index e0711dd3a..52f377cc3 100644 --- a/x/session/keeper/session_hydrator.go +++ b/x/session/keeper/session_hydrator.go @@ -144,6 +144,16 @@ func (k Keeper) hydrateSessionApplication(ctx context.Context, sh *sessionHydrat ) } + // Do not provide sessions for applications that initiated the unstaking process + // and that are no longer active. + if !foundApp.IsActive(sh.sessionHeader.SessionEndBlockHeight) { + return types.ErrSessionAppNotActive.Wrapf( + "application %q is not active for session %s", + sh.sessionHeader.ApplicationAddress, + sh.sessionHeader.SessionId, + ) + } + for _, appServiceConfig := range foundApp.ServiceConfigs { if appServiceConfig.Service.Id == sh.sessionHeader.Service.Id { sh.session.Application = &foundApp diff --git a/x/session/types/errors.go b/x/session/types/errors.go index 6ffff939c..82b720b40 100644 --- a/x/session/types/errors.go +++ b/x/session/types/errors.go @@ -15,4 +15,5 @@ var ( ErrSessionInvalidService = sdkerrors.Register(ModuleName, 1106, "invalid service in session") ErrSessionInvalidBlockHeight = sdkerrors.Register(ModuleName, 1107, "invalid block height for session") ErrSessionInvalidSessionId = sdkerrors.Register(ModuleName, 1108, "invalid sessionId") + ErrSessionAppNotActive = sdkerrors.Register(ModuleName, 1109, "application is not active") ) diff --git a/x/shared/keeper/msg_server_update_param.go b/x/shared/keeper/msg_server_update_param.go index fd74c05b4..d83e2fc74 100644 --- a/x/shared/keeper/msg_server_update_param.go +++ b/x/shared/keeper/msg_server_update_param.go @@ -67,6 +67,13 @@ func (k msgServer) UpdateParam(ctx context.Context, msg *types.MsgUpdateParam) ( } params.SupplierUnbondingPeriodSessions = uint64(value.AsInt64) + case types.ParamApplicationUnbondingPeriodSessions: + value, ok := msg.AsType.(*types.MsgUpdateParam_AsInt64) + if !ok { + return nil, types.ErrSharedParamInvalid.Wrapf("unsupported value type for %s param: %T", msg.Name, msg.AsType) + } + + params.ApplicationUnbondingPeriodSessions = uint64(value.AsInt64) default: return nil, types.ErrSharedParamInvalid.Wrapf("unsupported param %q", msg.Name) } diff --git a/x/shared/keeper/msg_server_update_param_test.go b/x/shared/keeper/msg_server_update_param_test.go index d77ba87cd..fe93eb97c 100644 --- a/x/shared/keeper/msg_server_update_param_test.go +++ b/x/shared/keeper/msg_server_update_param_test.go @@ -50,14 +50,22 @@ func TestMsgUpdateParam_UpdateClaimWindowOpenOffsetBlocks(t *testing.T) { defaultParams := sharedtypes.DefaultParams() - // Update the SupplierUnbondingPeriodSessions such that it is greater than the - // cumulative proof window close blocks to pass UpdateParam validation. - defaultParams.SupplierUnbondingPeriodSessions = getMinSupplierUnbondingPeriodSessions( + // Calculate the minimum unbonding period sessions required by the staking actors + // to pass UpdateParam validation. + minUnbodningPeriodSessions := getMinActorUnbondingPeriodSessions( &defaultParams, defaultParams.ClaimWindowOpenOffsetBlocks, uint64(expectedClaimWindowOpenOffestBlocks), ) + // Update the SupplierUnbondingPeriodSessions such that it is greater than the + // cumulative proof window close blocks to pass UpdateParam validation. + defaultParams.SupplierUnbondingPeriodSessions = minUnbodningPeriodSessions + + // Update the ApplicationUnbondingPeriodSessions such that it is greater than the + // cumulative proof window close blocks to pass UpdateParam validation. + defaultParams.ApplicationUnbondingPeriodSessions = minUnbodningPeriodSessions + // Set the parameters to their default values require.NoError(t, k.SetParams(ctx, defaultParams)) @@ -87,14 +95,22 @@ func TestMsgUpdateParam_UpdateClaimWindowCloseOffsetBlocks(t *testing.T) { defaultParams := sharedtypes.DefaultParams() - // Update the SupplierUnbondingPeriodSessions such that it is greater than the - // cumulative proof window close blocks to pass UpdateParam validation. - defaultParams.SupplierUnbondingPeriodSessions = getMinSupplierUnbondingPeriodSessions( + // Calculate the minimum unbonding period sessions required by the staking actors + // to pass UpdateParam validation. + minUnbodningPeriodSessions := getMinActorUnbondingPeriodSessions( &defaultParams, - defaultParams.ClaimWindowCloseOffsetBlocks, + defaultParams.ClaimWindowOpenOffsetBlocks, uint64(expectedClaimWindowCloseOffestBlocks), ) + // Update the SupplierUnbondingPeriodSessions such that it is greater than the + // cumulative proof window close blocks to pass UpdateParam validation. + defaultParams.SupplierUnbondingPeriodSessions = minUnbodningPeriodSessions + + // Update the ApplicationUnbondingPeriodSessions such that it is greater than the + // cumulative proof window close blocks to pass UpdateParam validation. + defaultParams.ApplicationUnbondingPeriodSessions = minUnbodningPeriodSessions + // Set the parameters to their default values require.NoError(t, k.SetParams(ctx, defaultParams)) @@ -124,14 +140,22 @@ func TestMsgUpdateParam_UpdateProofWindowOpenOffsetBlocks(t *testing.T) { defaultParams := sharedtypes.DefaultParams() - // Update the SupplierUnbondingPeriodSessions such that it is greater than the - // cumulative proof window close blocks to pass UpdateParam validation. - defaultParams.SupplierUnbondingPeriodSessions = getMinSupplierUnbondingPeriodSessions( + // Calculate the minimum unbonding period sessions required by the staking actors + // to pass UpdateParam validation. + minUnbodningPeriodSessions := getMinActorUnbondingPeriodSessions( &defaultParams, - defaultParams.ProofWindowOpenOffsetBlocks, + defaultParams.ClaimWindowOpenOffsetBlocks, uint64(expectedProofWindowOpenOffestBlocks), ) + // Update the SupplierUnbondingPeriodSessions such that it is greater than the + // cumulative proof window close blocks to pass UpdateParam validation. + defaultParams.SupplierUnbondingPeriodSessions = minUnbodningPeriodSessions + + // Update the ApplicationUnbondingPeriodSessions such that it is greater than the + // cumulative proof window close blocks to pass UpdateParam validation. + defaultParams.ApplicationUnbondingPeriodSessions = minUnbodningPeriodSessions + // Set the parameters to their default values require.NoError(t, k.SetParams(ctx, defaultParams)) @@ -161,14 +185,22 @@ func TestMsgUpdateParam_UpdateProofWindowCloseOffsetBlocks(t *testing.T) { defaultParams := sharedtypes.DefaultParams() - // Update the SupplierUnbondingPeriodSessions such that it is greater than the - // cumulative proof window close blocks to pass UpdateParam validation. - defaultParams.SupplierUnbondingPeriodSessions = getMinSupplierUnbondingPeriodSessions( + // Calculate the minimum unbonding period sessions required by the staking actors + // to pass UpdateParam validation. + minUnbodningPeriodSessions := getMinActorUnbondingPeriodSessions( &defaultParams, - defaultParams.ProofWindowCloseOffsetBlocks, + defaultParams.ClaimWindowOpenOffsetBlocks, uint64(expectedProofWindowCloseOffestBlocks), ) + // Update the SupplierUnbondingPeriodSessions such that it is greater than the + // cumulative proof window close blocks to pass UpdateParam validation. + defaultParams.SupplierUnbondingPeriodSessions = minUnbodningPeriodSessions + + // Update the ApplicationUnbondingPeriodSessions such that it is greater than the + // cumulative proof window close blocks to pass UpdateParam validation. + defaultParams.ApplicationUnbondingPeriodSessions = minUnbodningPeriodSessions + // Set the parameters to their default values require.NoError(t, k.SetParams(ctx, defaultParams)) @@ -261,10 +293,48 @@ func TestMsgUpdateParam_UpdateSupplierUnbondingPeriodSessions(t *testing.T) { require.ErrorIs(t, err, sharedtypes.ErrSharedParamInvalid) } -// getMinSupplierUnbondingPeriodSessions returns the supplier unbonding period +func TestMsgUpdateParam_UpdateApplicationUnbondingPeriodSessions(t *testing.T) { + var expectedApplicationUnbondingPerid int64 = 5 + + k, ctx := testkeeper.SharedKeeper(t) + msgSrv := keeper.NewMsgServerImpl(k) + + defaultParams := sharedtypes.DefaultParams() + // Set the parameters to their default values + require.NoError(t, k.SetParams(ctx, defaultParams)) + + // Ensure the default values are different from the new values we want to set + require.NotEqual(t, uint64(expectedApplicationUnbondingPerid), defaultParams.GetApplicationUnbondingPeriodSessions()) + + // Update the application unbonding period param + updateParamMsg := &sharedtypes.MsgUpdateParam{ + Authority: authtypes.NewModuleAddress(govtypes.ModuleName).String(), + Name: sharedtypes.ParamApplicationUnbondingPeriodSessions, + AsType: &sharedtypes.MsgUpdateParam_AsInt64{AsInt64: expectedApplicationUnbondingPerid}, + } + res, err := msgSrv.UpdateParam(ctx, updateParamMsg) + require.NoError(t, err) + + require.Equal(t, uint64(expectedApplicationUnbondingPerid), res.Params.GetApplicationUnbondingPeriodSessions()) + + // Ensure the other parameters are unchanged + testkeeper.AssertDefaultParamsEqualExceptFields(t, &defaultParams, res.Params, "ApplicationUnbondingPeriodSessions") + + // Ensure that a application unbonding period that is less than the cumulative + // proof window close blocks is not allowed. + updateParamMsg = &sharedtypes.MsgUpdateParam{ + Authority: authtypes.NewModuleAddress(govtypes.ModuleName).String(), + Name: sharedtypes.ParamApplicationUnbondingPeriodSessions, + AsType: &sharedtypes.MsgUpdateParam_AsInt64{AsInt64: 1}, + } + _, err = msgSrv.UpdateParam(ctx, updateParamMsg) + require.ErrorIs(t, err, sharedtypes.ErrSharedParamInvalid) +} + +// getMinActorUnbondingPeriodSessions returns the actors unbonding period // sessions such that it is greater than the cumulative proof window close blocks // to pass UpdateParam validation. -func getMinSupplierUnbondingPeriodSessions( +func getMinActorUnbondingPeriodSessions( params *sharedtypes.Params, oldParamBlocksValue uint64, newParamBlocksValue uint64, diff --git a/x/shared/types/genesis_test.go b/x/shared/types/genesis_test.go index c9988e146..fa07c23c5 100644 --- a/x/shared/types/genesis_test.go +++ b/x/shared/types/genesis_test.go @@ -24,8 +24,9 @@ func TestGenesisState_Validate(t *testing.T) { desc: "valid genesis state", genState: &types.GenesisState{ Params: types.Params{ - NumBlocksPerSession: defaultParams.NumBlocksPerSession, - SupplierUnbondingPeriodSessions: defaultParams.SupplierUnbondingPeriodSessions, + NumBlocksPerSession: defaultParams.NumBlocksPerSession, + SupplierUnbondingPeriodSessions: defaultParams.SupplierUnbondingPeriodSessions, + ApplicationUnbondingPeriodSessions: defaultParams.ApplicationUnbondingPeriodSessions, }, // this line is used by starport scaffolding # types/genesis/validField diff --git a/x/shared/types/message_update_param.go b/x/shared/types/message_update_param.go index 3a103fcf2..39334b5a8 100644 --- a/x/shared/types/message_update_param.go +++ b/x/shared/types/message_update_param.go @@ -53,7 +53,8 @@ func (msg *MsgUpdateParam) ValidateBasic() error { ParamClaimWindowCloseOffsetBlocks, ParamProofWindowOpenOffsetBlocks, ParamProofWindowCloseOffsetBlocks, - ParamSupplierUnbondingPeriodSessions: + ParamSupplierUnbondingPeriodSessions, + ParamApplicationUnbondingPeriodSessions: return msg.paramTypeIsInt64() default: return ErrSharedParamNameInvalid.Wrapf("unsupported param %q", msg.Name) diff --git a/x/shared/types/params.go b/x/shared/types/params.go index 2d359e22a..59f179763 100644 --- a/x/shared/types/params.go +++ b/x/shared/types/params.go @@ -5,31 +5,34 @@ import ( ) const ( - DefaultNumBlocksPerSession = 4 - ParamNumBlocksPerSession = "num_blocks_per_session" - DefaultGracePeriodEndOffsetBlocks = 1 - ParamGracePeriodEndOffsetBlocks = "grace_period_end_offset_blocks" - DefaultClaimWindowOpenOffsetBlocks = 2 - ParamClaimWindowOpenOffsetBlocks = "claim_window_open_offset_blocks" - DefaultClaimWindowCloseOffsetBlocks = 4 - ParamClaimWindowCloseOffsetBlocks = "claim_window_close_offset_blocks" - DefaultProofWindowOpenOffsetBlocks = 0 - ParamProofWindowOpenOffsetBlocks = "proof_window_open_offset_blocks" - DefaultProofWindowCloseOffsetBlocks = 4 - ParamProofWindowCloseOffsetBlocks = "proof_window_close_offset_blocks" - DefaultSupplierUnbondingPeriodSessions = 4 // 4 sessions - ParamSupplierUnbondingPeriodSessions = "supplier_unbonding_period_sessions" + DefaultNumBlocksPerSession = 4 + ParamNumBlocksPerSession = "num_blocks_per_session" + DefaultGracePeriodEndOffsetBlocks = 1 + ParamGracePeriodEndOffsetBlocks = "grace_period_end_offset_blocks" + DefaultClaimWindowOpenOffsetBlocks = 2 + ParamClaimWindowOpenOffsetBlocks = "claim_window_open_offset_blocks" + DefaultClaimWindowCloseOffsetBlocks = 4 + ParamClaimWindowCloseOffsetBlocks = "claim_window_close_offset_blocks" + DefaultProofWindowOpenOffsetBlocks = 0 + ParamProofWindowOpenOffsetBlocks = "proof_window_open_offset_blocks" + DefaultProofWindowCloseOffsetBlocks = 4 + ParamProofWindowCloseOffsetBlocks = "proof_window_close_offset_blocks" + DefaultSupplierUnbondingPeriodSessions = 4 // 4 sessions + ParamSupplierUnbondingPeriodSessions = "supplier_unbonding_period_sessions" + DefaultApplicationUnbondingPeriodSessions = 4 // 4 sessions + ParamApplicationUnbondingPeriodSessions = "application_unbonding_period_sessions" ) var ( - _ paramtypes.ParamSet = (*Params)(nil) - KeyNumBlocksPerSession = []byte("NumBlocksPerSession") - KeyGracePeriodEndOffsetBlocks = []byte("GracePeriodEndOffsetBlocks") - KeyClaimWindowOpenOffsetBlocks = []byte("ClaimWindowOpenOffsetBlocks") - KeyClaimWindowCloseOffsetBlocks = []byte("ClaimWindowCloseOffsetBlocks") - KeyProofWindowOpenOffsetBlocks = []byte("ProofWindowOpenOffsetBlocks") - KeyProofWindowCloseOffsetBlocks = []byte("ProofWindowCloseOffsetBlocks") - KeySupplierUnbondingPeriodSessions = []byte("SupplierUnbondingPeriodSessions") + _ paramtypes.ParamSet = (*Params)(nil) + KeyNumBlocksPerSession = []byte("NumBlocksPerSession") + KeyGracePeriodEndOffsetBlocks = []byte("GracePeriodEndOffsetBlocks") + KeyClaimWindowOpenOffsetBlocks = []byte("ClaimWindowOpenOffsetBlocks") + KeyClaimWindowCloseOffsetBlocks = []byte("ClaimWindowCloseOffsetBlocks") + KeyProofWindowOpenOffsetBlocks = []byte("ProofWindowOpenOffsetBlocks") + KeyProofWindowCloseOffsetBlocks = []byte("ProofWindowCloseOffsetBlocks") + KeySupplierUnbondingPeriodSessions = []byte("SupplierUnbondingPeriodSessions") + KeyApplicationUnbondingPeriodSessions = []byte("ApplicationUnbondingPeriodSessions") ) // ParamKeyTable the param key table for launch module @@ -40,13 +43,14 @@ func ParamKeyTable() paramtypes.KeyTable { // NewParams creates a new Params instance func NewParams() Params { return Params{ - NumBlocksPerSession: DefaultNumBlocksPerSession, - ClaimWindowOpenOffsetBlocks: DefaultClaimWindowOpenOffsetBlocks, - ClaimWindowCloseOffsetBlocks: DefaultClaimWindowCloseOffsetBlocks, - ProofWindowOpenOffsetBlocks: DefaultProofWindowOpenOffsetBlocks, - ProofWindowCloseOffsetBlocks: DefaultProofWindowCloseOffsetBlocks, - GracePeriodEndOffsetBlocks: DefaultGracePeriodEndOffsetBlocks, - SupplierUnbondingPeriodSessions: DefaultSupplierUnbondingPeriodSessions, + NumBlocksPerSession: DefaultNumBlocksPerSession, + ClaimWindowOpenOffsetBlocks: DefaultClaimWindowOpenOffsetBlocks, + ClaimWindowCloseOffsetBlocks: DefaultClaimWindowCloseOffsetBlocks, + ProofWindowOpenOffsetBlocks: DefaultProofWindowOpenOffsetBlocks, + ProofWindowCloseOffsetBlocks: DefaultProofWindowCloseOffsetBlocks, + GracePeriodEndOffsetBlocks: DefaultGracePeriodEndOffsetBlocks, + SupplierUnbondingPeriodSessions: DefaultSupplierUnbondingPeriodSessions, + ApplicationUnbondingPeriodSessions: DefaultApplicationUnbondingPeriodSessions, } } @@ -93,6 +97,11 @@ func (params *Params) ParamSetPairs() paramtypes.ParamSetPairs { ¶ms.SupplierUnbondingPeriodSessions, ValidateSupplierUnbondingPeriodSessions, ), + paramtypes.NewParamSetPair( + KeyApplicationUnbondingPeriodSessions, + ¶ms.ApplicationUnbondingPeriodSessions, + ValidateApplicationUnbondingPeriodSessions, + ), } } @@ -126,6 +135,10 @@ func (params *Params) ValidateBasic() error { return err } + if err := ValidateApplicationUnbondingPeriodSessions(params.ApplicationUnbondingPeriodSessions); err != nil { + return err + } + if err := validateGracePeriodOffsetBlocksIsLessThanNumBlocksPerSession(params); err != nil { return err } @@ -138,6 +151,10 @@ func (params *Params) ValidateBasic() error { return err } + if err := validateApplicationUnbondingPeriodIsGreaterThanCumulativeProofWindowCloseBlocks(params); err != nil { + return err + } + return nil } @@ -207,6 +224,22 @@ func ValidateSupplierUnbondingPeriodSessions(v interface{}) error { return nil } +// ValidateApplicationUnbondingPeriodSession validates the ApplicationUnbondingPeriodSessions +// governance parameter. +// NB: The argument is an interface type to satisfy the ParamSetPair function signature. +func ValidateApplicationUnbondingPeriodSessions(v interface{}) error { + applicationUnbondingPeriodSessions, err := validateIsUint64(v) + if err != nil { + return err + } + + if applicationUnbondingPeriodSessions < 1 { + return ErrSharedParamInvalid.Wrapf("invalid ApplicationUnbondingPeriodSessions: (%v)", applicationUnbondingPeriodSessions) + } + + return nil +} + // validateIsUint64 returns the casted uin64 value or an error if value is not // type assertable to uint64. func validateIsUint64(value any) (uint64, error) { @@ -266,6 +299,24 @@ func validateSupplierUnbondingPeriodIsGreaterThanCumulativeProofWindowCloseBlock return nil } +// validateApplicationUnbondingPeriodIsGreaterThanCumulativeProofWindowCloseBlocks +// ensures that a supplier cannot unbond before the pending claims are settled. +func validateApplicationUnbondingPeriodIsGreaterThanCumulativeProofWindowCloseBlocks(params *Params) error { + cumulativeProofWindowCloseBlocks := GetSessionEndToProofWindowCloseBlocks(params) + applicationUnbondingPeriodSessions := params.ApplicationUnbondingPeriodSessions * params.NumBlocksPerSession + + if applicationUnbondingPeriodSessions < cumulativeProofWindowCloseBlocks { + return ErrSharedParamInvalid.Wrapf( + "ApplicationUnbondingPeriodSessions (%v session) (%v blocks) must be greater than the cumulative ProofWindowCloseOffsetBlocks (%v)", + params.ApplicationUnbondingPeriodSessions, + applicationUnbondingPeriodSessions, + cumulativeProofWindowCloseBlocks, + ) + } + + return nil +} + // GetSessionEndToProofWindowCloseBlocks returns the total number of blocks // from the moment a session ends until the proof window closes. // NB: Using shared.GetProofWindowCloseOffsetHeight is not possible because of the diff --git a/x/shared/types/params.pb.go b/x/shared/types/params.pb.go index feac98afd..9dc1ba447 100644 --- a/x/shared/types/params.pb.go +++ b/x/shared/types/params.pb.go @@ -50,6 +50,11 @@ type Params struct { // On-chain business logic requires, and ensures, that the corresponding block count of the unbonding // period will exceed the end of any active claim & proof lifecycles. SupplierUnbondingPeriodSessions uint64 `protobuf:"varint,7,opt,name=supplier_unbonding_period_sessions,json=supplierUnbondingPeriodSessions,proto3" json:"supplier_unbonding_period_sessions"` + // application_unbonding_period_sessions is the number of sessions that an application must wait after + // unstaking before their staked assets are moved to their account balance. + // On-chain business logic requires, and ensures, that the corresponding block count of the + // application unbonding period will exceed the end of its corresponding proof window close height. + ApplicationUnbondingPeriodSessions uint64 `protobuf:"varint,8,opt,name=application_unbonding_period_sessions,json=applicationUnbondingPeriodSessions,proto3" json:"application_unbonding_period_sessions"` } func (m *Params) Reset() { *m = Params{} } @@ -134,6 +139,13 @@ func (m *Params) GetSupplierUnbondingPeriodSessions() uint64 { return 0 } +func (m *Params) GetApplicationUnbondingPeriodSessions() uint64 { + if m != nil { + return m.ApplicationUnbondingPeriodSessions + } + return 0 +} + func init() { proto.RegisterType((*Params)(nil), "poktroll.shared.Params") } @@ -141,34 +153,36 @@ func init() { func init() { proto.RegisterFile("poktroll/shared/params.proto", fileDescriptor_ee6189c7aa51bbf5) } var fileDescriptor_ee6189c7aa51bbf5 = []byte{ - // 431 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x93, 0xcd, 0xea, 0xd3, 0x40, - 0x14, 0xc5, 0x1b, 0xad, 0x11, 0xb2, 0x11, 0xa3, 0x48, 0x48, 0x4b, 0xa6, 0x56, 0x11, 0x11, 0x6c, - 0x16, 0xee, 0x5c, 0x56, 0x5c, 0xb8, 0x6a, 0x89, 0x88, 0xe0, 0x66, 0xc8, 0xc7, 0x24, 0x1d, 0x9a, - 0xcc, 0x1d, 0x66, 0x12, 0xaa, 0xaf, 0xa0, 0x1b, 0x1f, 0xc1, 0x47, 0xf0, 0x31, 0x5c, 0x76, 0xe9, - 0x2a, 0x48, 0xbb, 0x50, 0xf2, 0x14, 0x92, 0x49, 0x63, 0xbf, 0xec, 0x3f, 0xff, 0x4d, 0x18, 0xee, - 0xf9, 0xdd, 0x7b, 0x38, 0xe4, 0x5e, 0x63, 0xc8, 0x61, 0x99, 0x0b, 0x48, 0x53, 0x57, 0x2e, 0x7c, - 0x41, 0x22, 0x97, 0xfb, 0xc2, 0xcf, 0xe4, 0x84, 0x0b, 0xc8, 0xc1, 0xbc, 0xd3, 0xaa, 0x93, 0x46, - 0xb5, 0xef, 0xfa, 0x19, 0x65, 0xe0, 0xaa, 0x6f, 0xc3, 0xd8, 0xf7, 0x13, 0x48, 0x40, 0x3d, 0xdd, - 0xfa, 0xd5, 0x54, 0xc7, 0x5f, 0x74, 0x43, 0x9f, 0xab, 0x51, 0xe6, 0xcc, 0x78, 0xc0, 0x8a, 0x0c, - 0x07, 0x29, 0x84, 0x4b, 0x89, 0x39, 0x11, 0x58, 0x12, 0x29, 0x29, 0x30, 0x4b, 0x1b, 0x69, 0x4f, - 0xfb, 0x53, 0xbb, 0x2a, 0xd1, 0x05, 0xc2, 0xbb, 0xc7, 0x8a, 0x6c, 0xaa, 0xca, 0x73, 0x22, 0xde, - 0x36, 0x45, 0x33, 0x36, 0x9c, 0x44, 0xf8, 0x21, 0xa9, 0x49, 0x0a, 0x11, 0x26, 0x2c, 0xc2, 0x10, - 0xc7, 0x92, 0xe4, 0xbb, 0x11, 0xd6, 0x0d, 0x35, 0x78, 0x5c, 0x95, 0xa8, 0x83, 0xf4, 0x6c, 0xa5, - 0xcf, 0x95, 0xfc, 0x9a, 0x45, 0x33, 0x25, 0x36, 0x8e, 0x26, 0x35, 0x50, 0x98, 0xfa, 0x34, 0xc3, - 0x2b, 0xca, 0x22, 0x58, 0x61, 0xe0, 0x84, 0x9d, 0x18, 0xdd, 0x54, 0x46, 0x8f, 0xaa, 0x12, 0x75, - 0xa1, 0xde, 0x40, 0x01, 0xef, 0x95, 0x3e, 0xe3, 0x84, 0x1d, 0x59, 0xa5, 0xc6, 0xe8, 0xa8, 0x3f, - 0x4c, 0x41, 0x92, 0x13, 0xaf, 0xbe, 0xf2, 0x7a, 0x5c, 0x95, 0xa8, 0x93, 0xf5, 0x86, 0x07, 0x66, - 0xaf, 0x6a, 0xfd, 0x34, 0x18, 0x17, 0x00, 0xf1, 0x15, 0xc1, 0x6e, 0xed, 0x83, 0x75, 0xa0, 0xde, - 0x40, 0x01, 0x97, 0x83, 0x1d, 0xf5, 0xff, 0x2f, 0x98, 0xbe, 0x0f, 0xd6, 0xc5, 0x7a, 0xc3, 0x03, - 0xb3, 0xf3, 0x60, 0xd2, 0x18, 0xcb, 0x82, 0xf3, 0x94, 0x12, 0x81, 0x0b, 0x16, 0x00, 0x8b, 0x28, - 0x4b, 0xda, 0x9f, 0xbf, 0xdb, 0x29, 0x69, 0xdd, 0x56, 0x7e, 0x4f, 0xaa, 0x12, 0x5d, 0x83, 0xf6, - 0x50, 0xcb, 0xbc, 0x6b, 0x91, 0x66, 0x5b, 0x76, 0xdb, 0x28, 0x5f, 0x3e, 0xfc, 0xf3, 0x0d, 0x69, - 0x9f, 0x7f, 0x7f, 0x7f, 0x66, 0xfd, 0xbb, 0xa5, 0x8f, 0xed, 0x35, 0x35, 0x27, 0x30, 0x7d, 0xf3, - 0x63, 0xe3, 0x68, 0xeb, 0x8d, 0xa3, 0xfd, 0xda, 0x38, 0xda, 0xd7, 0xad, 0xd3, 0x5b, 0x6f, 0x9d, - 0xde, 0xcf, 0xad, 0xd3, 0xfb, 0xe0, 0x26, 0x34, 0x5f, 0x14, 0xc1, 0x24, 0x84, 0xcc, 0xad, 0xdb, - 0x9f, 0x33, 0x92, 0xaf, 0x40, 0x2c, 0xdd, 0xf3, 0x59, 0xf9, 0x27, 0x4e, 0x64, 0xa0, 0xab, 0xfb, - 0x7a, 0xf1, 0x37, 0x00, 0x00, 0xff, 0xff, 0x24, 0xa2, 0x48, 0x2e, 0xb9, 0x03, 0x00, 0x00, + // 462 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x93, 0xbf, 0x8e, 0xd3, 0x40, + 0x10, 0xc6, 0x63, 0x38, 0x72, 0xc8, 0x0d, 0xc2, 0x20, 0x14, 0xe5, 0x22, 0xef, 0x11, 0xfe, 0x08, + 0x90, 0x88, 0x0b, 0x3a, 0xca, 0x20, 0x0a, 0xaa, 0x44, 0x46, 0x08, 0x89, 0x66, 0xb5, 0xb1, 0xd7, + 0xbe, 0x55, 0xec, 0x9d, 0xd5, 0xae, 0xad, 0x80, 0xc4, 0x0b, 0x40, 0xc5, 0x23, 0xf0, 0x08, 0x3c, + 0x06, 0xe5, 0x95, 0x54, 0x16, 0x4a, 0x0a, 0x90, 0x9f, 0x02, 0x79, 0x1c, 0x73, 0x49, 0xee, 0x72, + 0xbe, 0xc6, 0x5a, 0xcd, 0xf7, 0x9b, 0xf9, 0xf4, 0xc9, 0x33, 0xf6, 0x40, 0xc1, 0x3c, 0xd3, 0x90, + 0x24, 0x9e, 0x39, 0x61, 0x9a, 0x87, 0x9e, 0x62, 0x9a, 0xa5, 0x66, 0xa4, 0x34, 0x64, 0xe0, 0xdc, + 0x6a, 0xd4, 0x51, 0xad, 0xf6, 0x6f, 0xb3, 0x54, 0x48, 0xf0, 0xf0, 0x5b, 0x33, 0xfd, 0xbb, 0x31, + 0xc4, 0x80, 0x4f, 0xaf, 0x7a, 0xd5, 0xd5, 0xe1, 0x97, 0x43, 0xbb, 0x3b, 0xc5, 0x51, 0xce, 0xc4, + 0xbe, 0x27, 0xf3, 0x94, 0xce, 0x12, 0x08, 0xe6, 0x86, 0x2a, 0xae, 0xa9, 0xe1, 0xc6, 0x08, 0x90, + 0x3d, 0xeb, 0xd8, 0x7a, 0x72, 0x30, 0xee, 0x97, 0x05, 0xd9, 0x43, 0xf8, 0x77, 0x64, 0x9e, 0x8e, + 0xb1, 0x3c, 0xe5, 0xfa, 0x6d, 0x5d, 0x74, 0x22, 0xdb, 0x8d, 0x35, 0x0b, 0x78, 0x45, 0x0a, 0x08, + 0x29, 0x97, 0x21, 0x85, 0x28, 0x32, 0x3c, 0x5b, 0x8f, 0xe8, 0x5d, 0xc3, 0xc1, 0xc3, 0xb2, 0x20, + 0x2d, 0xa4, 0xdf, 0x47, 0x7d, 0x8a, 0xf2, 0x6b, 0x19, 0x4e, 0x50, 0xac, 0x1d, 0x1d, 0x61, 0x93, + 0x20, 0x61, 0x22, 0xa5, 0x0b, 0x21, 0x43, 0x58, 0x50, 0x50, 0x5c, 0xee, 0x18, 0x5d, 0x47, 0xa3, + 0x07, 0x65, 0x41, 0xda, 0x50, 0xff, 0x08, 0x81, 0xf7, 0xa8, 0x4f, 0x14, 0x97, 0x5b, 0x56, 0x89, + 0x7d, 0xbc, 0xd5, 0x1f, 0x24, 0x60, 0xf8, 0x8e, 0xd7, 0x01, 0x7a, 0x3d, 0x2c, 0x0b, 0xd2, 0xca, + 0xfa, 0x83, 0x0d, 0xb3, 0x57, 0x95, 0xbe, 0x1b, 0x4c, 0x69, 0x80, 0xe8, 0x92, 0x60, 0x37, 0xce, + 0x82, 0xb5, 0xa0, 0xfe, 0x11, 0x02, 0xfb, 0x83, 0x6d, 0xf5, 0x5f, 0x14, 0xac, 0x7b, 0x16, 0xac, + 0x8d, 0xf5, 0x07, 0x1b, 0x66, 0xe7, 0x83, 0x19, 0x7b, 0x68, 0x72, 0xa5, 0x12, 0xc1, 0x35, 0xcd, + 0xe5, 0x0c, 0x64, 0x28, 0x64, 0xdc, 0xfc, 0xfc, 0xf5, 0x4e, 0x99, 0xde, 0x21, 0xfa, 0x3d, 0x2e, + 0x0b, 0x72, 0x05, 0xda, 0x27, 0x0d, 0xf3, 0xae, 0x41, 0xea, 0x6d, 0x59, 0x6f, 0xa3, 0x71, 0x3e, + 0xdb, 0x8f, 0x58, 0x45, 0x04, 0x2c, 0x13, 0x20, 0x2f, 0xf1, 0xbd, 0x89, 0xbe, 0x4f, 0xcb, 0x82, + 0x5c, 0xad, 0xc1, 0x1f, 0x6e, 0x60, 0x7b, 0xdc, 0x5f, 0xde, 0xff, 0xfb, 0x9d, 0x58, 0x5f, 0xff, + 0xfc, 0x78, 0xd6, 0xfb, 0x7f, 0xc9, 0x1f, 0x9b, 0x5b, 0xae, 0x0f, 0x70, 0xfc, 0xe6, 0xe7, 0xd2, + 0xb5, 0x4e, 0x97, 0xae, 0xf5, 0x7b, 0xe9, 0x5a, 0xdf, 0x56, 0x6e, 0xe7, 0x74, 0xe5, 0x76, 0x7e, + 0xad, 0xdc, 0xce, 0x07, 0x2f, 0x16, 0xd9, 0x49, 0x3e, 0x1b, 0x05, 0x90, 0x7a, 0x55, 0xfb, 0x73, + 0xc9, 0xb3, 0x05, 0xe8, 0xb9, 0x77, 0x7e, 0x56, 0xf6, 0x49, 0x71, 0x33, 0xeb, 0xe2, 0x75, 0xbf, + 0xf8, 0x17, 0x00, 0x00, 0xff, 0xff, 0x56, 0x60, 0x1b, 0xe5, 0x37, 0x04, 0x00, 0x00, } func (this *Params) Equal(that interface{}) bool { @@ -211,6 +225,9 @@ func (this *Params) Equal(that interface{}) bool { if this.SupplierUnbondingPeriodSessions != that1.SupplierUnbondingPeriodSessions { return false } + if this.ApplicationUnbondingPeriodSessions != that1.ApplicationUnbondingPeriodSessions { + return false + } return true } func (m *Params) Marshal() (dAtA []byte, err error) { @@ -233,6 +250,11 @@ func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.ApplicationUnbondingPeriodSessions != 0 { + i = encodeVarintParams(dAtA, i, uint64(m.ApplicationUnbondingPeriodSessions)) + i-- + dAtA[i] = 0x40 + } if m.SupplierUnbondingPeriodSessions != 0 { i = encodeVarintParams(dAtA, i, uint64(m.SupplierUnbondingPeriodSessions)) i-- @@ -309,6 +331,9 @@ func (m *Params) Size() (n int) { if m.SupplierUnbondingPeriodSessions != 0 { n += 1 + sovParams(uint64(m.SupplierUnbondingPeriodSessions)) } + if m.ApplicationUnbondingPeriodSessions != 0 { + n += 1 + sovParams(uint64(m.ApplicationUnbondingPeriodSessions)) + } return n } @@ -480,6 +505,25 @@ func (m *Params) Unmarshal(dAtA []byte) error { break } } + case 8: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ApplicationUnbondingPeriodSessions", wireType) + } + m.ApplicationUnbondingPeriodSessions = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowParams + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ApplicationUnbondingPeriodSessions |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipParams(dAtA[iNdEx:]) diff --git a/x/shared/types/params_test.go b/x/shared/types/params_test.go index 89d0aee22..c9c47dc94 100644 --- a/x/shared/types/params_test.go +++ b/x/shared/types/params_test.go @@ -225,3 +225,38 @@ func TestParams_ValidateSupplierUnbondingPeriodSessions(t *testing.T) { }) } } + +func TestParams_ValidateApplicationUnbondingPeriodSessions(t *testing.T) { + tests := []struct { + desc string + applicationUnbondingPeriodSessions any + err error + }{ + { + desc: "invalid type", + applicationUnbondingPeriodSessions: "invalid", + err: ErrSharedParamInvalid.Wrapf("invalid parameter type: %T", "invalid"), + }, + { + desc: "valid ApplicationUnbondingPeriodSessions", + applicationUnbondingPeriodSessions: uint64(2), + }, + { + desc: "zero ApplicationUnbondingPeriodSessions", + applicationUnbondingPeriodSessions: uint64(0), + err: ErrSharedParamInvalid.Wrapf("invalid ApplicationUnbondingPeriodSessions: (%v)", uint64(0)), + }, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + err := ValidateApplicationUnbondingPeriodSessions(tt.applicationUnbondingPeriodSessions) + if tt.err != nil { + require.Error(t, err) + require.Contains(t, err.Error(), tt.err.Error()) + } else { + require.NoError(t, err) + } + }) + } +} diff --git a/x/tokenomics/types/tx.pb.go b/x/tokenomics/types/tx.pb.go index 5bc7d33e4..91f06eeff 100644 --- a/x/tokenomics/types/tx.pb.go +++ b/x/tokenomics/types/tx.pb.go @@ -133,6 +133,7 @@ type MsgUpdateParam struct { // specified in the `Params` message in `proof/params.proto.` Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` // Types that are valid to be assigned to AsType: + // // *MsgUpdateParam_AsString // *MsgUpdateParam_AsInt64 // *MsgUpdateParam_AsBytes