Kontynuacja części pierwszej "Budujemy własny Blockchain cz 1"
Sama idea stworzenia łańcucha bloków (z ang. Blockchain) polega z grubsza na połączeniu pojedynczych zbiorów informacji, w jeden niepodważalny ciąg. Nie ma w tym przypadku znaczenia czy będą to informacje na temat stanu konta czy też smart kontrakty.
Każdy blok składa się ze znaku czasowego, danych transakcji oraz kryptograficznego haszu (ang. hash) poprzedniego bloku, dzięki któremu formują one jednokierunkowy łańcuch, w którym tworzone bloki powiązane są ze wszystkimi wcześniejszymi. źródło:https://pl.wikipedia.org/wiki/Blockchain
Na początku, nim przejdziemy dalej, przyjrzyj się kodowi i spróbuj powiedzieć co się tam dzieje.
class Blockchain(object):
"""It is a conceptual representation of the class of a simple
blockchain.
:param difficulty: int
"""
__name__ = "Mini blockchain class"
__author__ = "Kamil Mirończuk"
__license__ = "GNU General Public License v3.0"
__version__ = "1.0"
__status__ = "production"
def __init__(self, difficulty=2):
"""Constructor method"""
self.__difficulty = difficulty
self.__chain = []
self.__unconfirmed_transactions = []
self.__hash = None
self.create_genesis_block()
def create_genesis_block(self):
"""Create first blockchain block.
:return: None
"""
genesis_block: Block = Block(0, [], datetime.now(), "0")
genesis_block.hash = genesis_block.compute_hash
self.__chain.append(genesis_block)
@property
def last_block(self) -> Block:
"""Return last block added to blockchain.
:return: last block
:rtype: Block
"""
return self.__chain[-1]
def add_new_transaction(self, data):
"""Add new unconfirmed transaction to Blockchain.
:param data: str
:return: None
"""
self.__unconfirmed_transactions.append(data)
def proof_of_work(self, block) -> str:
"""Return valid computed hash for Block.
:param block: Block
:return: str
"""
block.nonce = 0
computed_hash = block.compute_hash
while not computed_hash.startswith("0" * self.__difficulty):
block.nonce += 1
computed_hash = block.compute_hash
return computed_hash
def mine(self) -> bool:
"""Add unconfirmed transactions as new Block.
:return: true when the block has been mined
:rtype: bool
"""
if not self.__unconfirmed_transactions:
return False
last_block = self.last_block
new_block = Block(
index=last_block.index + 1,
transactions=self.__unconfirmed_transactions,
timestamp=datetime.now(),
previous_hash=last_block.hash,
)
proof = self.proof_of_work(new_block)
self.add_block(new_block, proof)
self.__unconfirmed_transactions = []
return True
def is_valid_proof(self, block, block_hash) -> bool:
"""Check if is valid proof of work.
:return: true when block start with "0" x difficulty
:rtype: bool
"""
return (
block_hash.startswith("0" * self.__difficulty)
and block_hash == block.compute_hash
)
def add_block(self, new_block, proof) -> bool:
"""Add block to Blockchain.
:return: true when block has been appended
:rtype: bool
"""
previous_hash = self.last_block.hash
if previous_hash != new_block.previous_hash:
return False
if not self.is_valid_proof(new_block, proof):
return False
new_block.hash = proof
self.__chain.append(new_block)
return True
def get_full_block(self):
"""Print full blockchain into console.
:return: None
"""
for block in self.__chain:
print(block())
def verify_full_block_history(self):
"""Verify Blockchain history.
:return: true, if block is correct
"""
print("Verify block")
for block in self.__chain:
print(block())
if block.index != 0:
block_before_hash = self.__chain[
block.index - 1
].compute_hash
if block_before_hash == block.previous_hash:
print("Correct block")
else:
raise TypeError("Block invalid")
return True
Ciekawsze miejsca:
1. Na samym początku tworzymy sztucznie pierwszy blok, ze względu na to że każdy następny będzie wymagał poprawnego hasha poprzedniego, celem zachowania ciągłości łańcucha. A pierwszy naturalnie będzie miał wartość zgodnie z wzorcowym protokołem Etherum wypełnioną zerami.
2. Samo generowanie hash-a aktualnego bloku, polega zrzucie wartości wszystkich zmiennych klasy (poza zapisanym aktualnym hashem) i wygenerowaniu z nich skrótu sha256.
3. Dodawanie samych danych do bloku, wymaga wygenerowania takiego hash-a który zaczyna się od samych zer, gdzie ilość tych zer jest zdefiniowana przez wartość "difficulty". Następuje to przez manipulację i inkrementację wartości zmiennej "nonce"
4. Celem zweryfikowania poprawności samego bloku, możemy wywołać "verify_full_block_history" która sprawdza dla każdego bloku czy wartość zmiennej "__previous_hash" jest w pełni zgodna z realnie wygenerowanym hashem.
Pierwsze uruchomienie
Przekonajmy się jak to działa:
print("---Create Blockchain---")
myBlockchain = Blockchain(difficulty=3)
print(myBlockchain.last_block.hash)
print("\n---Add data to Blockchain---")
for i in range(1, 6, 2):
myBlockchain.add_new_transaction("part {}".format({i}))
myBlockchain.add_new_transaction("part {}".format({i + 1}))
myBlockchain.mine()
print(myBlockchain.last_block())
print(myBlockchain.last_block.compute_hash)
print("\n---Print block data---")
myBlockchain.get_full_block()
print("\n---Verify full block history---")
myBlockchain.verify_full_block_history()
Otrzymujemy wynik:
---Create Blockchain---
c98857fd9fa61605f41817e8e8d06ae83352004b343699d02ae1112d98390a57
---Add data to Blockchain---
Block nr 1 -> data ['part {1}', 'part {2}']
0005777be52c5bd6ce46ca95c1002046d11d2029fa132499315ef64802983eb5
Block nr 2 -> data ['part {3}', 'part {4}']
0006deddfcf114fa48a3d0dae8b57ced159a2b9bfc4861e5571655940f785edd
Block nr 3 -> data ['part {5}', 'part {6}']
000ea32560821eafe899be5e7b8ef96b1ec754d31a327ed504d6f20eafa6efbc
---Print block data---
Block nr 0 -> data []
Block nr 1 -> data ['part {1}', 'part {2}']
Block nr 2 -> data ['part {3}', 'part {4}']
Block nr 3 -> data ['part {5}', 'part {6}']
---Verify full block history---
Block nr 0 -> data []
Block nr 1 -> data ['part {1}', 'part {2}']
Correct block
Block nr 2 -> data ['part {3}', 'part {4}']
Correct block
Block nr 3 -> data ['part {5}', 'part {6}']
Correct block
Komentarze