블록의 개념 및 Jest를 활용한 Merkle 테스트

efforthye·2023년 1월 9일

BlockChain

목록 보기
4/6
post-thumbnail

블록의 개념

Block이란?

  • 데이터(정보)를 갖고있는 조각
  • 프로그래밍 상에서는 객체로 구현된다.

Jest 란?

  • FaceBook에서 개발한 테스트 라이브러리이다. (자칭 프레임 워크)
  • node --experimental-vm-modules node_modules/jest/bin/jest.js
    • ES6(type : module) 사용시 테스트 방식이 달라 기존 npx jest 로는 실행되지 않으므로 위 명령어로 실행한다.

Block Header 란?

  • Block에 포함되는 정보 단위 중 하나이다.
  • 버전, 머클루트, 타임스탬프, 난이도, 논스를 포함한다.
    • 버전 : 이름 그대로 Block의 현재 버전
    • 머클 루트 : 아래에서 상세 설명
    • 타임스탬프 : 블록이 생성된 시간이다.
    • 난이도 : 차후 Chain에 대해 설명할 때 상세 설명
      • 난이도의 경우 우리가 구현할 때는 difficulty라는 변수명을 사용할 예정이지만 비트코인에서는 bits라는 변수명을 사용한다.
    • 논스 : 난이도 설명할 때 함께 상세 설명
      • 난이도와 논스는 초기 임시값을 0으로 둔다.
  • Block Header의 내용은 블록체인마다 다를 수 있다.

Block Body 란?

  • 여러 트랜잭션기타 정보들을 뜻한다.
    • 비트코인에서는 1800개의 트랜잭션(거래 내역)을 블록에 저장한다.(잡담)

트랜잭션 이란?

  • 거래에 대한 정보를 말한다.
  • 지불과 수령이 모두 포함되어야 한다.(중요)
  • 사전적 의미로는 쪼갤 수 없는 업무 처리의 최소 단위이다.
    • DB 등에서도 사용된다고 한다.
  • 은행을 예로 들어보자.
    • A가 B에게 송금했다.
      1. B는 A가 보낸 송금을 받지 못했다.
        -> 트랜잭션이 취소되며 A에게 돈이 돌아온다.
      2. B는 A가 보낸 송금을 받았다.
        -> 송금과 수령이 함께 트랜잭션으로 처리된다.
  • 거래의 단위이다.

Merkle 이란?

  • 여러 정보들을 하나로 암호화한다.
  • 이진 트리로 구현되며 merkleTree 라고 부른다.
  • hash로 단방향 암호화된다.
    • 항상 일정한 길이(16진수)의 문자열로 처리할 수 있다.
  • hash를 쓰는 이유 : hash는 항상 같은 길이의 암호화된 문자열을 만든다. 생성된 암호화된 문자열로 이전 블록, 현재 블록 등등을 비교하여 정상적인 블록인지 빠르게 확인할 수 있다.(중요)
  • merkle을 쓰는 이유 : 데이터를 하나로 합치기 위해 사용된다. 여러 데이터를 하나의 hash 암호화 문자열로 만들어서 비교를 더욱 쉽게 해준다.

jest에 대해서

jest를 사용하는 방법

  • .js 파일을 .test.js 파일로 생성
    • npx jest를 실행하면 .test.js 파일을 모두 확인한다.
  • 또는 package.json 파일 안에 test 명령어를 jest로 수정한다.
    • ex) "test" : "jest"
    • jest 파일명을 실행하면 해당 파일만 확인할 수 있다.

jest 코드 설명

  • describe단순한 그룹이다. 목차에서 하위 목차 나누듯이 그룹으로 묶어서 하위 테스트 또는 하위 그룹을 만든다. describe는 describe 안에서도 사용할 수 있으며 사용하면 상위 describe의 하위 목록이 된다.
  • 테스트할 때에는 test 혹은 it을 사용한다.
  • it(name, func)
    • it 또는 test는 name 이라는 이름의 테스트를 실행한다.
    • 해당 테스트의 구현 내용(코드)은 func(함수)에 포함된다.
  • 코드 실행 자체를 테스트하기 위해 jest를 사용한다.
  • expect(확인할 내용) : 적혀있는 그대로 확인할 코드, 함수, 객체, 변수 등등을 매개변수로 전달한다. expect 하나만 있으면 아무 의미가 없다.
  • toBe(비교 내용) : 정확히 일치하는지를 확인한다.
    • toXXXXX 를 매쳐(matcher)라고 부른다. expect와 함께 사용하는 메서드인 matcher는 toBe() 외에도 찾아보면 엄청 많다.
    • toBe의 경우 완벽한 일치를 확인하기 때문에 객체 등을 비교할 수는 없다.
  • toEqual(정보) : 완벽한 일치가 아닌 포함된 정보들의 일치를 확인한다. 객체를 비교할 때 쓰인다. 하나만 일치해도 passed를 반환한다.
  • toStrictEqual : 내용 자체가 모두 일치해야 한다.

Jest로 테스트해보기

  • i : install --save 와 같다.
    • --save : package.json에 해당 라이브러리에 대해서 저장한다.(현재는 포함시키지 않아도 알아서 추가되므로 --save를 붙이지 않아도 된다.)
  • -D === -dev << develop : 테스트에서만 사용한다.

1. 일단 폴더에 기본적인 라이브러리 설치

npm init
npm i crypto-js merkle
npm i -D jest

npm install crypto-js merkle
npm install --save-dev jest

데이터 2개로 테스트해보기

2. merkle 폴더에 파일 생성

  • merkle.jsmerkle.test.js 파일 생성
  • merkle.test.js 에서

describe("이건 테스트의 그룹이다.", ()=>{
	// 아래의 테스트들을 이 그룹 안에 넣어주면 된다. 어떤 테스트인지 단순히 그룹을 만든 것이다.
});

test("이건 테스트다", () =>{
    expect("1234").toBe("1234");
});

// 안되는 이유 : 객체는 서로 다른 것으로 취급하기 때문이다. 
// 같은 객체가 되려면 변수에 정의(const obj = { a : 1 })해야 한다.
test("이건 안된다", ()=>{
    expect({a : 1}).toBe({a : 1});
});

it("객체의 비교", ()=>{
    expect({a:1}).toEqual({a:1});
});

...
  • merkle의 상위 폴더에서 npx jest 를 해주면 하위 폴더의 모든 .test.js 파일을 찾아 테스트한다.
  • merkle.js 에서

  • SHA256과 merkle 라이브러리를 가져온다.
	const SHA256 = require("crypto-js").SHA256;
	const merkle = require("merkle");
  • merkle 라이브러리를 사용해 data 배열로 머클루트를 만든다.
	const data = [1];
	const merkleRoot = merkle("sha256").sync(data).root();
  • data 배열로 직접 머클루트를 만든다.
	const createMerkleRoot = ()=>{
    	return SHA256(data[0]).toString().toUpperCase();
	}
  • export 해준다.
    module.exports = {merkleRoot, createdRoot : createMerkleRoot()};
  • merkle.test.js 에서

  • import 해준다.
    const {merkleRoot, createdRoot} = require("./merkle.js");
  • 테스트 해준다.
	// failed -> 이유 : merkle 라이브러리는 받는 데이터 배열의 
    // 아이템을 전부 문자열(string)으로 처리한다.
    // 내열 내의 데이터가 **숫자** 1이기 때문에 현재는 오류가 발생한다.
    // 머클 라이브러리는 알아서 바꿔줬기 때문에 다르게 나온 것이다.
    // merkle.js의 createMerkleRoot에서 data[0]을 data[0].toString()로 바꿔준다.
    describe("merkle 비교", ()=>{
        it("하나의 데이터 암호화 확인", ()=>{
            expect(merkleRoot).toBe(createdRoot);
        });
    });
  • merkle.js 에서 내용 수정
	const createMerkleRoot = ()=>{
    	return SHA256(data[0]).toString().toUpperCase();
	}
  • merkle.js 에서 트리 만들기

  • const data = [1, 2]; 로 수정
  • 데이터의 모든 정보(아이템)을 한번씩 hash(sha256) 방식으로 암호화한다.
	for(let i = 0; i<data.length; i++){
    	fitstTree.push(SHA256(data[i].toString()).toString().toUpperCase());
	};
  • 데이터(data) 안의 2개의 아이템을 암호화한 후 연결해서 다시 암호화하며 한 개의 문자열로 만든다.
const secondTreeRoot = SHA256(fitstTree[0] + fitstTree[1]).toString().toUpperCase();
  • export에 추가해준다.
module.exports = {merkleRoot, createdRoot : createMerkleRoot(), secondTreeRoot};
  • merkle.test.js 에서 내용 수정

  • secondTreeRoot를 import에 추가한다.
const {merkleRoot, createdRoot, secondTreeRoot} = require("./merkle.js");
  • 테스트 항목을 추가해준다.
    describe("merkle 비교", ()=>{
        it("하나의 데이터 암호화 확인", ()=>{
            expect(merkleRoot).toBe(createdRoot);
        });

		// 추가, passed
        it("두개의 데이터 암호화 확인", ()=>{
            expect(secondTreeRoot).toBe(merkleRoot);
        });
    });
  • 터미널에서 npx jest 로 테스트해준다. (위에서 failed 뜨는 친구들 주석처리 해준 뒤 테스트했다.)


데이터 4개로 테스트

  • merkle.js

  • data 4개가 되도록 추가
    const data = [1, 2, 3, 4];

  • 두번째 트리 빈배열 만들어줌
    const secondTree = [];

  • 두번째 트리에 암호화해서 넣어줌

  • 암호화된 데이터를 합쳐 다시 암호화해준다.

    • 1234가 있으면 12와 34를 처리해야 하므로 i+=2
for(let i = 0; i<fitstTree.length; i+=2){
    secondTree.push(
            SHA256(
                fitstTree[i].toString() + 
                fitstTree[i+1].toString()
            ).toString().toUpperCase()
        );
};
  • 뭘 만든 건지 모르겠음
const thirdTreeRoot = SHA256(secondTree[0] + secondTree[1]).toString().toUpperCase();
  • export

module.exports = {merkleRoot, createdRoot : createMerkleRoot(), secondTreeRoot, thirdTreeRoot};

  • merkle.test.js에서 4개 암호화 확인 테스트 추가

	describe("merkle 비교", ()=>{
        it("네 개의 데이터 암호화 확인", ()=>{
            expect(thirdTreeRoot).toBe(merkleRoot);
        });
    });
  • npx jest 로 테스트 -> passed

머클 라이브러리에서의 홀수 머클트리 처리 테스트

  • oddMerkle.js 파일 생성

  • merkle.js에서 data, merkleRoot, library 복붙해 가져와 data를 3개로 만듦
	const SHA256 = require("crypto-js").SHA256;
	const merkle = require("merkle");

	const data = [1, 2, 3]; 
	const merkleRoot = merkle("sha256").sync(data).root();
  • firstTree와 push하는 부분도 복붙해 가져옴
const fitstTree = [];
for(let i = 0; i<data.length; i++){
    fitstTree.push(SHA256(data[i].toString()).toString().toUpperCase());
};
  • secondTree 배열을 만들고 코드를 작성해줌
const secondTree = [];
for(let i = 0; i<fitstTree.length; i+=2){
    // 임시값, 아래에서 조건에 따라 값을 정의한다.
    let temp = '';
    // 홀수개이며 다음 인덱스(아이템)이 없을 경우 기존의 아이템을 그대로 사용한다.
    if(i+1===fitstTree.length){
        temp = fitstTree[i];
    }else{
    // 다음 인덱스(아이템)이 있을 때 
        temp = SHA256(fitstTree[i] + fitstTree[i+1]).toString().toUpperCase();
    }
    secondTree.push(temp);
};
  • secondTree들으로 oddThirdRoot로 만들어줌
const oddThirdRoot = SHA256(secondTree[0] + secondTree[1]).toString().toUpperCase();
  • export에 추가
module.exports = {oddMerkleRoot : merkleRoot, oddThirdRoot};
  • export해줌
    module.exports = {oddMerkleRoot : merkleRoot};

  • merkle.test.js에서 import

const { oddMerkleRoot, oddThirdRoot } = require("./oddMerkle.js");
  • 테스트 코드 작성
    describe("merkle 비교", ()=>{
        it("세 개의 데이터 암호화 확인", ()=>{
            expect(oddThirdRoot).toBe(oddMerkleRoot);
        });
    });
  • npx jest로 테스트 -> 3개의 데이터 암호화 확인 passed
  • oddMerkle.js에 머클루트 반환 함수 가져와줌

const createMerkle = (_data) =>{

    // 받은 매개변수값이 배열인지 확인
    if(!Array.isArray(_data)) return {isError : true, msg: "너 배열 아님"};

    // 배열의 값을 전체 암호화 해서 새로운 배열로 반환해준다.
    let merkleArr = _data.map((item) =>
        SHA256(item).toString().toUpperCase()
    );

    // 머클 루트가 나오는 조건 : 한 개의 값이 나올 때까지 계속 돌려야 한다.
    // merkleArr 배열의 1보다 크면 1이 될 때까지 반복 작업한다.
    while(merkleArr.length > 1){
        const tempArr = [];
        // 머클의 길이와 같아질 때까지 돌린다.
        for (let i = 0; i<merkleArr.length ; i+=2) {
            if(i+1 === merkleArr.length){
                // 마지막 홀수일 때 자기 자신을 암호화
                tempArr.push(merkleArr[i]);
            }else{
                tempArr.push(
                    SHA256(merkleArr[i]+merkleArr[i+1]).toString().toUpperCase()
                )
            }
        }
        merkleArr = tempArr;
    }
    // return을 객체로 내보내는 이유 : 블록 생성 후 해당 블록이 
    // 정상적인 블록인지 확인하기 위해 객체로 내보낸다.
    // isError를 통해 생성 도중 문제가 발생했짖니 파악할 수 있다.
    // - jest에서 사용하는 것이 아닌 블록 생성 단계에서 사용한다.
    return {isError : false, value : merkleArr[0]};
};

블록 만들기

0개의 댓글