From d8e653031c9efd2a4dac682a11b66cc8ddc66b58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Thu, 5 Nov 2020 16:58:21 -0800 Subject: [PATCH] don't use the cache when deploying or updating account code --- runtime/runtime.go | 9 +- runtime/runtime_test.go | 173 ++++++++++++++++++++++++++++++++--- runtime/tests/utils/utils.go | 15 +++ 3 files changed, 182 insertions(+), 15 deletions(-) diff --git a/runtime/runtime.go b/runtime/runtime.go index dd594baf33..0f278ae9b0 100644 --- a/runtime/runtime.go +++ b/runtime/runtime.go @@ -1404,7 +1404,14 @@ func (r *interpreterRuntime) newAuthAccountContractsChangeFunction( AddressLocation: addressValue[:], Name: nameArgument, } - checker, err := r.ParseAndCheckProgram(code, runtimeInterface, location) + + // NOTE: do NOT use the cache! + + const useCache = false + + functions := r.standardLibraryFunctions(runtimeInterface, runtimeStorage) + + checker, err := r.parseAndCheckProgram(code, runtimeInterface, location, functions, nil, useCache) if err != nil { panic(fmt.Errorf("invalid contract: %w", err)) } diff --git a/runtime/runtime_test.go b/runtime/runtime_test.go index bdcea78304..e2a963e27c 100644 --- a/runtime/runtime_test.go +++ b/runtime/runtime_test.go @@ -4360,7 +4360,7 @@ func TestRuntimeExternalError(t *testing.T) { ) } -func TestRuntimeUpdateCodeCaching(t *testing.T) { +func TestRuntimeDeployCodeCaching(t *testing.T) { t.Parallel() @@ -4389,7 +4389,117 @@ func TestRuntimeUpdateCodeCaching(t *testing.T) { } ` - createAccountScript := []byte(` + createAccountTx := []byte(` + transaction { + prepare(signer: AuthAccount) { + AuthAccount(payer: signer) + } + } + `) + + deployTx := utils.DeploymentTransaction("HelloWorld", []byte(helloWorldContract)) + + runtime := NewInterpreterRuntime() + + accountCodes := map[string][]byte{} + var events []cadence.Event + + cachedPrograms := map[LocationID]*ast.Program{} + + var accountCounter uint8 = 0 + + var signerAddresses []Address + + runtimeInterface := &testRuntimeInterface{ + createAccount: func(payer Address) (address Address, err error) { + accountCounter++ + return Address{accountCounter}, nil + }, + getCode: func(location Location) (bytes []byte, err error) { + key := string(location.(AddressLocation).ID()) + return accountCodes[key], nil + }, + cacheProgram: func(location Location, program *ast.Program) error { + cachedPrograms[location.ID()] = program + return nil + }, + getCachedProgram: func(location Location) (*ast.Program, error) { + return cachedPrograms[location.ID()], nil + }, + storage: newTestStorage(nil, nil), + getSigningAccounts: func() []Address { + return signerAddresses + }, + resolveLocation: singleIdentifierLocationResolver(t), + getAccountContractCode: func(address Address, _ string) (code []byte, err error) { + key := string(AddressLocation(address[:]).ID()) + return accountCodes[key], nil + }, + updateAccountContractCode: func(address Address, _ string, code []byte) error { + key := string(AddressLocation(address[:]).ID()) + accountCodes[key] = code + return nil + }, + emitEvent: func(event cadence.Event) { + events = append(events, event) + }, + } + + nextTransactionLocation := newTransactionLocationGenerator() + + // create the account + + signerAddresses = []Address{{accountCounter}} + + err := runtime.ExecuteTransaction(createAccountTx, nil, runtimeInterface, nextTransactionLocation()) + require.NoError(t, err) + + // deploy the contract + + signerAddresses = []Address{{accountCounter}} + + err = runtime.ExecuteTransaction(deployTx, nil, runtimeInterface, nextTransactionLocation()) + require.NoError(t, err) + + // call the hello function + + callTx := []byte(fmt.Sprintf(callHelloTxTemplate, Address{accountCounter})) + + err = runtime.ExecuteTransaction(callTx, nil, runtimeInterface, nextTransactionLocation()) + require.NoError(t, err) +} + +func TestRuntimeUpdateCodeCaching(t *testing.T) { + + t.Parallel() + + const helloWorldContract1 = ` + pub contract HelloWorld { + + pub fun hello(): String { + return "1" + } + } + ` + + const helloWorldContract2 = ` + pub contract HelloWorld { + + pub fun hello(): String { + return "2" + } + } + ` + + const callHelloScriptTemplate = ` + import HelloWorld from 0x%s + + pub fun main(): String { + return HelloWorld.hello() + } + ` + + createAccountTx := []byte(` transaction { prepare(signer: AuthAccount) { AuthAccount(payer: signer) @@ -4397,7 +4507,8 @@ func TestRuntimeUpdateCodeCaching(t *testing.T) { } `) - updateCodeScript := utils.DeploymentTransaction("HelloWorld", []byte(helloWorldContract)) + deployTx := utils.DeploymentTransaction("HelloWorld", []byte(helloWorldContract1)) + updateTx := utils.UpdateTransaction("HelloWorld", []byte(helloWorldContract2)) runtime := NewInterpreterRuntime() @@ -4410,6 +4521,8 @@ func TestRuntimeUpdateCodeCaching(t *testing.T) { var signerAddresses []Address + var cacheHits []string + runtimeInterface := &testRuntimeInterface{ createAccount: func(payer Address) (address Address, err error) { accountCounter++ @@ -4424,6 +4537,7 @@ func TestRuntimeUpdateCodeCaching(t *testing.T) { return nil }, getCachedProgram: func(location Location) (*ast.Program, error) { + cacheHits = append(cacheHits, string(location.ID())) return cachedPrograms[location.ID()], nil }, storage: newTestStorage(nil, nil), @@ -4447,20 +4561,44 @@ func TestRuntimeUpdateCodeCaching(t *testing.T) { nextTransactionLocation := newTransactionLocationGenerator() + // create the account + signerAddresses = []Address{{accountCounter}} - err := runtime.ExecuteTransaction(createAccountScript, nil, runtimeInterface, nextTransactionLocation()) + err := runtime.ExecuteTransaction(createAccountTx, nil, runtimeInterface, nextTransactionLocation()) require.NoError(t, err) + // deploy the contract + + cacheHits = nil + signerAddresses = []Address{{accountCounter}} - err = runtime.ExecuteTransaction(updateCodeScript, nil, runtimeInterface, nextTransactionLocation()) + err = runtime.ExecuteTransaction(deployTx, nil, runtimeInterface, nextTransactionLocation()) + require.NoError(t, err) + require.Empty(t, cacheHits) + + // call the initial hello function + + callScript := []byte(fmt.Sprintf(callHelloScriptTemplate, Address{accountCounter})) + + result1, err := runtime.ExecuteScript(callScript, nil, runtimeInterface, nextTransactionLocation()) + require.NoError(t, err) + require.Equal(t, cadence.NewString("1"), result1) + + // update the contract + + cacheHits = nil + + err = runtime.ExecuteTransaction(updateTx, nil, runtimeInterface, nextTransactionLocation()) require.NoError(t, err) + require.Empty(t, cacheHits) - callScript := []byte(fmt.Sprintf(callHelloTxTemplate, Address{accountCounter})) + // call the new hello function - err = runtime.ExecuteTransaction(callScript, nil, runtimeInterface, nextTransactionLocation()) + result2, err := runtime.ExecuteScript(callScript, nil, runtimeInterface, nextTransactionLocation()) require.NoError(t, err) + require.Equal(t, cadence.NewString("2"), result2) } func TestRuntimeNoCacheHitForToplevelPrograms(t *testing.T) { @@ -4495,7 +4633,7 @@ func TestRuntimeNoCacheHitForToplevelPrograms(t *testing.T) { } ` - createAccountScript := []byte(` + createAccountTx := []byte(` transaction { prepare(signer: AuthAccount) { AuthAccount(payer: signer) @@ -4503,7 +4641,7 @@ func TestRuntimeNoCacheHitForToplevelPrograms(t *testing.T) { } `) - updateCodeScript := utils.DeploymentTransaction("HelloWorld", []byte(helloWorldContract)) + deployTx := utils.DeploymentTransaction("HelloWorld", []byte(helloWorldContract)) runtime := NewInterpreterRuntime() @@ -4558,24 +4696,31 @@ func TestRuntimeNoCacheHitForToplevelPrograms(t *testing.T) { signerAddresses = []Address{{accountCounter}} - err := runtime.ExecuteTransaction(createAccountScript, nil, runtimeInterface, nextTransactionLocation()) + // create the account + + err := runtime.ExecuteTransaction(createAccountTx, nil, runtimeInterface, nextTransactionLocation()) require.NoError(t, err) signerAddresses = []Address{{accountCounter}} - err = runtime.ExecuteTransaction(updateCodeScript, nil, runtimeInterface, nextTransactionLocation()) + err = runtime.ExecuteTransaction(deployTx, nil, runtimeInterface, nextTransactionLocation()) require.NoError(t, err) - callScript := []byte(fmt.Sprintf(callHelloTxTemplate, Address{accountCounter})) + // call the function + + callTx := []byte(fmt.Sprintf(callHelloTxTemplate, Address{accountCounter})) - err = runtime.ExecuteTransaction(callScript, nil, runtimeInterface, nextTransactionLocation()) + err = runtime.ExecuteTransaction(callTx, nil, runtimeInterface, nextTransactionLocation()) require.NoError(t, err) // We should only receive a cache hit for the imported program, not the transactions/scripts. + + // NOTE: if this test case fails with an additional cache hit, + // then the deployment is incorrectly using the cache! + require.Equal(t, []string{ "AC.0100000000000000.HelloWorld", - "AC.0100000000000000.HelloWorld", }, cacheHits, ) diff --git a/runtime/tests/utils/utils.go b/runtime/tests/utils/utils.go index 608dcbd347..c5d22bc943 100644 --- a/runtime/tests/utils/utils.go +++ b/runtime/tests/utils/utils.go @@ -94,3 +94,18 @@ func DeploymentTransaction(name string, contract []byte) []byte { hex.EncodeToString(contract), )) } + +func UpdateTransaction(name string, contract []byte) []byte { + return []byte(fmt.Sprintf( + ` + transaction { + + prepare(signer: AuthAccount) { + signer.contracts.update__experimental(name: "%s", code: "%s".decodeHex()) + } + } + `, + name, + hex.EncodeToString(contract), + )) +}