From 47df65528a7f199d3a35ac913341a9856ab103c5 Mon Sep 17 00:00:00 2001 From: Pascal Precht Date: Mon, 2 Dec 2019 12:12:44 +0100 Subject: [PATCH] article: add part 3 of crystal vs nim article series --- ...8-nim-vs-crystal-part-3-cryto-dapps-p2p.md | 327 ++++++++++++++++++ 1 file changed, 327 insertions(+) create mode 100644 site/source/_posts/2019-11-28-nim-vs-crystal-part-3-cryto-dapps-p2p.md diff --git a/site/source/_posts/2019-11-28-nim-vs-crystal-part-3-cryto-dapps-p2p.md b/site/source/_posts/2019-11-28-nim-vs-crystal-part-3-cryto-dapps-p2p.md new file mode 100644 index 000000000..2a7a7dcb7 --- /dev/null +++ b/site/source/_posts/2019-11-28-nim-vs-crystal-part-3-cryto-dapps-p2p.md @@ -0,0 +1,327 @@ +title: Nim vs Crystal - Part 3 - Crypto, DApps & P2P +summary: "Crystal and Nim go head-to-head to figure out the best modern, low-level programming language! In part 3; Crypto, P2P & DApps are explored." +author: robin_percy +categories: + - tutorials +layout: blog-post +image: '/assets/images/nim-crystal-header_blank.jpg' +--- + +![crystal vs nim](/assets/images/nim-crystal-header-img_NEW.jpg) + +Welcome back to my series comparing the two sweethearts of the modern low-level programming world. Just to quickly recap: in [article #1](/news/2019/11/18/nim-vs-crystal-part-1-performance-interoperability/) I noted my thoughts on the interoperability capabilities of the two languages, alongside briefly reviewing the performance metrics for each (albeit with relatively simple tests). Whether simple or not, the tests ***did*** throw up some unexpected twists in the plot. Crystal used *very-nearly* half of the memory amount executing the tests when compared to Nim, and also took *very nearly* half of the execution time in doing so. **This seriously took me by surprise!** + +In [article #2](/news/2019/11/21/nim-vs-crystal-part-2-threading-tooling/); I looked at the Concurrency primitives of each language, and explored both the in-built tooling, and external package ecosystems surrounding each language. As I said in that article, one of the biggest factors I look at when considering adopting a new language; is its tooling ecosystem. This includes, but is not limited to: A comprehensive package manager, an intuitive testing suite, a good project scaffolder, and an in-built formatter/linter to ensure my code stays semantically correct – especially if I know I will be working in Open Source repos that others will contribute to. But they're just the high-level tools that I look for... + +From a low-level standpoint; I look for efficient use of technology in features such as in-memory storage, caching, garbage collection, and concurrency primitives that not just *markedly* improve our application performance, but that are also relatively simple, and intuitive to use. I see *this* as particularly important as I have, in my past, seen some truly shocking examples of trying to handle multi-threading, from languages that I love \*cough* ***Ruby*** \*cough*. I also like to see a fully-featured standard library that takes influence from previous successful languages. However, I digress... + +I regret to say that this is the final article in this series! It's been good fun for me; getting to the know the ins-and-outs of Nim, and to re-grow a fresh appreciation of Crystal, having put it on the back-burner for quite some time. However, whether the final article in the series or not, it's going to be a good one! We're going to be covering the benefits to the Cryptocurrency / DApp industries from both Crystal and Nim. So without further ado: + +***Let's dive on in!*** + +## Cryptocurrency + +Firstly, I'd like to talk about the possibility of using either Crystal or Nim, (or both!) in the development of crypto apps. Hypothetically; if we had the inclination to build out our own Cryptocurrency: Crystal and Nim have ***proven to be two of the strongest languages*** to consider for the undertaking.. (That being the *next* blog series I'm going to write – in the near future, so deciding which language to use will be heavily influenced by ***this*** blog series!) + +For our Cryptocurrency, we would need to be able to use an intelligent key manager, utilise smart hashing algorithms, maintain strong performance, and all of this atop of a distributed, decentralised virtual machine or blockchain. Now, all of this sounds like a ***very*** tall order! For all of these feature requirements to be met by a single programming language, it would mean that this language is going to have to be **ONE HELL** of an impressive piece of technology. + +Happily, both Crystal *and* Nim allow us ***all*** of the above functionality. In our hypothetical usecase, if we were to build out a fully-featured blockchain; mining *and* hashing functions would need to be continually made, both of which entail relatively heavy computations. As shown over the last 2 articles in the series, we can at least be sure that both langs can handle the performance stresses, no problemo. + +As I'd like to write this topic out into a further detailed article series, I will show off just 2 of the above pieces of functionality we'd require for our Crypto app: + + +### Calculating our Block Hashes + +When building our Blockchain; we need to consider how we're going to identify and chain our transaction blocks together (blockchain). Without going into details in *this* article on how blockchains function, we'll stick with the existing, and proven, SHA256 algorithm. + + +### In Crystal: + +``` crystal +require "json" +require "openssl" + +module OurCryptoApp::Model + struct Transaction + include JSON::Serializable + + alias TxnHash = String + + property from : String + property to : String + property amount : Float32 + getter hash : TxnHash + getter timestamp : Int64 + + def initialize(@from, @to, @amount) + @timestamp = Time.utc_now.to_unix + @hash = calc_hash + end + + private def calc_hash : TxnHash + sha = OpenSSL::Digest.new("SHA256") + sha.update("#{@from}#{@to}#{@amount}#{@timestamp}") + sha.hexdigest + end + end +end +``` + + +### In Nim: + +If we want to generate a similar hash in Nim, we could run the following: + +``` nim +import strutils + +const SHA256Len = 32 + +proc SHA256(d: cstring, n: culong, md: cstring = nil): cstring {.cdecl, dynlib: "libssl.so", importc.} + +proc SHA256(s: string): string = + result = "" + let s = SHA256(s.cstring, s.len.culong) + for i in 0 .. < SHA256Len: + result.add s[i].BiggestInt.toHex(2).toLower + +echo SHA256("Hash this block, yo") +``` + + +## Releasing our Crypto App + +Another serious factor we have to consider, is the ability to distribute our crypto app, once built, with great ease. Remembering that both Crystal and Nim are *compiled* languages, we're already off to a promising start. (A single executable binary is always going to be easier to distribute than something requiring its own specialist environment!) + +It pays rather large dividends being able to write our Crypto app just once, and having the ability to maintain one singular code repo for that app. To this end – I think it is definitely worth considering a multi-platform app framework. I already know that in my next article series, I will be exploring building a Crypto app using [React Native](https://facebook.github.io/react-native/). + +However, if you wish to build the frontend of your cross-platform crypto app in something else, there are a variety of technologies available - all of which seem to work well with both Crystal and Nim: + + - [Ionic Framework](http://ionicframework.com/) + - [Flutter](https://flutter.io/) + - [NativeScript](https://www.nativescript.org/) + +And if you come from a Windows background: + + - [Xamarin](https://dotnet.microsoft.com/apps/xamarin) + + +### Building & Releasing In Nim: + +If we wanted to build out and release our app for Android, we can run: + +``` +nim c -c --cpu:arm --os:android -d:androidNDK --noMain:on +``` + +To generate the C source files we need to include in our Android Studio project. We then simply add the generated C files to our CMake build script in our Android project. + +Similarly, we could run: + +``` +nim c -c --os:ios --noMain:on +``` + +To generate C files to include in our XCode project. Then, we can use XCode to compile, link, package and sign everything. + + +### Building & Releasing In Crystal: + +Crystal also allows for cross-compilation, and makes it just as easy. For example, to build our app for Linux distributions from our Mac, we can run: + +``` +crystal build your_program.cr --cross-compile --target "x86_64-unknown-linux-gnu" +``` + +***Worth noting:*** *Crystal doesn't offer the out-of-the-box iPhone / Android cross-compilation functionality that Nim does, so building our app in Nim gets a definite thumbs-up from a distribution point-of-view!* + +## Ethereum - Building, Signing & Sending a Transaction + +For the sake of this article, in Crystal, I didn't see the need to write out a more low-level example of the below action, as it *is* so similar to the Nim demo that follows. This actually worked out in my favour, as it means I get to further show off the native HTTP library for Crystal. + +### In Crystal: + +``` crystal +require "http/client" + +module Ethereum + class Transaction + + # /ethereum/create/ Create - Ethereum::Transaction.create(args) + def self.create(to : String, from : String, amount : UInt64, gas_price : UInt64? = nil, gas_limit : UInt64? = nil) : EthereumToSign | ErrorMessage + + headers = HTTP::Headers.new + if ENV["ONCHAIN_API_KEY"]? != nil + headers.add("X-API-KEY", ENV["ONCHAIN_API_KEY"]) + end + + response = HTTP::Client.post "https://onchain.io/api/ethereum/create//?to=#{to}&from=#{from}&amount=#{amount}&gas_price=#{gas_price}&gas_limit=#{gas_limit}", headers: headers + + return ErrorMessage.from_json response.body if response.status_code != 200 + + ethereumtosign = EthereumToSign.from_json response.body + + + return ethereumtosign + end + + # /ethereum/sign_and_send/ Sign and send - Ethereum::Transaction.sign_and_send(args) + def self.sign_and_send(to : String, from : String, amount : UInt64, r : String, s : String, v : String, gas_price : UInt64? = nil, gas_limit : UInt64? = nil) : SendStatus | ErrorMessage + + headers = HTTP::Headers.new + if ENV["ONCHAIN_API_KEY"]? != nil + headers.add("X-API-KEY", ENV["ONCHAIN_API_KEY"]) + end + + response = HTTP::Client.post "https://onchain.io/api/ethereum/sign_and_send//?to=#{to}&from=#{from}&amount=#{amount}&r=#{r}&s=#{s}&v=#{v}&gas_price=#{gas_price}&gas_limit=#{gas_limit}", headers: headers + + return ErrorMessage.from_json response.body if response.status_code != 200 + + sendstatus = SendStatus.from_json response.body + + + return sendstatus + end + + end +end +``` + +Then, in our application we could simply call: + +``` crystal +Ethereum::Transaction.create("0xA02378cA1c24767eCD776aAFeC02158a30dc01ac", "0xA02378cA1c24767eCD776aAFeC02158a30dc01ac", 80000) +``` + +And we would get a response similar to the following, ready to be signed and sent to the Ethereum network: + +``` json +{ + "tx": "02000000011cd5d7621e2a7c9403e54e089cb0b5430b83ed13f1b897d3e319b100ba1b059b01000000db00483045022100d7534c80bc0a42addc3d955f74e31610aa78bf15d79ec4df4c36dc98e802f5200220369cab1bccb2dbca0921444ce3fafb15129fa0494d041998be104df39b8895ec01483045022100fe48c4c1d46e163acaff6b0d2e702812d20", + "hash_to_sign": "955f74e31610aa78bf15d79ec4df4c36dc98e802f52002" +} +``` + + +## In Nim: + +From a deeper, more low-level perspective; instead of using an HTTP library as in the Crystal example above, we can use Status' very own Nim-Ethereum library to build our Ethereum transaction. Assuming we have imported `nim-eth` into our Nimble project, our Ethereum transaction can be built atop of the following protocol: + +``` nim +import + nim-eth/[common, rlp, keys], nimcrypto + +proc initTransaction*(nonce: AccountNonce, gasPrice, gasLimit: GasInt, to: EthAddress, + value: UInt256, payload: Blob, V: byte, R, S: UInt256, isContractCreation = false): Transaction = + result.accountNonce = nonce + result.gasPrice = gasPrice + result.gasLimit = gasLimit + result.to = to + result.value = value + result.payload = payload + result.V = V + result.R = R + result.S = S + result.isContractCreation = isContractCreation + +type + TransHashObj = object + accountNonce: AccountNonce + gasPrice: GasInt + gasLimit: GasInt + to {.rlpCustomSerialization.}: EthAddress + value: UInt256 + payload: Blob + mIsContractCreation {.rlpIgnore.}: bool + +proc read(rlp: var Rlp, t: var TransHashObj, _: type EthAddress): EthAddress {.inline.} = + if rlp.blobLen != 0: + result = rlp.read(EthAddress) + else: + t.mIsContractCreation = true + +proc append(rlpWriter: var RlpWriter, t: TransHashObj, a: EthAddress) {.inline.} = + if t.mIsContractCreation: + rlpWriter.append("") + else: + rlpWriter.append(a) + +const + EIP155_CHAIN_ID_OFFSET* = 35 + +func rlpEncode*(transaction: Transaction): auto = + # Encode transaction without signature + return rlp.encode(TransHashObj( + accountNonce: transaction.accountNonce, + gasPrice: transaction.gasPrice, + gasLimit: transaction.gasLimit, + to: transaction.to, + value: transaction.value, + payload: transaction.payload, + mIsContractCreation: transaction.isContractCreation + )) + +func rlpEncodeEIP155*(tx: Transaction): auto = + let V = (tx.V.int - EIP155_CHAIN_ID_OFFSET) div 2 + # Encode transaction without signature + return rlp.encode(Transaction( + accountNonce: tx.accountNonce, + gasPrice: tx.gasPrice, + gasLimit: tx.gasLimit, + to: tx.to, + value: tx.value, + payload: tx.payload, + isContractCreation: tx.isContractCreation, + V: V.byte, + R: 0.u256, + S: 0.u256 + )) + +func txHashNoSignature*(tx: Transaction): Hash256 = + # Hash transaction without signature + return keccak256.digest(if tx.V.int >= EIP155_CHAIN_ID_OFFSET: tx.rlpEncodeEIP155 else: tx.rlpEncode) +``` + +*Note* - I do realise the above Nim code example and the Crystal examples are different - I fully intended them to be. The Crystal example allowed me to further show off the HTTP library I touched on in the last article, and the Nim example allowed me to go to a lower-level; something I think brings the article relevancy full circle. + + +[Status' Eth Common Library](https://github.com/status-im/nim-eth/) contains a whole bunch of useful Nim libraries for interacting with the Ethereum Network, including: + + - [Recursive Length Prefix encoding (RLP)](https://github.com/status-im/nim-eth/blob/master/doc/rlp.md), + - [P2P](https://github.com/status-im/nim-eth/blob/master/doc/p2p.md), + - [Eth-keys](https://github.com/status-im/nim-eth/blob/master/doc/keys.md), + - [Eth-keyfile](https://github.com/status-im/nim-eth/blob/master/doc/keyfile.md), + - [Ethereum Trie structure](https://github.com/status-im/nim-eth/blob/master/doc/trie.md), and + - [Ethereum Bloom Filter](https://github.com/status-im/nim-eth/blob/master/doc/bloom.md). + +If you are going to be working in the Ethereum ecosystem using Nim, it goes without saying that these utilities are absolutely essential. With Status & the [Nimbus](https://nimbus.team) team being such early adopters and major contributors to the Nim/Crypto universe, you are more than likely to stumble across this code sooner or later! + + +## Conclusion + +Our hypothetical Crypto app has taken shape throughout this article, and I think both languages have shown off great promise, and have proven their respective abilities to power the Cryptocurrency universe. + +Realistically, if you were a brand-new developer looking to learn a language to break into the Crypto scene, the choice would almost definitely be **Crystal**. This is simply because of the *much* larger ecosystem and resources surrounding it. + +However, if you were an already-established developer, looking to build out a crypto app that you could develop and multi-platform release with greater ease, you'd inevitably choose **Nim**. Crystal not only lacks the ability to be developed properly on Windows, but also lacks the interoperability and multi-release functionality, as we have seen, with Nim. + +Alas, this brings me on to my final points... + + +## Series Conclusion + +It's funny – each article in this series, I've started by saying to myself "Right, Nim is going to win." And then half way through; changing my story to "Crystal is my choice, actually." + +But then I went and spoiled it all, by saying something stupid like "Cryptocurrency". + +Prior to this article, I *was swaying* towards settling on Crystal. Not only did it impress in performance, but also seemed to have an enthusiastic ecosystem building around it. Nim, however, refused to go down without a fight – offering up *extremely* impressive interoperability, awesome inbuilt tooling, and great efficiency overall. + +I hate to do this, but I'm just going to have to say it: for your usecase – **pick the best tool for the job**. Please ensure that you research properly into both languages, and weigh-up the pro's/con's that pertain to your specific usecase. + +***Cliches aside*** – if I had to pick a favourite overall language, it would have to be **Crystal**. Frankly, this opinion is formed from my extensive use of Crystal over Nim, the fact I **much** prefer the Crystal syntax, and the fact that I am simply more comfortable coding in Crystal than I am in Nim! + +So, to answer the epic question – Crystal vs Nim? + +Personally, I choose Crystal. But I think **you** should choose ***Nim.*** 😅 + +[ **- @rbin**](https://twitter.com/rbin)