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;
  }
}
  • 代理调用delegatecall()

    使用方法与call()相同,只是不能使用.value()提供ETH,也可以通过.gas()调整提供的gas数量

    区别在于是否可以切换上下文:call()切换到被调用的智能合约上下文中;deletgatecall()只能使用给定地址的代码,其他属性(存储, 余额,等)都取自当前合约。delegatecall的目的是使用存储在另外一个合约中的库代码。


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/