原文地址:https://github.com/aion-camus/rust_evm_intf
Our purpose providing this repository is to build a bridge between Rust Blockchain Kernel(such as Parity) and the llvm-based Ethereum Virtual Machine engine. Using this project, we can generate our own VM faster and much more stable.
The wrappered Virtual Machine mainly has three parts:
Rust has ffi mechanism to talk to outer world languages. But for now, only C is supported. So if we want to interact with other languages, such as Java/C++, we must use C-based bridge to have all these work.
for calling C from Rust(when creating evm instance, start evm or destroy evm), we could simply use:
extern "C" fn func_name(params) -> return_type {}
or
extern fn func_name(params) -> return_type {}
to tell the rustc that this is an unsafe function that is in C library. What’s more, we should update Rust link params by using #[link(name = "library_name")]
.
for calling Rust from C(when evm needs read/upgrade execution context, get/put storage from/to database, get block info and so on), first declare Rust functions as no_mangle.
#[no_mangle]
then declare it is an extern function. put them together, write as follows
#[no_mangle]
pub extern fn func_name(params) -> return_type {}
note that all parameters and return types should be compatible with C. It is easily facing unsafe problems, if data type size or member align is not consistent with C types. A more common usage when implementing such functions is to use bare pointer, but still keep in mind the layout pointed by your pointer.
since we have compiled the C++ evm into a dynamic library(in the previous section), we’d better register our Rust functions as callbacks for Ethereum Virtual machine. Or else, a __weak function in C may be needed for resolving compiler problems.
So we also implement a group of register methods, it is ugly, but since every callback’s signature is not the same, it seems to be the straight and fast way to get all these work. And for this register is used once during startup, it may not cost much performance.
A callback list is as follows:
void get_blockhash(const void *obj, struct evm_hash *result, int64_t number);
int exists(const void *obj, const struct evm_address *address);
uint8_t *get_storage(const void *obj, const struct evm_address *address,
const struct evm_word *key);
void put_storage_cb(const void *obj, const struct evm_address *address,
const struct evm_word *key,
const struct evm_word *value);
void get_balance_cb(const void *obj, struct evm_word *result,
const struct evm_address *address);
size_t get_code_cb(const void *obj, const uint8_t **result_code,
const struct evm_address *address);
void selfdestruct_cb(const void *obj, const struct evm_address *address,
const struct evm_address *beneficiary);
void call_cb(const void *obj, struct evm_result *result,
const struct evm_message *msg);
void get_tx_context_cb(const void *obj, struct evm_tx_context *result);
void get_blockhash_cb(const void *obj, struct evm_hash *result,
int64_t number);
void log_cb(const void *obj, const struct evm_address *address,
const uint8_t *data,
size_t data_size,
const struct evm_word topics[],
size_t topics_count);
To get evm running, we first parse execution context to gererate an evm_message, then use our callbacks from previous section and the generated message to run evm.
After evm ends, an evm_result is generated, including status code.
EVM_SUCCESS = 0, ///< Execution finished with success.
EVM_FAILURE = 1, ///< Generic execution failure.
EVM_OUT_OF_GAS = 2,
EVM_BAD_INSTRUCTION = 3,
EVM_BAD_JUMP_DESTINATION = 4,
EVM_STACK_OVERFLOW = 5,
EVM_STACK_UNDERFLOW = 6,
EVM_REVERT = 7,
EVM_STATIC_MODE_ERROR = 8,
EVM_REJECTED = -1,
EVM_INTERNAL_ERROR = -2,
gas left, output size and output data(if output size is not zero).