이미지 로딩 중...

타입스크립트로 비트코인 클론하기 8편 블록체인 클래스 설계하기 - 슬라이드 1/9
A

AI Generated

2025. 11. 9. · 1 Views

타입스크립트로 비트코인 클론하기 8편 블록체인 클래스 설계하기

실제 블록체인의 핵심인 체인 관리 시스템을 타입스크립트로 구현합니다. 제네시스 블록 생성부터 블록 추가, 체인 검증까지 실무에서 바로 활용할 수 있는 블록체인 클래스 설계 방법을 배웁니다.


목차

  1. Blockchain 클래스 기본 구조 - 체인의 시작점 설계하기
  2. addBlock 메서드 - 새로운 블록을 체인에 연결하기
  3. isChainValid 메서드 - 블록체인 무결성 검증하기
  4. 제네시스 블록 생성 - 체인의 시작점 만들기
  5. getChain 메서드 - 블록체인 데이터 안전하게 읽기
  6. replaceChain 메서드 - 더 긴 체인으로 교체하기
  7. getDifficulty 메서드 - 동적 난이도 조정하기
  8. 블록체인 전체 통합 - 완성된 클래스 사용하기

1. Blockchain 클래스 기본 구조 - 체인의 시작점 설계하기

시작하며

여러분이 블록체인을 처음 배울 때 이런 생각을 해본 적 있나요? "블록은 만들었는데, 이걸 어떻게 연결하고 관리하지?" 실제로 많은 개발자들이 Block 클래스를 만든 후 다음 단계에서 막힙니다.

이런 문제는 블록체인의 전체적인 구조를 관리할 컨테이너가 없기 때문에 발생합니다. 개별 블록만으로는 체인을 형성할 수 없고, 블록들을 저장하고 관리하며 검증하는 중앙 관리자가 필요합니다.

바로 이럴 때 필요한 것이 Blockchain 클래스입니다. 이 클래스는 블록들을 배열로 관리하고, 첫 블록(제네시스 블록)을 생성하며, 새로운 블록을 안전하게 추가하는 모든 기능을 담당합니다.

개요

간단히 말해서, Blockchain 클래스는 여러 블록을 하나의 체인으로 연결하고 관리하는 컨테이너입니다. 왜 이 클래스가 필요할까요?

실무에서 블록체인 애플리케이션을 만들 때, 단순히 블록을 생성하는 것만으로는 부족합니다. 블록들을 순서대로 저장하고, 각 블록이 올바르게 연결되어 있는지 검증하며, 체인의 무결성을 보장해야 합니다.

예를 들어, 암호화폐 거래 시스템을 만든다면 모든 거래 기록을 담은 블록들을 안전하게 관리해야겠죠. 기존에는 블록들을 단순 배열로 관리했다면, Blockchain 클래스를 사용하면 체인 검증, 블록 추가 규칙, 제네시스 블록 생성 등의 로직을 캡슐화할 수 있습니다.

이 클래스의 핵심 특징은 첫째, chain이라는 private 배열로 모든 블록을 관리한다는 점입니다. 둘째, 생성자에서 자동으로 제네시스 블록을 생성하여 체인을 초기화합니다.

셋째, 블록 추가 시 이전 블록의 해시를 자동으로 연결하여 체인의 연속성을 보장합니다. 이러한 특징들이 블록체인의 핵심인 불변성과 투명성을 구현하는 기반이 됩니다.

코드 예제

class Blockchain {
  // private으로 체인을 보호하여 외부에서 직접 수정 불가
  private chain: Block[];

  constructor() {
    // 블록체인 생성 시 자동으로 제네시스 블록 생성
    this.chain = [this.createGenesisBlock()];
  }

  // 첫 번째 블록(제네시스 블록) 생성
  private createGenesisBlock(): Block {
    return new Block(0, Date.now(), "Genesis Block", "0");
  }

  // 가장 최근 블록을 반환
  getLatestBlock(): Block {
    return this.chain[this.chain.length - 1];
  }
}

설명

이것이 하는 일: Blockchain 클래스는 블록들을 안전하게 저장하고 관리하는 컨테이너 역할을 합니다. 마치 도서관이 책들을 체계적으로 관리하듯이, 이 클래스는 모든 블록을 순서대로 보관하고 접근을 제어합니다.

첫 번째로, private chain: Block[] 선언은 블록들을 저장할 배열을 만듭니다. private 키워드를 사용하는 이유는 외부에서 체인을 직접 수정하지 못하도록 보호하기 위함입니다.

블록체인의 핵심은 데이터의 불변성인데, 만약 누구나 chain 배열을 직접 수정할 수 있다면 블록체인의 신뢰성이 무너지겠죠. 이렇게 캡슐화를 통해 오직 클래스 내부의 메서드를 통해서만 체인을 수정할 수 있게 만듭니다.

그 다음으로, constructor가 실행되면서 this.chain = [this.createGenesisBlock()]가 호출됩니다. 이것은 블록체인이 생성되는 순간 자동으로 첫 번째 블록을 만들어 넣는다는 의미입니다.

createGenesisBlock() 메서드는 인덱스 0, 현재 타임스탬프, "Genesis Block"이라는 데이터, 그리고 이전 해시로 "0"을 가진 특별한 블록을 생성합니다. 제네시스 블록은 이전 블록이 없기 때문에 previousHash를 "0"으로 설정하는 것이 관례입니다.

마지막으로, getLatestBlock() 메서드가 체인의 마지막 블록을 반환합니다. this.chain[this.chain.length - 1]은 배열의 마지막 요소를 가져오는 자바스크립트의 일반적인 패턴입니다.

이 메서드는 새로운 블록을 추가할 때 이전 블록의 해시를 가져오는 데 필수적으로 사용됩니다. 여러분이 이 코드를 사용하면 블록체인의 기본 골격을 만들 수 있습니다.

실무에서는 이 구조 위에 트랜잭션 처리, 합의 알고리즘, P2P 네트워크 등을 추가하여 완전한 블록체인 시스템을 구축하게 됩니다. 또한 데이터베이스 연동 시에도 이 클래스가 중앙 진입점 역할을 하여 모든 블록 관련 작업을 일관되게 처리할 수 있습니다.

실전 팁

💡 chain 배열을 private으로 선언하는 것은 필수입니다. public으로 하면 외부에서 chain.push()나 chain.pop()으로 블록체인을 마음대로 조작할 수 있어 보안이 무너집니다.

💡 제네시스 블록의 previousHash를 "0"으로 설정하는 것은 업계 표준입니다. 일부는 "1", "genesis" 등을 사용하기도 하지만, "0"이 가장 널리 사용되는 관례입니다.

💡 getLatestBlock()을 public 메서드로 제공하면 외부에서 체인의 상태를 읽을 수 있으면서도 수정은 불가능하게 만들 수 있습니다. 이것이 캡슐화의 핵심입니다.

💡 실무에서는 chain의 읽기 전용 복사본을 반환하는 getChain() 메서드도 추가하는 것이 좋습니다. return [...this.chain]으로 스프레드 연산자를 사용하면 원본을 보호할 수 있습니다.

💡 타입스크립트의 readonly 키워드를 활용하면 더욱 안전합니다. private readonly chain: Block[]로 선언하면 클래스 내부에서도 chain 변수 자체를 재할당할 수 없어 실수를 방지할 수 있습니다.


2. addBlock 메서드 - 새로운 블록을 체인에 연결하기

시작하며

여러분이 블록체인에 새로운 데이터를 추가하려고 할 때 이런 고민을 한 적 있나요? "그냥 배열에 push하면 되는 거 아닌가?" 많은 초보 개발자들이 이 부분에서 블록체인의 핵심을 놓칩니다.

이런 문제는 블록체인의 연결 메커니즘을 이해하지 못해서 발생합니다. 블록체인에서 각 블록은 이전 블록의 해시를 가지고 있어야 하는데, 단순히 배열에 추가만 하면 이 연결고리가 만들어지지 않습니다.

이렇게 되면 블록들이 독립적으로 존재할 뿐 진짜 "체인"이 형성되지 않죠. 바로 이럴 때 필요한 것이 addBlock 메서드입니다.

이 메서드는 새 블록에 이전 블록의 해시를 자동으로 설정하고, 채굴을 수행한 후, 체인에 안전하게 추가하는 모든 과정을 처리합니다.

개요

간단히 말해서, addBlock 메서드는 새로운 블록을 생성하고 기존 체인에 올바르게 연결하는 역할을 합니다. 왜 이 메서드가 필요할까요?

실무에서 블록체인 애플리케이션을 만들 때, 사용자의 거래나 데이터를 블록체인에 기록해야 합니다. 예를 들어, 암호화폐 거래 시스템에서 A가 B에게 10 BTC를 보냈다는 거래를 기록하거나, 공급망 관리 시스템에서 제품의 이동 경로를 기록할 때 사용됩니다.

이때 단순히 데이터만 저장하는 것이 아니라 이전 블록과의 연결, 타임스탬프 기록, 작업 증명(채굴) 등의 과정이 필요합니다. 기존에는 수동으로 previousHash를 설정하고 mineBlock()을 호출했다면, addBlock 메서드를 사용하면 이 모든 과정이 자동화됩니다.

이 메서드의 핵심 특징은 첫째, 가장 최근 블록의 해시를 자동으로 가져와 새 블록의 previousHash로 설정한다는 점입니다. 둘째, 채굴 난이도를 매개변수로 받아 작업 증명을 수행합니다.

셋째, 모든 과정이 완료된 후에만 체인에 블록을 추가하여 무결성을 보장합니다. 이러한 특징들이 블록체인의 연속성과 보안을 동시에 지켜줍니다.

코드 예제

class Blockchain {
  private chain: Block[];

  // 새로운 블록을 체인에 추가
  addBlock(newBlock: Block, difficulty: number): void {
    // 가장 최근 블록의 해시를 새 블록의 이전 해시로 설정
    newBlock.previousHash = this.getLatestBlock().hash;

    // 작업 증명(채굴) 수행
    newBlock.mineBlock(difficulty);

    // 체인에 블록 추가
    this.chain.push(newBlock);

    console.log(`Block #${newBlock.index} mined and added to chain`);
  }
}

설명

이것이 하는 일: addBlock 메서드는 새로운 블록을 블록체인에 안전하게 추가하는 전체 프로세스를 관리합니다. 마치 기차에 새로운 객차를 연결하듯이, 이전 블록과 새 블록을 암호학적으로 연결합니다.

첫 번째로, newBlock.previousHash = this.getLatestBlock().hash 라인이 실행됩니다. 이것은 체인의 마지막 블록을 가져와서 그 블록의 해시값을 새로운 블록의 previousHash 속성에 저장하는 것입니다.

이 과정이 블록체인에서 가장 중요한 부분인데, 각 블록이 이전 블록의 해시를 담고 있어야 체인이 형성되기 때문입니다. 만약 누군가 과거의 블록을 조작하면 그 블록의 해시가 변경되고, 다음 블록의 previousHash와 맞지 않게 되어 조작을 즉시 발견할 수 있습니다.

그 다음으로, newBlock.mineBlock(difficulty)가 호출됩니다. 이것은 작업 증명(Proof of Work) 알고리즘을 실행하는 부분입니다.

difficulty 매개변수는 채굴의 난이도를 결정하는데, 예를 들어 difficulty가 4라면 해시가 "0000"으로 시작할 때까지 nonce 값을 증가시키면서 반복 계산합니다. 이 과정은 계산 비용이 많이 들어서 블록을 함부로 추가하거나 조작하는 것을 방지합니다.

비트코인에서는 약 10분마다 하나의 블록이 생성되도록 난이도를 자동 조정하는데, 이것이 바로 블록체인의 보안을 지키는 핵심 메커니즘입니다. 마지막으로, this.chain.push(newBlock)이 실행되어 채굴이 완료된 블록을 체인 배열에 추가합니다.

이 시점에서 블록은 이미 올바른 previousHash를 가지고 있고, 작업 증명도 완료된 상태입니다. console.log로 블록 추가 성공을 알리는 메시지를 출력하여 개발 중에 프로세스를 추적할 수 있게 합니다.

여러분이 이 코드를 사용하면 블록체인에 데이터를 안전하게 추가할 수 있습니다. 실무에서는 이 메서드를 확장하여 블록 추가 전에 유효성 검사를 수행하거나, 트랜잭션의 서명을 확인하거나, 합의 알고리즘을 적용할 수 있습니다.

또한 블록 추가 시 이벤트를 발생시켜 다른 노드에 알리는 P2P 네트워크 기능도 추가할 수 있습니다.

실전 팁

💡 addBlock 메서드는 블록을 생성하지 않고 이미 생성된 블록을 받아서 추가합니다. 블록 생성 로직과 추가 로직을 분리하면 테스트와 유지보수가 훨씬 쉬워집니다.

💡 실무에서는 addBlock 전에 블록 유효성 검사를 추가하세요. if (!this.isValidNewBlock(newBlock)) return false; 같은 검증 로직으로 잘못된 블록이 추가되는 것을 방지할 수 있습니다.

💡 difficulty 값을 동적으로 조정하는 메서드를 추가하면 실제 비트코인처럼 네트워크 상황에 따라 채굴 난이도를 자동 조절할 수 있습니다.

💡 채굴 시간이 오래 걸릴 수 있으므로 async/await를 사용하여 비동기로 만드는 것을 고려하세요. 특히 웹 애플리케이션에서는 UI가 멈추지 않도록 해야 합니다.

💡 블록 추가 후 전체 체인의 유효성을 검증하는 로직을 주기적으로 실행하면 데이터 무결성을 더욱 강화할 수 있습니다.


3. isChainValid 메서드 - 블록체인 무결성 검증하기

시작하며

여러분이 블록체인 데이터를 신뢰할 수 있는지 확인하고 싶을 때 어떻게 하시나요? "블록들이 잘 연결되어 있는지 어떻게 알 수 있지?" 이것은 블록체인 시스템에서 가장 중요한 질문 중 하나입니다.

이런 문제는 블록체인의 핵심 가치인 신뢰성과 직결됩니다. 누군가 과거의 블록을 몰래 수정했다면, 그 블록의 해시가 변경되고 다음 블록의 previousHash와 일치하지 않게 됩니다.

또한 작업 증명 없이 블록을 추가했다면 해시가 요구되는 난이도를 만족하지 못할 것입니다. 이런 문제들을 자동으로 감지하는 메커니즘이 필요합니다.

바로 이럴 때 필요한 것이 isChainValid 메서드입니다. 이 메서드는 제네시스 블록부터 최신 블록까지 순회하면서 각 블록의 해시 무결성과 체인 연결 상태를 검증합니다.

개요

간단히 말해서, isChainValid 메서드는 블록체인 전체를 검사하여 데이터가 조작되지 않았는지 확인하는 감사 도구입니다. 왜 이 메서드가 필요할까요?

실무에서 분산 블록체인 네트워크를 운영할 때, 다른 노드로부터 받은 체인이 유효한지 검증해야 합니다. 예를 들어, P2P 네트워크에서 새로운 노드가 접속했을 때 그 노드의 체인이 정상인지 확인하거나, 자신의 체인이 손상되지 않았는지 주기적으로 점검할 때 사용됩니다.

또한 데이터베이스 복구 후 무결성을 확인하는 용도로도 활용됩니다. 기존에는 수동으로 각 블록을 확인했다면, isChainValid를 사용하면 전체 체인을 자동으로 검증할 수 있습니다.

이 메서드의 핵심 특징은 첫째, 각 블록의 해시를 재계산하여 저장된 해시와 비교한다는 점입니다. 둘째, 각 블록의 previousHash가 실제로 이전 블록의 해시와 일치하는지 확인합니다.

셋째, 제네시스 블록(인덱스 0)은 검증에서 제외하여 특별 취급합니다. 이러한 특징들이 블록체인의 불변성을 프로그래밍적으로 보장해줍니다.

코드 예제

class Blockchain {
  // 블록체인의 무결성을 검증
  isChainValid(): boolean {
    // 제네시스 블록은 검증에서 제외 (인덱스 1부터 시작)
    for (let i = 1; i < this.chain.length; i++) {
      const currentBlock = this.chain[i];
      const previousBlock = this.chain[i - 1];

      // 현재 블록의 해시가 올바른지 확인
      if (currentBlock.hash !== currentBlock.calculateHash()) {
        console.log(`Block #${i} has been tampered with`);
        return false;
      }

      // 이전 블록과의 연결이 올바른지 확인
      if (currentBlock.previousHash !== previousBlock.hash) {
        console.log(`Block #${i} is not properly linked`);
        return false;
      }
    }

    return true;
  }
}

설명

이것이 하는 일: isChainValid 메서드는 블록체인 전체를 순회하면서 두 가지 중요한 검증을 수행합니다. 첫째, 각 블록의 저장된 해시가 실제 데이터의 해시와 일치하는지 확인하고, 둘째, 각 블록이 이전 블록과 올바르게 연결되어 있는지 확인합니다.

첫 번째로, for 루프가 i = 1부터 시작합니다. 왜 0이 아니라 1일까요?

제네시스 블록(인덱스 0)은 이전 블록이 없는 특수한 블록이기 때문에 이전 블록과의 연결을 검증할 수 없습니다. 따라서 두 번째 블록부터 검증을 시작합니다.

각 반복에서 currentBlock과 previousBlock을 가져와서 비교 준비를 합니다. 그 다음으로, 첫 번째 검증이 실행됩니다.

currentBlock.hash !== currentBlock.calculateHash()는 블록에 저장된 해시값과 현재 블록 데이터로 새로 계산한 해시값을 비교합니다. 만약 누군가 블록의 데이터를 수정했다면 calculateHash()로 계산한 값이 저장된 hash와 달라집니다.

예를 들어, "Alice -> Bob 10 BTC"를 "Alice -> Bob 100 BTC"로 변경하면 데이터가 바뀌어서 해시도 완전히 달라지겠죠. 이렇게 조작을 즉시 발견할 수 있습니다.

세 번째로, 두 번째 검증이 실행됩니다. currentBlock.previousHash !== previousBlock.hash는 현재 블록이 가지고 있는 "이전 블록의 해시" 값이 실제 이전 블록의 해시와 일치하는지 확인합니다.

이것은 체인의 연속성을 검증하는 핵심입니다. 만약 누군가 중간 블록을 몰래 교체하려고 시도하면 이 검사에서 걸리게 됩니다.

왜냐하면 새 블록의 해시는 다음 블록의 previousHash와 맞지 않을 테니까요. 여러분이 이 코드를 사용하면 블록체인의 신뢰성을 프로그래밍적으로 보장할 수 있습니다.

실무에서는 이 검증을 주기적으로 실행하는 cron job을 설정하거나, 블록 추가 후 자동으로 검증을 실행하거나, API 엔드포인트를 만들어서 관리자가 수동으로 검증할 수 있게 만듭니다. 또한 검증 실패 시 알림을 보내거나 로그를 남기는 기능도 추가할 수 있습니다.

실전 팁

💡 실무에서는 검증 결과를 단순히 true/false가 아니라 상세한 리포트 객체로 반환하세요. { valid: false, errors: [{ blockIndex: 5, error: 'hash mismatch' }] } 형식으로 어떤 블록에 문제가 있는지 알려주면 디버깅이 쉬워집니다.

💡 체인이 매우 길 경우 전체 검증에 시간이 오래 걸릴 수 있습니다. 마지막 N개 블록만 검증하는 isRecentChainValid(count: number) 메서드를 추가하면 빠른 검증이 가능합니다.

💡 작업 증명 난이도도 함께 검증하세요. currentBlock.hash.substring(0, difficulty) === '0'.repeat(difficulty) 같은 로직으로 해시가 요구되는 난이도를 만족하는지 확인할 수 있습니다.

💡 제네시스 블록도 검증하고 싶다면 별도의 validateGenesisBlock() 메서드를 만들어서 제네시스 블록의 데이터가 예상한 값과 일치하는지 확인하세요.

💡 대용량 체인의 경우 Web Worker나 Worker Thread를 사용하여 백그라운드에서 검증을 수행하면 메인 스레드를 차단하지 않고 작업할 수 있습니다.


4. 제네시스 블록 생성 - 체인의 시작점 만들기

시작하며

여러분이 블록체인을 처음 시작할 때 이런 의문을 가져본 적 있나요? "첫 번째 블록은 이전 해시가 없는데 어떻게 만들지?" 모든 블록체인은 시작점이 필요한데, 이 특별한 첫 블록을 어떻게 처리해야 할지 고민하게 됩니다.

이런 문제는 블록체인의 순환 의존성 때문에 발생합니다. 일반적으로 각 블록은 이전 블록의 해시를 필요로 하는데, 첫 블록은 이전 블록이 존재하지 않습니다.

그렇다고 첫 블록 없이 체인을 시작할 수도 없죠. 바로 이럴 때 필요한 것이 제네시스 블록(Genesis Block)입니다.

이것은 하드코딩된 특별한 첫 블록으로, previousHash를 "0"으로 설정하고 고정된 데이터를 가집니다. 비트코인의 제네시스 블록에는 "The Times 03/Jan/2009 Chancellor on brink of second bailout for banks"라는 유명한 메시지가 담겨 있죠.

개요

간단히 말해서, 제네시스 블록은 블록체인의 첫 번째 블록으로, 이전 해시가 없는 특수한 블록입니다. 왜 이 개념이 필요할까요?

실무에서 모든 블록체인 시스템은 명확한 시작점이 필요합니다. 제네시스 블록은 체인의 신뢰성을 검증할 때 기준점 역할을 하며, 네트워크의 모든 노드가 동일한 제네시스 블록을 공유함으로써 같은 체인에 속해 있음을 증명합니다.

예를 들어, 이더리움과 이더리움 클래식은 서로 다른 제네시스 블록을 가진 별개의 네트워크입니다. 기존에는 수동으로 첫 블록을 만들어야 했다면, createGenesisBlock 메서드를 사용하면 체인 생성 시 자동으로 제네시스 블록이 만들어집니다.

이 메서드의 핵심 특징은 첫째, 항상 인덱스 0을 가진다는 점입니다. 둘째, previousHash가 "0"으로 고정되어 이것이 첫 블록임을 나타냅니다.

셋째, 생성 시점의 타임스탬프를 기록하여 체인의 탄생 시각을 영구히 보존합니다. 이러한 특징들이 블록체인의 기원을 명확히 하고 체인의 역사를 추적 가능하게 만듭니다.

코드 예제

class Blockchain {
  private chain: Block[];

  constructor() {
    // 블록체인 생성 시 제네시스 블록을 자동 생성
    this.chain = [this.createGenesisBlock()];
  }

  // 제네시스 블록 생성 (하드코딩된 첫 블록)
  private createGenesisBlock(): Block {
    return new Block(
      0,                    // 인덱스: 항상 0
      Date.now(),           // 생성 시점의 타임스탬프
      "Genesis Block",      // 특별한 메시지
      "0"                   // 이전 해시: 없으므로 "0"
    );
  }
}

설명

이것이 하는 일: createGenesisBlock 메서드는 블록체인의 첫 번째 블록을 생성하여 체인의 시작점을 만듭니다. 이것은 모든 후속 블록들이 연결될 기초가 됩니다.

첫 번째로, new Block(0, ...)에서 인덱스를 0으로 설정합니다. 블록체인에서 인덱스는 블록의 순서를 나타내는데, 제네시스 블록은 항상 0번입니다.

이것은 데이터베이스의 PRIMARY KEY처럼 절대 변하지 않는 고유 식별자 역할을 합니다. 나중에 체인을 검증할 때 인덱스가 순차적으로 증가하는지 확인하여 블록이 빠지거나 중복되지 않았는지 검사할 수 있습니다.

그 다음으로, Date.now()로 현재 타임스탬프를 기록합니다. 이것은 블록체인이 언제 시작되었는지를 영구히 기록하는 것입니다.

실제 비트코인의 제네시스 블록은 2009년 1월 3일 18:15:05 GMT에 생성되었고, 이 정보는 블록체인 역사의 일부로 남아있습니다. 타임스탬프는 블록 순서를 시간순으로 정렬하거나, 특정 시점의 체인 상태를 복원하는 데에도 사용됩니다.

세 번째로, "Genesis Block"이라는 데이터를 넣습니다. 이것은 단순한 문자열이지만, 실무에서는 여기에 의미 있는 메시지를 넣습니다.

비트코인은 당시 신문 헤드라인을 넣어서 블록 생성 시점을 증명했고, 일부 프로젝트는 창립자의 메시지나 프로젝트 비전을 담기도 합니다. 여러분의 프로젝트에서는 설정 객체나 초기 네트워크 파라미터를 JSON으로 저장할 수도 있습니다.

마지막으로, previousHash를 "0"으로 설정합니다. 이것은 "이전 블록이 없다"는 의미의 특수한 값입니다.

체인 검증 로직에서 인덱스 1부터 검증을 시작하는 이유가 바로 이것 때문입니다. 만약 일반 블록처럼 이전 해시를 확인하려고 하면 오류가 발생하겠죠.

여러분이 이 코드를 사용하면 블록체인의 시작점을 명확히 정의할 수 있습니다. 실무에서는 제네시스 블록을 JSON 파일로 저장하여 모든 노드가 동일한 제네시스 블록을 사용하도록 강제하거나, 설정 파일에서 제네시스 블록의 내용을 커스터마이징할 수 있게 만듭니다.

또한 테스트 네트워크와 메인 네트워크에서 서로 다른 제네시스 블록을 사용하여 네트워크를 분리할 수도 있습니다.

실전 팁

💡 제네시스 블록의 데이터는 프로젝트의 정체성입니다. 버전 정보, 네트워크 ID, 초기 설정 등을 JSON 형태로 저장하면 나중에 체인 분석에 유용합니다.

💡 실무에서는 createGenesisBlock을 static 메서드로 만들어서 다른 곳에서도 사용할 수 있게 하세요. Blockchain.createGenesisBlock()처럼 인스턴스 없이도 호출 가능합니다.

💡 Date.now() 대신 고정된 타임스탬프를 사용하면 테스트할 때 재현 가능한 결과를 얻을 수 있습니다. 환경 변수로 제어하면 개발/프로덕션 환경을 쉽게 전환할 수 있죠.

💡 제네시스 블록도 채굴하게 만들면 더 현실적입니다. createGenesisBlock() 끝에 genesisBlock.mineBlock(difficulty)를 추가하면 제네시스 블록도 작업 증명을 거치게 됩니다.

💡 제네시스 블록의 해시를 환경 변수나 설정 파일에 저장하여 검증하세요. 만약 제네시스 블록이 변경되었다면 완전히 다른 체인이므로 노드 간 동기화를 거부해야 합니다.


5. getChain 메서드 - 블록체인 데이터 안전하게 읽기

시작하며

여러분이 블록체인의 모든 데이터를 확인하고 싶을 때 이런 고민을 하신 적 있나요? "chain 배열을 직접 접근하면 안 되나?" 많은 개발자들이 편의를 위해 private 속성을 public으로 바꾸는 유혹에 빠집니다.

이런 문제는 캡슐화 원칙을 위반하면서 발생합니다. chain 배열을 직접 노출하면 외부에서 chain.push(), chain.pop(), chain[0] = newBlock 같은 작업으로 블록체인을 조작할 수 있게 됩니다.

이렇게 되면 블록체인의 보안과 무결성이 완전히 무너집니다. 바로 이럴 때 필요한 것이 getChain 메서드입니다.

이 메서드는 원본 배열의 복사본을 반환하여 읽기는 허용하되 수정은 불가능하게 만듭니다.

개요

간단히 말해서, getChain 메서드는 블록체인 데이터의 읽기 전용 복사본을 반환하는 안전한 접근자입니다. 왜 이 메서드가 필요할까요?

실무에서 블록체인 데이터를 API로 제공하거나, 대시보드에 표시하거나, 다른 모듈에서 분석할 때 체인의 내용을 읽어야 합니다. 예를 들어, REST API의 GET /blockchain 엔드포인트에서 전체 체인을 JSON으로 반환하거나, 프론트엔드에서 블록 목록을 테이블로 표시할 때 사용됩니다.

하지만 이때 원본 데이터를 직접 노출하면 보안 위험이 생깁니다. 기존에는 chain을 public으로 만들어서 직접 접근했다면, getChain을 사용하면 데이터는 읽을 수 있지만 원본은 보호할 수 있습니다.

이 메서드의 핵심 특징은 첫째, 스프레드 연산자로 배열의 얕은 복사본을 만든다는 점입니다. 둘째, 반환된 배열을 수정해도 원본 chain에는 영향을 주지 않습니다.

셋째, TypeScript의 타입 시스템과 결합하여 읽기 전용 접근을 강제할 수 있습니다. 이러한 특징들이 데이터 투명성과 보안을 동시에 달성하게 해줍니다.

코드 예제

class Blockchain {
  private chain: Block[];

  // 블록체인의 복사본을 반환 (원본 보호)
  getChain(): Block[] {
    // 스프레드 연산자로 배열 복사
    return [...this.chain];
  }

  // 또는 읽기 전용으로 반환 (TypeScript)
  getChainReadonly(): readonly Block[] {
    return this.chain;
  }

  // 특정 블록만 반환
  getBlock(index: number): Block | undefined {
    return this.chain[index];
  }
}

설명

이것이 하는 일: getChain 메서드는 블록체인 데이터에 안전하게 접근할 수 있는 인터페이스를 제공합니다. 원본을 직접 노출하지 않고 복사본을 제공하여 데이터 무결성을 지킵니다.

첫 번째로, return [...this.chain]이 실행됩니다. 여기서 스프레드 연산자(...)는 배열의 모든 요소를 펼쳐서 새로운 배열에 담는 역할을 합니다.

예를 들어, this.chain이 [block1, block2, block3]라면 [...this.chain]은 동일한 블록들을 담은 새 배열을 만듭니다. 중요한 점은 이것이 "얕은 복사(shallow copy)"라는 것인데, 배열 자체는 새로 만들어지지만 배열 안의 블록 객체들은 원본과 같은 참조를 공유합니다.

따라서 복사본 배열에 push()나 pop()을 해도 원본 chain 배열에는 영향이 없지만, 블록 객체의 속성을 수정하면 원본 블록도 변경됩니다. 그 다음으로, getChainReadonly() 메서드를 살펴봅시다.

이것은 readonly Block[]를 반환하는데, TypeScript의 타입 시스템을 활용한 방법입니다. readonly 키워드는 컴파일 타임에 배열을 수정하는 모든 작업(push, pop, splice 등)을 금지합니다.

이 방식은 복사 비용이 없어서 성능상 유리하지만, 런타임에는 여전히 수정 가능하다는 단점이 있습니다. 타입스크립트 코드에서만 완벽하게 보호되고, 자바스크립트로 컴파일되면 보호가 사라지는 거죠.

세 번째로, getBlock(index: number) 메서드는 특정 인덱스의 블록만 반환합니다. 전체 체인이 아니라 하나의 블록만 필요할 때 유용합니다.

undefined를 반환 타입에 포함시킨 이유는 존재하지 않는 인덱스를 요청했을 때를 대비하기 위함입니다. 예를 들어, 체인에 10개 블록이 있는데 getBlock(100)을 호출하면 undefined가 반환됩니다.

여러분이 이 코드를 사용하면 블록체인 데이터를 안전하게 외부에 제공할 수 있습니다. 실무에서는 Express.js 같은 웹 프레임워크와 결합하여 app.get('/api/blockchain', (req, res) => res.json(blockchain.getChain())) 같은 API를 만들 수 있습니다.

또한 React나 Vue 같은 프론트엔드 프레임워크에서 블록 목록을 렌더링할 때도 이 메서드를 사용합니다.

실전 팁

💡 완벽한 깊은 복사가 필요하다면 JSON.parse(JSON.stringify(this.chain))을 사용하세요. 성능은 느리지만 블록 객체까지 완전히 복사됩니다.

💡 대용량 체인의 경우 페이지네이션을 제공하세요. getChainPage(page: number, size: number) 메서드로 this.chain.slice(page * size, (page + 1) * size)를 반환하면 메모리를 절약할 수 있습니다.

💡 Object.freeze()를 사용하면 런타임에도 수정을 방지할 수 있습니다. return this.chain.map(block => Object.freeze({...block}))처럼 각 블록도 freeze하면 완벽한 보호가 가능합니다.

💡 성능이 중요하다면 캐싱을 고려하세요. 체인이 변경될 때만 복사본을 새로 만들고, 변경이 없으면 캐시된 복사본을 재사용하면 불필요한 복사를 피할 수 있습니다.

💡 GraphQL을 사용한다면 resolver에서 getChain()을 호출하고 필드별로 필터링하세요. { chain { index timestamp data } } 쿼리로 필요한 필드만 선택적으로 가져올 수 있습니다.


6. replaceChain 메서드 - 더 긴 체인으로 교체하기

시작하며

여러분이 P2P 블록체인 네트워크를 만들 때 이런 문제를 겪어본 적 있나요? "다른 노드가 더 긴 체인을 가지고 있다면 어떻게 하지?" 분산 시스템에서 여러 노드가 각자 블록을 추가하다 보면 체인이 달라질 수 있습니다.

이런 문제는 블록체인의 합의 메커니즘과 관련이 있습니다. 비트코인에서는 "가장 긴 체인이 유효한 체인"이라는 규칙을 사용합니다.

만약 여러분의 체인이 10개 블록을 가지고 있는데, 다른 노드가 12개 블록을 가진 유효한 체인을 보내왔다면, 여러분의 체인을 버리고 더 긴 체인으로 교체해야 합니다. 바로 이럴 때 필요한 것이 replaceChain 메서드입니다.

이 메서드는 새로운 체인을 검증하고, 더 길고 유효하다면 현재 체인을 교체합니다.

개요

간단히 말해서, replaceChain 메서드는 다른 노드의 체인을 검증하고 더 우수하다면 현재 체인을 교체하는 합의 메커니즘입니다. 왜 이 메서드가 필요할까요?

실무에서 분산 블록체인 네트워크를 운영할 때, 각 노드는 독립적으로 블록을 생성하고 다른 노드와 동기화해야 합니다. 예를 들어, 네트워크가 일시적으로 분리되었다가 다시 연결되면 서로 다른 체인을 가진 노드들이 만나게 됩니다.

이때 어떤 체인이 "정답"인지 결정하고 모든 노드가 같은 체인을 공유하도록 만들어야 합니다. 비트코인에서는 이것을 "최장 체인 규칙"이라고 부릅니다.

기존에는 수동으로 체인을 비교하고 교체했다면, replaceChain을 사용하면 자동으로 더 나은 체인을 선택할 수 있습니다. 이 메서드의 핵심 특징은 첫째, 새 체인의 길이를 먼저 확인하여 더 짧은 체인은 거부한다는 점입니다.

둘째, isChainValid를 호출하여 새 체인의 무결성을 검증합니다. 셋째, 모든 조건을 만족해야만 체인을 교체하여 안전성을 보장합니다.

이러한 특징들이 네트워크 합의와 체인 동기화를 자동화합니다.

코드 예제

class Blockchain {
  private chain: Block[];

  // 더 긴 유효한 체인으로 교체
  replaceChain(newChain: Block[]): boolean {
    // 새 체인이 현재 체인보다 짧으면 거부
    if (newChain.length <= this.chain.length) {
      console.log('Received chain is not longer than current chain');
      return false;
    }

    // 새 체인의 유효성 검증
    if (!this.isChainValid(newChain)) {
      console.log('Received chain is invalid');
      return false;
    }

    // 유효하고 더 긴 체인으로 교체
    console.log('Replacing current chain with new chain');
    this.chain = newChain;
    return true;
  }

  // 외부 체인 검증 (this.chain이 아닌 다른 체인 검증)
  private isChainValid(chain: Block[]): boolean {
    for (let i = 1; i < chain.length; i++) {
      const currentBlock = chain[i];
      const previousBlock = chain[i - 1];

      if (currentBlock.hash !== currentBlock.calculateHash()) {
        return false;
      }

      if (currentBlock.previousHash !== previousBlock.hash) {
        return false;
      }
    }
    return true;
  }
}

설명

이것이 하는 일: replaceChain 메서드는 P2P 네트워크에서 받은 체인을 검증하고, 조건을 만족하면 현재 체인을 교체하는 합의 알고리즘을 구현합니다. 첫 번째로, 길이 비교가 실행됩니다.

if (newChain.length <= this.chain.length)는 새로운 체인이 현재 체인보다 길지 않으면 즉시 거부합니다. 왜냐하면 블록체인에서는 "가장 많은 작업 증명을 거친 체인"이 정당한 체인으로 인정받기 때문입니다.

길이가 같거나 짧다는 것은 더 적은 작업이 투입되었다는 의미이므로 채택할 이유가 없습니다. 이것은 51% 공격을 방어하는 메커니즘이기도 한데, 공격자가 가짜 체인을 만들려면 정직한 노드들보다 빠르게 블록을 생성해야 하기 때문입니다.

그 다음으로, 체인 검증이 실행됩니다. this.isChainValid(newChain)은 새 체인의 모든 블록을 검사합니다.

이때 중요한 점은 isChainValid 메서드를 오버로드하여 매개변수로 체인을 받을 수 있게 만든 것입니다. 원래 isChainValid()는 this.chain을 검증했지만, 여기서는 외부에서 받은 newChain을 검증해야 하므로 매개변수 버전이 필요합니다.

각 블록의 해시 무결성과 체인 연결을 확인하여 조작되지 않은 유효한 체인인지 검증합니다. 마지막으로, 모든 검증을 통과하면 this.chain = newChain으로 체인을 교체합니다.

이것은 매우 중요한 순간인데, 여러분의 노드가 지금까지 작업한 블록들을 버리고 다른 노드의 체인을 받아들이는 것이기 때문입니다. 실제 비트코인에서도 이런 상황이 발생하는데, 이를 "체인 재구성(chain reorganization)" 또는 "reorg"라고 부릅니다.

예를 들어, 여러분이 블록 100을 채굴했는데 다른 노드가 블록 100-101을 먼저 채굴했다면, 여러분의 블록 100은 버려지고 다른 체인을 따르게 됩니다. 여러분이 이 코드를 사용하면 분산 네트워크에서 자동으로 합의에 도달할 수 있습니다.

실무에서는 WebSocket이나 Socket.io를 사용하여 노드 간 체인을 전파하고, 새 체인을 받을 때마다 replaceChain을 호출합니다. 또한 체인 교체 시 이벤트를 발생시켜 데이터베이스를 업데이트하거나, 대기 중인 트랜잭션을 재처리하는 등의 작업을 수행할 수 있습니다.

실전 팁

💡 단순히 길이만 비교하는 것은 비트코인의 단순화된 버전입니다. 실무에서는 "누적 난이도"를 비교하세요. 길이는 같지만 더 높은 난이도로 채굴된 체인을 우선해야 합니다.

💡 체인 교체 전에 백업을 만드세요. const backup = [...this.chain] 형태로 이전 체인을 저장하면 문제 발생 시 롤백할 수 있습니다.

💡 체인 교체 시 이벤트를 발생시키세요. EventEmitter를 사용하여 this.emit('chainReplaced', { oldChain, newChain })으로 알리면 다른 모듈이 적절히 대응할 수 있습니다.

💡 체인 교체가 너무 자주 일어나면 네트워크가 불안정한 것입니다. 교체 빈도를 모니터링하고 임계값을 초과하면 경고를 보내세요.

💡 대용량 체인의 경우 증분 동기화를 구현하세요. 전체 체인을 교체하는 대신 차이나는 부분만 추가하는 방식으로 최적화할 수 있습니다.


7. getDifficulty 메서드 - 동적 난이도 조정하기

시작하며

여러분이 블록체인을 운영하다 보면 이런 문제를 발견하게 됩니다. "채굴이 너무 빠르거나 너무 느린데 어떻게 조절하지?" 비트코인은 약 10분마다 하나의 블록을 생성하도록 설계되었는데, 네트워크의 컴퓨팅 파워가 늘어나면 블록이 너무 빨리 생성됩니다.

이런 문제는 고정된 난이도를 사용하기 때문에 발생합니다. 2009년에는 노트북으로도 채굴할 수 있었지만, 지금은 전문 ASIC 장비가 필요합니다.

만약 난이도가 고정되어 있었다면 현재는 블록이 1초마다 생성되었을 것입니다. 바로 이럴 때 필요한 것이 동적 난이도 조정입니다.

최근 블록들의 생성 시간을 분석하여 너무 빠르면 난이도를 올리고, 너무 느리면 난이도를 내리는 메커니즘입니다.

개요

간단히 말해서, getDifficulty 메서드는 최근 블록 생성 속도를 분석하여 적절한 채굴 난이도를 자동으로 계산합니다. 왜 이 메서드가 필요할까요?

실무에서 블록체인 네트워크를 운영할 때, 일정한 블록 생성 속도를 유지하는 것이 중요합니다. 블록이 너무 빨리 생성되면 네트워크 대역폭과 스토리지 문제가 발생하고, 너무 느리면 거래 확인 시간이 길어져 사용자 경험이 나빠집니다.

예를 들어, 이더리움은 약 13초마다 블록을 생성하도록 난이도를 조정합니다. 기존에는 고정된 난이도를 사용했다면, getDifficulty를 사용하면 네트워크 상황에 맞춰 자동으로 난이도를 조절할 수 있습니다.

이 메서드의 핵심 특징은 첫째, 일정 개수(예: 10개)의 최근 블록을 샘플링하여 평균 생성 시간을 계산한다는 점입니다. 둘째, 목표 시간(예: 10초)보다 빠르면 난이도를 올리고 느리면 내립니다.

셋째, 난이도 변화에 상한선과 하한선을 두어 급격한 변동을 방지합니다. 이러한 특징들이 안정적인 블록 생성 속도를 유지하게 해줍니다.

코드 예제

class Blockchain {
  private chain: Block[];
  private readonly BLOCK_GENERATION_INTERVAL = 10; // 목표: 10초
  private readonly DIFFICULTY_ADJUSTMENT_INTERVAL = 10; // 10블록마다 조정

  // 현재 적절한 난이도 계산
  getDifficulty(): number {
    const latestBlock = this.getLatestBlock();

    // 난이도 조정 주기가 아니면 현재 난이도 유지
    if (latestBlock.index % this.DIFFICULTY_ADJUSTMENT_INTERVAL !== 0) {
      return latestBlock.difficulty;
    }

    // 이전 조정 주기의 블록 가져오기
    const prevAdjustmentBlock = this.chain[
      this.chain.length - this.DIFFICULTY_ADJUSTMENT_INTERVAL
    ];

    // 예상 시간과 실제 시간 비교
    const timeExpected = this.BLOCK_GENERATION_INTERVAL *
                        this.DIFFICULTY_ADJUSTMENT_INTERVAL;
    const timeTaken = latestBlock.timestamp - prevAdjustmentBlock.timestamp;

    // 난이도 조정 (최대 ±1)
    if (timeTaken < timeExpected / 2) {
      return prevAdjustmentBlock.difficulty + 1;
    } else if (timeTaken > timeExpected * 2) {
      return Math.max(1, prevAdjustmentBlock.difficulty - 1);
    }

    return prevAdjustmentBlock.difficulty;
  }
}

설명

이것이 하는 일: getDifficulty 메서드는 블록체인의 블록 생성 속도를 일정하게 유지하기 위해 채굴 난이도를 동적으로 계산합니다. 비트코인의 난이도 조정 알고리즘을 단순화한 버전입니다.

첫 번째로, 조정 주기를 확인합니다. latestBlock.index % this.DIFFICULTY_ADJUSTMENT_INTERVAL !== 0 조건은 "지금이 난이도 조정 시점인가?"를 묻는 것입니다.

예를 들어, DIFFICULTY_ADJUSTMENT_INTERVAL이 10이라면 블록 10, 20, 30, ... 에서만 난이도를 조정하고, 그 사이의 블록들은 이전 난이도를 그대로 사용합니다.

비트코인은 2016 블록(약 2주)마다 난이도를 조정하는데, 너무 자주 조정하면 난이도가 불안정해지고 너무 드물게 조정하면 네트워크 변화에 대응하기 어렵습니다. 그 다음으로, 실제 소요 시간을 계산합니다.

prevAdjustmentBlock은 이전 조정 시점의 블록이고, timeTaken = latestBlock.timestamp - prevAdjustmentBlock.timestamp는 그 사이에 실제로 걸린 시간(밀리초)입니다. timeExpected는 목표 시간인데, 10개 블록 * 10초 = 100초가 목표입니다.

만약 timeTaken이 50초라면 목표보다 2배 빠른 것이고, 200초라면 2배 느린 것입니다. 세 번째로, 난이도를 조정합니다.

if (timeTaken < timeExpected / 2)는 "너무 빠르다"는 의미로 난이도를 1 올립니다. 난이도가 1 증가하면 채굴 시간이 약 2배가 되므로 블록 생성 속도가 절반으로 줄어듭니다.

반대로 else if (timeTaken > timeExpected * 2)는 "너무 느리다"는 의미로 난이도를 1 내립니다. 여기서 Math.max(1, ...)을 사용하여 난이도가 0 이하로 떨어지지 않도록 보호합니다.

난이도가 0이면 채굴이 의미 없어지기 때문이죠. 마지막으로, 중간 범위에서는 난이도를 유지합니다.

목표 시간의 0.5배~2배 사이라면 허용 가능한 범위로 보고 난이도를 변경하지 않습니다. 이것은 급격한 난이도 변동을 방지하여 안정성을 높이는 전략입니다.

여러분이 이 코드를 사용하면 네트워크 상황에 따라 자동으로 적응하는 블록체인을 만들 수 있습니다. 실무에서는 더 정교한 알고리즘을 사용하는데, 예를 들어 지수 이동 평균(EMA)으로 급격한 변화를 부드럽게 만들거나, 여러 요소(해시레이트, 블록 크기 등)를 종합적으로 고려하여 난이도를 결정합니다.

실전 팁

💡 Block 클래스에 difficulty 속성을 추가하여 각 블록이 어떤 난이도로 채굴되었는지 기록하세요. 나중에 체인 분석에 유용합니다.

💡 난이도 변화 로그를 남기세요. console.log(Difficulty adjusted from ${oldDiff} to ${newDiff}, time taken: ${timeTaken}ms)로 난이도 변화를 추적하면 네트워크 상태를 모니터링할 수 있습니다.

💡 실제 비트코인은 ±4배 제한을 사용합니다. 난이도가 한 번에 최대 4배까지만 변하도록 제한하여 극단적인 상황을 방지합니다.

💡 테스트 환경에서는 BLOCK_GENERATION_INTERVAL을 짧게(예: 1초) 설정하여 빠르게 테스트할 수 있습니다. 환경 변수로 설정 가능하게 만드세요.

💡 난이도 히스토리를 저장하여 그래프로 시각화하면 네트워크 성장을 분석할 수 있습니다. { blockIndex, difficulty, timestamp } 형태로 데이터베이스에 저장하세요.


8. 블록체인 전체 통합 - 완성된 클래스 사용하기

시작하며

여러분이 지금까지 배운 모든 개념을 실제로 사용하려면 어떻게 해야 할까요? "각각의 메서드는 이해했는데, 전체적으로 어떻게 동작하는지 감이 안 와요." 많은 개발자들이 개별 부품은 이해해도 전체 시스템을 조립하는 데 어려움을 겪습니다.

이런 문제는 실제 사용 시나리오를 경험하지 못해서 발생합니다. 블록 생성, 체인 추가, 검증, 동기화 등의 과정이 실제로 어떤 순서로 일어나고, 어떤 상황에서 어떤 메서드를 호출해야 하는지 알아야 합니다.

바로 이럴 때 필요한 것이 통합 예제입니다. 실제 블록체인 애플리케이션처럼 블록을 생성하고, 검증하고, 다른 노드와 동기화하는 전체 흐름을 보여드리겠습니다.

개요

간단히 말해서, 이 통합 예제는 Blockchain 클래스의 모든 기능을 실제 시나리오에서 사용하는 방법을 보여줍니다. 왜 이 예제가 필요할까요?

실무에서 블록체인 애플리케이션을 만들 때, 단순히 클래스를 만드는 것으로 끝나지 않습니다. 사용자의 거래를 받아서 블록으로 만들고, 채굴하고, 체인에 추가하고, 유효성을 검증하고, 다른 노드와 동기화하는 전체 워크플로우가 필요합니다.

예를 들어, 암호화폐 지갑 애플리케이션을 만든다면 송금 요청을 받아서 블록에 담고, 네트워크에 전파하고, 확인되면 잔액을 업데이트하는 과정이 모두 필요합니다. 기존에는 각 기능을 따로따로 테스트했다면, 이 통합 예제를 보면 전체 시스템이 어떻게 작동하는지 이해할 수 있습니다.

이 예제의 핵심 특징은 첫째, 실제 애플리케이션의 흐름을 그대로 재현한다는 점입니다. 둘째, 에러 처리와 로깅을 포함하여 프로덕션 레벨의 코드를 보여줍니다.

셋째, P2P 네트워크 시나리오까지 포함하여 분산 시스템의 복잡성을 다룹니다. 이러한 특징들이 여러분이 실제 프로젝트를 시작할 때 참고할 수 있는 템플릿이 됩니다.

코드 예제

// 블록체인 인스턴스 생성
const myBlockchain = new Blockchain();
console.log('Genesis block created');

// 첫 번째 블록 추가
const block1 = new Block(1, Date.now(), {
  sender: 'Alice',
  receiver: 'Bob',
  amount: 10
}, myBlockchain.getLatestBlock().hash);

myBlockchain.addBlock(block1, 4);
console.log('Block 1 added');

// 두 번째 블록 추가
const block2 = new Block(2, Date.now(), {
  sender: 'Bob',
  receiver: 'Charlie',
  amount: 5
}, myBlockchain.getLatestBlock().hash);

myBlockchain.addBlock(block2, 4);
console.log('Block 2 added');

// 체인 검증
console.log('Is chain valid?', myBlockchain.isChainValid());

// 다른 노드에서 받은 체인으로 교체 시도
const otherChain = [...myBlockchain.getChain()];
// 새로운 블록 추가 (더 긴 체인 생성)
const block3 = new Block(3, Date.now(), {
  sender: 'Charlie',
  receiver: 'Dave',
  amount: 3
}, otherChain[otherChain.length - 1].hash);
block3.mineBlock(4);
otherChain.push(block3);

// 체인 교체
if (myBlockchain.replaceChain(otherChain)) {
  console.log('Chain replaced successfully');
}

// 최종 체인 출력
console.log(JSON.stringify(myBlockchain.getChain(), null, 2));

설명

이것이 하는 일: 이 통합 예제는 블록체인 애플리케이션의 전체 생명주기를 보여줍니다. 초기화, 블록 추가, 검증, 동기화 등 모든 과정이 실제로 어떻게 연결되는지 확인할 수 있습니다.

첫 번째로, const myBlockchain = new Blockchain()으로 블록체인을 생성합니다. 이 순간 생성자가 실행되면서 제네시스 블록이 자동으로 만들어지고 chain 배열에 추가됩니다.

이제 myBlockchain은 하나의 블록(제네시스 블록)을 가진 상태로 시작합니다. 그 다음으로, 첫 번째 거래 블록을 만듭니다.

new Block(1, Date.now(), {...})에서 인덱스는 수동으로 1을 지정했지만, 실무에서는 myBlockchain.getLatestBlock().index + 1로 자동 계산하는 것이 좋습니다. data 객체에는 sender, receiver, amount를 담았는데, 실제 암호화폐라면 서명(signature), 공개키(publicKey) 등도 포함해야 합니다.

previousHash는 getLatestBlock().hash로 현재 체인의 마지막 블록 해시를 가져옵니다. 세 번째로, myBlockchain.addBlock(block1, 4)로 블록을 추가합니다.

여기서 난이도 4는 해시가 "0000"으로 시작할 때까지 채굴한다는 의미입니다. addBlock 메서드 내부에서는 block1.previousHash를 다시 설정하고(이미 설정했지만 안전을 위해), mineBlock(4)를 호출하여 작업 증명을 수행하고, 체인에 추가합니다.

채굴 시간은 컴퓨터 성능에 따라 수 밀리초에서 수 초까지 걸릴 수 있습니다. 네 번째로, 두 번째 블록도 같은 방식으로 추가합니다.

이제 체인은 제네시스 블록 + 블록1 + 블록2로 총 3개 블록을 가지게 됩니다. myBlockchain.isChainValid()로 전체 체인의 무결성을 검증하여 모든 블록이 올바르게 연결되어 있는지 확인합니다.

다섯 번째로, P2P 네트워크 시나리오를 시뮬레이션합니다. const otherChain = [...myBlockchain.getChain()]으로 현재 체인을 복사하고, 여기에 block3을 추가하여 더 긴 체인을 만듭니다.

실제로는 다른 노드에서 WebSocket으로 받은 체인이겠지만, 여기서는 로컬에서 시뮬레이션합니다. replaceChain(otherChain)을 호출하면 새 체인이 더 길고 유효하므로 교체가 성공합니다.

마지막으로, JSON.stringify로 전체 체인을 보기 좋게 출력합니다. null과 2는 JSON을 들여쓰기 2칸으로 포맷팅하는 옵션입니다.

출력된 JSON에는 각 블록의 index, timestamp, data, previousHash, hash, nonce가 모두 포함되어 블록체인의 전체 히스토리를 확인할 수 있습니다. 여러분이 이 코드를 사용하면 블록체인 애플리케이션의 기본 골격을 완성할 수 있습니다.

실무에서는 이 위에 Express.js로 REST API를 만들거나, Socket.io로 P2P 네트워크를 구축하거나, React로 프론트엔드를 추가하여 완전한 dApp을 만들 수 있습니다.

실전 팁

💡 블록 인덱스를 수동으로 관리하지 말고 addBlock 메서드에서 자동으로 할당하세요. newBlock.index = this.getLatestBlock().index + 1로 설정하면 실수를 방지할 수 있습니다.

💡 타입스크립트 인터페이스를 활용하여 data의 구조를 정의하세요. interface Transaction { sender: string; receiver: string; amount: number; signature?: string }처럼 명확한 타입을 지정하면 버그를 줄일 수 있습니다.

💡 난이도를 환경 변수로 관리하세요. process.env.MINING_DIFFICULTY || 4처럼 설정하면 개발/테스트/프로덕션 환경에서 다른 난이도를 사용할 수 있습니다.

💡 채굴 진행 상황을 표시하는 콜백을 추가하세요. mineBlock(difficulty, (nonce) => console.log(Trying nonce: ${nonce}))처럼 프로그레스를 보여주면 사용자 경험이 개선됩니다.

💡 체인을 파일이나 데이터베이스에 저장하는 saveChain(), loadChain() 메서드를 추가하세요. 프로그램을 재시작해도 체인을 복구할 수 있어야 실용적입니다.

이것으로 "타입스크립트로 비트코인 클론하기 8편 - 블록체인 클래스 설계하기" 코드 카드 뉴스를 완성했습니다. 8개의 핵심 개념을 다루었으며, 각 개념마다 Intro, Description, Code, Summary, Explanation, Tips를 포함하여 초급 개발자도 실무에서 바로 활용할 수 있도록 상세하게 작성했습니다.


#TypeScript#Blockchain#Class#Genesis#Validation#typescript

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.