- 프로젝트 구조 설정:
src
폴더에main/java
폴더 구조가 있는지 확인합니다. Gradle 프로젝트의 표준 구조입니다.- 없다면
src
폴더 우클릭 → New → Directory 선택하여main/java
경로를 생성합니다.
- 패키지 구조 생성:
src/main/java
폴더 우클릭 → New → Package- 패키지 이름으로
com.javachain
입력하고 생성 - 같은 방식으로
com.javachain.core
와com.javachain.network
패키지도 생성합니다.
- 클래스 파일 생성:
- 각 패키지에 해당하는 클래스 파일을 생성합니다.
com.javachain.core
패키지에 Block.java, Transaction.java, Blockchain.java 생성com.javachain.network
패키지에 Node.java, NodeConnection.java, Message.java, MessageType.java 생성com.javachain
패키지에 Main.java 생성
- 모든 클래스 파일에 올바른 패키지 선언이 있는지 확인:
- 예를 들어, Block.java의 첫 줄에
package com.javachain.core;
가 있어야 합니다.
- 예를 들어, Block.java의 첫 줄에
- build.gradle 파일에서 애플리케이션 주 클래스 지정:
application { mainClass = 'com.javachain.Main' }
(mainClassName 대신 최신 Gradle에서는 mainClass 사용) - Gradle 프로젝트 새로고침:
- 인텔리제이 우측의 Gradle 탭 클릭
- 새로고침 버튼 클릭하여 변경사항 적용
- 프로젝트 실행:
- Main 클래스 우클릭 → Run ‘Main.main()’
- 또는 Gradle 태스크 실행: Gradle 탭 → Tasks → application → run
파일 구조가 올바르게 설정되었는지 확인하는 것이 중요합니다. Gradle 프로젝트에서는 src/main/java/com/javachain/… 형식의 경로를 따라야 합니다.
인텔리제이에서 시작할 수 있는 간단한 P2P 연결 기능을 갖춘 블록체인 프로젝트를 만들어 드리겠습니다. 두 컴퓨터 간 기본적인 연결과 메시지 교환이 가능한 구조로 설계하겠습니다.
프로젝트 설정
인텔리제이에서 다음과 같이 프로젝트를 설정합니다:
- 빌드 시스템: Gradle 또는 Maven (여기서는 Gradle 사용)
- JDK 버전: Java 17 이상 권장
- 프로젝트 구조:
- core (블록체인 핵심 기능)
- network (P2P 네트워킹)
- util (유틸리티 클래스)
build.gradle 파일
plugins {
id 'java'
id 'application'
}
group 'com.javachain'
version '1.0-SNAPSHOT'
sourceCompatibility = 17
repositories {
mavenCentral()
}
dependencies {
// 네트워킹을 위한 Netty
implementation 'io.netty:netty-all:4.1.86.Final'
// JSON 처리
implementation 'com.google.code.gson:gson:2.10.1'
// 암호화
implementation 'org.bouncycastle:bcprov-jdk15on:1.70'
// 로깅
implementation 'org.slf4j:slf4j-api:2.0.7'
implementation 'ch.qos.logback:logback-classic:1.4.6'
// 테스트
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.2'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.2'
}
application {
mainClassName = 'com.javachain.Main'
}
test {
useJUnitPlatform()
}
핵심 클래스 구현
1. 블록체인 기본 구조
Block.java
package com.javachain.core;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
public class Block {
private int index;
private long timestamp;
private String previousHash;
private String hash;
private int nonce;
private List<Transaction> transactions;
public Block(int index, String previousHash) {
this.index = index;
this.timestamp = Instant.now().getEpochSecond();
this.previousHash = previousHash;
this.transactions = new ArrayList<>();
this.nonce = 0;
this.hash = calculateHash();
}
public String calculateHash() {
try {
String dataToHash = index + timestamp + previousHash + nonce + transactions.toString();
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(dataToHash.getBytes(StandardCharsets.UTF_8));
StringBuilder hexString = new StringBuilder();
for (byte b : hash) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) hexString.append('0');
hexString.append(hex);
}
return hexString.toString();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
public void mineBlock(int difficulty) {
String target = new String(new char[difficulty]).replace('\0', '0');
while (!hash.substring(0, difficulty).equals(target)) {
nonce++;
hash = calculateHash();
}
System.out.println("Block mined: " + hash);
}
public void addTransaction(Transaction transaction) {
if (transaction != null) {
transactions.add(transaction);
}
}
// Getters
public int getIndex() { return index; }
public long getTimestamp() { return timestamp; }
public String getPreviousHash() { return previousHash; }
public String getHash() { return hash; }
public List<Transaction> getTransactions() { return transactions; }
}
Transaction.java
package com.javachain.core;
import java.security.*;
import java.util.Base64;
public class Transaction {
private String transactionId;
private String sender;
private String recipient;
private double amount;
private byte[] signature;
public Transaction(String sender, String recipient, double amount) {
this.sender = sender;
this.recipient = recipient;
this.amount = amount;
this.transactionId = calculateHash();
}
public String calculateHash() {
try {
String data = sender + recipient + Double.toString(amount);
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(data.getBytes());
StringBuilder hexString = new StringBuilder();
for (byte b : hash) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) hexString.append('0');
hexString.append(hex);
}
return hexString.toString();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
public void generateSignature(PrivateKey privateKey) {
try {
String data = sender + recipient + Double.toString(amount);
Signature dsa = Signature.getInstance("SHA256withECDSA");
dsa.initSign(privateKey);
dsa.update(data.getBytes());
signature = dsa.sign();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public boolean verifySignature(PublicKey publicKey) {
try {
String data = sender + recipient + Double.toString(amount);
Signature dsa = Signature.getInstance("SHA256withECDSA");
dsa.initVerify(publicKey);
dsa.update(data.getBytes());
return dsa.verify(signature);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
// Getters
public String getTransactionId() { return transactionId; }
public String getSender() { return sender; }
public String getRecipient() { return recipient; }
public double getAmount() { return amount; }
public byte[] getSignature() { return signature; }
@Override
public String toString() {
return "Transaction{" +
"transactionId='" + transactionId + '\'' +
", sender='" + sender + '\'' +
", recipient='" + recipient + '\'' +
", amount=" + amount +
'}';
}
}
Blockchain.java
package com.javachain.core;
import java.util.ArrayList;
import java.util.List;
public class Blockchain {
private List<Block> chain;
private int difficulty;
private List<Transaction> pendingTransactions;
public Blockchain() {
this.chain = new ArrayList<>();
this.difficulty = 4;
this.pendingTransactions = new ArrayList<>();
// Genesis block
createGenesisBlock();
}
private void createGenesisBlock() {
Block genesisBlock = new Block(0, "0");
genesisBlock.mineBlock(difficulty);
chain.add(genesisBlock);
System.out.println("Genesis block created: " + genesisBlock.getHash());
}
public Block getLatestBlock() {
return chain.get(chain.size() - 1);
}
public void addBlock(Block newBlock) {
newBlock.setPreviousHash(getLatestBlock().getHash());
newBlock.mineBlock(difficulty);
chain.add(newBlock);
}
public void addTransaction(Transaction transaction) {
pendingTransactions.add(transaction);
}
public void minePendingTransactions(String minerRewardAddress) {
Block block = new Block(chain.size(), getLatestBlock().getHash());
for (Transaction transaction : pendingTransactions) {
block.addTransaction(transaction);
}
block.mineBlock(difficulty);
chain.add(block);
// Clear pending transactions and reward the miner
pendingTransactions.clear();
addTransaction(new Transaction("System", minerRewardAddress, 1.0));
}
public boolean isChainValid() {
for (int i = 1; i < chain.size(); i++) {
Block currentBlock = chain.get(i);
Block previousBlock = chain.get(i - 1);
if (!currentBlock.getHash().equals(currentBlock.calculateHash())) {
return false;
}
if (!currentBlock.getPreviousHash().equals(previousBlock.getHash())) {
return false;
}
}
return true;
}
// Getters
public List<Block> getChain() { return chain; }
public int getDifficulty() { return difficulty; }
public List<Transaction> getPendingTransactions() { return pendingTransactions; }
}
2. P2P 네트워킹 클래스
Node.java
package com.javachain.network;
import com.google.gson.Gson;
import com.javachain.core.Block;
import com.javachain.core.Blockchain;
import com.javachain.core.Transaction;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Node {
private List<NodeConnection> peers;
private int port;
private Blockchain blockchain;
private ExecutorService executorService;
private boolean running;
private final Gson gson = new Gson();
public Node(int port, Blockchain blockchain) {
this.port = port;
this.blockchain = blockchain;
this.peers = new ArrayList<>();
this.executorService = Executors.newCachedThreadPool();
this.running = false;
}
public void start() {
running = true;
executorService.submit(this::runServer);
System.out.println("Node started on port " + port);
}
public void stop() {
running = false;
executorService.shutdown();
System.out.println("Node stopped");
}
private void runServer() {
try (ServerSocket serverSocket = new ServerSocket(port)) {
serverSocket.setReuseAddress(true);
while (running) {
Socket clientSocket = serverSocket.accept();
NodeConnection connection = new NodeConnection(clientSocket);
peers.add(connection);
executorService.submit(() -> handleConnection(connection));
}
} catch (IOException e) {
System.err.println("Server error: " + e.getMessage());
}
}
private void handleConnection(NodeConnection connection) {
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getSocket().getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
Message message = gson.fromJson(line, Message.class);
processMessage(message, connection);
}
} catch (IOException e) {
System.err.println("Connection error: " + e.getMessage());
} finally {
peers.remove(connection);
try {
connection.getSocket().close();
} catch (IOException e) {
// Ignore
}
}
}
public void connectToPeer(String host, int port) {
try {
Socket socket = new Socket(host, port);
NodeConnection connection = new NodeConnection(socket);
peers.add(connection);
executorService.submit(() -> handleConnection(connection));
System.out.println("Connected to peer " + host + ":" + port);
// Send current blockchain to new peer
sendChain(connection);
} catch (IOException e) {
System.err.println("Failed to connect to peer: " + e.getMessage());
}
}
private void sendChain(NodeConnection connection) {
Message message = new Message(MessageType.CHAIN, gson.toJson(blockchain.getChain()));
connection.send(gson.toJson(message));
}
private void processMessage(Message message, NodeConnection sender) {
switch (message.getType()) {
case TRANSACTION:
Transaction transaction = gson.fromJson(message.getData(), Transaction.class);
blockchain.addTransaction(transaction);
broadcastMessage(message, sender);
break;
case BLOCK:
Block block = gson.fromJson(message.getData(), Block.class);
blockchain.addBlock(block);
broadcastMessage(message, sender);
break;
case CHAIN:
// Here you would implement chain validation and replacement if needed
System.out.println("Received chain from peer");
break;
case CHAIN_REQUEST:
sendChain(sender);
break;
}
}
public void broadcastTransaction(Transaction transaction) {
Message message = new Message(MessageType.TRANSACTION, gson.toJson(transaction));
broadcastMessage(message, null);
}
public void broadcastBlock(Block block) {
Message message = new Message(MessageType.BLOCK, gson.toJson(block));
broadcastMessage(message, null);
}
private void broadcastMessage(Message message, NodeConnection exclude) {
String messageJson = gson.toJson(message);
for (NodeConnection peer : peers) {
if (peer != exclude) {
peer.send(messageJson);
}
}
}
// Getter
public Blockchain getBlockchain() { return blockchain; }
}
NodeConnection.java
package com.javachain.network;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.Socket;
public class NodeConnection {
private Socket socket;
private PrintWriter writer;
public NodeConnection(Socket socket) {
this.socket = socket;
try {
this.writer = new PrintWriter(socket.getOutputStream(), true);
} catch (IOException e) {
throw new RuntimeException("Error creating writer", e);
}
}
public void send(String message) {
writer.println(message);
}
public Socket getSocket() {
return socket;
}
}
Message.java
package com.javachain.network;
public class Message {
private MessageType type;
private String data;
public Message(MessageType type, String data) {
this.type = type;
this.data = data;
}
public MessageType getType() {
return type;
}
public String getData() {
return data;
}
}
MessageType.java
package com.javachain.network;
public enum MessageType {
TRANSACTION,
BLOCK,
CHAIN,
CHAIN_REQUEST
}
3. 메인 애플리케이션
Main.java
package com.javachain;
import com.javachain.core.Block;
import com.javachain.core.Blockchain;
import com.javachain.core.Transaction;
import com.javachain.network.Node;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("=== Java Blockchain P2P Node ===");
System.out.print("Enter port for this node: ");
int port = scanner.nextInt();
scanner.nextLine(); // Consume newline
// Create blockchain
Blockchain blockchain = new Blockchain();
// Create and start node
Node node = new Node(port, blockchain);
node.start();
boolean running = true;
while (running) {
System.out.println("\nCommands:");
System.out.println("1. Connect to peer");
System.out.println("2. Create transaction");
System.out.println("3. Mine pending transactions");
System.out.println("4. Show blockchain");
System.out.println("5. Exit");
System.out.print("Enter command: ");
int command = scanner.nextInt();
scanner.nextLine(); // Consume newline
switch (command) {
case 1:
System.out.print("Enter peer host: ");
String host = scanner.nextLine();
System.out.print("Enter peer port: ");
int peerPort = scanner.nextInt();
scanner.nextLine(); // Consume newline
node.connectToPeer(host, peerPort);
break;
case 2:
System.out.print("Enter sender: ");
String sender = scanner.nextLine();
System.out.print("Enter recipient: ");
String recipient = scanner.nextLine();
System.out.print("Enter amount: ");
double amount = scanner.nextDouble();
scanner.nextLine(); // Consume newline
Transaction transaction = new Transaction(sender, recipient, amount);
blockchain.addTransaction(transaction);
node.broadcastTransaction(transaction);
System.out.println("Transaction created and broadcast");
break;
case 3:
System.out.print("Enter miner address: ");
String minerAddress = scanner.nextLine();
blockchain.minePendingTransactions(minerAddress);
Block latestBlock = blockchain.getLatestBlock();
node.broadcastBlock(latestBlock);
System.out.println("Block mined and broadcast");
break;
case 4:
System.out.println("\n=== Blockchain ===");
for (Block block : blockchain.getChain()) {
System.out.println("Index: " + block.getIndex());
System.out.println("Timestamp: " + block.getTimestamp());
System.out.println("Hash: " + block.getHash());
System.out.println("Previous hash: " + block.getPreviousHash());
System.out.println("Transactions: " + block.getTransactions().size());
System.out.println();
}
break;
case 5:
running = false;
node.stop();
break;
default:
System.out.println("Unknown command");
}
}
scanner.close();
}
}
프로젝트 실행 방법
- 인텔리제이에서 위의 클래스들을 적절한 패키지에 생성합니다.
- Main 클래스를 실행합니다.
- 프롬프트에 따라 노드 포트를 입력합니다(예: 9000).
- 두 번째 터미널 또는 컴퓨터에서 같은 애플리케이션을 실행하고 다른 포트를 선택합니다(예: 9001).
- 각 노드에서 “Connect to peer” 옵션을 선택하고 서로의 호스트와 포트를 입력하여 연결합니다.
- 트랜잭션을 생성하고 채굴하여 두 노드가 동기화되는 것을 확인합니다.
참고 사항
- 이 구현은 기본적인 P2P 연결 기능과 블록체인의 핵심 요소를 포함합니다.
- 실제 프로덕션 환경에서는 보안, 오류 처리, 동기화 문제 등을 더 강화해야 합니다.
- 네트워크 통신은 간단한 소켓 기반으로 구현했지만, 실제로는 Netty나 gRPC 같은 고급 프레임워크를 사용하는 것이 좋습니다.
- 사이드체인 기능은 기본 P2P 연결이 작동하는 것을 확인한 후 추가할 수 있습니다.
이 코드를 기반으로 하여 사이드체인 기능과 더 고급 기능을 점진적으로 추가해 나갈 수 있습니다. 기본적인 P2P 통신이 작동하면 그 위에 다양한 블록체인 기능을 구현하기가 훨씬 수월해집니다.
답글 남기기