Dodajemy p2p i kończymy blockchain!

01/21/20192 min czytania — w Programowanie, Blockchain

Stało się! Ostatnia część najlepszej serii. Zostało dodać p2p i synchronizację. Warto się zapoznać z https://blog.patys.pl/2018/09/10/p2p-komunikacja-serwerow-nodejs-z-wykorzystaniem-websockets/ Będziemy bazować na tym artykule.

Serwer http

Od tego będzie najprościej zacząć: https://expressjs.com/en/starter/hello-world.html. Na podstawie przykładu dodamy tylko swoje końcówki, czyli:

Root/Home (zależy kto jak nazywa):

app.get('/', (req, res) => {
res.send(`numberOfBlocks: ${patyscoin.blockchain.length}
difficulty: ${patyscoin.difficulty},`);
});

Wyświetli nam liczbę bloków i trudność.

Connect:

app.get('/connect/:address', (req, res) => {
p2p.addNewConnection(req.params.address);
res.redirect('/');
});

Posłuży do stworzenia nowego połączenia z innym serwerem.

Block:

app.get('/block/:blockNr', (req, res) =>
res.json(patyscoin.blockchain[req.params.blockNr]));

Wyświetli nam blok o wybranym numerze i zwróci jego json.

Transactions:

app.get('/transactions', (req, res) =>
res.json(patyscoin.transactions));

Pokaże listę oczekujących transakcji w json.

Test:

app.get('/test', (req, res) => {
p2p.broadcast({ type: 'TRANSACTION', data: transaction });
patyscoin.addTransaction(fromJSON(JSON.parse(transaction)));
res.redirect('/');
});

Doda nam testową transakcję do blockchain.

Mine:

app.get('/mine', (req, res) => {
patyscoin.addBlock();
res.redirect('/');
});

Wykopie nowy blok. Robimy to poprzez ręczne uruchomienie kopania w ramach testów. Daje większą kontrolę żeby zobaczyć jak działa nasz blockchain.

Bloki i transakcje

Jak przyjrzymy się powyższym końcówkom potrzebujemy dwie rzeczy. “p2p” oraz “patyscoin”. Drugi z tych elementów już mamy, czyli nasz blockchain stworzony w poprzednich artykułach. Zostaje nam połączenie między serwerami.

Tworząc to napotkałem spory problem. Pewnie źle do tego podszedłem, ale w sumie potrzebowałem p2p i blockchain w każdym pliku odpowiadającym za połączenie i wpadłem w nazwijmy to “require hell” – https://stackoverflow.com/questions/23341883/can-node-modules-require-each-other. p2p i blockchain potrzebowały dołączać się razem i byłem zmuszony zrobić p2pBridge, który połączy nam blockchain oraz p2p.

Tutaj pojawił się problem. Jak zrobić to najprościej. Zdecydowałem się dodać coś w stylu “subscribe pattern”. Jednak poszło mi to dosyć nieudolnie. Próbowałem zrobić to samemu nie sprawdzając dokładnie istniejących rozwiązań i popełniłem spory błąd. Nauczka na przyszłość: zawsze robić research jeśli tworzysz coś co ma zapewne wzorzec. (Tutaj jest jak powinno to mniej więcej wyglądać: https://gist.github.com/learncodeacademy/777349747d8382bfb722). A oto moja implementacja:

const patyscoin = require('./index');
const { fromJSON } = require('./src/transactionBuilder');
const p2p = require('./p2p');
const onNewTransaction = (data) => {
let transaction;
try {
transaction = fromJSON(JSON.parse(data));
} catch(e) {
transaction = fromJSON(data);
}
patyscoin.addTransaction(transaction);
}
const onNewBlock = (data) => {
let block;
try {
block = patyscoin.blockFromJSON(JSON.parse(data));
} catch(e) {
block = patyscoin.blockFromJSON(data);
}
patyscoin.addBlock(block);
}
const broadcastNewTransaction = (transaction) => {
p2p.broadcast({ type: 'TRANSACTION', data: transaction });
}
const broadcastNewBlock = (block) => {
p2p.broadcast({ type: 'BLOCK', data: block });
}
const init = () => {
patyscoin.subscribe(broadcastNewTransaction, broadcastNewBlock);
p2p.subscribe(onNewTransaction, onNewBlock);
}
module.exports = { init };

Jak widać najważniejsza jest funkcja „init”. Dodaje do patyscoin oraz p2p wszystkie wymienione wyżej funkcje. Czyli blockchain (patyscoin) może reagować na transakcje i je rozsyłać oraz p2p może dodawać bloki i transakcje jeśli takie przyjdą.

Blockchain

Przejdźmy na początek do blockchain i przyjrzyjmy się funkcji “subscribe”.

subscribe(newTransaction, newBlock) {
this.newTransactionCreated = newTransaction;
this.newBlockCreated = newBlock;
}

Jak widzimy przypisuje funkcje do blockchain. W ten sposób blockchain może reagować, kiedy jest dodana transakcja lub blok i pokazać to światu. Na przykład transakcja:

addTransaction(newTransaction) {
if (this.checkTransaction(newTransaction)) {
this.transactions.push(newTransaction);
if(this.newTransactionCreated) {
this.newTransactionCreated(newTransaction);
}
console.info(`Transaction added: ${newTransaction.id}`)
} else {
console.error(`Cannot add transaction`);
}
}

Lub blok:

addBlock(newBlock = null) {
let block;
if (!newBlock) {
block = this.mineNewBlock();
} else {
block = newBlock;
}
if (this.validateBlock(block)) {
this.blockchain.push(block);
if (this.newBlockCreated) {
this.newBlockCreated(block);
}
} else console.error('invalid block!')
}

Dzięki temu odpalą się odpowiednie funkcje, czyli broadcastNewTransaction oraz broadcastNewBlock

P2P

Teraz czas zobaczyć jak p2p przekazuje informacje. Zasada działania jest taka sama.

/*
{ type: 'TRANSACTION', data: string }
{ type: 'SOCKETS', addresses: array }
*/
ws.on('message', (data) => {
const message = JSON.parse(data);
switch(message.type) {
case 'TRANSACTION':
onNewTransaction && onNewTransaction(message.data);
console.log('New transaction');
break;
case 'BLOCK':
onNewBlock && onNewBlock(message.data);
console.log('New block');
break;
case 'SOCKETS':
const newConnections = message.addresses.filter(add => !addresses.includes(add));
newConnections.forEach((con) => {
addNewConnection(con);
});
break;
default:
break;
}
});
};

Jak widzimy odpowiednia funkcja jest uruchamiana w zależności co zostanie przesłane. Gdy otrzymamy message z typem “TRANSACTION” to uruchomimy funkcję “onNewTransaction” jeśli istnieje.

Spójrzmy wyżej w implementację subscribe. Widzimy, że dane są parsowane i dodawane do blockchain.

Podsumowanie

To jest kawał kodu, z którego nie jestem dumny. Jest brzydki, ma dużo zależności i nie tak się powinno programować. To kod, który działa. Zdecydowałem się go opublikować, bo to jest pierwsza rzecz napisana jaka przyszła mi do głowy. Zasada była robić jak najszybciej i najprościej.

Powoli kończymy serię. Pojawią się tutaj jeszcze dodatki, jako kolejne posty mające na celu usprawnić i uzupełnić całą wiedzę potrzebną do stworzenia najprostszego blockchain. Podsumujmy co zrobiliśmy przez te wszystkie posty:

Opowiedziałem o mojej fascynacji blockchain. Jak zaczęła się przygoda, która doprowadziła nas tutaj po roku tworzenia od zera. Rozpoczęliśmy implementację. Stworzyliśmy najprostsze bloki i strukturę. Był to pierwszy kod. Dalej trzeba było poznać matematykę – podstawa na jakiej opiera się ta technologia. Gdy tylko skończyliśmy ruszyliśmy zobaczyć zabezpieczenia. Dlaczego możemy zaufać blockchain, pamiętasz? Warto się było przyjrzeć istniejącemu produktowi. Jak działa i co go napędza. Dalej dodaliśmy kopanie. Proof of work, który zabezpieczył wszystko. Dodaliśmy portfel i transakcje. Możemy przesyłać na swoje konta dane. Znaleźliśmy rozwiązanie na połączenia pomiędzy serwerami i zrobiliśmy prosty czat. Teraz połączyliśmy wszystko w całość i mamy działający blockchain.

Zajęło mi to rok czasu. Nie robiłem tego fulltime ani za pieniądze. Po prostu w wolnych chwilach jako pasja. Żeby nie zatrzymywać się poszerzałem swoją wiedzę o inne przykłady i formy blockchain: https://blog.patys.pl/category/kryptowaluty/

Ruszyłem z informacjami dla początkujących, żeby unormować wiedzę: https://blog.patys.pl/category/krypto/

Wymagało to dużo samozaparcia i motywacji. Zajęło dużo czasu, ale się udało. Skończyłem to i ruszam dalej. Z nowym projektem i pomysłem.

Dam radę!

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.