我爱编程

使用node.js+ts开发一个加密货币01

2018-04-10  本文已影响0人  万优教育

#1: 一个小型可工作区块链

概述

区块链的基本概念很简单:一个分布式数据库,保持不断增长的命令列表记录。在这一章里,我们将实现这样的区块链精简具版本。最后一章我们将在区块链中加入以下基本功能:

本章中实现的完整代码,可以在这里找到。


区块的结构

我们需要定义块的基本结构,这里只定义了一些最基本的属性

image

就像下面的代码一样:

class Block {

    public index: number; //区块的索引
    public hash: string; //区块的hash
    public previousHash: string; //前一个区块的hash
    public timestamp: number; //生成区块的时间戳
    public data: string; //交易数据

    constructor(index: number, hash: string, previousHash: string, timestamp: number, data: string) {
        this.index = index;
        this.previousHash = previousHash;
        this.timestamp = timestamp;
        this.data = data;
        this.hash = hash;
    }
}

区块的哈希(hash)

区块的hash是重要的属性之一,hash是通过区块的所有数据计算得到。这就意味着任何hash改变,原来的hash都会失效。hash也可以认为是一个区块的唯一标识符。

我们通过下面的代码计算得到hash

const calculateHash = (index: number, previousHash: string, timestamp: number, data: string): string =>
    CryptoJS.SHA256(index + previousHash + timestamp + data).toString();

应该注意,hash还不能被挖掘,因为还没有使用proof-of-work来解决。我们用hash来保存完整的块和显式地引用前面的块。

一个重要的一点,一个区块中==hash==和==previousHash==不能被改变,除非改变所有的区块。

在下面的例子中,如果第44区块的数据从==desert==变为==street==,所有的区块都应该全部改变。这是由于==hash==依赖于==previoushash==。(除此之外)

image

当介绍POW(工作量证明)时,这是一个重要的概念,区块的高度越高,就越难被修改,因为要修改所有连续的区块。


创世区块

创世区块是区块链的第一条区块,这是唯一一个没有previoushash的区块。我们将通过编码生成第一条区块。

const genesisBlock: Block = new Block(
    0, '816534932c2b7154836da6afc367695e6337db8a921823784c14378abed4f7d7', null, 1465154705, 'my genesis block!!'
);

生成一个区块

生成一个区块之前,我们先需要知道前一个区块的hash和当前区块的索引、hash、数据、时间戳。区块的数据将由最终的用户给定。代码如下。

const generateNextBlock = (blockData: string) => {
    const previousBlock: Block = getLatestBlock();
    const nextIndex: number = previousBlock.index + 1;
    const nextTimestamp: number = new Date().getTime() / 1000;
    const nextHash: string = calculateHash(nextIndex, previousBlock.hash, nextTimestamp, blockData);
    const newBlock: Block = new Block(nextIndex, nextHash, previousBlock.hash, nextTimestamp, blockData);
    return newBlock;
};

区块的存储

现在我们将通过JavaScript的数组来保存区块链,这就意味着当节点结束时,数据不会被保存。

const blockchain: Block[] = [genesisBlock];

验证区块的完整性

在必要时候,我们验证一个区块的完整性或者整个区块链的完整性,我们就要从其他节点接受区块,并选择接受与否。

一个区块的有效性必须满足一下条件

下面通过代码演示这点

const isValidNewBlock = (newBlock: Block, previousBlock: Block) => {
    if (previousBlock.index + 1 !== newBlock.index) {
        console.log('invalid index');
        return false;
    } else if (previousBlock.hash !== newBlock.previousHash) {
        console.log('invalid previoushash');
        return false;
    } else if (calculateHashForBlock(newBlock) !== newBlock.hash) {
        console.log(typeof (newBlock.hash) + ' ' + typeof calculateHashForBlock(newBlock));
        console.log('invalid hash: ' + calculateHashForBlock(newBlock) + ' ' + newBlock.hash);
        return false;
    }
    return true;
};

我们还必须验证区块的结构:

const isValidBlockStructure = (block: Block): boolean => {
    return typeof block.index === 'number'
        && typeof block.hash === 'string'
        && typeof block.previousHash === 'string'
        && typeof block.timestamp === 'number'
        && typeof block.data === 'string';
};

到这里我们已经验证了一个单独的区块,现在我们验证整个区块链。首先我们要检验第一个区块==genesisBlock==,后面我们用同意的方法来验证后面所有连续的块。下面是给出的代码。

const isValidChain = (blockchainToValidate: Block[]): boolean => {
    const isValidGenesis = (block: Block): boolean => {
        return JSON.stringify(block) === JSON.stringify(genesisBlock);
    };

    if (!isValidGenesis(blockchainToValidate[0])) {
        return false;
    }

    for (let i = 1; i < blockchainToValidate.length; i++) {
        if (!isValidNewBlock(blockchainToValidate[i], blockchainToValidate[i - 1])) {
            return false;
        }
    }
    return true;
};

选择最长的区块链

在一定时间内,只有一条链是合法的。在冲突的情况下(例如,有两个节点生成块编号72)我们应该选择最长的区块链。 在以下示例中,区块72:a350235b00不会区块链中,因为它将由长链覆盖。

image

以下是代码实现:

const replaceChain = (newBlocks: Block[]) => {
    if (isValidChain(newBlocks) && newBlocks.length > getBlockchain().length) {
        console.log('Received blockchain is valid. Replacing current blockchain with received blockchain');
        blockchain = newBlocks;
        broadcastLatest();
    } else {
        console.log('Received blockchain invalid');
    }
};

节点间的通信

一个节点的基本功能是与其他节点共享并同最新的区块链,下面的规则是用于同步。

控制节点

用户必须通过一些方式控制节点,这是设置一个http服务器。

const initHttpServer = ( myHttpPort: number ) => {
    const app = express();
    app.use(bodyParser.json());

    app.get('/blocks', (req, res) => {
        res.send(getBlockchain());
    });
    app.post('/mineBlock', (req, res) => {
        const newBlock: Block = generateNextBlock(req.body.data);
        res.send(newBlock);
    });
    app.get('/peers', (req, res) => {
        res.send(getSockets().map(( s: any ) => s._socket.remoteAddress + ':' + s._socket.remotePort));
    });
    app.post('/addPeer', (req, res) => {
        connectToPeers(req.body.peer);
        res.send();
    });

    app.listen(myHttpPort, () => {
        console.log('Listening http on port: ' + myHttpPort);
    });
};

可以看到,用户可以通过节点实现以下功能

最直接的方式通过curl来控制节点

#get all blocks from the node
> curl http://localhost:3001/blocks

体系结构

应该注意的是,实际上暴露了两个web服务器的节点:一个用于用户控制的节点(HTTP服务器)和一个对等节点之间的通信。 (Websocket HTTP服务器)

[图片上传失败...(image-ddda7f-1523368323591)]

结论

Naivecoin现在还是一个低级区块程序。 此外,这一章显示区块链的一些基本原理可以用很简单的方式来实现。 在下一章,我们将添加工作量证明算法(挖矿)功能。

本章中实现的完整代码,可以在这里找到。

上一篇 下一篇

猜你喜欢

热点阅读