Java 코드로 이해하는 블록체인(Blockchain)

블록체인(Blockchain)

 

블록체인(Blockchain)은 분산 데이터베이스 기술과 암호화 기술(cryptography)을 통해서 거래 정보(transactions)를 보다 투명하고 안전하게 관리할 수 있도록 고안된 공공(public) 거래장부(ledger)입니다. (블록체인의 구현에 따라 private, permissioned 블록체인도 존재합니다). 블록체인은 바라보는 관점에 따라서 일종의 네트워크 개념이기도 하며, 분산(distributed) 저장소로 정의되기도 합니다. 또한, 비즈니스 관점에서 블록체인은 일종의 패러다임에 가깝기도 합니다.

블록체인에 대한 정의와 설명은 인터넷, 또는 시중의 서적에서 쉽게 찾아볼 수 있습니다. 그러나 소프트웨어 엔지니어 입장에서 문장으로 서술된 개념의 설명은 이해하는 데에 있어서 조금 아쉬운 점이 있습니다. 이번 포스트에서는 블록체인의 개념을 Java 클래스로 구현해보면서 블록체인의 데이터 구조, 블록과 블록 간의 연결 등에 대해서 이해해 보도록 하겠습니다. 참고로 이 포스팅에서 예시를 들어 설명하는 블록체인은 비트코인 블록체인이며 블록체인의 다른 구현의 경우 데이터 구조의 세부적인 내용이 조금 다를 수 있습니다.

블록체인의 데이터 구조와 동작 방식에 대해서 코드를 통해 이해하기에 앞서, 블록체인에서 자주 사용되는 몇 가지 핵심 용어들에 대해서 먼저 이해하고 넘어가도록 하겠습니다. 각 용어들은 지금 당장 이해가 되지 않더라도 이어지는 내용들을 살펴보면서 다시 찾아와 확인할 수 있습니다.

  • 블록 (Block) : 블록체인을 구성하는 개별 요소로서 이전 블록에 대한 참조 값과 거래 내역 정보를 가지고 있습니다.
  • 최초블록 (Genesis Block) : 블록체인은 한 방향으로만 확장될 수 있으며 블록체인 상에서 가장 먼저 생성된 블록을 최초블록이라고 말합니다.
  • 블록체인 (Blockchain) : (유효한) 블록들의 연결을 통해서 구성되는 데이터 집합이며 바라보는 관점에 따라 일종의 네트워크이기도 합니다.
  • 채굴 (Mining) : 비트코인 블록체인은 모든 노드가 새로운 블록을 생성하지 못하도록 일종의 제한을 두었습니다. 쉽게 설명하자면 많은 양의 컴퓨팅 자원을 통해서 특정 문제를 풀어야만 새로운 블록을 생성할 수 있고 이를 통해 채굴자(Miner)는 네트워크의 안정성을 검증한 것에 대한 보상(reward)을 받습니다.
  • 작업증명 (Proof-Of-Work) : 채굴과정에서 새로운 블록을 찾기 위해 사용되는 알고리즘 데이터이며 난스(nonce)라고 불리는 임의의 값을 대입함으로써 계산을 수행합니다. 난이도(Difficulty)를 통해 블록의 채굴 속도를 조절할 수 있습니다.
  • 머클트리 (Merkle Tree) : 일종의 이진 트리(Binary Tree)이며, 거래 내역에 대한 요약정보를 해시 형태로 담고 있습니다. 많은 양의 거래 데이터를 보다 효율적으로 검증하기 위해서 사용되는 데이터 구조입니다.
  • 머클 루트(Merkel Root) : 머클트리의 루트 노드입니다.

 

블록의 데이터 구조(Data structure of a Block)

 

블록(Block)은 블록체인을 구성하는 개별 단위이며 각각의 블록들이 서로 연결(chain)되어 블록체인을 구성하게 됩니다. 블록을 구성하는 핵심 요소는 블록체인 연결을 위해 필요한 이전 블록 해시(previousBlockHash) 정보와 거래내역(transactions) 이라고 할 수 있습니다. 각 블록은 블록 헤더에 존재하는 이전 블록 해시(previousBlockHash)의 값을 통해서 부모 블록(Parent Block)을 참조합니다. 이런 식으로 부모 블록을 거슬러 올라가다 보면 더이상 부모를 찾을 수 없는 최초 블록을 만나게 되는데 이를 Genesis Block이라고 부릅니다. 그렇다면, 실제 블록의 데이터 구조는 어떤 모습을 하고 있는지 코드를 통해서 하나씩 살펴보도록 하겠습니다.

먼저, 블록은 '블록 크기', '블록 헤더', '거래 카운터', '거래', 이렇게 4가지 종류의 데이터로 구성됩니다. 이를 Java의 `Block` class로 표현해 보면 <코드 1>과 같이 표현될 수 있습니다.

<코드 1. 블록의 데이터 구조>

1
2
3
4
5
6
7
8
9
/**
 * Created by hosang on 2017. 12. 2..
 */
public class Block {
  private int blockSize;            // Ignore for now.
  private BlockHeader blockHeader;
  private int transactionCount;     // Ignore for now.
  private Object[] transactions;
}

그러면, 각각의 항목들이 무엇을 의미하고 블록체인에서 어떤 역할을 하는지에 대해서 알아보도록 하겠습니다.

  • 블록 크기 : 블록 크기는 이 필드를 제외한 나머지 데이터들의 크기를 바이트 단위로 표현한 값입니다.
  • 블록 헤더 : 블록 헤더는 해당 블록의 메타 데이터를 담고 있는 객체이며, 이어지는 내용에서 자세히 설명하도록 하겠습니다.
  • 거래 카운트 : 거래(transactions)의 수를 저장하는 필드입니다.
  • 거래 : 거래 정보를 담고 있는 컬렉션(collection)입니다.

블록의 데이터 구조에서 '블록 크기(blockSize)'와 '거래 카운트(transactionCount)' 필드는 직관적으로 쉽게 이해할 수 있습니다. 조금 더 자세히 살펴보아야 하는 부분은 '블록 헤더(blockHeader)'와 '거래(transactions)' 필드인데, 먼저 블록 헤더에 대해서 조금 더 자세히 살펴보도록 하겠습니다.

 

블록 헤더

 

앞에서도 설명했지만, 블록 헤더는 블록의 메타 데이터를 담고 있는 객체이며 총 6가지 메타 데이터를 가지고 있습니다. 이러한 블록 헤더를 코드로 표현하면 <코드 2>와 같은 클래스로 표현할 수 있으며, 각 필드가 무엇을 의미하는지 알아보도록 하겠습니다.

<코드 2> 블록 헤더의 데이터 구조

1
2
3
4
5
6
7
8
9
10
11
/**
 * Created by hosang on 2017. 12. 2..
 */
public class BlockHeader {
  private int version;              // Ignore for now.
  private byte[] previousBlockHash;
  private int merkleRootHash;
  private int timestamp;            // Ignore for now.
  private int difficultyTarget;     // Ignore for now.
  private int nonce;                // Ignore for now.
}
  • 버전 : 소프트웨어, 또는 프로토콜 등의 업그레이드를 추적하기 위해 사용되는 버전 정보
  • 이전 블록의 해시 : 블록체인 상의 이전 블록(부모 블록)의 해시값
  • 머클 루트의 해시 : 머클트리의 루트에 대한 해시값
  • 타임스탬프 : 해당 블록의 생성 시각
  • 난이도 목표 : 채굴과정에서 필요한 작업 증명(Proof of Work) 알고리즘의 난이도 목표
  • 난스 : 채굴과정의 작업 증명에서 사용되는 카운터

이렇게 블록 헤더를 이루는 6가지의 필드 가운데 '타임스탬프', '난이도 목표', '난스'는 채굴(mining) 과정과 연관되는 값들로, 이번 포스팅에서는 채굴에 대한 내용을 다루지 않기 때문에 그냥 이러한 필드가 블록 헤더에 포함된다는 정도만 기억하고 넘어가도록 하겠습니다.

 

블록 헤더의 데이터 중 블록체인의 동작 원리를 이해하는 데에 가장 핵심적인 요소는 '이전 블록의 해시' 값과 '머클 루트의 해시' 값입니다.

 

이에 대해서는 실제 블록의 연결이 어떤 방식으로 이루어지는지에 대해서 설명하면서 알아보도록 하겠습니다.

 

블록의 연결

 

블록체인이라는 단어에서도 알 수 있지만, 각각의 블록들은 고유한 값의 참조를 통해 서로 연결이 되어있습니다. 이러한 연결에서 키(Key)가 되는 값이 바로 앞서 블록 헤더의 데이터 구조에서 살펴본 '이전 블록의 해시(previousBlockHash)' 입니다. 좀 더 정확히 표현하자면, 이 값은 이전 블록의 헤더에 대한 해시값입니다. 그 이유는 실제 이전 블록의 해시값(previousBlockHash)을 계산하는 데에 이전 블록의 헤더 정보만이 사용되기 때문입니다. 즉, 블록체인에서 사용되는 '블록 해시'라는 용어는 '블록 헤더 해시'와 같은 의미라고 이해하시면 됩니다.

그렇다면 이 블록 해시는 어떻게 얻어질까요? 비트코인 블록체인에서 이 블록 해시값은 블록 헤더를 SHA-256(Secure Hash Algorithm) 알고리즘으로 두 번 연속 해싱하여 얻어지게 됩니다. 이 때문에"더블(Double) SHA-256" 알고리즘"이라고 부르기도 합니다.

지금까지는 간단한 Java class 정의와 설명을 통해서 블록체인을 구성하는 개별 블록의 데이터 구조가 어떠한 모습을 하고 있는지에 대해서 살펴보았습니다. 그렇다면 이 데이터 구조를 통해서 실제 블록체인이 어떤 방식으로 동작하는지 간단한 코드 구현을 통해서 살펴보도록 하겠습니다.

 

블록체인(Blockchain) 만들기

 

경축! 아무것도 안하여 에스천사게임즈가 새로운 모습으로 재오픈 하였습니다.
어린이용이며, 설치가 필요없는 브라우저 게임입니다.
https://s1004games.com

앞에서 살펴본 블록의 데이터 구조는 실제로 암호화폐, 또는 다른 목적의 블록체인 네트워크를 구성하는 데 필요한 각각의 필드를 설명한 것입니다. 이 포스팅은 코드 작성을 통해 블록체인의 개념과 동작 원리를 이해하는 것이 목적이므로 블록을 구성하는 각 필드들 가운데 몇 가지 필드는 무시하고 코드 작성을 진행하도록 하겠습니다.

먼저, 새로운 블록이 생성되려면 해당 블록이 연결되어야 하는 이전 블록의 해시값을 알아야 합니다. 블록 해시값은 블록 내에 데이터 형태로 저장되어있지 않고 필요한 시점에 네트워크를 구성하는 노드에서 계산됩니다. <코드 3>은 블록 해시의 계산을 노드에서 빠르게 수행하기 위해 Block class에 `getBlockHash` 메서드를 추가하여 앞에서 설명한 "더블 SHA-256" 알고리즘을 적용한 코드의 예시입니다. (개념 이해를 위해서 Byte 배열을 추출하는 메서드를 간소화 하였습니다.)

<코드 3> 더블 SHA-256 알고리즘을 이용한 블록 해시의 계산

1
2
3
4
5
6
7
public String getBlockHash() throws NoSuchAlgorithmException {
  MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
  // Hash twice - Not a K-pop girl group.
  byte[] blockHash = messageDigest.digest(blockHeader.toString().getBytes());
  blockHash = messageDigest.digest(blockHash);
  return new String(blockHash,0,blockHash.length);
}

이제 하나의 거래 내역을 가지고 있는 최초 블록(genesisBlock)을 생성하고 이 블록의 블록 해시값을 출력하는 코드를 만들어 보도록 하겠습니다. <코드 4>는 최초 블록을 생성하는 코드를 간략하게 보여줍니다.

<코드 4> 최초 블록(genesisBlock)의 생성과 블록 해시값 출력

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
/**
 * Created by hosang on 2017. 12. 2..
 */
public class BlockchainDriver {
  List<Block> blockchain = new ArrayList<Block>();
  public static void main(String[] args) throws NoSuchAlgorithmException {
    // Genesis block
    String[] transactions = {"Hosang sent 1k Bitcoins to Zuckerberg."};
    Block genesisBlock = new Block(new BlockHeader(), transactions);
    System.out.println(genesisBlock.getBlockHash());
  }
}

<코드 4>의 내용을 확인해보면 조금 이상한 점을 확인할 수 있습니다. 이상한 점은 바로 거래(transactions)의 내용을 수정하여도 블록 해시의 값이 바뀌지 않는다는 점입니다. 거래 내역(transaction)이 위변조되면 블록 해시의 값이 변경되기 때문에 연결된 블록의 previousHash 값과 일치하지 않아 블록체인의 견고함이 유지됩니다. 하지만, 이 코드에서는 거래의 배열이 변경되어도 블록 해시의 값이 변경되지 않습니다. 그 이유는 개념의 이해를 위해 코드 작성을 최소화하면서 거래내역이 블록 헤더 데이터에 영향을 미치지 않기 때문입니다. 실제 블록체인의 구현에서는 거래의 내역이 블록 헤더에 존재하는 머클 루트 해시(Merkel root hash)값에 영향을 주기 때문에 블록 헤더의 내용이 변경됩니다. 즉, 블록 헤더의 데이터를 통해서 만들어지는 블록 해시의 값도 변경되게 됩니다.

그렇다면, 거래의 내역이 위변조되었을 때 블록 해시의 값이 변경될 수 있도록 코드를 조금 수정해 보도록 하겠습니다. 먼저, 거래 내역이 블록 헤더 데이터에 영향을 미치게 하기 위해서 블록 헤더 생성자(constructor)의 인자로 거래내역을 받은 뒤, 이 데이터를 특정 메서드(someMethod)를 통해 머클 루트 해시값을 계산하도록 코드를 수정하도록 하겠습니다. <코드 5-1>과 <코드 5-2>는 이 내용을 구현한 내용이며 실제 머클 루트 해시값의 계산 로직은 생략되어 있습니다.

<코드 5-1> 블록 헤더의 생성자

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
/**
 * Created by hosang on 2017. 12. 2..
 */
public class BlockHeader {
  private int version;              // Ignore for now.
  private byte[] previousBlockHash;
  private int merkleRootHash;
  private int timestamp;            // Ignore for now.
  private int difficultyTarget;     // Ignore for now.
  private int nonce;                // Ignore for now.
  public BlockHeader(byte[] previousBlockHash, Object[] transactions) {
    this.previousBlockHash = previousBlockHash;
    this.merkleRootHash = this.someMethod(transactions);
  }
  public byte[] toByteArray(){
    String tmpStr = "";
    if(previousBlockHash != null){
      tmpStr += new String(previousBlockHash,0,previousBlockHash.length);
    }
    tmpStr += merkleRootHash;
    return tmpStr.getBytes(StandardCharsets.UTF_8);
  }
  private int someMethod(Object[] transations){
    return Arrays.hashCode(transations);
  }
}

<코드 5-2> 블록 해시 계산 코드의 수정

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
 * Created by hosang on 2017. 12. 2..
 */
public class Block {
  private int blockSize;            // Ignore for now.
  private BlockHeader blockHeader;
  private int transactionCount;    // Ignore for now.
  private Object[] transactions;
  public Block(BlockHeader blockHeader, Object[] transactions){
    this.blockHeader = blockHeader;
    this.transactions = transactions;
  }
  public String getBlockHash() throws NoSuchAlgorithmException {
    MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
    // Hash twice - Not a K-pop girl group.
    byte[] blockHash = messageDigest.digest(blockHeader.toByteArray());
    blockHash = messageDigest.digest(blockHash);
    return new String(blockHash,0,blockHash.length);
  }
}

이제 최초 블록의 생성 및 블록 해시의 출력 코드를 다음 <코드 6>과 같이 수정하면 거래내역의 변경이 실제 블록 해시의 값에 영향을 미친다는 점을 확인할 수 있습니다.

<코드 6> 수정된 최초 블록 생성 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
/**
 * Created by hosang on 2017. 12. 2..
 */
public class BlockchainDriver {
  List<Block> blockchain = new ArrayList<Block>();
  public static void main(String[] args) throws NoSuchAlgorithmException {
    // Genesis block
    String[] transactions = {"Hosang sent 1k Bitcoins to Zuckerberg."};
    Block genesisBlock = new Block(new BlockHeader(null, transactions), transactions);
    System.out.println("Block Hash : " + genesisBlock.getBlockHash());
    // Transaction Forgery
    transactions[0] = "Hosang sent 10k Bitcoins to Zuckerberg.";
    genesisBlock = new Block(new BlockHeader(null, transactions), transactions);
    System.out.println("Block Hash : " + genesisBlock.getBlockHash());
  }
}

이제, 최초 블록의 생성과 블록 해시의 출력 테스트를 완료하였으니 두 개의 블록을 추가로 생성하여 최초 블록에 연결함으로써 블록체인의 동작 방식을 조금 더 살펴보도록 하겠습니다.

<코드 7>은 두 개의 블록을 생성하고 이전 블록의 해시값을 연결된 블록의 `previousBlockHash` 값으로 설정하는 예시입니다. 이 예시에서 우리가 확인할 것은 이전 블록의 거랫값을 변경하였을 때 해당 블록의 해시값 뿐만 아니라 연결된 모든 블록의 해시값이 변경된다는 점입니다. 다만, 두 번째 블록의 거래 내역을 수정하는 경우 최초 블록의 해시값에는 변화가 없습니다.

 

이러한 이유 때문에 블록의 깊이(depth)가 깊어질수록 위변조가 어려워지게 됩니다.

 

<코드 7> 이전 블록의 해시값을 이용한 두, 세 번째 블록의 생성 및 연결

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
/**
 * Created by hosang on 2017. 12. 2..
 */
public class BlockchainDriver {
  List<Block> blockchain = new ArrayList<Block>();
  public static void main(String[] args) throws NoSuchAlgorithmException {
    // Genesis block
    String[] transactions = {"Hosang sent 1k Bitcoins to Zuckerberg."};
    Block genesisBlock = new Block(new BlockHeader(null, transactions), transactions);
    System.out.println("Block Hash : " + genesisBlock.getBlockHash());
    // Second block
    String[] secondTransactions = {"Zuckerberg sent 500 Bitcoins to Hosang."};
    Block secondBlock = new Block(new BlockHeader(genesisBlock.getBlockHash().getBytes(), secondTransactions), secondTransactions);
    System.out.println("Second Block Hash : " + secondBlock.getBlockHash());
    // Third block
    String[] thirdTransactions = {"Hosang sent 500 Bitcoins to Moon."};
    Block thirdBlock = new Block(new BlockHeader(secondBlock.getBlockHash().getBytes(), thirdTransactions), thirdTransactions);
    System.out.println("Third Block Hash : " + thirdBlock.getBlockHash());
  }
}

이 코드를 실제로 실행해보면 앞선 블록의 거래내역(문자열)을 변경하였을 때 해당 블록의 해시값은 물론 연결된 다음 블록의 해시값도 변경되는 것을 확인할 수 있습니다. 이러한 연쇄효과 덕분에 블록체인을 구성하는 블록들 중 하나의 거래내역을 변경하려면 해당 블록과 연결된 모든 블록을 전부 재계산해야만 합니다.

 

이러한 재계산 과정은 엄청난 시간과 컴퓨팅 자원이 필요하기 때문에 블록체인의 무결성을 유지할 수 있습니다.

 

지금까지 간단한 Java 코드를 통해서 블록체인을 구성하는 블록의 데이터 구조와 블록체인의 동작 방식에 대해서 개념 위주로 알아보았습니다. 물론, 실제 비트코인 블록체인을 구현하는 오픈소스 코드는 이 예시 코드와 비할 수 없을 정도로 복잡하고 방대합니다. 하지만 블록체인의 개념을 이해하는 데에 있어서 그림과 텍스트보다는 간단한 실제 코드를 통해서 이해하는 것이 좀 더 명확하게 손에 잡히는 느낌을 받으실 수 있었다면 좋겠습니다.

원본글 : Java 코드로 이해하는 블록체인(Blockchain)

[출처] https://www.popit.kr/java-%EC%BD%94%EB%93%9C%EB%A1%9C-%EC%9D%B4%ED%95%B4%ED%95%98%EB%8A%94-%EB%B8%94%EB%A1%9D%EC%B2%B4%EC%9D%B8blockchain/

 

 

 

본 웹사이트는 광고를 포함하고 있습니다.
광고 클릭에서 발생하는 수익금은 모두 웹사이트 서버의 유지 및 관리, 그리고 기술 콘텐츠 향상을 위해 쓰여집니다.
번호 제목 글쓴이 날짜 조회 수
27 [java][maven] jar 파일 의존성 한번에 다운로드 maven 사용 졸리운_곰 2023.08.24 12
26 Prometheus + Grafana로 Java 애플리케이션 모니터링하기 file 졸리운_곰 2020.12.17 79
25 Blockchain Implementation With Java Code file 졸리운_곰 2019.06.16 108
» Java 코드로 이해하는 블록체인(Blockchain) 졸리운_곰 2019.06.16 126
23 순수 Java Application 코드로 Restful api 호출 졸리운_곰 2018.10.10 244
22 WebDAV 구현을 위한 환경 설정 file 졸리운_곰 2017.09.24 74
21 [Java] Apache Commons HttpClient로 SSL 통신하기 졸리운_곰 2017.03.27 545
20 JSoup를 이용한 HTML 파싱 졸리운_곰 2017.03.04 100
19 jsoup을 활용해서 Java에서 HTML 파싱하는 방법 정리 file 졸리운_곰 2017.03.04 157
18 NSA의 Dataflow 엔진 Apache NiFi 소개와 설치 file 졸리운_곰 2017.01.23 463
17 wordpress-java-integration 자바와 워드프레스 통합 졸리운_곰 2016.12.30 166
16 Create New Posts in Wordpress using Java and XMLRpc 졸리운_곰 2016.11.14 69
15 자바로 POST 방식으로 통신하기, java httppost 클래스를 활용한 예제 졸리운_곰 2016.11.14 444
14 [Java]아파치 HttpClient사용하기 file 졸리운_곰 2016.11.14 104
13 Building a Search Engine With Nutch Solr And Hadoop file 졸리운_곰 2016.04.21 219
12 Nutch and Hadoop Tutorial file 졸리운_곰 2016.04.21 209
11 Latest step by Step Installation guide for dummies: Nutch 0. file 졸리운_곰 2016.04.21 131
10 Nutch 초간단 빌드와 실행 졸리운_곰 2016.04.21 454
9 Nutch로 알아보는 Crawling 구조 - Joinc 졸리운_곰 2016.04.21 352
8 A tiny bittorrent library Java: 자바로 만든 작은 bittorrent 라이브러리 file 졸리운_곰 2016.04.20 242
대표 김성준 주소 : 경기 용인 분당수지 U타워 등록번호 : 142-07-27414
통신판매업 신고 : 제2012-용인수지-0185호 출판업 신고 : 수지구청 제 123호 개인정보보호최고책임자 : 김성준 sjkim70@stechstar.com
대표전화 : 010-4589-2193 [fax] 02-6280-1294 COPYRIGHT(C) stechstar.com ALL RIGHTS RESERVED