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  }