1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
type Header struct {
ParentHash common.Hash `json:"parentHash" gencodec:"required"`
UncleHash common.Hash `json:"sha3Uncles" gencodec:"required"`
Coinbase common.Address `json:"miner" gencodec:"required"`
Root common.Hash `json:"stateRoot" gencodec:"required"`
TxHash common.Hash `json:"transactionsRoot" gencodec:"required"`
ReceiptHash common.Hash `json:"receiptsRoot" gencodec:"required"`
Bloom Bloom `json:"logsBloom" gencodec:"required"`
Difficulty *big.Int `json:"difficulty" gencodec:"required"`
Number *big.Int `json:"number" gencodec:"required"`
GasLimit uint64 `json:"gasLimit" gencodec:"required"`
GasUsed uint64 `json:"gasUsed" gencodec:"required"`
Time *big.Int `json:"timestamp" gencodec:"required"`
Extra []byte `json:"extraData" gencodec:"required"`
MixDigest common.Hash `json:"mixHash" gencodec:"required"`
Nonce BlockNonce `json:"nonce" gencodec:"required"`
}
|
状态树、交易树和收据树
Root状态树,数据结构为MPT(Merkle Patricia Tree),针对更新的状态创建新的分支,包含之前的状态树。每一个区块TxHash和ReceiptHash分别是一棵MPT树。
bloom filter
bloom filter用于查找某个元素是否在集合里,是一个基于概率的数据结构:过滤掉的元素一定不在集合里,但通过滤器的元素未必在集合里。 Bloom filter基于一个比特向量( Bit Vector) 定位一个元素,
举例:一共有n元素经过bloom filter,bloom filter中一共有m各比特,需要k个hash函数分别计算元素的k个向量,则k的选取为k: (m/n)ln(2)
// the wordSize of a bit set
const wordSize = uint(64)
// log2WordSize is lg(wordSize)
const log2WordSize = uint(6)
»
GHOST协议
叔块:祖先的兄弟(不包含父亲的兄弟)
以太坊鼓励分叉合并的方案。叔块奖励最多到前7代,奖励比例依次为$$ \frac{7}{8} 、\frac{6}{8} 、\frac{5}{8} 、\frac{4}{8} 、\frac{3}{8} 、 \frac{2}{8} $$ , 每个区块最多包含2个叔块,每包含一个叔块的区块也会得到$$\frac{1}{32} blcokReward$$ 的奖励。验证区块只验证其难度,不验证里面的交易。
智能合约
交易只能由外部账户发起
合约之间调用
-
直接调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
contract A {
event LogCallFoo(string str);
function foo(string str) return (uint){
emit LogCallFoo(str);
return 123;
}
}
contract B {
uint ua
function callAFooDirectly(address addr){
A a = A(addr);
ua = a.foo("call foo directly"); // 此执行a.foo()过程中抛出错误,则callAFooDirectly也抛出错误,本次调用全部回滚。
}
}
|
-
使用address 类型的call()函数
1
2
3
4
5
6
7
8
|
contract C {
function callAFooByCall(address addr) public returns (bool) {
bytes4 funcsig = bytes4(keccak256("foo(string)"));
if(addr.call(funcsig, "call foo by func call")) //无法获取函数返回值
return true;
return false;
}
}
|
fallback()函数
1
2
3
|
function() public [payable] {
...
}
|
- 匿名函数,没有参数也没有返回值
- 在两种情况下被调用
- 直接向一个合约地址转账而不加任何data
- 被调用的函数不存在
- 如果转账金额不是0,同样需要声明payable, 否则会抛出异常
智能合约创建和允许
创建: 代码编译成bytecode放入data中,发起交易to为0x0
运行:在EVM(Ethereum Virtual Machine)上
汽油费(gas fee)
增加攻击成本
Block的gasLimit作用:对区块交易所消耗的资源进行限制,类似比特币对区块大小的限制。矿工可以在之前的区块的gaslimit 对当前区块的gaslimit上下条1/1024比例。
错误处理
- 不存在try-catch
- 出现异常回滚
- 可以抛错误的语句:
- assert(bool condition): 如果条件不满足就抛出—-用于内部错误
- require(bool condition)
- revert(): 终止运行并会滚状态变动。
嵌套调用
具体情况具体考虑
挖矿和执行职能合约的先后顺序?
先执行执行智能合约,更新三棵树(Root, ReceiptHash, TxHash)。每个全节点还要对智能合约执行一遍以对发布的区块验证。验证需要执行交易一遍并更新本地数据库的三棵树以验证块头信息。发布的区块了没有三棵树全部具体内容,只是块头里有根其对应的根hash值。
智能合约支持多线程吗
不支持。多核访问内存顺序不同,执行结果不确定。而且所有不确定的执行都不会支持,如java的生成随机数。
智能合约可以获取的区块信息
block.blockhash(uint blockNumber) returns (bytes32)
给定区块的hash,仅对最近的256个区块有效,不包含当前区块
block.coinbase
block.difficulty
block.gaslimit
block.number
block.timestamp
智能合约可以获得的调用信息
msg.data(bytes)
完整的calldata
msg.gas(uint)
,剩余的gas决定了还可以做什么动作
msg.sender(address)
msg.sig(bytes4)
calldata的前4个字节,也就是函数标识符
msg.value(uint)
now
tx.gasprice
tx.origin
交易的发起者,A->C->B, B中 A为tx.origin
地址类型
balance
transfer(uint256 amount)
向地址类型发送amount的wei,失败时抛出异常,发送2300 gas的矿工费,不可调节
send(uint256 amount) returns (bool)
向地址类型发送数量为amount 的wei,失败时返回false, 发送2300 gas的矿工费,不可调节。
call(...) returns (bool)
发出底层call,失败返回false,发送所有gas,不可调节。
callcode(...) returns (bool)
发出底层callcode, 失败返回false,发送所有可用的gas, 不可调节。
deletegatecall(...) returns (bool)
。发出底层callcode, 失败返回false,发送所有可用的gas, 不可调节。
漏洞
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
美链
function batchTransfer(address[] _receivers, uint256 _value){
uint cnt = _receivers.length;
uint256 amount = uint256(cnt) * _value; //溢出可能
require(cnt > 0 && cnt<=20);
require(_value>0 && balances[msg.sender]>=amount);
balances[msg.sender] = balances[msg.sender].sub(amount);
for(uint i=0; i<cnt; i++){
balances[_recievers[i]] = balances[_recievers[i]].add(_value);
Transfer(msg.sender, _receivers[i], _value);
}
}
|
美链攻击细节
1
2
3
4
5
6
7
8
9
10
|
function: batachTransfer(address[] _recievers, uint256 _value)
MethodID:0x83f12ec
[0]: 00000000000000000000000000000040 //第一个参数出现在4*16个字节位置
[1]: 80000000000000000000000000000000 //_value值
[2]: 00000000000000000000000000000002 //数组长度
[3]: 00000000000000003333333333333333 // 接收的地址
[4]: 00000000000000004444444444444444 //接收的地址
计算溢出的amout恰好为0,
使用SafeMath库
|
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
|
下面为两个有问题合约,它们没有考虑被恶意合约调用可能
第一版本:
function bid() public payable {
require(now <= auctionEnd);
require(bids[msg.sender]+msg.value > bids[highestBidder]);
if(!(bids[msg.sender] == uint(0))){
bidders.push(msg.sender);
}
highestBidder = msg.sender;
bids[msg.sender] += msg.value;
emit HighestBidIncreased(msg.sender, bids[msg.sender]);
}
function acutionEnd() public {
require(now > autionEnd);
require(!ended);
beneficiary.transfer(bids[highestBidder]);
for(uint i = 0; i< bidders.length;i++){
address bidder = bidders[i];
if(bidder == highestBidder) continue;
bidder.transfer(bids[bidder]);
}
}
第二版本:
function withdraw() public returns (bool) {
require(now > autionEnd);
require(msg.sender != highestBidder);
require(bids[msg.sender] > 0);
uint amount = bids[msg.sender];
if(msg.sender.call.value(amount)()){ //可重复攻击
bids[msg.sender] = 0;
return true;
}
return false;
}
function pay2Beneficiary(address winner, uint amount){
require(now > auctionEnd);
require(bids[highestBidder] > 0);
uint amount = bids[highestBidder];
bids[highestBidder] = 0;
emit Pay2Beneficiary(highestBidder, bids[highestBidder]);
if(!beneficiary.call.value(amount)()){
bids[highestBidder] = amount;
return false;
}
return true;
}
|
最新完整安全合约参考
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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
|
pragma solidity >=0.4.22 <0.7.0;
contract SimpleAuction {
// Parameters of the auction. Times are either
// absolute unix timestamps (seconds since 1970-01-01)
// or time periods in seconds.
address payable public beneficiary;
uint public auctionEndTime;
// Current state of the auction.
address public highestBidder;
uint public highestBid;
// Allowed withdrawals of previous bids
mapping(address => uint) pendingReturns;
// Set to true at the end, disallows any change.
// By default initialized to `false`.
bool ended;
// Events that will be emitted on changes.
event HighestBidIncreased(address bidder, uint amount);
event AuctionEnded(address winner, uint amount);
// The following is a so-called natspec comment,
// recognizable by the three slashes.
// It will be shown when the user is asked to
// confirm a transaction.
/// Create a simple auction with `_biddingTime`
/// seconds bidding time on behalf of the
/// beneficiary address `_beneficiary`.
constructor(
uint _biddingTime,
address payable _beneficiary
) public {
beneficiary = _beneficiary;
auctionEndTime = now + _biddingTime;
}
/// Bid on the auction with the value sent
/// together with this transaction.
/// The value will only be refunded if the
/// auction is not won.
function bid() public payable {
// No arguments are necessary, all
// information is already part of
// the transaction. The keyword payable
// is required for the function to
// be able to receive Ether.
// Revert the call if the bidding
// period is over.
require(
now <= auctionEndTime,
"Auction already ended."
);
// If the bid is not higher, send the
// money back (the failing require
// will revert all changes in this
// function execution including
// it having received the money).
require(
msg.value > highestBid,
"There already is a higher bid."
);
if (highestBid != 0) {
// Sending back the money by simply using
// highestBidder.send(highestBid) is a security risk
// because it could execute an untrusted contract.
// It is always safer to let the recipients
// withdraw their money themselves.
pendingReturns[highestBidder] += highestBid;
}
highestBidder = msg.sender;
highestBid = msg.value;
emit HighestBidIncreased(msg.sender, msg.value);
}
/// Withdraw a bid that was overbid.
function withdraw() public returns (bool) {
uint amount = pendingReturns[msg.sender];
if (amount > 0) {
// It is important to set this to zero because the recipient
// can call this function again as part of the receiving call
// before `send` returns.
pendingReturns[msg.sender] = 0;
if (!msg.sender.send(amount)) {
// No need to call throw here, just reset the amount owing
pendingReturns[msg.sender] = amount;
return false;
}
}
return true;
}
/// End the auction and send the highest bid
/// to the beneficiary.
function auctionEnd() public {
// It is a good guideline to structure functions that interact
// with other contracts (i.e. they call functions or send Ether)
// into three phases:
// 1. checking conditions
// 2. performing actions (potentially changing conditions)
// 3. interacting with other contracts
// If these phases are mixed up, the other contract could call
// back into the current contract and modify the state or cause
// effects (ether payout) to be performed multiple times.
// If functions called internally include interaction with external
// contracts, they also have to be considered interaction with
// external contracts.
// 1. Conditions
require(now >= auctionEndTime, "Auction not yet ended.");
require(!ended, "auctionEnd has already been called.");
// 2. Effects
ended = true;
emit AuctionEnded(highestBidder, highestBid);
// 3. Interaction
beneficiary.transfer(highestBid);
}
}
|
参考
https://llimllib.github.io/bloomfilter-tutorial/