github.com/koko1123/flow-go-1@v0.29.6/fvm/environment/programs.go (about)

     1  package environment
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/onflow/cadence"
     7  	jsoncdc "github.com/onflow/cadence/encoding/json"
     8  	"github.com/onflow/cadence/runtime/common"
     9  	"github.com/onflow/cadence/runtime/interpreter"
    10  
    11  	"github.com/koko1123/flow-go-1/fvm/derived"
    12  	"github.com/koko1123/flow-go-1/fvm/errors"
    13  	"github.com/koko1123/flow-go-1/fvm/state"
    14  	"github.com/koko1123/flow-go-1/fvm/tracing"
    15  	"github.com/koko1123/flow-go-1/model/flow"
    16  	"github.com/koko1123/flow-go-1/module/trace"
    17  )
    18  
    19  // TODO(patrick): remove and switch to *programs.DerivedTransactionData once
    20  // https://github.com/onflow/flow-emulator/pull/229 is integrated.
    21  type DerivedTransactionData interface {
    22  	GetProgram(loc common.AddressLocation) (*derived.Program, *state.State, bool)
    23  	SetProgram(loc common.AddressLocation, prog *derived.Program, state *state.State)
    24  }
    25  
    26  // Programs manages operations around cadence program parsing.
    27  //
    28  // Note that cadence guarantees that Get/Set methods are called in a LIFO
    29  // manner. Hence, we create new nested transactions on Get calls and commit
    30  // these nested transactions on Set calls in order to capture the states
    31  // needed for parsing the programs.
    32  type Programs struct {
    33  	tracer tracing.TracerSpan
    34  	meter  Meter
    35  
    36  	txnState *state.TransactionState
    37  	accounts Accounts
    38  
    39  	derivedTxnData DerivedTransactionData
    40  
    41  	// NOTE: non-address programs are not reusable across transactions, hence
    42  	// they are kept out of the derived data database.
    43  	nonAddressPrograms map[common.Location]*interpreter.Program
    44  
    45  	// dependencyStack tracks programs currently being loaded and their dependencies.
    46  	dependencyStack *dependencyStack
    47  }
    48  
    49  // NewPrograms construts a new ProgramHandler
    50  func NewPrograms(
    51  	tracer tracing.TracerSpan,
    52  	meter Meter,
    53  	txnState *state.TransactionState,
    54  	accounts Accounts,
    55  	derivedTxnData DerivedTransactionData,
    56  ) *Programs {
    57  	return &Programs{
    58  		tracer:             tracer,
    59  		meter:              meter,
    60  		txnState:           txnState,
    61  		accounts:           accounts,
    62  		derivedTxnData:     derivedTxnData,
    63  		nonAddressPrograms: make(map[common.Location]*interpreter.Program),
    64  		dependencyStack:    newDependencyStack(),
    65  	}
    66  }
    67  
    68  func (programs *Programs) set(
    69  	location common.Location,
    70  	program *interpreter.Program,
    71  ) error {
    72  	// ignore empty locations
    73  	if location == nil {
    74  		return nil
    75  	}
    76  
    77  	// derivedTransactionData only cache program/state for AddressLocation.
    78  	// For non-address location, simply keep track of the program in the
    79  	// environment.
    80  	address, ok := location.(common.AddressLocation)
    81  	if !ok {
    82  		programs.nonAddressPrograms[location] = program
    83  		return nil
    84  	}
    85  
    86  	state, err := programs.txnState.CommitParseRestricted(address)
    87  	if err != nil {
    88  		return err
    89  	}
    90  
    91  	// Get collected dependencies of the loaded program.
    92  	stackLocation, dependencies, err := programs.dependencyStack.pop()
    93  	if err != nil {
    94  		return err
    95  	}
    96  	if stackLocation != address {
    97  		// This should never happen, and indicates an implementation error.
    98  		// GetProgram and SetProgram should be always called in pair, this check depends on this assumption.
    99  		// Get pushes the stack and set pops the stack.
   100  		// Example: if loading B that depends on A (and none of them are in cache yet),
   101  		//   - get(A): pushes A
   102  		//   - get(B): pushes B
   103  		//   - set(B): pops B
   104  		//   - set(A): pops A
   105  		// Note: technically this check is redundant as `CommitParseRestricted` also has a similar check.
   106  		return fmt.Errorf(
   107  			"cannot set program. Popped dependencies are for an unexpeced address"+
   108  				" (expected %s, got %s)", address, stackLocation)
   109  	}
   110  
   111  	programs.derivedTxnData.SetProgram(address, &derived.Program{
   112  		Program:      program,
   113  		Dependencies: dependencies,
   114  	}, state)
   115  	return nil
   116  }
   117  
   118  func (programs *Programs) get(
   119  	location common.Location,
   120  ) (
   121  	*interpreter.Program,
   122  	bool,
   123  ) {
   124  	// ignore empty locations
   125  	if location == nil {
   126  		return nil, false
   127  	}
   128  
   129  	address, ok := location.(common.AddressLocation)
   130  	if !ok {
   131  		program, ok := programs.nonAddressPrograms[location]
   132  		return program, ok
   133  	}
   134  
   135  	program, state, has := programs.derivedTxnData.GetProgram(address)
   136  	if has {
   137  		programs.dependencyStack.addDependencies(program.Dependencies)
   138  		err := programs.txnState.AttachAndCommit(state)
   139  		if err != nil {
   140  			panic(fmt.Sprintf(
   141  				"merge error while getting program, panic: %s",
   142  				err))
   143  		}
   144  
   145  		return program.Program, true
   146  	}
   147  
   148  	// this program is not in cache, so we need to load it into the cache.
   149  	// tho have proper invalidation, we need to track the dependencies of the program.
   150  	// If this program depends on another program,
   151  	// that program will be loaded before this one finishes loading (calls set).
   152  	// That is why this is a stack.
   153  	programs.dependencyStack.push(address)
   154  
   155  	// Address location program is reusable across transactions.  Create
   156  	// a nested transaction here in order to capture the states read to
   157  	// parse the program.
   158  	_, err := programs.txnState.BeginParseRestrictedNestedTransaction(
   159  		address)
   160  	if err != nil {
   161  		panic(err)
   162  	}
   163  
   164  	return nil, false
   165  }
   166  
   167  func (programs *Programs) GetProgram(
   168  	location common.Location,
   169  ) (
   170  	*interpreter.Program,
   171  	error,
   172  ) {
   173  	defer programs.tracer.StartChildSpan(trace.FVMEnvGetProgram).End()
   174  
   175  	err := programs.meter.MeterComputation(ComputationKindGetProgram, 1)
   176  	if err != nil {
   177  		return nil, fmt.Errorf("get program failed: %w", err)
   178  	}
   179  
   180  	if addressLocation, ok := location.(common.AddressLocation); ok {
   181  		address := flow.Address(addressLocation.Address)
   182  
   183  		freezeError := programs.accounts.CheckAccountNotFrozen(address)
   184  		if freezeError != nil {
   185  			return nil, fmt.Errorf("get program failed: %w", freezeError)
   186  		}
   187  	}
   188  
   189  	program, has := programs.get(location)
   190  	if has {
   191  		return program, nil
   192  	}
   193  
   194  	return nil, nil
   195  }
   196  
   197  func (programs *Programs) SetProgram(
   198  	location common.Location,
   199  	program *interpreter.Program,
   200  ) error {
   201  	defer programs.tracer.StartChildSpan(trace.FVMEnvSetProgram).End()
   202  
   203  	err := programs.meter.MeterComputation(ComputationKindSetProgram, 1)
   204  	if err != nil {
   205  		return fmt.Errorf("set program failed: %w", err)
   206  	}
   207  
   208  	err = programs.set(location, program)
   209  	if err != nil {
   210  		return fmt.Errorf("set program failed: %w", err)
   211  	}
   212  	return nil
   213  }
   214  
   215  func (programs *Programs) DecodeArgument(
   216  	bytes []byte,
   217  	_ cadence.Type,
   218  ) (
   219  	cadence.Value,
   220  	error,
   221  ) {
   222  	defer programs.tracer.StartExtensiveTracingChildSpan(
   223  		trace.FVMEnvDecodeArgument).End()
   224  
   225  	v, err := jsoncdc.Decode(programs.meter, bytes)
   226  	if err != nil {
   227  		return nil, fmt.Errorf(
   228  			"decodeing argument failed: %w",
   229  			errors.NewInvalidArgumentErrorf(
   230  				"argument is not json decodable: %w",
   231  				err))
   232  	}
   233  
   234  	return v, err
   235  }
   236  
   237  // dependencyTracker tracks dependencies for a location
   238  // Or in other words it builds up a list of dependencies for the program being loaded.
   239  // If a program imports another program (A imports B), then B is a dependency of A.
   240  // Assuming that if A imports B which imports C (already in cache), the loading process looks like this:
   241  //   - get(A): not in cache, so push A to tracker to start tracking dependencies for A.
   242  //     We can be assured that set(A) will eventually be called.
   243  //   - get(B): not in cache, push B
   244  //   - get(C): in cache, do no push C, just add C's dependencies to the tracker (C's dependencies are also in the cache)
   245  //   - set(B): pop B, getting all the collected dependencies for B, and add B's dependencies to the tracker
   246  //     (because A also depends on everything B depends on)
   247  //   - set(A): pop A, getting all the collected dependencies for A
   248  type dependencyTracker struct {
   249  	location     common.AddressLocation
   250  	dependencies derived.ProgramDependencies
   251  }
   252  
   253  // dependencyStack is a stack of dependencyTracker
   254  // It is used during loading a program to create a dependency list for each program
   255  type dependencyStack struct {
   256  	trackers []dependencyTracker
   257  }
   258  
   259  func newDependencyStack() *dependencyStack {
   260  	return &dependencyStack{
   261  		trackers: make([]dependencyTracker, 0),
   262  	}
   263  }
   264  
   265  // push a new location to track dependencies for.
   266  // it is assumed that the dependencies will be loaded before the program is set and pop is called.
   267  func (s *dependencyStack) push(loc common.AddressLocation) {
   268  	dependencies := make(derived.ProgramDependencies, 1)
   269  
   270  	// A program is listed as its own dependency.
   271  	dependencies.AddDependency(loc.Address)
   272  
   273  	s.trackers = append(s.trackers, dependencyTracker{
   274  		location:     loc,
   275  		dependencies: dependencies,
   276  	})
   277  }
   278  
   279  // addDependencies adds dependencies to the current dependency tracker
   280  func (s *dependencyStack) addDependencies(dependencies derived.ProgramDependencies) {
   281  	l := len(s.trackers)
   282  	if l == 0 {
   283  		// stack is empty.
   284  		// This is expected if loading a program that is already cached.
   285  		return
   286  	}
   287  
   288  	s.trackers[l-1].dependencies.Merge(dependencies)
   289  }
   290  
   291  // pop the last dependencies on the stack and return them.
   292  func (s *dependencyStack) pop() (common.AddressLocation, derived.ProgramDependencies, error) {
   293  	if len(s.trackers) == 0 {
   294  		return common.AddressLocation{},
   295  			nil,
   296  			fmt.Errorf("cannot pop the programs dependency stack, because it is empty")
   297  	}
   298  
   299  	// pop the last tracker
   300  	tracker := s.trackers[len(s.trackers)-1]
   301  	s.trackers = s.trackers[:len(s.trackers)-1]
   302  
   303  	// there are more trackers in the stack.
   304  	// add the dependencies of the popped tracker to the parent tracker
   305  	// This is an optimisation to avoid having to iterate through the entire stack
   306  	// everytime a dependency is pushed or added, instead we add the popped dependencies to the new top of the stack.
   307  	// (because if C depends on B which depends on A, A's dependencies include C).
   308  	if len(s.trackers) > 0 {
   309  		s.trackers[len(s.trackers)-1].dependencies.Merge(tracker.dependencies)
   310  	}
   311  
   312  	return tracker.location, tracker.dependencies, nil
   313  }