Web工程师以太坊入门

我经常构建使用以太坊的Web应用程序,我理所当然地认为每天都使用的是神奇的工具集。我们的生态系统正在迅速发展,我认为很多新人都感到不知所措。以太坊是一项了不起的技术,但它也是新生的,而且根本没有足够的时间让专业知识充分渗透。我希望人们知道以太坊开发实际上与现代Web开发人员工作流程非常兼容——将以太坊功能集成到任何Web应用程序中相对容易,你可以从今天开始。

因为我认为自己是以太坊的高手,可以向主流开发者展示方向,我决定将一堆分散的知识放到一个地方(我知道不是非常去中心化)。你当然需要在每一步都查阅相应的文档,但我希望本文将向你展示如何将所有内容(或多或少)放在一起。

如果你准备好学习,请让我为你提供方向和指南。加入以太坊生态系统,一起征服世界。

获取区块链

有很多客户端可供选择,但我建议不要担心gethparitypyethapp(即将到来的python客户端代表!)。对于那些只想要一个可复用的区块链以便可以开始构建东西(例如你)的人,我建议testrpc满足你的所有开发需求。安装完成后,可以使用以下命令启动它:

1
testrpc

恭喜你,这就有一个区块链了。请注意,默认情况下,testrpc不会挖掘块,但-b标志允许你指定块间隔(例如1秒)。我喜欢这种配置有很多原因,我不会介入,但请记住它是可用的。

与区块链交互

一旦你的区块链旋转,你需要一种与它交谈的方法。 你可能已经下载了web3.js。 如果你没有,你必须下载新的。好吧,继续并确保安装了web3,然后打开一个config.js文件并将其放入其中:

1
2
3
4
5
var web3 = require('web3');
var web3_provider = 'http://localhost:8545';
var _web3 = new web3();
_web3.setProvider(new web3.providers.HttpProvider(web3_provider));
exports.web3 = _web3;

任何时候你想与后端服务器上的区块链交互,只需要这样做:

1
2
3
var config = require('./config.js');

config.web3.eth.X

可以在此处找到X(即你想要的任何web3 API函数)。

写智能合约

我会在这里为你节省一些时间:你将使用solidity来编写智能合约。如果你认为智能合约是可怕的,没必要。对于许多应用程序,只要遵循一条规则,它实际上非常简单:保持合约简单。

有两个原因,你总是始终保持合约绝对简单,因为必须这样:

  • 每次计算/存储操作都需要gas,等于以太币,等于货币。我们正在谈论支付0.05美元和1.50美元之间的差异来调用你的合约。以太坊的观点不是要替换你的数据库(至少在我看来不是这样),所以保持逻辑简短和存储最小化。
  • 更复杂=更多地方出错。当你的代码负责人们的钱并且无法回滚时,这很糟糕。请花一点时间只让有用的话写在其中。

好的,简单的合约——得到它。让我们继续。

部署智能合约

如果你还没有听说过truffle,那么现在一定要看一下。我喜欢在truffle目录中管理我的测试者合约。关于这一点的巧妙之处在于,你可以轻松地将其用于测试框架。在package.json中考虑这个脚本:

1
2
3
"scripts": {
"test": "cd truffle && truffle deploy && truffle test ./myTruffleTest.js && cd .. && npm run myOtherTests"
}

这样做:1.部署合约,2.运行truffle测试,3.运行常规测试——所有这些都在同一个脚本中!

请注意,你的truffle测试是“特殊的”,因为它们会在测试范围内注入一堆很酷的区块链内容。有多种方法可以将此信息传递给你的测试套件的其余部分。我个人使用truffle测试将合约地址保存到配置文件中,然后将该配置导入到我常规mocha测试中。只要我有正确的地址,我就可以通过web3.js在任何测试中与我的合约进行交互。无论如何,你会发现什么最适合你。

回到主要内容。你可以通过转到truffle目录并键入以下内容来部署智能合约:

1
truffle deploy

请注意,testrpc必须在另一个窗口中运行!

这将打印你刚刚部署的合约的地址,稍后你将需要该地址。正如我所提到的,你总是可以在truffle测试中以编程方式保存这个地址,但是现在你可以将它复制并粘贴到你的config.js文件中:

1
exports.contract_addr = '0xe73e8e0a4442e140aea87a4b150ef07b82492500'

进行智能合约调用

既然我们有合约,我们需要调用它。好的,这个看起来很简陋——我们将用纯十六进制字符串调用合约。当然有libraries可以让这更容易,但是当涉及到合约调用时,我就开始要讲课了。请记住,我是你的领路人。

首先要注意的是,所有内容都必须是十六进制的。数字,字符串等要注意的第二件事是以太坊中的words是256位。这意味着你需要用零填充所有内容到64个字符。需要注意的第三件事是必须在函数定义中规范地声明类型。

好吧,这真的挺乱。我们来看一个例子,更好理解:

1
2
3
function add(uint x, uint y) public constant returns (uint) {
return x + y;
}

假设你要做个加法如1加2,以下是你调用此函数的方法:

  • 1.获取封装好的规范函数定义的keccak 256哈希的前4个字节。

说什么?好吧,我没有做到这一点,但你可以在这个网站上输入你的功能声明并取前8个字符。规范是什么意思?好吧,在以太坊中有规范类型和速记类型(例如uint256uint的规范类型)。我实际上不知道它们的定义在哪里,但是查看以太坊ABI定义的例子以及这篇文章

无论如何,这就是我们的定义:

1
add(uint256,uint256)

返回keccak256哈希:

1
771602f7f25ce61b0d4f2430f7e4789bfd9e6e4029613fda01b7f2c89fbf44ad

其中前4个字节(8个字符)是:

1
771602f7
  • 2.将参数填充为256位

这个更容易掌握:

x = 1是:

1
0000000000000000000000000000000000000000000000000000000000000001

y = 2是:

1
0000000000000000000000000000000000000000000000000000000000000002

他们在一起是:

1
00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002
  • 3.将所有内容打包在一起并添加0x前缀

自定义:

1
0x771602f700000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002

现在我们有了有效负载,我们可以通过web3调用合约:

1
2
3
4
5
6
7
var config = require('./config.js');

var call = '0x771602f700000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002'

var to = config.contract_addr;

var res = config.web3.eth.call({ to: to, data: call });

在那之后,你应该返回res=3。实际上,你会得到一个BigNumber对象:

1
2
res.toString()
>'3'

你可能应该阅读此内容以了解有关在整个应用中使用BigNumbers的原因。

好的,你可以使用我之前提到的

等等,我们还没有完成!我刚刚告诉你如何调用合约。但是,如果你想写入些东西(即更新状态)怎么办?以上的是不行的!你需要使用私钥签署一个交易,但在此之前,你需要一些以太。

设置帐户

我们回到truffle吧。在我们的测试中,需要添加如下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var keys = require(`${process.cwd()}/../test/keys.json`);

it('Should send me some ether.', function() {
assert.notEqual(keys.me.addr, null);

var eth = 1*Math.pow(10, 18);
var sendObj = {
from: accounts[0],
value: eth,
to: keys.me.addr
}

Promise.resolve(web3.eth.sendTransaction(sendObj))
.then(function(txHash) {
assert.notEqual(txHash, null);
return web3.eth.getBalance(keys.me.addr)
})
.then(function(balance) {
assert.notEqual(balance.toNumber(), 0);
})
})

重要提示:我们实际发送1个以太,这与10^18 wei相同。我们总是使用wei值进行调用/交易。

现在,我在这里跳过一步。你需要先获得以太坊帐户,该帐户来自你生成的私人/公共密钥对。我喜欢使用eth-lightwallet在后端进行密钥管理。

为了简单起见,让我假装在不断增长的config.js中硬编码这个变量:

1
2
3
4
exports.me = {
addr: "0x29f2f6405e6a307baded0b0672745691358e3ee6",
pkey: "8c2bcfce3d9c4f215fcae9b215eb7c95831da0219ebfe0bb909eb951c3134515"
}

强制性提醒:永远不要共享你的私钥,将其上传到github,或者如果有任何资金就将其发布在Medium上。

回到测试,你可以看到以太被从accounts[0](默认情况下有一堆以太)移动到你的配置文件中的me.addr

与智能合约进行交易

现在你的帐户已经有了一些以太,现在是时候花钱了。有三种方式可以用以太:

  • 1.将其作为Value发送到另一个地址。
  • 2.调用更新合约函数去更新网络状态,这需要gas来激励矿工处理你的更新。
  • 3.调用更新合约状态,但也接受以太币来支付(仅供参考,用solidity修正)——将发送Value,你还必须支付gas费用。

我们接下来要做的是第2种。假设我们有以下函数来跟踪用户的余额:

1
2
3
4
5
6
7
function addUserBalance(uint balance)
public returns (bool) {
if (!accounts[msg.sender]) { throw; }
if (accounts[msg.sender].balance + balance < accounts[msg.sender].balance) { throw; }
accounts[msg.sender].balance += balance;
return true;
}

注意第二个if语句,这是必要的,因为加和减在solidity会导致数值溢出和下溢——小心!还要注意在函数范围内的未声明的msg对象。

当我们通过发送交易调用此函数时,我们要求更新网络的全局状态以说明以下内容:

在合约范围内,msg.sender帐户的余额已经增加了balance

我们没有权力自己更新状态,所以需要一个矿工做这件事。我们用gas向他或她支付这项服务,这意味着付出以太。

要正确调用此函数,我们需要再次使用ABI:

1
addUserBalance(uint256) --> 22526328 --> 0x225263280000000000000000000000000000000000000000000000000000000000000001

我们使用这些数据来形成一个未签名的交易:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var data = '0x225263280000000000000000000000000000000000000000000000000000000000000001';
var nonce = config.web3.eth.getTransactionCount(keys.me.addr);
var gasPrice = 20 * Math.pow(10, 9);
var gasLimit = 100000;

var txn = {
from: config.me.addr,
to: config.contract_address,
gas: `0x${gasLimit.toString(16)}`,
gasPrice: `0x${gasPrice.toString(16)}`,
data: data,
nonce: `0x${nonce.toString(16)}`,
value: '0x0'
}

如上所述,需要gas进行交易(即更新状态)。gas*gasPrice是矿工执行交易可能花费的金额。如果操作成本高于你提供的成本,则交易将不会更新状态,并且矿工将保留你的所有gas费用。如果使用的gas少于所用gas,则退还余额。

如果我们将此对象提交给网络,它将失败,因为没有证据表明我实际上正在授权此交易。谁知道,有些陌生人可能会将我的余额更新为10亿(虽然目前还不清楚为什么有人会这样做)。

无论如何,我需要做的是用我的私钥签署交易。还记得你在配置文件中内容,我告诉过你不要与任何人分享吗?这样做:

1
2
3
4
5
6
var Tx = require('ethereumjs-tx');

var privateKey = Buffer.from(config.me.pkey, 'hex')
var tx = new Tx(txn);
tx.sign(privateKey);
var serializedTx = tx.serialize();

在这里,使用我最喜欢的库之一,根据你的私钥签署一个交易对象。这应该返回如下内容:

1
0xf8aa808504a817c800830f424094a0f68379088f9aee95ba5c9d178693b874c4cd6880b844a9059cbb000000000000000000000000053b2188b0b100e68299708864e2ccecb62cdf0d000000000000000000000000000000000000000000000000000000746a5288001ca01f683f083c2d7c741a1218efc0144adc1749125a9ca53134b06353a8e4ef72afa07c50fb59647ff8b8895b75795b0f51de745fa5987b985f7d1025eb346755bca0

最后,我们可以通过web3将其提交给区块链。它将返回一个交易哈希,它只是提供的交易的哈希值(这非常重要的是,不能证明交易是成功的!)

1
var txHash = config.web3.eth.sendRawTransaction(raw_txn);

看起来像这样:

1
0xac8914ecb06b333a9e655a85a0cd0cccddb8ac627098e7c40877d27a130a7293

现在的这一步,严格来说是可选的,但对于验证你的交易是否已被接受和处理非常重要:获取你的交易收据。

1
var txReceipt = config.web3.eth.getTransactionReceipt(txHash);

如果返回null,则你的交易未被提取(可能是你使用错误的私钥进行了签名?)。如果它不为null,可能仍有各种其他失败情况,来看看你的交易。

好的,有一条线索——如果你的gasUsed等于发送的gas,则意味着你的函数调用失败了。这意味着1.你没有提供足够的gas或者同时2.你的合约遇到了throw

总结

我知道,这是很多内容。

如果你感觉太多了,我建议你慢慢来,并使用这篇文章作为参考。你可能需要花费大量时间阅读文档。

也就是说,我上面描述的是80%的内容。一旦你掌握了这些东西,我个人会认为你是一个有能力的以太坊开发者。

如果你有兴趣,可以开始修修补补!这些工具变得越来越好,并且从未如此容易地进入。欢迎上岸。

更新:我已经为你创建了一个repo来展示本文中介绍的大部分内容。

如果希望快速进行以太坊开发,那请看我们精心打造的教程: 以太坊入门教程,主要介绍智能合约与dapp应用开发,适合入门。

其他区块链教程如下:

  • 以太坊开发进阶教程,主要是介绍使用node.js、mongodb、区块链、ipfs实现去中心化电商DApp实战,适合进阶。
  • java以太坊开发教程,主要是针对java和android程序员进行区块链以太坊开发的web3j详解。
  • python以太坊,主要是针对python工程师使用web3.py进行区块链以太坊开发的详解。
  • php以太坊,主要是介绍使用php进行智能合约开发交互,进行账号创建、交易、转账、代币开发以及过滤器和事件等内容。
  • C#以太坊,主要讲解如何使用C#开发基于.Net的以太坊应用,包括账户管理、状态与交易、智能合约开发与交互、过滤器和事件等。
  • php比特币开发教程,本课程面向初学者,内容即涵盖比特币的核心概念,例如区块链存储、去中心化共识机制、密钥与脚本、交易与UTXO等,同时也详细讲解如何在Php代码中集成比特币支持功能,例如创建地址、管理钱包、构造裸交易等,是Php工程师不可多得的比特币开发学习课程。
  • EOS入门教程,本课程帮助你快速入门EOS区块链去中心化应用的开发,内容涵盖EOS工具链、账户与钱包、发行代币、智能合约开发与部署、使用代码与智能合约交互等核心知识点,最后综合运用各知识点完成一个便签DApp的开发。

汇智网原创翻译,转载请标明出处。这里是原文