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

On-demand module compile #2739

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
165 changes: 119 additions & 46 deletions arbos/programs/native.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,16 +70,17 @@ func activateProgram(
debug bool,
burner burn.Burner,
) (*activationInfo, error) {
info, asmMap, err := activateProgramInternal(db, program, codehash, wasm, page_limit, stylusVersion, arbosVersionForGas, debug, burner.GasLeft())
targets := db.Database().WasmTargets()
moduleActivationMandatory := true
info, asmMap, err := activateProgramInternal(program, codehash, wasm, page_limit, stylusVersion, arbosVersionForGas, debug, burner.GasLeft(), targets, moduleActivationMandatory)
if err != nil {
return nil, err
}
db.ActivateWasm(info.moduleHash, asmMap)
return info, nil
}

func activateProgramInternal(
db vm.StateDB,
func activateModule(
addressForLogging common.Address,
codehash common.Hash,
wasm []byte,
Expand All @@ -88,7 +89,7 @@ func activateProgramInternal(
arbosVersionForGas uint64,
debug bool,
gasLeft *uint64,
) (*activationInfo, map[ethdb.WasmTarget][]byte, error) {
) (*activationInfo, []byte, error) {
output := &rustBytes{}
moduleHash := &bytes32{}
stylusData := &C.StylusData{}
Expand All @@ -106,80 +107,150 @@ func activateProgramInternal(
stylusData,
(*u64)(gasLeft),
))

module, msg, err := status_mod.toResult(output.intoBytes(), debug)
if err != nil {
if debug {
log.Warn("activation failed", "err", err, "msg", msg, "program", addressForLogging)
}
if errors.Is(err, vm.ErrExecutionReverted) {
return nil, nil, fmt.Errorf("%w: %s", ErrProgramActivation, msg)
} else {
return nil, nil, err
}
}
info := &activationInfo{
moduleHash: moduleHash.toHash(),
initGas: uint16(stylusData.init_cost),
cachedInitGas: uint16(stylusData.cached_init_cost),
asmEstimate: uint32(stylusData.asm_estimate),
footprint: uint16(stylusData.footprint),
}
return info, module, nil
}

func compileNative(
wasm []byte,
stylusVersion uint16,
debug bool,
target ethdb.WasmTarget,
) ([]byte, error) {
output := &rustBytes{}
status_asm := C.stylus_compile(
goSlice(wasm),
u16(stylusVersion),
cbool(debug),
goSlice([]byte(target)),
output,
)
asm := output.intoBytes()
if status_asm != 0 {
return nil, fmt.Errorf("%w: %s", ErrProgramActivation, string(asm))
}
return asm, nil
}

func activateProgramInternal(
addressForLogging common.Address,
codehash common.Hash,
wasm []byte,
page_limit uint16,
stylusVersion uint16,
arbosVersionForGas uint64,
debug bool,
gasLeft *uint64,
targets []ethdb.WasmTarget,
moduleActivationMandatory bool,
) (*activationInfo, map[ethdb.WasmTarget][]byte, error) {
var wavmFound bool
for _, target := range targets {
if target == rawdb.TargetWavm {
wavmFound = true
break
}
return nil, nil, err
}
hash := moduleHash.toHash()
targets := db.Database().WasmTargets()
type result struct {
target ethdb.WasmTarget
asm []byte
err error
}
asmMap := make(map[ethdb.WasmTarget][]byte, len(targets))

// info can be set in separate thread, make sure to wait before reading
var info *activationInfo
var moduleActivationStarted bool
if moduleActivationMandatory {
moduleActivationStarted = true
var err error
var module []byte
info, module, err = activateModule(addressForLogging, codehash, wasm, page_limit, stylusVersion, arbosVersionForGas, debug, gasLeft)
if err != nil {
return nil, nil, err
}
if wavmFound {
asmMap[rawdb.TargetWavm] = module
}
}

results := make(chan result, len(targets))
for _, target := range targets {
target := target
if target == rawdb.TargetWavm {
results <- result{target, module, nil}
if moduleActivationStarted {
// skip if already started or activated because of moduleActivationMandatory
results <- result{target, nil, nil}
continue
}
go func() {
var err error
var module []byte
info, module, err = activateModule(addressForLogging, codehash, wasm, page_limit, stylusVersion, arbosVersionForGas, debug, gasLeft)
results <- result{target, module, err}
}()
moduleActivationStarted = true
} else {
go func() {
output := &rustBytes{}
status_asm := C.stylus_compile(
goSlice(wasm),
u16(stylusVersion),
cbool(debug),
goSlice([]byte(target)),
output,
)
asm := output.intoBytes()
if status_asm != 0 {
results <- result{target, nil, fmt.Errorf("%w: %s", ErrProgramActivation, string(asm))}
return
}
results <- result{target, asm, nil}
asm, err := compileNative(wasm, stylusVersion, debug, target)
results <- result{target, asm, err}
}()
}
}
asmMap := make(map[ethdb.WasmTarget][]byte, len(targets))
var err error
for range targets {
res := <-results
if res.err != nil {
err = errors.Join(res.err, err)
if res.asm == nil {
continue
} else if res.err != nil {
err = errors.Join(res.err, fmt.Errorf("%s:%w", res.target, err))
} else {
asmMap[res.target] = res.asm
}
}
if err != nil {
log.Error(
"Compilation failed for one or more targets despite activation succeeding",
"address", addressForLogging,
"codeHash", codeHash,
"moduleHash", hash,
"targets", targets,
"err", err,
)
if err != nil && moduleActivationMandatory {
if info != nil {
log.Error(
"Compilation failed for one or more targets despite activation succeeding",
"address", addressForLogging,
"codehash", codehash,
"moduleHash", info.moduleHash,
"targets", targets,
"err", err,
)
} else {
log.Error(
"Compilation failed for one or more targets despite activation succeeding",
"address", addressForLogging,
"codehash", codehash,
"targets", targets,
"err", err,
)
}
panic(fmt.Sprintf("Compilation of %v failed for one or more targets despite activation succeeding: %v", addressForLogging, err))
}

info := &activationInfo{
moduleHash: hash,
initGas: uint16(stylusData.init_cost),
cachedInitGas: uint16(stylusData.cached_init_cost),
asmEstimate: uint32(stylusData.asm_estimate),
footprint: uint16(stylusData.footprint),
}
return info, asmMap, err
}

func getLocalAsm(statedb vm.StateDB, moduleHash common.Hash, addressForLogging common.Address, code []byte, codeHash common.Hash, pagelimit uint16, time uint64, debugMode bool, program Program) ([]byte, error) {
func getLocalAsm(statedb vm.StateDB, moduleHash common.Hash, addressForLogging common.Address, code []byte, codehash common.Hash, pagelimit uint16, time uint64, debugMode bool, program Program) ([]byte, error) {
localTarget := rawdb.LocalTarget()
localAsm, err := statedb.TryGetActivatedAsm(localTarget, moduleHash)
if err == nil && len(localAsm) > 0 {
Expand All @@ -197,8 +268,10 @@ func getLocalAsm(statedb vm.StateDB, moduleHash common.Hash, addressForLogging c
zeroArbosVersion := uint64(0)
zeroGas := uint64(0)

targets := statedb.Database().WasmTargets()
// we know program is activated, so it must be in correct version and not use too much memory
info, asmMap, err := activateProgramInternal(statedb, addressForLogging, codeHash, wasm, pagelimit, program.version, zeroArbosVersion, debugMode, &zeroGas)
moduleActivationMandatory := false
info, asmMap, err := activateProgramInternal(addressForLogging, codehash, wasm, pagelimit, program.version, zeroArbosVersion, debugMode, &zeroGas, targets, moduleActivationMandatory)
if err != nil {
log.Error("failed to reactivate program", "address", addressForLogging, "expected moduleHash", moduleHash, "err", err)
return nil, fmt.Errorf("failed to reactivate program address: %v err: %w", addressForLogging, err)
Expand Down Expand Up @@ -300,10 +373,10 @@ func handleReqImpl(apiId usize, req_type u32, data *rustSlice, costPtr *u64, out

// Caches a program in Rust. We write a record so that we can undo on revert.
// For gas estimation and eth_call, we ignore permanent updates and rely on Rust's LRU.
func cacheProgram(db vm.StateDB, module common.Hash, program Program, addressForLogging common.Address, code []byte, codeHash common.Hash, params *StylusParams, debug bool, time uint64, runMode core.MessageRunMode) {
func cacheProgram(db vm.StateDB, module common.Hash, program Program, addressForLogging common.Address, code []byte, codehash common.Hash, params *StylusParams, debug bool, time uint64, runMode core.MessageRunMode) {
if runMode == core.MessageCommitMode {
// address is only used for logging
asm, err := getLocalAsm(db, module, addressForLogging, code, codeHash, params.PageLimit, time, debug, program)
asm, err := getLocalAsm(db, module, addressForLogging, code, codehash, params.PageLimit, time, debug, program)
if err != nil {
panic("unable to recreate wasm")
}
Expand Down
4 changes: 2 additions & 2 deletions arbos/programs/programs.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ func (p Programs) CallProgram(
tracingInfo *util.TracingInfo,
calldata []byte,
reentrant bool,
runmode core.MessageRunMode,
runMode core.MessageRunMode,
) ([]byte, error) {
evm := interpreter.Evm()
contract := scope.Contract
Expand Down Expand Up @@ -246,7 +246,7 @@ func (p Programs) CallProgram(
address = *contract.CodeAddr
}
var arbos_tag uint32
if runmode == core.MessageCommitMode {
if runMode == core.MessageCommitMode {
arbos_tag = statedb.Database().WasmCacheTag()
}
ret, err := callProgram(address, moduleHash, localAsm, scope, interpreter, tracingInfo, calldata, evmData, goParams, model, arbos_tag)
Expand Down
3 changes: 2 additions & 1 deletion arbos/programs/wasmstorehelper.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ func (p Programs) SaveActiveProgramToWasmStore(statedb *state.StateDB, codeHash

// We know program is activated, so it must be in correct version and not use too much memory
// Empty program address is supplied because we dont have access to this during rebuilding of wasm store
info, asmMap, err := activateProgramInternal(statedb, common.Address{}, codeHash, wasm, progParams.PageLimit, program.version, zeroArbosVersion, debugMode, &zeroGas)
moduleActivationMandatory := false
info, asmMap, err := activateProgramInternal(common.Address{}, codeHash, wasm, progParams.PageLimit, program.version, zeroArbosVersion, debugMode, &zeroGas, targets, moduleActivationMandatory)
if err != nil {
log.Error("failed to reactivate program while rebuilding wasm store", "expected moduleHash", moduleHash, "err", err)
return fmt.Errorf("failed to reactivate program while rebuilding wasm store: %w", err)
Expand Down
3 changes: 0 additions & 3 deletions execution/gethexec/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,6 @@ func (c *StylusTargetConfig) Validate() error {
}
targetsSet[target] = true
}
if !targetsSet[rawdb.TargetWavm] {
return fmt.Errorf("%s target not found in archs list, archs: %v", rawdb.TargetWavm, c.ExtraArchs)
}
targetsSet[rawdb.LocalTarget()] = true
targets := make([]ethdb.WasmTarget, 0, len(c.ExtraArchs)+1)
for target := range targetsSet {
Expand Down
4 changes: 3 additions & 1 deletion system_tests/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1322,6 +1322,7 @@ func createNonL1BlockChainWithStackConfig(
if execConfig == nil {
execConfig = ExecConfigDefaultTest(t)
}
Require(t, execConfig.Validate())

stack, err := node.New(stackConfig)
Require(t, err)
Expand Down Expand Up @@ -1409,6 +1410,8 @@ func Create2ndNodeWithConfig(
if execConfig == nil {
execConfig = ExecConfigDefaultNonSequencerTest(t)
}
Require(t, execConfig.Validate())

feedErrChan := make(chan error, 10)
parentChainRpcClient := parentChainStack.Attach()
parentChainClient := ethclient.NewClient(parentChainRpcClient)
Expand Down Expand Up @@ -1442,7 +1445,6 @@ func Create2ndNodeWithConfig(

AddValNodeIfNeeded(t, ctx, nodeConfig, true, "", valnodeConfig.Wasm.RootPath)

Require(t, execConfig.Validate())
Require(t, nodeConfig.Validate())
configFetcher := func() *gethexec.Config { return execConfig }
currentExec, err := gethexec.CreateExecutionNode(ctx, chainStack, chainDb, blockchain, parentChainClient, configFetcher)
Expand Down
Loading
Loading