区块链测试驱动开发的错误

Error with Blockchain Test-Driven Development

我目前正在学习如何使用 Javascript 和 Node.js 开发区块链。我正在使用一种测试驱动的开发格式,到目前为止它非常有用。然而,不幸的是,我的一项测试不断失败,我无法查明原因。这是与此错误相关的三个代码片段;他们每个人都是一个不同的文件。为了清楚起见,第一个片段是 block.js,第二个片段是 block.test.js,第三个片段是 config.js。此外,cryptoHash 函数(在另一个文件中定义)是用于使用给定信息创建 SHA-256 哈希的函数。

const {GENESIS_DATA, MINE_RATE} = require("./config");
const cryptoHash = require("./crypto-hash");

class Block {
    constructor({timestamp, lastHash, hash, data, nonce, difficulty}) {
        this.timestamp = timestamp;
        this.lastHash = lastHash;
        this.hash = hash;
        this.data = data;
        this.nonce = nonce;
        this.difficulty = difficulty;
    }

    static genesis() {
        return new this(GENESIS_DATA);
    }

    static mineBlock({lastBlock,data}) {
        let hash, timestamp;
        const lastHash = lastBlock.hash;
        let {difficulty} = lastBlock;
        let {nonce} = 0;

        do {
            nonce ++;
            timestamp = Date.now();
            hash = cryptoHash(timestamp, lastHash, data, nonce, difficulty);
        } while (hash.substring(0, difficulty) !== '0'.repeat(difficulty));

        return new this({
            timestamp,
            lastHash,
            data,
            difficulty, 
            nonce,
            hash
        });
    }  

    static adjustDifficulty({originalBlock, timestamp}) {
        
        const {difficulty} = originalBlock;

        if ((timestamp - originalBlock.timestamp) > MINE_RATE) return difficulty - 1;

        return difficulty + 1;
    }
} 

module.exports = Block;

const Block = require('./block');
const { GENESIS_DATA, MINE_RATE } = require('./config');
const cryptoHash = require('./crypto-hash');
 
describe('Block', () => {
    const timestamp = '2000';
    const lastHash = 'foo-hash';
    const hash = 'bar-hash';
    const data = ['blockchain', 'data'];
    const nonce = 1;
    const difficulty = 1;
    const block = new Block ({timestamp, lastHash, hash, data, nonce, difficulty});
 
    it('has a timestamp, lastHash, hash, data property', () => {
        expect(block.timestamp).toEqual(timestamp);
        expect(block.lastHash).toEqual(lastHash);
        expect(block.hash).toEqual(hash);
        expect(block.data).toEqual(data);
        expect(block.nonce).toEqual(nonce);
        expect(block.difficulty).toEqual(difficulty);
    });
 
    describe('genesis()', () => {
        const genesisBlock = Block.genesis();
 
        it('returns a Block instance', () => {
            expect(genesisBlock instanceof Block).toBe(true);
        });
 
        it('returns the genesis data', () => {
            expect(genesisBlock).toEqual(GENESIS_DATA);
        });
    });
 
    describe('mineBlock()', () => {
        const lastBlock = Block.genesis();
        const data = 'mined data';
        const minedBlock = Block.mineBlock({ lastBlock, data });
 
        it('returns a Block instance', () => {
            expect(minedBlock instanceof Block).toBe(true);
        });
 
        it('sets the `lastHash` to be the `hash` of the lastBlock', () => {
            expect(minedBlock.lastHash).toEqual(lastBlock.hash);
        });
 
        it('sets the `data`', () => {
            expect(minedBlock.data).toEqual(data);
        });
 
        it('sets a `timestamp`', () => {
            expect(minedBlock.timestamp).not.toEqual(undefined);
        });

        it('creates a SHA-256 `hash` based on the proper inputs', () => {
            expect(minedBlock.hash).toEqual(
                cryptoHash(
                    minedBlock.timestamp,
                    minedBlock.nonce,
                    minedBlock.difficulty,
                    lastBlock.hash,
                    data
                )
            );
        }); 

        it('sets a `hash` that matches the difficulty criteria', () => {
            expect(minedBlock.hash.substring(0, minedBlock.difficulty))
                .toEqual('0'.repeat(minedBlock.difficulty));
        });
    });

    describe('adjustDifficulty()', () => {
        it('raises the difficulty for a quickly mined block', () => {
          expect(Block.adjustDifficulty({
            originalBlock: block, timestamp: block.timestamp + MINE_RATE - 100
          })).toEqual(block.difficulty+1);
        });
    
        it('lowers the difficulty for a slowly mined block', () => {
          expect(Block.adjustDifficulty({
            originalBlock: block, timestamp: block.timestamp + MINE_RATE + 100
          })).toEqual(block.difficulty-1);
        });
    });
});

const MINE_RATE = 1000;
const INITIAL_DIFFICULTY = 1;

const GENESIS_DATA = {
  timestamp: 1,
  lastHash: '-----',
  hash: 'hash-one',
  difficulty: INITIAL_DIFFICULTY,
  nonce: 0,
  data: []
};

module.exports = {
  GENESIS_DATA,
  MINE_RATE,
};

错误是这样说的:

● 区块 › adjustDifficulty() › 提高快速开采区块的难度

expect(received).toEqual(expected)

Expected value to equal:
  2
Received:
  0

  76 |           expect(Block.adjustDifficulty({
  77 |             originalBlock: block, timestamp: block.timestamp + MINE_RATE - 100
> 78 |           })).toEqual(block.difficulty+1);
     |               ^
  79 |         });
  80 |     
  81 |         it('lowers the difficulty for a slowly mined block', () => {

  at Object.<anonymous> (block.test.js:78:15)

我知道这很多,但如果有人能帮助我,我将不胜感激 - 谢谢!

这一行...

const timestamp = '2000'; // why so stringious?

...似乎是万恶之源。看,当你创建你的测试 block 时,它的 timestamp 属性最终是一个字符串,而不是一个数字。所以这个表达式...

timestamp: block.timestamp + MINE_RATE - 100

... 给你的不是 2900 (2000 + 1000 - 100),而是 20000900 ('20001000' - 100)。您可以通过添加某种中间日志记录轻松地检查这一点。

当然,在您的其他测试中也会发生同样的情况,并且结束时间戳甚至更高(因为两个 + 都被视为 concat 操作)。但是由于存储在那里的字符串最终通过 - 操作转换为数字(尽管相当大),lowers the difficulty 测试通过得很好。

因此,直接的补救措施似乎相当简单:删除那些引号并重试。尽管如此,我还是建议至少考虑采用 TypeScript 方式 - 从长远来看 运行,利大于弊。