Dodajemy p2p i kończymy blockchain!

the-end-p2p

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ę!

Dodaj komentarz

avatar
  Subscribe  
Powiadom o