I have a small (formerly) ruby blockchain script I'm trying to convert over into Crystal, that looks like this so far:
# build your own blockchain from scratch in crystal!
#
# to run use:
# $ crystal ./blockchain_with_proof_of_work.cr
require "openssl" # for hash checksum digest function SHA256
class Block
getter index : Int32
getter timestamp : Time
getter data : String
getter previous_hash : String
getter nonce : Int32 # # proof of work if hash starts with leading zeros (00)
getter hash : String
def initialize(index, data, previous_hash)
@index = index
@timestamp = Time.now
@data = data
@previous_hash = previous_hash
@nonce, @hash = compute_hash_with_proof_of_work
end
def compute_hash_with_proof_of_work(difficulty = "00")
nonce = 0
loop do
hash = calc_hash_with_nonce(nonce)
if hash.starts_with?(difficulty)
return [nonce, hash] # # bingo! proof of work if hash starts with leading zeros (00)
else
nonce += 1 # # keep trying (and trying and trying)
end
end
end
def calc_hash_with_nonce(nonce = 0)
sha = OpenSSL::Digest.new("SHA256")
sha.update(nonce.to_s + @index.to_s + @timestamp.to_s + @data + @previous_hash)
sha.hexdigest
end
def self.first(data = "Genesis") # create genesis (big bang! first) block
# # uses index zero (0) and arbitrary previous_hash ("0")
Block.new(0, data, "0")
end
def self.next(previous, data = "Transaction Data...")
Block.new(previous.index + 1, data, previous.hash)
end
end # class Block
#####
# # let's get started
# # build a blockchain a block at a time
b0 = Block.first("Genesis")
b1 = Block.next(b0, "Transaction Data...")
b2 = Block.next(b1, "Transaction Data......")
b3 = Block.next(b2, "More Transaction Data...")
blockchain = [b0, b1, b2, b3]
puts blockchain
######
# will print something like:
#
# [#<Block:0x1e204f0
# @data="Genesis",
# @hash="00b8e77e27378f9aa0afbcea3a2882bb62f6663771dee053364beb1887e18bcf",
# @index=0,
# @nonce=242,
# @previous_hash="0",
# @timestamp=2017-09-20 20:13:38 +0200>,
# #<Block:0x1e56e20
# @data="Transaction Data...",
# @hash="00aae8d2e9387e13c71b33f8cd205d336ac250d2828011f5970062912985a9af",
# @index=1,
# @nonce=46,
# @previous_hash=
# "00b8e77e27378f9aa0afbcea3a2882bb62f6663771dee053364beb1887e18bcf",
# @timestamp=2017-09-20 20:13:38 +0200>,
# #<Block:0x1e2bd58
# @data="Transaction Data......",
# @hash="00ea45e0f4683c3bec4364f349ee2b6816be0c9fd95cfd5ffcc6ed572c62f190",
# @index=2,
# @nonce=350,
# @previous_hash=
# "00aae8d2e9387e13c71b33f8cd205d336ac250d2828011f5970062912985a9af",
# @timestamp=2017-09-20 20:13:38 +0200>,
# #<Block:0x1fa8338
# @data="More Transaction Data...",
# @hash="00436f0fca677652963e904ce4c624606a255946b921132d5b1f70f7d86c4ab8",
# @index=3,
# @nonce=59,
# @previous_hash=
# "00ea45e0f4683c3bec4364f349ee2b6816be0c9fd95cfd5
However when I run it I get an error that states:
Error in blockchain.cr/blockchain_with_proof_of_work.cr:57: instantiating
'Block:Class#first(String)'
b0 = Block.first("Genesis")
^~~~~
in blockchain.cr/blockchain_with_proof_of_work.cr:45: instantiating
'Block:Class#new(Int32, String, String)'
Block.new(0, data, "0")
^~~
in blockchain.cr/blockchain_with_proof_of_work.cr:22: instance variable
'@nonce' of Block must be Int32, not (Int32 | String)
@nonce, @hash = compute_hash_with_proof_of_work
^~~~~~
Looking at Crystal docs on multiple assignment, I'm unsure of how I can refactor this method so that it doesn't fail Crystal's automatic static type checking and type inference? The method in question, of an array of two types being returned, doesn't seem covered by the docs:
@nonce, @hash = compute_hash_with_proof_of_work # return [nonce, hash]