파이썬으로 나만의 블록체인 만들기 #3

정보보안 2017. 11. 16. 05:21

#참고 : 이 글은 @ecomusing 님의 포스트를 제 맘대로 번역한 글입니다. 원문보기

#참고2 : 전체 Jupyter / iPython 소스코드 받기 


체인 유효성 검사


어떻게 새로운 블록들을 만들고 블록들이 체인 안으로 어떻게 서로 연결이 되는지 확인하였으니 이제 새로운 블록들이 유효한지, 더 나아가 전체 체인이 유효한지 확인하는 함수를 정의해보도록 합시다.


블록체인 네트워크에서 유효성 검사는 다음과 같은 두 가지 의미에서 중요합니다.


◆ 노드를 초기 설정할 떄, 전체 블록체인 히스토리를 다운로드 합니다. 체인의 다운로드 이후에는 시스템 상태 확인을 위해서 블록체인을 훓어보는 것이 필요합니다. 누군가가 유효하지 않은 거래내역을 체인 앞단에 삽입한는 것을 방지하지 위해서 이 초기 다운로드된 체인 전체에 대한 유효성 검사를 하는 것이 필요합니다.


◆ 노드가 네트워크와 동기화 되었을 때(최신의 블록체인 사본을 갖고 시스템이 준비된 상태), 네트워크에 브로드캐스트(broadcast)하여 새로운 블록들에 대한 유효성 검사를 하는 것이 필요합니다.


이 유효성 검사를 위해서 3가지 함수들이 필요합니다.


◆ checkBlockHash : 블록의 내용(contents)가 해쉬값과 동일한지 확인 시켜주는 간단한 helper function 입니다


◆ checkBlockValidity : 블록의 부모 및 현재 시스템 상태를 고려한 블록에 대한 유효성 검사를 합니다. 만약 블록이 유효하면 업데이트된 상태를 리턴하고, 유효하지 않으면 에러를 발생시킵니다.


◆ checkChain : 모든 체인에 대해서 유효성 검사를 하고 genesis block 을 포함한 시스템 상태에 대한 확인을 수행합니다. 만약 체인이 유요하다면 시스템 상태를 리턴해주고 그렇지 않으면 에러를 발생시킵니다.


In [14]:
def checkBlockHash(block):
    # Raise an exception if the hash does not match the block contents
    expectedHash = hashMe( block['contents'] )
    if block['hash']!=expectedHash:
        raise Exception('Hash does not match contents of block %s'%
                        block['contents']['blockNumber'])
    return
In [15]:
def checkBlockValidity(block,parent,state):    
    # We want to check the following conditions:
    # - Each of the transactions are valid updates to the system state
    # - Block hash is valid for the block contents
    # - Block number increments the parent block number by 1
    # - Accurately references the parent block's hash
    parentNumber = parent['contents']['blockNumber']
    parentHash   = parent['hash']
    blockNumber  = block['contents']['blockNumber']
    
    # Check transaction validity; throw an error if an invalid transaction was found.
    for txn in block['contents']['txns']:
        if isValidTxn(txn,state):
            state = updateState(txn,state)
        else:
            raise Exception('Invalid transaction in block %s: %s'%(blockNumber,txn))

    checkBlockHash(block) # Check hash integrity; raises error if inaccurate

    if blockNumber!=(parentNumber+1):
        raise Exception('Hash does not match contents of block %s'%blockNumber)

    if block['contents']['parentHash'] != parentHash:
        raise Exception('Parent hash not accurate at block %s'%blockNumber)
    
    return state
In [16]:
def checkChain(chain):
    # Work through the chain from the genesis block (which gets special treatment), 
    #  checking that all transactions are internally valid,
    #    that the transactions do not cause an overdraft,
    #    and that the blocks are linked by their hashes.
    # This returns the state as a dictionary of accounts and balances,
    #   or returns False if an error was detected

    
    ## Data input processing: Make sure that our chain is a list of dicts
    if type(chain)==str:
        try:
            chain = json.loads(chain)
            assert( type(chain)==list)
        except:  # This is a catch-all, admittedly crude
            return False
    elif type(chain)!=list:
        return False
    
    state = {}
    ## Prime the pump by checking the genesis block
    # We want to check the following conditions:
    # - Each of the transactions are valid updates to the system state
    # - Block hash is valid for the block contents

    for txn in chain[0]['contents']['txns']:
        state = updateState(txn,state)
    checkBlockHash(chain[0])
    parent = chain[0]
    
    ## Checking subsequent blocks: These additionally need to check
    #    - the reference to the parent block's hash
    #    - the validity of the block number
    for block in chain[1:]:
        state = checkBlockValidity(block,parent,state)
        parent = block
        
    return state

이제 유효성 검사를 해봅시다.

In [17]:

checkChain(chain)
Out[17]:
{'Alice': 72, 'Bob': 28}

비록 우리는 텍스트 파일(백업을 하거나 초기에 불러오는 작업에 쓰일 수 있는)에서 체인을 불러왔지만, 우리는 체인의 무결성을 확인할 수 있었고 현재 상태(거래 결과)를 확인할 수 있습니다.

In [18]:
chainAsText = json.dumps(chain,sort_keys=True)
checkChain(chainAsText)
Out[18]:
{'Alice': 72, 'Bob': 28}