python으로 간단한 블록체인 만들기 (2) - mining

in #kr6 years ago

https://busy.org/@pangol/python-1 편에서는 블록체인 기본 기능인 블록요소로 해쉬 값을 생성하고 이 전 블록의 해쉬값을 연결하는 걸 구현해봤습니다. 이 번에는 블록을 생성할 때 마이닝을 하는 기능을 만들어 보겠습니다.

먼저, 이전에 구현한 Block 클래스를 보면

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()

여기에서 추가해야 할 기능은, 해쉬를 맞추면 블록이 생성 되는 기능입니다.
우선 mine 함수를 정의하고 설정된 difficulty에 따라 해당 블록의 해쉬 값을 설정하는 코드를 만들어 보겠습니다

    def mine(self, difficulty):
        ans = ["0"]*difficulty
        answer = "".join(ans)
        while(str(self.hash)[:difficulty]  != answer):
            self.nonce += 1
            self.hash = self.calHash()

        print('mined block: ', self.hash)
        return self.hash

해쉬 값 맞추기 게임에서 난이도 설정 값을 받고 해쉬 값이 범위에 들어와 있으면 값을 설정하고 반환을 해주는 함수입니다.

mine 함수의 1,2 라인은 계산된 해쉬 값이 범위안에 들어오는지 확인하기 위해 사용된 값입니다. 예를 들어 난이도가 2로 설정되었다면 ans의 값은 '00'이 되고 while 구문에서 hash의 0~2까지의 substring 이 00이 될 때까지 계속 해쉬 값을 찾는 구문입니다. 그리고 찾을 때 까지 nonce의 값을 1씩 올려서 계산된 hash값의 변화를 줍니다.

해쉬를 계산할 때, nonce를 추가해서 계산할 수 있게 calHash함수와 생성자도 변경해줍니다.

    def __init__(self, index, timestamp, data):
        self.index = index
        self.timestamp = timestamp
        self.data = data
        self.previousHash = 0
        self.nonce = 0
        self.hash = self.calHash()

    def calHash(self):
        return hashlib.sha256(str(self.index).encode() + str(self.data).encode() +
                            str(self.nonce).encode() + str(self.timestamp).encode() + str(self.previousHash).encode()).hexdigest()

생성자에서는 nonce 초기화 하는 코드를 calHash함수에서는 str(self.nonce).encode를 추가해줬습니다. 아래는 block과 관련된 전체 코드입니다.

class Block():
    def __init__(self, index, timestamp, data):
        self.index = index
        self.timestamp = timestamp
        self.data = data
        self.previousHash = 0
        self.nonce = 0
        self.hash = self.calHash()

    def calHash(self):
        return hashlib.sha256(str(self.index).encode() + str(self.data).encode() +
                            str(self.nonce).encode() + str(self.timestamp).encode() + str(self.previousHash).encode()).hexdigest()

    def mine(self, difficulty):
        ans = ["0"]*difficulty
        answer = "".join(ans)
        while(str(self.hash)[:difficulty] != answer):
            self.nonce += 1
            self.hash = self.calHash()

        print('mined block: ', self.hash)
        return self.hash

그럼 Blockchain 클래스에서 블록을 추가할 때 사용한 addBlock에서 mine함수를 통해서 블록을 생성할 수 있게 변경하겠습니다.

 def addBlock(self, nBlock):
        nBlock.previousHash = self.chain[len(self.chain)-1].hash
        nBlock.hash = nBlock.mine(self.difficulty)
        self.chain.append(nBlock)

두 번째 줄의 nBlock.mine(self.difficulty)로 변경하였습니다. difficulty는 blockchain을 초기화할 때 값을 설정하도록 변경합니다. difficulty 수를 변경하면 마이닝 되는 블록의 시간을 조절할 수 있겠죵?

    def __init__(self, ):
        self.chain = []
        self.difficulty = 2
        self.createGenesis()

아래는 Blockchain 클래스의 전체 코드입니다

class BlockChain:
    def __init__(self, ):
        self.chain = []
        self.difficulty = 2
        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.mine(self.difficulty)
        self.chain.append(nBlock)

    def getLatestBlock(self):
        return self.chain[len(self.chain)-1]

    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

설정된 난이도에 따라 마이닝이 어떻게 달라지는 한 번 보겠습니다.
처음에 난이도를 2로 설정하고 실행시켜보면..

onion = BlockChain()
print('first block ..')
start_time = time.time();
onion.addBlock(Block(len(onion.chain),time.time(), {"amount":4}))
print('Passed time: ', time.time() - start_time)
first block ..
mined block:  00cff1af236ef6bc29f2f607ded9ee2d19880ef9075f292649b39835539a5002
Passed time:  0.0009968280792236328

난이도를 4로 설정하고 실행해보면..

first block ..
mined block:  00007dced727e23b17f336320ea35a513fd0d8500af4d6712616e124ae87f1c7
Passed time:  0.2004687786102295

시간이 꽤 오래걸린다는 걸 확인해볼 수 있습니다~~

전체 코드

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.nonce = 0
        self.hash = self.calHash()

    def calHash(self):
        return hashlib.sha256(str(self.index).encode() + str(self.data).encode() +
                            str(self.nonce).encode() + str(self.timestamp).encode() + str(self.previousHash).encode()).hexdigest()

    def mine(self, difficulty):
        ans = ["0"]*difficulty
        answer = "".join(ans)
        while(str(self.hash)[:difficulty] != answer):
            self.nonce += 1
            self.hash = self.calHash()

        print('mined block: ', self.hash)
        return self.hash


class BlockChain:
    def __init__(self, ):
        self.chain = []
        self.difficulty = 4
        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.mine(self.difficulty)
        self.chain.append(nBlock)

    def getLatestBlock(self):
        return self.chain[len(self.chain)-1]

    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
Sort:  

전혀 모르겠네요 이런 건 그냥 멋있어보여서 보팅하고 갑니다 ㅎ

ㅎㅎ 감사합니다 :)