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  }