다수의 트랜잭션을 모아서 하나로 관리하기 위한 묶음. 비트코인의 경우 10분간 진행된 약 2,000건의 거래내역을 하나의 블록으로 묶어서 관리한다.
즉, 블록은 데이터를 저장하는 단위로, 기존에는 중앙화된 db에서 모든 거래내역을 담아 관리하던 것을 일정한 row 단위로 분리한 것을 블록이다.
블록은 바디(body)와 헤더(header)로 구분된다. 바디에는 거래 내용이, 헤더에는 머클해시(머클루트)나 넌스(nounce, 암호화와 관련되는 임의의 수) 등의 암호코드가 담겨 있다.
위 사이트에서는 지금까지 생성된 비트코인 블록의 기록을 볼 수 있는데 여기에서 블록을 구성하는 속성은 무엇이 있는지 확인 할 수 있다. 사진에 나타나는 항목 중 몇가지만 정리해보았다.
해당 블록을 구성하는 내용을 암호화한 것. 이 해시값을 다음 블록으로 전달하게 되고 이러한 체인구조를 통해 담긴 데이터의 불변성을 보장할 수 있다.
블록의 대략적인 생성 시간.
생성된 블록의 인덱스.
말 그대로 이 블록헤더의 버전(윈도우 7,8, 10과 같은). 현재 이 블록헤더를 만든 비트코인 프로그램의 버전 번호가 된다.
이전 블록에게서 전달 받은 해시값. 이전 블록 중 하나를 수정하게 되면 연쇄작용으로 모든 해시값이 바뀌게 되므로 이를 이용하여 블록의 불변성을 검증할 수 있는 수단이 된다.
블록 바디의 데이터를 이용해 만든 머클트리의 루트값. 이에 대해서는 아래에서 조금 더 상세하게 정리하도록 하겠다. 쉽게 말하자면 블록바디를 이용해 만든 해시값으로 이를 통해 블록에 담긴 거래내역 데이터가 변조되지 않음을 쉽게 검증할 수 있다.
머클루트는 블록이 보유하고 있는 거래 내역들의 해시값을 가장 가까운 거래내역끼리 쌍을 지어 해시화하고 쌍을 지을 수 없을 때까지 이 과정을 반복했을 때 얻게 되는 값이다.
자세한 머클루트의 생성 과정은 다음과 같다.
머클루트를 구하기까지 반복하게 되는 이 과정이 토너먼트 대진표의 모양으로 만들어지는데 이것을 머클트리 라고 부른다. 이런 머클트리 구조를 이용하면 거래량이 기하급수적으로 늘어나더라도 특정 거래를 찾는 경로는 단순하다는 이점이 있다. 거래의 건수 N이 증가하더라도 경로를 찾는 경우의 수는 log₂N으로 늘어나기 때문이다.
머클트리를 이용하면 블록에 담긴 거래내역 데이터를 쉽게 검증하고, 필요한 정보를 쉽게 검색할 수 있게 해주는 역할을 해준다
// npm i crypto-js merkle
const merkle = require('merkle')
const SHA256 = require('crypto-js/sha256')
암호화를 위한 crypto-js와 머클루트 생성을 위한 merkle 라이브러리를 설치해준다.
class BlockForms {
constructor(_header, _data) {
this.version;
this.height;
this.timestamp;
this.data;
this.merkleRoot;
this.hash;
}
}
우선은 블록의 모든 속성을 담지 않고 위와 같이 몇가지의 속성만 담아 생성하기로 해보았다. 블록객체를 생성할 때 고려해야할 것은 크게 다음과 같다.
class BlockHeader {
constructor(_height, _previousHash) {
this.version = BlockHeader.getVersion();
this.height = _height;
this.timestamp = BlockHeader.getTimestamp();
this._previousHash = _previousHash || '0'.repeat(64);
}
static getVersion() {
return '1.0.0';
}
static getTimestamp() {
return new Date().getTime();
}
}
이전 블록에게서 전달받아야 하는 height
와 previousHash
값을 인자로 받아 header 인스턴스를 생성해주는 클래스이다.
클래스 내부적으로 (인스턴스 생성 전) 사용하는 getVersion
과 getTimestamp
메소드는 정적메소드로 만들어 생성되는 인스턴스에 불필요한 상속이 되지 않도록 해준다.
class Block {
constructor(_header, _data) {
const merkleroot = Block.getMerkleRoot(_data);
this.version = _header.version;
this.height = _header.height;
this.timestamp = _header.timestamp;
this.previousHash = _header.previousHash;
this.merkleRoot = merkleroot;
this.hash = Block.getHash(_header, merkleroot);
this.data = _data;
}
static getMerkleRoot(_data) {
const merkleTree = merkle('sha256').sync(_data);
const merkleRoot = merkleTree.root();
return merkleRoot;
}
static getHash(_header, _merkleroot) {
// 인자로 전달받은 header객체 + merkleroot값을 string으로 이어준 뒤 해당 string의 해시값 반환
const str = Object.values(_header).join('') + _merkleroot;
return SHA256(str).toString();
}
}
BlockHeader 클래스를 통해 생성한 header 객체와 거래내역이 담긴 data를 인자로 받아 블록을 생성하는 클래스를 만들었다.
헤더속성 중 data값을 필요로 하는 merkle root
속성과 hash
속성은 Header클래스가 아닌 이 Block클래스에서 정적메소드를 이용해 생성해준다.
const data = ['111', '222', '333', '444', '555']
const header = new BlockHeader(0)
const block = new Block(header, data)
console.log(block)
임의의 데이터와 제네시스 블록임을 가정하고 height를 0으로, previousHash는 넣어주지 않은 채로 블록을 생성해보았다.
다음과 같이 출력된다.
블록체인의 여러 개념들 중 '블록'과 관련된 개념을 알아보았다. 블록 생성 시 클래스를 활용하여 최대한 객체지향적으로 만들 수 있도록 하였고, merkle tree와 같은 생소한 개념도 알게 되었다. 블록체인 첫 수업이었는데 아직은 모르는 것도 많고 생소한 것도 너무 많았다. 열심히 공부해야겠다. 🔥