github.com/mavryk-network/mvgo@v1.19.9/micheline/translate_trace.go (about) 1 // Copyright (c) 2020-2021 Blockwatch Data Inc. 2 // Author: alex@blockwatch.cc 3 4 //go:build trace 5 // +build trace 6 7 package micheline 8 9 import ( 10 "encoding/json" 11 "fmt" 12 "strconv" 13 ) 14 15 func walkTree(m map[string]interface{}, label string, typ Type, stack *Stack, lvl int) error { 16 // abort infinite type recursions 17 if lvl > 99 { 18 return fmt.Errorf("micheline: max nesting level reached") 19 } 20 21 // take next value from stack 22 val := stack.Pop() 23 24 // Trace Helper 25 Trace(func(log LogFn) { 26 ps := func(p Prim) string { 27 if p.WasPacked { 28 return "unpacked" 29 } 30 return "" 31 } 32 oc := func(p Prim) string { 33 if p.OpCode == 0 { 34 return p.Type.String() 35 } 36 return p.OpCode.String() 37 } 38 log("L%0d: %s/%s %s typ[%s]=%s", lvl, label, typ.Label(), ps(typ.Prim), typ.OpCode, typ.Dump()) 39 log("L%0d: %s/%s %s val[%s]=%s", lvl, label, typ.Label(), ps(val), oc(val), val.Dump()) 40 log("L%0d: %s stack[%d]:\n%s\n", lvl, label, stack.Len(), stack.DumpIdent(4)) 41 }) 42 43 // unfold unexpected pairs 44 if !val.WasPacked && val.IsPair() && !typ.IsPair() { 45 unfolded := val.UnfoldPair(typ) 46 Trace(func(log LogFn) { 47 log("L%0d: %s EXTRA UNFOLD PAIR args[%d(+%d)]=%s typ=%s", lvl, label, stack.Len(), len(unfolded), NewSeq(unfolded...).Dump(), typ.Dump()) 48 }) 49 stack.Push(unfolded...) 50 Trace(func(log LogFn) { 51 log("L%0d: %s stack[%d]:\n%s\n", lvl, label, stack.Len(), stack.DumpIdent(4)) 52 }) 53 val = stack.Pop() 54 } 55 56 // detect type for unpacked values 57 if val.WasPacked && (!val.IsScalar() || typ.OpCode == T_BYTES) { 58 labels := typ.Anno 59 typ = val.BuildType() 60 typ.WasPacked = true 61 typ.Anno = labels 62 Trace(func(log LogFn) { 63 log("L%0d: packed type detect typ=%s %s val=%s", lvl, typ.OpCode, typ.Dump(), val.Dump()) 64 }) 65 } 66 67 // make sure value + type we're going to process actually match up 68 // accept any kind of pairs/seq which will be unfolded again below 69 if !typ.IsPair() && !val.IsSequence() && !val.matchOpCode(typ.OpCode) { 70 return fmt.Errorf("micheline: type mismatch: type[%s]=%s value[%s/%d]=%s", 71 typ.OpCode, typ.DumpLimit(512), val.Type, val.OpCode, val.DumpLimit(512)) 72 } 73 74 // get the label from our type tree 75 typeLabel := typ.Label() 76 haveTypeLabel := len(typeLabel) > 0 77 haveKeyLabel := label != EMPTY_LABEL && len(label) > 0 78 if label == EMPTY_LABEL { 79 if haveTypeLabel { 80 // overwrite struct field label from type annotation 81 label = typeLabel 82 } else { 83 // or use sequence number when type annotation is empty 84 label = strconv.Itoa(len(m)) 85 } 86 } 87 88 // attach sub-records and array elements based on type code 89 switch typ.OpCode { 90 case T_SET: 91 // set <comparable type> 92 if len(typ.Args) == 0 { 93 return fmt.Errorf("micheline: broken T_SET type prim") 94 } 95 arr := make([]interface{}, 0, len(val.Args)) 96 for _, v := range val.Args { 97 if v.IsScalar() && !v.IsSequence() { 98 // array of scalar types 99 arr = append(arr, v.Value(typ.Args[0].OpCode)) 100 } else { 101 // array of complex types 102 mm := make(map[string]interface{}) 103 if err := walkTree(mm, EMPTY_LABEL, Type{typ.Args[0]}, NewStack(v), lvl+1); err != nil { 104 return err 105 } 106 arr = append(arr, mm) 107 } 108 } 109 m[label] = arr 110 111 case T_LIST: 112 // list <type> 113 114 // fix for pair(list, x) - we wrongly prevent a nested list from being unpacked 115 // this is to compensate for CanUnfold() and another case where list/pair unfold 116 // does not work 117 // 118 // Conflicting cases 119 // TestParamsValues/Jakartanet/oorcMSVaYBH3rcsDJ3n8EvpU4e8h38WFjJJfYUu2wXyDN4N7NMX 120 // TestStorageValues/Mainnet/KT1K4jn23GonEmZot3pMGth7unnzZ6EaMVjY 121 // 122 if len(typ.Args) > 0 && !typ.Args[0].IsList() && len(val.Args) > 1 && !val.LooksLikeContainer() && val.Args[0].IsSequence() && !val.Args[0].IsConvertedComb() { 123 Trace(func(log LogFn) { 124 log("L%0d: %s NESTED LIST args[%d(+%d)]=%s", lvl, label, stack.Len(), len(val.Args), val.Dump()) 125 }) 126 stack.Push(val.Args...) 127 Trace(func(log LogFn) { 128 log("L%0d: %s stack[%d]:\n%s\n", lvl, label, stack.Len(), stack.DumpIdent(4)) 129 }) 130 val = stack.Pop() 131 } 132 133 arr := make([]interface{}, 0, len(val.Args)) 134 for i, v := range val.Args { 135 // lists may contain different types, i.e. when unpack+detect is used 136 valType := typ.Args[0] 137 if len(typ.Args) > i { 138 valType = typ.Args[i] 139 } 140 // unpack into map 141 mm := make(map[string]interface{}) 142 if err := walkTree(mm, EMPTY_LABEL, Type{valType}, NewStack(v), lvl+1); err != nil { 143 return err 144 } 145 // lift scalar nested list and simple element 146 unwrapped := false 147 if len(mm) == 1 { 148 if mval, ok := mm["0"]; ok { 149 if marr, ok := mval.([]interface{}); ok { 150 arr = append(arr, marr) 151 } else { 152 arr = append(arr, mval) 153 } 154 unwrapped = true 155 } 156 } 157 if !unwrapped { 158 arr = append(arr, mm) 159 } 160 } 161 m[label] = arr 162 163 case T_LAMBDA: 164 // LAMBDA <type> <type> { <instruction> ... } 165 Trace(func(log LogFn) { 166 log("L%0d: OUTPUT typ=%s %s\n", lvl, typ.OpCode, val.Dump()) 167 }) 168 m[label] = val 169 170 case T_MAP, T_BIG_MAP: 171 // map <comparable type> <type> 172 // big_map <comparable type> <type> 173 // sequence of Elt (key/value) pairs 174 175 // render bigmap reference 176 if typ.OpCode == T_BIG_MAP && (len(val.Args) == 0 || !val.Args[0].IsElt()) { 177 switch val.Type { 178 case PrimInt: 179 // Babylon bigmaps contain a reference here 180 m[label] = val.Int.Int64() 181 case PrimSequence: 182 if len(val.Args) == 0 { 183 // pre-babylon there's only an empty sequence 184 // FIXME: we could insert the bigmap id, but this is unknown at ths point 185 m[label] = nil 186 } else { 187 if val.Args[0].IsSequence() { 188 if err := walkTree(m, label, typ, NewStack(val.Args[0]), lvl); err != nil { 189 return err 190 } 191 } else { 192 m[label] = val.Args[0].Int.Int64() 193 } 194 stack.Push(val.Args[1:]...) 195 } 196 } 197 return nil 198 } 199 200 if len(typ.Args) == 0 { 201 return fmt.Errorf("micheline: broken T_BIG_MAP type prim") 202 } 203 204 switch val.Type { 205 case PrimBinary: // single ELT 206 keyType := Type{typ.Args[0]} 207 valType := Type{typ.Args[1]} 208 209 // build type info if prim was packed 210 if val.Args[0].WasPacked { 211 keyType = val.Args[0].BuildType() 212 } 213 214 // build type info if prim was packed 215 if val.Args[1].WasPacked { 216 valType = val.Args[1].BuildType() 217 } 218 219 // prepare key 220 key, err := NewKey(keyType, val.Args[0]) 221 if err != nil { 222 return err 223 } 224 225 mm := make(map[string]interface{}) 226 if err := walkTree(mm, key.String(), valType, NewStack(val.Args[1]), lvl+1); err != nil { 227 return err 228 } 229 m[label] = mm 230 231 case PrimSequence: // sequence of ELTs 232 mm := make(map[string]interface{}) 233 for _, v := range val.Args { 234 if v.OpCode != D_ELT { 235 return fmt.Errorf("micheline: unexpected type %s [%s] for %s Elt item", v.Type, v.OpCode, typ.OpCode) 236 } 237 238 keyType := Type{typ.Args[0]} 239 valType := Type{typ.Args[1]} 240 241 // build type info if prim was packed 242 if v.Args[0].WasPacked { 243 keyType = v.Args[0].BuildType() 244 } 245 246 // build type info if prim was packed 247 if v.Args[1].WasPacked { 248 valType = v.Args[1].BuildType() 249 } 250 251 key, err := NewKey(keyType, v.Args[0]) 252 if err != nil { 253 return err 254 } 255 256 if err := walkTree(mm, key.String(), valType, NewStack(v.Args[1]), lvl+1); err != nil { 257 return err 258 } 259 } 260 m[label] = mm 261 262 default: 263 buf, _ := json.Marshal(val) 264 return fmt.Errorf("%*s> micheline: unexpected type %s [%s] for %s Elt sequence: %s", 265 lvl, "", val.Type, val.OpCode, typ.OpCode, buf) 266 } 267 268 case T_PAIR: 269 // pair <type> <type> or COMB 270 mm := m 271 if haveTypeLabel || haveKeyLabel { 272 mm = make(map[string]interface{}) 273 } 274 275 // Try unfolding value (again) when type is T_PAIR, 276 // reuse the existing stack and push unfolded values 277 if val.IsPair() && !typ.IsPair() { 278 // unfold regular pair 279 unfolded := val.UnfoldPair(typ) 280 Trace(func(log LogFn) { 281 log("L%0d: %s UNFOLD PAIR args[%d(+%d)]=%s typ=%s", lvl, label, stack.Len(), len(unfolded), NewSeq(unfolded...).Dump(), typ.Dump()) 282 }) 283 stack.Push(unfolded...) 284 Trace(func(log LogFn) { 285 log("L%0d: %s stack[%d]:\n%s\n", lvl, label, stack.Len(), stack.DumpIdent(4)) 286 }) 287 } else if val.CanUnfold(typ) { 288 // comb pair 289 Trace(func(log LogFn) { 290 log("L%0d: %s PUSH COMB args[%d(+%d)]=%s", lvl, label, stack.Len(), len(val.Args), val.Dump()) 291 }) 292 stack.Push(val.Args...) 293 Trace(func(log LogFn) { 294 log("L%0d: %s stack[%d]:\n%s\n", lvl, label, stack.Len(), stack.DumpIdent(4)) 295 }) 296 } else { 297 // push value back on stack 298 Trace(func(log LogFn) { 299 log("L%0d: %s PUSH VAL args[%d(+1)]=%s", lvl, label, stack.Len(), val.Dump()) 300 }) 301 stack.Push(val) 302 Trace(func(log LogFn) { 303 log("L%0d: %s stack[%d]:\n%s\n", lvl, label, stack.Len(), stack.DumpIdent(4)) 304 }) 305 } 306 307 for i, t := range typ.Args { 308 Trace(func(log LogFn) { 309 log("L%0d: CHILD-%s[%s] [n=%d/%d] top=%s", lvl, label, t.GetVarAnnoAny(), i+1, len(typ.Args), stack.Peek().Dump()) 310 log("L%0d: CHILD-%s[%s] [n=%d/%d] typ[%s]=%s\n", lvl, label, t.GetVarAnnoAny(), i+1, len(typ.Args), t.OpCode, t.Dump()) 311 }) 312 if err := walkTree(mm, EMPTY_LABEL, Type{t}, stack, lvl+1); err != nil { 313 return err 314 } 315 } 316 317 if haveTypeLabel || haveKeyLabel { 318 m[label] = mm 319 } 320 321 case T_OPTION: 322 // option <type> 323 switch val.OpCode { 324 case D_NONE: 325 // add empty option values as null 326 m[label] = nil 327 case D_SOME: 328 Trace(func(log LogFn) { 329 log("L%0d: OPTION label=%q anno=%q", lvl, label, typ.GetVarAnnoAny()) 330 log("L%0d: > top=%s", lvl, stack.Peek().Dump()) 331 log("L%0d: > typ[%s]=%s\n", lvl, typ.OpCode, typ.Dump()) 332 log("L%0d: > val[%s]=%s\n", lvl, val.OpCode, val.Dump()) 333 }) 334 335 // skip if broken 336 if len(val.Args) == 0 { 337 return fmt.Errorf("micheline: broken T_OPTION value prim") 338 } 339 340 // detect nested type when missing, this can happen with option types 341 // inside containers when the first element (used to detect the type 342 // for all elements) has option None. 343 if len(typ.Args) == 0 { 344 typ = val.BuildType() 345 // skip if broken 346 if len(typ.Args) == 0 { 347 return fmt.Errorf("micheline: broken T_OPTION type/value prim") 348 } 349 } 350 351 // with annots (name) use it for scalar or complex render 352 // when next level annot equals this option annot, skip this annot 353 if val.IsScalar() || label == typ.Args[0].GetVarAnnoAny() { 354 if err := walkTree(m, label, Type{typ.Args[0]}, NewStack(val.Args[0]), lvl+1); err != nil { 355 return err 356 } 357 } else { 358 mm := make(map[string]interface{}) 359 if err := walkTree(mm, EMPTY_LABEL, Type{typ.Args[0]}, NewStack(val.Args[0]), lvl+1); err != nil { 360 return err 361 } 362 m[label] = mm 363 } 364 default: 365 return fmt.Errorf("micheline: unexpected T_OPTION code %s [%s]: %s", val.OpCode, val.OpCode, val.Dump()) 366 } 367 368 case T_OR: 369 // or <type> <type> 370 // skip if broken 371 if len(typ.Args) == 0 { 372 return fmt.Errorf("micheline: broken T_OR type prim") 373 } 374 if len(val.Args) == 0 { 375 return fmt.Errorf("micheline: broken T_OR value prim") 376 } 377 378 // use map to capture nested names 379 mm := make(map[string]interface{}) 380 switch val.OpCode { 381 case D_LEFT: 382 if !(haveTypeLabel || haveKeyLabel) { 383 mmm := make(map[string]interface{}) 384 if err := walkTree(mmm, EMPTY_LABEL, Type{typ.Args[0]}, NewStack(val.Args[0]), lvl+1); err != nil { 385 return err 386 } 387 // lift named content 388 if len(mmm) == 1 { 389 for n, v := range mmm { 390 switch n { 391 case "0": 392 mm["@or_0"] = v 393 default: 394 mm[n] = v 395 } 396 } 397 } else { 398 mm["@or_0"] = mmm 399 } 400 } else { 401 if err := walkTree(mm, EMPTY_LABEL, Type{typ.Args[0]}, NewStack(val.Args[0]), lvl+1); err != nil { 402 return err 403 } 404 } 405 case D_RIGHT: 406 if !(haveTypeLabel || haveKeyLabel) { 407 mmm := make(map[string]interface{}) 408 if err := walkTree(mmm, EMPTY_LABEL, Type{typ.Args[1]}, NewStack(val.Args[0]), lvl+1); err != nil { 409 return err 410 } 411 // lift named content 412 if len(mmm) == 1 { 413 for n, v := range mmm { 414 switch n { 415 case "0": 416 mm["@or_1"] = v 417 default: 418 mm[n] = v 419 } 420 } 421 } else { 422 mm["@or_1"] = mmm 423 } 424 } else { 425 if err := walkTree(mm, EMPTY_LABEL, Type{typ.Args[1]}, NewStack(val.Args[0]), lvl+1); err != nil { 426 return err 427 } 428 } 429 430 default: 431 return fmt.Errorf("micheline: unexpected T_OR branch with value opcode %s", val.OpCode) 432 } 433 434 // lift anon content 435 if v, ok := mm["0"]; ok && len(mm) == 1 { 436 m[label] = v 437 } else { 438 m[label] = mm 439 } 440 441 case T_TICKET: 442 if len(typ.Args) == 0 { 443 return fmt.Errorf("micheline: broken T_TICKET type prim") 444 } 445 // always Pair( ticketer:address, Pair( original_type, int )) 446 stack.Push(val) 447 if err := walkTree(m, label, TicketType(typ.Args[0]), stack, lvl+1); err != nil { 448 return err 449 } 450 451 case T_SAPLING_STATE: 452 if len(typ.Args) == 0 { 453 return fmt.Errorf("micheline: broken T_SAPLING_STATE value prim") 454 } 455 mm := make(map[string]interface{}) 456 if err := walkTree(mm, "memo_size", Type{NewPrim(T_INT)}, NewStack(typ.Args[0]), lvl+1); err != nil { 457 return err 458 } 459 if err := walkTree(mm, "content", val.BuildType(), NewStack(val), lvl+1); err != nil { 460 return err 461 } 462 m[label] = mm 463 464 default: 465 // int 466 // nat 467 // string 468 // bytes 469 // mumav 470 // bool 471 // key_hash 472 // timestamp 473 // address 474 // key 475 // unit 476 // signature 477 // operation 478 // contract <type> (??) 479 // chain_id 480 // never 481 // chest_key 482 // chest 483 // append scalar or other complex value 484 485 // comb-pair records might have slipped through our in LooksLikeContainer() 486 // so if we detect an unpack comb part (i.e. sequence) we unpack it here 487 if val.IsSequence() { 488 Trace(func(log LogFn) { 489 log("L%0d: %s EXTRA UNPACK SEQU args[%d(+%d)]=%s typ=%s", lvl, label, stack.Len(), len(val.Args), NewSeq(val.Args...).Dump(), typ.Dump()) 490 }) 491 stack.Push(val.Args...) 492 Trace(func(log LogFn) { 493 log("L%0d: %s stack[%d]:\n%s\n", lvl, label, stack.Len(), stack.DumpIdent(4)) 494 }) 495 val = stack.Pop() 496 } 497 498 // if val.IsPair() { 499 // Trace(func(log LogFn) { 500 // log("L%0d: %s EXTRA UNPACK PAIR args[%d(+%d)]=%s typ=%s", lvl, label, stack.Len(), len(val.Args), val.Dump(), typ.Dump()) 501 // }) 502 // stack.Push(val.Args...) 503 // Trace(func(log LogFn) { 504 // log("L%0d: %s stack[%d]:\n%s\n", lvl, label, stack.Len(), stack.DumpIdent(4)) 505 // }) 506 // val = stack.Pop() 507 // } 508 509 // safety check: skip invalid values (only happens if type detect was wrong) 510 if !val.IsValid() { 511 break 512 } 513 514 if val.IsScalar() { 515 m[label] = val.Value(typ.OpCode) 516 } else { 517 mm := make(map[string]interface{}) 518 if err := walkTree(mm, EMPTY_LABEL, typ, NewStack(val), lvl+1); err != nil { 519 return err 520 } 521 m[label] = mm 522 } 523 } 524 Trace(func(log LogFn) { 525 log("L%0d: done\n\n", lvl) 526 }) 527 return nil 528 }