Własny blockchain – implementacja – poradnik

09/29/20173 min czytania — w Programowanie, Blockchain

Każdy chce własny blockchain

Tak jak obiecałem zaczynamy implementację blockchain. Temat prosty nie jest. Jednak zauważyłem, że wystarczy podzielić temat na mniejsze części. Teraz przedstawię same podstawy. Co to blok, transakcja i jak jedno z drugim się łączy.

Transakcja

Chociaż według mnie nie jest to dobra nazwa, to skoro została ogólnie przyjęta to możemy jej tutaj też używać, aby nie robić zamieszania.

Transakcja jest to podstawowa część bloku. To tutaj pojawią się informacje o przeprowadzonych działaniach. Tutaj zostanie wszystko zapisane. Posługując się przykładem kryptowalut, to właśnie w transakcji znajdują się adresy portfeli czy ilość przesłanych środków.

Jednak w transakcji możemy zdefiniować dowolne dane, które chcemy dołączyć do blockchain. Mogą to być informacje o zawartej umowie, crowdfundingu albo o Twoim głosie w ankiecie/wyborach. Możliwości są nieograniczone.

Blok

W skrócie jest to zbiór transakcji.

Pewnie słyszeliście o kopaniu bloków. Ogólnie kopanie składa się z tworzenia bloku z nieprzetworzonych transakcji (czyli takich które nie są jeszcze w jakimś innym bloku), dodaniu transakcji z na przykład nagrodą dla kopiącego i sprawdzeniu poprawności wszystkich transakcji (żeby nikt nie przesłał sobie za dużo pieniędzy) oraz przeliczeniu hashu bloku.

Hash

Bardzo ważny element bloku i transakcji. Jest to losowy ciąg znaków. Jednak jest to zrobione w sposób kryptograficzny. Wszelkie dane z transakcji, czas i hash poprzedniego bloku są brane pod uwagę i z nich jest tworzony nowy, unikalny hash.

Gwarancja niezmienności

Tą gwarancję zapewniają hashe. Każdy blok zawiera dwa hashe: jeden z poprzedniego bloku i jeden wygenerowany na podstawie swoich danych. Czemu to działa? Każdy blok łączy się z poprzednim i zawiera jego hash. Jakakolwiek zmiana w bloku sprawi, że w nowo wygenerowanym bloku hashe nie będą się zgadzać i blok nie zostanie zaakceptowany.

Zaczynamy programować

No to wyjaśniliśmy sobie wszystko, więc przejdźmy do kodu.

Implementacja będzie zawierać wyłącznie najprostszą wersję. Bez kopania, bez transakcji. Będziemy to dodawać później. W kolejnych poradnikach, krok po kroku. Jak to się mówi, nie od razu Rzym zbudowali.

Implementacja bloku

Na początek określmy sobie czym jest blok. W naszym wypadku będzie to miejsce przechowywania dowolnych danych. Będzie posiadało index, hash poprzedniego bloku, aktualny czas oraz własny hash obliczony na podstawie tych wszystkich pól.

class Block {
constructor(index, previousHash, timestamp, data) {
this.index = index
this.previousHash = previousHash
this.timestamp = timestamp
this.data = data
this.hash = this.calculateHash()
}
calculateHash() {
return CryptoJS.SHA256(this.index + this.previousHash + this.timestamp + JSON.stringify(this.data)).toString()
}
}

W konstruktorze podajemy index, czyli numer naszego bloku, hash poprzedniego aby zapewnić spójność i gwarancję poprawności danych, obecny czas oraz dane, które chcemy zapisać do bloku. Dodatkowo określiłem funkcję calculateHash, która ma za zadanie przy użyciu biblioteki CryptoJS obliczyć hash tego wszystkiego.

Implementacja blockchain

Skoro mamy już nasz blok, możemy zacząć tworzyć z tego łańcuch z naszymi danymi.

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

Blockchain to tablica zawierająca wszystkie wytworzone bloki. Na samym początku musi posiadać tak zwany genesis block. Jest to pierwszy blok, który nie zawiera żadnych istotnych informacji, poza hashem, dzięki któremu kolejny blok może się podłączyć.

generateGenesisBlock() {
return new Block(0, '0', new Date().toISOString(), { info: 'Genesis Block' })
}

Funkcja generateGenesisBlock zwraca po prostu nowy blok z podstawowymi informacjami. Skoro mamy blok, z którego otrzymamy hash, to nic nie stoi już na przeszkodzie dodawać nowe.

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)
if (this.isValidBlock(newBlock, this.getLatestBlock())) {
this.blockchain.push(newBlock)
} else console.error('invalid block!')
}

No to zaczynamy dodawać nowy blok. Normalnie w tym momencie następuje kopanie, jednak dla uproszczenia zrobimy to w taki sposób.

Na samym początku potrzebujemy poprzedni hash. Więc wybieramy go z pomocą funkcji getLatestBlok. Zwraca ona ostatni blok.

getLatestBlock() {
return this.blockchain[this.blockchain.length - 1]
}

Następnie bierzemy index. Do tego posłużymy się po prostu długością naszego blockchainu. Kolejny jest timestamp. Jest to aktualna data. Dla ułatwienia i spójności zapisujemy ją zgodnie ze standardem ISO. Mając timestamp możemy przejść do stworzenia nowego bloku.

Po stworzeniu bloku, warto sprawdzić czy wszystko się zgadza.

isValidBlock(newBlock, previousBlock) {
if (newBlock.index !== previousBlock.index + 1) {
console.error('invalid index')
return false
}
if (newBlock.previousHash !== previousBlock.hash) {
console.error('current and previous hash dont match')
return false
}
if (newBlock.hash !== newBlock.calculateHash()) {
console.error('recalculated hash is wrong')
return false
}
return true
}

Sprawdzanie zaczynamy od poprawności indeksu. Bierzemy poprzedni blok i sprawdzamy czy powiększony o 1 indeks jest taki sam. Dalej sprawdzamy czy previousHash z nowego bloku zgadza się z hasem poprzedniego. Na koniec przeliczamy ponownie hash, żeby się upewnić czy po drodze nie nastąpiły jakieś zmiany.

Kiedy wszystko jest ok, dodajemy blok do blockchain.

To prawie cała implementacja. Została ostatnia funkcja.

isChainValid() {
for (var i = 1; i < this.blockchain.length; i++) {
if (!this.isValidBlock(this.blockchain[i], this.blockchain[i - 1])) return false
}
return true
}

Odpowiada ona za sprawdzenie wszystkich bloków po kolei i ich zgodności. Przeprowadźmy małe testy, czy blockchain jest poprawnie zrobiony.

Test

Zrobimy sobie prostą symulację. Mamy zbiór w blockchain i powiedzmy, że chcemy oszukać 🙂 Chcemy zmienić jeden z bloków, tak aby przesyłał inną ilość patyscoin do innego użytkownika.

const patyscoin = new Blockchain
patyscoin.addBlock({ ownerID: 1, amount: 100 })
patyscoin.addBlock({ ownerID: 2, amount: 132 })
patyscoin.addBlock({ ownerID: 1, amount: -5 })
console.log(patyscoin.isChainValid())
patyscoin.blockchain[1].data = { ownerID: 666, amount: 999999 }
console.log(patyscoin.isChainValid())

Po uruchomieniu otrzymamy następujące wartości w konsoli:

true
false

Analizując po kolei. Na początku tworzymy blockchain i dodajemy do niego trzy bloki. Sprawdzamy czy nasz blockchain jest poprawny i otrzymujemy ‚true’, czyli wszystko jest ok. Jednak decydujemy, że zmienimy dane jednego z bloków. Po zmianie sprawdzamy, czy blockchain jest poprawny i wychodzi ‚false’. Nasz blockchain jest uszkodzony.

Na tym polega idea blockchain. Raz wrzuconych danych nie da się zmienić. Kiedy blockchain zostaje naruszony, zostaje przywrócona lub pobrana ostatnia poprawna wersja.

Podsumowanie

Tak wygląda implementacja blockchain. Jest najprościej napisana jak tylko się dało. Dalej postaramy się dodać obsługę sieci, żeby można było korzystać z serwerów. Nie nada się do publicznej chmury ze względu na brak różnych zabezpieczeń, ale na prywatny blockchain jest wystarczająca.

Tutaj macie cały kod: https://github.com/Patys/blockchain-basic

Mam nadzieję, że podobało Wam się. Zapraszam do śledzenia kolejnych postów.

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.