Immutability in Go

Post mortem from a DoS-ed blockchain


10 October 2016

dotGo, Paris



Péter Szilágyi

Ethereum Core Developer

Glimpse into a new world

Blockchains from a distance

Global peer-to-peer networks

5232 Bitcoin nodes (29th September, 2016) 7480 Ethereum nodes (29th September, 2016)

Blockchains from close up

Attempts at uncorruptible shared truths

Initial state Update requests Aggregation Block propagation Next state To infinity... and beyond!

Blockchains from the inside

Most toxic ecosystems in technology... (⊙_⊙)

Mt. Gox: Bitcoin's largest heist TheDAO: Ethereum's largest heist

So... where's the connection to dotGo?

Where blockchain meets Go

"Ethereum is a decentralized platform that runs smart contracts: applications that run exactly as programmed without any possibility of downtime, censorship, fraud or third party interference."

Where blockchain meets dotGo

19th September, 4 in the morning

Ethereum's current state is a 2.8M node trie

Sharing memory the Go way – Communication

Slight caveats with this model

(1) (2) (3)

Sharing through immutability

Back to basics – String vs. []byte

Slices can change, but strings are immutable¹

Userspace immutability

Custom structs can be made immutable

Data purity must be preserved

Userspace immutability – Gopher

type Gopher struct {
    name    string // Immutable type
    picture []byte // Mutable type
}
func NewGopher(name string, pic []byte) *Gopher {
    g := &Gopher{
        name:    name,                   // Shallow copy immutable
        picture: make([]byte, len(pic)), // Deep copy mutable
    }
    copy(g.picture, pic)
    return g
}
func (g *Gopher) Name() string    { return g.name } // Shallow copy immutable
func (g *Gopher) Picture() []byte {                 // Deep copy mutable
    pic := make([]byte, len(g.picture))
    copy(pic, g.picture)
    return pic
}

Object composition

Arbitrarily complex object hierarchy

Immutable objects need to remain light

Object composition – Gopher family tree

type Gopher struct {
    name    string
    picture []byte
    parents [2]*Gopher // Lightweight immutables
}
func NewGopher(name string, pic []byte, parents []*Gopher) *Gopher {
    // [...] Init basic fields as previously

    for i, parent := range parents { // Dereference any pointers
        cpy := *parent
        g.parents[i] = &cpy
    }
    return g
}
func (g *Gopher) Parent(idx int) *Gopher {
    cpy := *g.parents[idx] // Internal pointers never escape
    return &cpy
}

Structure mutation

Updates can be done (only) via recompositions

Mutations always result in new objects

Structure mutation – Gopher family tree (1)

func ExtendFamilyTree(gopher, ancestor *Gopher, ancestry []int) *Gopher {
    mutable := *gopher // Reconstruct all objects on the update path

    if len(ancestry) == 0 {
        // Mutate the object at the deepest level
        if mutable.parents[0] == nil {
            mutable.parents[0] = ancestor
        } else {
            mutable.parents[1] = ancestor
        }
    } else {
        // Unwind and recombine objects on the update path
        index := ancestry[0]
        parent := mutable.parents[ancestry[0]]

        mutable.parents[index] = ExtendFamilyTree(parent, ancestry[1:], ancestor)
    }
    return &mutable
}

Structure mutation – Gopher family tree (2)

Lightweight object trees

Epilogue

Data structure immutability is nice

Data structure immutability is hard

Thank you

Ethereum Core Developer

Gophers drawn by Renee French
Use the left and right arrow keys or click the left and right edges of the page to navigate between slides.
(Press 'H' or navigate to hide this message.)