Rexistro distribuído para rodas: unha experiencia con Hyperledger Fabric

Ola, traballo no equipo do proxecto DRD KP (rexistro de datos distribuídos para o seguimento do ciclo de vida dos xogos de rodas). Aquí gustaríame compartir a experiencia do noso equipo no desenvolvemento dunha cadea de bloques empresarial para este proxecto baixo as limitacións da tecnoloxía. Na súa maior parte, falarei de Hyperledger Fabric, pero o enfoque descrito aquí pódese extrapolar a calquera cadea de bloques autorizada. O obxectivo final da nosa investigación é preparar solucións de cadea de bloques empresariais de forma que o produto final sexa agradable de usar e non sexa demasiado difícil de manter.

Non haberá descubrimentos, solucións inesperadas e non se tratarán desenvolvementos únicos aquí (porque non os teño). Só quero compartir a miña humilde experiencia, demostrar que "foi posible" e, quizais, ler sobre a experiencia doutra persoa na toma de decisións boas e non tan boas nos comentarios.

Problema: as cadeas de bloques aínda non son escalables

Hoxe, os esforzos de moitos desenvolvedores están dirixidos a facer da cadea de bloques unha tecnoloxía realmente conveniente, e non unha bomba de reloxería nun fermoso envoltorio. As canles estatais, a acumulación optimista, o plasma e a fragmentación poden chegar a ser habituais. Algún día. Ou quizais TON volverá aprazar o lanzamento durante seis meses e o próximo Grupo Plasma deixará de existir. Podemos crer noutra folla de ruta e ler brillantes libros brancos pola noite, pero aquí e agora temos que facer algo co que temos. Fai unha merda.

A tarefa encomendada ao noso equipo no actual proxecto parece esta en termos xerais: son moitos os suxeitos, chegando a varios miles, que non queren construír relacións de confianza; é necesario construír en DLT unha solución que funcione en ordenadores ordinarios sen requisitos especiais de rendemento e que proporcione unha experiencia de usuario non peor que calquera sistema de contabilidade centralizado. A tecnoloxía detrás da solución debería minimizar a posibilidade de manipulación maliciosa de datos, por iso a cadea de bloques está aquí.

Os slogans dos libros brancos e dos medios de comunicación prométennos que o próximo desenvolvemento permitirá millóns de transaccións por segundo. Que é realmente?

Mainnet Ethereum funciona actualmente a ~30 tps. Só por iso, é difícil percibilo como unha cadea de bloques que sexa de algunha maneira adecuada para as necesidades corporativas. Entre as solucións autorizadas, coñécense puntos de referencia que mostran 2000 tps (quorum) ou 3000 tps (Tela de hipertensión, hai algo menos na publicación, pero hai que ter en conta que o benchmark realizouse no antigo motor de consenso). Foi un intento de reelaborar radicalmente Fabric, que non deu os peores resultados, 20000 tps, pero ata agora só son estudos académicos á espera da súa implantación estable. É improbable que unha corporación que poida permitirse o luxo de manter un departamento de desenvolvedores de blockchain aguante tales indicadores. Pero o problema non está só no rendemento, tamén hai a latencia.

Latencia

O atraso desde o momento en que se inicia unha transacción ata a súa aprobación final polo sistema depende non só da velocidade da mensaxe que pasa por todas as fases de validación e ordenación, senón tamén dos parámetros de formación do bloque. Aínda que a nosa cadea de bloques nos permita comprometernos a 1000000 tps, pero leva 10 minutos formar un bloque de 488 MB, será máis fácil para nós?

Vexamos o ciclo de vida dunha transacción en Hyperledger Fabric para comprender o que leva tempo e como se relaciona cos parámetros de formación de bloques.

Rexistro distribuído para rodas: unha experiencia con Hyperledger Fabric
tomado de aquí: hyperledger-fabric.readthedocs.io/en/release-1.4/arch-deep-dive.html#swimlane

(1) O cliente forma unha transacción, envíaa a pares que avalan, estes últimos simulan a transacción (aplican os cambios realizados polo chaincode ao estado actual, pero non se comprometen co libro maior) e reciben RWSet: nomes clave, versións e valores tomados da colección en CouchDB, (2) os endosadores envían o RWSet asinado de volta ao cliente, (3) o cliente verifica as sinaturas de todos os pares necesarios (endosadores) e despois envía a transacción ao servizo de pedidos. , ou envíao sen verificación (a verificación aínda terá lugar máis tarde), o servizo de pedidos forma un bloque e (4) envía de volta a todos os compañeiros, non só aos endosadores; os pares verifican que as versións das claves no conxunto de lectura coincidan coas versións da base de datos, as sinaturas de todos os endosadores e, finalmente, confirman o bloqueo.

Pero iso non é todo. Detrás das palabras "orden forma un bloque" escóndese non só a ordenación das transaccións, senón tamén 3 solicitudes de rede consecutivas do líder aos seguidores e de volta: o líder engade unha mensaxe ao rexistro, envía aos seguidores, estes últimos engádense a o seu rexistro, envía a confirmación da replicación exitosa ao líder, o líder envía a mensaxe, envía confirmación de confirmación aos seguidores, os seguidores commit. Canto menor sexa o tamaño e o tempo do bloque, máis veces o servizo de pedidos terá que establecer consenso. Hyperledger Fabric ten dous parámetros de formación de bloques: BatchTimeout - tempo de formación do bloque e BatchSize - tamaño do bloque (o número de transaccións e o tamaño do propio bloque en bytes). En canto un dos parámetros alcanza o límite, emítese un novo bloque. Cantos máis nodos de orde, máis tempo tardará. Polo tanto, cómpre aumentar BatchTimeout e BatchSize. Pero como os RWSets están versionados, canto máis grande fagamos o bloque, maior é a probabilidade de conflitos MVCC. Ademais, cun aumento de BatchTimeout, a UX degrádase catastróficamente. Paréceme razoable e obvio o seguinte esquema para resolver estes problemas.

Como evitar esperar á finalización do bloque e non perder o control do estado da transacción

Canto maior sexa o tempo de formación e o tamaño do bloque, maior será o rendemento da cadea de bloques. Un non segue directamente ao outro, pero hai que lembrar que establecer consenso en RAFT require de tres peticións de rede do líder aos seguidores e viceversa. Cantos máis nodos de orde, máis tempo tardará. Canto menor sexa o tamaño e o tempo de formación do bloque, máis interaccións deste tipo. Como aumentar o tempo de formación e o tamaño do bloque sen aumentar o tempo de resposta do sistema para o usuario final?

En primeiro lugar, cómpre resolver dalgún xeito os conflitos de MVCC causados ​​por un gran tamaño de bloque, que pode incluír diferentes RWSets coa mesma versión. Obviamente, no lado do cliente (en relación á rede blockchain, isto ben pode ser un backend, e digo en serio) Manexador de conflitos MVCC, que pode ser un servizo separado ou un decorador normal nunha chamada de inicio de transacción con lóxica de reintento.

Pódese implementar de novo cunha estratexia exponencial, pero entón a latencia tamén se degradará exponencialmente. Polo tanto, deberías usar un reintento aleatorio dentro de certos pequenos límites ou un constante. Coa mirada en posibles colisións na primeira variante.

O seguinte paso é facer asíncrona a interacción do cliente co sistema para que non espere 15, 30 ou 10000000 de segundos, que estableceremos como BatchTimeout. Pero ao mesmo tempo, é necesario manter a capacidade de asegurarse de que os cambios iniciados pola transacción están rexistrados / non rexistrados na cadea de bloques.
Pódese usar unha base de datos para almacenar o estado das transaccións. A opción máis sinxela é CouchDB pola súa facilidade de uso: a base de datos ten unha interface de usuario lista, unha API REST e pode configurar facilmente a replicación e a fragmentación. Podes crear unha colección separada na mesma instancia de CouchDB que usa Fabric para almacenar o seu estado mundial. Necesitamos almacenar documentos deste tipo.

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

Este documento escríbese na base de datos antes de enviar a transacción aos pares, o ID da entidade devólvese ao usuario (utilízase o mesmo ID como clave) se se trata dunha operación de creación e, a continuación, aparecen os campos Estado, TxID e Erro. actualizado a medida que se recibe información relevante dos compañeiros.

Rexistro distribuído para rodas: unha experiencia con Hyperledger Fabric

Neste esquema, o usuario non espera a que finalmente se forme o bloque, observando a roda que xira na pantalla durante 10 segundos, recibe unha resposta instantánea do sistema e segue a traballar.

Escollemos BoltDB para almacenar os estados das transaccións porque necesitamos aforrar memoria e non queremos perder tempo na interacción da rede cun servidor de bases de datos autónomo, especialmente cando esta interacción se realiza mediante o protocolo de texto plano. Por certo, se usa CouchDB para implementar o esquema descrito anteriormente ou só para almacenar o estado mundial, en calquera caso, ten sentido optimizar a forma en que se almacenan os datos en CouchDB. Por defecto, en CouchDB, o tamaño dos nodos da árbore b é de 1279 bytes, o que é moito menor que o tamaño do sector no disco, o que significa que tanto a lectura como o reequilibrio da árbore requirirán máis accesos ao disco físico. O tamaño óptimo cumpre co estándar formato avanzado e é de 4 kilobytes. Para a optimización, necesitamos establecer o parámetro btree_chunk_size igual a 4096 no ficheiro de configuración de CouchDB. Para BoltDB tal intervención manual non se require.

Contrapresión: estratexia de amortiguación

Pero pode haber moitas mensaxes. Máis do que o sistema pode manexar, compartir recursos con outros servizos ademais dos mostrados no diagrama, e todo isto debería funcionar perfectamente mesmo en máquinas nas que executar Intellij Idea sería moi tedioso.

O problema dos diferentes rendementos dos sistemas de comunicación, produtores e consumidores, resólvese de diferentes xeitos. A ver que podemos facer.

Deixar caer: podemos afirmar que podemos procesar como máximo X transaccións en T segundos. Descartaranse todas as solicitudes que superen este límite. É bastante sinxelo, pero entón podes esquecer a UX.

Controlar: o consumidor debe ter algunha interface a través da cal, dependendo da carga, poida controlar os tps do produtor. Non está mal, pero impón aos desenvolvedores do cliente de carga a obriga de implementar esta interface. Para nós, isto é inaceptable, xa que a cadea de bloques no futuro integrarase nun gran número de sistemas de longa data.

Buffering: en lugar de resistir o fluxo de datos de entrada, podemos almacenar este fluxo e procesalo á velocidade necesaria. Obviamente, esta é a mellor solución se queremos ofrecer unha boa experiencia de usuario. Implementamos o búfer usando unha cola en RabbitMQ.

Rexistro distribuído para rodas: unha experiencia con Hyperledger Fabric

Engadíronse dúas novas accións ao esquema: (1) despois de recibir unha solicitude da API, ponse en cola unha mensaxe cos parámetros necesarios para chamar a transacción e o cliente recibe unha mensaxe de que a transacción foi aceptada polo sistema, ( 2) o backend le os datos a unha velocidade especificada na configuración desde a cola; inicia unha transacción e actualiza os datos no almacén de estado.
Agora podes aumentar o tempo de construción e bloquear a capacidade tanto como queiras, ocultando os atrasos ao usuario.

Outras ferramentas

Aquí non se dixo nada sobre o código en cadea, porque normalmente non hai nada que optimizar nel. O código de cadea debe ser o máis sinxelo e seguro posible; iso é todo o que se lle require. O marco axúdanos moito a escribir código en cadea de forma sinxela e segura. CSKit de S7 Techlab e analizador estático revivir^CC.

Ademais, o noso equipo está a desenvolver un conxunto de utilidades para facer que traballar con Fabric sexa sinxelo e agradable: explorador blockchain, utilidade para reconfiguración automática da rede (engadir/eliminar organizacións, nodos RAFT), utilidade para revogación do certificado e eliminación da identidade. Se queres colaborar, benvido.

Conclusión

Este enfoque facilita a substitución de Hyperledger Fabric por Quorum, outras redes privadas de Ethereum (PoA ou incluso PoW), reducen significativamente o rendemento real, pero ao mesmo tempo mantén a UX normal (tanto para os usuarios do navegador como para os sistemas integrados). ). Ao substituír Fabric por Ethereum no esquema, só haberá que cambiar a lóxica do servizo de reintento/decorador de xestionar conflitos MVCC a un incremento e reenvío atómico nonce. O almacenamento en búfer e o estado permitiu desvincular o tempo de resposta do tempo de formación do bloque. Agora podes engadir miles de nodos de pedidos e non ter medo de que os bloques se formen con demasiada frecuencia e cargar o servizo de pedidos.

En xeral, isto é todo o que quería compartir. Estarei feliz se axuda a alguén no seu traballo.

Fonte: www.habr.com

Engadir un comentario