Registro distribuito per sale montate: un'esperienza con Hyperledger Fabric

Buongiorno, lavoro nel team del progetto DRD KP (registro dati distribuito per il monitoraggio del ciclo di vita delle sale montate). Qui voglio condividere l'esperienza del nostro team nello sviluppo di una blockchain aziendale per questo progetto sotto i vincoli della tecnologia. Parlerò principalmente dell'Hyperledger Fabric, ma l'approccio qui descritto può essere estrapolato a qualsiasi blockchain autorizzata. L'obiettivo finale della nostra ricerca è preparare soluzioni blockchain aziendali in modo che il prodotto finale sia piacevole da usare e non troppo difficile da mantenere.

Non ci saranno scoperte, soluzioni inaspettate e nessuno sviluppo unico verrà evidenziato qui (perché non ne ho). Voglio solo condividere la mia modesta esperienza, dimostrare che "era possibile" e, forse, leggere nei commenti le esperienze di altre persone nel prendere decisioni buone e non così buone.

Problema: le blockchain non sono ancora scalabili

Oggi, gli sforzi di molti sviluppatori mirano a rendere la blockchain una tecnologia davvero conveniente e non una bomba a orologeria in un bellissimo involucro. I canali statali, il rollup ottimistico, il plasma e lo sharding diventeranno probabilmente un luogo comune. Un giorno. O forse TON rimanderà nuovamente il lancio di sei mesi e il prossimo Gruppo Plasma cesserà di esistere. Possiamo credere nella prossima tabella di marcia e leggere di notte brillanti libri bianchi, ma qui e ora dobbiamo fare qualcosa con ciò che abbiamo. Fai la merda.

Il compito assegnato al nostro team nell'attuale progetto si presenta in generale così: ci sono molti soggetti, fino a diverse migliaia, che non vogliono costruire relazioni sulla fiducia; È necessario creare una soluzione su DLT che funzioni su normali PC senza particolari requisiti di prestazioni e fornisca un'esperienza utente non peggiore di qualsiasi sistema di contabilità centralizzata. La tecnologia alla base della soluzione deve ridurre al minimo la possibilità di manipolazione dannosa dei dati: ecco perché esiste la blockchain.

Gli slogan dei white paper e dei media ci promettono che il prossimo sviluppo ci consentirà di effettuare milioni di transazioni al secondo. Cos'è veramente?

Mainnet Ethereum funziona attualmente a ~ 30 tps. Già solo per questo motivo è difficile percepirla come blockchain in qualche modo adatta alle esigenze aziendali. Tra le soluzioni autorizzate ci sono benchmark che mostrano 2000 tps (Quorum) o 3000 tps (Tessuto Hyperledger, nella pubblicazione c'è qualcosa in meno, ma bisogna tenere conto che il benchmark è stato effettuato sul vecchio motore di consenso). Era un tentativo di lavorazione radicale del tessuto, che non ha dato i risultati peggiori, 20000 tps, ma finora si tratta solo di ricerca accademica, in attesa di una sua stabile implementazione. È improbabile che una società che può permettersi di mantenere un dipartimento di sviluppatori blockchain sopporti tali indicatori. Ma il problema non è solo il throughput, c’è anche la latenza.

Latenza

Il ritardo dal momento in cui una transazione viene avviata alla sua approvazione finale da parte del sistema dipende non solo dalla velocità con cui il messaggio attraversa tutte le fasi di convalida e ordinamento, ma anche dai parametri di formazione dei blocchi. Anche se la nostra blockchain ci permette di impegnarci ad una velocità di 1000000 di tps, ma richiede 10 minuti per generare un blocco da 488 MB, diventerà più facile per noi?

Diamo uno sguardo più da vicino al ciclo di vita delle transazioni in Hyperledger Fabric per capire dove viene impiegato il tempo e come si collega ai parametri di generazione dei blocchi.

Registro distribuito per sale montate: un'esperienza con Hyperledger Fabric
preso da qui: hyperledger-fabric.readthedocs.io/en/release-1.4/arch-deep-dive.html#swimlane

(1) Il client crea una transazione, la invia ai peer di approvazione, questi ultimi simulano la transazione (applicano le modifiche apportate dal chaincode allo stato corrente, ma non si impegnano nel registro) e ricevono RWSet: nomi chiave, versioni e valori ​​preso dalla raccolta in CouchDB, (2) i giratori rispediscono un RWSet firmato al client, (3) il client controlla la presenza delle firme di tutti i peer necessari (endors) e quindi invia la transazione al servizio di ordinazione , oppure lo invia senza verifica (il controllo avverrà comunque in seguito), il servizio di ordinazione forma un blocco e (4) lo rimanda a tutti i peer, non solo ai giratori; i peer controllano che le versioni della chiave nel set di lettura corrispondano alle versioni nel database, che tutti i firmatari abbiano firme e infine eseguono il commit del blocco.

Ma non è tutto. Le parole “l'ordinante forma un blocco” nascondono non solo l'ordinamento delle transazioni, ma anche 3 richieste di rete sequenziali dal leader ai follower e viceversa: il leader aggiunge un messaggio al log, lo invia ai follower, quest'ultimo lo aggiunge al loro log, invia la conferma dell'avvenuta replica al leader, il leader conferma il messaggio, invia la conferma del commit ai follower, i follower confermano il commit. Minori sono le dimensioni e il tempo di formazione dei blocchi, più spesso il servizio di ordinazione dovrà stabilire un consenso. Hyperledger Fabric ha due parametri per la formazione dei blocchi: BatchTimeout - tempo di formazione del blocco e BatchSize - dimensione del blocco (il numero di transazioni e la dimensione del blocco stesso in byte). Non appena uno dei parametri raggiunge il limite, viene rilasciato un nuovo blocco. Maggiore è il numero di nodi ordinati, maggiore sarà il tempo necessario. Pertanto, è necessario aumentare BatchTimeout e BatchSize. Ma poiché gli RWSets hanno una versione, maggiore è il blocco che creiamo, maggiore è la probabilità di conflitti MVCC. Inoltre, all’aumentare del BatchTimeout, la UX si degrada in modo catastrofico. Il seguente schema per risolvere questi problemi mi sembra ragionevole e ovvio.

Come evitare di attendere la finalizzazione del blocco e non perdere la possibilità di monitorare lo stato della transazione

Maggiore è il tempo di formazione e la dimensione del blocco, maggiore è il rendimento della blockchain. L'uno non segue direttamente dall'altro, ma va ricordato che per stabilire il consenso in RAFT sono necessarie tre richieste di rete dal leader ai follower e viceversa. Maggiore è il numero di nodi ordinati, maggiore sarà il tempo necessario. Minori sono le dimensioni e il tempo di formazione dei blocchi, maggiori saranno le interazioni di questo tipo. Come aumentare il tempo di generazione e la dimensione del blocco senza aumentare il tempo di risposta del sistema per l'utente finale?

Per prima cosa dobbiamo risolvere in qualche modo i conflitti MVCC causati da una grande dimensione del blocco, che potrebbe includere diversi RWSets con la stessa versione. Ovviamente, lato client (in relazione alla rete blockchain, questo potrebbe benissimo essere il backend, e dico sul serio) è necessario Gestore dei conflitti MVCC, che può essere un servizio separato o un decoratore regolare sopra la chiamata che avvia la transazione con logica di ripetizione.

I nuovi tentativi possono essere implementati con una strategia esponenziale, ma in questo caso la latenza diminuirà in modo altrettanto esponenziale. Quindi dovresti usare un tentativo randomizzato entro certi limiti piccoli o uno costante. Con un occhio alle possibili collisioni nella prima opzione.

Il passo successivo è rendere asincrona l'interazione del client con il sistema in modo che non attenda 15, 30 o 10000000 secondi, che imposteremo come BatchTimeout. Ma allo stesso tempo è necessario mantenere la capacità di verificare che le modifiche avviate dalla transazione siano/non siano registrate nella blockchain.
È possibile utilizzare un database per memorizzare lo stato delle transazioni. L'opzione più semplice è CouchDB per la sua facilità d'uso: il database ha un'interfaccia utente pronta all'uso, un'API REST e puoi facilmente configurarne la replica e lo sharding. Puoi semplicemente creare una raccolta separata nella stessa istanza di CouchDB che utilizza Fabric per archiviare il suo stato mondiale. Dobbiamo archiviare questo tipo di documenti.

{
 Status string // Статус транзакции: "pending", "done", "failed"
 TxID: string // ID транзакции
 Error: string // optional, сообщение об ошибке
}

Questo documento viene scritto nel database prima che la transazione venga inviata ai peer, l'ID dell'entità viene restituito all'utente (lo stesso ID viene utilizzato come chiave) se si tratta di un'operazione di creazione, e poi i campi Status, TxID ed Error vengono aggiornato man mano che le informazioni rilevanti vengono ricevute dai peer.

Registro distribuito per sale montate: un'esperienza con Hyperledger Fabric

In questo schema, l'utente non aspetta che il blocco si formi finalmente, guardando la ruota che gira sullo schermo per 10 secondi, riceve una risposta immediata dal sistema e continua a funzionare.

Abbiamo scelto BoltDB per archiviare gli stati delle transazioni perché dobbiamo risparmiare memoria e non vogliamo perdere tempo nell'interazione di rete con un server di database separato, soprattutto quando questa interazione avviene utilizzando un protocollo di testo normale. A proposito, sia che utilizzi CouchDB per implementare lo schema sopra descritto o semplicemente per archiviare lo stato mondiale, in ogni caso ha senso ottimizzare il modo in cui i dati vengono archiviati in CouchDB. Per impostazione predefinita, in CouchDB, la dimensione dei nodi dell'albero b è 1279 byte, che è molto inferiore alla dimensione del settore sul disco, il che significa che sia la lettura che il ribilanciamento dell'albero richiederanno un maggiore accesso fisico al disco. La dimensione ottimale corrisponde allo standard Formato avanzato ed è di 4 kilobyte. Per ottimizzare dobbiamo impostare il parametro btree_chunk_size uguale a 4096 nel file di configurazione di CouchDB. Per BoltDB tale intervento manuale Non richiede.

Contropressione: strategia tampone

Ma i messaggi possono essere molti. Più di quanto il sistema possa gestire, condividendo risorse con una dozzina di altri servizi oltre a quelli mostrati nel diagramma - e tutto ciò dovrebbe funzionare perfettamente anche su macchine su cui eseguire Intellij Idea sarebbe estremamente noioso.

Il problema della diversa capacità comunicante dei sistemi, produttore e consumatore, viene risolto in modi diversi. Vediamo cosa possiamo fare.

lancio: Possiamo affermare di essere in grado di elaborare al massimo X transazioni in T secondi. Tutte le richieste che superano questo limite vengono scartate. Questo è abbastanza semplice, ma poi puoi dimenticarti della UX.

Controllo: il consumatore deve disporre di una sorta di interfaccia attraverso la quale, a seconda del carico, può controllare il TPS del produttore. Non male, ma impone degli obblighi agli sviluppatori del client che creano il carico per implementare questa interfaccia. Per noi questo è inaccettabile, poiché in futuro la blockchain sarà integrata in un gran numero di sistemi esistenti da molto tempo.

buffer: Invece di cercare di resistere al flusso di dati in ingresso, possiamo bufferizzare questo flusso ed elaborarlo alla velocità richiesta. Ovviamente questa è la soluzione migliore se vogliamo fornire una buona esperienza all'utente. Abbiamo implementato il buffer utilizzando una coda in RabbitMQ.

Registro distribuito per sale montate: un'esperienza con Hyperledger Fabric

Allo schema sono state aggiunte due nuove azioni: (1) dopo l'arrivo di una richiesta all'API, un messaggio con i parametri necessari per chiamare una transazione viene messo in coda e il client riceve un messaggio che la transazione è stata accettata da il sistema, (2) il backend legge i dati alla velocità specificata nella configurazione dalla coda; avvia una transazione e aggiorna i dati nell'archivio di stato.
Ora puoi aumentare il tempo di formazione e la capacità di blocco quanto vuoi, nascondendo i ritardi all'utente.

Altri strumenti

Qui non è stato detto nulla sul chaincode, perché, di regola, non c'è nulla da ottimizzare in esso. Il chaincode dovrebbe essere il più semplice e sicuro possibile: questo è tutto ciò che gli viene richiesto. Il framework ci aiuta a scrivere il chaincode in modo semplice e sicuro CCKit da S7 Techlab e analizzatore statico ravvivare^CC.

Inoltre, il nostro team sta sviluppando una serie di utilità per rendere il lavoro con Fabric semplice e divertente: esploratore blockchain, un'utilità per modifiche automatiche della configurazione di rete (aggiunta/rimozione di organizzazioni, nodi RAFT), utilità per revoca dei certificati e rimozione dell'identità. Se vuoi contribuire sei il benvenuto.

conclusione

Questo approccio consente di sostituire facilmente Hyperledger Fabric con Quorum, altre reti Ethereum private (PoA o anche PoW), ridurre significativamente il throughput effettivo, ma allo stesso tempo mantenere la normale UX (sia per gli utenti nel browser che per i sistemi integrati). Quando si sostituisce Fabric con Ethereum nello schema, sarà sufficiente modificare la logica del servizio/decoratore dei tentativi dall'elaborazione dei conflitti MVCC all'incremento atomico del nonce e al nuovo invio. Il buffering e la memorizzazione dello stato hanno permesso di disaccoppiare il tempo di risposta dal tempo di formazione del blocco. Ora puoi aggiungere migliaia di nodi di ordine e non aver paura che i blocchi si formino troppo spesso e carichino il servizio di ordinazione.

Fondamentalmente, questo è tutto ciò che volevo condividere. Sarò felice se questo aiuta qualcuno nel suo lavoro.

Fonte: habr.com

Aggiungi un commento