Hyperledger Burrow EVM

Hyperledger Burrow由Linux基金会托管,最初由Monax设计,Monax是一个开放平台,可为企业生态系统构建,交付和运行基于区块链的应用程序。著名的处理器和芯片制造商英特尔也共同赞助了该项目。

Hyperledger Burrow充当许可的智能合约应用程序引擎,其主要工作是以安全有效的方式执行和处理智能合约程序。它是为支持特定于应用程序的优化的多链环境而构建的。

以太坊等许多区块链网络都支持智能合约,自行执行的合约,这些合约的合约条款直接写入代码中。简而言之,Hyperledger Burrow充当智能合同解释器,促进遵循以太坊虚拟机(EVM)标准的网络上此类合同的执行。EVM使用全球公共节点网络执行以太坊智能合约脚本。Burrow充当区块链上的一个节点,该区块链使用EVM标准来提供各种智能合约交易的结论性和高交易吞吐量。

Hyperledger Burrow包含以下组件:

共识引擎负责在区块链上订购和处理各种交易,并确保高交易量输出。它具有一组内置的事务验证器,还可以防止任何可能的恶意企图来破解和分叉区块链。共识引擎与智能合约应用程序无关,因为另一层应用程序区块链接口(ABCI)将两者分开,从而确保了核心引擎与各种应用程序(有时可能包括恶意应用程序)之间的安全性。

每当在区块链网络上发生的交易要求执行智能合约代码时,智能合约应用程序(SCA)组件都会在许可的EVM中激活该帐户代码的必要执行。EVM的工作是确保执行应用程序的代码遵守以太坊操作代码规范,并正确授予所需的权限。Burrow通过EVM模块支持合约的执行与调用,调用时根据合约地址获取到代码,生成环境后载入到EVM中运行。通常智能合约的开发流程是用solidlity编写逻辑代码,再通过编译器编译元数据,最后再发布。

虽然Burrow遵循以太坊虚拟机标准,但是其EVM的设计与以太坊的虚拟机还是有一定差别。

Burrow EVM

Hyperledger Burrow 的EVM代码存放于execution\evm中。
在这里插入图片描述
其结构:
在这里插入图片描述
Burrow EVM 其他部分与以太坊虚拟机差别不大,都是基于栈的虚拟机,其中:
•contract.go 定义了智能合约的数据结构
•evm.go 定义了执行器,以及对外提供一些外部接口
•memory.go 定义了EVM的内存
•stack.go 定义了EVM的栈

gas

其gas花费代码位于execution\native\gas.go中,与以太坊虚拟机不同,Burrow EVM每次花费固定为1:
在这里插入图片描述

智能合约

合约是EVM智能合约的存储单位也是解释器执行的基本单位,包含了代码,调用人,所有人,gas相关的信息。定义于native\contract.go中:

type Contract struct {
	// Comment describing purpose of native contract and reason for assembling
	// the particular functions
	Comment string
	// Name of the native contract
	Name          string
	functionsByID map[abi.FunctionID]*Function
	functions     []*Function
	address       crypto.Address
	logger        *logging.Logger
}

执行环境

执行入口定义在evm.go中,功能是组装执行环境(代码,执行人关系,参数等)。同样需要创建调用对象的state,但参数稍有不同,这里没有考虑别人合约可能需要花钱。

func (vm *EVM) Execute(st acmstate.ReaderWriter, blockchain engine.Blockchain, eventSink exec.EventSink,
	params engine.CallParams, code []byte) ([]byte, error) {

	// Make it appear as if natives are stored in state
	st = native.NewState(vm.options.Natives, st)

	state := engine.State{
		CallFrame:  engine.NewCallFrame(st).WithMaxCallStackDepth(vm.options.CallStackMaxDepth),
		Blockchain: blockchain,
		EventSink:  eventSink,
	}

	output, err := vm.Contract(code).Call(state, params)
	if err == nil {
		// Only sync back when there was no exception
		err = state.CallFrame.Sync()
	}
	// Always return output - we may have a reverted exception for which the return is meaningful
	return output, err
}

ABI

ABI即Application Binary Interface,是应用程序二进制接口,描述了应用程序和EVM之间,一个应用和它的库之间,或者应用的组成部分之间的低接口。ABI涵盖了各种细节,如:
●数据类型的大小、布局和对齐;
●调用约定(控制着函数的参数如何传送以及如何接受返回值),例如,是所有的参数都通过栈传递,还是部分参数通过寄存器传递;哪个寄存器用于哪个函数参数;通过栈传递的第一个函数参数是最先push到栈上还是最后;
●系统调用的编码和一个应用如何向EVM进行系统调用;
●以及在一个完整的EVM的ABI中,目标文件的二进制格式、程序库等等。

这么说可能有点难懂,先从智能合约的角度说起。

我们编写智能合约的流程是:
●编写合约代码(一般使用solidity语言)
●编译合约,将solidity编写的代码编译成EVM可识别的bytecode,这一步生成abi
●部署合约,将合约部署到区块链上,生成合约地址,将合约内容(即上一步生成的bytecode)作为input date输入。部署合约是一个交易过程,所以也会生成一个交易Hash
●执行合约,获取合约地址,然后传入参数调用合约中的方法,获得执行结果

从上面的步骤可以看出,abi对于EVM来说,其实是不需要的。但是对于调用者来说,就需要知道合约有哪些方法,方法的参数是什么,返回值是什么,而这些信息就记录在智能合约的abi中。
所以简单来说,ABI其实就相当于开发者的接口文档,方便开发者调用执行合约。

以一个简单的solidity为例,通过Web3,Remix等工具编译生成ABI。

pragma solidity ^0.4.24;


contract Demo {

    uint private x;

    function set(uint _x) public {
        x = _x;
    }

}

执行 truffle compile 编译合约,就会生成对应的文件Demo.json

{
  "contractName": "Demo",
  "abi": [
    {
      "constant": false,
      "inputs": [
        {
          "name": "_x",
          "type": "uint256"
        }
      ],
      "name": "set",
      "outputs": [],
      "payable": false,
      "stateMutability": "nonpayable",
      "type": "function"
    }
  ],
  "bytecode": "0x6080604052348015600f57600080fd5b5060a48061001e6000396000f300608060405260043610603f576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b1146044575b600080fd5b348015604f57600080fd5b50606c60048036038101908080359060200190929190505050606e565b005b80600081905550505600a165627a7a723058201dfe7c019fec67ccd87250c9ac8642c163cc5f43588715b33e8a8953df3715f60029",
  "deployedBytecode": "0x608060405260043610603f576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806360fe47b1146044575b600080fd5b348015604f57600080fd5b50606c60048036038101908080359060200190929190505050606e565b005b80600081905550505600a165627a7a723058201dfe7c019fec67ccd87250c9ac8642c163cc5f43588715b33e8a8953df3715f60029",
  "sourceMap": "27:97:1:-;;;;8:9:-1;5:2;;;30:1;27;20:12;5:2;27:97:1;;;;;;;",
  "deployedSourceMap": "27:97:1:-;;;;;;;;;;;;;;;;;;;;;;;;69:52;;8:9:-1;5:2;;;30:1;27;20:12;5:2;69:52:1;;;;;;;;;;;;;;;;;;;;;;;;;;;112:2;108:1;:6;;;;69:52;:::o",
  "source": "pragma solidity ^0.4.24;\n\n\ncontract Demo {\n\n    uint private x;\n\n    function set(uint _x) public {\n        x = _x;\n    }\n\n}\n",
  "sourcePath": "/Users/root/Workspace/DApp/demo/contracts/Demo.sol",
  "ast": {
    ...
  },
  "legacyAST": {
    ...
  },
  "compiler": {
    "name": "solc",
    "version": "0.4.24+commit.e67f0147.Emscripten.clang"
  },
  "networks": {},
  "schemaVersion": "2.0.1",
  "updatedAt": "2018-09-14T11:57:49.750Z"
}

其中,各字段的意义分别为:
name:函数名称
type:方法类型,包括function, constructor, fallback(缺省方法)可以缺省,默认为function
constant:布尔值,如果为true指明方法不会修改合约字段的状态变量
payable:布尔值,标明方法是否可以接收ether
stateMutability:状态类型,包括pure (不读取区块链状态),view (和constant类型,只能查看,不会修改合约字段),nonpayable(和payable含义一样),payable(和payable含义一样)。其实保留payable和constant是为了向后兼容
inputs:数组,描述参数的名称和类型
name:参数名称
type:参数类型
outputs:和inputs一样,如果没有返回值,缺省是一个空数组

在使用ABI调用合约函数时,传入的ABI会被编码成calldata(一串hash值)。calldata由function signature和argument encoding两部分组成。通过读取call data的内容, EVM可以得知需要执行的函数,以及函数的传入值,并作出相应的操作。

EVM读取并执行call data的规则:
●函数选择器: Call data的前4个bytes对应了合约中的某个函数。因此EVM通过这4个bytes, 可以跳转到相应的函数。

ByteCode中的体现:
在这里插入图片描述
在这里插入图片描述
上面两张图是合约bytecode最开始的部分,EVM依次执行每条命令,当执行到CALLDATASIZE(L6)时,EVM读取input的size(读取的是function的hash, 长度为4bytes)。L7会和4进行比较,作为L9的JUMPI的跳转条件

input的size比4小,则跳转到fallback function(fallback function是唯一一个没有名字的函数)。跳转的地址是由L8 push到栈中的,跳转的地址是6d,转换成10进制为109,也就是L42。

input的size长度为4,则会继续执行每个指令。L11的CALLDATALOAD会从读取input的具体值,与L18,23,28,33,38的hash值(该合约中的函数签名)进行比较,以L18为例,L19会比较input的函数签名与L18的哈希值是否相等。如果相等,则跳转到L20表明的地址(PC:8d),否则继续执行,直到遇到函数签名相等的情况为止

如果没有一个函数签名相等(此时执行到L41),因为没有执行到终止命令,如stop。EVM会继续往下执行L42,L42是fallback function的起始位置。因此fallback function的执行情况是:
A contractcan have exactly one unnamed function. This function cannot have arguments and cannot return anything. It is executed on a call to the contract if none of theother functions match the given function identifier. Furthermore, this functionis executed whenever the contract receives plain Ether (without data).

●参数读取:call data是32bytes的整数倍(头4 bytes的函数签名除外),EVM通过CALLDATALOAD指令,每次能从call data中读取32byte的值,放入stack中。上面的图通过2个CALLDATALOAD分别读出了newState和value的值,放入了stack中。通过JUMP指令跳转到函数体(tag19),并继续执行。

●返回值:

当函数结束完时,会跳转到tag18。Tag 18最末尾的RETURN指令。RETURN指令会从stack中读取两个值,通过这两个值从memory中读取相应的值,以返回给调用方。

ABI是如何编码的

●函数编码:在EVM中,每个函数都由4个byte长度的16进制值来唯一标识,这4个bytes叫做函数签名。函数签名是对函数名,函数参数做Keccak(SHA-3) 运算后,获得的hash值的前4个bytes.

如下面increaseAge这个函数,直接通过web3提供的sha3,取前4bytes即可获得函数签名F9EA5E79. EVM会通过这个函数签名找到对应的函数,通过JUMPI跳转到对应的函数。

functionincreaseAge(string name, uint num)returns (uint){

       return ++age;
}

●函数参数编码:

每个参数都是以一个32byte长度hash值的形式传入的,长度不够用0补,如uint8的长度是8bytes,前面不足的24bytes都用0来补。用下面的合约来举例:

pragma solidity ^0.4.16;

contract Foo {
  function baz(uint32 x, bool y) public pure returns (bool r) { r = x > 32 || y; }
  function bar(bytes3[2] memory) public pure {}
  function f(uint, uint32[], bytes10, bytes) public pure {}
}

案例一:
函数:baz(bytes3[2] memory)
调用:baz(69, true)

0xcdcd77c0,在node中使用new Web3().sha3('baz(uint32,bool)')生成

const Web3 = require('web3')

const web3 = new Web3()
console.log(web3.sha3('f(uint256,uint32[],bytes10,bytes)'))

0x0000000000000000000000000000000000000000000000000000000000000045,十进制69,转成16进制为45,因为是正数,高位补0至32bytes

0x0000000000000000000000000000000000000000000000000000000000000001,bool类型,true=1,false=0,高位补0
所以最终字符串为:

0xcdcd77c0
0000000000000000000000000000000000000000000000000000000000000045
0000000000000000000000000000000000000000000000000000000000000001

一共68bytes。

返回:该函数返回的是true,output将会是

0x0000000000000000000000000000000000000000000000000000000000000000
案例二:
函数:bar(bytes3[2] memory)
调用:bar(["abc", "def"])

0xfce353f6,在node中使用new Web3().sha3('bar(bytes3[2])')生成
固定长度不需要计算偏移量
0x6162630000000000000000000000000000000000000000000000000000000000,字符串abc转成16进制后为616263,低位补0
0x6465660000000000000000000000000000000000000000000000000000000000,同上
所以最终字符串为:

0xfce353f6
6162630000000000000000000000000000000000000000000000000000000000
6465660000000000000000000000000000000000000000000000000000000000

案例三:
函数:f(uint,uint32[],bytes10,bytes)
调用:

f(0x123, [0x456, 0x789], "1234567890", "Hello, world!")

0x8be65246,在node中使用f(uint256,uint32[],bytes10,bytes)生成
0x00000000000000000000000000000000000000000000000000000000000001230x123对应的16进制,正数补全
0x0000000000000000000000000000000000000000000000000000000000000080,动态类型,计算偏移量。这个的偏移量是指实际存储值的位置,由于这个函数有4个变量,那么实际存储值的位置就是第五个32bytes位置,也就是说偏移量等于4×32bytes=128,转成16进制后就是对应的值
0x3132333435363738393000000000000000000000000000000000000000000000,字符串1234567890转成16进制后为31323334353637383930,bytes类型,低位补全
0x00000000000000000000000000000000000000000000000000000000000000e0,动态类型,计算偏移量,这个偏移量就等于参数长度4×32bytes+前面的动态参数参数占有的长度(因为前面只有一个动态参数,所以这个长度就是1×32bytes+2×32bytes,1×32bytes是第一个动态参数长度所占的bytes数,2×32bytes是因为该函数中的第一个动态参数有2个值),那么具体的值就是 4×32bytes+(1×32bytes+2×32bytes)=7×32bytes=224,转成16进制就是e0,高位补全就是对应的值
0x0000000000000000000000000000000000000000000000000000000000000002,第一个动态参数的长度,长度为2
0x0000000000000000000000000000000000000000000000000000000000000456,第一个动态参数中的第一个元素
0x0000000000000000000000000000000000000000000000000000000000000789,第一个动态参数中的第二个元素
0x000000000000000000000000000000000000000000000000000000000000000d,第二个动态参数的长度,长度为13
0x48656c6c6f2c20776f726c642100000000000000000000000000000000000000,第二个动态参数的值编码
所以最终字符串为:

0x8be65246
0000000000000000000000000000000000000000000000000000000000000123
0000000000000000000000000000000000000000000000000000000000000080
3132333435363738393000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000000000000000e0
0000000000000000000000000000000000000000000000000000000000000002
0000000000000000000000000000000000000000000000000000000000000456
0000000000000000000000000000000000000000000000000000000000000789
000000000000000000000000000000000000000000000000000000000000000d
48656c6c6f2c20776f726c642100000000000000000000000000000000000000

用ABI与smart contract进行交互

当DAPP端调用smart contract的某个函数时,web3的作用就是把ABI通过网络发送给Node。Node接收到ABI之后,编译成hash值并且执行。Node把执行完的结果上传到区块链。如果有返回值,Node再通过网络的方式返回给DAPP。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
1444. Elephpotamus Time limit: 0.5 second Memory limit: 64 MB Harry Potter is taking an examination in Care for Magical Creatures. His task is to feed a dwarf elephpotamus. Harry remembers that elephpotamuses are very straightforward and imperturbable. In fact, they are so straightforward that always move along a straight line and they are so imperturbable that only move when attracted by something really tasty. In addition, if an elephpotamus stumbles into a chain of its own footprints, it falls into a stupor and refuses to go anywhere. According to Hagrid, elephpotamuses usually get back home moving along their footprints. This is why they never cross them, otherwise they may get lost. When an elephpotamus sees its footprints, it tries to remember in detail all its movements since leaving home (this is also the reason why they move along straight lines only, this way it is easier to memorize). Basing on this information, the animal calculates in which direction its burrow is situated, then turns and goes straight to it. It takes some (rather large) time for an elephpotamus to perform these calculations. And what some ignoramuses recognize as a stupor is in fact a demonstration of outstanding calculating abilities of this wonderful, though a bit slow-witted creature. Elephpotamuses' favorite dainty is elephant pumpkins, and some of such pumpkins grow on the lawn where Harry is to take his exam. At the start of the exam, Hagrid will drag the elephpotamus to one of the pumpkins. Having fed the animal with a pumpkin, Harry can direct it to any of the remaining pumpkins. In order to pass the exam, Harry must lead the elephpotamus so that it eats as many pumpkins as possible before it comes across its footprints. Input The first input line contains the number of pumpkins on the lawn N (3 ≤ N ≤ 30000). The pumpkins are numbered from 1 to N, the number one being assigned to the pumpkin to which the animal is brought at the start of the trial. In the next N lines, the coordinates of the pumpkins are given in the order corresponding to their numbers. All the coordinates are integers in the range from −1000 to 1000. It is guaranteed that there are no two pumpkins at the same location and there is no straight line passing through all the pumpkins. Output In the first line write the maximal number K of pumpkins that can be fed to the elephpotamus. In the next K lines, output the order in which the animal will eat them, giving one number in a line. The first number in this sequence must always be 1.写一段Java完成此目的
最新发布
06-03

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值