github.com/mavryk-network/mvgo@v1.19.9/internal/compose/alpha/task/wait.go (about) 1 // Copyright (c) 2023 Blockwatch Data Inc. 2 // Author: alex@blockwatch.cc, abdul@blockwatch.cc 3 4 package task 5 6 import ( 7 "fmt" 8 "strconv" 9 "time" 10 11 "github.com/mavryk-network/mvgo/codec" 12 "github.com/mavryk-network/mvgo/internal/compose" 13 "github.com/mavryk-network/mvgo/internal/compose/alpha" 14 "github.com/mavryk-network/mvgo/rpc" 15 16 "github.com/pkg/errors" 17 ) 18 19 var _ alpha.TaskBuilder = (*WaitTask)(nil) 20 21 func init() { 22 alpha.RegisterTask("wait", NewWaitTask) 23 } 24 25 type WaitTask struct { 26 Mode alpha.WaitMode // cycle, block, time 27 Relative bool // relative/absolute 28 Value int64 // parsed config value 29 Start int64 // wait start (for relative) 30 } 31 32 func NewWaitTask() alpha.TaskBuilder { 33 return &WaitTask{} 34 } 35 36 func (t *WaitTask) Type() string { 37 return "wait" 38 } 39 40 func (t *WaitTask) Build(ctx compose.Context, task alpha.Task) (*codec.Op, *rpc.CallOptions, error) { 41 if err := t.parse(ctx, task); err != nil { 42 return nil, nil, errors.Wrap(err, "parse") 43 } 44 45 done := make(chan struct{}) 46 id, err := ctx.SubscribeBlocks(func(h *rpc.BlockHeaderLogEntry, height int64, _ int, _ int, _ bool) bool { 47 isDone := false 48 var val int64 49 p := ctx.Params() 50 switch t.Mode { 51 case alpha.WaitModeCycle: 52 val = p.CycleFromHeight(height) 53 if t.Start == 0 { 54 t.Start = val 55 if t.Relative { 56 diff := time.Duration((p.CycleStartHeight(val+t.Value) - height)) * p.MinimalBlockDelay 57 ctx.Log.Infof("waiting for %d cycles, approx %s", t.Value, diff) 58 } else { 59 diff := time.Duration((p.CycleStartHeight(t.Value) - height)) * p.MinimalBlockDelay 60 ctx.Log.Infof("waiting until cycle %d, approx %s", t.Value, diff) 61 } 62 } 63 ctx.Log.Debugf("block %d cycle=%d", height, val) 64 case alpha.WaitModeBlock: 65 val = height 66 if t.Start == 0 { 67 t.Start = val 68 if t.Relative { 69 diff := time.Duration(val+t.Value-height) * p.MinimalBlockDelay 70 ctx.Log.Infof("waiting for %d blocks, approx %s", t.Value, diff) 71 } else { 72 diff := time.Duration(t.Value-height) * p.MinimalBlockDelay 73 ctx.Log.Infof("waiting until block %d, approx %s", t.Value, diff) 74 } 75 } 76 ctx.Log.Debugf("block %d", height) 77 case alpha.WaitModeTime: 78 val = time.Now().UTC().Unix() 79 if t.Start == 0 { 80 t.Start = val 81 if t.Relative { 82 ctx.Log.Infof("waiting for %s", time.Duration(t.Value)*time.Second) 83 } else { 84 diff := time.Unix(t.Value, 0).Sub(time.Unix(val, 0)) 85 ctx.Log.Infof("waiting until %s, approx %s", time.Unix(t.Value, 0), diff) 86 } 87 } 88 ctx.Log.Debugf("block %d time=%d", height, val) 89 } 90 if t.Relative { 91 isDone = val >= t.Start+t.Value 92 } else { 93 isDone = val >= t.Value 94 } 95 if isDone { 96 ctx.Log.Debug("wait done") 97 close(done) 98 return true 99 } 100 return false 101 }) 102 if err != nil { 103 return nil, nil, err 104 } 105 defer ctx.UnsubscribeBlocks(id) 106 107 // wait 108 select { 109 case <-done: 110 return nil, nil, compose.ErrSkip 111 case <-ctx.Done(): 112 return nil, nil, ctx.Err() 113 } 114 } 115 116 func (t *WaitTask) Validate(ctx compose.Context, task alpha.Task) error { 117 return t.parse(ctx, task) 118 } 119 120 func (t *WaitTask) parse(ctx compose.Context, task alpha.Task) error { 121 if task.WaitMode == alpha.WaitModeInvalid { 122 return fmt.Errorf("missing wait mode") 123 } 124 t.Mode = task.WaitMode 125 val, err := ctx.ResolveString(task.Value) 126 if err != nil { 127 return errors.Wrap(err, "value") 128 } 129 if val == "" { 130 return fmt.Errorf("missing value") 131 } 132 t.Relative = val[0] == '+' 133 if t.Relative { 134 val = val[1:] 135 } 136 switch t.Mode { 137 case alpha.WaitModeCycle, alpha.WaitModeBlock: 138 var u uint64 139 u, err = strconv.ParseUint(val, 10, 64) 140 if err != nil { 141 return err 142 } 143 t.Value = int64(u) 144 case alpha.WaitModeTime: 145 if t.Relative { 146 var d time.Duration 147 d, err = time.ParseDuration(val) 148 if err != nil { 149 return err 150 } 151 t.Value = int64(d / time.Second) 152 } else { 153 // accept time expressions ($now+dur) and timestamps as text or unix seconds 154 val, err = compose.ConvertTime(val) 155 if err != nil { 156 return err 157 } 158 var tm time.Time 159 tm, err = time.Parse(time.RFC3339, val) 160 if err != nil { 161 return err 162 } 163 t.Value = tm.Unix() 164 } 165 } 166 return nil 167 }