区块链基础三

Posted by Alex Kinhoom on July 5, 2018

DEMO—区块的结构

/*
    一个区块的结构:
    block = {
        'index': 1,
        'timestamp': 1506057125.900785,
        'transactions': [
            {
                'sender': "8527147fe1f5426f9dd545de4b27ee00",
                'recipient': "a77f5cdfa2934df3954a5c7c7da5df1f",
                'amount': 5,
            }
        ],
        'proof': 324984774000,
        'previous_hash': "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
    }
*/




这里面每个区块包含属性:索引(index),Unix时间戳(timestamp),交易列表(transactions),工作量证明以及前一个区块的Hash值。每个新的区块都包含上一个区块的Hash,这是关键的一点,它保障了区块链不可变性。如果攻击者破坏了前面的某个区块,那么后面所有区块的Hash都会变得不正确。

DEMO—区块类

class Blockchain{      

    # 区块链
    public  $chain;
    # 交易
    public  $current_transactions;
    # 节点
    public  $node;

    public function __construct()
    {        
        $this->chain = json_decode( Redis::get('BlockChain'),true);        
        $this->node = json_decode(Redis::get('BlockNode'),true);
        $this->current_transactions = array();

        # 创建创世块
        if( count($this->last_block()) == 0 ){
            $this->new_block('1',100);
        }         
    }    
    
    # 获取区块链信息
    public function getChain()
    {
        return $this->chain;
    }

    # 获取交易信息
    public function getTran(Type $var = null)
    {
        return $this->current_transactions;
    }

    # 获取节点信息
    public function getNode(Type $var = null)
    {
        return $this->node;
    }

    /*
        将新节点添加到节点列表中
        :param address: <str> 节点的地址
        :return: None
    */
    public function register_node($address)
    {
        $this->node[] = $address;
        Redis::set('BlockNode',json_encode( $this->node ));
    }

    /*
        确定给定的区块链是否有效
        :param chain: <list> 区块链
        :return: <bool> 如果有效则为真,否则为假
    */
    public function valid_chain($chain)
    {
        $last_block = $chain[0];
        $current_index = 1;
        while($current_index < count($chain)){
            $block = $chain[$current_index];
            // print_r( $last_block );
            // print_r( $block );
            // print("\n-----------\n");
            # 检查块的散列是否正确
            if($block['previous_hash'] != $this->hash($last_block)){
                return false;
            }
            # 检查工作证明是否正确
            if( ! $this->valid_proof($last_block['proof'],$block['proof'])){
                return false;
            }
            $last_block = $block;
            $current_index ++;
        }
        return true;
    }

    /*
        共识算法解决冲突
        使用网络中最长的链.
        :return: <bool> True 如果链被取代, 否则为False
    */
    public function resolve_conflicts()
    {
        $neighbours = $this->node;
        # 去除重复的节点
        $neighbours = array_unique($neighbours);
        $new_chain = null;
        # 我们只是在寻找比我们更长的链条
        $max_length = count($this->chain);
        # 抓取并验证我们网络中所有节点的链
        foreach($neighbours as $node){
            $url = "http://".$node."/block/chain";
            $ch  = curl_init($url);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);  //返回数据不直接输出
            $content = curl_exec($ch);                    //执行并存储结果
            curl_close($ch);
            if($content == null)
                continue;
            $content = json_decode($content,true);
            $length = $content['length'];
            $chain = $content['chain'];
            # 检查长度是否更长,链条是否有效
            if($length > $max_length && $this->valid_chain($chain)){
                $max_length = $length;
                $new_chain = $chain;
            }
        }
        # 如果我们发现一个比我们的更长的新的有效链条,就取代我们的链条
        if($new_chain != null){
            $this->chain = $new_chain;
            Redis::set('BlockChain',json_encode( $this->chain ));
            return true;
        }
        return false;
    }

    /*
        生成新块        
        :param previous_hash: (Optional) <str> 前面的块的哈希
        :param proof: <int> 证明工作算法给出的证明
        :return: <dict> 新的块
    */
    public function new_block($previous_hash,$proof)
    {
        $previous_hash = $previous_hash != "1" ? $this->hash($this->last_block()) : $previous_hash;

        # 创建一个新的块并将其添加到链中
        $tmpBlock = array(
            'index' => count(@$this->chain) + 1,
            'timestamp' => time(),
            'transactions' => @$this->current_transactions,
            'proof' => $proof,
            'previous_hash' => $previous_hash
        );

        $this->current_transactions = array();
        $this->chain[] = $tmpBlock;
        Redis::set('BlockChain',json_encode( $this->chain ));
        return $tmpBlock;
    }

    /*
        生成新交易信息,信息将加入到下一个待挖的区块中
        :param sender: <str> 发件人的地址
        :param recipient: <str> 收件人的地址
        :param amount: <int> 数量
        :return: <int> 将持有此交易的Block的索引
    */    
    public function new_transaction($sender,$recipient,$amount)
    {
        # 将新的交易添加到交易列表        
        $this->current_transactions[] = array(
            'sender' => $sender,
            'recipient' => $recipient,
            'amount' => $amount
        );
        return $this->last_block();
    }

    /*
        简单的工作量证明:
         - 查找一个 p' 使得 hash(pp') 以4个0开头
         - p 是上一个块的证明,  p' 是当前的证明
        :param last_proof: <int>
        :return: <int>
    */
    public function proof_of_work($last_proof)
    {
        $proof = 0;
        while(!$this->valid_proof($last_proof,$proof)){
            $proof ++;
        }
        return $proof;
    }

    /*
        验证证明: 是否hash(last_proof, proof)以4个0开头?
        :param last_proof: <int> 先前的证明
        :param proof: <int> 当前证明
        :return: <bool> 如果正确则为真,否则为假。
    */
    public function valid_proof($last_proof,$proof)
    {
        $guess = $last_proof . $proof;
        $guess_hash = bin2hex(hash('sha256', $guess, true));
        return preg_match('/^0000/is', $guess_hash ) ? true : false;
    }
    
    /*
        生成块的 SHA-256 hash值
        :param block: <dict> 区块
        :return: <str>
    */
    public function hash($block)
    {
        # Hash区块
        # 我们必须确保字典是有序的,否则我们将有不一致的哈希值
        $block_string = json_encode($block);
        return bin2hex(hash('sha256', $block_string, true));
    }

    # 返回链中的最后一个块
    public function last_block()
    {
        return @end($this->chain);
    }
}