Skip to content

Commit

Permalink
chore(lib/runtime/wasmer): refactor Exec and exec methods (#2686)
Browse files Browse the repository at this point in the history
- Remove nil storage check (temporary instances might not need it, and we should panic if this happens)
- Fix behavior for pointers and/or data lengths larger than the max int32 value
- Only use `Exec` instead of both directly wrapping `exec`
- Inline single/2-lines functions content in `exec`: `malloc`, `clear`, `store`, `load`
- Add sentinel errors and error wrappings
- Add named returns
  • Loading branch information
qdm12 committed Jul 22, 2022
1 parent 2311061 commit 491af95
Show file tree
Hide file tree
Showing 7 changed files with 57 additions and 76 deletions.
6 changes: 4 additions & 2 deletions dot/rpc/websocket_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,10 @@ var testCalls = []struct {
expected: []byte(`{"jsonrpc":"2.0","result":3,"id":5}` + "\n")},
{
call: []byte(`{"jsonrpc":"2.0","method":"author_submitAndWatchExtrinsic","params":["0x010203"],"id":6}`),
expected: []byte(`{"jsonrpc":"2.0","error":{"code":null,"message":"Failed to call the ` +
"`" + `TaggedTransactionQueue_validate_transaction` + "`" + ` exported function."},"id":6}` + "\n")},
expected: []byte(`{"jsonrpc":"2.0","error":{"code":null,` +
`"message":"running runtime function: Failed to call the ` +
"`" + `TaggedTransactionQueue_validate_transaction` + "`" +
` exported function."},"id":6}` + "\n")},
{
call: []byte(`{"jsonrpc":"2.0","method":"state_subscribeRuntimeVersion","params":[],"id":7}`),
expected: []byte(`{"jsonrpc":"2.0","result":6,"id":7}` + "\n")},
Expand Down
3 changes: 0 additions & 3 deletions lib/runtime/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,3 @@ var ErrInvalidTransaction = &json2.Error{Code: 1010, Message: "Invalid Transacti
// ErrUnknownTransaction is returned if the call to runtime function
// TaggedTransactionQueueValidateTransaction fails with value of [1, 1, x]
var ErrUnknownTransaction = &json2.Error{Code: 1011, Message: "Unknown Transaction Validity"}

// ErrNilStorage is returned when the runtime context storage isn't set
var ErrNilStorage = errors.New("runtime context storage is nil")
22 changes: 11 additions & 11 deletions lib/runtime/wasmer/exports.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
// ValidateTransaction runs the extrinsic through the runtime function
// TaggedTransactionQueue_validate_transaction and returns *Validity
func (in *Instance) ValidateTransaction(e types.Extrinsic) (*transaction.Validity, error) {
ret, err := in.exec(runtime.TaggedTransactionQueueValidateTransaction, e)
ret, err := in.Exec(runtime.TaggedTransactionQueueValidateTransaction, e)
if err != nil {
return nil, err
}
Expand All @@ -33,7 +33,7 @@ func (in *Instance) ValidateTransaction(e types.Extrinsic) (*transaction.Validit

// Version calls runtime function Core_Version
func (in *Instance) Version() (runtime.Version, error) {
res, err := in.exec(runtime.CoreVersion, []byte{})
res, err := in.Exec(runtime.CoreVersion, []byte{})
if err != nil {
return nil, err
}
Expand All @@ -56,12 +56,12 @@ func (in *Instance) Version() (runtime.Version, error) {

// Metadata calls runtime function Metadata_metadata
func (in *Instance) Metadata() ([]byte, error) {
return in.exec(runtime.Metadata, []byte{})
return in.Exec(runtime.Metadata, []byte{})
}

// BabeConfiguration gets the configuration data for BABE from the runtime
func (in *Instance) BabeConfiguration() (*types.BabeConfiguration, error) {
data, err := in.exec(runtime.BabeAPIConfiguration, []byte{})
data, err := in.Exec(runtime.BabeAPIConfiguration, []byte{})
if err != nil {
return nil, err
}
Expand All @@ -77,7 +77,7 @@ func (in *Instance) BabeConfiguration() (*types.BabeConfiguration, error) {

// GrandpaAuthorities returns the genesis authorities from the runtime
func (in *Instance) GrandpaAuthorities() ([]types.Authority, error) {
ret, err := in.exec(runtime.GrandpaAuthorities, []byte{})
ret, err := in.Exec(runtime.GrandpaAuthorities, []byte{})
if err != nil {
return nil, err
}
Expand All @@ -98,23 +98,23 @@ func (in *Instance) InitializeBlock(header *types.Header) error {
return fmt.Errorf("cannot encode header: %w", err)
}

_, err = in.exec(runtime.CoreInitializeBlock, encodedHeader)
_, err = in.Exec(runtime.CoreInitializeBlock, encodedHeader)
return err
}

// InherentExtrinsics calls runtime API function BlockBuilder_inherent_extrinsics
func (in *Instance) InherentExtrinsics(data []byte) ([]byte, error) {
return in.exec(runtime.BlockBuilderInherentExtrinsics, data)
return in.Exec(runtime.BlockBuilderInherentExtrinsics, data)
}

// ApplyExtrinsic calls runtime API function BlockBuilder_apply_extrinsic
func (in *Instance) ApplyExtrinsic(data types.Extrinsic) ([]byte, error) {
return in.exec(runtime.BlockBuilderApplyExtrinsic, data)
return in.Exec(runtime.BlockBuilderApplyExtrinsic, data)
}

// FinalizeBlock calls runtime API function BlockBuilder_finalize_block
func (in *Instance) FinalizeBlock() (*types.Header, error) {
data, err := in.exec(runtime.BlockBuilderFinalizeBlock, []byte{})
data, err := in.Exec(runtime.BlockBuilderFinalizeBlock, []byte{})
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -161,7 +161,7 @@ func (in *Instance) ExecuteBlock(block *types.Block) ([]byte, error) {

// DecodeSessionKeys decodes the given public session keys. Returns a list of raw public keys including their key type.
func (in *Instance) DecodeSessionKeys(enc []byte) ([]byte, error) {
return in.exec(runtime.DecodeSessionKeys, enc)
return in.Exec(runtime.DecodeSessionKeys, enc)
}

// PaymentQueryInfo returns information of a given extrinsic
Expand All @@ -171,7 +171,7 @@ func (in *Instance) PaymentQueryInfo(ext []byte) (*types.TransactionPaymentQuery
return nil, err
}

resBytes, err := in.exec(runtime.TransactionPaymentAPIQueryInfo, append(ext, encLen...))
resBytes, err := in.Exec(runtime.TransactionPaymentAPIQueryInfo, append(ext, encLen...))
if err != nil {
return nil, err
}
Expand Down
22 changes: 11 additions & 11 deletions lib/runtime/wasmer/exports_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ package wasmer

import (
"encoding/json"
"errors"
"fmt"
"math/big"
"os"
Expand Down Expand Up @@ -1022,15 +1021,14 @@ func TestInstance_DecodeSessionKeys(t *testing.T) {

func TestInstance_PaymentQueryInfo(t *testing.T) {
tests := []struct {
extB []byte
ext string
err error
expect *types.TransactionPaymentQueryInfo
extB []byte
ext string
errMessage string
expect *types.TransactionPaymentQueryInfo
}{
{
// Was made with @polkadot/api on https://github.com/danforbes/polkadot-js-scripts/tree/create-signed-tx
ext: "0xd1018400d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d01bc2b6e35929aabd5b8bc4e5b0168c9bee59e2bb9d6098769f6683ecf73e44c776652d947a270d59f3d37eb9f9c8c17ec1b4cc473f2f9928ffdeef0f3abd43e85d502000000012844616e20466f72626573", //nolint:lll
err: nil,
expect: &types.TransactionPaymentQueryInfo{
Weight: 1973000,
Class: 0,
Expand All @@ -1043,12 +1041,14 @@ func TestInstance_PaymentQueryInfo(t *testing.T) {
{
// incomplete extrinsic
ext: "0x4ccde39a5684e7a56da23b22d4d9fbadb023baa19c56495432884d0640000000000000000000000000000000",
err: errors.New("Failed to call the `TransactionPaymentApi_query_info` exported function."), //nolint:revive
errMessage: "running runtime function: " +
"Failed to call the `TransactionPaymentApi_query_info` exported function.",
},
{
// incomplete extrinsic
extB: nil,
err: errors.New("Failed to call the `TransactionPaymentApi_query_info` exported function."), //nolint:revive
errMessage: "running runtime function: " +
"Failed to call the `TransactionPaymentApi_query_info` exported function.",
},
}

Expand All @@ -1066,11 +1066,11 @@ func TestInstance_PaymentQueryInfo(t *testing.T) {
ins := NewTestInstance(t, runtime.NODE_RUNTIME)
info, err := ins.PaymentQueryInfo(extBytes)

if test.err != nil {
require.Error(t, err)
require.Equal(t, err.Error(), test.err.Error())
if test.errMessage != "" {
assert.EqualError(t, err, test.errMessage)
continue
}
require.NoError(t, err)

fmt.Println(info.PartialFee.String())
fmt.Println(test.expect.PartialFee.String())
Expand Down
18 changes: 11 additions & 7 deletions lib/runtime/wasmer/imports_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,9 @@ func Test_ext_offchain_timestamp_version_1(t *testing.T) {
res, err := runtimeFunc(0, 0)
require.NoError(t, err)

offset, length := runtime.Int64ToPointerAndSize(res.ToI64())
data := inst.load(offset, length)
outputPtr, outputLength := runtime.Int64ToPointerAndSize(res.ToI64())
memory := inst.vm.Memory.Data()
data := memory[outputPtr : outputPtr+outputLength]
var timestamp int64
err = scale.Unmarshal(data, &timestamp)
require.NoError(t, err)
Expand Down Expand Up @@ -718,10 +719,12 @@ func Test_ext_crypto_ed25519_generate_version_1(t *testing.T) {
// we manually store and call the runtime function here since inst.exec assumes
// the data returned from the function is a pointer-size, but for ext_crypto_ed25519_generate_version_1,
// it's just a pointer
ptr, err := inst.malloc(uint32(len(params)))
ptr, err := inst.ctx.Allocator.Allocate(uint32(len(params)))
require.NoError(t, err)

inst.store(params, int32(ptr))
memory := inst.vm.Memory.Data()
copy(memory[ptr:ptr+uint32(len(params))], params)

dataLen := int32(len(params))

runtimeFunc, ok := inst.vm.Exports["rtm_ext_crypto_ed25519_generate_version_1"]
Expand Down Expand Up @@ -921,15 +924,15 @@ func Test_ext_crypto_ecdsa_verify_version_2_Table(t *testing.T) {
key: []byte{132, 2, 39, 55, 134, 131, 142, 43, 100, 63, 134, 96, 14, 253, 15, 222, 119, 154, 110, 188, 20, 159, 62, 125, 42, 59, 127, 19, 16, 0, 161, 236, 109}, //nolint:lll
err: wasmer.NewExportedFunctionError(
"rtm_ext_crypto_ecdsa_verify_version_2",
"Failed to call the `%s` exported function."),
"running runtime function: Failed to call the `%s` exported function."),
},
"invalid message": {
sig: []byte{5, 1, 187, 179, 88, 183, 46, 115, 242, 32, 9, 54, 141, 207, 44, 15, 238, 42, 217, 196, 111, 173, 239, 204, 128, 93, 49, 179, 137, 150, 162, 125, 226, 225, 28, 145, 122, 127, 15, 154, 185, 11, 3, 66, 27, 187, 204, 242, 107, 68, 26, 111, 245, 30, 115, 141, 85, 74, 158, 211, 161, 217, 43, 151, 120, 125, 1}, //nolint:lll
msg: []byte{48, 72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100},
key: []byte{132, 2, 39, 206, 55, 134, 131, 142, 43, 100, 63, 134, 96, 14, 253, 15, 222, 119, 154, 110, 188, 20, 159, 62, 125, 42, 59, 127, 19, 16, 0, 161, 236, 109}, //nolint:lll
err: wasmer.NewExportedFunctionError(
"rtm_ext_crypto_ecdsa_verify_version_2",
"Failed to call the `%s` exported function."),
"running runtime function: Failed to call the `%s` exported function."),
},
}
for name, tc := range testCases {
Expand Down Expand Up @@ -1592,7 +1595,8 @@ func Test_ext_default_child_storage_storage_kill_version_3(t *testing.T) {
key: []byte(`fakekey`),
limit: optLimit2,
expected: []byte{0, 0, 0, 0, 0},
errMsg: "Failed to call the `rtm_ext_default_child_storage_storage_kill_version_3` exported function.",
errMsg: "running runtime function: " +
"Failed to call the `rtm_ext_default_child_storage_storage_kill_version_3` exported function.",
},
{key: testChildKey, limit: optLimit2, expected: []byte{1, 2, 0, 0, 0}},
{key: testChildKey, limit: nil, expected: []byte{0, 1, 0, 0, 0}},
Expand Down
58 changes: 18 additions & 40 deletions lib/runtime/wasmer/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,67 +264,45 @@ func (in *Instance) Stop() {
}
}

// Store func
func (in *Instance) store(data []byte, location int32) {
mem := in.vm.Memory.Data()
copy(mem[location:location+int32(len(data))], data)
}

// Load load
func (in *Instance) load(location, length int32) []byte {
mem := in.vm.Memory.Data()
return mem[location : location+length]
}
var (
ErrInstanceIsStopped = errors.New("instance is stopped")
ErrExportFunctionNotFound = errors.New("export function not found")
)

// Exec calls the given function with the given data
func (in *Instance) Exec(function string, data []byte) ([]byte, error) {
return in.exec(function, data)
}

// Exec func
func (in *Instance) exec(function string, data []byte) ([]byte, error) {
if in.ctx.Storage == nil {
return nil, runtime.ErrNilStorage
}

func (in *Instance) Exec(function string, data []byte) (result []byte, err error) {
in.Lock()
defer in.Unlock()

if in.isClosed {
return nil, errors.New("instance is stopped")
return nil, ErrInstanceIsStopped
}

ptr, err := in.malloc(uint32(len(data)))
dataLength := uint32(len(data))
inputPtr, err := in.ctx.Allocator.Allocate(dataLength)
if err != nil {
return nil, err
return nil, fmt.Errorf("allocating input memory: %w", err)
}

defer in.clear()
defer in.ctx.Allocator.Clear()

// Store the data into memory
in.store(data, int32(ptr))
datalen := int32(len(data))
memory := in.vm.Memory.Data()
copy(memory[inputPtr:inputPtr+dataLength], data)

runtimeFunc, ok := in.vm.Exports[function]
if !ok {
return nil, fmt.Errorf("could not find exported function %s", function)
return nil, fmt.Errorf("%w: %s", ErrExportFunctionNotFound, function)
}

res, err := runtimeFunc(int32(ptr), datalen)
wasmValue, err := runtimeFunc(int32(inputPtr), int32(dataLength))
if err != nil {
return nil, err
return nil, fmt.Errorf("running runtime function: %w", err)
}

offset, length := runtime.Int64ToPointerAndSize(res.ToI64())
return in.load(offset, length), nil
}

func (in *Instance) malloc(size uint32) (uint32, error) {
return in.ctx.Allocator.Allocate(size)
}

func (in *Instance) clear() {
in.ctx.Allocator.Clear()
outputPtr, outputLength := runtime.Int64ToPointerAndSize(wasmValue.ToI64())
memory = in.vm.Memory.Data() // call Data() again to get larger slice
return memory[outputPtr : outputPtr+outputLength], nil
}

// NodeStorage to get reference to runtime node service
Expand Down
4 changes: 2 additions & 2 deletions lib/runtime/wasmer/instance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ func TestConcurrentRuntimeCalls(t *testing.T) {

// execute 2 concurrent calls to the runtime
go func() {
_, _ = instance.exec(runtime.CoreVersion, []byte{})
_, _ = instance.Exec(runtime.CoreVersion, []byte{})
}()
go func() {
_, _ = instance.exec(runtime.CoreVersion, []byte{})
_, _ = instance.Exec(runtime.CoreVersion, []byte{})
}()
}

Expand Down

0 comments on commit 491af95

Please sign in to comment.