Convertendo FunC en FunCtional con Haskell: como Serokell gañou o Concurso Blockchain de Telegram

Probablemente escoitaches ese Telegram está a piques de lanzar a plataforma de blockchain Ton. Pero é posible que te perdases a noticia de Telegram non hai moito anunciou un concurso para a implantación dun ou varios contratos intelixentes para esta plataforma.

O equipo de Serokell, cunha ampla experiencia no desenvolvemento de grandes proxectos de blockchain, non podía quedarse de lado. Delegamos cinco empregados no concurso, e dúas semanas máis tarde ocuparon o primeiro lugar nel baixo o (in)modesto alcume aleatorio Sexy Chameleon. Neste artigo falarei de como o fixeron. Agardamos que nos próximos dez minutos leades polo menos unha historia interesante, e como moito atopedes nela algo útil que poidades aplicar no voso traballo.

Pero comecemos cun pequeno contexto.

A competencia e as súas condicións

Así, as principais tarefas dos participantes foron a posta en marcha dun ou varios dos contratos intelixentes propostos, así como facer propostas para mellorar o ecosistema TON. O concurso desenvolveuse do 24 de setembro ao 15 de outubro e os resultados foron anunciados só o 15 de novembro. Moito tempo, tendo en conta que durante este tempo Telegram conseguiu celebrar e anunciar os resultados de concursos de deseño e desenvolvemento de aplicacións en C++ para probar e avaliar a calidade das chamadas VoIP en Telegram.

Seleccionamos dous contratos intelixentes da lista proposta polos organizadores. Para un deles, utilizamos ferramentas distribuídas con TON, e a segunda implementouse nunha nova linguaxe desenvolvida polos nosos enxeñeiros especificamente para TON e integrada en Haskell.

A elección dunha linguaxe de programación funcional non é casual. No noso blog corporativo Moitas veces falamos de por que pensamos que a complexidade das linguaxes funcionais é unha enorme esaxeración e por que xeralmente as preferimos ás orientadas a obxectos. Por certo, tamén contén orixinal deste artigo.

Por que decidimos participar?

En definitiva, porque a nosa especialización son proxectos non estándar e complexos que requiren habilidades especiais e que adoitan ter un valor científico para a comunidade informática. Apoiamos firmemente o desenvolvemento de código aberto e estamos comprometidos na súa popularización e tamén cooperamos coas principais universidades rusas no campo da informática e das matemáticas.

As interesantes tarefas do concurso e a participación no noso querido proxecto de Telegram foron en si mesmas unha excelente motivación, pero o fondo do premio converteuse nun incentivo adicional. 🙂

Investigación da cadea de bloques TON

Seguimos de preto os novos desenvolvementos en blockchain, intelixencia artificial e aprendizaxe automática e intentamos non perdernos nin un só lanzamento significativo en cada unha das áreas nas que traballamos. Polo tanto, cando comezou a competición, o noso equipo xa estaba familiarizado coas ideas de Papel branco TON. Non obstante, antes de comezar a traballar con TON, non analizamos a documentación técnica e o código fonte real da plataforma, polo que o primeiro paso foi bastante obvio: un estudo exhaustivo da documentación oficial sobre On-line e repositorios de proxectos.

Cando comezou o concurso, o código xa estaba publicado, polo que para aforrar tempo, decidimos buscar unha guía ou resumo escrito por polos usuarios. Desafortunadamente, isto non deu ningún resultado: ademais das instrucións para montar a plataforma en Ubuntu, non atopamos ningún outro material.

A documentación en si estaba ben investigada, pero era difícil de ler nalgunhas áreas. Moitas veces tivemos que volver a certos puntos e pasar de descricións de alto nivel de ideas abstractas a detalles de implementación de baixo nivel.

Sería máis doado se a especificación non incluíra unha descrición detallada da implementación. A información sobre como representa unha máquina virtual a súa pila ten máis probabilidades de distraer aos desenvolvedores que crean contratos intelixentes para a plataforma TON que de axudalos.

Nix: montando o proxecto

En Serokell somos grandes fans Nix. Recollemos os nosos proxectos con el e despregámolos usando NixOps, e instalado en todos os nosos servidores Nix OS. Grazas a isto, todas as nosas compilacións son reproducibles e funcionan en calquera sistema operativo no que se poida instalar Nix.

Entón comezamos por crear Superposición Nix con expresión para a montaxe TON. Coa súa axuda, compilar TON é o máis sinxelo posible:

$ cd ~/.config/nixpkgs/overlays && git clone https://github.com/serokell/ton.nix
$ cd /path/to/ton/repo && nix-shell
[nix-shell]$ cmakeConfigurePhase && make

Teña en conta que non precisa instalar ningunha dependencia. Nix fará todo por ti máxicamente, tanto se estás usando NixOS, Ubuntu ou macOS.

Programación para TON

O código de contrato intelixente na rede TON execútase na máquina virtual TON (TVM). TVM é máis complexo que a maioría das outras máquinas virtuais e ten unha funcionalidade moi interesante, por exemplo, pode funcionar con continuacións и ligazóns a datos.

Ademais, os mozos de TON crearon tres novas linguaxes de programación:

Quinto é unha linguaxe de programación de pila universal que se asemella Forth. A súa super habilidade é a capacidade de interactuar con TVM.

FunC é unha linguaxe de programación de contrato intelixente que é semellante a C e está compilado noutro idioma - Fift Assembler.

Quinto ensamblador — Quinta biblioteca para xerar código executable binario para TVM. Fifth Assembler non ten un compilador. Isto Linguaxe eDSL (Embedded Domain Specific Language).

O noso concurso funciona

Finalmente, é hora de ver os resultados dos nosos esforzos.

Canle de pago asíncrona

A canle de pago é un contrato intelixente que permite a dous usuarios enviar pagos fóra da cadea de bloques. Como resultado, aforra non só diñeiro (non hai comisión), senón tamén tempo (non tes que esperar a que se procese o seguinte bloque). Os pagos poden ser tan pequenos como se desexe e tantas veces como sexa necesario. Neste caso, as partes non teñen que confiar entre si, xa que a equidade da liquidación final está garantida polo contrato intelixente.

Atopamos unha solución bastante sinxela ao problema. Dúas partes poden intercambiar mensaxes asinadas, cada unha con dous números, o importe total pagado por cada parte. Estes dous números funcionan como reloxo vectorial en sistemas distribuídos tradicionais e establecer a orde "ocorreu antes" nas transaccións. Mediante estes datos, o contrato poderá resolver calquera posible conflito.

De feito, un número é suficiente para implementar esta idea, pero deixamos os dous porque deste xeito poderiamos facer unha interface de usuario máis cómoda. Ademais, decidimos incluír o importe do pago en cada mensaxe. Sen ela, se a mensaxe se perde por algún motivo, entón, aínda que todas as cantidades e o cálculo final serán correctos, o usuario pode non notar a perda.

Para probar a nosa idea, buscamos exemplos de uso dun protocolo de canle de pago tan sinxelo e conciso. Sorprendentemente, só atopamos dous:

  1. Descrición un enfoque similar, só para o caso dunha canle unidireccional.
  2. Titoría, que describe a mesma idea que a nosa, pero sen explicar moitos detalles importantes, como a corrección xeral e os procedementos de resolución de conflitos.

Quedou claro que ten sentido describir o noso protocolo en detalle, prestando especial atención á súa corrección. Despois de varias iteracións, a especificación estaba lista, e agora ti tamén podes. mira para ela.

Implementamos o contrato en FunC e escribimos a utilidade de liña de comandos para interactuar co noso contrato completamente en Fift, tal e como recomendaron os organizadores. Poderiamos ter escollido calquera outro idioma para a nosa CLI, pero estabamos interesados ​​en probar Fit para ver como funcionaba na práctica.

Para ser honesto, despois de traballar con Fift, non vimos ningún motivo convincente para preferir esta linguaxe a linguaxes populares e de uso activo con ferramentas e bibliotecas desenvolvidas. Programar nunha linguaxe baseada na pila é bastante desagradable, xa que tes que manter constantemente na túa cabeza o que hai na pila, e o compilador non axuda con isto.

Polo tanto, na nosa opinión, a única xustificación da existencia de Fift é o seu papel como lingua de acollida para Fift Assembler. Pero non sería mellor incorporar o ensamblador TVM nalgún idioma existente, en lugar de inventar un novo para este propósito esencialmente único?

TVM Haskell eDSL

Agora toca falar do noso segundo contrato intelixente. Decidimos desenvolver unha carteira multisinatura, pero escribir outro contrato intelixente en FunC sería demasiado aburrido. Queriamos engadir algo de sabor, e esa era a nosa propia linguaxe ensambladora para TVM.

Do mesmo xeito que Fift Assembler, o noso novo idioma está incorporado, pero escollemos Haskell como anfitrión en lugar de Fift, o que nos permite aproveitar ao máximo o seu sistema de tipos avanzado. Cando se traballa con contratos intelixentes, onde o custo incluso dun pequeno erro pode ser moi alto, a escritura estática, na nosa opinión, é unha gran vantaxe.

Para demostrar o que parece o ensamblador TVM integrado en Haskell, implementamos nel unha carteira estándar. Aquí tes algunhas cousas ás que prestar atención:

  • Este contrato consta dunha función, pero podes usar tantas como queiras. Cando defines unha nova función na lingua de acollida (por exemplo, Haskell), o noso eDSL permítelle escoller se queres que se converta nunha rutina separada en TVM ou simplemente integrada no punto de chamada.
  • Como Haskell, as funcións teñen tipos que se verifican no momento da compilación. No noso eDSL, o tipo de entrada dunha función é o tipo de pila que a función espera e o tipo de resultado é o tipo de pila que se producirá despois da chamada.
  • O código ten anotacións stacktype, describindo o tipo de pila esperado no punto de chamada. No contrato de carteira orixinal estes eran só comentarios, pero no noso eDSL son en realidade parte do código e compróbanse no momento da compilación. Poden servir como documentación ou declaracións que axudan ao desenvolvedor a atopar o problema se o código cambia e o tipo de pila cambia. Por suposto, tales anotacións non afectan o rendemento do tempo de execución, xa que non se xera ningún código TVM para elas.
  • Este aínda é un prototipo escrito en dúas semanas, polo que aínda queda moito traballo por facer no proxecto. Por exemplo, todas as instancias das clases que ves no código seguinte deberían xerarse automaticamente.

Así se ve a implementación dunha carteira multisig no noso 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 @Word32

O código fonte completo do noso contrato de carteira eDSL e multisinatura pódese atopar en este repositorio. E máis contado con detalle sobre as linguas integradas, o noso compañeiro Georgy Agapov.

Conclusións sobre o concurso e TON

En total, o noso traballo levou 380 horas (incluíndo familiarización coa documentación, reunións e desenvolvemento real). No proxecto da competición participaron cinco desenvolvedores: CTO, xefe de equipo, especialistas en plataformas blockchain e desenvolvedores de software Haskell.

Atopamos recursos para participar no concurso sen dificultade, xa que o espírito de hackathon, o traballo en equipo cercano e a necesidade de mergullarnos rapidamente en aspectos das novas tecnoloxías sempre é apaixonante. Varias noites sen durmir para acadar os máximos resultados en condicións de recursos limitados son compensadas por unha experiencia inestimable e excelentes recordos. Ademais, traballar nesas tarefas é sempre unha boa proba dos procesos da empresa, xa que é extremadamente difícil conseguir resultados verdadeiramente decentes sen unha interacción interna que funcione ben.

Letras aparte: quedamos impresionados pola cantidade de traballo realizado polo equipo de TON. Conseguiron construír un sistema complexo, fermoso e, sobre todo, de traballo. TON demostrou ser unha plataforma con gran potencial. Non obstante, para que este ecosistema se desenvolva, cómpre facer moito máis, tanto no que se refire ao seu uso en proxectos blockchain como no que se refire á mellora das ferramentas de desenvolvemento. Estamos orgullosos de ser agora parte deste proceso.

Se despois de ler este artigo aínda tes algunha dúbida ou tes ideas sobre como usar TON para resolver os teus problemas, escríbenos - Estaremos encantados de compartir a nosa experiencia.

Fonte: www.habr.com

Engadir un comentario