Budowanie blockchain w 15 minut

18

Co to jest technologia blockchain? To pytanie pojawia się coraz częściej w środowisku programistów. Jestem przekonany, że w niedalekiej przyszłości każdy ze środowiska IT zetknie się z tą technologią. Warto więc znać przynajmniej jej podstawy teoretyczne i budowę.

W tym artykule chciałbym przedstawić prostą implementację blockchain w języku C#. Postaram się pokazać, że sama technologia nie jest skomplikowana. Można ją zaimplementować w bardzo prosty sposób w kilkanaście minut, używając kilkudziesięciu linii kodu.

Wprowadzenie

Zastosowanie tej technologii może być skomplikowane, ale sam blockchain nie jest tego powodem. Komplikacje pojawiają się na poziomie bazującego na nim protokołu. Mam tu na myśli różnego rodzaju algorytmy kryptograficzne, metody uzyskiwania konsensusu, sposoby gratyfikacji za utrzymanie sieci, metody tworzenia transakcji, budowę węzłów sieci i ich komunikację między sobą oraz wiele innych elementów składowych kompletnego systemu. Prostotę samej technologii postaram się przedstawić na prostym przykładzie, ponieważ preferuję naukę poprzez wykonywanie, w myśl teorii:

To, co usłyszysz, zapomnisz; to, co zobaczysz, zapamiętasz; to, co zrobisz, zrozumiesz

T. Harv Eker (“Bogaty albo biedny. Po prostu różni mentalnie“)

Teoria

Zanim przejdziemy do implementacji – krótki wstęp teoretyczny. :]

Blockchain

Blockchain, jak sama nazwa wskazuje, to łańcuch bloków, które połączone są między sobą za pomocą kryptografii. W każdym bloku zapisywane są różnego rodzaju dane. Mogą to być np. dane dotyczące transakcji finansowych (obecnie najbardziej popularne), ale ta technologia bardzo szybko się rozwija i mamy już projekty zapisujące inne informacje, choćby dane medyczne, osobowe czy własność intelektualną.

Hasz (hash)

Zanim przejdziemy do implementacji, warto zapoznać się jeszcze z pojęciem hasza. W prostych słowach jest to pewien podpis cyfrowy pakietu danych. Powstaje za pomocą odpowiedniej funkcji (skrótu/haszującej), w której zaimplementowany jest specjalny algorytm. Funkcja ta dla tego samego pakietu danych wejściowych zwraca taki sam wynik – hasz.

Konstrukcja blockchain

W najprostszej wersji minimalistycznej blockchain jest złożony z listy bloków, z których każdy składa się z:

  1. hasza bloku poprzedniego,
  2. listy transakcji,
  3. hasza bloku aktualnego.
Blockchain - prosty diagram

Blockchain – prosty diagram

Implementacja

Na początek przygotowałem swoją funkcję haszującą, która bazuje na algorytmie SHA256:

public static string GetSha256Hash(object obj)
{
    var sha256 = new SHA256Managed();
    var hashBuilder = new StringBuilder();
            
    // zamiana obiektu na tablicę bajtów
    byte[] bytes = ObjectToByteArray(obj);
    // obliczanie hasza
    byte[] hash = sha256.ComputeHash(bytes);
            
    // konwersja tablicy bajtów na łańcuch znaków hexadecymalnych
    foreach (byte x in hash)
        hashBuilder.Append($"{x:x2}");

    return hashBuilder.ToString();
}

Żeby zobrazować, czym jest hasz, utworzyłem dwa obiekty:

string[] test1 = { "a", "b", "c" };
string[] test2 = { "a", "b", "c" };

i wypisałem na konsolę ich hasze za pomocą przedstawionej wcześniej funkcji haszującej:

Console.WriteLine($"Hash 1: {Helper.GetSha256Hash(test1) }");
Console.WriteLine($"Hash 2: {Helper.GetSha256Hash(test2) }");

Dało to wynik:

Hash 1: 3b5777b0a5baf0ef260f46e3228f6cc033d7daecd1ba5240c1c96f40ea07d08d
Hash 2: 3b5777b0a5baf0ef260f46e3228f6cc033d7daecd1ba5240c1c96f40ea07d08d

Następnie zmieniłem wartość jednego z obiektów – dodałem do tablicy dodatkowy element:

string[] test1 = { "a", "b", "c" };
string[] test2 = { "a", "b", "c", "d" };

co dało wynik:

Hash 1: 3b5777b0a5baf0ef260f46e3228f6cc033d7daecd1ba5240c1c96f40ea07d08d
Hash 2: 5b0ec165e91dd27eaf5ae4c59d0ed41c38a4b496073eef0825e5972e4e4ff427

Jak widać, gdy dane mają stałą wartość, hasz jest zawsze taki sam. Jeśli natomiast dane się zmienią, to hasz również będzie inny. Wystarczy zmiana jednego bajtu, by hasz miał zupełnie inną wartość.

Pora przejść do sedna wpisu i zbudować w końcu blockchain. :) Będziemy do tego potrzebowali klasy transakcji:

[Serializable]
public class Transaction
{
    public double Amount { get; set; }
    public string From { get; set; }
    public string To { get; set; }
}

Właściwości klasy transakcji nie mają większego znaczenia – może tam się znaleźć cokolwiek w zależności od tego, co w danym blockchainie będziemy zapisywać. W tym przykładzie zapisujemy transakcje transferu wartości – mamy nadawcę, odbiorcę i ilość, jak w najprostszym scenariuszu transferu środków finansowych. W innych przypadkach struktura klasy mogłaby wyglądać zupełnie inaczej.

Następnie przygotujmy klasę bloku:

[Serializable]
public class Block
{
    public string Hash { get; set; }
    public string PreviousHash { get; set; }
    public List<Transaction> TransactionList { get; set; }
}

Na koniec utwórzmy klasę Blockchain:

public class Blockchain
{
    public List<Block> BlockList { get; set; }
    public Block CurrentBlock { get; set; }
}

Można powiedzieć, że to tylko klasa pomocnicza, bo nie jest ona konieczna.

Wykorzystanie utworzonych klas:

Blockchain blockchain1 = new Blockchain();

blockchain1.AddTransaction(5, "Jan", "Michał");
blockchain1.AddTransaction(2.1, "Grzegorz", "Kamil");
blockchain1.AddTransaction(1.23, "Paweł", "Jakub");

blockchain1.SaveBlock();
Console.WriteLine(blockchain1.CurrentBlock.PreviousHash);
blockchain1.SaveBlock();
Console.WriteLine(blockchain1.CurrentBlock.PreviousHash);

Tworzę obiekt blockchain, do którego dodaję transakcje (dodawane są one do aktualnego bloku). Następnie zapisuję transakcję, co wiąże się z dodaniem aktualnego bloku do łańcucha i utworzeniem nowego, pustego bloku. Wynikiem działania takiego programu będzie wypisanie dwóch haszów poszczególnych bloków w łańcuchu:

3021721be0112ccb3895ffa0372ba1e901db7bed68e89dd6c241cc76e4e8c847
9e9f9dbfab7c1b9efb0e1a645f805536ea81249d8926b714ee5805be737a6e5d

Jedną z głównych cech blockchainu, jaką możemy tutaj zaobserwować, jest jego niezmienialność. Wystarczy, że zmienię jakąkolwiek informację w którejkolwiek transakcji w którymś z bloków, a wszystkie hasze się zaktualizują. Przykład:

zmieniam:

blockchain1.AddTransaction(5, "Jan", "Michał");

na:

blockchain1.AddTransaction(4, "Jan", "Michał");

Wynik działania programu po tej zmianie będzie następujący:

ed71792dc315cecf92b4d6f0459220c94846fcbc224f726b7133c7579e92563f
f95020f748251092b8747681329eefc52a769b69d8581f8aa8efb9274fe45fd4

Modyfikacja jakiejkolwiek transakcji w łańcuchu powoduje zmianę haszów w całym łańcuchu od bloku, w którym modyfikacja transakcji nastąpiła. Jest to więc metoda na zachowanie niezmienności, ponieważ jakiekolwiek zmiany w łańcuchu spowodują niezgodność haszów.

Kompletny kod źródłowy jest dostępny na GitHubie:
https://github.com/plipowczan/MySimpleBlockchain.

Podsumowanie

Cała idea wykorzystania technologii blockchain polega na utrzymaniu niezmienności danych i ich bezpieczeństwie. Na podstawie powyższego przykładu implementacji jesteśmy w stanie zaobserwować, że da się to osiągnąć. Mechanizmy uzyskania niezmienności i bezpieczeństwa są zapewnione przez odpowiednie protokoły. Budowę prostego systemu opartego na takim protokole przedstawię w kolejnym wpisie.

Pytania? Sugestie? Zostaw komentarz!

Nie przegap kolejnych postów!

Dołącz do ponad 9000 programistów w devstyle newsletter!

Tym samym wyrażasz zgodę na otrzymanie informacji marketingowych z devstyle.pl (doh...). Powered by ConvertKit
Share.

About Author

My name is Paweł and I work as a project manager and software developer. I love programming and new technologies. That is why blockchain technology does really turn me on these days. I like to read and learn new stuff which I can use on daily basis. I'm relatively new to this stuff since I started to put my interest in blockchain technology in early april 2018. I live in Ustroń, Poland where I also work - remotely most of the time. When I'm not programming or managing the projects I care for my wife and kids (I have two of them boy - 7 years old and girl - 5 years old) and I practice some sports - mostly gym and running at the moment.

18 Comments

    • Postaram się aby wpisy pojawiały się jak najczęściej. Najprawdopodobniej będzie to jeden wpis w miesiącu – postaram się częściej, ale nie mogę nic obiecać bo jestem ostatnio mocno obciążony innymi obowiązkami. Prośba o wyrozumiałość i cierpliwość :)

  1. Cześć,

    Dobrze się składa, bo akurat zacząłem zgłębiać tę tematykę i mam następujące pytanie (być może wybiegnę o 1 lub 2 artykuły w przód).

    Gdy dochodzimy do PoW i ustalamy pewne difficulty (czyli ilość 0 na początku hasha), w naszym bloku posiadamy nonce, inkrementowany co kolejne obliczenie hasha – dla większej ilości 0 bardzo łatwo go “przekręcić” (mamy tylko int i 32 bity), a nie mogłem się doszukać jednoznacznej odpowiedzi w sieci – jakie inne pole w bloku możemy wtedy zmodyfikować/dodać aby “kopać dalej” – extra nonce, timestamp, coś innego?

    • to zależy od konkretnej implementacji blockchaina. Generalnie możesz zmieniać wszystko, co na końcu jest hashowane. By zmienił się hash transakcji, po prostu chcesz wprowadzić zmianę w blocku. Natomiast może to być dowolna zmiana. Najprostszą jest oczywiście zmiana nonce, ale kolejną może być np. zmiana kolejności transakcji. Transakcje najczęściej są podczepione za pomocą merkle-tree do roota. Gdy hash roota się zmieni, to efekt jest ten sam jakby zmienił się nonce.

      Zakładając, że trzeba wpakować do blocku transakcje, które są od siebie niezależne, to mając 1000 transakcji, możesz je ułożyć na 1000! ~= 4*10^2567 sposobów… więc możliwych permutacji raczej nigdy nie braknie.

      • Dzięki, o tym nie pomyślałem. A możesz mi wskazać przykładowe (życiowe) rozwiązania co jest wykorzystane w jakim blockchainie? Przeglądając komentarze po rożnych forach trafiłem np. na “getblocktemplate” ale chciałem wyjść od czegoś możliwie prostego na start.

      • Pawel Nowosielski on

        @Piotr – myślę, że dobrym, praktycznym rozwiązaniem jest zignorowanie problemu “przekręconego nonce”.
        1) jesteś jednym z wielu nodów kopiących dany blok, każdy z nich ma inny zbiór tx i w innej kolejności.
        2) “difficulty” nie jest zadane z góry, ale jest funkcją – np. sieć zaczyna akceptować bloki “z mniejszą ilością zer” jeśli od ostatniego bloku minęło ileś czasu.
        A więc sprowadza się to do tego, że szansa niewykopania następnego bloku jest coraz miej prawdopodobna

  2. Ostatnio coraz bardziej interesuje mnie wykorzystanie blockchaina w biznesie. Sporo czytałem w innych źródłach i trochę brakowało mi tego tematu na devstyle. Trzymam kciuki i z pewnością będę śledził cykl! :-)

    Pozdrawiam!

  3. Łukasz Krawczak on

    Ciekaw jestem jakby się to prezentowało w bardziej skomplikowanych projektach. Mimo wszystko będę zaglądał. :)

  4. Do pełnego zrozumienia kodu zarakło mi implementacji metody SaveBlock().

    Dobry artykół, świetne wprowadzenie. Z chęcią zobaczyłbym jeszcze modyfikację bloku oraz przeliczanie wszystkich hashów. Oczywiście jeżeli byłoby to ok z założeniami blockchain.

    • Dzięki za feedback. Kompletne źródła możesz obejrzeć na github – link w artykule (na końcu).
      Jeśli chodzi o modyfikację bloków to po zatwierdzeniu się tego już nie robi – to jedna z idei blockchain, czyli niezmienialność a te hasze umożliwiają łatwą weryfikację czy łańcuch zatwierdzonych bloków nie został zmieniony.

  5. Pozycjonowanie on

    Fajnie to wyjaśniłeś. Zawsze miałem problem ze zrozumieniem tego :D Pytałem kumpli po fachu to też mi jasno nie potrafili odpowiedzieć. Dzięki fajny artykuł :)

  6. Świetny wstęp – można spokojnie załapać ideę za tym stojącą, bez przegrzebywania się przed setki artykułów napakowanych ‘buzzwordami’ :)

Leave A Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.