[以太坊源码学习] 客户端发起合约调用

我们都知道,乙太坊中的合约其实就是一段程序,一般由solidity开发。

在这一段程序中,定义了:

一系列的变量,用来做合约本地存储,一般只在合约内部被调用,也可以声明成public的,solidity编译器会自动为其生成对应的get函数。

一个构造函数,仅在合约部署时被被自动调用一次,用来初始化合约;

很多方法,其中只有public或external的方法才可以被外界调用,用户主要面向的是这些函数。

所谓的调用合约,其实就是调用合约中的public或external方法。

下面是一个简单的合约,就是实现了一个给一个整数乘于7的函数:

contract Multiply7 {
   event Print(uint);
   function multiply(uint input) returns (uint) {
      Print(input * 7);
      return input * 7;
   }
}

这个合约在编译后,就会生成一段二进制字节码,一个abi文件(合约描述文件,这个文件后面会用到)。

既然要调用方法,那么就需要知道合约的地址,方法名,参数列表。

我们以执行multiply(6)来举例:

合约地址:xxxxxxxxxxxxxx这个就说了

方法名: multiply

函数:6

如果通过rpc接口来调用,就是以下的语法:

curl --data '{"jsonrpc":"2.0","method": "eth_sendTransaction", "params": [{"from": "0xeb85a5557e5bdc18ee1934a89d8bb402398ee26a", "to": "0x6ff93b4b46b41c0c3c9baee01c255d3b4675963d", "data": "0xc6888fa10000000000000000000000000000000000000000000000000000000000000006"}], "id": 8}' localhost:8545

我们发现,这个接口调口的参数params中,除了from代表合约调起人,to为合约地址,还有一个不知何物的data,没有出现multiply和6,这是为什么?

其实,玄机就隐藏在data中: 

"data": "0xc6888fa10000000000000000000000000000000000000000000000000000000000000006"

这是对调用数据的16进制编码,而这个16进制编码正是evm所需要的。我们先不管evm怎么处理这段编码,先来看看这段编码是怎么生成的。

说这个之前,就要先说一下合约的abi,即Application Binary Interface Specification,“接口定义说明”。

它会用json的形式描述一个合约中所有方法,以这个合约为例,它的abi就是下面的样子:

[{
"constant": false,
"inputs": [{
"name": "input",
"type": "uint256"
}],
"name": "multiply",
"outputs": [{
"name": "",
"type": "uint256"
}],
"type": "function"
}, {
"anonymous": false,
"inputs": [{
"indexed": false,
"name": "",
"type": "uint256"
}],
"name": "Print",
"type": "event"

}]

可以看到,这个文件描述了方法multiply和事件Print,它们的方法名,输入参数和输入结果等等。

那么上面的16进制编码

0xc6888fa10000000000000000000000000000000000000000000000000000000000000006

是怎么生成的呢?

(1)生成函数签名:

通过abi定义,找到multiply的方法和它的参数类型列表(unin256),组成函数签名 multiply(unin256),其实这个函数签名就是类似一个函数声明的样子,只不过,只有函数名和参数类型的列表。

(2)对函数签名进行hash:

sha3("multiply(uint256)").substring(0, 8)
该函数对hash结果取了前4个字节,也就是32位,表现出来就是8位的16进制字符串,也就是
c6888fa1

(3)对参数值进行编码:

我们的参数是6,需要将其转化为32位的字节码,表现为16进制的字符串就是:

0000000000000000000000000000000000000000000000000000000000000006

(4)将2和3的结果组合起来,就完成了函数调用的编码了。


这段逻辑的实现,在乙太坊源码中,是如下的位置:

accounts/abi/bind/base.go的Call方法中:

(1)method和params进行编码:

input, err := c.abi.Pack(method, params...)

(2)组装调用数据结构体:

msg    = ethereum.CallMsg{From: opts.From, To: &c.address, Data: input}

(3)调用合约:

output, err = c.caller.CallContract(ctx, msg, nil)


好了,以上就是客户端发起合约调用的过程。

你可能感兴趣的:(区块链)