Skip to content

Commit

Permalink
Merge pull request #6199 from onflow/ramtin/evm-delay-block-commit
Browse files Browse the repository at this point in the history
[Flow EVM] delay EVM block proposal commitment till system chunk execution
  • Loading branch information
ramtinms authored Jul 16, 2024
2 parents 9e7fe2c + 926230a commit 08e28b9
Show file tree
Hide file tree
Showing 16 changed files with 292 additions and 238 deletions.
6 changes: 4 additions & 2 deletions engine/execution/computation/computer/computer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,7 @@ func TestBlockExecutor_ExecuteBlock(t *testing.T) {

// include all fees. System chunk should ignore them
contextOptions := []fvm.Option{
fvm.WithEVMEnabled(true),
fvm.WithTransactionFeesEnabled(true),
fvm.WithAccountStorageLimit(true),
fvm.WithBlocks(&environment.NoopBlockFinder{}),
Expand Down Expand Up @@ -1223,6 +1224,7 @@ func (f *FixedAddressGenerator) AddressCount() uint64 {
func Test_ExecutingSystemCollection(t *testing.T) {

execCtx := fvm.NewContext(
fvm.WithEVMEnabled(true),
fvm.WithChain(flow.Localnet.Chain()),
fvm.WithBlocks(&environment.NoopBlockFinder{}),
)
Expand All @@ -1245,8 +1247,8 @@ func Test_ExecutingSystemCollection(t *testing.T) {

noopCollector := metrics.NewNoopCollector()

expectedNumberOfEvents := 3
expectedEventSize := 1497
expectedNumberOfEvents := 4
expectedEventSize := 1961

// bootstrapping does not cache programs
expectedCachedPrograms := 0
Expand Down
1 change: 1 addition & 0 deletions engine/execution/computation/programs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ func TestPrograms_TestBlockForks(t *testing.T) {
chain := flow.Emulator.Chain()
vm := fvm.NewVirtualMachine()
execCtx := fvm.NewContext(
fvm.WithEVMEnabled(true),
fvm.WithBlockHeader(block.Header),
fvm.WithBlocks(blockProvider{map[uint64]*flow.Block{0: &block}}),
fvm.WithChain(chain))
Expand Down
2 changes: 1 addition & 1 deletion engine/execution/state/bootstrap/bootstrap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func TestBootstrapLedger(t *testing.T) {
}

func TestBootstrapLedger_ZeroTokenSupply(t *testing.T) {
expectedStateCommitmentBytes, _ := hex.DecodeString("48460c42a43f700562d6c17408af8b52823774cc24fe795148c8e37b62f77615")
expectedStateCommitmentBytes, _ := hex.DecodeString("a738c835be7196f62900595d186557ba56d5ee4221b0a108782967c5d1d110d7")
expectedStateCommitment, err := flow.ToStateCommitment(expectedStateCommitmentBytes)
require.NoError(t, err)

Expand Down
6 changes: 6 additions & 0 deletions fvm/blueprints/scripts/systemChunkTransactionTemplate.cdc
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import FlowEpoch from "FlowEpoch"
import NodeVersionBeacon from "NodeVersionBeacon"
import RandomBeaconHistory from "RandomBeaconHistory"
import EVM from "EVM"

transaction {
prepare(serviceAccount: auth(BorrowValue) &Account) {
Expand All @@ -17,5 +18,10 @@ transaction {
.borrow<&RandomBeaconHistory.Heartbeat>(from: RandomBeaconHistory.HeartbeatStoragePath)
?? panic("Couldn't borrow RandomBeaconHistory.Heartbeat Resource")
randomBeaconHistoryHeartbeat.heartbeat(randomSourceHistory: randomSourceHistory())

let evmHeartbeat = serviceAccount.storage.borrow<&EVM.Heartbeat>(from: /storage/EVMHeartbeat)
if evmHeartbeat != nil { // skip if not available
evmHeartbeat!.heartbeat()
}
}
}
27 changes: 21 additions & 6 deletions fvm/blueprints/system.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package blueprints

import (
_ "embed"
"strings"

"github.com/onflow/flow-core-contracts/lib/go/templates"

Expand All @@ -17,17 +18,31 @@ const SystemChunkTransactionGasLimit = 100_000_000
//go:embed scripts/systemChunkTransactionTemplate.cdc
var systemChunkTransactionTemplate string

// TODO: when the EVM contract is moved to the flow-core-contracts, we can
// just directly use the replace address functionality of the templates package.

var placeholderEVMAddress = "\"EVM\""

func prepareSystemContractCode(chainID flow.ChainID) string {
sc := systemcontracts.SystemContractsForChain(chainID)
code := templates.ReplaceAddresses(
systemChunkTransactionTemplate,
sc.AsTemplateEnv(),
)
code = strings.ReplaceAll(
code,
placeholderEVMAddress,
sc.EVMContract.Address.HexWithPrefix(),
)
return code
}

// SystemChunkTransaction creates and returns the transaction corresponding to the
// system chunk for the given chain.
func SystemChunkTransaction(chain flow.Chain) (*flow.TransactionBody, error) {
contracts := systemcontracts.SystemContractsForChain(chain.ChainID())

tx := flow.NewTransactionBody().
SetScript(
[]byte(templates.ReplaceAddresses(
systemChunkTransactionTemplate,
contracts.AsTemplateEnv(),
)),
[]byte(prepareSystemContractCode(chain.ChainID())),
).
// The heartbeat resources needed by the system tx have are on the service account,
// therefore, the service account is the only authorizer needed.
Expand Down
8 changes: 4 additions & 4 deletions fvm/blueprints/system_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ func TestSystemChunkTransactionHash(t *testing.T) {

// this is formatted in a way that the resulting error message is easy to copy-paste into the test.
expectedHashes := []chainHash{
{chainId: "flow-mainnet", expectedHash: "0a7ea89ad32d79a30b91b4c1202230a1e29310e1b92e01c76d036d2e3839159b"},
{chainId: "flow-testnet", expectedHash: "368434cb7c792c3c35647f30aa90aae5798a45efcf2ff6abb7123b70c1e7850c"},
{chainId: "flow-previewnet", expectedHash: "e90268cb6e8385d9eb50f2956f47c1c5f77a7b3111de2f66756b2a48855e05ce"},
{chainId: "flow-emulator", expectedHash: "c6ccd6b805adcfaa6f9719f1dc71c831c40712977f12d82332ba23e2cb499475"},
{chainId: "flow-mainnet", expectedHash: "0e56f890392ad3f2a0dcc7a0e859b6d41f3c17556aa85a0f8831ae25a6537e39"},
{chainId: "flow-testnet", expectedHash: "69c922177af520e8ecaf0ba97cb174a3ebbd03de6d5b5d1f6b6c043a9638dba3"},
{chainId: "flow-previewnet", expectedHash: "2ec3b6b7e7d70a651600eb80f775cb57613a0a168c847b70040c469f09066a55"},
{chainId: "flow-emulator", expectedHash: "7dcc0daaecebc7be33b068ed5c8da0620c89fd896abfc498fc0cc32a261aab1f"},
}

var actualHashes []chainHash
Expand Down
179 changes: 84 additions & 95 deletions fvm/evm/evm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ import (
"math/big"
"testing"

"github.com/onflow/cadence/runtime/common"

"github.com/onflow/cadence/encoding/ccf"
gethTypes "github.com/onflow/go-ethereum/core/types"
gethParams "github.com/onflow/go-ethereum/params"
Expand Down Expand Up @@ -103,49 +101,25 @@ func TestEVMRun(t *testing.T) {
require.NoError(t, err)
require.NoError(t, output.Err)
require.NotEmpty(t, state.WriteSet)
snapshot = snapshot.Append(state)

// assert event fiedls are correct
require.Len(t, output.Events, 2)

blockEvent := output.Events[1]

assert.Equal(
t,
common.NewAddressLocation(
nil,
common.Address(sc.EVMContract.Address),
string(types.EventTypeBlockExecuted),
).ID(),
string(blockEvent.Type),
)

ev, err := ccf.Decode(nil, blockEvent.Payload)
require.NoError(t, err)
cadenceEvent, ok := ev.(cadence.Event)
require.True(t, ok)

blockEventPayload, err := types.DecodeBlockEventPayload(cadenceEvent)
require.NoError(t, err)
require.NotEmpty(t, blockEventPayload.Hash)

// assert event fields are correct
require.Len(t, output.Events, 1)
txEvent := output.Events[0]

assert.Equal(
t,
common.NewAddressLocation(
nil,
common.Address(sc.EVMContract.Address),
string(types.EventTypeTransactionExecuted),
).ID(),
string(txEvent.Type),
)
// commit block
blockEventPayload, snapshot := callEVMHeartBeat(t,
ctx,
vm,
snapshot)

ev, err = ccf.Decode(nil, txEvent.Payload)
require.NoError(t, err)
cadenceEvent, ok = ev.(cadence.Event)
require.True(t, ok)
require.NotEmpty(t, blockEventPayload.Hash)
require.Equal(t, uint64(43785), blockEventPayload.TotalGasUsed)
require.NotEmpty(t, blockEventPayload.Hash)
require.Len(t, blockEventPayload.TransactionHashes, 1)
require.NotEmpty(t, blockEventPayload.ReceiptRoot)

txEventPayload, err := types.DecodeTransactionEventPayload(cadenceEvent)
txEventPayload := testutils.TxEventToPayload(t, txEvent, sc.EVMContract.Address)
require.NoError(t, err)

txPayload, err := types.CadenceUInt8ArrayValueToBytes(txEventPayload.Payload)
Expand Down Expand Up @@ -388,16 +362,11 @@ func TestEVMRun(t *testing.T) {
require.NotEmpty(t, state.WriteSet)

txEvent := output.Events[0]
ev, err := ccf.Decode(nil, txEvent.Payload)
require.NoError(t, err)
cadenceEvent, ok := ev.(cadence.Event)
require.True(t, ok)
txEventPayload := testutils.TxEventToPayload(t, txEvent, sc.EVMContract.Address)

event, err := types.DecodeTransactionEventPayload(cadenceEvent)
require.NoError(t, err)
require.NotEmpty(t, event.Hash)
require.NotEmpty(t, txEventPayload.Hash)

encodedLogs, err := types.CadenceUInt8ArrayValueToBytes(event.Logs)
encodedLogs, err := types.CadenceUInt8ArrayValueToBytes(txEventPayload.Logs)
require.NoError(t, err)

var logs []*gethTypes.Log
Expand Down Expand Up @@ -491,7 +460,10 @@ func TestEVMBatchRun(t *testing.T) {
require.NoError(t, output.Err)
require.NotEmpty(t, state.WriteSet)

require.Len(t, output.Events, batchCount+1) // +1 block executed
// append the state
snapshot = snapshot.Append(state)

require.Len(t, output.Events, batchCount)
for i, event := range output.Events {
if i == batchCount { // last one is block executed
continue
Expand Down Expand Up @@ -519,31 +491,15 @@ func TestEVMBatchRun(t *testing.T) {
assert.Equal(t, storedValues[i], last.Big().Int64())
}

// last one is block executed, make sure TotalGasUsed is non-zero
blockEvent := output.Events[batchCount]

assert.Equal(
t,
common.NewAddressLocation(
nil,
common.Address(sc.EVMContract.Address),
string(types.EventTypeBlockExecuted),
).ID(),
string(blockEvent.Type),
)

ev, err := ccf.Decode(nil, blockEvent.Payload)
require.NoError(t, err)
cadenceEvent, ok := ev.(cadence.Event)
require.True(t, ok)
// commit block
blockEventPayload, snapshot := callEVMHeartBeat(t,
ctx,
vm,
snapshot)

blockEventPayload, err := types.DecodeBlockEventPayload(cadenceEvent)
require.NoError(t, err)
require.NotEmpty(t, blockEventPayload.Hash)
require.Equal(t, uint64(155513), blockEventPayload.TotalGasUsed)

// append the state
snapshot = snapshot.Append(state)
require.Len(t, blockEventPayload.TransactionHashes, 5)

// retrieve the values
retrieveCode := []byte(fmt.Sprintf(
Expand Down Expand Up @@ -1003,38 +959,31 @@ func TestEVMAddressDeposit(t *testing.T) {
bal := getEVMAccountBalance(t, ctx, vm, snapshot, addr)
require.Equal(t, expectedBalance, bal)

// block executed event, make sure TotalGasUsed is non-zero
blockEvent := output.Events[3]

assert.Equal(
t,
common.NewAddressLocation(
nil,
common.Address(sc.EVMContract.Address),
string(types.EventTypeBlockExecuted),
).ID(),
string(blockEvent.Type),
)

ev, err := ccf.Decode(nil, blockEvent.Payload)
// tx executed event
txEvent := output.Events[2]
txEventPayload := testutils.TxEventToPayload(t, txEvent, sc.EVMContract.Address)
require.NoError(t, err)
cadenceEvent, ok := ev.(cadence.Event)
require.True(t, ok)

blockEventPayload, err := types.DecodeBlockEventPayload(cadenceEvent)
require.NoError(t, err)
require.NotEmpty(t, blockEventPayload.Hash)
require.Equal(t, uint64(21000), blockEventPayload.TotalGasUsed)

// deposit event
depositEvent := output.Events[4]
depositEvent := output.Events[3]
depEv, err := types.FlowEventToCadenceEvent(depositEvent)
require.NoError(t, err)

depEvPayload, err := types.DecodeFLOWTokensDepositedEventPayload(depEv)
require.NoError(t, err)

require.Equal(t, types.OneFlow, depEvPayload.BalanceAfterInAttoFlow.Value)

// commit block
blockEventPayload, _ := callEVMHeartBeat(t,
ctx,
vm,
snapshot)

require.NotEmpty(t, blockEventPayload.Hash)
require.Equal(t, uint64(21000), blockEventPayload.TotalGasUsed)
require.Len(t, blockEventPayload.TransactionHashes, 1)
require.Equal(t, txEventPayload.Hash, string(blockEventPayload.TransactionHashes[0]))
})
}

Expand Down Expand Up @@ -1197,7 +1146,7 @@ func TestCadenceOwnedAccountFunctionalities(t *testing.T) {
require.NoError(t, err)
require.NoError(t, output.Err)

withdrawEvent := output.Events[10]
withdrawEvent := output.Events[7]

ev, err := types.FlowEventToCadenceEvent(withdrawEvent)
require.NoError(t, err)
Expand Down Expand Up @@ -2557,12 +2506,52 @@ func setupCOA(
snap = snap.Append(es)

// 3rd event is the cadence owned account created event
coaAddress, err := types.COAAddressFromFlowCOACreatedEvent(sc.EVMContract.Address, output.Events[2])
coaAddress, err := types.COAAddressFromFlowCOACreatedEvent(sc.EVMContract.Address, output.Events[1])
require.NoError(t, err)

return coaAddress, snap
}

func callEVMHeartBeat(
t *testing.T,
ctx fvm.Context,
vm fvm.VM,
snap snapshot.SnapshotTree,
) (*types.BlockEventPayload, snapshot.SnapshotTree) {
sc := systemcontracts.SystemContractsForChain(ctx.Chain.ChainID())

heartBeatCode := []byte(fmt.Sprintf(
`
import EVM from %s
transaction {
prepare(serviceAccount: auth(BorrowValue) &Account) {
let evmHeartbeat = serviceAccount.storage
.borrow<&EVM.Heartbeat>(from: /storage/EVMHeartbeat)
?? panic("Couldn't borrow EVM.Heartbeat Resource")
evmHeartbeat.heartbeat()
}
}
`,
sc.EVMContract.Address.HexWithPrefix(),
))
tx := fvm.Transaction(
flow.NewTransactionBody().
SetScript(heartBeatCode).
AddAuthorizer(sc.FlowServiceAccount.Address),
0)

state, output, err := vm.Run(ctx, tx, snap)
require.NoError(t, err)
require.NoError(t, output.Err)
require.NotEmpty(t, state.WriteSet)
snap = snap.Append(state)

// validate block event
require.Len(t, output.Events, 1)
blockEvent := output.Events[0]
return BlockEventToPayload(t, blockEvent, sc.EVMContract.Address), snap
}

func getFlowAccountBalance(
t *testing.T,
ctx fvm.Context,
Expand Down
Loading

0 comments on commit 08e28b9

Please sign in to comment.