github.com/KiraCore/sekai@v0.3.43/x/layer2/keeper/dapp_session.go (about) 1 package keeper 2 3 import ( 4 "github.com/KiraCore/sekai/x/layer2/types" 5 sdk "github.com/cosmos/cosmos-sdk/types" 6 ) 7 8 func (k Keeper) SetDappSession(ctx sdk.Context, session types.ExecutionRegistrar) { 9 bz := k.cdc.MustMarshal(&session) 10 store := ctx.KVStore(k.storeKey) 11 store.Set(types.ExecutionRegistrarKey(session.DappName), bz) 12 } 13 14 func (k Keeper) DeleteDappSession(ctx sdk.Context, name string) { 15 store := ctx.KVStore(k.storeKey) 16 store.Delete(types.ExecutionRegistrarKey(name)) 17 } 18 19 func (k Keeper) GetDappSession(ctx sdk.Context, name string) types.ExecutionRegistrar { 20 store := ctx.KVStore(k.storeKey) 21 bz := store.Get(types.ExecutionRegistrarKey(name)) 22 if bz == nil { 23 return types.ExecutionRegistrar{} 24 } 25 26 sessionInfo := types.ExecutionRegistrar{} 27 k.cdc.MustUnmarshal(bz, &sessionInfo) 28 return sessionInfo 29 } 30 31 func (k Keeper) GetAllDappSessions(ctx sdk.Context) []types.ExecutionRegistrar { 32 store := ctx.KVStore(k.storeKey) 33 34 sessions := []types.ExecutionRegistrar{} 35 it := sdk.KVStorePrefixIterator(store, []byte(types.PrefixDappSessionKey)) 36 defer it.Close() 37 38 for ; it.Valid(); it.Next() { 39 session := types.ExecutionRegistrar{} 40 k.cdc.MustUnmarshal(it.Value(), &session) 41 sessions = append(sessions, session) 42 } 43 return sessions 44 } 45 46 // Halt the dapp if verifiers and executors number does not meet on ExitDapp and after Jail operation 47 func (k Keeper) HaltDappIfNoEnoughActiveOperators(ctx sdk.Context, dappName string) bool { 48 dapp := k.GetDapp(ctx, dappName) 49 executors := k.GetDappExecutors(ctx, dappName) 50 verifiers := k.GetDappVerifiers(ctx, dappName) 51 // active executors 52 activeExecutors := int(0) 53 for _, executor := range executors { 54 if executor.Status == types.OperatorActive { 55 activeExecutors++ 56 } 57 } 58 activeVerifiers := int(0) 59 for _, verifier := range verifiers { 60 if verifier.Status == types.OperatorActive { 61 activeVerifiers++ 62 } 63 } 64 65 if activeExecutors < int(dapp.ExecutorsMin) || activeVerifiers < int(dapp.VerifiersMin) { 66 dapp.Status = types.Halted 67 k.SetDapp(ctx, dapp) 68 return true 69 } 70 return false 71 } 72 73 func (k Keeper) ResetNewSession(ctx sdk.Context, name string, prevLeader string) { 74 operators := k.GetDappOperators(ctx, name) 75 for _, operator := range operators { 76 if operator.Status == types.OperatorExiting { 77 // If the operator leaving the dApp was a verifier then 78 // as the result of the exit tx his LP tokens bond should be returned once the record is deleted. 79 // The bond can only be claimed if and only if the status didn’t change to jailed in the meantime. 80 if operator.BondedLpAmount.IsPositive() { 81 dapp := k.GetDapp(ctx, name) 82 dappLpToken := dapp.LpToken() 83 lpCoins := sdk.NewCoins(sdk.NewCoin(dappLpToken, operator.BondedLpAmount)) 84 addr := sdk.MustAccAddressFromBech32(operator.Operator) 85 err := k.bk.SendCoinsFromModuleToAccount(ctx, types.ModuleName, addr, lpCoins) 86 if err != nil { 87 panic(err) 88 } 89 } 90 // remove operators exiting when session ends 91 k.DeleteDappOperator(ctx, name, operator.Operator) 92 } 93 } 94 95 // halt the dapp if operators condition does not meet on session reset 96 halted := k.HaltDappIfNoEnoughActiveOperators(ctx, name) 97 if halted { 98 return 99 } 100 101 leader := "" 102 executors := k.GetDappExecutors(ctx, name) 103 if len(executors) > 0 { 104 leader = executors[0].Operator 105 for index, executor := range executors { 106 if executor.Operator == prevLeader { 107 leader = executors[(index+1)%len(executors)].Interx 108 } 109 } 110 } 111 if leader == "" { 112 operator := k.GetDappOperator(ctx, name, prevLeader) 113 if operator.Status == types.OperatorActive { 114 leader = prevLeader 115 } 116 } 117 118 session := k.GetDappSession(ctx, name) 119 session.NextSession = &types.DappSession{ 120 Leader: leader, 121 Start: uint64(ctx.BlockTime().Unix()), 122 StatusHash: "", 123 Status: types.SessionScheduled, 124 } 125 k.SetDappSession(ctx, session) 126 127 // halt the dapp if next session leader is not available 128 if session.NextSession.Leader == "" { 129 dapp := k.GetDapp(ctx, name) 130 dapp.Status = types.Halted 131 k.SetDapp(ctx, dapp) 132 } 133 } 134 135 func (k Keeper) CreateNewSession(ctx sdk.Context, name string, prevLeader string) { 136 session := k.GetDappSession(ctx, name) 137 session.PrevSession = session.CurrSession 138 session.CurrSession = session.NextSession 139 k.SetDappSession(ctx, session) 140 141 // handle bridge and mint messages 142 msgServer := NewMsgServerImpl(k) 143 for _, msg := range session.PrevSession.OnchainMessages { 144 cacheCtx, write := ctx.CacheContext() 145 var err error 146 switch msg := msg.GetCachedValue().(type) { 147 case *types.MsgTransferDappTx: 148 _, err = msgServer.TransferDappTx(sdk.WrapSDKContext(cacheCtx), msg) 149 case *types.MsgAckTransferDappTx: 150 _, err = msgServer.AckTransferDappTx(sdk.WrapSDKContext(ctx), msg) 151 case *types.MsgMintCreateFtTx: 152 _, err = msgServer.MintCreateFtTx(sdk.WrapSDKContext(ctx), msg) 153 case *types.MsgMintCreateNftTx: 154 _, err = msgServer.MintCreateNftTx(sdk.WrapSDKContext(ctx), msg) 155 case *types.MsgMintIssueTx: 156 _, err = msgServer.MintIssueTx(sdk.WrapSDKContext(ctx), msg) 157 case *types.MsgMintBurnTx: 158 _, err = msgServer.MintBurnTx(sdk.WrapSDKContext(ctx), msg) 159 } 160 if err == nil { 161 write() 162 } 163 } 164 k.ResetNewSession(ctx, name, prevLeader) 165 } 166 167 // **Next Session** 168 169 // A summary of possible “next session” states: 170 // - `unscheduled` - The session is NOT ready yet to be picked up by another dApp Leader (e.g. previous dApp Session is ongoing) 171 // - `scheduled`- The session is ready to be picked up by the next dApp Leader 172 // - `ongoing` - Session was claimed by the dApp Leader and is currently being executed 173 174 // **Current Session** 175 176 // Allow every executor to send a `denounce-leader-tx` (true/false) which at any point in time via majority vote can invalidate 177 // further changes to the current session 178 // and will cause the next session to change its status to `scheduled`. 179 180 // It must be possible for the automatic denouncement of the leader to happen if the current session data is not updated within 181 // `update_time_max` since `sessions.next.data.time` while the executor 182 // did NOT change his status to `paused` 183 // If automatic denouement happens then the same as in the case of manual votes of denouement the current session status 184 // should change to `denounced` while the next session status should be set to `scheduled`. 185 186 // The most important property of the current session is `data`. 187 // The objective of the leader is to periodically update the `sessions.current.data` property with hashes of the `old` state, 188 // user `input`, the `new` proposed state, expected changes to account balances, 189 // and optional `proof` of the correctness of the execution 190 191 // The `transition-dapp-tx` can be submitted ONLY by the leader and can be sent at ANY point in time allowing for finality 192 // the moment a sufficient number of verifiers or executors accept the changes**. 193 194 // Whenever leader submits the new dApp state transition the `version` MUST be included and match the application version**. 195 // The transition tx should fail if the dApp `version` is NOT correct, the leader status is NOT active or 196 // if the `old` state hash does NOT match the `sessions.previous.data.new` hash. 197 198 // There are no limitations to how many times the current session leader can submit `transition-dapp-tx` to replace the content 199 // of the current session data, however, every time a new dApp state transition is submitted, 200 // the list of approvals must be wiped, meaning any verifications will be lost. 201 202 // Regardless if the approvals are lost or not the performance counters should maintain their count allowing for a fair 203 // reward distribution to operators later on. 204 // Additionally, the dApp leader must send the `transition-dapp-tx` before `update_time_max` elapses, 205 // otherwise his session will expire, and sending of any further `transition-dapp-tx` should fail, and the 206 // [sessions.currend.data.final](http://sessions.currend.data.final)` flag should be set to true. 207 // Finally, the session leader must be able to include a boolean flag in the `transition-dapp-tx` 208 // indicating if the transition tx is final on his own. 209 // If the final session flag is set then the leader should NOT be allowed to submit another 210 // `transition-dapp-tx` to the current session. 211 212 // If the non-final session is approved before the finality flag is set and the session changes state to `approved` 213 // then the default next session leader should become the current session leader. 214 // This way the operator can continue execution with all data already available to him and provide fully uninterrupted service 215 // to the users. 216 // In order to give verifiers sufficient time to approve non-final sessions we should prohibit the submission of new 217 // `transition-dapp-tx` by the leader unless no less than `update_time_max/2` seconds elapsed since the last submission. 218 219 // A summary of possible “current session” states: 220 221 // - `accepted` - Session was accepted by verifiers and the new state is irreversible (changes will be applied to the blockchain unless status changes to halted or failed) 222 // - `ongoing` - Session was claimed by the dApp Leader and is currently being executed 223 // - `denounced` - The session was rejected by either a manual vote of executors to change the leader or due to the leader becoming jailed. 224 // - `halted` - The session was halted because it was questioned by one of the verifiers submitting evidence against it. Validators will have to assist unhalting and decide slashing penalty to either validator or fisherman 225 // - `failed` - The session failed to transition from “current” into “previous session” because internal data submitted by the leader was invalid and could NOT result in the modification of the blockchain state. 226 227 // In the case where the current dApp Session becomes `failed` or `halted` but the new Leader already started execution based on an unverified state then both sessions (current and next) should be rejected by the network and a consecutive dApp Leader MUST start the execution from the dApp State that was persisted as a result of previous Session. If by any chance resources from the previous dApp state are no longer accessible (e.g. IPFS/URL link does not work and no validator has it saved on their execution node) then users can begin recovery of funds in accordance to the latest known settlement of balances within the Execution Registrar. 228 229 // **Previous Session** 230 231 // In order for the already approved “current session” to become a “previous session” the changes proposed in the `data.internal` 232 // section of the current session properties must be applied to the blockchain state. 233 // We will differentiate two types of internal data: 234 235 // - Application Data (`appdata`) - A simple key-value pair dictionary, each application should be able to store on-chain 236 // up to `256` keys with up to `8192` characters each. 237 // The purpose of `appdata` is to enable applications communicate informations to the users that can be easily accessed 238 // and can be trusted, this might include location of IPFS gateways preserving dApp state files, 239 // deposit addresses of bridges, communicates to other applications and more. 240 // - JSON Structure: 241 242 // ```json 243 // { "<key-1>": "<value-1>", "<key-2>": "<value-2>", ... } 244 // ``` 245 246 // - Key names must be unique and adhere to the same rules as IR keys 247 // - Values must be a string or set to integer value `0` indicating that the key should be deleted 248 // - Compression Algorithm: `zip (max compression, level 9)` 249 // - Format: `base64` 250 // - Cross-Application Message Data (`xamdata`) - A simple json structure containing changes that must be communicated 251 // to the blockchain and in particular to the Application Bridge Registrar (ABR). 252 // The ABR is responsible for maintaining balances of users who deposit tokens to various dApps, 253 // minting new tokens and sending communicates between dApps & internal modules. 254 // - JSON Structure: 255 256 // ```json 257 // { // key-array list defninig changes that must be applied to the Application Bridge Registrar 258 // "<key-1>": [ "<value-1>", "<value-2>", "<value-3>", ... ], ... } 259 // ``` 260 261 // - Compression Algorithm: `zip (max compression, level 9)` 262 // - Format: `base64` 263 264 // It is NOT guaranteed that even if dApp state was accepted the changes to the blockchain can actually be executed. 265 // For example application might be requesting to transfer to the user more assets then it actually holds. 266 // In such cases the State of the “current session” state must change to `failed` and the next session should automatically be wiped 267 // and set to `scheaduled`. 268 // The operators must also be explicitly informed what is the exact reason for the failure though `errors` 269 // property in the current session. 270 // Any failures should automatically result in the current session leader being denounced, 271 // meaning that the next session leader should be changed even if session finalization flag was not set in `transition-dapp-tx`. 272 273 // **Reporting Failed Execution** 274 275 // Fisherman (verifiers and executors who are not a leader) must be able to report any potential misbehavior of a leader 276 // and/or otherwise issues with the application to ensure that funds locked within can remain safe. 277 // In certain cases the finality gadget might not even exists or be possible to be created while fisherman might want 278 // to create custom systems monitoring behavior of the application far beyond the code logic itself. 279 // ANY fisherman must be able to send a `reject-dapp-transition-tx` with a message body (up to `8192` characters) 280 // containing a dApp name, session id and a reason for halting the application. 281 // As the result of `reject-dapp-transition-tx` the status of the current session should immediately change to 282 // `halted` and a `dapp-slash` proposal raised, similar to the way that the validator slashing proposals are created. 283 // In this case however the validators will need to make following judgements: 284 285 // - should the dApp session be unhalted and state transition accepted (boolean) - true / false 286 // - if `true` then status of session should change to `ongoing` unless sufficient number of approvals is sent then `accepted` 287 // - if `false` then status should be set to `failed` and `errors` specify that governance vote decided to reject the session 288 // - what percentage of the fisherman bond should be slashed (percentage) - from 0 to 1 (100%) 289 // - slashed dApp LP tokens should be simply deminted (increasing value of other LP token holders positions) 290 // - if fisherman is slashed the validator can NOT be slashed 291 // - what percentage of the validator stake should be slashed (percentage) - from 0 to 0.1 (10%) 292 // - From the amount slashed 1% should be sent to fisherman address as reward and 99% to community pool 293 // - if validator is slashed the fisherman can NOT be slashed 294 295 // Voting on `dapp-slash` proposal should only be possible by validators **with exception for the dApp leader**. 296 // If quorum is not reached or result is inconclusive then the default behavior should be no slashing and 297 // rejection of the dApp session state. If as the result of the proposal the bond of the fisherman was slashed, 298 // then he should be removed from the fisherman position until sufficient amount is locked by him again.