Jak stworzyć Blockchain w Pythonie?

Photo of author

By maciekx

Czy zdajesz sobie sprawę, że Bitcoin opiera się na technologii łańcucha bloków? W tym artykule pokażemy, jak samodzielnie zbudować prosty łańcuch bloków w języku Python.

Czym jest łańcuch bloków?

W roku 2008, anonimowa postać lub grupa o pseudonimie Satoshi Nakamoto, opublikowała dokumentację Bitcoina. Bitcoin został zaprojektowany jako zdecentralizowana wersja pieniądza elektronicznego, umożliwiająca przeprowadzanie transakcji bez udziału pośredników, takich jak banki. Niewielu zdaje sobie sprawę, że w tym samym dokumencie Nakamoto przedstawił ideę rozproszonego sposobu przechowywania danych, który obecnie znamy jako łańcuch bloków.

Technologia Łańcucha Bloków

Mówiąc najprościej, łańcuch bloków to współdzielona, nienaruszalna księga cyfrowa, która zapisuje transakcje w zdecentralizowanej sieci komputerów.

Strukturę łańcucha bloków można uprościć do dwóch kluczowych elementów:

  • Blok: Miejsce, w którym przechowuje się dane transakcji.
  • Łańcuch: Sekwencja połączonych ze sobą rekordów.

Zatem łańcuch bloków to sekwencja połączonych bloków, gdzie każdy blok zawiera informacje o transakcjach wraz z określonymi parametrami.

Każdy blok jest powiązany z poprzednim, tworząc łańcuch bloków o niezmiennej strukturze. Oznacza to, że każdy blok jest zależny od poprzedniego. Dzięki temu otrzymujemy solidny i niezmienny system, w którym osoby z odpowiednimi uprawnieniami mogą weryfikować integralność danych.

Łańcuch bloków charakteryzuje się istotnymi właściwościami:

  • Nienaruszalność historii.
  • Trwałość przechowywanych informacji.
  • Wykluczenie możliwości błędów w danych.

Obecnie wiele systemów korzysta z technologii łańcucha bloków, w tym kryptowaluty, systemy transferu aktywów (NFT) oraz potencjalnie systemy głosowania w przyszłości.

Warto wspomnieć, że implementacja łańcucha bloków w języku Python nie musi być skomplikowanym projektem z tysiącami linii kodu. W swojej istocie jest to po prostu lista powiązanych ze sobą transakcji.

Oczywiście to tylko krótkie wprowadzenie. Jeśli chcesz dogłębnie zapoznać się z tematem, przygotowaliśmy obszerny przewodnik po łańcuchu bloków dla początkujących. Zachęcamy do jego lektury.

Przejdźmy teraz do praktyki i zbudujmy uproszczony łańcuch bloków przy użyciu języka Python.

Tworzenie łańcucha bloków w Pythonie

Zanim zaczniemy kodować, zdefiniujmy, jakie cele zamierzamy osiągnąć w ramach tego tutoriala:

  • Stworzenie prostego systemu łańcucha bloków w języku Python.
  • Wykorzystanie łańcucha bloków z predefiniowanymi transakcjami, które będą reprezentowane jako napisy.
  • Przetestowanie niezmienności utworzonego łańcucha bloków.

W tym projekcie nie będziemy używać JSON, ale list Pythona. Pozwoli nam to uprościć proces i skoncentrować się na najważniejszych koncepcjach łańcucha bloków.

Lista narzędzi i wiedzy, których potrzebujesz, aby wykonać ten tutorial:

Konstrukcja klasy Block

Uruchom preferowany edytor kodu i stwórz plik o nazwie main.py. W tym pliku będziemy pracować.

Na początek zaimportuj moduł hashlib, który umożliwi nam generowanie jednokierunkowych szyfrów. Techniki kryptograficzne, takie jak hashowanie, są kluczowe dla tworzenia bezpiecznych transakcji w łańcuchu bloków.

Funkcja hashująca to algorytm, który przetwarza dane wejściowe (zazwyczaj zakodowany ciąg) i generuje unikatowy identyfikator, często nazywany „skrótem” lub „sygnaturą”. Kluczową cechą funkcji hashujących jest to, że nawet niewielka zmiana w danych wejściowych powoduje drastyczną zmianę w wartości wyjściowej. Zobaczymy to w praktyce nieco później.

Na razie zaimportujmy wbudowany moduł hashlib:

# main.py
"""
Prosty łańcuch bloków w Pythonie
"""

import hashlib

Moduł ten udostępnia większość algorytmów hashowania, których możemy potrzebować. W naszym przykładzie będziemy używać funkcji hashlib.sha256().

Przejdźmy teraz do stworzenia klasy GeekCoinBlock, nazwy naszego własnego łańcucha bloków.

class GeekCoinBlock:
    
    def __init__(self, previous_block_hash, transaction_list):

        self.previous_block_hash = previous_block_hash
        self.transaction_list = transaction_list

        self.block_data = f"{' - '.join(transaction_list)} - {previous_block_hash}"
        self.block_hash = hashlib.sha256(self.block_data.encode()).hexdigest()

Możliwe, że ten fragment kodu wydaje się skomplikowany, więc przeanalizujmy jego poszczególne części.

Szczegółowa analiza klasy GeekCoinBlock

Zaczynamy od zdefiniowania klasy o nazwie GeekCoinBlock, która działa jako opakowanie dla obiektów, posiadających określone cechy (atrybuty) i zachowania (metody).

Następnie definiujemy metodę __init__ (konstruktor), która jest uruchamiana za każdym razem, gdy tworzymy nowy obiekt GeekCoinBlock.

Ta metoda przyjmuje trzy parametry:

  • self (odwołanie do bieżącej instancji obiektu)
  • previous_block_hash (odniesienie do skrótu poprzedniego bloku)
  • transaction_list (lista transakcji w bieżącym bloku)

Zapisujemy poprzedni skrót i listę transakcji, a następnie tworzymy zmienną instancji block_data jako ciąg. W rzeczywistych kryptowalutach dane te są często przechowywane jako hash, ale dla uproszczenia będziemy przechowywać dane każdego bloku jako ciąg.

Na koniec generujemy block_hash, który będzie używany przez inne bloki w łańcuchu. Tutaj wykorzystujemy moduł hashlib. Zamiast implementować własną funkcję skrótu, używamy gotowej funkcji sha256 do tworzenia niezmiennych bloków.

Funkcja ta oczekuje zakodowanych ciągów (lub bajtów) jako danych wejściowych, dlatego używamy metody block_data.encode(). Następnie wywołujemy hexdigest(), aby zwrócić zakodowane dane w formacie szesnastkowym.

Zdajemy sobie sprawę, że to może być przytłaczające, więc wypróbujmy działanie modułu hashlib w interaktywnej powłoce Pythona.

In [1]: import hashlib

In [2]: message = "Python jest super"

In [3]: h1 = hashlib.sha256(message.encode())

In [4]: h1
Out[4]: <sha256 ... object @ 0x7efcd55bfbf0>

In [5]: h1.hexdigest()
Out[5]: 'a40cf9cca ... 42ab97'

In [6]: h2 = hashlib.sha256(b"Python nie jest super")

In [7]: h2
Out[7]: <sha256 ... object @ 0x7efcd55bfc90>

In [8]: h2.hexdigest()
Out[8]: 'fefe510a6a ... 97e010c0ea34'

Jak widać, niewielka zmiana w danych wejściowych, np. z „Python jest super” na „Python nie jest super”, generuje zupełnie inny hash. Jest to kluczowe dla zapewnienia integralności łańcucha bloków. Nawet mała modyfikacja w łańcuchu spowoduje drastyczną zmianę jego skrótu. Dlatego stwierdzenie „Nie można zepsuć łańcucha bloków” jest prawdziwe.

Wykorzystanie klasy bloku

W dalszej części tutoriala stworzymy pełną klasę łańcucha bloków, ale na razie wykorzystajmy naszą klasę Block do stworzenia prostego łańcucha.

W tym samym pliku utwórz kilka transakcji reprezentowanych przez proste ciągi znaków, na przykład:

class GeekCoinBlock:
    ...

t1 = "Alicja przesyła 5 GC do Bartka"
t2 = "Bartek przesyła 2.3 GC do Czarka"
t3 = "Czarek przesyła 4.2 GC do Dagmary"
t4 = "Dagmara przesyła 1.1 GC do Alicji"

GC to oczywiście skrót od GeekCoin.

Teraz zbudujmy pierwszy blok naszego łańcucha bloków przy użyciu klasy GeekCoinBlock i wyświetlmy jego atrybuty. Zauważ, że parametr previous_hash bloku genezy (pierwszy blok w łańcuchu) przyjmuje dowolną wartość. W naszym przypadku jest to ciąg „pierwszyblok”.

block1 = GeekCoinBlock('pierwszyblok', [t1, t2])

print(f"Dane bloku 1: {block1.block_data}")
print(f"Hash bloku 1: {block1.block_hash}")

Następnie tworzymy drugi blok, wykorzystując hash pierwszego bloku jako argument previous_hash.

block2 = GeekCoinBlock(block1.block_hash, [t3, t4])

print(f"Dane bloku 2: {block2.block_data}")
print(f"Hash bloku 2: {block2.block_hash}")

Uruchommy kod i przeanalizujmy dane wyjściowe. Wpisz w terminalu:

❯ python main.py
Dane bloku 1: Alicja przesyła 5 GC do Bartka - Bartek przesyła 2.3 GC do Czarka - pierwszyblok
Hash bloku 1: 01e4e15242a9601725f4a86ca01fbddaaec7105b442955bb0efcadbfc759806d
Dane bloku 2: Czarek przesyła 4.2 GC do Dagmary - Dagmara przesyła 1.1 GC do Alicji - 01e4e15242a9601725f4a86ca01fbddaaec7105b442955bb0efcadbfc759806d
Hash bloku 2: 448c4306caf7f6937b0307f92f27fbea3bb73b3470363dee5026a1209dadcfa8

Na razie widzimy tylko tekst i 64-znakowe hashe, ale w ten sposób działa mechanizm łańcucha bloków.

Zaczynamy od bloku genezy, który jest podstawą wszystkich pozostałych bloków.

Każdy może zweryfikować integralność łańcucha, co czyni go tak bezpiecznym systemem. Na przykład, jeśli zmodyfikujemy treść transakcji, np.:

t2 = "Bartek przesyła 2.3 GC do Czarka" -> t2 = "Bartek przesyła 3.2 GC do Czarka"

Zauważymy drastyczną zmianę w hashach bloków.

Dane bloku 1: Alicja przesyła 5 GC do Bartka - Bartek przesyła 3.2 GC do Czarka - pierwszyblok
Hash bloku 1: 7a990bf1d70230bf2dad6160496c0b3046da7a17b1281fd1d4c63d4eac58e78c
Dane bloku 2: Czarek przesyła 4.2 GC do Dagmary - Dagmara przesyła 1.1 GC do Alicji - 7a990bf1d70230bf2dad6160496c0b3046da7a17b1281fd1d4c63d4eac58e78c
Hash bloku 2: 569b977306ce88b53e001dca7ba00c03a51c60d6df4650e7657dcd136f2da0ac

Możesz podejrzeć aktualny projekt w Repozytorium GitHub.

Implementacja klasy łańcucha bloków

Opieranie integralności systemu na ręcznie zakodowanych zmiennych nie jest najlepszym rozwiązaniem, więc potrzebujemy innego podejścia.

Mamy już bloki, więc stwórzmy klasę, która połączy je w łańcuch bloków.

Zacznijmy od usunięcia poprzednich transakcji i obiektów bloków, a następnie wykorzystajmy poniższy kod:

# main.py

class Blockchain:
    def __init__(self):
        self.chain = []
        self.generate_genesis_block()

    def generate_genesis_block(self):
        self.chain.append(GeekCoinBlock("0", ['Blok genezy']))
    
    def create_block_from_transaction(self, transaction_list):
        previous_block_hash = self.last_block.block_hash
        self.chain.append(GeekCoinBlock(previous_block_hash, transaction_list))

    def display_chain(self):
        for i in range(len(self.chain)):
            print(f"Dane {i + 1}: {self.chain[i].block_data}")
            print(f"Hash {i + 1}: {self.chain[i].block_hash}n")

    @property
    def last_block(self):
        return self.chain[-1]

Ponownie mamy duży fragment kodu. Przeanalizujmy go krok po kroku:

  • self.chain — Lista, w której przechowujemy wszystkie bloki. Dostęp do poszczególnych bloków uzyskujemy za pomocą indeksów listy.
  • generate_genesis_block — Dodaje blok genezy (pierwszy blok) do łańcucha. Poprzedni hash bloku wynosi „0”, a lista transakcji to po prostu „Blok genezy”.
  • create_block_from_transaction — Umożliwia dodawanie do łańcucha bloków zawierających listę transakcji. Ręczne tworzenie bloków za każdym razem, gdy chcemy zarejestrować transakcję, byłoby bardzo uciążliwe.
  • display_chain — Wyświetla łańcuch bloków za pomocą pętli for.
  • last_block — Właściwość, która umożliwia dostęp do ostatniego elementu łańcucha. Używamy jej w metodzie create_block_from_transaction.

Przetestujmy teraz działanie tego łańcucha bloków.

# main.py

import hashlib

class GeekCoinBlock:
    ...


class Blockchain:
    ...

t1 = "Grzegorz przesyła 3.1 GC do Jacka"
t2 = "Jacek przesyła 2.5 GC do Adama"
t3 = "Adam przesyła 1.2 GC do Bartka"
t4 = "Bartek przesyła 0.5 GC do Cezarego"
t5 = "Cezary przesyła 0.2 GC do Daniela"
t6 = "Daniel przesyła 0.1 GC do Eryka"

myblockchain = Blockchain()

myblockchain.create_block_from_transaction([t1, t2])
myblockchain.create_block_from_transaction([t3, t4])
myblockchain.create_block_from_transaction([t5, t6])

myblockchain.display_chain()

Uruchom teraz plik main.py.

Data 1: Blok genezy - 0
Hash 1: 39331a6a2ea1cf31a5014b2a7c9e8dfad82df0b0666e81ce04cf8173cc5aed3e

Data 2: Grzegorz przesyła 3.1 GC do Jacka - Jacek przesyła 2.5 GC do Adama - 39331a6a2ea1cf31a5014b2a7c9e8dfad82df0b0666e81ce04cf8173cc5aed3e
Hash 2: 98cf363aecb33989aea0425a3c1287268bd86f63851bc08c0734a31db08506d5

Data 3: Adam przesyła 1.2 GC do Bartka - Bartek przesyła 0.5 GC do Cezarego - 98cf363aecb33989aea0425a3c1287268bd86f63851bc08c0734a31db08506d5
Hash 3: 6f1cfcc3082488b97db8fdf8ed33f9ac7519be3e285a37a6fcc2f1904f373589

Data 4: Cezary przesyła 0.2 GC do Daniela - Daniel przesyła 0.1 GC do Eryka - 6f1cfcc3082488b97db8fdf8ed33f9ac7519be3e285a37a6fcc2f1904f373589
Hash 4: 869df2f03c9860767d35b30a46233fbeea89a3000ae5019d1491e3829d1ab929

Gratulacje! Właśnie udało Ci się stworzyć prosty łańcuch bloków w języku Python.

Teraz możesz wzmocnić niezmienność łańcucha bloków, używając getterów i setterów oraz zaimplementować dodatkowe funkcje, takie jak dowód pracy, proces kopania lub inne koncepcje, które omówiliśmy w artykule na temat podstaw kopania bitcoinów.

Podsumowanie

Łańcuch bloków to technologia, która leży u podstaw Bitcoina, Etherium i wszystkich innych kryptowalut. W tym artykule nauczyłeś się, jak stworzyć łańcuch bloków w języku Python, wykorzystując algorytmy hashowania, takie jak sha256, klasy i obiekty.

Twoim zadaniem jest stworzenie systemu wydobywczego i ewentualne zaimplementowanie go za pomocą REST API z wykorzystaniem frameworków takich jak Django lub Flask.

Wiele osób zarabia fortunę na kryptowalutach. Wyobraź sobie, co mógłbyś osiągnąć, gdybyś stworzył ją samodzielnie.

Nie przestawaj kodować! 👨‍💻


newsblog.pl