Lalotai

A simple proof-of-work blockchain written in Go (Part 2)

This is a follow-up post to the first implementation written in Rust, which covers basic features as well as design decisions. If you haven't read it, I suggest giving it a glance first.

This time the same implementation has been re-written in Go and is available at go-blockchain. Once again, the code is merely a playground and not intended to be taken [too] seriously:

Implementation

It's common for cryptocurrencies to implement several versions [of nodes/wallets/etc] in different programming languages. Not only does it give more options and stability to developers, miners and users, but it also encourages these codebases to follow the whitepaper / protocol, as opposed to the flow being the other way around.

Rewriting the same small codebase in Go has turned out to be relatively straightforward. The repo still impelements all the same things: two modes of mining and broadcasting, an implementation of mempool and transactions, and a basic sync functionality across nodes -- except everything is now in Go.

A few notable differences:

  • You won't see any code that directly references "threads". Of course the program still uses concurrency by letting goroutines and channels do all the magic: ✨

    generateTxs := func(done <-chan interface{}) <-chan Tx {
    	stream := make(chan Tx)
    	go func() {
    		defer close(stream)
    		for {
    			select {
    			case stream <- func() Tx {
    				// do stuff
    				return Tx{}
    			}():
    			case <-done:
    				return
    			}
    		}
    	}()
    	return stream
    }
  • There are no typed enums in Go (though you can use a combination of const and iota), so this code relies on structs to achieve parity in constructing TCP messages

  • Struct tags are also used in some cases to match rust-blockchain's naming convention

Languages

Going back and forth between two implementations helps not only understand the strengths of both languages, but also improve awareness of what your own code does (I even found a bug in my initial Rust implementation).

Awesome things about Rust in no particular order:

  • The compiler is so helpful and detailed, it's hard not to notice and appreciate all the explanations (ownership is here, borrow is there 😍)

  • Compile time memory safety + no garbage collection 🤯

  • Once you're past the steep learning curve, everything falls into place and you're left wondering how you've ever programmed without Rust

Now about Go:

  • Goroutines and channel handling is awesome; not only are they powerful yet easy to use, but the patterns you can form with them are really impressive 💪

  • The ease with which you can pick up Go and ramp up deserves a mention too

This is by no means an exhaustive list of all the benefits, and I'm sure I'm forgetting a lot of goodness. Obviously these are meant as personal highlights, rather than a comparison (if anything, I tend to think of these two as complimentary).

By the way, communities behind both languages are amazing and seem to grow quickly.

If you're wondering which language you should learn (first?), hopefully this post sheds some light. In my opinion, they are both well worth the time.

Notes

  • A fun thing to do is to spin up both implementations side by side and watch them compete for mining new blocks. You can even help one of them cheat by decreasing the difficulty in Settings.toml and watch it mine blocks faster. Of course in the real world, other nodes would check for this kind of stuff and reject the block.

  • If you're lucky enough, you might see two miners produce two different blocks at the same time and share them with each other, thinking they've both mined it first (as I've mentioned in the first post, no support for uncle chains and proper checks).

  • There's a good chance that one implementation is a bit faster than the other. This shouldn't be a reflection on the underlying language as there were zero performance optimizations and this was not the main focus anyway. A lot of things could be written better in both repos. Speaking of which:

Contributing

As usual, pull requests in the form of bug fixes, idiomatic or general code improvements, typos and cleanup are always welcome. No promises on big feature additions as this repo is meant to stay lightweight and simple.

Stay up-to-date 📬

For those rare occasions that I write something new:

this website is open-source. something to improve? make a pull request