Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wasmstore #2292

Merged
merged 17 commits into from
May 16, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 67 additions & 7 deletions arbos/programs/native.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/log"
Expand Down Expand Up @@ -53,6 +54,24 @@ func activateProgram(
debug bool,
burner burn.Burner,
) (*activationInfo, error) {
info, asm, module, err := activateProgramInternal(db, program, codehash, wasm, page_limit, version, debug, burner.GasLeft())
if err != nil {
return nil, err
}
db.ActivateWasm(info.moduleHash, asm, module)
return info, nil
}

func activateProgramInternal(
db vm.StateDB,
program common.Address,
codehash common.Hash,
wasm []byte,
page_limit uint16,
version uint16,
debug bool,
gasLeft *uint64,
) (*activationInfo, []byte, []byte, error) {
output := &rustBytes{}
asmLen := usize(0)
moduleHash := &bytes32{}
Expand All @@ -69,7 +88,7 @@ func activateProgram(
&codeHash,
moduleHash,
stylusData,
(*u64)(burner.GasLeft()),
(*u64)(gasLeft),
))

data, msg, err := status.toResult(output.intoBytes(), debug)
Expand All @@ -78,9 +97,9 @@ func activateProgram(
log.Warn("activation failed", "err", err, "msg", msg, "program", program)
}
if errors.Is(err, vm.ErrExecutionReverted) {
return nil, fmt.Errorf("%w: %s", ErrProgramActivation, msg)
return nil, nil, nil, fmt.Errorf("%w: %s", ErrProgramActivation, msg)
}
return nil, err
return nil, nil, nil, err
}

hash := moduleHash.toHash()
Expand All @@ -95,13 +114,55 @@ func activateProgram(
asmEstimate: uint32(stylusData.asm_estimate),
footprint: uint16(stylusData.footprint),
}
db.ActivateWasm(hash, asm, module)
return info, err
return info, asm, module, err
}

func getLocalAsm(statedb vm.StateDB, moduleHash common.Hash, address common.Address, pagelimit uint16, time uint64, debugMode bool, program Program) ([]byte, error) {
localAsm, err := statedb.TryGetActivatedAsm(moduleHash)
if err == nil || len(localAsm) > 0 {
PlasmaPower marked this conversation as resolved.
Show resolved Hide resolved
return localAsm, nil
}

codeHash := statedb.GetCodeHash(address)

wasm, err := getWasm(statedb, address)
if err != nil {
return nil, err
PlasmaPower marked this conversation as resolved.
Show resolved Hide resolved
}

unlimitedGas := uint64(0xffffffffffff)
// we know program is activated, so it must be in correct version and not use too much memory
info, asm, module, err := activateProgramInternal(statedb, address, codeHash, wasm, pagelimit, program.version, debugMode, &unlimitedGas)

if err != nil {
return nil, err
PlasmaPower marked this conversation as resolved.
Show resolved Hide resolved
}

if info.moduleHash != moduleHash {
return nil, errors.New("failed to re-activate program not found in database")
PlasmaPower marked this conversation as resolved.
Show resolved Hide resolved
}

currentHoursSince := hoursSinceArbitrum(time)
if currentHoursSince > program.activatedAt {
// stylus program is active on-chain, and was activated in the past
PlasmaPower marked this conversation as resolved.
Show resolved Hide resolved
// so we store it directly to database
batch := statedb.Database().WasmStore().NewBatch()
rawdb.WriteActivation(batch, moduleHash, asm, module)
if err := batch.Write(); err != nil {
log.Error("failed writing re-activation to state", "address", address, "err", err)
}
} else {
// program activated recently, possibly in this eth_call
// store it to statedb. It will be stored to database if statedb is commited
statedb.ActivateWasm(info.moduleHash, asm, module)
}
return asm, nil
}

func callProgram(
address common.Address,
moduleHash common.Hash,
localAsm []byte,
scope *vm.ScopeContext,
interpreter *vm.EVMInterpreter,
tracingInfo *util.TracingInfo,
Expand All @@ -111,7 +172,6 @@ func callProgram(
memoryModel *MemoryModel,
) ([]byte, error) {
db := interpreter.Evm().StateDB
asm := db.GetActivatedAsm(moduleHash)
debug := stylusParams.debugMode

if db, ok := db.(*state.StateDB); ok {
Expand All @@ -123,7 +183,7 @@ func callProgram(

output := &rustBytes{}
status := userStatus(C.stylus_call(
goSlice(asm),
goSlice(localAsm),
goSlice(calldata),
stylusParams.encode(),
evmApi.cNative,
Expand Down
8 changes: 7 additions & 1 deletion arbos/programs/programs.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,12 @@ func (p Programs) CallProgram(
statedb.AddStylusPages(program.footprint)
defer statedb.SetStylusPagesOpen(open)

localAsm, err := getLocalAsm(statedb, moduleHash, contract.Address(), params.PageLimit, evm.Context.Time, debugMode, program)
if err != nil {
log.Crit("failed to get local wasm for activated program", "program", contract.Address())
return nil, err
}

evmData := &evmData{
blockBasefee: common.BigToHash(evm.Context.BaseFee),
chainId: evm.ChainConfig().ChainID.Uint64(),
Expand All @@ -227,7 +233,7 @@ func (p Programs) CallProgram(
if contract.CodeAddr != nil {
address = *contract.CodeAddr
}
return callProgram(address, moduleHash, scope, interpreter, tracingInfo, calldata, evmData, goParams, model)
return callProgram(address, moduleHash, localAsm, scope, interpreter, tracingInfo, calldata, evmData, goParams, model)
}

func getWasm(statedb vm.StateDB, program common.Address) ([]byte, error) {
Expand Down
5 changes: 5 additions & 0 deletions arbos/programs/wasm.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,9 +128,14 @@ func startProgram(module uint32) uint32
//go:wasmimport programs send_response
func sendResponse(req_id uint32) uint32

func getLocalAsm(statedb vm.StateDB, moduleHash common.Hash, address common.Address, pagelimit uint16, time uint64, debugMode bool, program Program) ([]byte, error) {
return nil, nil
}

func callProgram(
address common.Address,
moduleHash common.Hash,
_localAsm []byte,
scope *vm.ScopeContext,
interpreter *vm.EVMInterpreter,
tracingInfo *util.TracingInfo,
Expand Down
6 changes: 3 additions & 3 deletions cmd/nitro/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,13 +172,13 @@ func validateBlockChain(blockChain *core.BlockChain, chainConfig *params.ChainCo

func openInitializeChainDb(ctx context.Context, stack *node.Node, config *NodeConfig, chainId *big.Int, cacheConfig *core.CacheConfig, l1Client arbutil.L1Interface, rollupAddrs chaininfo.RollupAddresses) (ethdb.Database, *core.BlockChain, error) {
if !config.Init.Force {
if readOnlyDb, err := stack.OpenDatabaseWithFreezer("l2chaindata", 0, 0, "", "l2chaindata/", true); err == nil {
if readOnlyDb, err := stack.OpenDatabaseWithFreezerAndWasm("l2chaindata", "wasm", 0, 0, "", "l2chaindata/", true); err == nil {
if chainConfig := gethexec.TryReadStoredChainConfig(readOnlyDb); chainConfig != nil {
readOnlyDb.Close()
if !arbmath.BigEquals(chainConfig.ChainID, chainId) {
return nil, nil, fmt.Errorf("database has chain ID %v but config has chain ID %v (are you sure this database is for the right chain?)", chainConfig.ChainID, chainId)
}
chainDb, err := stack.OpenDatabaseWithFreezer("l2chaindata", config.Execution.Caching.DatabaseCache, config.Persistent.Handles, config.Persistent.Ancient, "l2chaindata/", false)
chainDb, err := stack.OpenDatabaseWithFreezerAndWasm("l2chaindata", "wasm", config.Execution.Caching.DatabaseCache, config.Persistent.Handles, config.Persistent.Ancient, "l2chaindata/", false)
if err != nil {
return chainDb, nil, err
}
Expand Down Expand Up @@ -230,7 +230,7 @@ func openInitializeChainDb(ctx context.Context, stack *node.Node, config *NodeCo

var initDataReader statetransfer.InitDataReader = nil

chainDb, err := stack.OpenDatabaseWithFreezer("l2chaindata", config.Execution.Caching.DatabaseCache, config.Persistent.Handles, config.Persistent.Ancient, "l2chaindata/", false)
chainDb, err := stack.OpenDatabaseWithFreezerAndWasm("l2chaindata", "wasm", config.Execution.Caching.DatabaseCache, config.Persistent.Handles, config.Persistent.Ancient, "l2chaindata/", false)
if err != nil {
return chainDb, nil, err
}
Expand Down
4 changes: 2 additions & 2 deletions system_tests/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -772,7 +772,7 @@ func createL2BlockChainWithStackConfig(
stack, err = node.New(stackConfig)
Require(t, err)

chainDb, err := stack.OpenDatabase("l2chaindata", 0, 0, "l2chaindata/", false)
chainDb, err := stack.OpenDatabaseWithFreezerAndWasm("l2chaindata", "wasm", 0, 0, "ancient", "l2chaindata/", false)
Require(t, err)
arbDb, err := stack.OpenDatabase("arbitrumdata", 0, 0, "arbitrumdata/", false)
Require(t, err)
Expand Down Expand Up @@ -976,7 +976,7 @@ func Create2ndNodeWithConfig(
l2stack, err := node.New(stackConfig)
Require(t, err)

l2chainDb, err := l2stack.OpenDatabase("l2chaindata", 0, 0, "l2chaindata/", false)
l2chainDb, err := l2stack.OpenDatabaseWithFreezerAndWasm("l2chaindata", "wasm", 0, 0, "", "l2chaindata/", false)
Require(t, err)
l2arbDb, err := l2stack.OpenDatabase("arbitrumdata", 0, 0, "arbitrumdata/", false)
Require(t, err)
Expand Down
78 changes: 78 additions & 0 deletions system_tests/program_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1458,3 +1458,81 @@ func formatTime(duration time.Duration) string {
}
return fmt.Sprintf("%.2f%s", span, units[unit])
}

func TestWasmRecreate(t *testing.T) {
builder, auth, cleanup := setupProgramTest(t, true)
ctx := builder.ctx
l2info := builder.L2Info
l2client := builder.L2.Client
defer cleanup()

storage := deployWasm(t, ctx, auth, l2client, rustFile("storage"))

zero := common.Hash{}
val := common.HexToHash("0x121233445566")

// do an onchain call - store value
storeTx := l2info.PrepareTxTo("Owner", &storage, l2info.TransferGas, nil, argsForStorageWrite(zero, val))
Require(t, l2client.SendTransaction(ctx, storeTx))
_, err := EnsureTxSucceeded(ctx, l2client, storeTx)
Require(t, err)

testDir := t.TempDir()
nodeBStack := createStackConfigForTest(testDir)
nodeB, cleanupB := builder.Build2ndNode(t, &SecondNodeParams{stackConfig: nodeBStack})

_, err = EnsureTxSucceeded(ctx, nodeB.Client, storeTx)
Require(t, err)

// make sure reading 2nd value succeeds from 2nd node
loadTx := l2info.PrepareTxTo("Owner", &storage, l2info.TransferGas, nil, argsForStorageRead(zero))
result, err := arbutil.SendTxAsCall(ctx, nodeB.Client, loadTx, l2info.GetAddress("Owner"), nil, true)
Require(t, err)
if common.BytesToHash(result) != val {
Fatal(t, "got wrong value")
}
// close nodeB
cleanupB()

// delete wasm dir of nodeB

wasmPath := filepath.Join(testDir, "system_tests.test", "wasm")
dirContents, err := os.ReadDir(wasmPath)
Require(t, err)
if len(dirContents) == 0 {
Fatal(t, "not contents found before delete")
}
os.RemoveAll(wasmPath)

// recreate nodeB - using same source dir (wasm deleted)
nodeB, cleanupB = builder.Build2ndNode(t, &SecondNodeParams{stackConfig: nodeBStack})

// test nodeB - sees existing transaction
_, err = EnsureTxSucceeded(ctx, nodeB.Client, storeTx)
Require(t, err)

// test nodeB - answers eth_call (requires reloading wasm)
result, err = arbutil.SendTxAsCall(ctx, nodeB.Client, loadTx, l2info.GetAddress("Owner"), nil, true)
Require(t, err)
if common.BytesToHash(result) != val {
Fatal(t, "got wrong value")
}

// send new tx (requires wasm) and check nodeB sees it as well
Require(t, l2client.SendTransaction(ctx, loadTx))

_, err = EnsureTxSucceeded(ctx, l2client, loadTx)
Require(t, err)

_, err = EnsureTxSucceeded(ctx, nodeB.Client, loadTx)
Require(t, err)

cleanupB()
dirContents, err = os.ReadDir(wasmPath)
Require(t, err)
if len(dirContents) == 0 {
Fatal(t, "not contents found before delete")
}
os.RemoveAll(wasmPath)

}