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 }