Zhuang's Diary

言之有物,持之以恒

目录

  1. 迈出第一步

  2. 与合约进行交互

  3. 现实世界中的构架和工具

3. 现实世界中的构架和工具

或许你已经注意到了,我们所做的工作大部分都很依赖人力。尽管这是一个新兴产业,但是一些工具将会降低开发难度。下面将介绍其中一部分。

3.1. 通过Truffle部署

目前为止,我们与合约进行交互的唯一方式是通过节点控制台将它们手动部署到一个 testrpc 节点上,再使用 Web3 加载它们。现在,我要向你介绍 Truffle 。它是一个以太坊开发构架,具有调试、部署和测试智能合约等功能。

我们要做的第一件事是通过Truffle部署合约。让我们为此创建一个新的目录,运行以下指令来安装Truffle,启动我们的项目:

1
2
3
4
$ mkdir truffle-experiment
$ cd truffle-experiment/
$ npm install truffle@4.0.4
$ npx truffle init

会看见有一些文件夹和文件被创建出来。目录结构如下所示:

1
2
3
4
5
6
7
8
truffle-experiment/
├── contracts/
│ └── Migrations.sol
├── migrations/
│ └── 1_initial_migration.js
├── test/
├── truffle.js
└── truffle-config.js

智能合约应当放在 contracts 文件夹里。migrations 文件夹中的 javascript 文件将帮助我们把合约部署在网络上。你或许也看见了第一个文件夹中的 Migrations 合约,在这个文件夹中,迁移历史将会存储在区块链上。测试文件夹最初是空的,专门用来保存我们的测试文件。最后,你会看见 truffle.js 和 truffle-config.js 这两个文件。我们先略过它们不谈,你也可以查看文档了解详情。

现在,让我们抛开这些无聊的东西,聚焦于一些有趣的地方。要举例说明我们是如何通过Truffle 部署合约的,可以采用与该指南上一篇文章中相同的代币合约的例子。请复制该代码并将它粘贴至合约文件夹里的 MyToken.sol 文件中。之后,创建一个名为 2_deploy_my_token.js 的新迁移文件,并将下列几行代码复制进去:

1
2
3
4
const MyToken = artifacts.require('./MyToken.sol')
module.exports = function(deployer) {
deployer.deploy(MyToken)
}

如你所见,迁移会将我们的代币部署于网络中。这次,我们无需运行 testrpc 节点,因为 Truffle 已经自带了一个模拟节点,可用于开发和测试之途。我们只需要打开一个终端运行 npx truffle develop 并使用 truffle migrate 运行迁移。npx介绍。之后,你会看到下列输出值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
truffle(develop)> truffle migrate

Using network ‘develop’.

Running migration: 1_initial_migration.js
Deploying Migrations…
… 0xf5776c9f32a9b5b7600d88a6a24b0ef433f559c31aaeb5eaf6e2fc5e2f7fa669
Migrations: 0x8cdaf0cd259887258bc13a92c0a6da92698644c0
Saving successful migration to network…
… 0xd7bc86d31bee32fa3988f1c1eabce403a1b5d570340a3a9cdba53a472ee8c956
Saving artifacts…

Running migration: 2_deploy_my_token.js
Deploying MyToken…
… 0xc74019c2fe3b3ef1d4e2033c2e4b9fa13611f3150f8b6b37334a8e29e24b056c
MyToken: 0x345ca3e014aaf5dca488057592ee47305d9b3e10
Saving successful migration to network…
… 0xf36163615f41ef7ed8f4a8f192149a0bf633fe1a2398ce001bf44c43dc7bdda0
Saving artifacts…

我们只关注 MyToken: 0x345ca3e014aaf5dca488057592ee47305d9b3e10 这行代码,它是我们已部署的代币合约的地址。在默认情况下,Truffle 为模拟节点预置了10个拥有虚拟ETH的地址,就像使用 testrpc 时那样。我们可以通过 web3 以太币账户访问该地址列表。此外,Truffle 使用列表中的第一个地址(索引为0的那个)部署这些合约,这意味着它将成为 MyToken 的所有者。

鉴于 Web3 可用于Truffle控制台内,你可以运行下列指令来检查所有者的余额:

1
2
3
truffle(develop)> owner = web3.eth.accounts[0]
truffle(develop)> instance = MyToken.at('[DEPLOYED_ADDRESS]')
truffle(develop)> instance.balanceOf(owner)

注意:别忘了将 [DEPLOYED_ADDRESS] 替换成由Truffle赋予的已部署合约的地址,例如: 0x345ca3e014aaf5dca488057592ee47305d9b3e10

我们也可以先将一些代币发送至另一个地址,再检查更新过后的余额:

1
2
3
4
5
6
7
8
// send tokens
amount = 10
recipient = web3.eth.accounts[1]
txHash = instance.sendTokens(recipient, amount, { from: owner })

// check balances
instance.balanceOf(owner)
instance.balanceOf(recipient)

现在已经可以看到接受者的账户里有10枚代币了!我们可以通过下列代码搜索交易信息:

1
web3.eth.getTransaction(txHash)

3.2. 测试智能合约
接下来,Truffle更有趣又有用的一点是,我们可以测试我们的合约。这一构架能让你通过两种不同方式编写测试代码:Javascript 和 Solidity。在这篇文章中,我们将学习一些关于JS测试这个最常用选项的基本知识。

Truffle使用后台的 Mocha 作为测试构架,并使用 Chai 来执行断言。如果你不熟悉这两个库也没有关系,它们真的都很简单,执行的语法也与其它测试架构相似。

准备好了,让我们开始介绍第一则测试实例吧。我们需要在测试文件夹里创建一个 MyToken.js 文件。一旦你创建完成之后,请将下方代码粘贴进去。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const MyToken = artifacts.require('MyToken')

contract('MyToken', accounts => {

it('has a total supply and a creator', async function () {
const owner = accounts[0]
const myToken = await MyToken.new({ from: owner })

const creator = await myToken.creator()
const totalSupply = await myToken.totalSupply()

assert(creator *= owner)
assert(totalSupply.eq(10000))
})
})

要运行 Truffle 测试的话,你只需使用指令 npx truffle test。再次提醒:此处无需在后台运行 rpc 测试节点,因为 Truffle 会帮你运行好。

你可能已经注意到了,这是我们第二次在代码中使用 artifacts.require() 。第一次是在编写 MyToken migration 代码的时候。Artifact是分别编译每个合约的结果。这些 Artifacts 将被置于与你的项目根相关的 build/contracts/ 目录之内。我们通过 artifacts.require() 告诉 Truffle 想与哪个合约进行交互。只提供合约名并实现抽象化使用。你也可以阅读这篇文章来详细了解 Truffle artifacts。

剩下的最后一个重点是 contract() 函数,它确实与Mocha的 describe() 函数相似。这就是 Truffle 保障clean-room environment的方式。Truffle 将重新把合约部署给以太坊客户端,并在每次被调用之时提供一列可用账户。不过,我们不建议将已部署的合约实例用于测试。让每个测试管理它们自己的实例会更好。

既然我们了解了有关 Truttle 测试的一些基本知识,让我们再介绍一个有趣的场景吧。我们将测试账户之间的代币转让:

1
2
3
4
5
6
7
8
9
10
11
it('allows token transfers', async function () {
const owner = accounts[0]
const recipient = accounts[1]
const myToken = await MyToken.new({ from: owner })

await myToken.sendTokens(recipient, 10, { from: owner })
const ownerBalance = await myToken.balanceOf(owner)
assert(ownerBalance.eq(9990))
const recipientBalance = await myToken.balanceOf(recipient)
assert(recipientBalance.eq(10))
})

其它的测试实例见此处,从中你还可以看到我是如何使用 Truffle 来完成这个 mini DApp 的。你会看到我设置了同样的特性,就像我们在上一篇文章中对 app 所做的那样。 唯一改变之处是我们正在使用 Truffle 启动测试节点、部署合约并添加测试,从而确保我们的合约能达到我们预期的效果。

3.3.OpenZeppelin

如果你阅读到此处,想必你已经听说过 OpenZeppelin 了吧。如果你还没有的话,你只需要知道它是有助于你构建智能合约的最常用构架。它是一个开源构架,提供可重复使用的智能合约构建分布式应用、协议和组织,从而降低使用经测试和社区审查的标准代码所带来的安全隐患。

鉴于代币合约的数量之大,以太坊社区于两年前创建了一个名为 ERC20 的代币标准。其理念是允许 DApps 和钱包以共同的方式在多种界面和 DApps 之间处理代币。

也就是说,最常用的 OpenZeppelin 合约就是 ERC20 用例。这就是我们第一步要对 MyToken 合约做的事:使之与 ERC20 兼容。让我们先安装 OpenZeppelin 构架,这需要运行:

1
$ npm install zeppelin-solidity

现在,看看我们用一些OpenZeppelin合约构建的新用例。

1
2
3
4
5
6
7
8
9
10
11
import 'zeppelin-solidity/contracts/token/BasicToken.sol';
import 'zeppelin-solidity/contracts/ownership/Ownable.sol';

contract MyToken is BasicToken, Ownable {

uint256 public constant INITIAL_SUPPLY = 10000;
function MyToken() {
totalSupply = INITIAL_SUPPLY;
balances[msg.sender] = INITIAL_SUPPLY;
}
}

如你所见,我们已经去除了很多核心功能。好吧,我们还没有去除这些功能,只是向OpenZeppelin 合约下达了该指令。这的确很有用,因为我们是在重复使用经过审核的安全代码,这意味着我们已经减少了合约的受攻击可能性。

此外,我们正将代币合约从 OwnableBasicToken 这两个 OpenZeppelin 合约扩展而来。是的,Solidity 支持多重继承,而且对你来说,知道顺序的重要性真的很重要。不过,这超出了本文的介绍范围,不过你可以从此处了解更多详情。

正如上文所说,我们正将MyTokenOwnable中扩展而来。让我们看一看这个合约:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
contract Ownable {
address public owner;

event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

function Ownable() public {
owner = msg.sender;
}

modifier onlyOwner() {
require(msg.sender * owner);
_;
}

function transferOwnership(address newOwner) public onlyOwner {
require(newOwner != address(0));
OwnershipTransferred(owner, newOwner);
owner = newOwner;
}
}

Ownable提供三大主要功能:

  • 它有一个特殊地址供我们调用其“owner”;
  • 它允许我们转让合约的所有权;
  • 它提供了有用的onlyOwner修改器,确保只有所有者才能调用某个函数。

很有用对吧?另一方面,我们也在扩展 BasicTokencontract(基础代币合约)。让我们了解下它的功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import '../math/SafeMath.sol';

contract ERC20Basic {
uint256 public totalSupply;
function balanceOf(address who) public view returns (uint256);
function transfer(address to, uint256 value) public returns (bool);
event Transfer(address indexed from, address indexed to, uint256 value);
}

contract BasicToken is ERC20Basic {
using SafeMath for uint256;

mapping(address => uint256) balances;

function transfer(address _to, uint256 _value) public returns (bool) {
require(_to != address(0));
require(_value <= balances[msg.sender]);

balances[msg.sender] = balances[msg.sender].sub(_value);
balances[_to] = balances[_to].add(_value);
Transfer(msg.sender, _to, _value);
return true;
}

function balanceOf(address _owner) public view returns (uint256 balance) {
return balances[_owner];
}
}

相信你更熟悉这个代码。这基本上是我们过去在 MyToken 合约中所做的事。这里存在一些细微的差异,因为我们没有遵循原始版本的ERC20标准。我们此处所说的 sendTokens 只是转让,除了触发 transfer 事件之外执行的几乎是同样的行为。

另一个重要的事是使用SafeMath编写uint256代码。SafeMath是 OpenZeppelin 推荐使用的库,用来进行带有安全检查的数学运算。这是另一个常用合约,因为它能保证数学运算不会溢出。

OpenZeppelin 本身就是一个完整的领域,请花点时间深入分析并学习它。你可以先从读取并密切关注已经审查的代码库的安全细节开始。

原文链接: https://blog.zeppelin.solutions/a-gentle-introduction-to-ethereum-programming-part-3-abdd9644d0c2

作者: Facu Spagnuolo

目录

  1. 迈出第一步

  2. 与合约进行交互

  3. 现实世界中的构架和工具

2.尝试与合约进行交互

2.1以太坊智能合约介绍

阅读到这里,对以太坊已经有了基础的了解,已经与以太坊节点进行了交互,并在账户之间发送了一些交易等等。但除此之外,让以太坊如此惊艳的还有:智能合约。

正如我在简介中所说,智能合约是一个运行在以太坊虚拟机(EVM)上的程序。你可以创建智能合约来做任何你想做的事情,但是在今天,大多数智能合约都被用于像I-C-Os或者代币销售那样的众筹工具。接下来请允许我解释这些概念。

从众筹开始说起,相信这是一个你非常熟悉的概念。举办众筹活动的项目,其目的是为了开展项目而筹集资金。你可以几乎零成本的发行一种与你的项目相关的数字资产,并将其出售给任何人。这就是我们所说的初始代币发行(I-C-O)。

要想实现一个具有智能合约的I-C-O,你只需要实现使你的数字资产可交易并且有价值的逻辑。这听起来不错,这些就是以太坊代币,是以太坊生态系统中的一种数字资产。

接下来让我们试着通过一个例子来分析这些例子。

假设你的健康食品公司想要推出一种新的品牌。你决定进行一次I-C-O来筹集20,000个ETH。你用10个代币换取你收集到的每个ETH,并承诺,贡献者可以在你的商店使用这些代币购买食物。为此,你需要开发一个代币智能合约为每个贡献者存储他们相应的代币数量。

现在,假设你筹集到了这笔钱,开展了你的项目并开了你的第一家店。然后,你决定每份沙拉以1个代币的价格出售。一周以后,你的客人越来越多,但是沙拉的供应却是有限的,客人意识到这一点并开始把你的代币当作资产交易,从而提高了其市场价值。

这一过程在现实生活中实际存在,因为以太坊几乎允许任何人创建他们自己的可交易的数字资产。

2.2你的第一个智能合约

让我们看看如何建立一种基本的以太坊代币吧。我将通过这个例子介绍一些Solidity的基础知识。

请记住这个例子仅用于学习,不能使用它从事商业活动。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
pragma solidity ^0.4.0;
contract MyToken {
address public creator;
uint256 public totalSupply;
mapping (address => uint256) public balances;

function MyToken() public {
creator = msg.sender;
totalSupply = 10000;
balances[creator] = totalSupply;
}

function balanceOf(address owner) public constant returns(uint256){
return balances[owner];
}

function sendTokens(address receiver, uint256 amount) public returns(bool){
address owner = msg.sender;

require(amount > 0);
require(balances[owner] >= amount);

balances[owner] -= amount;
balances[receiver] += amount;
return true;
}
}

让我们一步步来。pragma关键词显示了你使用的源码的Solidity版本。然后,用合约的名字进行合约定义初始化,在这个例子中,名字就是MyToken

接下来,你可以看到三个变量:

creator 是一个地址变量,用于存储该合约的拥有者。

TotalSupply 是一个256位的无符号整数,用于存储愿意与投资者共享的代币总数。

balance 是从地址到无符号整数的映射,其记录着每个投资者的余额。

之后,你将看到构造函数。正如你所见,这是一个与合约同名的函数,同时每当该合约的一个新的实例被部署在网络中时它将被调用一次。这就是合约的所有者被存储的地方。由于所有的函数调用都是一笔交易,因此可以通过交易的发送者即 msg.sender 获得合约的所有者信息。这个合约定义了总共10,000个代币。

下一个函数十分简单:balanceOf 用于展示参数指定的地址的余额。也许你想知道 constant 关键字是什么意思。这是因为Solidity的函数分为两种,一种是常量函数,一种是非常量函数。

非常量函数执行后状态会发生变化。另一方面,常量函数只读一次,这意味着它不执行任何状态转换,而是只读取数据。实际上,共有两种类型的常量函数:

view 声明函数承诺不修改状态(常量的别名)

pure 声明函数承诺不读取或者修改状态

sendTokens 函数允许我们在地址间交换代币。这是一个非常量函数或者说是一个交易函数,因为使用这个函数将改变余额。该函数的参数是接收者的地址以及欲转移的代币数量,函数的返回值是一个表示交易是否成功执行的布尔(Boolean)类型数据。你可以跳过第一行,它只是把函数的发送者保存在owner变量中。

接下来,你将看到两个先决条件:

1
2
3
4
...
require(amount > 0);
require(balances[owner] >= amount);
...

Require 是你可以用来检查条件或者进行验证的方法之一。它将评估一个条件并在条件不满足时恢复原状。因此,在这个例子中,要求被转移的代币 amount 大于零,同时要保证发送者有足够的余额来支付该笔代币转移。

最后,你要从 owner 的余额中减去交易转移的代币数量,并将其添加到 receiver 的余额中:

1
2
3
4
5
...
balances[owner] -= amount;
balances[receiver] += amount;
return true;
}

2.3 部署智能合约

现在让我们开始试着玩一下智能合约吧!首先需要在网络上部署智能合约。为了实现部署,需要使用名为 solc的Solidity编译器用于编译node.js。你可以通过以下指令安装它:

1
npm install -g solc

创建一个叫做 MyToken.sol 的文件并把合约代码粘贴到文件中,并在放置该文件的路径下打开一个控制终端。首先,通过运行以下指令编译文件:

1
solcjs MyToken.sol --bin

执行完该指令,编译器将创建一个 MyToken_sol_MyToken.bin 文件作为输出。你可以看到该文件只包含字节码。接着,你将需要使用solc来构建ABI(应用二进制接口),它是合约的接口或者说模板,通过它你可以获得可用的方法。这就是与Web3的联系点。你只需要运行:

1
solcjs MyToken.sol --abi

接着,你将看到一个叫做 MyTolen_sol_MyToken.abi 的新文件,其中包含的JSON内容定义了你的合约的接口。

最后,你只需要使用在后台运行的 testrpc ,通过node.js控制台部署你的合约。你完成了这些工作后,我们就开始初始化 web3 吧:

1
2
3
4
//instance web3
Web3 = require('web3')
provider = new Web3.providers.HttpProvider("http://localhost:8545")
web3 = new Web3(provider)

Web3 为你提供了解析合约ABI的可能性,并提供了一个JavaScriot API 与之交互。接着,你只需使用字节码就可以将该合约的一个新实例部署到 testrpc 上。请按照下面的命令输入:

1
2
3
4
5
6
7
8
9
10
// load files
myTokenABIFile = fs.readFileSync('MyToken_sol_MyToken.abi')
myTokenABI = JSON.parse(myTokenABIFile.toString())
myTokenBINFile = fs.readFileSync('MyToken_sol_MyToken.bin')
myTokenByteCode = myTokenBINFile.toString()
//deploy
account = web3.eth.accounts[0]
MyTokenContract = web3.eth.contract(myTokenABI)
contractData = { data: myTokenByteCode, from: account, gas: 999999 }
deployedContract = MyTokenContract.new(contractData)

最后,你可以通过调用 deployedContract.address 检查新部署的合约地址。请保存该地址,因为将需要使用这个地址与你的合约进行交互 :) 。

2.4 Web3与智能合约

让我们从搜索你的 testrpc 账户余额开始。为此,你首先需要访问已部署的合约实例:

1
2
3
4
5
6
contractAddress = deployedContract.address
instance = MyTokenContract.at(contractAddress)
web3.eth.accounts.forEach(address => {
tokens = instance.balanceOf.call(address)
console.log(address + ": " + tokens)
})

正如我们所预期的,第一个账户拥有所有的代币。太棒了!接下来,让我们将一部分代币转移到其他账户:

1
2
3
4
5
6
7
8
9
10
// send tokens
amount = 10
from = web3.eth.accounts[0]
to = web3.eth.accounts[1]
transactionHash = instance.sendTokens(to, amount, { from: from })
// checkout balances again
web3.eth.accounts.forEach(address => {
tokens = instance.balanceOf.call(address)
console.log(address + ": " + tokens)
})

你应该可以看到,现在第二个地址有了10个代币!你也可以搜索交易信息,正如你在本指南第一部分所做的那样:

1
web3.eth.getTransaction(transactionHash)

我还为这个mini DApp设计了一个简单的UI,你可以在这里看到它。你将看到一个包含了我们合约ABI的 MyToken.json 文件。我只是把solidity编译器生成的ABI的内容粘贴到这里面。你还可以看到一个与前一个应用相似的 app.js 文件,但是这个app.js文件还包含了我刚刚向你展示的用于发送代币以及展示账户代币余额详细信息的逻辑。


开发的用来测试 MyToken 转账的 DApp 的 UI

你也可以下载这个 App 并开始与它游戏。你将被要求提供你开发的合约案例的地址。

注意:为了减轻术语负担,这篇文章中描述的代币并非ERC20标准的代币。如果你不知道什么是ERC20协议代币,我们会在下一篇文章中解释它。

原文链接: https://blog.zeppelin.solutions/a-gentle-introduction-to-ethereum-programming-part-2-7bbf15e1a953

作者: Facu Spagnuolo

目录

  1. 迈出第一步

  2. 与合约进行交互

  3. 现实世界中的构架和工具

第一步

假设你已经有一些计算机编程方面的基础知识,并知道区块链数据结构是怎样的。如果不具备,请先了解ethfans的内容再回来看这篇博客。

以太坊

以太坊是一个开源的,分布式的,基于区块链技术的公共平台,无需审查及第三方干扰即可运行应用程序。

智能合约

智能合约仅仅是电脑程序。我们基于智能合约创建以太坊应用。虽然这个概念现在随着以太坊大红大紫,但是它实际上是由Nick Szabo于1996年提出。

EVM 以太坊虚拟机

EVM 是以太坊智能合约的沙盒运行时,是一个完全独立的环境。这意味着每个在EVM中运行的智能合约无法连接网络,无法调用文件系统和其他进程。

Gas

鉴于以太坊是一个分布式平台,所以必须有一种方式来限定智能合约的可用资源,否则整个网络的算力瘫痪都可能会被耗尽。Gas 通过为EVM中的每个指令确定执行成本来解决问题。重要的是,网络中的每笔交易都有一个“Gas预算”。如果预算的Gas用完了,交易将以失败告终,但是这笔交易仍然会被加载到区块链中。

Ether(ETH)

这是以太坊的加密数字货币。Gas/Ether的兑换价格用来衡量一个操作将花费多少ETH。执行交易所需支付的费用通过Gas用量与Gas价格相乘得出(计算所得的费用将使用ETH支付)。你可以将交易的Gas价格设置为任何值。但是,如果你设置的Gas价格太低的话,没有人会执行你的代码。

账户

每个账户都是由一个地址标识的。同一地址空间由两种账户共享。一种是由公私钥对控制的外部账户,该账户通常由用户持有,用来存储ETH。另一种是合约账户,合约账户下存储着智能合约的代码。重要的是,只有外部账户才能启动交易。

交易

交易是从一个账户发送到另一个账户的消息。消息内容可以是转移ETH。如果目的账户是合约账户,其智能合约代码将被执行。有关智能合约代码执行的每一笔交易将在网络中的所有节点上执行。所有的智能合约代码运行以及交易执行都将被记录在以太坊区块链上。

Solidity

Solidity 是一种面向合约的高级语言,其语法与JavaScript相似。Solidity是静态类型的,支持继承,库以及复杂的用户定义类型。编译后就是节点运行的EVM程序集。

WEB3
先通过简单的将ETH从一个账户发送到另一个账户开始与以太坊互动。因为刚开始,我们可能会弄的一团糟,所以不想用真正的ETH来做这个实验,经过搜索,发现了testrpc,这是一个用于测试和开发的以太坊客户端,是由npm管理的。让我们安装这个客户端并开始使用它:

1
2
npm install -g ethereumjs-testrpc
testrpc

你会注意到testrpc已经产生了10个地址,这些地址都拥有虚拟的ETH可供测试无需担心。有一点是testrpc的状态并不稳定,每次关闭后,节点和账户状态将被清除。

Web3.js 是一个实现了以太坊 JSON RPC 的JavaScript库。我们将使用Web3.js与以太坊节点(在这个例子中是testrpc)进行交互。安装只需运行:

1
npm install -g web3@0.20.1

顺便提一句,在这个例子中,安装 Web3 0.20.x 版本而不是公测1.0.0版本。首先,需要将本地testrpc测试节点连接到Web3.js,为了做到这一点,我们要求Web3 使用localhost provider。让我们打开一个节点控制台并输入以下命令:

1
2
3
Web3 = require('web3')
provider = new Web3.providers.HttpProvider("http://localhost:8545")
web3 = new Web3(provider)

我们正在使用默认的testrpc端口(8545),如果你设置了另一个端口,记得更改provider的URL。当你获得web3 实例后,首先运行以下命令以获得你的以太坊节点中的账户列表及其各自余额:

1
2
3
4
web3.eth.accounts.forEach(account => {
balance = web3.eth.getBalance(account);
console.log(balance);
})

你可能注意到了,输出不全是数字列表,这是因为Web3使用大数对象来处理数字值,而JavaScript无法正确处理大数类型。可以通过这个链接了解更多。

同时,这些余额不是用ETH表示的,实际上它们的单位都是基本单位wei。1ETH是10¹⁸ wei。

回到刚刚的话题,让我们试着在两个账户之间发送ETH。指需输入web3.eth.accounts并选择其中的两个,你可以使用sendTransaction方法:

1
2
3
4
from = web3.eth.accounts[0]
to = web3.eth.accounts[1]
transaction = { from: from, to: to, value: 100000 }
transactionHash = web3.eth.sendTransaction(transaction)

命令输出的是交易哈希,你也可以通过以下命令获得交易信息:

1
web3.eth.getTransaction(transactionHash)

你可能还想检查你使用的账户的余额是否已经更改。你可以使用下面的命令来验证:

1
2
3
4
web3.eth.accounts.forEach(account => {
balance = web3.eth.getBalance(account);
console.log(balance);
})

接下来,我使用HTML和jQuery以及一点Bootstrap搭建了一个简单的UI使得它更美观一点。你可以在repo中看看它。这就是所说的DApp即分布式应用。也就是说,一个部分后端代码运行在分布式点对点网络上的应用程序;在这个例子中,分布式点对点网络是指以太坊网络。

上图是用以测试ETH交易的DApp的UI

你将找到UI的index.html文件以及与以太坊交互的app.js文件,这基本上就是我们前面所描述的用一些jQuery回调来填充UI。可以复制repo并运行起来试一下。

谢谢你阅读这篇博客,如果你有任何问题,意见或者建议欢迎告诉我!如果你喜欢这篇博客,请继续关注本指南的第二部分,我将重点介绍智能合约!

谢谢Manuel Araoz

原文链接: https://blog.zeppelin.solutions/a-gentle-introduction-to-ethereum-programming-part-1-783cc7796094

作者: Facu Spagnuolo

翻译:https://www.bankofcanada.ca/wp-content/uploads/2017/05/fsr-june-2017-chapman.pdf

  • 分布式账本技术(DLT)通常被称为比特币的基础。DLT提供了一种全新方式进行(和跟踪)金融交易。研究人员正在调查其在金融体系的实用性。
  • Jasper项目是基于DLT大额支付系统的技术验证。该验证对DLT作为金融系统基础建设技术的成熟度提供了重要见解。
  • 对于大额支付系统等关键的金融市场基础设施(FMI),目前版本的DLT还无法提供与集中式系统同样的整体能力。然而,与DLT的初始加密货币应用相比,最新版本的DLT已经取得了巨大的进步。
  • 基于DLT的大额支付系统的优势是其可以接入更大的DLT生态市场(可能包括跨境交易)。

什么是分布式账本系统?

比特币在2009年引入分布式账本技术,其后开始流行。比特币是数字货币的代表。交易都记录在账本上,对所有人都是可见的,由分布式的计算机(矿工)维护。这些计算机是系统上的节点,这些节点在创建新交易时更新账本。该账本使用一系列密码学技术记录交易,若干笔交易被封装成块,交易块与块连接在一起,这种账本被称为区块链。
区块链是一个突破,它展示了一种方法来维护各方之间的账本,(i)没有人监督该系统;(ii)交易可以可靠地更新并且记录到比特币系统(无需担心其他成员的诚实度)。

通过让矿工解决数学难题来争取获得验证交易块的权利,从而实现分布式账本的“可靠性”更新。第一个完成一个新的区块广播的矿工为其他矿工提供区块,并以该区块创建的新比特币(矿山)作为回报。虽然挖矿很难,但很容易验证。一旦其他节点看到并验证了这个新矿山,新的区块就被添加到链的后面,块中的交易则被认为是正确的,矿工们开始挖掘其他的新交易。节点对新区块达成一致的方式称为共识机制,比特币的共识机制叫做工作证明(PoW)。

虽然比特币系统已具有相当的弹性,但是他有以下几个不足:

(i)所有交易对每个人都是可见的,但是这是违反银行法的,并使某些参与方利益受损;

(ii)在时间和能源方面,挖矿的代价非常高,在受信任的环境中通常不需要它的好处;

(iii)该系统对任何参加者都是匿名开放的。

为了解决上述问题,人们一直在开发比特币的替代品。新的分布式账本系统只允许受限制的可信对手访问。在一些系统中,共识机制被其他方法所取代。在Jasper阶段2中使用的Corda平台,是通过每个参与方都信任的公证节点完成的,并替换PoW。并且,放弃了区块链的链式结构,并将其替换为分布式账本结构,每个节点只能访问必要的数据,减少系统的透明度,为参与者提供更多隐私**。

Jasper流动资金模型(LSM)利用一个定时多边支付结算的队列,如果银行有非紧急付款,将付款存入队列中。在银行提交付款通知后,提交的付款将与其他排队付款一起等待。定时到达时,该队列暂时被锁定,而算法结合所有提交的付款,确定每个银行的净付款并评其流动性。

支付队列本质上是集中的。关键的问题是如何在DLT系统中实现它,而不是使用传统的集中账本系统。这些技术问题带来了极大的复杂性。

流动资金模型

Jasper项目的创新解决方案是在Corda平台上加入“吸入/呼出”程序。在”匹配”周期开始之前,银行向队列提交付款。这些付款不会立即添加到账本中,而是付款指令加载到队列中,直到”匹配”周期开始。此时,会发生一系列事件。首先,在“吸入”阶段,向参与匹配周期的所有银行发送通知,要求他们向加拿大银行发送数字存托凭证(DDR)。然后验证每一笔付款并添加到账本中。然后,在“呼出”阶段,”匹配”算法确定付款的全部子集,以净额为基础,在具备可用资金的情况下进行清算。加拿大银行将DDR付款返还给所有参与银行,金额等于他们所提供的金额,加上或减去”匹配”算法完成后的金额。

举例说明,假设有两家银行A和B,在队列中支付对方100美元和90美元。此外,作为吸入阶段的一部分,每家银行都将15美元送到队列中。扣除两次付款后,该算法将向A银行收取10美元,并向B银行支付10美元。鉴于他们在吸入阶段的贡献,在呼出阶段A银行为支付5美元,对B银行为支付25美元。

然后验证这些交易并将其添加到账本中。与算法不匹配的付款保留在队列中。此时开始一个新的匹配周期。在下一个匹配周期结束之前,银行可以自由输入或移除队列中的付款。重复该过程。

效率和金融稳定性风险

信用和流动性风险

Jasper平台没有设计信用风险,因为所有的支付都是对中央银行存款的索赔,这是无风险的资产。参与者通过LVTS将现金转入加拿大银行,然后,大额转账系统(LVTS创建DDR,DDR可以在分布式账本平台上交换。总的来说,本设计证明信用上是可兼容的。

Jasper合并了一个模拟现有实时总额结算(RTGS)系统功能的流动资金模型(LSM),以缓解流动性风险,参与者将对不够充足的DDR进行支付。Jasper流动资金模型(LSM)的性能目前正在使用模拟数据进行测试。预测这些模拟结果还为时尚早,但我们可以报告,迄今为止,还没有看到证据表明在分布式账本上实施LSM会改变其相对于中央系统的使用或性能。基于DLT技术的LSM很可能可以做出与现有LSM类似的流动性资金系统。

结算风险

结算定义为资产不可撤销和无条件的转移。

与Jasper方案相关的两个方面:分布式账本更新过程的确定性,和法律确权。

为了确保法律的正当性,Jasper项目的结构如下:

DDR的转让是相当于中央银行存款基础债权的完全(不可撤销)转移。这个设计与DDR的发行有关,因此独立于Jasper平台。

另一方面,为确保方案最终完成,需要解决DLT基础技术相关的问题。在以太坊中,使用PoW共识机制来验证付款。但是PoW是不确定的,付款可能出现失败。在Corda平台中,理论上讲,可信公证人的角色将消除这种不确定性,交易一旦完成就无法撤销。但是,系统还没有经过压力测试,因此一些风险可能仍然与方案有关。

运营风险

总体评估表明,与集中式平台相比,分布式账本如果没有经过精心设计,可能会降低运营弹性。基于Corda,Jasper阶段2在满足金融市场基础设施(PFMI)方面比当前集中式系统更加昂贵。在Jasper阶段2中,参与者需要投资高可用节点以减少停机的可能性。

另一个关键方面是可扩展性。目前,LVTS每天处理32,000笔交易,峰值吞吐量约为每秒10笔交易。在DLT中,”分配”算法消耗了一定的时间。在像以太坊这样的PoW平台上,扩展能力有限。在第一阶段,大概是每秒14笔交易,以太坊是为公有链设计的,速度限制节点之间的信息流。虽然这个速度足以处理当前每日的LVTS,但它限制了未来的高峰量。相比之下,可扩展性不会成为Corda平台的一个限制,因为Corda没有基于固定时间的共识方法,只需要相关方的节点和公证人验证事务。

透明性和隐私

大额支付系统需要参与者保持交易私密性,防止其他参与者窥视这些信息。参与者的客户也需要保持隐私。PoW系统是不适合的,因为所有交易都是公开的。

相比之下,基于公证的DLT系统(如Corda)则允许增加隐私,因为受信任的第三方(例如加拿大银行)有助于验证所有交易。Corda系统缺乏透明性意味着系统中没有任何节点(公证人可能除外)拥有全部信息。如果一个或多个节点的信息被破坏,则可能无法重建整个网络,因为即使公证人也没有全量的账本副本。这就需要对单个节点进行备份。这提出了在交易保持私密的限制下,DLT的运营恢复能力是否有可能的问题。

结论

Jasper项目让我们更好地理解了DLT大额支付系统运营者,参与者和中央银行的角色和责任。在DLT框架中,运营者的角色可能更接近规则制定者或标准制定者的角色,而不是传统的IT基础架构运营商。

此外,Jasper项目促使大额支付系统相关者共同开发平台。私人和公共部门合作,从项目中了解了DLT技术的很多内容。他们发现这降低了相互承认所涉过程的复杂性。

纯粹独立的DLT大额支付系统不太可能与集中式的收益相匹配。因为上面讨论的LSM,与目前的集中式系统相比,增加的复杂性导致进一步的运营风险。

相反,基于DLT的大额支付系统的好处是可以与更广泛的FMI生态系统互动。例如,将其他资产与支付相结合(抵押品质押和资产出售),通过整合后端系统,扩大范围经济并降低参与者的成本。

  1. 编写 solidity 代码,选择 Web3 Provider

![](Remix部署以太坊智能合约至远程节点/remix remote deploy 1.png)

  1. Yes

![](Remix部署以太坊智能合约至远程节点/remix remote deploy 2.png)

  1. 节点IP地址

![](Remix部署以太坊智能合约至远程节点/remix remote deploy 3.png)

  1. 点击 Create,部署代码。查看底部 log 成功与否,这里需要花费几秒钟的时间。成功后 copy 合约地址。

![](Remix部署以太坊智能合约至远程节点/remix remote deploy 4.png)

  1. 本地查看调试网页。

![](Remix部署以太坊智能合约至远程节点/remix remote deploy 5.png)

示例源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>

<link rel="stylesheet" type="text/css" href="main.css">

<script src="./node_modules/web3/dist/web3.min.js"></script>

</head>
<body>
<div class="container">
<h1>Coursetro Instructor</h1>

<h2 id="instructor"></h2>

<img id="loader" src="https://loading.io/spinners/double-ring/lg.double-ring-spinner.gif">
<label for="name" class="col-lg-2 control-label">Instructor Name</label>
<input id="name" type="text">

<label for="name" class="col-lg-2 control-label">Instructor Age</label>
<input id="age" type="text">
<button id="button">Update Instructor</button>
</div>
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"></script>

<script>
if (typeof web3 !== 'undefined') {
web3 = new Web3(web3.currentProvider);
} else {
// set the provider you want from Web3.providers
web3 = new Web3(new Web3.providers.HttpProvider("http://47.92.53.158:8545"));
}
web3.eth.defaultAccount = web3.eth.accounts[0];

var CoursetroContract = web3.eth.contract([{"constant":false,"inputs":[{"name":"_fName","type":"string"},{"name":"_age","type":"uint256"}],"name":"setInstructor","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getInstructor","outputs":[{"name":"","type":"string"},{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"name","type":"string"},{"indexed":false,"name":"age","type":"uint256"}],"name":"Instructor","type":"event"}]);

var Coursetro = CoursetroContract.at('0x6f57ae4ea0bb4a6bc3c153ced8202217d63d5fca');
console.log(Coursetro);

var instructorEvent = Coursetro.Instructor();
instructorEvent.watch(function(error, result){
if (!error)
{
$("#loader").hide();
$("#instructor").html(result.args.name + ' (' + result.args.age + ' years old)');
} else {
$("#loader").hide();
console.log(error);
}
});
$("#button").click(function() {
Coursetro.setInstructor($("#name").val(), $("#age").val());
$("#loader").show();
});

</script>

</body>
</html>