From c9d63a4eb0fe05a6c4f8af4fcc7ededb57d4152a Mon Sep 17 00:00:00 2001 From: Tomasz Kulik Date: Wed, 21 Aug 2024 08:26:10 +0200 Subject: [PATCH] feat: Add conditional migrate calling --- internal/api/bindings.h | 13 ++++++++++++ internal/api/lib.go | 45 +++++++++++++++++++++++++++++++++++++++++ lib_libwasmvm.go | 44 ++++++++++++++++++++++++++++++++++++++++ libwasmvm/bindings.h | 13 ++++++++++++ libwasmvm/src/calls.rs | 36 +++++++++++++++++++++++++++++++-- types/types.go | 15 ++++++++++++++ 6 files changed, 164 insertions(+), 2 deletions(-) diff --git a/internal/api/bindings.h b/internal/api/bindings.h index 28bb947fc..c66231b94 100644 --- a/internal/api/bindings.h +++ b/internal/api/bindings.h @@ -491,6 +491,19 @@ struct UnmanagedVector migrate(struct cache_t *cache, struct GasReport *gas_report, struct UnmanagedVector *error_msg); +struct UnmanagedVector migrate_with_info(struct cache_t *cache, + struct ByteSliceView checksum, + struct ByteSliceView env, + struct ByteSliceView msg, + struct ByteSliceView migrate_info, + struct Db db, + struct GoApi api, + struct GoQuerier querier, + uint64_t gas_limit, + bool print_debug, + struct GasReport *gas_report, + struct UnmanagedVector *error_msg); + struct UnmanagedVector sudo(struct cache_t *cache, struct ByteSliceView checksum, struct ByteSliceView env, diff --git a/internal/api/lib.go b/internal/api/lib.go index 81df86c84..80e0641b8 100644 --- a/internal/api/lib.go +++ b/internal/api/lib.go @@ -337,6 +337,51 @@ func Migrate( return copyAndDestroyUnmanagedVector(res), convertGasReport(gasReport), nil } +func MigrateWithInfo( + cache Cache, + checksum []byte, + env []byte, + msg []byte, + migrateInfo []byte, + gasMeter *types.GasMeter, + store types.KVStore, + api *types.GoAPI, + querier *Querier, + gasLimit uint64, + printDebug bool, +) ([]byte, types.GasReport, error) { + cs := makeView(checksum) + defer runtime.KeepAlive(checksum) + e := makeView(env) + defer runtime.KeepAlive(env) + m := makeView(msg) + defer runtime.KeepAlive(msg) + i := makeView(migrateInfo) + defer runtime.KeepAlive(i) + var pinner runtime.Pinner + pinner.Pin(gasMeter) + checkAndPinAPI(api, pinner) + checkAndPinQuerier(querier, pinner) + defer pinner.Unpin() + + callID := startCall() + defer endCall(callID) + + dbState := buildDBState(store, callID) + db := buildDB(&dbState, gasMeter) + a := buildAPI(api) + q := buildQuerier(querier) + var gasReport C.GasReport + errmsg := uninitializedUnmanagedVector() + + res, err := C.migrate_with_info(cache.ptr, cs, e, m, i, db, a, q, cu64(gasLimit), cbool(printDebug), &gasReport, &errmsg) + if err != nil && err.(syscall.Errno) != C.ErrnoValue_Success { + // Depending on the nature of the error, `gasUsed` will either have a meaningful value, or just 0. + return nil, convertGasReport(gasReport), errorWithMessage(err, errmsg) + } + return copyAndDestroyUnmanagedVector(res), convertGasReport(gasReport), nil +} + func Sudo( cache Cache, checksum []byte, diff --git a/lib_libwasmvm.go b/lib_libwasmvm.go index e9c160926..2e5863c81 100644 --- a/lib_libwasmvm.go +++ b/lib_libwasmvm.go @@ -262,6 +262,50 @@ func (vm *VM) Migrate( return &result, gasReport.UsedInternally, nil } +// MigrateWithInfo will migrate an existing contract to a new code binary. +// This takes storage of the data from the original contract and the Checksum of the new contract that should +// replace it. This allows it to run a migration step if needed, or return an error if unable to migrate +// the given data. +// +// MigrateMsg has some data on how to perform the migration. +// +// MigrateWithInfo takes one more argument - `migateInfo`. It consist of an additional data +// related to the on-chain current contract's state version. +func (vm *VM) MigrateWithInfo( + checksum Checksum, + env types.Env, + migrateMsg []byte, + migrateInfo types.MigrateInfo, + store KVStore, + goapi GoAPI, + querier Querier, + gasMeter GasMeter, + gasLimit uint64, + deserCost types.UFraction, +) (*types.ContractResult, uint64, error) { + envBin, err := json.Marshal(env) + if err != nil { + return nil, 0, err + } + + migrateBin, err := json.Marshal(migrateInfo) + if err != nil { + return nil, 0, err + } + + data, gasReport, err := api.MigrateWithInfo(vm.cache, checksum, envBin, migrateMsg, migrateBin, &gasMeter, store, &goapi, &querier, gasLimit, vm.printDebug) + if err != nil { + return nil, gasReport.UsedInternally, err + } + + var result types.ContractResult + err = DeserializeResponse(gasLimit, deserCost, &gasReport, data, &result) + if err != nil { + return nil, gasReport.UsedInternally, err + } + return &result, gasReport.UsedInternally, nil +} + // Sudo allows native Go modules to make priviledged (sudo) calls on the contract. // The contract can expose entry points that cannot be triggered by any transaction, but only via // native Go modules, and delegate the access control to the system. diff --git a/libwasmvm/bindings.h b/libwasmvm/bindings.h index 28bb947fc..c66231b94 100644 --- a/libwasmvm/bindings.h +++ b/libwasmvm/bindings.h @@ -491,6 +491,19 @@ struct UnmanagedVector migrate(struct cache_t *cache, struct GasReport *gas_report, struct UnmanagedVector *error_msg); +struct UnmanagedVector migrate_with_info(struct cache_t *cache, + struct ByteSliceView checksum, + struct ByteSliceView env, + struct ByteSliceView msg, + struct ByteSliceView migrate_info, + struct Db db, + struct GoApi api, + struct GoQuerier querier, + uint64_t gas_limit, + bool print_debug, + struct GasReport *gas_report, + struct UnmanagedVector *error_msg); + struct UnmanagedVector sudo(struct cache_t *cache, struct ByteSliceView checksum, struct ByteSliceView env, diff --git a/libwasmvm/src/calls.rs b/libwasmvm/src/calls.rs index 38d8fbf99..8618aef8c 100644 --- a/libwasmvm/src/calls.rs +++ b/libwasmvm/src/calls.rs @@ -10,8 +10,8 @@ use cosmwasm_vm::{ call_execute_raw, call_ibc_channel_close_raw, call_ibc_channel_connect_raw, call_ibc_channel_open_raw, call_ibc_destination_callback_raw, call_ibc_packet_ack_raw, call_ibc_packet_receive_raw, call_ibc_packet_timeout_raw, call_ibc_source_callback_raw, - call_instantiate_raw, call_migrate_raw, call_query_raw, call_reply_raw, call_sudo_raw, Backend, - Cache, Instance, InstanceOptions, VmResult, + call_instantiate_raw, call_migrate_raw, call_migrate_with_info_raw, call_query_raw, + call_reply_raw, call_sudo_raw, Backend, Cache, Instance, InstanceOptions, VmResult, }; use crate::api::GoApi; @@ -126,6 +126,38 @@ pub extern "C" fn migrate( ) } +#[no_mangle] +pub extern "C" fn migrate_with_info( + cache: *mut cache_t, + checksum: ByteSliceView, + env: ByteSliceView, + msg: ByteSliceView, + migrate_info: ByteSliceView, + db: Db, + api: GoApi, + querier: GoQuerier, + gas_limit: u64, + print_debug: bool, + gas_report: Option<&mut GasReport>, + error_msg: Option<&mut UnmanagedVector>, +) -> UnmanagedVector { + call_3_args( + call_migrate_with_info_raw, + cache, + checksum, + env, + msg, + migrate_info, + db, + api, + querier, + gas_limit, + print_debug, + gas_report, + error_msg, + ) +} + #[no_mangle] pub extern "C" fn sudo( cache: *mut cache_t, diff --git a/types/types.go b/types/types.go index 717625c0c..92eb2e99e 100644 --- a/types/types.go +++ b/types/types.go @@ -218,6 +218,21 @@ func (pm *PinnedMetrics) UnmarshalMessagePack(data []byte) error { // never result in a "None" value on the Rust side, making the "Option" pointless. type Array[C any] []C +// The structure contains additional information related to the +// contract's migration procedure - the sender address and +// the contract's migrate version currently stored on the blockchain. +// The `OldMigrateVersion` is optional, since there is no guarantee +// that the currently stored contract's binary contains that information. +type MigrateInfo struct { + // Address of the sender. + // + // This is the `sender` field from [`MsgMigrateContract`](https://github.com/CosmWasm/wasmd/blob/v0.53.0/proto/cosmwasm/wasm/v1/tx.proto#L217-L233). + Sender HumanAddress `json:"sender"` + // Migrate version of the previous contract. It's optional, since + // adding the version number to the binary is not a mandatory feature. + OldMigrateVersion *uint64 `json:"old_migrate_version"` +} + // MarshalJSON ensures that we get "[]" for nil arrays func (a Array[C]) MarshalJSON() ([]byte, error) { if len(a) == 0 {