자바스크립트 버전 을 python으로 포팅한 내용입니다.
블록체인의 가장 기초적인 기능인 블록을 만들고 생성된 블록들을 체인으로 연결하는 법을 구현해보겠습니다.
블록 구성요소를 보면 1. index (블록 인덱스 값) 2.timestamp(블록이 생성된 시간) 3. data (블록에 저장된 데이터, 트랜잭션 등등) 4. previous hash (체인을 구성하기 위한 이 전 블록의 해쉬 값) 5. hash (현재 블록의 해쉬 값) 으로 구성하겠습니다.
class Block():
def __init__(self, index, timestamp, data):
self.index = index
self.timestamp = timestamp
self.data = data
self.previousHash = 0
self.hash = self.calHash()
init 생성자를 통해서 Block 객체가 생성될 때 초기화를 해줍니다. 현재 블록의 해쉬 값은 calHash()함수를 통해서 생성합니다.
import hashlib
class Block():
def __init__(self, index, timestamp, data):
self.index = index
self.timestamp = timestamp
self.data = data
self.previousHash = 0
self.hash = self.calHash()
def calHash(self):
return hashlib.sha256(str(self.index).encode() + str(self.data).encode() +
str(self.timestamp).encode() + str(self.previousHash).encode()).hexdigest()
calHash함수에서 hash함수로 SHA256을 사용하겠습니다. 파이썬에서는 sha256을 hashlib에서 제공하고 있습니다. hashlib를 import 합니다. sha256에 들어갈 수 있는 데이터 타입은 byte입니다. 그래서 index, timestamp,data, previousHash 를 string으로 변환 후 encode로 한 번 더 변환합니다.
마지막으로 해쉬 값을 16진수로 표현하기 위해서 hexdigest 함수를 사용합니다.
이제 블록체인 클래스를 만들어보겠습니다. 블록체인 클래스에는 블록들이 들어갈 리스트 변수 chain을 만들고 최초 블록을 생성하는 createGenesis함수와 블록을 체인에 추가하는 addBlock함수, 체인이 유효한지 검사하는 isValid 함수로 구성됩니다.
class BlockChain:
def __init__(self):
self.chain = []
self.createGenesis()
def createGenesis(self):
self.chain.append(Block(0, time.time(), 'Genesis'))
def addBlock(self, nBlock):
nBlock.previousHash = self.chain[len(self.chain)-1].hash
nBlock.hash = nBlock.calHash()
self.chain.append(nBlock)
def isValid(self):
i = 1
while(i<len(self.chain)):
if(self.chain[i].hash != self.chain[i].calHash()):
return False
if(self.chain[i].previousHash != self.chain[i-1].hash):
return False
i += 1
return True
Genesis 블록을 만들 때 timestamp 값으로 time.time()을 호출하고 있습니다. 파이썬에서 제공하는 timestamp 라이브러리 입니다. time.time()을 사용하기 위해서 import time 을 추가해야 합니다.
def createGenesis(self):
self.chain.append(Block(0, time.time(), 'Genesis'))
생성된 블록을 체인에 추가할 때는 이 전 블록의 해쉬 값과 현 블록의 해쉬 값을 계산해주고 chain에 추가합니다.
def addBlock(self, newBlock):
nBlock.previousHash = self.chain[len(self.chain)-1].hash
nBlock.hash = nBlock.calHash()
self.chain.append(nBlock)
체인의 유효성 검사는 현재 블록의 해쉬 값과 계산된 해쉬 값의 비교와 현재 블록이 가지고 있는 이전 블록의 해쉬 값과 이전 블록에 저장되어 있는 해쉬 값을 비교를 하면 됩니다
def isValid(self):
i = 1
while(i<len(self.chain)):
if(self.chain[i].hash != self.chain[i].calHash()):
return False
if(self.chain[i].previousHash != self.chain[i-1].hash):
return False
i += 1
return True
총 코드는 아래와 같습니다.
import hashlib
import time
import json
class Block():
def __init__(self, index, timestamp, data):
self.index = index
self.timestamp = timestamp
self.data = data
self.previousHash = 0
self.hash = self.calHash()
def calHash(self):
return hashlib.sha256(str(self.index).encode() + str(self.data).encode() +
str(self.timestamp).encode() + str(self.previousHash).encode()).hexdigest()
class BlockChain:
def __init__(self):
self.chain = []
self.createGenesis()
def createGenesis(self):
self.chain.append(Block(0, time.time(), 'Genesis'))
def addBlock(self, nBlock):
nBlock.previousHash = self.chain[len(self.chain)-1].hash
nBlock.hash = nBlock.calHash()
self.chain.append(nBlock)
def isValid(self):
i = 1
while(i<len(self.chain)):
if(self.chain[i].hash != self.chain[i].calHash()):
return False
if(self.chain[i].previousHash != self.chain[i-1].hash):
return False
i += 1
return True
import json 부분은 실행해보면서 결과를 보기 편하게 하기 위해 json을 사용하려고 import 했습니다. BlockChain을 객체를 생성하고 프린트 해서 genesis block이 생성됐는지 보겠습니다.
onion = BlockChain()
print(json.dumps(vars(onion.chain[0]), indent=4))
{
"index": 0,
"timestamp": 1528433633.7411778,
"data": "Genesis",
"previousHash": 0,
"hash": "08de7c50a83af89e62b66f407083100aae6104c8dd960f165d69ecc8c3a7ea9b"
}
잘 만들어졌습니다. 그러면 새로 블록을 만들어서 추가해보겠습니다.
onion.addBlock(Block(len(onion.chain),time.time(), {"amount":4}))
for block in onion.chain:
print(json.dumps(vars(block), indent=4))
실행 후에 block이 하나 추가 됐고 previous hash 값이랑 잘 들어갔네요.
{
"index": 0,
"timestamp": 1528433734.2385259,
"data": "Genesis",
"previousHash": 0,
"hash": "53aefb7a602912bc49160d34f46e880b433a9209dbe3fd6118802614d91ca9ca"
}
{
"index": 1,
"timestamp": 1528433734.2385259,
"data": {
"amount": 4
},
"previousHash": "53aefb7a602912bc49160d34f46e880b433a9209dbe3fd6118802614d91ca9ca",
"hash": "85afa45d965a5db0d252a625ff69a13134d001f00b58b0aa4430c8bf8b255874"
}
블록을 두 개 더 만들어 보겠습니다.
onion.addBlock(Block(len(onion.chain),time.time(), {"amount":4}))
onion.addBlock(Block(len(onion.chain),time.time(), {"amount":100}))
onion.addBlock(Block(len(onion.chain),time.time(), {"amount":1000}))
체인이 유효한 지 검사도 해보겠습니다
print('Chain is OK? ', onion.isValid())
변경 한게 없으니 당연히 True가 나오겠죵?
Chain is OK? True
그럼 중간 block의 데이터를 변경하고 valid를 통과하는 지 보겠습니다.
첫 번째 데이터만 변경해보고 두 번째는 해당 블록의 데이터도 변경하고 해쉬 값도 변경해보겠습니다.
첫 번째 데이터만 변경하게 되면 첫 번째 조건문에서 잡아 내고 해쉬 값까지 변경했을 때는 두 번째 previous hash검사할 때 잡아 내게 됩니다.
onion.chain[2].data = "fake"
print('Chain is OK? ', onion.isValid())
onion.chain[2].hash = onion.chain[2].calHash()
print('Chain is OK? ', onion.isValid())
결과는 아래와 같습니다
Chain is OK? False
Chain is OK? False
간단하게 블록체인을 python으로 구현해봤습니다.
스터디하면서 이론으로 본 걸 간단하게라도 구현해보니 이해가 덜 된 부분이 있다는 게 느껴지기도 하네요. 다음에 시간나면 여기에 nonce도 구현하고 transaction 절차와 계좌 생성하는 것도 붙여 봐야겠습니다~