블럭 (Block)

블럭 (Block)

블럭(Block)이란?

블럭(Block)은 데이타들의 집합을 포함하는 컨테이너로서, 블럭체인에서의 블럭은 이전 블럭의 해시값을 가지고 있어서 블럭들을 체인으로 연결하게 된다. 이는 블럭체인 내에 있는 임의의 블럭을 위변조하는 것을 방지하기 위한 것으로, 블럭의 데이타를 변경 불가능한 데이타로 만들게 된다.

비트코인 블럭체인의 경우, 각 블럭은 자신의 블럭 해시값을 동적으로 계산하고 이를 별도로 메인 블럭 파일에 저장하지는 않는다. 하지만, 블럭을 체인으로 연결하기 위해 각 블럭은 이전블럭의 해시값을 가지게 되고, 이는 메인 블럭 파일(blocks/blk*.dat)에 저장된다.

비트코인에는 메인 블럭 파일과 더불어 보조적인 역활을 하는 인덱스 파일들(blocks/index/*)이 있으며, 여기에는 각 블럭에 대한 메타 데이타(metadata)를 저장하게 된다. 이 인덱스 파일은 LevelDB를 사용한 DB로서, 각 블럭 해시값에 상응하는 메타 데이타를 가지고 있다. 인덱스 파일에는 특정 블럭이 어느 블럭파일 안의 어디에 위치하는지, 블럭의 헤더 내용은 무엇인지, 트랜잭션 수는 얼마나 되는지 등의 정보를 가지고 있다.

블럭의 구조

블럭은 블럭헤더(Block Header)와 블럭바디(Block Body)로 구성되어 있는데, Block Header는 블럭에 대한 기본적인 정보를 가지고 있으며, Block Body에는 여러 트랜잭션(transaction)들이 순차적으로 저장되어 있다.

블럭의 구조를 C#으로 표현해 보면 아래와 같이 작성할 수 있는데, 블럭크기(BlockSize)는 해당 블럭의 전체 바이트수를 나타내며, 그 뒤에 헤더와 트랜잭션 수, 그리고 블럭 내의 실제 트랜잭션들이 순차적으로 저장된다. 하나의 블럭파일은 여러 개의 블럭들을 순차적으로 저장하는데, 각 블럭의 크기를 알기 위해 BlockSize 가 필요하다.

    public class Block : ISerialize
    {
        public uint BlockSize { get => (uint)this.Serialize().Length; }
        public BlockHeader Header { get; set; }  // 80 bytes
        public VarInt TransactionCount { get; set; }
        public List<Transaction> Transactions { get; set; }
            
        //...생략...
    }

블럭헤더

블럭헤더는 해당 블럭에 대한 기본적인 정보를 가지고 있으며, 특히 이전 블럭의 해시값을 가지고 있어서 블럭들을 체인으로 연결하는 정보를 가지게 된다. 비트코인의 블럭헤더는 80바이트로 되어 있으며, 헤더에는 다음과 같은 데이타가 들어 있다.

    /// 
    /// BlockHeader is 80 bytes long
    /// 
    public class BlockHeader
    {
        public int Version { get; set; }    // 4 bytes
        public Hash HashPrev { get; set; }  // 32 (SHA256)
        public Hash HashMerkleRoot { get; set; }  // 32 (SHA256)
        public uint Timestamp { get; set; }  // 4 bytes
        public uint Bits { get; set; }       // 4 bytes
        public uint Nonce { get; set; }      // 4 bytes
        
        //...생략...
    }

블럭헤더의 버전은 블럭의 종류를 구분하기 위한 버전 정보이다. 블럭헤더의 두번째와 세번째 필드가 블럭을 체인으로 만드는데 필요한 주요 데이타이다. 두번째 필드인 이전 블럭의 해시는 말 그대로 현재 블럭의 바로 앞에 있는 블럭의 블럭 해시값을 의미한다. 모든 블럭은 이전 블럭의 해시값을 가지고 있어서, 블럭들이 체인으로 연결되게 한다.

세번째 필드인 MerkleRoot는 블럭바디에 있는 트랜잭션들 전체를 요약한 해시값으로서 현재 블럭의 트랜잭션들이 변경되지 못하게 하는 역활을 한다. 만약 트랜잭션이 변경되면, (Merkle Tree의 해시값이 변경되어) MerkleRoot 해시값이 변경되기 때문에 블럭헤더의 내용이 변경되게 된다.

블럭헤더를 SHA256로 해싱한 결과가 그 블럭의 해시값이 된다. 즉, 블럭해시는 현재 블럭 내의 트랜잭션이 변경되면 블럭 해시값이 변경되게 되고, 또한 이전블럭의 해시값이 변경되어도 역시 블럭 해시값이 변경되게 된다. 블럭체인 내의 블럭 해시값들은 이전 블럭으로 계속 연결되어 있는데, 계속 이전 블럭을 따라 가다 보면, 최초의 블럭인 Genesis 블럭까지 이동하게 되고, Genesis 블럭의 해시값은 고정되어 있으므로, 결국 모든 블럭들이 체인으로 연결되어 중간에 변경되는 것을 불허하게 된다.

블럭헤더의 Timestamp는 그 블럭이 생성된 시간이고, Bits는 블럭체인 채굴(Mining)과 연관된 것으로 마이닝에 의해 생성된 블럭해시값이 Bits에 의해 지정되는 Target 값보다 작을 경우 마이닝에 성공하게 된다. Bits는 개념적으로 해시값 앞에 얼마만큼의 비트들이 0로 채워져야 하는지를 표시한다.

Nonce는 보통 한번만 사용하는 임의의 값을 의미하는데, 마이닝에서 이 Nonce값을 순차적으로 증가시면서 해시값을 다시 계산하면서 블럭 해시값을 구하는데 사용된다. 블럭헤더의 나머지 필드들은 고정되어 있으므로, Target 조건에 맞는 블럭 해시값을 구하기 위해서는 블럭 헤더 안에 무언가 값이 변경되어야 한다. 채굴(Mining)은 이 Nonce 값을 변경해가면 다른 해시값을 계산하면서 Target 조건에 맞는 해시값을 찾는 과정이다.