Mar 26
If you find this article helpful, you may like to donate to my ETH address:

  0x84D5084a0142a26081a2d06F3505cfc2CDaE9009


Detailed guide to compile viabtc_exchange_server on Ubuntu 16.04

## DEPENDENCIES ##
引用
$ sudo apt install -y libev-dev libjansson-dev libmpdec-dev libmysqlclient-dev libcurl4-gnutls-dev libldap2-dev libgss-dev librtmp-dev libsasl2-dev

# librdkafka: 0.11.3+; DO NOT INSTALL BY APT: version too old (0.8.x);
# if you do, remove them by: sudo apt remove librdkafka1 librdkafka-dev
$ wget https://github.com/edenhill/librdkafka/archive/v0.11.3.tar.gz -O librdkafka-0.11.3.tar.gz
$ tar zxf librdkafka-0.11.3.tar.gz
$ cd librdkafka-0.11.3
$ ./configure
$ make
$ sudo make install


## COMPILATION ##

引用
$ git clone https://github.com/viabtc/viabtc_exchange_server.git
$ cd viabtc_exchange_server

$ make -C depends/hiredis
$ make -C network

$ vi utils/makefile #modify INCS
# INCS = -I ../network -I ../depends
$ make -C utils

$ vi accesshttp/makefile #modify INCS & LIBS
# INCS = -I ../network -I ../utils -I ../depends
# LIBS = -L ../utils -lutils -L ../network -lnetwork -L ../depends/hiredis -Wl,-Bstatic -lev -ljansson -lmpdec -lrdkafka -lz -lssl -lcrypto -lhiredis -lcurl -Wl,-Bdynamic -lm -lpthread -ldl -lssl -lldap -llber -lgss -lgnutls -lidn -lnettle -lrtmp -lsasl2 -lmysqlclient
$ make -C accesshttp

$ vi accessws/makefile
{modify INCS and LIBS like accesshttp/makefile}
$ make -C accessws

vi alertcenter/makefile
{modify INCS and LIBS like accesshttp/makefile}
$ make -C alertcenter

$ vi marketprice/makefile
{modify INCS and LIBS like accesshttp/makefile}
$ make -C marketprice

$ vi matchengine/makefile
{modify INCS and LIBS like accesshttp/makefile}
$ make -C matchengine

$ vi readhistory/makefile
{modify INCS and LIBS like accesshttp/makefile}
$ make -C readhistory

Mar 14
之前接触以太坊的时候,确实能搜到很多资料,还有一个看起来很丰富的 Homestead Documentation,但这些材料都太不接地气了,看完还是不知道以太坊区块链到底长什么样,因此整理了这篇说明,希望能够在一定程度上解决这个问题吧。

# 1. 安装 ethereum (@ubuntu)

参考 官方说明
引用
sudo apt-get install software-properties-common
sudo add-apt-repository -y ppa:ethereum/ethereum
sudo apt-get update
sudo apt-get install ethereum solc


# 2. 生成创世文件

将以下内容保存于 ~/.ethereum/genesis.json
引用
{
    "config": {
        "chainID": 1048576,
        "homesteadBlock": 0,
        "eip155Block": 0,
        "eip158Block": 0,
        "ByzantiumBlock": 0
    },
    "alloc": { "0xc1de867b55fdb749be0c927ecf7b19451777042b" : { "balance" : "20000000000000000000"} },
    "coinbase": "0x0000000000000000000000000000000000000000",
    "difficulty": "0x0400",
    "extraData": "0x00",
    "gasLimit": "0x2fefd8",
    "nonce": "0xdeadbeefdeadbeef",
    "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
    "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
    "timestamp": "0x00"
}

说明:
1) 来源于 https://g2ex.github.io/2017/09/12/ethereum-guidance/
2) 以太坊公链的chainID是1,私有链需要使用不同的chainID
3) 指定 ByzantiumBlock 这个参数,getTransactionReceipt() 才有 status ,用于判断交易执行结果
4) alloc 中指定了预分配 20个eth 的地址,其私钥是 22a0b3688dd46ab1a37d6237871913037681d57f628862336bc9c3c468c4a449

# 3. 初始化私有链

$ cd ~/.ethereum
$ geth --datadir ~/.ethereum/ init ./genesis.json

# 4. 账户基本操作

关于以太坊的私钥、公钥、地址、钱包的说明,参见第8节。

## 4.1 导入账户

$ echo 22a0b3688dd46ab1a37d6237871913037681d57f628862336bc9c3c468c4a449 > ~/.ethereum/coinbase.key
$ geth account import ~/.ethereum/coinbase.key

输入密码后,会在 ~/.ethereum/keystore/ 目录下生成一个账户文件,对应的地址就是创世文件里预分配了 20 eth 的那个 0xc1de867b55fdb749be0c927ecf7b19451777042b

## 4.2 生成新账户

执行以下命令,输入密码后会随机生成一个新的账户

$ geth account new
Your new account is locked with a password. Please give a password. Do not forget this password.
Passphrase:
Repeat passphrase:
Address: {a90bbc1870e06a84b3dcf92a98f55453e6404a32}

## 4.3 列出账户

$ geth account list
Account #0: {c1de867b55fdb749be0c927ecf7b19451777042b} keystore:///data/www/.ethereum/keystore/UTC--2018-02-09T13-11-19.026113731Z--c1de867b55fdb749be0c927ecf7b19451777042b
Account #1: {7aa99ede746e863e87cdd1e686abadb31325cb92} keystore:///data/www/.ethereum/keystore/UTC--2018-02-18T09-00-05.982653587Z--7aa99ede746e863e87cdd1e686abadb31325cb92

# 5. 启动私有链

$ geth --identity "TestNode" --rpc --rpcport 8545 --port 30303 --nodiscover console

说明:
1) nodiscover 表示不会去连接其他节点(单节点私有链)
2) console 表示启动 geth 的 javascript 控制台

# 6. 控制台操作

在上一步启动的控制台里执行以下命令;或者通过 geth attach 连接到已经启动的 geth 实例执行。

## 6.1 查看账户

引用
> eth.accounts
["0xc1de867b55fdb749be0c927ecf7b19451777042b", "0x7aa99ede746e863e87cdd1e686abadb31325cb92"]
> eth.coinbase
"0xc1de867b55fdb749be0c927ecf7b19451777042b"

一般来说 coinbase 账户(挖矿的收益账户)是第一个导入的账户,也就是 eth.accounts[0]。

## 6.2 查看余额

引用
> eth.getBalance(eth.coinbase)
20000000000000000000


## 6.3 转账

引用
> personal.unlockAccount(eth.coinbase)
Unlock account 0xc1de867b55fdb749be0c927ecf7b19451777042b
Passphrase:
true
> var ether = Math.pow(10, 18)
> var gwei = Math.pow(10, 9)
> tx_hash = eth.sendTransaction({from:eth.coinbase, to: eth.accounts[1], value: 1 * ether, gasPrice: 2 * gwei, gas: 21000, data: null})
"0x18629cdb404ef96873ebcbfbc97fee6e18a04c85eabecf70d57b0010684aecc6"
> eth.getTransaction(tx_hash)
{
  blockHash: "0x0000000000000000000000000000000000000000000000000000000000000000",
  blockNumber: null,
  from: "0xc1de867b55fdb749be0c927ecf7b19451777042b",
  gas: 21000,
  gasPrice: 2000000000,
  hash: "0x18629cdb404ef96873ebcbfbc97fee6e18a04c85eabecf70d57b0010684aecc6",
  input: "0x",
  nonce: 0,
  r: "0xe99aa75fdb2d071632f7d7796229e3d84df60407007032f29b69296677df798c",
  s: "0x616be42ff2a41a153f953685dc497d58ab4cca6ace4eac70405c611f078fcf4b",
  to: "0x45703fdcb21e1e129c9f61cec65a65b6aa8b7acd",
  transactionIndex: 0,
  v: "0x200024",
  value: 1000000000000000000
}


说明:
1) 以太币的最小单位 wei = 10^-18 ether
2) sendTransaction 的几个参数说明:
    from: 交易发起方地址,必填,需要先解锁
    to: 交易接收方地址,转账交易必填
    value: 转账金额,单位为 wei
    gasPrice: gas的价格,选填(geth会用估算值自动填充)
    gas: 交易消耗的最大gas数量,选填(转账交易正好消耗21000,少于这个数字geth会拒绝创建交易); 多出的部分会在交易成功以后返还
    data: 交易的输入数据,选填(转账交易不需要)
3) getTransaction 根据交易的hash获取交易详情,包括sendTransaction给出的参数,签名数据,和包含该交易的区块信息。由于当前还没有被确认,所以blockHash和blockNumber都是无效数据;特别提一下,nonce 其实也是 sendTransaction 的一个参数,相当于由该账户发起的交易的订单号,以太坊要求nonce严格有序递增(即下一笔订单的nonce一定是上一笔的nonce+1)。

关于gas和gasPrice,参见第9节。

由于交易还未被确认,所以暂不能得知交易的执行结果。

## 6.4 挖矿

引用
> eth.blockNumber
0
> miner.start();
> admin.sleepBlocks(1);
> miner.stop();
> eth.blockNumber
1
> eth.getBlock(1)
{
  difficulty: 131072,
  extraData: "0xd783010802846765746887676f312e392e34856c696e7578",
  gasLimit: 4076476,
  gasUsed: 21000,
  hash: "0xefbecf5907f9116d2f3abf46b0e6a2798939cdbd799249b46ad0a0169eaac8de",
  logsBloom: "0x000(省略部分)000",
  miner: "0xc1de867b55fdb749be0c927ecf7b19451777042b",
  mixHash: "0x16639b8a9e2adaddbb9a48f1963b16ebdb997a528c4a1aeb4ac06e04501b56e9",
  nonce: "0x1cf42797e7b2c1d8",
  number: 1,
  parentHash: "0xc75321a9915b2a2c973528f713a2a587e28244a032e3fcd46a5915112269c6a4",
  receiptsRoot: "0xd95b673818fa493deec414e01e610d97ee287c9421c8eff4102b1647c1a184e4",
  sha3Uncles: "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
  size: 764,
  stateRoot: "0x0de36a5d34f176a081fde3f26646b9368f28736ad01e96000c7ac05565213d53",
  timestamp: 1521027698,
  totalDifficulty: 35081353,
  transactions: ["0x18629cdb404ef96873ebcbfbc97fee6e18a04c85eabecf70d57b0010684aecc6"],
  transactionsRoot: "0x4ae833bcf2114a237b86755453117da68876888d8c4215da8fb750016d5470f6",
  uncles: []
}


## 6.5 查看交易执行结果

引用
> eth.getTransaction(tx_hash)
{
  blockHash: "0xefbecf5907f9116d2f3abf46b0e6a2798939cdbd799249b46ad0a0169eaac8de",
  blockNumber: 267,
  ...
}
> eth.getTransactionReceipt(tx_hash)
{
  blockHash: "0xefbecf5907f9116d2f3abf46b0e6a2798939cdbd799249b46ad0a0169eaac8de",
  blockNumber: 267,
  contractAddress: null,
  cumulativeGasUsed: 42000,
  from: "0xc1de867b55fdb749be0c927ecf7b19451777042b",
  gasUsed: 21000,
  logs: [],
  logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
  status: "0x1",
  to: "0x45703fdcb21e1e129c9f61cec65a65b6aa8b7acd",
  transactionHash: "0x18629cdb404ef96873ebcbfbc97fee6e18a04c85eabecf70d57b0010684aecc6",
  transactionIndex: 1
}
> eth.getBalance(eth.accounts[1])
1000000000000000000


说明:

1) 交易已经被确认,所以 getTransaction 可以看到区块信息
2) getTransactionReceipt 得到交易回执,其中:
    gasUsed: 实际消耗的gas(转账交易总是21000,除非指定了额外的data)
    status: "0x1" 表示成功, "0x0" 表示失败
    contractAddress: 新部署的合约地址;这个交易只是普通转账交易,不会部署合约,所以是null
    logs: 记录发送给合约的交易触发的事件,普通转账交易不会产生事件,所以是 [] 。

# 7. 合约

以太坊的账户分成两种,一种是EOA账户(Externally Owned Account,由私钥控制),里面存储了账户的以太币余额信息和由该账户发起的交易数量(nonce),上面创建的都是EOA账户。另一种是合约账户(Contract Account),是由合约部署交易创建的,里面除了存储以太币余额,还存储了合约代码,以及代码的状态(存储)。通过创建并发送由某个EOA账户私钥签名的交易,可以从该账户给其他EOA账户转账,或者向某个合约账户发起一笔交易(调用合约账户里的某个方法);而合约账户只能被动触发,接受某个EOA账户(或其他被触发的合约账户)发起的交易,根据该交易的指令执行相应的动作,修改内部状态,或向其他账户发出交易。

所谓合约,并不是某个要被执行的合同,其实质是一段代码,可以类比为一个Class,其中定义了一些方法、状态、事件。将这段代码编译成以太坊虚拟机(Ethereum Virtual Machine,简称EVM)机器码,就可以作为 sendTransaction() 方法的 data 参数,被部署到以太坊区块链上(会自动分配一个地址),这样就创建了一个合约账户。后续按照EVM ABI约定发送到该合约账户的交易,可以指定触发这段代码的相应方法执行预定的操作。

这样的描述有点抽象,下面就用具体的栗子来说明合约账户。

## 7.1 ERC20

以太坊上线几年了,目前为止最成熟的应用就是发行 ERC20 Token ,圆了好多人的会所嫩模梦。所谓 ERC20 ,实际上就是一个 Interface ,凡是遵守这个 Interface 的合约,都可以被相同的方式触发。

具体的 ERC20 Interface 实现了多个方法和事件,详见Wiki:https://theethereum.wiki/w/index.php/ERC20_Token_Standard

下面的栗子里,只挑最重要的 balanceOf 和 transfer 这两个方法,以及 Transfer 这个事件:
引用
contract ERC20Interface {
    function balanceOf(address tokenOwner) public constant returns (uint balance);
    function transfer(address to, uint tokens) public returns (bool success);
    event Transfer(address indexed from, address indexed to, uint tokens);
}

从interface的定义可以比较直接的看懂,balanceOf用于查询某个地址的余额,tranfer用于转账到某个地址;如果转账成功,transfer方法会触发一个 Transfer 事件,记录在区块链上。

下面看一个具体的代码 (保存为 token.sol ):

引用
pragma solidity ^0.4.0; //指定solidity编译器的最小版本

contract MyToken {
    string public name; //token的名称,例如 Tether USD
    string public symbol; //token的代码,例如 USDT
    uint8 public decimals; //token的精度,例如4表示该token精确到小数点后4位
    mapping (address => uint256) public balances; //该合约的账本,记录某地址的token余额

    event Transfer(address indexed from, address indexed to, uint256 value);

    /* 合约的初始化函数,在部署合约时被调用,指定供应量等信息 */
    function MyToken(uint256 initialSupply, string tokenName, string tokenSymbol, uint8 decimalUnits) {
        balances[msg.sender] = initialSupply;
        name = tokenName;
        symbol = tokenSymbol;
        decimals = decimalUnits;
    }

    function transfer(address _to, uint256 _value) {
        /* 检查发送方余额充足,且不会溢出 */
        require(balances[msg.sender] >= _value && balances[_to] + _value >= balances[_to]);
        balances[msg.sender] -= _value;
        balances[_to] += _value;
        Transfer(msg.sender, _to, _value); //关注该合约的用户可以得知发生了一笔转账事件
    }

    function balanceOf(address tokenOwner) public constant returns (uint balance) {
        return balances[tokenOwner]; //查询某个地址的token余额
    }
}


## 7.2 编译合约

引用
$ solc token.sol --bin --abi
...(编译器warning等)...
======= token.sol:MyToken =======
Binary:
6060604052341{合约被编译后产生的EVM字节码}fd000029
Contract JSON ABI
[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"balances","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"tokenOwner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"initialSupply","type":"uint256"},{"name":"tokenName","type":"string"},{"name":"tokenSymbol","type":"string"},{"name":"decimalUnits","type":"uint8"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"}]


## 7.3 部署合约

从上面的编译结果 copy 字节码 和 abi json,执行下面的命令:

引用
$ geth attach #连接到 geth 的 console
> var code = '0x6060604052341{EVM字节码}fd000029'; //这里以字符串形式保存字节码
> var abi = [{"constant":true,....:"event"}]; //注意这里不是字符串,而是json数组
> personal.unlockAccount(eth.coinbase)
Unlock account 0xc1de867b55fdb749be0c927ecf7b19451777042b
Passphrase:
true
> var contract = eth.contract(abi).new(1000000, 'Test Token', 'TEST', 2, {from: eth.coinbase, data: code, gas: 1000000})
> contract.transactionHash
"0xddc0bf88974d203ac022210dff19d315c1b2eac7de27cbee2800789e0fa9619c"

创建合约交易:初始化参数、合约部署方、gas limit;因为没有接收方,也不需要发送以太币,所以省略to和value

挖矿确认交易,然后看看部署的结果:
引用
> miner.start(); admin.sleepBlocks(1); miner.stop();
true
> eth.getTransaction(contract.transactionHash)
{
  blockHash: "0xe9ec25459ba61cd2f4026cdf4ace12f450ec263ece677f543a92565e50376d9f",
  blockNumber: 2,
  from: "0xc1de867b55fdb749be0c927ecf7b19451777042b",
  gas: 1000000,
  gasPrice: 1000000000,
  hash: "0xddc0bf88974d203ac022210dff19d315c1b2eac7de27cbee2800789e0fa9619c",
  input: "0x606060{EVM字节码}fd000029{合约初始化参数的ABI编码}",
  nonce: 1,
  r: "0x32c5cb0c44b03dd273b6973f59f85c8e09d0d6212a38263b3c199329dbbc9ef",
  s: "0x758efe028c494fc8e637792aa3e09adceb1e7cdafb8567d4a7eecf95f54accc4",
  to: null,
  transactionIndex: 0,
  v: "0x200024",
  value: 0
}
> eth.getTransactionReceipt(contract.transactionHash)
{
  blockHash: "0xe9ec25459ba61cd2f4026cdf4ace12f450ec263ece677f543a92565e50376d9f",
  blockNumber: 2,
  contractAddress: "0x52ce1b10f214abb88517ceb3de2fbfbbfc305791",
  cumulativeGasUsed: 594886,
  from: "0xc1de867b55fdb749be0c927ecf7b19451777042b",
  gasUsed: 594886,
  logs: [],
  logsBloom: "0x0000{省略一些0}0000",
  status: "0x1",
  to: null,
  transactionHash: "0xddc0bf88974d203ac022210dff19d315c1b2eac7de27cbee2800789e0fa9619c",
  transactionIndex: 0
}
> contract.address
"0x52ce1b10f214abb88517ceb3de2fbfbbfc305791"


可以看到,发送的交易在被挖矿确认以后,部署到了 0x52ce1b10f214abb88517ceb3de2fbfbbfc305791 这个地址,消耗了 594886 个 gas 。这样就“发行”了一个ERC20 Token,名字是 Test Token,代码 TEST ,一共发行 10000 个,每个 token 精确到小数点后 2 位。

## 7.4 调用合约

上面的 contract 变量就是合约对象了,但不能每次想调用一个合约都得创建它,通常我们是需要直接调用某个合约地址里的方法,那就需要用这种方式创建合约对象:
引用
> var abi = [{"contract"...}];
> var contract = eth.contract(abi).at("0x52ce1b10f214abb88517ceb3de2fbfbbfc305791")

余额查询:
引用
> contract.balanceOf.call(eth.coinbase)
1000000

可以看到,由于我们是用 coinbase 帐号发起的交易,合约在初始化的时候,根据合约代码的实现,把初始发行的 10000 个 TEST 都记到了 coinbase 帐号名下。要特别注意的是,这个账,不是记在 coinbase 这个EOA帐号的存储里,而是记在这个合约帐号的存储(代码里balances这个map)里面。

转账(误):
引用
> contract.transfer.call(eth.accounts[1], 100)
[]
> contract.balanceOf(eth.accounts[1])
0

可以看到,虽然调用了transfer方法,但是 eth.accounts[1] 的余额并没变化。这是因为transfer方法是要修改存储的,而以太坊上的状态,只能由被区块链确认了的交易出发的动作修改。

那就试试往区块链上发送一笔转账交易:
引用
> personal.unlockAccount(eth.coinbase)
Unlock account 0xc1de867b55fdb749be0c927ecf7b19451777042b
Passphrase:
true
> tx_hash = contract.transfer.sendTransaction(eth.accounts[1], 100, {from:eth.coinbase, gas:100000})
"0x061fc0ff8945257e30fc2d243e2888a4a1668856b7b7df8996fb9f01b5fa3bad"


挖矿确认这笔交易,然后查看执行结果:
引用
> miner.start(); admin.sleepBlocks(1); miner.stop();
true
> contract.balanceOf(eth.accounts[1])
100
> contract.balanceOf(eth.coinbase)
999900
> eth.getTransaction(tx_hash)
{
  blockHash: "0x6c993eb1b003a3e315ad15a4a8544c8a001d5f76d4dce614ea840c0d3da4b49d",
  blockNumber: 3,
  from: "0xc1de867b55fdb749be0c927ecf7b19451777042b",
  gas: 100000,
  gasPrice: 1000000000,
  hash: "0x061fc0ff8945257e30fc2d243e2888a4a1668856b7b7df8996fb9f01b5fa3bad",
  input: "0xa9059cbb00000000000000000000000045703fdcb21e1e129c9f61cec65a65b6aa8b7acd0000000000000000000000000000000000000000000000000000000000000064",
  nonce: 2,
  r: "0x53b6312127838fe8cdca5209b51d33175f370b2a0921215d07f2df9a572c120d",
  s: "0xd18471632a066386c42a42226f441442f73e0ba39b5a9b6db58a283d957579a",
  to: "0x52ce1b10f214abb88517ceb3de2fbfbbfc305791",
  transactionIndex: 0,
  v: "0x200024",
  value: 0
}
> eth.getTransactionReceipt(tx_hash)
{
  blockHash: "0x6c993eb1b003a3e315ad15a4a8544c8a001d5f76d4dce614ea840c0d3da4b49d",
  blockNumber: 3,
  contractAddress: null,
  cumulativeGasUsed: 51595,
  from: "0xc1de867b55fdb749be0c927ecf7b19451777042b",
  gasUsed: 51595,
  logs: [{
      address: "0x52ce1b10f214abb88517ceb3de2fbfbbfc305791",
      blockHash: "0x6c993eb1b003a3e315ad15a4a8544c8a001d5f76d4dce614ea840c0d3da4b49d",
      blockNumber: 271,
      data: "0x0000000000000000000000000000000000000000000000000000000000000064",
      logIndex: 0,
      removed: false,
      topics: ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x000000000000000000000000c1de867b55fdb749be0c927ecf7b19451777042b", "0x00000000000000000000000045703fdcb21e1e129c9f61cec65a65b6aa8b7acd"],
      transactionHash: "0x061fc0ff8945257e30fc2d243e2888a4a1668856b7b7df8996fb9f01b5fa3bad",
      transactionIndex: 0
  }],
  logsBloom: "0x0000010{省略部分数据}0000000000000",
  status: "0x1",
  to: "0x52ce1b10f214abb88517ceb3de2fbfbbfc305791",
  transactionHash: "0x061fc0ff8945257e30fc2d243e2888a4a1668856b7b7df8996fb9f01b5fa3bad",
  transactionIndex: 0
}

向区块链发送一个交易,等到交易被确认以后,再查询 eth.accounts[1] 的 token 余额,就可以看到发生了变化。

这里有几个比较重要的信息:

1) getTransaction 返回结果里的 input

之前的普通转账交易,input的内容(就是sendTransaction里指定的data)都是 "0x" ,也就是 0 字节,无数据。而这个交易,有了一个比较复杂的值。这个值可以被拆成3段:

    0xa9059cbb
        => 按照evm abi的约定,这是合约方法的签名的前4个字节。签名是用sha3对方法的定义进行hash计算出来的
        => 例如这个 0xa9059cbb 就来源于 sha3("transfer(address,uint256)")
    00000000000000000000000045703fdcb21e1e129c9f61cec65a65b6aa8b7acd
        => 这就是接收方的地址,但用32字节表示,前面用0补齐
    0000000000000000000000000000000000000000000000000000000000000064
        => 这是转账金额,32字节无符号整数,0x64 = 100

2) getTransactionReceipt 返回结果里的 logs
    logs是个数组,其中的每一项代表一个被触发的 event ,这个栗子里,logs[0].topics 包含3个元素:
   
    "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
        => 这是事件的签名,sha3("Transfer(address,address,uint256)")
    "0x000000000000000000000000c1de867b55fdb749be0c927ecf7b19451777042b"
        => 这是发送方地址,也就是 eth.coinbase
    "0x00000000000000000000000045703fdcb21e1e129c9f61cec65a65b6aa8b7acd"],
        => 这是接受方地址,也就是 eth.coinbase

    因为 Transfer 事件的定义里,from和to都带了一个 indexed 修饰符,所以会被记录到 topics 里,而没带 indexed 修饰符的 value ,则被记录在了 logs[0].data 里。

# 8. 以太坊的私钥、公钥、地址、钱包

以太坊和比特币一样,都是用的椭圆曲线加密算法 ECDSA-secp256k1。通常将私钥记为 k ,公钥记为 K 。下面使用node,安装bitcore-lib来演示。
引用
$ node
> var bitcore = require("bitcore-lib")


## 8.1 私钥

私钥 k 通常是随机选出的 32 字节数据(可以看作一个 uint256 大整数),但要特别注意,不能使用伪随机数生成器。更准确地说,私钥可以是1和n-1之间的任何数字,其中n是一个常数(n=1.158 * 10^77,略小于2^256)。从编程的角度来看,一般是通过在一个密码学安全的随机源中取出一长串随机字节,对其使用SHA256哈希算法进行运算,这样就可以方便地产生一个256位的数字。如果运算结果小于n-1,我们就有了一个合适的私钥。否则,我们就用另一个随机数再重复一次。

引用
> var k = bitcore.PrivateKey()
> k.toBuffer().hexSlice()
'3f4d9a11b162c61af91c63e601b0dadaec09c74abb7f6659364549011b0dcf15'


## 8.2 公钥

将私钥 k 与一个指定的生成点 G 运算,就能得到公钥 K = k * G。K是椭圆曲线上的一个点 (x, y)。其中x和y都是uint256大整数。在以太坊里,公钥的二进制存储格式为 x + y,一共64字节。

引用
> var K = k.toPublicKey()
> K.point.x.toBuffer().hexSlice()
'fbc1994297bd2bdd5a4464ae7f6c88338587a175faffeb414fb2717207638435'
> K.point.y.toBuffer().hexSlice()
'b5d1a6015fc9fc6cf947046213cfb7d3341491934a90a2e3880af6a940cdd1df'
> var K_buffer = Buffer.concat([K.point.x.toBuffer(), K.point.y.toBuffer()])


## 8.3 地址

通过对公钥K做sha3(keccak256)哈希,得到一个32字节的结果,取其中的后20字节,就是以太坊的地址了:

引用
> const SHA3 = require('keccakjs')
> addr = '0x' + SHA3(256).update(K_buffer).digest('hex').slice(-40)
'0x2864cdd53410820bc684f746871e9820fbb57243'


这样就得到了一个以太坊的地址。为了检测在地址传递过程中发生的错误,EIP55给出了一个校验码方案,其思路很简单,对地址再做一次sha3,根据哈希结果修改地址中对应位置的字符大小写,详细方案参见:https://github.com/ethereum/EIPs/blob/master/EIPS/eip-55.md

加上校验码后,地址长这样:0x2864Cdd53410820BC684F746871E9820fBb57243

## 8.4 钱包

由于以太坊和比特币都用的是 secp256k1 ,所以很多以太坊钱包是参考BIP32+BIP44实现的(包括KeepKey这样的硬件钱包),其中Coin Type用的是60H。

详情参考BIP44:https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki

# 9. Gas/GasLimit/GasPrice

为了避免死循环导致的拒绝服务攻击,比特币区块链的目标是实现一个点对点的电子现金系统,因此其内置的脚本不是图灵完备的,不支持循环,只能完成一些条件判断。

以太坊有更大的野心,实现了一个图灵完备的EVM。为了解决这个拒绝服务攻击的问题,以太坊实现了这样一个机制:为了让EVM这台机器运转起来,你需要给它提供“加密燃料”——gas。gas是用以太币计价的,因此在发送交易的时候,需要指定购买gas的价格——gasPrice。由于停机问题不可解,为了避免过度消耗,交易发送者还需要指定gasLimit(对应sendTransaction方法的gas参数),表示他愿意为这笔交易的执行支付的最大gas数量。

当EVM收到开始执行一个交易的时候,会先冻结 value + gas * gasPrice 这些以太币(转账金额 + 为这笔交易愿意支付的最大金额),然后开始执行交易。以太坊的黄皮书里定义了不同操作需要消耗的gas数量,EVM在执行过程中,会根据定义好的规则累计消耗的gas总量。如果交易尚未执行完,但所有gas都消耗完,EVM会回滚所有改动,并将交易标记为失败(getTransactionReceipt返回的status='0x0')。无论交易最终是否成功,EVM累计消耗的gas都会被扣除,记录在 receipt 的 gasUsed 字段里,剩余的gas则按gasPrice换成以太币,退给交易发送者。特别地,普通转账交易消耗的gas数量正好是21000。作为以太坊网络的维护者和验证者,挖出包含这个交易的block的矿工可以获得这些被消耗的gas。

因此,如果有人要用死循环来恶意攻击以太坊网络,他就要付出无限的以太币。


# 10. 其他

## 10.1 以太币的总量

以太坊正式上线时,发行了72002454个以太币,每年会按这个数字的25%增发(即不超过1800万)。截止到目前(2018-03-17),流通总量为9823万(数据来源于 coinmarketcap.com)。

## 10.2 大整数:十六进制

由于以太币的最小单位 wei = 1/10^18 ether,所以连 UINT64 都无法保存所有的以太币。很多语言没有提供原生的大整数支持,所以以太坊的rpc接口传递大整数时都使用16进制编码,由语言自行处理。

## 10.3 版本

以太坊最早于是2015年5月上线 Olympic 测试网,7月30日上线 Frontier 版本。Frontier是beta版本,供开发者学习、体验,直到2016年3月14日(圆周率日)上线了正式的 Homestead 版本。

以太坊后续还规划了 Metropolis 和 Serenity 两个版本,上线时间尚未确定。

## 10.4 挖矿和共识机制

Homestead 的共识机制与比特币一样,都是PoW(Proof of Work),但使用了一个基于DAG的Ethash算法,对内存的要求较高,因此像ASIC矿机这种大规模嵌入运算单元的方式无法加速挖矿效率,从而尽量避免挖矿的中心化,增加网络的安全性。

在接下来的Metropolis版本里,以太坊计划切换为PoS机制,提高出块的效率。

## 10.5 还有啥?

想起来再补吧。
Feb 28
# 1. 编译安装 (@ubuntu)
引用
sudo mkdir -p /usr/local/services/bitcoin
sudo apt-get install autoconf pkg-config libtool build-essential libdb++-dev libboost-dev libssl-dev libevent-dev libboost-system-dev libboost-filesystem-dev libboost-program-options-dev libboost-thread-dev libboost-test-dev libboost-*-dev

git clone https://github.com/bitcoin/bitcoin.git
cd bitcoin
git checkout v0.15.1

./autogen.sh
./configure --prefix=/usr/local/services/bitcoin --with-incompatible-bdb
make -j 6 &> make.log &
sudo make install

echo 'export PATH=$PATH:/usr/local/services/bitcoin/bin' >> ~/.bashrc


# 2. 启动 regtest 网络

$ bitcoind -regtest -txindex -daemon

说明:

-regtest 表示启动的是 regtest 网络;不同网络之间的区别在于创世区块不同。

-txindex 表示对所有的交易进行索引;否则默认只对与钱包地址有关的交易索引

-daemon 表示在后台运行

也可以通过写入配置文件 ~/.bitcoin/bitcoin.conf 的方式 :
引用
regtest=1

rpcbind=127.0.0.1
rpcallowip=127.0.0.1
rpcuser=test
rpcpassword=test

server=1
daemon=1
txindex=1

然后直接调用 bitcoind 就好了。

# 3. 基本命令

引用
$ bitcoin-cli -regtest generate 101 #挖101个block;挖矿的奖励要在100个block以后才能使用
[
  "26d4fb4dd449b93ebbda9a36f390d2c7b1dd9557e662840fe61b1f6e3a09d218",
  .... #共101块
]

$ bitcoin-cli -regtest getaccountaddress "" #钱包默认账户的地址; 测试网络的地址是m或n开头的
mmmSrYNiRQzeiTypt8rF9L3F742PW4ksmL

$ bitcoin-cli -regtest getbalance #查看挖矿奖励(默认账户)
50.00000000

$ bitcoin-cli -regtest getnewaddress #分配一个新的地址
mn41xHRzXuUAAVtujPLLo3Hzbe63GSP117

$ bitcoin-cli -regtest sendtoaddress mn41xHRzXuUAAVtujPLLo3Hzbe63GSP117 10 #往这个新生成的地址转10个BTC;输出是交易的hash
4deb2fbb98d9029a4e5808b8236d1119ad7315efdfe7406a88c923f5d4b0d5af

$ bitcoin-cli -regtest getrawtransaction 4deb2fbb98d9029a4e5808b8236d1119ad7315efdfe7406a88c923f5d4b0d5af true #查看交易详细信息
(输出json,包含size、vin、vout等,因为还没被打包进区块链,所以没有confirmations)

$ bitcoin-cli -regtest generate 1
[
    "4deb2fbb98d9029a4e5808b8236d1119ad7315efdfe7406a88c923f5d4b0d5af"
]

$ bitcoin-cli -regtest getrawtransaction 4deb2fbb98d9029a4e5808b8236d1119ad7315efdfe7406a88c923f5d4b0d5af true #查看交易详细信息
(输出json,这次可以看到 blockhash 和 confirmations 了,说明交易已经被打包)

$ bitcoin-cli -regtest listreceivedbyaddress #查看收到的BTC
[
  {
    "address": "mn41xHRzXuUAAVtujPLLo3Hzbe63GSP117",
    "account": "",
    "amount": 10.00000000,
    "confirmations": 1,
    "label": "",
    "txids": [
      "4deb2fbb98d9029a4e5808b8236d1119ad7315efdfe7406a88c923f5d4b0d5af"
    ]
  }
]
分页: 1/1 第一页 1 最后页 [ 显示模式: 摘要 | 列表 ]