** 파이썬으로 공부하는 블록체인 (일등박사) 서적 기반으로 작성 **
앞서 배운 내용들을 기반으로 다음 단계를 진행해볼 것이다.
1) PoW(Proof of Work) 기반의 블록체인 노드를 구현하고, 해당 노드가 블록체인의 원리에 의해 작동하는지 확인한 뒤 노드에 거래 내역을 저장
2) 블록체인 네트워크의 거래 내역을 확인할 수 있는 Block scan 사이트를 구현하여 채굴될 때마다 저장되는 거래 내역과 previous hash 값을 확인
3) 사용자가 생성된 코인을 거래할 수 있는 거래 지갑 사이트를 구축
- 여기서 구축할 블록체인 네트워크의 코인 명칭은 pyBTC
- 해당 지갑 사이트를 통해 사용자는 자신의 지갑의 잔액을 확인할 수 있으며, 다른 지갑으로 py BTC 코인을 전송할 수 있을 것
- 또한 단일 노드에서 진행되었던 작업을 여러 노드에서 진행하여, 각 노드가 경쟁적으로 채굴을 하며 거래 내역 데이터가 분산원장에 모두 저장되는 탈중앙화 원리를 이해해보자.
1. 블록체인 노드 구축 (one_code.ipynb)
1) 파이썬 패키지 import
기존에 갖고 있던 주피터 노트북 환경에서 아나콘다 기반의 파이썬으로 진행
패키지명 | 패키지 설명 |
json | JSON은 XML, YAML과 함께 효율적으로 데이터를 저장하고 교환하는 데 사용하는 텍스트 데이터 포맷 중 하나로 블록체인상 데이터는 json 상태로 저장됨 |
flask | 파이썬의 web flask로 블록체인 API를 운영하고, 블록스캔 사이트, 지갑 사이트를 운영 (flask, request, jsonify 활용) |
time | 기본 파이썬에 내장된 모듈로 컴퓨터 친화적인 timestamp를 인간 친화적 시간 타입으로 바꿔줌 |
hashlib | 블록체인 운영의 핵심인 해시암호를 파이썬에서 적용할 수 있게 하는 모듈로 블록의 내용을 해당 라이브러리를 통하여 해시 처리 |
requests | 파이썬에서 HTTP 요청을 보낼 때 사용되는 모듈로 flask로 구축된 블록체인 API와 소통할 때 사용됨 |
random | 임의의 난수를 생성하는 모듈로 채굴 시 알맞은 nonce 값을 찾기 위하여 활용됨 |
import hashlib
import json
from time import time
import random
import requests
from flask import Flask, request, jsonify
2) 블록체인 객체 만들기
1. 객체 생성
# 블록체인 객체 선언
class Blockchain(object):
def __init__(self):
self.chain = [] # 블록을 연결하는 체인
self.current_transaction = [] # 블록 내에 기록되는 거래 내역 리스트
self.nodes = set() # 블록체인을 운영하는 노드들의 정보
self.new_block(previous_hash=1, proof=100) # 블록체인 첫 생성 시 자동으로 첫 블록(genesis block)을 생성하는 코드
2. 해시화
: 거래 내역 블록에 저장할 때, 암호 해시의 원리에 의해 json 형식의 거래 내역들이 SHA-256 방식으로 해시암호화 된다.
이 로직이 객체 내에서 아래와 같이 함수화되어 사용됨
@staticmethod
def hash(block):
block_string = json.dumps(block, sort_keys = True).encode()
return hashlib.sha256(block_string).hexdigest()
<참고>
@staticmethod
- 클래스 바깥에 정의된 일반 함수와 완전히 동일하게 동작함
- 정적 메소드이지만 다른 언어와는 달리 인스턴스에서도 접근 가능!
- 특정 클래스와 보다 밀접한 관계에 있다는 것을 나타내거나 코드를 간단하게 만들기 위해 사용
접근 제어자
- public : 접두사에 아무 밑줄이 없음
- protected : 접두사에 한 개의 밑줄을 적용
- __private : 접두사에 두 개의 밑줄을 적용
- private을 이용한 속성값을 가져오기 위한 접근자와 설정자로 get, set 메소드를 사용
@property
- 위의 get 메소드를 직관적으로 표현하는 역할
# loads() 함수: JSON 문자열을 python 객체로 변환
import json
json_string = '''{
"id": 1,
"username": "James",
"email": "abc@email.com",
"address": {
"street": "Cheongpa",
"suite": "Apt. 516",
"city": "seoul",
},
"admin": false,
"hobbies": null
}'''
json_object = json.loads(json_string)
assert json_object['id'] == 1
assert json_object['email'] == 'abc@email.com'
assert json_object['address']['city'] == 'seoul'
assert json_object['admin'] is False
assert json_object['hobbies'] is None
# dumps() 함수 : python 객체를 JSON 문자열로 변환
import json
json_object = {
"id": 1,
"username": "James",
"email": "abc@email.com",
"address": {
"street": "Cheongpa",
"suite": "Apt. 516",
"city": "seoul",
},
"admin": false,
"hobbies": null
}
json_string = json.dumps(json_object)
print(json_string)
3. 블록체인의 마지막 블록 호출
: 블록의 최근 nonce 값을 기반으로 새로운 nonce 값을 찾기 위해, 블록체인의 가장 최근(마지막) 블록을 호출하는 함수가 필요
@property
def last_block(self):
return self.chain[-1]
4. 검증(valid_proof)
- 블록체인 채굴 시 작업으로 산출된 nonce 값이 조건에 맞는 알맞은 값인지 검증이 필요
- 검증 함수에서는 마지막 블록의 nonce 값과 신규 nonce 후보 값을 결합하여 해시화한 뒤 첫 4개의 단어가 '0000'일 때 해당 nonce 값이 유효(valid)하다고 판단
@staticmethod
def valid_proof(last_proof, proof):
guess = str(last_proof + proof).encode()
guess_hash = hashlib.sha256(guess).hexdigest()
return guess_hash[:4] == "0000"
5. PoW(Proof of Work) 채굴
- 블록체인 내에 거래 내역이 저장되기 위해서는 유효(valid) nonce 값이 확인되어야 함
- 이에 지속적으로 proof에 난수값을 생성하여 가장 최근 블록의 nonce 값(last_proof)과 비교하며 작업 증명(PoW)이 성공할 때까지 반복됨
def pow(self, last_proof):
proof = random.randint(-1000000, 1000000)
while self.valid_proof(last_proof, proof) is False:
proof = random.randint(-1000000, 1000000)
return proof
6. 거래 내역 추가(new_transaction)
- 매번 블록이 생성되기 전 (채굴되기 전)까지 지속적으로 예비 블록 내에 거래 내역이 추가됨
- 이 블록체인의 거래 내역에는 발신자, 수신자, 보내는 금액, 시간 4가지 요소를 저장할 것임
def new_transaction(self, sender, recipient, amount):
self.current_transaction.append(
{
'sender' : sender, # 송신자
'recipient' : recipient, # 수신자
'amount' : amount, # 금액
'timestamp' : time()
}
)
return self.last_block['index'] + 1
7. 블록 추가 (new_block)
- current_transaction에 거래 내역이 추가되며, PoW 작업을 통해 유효한 nonce 값이 찾아졌을 때 신규 블록이 생성됨
- 신규 블록이 생성될 때 필요한 인자는 5가지 (블록 번호, 생성 시간, 거래 내역, nonce 값, 전 블록의 해시값)
- 기존 거래 내역이 블록에 저장된 후에는 현 거래 내역 리스트는 초기화 되어야 하며 (self.current_transaction = []) 생성된 블록은 객체의 체인 리스트에 추가됨
def new_block(self, proof, previous_hash = None):
block = {
'index' : len(self.chain) + 1,
'timestamp' : time(),
'transaction' : self.current_transaction,
'nonce' : proof,
'previous_hash' : previous_hash or self.hash(self.chain[-1]),
}
self.current_transaction = []
self.chain.append(block)
return block
8. 블록 검증 (valid_chain)
- 블록 생성 후 알맞은 블록이 생성되었는지 검증이 필요
- 마지막 블록의 해시값과 그 전 블록을 직접 해시한 값을 비교하여 과거 거래 내역에 변동된 것은 없는지 체크
- 이상이 있을 경우 False 값을 반환하고 모든 값이 정상일 경우 True 값을 반환
def valid_chain(self, chain):
last_block = chain[0]
current_index = 1
while current_index < len(chain):
block = chain[current_index]
print('%s' % last_block)
print('%s' % block)
print("\n--------\n")
if block['previous_hash'] != self.hash(last_block):
return False
last_block = block
current_index += 1
return True
9. 최종 코드
import hashlib
import json
from time import time
import random
import requests
from flask import Flask, request, jsonify
# 블록체인 객체 선언
class Blockchain(object):
def __init__(self):
self.chain = [] # 블록을 연결하는 체인
self.current_transaction = [] # 블록 내에 기록되는 거래 내역 리스트
self.nodes = set() # 블록체인을 운영하는 노드들의 정보
self.new_block(previous_hash=1, proof=100) # 블록체인 첫 생성 시 자동으로 첫 블록을 생성하는 코드
@staticmethod
def hash(block):
block_string = json.dumps(block, sort_keys = True).encode()
return hashlib.sha256(block_string).hexdigest()
@property
def last_block(self):
return self.chain[-1]
@staticmethod
def valid_proof(last_proof, proof):
guess = str(last_proof + proof).encode()
guess_hash = hashlib.sha256(guess).hexdigest()
return guess_hash[:4] == "0000"
def pow(self, last_proof):
proof = random.randint(-1000000, 1000000)
while self.valid_proof(last_proof, proof) is False:
proof = random.randint(-1000000, 1000000)
return proof
def new_transaction(self, sender, recipient, amount):
self.current_transaction.append(
{
'sender' : sender, # 송신자
'recipient' : recipient, # 수신자
'amount' : amount, # 금액
'timestamp' : time()
}
)
return self.last_block['index'] + 1
def new_block(self, proof, previous_hash = None):
block = {
'index' : len(self.chain) + 1,
'timestamp' : time(),
'transaction' : self.current_transaction,
'nonce' : proof,
'previous_hash' : previous_hash or self.hash(self.chain[-1]),
}
self.current_transaction = []
self.chain.append(block)
return block
def valid_chain(self, chain):
last_block = chain[0]
current_index = 1
while current_index < len(chain):
block = chain[current_index]
print('%s' % last_block)
print('%s' % block)
print("\n--------\n")
if block['previous_hash'] != self.hash(last_block):
return False
last_block = block
current_index += 1
return True
3) 블록체인 객체 기반으로 노드 만들기
1. 노드 기본 정보 설정
- 위의 블록체인 객체를 호출한 뒤, 운영할 노드의 IP주소와 포트 주소를 선언
- 이를 통해 노드의 key 값(node_identifier, 노드 IP + 포트 번호)을 생성하고, 노드의 채굴 결과 발생하는 수익을 보낼 지갑의 주소(mine_owner)와 채굴 보상값(mine_profit)을 선언
blockchain = Blockchain()
my_ip = '0.0.0.0'
my_port = '5000'
node_identifier = 'node_' + my_port
mine_owner = 'master'
mine_profit = 0.1
2. 블록 정보 호출 (full_chain)
- 블록체인 기술의 큰 장점 : 모든 거래 내역이 투명하게 공개됨
- 임의의 사용자가 블록체인 정보 호출 시 블록체인 내의 블록의 길이와 블록의 모든 정보를 json 양식으로 리턴
@app.route('/chain', methods=['GET'])
def full_chain():
print("chain info requested!")
response = {
'chain' : blockchain.chain,
'length' : len(blockchain.chain),
}
return jsonify(response), 200
3. 신규 거래 추가(new_transaction)
- 사용자 간의 거래가 발생할 경우 해당 거래 내역은 json 형식으로 요청되며, 이때 요청 사항 내에 거래 내역의 3가지 요소 (발신자, 수신자, 보내는 금액)가 있는지 확인한 뒤 없을 경우에는 400 에러를 배출
- 에러가 없을 경우에는 블록체인 객체의 new_transaction 함수를 활용하여 블록 거래 내역 내에 신규 거래 내역을 추가
# 신규 거래 추가
@app.route('/transactions/new', methods=['POST'])
def new_transaction():
values = request.get_json()
print("transactions_new!!! : ", values)
required = ['sender', 'recipient', 'amount']
if not all(k in values for k in required):
return 'missing values', 400
index = blockchain.new_transaction(values['sender'], values['recipient'], values['amount'])
response = {'message' : 'Transaction will be added to Block {%s}' % index}
return jsonify(response), 201
4. 채굴(mine)
- 채굴이 시작되면 마지막 블록 내의 nonce 값을 블록 객체의 pow에 넣은 뒤 작업을 시작
- 작업이 완료되고 작업 증명을 위한 nonce 값이 생성되면 mine_owner로부터 노드 운영자에게 채굴 보상이 주어지고 최종적으로 전 블록의 해시값을 포함하여 블록 객체의 new_block 함수로 블록이 생성됨
- 그리고 정상적으로 처리되었음을 json 결과값으로 리턴
# 채굴
@app.route('/mine', methods = ['GET'])
def mine():
print("MINING STARTED")
last_block = blockchain.last_block
last_proof = last_block['nonce']
proof = blockchain.pow(last_proof)
blockchain.new_transaction(
sender = mine_owner,
recipient=node_identifier,
amount=mine_profit # coinbase transaction
)
previous_hash = blockchain.hash(last_block)
block = blockchain.new_block(proof, previous_hash)
print("MINING FINISHED")
response = {
'message' : 'new block found',
'index' : block['index'],
'transactions' : block['transactions'],
'nonce' : block['nonce'],
'previous_hash' : block['previous_hash']
}
return jsonify(response), 200
5. 노드 운영
- 지금까지 구성된 정보를 바탕으로 노드 운영을 시작 (노드의 IP 정보, port 정보 등)
app = Flask(__name__)
if __name__ == '__main__':
app.run(host=my_ip, port=my_port)
6. 최종 실행 코드
blockchain = Blockchain()
my_ip = '0.0.0.0'
my_port = '5000'
node_identifier = 'node_' + my_port
mine_owner = 'master'
mine_profit = 0.1
# 블록 정보 호출
@app.route('/chain', methods=['GET'])
def full_chain():
print("chain info requested!")
response = {
'chain' : blockchain.chain,
'length' : len(blockchain.chain),
}
return jsonify(response), 200
# 신규 거래 추가
@app.route('/transactions/new', methods=['POST'])
def new_transaction():
values = request.get_json()
print("transactions_new!!! : ", values)
required = ['sender', 'recipient', 'amount']
if not all(k in values for k in required):
return 'missing values', 400
index = blockchain.new_transaction(values['sender'], values['recipient'], values['amount'])
response = {'message' : 'Transaction will be added to Block {%s}' % index}
return jsonify(response), 201
# 채굴
@app.route('/mine', methods = ['GET'])
def mine():
print("MINING STARTED")
last_block = blockchain.last_block
last_proof = last_block['nonce']
proof = blockchain.pow(last_proof)
blockchain.new_transaction(
sender = mine_owner,
recipient=node_identifier,
amount=mine_profit # coinbase transaction
)
previous_hash = blockchain.hash(last_block)
block = blockchain.new_block(proof, previous_hash)
print("MINING FINISHED")
response = {
'message' : 'new block found',
'index' : block['index'],
'transactions' : block['transactions'],
'nonce' : block['nonce'],
'previous_hash' : block['previous_hash']
}
return jsonify(response), 200
app = Flask(__name__)
if __name__ == '__main__':
app.run(host=my_ip, port=my_port)
2. 운영 중인 노드에 실행 명령하기 (one_node_command.ipynb)
이제 구성된 노드에 거래 내역을 저장하고, PoW을 통하여 블록을 생성하고, 생성된 데이터를 조회하는 등 블록체인 노드를 운영해보자.
1. 모듈 호출
import requests
import json
import pandas as pd
import hashlib
import random
2. 블록 조회
- requests의 GET 방식을 통해 블록을 조회할 수 있는 URL API(http://localhost:5000/chain)에 현재 운영되는 노드의 모든 블록 데이터를 조회
- 정상적으로 조회되었다면 다음과 같이 노드 운영 노트북에 chain info requestes!! 라는 메시지가 프린트 된다.
- 현재는 첫 블록이 생성된 이후 채굴이 이루어지지 않았기에 1개의 블록 정보만 저장되어 있는 상태
3. Transaction 추가
- requests의 POST 방식을 통해 거래를 추가할 수 있는 API URL(http://localhost:5000/transactions/new)에 거래 내역 데이터를 보낸다. 아래 코드에서는 test_from 이라는 지갑으로부터 test_to 라는 지갑으로 3개의 pyBTC를 보냄
## transaction 입력하기
headers = {'Content-Type' : 'application/json; charset=utf-8'}
data = {
"sender" : "test_from",
"recipient" : "test_to",
"amount" : 3,
}
requests.post("http://localhost:5000/transactions/new", headers=headers, data=json.dumps(data)).content
운영 노트북 (one_node)을 확인해보면 아래와 같이 정상적으로 신규 Transaction이 추가되었다는 로그가 발생하며, 로그 내용을 확인해보면 sender 인 test_from으로부터 recipient인 test_to에게 3개의 pyBTC가 보내졌음을 확인할 수 있음
블록에도 거래 내역이 잘 추가 되었을지 확인하기 위해 아래와 같이 블록 정보를 재조회한다.
현재 Transaction이 추가되었지만 추가되기 전인 모습과 차이가 없는데, 그 이유는 아직 블록에서 작업 증명 (PoW, 채굴)이 이루어지지 않았기 때문에 거래 내역이 블록체인 내의 current_transaction 리스트에 존재할 뿐 블록에 추가되지 않았기 때문이다!
4. 채굴 시작
- requests의 GET 방식을 통하여 채굴 실시를 명령하는 URL API(http://localhost:5000/mine)에 채굴 시작 신호를 줌
## 채굴 명령
headers = {'Content-Type' : 'application/json; charset=utf-8'}
res = requests.get("http://localhost:5000/mine")
print(res)
print(res.text)
그 결과 다음과 같이 정상적으로 블록체인의 채굴이 발생하였음을 확인할 수 있고, 노드 운영 노트북인 one_node.ipynb에서도 MINING STARTED 와 MINING FINISHED로 정상적인 채굴이 발생하였다는 로그를 확인할 수 있다!
이제 다시 한 번 블록의 정보를 조회하면 index 값2의 두 번째 블록이 생긴 것을 확인할 수 있다!
- 이렇게 새로 생성된 블록에는 967400 라는 nonce 값이 입력되어 있고 2개의 거래 내역이 저장되어 있다.
- 우리는 1개의 거래 내역만 신청하였지만 블록 채굴에 따른 보상이 지급되어야 하기에 master로부터 채굴 노드인 node_5000에 1개의 거래 내역이 추가되어 총 2개의 거래 내역이 추가 저장되었음
- Transaction 1 : test_from으로부터 test_to로 전달된 3개의 pyBTC
- Transaction 2 : master부터 채굴노드(node_5000)에 제공된 채굴 보상
지금까지 나누어서 진행했던 거래 및 채굴을 한 번의 코드로 진행해보자.
이번 코드에서 진행되는 거래 내역은 아래와 같음
From | To | Amount |
test_from | test_to2 | 30 |
test_from | test_to3 | 300 |
## transaction2 입력하기
headers = {'Content-Type' : 'application/json; charset=utf-8'}
data = {
"sender" : "test_from",
"recipient" : "test_to2",
"amount" : 30,
}
requests.post("http://localhost:5000/transactions/new", headers=headers, data=json.dumps(data)).content
## transaction3 입력하기
headers = {'Content-Type' : 'application/json; charset=utf-8'}
data = {
"sender" : "test_from",
"recipient" : "test_to3",
"amount" : 300,
}
requests.post("http://localhost:5000/transactions/new", headers=headers, data=json.dumps(data)).content
## 채굴하기
headers = {'Content-Type' : 'application/json; charset=utf-8'}
res = requests.get("http://localhost:5000/mine")
print(res)
## 노드의 블록 정보 확인 - 4
headers = {'Content-Type' : 'application/json; charset=utf-8'}
res = requests.get("http://localhost:5000/chain", headers=headers)
코드를 실행해보면 위와 같이 여러 거래 내역을 포함한 세 번째 블록이 생성된 것을 확인할 수 있다.
거래 transaction이 증가함에 따라 문자열이 나열된 JSON 방식으로는 확인이 어렵다는 단점이 있기에, pandas를 활용하여 지금까지의 거래 내역을 table로 확인해보자.
status_json = json.loads(res.text)
status_json['chain']
tx_amount_l = []
tx_sender_l = []
tx_reciv_l = []
tx_time_l = []
for chain_index in range(len(status_json['chain'])):
chain_tx = status_json['chain'][chain_index]['transactions']
for each_tx in range(len(chain_tx)):
tx_amount_l.append(chain_tx[each_tx]['amount'])
tx_sender_l.append(chain_tx[each_tx]['sender'])
tx_reciv_l.append(chain_tx[each_tx]['recipient'])
tx_time_l.append(chain_tx[each_tx]['timestamp'])
df_tx = pd.DataFrame()
df_tx['timestamp'] = tx_time_l
df_tx['sender'] = tx_sender_l
df_tx['recipient'] = tx_reciv_l
df_tx['amount'] = tx_amount_l
df_tx
- 지금까지 우리가 입력했던 test_from 부터 test_to/test_to2/test_to3 와의 3가지 거래 내역 뿐만 아니라 master로부터 node_5000에 제공된 채굴 보상 내역까지 확인할 수 있음
- 위 거래 내역을 기반으로 지갑별 잔액을 계산하는 아래 코드를 실행해보면 다음과 같다.
df_sended = pd.DataFrame(df_tx.groupby('sender')['amount'].sum()).reset_index()
df_sended.columns = ['user','sended_amount']
df_received= pd.DataFrame(df_tx.groupby('recipient')['amount'].sum()).reset_index()
df_received.columns = ['user','received_amount']
df_received
df_status = pd.merge(df_received,df_sended, on ='user', how= 'outer').fillna(0)
df_status['balance'] = df_status['received_amount'] - df_status['sended_amount']
df_status
이렇게 위와 같이 각 계정별 잔고를 조회할 수 있게 된다!
채굴 노드였던 node_5000의 경우 작업 증명에 대한 보상으로 0.2pyBTC를 보유하게 되었고, pyBTC를 송금받았던 test_to, test_to2, test_to3 계정은 각각 3 / 30 / 300 개의 pyBTC를 보유하였음을 확인할 수 있다.
반대로 송금하기만 했던 test_from은 -336pyBTC, 채굴 보상을 제공했던 master는 -0.3pyBTC의 잔고가 있음을 확인할 수 있다.
이렇게 pyBTC 블록체인 운영을 위해 노드 운영 (one_node)과 노드 명령(one_node_command)으로 코드를 분리하여 운영했다. 그리고 실제 이더리움 네트워크도 이렇게 두 가지로 구분되어 운영된다!
1) 노드 운영
- 이더리움 네트워크는 모두에게 공개된 오픈 소스 기반의 네트워크
이에 하드웨어를 소유한 누구나 모두 이더리움 네트워크에 참가하여 노드를 운영할 수 있다. 혹은 이더리움 오픈소스를 기반으로 사적 블록체인 네트워크(Private Blockchain Network)를 운영할 수도 있으며 개발 테스트를 목적으로 사적 블록체인 네트워크를 운영해 주는 가나슈(Ganache)를 활용할 수도 있다.
이때 사용자는 이더리움 운영 언어인 Go를 다운받은 뒤 이더리움의 git을 복제하고 환경변수 설정 등 작업환경을 설정한다. (혹은 가나슈를 설치한다.) 이후 geth 명령을 통하여 노드의 운영을 시작한다.
이 과정에서 우리가 노드의 운영(one_node.ipynb)에서 학습했던 블록체인 객체 생성, 환경 세팅 등의 작업이 진행된다.
2) 노드에 실행 명령
노드가 운영되고 있다면 노드에 정보 조회/채굴/거래 내역 저장 등 실행 명령을 내려야 한다.
우리는 해당 과정을 one_node_command.ipynb이라는 파일에서 진행했다.
한편 실제 이더리움에서도 이와 동일하게 블록 정보 조회/지갑 리스트 조회/채굴 시작, 종료/거래 내역 조회 등의 명령을 내릴 수 있다. 명령을 내리는 기본 방식은 geth 명령어로 이더리움 네트워크에 접속한 뒤 Go 기반의 명령어로 내리는 것이다.
한편 외부 환경에서 API 방식으로 이 이더리움 네 트워크에 운영 명령을 내릴 수 있다.
이때 대표적으로 JavaScript 기반의 web3.js가 활용되며 그 외에도 위와 같이 파이썬(web3.py), 자바(web3j) 등 프로그래밍 언어가 활용된다.
'Blockchain' 카테고리의 다른 글
[Blockchain] Block Wallet 사이트 만들기 (0) | 2024.10.05 |
---|---|
[Blockchain] 블록체인 네트워크 구축을 위한 준비 - SQLite, flask, JS (3) | 2024.09.21 |
[Blockchain] 블록체인이란 무엇일까? - 정의, 구성 요소, 암호 해시, LAYER (1) | 2024.09.20 |