Skip to content

Commit

Permalink
don't use the cache when deploying or updating account code
Browse files Browse the repository at this point in the history
  • Loading branch information
turbolent committed Nov 6, 2020
1 parent e0e3f92 commit d8e6530
Show file tree
Hide file tree
Showing 3 changed files with 182 additions and 15 deletions.
9 changes: 8 additions & 1 deletion runtime/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
Expand Down
173 changes: 159 additions & 14 deletions runtime/runtime_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4360,7 +4360,7 @@ func TestRuntimeExternalError(t *testing.T) {
)
}

func TestRuntimeUpdateCodeCaching(t *testing.T) {
func TestRuntimeDeployCodeCaching(t *testing.T) {

t.Parallel()

Expand Down Expand Up @@ -4389,15 +4389,126 @@ 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)
}
}
`)

updateCodeScript := utils.DeploymentTransaction("HelloWorld", []byte(helloWorldContract))
deployTx := utils.DeploymentTransaction("HelloWorld", []byte(helloWorldContract1))
updateTx := utils.UpdateTransaction("HelloWorld", []byte(helloWorldContract2))

runtime := NewInterpreterRuntime()

Expand All @@ -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++
Expand All @@ -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),
Expand All @@ -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) {
Expand Down Expand Up @@ -4495,15 +4633,15 @@ func TestRuntimeNoCacheHitForToplevelPrograms(t *testing.T) {
}
`

createAccountScript := []byte(`
createAccountTx := []byte(`
transaction {
prepare(signer: AuthAccount) {
AuthAccount(payer: signer)
}
}
`)

updateCodeScript := utils.DeploymentTransaction("HelloWorld", []byte(helloWorldContract))
deployTx := utils.DeploymentTransaction("HelloWorld", []byte(helloWorldContract))

runtime := NewInterpreterRuntime()

Expand Down Expand Up @@ -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,
)
Expand Down
15 changes: 15 additions & 0 deletions runtime/tests/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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),
))
}

0 comments on commit d8e6530

Please sign in to comment.