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

Go bindings #41

Merged
merged 1 commit into from
Aug 20, 2018
Merged
Show file tree
Hide file tree
Changes from all 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
273 changes: 273 additions & 0 deletions bindings/go/evmc/evmc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
// EVMC: Ethereum Client-VM Connector API.
// Copyright 2018 Pawel Bylica.
// Licensed under the MIT License. See the LICENSE file.

package evmc

/*
#cgo CFLAGS: -I${SRCDIR}/.. -Wall -Wextra
#cgo !windows LDFLAGS: -ldl

#include <evmc/evmc.h>
#include <evmc/helpers.h>
#include <evmc/loader.h>

#include <stdlib.h>
#include <string.h>

static inline int set_option(struct evmc_instance* instance, char* name, char* value)
{
int ret = evmc_set_option(instance, name, value);
free(name);
free(value);
return ret;
}

struct extended_context
{
struct evmc_context context;
int64_t index;
};

extern const struct evmc_context_fn_table evmc_go_fn_table;

static struct evmc_result execute_wrapper(struct evmc_instance* instance, int64_t context_index, enum evmc_revision rev,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this now have create2_salt parameter?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah it's only for calls...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be passed back to the Host interface. I have not done this yet.

const struct evmc_address* destination, const struct evmc_address* sender, const struct evmc_uint256be* value,
const uint8_t* input_data, size_t input_size, const struct evmc_uint256be* code_hash, int64_t gas,
int32_t depth, enum evmc_call_kind kind, uint32_t flags, const uint8_t* code, size_t code_size)
{
struct evmc_uint256be create2_salt = {};
struct evmc_message msg = {
*destination,
*sender,
*value,
input_data,
input_size,
*code_hash,
create2_salt,
gas,
depth,
kind,
flags,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not keep the named version? Can you have named members here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm counting on compiler warning in case a field is missed.

};

struct extended_context ctx = {{&evmc_go_fn_table}, context_index};
return instance->execute(instance, &ctx.context, rev, &msg, code, code_size);
}
*/
import "C"

import (
"errors"
"fmt"
"sync"
"unsafe"

"github.com/ethereum/go-ethereum/common"
)

// Static asserts.
const (
_ = uint(common.HashLength - C.sizeof_struct_evmc_uint256be) // The size of evmc_uint256be equals the size of Hash.
_ = uint(C.sizeof_struct_evmc_uint256be - common.HashLength)
_ = uint(common.AddressLength - C.sizeof_struct_evmc_address) // The size of evmc_address equals the size of Address.
_ = uint(C.sizeof_struct_evmc_address - common.AddressLength)
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you also add "asserts" for:

  • evmc_message.destination == AddressLength
  • evmc_message.sender == AddressLength
  • evmc_message.value == HashLength (?? see above)
  • evmc_message.code_hash == HashLength

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The assertion checks the size of the types of these.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, the underlying type. Not sure if we want to ensure here that those fields are actually of the underlying type.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has been reworked.


type Error int32

func (err Error) IsInternalError() bool {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem to be used below (or anywhere?).

return err < 0
}

func (err Error) Error() string {
Copy link
Member

@axic axic Jul 27, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would a name like ErrorToString or EVMCErrorString be more descriptive?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is implementation of error interface.

type error interface {
    Error() string
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah ok, so all the wrappers return a standard Error object which contain an internal error value and this can be used to turn that into a string.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It means the evmc.Error type can used as the builtin error type.

code := C.enum_evmc_status_code(err)

switch code {
case C.EVMC_FAILURE:
return "evmc: failure"
case C.EVMC_REVERT:
return "evmc: revert"
case C.EVMC_OUT_OF_GAS:
return "evmc: out of gas"
case C.EVMC_INVALID_INSTRUCTION:
return "evmc: invalid instruction"
case C.EVMC_UNDEFINED_INSTRUCTION:
return "evmc: undefined instruction"
case C.EVMC_STACK_OVERFLOW:
return "evmc: stack overflow"
case C.EVMC_STACK_UNDERFLOW:
return "evmc: stack underflow"
case C.EVMC_BAD_JUMP_DESTINATION:
return "evmc: bad jump destination"
case C.EVMC_INVALID_MEMORY_ACCESS:
return "evmc: invalid memory access"
case C.EVMC_CALL_DEPTH_EXCEEDED:
return "evmc: call depth exceeded"
case C.EVMC_STATIC_MODE_VIOLATION:
return "evmc: static mode violation"
case C.EVMC_PRECOMPILE_FAILURE:
return "evmc: precompile failure"
case C.EVMC_CONTRACT_VALIDATION_FAILURE:
return "evmc: contract validation failure"
case C.EVMC_INTERNAL_ERROR:
return "evmc: internal error"
case C.EVMC_REJECTED:
return "evmc: rejected"
}

if code < 0 {
return fmt.Sprintf("evmc: unknown internal error (%d)", int32(code))
}

panic(fmt.Sprintf("evmc: unknown status code %d", int32(code)))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure it is a good idea making this a panic?

}

const (
Failure = Error(C.EVMC_FAILURE)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why consts only for two status codes?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm waiting for a comment from Go devs, because sometimes they predefined errors as consts and sometimes create errors at hoc.

Revert = Error(C.EVMC_REVERT)
)

type Revision int32

const (
Frontier Revision = C.EVMC_FRONTIER
Homestead Revision = C.EVMC_HOMESTEAD
TangerineWhistle Revision = C.EVMC_TANGERINE_WHISTLE
SpuriousDragon Revision = C.EVMC_SPURIOUS_DRAGON
Byzantium Revision = C.EVMC_BYZANTIUM
Constantinople Revision = C.EVMC_CONSTANTINOPLE
)

type Instance struct {
handle *C.struct_evmc_instance
}

func Load(filename string) (instance *Instance, err error) {
cfilename := C.CString(filename)
var loaderErr C.enum_evmc_loader_error_code
handle := C.evmc_load_and_create(cfilename, &loaderErr)
C.free(unsafe.Pointer(cfilename))
switch loaderErr {
case C.EVMC_LOADER_SUCCESS:
instance = &Instance{handle}
case C.EVMC_LOADER_CANNOT_OPEN:
err = fmt.Errorf("evmc loader: cannot open %s", filename)
case C.EVMC_LOADER_SYMBOL_NOT_FOUND:
err = fmt.Errorf("evmc loader: the EVMC create function not found in %s", filename)
case C.EVMC_LOADER_INVALID_ARGUMENT:
panic("evmc loader: filename argument is invalid")
case C.EVMC_LOADER_INSTANCE_CREATION_FAILURE:
err = errors.New("evmc loader: VM instance creation failure")
case C.EVMC_LOADER_ABI_VERSION_MISMATCH:
err = errors.New("evmc loader: ABI version mismatch")
default:
panic(fmt.Sprintf("evmc loader: unexpected error (%d)", int(loaderErr)))
}
return instance, err
}

func (instance *Instance) Destroy() {
C.evmc_destroy(instance.handle)
}

func (instance *Instance) Name() string {
return C.GoString(instance.handle.name)
}

func (instance *Instance) Version() string {
return C.GoString(instance.handle.version)
}

func (instance *Instance) SetOption(name string, value string) (err error) {

r := C.set_option(instance.handle, C.CString(name), C.CString(value))
if r != 1 {
err = fmt.Errorf("evmc: option '%s' not accepted", name)
}
return err
}

func (instance *Instance) Execute(ctx HostContext, rev Revision,
destination common.Address, sender common.Address, value common.Hash, input []byte, codeHash common.Hash, gas int64,
depth int, kind CallKind, static bool, code []byte) (output []byte, gasLeft int64, err error) {

flags := C.uint32_t(0)
if static {
flags |= C.EVMC_STATIC
}

ctxId := addHostContext(ctx)
// FIXME: Clarify passing by pointer vs passing by value.
evmcDestination := evmcAddress(destination)
evmcSender := evmcAddress(sender)
evmcValue := evmcUint256be(value)
evmcCodeHash := evmcUint256be(codeHash)
result := C.execute_wrapper(instance.handle, C.int64_t(ctxId), uint32(rev), &evmcDestination, &evmcSender, &evmcValue,
bytesPtr(input), C.size_t(len(input)), &evmcCodeHash, C.int64_t(gas), C.int32_t(depth), C.enum_evmc_call_kind(kind),
flags, bytesPtr(code), C.size_t(len(code)))
removeHostContext(ctxId)

output = C.GoBytes(unsafe.Pointer(result.output_data), C.int(result.output_size))
gasLeft = int64(result.gas_left)
if result.status_code != C.EVMC_SUCCESS {
err = Error(result.status_code)
}

if result.release != nil {
C.evmc_release_result(&result)
}

return output, gasLeft, err
}

var (
hostContextCounter int
hostContextMap = map[int]HostContext{}
hostContextMapMu sync.Mutex
)

func addHostContext(ctx HostContext) int {
hostContextMapMu.Lock()
id := hostContextCounter
hostContextCounter++
hostContextMap[id] = ctx
hostContextMapMu.Unlock()
return id
}

func removeHostContext(id int) {
hostContextMapMu.Lock()
delete(hostContextMap, id)
hostContextMapMu.Unlock()
}

func getHostContext(idx int) HostContext {
hostContextMapMu.Lock()
ctx := hostContextMap[idx]
hostContextMapMu.Unlock()
return ctx
}

func evmcUint256be(in common.Hash) C.struct_evmc_uint256be {
out := C.struct_evmc_uint256be{}
for i := 0; i < len(in); i++ {
Copy link
Member

@axic axic Jul 13, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we assert that len(in) equals that of uint256be?

Is there an option for static (compile time) assert in Go?

Copy link
Member Author

@chfast chfast Jul 13, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no static assert. You can't even check the struct size (without "unsafe" package). But this is not a big problem here, because the array access is safe (will panic if wrong, but I have not checked if the go compiler is able to optimize this).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has been fixed by the assertion commit.

out.bytes[i] = C.uint8_t(in[i])
}
return out
}

func evmcAddress(address common.Address) C.struct_evmc_address {
r := C.struct_evmc_address{}
for i := 0; i < len(address); i++ {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has been fixed by the assertion commit.

r.bytes[i] = C.uint8_t(address[i])
}
return r
}

func bytesPtr(bytes []byte) *C.uint8_t {
if len(bytes) == 0 {
return nil
}
return (*C.uint8_t)(unsafe.Pointer(&bytes[0]))
}
1 change: 1 addition & 0 deletions bindings/go/evmc/evmc.h
24 changes: 24 additions & 0 deletions bindings/go/evmc/evmc_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// EVMC: Ethereum Client-VM Connector API.
// Copyright 2018 Pawel Bylica.
// Licensed under the MIT License. See the LICENSE file.

package evmc

import (
"os"
"testing"
)

func TestLoad(t *testing.T) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is this used? What file is it loading?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are unit tests. You can run them with go test .... It will load file pointed by EVMC_PATH.

i, err := Load(os.Getenv("EVMC_PATH"))
if err != nil {
t.Fatal(err.Error())
}
defer i.Destroy()
if i.Name() != "interpreter" {
t.Fatal("name is not 'interpreter'")
}
if i.Version()[0] != '1' {
t.Fatalf("version is %s", i.Version())
}
}
1 change: 1 addition & 0 deletions bindings/go/evmc/helpers.h
28 changes: 28 additions & 0 deletions bindings/go/evmc/host.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/* EVMC: Ethereum Client-VM Connector API.
* Copyright 2018 Pawel Bylica.
* Licensed under the MIT License. See the LICENSE file.
*/

#include "_cgo_export.h"

#include <stdlib.h>

const struct evmc_context_fn_table evmc_go_fn_table = {
(evmc_account_exists_fn)accountExists,
(evmc_get_storage_fn)getStorage,
(evmc_set_storage_fn)setStorage,
(evmc_get_balance_fn)getBalance,
(evmc_get_code_size_fn)getCodeSize,
(evmc_get_code_hash_fn)getCodeHash,
(evmc_copy_code_fn)copyCode,
(evmc_selfdestruct_fn)selfdestruct,
(evmc_call_fn)call,
(evmc_get_tx_context_fn)getTxContext,
(evmc_get_block_hash_fn)getBlockHash,
(evmc_emit_log_fn)emitLog,
};

void evmc_go_free_result_output(const struct evmc_result* result)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually instead of this one could use evmc_release_result from helpers.h.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually not. This is the implementation. evmc_release_result is just a wrapper that calls the implementation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad, too early.

{
free((void*)result->output_data);
}
Loading