Javascript의 Class로 블록체인 Block 만들기(feat. Jest)

efforthye·2023년 1월 10일

BlockChain

목록 보기
5/6
post-thumbnail

기본 설치 및 폴더 생성

  • 사용할 최상위 폴더에 관련 라이브러리 설치

    1. npm init : npm init으로 기본적인 package.json 설치
    2. npm i crypto-js merkle : 블록의 해시를 생성하기 위한 암호화 라이브러리인 crypto-js 및 블록의 머클과 머클루트를 만들기 위한 merkle 라이브러리 설치
    3. npm i -D jest : 개발 테스트를 위한 jest 라이브러리 설치
  • 최상위 폴더 내부에 block, class 폴더 생성

Class 란?

  • 객체(object)를 만들기 위해 미리 지정해놓은 틀이며, 같은 형식의 객체를 쉽게 만들기 위해 사용한다.
  • 사용하는 방법 : class ClassName{} 형식으로 정의해 만들고, new ClassName() 형식으로 객체를 생성해 사용한다.
  • Class에서 사용하는 키워드
    • extends
      • 상속할 때 사용한다. 상속받을 클래스를 생성할 때 extends 키워드를 통해 사용하게 된다.
        • ex) class A extends B : B를 기초로 A를 정의하여 만든다. B를 상속받은 A는 B에 있는 key, value, method를 모두 갖는다.
    • public
      • 생성한 객체 기준에서 외부에서 사용(접근) 가능한 키
      • ex) 따로 설정할 필요 없이 this.abcd 형식으로 정의한다.(지금까지 써왔던 방식)
    • private
      • 클래스 내부에서만 사용(접근) 가능한 키이며 상속이 불가능하다.
      • 정의할 때 앞에 #을 붙인다.
      • ex) this.#abcd 로 클래스 내에서 사용 가능하지만 obj.#abcd로는 사용할 수 없다.
      • 블록체인의 블록을 만들기 위한 개념과 유사하다.
    • protected - 자바스크립트에서는 없는 개념이다.
      • private와 같이 클래스 내부에서만 사용(접근) 가능한 키이지만, 상속이 가능하다.
      • 정의할 때 앞에 _를 붙인다.
      • 단, javascript에서는 사용할 수 없다.
    • static
      - 클래스 자체로 사용 가능한 함수, 키 정의
      - 기존에 obj.add 와 같이 사용했던 방식이 아닌 ClassName.add 처럼 클래스 자체에 .을 붙여서 호출해 사용한다.

Class의 private에 대해 알아보기

  • private은 클래스의 내부에서만 쓰이고 외부에서는 아예 없는 것처럼 처리하는 변수 생성 방식이다.
  • 보통 개발자가 실수로 바꾸지 않아야 할 값을 바꾸는 것을 방지하기 위해서 사용한다.

private 변수 접근 테스트

1. class 폴더에 파일 2개 생성

  • class.js(개발용), class.test.js(테스트용) 파일 생성

2. class.js 파일에 TestClass 클래스 생성

  • #privateValue : privateValue이라는 이름의 private 변수 선언
class TestClass{
    #privateValue;

    constructor(value){
        this.#privateValue = value;
    }
}

module.exports = TestClass;

3. class.test.js에서 Class의 private 변수에 접근 가능한지 jest로 확인해보기

const TestClass = require("./class.js");

describe("Class Test", ()=>{
    it("private test", ()=>{
        const test = new TestClass(5);
        expect(test.#privateValue).toBe(5); 
    });
});
  • 애초에 #privateValue에 접근할 수 없기 때문에 빨간줄 에러가 뜬다.
  • npx jest 로 테스트해보면 #privateValue에 접근 불가능하기 때문에 failed 가 출력된다.

private 변수 접근 테스트 - get

1. class.js의 TestClass 클래스 내부에 get 메서드 만들기

  • get : 보통 외부에서 private 키에 접근하고 싶을 때 클래스 내부에 선언한다. (private : 일종의 보안절차)
    get privateValue(){
        return this.#privateValue;
    }

2. class.test.js에서 class의 private 접근 테스트 코드 수정

const TestClass = require("./class.js");

describe("Class Test", ()=>{
    it("private test", ()=>{
        const test = new TestClass(5);
        expect(test.privateValue).toBe(5); // 이렇게 접근해 사용할 수 있다.
    });
});

3. npx jest 로 테스트

  • 위의 코드가 passed 됨으로서 class 함수 내부에서 private 변수를 get 함수로 선언하면 외부에서 해당 private 변수에 접근 가능함을 확인할 수 있다.


private 변수 접근 테스트 - set

1. class.js의 TestClass 클래스 내부에 set 메서드 만들기

  • set : 외부에서 private 변수의 값을 바꿔주고 싶을 때 클래스 내부에 작성하여 사용한다.
  • set에는 매개변수를 꼭 적어줘야 한다.
    set privateValue(value){
        this.#privateValue = value;
    }

2. class.test.js에서 class의 private 접근 테스트 코드 수정

const TestClass = require("./class.js");

describe("Class Test", ()=>{
    it("private test", ()=>{
        const test = new TestClass(5);
        
        test.privateValue = 200 // set 접근
        expect(test.privateValue).toBe(200); // passed
        
    });
});

3. npx jest 로 테스트

  • 위의 코드가 passed 됨으로서 set 메서드를 사용하면 해당 private 변수에 접근하여 값을 수정할 수 있음을 확인할 수 있다.

BlockChain 이란?

블록이라고 하는 소규모 데이터들이 P2P 방식을 기반으로 생성된 체인 형태의 연결고리 기반 분산 데이터 저장 환경에 저장하여 누구라도 임의로 수정할 수 없고 누구나 변경의 결과를 열람할 수 있는 분산 컴퓨팅 기술 기반의 원장 관리 기술이다.

  • P2P 방식

    • P2P : Peer To Peer의 줄임말
      • peer : 사전적 의미로는 동료, 블록체인에서는 블록체인에 포함된 컴퓨터이다.
    • 하나의 컴퓨터에 서버를 두어 관리/거래하는 것이 아닌, 여러 컴퓨터가 직접 정보를 주고받음
      • 우리는 지난 프로젝트 때 EC2 하나를 만들어두고 거기에 이미지 파일, mysql 데이터 등등을 저장했었는데 이번에는 위와 같은 방법이 아니라 컴퓨터끼리, 중간과정 없이 내가 예성이한테 이미지를 보낸다.
  • 체인

    • hash를 사용하여 블록간에 연결을 만든다.
    • 우리는 배열로 생성 후에 hash로 확인을 할 것이다.
  • 분산 데이터 저장

    • P2P에서 각 peer 들이 같은 블록체인 데이터를 저장하는 것을 말한다.
    • 여러 컴퓨터에 같은 데이터를 저장하여 해킹 또는 오류 등이 발생했을 때 비교하여 올바르게 수정할 수 있다.
  • 분산 컴퓨팅 기술

    • 블록체인에 포함된 각 컴퓨터가 한번에 같은 블록을 추가하지는 않는다.
    • 각 컴퓨터가 다른 작업을 진행하지만 하나의 결과물을 만들어내는 기술을 뜻한다.
      • 다른 작업 : 블록을 추가하는 것
      • 하나의 결과물 : 블록체인
  • 원장 관리 기술

    • 원장 : 원본 장부의 줄임말로 거래에 대한 정보를 기록한 원본 문서이다.
    • 즉 거래에 대한 원본을 관리하는 기술이다.

Javascript의 Class로 블록 만들기

Class로 블록 만들기

1. block 폴더에 블록을 만들 파일 생성

  • block.js(개발용), block.test.js(테스트용) 파일 생성

2. block.js 에서 관련 라이브러리 불러오기

  • 라이브러리 import
    const merkle = require("merkle");
    const SHA256 = require("crypto-js").SHA256;

3. block.js 에서 BlockHeader 클래스 생성

class BlockHeader{

    // private으로 블록에 들어갈 Header 값 선언
    #version;
    #merkleRoot;
    #timestamp;
    #height;
    #difficulty;
    #nonce;

	// 생성자 : class를 사용해 새로운 객체를 생성할 때 어떤 값을 집어넣을 지 미리 정의해둔다.
    constructor(_data, _previousBlock){
        // 버전
        this.#version = "1.0.0";
        
        // 머클루트 : data가 있으면 머클 라이브러리를 사용해 머클루트를 생성하고, 
        // 없으면 0으로 채운다.
        this.#merkleRoot 
        	= _data ? merkle("sha256").sync(_data).root() : "0".repeat(64);
        
        // 체인에 추가될 때의 시점으로 블록 생성 시간을 정의하기 위해 
        // 하단에 setTimestamp() 메서드를 만들었으며 그것을 호출해 그대로 정의했다.
        this.setTimestamp;
        
        // 높이는 이전 블록이 있으면 이전 블록+1, 없으면 0으로 채운다.
        this.#height = _previousBlock ? _previousBlock.height + 1 : 0;
        
        // 난이도와 논스는 0으로 초기화한다.
        this.#difficulty = 0;
        this.#nonce = 0;
    }

    // 일단 클래스 외부에서 private 값에 접근할 수 있도록 get으로 정의
    get version(){
        return this.#version;
    }
    get merkleRoot(){
        return this.#merkleRoot;
    }
    get timestamp(){
        return this.#timestamp;
    }
    get height(){
        return this.#height;
    }
    get difficulty(){
        return this.#difficulty;
    }
    get nonce(){
        return this.#nonce;
    }
    
    // 시간을 수정하는 데에 매개변수가 필요 없으므로 get 메서드가 아닌 일반 메서드로 만들었다.
    setTimestamp() {
        // Date : 클래스, now() : Date 클래스 내부에 static으로 정의되어 접근 가능한 메서드
        this.#timestamp = Date.now();
    }
    
}

4. BlockHeader 클래스를 상속받아(extends) Block 클래스 생성

class Block extends BlockHeader{

	// private으로 블록에 들어갈 값 선언
    #previousHash
    #hash
    #data

	// 새로운 객체를 생성할 때 어떤 값을 집어넣을 지 생성자에 정의해둔다.
    constructor(_data, _previousBlock){
    
        // super() : 부모 클래스(BlockHeader)의 constructor를 호출해 
        // 부모의 생성자 값을 가져온다. 부모에게도 받은 data와 _previousBlock을 보내준다.
        super(_data, _previousBlock); 
        
        this.#previousHash = _previousBlock ? _previousBlock.hash : "0".repeat(64);
        
        // createHash(this) : 여기서 this는 현재 객체(Block 클래스) 자체를 
        // createHash 함수의 매개변수로 보내겠다는 의미이다.
        // 만약 _data가 있으면서 _previousBlock(이전 블록)도 있으면 해시를 
        // 만들어주고, 아니면 0으로 채운다.
        this.#hash = _data && _previousBlock ? Block.createHash(this) : "0".repeat(64);
        
        this.#data = _data; // data 값은 매개변수로 받아와 저장한다.
    }

    // 클래스 외부에서 접근하기 위해 private 변수 각각의 get을 만들어준다.
    get previousHash(){
        return this.#previousHash;
    }
    get hash(){
        return this.#hash;
    }
    get data(){
        return this.#data;
    }
    
    // 블록으로 해시를 생성하는 static 메서드
    static createHash(_block){

        // 블록의 정보를 임시로 합칠 변수
        let tempStr = "";
        
        // 매개변수로 받은 블록의 정보를 합친다.
        tempStr += _block.version;
        tempStr += _block.merkleRoot;
        tempStr += _block.timestamp;
        tempStr += _block.height;
        tempStr += _block.difficulty;
        tempStr += _block.nonce;
        tempStr += _block.previousHash;
        // hash는 현재 만들고 있는 키이기 때문에 추가하지 않는다.
        // data는 merkleRoot로 이미 합쳐져 있기 때문에 merkleRoot로 대체한다.
		
        // crypto-js 라이브러리를 통해 SHA256 방식으로 암호화한 후 생성된 해시 리턴
        return SHA256(tempStr).toString().toUpperCase();
        
    }

}

5. Block 클래스의 외부에서 테스트

  • temp : Block 클래스에 ["a"]라는 데이터를 넣어 그에따른 객체를 생성해 temp 변수에 담는다.
  • blockHash : Block 클래스에서 static으로 선언된 createHash 메서드에 해당 객체(temp)를 집어넣으면 해시가 생성된다.
	const temp = new Block(["a"]);
	const blockHash = Block.createHash(temp);

	// 기존 제네시스 블록의 해시
	console.log(temp.hash);
    
    // createHash 메서드를 통해 생성된 해시
	console.log(blockHash); 
  • 테스트 결과

6. block.test.js에서 테스트하기 위해 Block 클래스 export

module.exports = Block;

머클루트 및 해시 생성 테스트

1. block.test.js 에서 Block 클래스와 merkle 라이브러리를 불러온다.

const Block = require("./block.js");
const merkle = require("merkle");

2. 머클루트 및 해시 생성 테스트 코드 작성

describe("Block Test", ()=>{

    // 블록의 머클루트와 테스트 환경에서 
    // 새로 만든 머클루트가 서로 일치하는지 확인한다.
    it("merkle Test", () =>{
        const data = ["a", "b", "c"];
        const block = new Block(data);

        const merkleRoot = merkle("sha256").sync(data).root();
        expect(block.merkleRoot).toBe(merkleRoot);
    
    });

    // hash를 확인한다.
    it("hash Test", () =>{
        const data = ["a", "b", "c"];

        // 블록 하나로 비교하면 이전 블록이 없어서 해시가 
        // 0으로 채워지기 때문에 블록 2개를 만들어 테스트한다.
        const block1 = new Block(data);
        const block2 = new Block(data, block1);

		// 새로 만든 해시
        const hash = Block.createHash(block2);
        expect(block2.hash).toBe(hash);
        
    });
});

3. Jest로 테스트 결과 확인

  • 위의 코드가 passed 됨으로서 블록의 머클루트 및 해시 생성 코드에 문제가 없음을 확인할 수 있다.

0개의 댓글