Haskell ile FunC'yi FunCtional'a Dönüştürmek: Serokell, Telegram Blockchain Yarışmasını Nasıl Kazandı?

Muhtemelen Telegram'ı duymuşsunuzdur Ton blockchain platformunu başlatmak üzere. Ancak kısa süre önce Telegram'ın haberini kaçırmış olabilirsiniz. bir yarışma duyurdu Bu platform için bir veya daha fazla akıllı sözleşmenin uygulanması için.

Büyük blockchain projeleri geliştirme konusunda geniş deneyime sahip olan Serokell ekibi kenara çekilemedi. Yarışmaya beş çalışanımızı görevlendirdik ve iki hafta sonra onlar, mütevazi ve rastgele bir takma ad olan Seksi Bukalemun altında birinci oldular. Bu yazımda bunu nasıl yaptıklarından bahsedeceğim. Önümüzdeki on dakika içinde en azından ilginç bir hikaye okuyacağınızı ve en fazla bu hikayede işinize uygulayabileceğiniz yararlı bir şeyler bulacağınızı umuyoruz.

Ama küçük bir bağlamla başlayalım.

Rekabet ve koşulları

Dolayısıyla katılımcıların ana görevleri, önerilen akıllı sözleşmelerden bir veya daha fazlasının uygulanmasının yanı sıra TON ekosistemini iyileştirecek önerilerde bulunmaktı. Yarışma 24 Eylül'den 15 Ekim'e kadar sürdü ve sonuçlar yalnızca 15 Kasım'da açıklandı. Bu süre zarfında Telegram'ın, Telegram'daki VoIP çağrılarının kalitesini test etmek ve değerlendirmek için C++ uygulamalarının tasarımı ve geliştirilmesi üzerine yarışmalar düzenleyip sonuçlarını duyurmayı başardığı göz önüne alındığında oldukça uzun bir süre.

Organizatörlerin önerdiği listeden iki akıllı sözleşme seçtik. Bunlardan biri için TON ile dağıtılan araçları kullandık, ikincisi ise mühendislerimiz tarafından özel olarak TON için geliştirilen ve Haskell'e yerleşik yeni bir dilde uygulandı.

İşlevsel bir programlama dilinin seçimi tesadüfi değildir. bizim kurumsal blog İşlevsel dillerin karmaşıklığının neden büyük bir abartı olduğunu düşündüğümüzden ve neden genel olarak bunları nesne yönelimli dillere tercih ettiğimizden sık sık bahsediyoruz. Bu arada, aynı zamanda şunları da içeriyor bu makalenin orijinali.

Neden katılmaya karar verdik?

Kısacası uzmanlığımız, özel beceriler gerektiren ve genellikle BT topluluğu için bilimsel değere sahip, standart dışı ve karmaşık projeler olduğundan. Açık kaynak geliştirmeyi güçlü bir şekilde destekliyoruz ve yaygınlaştırılmasına çalışıyoruz ve aynı zamanda bilgisayar bilimi ve matematik alanında önde gelen Rus üniversiteleriyle işbirliği yapıyoruz.

Yarışmanın ilginç görevleri ve sevgili Telegram projemize katılım başlı başına mükemmel bir motivasyondu, ancak ödül fonu ek bir teşvik haline geldi. 🙂

TON blockchain araştırması

Blockchain, yapay zeka ve makine öğrenimi alanlarındaki yeni gelişmeleri yakından takip ediyor ve çalıştığımız alanların her birinde önemli bir gelişmeyi kaçırmamaya çalışıyoruz. Bu nedenle, yarışma başladığında ekibimiz zaten fikirlere aşinaydı. TON teknik incelemesi. Ancak TON ile çalışmaya başlamadan önce platformun teknik belgelerini ve gerçek kaynak kodunu analiz etmedik, bu nedenle ilk adım oldukça açıktı: resmi belgelerin kapsamlı bir şekilde incelenmesi web sitesi ve proje havuzları.

Yarışma başladığında kod zaten yayınlanmıştı, bu yüzden zaman kazanmak için tarafından yazılan bir rehber veya özet aramaya karar verdik. kullanıcılar tarafından. Ne yazık ki bu herhangi bir sonuç vermedi - platformun Ubuntu'da montajına ilişkin talimatlar dışında başka malzeme bulamadık.

Belgelerin kendisi iyi araştırılmıştı ancak bazı alanlarda okunması zordu. Çoğunlukla belirli noktalara dönmek ve soyut fikirlerin üst düzey açıklamalarından düşük düzeyli uygulama ayrıntılarına geçmek zorunda kaldık.

Spesifikasyonun uygulamanın ayrıntılı bir tanımını hiç içermemesi daha kolay olurdu. Bir sanal makinenin yığınını nasıl temsil ettiğine ilişkin bilgilerin, TON platformu için akıllı sözleşmeler oluşturan geliştiricilerin dikkatini onlara yardımcı olmaktan daha çok dağıtması muhtemeldir.

Nix: projeyi bir araya getirmek

Biz Serokell'in büyük hayranlarıyız Reddetmek. Projelerimizi onunla topluyoruz ve kullanarak dağıtıyoruz. NixOps, ve tüm sunucularımızda yüklü Nix işletim sistemi. Bu sayede tüm yapılarımız tekrarlanabilir ve Nix'in kurulabileceği her işletim sisteminde çalışır.

Bu yüzden yaratarak başladık TON derlemesi için ifade içeren Nix kaplaması. Onun yardımıyla TON'u derlemek mümkün olduğu kadar basittir:

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

Herhangi bir bağımlılık kurmanıza gerek olmadığını unutmayın. İster NixOS, ister Ubuntu veya macOS kullanıyor olun, Nix sihirli bir şekilde sizin için her şeyi yapacaktır.

TON için programlama

TON Ağındaki akıllı sözleşme kodu, TON Sanal Makinesinde (TVM) çalışır. TVM, diğer sanal makinelerin çoğundan daha karmaşıktır ve çok ilginç işlevlere sahiptir; örneğin, devamlar и verilere bağlantılar.

Üstelik TON'daki adamlar üç yeni programlama dili yarattılar:

Beşinci benzeyen evrensel bir yığın programlama dilidir. ileri. Onun süper yeteneği TVM ile etkileşime girebilme yeteneğidir.

Eğlence C benzer bir akıllı sözleşme programlama dilidir. C ve başka bir dil olan Fift Assembler'da derlenmiştir.

Beşinci Montajcı — TVM için ikili yürütülebilir kod oluşturmaya yönelik beşinci kitaplık. Beşinci Assembler'ın derleyicisi yok. Bu Gömülü Etki Alanına Özel Dil (eDSL).

Yarışma çalışmalarımız

Son olarak çabalarımızın sonuçlarına bakmanın zamanı geldi.

Asenkron ödeme kanalı

Ödeme kanalı, iki kullanıcının blockchain dışında ödeme göndermesine olanak tanıyan akıllı bir sözleşmedir. Sonuç olarak, yalnızca paradan değil (komisyon yoktur) aynı zamanda zamandan da tasarruf edersiniz (bir sonraki bloğun işlenmesini beklemek zorunda kalmazsınız). Ödemeler istenildiği kadar küçük ve gerektiği sıklıkta yapılabilir. Bu durumda, nihai çözümün adilliği akıllı sözleşmeyle garanti altına alındığı için tarafların birbirlerine güvenmesi gerekmiyor.

Soruna oldukça basit bir çözüm bulduk. İki taraf, her biri iki numara içeren, yani her bir tarafın ödediği tutarın tamamını içeren imzalı mesajlar alışverişinde bulunabilir. Bu iki sayı şu şekilde çalışır: vektör saati geleneksel dağıtılmış sistemlerde ve işlemlerde "daha önce oldu" sırasını belirler. Bu verileri kullanarak sözleşme olası herhangi bir anlaşmazlığı çözebilecektir.

Aslında bu fikri hayata geçirmek için tek bir sayı yeterli ancak bu şekilde daha kullanışlı bir kullanıcı arayüzü yapabileceğimiz için ikisini de bıraktık. Ayrıca her mesaja ödeme tutarını da eklemeye karar verdik. Bu olmadan, herhangi bir nedenden dolayı mesaj kaybolursa, tüm tutarlar ve son hesaplama doğru olsa da kullanıcı kaybı fark etmeyebilir.

Fikrimizi test etmek için bu kadar basit ve özlü bir ödeme kanalı protokolünün kullanımına ilişkin örnekler aradık. Şaşırtıcı bir şekilde yalnızca ikisini bulduk:

  1. Açıklama benzer bir yaklaşım yalnızca tek yönlü bir kanal durumunda.
  2. öğretici, bizimkiyle aynı fikri anlatıyor ancak genel doğruluk ve uyuşmazlık çözüm prosedürleri gibi pek çok önemli ayrıntıyı açıklamıyor.

Doğruluğuna özellikle dikkat ederek protokolümüzü ayrıntılı olarak açıklamanın mantıklı olduğu ortaya çıktı. Birkaç yinelemeden sonra spesifikasyon hazırdı ve artık siz de yapabilirsiniz. Ona bakmak.

Sözleşmeyi FunC'de uyguladık ve organizatörlerin önerdiği şekilde sözleşmemizle etkileşime geçmek için komut satırı yardımcı programını tamamen Fift'te yazdık. CLI'miz için başka bir dil seçebilirdik ama pratikte nasıl performans gösterdiğini görmek için Fit'i denemek istiyorduk.

Dürüst olmak gerekirse, Fift ile çalıştıktan sonra, geliştirilmiş araç ve kütüphanelere sahip, popüler ve aktif olarak kullanılan diller yerine bu dili tercih etmek için herhangi bir zorlayıcı neden göremedik. Yığın tabanlı bir dilde programlama yapmak oldukça tatsızdır, çünkü yığında olanı sürekli kafanızda tutmanız gerekir ve derleyici bu konuda yardımcı olmaz.

Bu nedenle, bize göre Fift'in varlığının tek gerekçesi, Fift Assembler'ın ana dili olarak oynadığı roldür. Ancak bu tek amaç için yeni bir dil icat etmek yerine TVM birleştiricisini mevcut bir dilin içine yerleştirmek daha iyi olmaz mıydı?

TVM Haskell eDSL

Şimdi ikinci akıllı sözleşmemiz hakkında konuşma zamanı. Çoklu imzalı bir cüzdan geliştirmeye karar verdik ancak FunC'de başka bir akıllı sözleşme yazmak çok sıkıcı olurdu. Biraz lezzet katmak istedik ve bu, TVM için kendi montaj dilimizdi.

Fift Assembler gibi, yeni dilimiz de yerleşiktir, ancak ana bilgisayar olarak Fift yerine Haskell'i seçtik, bu da gelişmiş tür sisteminin tüm avantajlarından yararlanmamıza olanak sağladı. Küçük bir hatanın bile maliyetinin çok yüksek olabileceği akıllı sözleşmelerle çalışırken statik yazmanın bizce büyük bir avantaj olduğunu düşünüyoruz.

TVM derleyicisinin Haskell'de yerleşik olarak nasıl göründüğünü göstermek için üzerine standart bir cüzdan uyguladık. Burada dikkat etmeniz gereken birkaç nokta var:

  • Bu sözleşme tek bir işlevden oluşur, ancak istediğiniz kadarını kullanabilirsiniz. Ana dilde (ör. Haskell) yeni bir işlev tanımladığınızda, eDSL'imiz bunun TVM'de ayrı bir rutin mi olmasını yoksa çağrı noktasında basit bir şekilde satır içi mi olmasını istediğinizi seçmenize olanak tanır.
  • Haskell gibi işlevlerin de derleme zamanında kontrol edilen türleri vardır. eDSL'imizde bir fonksiyonun giriş tipi, fonksiyonun beklediği stack tipi, sonuç tipi ise çağrı sonrasında üretilecek stack tipidir.
  • Kodun ek açıklamaları var stacktypeçağrı noktasında beklenen yığın türünü açıklayan. Orijinal cüzdan sözleşmesinde bunlar yalnızca yorumlardı, ancak eDSL'imizde bunlar aslında kodun bir parçasıdır ve derleme zamanında kontrol edilir. Kod değiştiğinde ve yığın türü değiştiğinde geliştiricinin sorunu bulmasına yardımcı olan belgeler veya ifadeler olarak hizmet edebilirler. Elbette bu tür ek açıklamalar, onlar için herhangi bir TVM kodu oluşturulmadığından çalışma zamanı performansını etkilemez.
  • Bu hâlâ iki haftada yazılan bir prototip, dolayısıyla proje üzerinde hâlâ yapılacak çok iş var. Örneğin aşağıdaki kodda gördüğünüz sınıfların tüm örneklerinin otomatik olarak oluşturulması gerekmektedir.

Çoklu imzalı cüzdanın eDSL'de uygulanması şöyle görünür:

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

eDSL ve çoklu imzalı cüzdan sözleşmemizin tam kaynak kodunu şu adreste bulabilirsiniz: bu depo. Ve dahası ayrıntılı olarak anlattım Yerleşik diller hakkında meslektaşımız Georgy Agapov.

Rekabet ve TON ile ilgili sonuçlar

Toplamda çalışmamız 380 saat sürdü (belgelere aşinalık, toplantılar ve fiili geliştirme dahil). Yarışma projesinde beş geliştirici yer aldı: CTO, ekip lideri, blockchain platform uzmanları ve Haskell yazılım geliştiricileri.

Bir hackathonun ruhu, yakın ekip çalışması ve kendimizi hızla yeni teknolojilerin çeşitli yönlerine kaptırma ihtiyacı her zaman heyecan verici olduğundan, zorluk yaşamadan yarışmaya katılmak için kaynak bulduk. Sınırlı kaynak koşullarında maksimum sonuçlara ulaşmak için geçirilen birkaç uykusuz gece, paha biçilmez deneyim ve mükemmel anılarla telafi edilir. Ek olarak, bu tür görevler üzerinde çalışmak her zaman şirketin süreçleri için iyi bir testtir, çünkü iyi işleyen bir iç etkileşim olmadan gerçekten iyi sonuçlar elde etmek son derece zordur.

Şarkı sözleri bir yana: TON ekibinin ortaya koyduğu çalışma miktarından etkilendik. Karmaşık, güzel ve en önemlisi çalışan bir sistem kurmayı başardılar. TON büyük potansiyele sahip bir platform olduğunu kanıtladı. Ancak bu ekosistemin gelişebilmesi için hem blockchain projelerinde kullanılması hem de geliştirme araçlarının iyileştirilmesi açısından daha pek çok şeyin yapılması gerekiyor. Artık bu sürecin bir parçası olmaktan gurur duyuyoruz.

Bu makaleyi okuduktan sonra hala sorularınız varsa veya sorunlarınızı çözmek için TON'u nasıl kullanacağınıza dair fikirleriniz varsa, bize yazın — Deneyimlerimizi paylaşmaktan mutluluk duyacağız.

Kaynak: habr.com

Yorum ekle