Implementacja proof of work – blockchain poradnik

11/21/20174 min czytania — w Programowanie, Blockchain

Wstęp

No i jest kolejny tekst o blockchain. Tym razem implementacja proof of work.

Jak to działa? Jak stworzyć samemu taki system? Jak to pomaga? Dlaczego warto? Czy bezpieczne? Trzeba to udowodnić, lecz najpierw proponuję zapoznać się z poprzednimi tekstami.

Jak działa proof of work?

Po Polsku – dowód pracy. Jest to prosty mechanizm do udowodnienia, że dane to nie jakiś spam albo wydłużenia procesu dodawania danych. Wymaga pracy komputera do rozwiązania pewnych wyrażeń np. mnożenia, dzielenia albo odnalezienia jakiś wartości.

Co robi w blockchain? Każdy blok musi zostać wykopany, czyli inaczej muszą zostać wykonane obliczenia, które zabiorą trochę czasu. Potwierdzi to prawdziwość bloku.

Ale jak potwierdzi? Wiemy, że blockchain to ciąg połączony hashami. Każdy jest przeliczany i łączony z poprzednim blokiem. Jeśli przeliczenie hashu będzie trwało bardzo krótko to bez problemu możemy podmienić dane i zmienić blockchain.

Jak działa hash?

Zacznijmy sobie od krótkiego wstępu do hashy. Hash to matematyczna funkcja, która dowolnej liczbie przyporządkowuje zawsze taki sam i o takiej samej długości zestaw znaków. W dużym uproszczeniu oczywiście.

Jak taki hash wygląda? – 034f3892jd3982ed

Hashowanie posiada wspaniałą właściwość. Nawet malutka zmiana prowadzi do ogromnej zmiany w hashu. Pozwala to zastosować ciekawe rozwiązanie w kontekście proof of work.

Teoria kopania

Tworząc pierwszą wersję naszego blockchain, zakładamy, że musi zostać wykonana pewna praca, która zabierze jakiś czas. Będzie ona zwiększana, przez co coraz ciężej będzie wpłynąć na nasz blockchain. Każdy blok będzie miał hash, który był dosyć długo liczony. Jeśli ktoś zmieni coś to będzie musiał przeliczyć od nowa coś co trwa długo i wszystkie wcześniej i później.

Przy trzech blokach mamy: długo + długo + długo = bardzo długo

Przy wielu, wielu więcej mamy jeszcze większy problem. Do tego weźmy pod uwagę, że będzie coraz ciężej kopać (czyli coraz dłużej będzie trwało obliczanie), więc: 1długo + 2długo + 3*długo = ∞ długo

Dlaczego trzeba zwiększać trudność kopania?

Czasami można zastanowić się, po co zwiększać trudność kopania skoro i tak dużo czasu zajmuje. Chodzi o to, że technologia postępuje do przodu. Nasze komputery mają coraz większą moc obliczeniową. Do tego coraz więcej osób inwestuje i zaczyna kopać. Przez co coraz szybciej rozwiązujemy hashe. Jeżeli chcemy, aby nasze bloki pojawiały się w równych odstępach czasu, musimy zwiększać trudność, aby utrzymać zabezpieczony blockchain.

Tworzymy koncepcję

Hash to jakiś ciąg znaków stałej długości. Bardzo się zmienia, przy najmniejszej zmianie w danych. Możemy zrobić tak, że komputer będzie musiał znaleźć odpowiedni hash z np: 2 zerami na początku. Czyli będziemy chcieli hash wyglądający w ten sposób: 00fiju98r34u2rh, potem jak zwiększymy sobie trudność kopania to będziemy chcieli mieć 3 zera na początku, czyli 000jdf928fh892 itd.

Znalezienie jednego zera na początku trudne raczej nie będzie, dwa już trochę ciężej, ale na przykład znaleźć hash, gdzie jest 7 zer na początku sprawi problem.

Jak wiemy, żeby hash się zmieniał potrzebujemy zmieniać dane. Najczęściej tworzy się specjalne pole o nazwie nonce (https://en.wikipedia.org/wiki/Cryptographic_nonce) Będzie to losowa liczba, która pomoże nam zmieniać hash, dopóki nie odnajdziemy odpowiedniej ilości zer na początku. Za każdym razem jak dostaniemy według nas zły hash, to zmienimy tą liczbę. I tak w kółko. W końcu odnajdzie się odpowiedni hash.

Implementacja

Na wstępie do implementacji proponuję zapoznać się z poprzednim artykułem. Implementacja własnego blockchain Cały jego kod jest wykorzystywany i jest to kontynuacja. Dzięki niemu łatwiej będzie Wam zrozumieć co się tutaj dzieje.

constructor(index, previousHash, timestamp, data) {
this.index = index
this.previousHash = previousHash
this.timestamp = timestamp
this.data = data
this.hash = this.calculateHash()
}

Tak wygląda nasz blok. Zawiera index, dzięki któremu wiemy który to blok. Mamy previousHash, który linkuje do poprzedniego tworząc łańcuch. Mamy timestamp, czyli czas powstania. Data, gdzie są przechowywane wszystkie informacje i na końcu mamy liczenie aktualnego hashu.

Dodajmy tutaj jedną funkcję o nazwie mineBlock. Będzie ona przeliczać hash dopóki nie odnajdzie takiego, który nas interesuje.

mineBlock(difficulty) {
while(this.hash.substring(0, difficulty) !== Array(difficulty+1).join('0')) {
this.nonce += 1
this.hash = this.calculateHash()
}
console.log('Blocked ', this.index, ' mined: ', this.hash)
}

Jak widzimy przyjmuje ona jeden argument. Difficulty – trudność. Będzie określać jak wiele zer ma się znaleźć w naszym hashu. Tworzymy pętlę while, która ma się wykonywać dopóki na początku naszego hashu nie będzie odpowiedniej ilości zer. Dalej pojawia nam się magiczna liczba nonce. Musimy ją dodać do naszego bloku. Posłuży ona nam do zmieniania hashu. W kolejnym kroku przeliczamy hash i tak w kółko, aż do znalezienia pasującego.

Czyli nasz blok wygląda tak:

constructor(index, previousHash, timestamp, data) {
this.index = index
this.previousHash = previousHash
this.timestamp = timestamp
this.data = data
this.hash = this.calculateHash()
this.nonce = 0
}

Obliczanie hashu przedstawia się następująco:

calculateHash() {
return CryptoJS.SHA256(this.index + this.previousHash + this.timestamp + JSON.stringify(this.data) + this.nonce).toString()
}

Jak widać dzięki nonce uzyskaliśmy możliwość zmiany hashu. Możemy ten hash przeliczać, aż otrzymamy pożądaną formę. W naszym wypadku jest to hash z odpowiednią ilością zer na początku.

Blockchain z kopaniem

Do naszego blockchaina należy dodać kilka zmian skoro mamy już pierwszy sposób jego zabezpieczenia. Zaczniemy od ustalenia jego trudności. Ja domyślnie dałem na 4. Wymaga to na moim komputerze około 5-10 sekund na odnalezienie odpowiedniego hashu.

constructor() {
this.blockchain = []
this.blockchain.push(this.generateGenesisBlock())
this.difficulty = 4
}

Skoro mamy już ustaloną trudność, zajmijmy się dodawaniem bloku do naszego blockchaina.

addBlock(data) {
const previousHash = this.getLatestBlock().hash
const index = this.blockchain.length
const timestamp = new Date().toISOString()
const newBlock = new Block(index, previousHash, timestamp, data)
newBlock.mineBlock(this.difficulty)
if (this.isValidBlock(newBlock, this.getLatestBlock())) {
this.blockchain.push(newBlock)
} else console.error('invalid block!')
}

Dodaliśmy nową linię. Przed dodaniem do blockchain wywołujemy funkcję mineBlock, która kopie nasz blok.

Droga do poznania blockchain

Sporo już osiągnęliśmy, więc nadszedł mały czas podsumowania. Wszystko zaczęło się od bitcoinów i tego, że pojawiły się w moim życiu. Moja historia z bitcoin Następnie zacząłem tworzyć implementację najprostszej struktury Implementacja własnego blockchain Gdy udało się to stworzyć, przyszedł czas na zrozumienie dlaczego to działa: Matematyka tworząca blockchain Potem przydała się wiedza o zabezpieczeniach: Kryptografia i zabezpieczenia Skoro już wiedzieliśmy wszystko, nadszedł czas zobaczyć prawdziwe produkty w akcji: Experty.io jako przykład aplikacji opartej na blockchain

Ta długa droga doprowadziła nas do tego momentu. Dodaliśmy zabezpieczenie – kopanie. Kolejny etap to dodanie możliwości przesyłania naszych danych, nawet szyfrowanych, przez sieć. Dlatego następny post to wstęp do implementacji portfela.

Jak na razie nasz blockchain zawiera 80 linii kodu a jest już w stanie bezpiecznie przechowywać informacje. Na prywatne miejsce do przechowywania historii naszych domowych rachunków nadaje się idealnie 🙂 Nasza dziewczyna już nie zmieni co takiego i za ile kupiła 🙂 Ani my ile kosztował nasz wieczór z kolegami 🙂

Zapraszam Was do śledzenia facebook’a: https://www.facebook.com/patysblog Wrzucam tam wszystkie informacje o tym co się dzieje.

Patryk Szczygło
Programista w Netguru. Bloger od 2017 roku. Miłośnik podróży, książek i elektroniki. Stworzył własny blockchain w JavaScript. Marzy o automatyzacji i robotyce w życiu.