Seguramente has oído que Telegram . Pero puede que te hayas perdido la noticia de que, no hace mucho, Telegram para implementar uno o varios contratos inteligentes para esta plataforma.
El equipo de Serokell, con una rica experiencia en el desarrollo de grandes proyectos blockchain, no pudo permanecer al margen. Delegamos a cinco empleados para el concurso y, ya a las dos semanas, ocuparon el primer lugar con el (no) modesto apodo aleatorio Sexy Chameleon. En este artículo, te contaré cómo lo lograron. Esperamos que en los próximos diez minutos, al menos leas una historia interesante y, en el mejor de los casos, encuentres algo útil que puedas aplicar en tu trabajo.
Pero comencemos con una pequeña inmersión en el contexto.
El concurso y sus condiciones
Así que, las principales tareas de los participantes fueron implementar uno o más de los contratos inteligentes propuestos, así como hacer sugerencias para mejorar el ecosistema de TON. El concurso se llevó a cabo desde el 24 de septiembre hasta el 15 de octubre, y los resultados se anunciaron solo el 15 de noviembre. Bastante largo, considerando que durante ese tiempo Telegram también realizó y divulgó los resultados de concursos de diseño y de desarrollo de aplicaciones en C++ para probar y evaluar la calidad de las llamadas VoIP en Telegram.
Elegimos de la lista propuesta por los organizadores, dos contratos inteligentes. Para uno de ellos usamos las herramientas que se distribuyen con TON, y el segundo lo implementamos en un nuevo lenguaje, desarrollado por nuestros ingenieros específicamente para TON e integrado en Haskell.
La elección del lenguaje de programación funcional no fue casual. En nuestro a menudo explicamos por qué consideramos que la complejidad de los lenguajes funcionales es una gran exageración y por qué en general preferimos estos a los orientados a objetos. Por cierto, en él también está el .
¿Por qué decidimos participar?
En resumen, porque nuestra especialización son proyectos no estándar y complejos que requieren habilidades especiales y, a menudo, tienen un valor científico para la comunidad de TI. Apoyamos firmemente el desarrollo de código abierto y trabajamos para su difusión, así como colaboramos con las principales universidades de Rusia en el campo de la informática y las matemáticas.
Las interesantes tareas del concurso y la implicación en el querido proyecto Telegram fueron, por sí mismas, una excelente motivación, y el fondo del premio fue un incentivo adicional. 🙂
Investigación de la blockchain TON
Seguimos de cerca los nuevos desarrollos en blockchain, inteligencia artificial y aprendizaje automático, y nos esforzamos por no perdernos ningún lanzamiento significativo en cada uno de los campos en los que trabajamos. Por eso, cuando comenzó el concurso, nuestro equipo ya estaba familiarizado con las ideas de . Sin embargo, antes de comenzar a trabajar con TON, no analizamos la documentación técnica ni el código fuente real de la plataforma, por lo que el primer paso fue bastante obvio: un exhaustivo estudio de la documentación oficial en y en .
Para el inicio del concurso, el código ya estaba publicado, así que, para ahorrar tiempo, decidimos buscar una guía o resumen escrito por usuarios. Desafortunadamente, esto no dio resultado: aparte de la instrucción sobre cómo compilar la plataforma en Ubuntu, no encontramos ningún otro material.
La documentación en sí resultó estar muy bien elaborada, pero en algunos momentos fue difícil de leer. Con bastante frecuencia tuvimos que volver a ciertos puntos y cambiar de descripciones de alto nivel de ideas abstractas a detalles de implementación de bajo nivel.
Sería más fácil si la especificación no tuviera una descripción detallada de la implementación. La información sobre cómo la máquina virtual representa su pila tiende a distraer a los desarrolladores que crean contratos inteligentes para la plataforma TON, en lugar de ayudarles.
Nix: construyendo el proyecto
En Serokell somos grandes aficionados a . Construimos nuestros proyectos y los desplegamos usando , y en todos nuestros servidores está instalado . Gracias a eso, todas nuestras compilaciones son reproducibles y funcionan en cualquier sistema operativo en el que se pueda instalar Nix.
Por lo tanto, comenzamos por crear . Con ella, compilar TON es muy sencillo:
$ cd ~/.config/nixpkgs/overlays && git clone https://github.com/serokell/ton.nix
$ cd /path/to/ton/repo && nix-shell
[nix-shell]$ cmakeConfigurePhase && makeTengan en cuenta que no necesitan instalar ninguna dependencia. Nix mágicamente hará todo por ustedes, sin importar si están usando NixOS, Ubuntu o macOS.
Programación para TON
El código de los contratos inteligentes en la red TON se ejecuta en la TON Virtual Machine (TVM). La TVM es más compleja que la mayoría de las otras máquinas virtuales y tiene funciones bastante interesantes, como la capacidad de trabajar con continuaciones y enlaces a datos.
Además, el equipo de TON ha creado tres nuevos lenguajes de programación:
Fift — un lenguaje de programación de pila universal que se asemeja a . Su superpoder es la capacidad de interactuar con la TVM.
FunC — un lenguaje de programación de contratos inteligentes que se asemeja a y se compila en otro lenguaje: Fift Assembler.
Fift Assembler — una biblioteca Fift para generar código ejecutable binario para la TVM. Fift Assembler no tiene compilador. Es un .
Nuestros trabajos de concurso
Finalmente, ha llegado el momento de ver los resultados de nuestros esfuerzos.
Canal de pago asíncrono
Un canal de pago (payment channel) es un contrato inteligente que permite a dos usuarios enviar pagos fuera de la cadena de bloques. Como resultado, se ahorran no solo dinero (sin comisiones), sino también tiempo (no es necesario esperar a que se procese el siguiente bloque). Los pagos pueden ser tan pequeños como se desee y pueden ocurrir tan a menudo como se necesite. Además, las partes no tienen que confiar entre sí, ya que la equidad del cálculo final está garantizada por el contrato inteligente.
Hemos encontrado una solución bastante simple al problema. Ambas partes pueden intercambiar mensajes firmados, cada uno de los cuales contiene dos números: el monto total pagado por cada participante. Estos dos números funcionan como en sistemas distribuidos tradicionales y definen el orden de "ocurrió antes" en las transacciones. Usando estos datos, el contrato podrá resolver cualquier posible conflicto.
De hecho, para implementar esta idea es suficiente con un solo número, pero dejamos ambos, ya que así pudimos hacer una interfaz de usuario más conveniente. Además, decidimos incluir en cada mensaje el tamaño del pago. Sin él, si el mensaje se pierde por alguna razón, aunque todas las cantidades y el cálculo final sean correctos, el usuario podría no notar la pérdida.
Para comprobar nuestra idea, buscamos ejemplos de uso de un protocolo de canal de pagos tan simple y conciso. Para nuestra sorpresa, encontramos solo dos:
- un enfoque similar, pero para el caso de un canal unidireccional.
- , que describe la misma idea que la nuestra, solo que sin explicar muchos detalles importantes, como la corrección general y el procedimiento para resolver conflictos.
Quedó claro que tiene sentido describir nuestro protocolo en detalle, prestando especial atención a su corrección. Después de varias iteraciones, la especificación estaba lista, y ahora también pueden .
Implementamos el contrato en FunC, y la utilidad de línea de comandos para interactuar con nuestro contrato la escribimos completamente en Fift, como recomendaron los organizadores. Podríamos haber elegido cualquier otro idioma para nuestro CLI, pero nos interesaba probar Fift para ver cómo se desempeñaría en la práctica.
Honestamente, después de trabajar con Fift, no vimos razones de peso para preferir este lenguaje a idiomas populares y ampliamente utilizados con herramientas y bibliotecas desarrolladas. Programar en un lenguaje de pila es bastante incómodo, ya que hay que mantener constantemente en mente lo que está donde en la pila, y el compilador no ayuda en esto.
Por lo tanto, la única justificación que vemos para la existencia de Fift es su papel como lenguaje anfitrión para Fift Assembler. Pero, ¿no sería mejor integrar el ensamblador TVM en algún lenguaje existente en vez de inventar uno nuevo para este, en esencia, único propósito?
TVM Haskell eDSL
Ahora es el momento de hablar sobre nuestro segundo contrato inteligente. Decidimos desarrollar una billetera con múltiples firmas, pero escribir otro contrato inteligente en FunC sería demasiado aburrido. Queríamos agregar un toque especial, y ese toque fue nuestro propio lenguaje de ensamblador para TVM.
Al igual que Fift Assembler, nuestro nuevo lenguaje es embebido, solo que elegimos Haskell como anfitrión en lugar de Fift, lo que nos permitió aprovechar al máximo su avanzada sistema de tipos. Al trabajar con contratos inteligentes, donde el costo de un pequeño error puede ser muy alto, la tipificación estática es, en nuestra opinión, una gran ventaja.
Para demostrar cómo se ve el ensamblador TVM, integrado en Haskell, hemos implementado una billetera estándar. Aquí hay algunas cosas a tener en cuenta:
- Este contrato consiste en una sola función, pero puedes utilizar tantas como quieras. Al definir una nueva función en el lenguaje anfitrión (es decir, en Haskell), nuestro eDSL te permite elegir si deseas que se convierta en un subprograma separado en TVM o si simplemente esté integrada en el lugar de llamada.
- Al igual que en Haskell, las funciones tienen tipos que se verifican en tiempo de compilación. En nuestro eDSL, el tipo de entrada de la función es el tipo de pila que la función espera, y el tipo de resultado es el tipo de pila que se obtiene tras la llamada.
- En el código hay anotaciones
stacktype, que describen el tipo esperado de la pila en el punto de llamada. En el contrato original de la billetera, eran solo comentarios, pero en nuestro eDSL son parte del código y se verifican durante la compilación. Pueden servir como documentación o afirmaciones que ayudan al desarrollador a detectar problemas en caso de que el tipo de pila cambie al modificar el código. Por supuesto, tales anotaciones no afectan el rendimiento en tiempo de ejecución, ya que no se genera ningún código TVM para ellas. - Este sigue siendo un prototipo, escrito en dos semanas, por lo que aún queda mucho trabajo por hacer en el proyecto. Por ejemplo, todas las instancias de las clases que ves en el código a continuación deben generarse automáticamente.
Así es como se ve la implementación de una billetera multisig en nuestro eDSL:
main :: IO ()
main = putText $ pretty $ declProgram procedures methods
where
procedures =
[ ("recv_external", decl recvExternal)
, ("recv_internal", decl recvInternal)
]
methods =
[ ("seqno", declMethod getSeqno)
]
data Storage = Storage
{ sCnt :: Word32
, sPubKey :: PublicKey
}
instance DecodeSlice Storage where
type DecodeSliceFields Storage = [PublicKey, Word32]
decodeFromSliceImpl = do
decodeFromSliceImpl @Word32
decodeFromSliceImpl @PublicKey
instance EncodeBuilder Storage where
encodeToBuilder = do
encodeToBuilder @Word32
encodeToBuilder @PublicKey
data WalletError
= SeqNoMismatch
| SignatureMismatch
deriving (Eq, Ord, Show, Generic)
instance Exception WalletError
instance Enum WalletError where
toEnum 33 = SeqNoMismatch
toEnum 34 = SignatureMismatch
toEnum _ = error "Uknown MultiSigError id"
fromEnum SeqNoMismatch = 33
fromEnum SignatureMismatch = 34
recvInternal :: '[Slice] :-> '[]
recvInternal = drop
recvExternal :: '[Slice] :-> '[]
recvExternal = do
decodeFromSlice @Signature
dup
preloadFromSlice @Word32
stacktype @[Word32, Slice, Signature]
-- cnt cs sign
pushRoot
decodeFromCell @Storage
stacktype @[PublicKey, Word32, Word32, Slice, Signature]
-- pk cnt' cnt cs sign
xcpu @1 @2
stacktype @[Word32, Word32, PublicKey, Word32, Slice, Signature]
-- cnt cnt' pk cnt cs sign
equalInt >> throwIfNot SeqNoMismatch
push @2
sliceHash
stacktype @[Hash Slice, PublicKey, Word32, Slice, Signature]
-- hash pk cnt cs sign
xc2pu @0 @4 @4
stacktype @[PublicKey, Signature, Hash Slice, Word32, Slice, PublicKey]
-- pubk sign hash cnt cs pubk
chkSignU
stacktype @[Bool, Word32, Slice, PublicKey]
-- ? cnt cs pubk
throwIfNot SignatureMismatch
accept
swap
decodeFromSlice @Word32
nip
dup
srefs @Word8
pushInt 0
if IsEq
then ignore
else do
decodeFromSlice @Word8
decodeFromSlice @(Cell MessageObject)
stacktype @[Slice, Cell MessageObject, Word8, Word32, PublicKey]
xchg @2
sendRawMsg
stacktype @[Slice, Word32, PublicKey]
endS
inc
encodeToCell @Storage
popRoot
getSeqno :: '[] :-> '[Word32]
getSeqno = do
pushRoot
cToS
preloadFromSlice @Word32El código fuente completo de nuestro eDSL y el contrato de la billetera con múltiples firmas se puede encontrar en Y más sobre los lenguajes integrados nuestro colega Georgiy Agapov.
Conclusiones sobre el concurso y TON
En total, nuestro trabajo tomó 380 horas (incluyendo la familiarización con la documentación, reuniones y el desarrollo en sí). En el proyecto del concurso participaron cinco desarrolladores: CTO, líder de equipo, especialistas en plataformas de blockchain y desarrolladores de software en Haskell.
Los recursos para participar en el concurso los encontramos sin dificultad, ya que el espíritu del hackatón, el trabajo en equipo intenso y la necesidad de una rápida inmersión en aspectos de nuevas tecnologías siempre son emocionantes. Varias noches sin dormir en busca de alcanzar un resultado óptimo en condiciones de recursos limitados se compensan con una experiencia invaluable y recuerdos excelentes. Además, trabajar en tareas como estas siempre es una buena prueba de los procesos de la empresa, ya que lograr resultados realmente dignos sin una interacción interna bien ajustada es muy difícil.
Más allá de la lírica: nos impresionó la cantidad de trabajo realizada por el equipo de TON. Lograron construir un sistema complejo, hermoso y, lo más importante, funcional. TON se ha mostrado como una plataforma con gran potencial. Sin embargo, para que este ecosistema se desarrolle, se necesita hacer mucho más, tanto en términos de su uso en proyectos blockchain como en la mejora de las herramientas de desarrollo. Nos enorgullece ser parte de este proceso.
Si después de leer este artículo tienes alguna pregunta o surge alguna idea sobre cómo aplicar TON para resolver tus desafíos, — estaremos encantados de compartir nuestra experiencia.
Fuente: habr.com
