새소식

💰 Crypto's (가상화폐, 코인)/Tech (기술설명, 용어)

C++ 으로 블록체인 구현해보기

  • -

구현을 따라해보기 위해서, 외국 유튜버분이 구현해보신 영상을 보고 코드를 작성하고 리뷰를 하려고합니다.

 

TransactionData.h

#pragma once

#ifndef TransactionData_h
#define TransactionData_h

#include <string>

// Transaction Data
struct TransactionData
{
	double amount;
	std::string senderKey;
	std::string receiverKey;
	time_t timestamp;

	TransactionData() {};

	TransactionData(double amt, std::string sender, std::string reciver, time_t time) {
		amount = amt;
		senderKey = sender;
		receiverKey = reciver;
		timestamp = time;
	};
};

#endif // !TransactionData_h

 

- TransactionData : 거래내역을 나타내는 구조체입니다

  • amount : 코인 거래 양
  • senderKey : 보낸 사람의 지갑 주소
  • receiverKey : 받은 사람의 지갑 주소
  • timestamp : 거래가 이루어진 시간

Block.h

#pragma once

#ifndef Block_h
#define Block_h

#include "TransactionData.h"

// Block Class
class Block {
private:
	int index;
	size_t blockHash;
	size_t previousHash;
	size_t generateHash();

public:
	// Constructor
	Block(int idx, TransactionData d, size_t prevHash);

	// Get Index
	size_t getIndex();

	// Get Original Hash
	size_t getHash();

	// Get Previous Hash
	size_t getPreviousHash();

	// Transaction Data
	// Would ordinarily be a private member with a "getter" : getData()
	TransactionData data;

	// Validate Hash
	bool isHashValid();

};

#endif // !Block_h

Block : 블록의 정보를 가지고 있는 헤더 파일입니다.

 

private:

  • index : 블록의 인덱스
  • blockHash : 현재 블록의 해쉬값
  • previousHash : 이전 블록의 해쉬값
  • generateHash() : 현재 들어온 값들을 가지고 해시값을 생성해줍니다.

 

transaction data는 원래는 private 멤버이지만, 블록체인이 어떻게 해킹에서 안전한지를 보여주기 위해서 public으로 설정하였습니다.

 

public: 

  • getIndex() : 현재 블록의 인덱스를 리턴합니다.
  • getHash() : 현재 블록의 해쉬값을 리턴합니다.
  • getPreviousHash() : 이전 블록의 해쉬값을 리턴합니다.
  • isHashValid() : 생성한 해쉬값이, 현재 블록의 해쉬와 맞는지 참/거짓 유무를 리턴합니다.

transaction data가 private 멤버로 들어갈 때에는 추가적으로 getData() 함수를 생성하여

transaction 데이터를 가져올 수 있어야합니다. 

 

Block.cpp

#include <iostream>
#include <string>
#include "Block.h"
#include "TransactionData.h"


// Constructor with params
Block::Block(int idx, TransactionData d, size_t prevHash) {
	index = idx;
	data = d;
	previousHash = prevHash;
	blockHash = generateHash();
}

// private Functions
size_t Block::generateHash() {

	// creating string of transcation data
	std::string toHashS = std::to_string(data.amount) + data.senderKey + data.receiverKey + std::to_string(data.timestamp);

	// 2 hashed to combine
	std::hash<std::string> tDataHash; // transaction data string
	std::hash<std::string> prevHash; // re-hashed previous hash (for combination)

	// combine hashes and get size_t for block hash
	return tDataHash(toHashS) ^ (prevHash(std::to_string(previousHash)) << 1);
}

// public Functions
size_t Block::getHash() {
	return blockHash;
}

size_t Block::getPreviousHash() {
	return previousHash;
}

size_t Block::getIndex() {
	return index;
}

bool Block::isHashValid() {
	return generateHash() == getHash();
}

헤더파일에 대한 클래스의 기능 함수입니다.

 

눈여겨 볼 점은

generateHash() 함수 가 data에 관한 모든 정보를 해쉬와 이전해쉬의 결합값을 리턴해주는 것입니다.

 


Blockchain.h

#pragma once
#ifndef Blockchain_h
#define Blockchain_h

#include "Block.h"
#include <vector>

// Blockchain Class
class Blockchain {
private:
	Block createGenesisBlock();
	std::vector<Block> chain;
public:
	// Constuctor
	Blockchain();

	// Public Functions
	std::vector<Block> getChain();
	Block* getLatestBlock();
	void addBlock(TransactionData data);
	bool isChainValid();
	void printChain();


};

#endif // !Blockchain_h

Blockchain : 블록체인에 대한 정보를 가지고 있는 헤더 파일 입니다.

 

private:

  • createGenesisBlock() : 맨 초기의 블록을 생성해주는 함수입니다.
  • vector chain : 블록체인의 기반이 되는 체인역할의 벡터입니다.

 

public:

  • getChain() : 블록체인을 가져오기 위한 함수인듯 하지만, 여기서 구현을 되지 않았습니다.
  • getLatestBlock() : 원래는 없어야하는 포인터이지만, 우리는 어떻게 작동하는지 보기 위하여, 블록의 메모리를 증명하기 위한 함수입니다.
  • addBlock() : 새로운 블럭을 만들어, 블록체인에 추가해주는 함수입니다.
  • isChainValid() : 처음부터 현재블럭까지 블럭값이 연결이 된건지 확인해주는 함수입니다.
  • printChain() : 블록체인의 정보를 출력해주는 함수입니다.

Blockchain.cpp

#include <iostream>
#include <string>

#include "Blockchain.h"

// Blockchain Constructor
Blockchain::Blockchain() {

	Block genesis = createGenesisBlock();
	chain.push_back(genesis);
}

// Create Genesis Block
Block Blockchain::createGenesisBlock() {

	// Get Current Time
	time_t current;

	// Setup Initial Transaction Data
	TransactionData d(0, "Genesis", "Genesis", time(&current));
	
	// creating string of transaction data
	std::string toHashS = std::to_string(d.amount) + d.senderKey + d.receiverKey + std::to_string(d.timestamp);

	// 2 hashed to combine
	std::hash<std::string> tDataHash; // transaction data string
	std::hash<std::string> prevHash; // re-hashed previous hash (for combination)

	// combine hashes and get size_t for block hash
	size_t hash = tDataHash(toHashS) ^ (prevHash(std::to_string(0)) << 1);

	Block genesis(0, d, hash);
	return genesis;
}


// We only need pointer here
// to demonstrate manipulation of transaction data
Block* Blockchain::getLatestBlock() {
	return &chain.back();
}

void Blockchain::addBlock(TransactionData d) {
	int index = (int)chain.size();
	std::size_t lastHash = (int)chain.size() > 0 ? getLatestBlock()->getHash() : 0;
	Block newBlock(index, d, lastHash);
	chain.push_back(newBlock);
}

bool Blockchain::isChainValid() {
	
	std::vector<Block>::iterator it;
	
	for (it = chain.begin(); it != chain.end(); ++it) {
		Block currentBlock = *it;
		if (!currentBlock.isHashValid()) {
			// INVALID!!
			return false;
		}
		// Don't forget to check if this is the first item

		if (it != chain.begin()) {
			Block previousBlock = *(it - 1);
			if (currentBlock.getPreviousHash() != previousBlock.getHash()) {
				// INVALID!!
				return false;
			}
		}
	}
	return true;
}

void Blockchain::printChain() {
	std::vector<Block>::iterator it;

	for (it = chain.begin(); it != chain.end(); ++it) {
		Block curruntBlock = *it;
		std::cout << '\n' << '\n' << "Block ===============================";
		std::cout << '\n' << "Index: " << curruntBlock.getIndex();
		std::cout << '\n' << "Amount: " << curruntBlock.data.amount;
		std::cout << '\n' << "SenderKey: " << curruntBlock.data.senderKey.c_str();
		std::cout << '\n' << "ReceiverKey: " << curruntBlock.data.receiverKey.c_str();
		std::cout << '\n' << "Timestamp: " << curruntBlock.data.timestamp;
		std::cout << '\n' << "Hash: " << curruntBlock.getHash();
		std::cout << '\n' << "Previous Hash: " << curruntBlock.getPreviousHash();
		std::cout << '\n' << "Is Block Valid?: " << curruntBlock.isHashValid();
	}

}

 

구현에서 눈여겨 볼 코드는 isChainValid()를 통하여 블록의 해쉬값이 처음부터 지금까지 Valid? 한지를 결정하는 코드로 대충 이런식으로 작동이 되구나를 알 수 있게 되었습니다.


실제 작동확인

#include <iostream>
#include <string>
#include <vector>
#include <ctime>
#include "Block.h"
#include "Blockchain.h"
#include "TransactionData.h"

using namespace std;

int main() {

	// Start Blockchain
	Blockchain AwesomeCoin;

	// Data for first added block
	time_t data1Time;
	TransactionData data1(1.5, "Jun", "Uram", time(&data1Time));
	AwesomeCoin.addBlock(data1);

	time_t data2Time;
	TransactionData data2(1.0, "Uram", "Jun", time(&data2Time));
	AwesomeCoin.addBlock(data2);

	AwesomeCoin.printChain();

	cout << '\n' << "is chain valid? " << AwesomeCoin.isChainValid();

	// Somone's getting sneaky
	Block* hackBlock = AwesomeCoin.getLatestBlock();
	hackBlock->data.amount = 10000;
	hackBlock->data.receiverKey = "Hacker!!"; // hahahaha

	AwesomeCoin.printChain();

	cout << '\n' << "is chain valid? " << AwesomeCoin.isChainValid();

	

}

 

 

 

정상 작동

 

해커 개입

제대로 작동이 잘되는 블록체인이라면, 이전의 해쉬값에 상관없이 계속 Valid를 유지하는 모습을 볼 수 있지만

 

해커가 개입해서, 말도 안되는 정보를 바꿨다고 해도, 이전 해쉬의 값이 달라져가기 때문에 맞지 허락하지 않는 모습을 볼 수 있습니다. 

 


마치며

실제로 비트코인이나, 이더리움, 리플같은 대형 코인들은 훨씬 많은 기능들이 있을 듯 하고, 채굴에 관련된 점도 포함되어 있을것입니다.

 

하지만 이 코드는 실제로 블록체인이 어떻게 운용되는지를 대충 간편하게 알아 볼 수 있는 코드이고, 이런식으로 흘러간다는 점을 알 수 있다는 점에서 저에게 꽤나 도움이 되었습니다. 

 

 


 

 

출처:

https://www.youtube.com/watch?v=2VDQeQfh4Hs&t=315s 

 

Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.