Dodajemy p2p i kończymy 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ę!