Skip to content

Commit

Permalink
add contract design part (#1571)
Browse files Browse the repository at this point in the history
* add contract design

Co-authored-by: 李雯林 <liwenlin@liwenlindeMacBook-Pro.local>
  • Loading branch information
wenlinlee and 李雯林 committed Nov 10, 2022
1 parent 4dbe60d commit b320d9a
Show file tree
Hide file tree
Showing 17 changed files with 683 additions and 3 deletions.
3 changes: 2 additions & 1 deletion 3.x/zh_CN/docs/design/consensus.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,5 @@

当区块`block(i)`执行完毕后,区块`block(i+1)`即可基于区块`block(i)`的状态执行,并将区块`block(i)`执行后的区块哈希作为父区块哈希,产生新的执行结果`checkPoint(i+1)`,并基于区块执行结果继续重复以上步骤对第(i+1)个区块执行结果进行共识。

另外,在进行区块批量共识排序的同时,区块执行结果流水线共识可并行进行,优化了系统资源利用效率,提升了区块链系统共识性能。
另外,在进行区块批量共识排序的同时,区块执行结果流水线共识可并行进行,优化了系统资源利用效率,提升了区块链系统共识性能。

38 changes: 38 additions & 0 deletions 3.x/zh_CN/docs/design/contract.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
##############################################################
智能合约
##############################################################

标签: ``智能合约`` ``虚拟机``

----

交易的执行是区块链节点上的一个重要的功能。交易的执行,是把交易中的智能合约二进制代码取出来,用执行器(Executor_)执行。共识模块(Consensus_)把交易从交易池(TxPool_)中取出,打包成区块,并调用执行器去执行区块中的交易。在交易的执行过程中,会对区块链的状态(State)进行修改,形成新区块的状态储存下来(Storage)。执行器在这个过程中,类似于一个黑盒,输入是智能合约代码,输出是状态的改变。

随着技术的发展,人们开始关注执行器的性能和易用性。一方面,人们希望智能合约在区块链上能有更快的执行速度,满足大规模交易的需求。另一方面,人们希望能用更熟悉更好用的语言进行开发。进而出现了一些替代传统的执行器(EVM)的方案,如:JIT_、 WASM_甚至JVM。然而,传统的EVM是耦合在节点代码中的。首先要做的,是将执行器的接口抽象出来,兼容各种虚拟机的实现。因此,EVMC被设计出来。


EVMC (Ethereum Client-VM Connector API),是以太坊抽象出来的执行器的接口,旨在能够对接各种类型的执行器。FISCO BCOS目前采用了以太坊的智能合约语言Solidity,因此也沿用了以太坊对执行器接口的抽象。

![](../../images/evm/evmc_frame.png)

在节点上,共识模块会调用EVMC,将打包好的交易交由执行器执行。执行器执行时,对状态进行的读写,会通过EVMC的回调反过来操作节点上的状态数据。

经过EVMC一层的抽象,FISCO BCOS能够对接今后出现的更高效、易用性更强的执行器。目前,FISCO BCOS采用的是传统的EVM根据EVMC抽象出来的执行器---Interpreter。因此能够支持基于Solidity语言的智能合约。目前其他类型的执行器发展尚未成熟,后续将持续跟进。


.. toctree::
:maxdepth: 1

evm.md
precompiled.md
gas.md

.. _Executor: ./evm.html

.. _Consensus: ../consensus

.. _TxPool: ../architecture/transaction_stream.html

.. _JIT: https://github.com/ethereum/evmjit

.. _WASM: https://webassembly.org/
206 changes: 206 additions & 0 deletions 3.x/zh_CN/docs/design/evm.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
# EVM 以太坊虚拟机

标签:``EVM`` ``智能合约`` ``虚拟机``

----

在区块链上,用户通过运行部署在区块链上的合约,完成需要共识的操作。以太坊虚拟机,是智能合约代码的执行器。

当智能合约被编译成二进制文件后,被部署到区块链上。用户通过调用智能合约的接口,来触发智能合约的执行操作。EVM执行智能合约的代码,修改当前区块链上的数据(状态)。被修改的数据,会被共识,确保一致性。



## EVMC – Ethereum Client-VM Connector API

新版本的以太坊将EVM从节点代码中剥离出来,形成一个独立的模块。EVM与节点的交互,抽象出EVMC接口标准。通过EVMC,节点可以对接多种虚拟机,而不仅限于传统的基于solidity的虚拟机。

传统的solidity虚拟机,在以太坊中称为interpreter,下文主要解释interpreter的实现。

### EVMC 接口

EVMC主要定义了两种调用的接口:

- Instance接口:节点调用EVM的接口
- Callback接口:EVM回调节点的接口

EVM本身不保存状态数据,节点通过instance接口操作EVM,EVM反过来,调Callback接口,对节点的状态进行操作。

![](../../images/evm/evmc.png)

**Instance 接口**

定义了节点对虚拟机的操作,包括创建,销毁,设置等。

接口定义在evmc_instance(evmc.h)中

* abi_version
* name
* version
* destroy
* execute
* set_tracer
* set_option

**Callback接口**

定义了EVM对节点的操作,主要是对state读写、区块信息的读写等。

接口定义在evmc_context_fn_table(evmc.h)中。


* evmc_account_exists_fn account_exists
* evmc_get_storage_fn get_storage
* evmc_set_storage_fn set_storage
* evmc_get_balance_fn get_balance
* evmc_get_code_size_fn get_code_size
* evmc_get_code_hash_fn get_code_hash
* evmc_copy_code_fn copy_code
* evmc_selfdestruct_fn selfdestruct
* evmc_call_fn call
* evmc_get_tx_context_fn get_tx_context
* evmc_get_block_hash_fn get_block_hash
* evmc_emit_log_fn emit_log


## EVM 执行

### EVM 指令

solidity是合约的执行语言,solidity被solc编译后,变成类似于汇编的EVM指令。Interpreter定义了一套完整的指令集。solidity被编译后,生成二进制文件,二进制文件就是EVM指令的集合,交易以二进制的形式发往节点,节点收到后,通过EVMC调用EVM执行这些指令。在EVM中,用代码模拟实现了这些指令的逻辑。

Solidity是基于堆栈的语言,EVM在执行二进制时,也是以堆栈的方式进行调用。

**算术指令举例**

一条ADD指令,在EVM中的代码实现如下。SP是堆栈的指针,从栈顶第一和第二个位置(```SP[0]``````SP[1]```)拿出数据,进行加和后,写入结果堆栈SPP的顶端```SPP[0]```

``` cpp
CASE(ADD)
{
ON_OP();
updateIOGas();

// pops two items and pushes their sum mod 2^256.
m_SPP[0] = m_SP[0] + m_SP[1];
}
```
**跳转指令举例**
JUMP指令,实现了二进制代码间的跳转。首先从堆栈顶端```SP[0]```取出待跳转的地址,验证一下是否越界,放到程序计数器PC中,下一个指令,将从PC指向的位置开始执行。
``` cpp
CASE(JUMP)
{
ON_OP();
updateIOGas();
m_PC = verifyJumpDest(m_SP[0]);
}
```

**状态读指令举例**

SLOAD可以查询状态数据。大致过程是,从堆栈顶端```SP[0]```取出要访问的key,把key作为参数,然后调evmc的callback函数```get_storage()``` ,查询相应的key对应的value。之后将读到的value写到结果堆栈SPP的顶端```SPP[0]```

``` cpp
CASE(SLOAD)
{
m_runGas = m_rev >= EVMC_TANGERINE_WHISTLE ? 200 : 50;
ON_OP();
updateIOGas();

evmc_uint256be key = toEvmC(m_SP[0]);
evmc_uint256be value;
m_context->fn_table->get_storage(&value, m_context, &m_message->destination, &key);
m_SPP[0] = fromEvmC(value);
}
```
**状态写指令举例**
SSTORE指令可以将数据写到节点的状态中,大致过程是,从栈顶第一和第二个位置(```SP[0]```、```SP[1]```)拿出key和value,把key和value作为参数,调用evmc的callback函数```set_storage()``` ,写入节点的状态。
``` cpp
CASE(SSTORE)
{
ON_OP();
if (m_message->flags & EVMC_STATIC)
throwDisallowedStateChange();
static_assert(
VMSchedule::sstoreResetGas <= VMSchedule::sstoreSetGas, "Wrong SSTORE gas costs");
m_runGas = VMSchedule::sstoreResetGas; // Charge the modification cost up front.
updateIOGas();
evmc_uint256be key = toEvmC(m_SP[0]);
evmc_uint256be value = toEvmC(m_SP[1]);
auto status =
m_context->fn_table->set_storage(m_context, &m_message->destination, &key, &value);
if (status == EVMC_STORAGE_ADDED)
{
// Charge additional amount for added storage item.
m_runGas = VMSchedule::sstoreSetGas - VMSchedule::sstoreResetGas;
updateIOGas();
}
}
```

**合约调用指令举例**

CALL指令能够根据地址调用另外一个合约。首先,EVM判断是CALL指令,调用```caseCall()```,在caseCall()```中,用```caseCallSetup()```从堆栈中拿出数据,封装成msg,作为参数,调用evmc的callback函数call。Eth在被回调```call()```后,启动一个新的EVM,处理调用,之后将新的EVM的执行结果,通过```call()```的参数返回给当前的EVM,当前的EVM将结果写入结果堆栈SSP中,调用结束。合约创建的逻辑与此逻辑类似。

``` cpp
CASE(CALL)
CASE(CALLCODE)
{
ON_OP();
if (m_OP == Instruction::DELEGATECALL && m_rev < EVMC_HOMESTEAD)
throwBadInstruction();
if (m_OP == Instruction::STATICCALL && m_rev < EVMC_BYZANTIUM)
throwBadInstruction();
if (m_OP == Instruction::CALL && m_message->flags & EVMC_STATIC && m_SP[2] != 0)
throwDisallowedStateChange();
m_bounce = &VM::caseCall;
}
BREAK

void VM::caseCall()
{
m_bounce = &VM::interpretCases;

evmc_message msg = {};

// Clear the return data buffer. This will not free the memory.
m_returnData.clear();

bytesRef output;
if (caseCallSetup(msg, output))
{
evmc_result result;
m_context->fn_table->call(&result, m_context, &msg);

m_returnData.assign(result.output_data, result.output_data + result.output_size);
bytesConstRef{&m_returnData}.copyTo(output);

m_SPP[0] = result.status_code == EVMC_SUCCESS ? 1 : 0;
m_io_gas += result.gas_left;

if (result.release)
result.release(&result);
}
else
{
m_SPP[0] = 0;
m_io_gas += msg.gas;
}
++m_PC;
}
```
## 总结
EVM是一个状态执行的机器,输入是solidity编译后的二进制指令和节点的状态数据,输出是节点状态的改变。以太坊通过EVMC实现了多种虚拟机的兼容。但截至目前,并未出现除开interpreter之外的,真正生产可用的虚拟机。也许要做到同一份代码在不同的虚拟机上跑出相同的结果,是一件很难的事情。BCOS将持续跟进此部分的发展。

0 comments on commit b320d9a

Please sign in to comment.