自2019年10月,越来越多的行业开始探索区块链项目的应用。其中除了IT行业外,便属金融业最为敏感,许多金融机构都在研究区块链。
本蒟蒻目前在北京一家金融机构的金融科技部门实习,接触到区块链技术,这篇文章整理了本人在学习关于利用Python开发以太坊智能合约的一些简单内容,供自己以后回顾和与大家分享。
因为本蒟蒻水平非常有限,如有错误,欢迎各路大佬指正。
简单来讲,区块链是一个链式数据结构,由一个个”区块“组成。通过一定的机制,来保证如果某人想要修改区块链上的数据,其需要花费极其高昂的成本才能不被人发现数据被修改过。而且每一个区块链的参与者都存储了整条区块链的数据的(其实也有特殊的情况),所以其不仅要修改自己存储的数据,还要想办法修改其余至少50%的参与者存储的数据,这计算成本是极其高昂的。即使我们假设一个人有足够的资源可以在有限的时间内做到(几乎不可能),他也不会那么做。因为这样做,会使得整个区块链系统不再被认可,人们不会承认其数据的价值。
从上面得知,区块链具有防篡改性(当然还有其他优秀的特性)。也就保证了数据的完全。如果两个人通过区块链达成一笔交易,那么谁也不能耍赖。所以区块链技术可以使两个陌生人可以在交易时信任彼此。区块链解决的是人与人在生产过程中的信任问题。人们用区块链交易不用担心受骗、反悔等等。
人工智能、大数据和区块链技术的发展,分别对应了当今社会生产力、生产资料和生产关系的深刻变革。区块链无疑会是改变世界的技术,只是现在人们还没有完全挖掘出其应用的价值。就像曾经的互联网一样。
大家听说过的比特币是区块链应用的1.0版本,以太坊是区块链应用的2.0版本。而智能合约是以太坊中的重要概念。智能合约是部署在以太坊中的一些交易规则,用户调用智能合约的特定接口,便可以自动执行特定的交易,同时被记录到链中。比如我可以写一个领养宠物的智能合约,其他用户可以通过该智能合约,选择要领养的宠物以及支付费用,智能合约会自动改变该宠物的主人的记录,并上传到链中。
开发智能合约的官方指定语言是Solidity。这篇帖子介绍如果利用Python进一步包装智能合约,来开发具体的区块链应用。
1.下载ganache
快速生成虚拟的私有链节点,用于开发测试
下载地址:https://www.trufflesuite.com/ganache
我们下载得到的是客户端版本,也可以安装其命令行版本:ganache-cli,但安装命令行版本需要先按照nodejs和npm.所以这里暂时不使用命令行版本。
下载得到Ganache-2.x-win-x64.appx ,解压后打开其app目录下的Ganache.exe即可。
效果图:
我们后期开发调用该私有链时,通过127.0.0.1:7545接口访问即可。(即图片中的RCP SERVER)
2.在pycharm中安装solidity插件
目前来看,开发智能合约貌似绕不开solidity。但可以用Python来控制solidity的编译,调用智能合约.
开发solidity的IDE有很多,官方推荐Remix.但为了让整个项目管理起来更方便,我们直接在Pycharm中安装solidity插件,在Pycharm中开发solidity.
安装方式:打开Pycharm,依次选择Settings–>Plugins,搜索solidity,下载Intellij-Solidity.
若通过上述方式下载失败,则直接通过官网下载压缩包https://plugins.jetbrains.com/plugin/9475-intellij-solidity,再解压到Pycharm安装目录的plugins文件夹下即可。
正确配制后,我们新建文件时,就会出现Solidity文件的选项。
3.下载solidity编译器
上一步在pycharm中安装的solidity插件应该(未考证)并不包含对solidity代码的编译模块,只是提供了代码高亮、代码提示等功能。所以我们需要下载solidity编译器。
下载地址:https://github.com/ethereum/solidity/releases。这里我们下载的是0.4.21的windows版本:solidity-windows .zip.
下载完成后进行解压:
接下来,为了方便使用,我们将solc.exe配置到环境变量的Path中。
配置环境变量的主要原因是方便调用solc.exe。例如,我们在cmd中调用solc.exe时,直接在根目录输入solc指令即可,而不必cd到solc.exe的解压目录下。
4.在Pycharm中编译solidity文件。
我们安装完solidity编译器后,如果需要编译solidity文件,可以通过在cmd中调用相关指令来实现。但这显然比较麻烦。我们希望在Pycharm中直接编译solidity,所以需要简单配置一下。
打开Pycharm中的file-Settings-Tools-External Tools,点击‘+’,填入以下内容。
其中三个配置参数为:
Program:D:\solidity-windows\solc.exe(这个需要换成自己的solc.exe的路径)
Arguments:–abi --bin F i l e N a m e FileName FileName -o P r o j e c t F i l e D i r ProjectFileDir ProjectFileDir\CompiledSolidity (solc的指令)
Working directory: F i l e D i r FileDir FileDir (将目录切换到项目所在目录中来)
当需要编译时,我们只需要右键单击.sol文件,选择该工具即可。
之后便会出现CompiledSolidity文件夹,里面包含了编译好的智能合约的相关文件,我们将会在Python中使用这两个文件。
而文件夹的名字是在上面配置工具时,
Arguments:–abi --bin F i l e N a m e FileName FileName -o P r o j e c t F i l e D i r ProjectFileDir ProjectFileDir\CompiledSolidity中的-o参数决定的。读者可自行修改。
两个文件的名字是由solidity文件中定义的合约名称决定的。我们编译的文件中定义了名为IntDemo的智能合约。
5.安装web3.py
web3.py是以太坊官方维护的Python版rpc接口封装库,用来完成Python和智能合约的交互。
我们利用pip install web3即可安装完成。
6.测试连接节点
我们在Python中开发以太坊智能合约,首先需要连接到以太坊节点、获取账户。
我们这里使用上面介绍的Ganache来创建本地的虚拟节点,同时生成10个虚拟账户。我们将用这些账户来完成合约的测试。
首先我们需要打开Ganache,创建一个本地的虚拟以太坊节点。该节点的地址为http://localhost:7545
下面我们在Python中连接该以太坊节点,并输出创建的账户。
from web3 import Web3
w3 = Web3(Web3.HTTPProvider('http://localhost:7545'))
accounts = w3.eth.accounts
print(accounts)
运行上述代码,便可以打印出10个账户的Address.这样便代表连接成功了。
上面的步骤全部成功实现后,我们开发的环境就搭建完成了。后面的工作就是利用solidity编写智能合约+Python来进一步包装智能合约,使其可以方便得应用。
**
**
因为我们现在的首要目标是整理用Python开发以太坊项目的过程,具体Solidity的语法不是我们关注的重点,所以我们这里直接使用网上的一个智能合约代码来研究,我们只需要知道,该程序创建了一个名为CrowFunding的智能合约,合约中定义了几个结构体和若干函数即可。
pragma solidity ^0.4.21;
// 主要完成产品的众筹
contract CrowdFunding{
// 投资者是结构体
struct Funder{
address addr; // 投资者地址
uint amount; // 投资金额
}
// 采用结构体来描述众筹产品
struct Product{
address addr; // 如果众筹成功,则金额会转到当前地址
uint goal; // 预期众筹的目标,如果达到此目标则说明众筹成功
uint amount; // 实际众筹的金额
uint funderNum; // 统计投资者的人数,缺省值为0
// 映射类型,统计当前产品的投资者
mapping(uint => Funder) funders;
}
// 平台要统计众筹的产品数量
uint count;
// 此映射主要记录平台的众筹产品
mapping (uint => Product) public products;
// 添加众筹产品信息
function candidate(address addr, uint goal) returns (uint){
// 结构体是不需要new,此处按照结构体声明的变量顺序进行赋值
products[count++] = Product(addr, goal*10**18, 0, 0);
}
// 此函数实现对产品进行众筹功能
function vote(uint index) payable {
// 通过索引获取要众筹产品信息
Product p = products[index];
// 创建投资者,并且存储到产品众筹映射中
// msg.sender:当前函数调用者,就是众筹者, msg.value:众筹金额是调用函数时传入的value值
p.funders[p.funderNum++] = Funder({addr: msg.sender, amount: msg.value});
// 把当前众筹金额追加到amount中
p.amount += msg.value;
}
// 检测当前产品众筹是否成功(如果成功则众筹金额转到产品提供的地址)
function check(uint index) payable returns (bool){
Product p = products[index];
// 判断当前众筹金额是否大于设置金额
if(p.amount < p.goal){
return false;
}
// 众筹成功,当前金额要转给产品地址
uint amount = p.amount;
// 初始化amount
p.amount = 0;
p.addr.transfer(amount); // 如果失败则返回为false
// if(!p.addr.send(amount)){
// throw;
// }
return true;
}
}
部署合约
下面介绍如何利用Python部署该智能合约到我们的私有链。
首先,我们需要编译上述Solidity。利用上述介绍的方法编译即可,这里我们使用扩展工具来编译,因为其在编译完成后会自动生成存储我们需要的信息的文件到指定文件夹。
导入需要的包并连接到我们的私有链,同时获取链上的账户信息。
from web3 import Web3
import json
w3 = Web3(Web3.HTTPProvider('http://localhost:7545'))
accounts = w3.eth.accounts
紧接着,加载上一步编译生成的abi和bin文件。
注:fn_addr文件用来存储合约地址。在合约成功部署后,会得到一个合约地址。之后便是通过该地址对合约进行交易。所以保存该地址是有必要的。
artifact = 'CrowdFunding'
fn_abi = 'CompiledSolidity/{0}.abi'.format(artifact)
fn_bin = 'CompiledSolidity/{0}.bin'.format(artifact)
fn_addr = 'CompiledSolidity/{0}.addr'.format(artifact)
with open(fn_abi,'r') as f:
abi = json.load(f)
with open(fn_bin,'r') as f:
bin = f.read()
随后我们便可以利用abi和bin生成智能合约的一个对象
factory = w3.eth.contract(abi=abi,bytecode=bin)
之后通过发送新的公共交易来部署合约。这里我们使用第一个来账户来部署该合约。
tx_hash = factory.constructor().transact({'from':accounts[0]})
发送交易后,我们需要等待交易被接收。
receipt = w3.eth.waitForTransactionReceipt(tx_hash)
print(receipt)
当交易被链正确接收后,可以打印receipt的值,其中记录了该笔交易和链相关的信息,合约的地址就在其中:
AttributeDict({
'transactionHash': HexBytes('0xb4d53764c7cba24b36cc02842af093ad261d9d8c0e04f963409588d4ae8b29fd'),
'transactionIndex': 0,
'blockHash': HexBytes('0x4f5a86f62bd1b33f0e2e90dcb7bf973d19117e44ea50c842224d8829b9d4381b'),
'blockNumber': 7,
'from': '0xA77bbC612F03A9064aebcCA593dc7bF5A0aFb836',
'to': None,
'gasUsed': 347082,
'cumulativeGasUsed': 347082,
'contractAddress': '0x6C5602a2c4d7D96479B0006edF46fd9756De7AE4', 'logs': [],
'status': 1,
......})
最后保存一下合约的地址
with open(fn_addr,'w') as f:
f.write(receipt.contractAddress)
部署完成合约后,接下来就是按照Web3.py提供的接口,对智能合约中定义的方法进行调用。
我们可以通过**Contract.functions.myMethod()**来调用合约中的函数。其中Contract是合约对象,myMethod是合约中定义的函数。例如:
factory.functions.candidate('0xEF43AAeaAD58b47250654efd889137Bd116e92fc',10).transact({'from':accounts[1],'to':receipt.contractAddress})
便调用了上述合约中的candidate函数,并传入了需要的参数,调用者是第一个账户。
调用合约
在合约部署到链上后,如果我们想要执行合约。可以分为两步:1.生成合约对象 2.通过web3.py与合约交互。
生成合约对象有两种方式:
第一种:
fn_abi = 'CompiledSolidity/{0}.abi'.format(artifact)
fn_bin = 'CompiledSolidity/{0}.bin'.format(artifact)
with open(fn_abi,'r') as f:
abi = json.load(f)
with open(fn_bin,'r') as f:
bin = f.read()
factory =w3.eth.contract(abi=abi,bytecode=bin)
也就是通过abi和bin实例化一个智能合约对象。这里的abi和bin便是上面部署合约时保存的内容。
第二种:
fn_abi = 'CompiledSolidity/{0}.abi'.format(artifact)
fn_addr = 'CompiledSolidity/{0}.addr'.format(artifact)
with open(fn_abi,'r') as f:
abi = json.load(f)
with open(fn_addr,'r') as f:
addr = f.read()
factory = w3.eth.contract(abi=abi,address=Web3.toChecksumAddress(addr))
这种方式是通过abi和地址实例化一个智能合约。
这里建议使用第二种方式。因为使用第一种方式实例化的智能合约对象,在调用其函数(也就是交易)时,我们仍要在transact中填写’to’字段(也就是智能合约的地址)。而用第二种方式,也就是通过地址实例化的对象,便可以省略‘to’字段。(至少我用上述的智能合约测试时可以省略)。
生成合约对象后,我们要做的便是调用合约中的函数来修改合约的状态或者查询合约中的数据。
factory.functions.candidate(accounts[2],10).transact(
{'from':accounts[1]})
例如上述,便是调用了上述智能合约的candidate函数。我们这里暂时不用知道candidate函数的具体内容,只需要知道该函数修改了一个“重要”变量(也就是权限为 public,可供外部查看的变量)的值。而在以太坊智能合约中,一旦这种“重要变量“的值发生改变,其状态便会更新到以太坊中。而最后的transact({})便是发起交易,{}中要给出交易的一些参数,比如上面就给出了’from‘,也就是交易发起者的地址。该交易一旦被确认,便会被记录到区块链中。
而会修改区块链状态的函数没有返回值的(或者更准确地说,它会返回交易的ID),而如果你想要查看一些智能合约的数据。可以有两种途径。
1.编写相应的get函数,就像Java中的POJO类一样。
2.对应那些”重要的变量“(也就是权限为public的变量),solidity编译器会自动生成一个以变量名为函数名的getter函数。我们通过该函数来获取solidity中我们感兴趣的变量的值。
例如:
count=factory.functions.count().call()
上述便是调用了count()来获取合约中count的值的代码。因为我们调用该函数不会改变区块链的状态,所以最后用的是.call()而不是上述的.transact().
另外,对于基础的数据类型,我们调用其getter函数会直接返回相应变量的值。
但对于非基础类型的数据,比如数组、映射等等,我们调用其getter()函数时需要传入参数。对于数组,我们要传入一个int值作为索引,来获取数组中对应位置的元素的值(是的,一次只能获取一个元素,而无法获取整个数组)。相应的,对于映射,便是传入key作为参数了。
现在,我们已经大体知道了如何部署并调用合约。
后面要完成的工作便是学习solidity的语法细节和研究web3.py的开发文档,再结合一些Python的框架,开发出智能合约的应用了。
剩下的以后再补充,欢迎指教。