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  }