Vytvorme si vlastný Blockchain v Jave
V tomto tutoriály spoločne vytvoríme svoj vlastný blockchain. Nebude to samozrejme reálny blockchain, s ktorým by si mohol spraviť dieru do sveta, ale naučíme sa pri tom, aké sú stavebné kamene blockhainu. Použité príklady sú zjednodušené a nezobrazujú realitu.
V prvom rade si treba ale povedať, čo je to blockchain.
Čo to vlastne je ten blockchain?
Blockchain je technológia, na ktorej sú postavené kryptomeny. Prvýkrát túto technológiu ako ju poznáme popísal mysteriózny Satoshi Nakamoto v roku 2008 vo svojej práci: Bitcoin: A Peer-to-Peer Electronic Cash System.
Kryptomena a blockchain nie je to isté. Bitcoin je kryptomena, ktorá používa princípy blockchain technológie.
Blockchain je dosť komplikovaná vec, ktorej ani ja úplne nerozumiem. Pokúsim sa to vysvetliť veľmi zjednodušene v rámci možností.
Predstavme si nejakú účtovnú knihu, alebo vkladnú knižku, kde sú nie len vklady, ale aj výbery.
V blockchaine sú ale transakcie všetkých, nie len mňa. Každý, kto je súčasťou tejto veľkej účtovnej knihy môže do nej vkladať a vyberať svoje peniaze. Každý má vlastné peniaze, a teda keď Zuzka vloží do tejto knihy 100 peňazí tak Ferko s nimi nemôže nič robiť.
No ale čo keď Janko dlží peniaze Radovi? A čo keď Petra chce zaplatiť Zuzke, ale nemá dostatok peňazí? Zatiaľ sme mali účtovne knihy, v ktorej si každý vkladal a vyberal svoje peniaze, ale neposielali si ich navzájom. V Blockchaine každý môže poslať časť svojich peňazí hocikomu inému, a samozrejme nemôže poslať viac ako vlastní. Jeden prevod (či už vloženie alebo poslanie inému) nazývame transakcia.
Už sa nám to začína pomaličky podobať blockchainu, jedine že by nie. Podobá sa nám to skôr na obyčajnú banku, v ktorej majú ľudia založené účty a môžu si vkladať, vyberať a posielať peniaze. V čom je rozdiel medzi bankou a blockchainom? To je dobrá otázka.
Rozdiel medzi bankou a blockchainom
Najväčším rozdielom je fakt, že banka je centralizovaná a blockchain je decentralizovaný. Čo to znamená? Banka je firma, spoločnosť, ktorá spravuje všetky transakcie, určuje pravidlá a spĺňa úlohu centrálnej autority, ktorej veríme, že ak sa stane nejaký omyl alebo problém, tak nám pomôže s riešením.
Blockchain je na rozdiel od banky decentralizovaný a to znamená, že neexistuje žiadna centrálna autorita, cez ktorú by prechádzali transakcie, a ktorá by riešila prípadne problémy.
Ako teda môžeme veriť niečomu, čo nemá centrálnu autoritu?
Každý decentralizovaný blockchain, a teda aj kryptomena funguje na nejakom protokole. Protokol väčšinou spĺňa tieto (a ďalšie) podmienky:
- Tá účtovná kniha všetkých (blockchain), ktorú sme opisovali vyššie je verejná a môže byť zdieľaná a udržiavaná hocikým
- Tisícky ľudí vlastnia kópiu tejto účtovnej knihy po celom svete
- Na rodziel od účtov, ktoré máme v banke, v blockchaine máme adresy
- Ľudia si môžu posielať kryptomeny na základe adries
- Overenie transakcie, vytvorenie transakcie a všetko ostatné je založené na kryptografii, ktorá hrá v tomto celom najdôležitejšiu úlohu
Vlastnosti blockchainu riešia problém dôvery.
Poďme teda programovať
Vytvorme si klasický Javovský projekt vo svojom obľúbenom IDE. Ja používam IntelliJ IDEA.
Program bude na konci spočívať v nasledujúcich krokoch:
- Vytvorenie transakcie
- Miner (Ten čo taží kryptomeny) vytvorí z tejto transakcie nový blok
- Blok pridáme do blockchainu
Štruktúra bloku
Blockchain je rozdelený do blokov a každý blok obsahuje nejakú transakciu/transakcie. Blok okrem transakcie obsahuje aj svoj digitálny podpis, a taktiež aj digitálny podpis predchádzajúceho bloku.
Takže ak by sme si chceli vytvoriť triedu Block, tak by to vyzeralo takto:
class Block { private String hash; private String previousHash; private String transaction; Block(String transaction, String previousHash, String hash) { this.transaction = transaction; this.previousHash = previousHash; this.hash = hash; } String getHash() { return hash; } String getPreviousHash() { return previousHash; } String getTransaction() { return transaction; } }
V našej triede Block máme 3 premenné:
- hash – digitálny podpis bloku
- previousHash – digitálny podpis predošlého bloku
- transaction – dáta, v našom prípade transakcia
Prečo sme digitálny podpis nazvali hash? Čo je to hash?
SHA256 je príkladom kryptografickej hash funkcie, ktorá pre ľubovoľný input vráti 256 bitový výstup. Vstupom môže byť hocičo, či už celá encyklopédia alebo len čislo 1. Hash funkcia vráti vždy ten istý výstup pre ten istý vstup. A čo i len jeden znak vo vstupe sa zmení, výstup je totálne odlišný. Funkcia má tiež takú vlastnosť, že na základe výstupu sa nedá zistiť vstup.
SHA256 sa používa vo viacerých kryptomenách, ako aj napríklad pri Bitcoine.
Pomocou tejto funkcie budeme aj my vyrátavať hashe blokov.
Vytvorenie/ťažba nového bloku
Asi ste už počuli niečo také, že v blockchainoch sa nové bloky vytvárajú tak, že sa ťažia. Tí, ktorí ťažia nové bloky sa nazývajú Mineri (ťažiči).
Keď vznikne v blockchaine nová transakcia, tak mineri sa snažia vytvoriť nový blok s touto transakciou. Mineri pomocou svojej výpočtovej sily hľadajú špeciálne číslo, pomocou ktorého vedia vytvoriť podpis/hash nového bloku. Ukážeme si to na obrázku.
Miner chce vypočítať taký hash, že keď dá do SHA256 vstupu hash predošlého bloku, novú transakciu a nounce, tak výsledný hash má počiatočný počet núl rovný zložitosti blockchainu. Stále to skúša dokola tak, že vždy zvýši číslo nounce o 1, až kým nájde také číslo, vďaka ktorému výstup z hash funkcie spĺňa danú zložitosť. Tento mechanizmus sa nazýva aj proof of work. Pre zaujímavosť, prvý blok v Bitcoine mal zložitosť 10.
Na obrázku vidíme výstup v bitoch, ale v našom programe a aj keď si pozrieme hashe bitcoinov, tak tie sú v hexa notácii.
Počítanie hashu v kóde
Pre vypočítanie hashu budeme používať funkciu SHA256. Pomôžeme si knižnicou od Google zvanú guava, ktorá poskytuje funkciu Hashing.sha256(), (funkcia vracia 64 znakov dlhý string v hexa notácii). Túto knižnicu si pridajme do nášho Java projektu.
Ďalej si vytvoríme triedu Hasher s funkciou calculateHash.
import java.nio.charset.StandardCharsets; import com.google.common.hash.Hashing; class Hasher { static String calculateHash(String previousHash, String transaction, long nonce) { final String toHash = previousHash + transaction + nonce; return Hashing.sha256() .hashString(toHash, StandardCharsets.UTF_8) .toString(); } }
Na základe predchádzajúceho hashu, transakcie a čísla nounce počítame hash. Túto funkciu bude používať Miner.
Vytvorenie Minera
class Miner { private int difficulty; private String target; Miner(int difficulty) { this.difficulty = difficulty; // Create a string of '0' of difficulty size this.target = new String(new char[difficulty]).replace('\0', '0'); } Block mineBlock(String transaction, String previousHash) { System.out.println("Mining block... "); long nonce = 0; String hash = Hasher.calculateHash(previousHash, transaction, nonce); while (!hash.substring(0, difficulty).equals(target)) { nonce++; hash = Hasher.calculateHash(previousHash, transaction, nonce); } System.out.println("Yuhuu. I mined a block at " + nonce + " attempt! Hash: " + hash); System.out.println(); return new Block(transaction, previousHash, hash, nonce); } }
Miner má 2 premenné:
- difficulty – náročnosť vytvorenia nového bloku (koľko ‘0’ má byť na začiatku)
- target – String, ktorý má dĺžky rovnú náročnosti a každý znak je ‘0’. Táto premenná sa používa pri porovnávaní vypočítaného hashu s cieľovým hashom (hashom, ktorý chceme vypočítať)
Máme jednu metódu – mineBlock, ktorá má na vstupe transakciu a predošlý hash.
Na riadku 16 si inicializujeme nounce číslom 0 a na riadku 17 si vypočítame prvý hash, kde už dosádzame nounce. Na tomto riadku používame statickú metódu calculateHash z našej triedy Hasher.
Ďalej to skúšame v cykle naďalej vždy s o jedno väčším nounce, až kým vrátený hash spĺňa danú náročnosť. Ak sme našli také číslo, tak vypíšeme radostnú hlášku a na riadku 25 vrátime vyťažený nový blok.
Triedu Block potrebujeme rozšíriť o nounce, čiže finálne bude vyzerať takto:
class Block { private String hash; private String previousHash; private String transaction; // Nonce is the number that blockchain miners are solving for private long nonce; Block(String transaction, String previousHash, String hash, long nonce) { this.transaction = transaction; this.previousHash = previousHash; this.hash = hash; this.nonce = nonce; } String getHash() { return hash; } String getPreviousHash() { return previousHash; } String getTransaction() { return transaction; } long getNonce() { return nonce; } }
Poďme to zlepiť dokopy
Ako bude vyzerať naša Main trieda?
import java.util.ArrayList; import com.google.gson.GsonBuilder; public class Main { private static ArrayList<Block> blockchain = new ArrayList<>(); private final static int difficulty = 5; public static void main(String[] args) { Miner miner = new Miner(difficulty); // 1. Create transaction String firstTransaction = "Thomas pays Lucy 5 CC"; // 2. Miner listens to this transaction and mines block Block firstBlock = miner.mineBlock(firstTransaction, "0"); // 3. Block is added to the blockchain blockchain.add(firstBlock); // 1. Create transaction String secondTransaction = "John pays Paul 2 CC"; // 2. Miner listens to this transaction and mines block Block secondBlock = miner.mineBlock(secondTransaction, firstBlock.getHash()); // 3. Block is added to the blockchain blockchain.add(secondBlock); // 1. Create transaction String thirdTransaction = "Paul pays Thomas 4 CC"; // 2. Miner listens to this transaction and mines block Block thirdBlock = miner.mineBlock(thirdTransaction, secondBlock.getHash()); // 3. Block is added to the blockchain blockchain.add(thirdBlock); String blockchainJson = new GsonBuilder().setPrettyPrinting().create().toJson(blockchain); System.out.println("Blockchain:"); System.out.println(blockchainJson); } }
Na 6. riadku si vytvoríme zoznam Blokov, ktorý nazveme blockchain. Na ďalšom riadku zvolíme náročnosť 5. Prečo práve 5? Lebo už pri tomto čísle ťažba bloku trvá niekoľko sekúnd, a keby bola náročnosť čo i len o jedno väčšia – 6 – tak by to trvalo už cca 30sekúnd na mojom počítači.
V main metóde si vytvoríme minera, ktorému dodáme našu náročnosť.
Potom už len vytvárame transakcie, ktoré dáme následne minerovi, aby nám z nich vytvoril nový blok, ktorý napokon pridáme do blockchainu.
Na 33. riadku použijeme knižnicu Gson, aby nám pekne vypísal blockchain do konzoly.
Výstup programu:
Prvý blok bol vyriešený na 5 911 684. pokus. Skoro 6 miliónov pokusov. Ak dosadíme toto číslo spolu s predošlým hashom a transakciou, tak String ktorý dostaneme, bude mať prvých 5 znakov ‘0’.
Tretí blok bol ale vyriešený na 288 309. pokus, čiže je to v podstate o náhode, ale každopádne čím väčšia zložitosť, tým ťažšie to bude nájsť (dlhšie to bude trvať).
Integrita blockchainu
Treba ešte skontrolovať, či náš blockchain je naozaj validný.
Vytvorme si funkciu v Main triede s nazvon isBlockChainValid, ktorá nám skontroluje, či sú hashe valídne, a teda nikto sa nezahrával s našim blockchainom.
private static Boolean isBlockchainValid() { if (blockchain.size() > 0) { for (int i = 0; i < blockchain.size(); i++) { Block block = blockchain.get(i); // has valid hash String expectedHash = Hasher.calculateHash(block.getPreviousHash(), block.getTransaction(), block.getNonce()); if (!expectedHash.equals(block.getHash())) { System.out.println("Block has invalid hash"); return false; } // block was mined/solved String hashTarget = new String(new char[difficulty]).replace('\0', '0'); if (!block.getHash().substring(0, difficulty).equals(hashTarget)) { System.out.println("Block wasn't mined"); return false; } // For every block except the first compare previousHash if (i > 0) { Block previousBlock = blockchain.get(i - 1); // Previous hash is equal to actual previous hash if (!block.getPreviousHash().equals(previousBlock.getHash())) { System.out.println("Block has invalid previous hash"); return false; } } } } else { System.out.println("Empty blockchain"); return true; } return true; }
Iterujeme všetky bloky. Pre každý blok vypočítame hash z predošlého hashu, transakcie a nounce, či sa tento hash rovná hashu tohto bloku. Ďalej kontrolujeme, či hash bloku má splnenú náročnosť, a teda či má prvých 5 znakov 0.
Na záver pre všetky bloky okrem úplne prvého kontrolujeme, či predošlý hash tohto bloku je skutočne rovný predošlému hashu.
Na konci programu môžeme pridať ešte jeden riadok:
System.out.println("Is our blockchain valid?: " + isBlockchainValid());
a keď si spustíme program, tak by nám malo vypísať, že náš blockchain je valídny.
Ak by sme pred skontrolovaním validity blockchainu napr. zmenili transakciu prvého bloku, tak by nám táto funkcia vrátila false. Môžete si to vyskúšať. (Pridajte si setter na napr. transaction v triede block, a potom pred zavolaním isBlockchainValid zmeňte pomocou tohto settera transaction niektorého bloku)
Záver
Vytvorili sme blockchain, ktorý má nasledovné vlastnosti:
- blockchain je tvorený blokmi, z ktorých každý blok obsahuje jednu transakciu
- každý blok ma digitálny podpis/hash, vďaka ktorým vieme bloky “chainovať”
- vytvorenie nového boku vyžaduje “proof of work”, čo zabezpečuje Miner
- blockchain vieme otestovať, či jeho dáta neboli zmenené a je teda valídny
Zdrojový kód nájdete na githube
Poznámky
- Video, ktoré mi najviac pomohlo pochopiť blockchain
- Inšpirácia na tento blog vznikla po prečítaní iného blogu na Mediume