github.com/lino-network/lino@v0.6.11/x/global/manager/manager.go (about) 1 package manager 2 3 import ( 4 "fmt" 5 "strconv" 6 7 codec "github.com/cosmos/cosmos-sdk/codec" 8 sdk "github.com/cosmos/cosmos-sdk/types" 9 10 "github.com/lino-network/lino/param" 11 linotypes "github.com/lino-network/lino/types" 12 13 "github.com/lino-network/lino/utils" 14 "github.com/lino-network/lino/x/global/model" 15 "github.com/lino-network/lino/x/global/types" 16 17 accmn "github.com/lino-network/lino/x/account/manager" 18 ) 19 20 const ( 21 exportVersion = 2 22 importVersion = 2 23 ) 24 25 // GlobalManager - a event manager module, it schedules event, execute events 26 // and store errors of executed events. 27 type GlobalManager struct { 28 storage model.GlobalStorage 29 paramHolder param.ParamKeeper 30 31 // events 32 hourly linotypes.BCEventExec 33 daily linotypes.BCEventExec 34 monthly linotypes.BCEventExec 35 yearly linotypes.BCEventExec 36 } 37 38 // NewGlobalManager - return the global manager 39 func NewGlobalManager(key sdk.StoreKey, keeper param.ParamKeeper, cdc *codec.Codec, 40 hourly linotypes.BCEventExec, 41 daily linotypes.BCEventExec, 42 monthly linotypes.BCEventExec, 43 yearly linotypes.BCEventExec, 44 ) GlobalManager { 45 return GlobalManager{ 46 storage: model.NewGlobalStorage(key, cdc), 47 paramHolder: keeper, 48 hourly: hourly, 49 daily: daily, 50 monthly: monthly, 51 yearly: yearly, 52 } 53 } 54 55 func (gm GlobalManager) InitGenesis(ctx sdk.Context) { 56 // will be updated on the first OnBeginBlock. 57 gm.storage.SetGlobalTime(ctx, &model.GlobalTime{ 58 ChainStartTime: ctx.BlockTime().Unix(), 59 LastBlockTime: ctx.BlockTime().Unix(), 60 PastMinutes: 0, 61 }) 62 } 63 64 // OnBeginBlock - update internal time related fields and execute 65 // blockchain scheduled events. 66 func (gm GlobalManager) OnBeginBlock(ctx sdk.Context) { 67 blockTime := ctx.BlockHeader().Time.Unix() 68 globalTime := gm.storage.GetGlobalTime(ctx) 69 if blockTime < globalTime.LastBlockTime { 70 // our simulation tests do not follow tendermint's spec that 71 // the BFT Time H2.Time > H1.Time, if H2 = H1 + 1. 72 // precisely, we use a same time point all the time. 73 // panic("Premise of BFT time is BROKEN") 74 return 75 } 76 pastMinutes := globalTime.PastMinutes 77 nowMinutes := (blockTime - globalTime.ChainStartTime) / 60 78 for next := pastMinutes + 1; next <= nowMinutes; next++ { 79 gm.execBCEventsAt(ctx, next) 80 } 81 globalTime.PastMinutes = nowMinutes 82 gm.storage.SetGlobalTime(ctx, globalTime) 83 } 84 85 // execBCEventsAt - execute blockchain events. 86 func (gm GlobalManager) execBCEventsAt(ctx sdk.Context, pastMinutes int64) { 87 if pastMinutes%60 == 0 && gm.hourly != nil { 88 gm.appendBCErr(ctx, gm.hourly(ctx)...) 89 } 90 if pastMinutes%linotypes.MinutesPerDay == 0 && gm.daily != nil { 91 gm.appendBCErr(ctx, gm.daily(ctx)...) 92 } 93 if pastMinutes%linotypes.MinutesPerMonth == 0 && gm.monthly != nil { 94 gm.appendBCErr(ctx, gm.monthly(ctx)...) 95 } 96 if pastMinutes%linotypes.MinutesPerYear == 0 && gm.yearly != nil { 97 gm.appendBCErr(ctx, gm.yearly(ctx)...) 98 } 99 } 100 101 func (gm GlobalManager) appendBCErr(ctx sdk.Context, newErrs ...linotypes.BCEventErr) { 102 errs := gm.storage.GetBCErrors(ctx) 103 for _, e := range newErrs { 104 ctx.Logger().Error(fmt.Sprintf("eventErr: %+v", e)) 105 errs = append(errs, e) 106 } 107 gm.storage.SetBCErrors(ctx, errs) 108 } 109 110 // OnEndBlock - update last block time. 111 func (gm GlobalManager) OnEndBlock(ctx sdk.Context) { 112 globalTime := gm.storage.GetGlobalTime(ctx) 113 globalTime.LastBlockTime = ctx.BlockHeader().Time.Unix() 114 gm.storage.SetGlobalTime(ctx, globalTime) 115 } 116 117 func (gm GlobalManager) RegisterEventAtTime(ctx sdk.Context, unixTime int64, event linotypes.Event) sdk.Error { 118 // XXX(yumin): events are executed at begin block, but not include 119 // the current time. So event registered at this block time will be executed 120 // in the next block. 121 if unixTime < ctx.BlockHeader().Time.Unix() { 122 return types.ErrRegisterExpiredEvent(unixTime) 123 } 124 125 // see if event is allowed or not. 126 if !gm.storage.CanEncode(event) { 127 return types.ErrRegisterInvalidEvent() 128 } 129 130 eventList := gm.storage.GetTimeEventList(ctx, unixTime) 131 eventList.Events = append(eventList.Events, event) 132 gm.storage.SetTimeEventList(ctx, unixTime, eventList) 133 return nil 134 } 135 136 func (gm GlobalManager) runEventIsolated(ctx sdk.Context, exec linotypes.EventExec, event linotypes.Event) sdk.Error { 137 cachedCtx, write := ctx.CacheContext() 138 err := exec(cachedCtx, event) 139 if err == nil { 140 write() 141 return nil 142 } 143 return err 144 } 145 146 // ExecuteEvents - execute events, log errors to storage, up to current time (exclusively). 147 func (gm GlobalManager) ExecuteEvents(ctx sdk.Context, exec linotypes.EventExec) { 148 currentTime := ctx.BlockTime().Unix() 149 lastBlockTime := gm.storage.GetGlobalTime(ctx).LastBlockTime 150 for i := lastBlockTime; i < currentTime; i++ { 151 events := gm.storage.GetTimeEventList(ctx, i) 152 for _, event := range events.Events { 153 err := gm.runEventIsolated(ctx, exec, event) 154 if err != nil { 155 ctx.Logger().Error(fmt.Sprintf( 156 "ExecEventErr: %+v, code: %d", event, err.Code())) 157 errs := gm.storage.GetEventErrors(ctx) 158 errs = append(errs, model.EventError{ 159 Time: i, 160 Event: event, 161 ErrCode: err.Code(), 162 }) 163 gm.storage.SetEventErrors(ctx, errs) 164 } 165 } 166 gm.storage.RemoveTimeEventList(ctx, i) 167 } 168 169 // upgrade-3 unlock all. 170 if ctx.BlockHeight() == linotypes.Upgrade5Update3 { 171 gm.execFutureEvents(ctx, exec, func(ts int64, event linotypes.Event) bool { 172 if ts < currentTime { 173 return false 174 } 175 switch event.(type) { 176 case accmn.ReturnCoinEvent: 177 return true 178 default: 179 return false 180 } 181 }) 182 } 183 } 184 185 // resolveFutureEvents does not return err. Events are executed in an isolated env, 186 // so it's fine to ignore errors but leave them in the store. 187 func (gm GlobalManager) execFutureEvents( 188 ctx sdk.Context, exec linotypes.EventExec, 189 filter func(ts int64, event linotypes.Event) bool) { 190 eventLists := make(map[int64]*linotypes.TimeEventList) 191 store := gm.storage.PartialStoreMap(ctx) 192 // change store will invalidate the iterator, so copy first. 193 store[string(model.TimeEventListSubStore)].Iterate(func(key []byte, val interface{}) bool { 194 ts, err := strconv.ParseInt(string(key), 10, 64) 195 if err != nil { 196 return false 197 } 198 eventLists[ts] = val.(*linotypes.TimeEventList) 199 return false 200 }) 201 202 for ts, eventList := range eventLists { 203 if eventList == nil { 204 continue 205 } 206 left := linotypes.TimeEventList{} 207 for _, event := range eventList.Events { 208 if filter(ts, event) { 209 err := gm.runEventIsolated(ctx, exec, event) 210 if err != nil { 211 left.Events = append(left.Events, event) 212 } 213 } else { 214 left.Events = append(left.Events, event) 215 } 216 } 217 if len(left.Events) == 0 { 218 gm.storage.RemoveTimeEventList(ctx, ts) 219 } else { 220 gm.storage.SetTimeEventList(ctx, ts, &left) 221 } 222 } 223 } 224 225 // GetLastBlockTime - get last block time from KVStore 226 func (gm GlobalManager) GetLastBlockTime(ctx sdk.Context) int64 { 227 return gm.storage.GetGlobalTime(ctx).LastBlockTime 228 } 229 230 // GetPastDay - get start time from KVStore to calculate past day 231 func (gm GlobalManager) GetPastDay(ctx sdk.Context, unixTime int64) int64 { 232 globalTime := gm.storage.GetGlobalTime(ctx) 233 pastDay := (unixTime - globalTime.ChainStartTime) / (3600 * 24) 234 if pastDay < 0 { 235 return 0 236 } 237 return pastDay 238 } 239 240 func (gm GlobalManager) GetBCEventErrors(ctx sdk.Context) []linotypes.BCEventErr { 241 return gm.storage.GetBCErrors(ctx) 242 } 243 244 func (gm GlobalManager) GetEventErrors(ctx sdk.Context) []model.EventError { 245 return gm.storage.GetEventErrors(ctx) 246 } 247 248 func (gm GlobalManager) GetGlobalTime(ctx sdk.Context) model.GlobalTime { 249 return *gm.storage.GetGlobalTime(ctx) 250 } 251 252 func (gm GlobalManager) ExportToFile(ctx sdk.Context, cdc *codec.Codec, filepath string) error { 253 state := &model.GlobalTablesIR{ 254 Version: exportVersion, 255 } 256 storeMap := gm.storage.PartialStoreMap(ctx) 257 258 // export events 259 storeMap[string(model.TimeEventListSubStore)].Iterate(func(key []byte, val interface{}) bool { 260 ts, err := strconv.ParseInt(string(key), 10, 64) 261 if err != nil { 262 panic(err) 263 } 264 events := val.(*linotypes.TimeEventList) 265 state.GlobalTimeEventLists = append(state.GlobalTimeEventLists, model.GlobalTimeEventsIR{ 266 UnixTime: ts, 267 TimeEventList: *events, 268 }) 269 return false 270 }) 271 272 globalt := gm.storage.GetGlobalTime(ctx) 273 state.Time = model.GlobalTimeIR(*globalt) 274 275 // errors are not export, because we are performing an upgrade, why not fix the errors? 276 // EventErrorSubStore 277 // BCErrorSubStore 278 279 return utils.Save(filepath, cdc, state) 280 } 281 282 func (gm GlobalManager) ImportFromFile(ctx sdk.Context, cdc *codec.Codec, filepath string) error { 283 rst, err := utils.Load(filepath, cdc, func() interface{} { return &model.GlobalTablesIR{} }) 284 if err != nil { 285 return err 286 } 287 table := rst.(*model.GlobalTablesIR) 288 289 if table.Version != importVersion { 290 return fmt.Errorf("unsupported import version: %d", table.Version) 291 } 292 293 // import events 294 for _, v := range table.GlobalTimeEventLists { 295 gm.storage.SetTimeEventList(ctx, v.UnixTime, &v.TimeEventList) 296 } 297 298 t := model.GlobalTime(table.Time) 299 gm.storage.SetGlobalTime(ctx, &t) 300 return nil 301 }