github.com/mavryk-network/mvgo@v1.19.9/internal/compose/context.go (about) 1 // Copyright (c) 2023 Blockwatch Data Inc. 2 // Author: alex@blockwatch.cc, abdul@blockwatch.cc 3 4 package compose 5 6 import ( 7 "context" 8 "encoding/json" 9 "fmt" 10 "path/filepath" 11 "strconv" 12 "strings" 13 14 "github.com/mavryk-network/mvgo/codec" 15 "github.com/mavryk-network/mvgo/mavryk" 16 "github.com/mavryk-network/mvgo/micheline" 17 "github.com/mavryk-network/mvgo/rpc" 18 19 "github.com/echa/log" 20 ) 21 22 type Engine interface { 23 Clone(Context, []Op, CloneConfig) ([]byte, error) 24 Validate(Context, string) error 25 Run(Context, string) error 26 } 27 28 type Account struct { 29 Id int 30 Address mavryk.Address 31 PrivateKey mavryk.PrivateKey 32 } 33 34 type Context struct { 35 context.Context 36 BaseAccount Account 37 MaxId int 38 Accounts map[mavryk.Address]Account 39 Contracts map[mavryk.Address]*micheline.Script 40 Variables map[string]string 41 Log log.Logger 42 client *rpc.Client // RPC client 43 url string // RPC service URL 44 apiKey string // RPC service API key 45 path string // current compose file path 46 resume bool // continue pipeline execution were we left off 47 mode RunMode // selected engine run mode 48 cache *PipelineCache 49 savedLoggers [2]log.Logger 50 } 51 52 func NewContext(ctx context.Context) Context { 53 return Context{ 54 Context: ctx, 55 Accounts: make(map[mavryk.Address]Account), 56 Contracts: make(map[mavryk.Address]*micheline.Script), 57 MaxId: -1, 58 Variables: make(map[string]string), 59 Log: log.Disabled, 60 cache: NewCache(), 61 } 62 } 63 64 func (c *Context) WithLogger(l log.Logger) *Context { 65 c.Log = l 66 return c 67 } 68 69 func (c *Context) WithUrl(u string) *Context { 70 if !strings.HasPrefix(u, "http") { 71 u = "http://" + u 72 } 73 c.url = u 74 return c 75 } 76 77 func (c *Context) WithApiKey(k string) *Context { 78 c.apiKey = k 79 return c 80 } 81 82 func (c *Context) WithResume(b bool) *Context { 83 c.resume = b 84 return c 85 } 86 87 func (c *Context) WithMode(m RunMode) *Context { 88 c.mode = m 89 return c 90 } 91 92 func (c Context) ShouldResume() bool { 93 return c.resume 94 } 95 96 func (c Context) Filepath() string { 97 return c.path 98 } 99 100 func (c *Context) WithPath(p string) *Context { 101 c.path = p 102 return c 103 } 104 105 func (c *Context) WithBase(k string) *Context { 106 if k == "" { 107 return c 108 } 109 sk, err := mavryk.ParsePrivateKey(k) 110 if err != nil { 111 c.Log.Errorf("base key: %v", err) 112 return c 113 } 114 c.BaseAccount.Id = -1 115 c.BaseAccount.PrivateKey = sk 116 c.BaseAccount.Address = sk.Address() 117 c.AddVariable("base", c.BaseAccount.Address.String()) 118 c.AddAccount(c.BaseAccount) 119 return c 120 } 121 122 func (c *Context) Cache() *PipelineCache { 123 return c.cache 124 } 125 126 func (c *Context) Init() (err error) { 127 if !c.BaseAccount.PrivateKey.IsValid() { 128 err = ErrNoBaseKey 129 return 130 } 131 c.Log.Infof("Using base account %s", c.BaseAccount.Address) 132 c.AddVariable("zero", mavryk.ZeroAddress.String()) 133 c.AddVariable("burn", mavryk.BurnAddress.String()) 134 c.client, err = rpc.NewClient(c.url, nil) 135 if err != nil { 136 return 137 } 138 c.client.ApiKey = c.apiKey 139 c.client.CloseConns = true // fix node EOF 140 err = c.client.Init(c.Context) 141 if err == nil { 142 c.Log.Infof("Using chain %s (%s) with %s blocks", 143 c.client.ChainId, c.client.Params.Network, c.client.Params.MinimalBlockDelay) 144 } 145 return 146 } 147 148 func (c *Context) AddVariable(key, val string) { 149 if key == "" { 150 return 151 } 152 c.Log.Debugf("Add var %s=%s", key, val) 153 c.Variables[CreateVariable(key)] = val 154 } 155 156 func (c *Context) AddAccount(acc Account) { 157 c.Log.Debugf("Add account %d=%s key=%s", acc.Id, acc.Address, acc.PrivateKey) 158 c.Accounts[acc.Address] = acc 159 } 160 161 func (c *Context) ResolveString(val any) (string, error) { 162 if val == nil { 163 return "", nil 164 } 165 v, ok := val.(string) 166 if !ok { 167 return "", fmt.Errorf("invalid type %T, expected string", val) 168 } 169 switch { 170 case IsTimeExpression(v): 171 if conv, err := ConvertTime(v); err != nil { 172 return "", err 173 } else { 174 return conv, nil 175 } 176 case IsVariable(v): 177 if val, ok := c.Variables[v]; ok { 178 return val, nil 179 } else { 180 return "", fmt.Errorf("undefined variable %s", v) 181 } 182 case IsFile(v): 183 fname := filepath.Join(c.path, v[1:]) 184 val, err := ReadJsonFile[string](fname) 185 if err != nil { 186 return "", err 187 } 188 return *val, nil 189 default: 190 return v, nil 191 } 192 } 193 194 func (c *Context) ResolveAddress(val any) (a mavryk.Address, err error) { 195 if val == nil { 196 err = fmt.Errorf("missing value") 197 return 198 } 199 v, err := c.ResolveString(val) 200 if err != nil { 201 return 202 } 203 if v == "" { 204 err = fmt.Errorf("missing value") 205 return 206 } 207 a, err = mavryk.ParseAddress(v) 208 return 209 } 210 211 func (c *Context) ResolveInt64(val any) (i int64, err error) { 212 if val == nil { 213 err = fmt.Errorf("missing value") 214 return 215 } 216 switch v := val.(type) { 217 case int: 218 i = int64(v) 219 case int64: 220 i = v 221 case uint: 222 i = int64(v) 223 case uint64: 224 i = int64(v) 225 case string: 226 i, err = strconv.ParseInt(v, 10, 64) 227 default: 228 err = fmt.Errorf("invalid type %T for int64 value", val) 229 } 230 return 231 } 232 233 func (c *Context) ResolveZ(val any) (z mavryk.Z, err error) { 234 if val == nil { 235 err = fmt.Errorf("missing value") 236 return 237 } 238 switch v := val.(type) { 239 case int: 240 z = mavryk.NewZ(int64(v)) 241 case int64: 242 z = mavryk.NewZ(v) 243 case uint: 244 z = mavryk.NewZ(int64(v)) 245 case uint64: 246 z = mavryk.NewZ(int64(v)) 247 case string: 248 v, err = c.ResolveString(v) 249 if err != nil { 250 return 251 } 252 z, err = mavryk.ParseZ(v) 253 default: 254 err = fmt.Errorf("invalid type %T for bigint value", val) 255 } 256 return 257 } 258 259 func (c *Context) ResolvePrivateKey(val any) (sk mavryk.PrivateKey, err error) { 260 v, ok := val.(string) 261 if !ok { 262 err = fmt.Errorf("invalid type %T, expected string", val) 263 return 264 } 265 if v == "" { 266 return c.BaseAccount.PrivateKey, nil 267 } 268 var addr mavryk.Address 269 addr, err = c.ResolveAddress(val) 270 if err != nil { 271 return 272 } 273 acc, ok := c.Accounts[addr] 274 if !ok { 275 err = ErrNoAccount 276 return 277 } 278 sk = acc.PrivateKey 279 return 280 } 281 282 func (c *Context) ResolveScript(addr mavryk.Address) (*micheline.Script, error) { 283 if s, ok := c.Contracts[addr]; ok { 284 return s, nil 285 } 286 script, err := c.client.GetNormalizedScript(c.Context, addr, "") 287 if err != nil { 288 return nil, err 289 } 290 script.Code.Code = micheline.InvalidPrim 291 script.Code.View = micheline.InvalidPrim 292 c.Contracts[addr] = script 293 return script, nil 294 } 295 296 func (c *Context) Send(op *codec.Op, opts *rpc.CallOptions) (*rpc.Receipt, error) { 297 if c.mode == RunModeSimulate { 298 key, err := opts.Signer.GetKey(c.Context, op.Source) 299 if err != nil { 300 return nil, err 301 } 302 if err := c.client.Complete(c.Context, op, key); err != nil { 303 return nil, err 304 } 305 return c.client.Simulate(c.Context, op, opts) 306 } 307 rcpt, err := c.client.Send(c.Context, op, opts) 308 if err != nil { 309 return nil, err 310 } 311 if !rcpt.IsSuccess() { 312 return rcpt, rcpt.Error() 313 } 314 return rcpt, nil 315 } 316 317 func (c *Context) SubscribeBlocks(cb rpc.ObserverCallback) (int, error) { 318 c.client.Listen() 319 id := c.client.BlockObserver.Subscribe(mavryk.ZeroOpHash, cb) 320 return id, nil 321 } 322 323 func (c *Context) UnsubscribeBlocks(id int) error { 324 c.client.BlockObserver.Unsubscribe(id) 325 return nil 326 } 327 328 func (c *Context) Params() *mavryk.Params { 329 return c.client.Params 330 } 331 332 func (c *Context) HeadBlock() *rpc.BlockHeaderLogEntry { 333 return c.client.BlockObserver.Head() 334 } 335 336 func (c *Context) SwitchLogger(tag, lvl string) { 337 if c.savedLoggers[0] == nil { 338 c.savedLoggers[0] = c.Log 339 c.savedLoggers[1] = c.client.Log 340 } 341 c.Log = c.savedLoggers[0].Clone().WithTag(tag).SetLevelString(lvl) 342 c.client.Log = c.savedLoggers[1].Clone().WithTag(tag).SetLevelString(lvl) 343 } 344 345 func (c *Context) RestoreLogger() { 346 if c.savedLoggers[0] != nil { 347 c.Log = c.savedLoggers[0] 348 c.client.Log = c.savedLoggers[1] 349 } 350 c.savedLoggers[0] = nil 351 c.savedLoggers[1] = nil 352 } 353 354 func (c *Context) WaitNumBlocks(n int) error { 355 if n == 0 { 356 return nil 357 } 358 done := make(chan struct{}) 359 _, err := c.SubscribeBlocks(func(_ *rpc.BlockHeaderLogEntry, _ int64, _ int, _ int, _ bool) bool { 360 n-- 361 if n <= 0 { 362 close(done) 363 return true 364 } 365 return false 366 }) 367 if err != nil { 368 return err 369 } 370 select { 371 case <-done: 372 return nil 373 case <-c.Done(): 374 return c.Err() 375 } 376 } 377 378 func (c Context) Fetch(u string, v any) error { 379 if !strings.HasPrefix(u, "http") { 380 u = c.url + u 381 } 382 raw, err := Fetch[json.RawMessage](c, u) 383 if err != nil { 384 return err 385 } 386 return json.Unmarshal(*raw, v) 387 }