From 6ccf57daceb4387389c59be064809ca4c88318c6 Mon Sep 17 00:00:00 2001
From: Alex Sharov <AskAlexSharov@gmail.com>
Date: Fri, 2 Oct 2020 15:16:21 +0700
Subject: [PATCH] switch to go implementation of roaring bitmaps for alpine
 support (#1165)

---
 Dockerfile                             |  7 ++-
 cmd/rpcdaemon/commands/get_receipts.go | 20 ++++-----
 eth/stagedsync/stage_log_index.go      | 20 ++++-----
 eth/stagedsync/stage_log_index_test.go |  2 +-
 ethdb/bitmapdb/dbutils.go              | 62 +++++++++++++-------------
 ethdb/bitmapdb/dbutils_test.go         |  8 ++--
 go.mod                                 |  2 +-
 go.sum                                 | 20 ++++++++-
 8 files changed, 81 insertions(+), 60 deletions(-)

diff --git a/Dockerfile b/Dockerfile
index 83d2e167af..6ba81180b7 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,6 @@
-FROM golang:1.15 as builder
+FROM golang:1.15-alpine as builder
+
+RUN apk add --no-cache make gcc musl-dev linux-headers git
 
 WORKDIR /app
 
@@ -9,8 +11,9 @@ RUN go mod download
 ADD . .
 RUN make all
 
-FROM ubuntu:18.04
+FROM alpine:3
 
+RUN apk add --no-cache ca-certificates
 COPY --from=builder /app/build/bin/* /usr/local/bin/
 
 EXPOSE 8545 8546 8547 30303 30303/udp 8080 9090 6060
diff --git a/cmd/rpcdaemon/commands/get_receipts.go b/cmd/rpcdaemon/commands/get_receipts.go
index 99dbdbc0f5..1d0ad86ba1 100644
--- a/cmd/rpcdaemon/commands/get_receipts.go
+++ b/cmd/rpcdaemon/commands/get_receipts.go
@@ -3,7 +3,7 @@ package commands
 import (
 	"context"
 	"fmt"
-	"github.com/RoaringBitmap/gocroaring"
+	"github.com/RoaringBitmap/roaring"
 	"github.com/ledgerwatch/turbo-geth/common"
 	"github.com/ledgerwatch/turbo-geth/common/dbutils"
 	"github.com/ledgerwatch/turbo-geth/common/hexutil"
@@ -105,7 +105,7 @@ func (api *APIImpl) GetLogs(ctx context.Context, crit filters.FilterCriteria) ([
 		}
 	}
 
-	blockNumbers := gocroaring.New()
+	blockNumbers := roaring.New()
 	blockNumbers.AddRange(begin, end+1) // [min,max)
 
 	topicsBitmap, err := getTopicsBitmap(tx.(ethdb.HasTx).Tx().Cursor(dbutils.LogTopicIndex).Prefetch(1), crit.Topics, uint32(begin), uint32(end))
@@ -121,7 +121,7 @@ func (api *APIImpl) GetLogs(ctx context.Context, crit filters.FilterCriteria) ([
 	}
 
 	logAddrIndex := tx.(ethdb.HasTx).Tx().Cursor(dbutils.LogAddressIndex).Prefetch(1)
-	var addrBitmap *gocroaring.Bitmap
+	var addrBitmap *roaring.Bitmap
 	for _, addr := range crit.Addresses {
 		m, err := bitmapdb.Get(logAddrIndex, addr[:], uint32(begin), uint32(end))
 		if err != nil {
@@ -130,7 +130,7 @@ func (api *APIImpl) GetLogs(ctx context.Context, crit filters.FilterCriteria) ([
 		if addrBitmap == nil {
 			addrBitmap = m
 		} else {
-			addrBitmap = gocroaring.Or(addrBitmap, m)
+			addrBitmap = roaring.Or(addrBitmap, m)
 		}
 	}
 
@@ -142,7 +142,7 @@ func (api *APIImpl) GetLogs(ctx context.Context, crit filters.FilterCriteria) ([
 		}
 	}
 
-	if blockNumbers.Cardinality() == 0 {
+	if blockNumbers.GetCardinality() == 0 {
 		return returnLogs(logs), nil
 	}
 
@@ -177,10 +177,10 @@ func (api *APIImpl) GetLogs(ctx context.Context, crit filters.FilterCriteria) ([
 // {{}, {B}}          matches any topic in first position AND B in second position
 // {{A}, {B}}         matches topic A in first position AND B in second position
 // {{A, B}, {C, D}}   matches topic (A OR B) in first position AND (C OR D) in second position
-func getTopicsBitmap(c ethdb.Cursor, topics [][]common.Hash, from, to uint32) (*gocroaring.Bitmap, error) {
-	var result *gocroaring.Bitmap
+func getTopicsBitmap(c ethdb.Cursor, topics [][]common.Hash, from, to uint32) (*roaring.Bitmap, error) {
+	var result *roaring.Bitmap
 	for _, sub := range topics {
-		var bitmapForORing *gocroaring.Bitmap
+		var bitmapForORing *roaring.Bitmap
 		for _, topic := range sub {
 			m, err := bitmapdb.Get(c, topic[:], from, to)
 			if err != nil {
@@ -189,7 +189,7 @@ func getTopicsBitmap(c ethdb.Cursor, topics [][]common.Hash, from, to uint32) (*
 			if bitmapForORing == nil {
 				bitmapForORing = m
 			} else {
-				bitmapForORing = gocroaring.FastOr(bitmapForORing, m)
+				bitmapForORing = roaring.FastOr(bitmapForORing, m)
 			}
 		}
 
@@ -197,7 +197,7 @@ func getTopicsBitmap(c ethdb.Cursor, topics [][]common.Hash, from, to uint32) (*
 			if result == nil {
 				result = bitmapForORing
 			} else {
-				result = gocroaring.And(bitmapForORing, result)
+				result = roaring.And(bitmapForORing, result)
 			}
 		}
 	}
diff --git a/eth/stagedsync/stage_log_index.go b/eth/stagedsync/stage_log_index.go
index 01dae09bac..40c0a3e159 100644
--- a/eth/stagedsync/stage_log_index.go
+++ b/eth/stagedsync/stage_log_index.go
@@ -8,7 +8,7 @@ import (
 	"sort"
 	"time"
 
-	"github.com/RoaringBitmap/gocroaring"
+	"github.com/RoaringBitmap/roaring"
 	"github.com/c2h5oh/datasize"
 	"github.com/ledgerwatch/turbo-geth/common"
 	"github.com/ledgerwatch/turbo-geth/common/dbutils"
@@ -74,8 +74,8 @@ func promoteLogIndex(db ethdb.DbWithPendingMutations, start uint64, quit <-chan
 	defer logEvery.Stop()
 
 	tx := db.(ethdb.HasTx).Tx()
-	topics := map[string]*gocroaring.Bitmap{}
-	addresses := map[string]*gocroaring.Bitmap{}
+	topics := map[string]*roaring.Bitmap{}
+	addresses := map[string]*roaring.Bitmap{}
 	logTopicIndexCursor := tx.Cursor(dbutils.LogTopicIndex)
 	defer logTopicIndexCursor.Close()
 	logAddrIndexCursor := tx.Cursor(dbutils.LogAddressIndex)
@@ -114,14 +114,14 @@ func promoteLogIndex(db ethdb.DbWithPendingMutations, start uint64, quit <-chan
 				if err := flushBitmaps(logTopicIndexCursor, topics); err != nil {
 					return err
 				}
-				topics = map[string]*gocroaring.Bitmap{}
+				topics = map[string]*roaring.Bitmap{}
 			}
 
 			if needFlush(addresses, logIndicesMemLimit) {
 				if err := flushBitmaps(logAddrIndexCursor, addresses); err != nil {
 					return err
 				}
-				addresses = map[string]*gocroaring.Bitmap{}
+				addresses = map[string]*roaring.Bitmap{}
 			}
 		}
 
@@ -137,7 +137,7 @@ func promoteLogIndex(db ethdb.DbWithPendingMutations, start uint64, quit <-chan
 					topicStr := string(topic.Bytes())
 					m, ok := topics[topicStr]
 					if !ok {
-						m = gocroaring.New()
+						m = roaring.New()
 						topics[topicStr] = m
 					}
 					m.Add(uint32(blockNum))
@@ -146,7 +146,7 @@ func promoteLogIndex(db ethdb.DbWithPendingMutations, start uint64, quit <-chan
 				accStr := string(log.Address.Bytes())
 				m, ok := addresses[accStr]
 				if !ok {
-					m = gocroaring.New()
+					m = roaring.New()
 					addresses[accStr] = m
 				}
 				m.Add(uint32(blockNum))
@@ -235,16 +235,16 @@ func unwindLogIndex(db ethdb.DbWithPendingMutations, from, to uint64, quitCh <-c
 	return nil
 }
 
-func needFlush(bitmaps map[string]*gocroaring.Bitmap, memLimit datasize.ByteSize) bool {
+func needFlush(bitmaps map[string]*roaring.Bitmap, memLimit datasize.ByteSize) bool {
 	sz := uint64(0)
 	for _, m := range bitmaps {
-		sz += uint64(m.SerializedSizeInBytes())
+		sz += m.GetSerializedSizeInBytes()
 	}
 	const memoryNeedsForKey = 32 * 2 // each key stored in RAM: as string ang slice of bytes
 	return uint64(len(bitmaps)*memoryNeedsForKey)+sz > uint64(memLimit)
 }
 
-func flushBitmaps(c ethdb.Cursor, inMem map[string]*gocroaring.Bitmap) error {
+func flushBitmaps(c ethdb.Cursor, inMem map[string]*roaring.Bitmap) error {
 	keys := make([]string, 0, len(inMem))
 	for k := range inMem {
 		keys = append(keys, k)
diff --git a/eth/stagedsync/stage_log_index_test.go b/eth/stagedsync/stage_log_index_test.go
index 9a2f63c834..53f325d269 100644
--- a/eth/stagedsync/stage_log_index_test.go
+++ b/eth/stagedsync/stage_log_index_test.go
@@ -52,7 +52,7 @@ func TestLogIndex(t *testing.T) {
 	err = promoteLogIndex(tx, 0, nil)
 	require.NoError(err)
 
-	// Check indices cardinality (in how many blocks they meet)
+	// Check indices GetCardinality (in how many blocks they meet)
 	logTopicIndex := tx.(ethdb.HasTx).Tx().Cursor(dbutils.LogTopicIndex)
 	logAddrIndex := tx.(ethdb.HasTx).Tx().Cursor(dbutils.LogAddressIndex)
 
diff --git a/ethdb/bitmapdb/dbutils.go b/ethdb/bitmapdb/dbutils.go
index db9845e466..05f8cab616 100644
--- a/ethdb/bitmapdb/dbutils.go
+++ b/ethdb/bitmapdb/dbutils.go
@@ -3,7 +3,7 @@ package bitmapdb
 import (
 	"bytes"
 	"encoding/binary"
-	"github.com/RoaringBitmap/gocroaring"
+	"github.com/RoaringBitmap/roaring"
 	"github.com/c2h5oh/datasize"
 	"github.com/ledgerwatch/turbo-geth/common"
 	"github.com/ledgerwatch/turbo-geth/ethdb"
@@ -18,7 +18,7 @@ const ShardLimit = 3 * datasize.KB
 // if last existing shard size merge it with delta
 // if serialized size of delta > ShardLimit - break down to multiple shards
 // shard number - it's biggest value in bitmap
-func AppendMergeByOr(c ethdb.Cursor, key []byte, delta *gocroaring.Bitmap) error {
+func AppendMergeByOr(c ethdb.Cursor, key []byte, delta *roaring.Bitmap) error {
 	lastShardKey := make([]byte, len(key)+4)
 	copy(lastShardKey, key)
 	binary.BigEndian.PutUint32(lastShardKey[len(lastShardKey)-4:], ^uint32(0))
@@ -35,13 +35,13 @@ func AppendMergeByOr(c ethdb.Cursor, key []byte, delta *gocroaring.Bitmap) error
 		}
 		return nil
 	}
-
-	last, err := gocroaring.Read(currentLastV)
+	last := roaring.New()
+	_, err := last.FromBuffer(currentLastV)
 	if err != nil {
 		return err
 	}
 
-	delta = gocroaring.Or(delta, last)
+	delta = roaring.Or(delta, last)
 
 	err = writeBitmapSharded(c, key, delta)
 	if err != nil {
@@ -51,18 +51,18 @@ func AppendMergeByOr(c ethdb.Cursor, key []byte, delta *gocroaring.Bitmap) error
 }
 
 // writeBitmapSharded - write bitmap to db, perform sharding if delta > ShardLimit
-func writeBitmapSharded(c ethdb.Cursor, key []byte, delta *gocroaring.Bitmap) error {
+func writeBitmapSharded(c ethdb.Cursor, key []byte, delta *roaring.Bitmap) error {
 	shardKey := make([]byte, len(key)+4)
 	copy(shardKey, key)
-	sz := delta.SerializedSizeInBytes()
+	sz := int(delta.GetSerializedSizeInBytes())
 	if sz <= int(ShardLimit) {
-		newV := make([]byte, delta.SerializedSizeInBytes())
-		err := delta.Write(newV)
+		newV := bytes.NewBuffer(make([]byte, 0, delta.GetSerializedSizeInBytes()))
+		_, err := delta.WriteTo(newV)
 		if err != nil {
 			return err
 		}
 		binary.BigEndian.PutUint32(shardKey[len(shardKey)-4:], ^uint32(0))
-		err = c.Put(common.CopyBytes(shardKey), newV)
+		err = c.Put(common.CopyBytes(shardKey), newV.Bytes())
 		if err != nil {
 			return err
 		}
@@ -75,8 +75,8 @@ func writeBitmapSharded(c ethdb.Cursor, key []byte, delta *gocroaring.Bitmap) er
 	}
 	step := (delta.Maximum() - delta.Minimum()) / shardsAmount
 	step = step / 16
-	shard, tmp := gocroaring.New(), gocroaring.New() // shard will write to db, tmp will use to add data to shard
-	for delta.Cardinality() > 0 {
+	shard, tmp := roaring.New(), roaring.New() // shard will write to db, tmp will use to add data to shard
+	for delta.GetCardinality() > 0 {
 		from := uint64(delta.Minimum())
 		to := from + uint64(step)
 		tmp.Clear()
@@ -85,18 +85,18 @@ func writeBitmapSharded(c ethdb.Cursor, key []byte, delta *gocroaring.Bitmap) er
 		shard.Or(tmp)
 		shard.RunOptimize()
 		delta.RemoveRange(from, to)
-		if delta.Cardinality() == 0 {
+		if delta.GetCardinality() == 0 {
 			break
 		}
-		if shard.SerializedSizeInBytes() >= int(ShardLimit) {
-			newV := make([]byte, shard.SerializedSizeInBytes())
-			err := shard.Write(newV)
+		if shard.GetSerializedSizeInBytes() >= uint64(ShardLimit) {
+			newV := bytes.NewBuffer(make([]byte, 0, shard.GetSerializedSizeInBytes()))
+			_, err := shard.WriteTo(newV)
 			if err != nil {
 				return err
 			}
 			binary.BigEndian.PutUint32(shardKey[len(shardKey)-4:], shard.Maximum())
 
-			err = c.Put(common.CopyBytes(shardKey), newV)
+			err = c.Put(common.CopyBytes(shardKey), newV.Bytes())
 			if err != nil {
 				return err
 			}
@@ -104,14 +104,14 @@ func writeBitmapSharded(c ethdb.Cursor, key []byte, delta *gocroaring.Bitmap) er
 		}
 	}
 
-	if shard.SerializedSizeInBytes() > 0 {
-		newV := make([]byte, shard.SerializedSizeInBytes())
-		err := shard.Write(newV)
+	if shard.GetSerializedSizeInBytes() > 0 {
+		newV := bytes.NewBuffer(make([]byte, 0, shard.GetSerializedSizeInBytes()))
+		_, err := shard.WriteTo(newV)
 		if err != nil {
 			return err
 		}
 		binary.BigEndian.PutUint32(shardKey[len(shardKey)-4:], ^uint32(0))
-		err = c.Put(common.CopyBytes(shardKey), newV)
+		err = c.Put(common.CopyBytes(shardKey), newV.Bytes())
 		if err != nil {
 			return err
 		}
@@ -142,7 +142,8 @@ func TruncateRange(tx ethdb.Tx, bucket string, key []byte, from, to uint64) erro
 			break
 		}
 
-		bm, err := gocroaring.Read(v)
+		bm := roaring.New()
+		_, err := bm.FromBuffer(v)
 		if err != nil {
 			return err
 		}
@@ -161,12 +162,12 @@ func TruncateRange(tx ethdb.Tx, bucket string, key []byte, from, to uint64) erro
 		}
 
 		bm.RunOptimize()
-		newV := make([]byte, bm.SerializedSizeInBytes())
-		err = bm.Write(newV)
+		newV := bytes.NewBuffer(make([]byte, 0, bm.GetSerializedSizeInBytes()))
+		_, err = bm.WriteTo(newV)
 		if err != nil {
 			return err
 		}
-		err = c.Put(common.CopyBytes(k), newV)
+		err = c.Put(common.CopyBytes(k), newV.Bytes())
 		if err != nil {
 			return err
 		}
@@ -212,8 +213,8 @@ func TruncateRange(tx ethdb.Tx, bucket string, key []byte, from, to uint64) erro
 
 // Get - reading as much shards as needed to satisfy [from, to] condition
 // join all shards to 1 bitmap by Or operator
-func Get(c ethdb.Cursor, key []byte, from, to uint32) (*gocroaring.Bitmap, error) {
-	var shards []*gocroaring.Bitmap
+func Get(c ethdb.Cursor, key []byte, from, to uint32) (*roaring.Bitmap, error) {
+	var shards []*roaring.Bitmap
 
 	fromKey := make([]byte, len(key)+4)
 	copy(fromKey, key)
@@ -227,7 +228,8 @@ func Get(c ethdb.Cursor, key []byte, from, to uint32) (*gocroaring.Bitmap, error
 			break
 		}
 
-		bm, err := gocroaring.Read(v)
+		bm := roaring.New()
+		_, err := bm.FromBuffer(v)
 		if err != nil {
 			return nil, err
 		}
@@ -239,7 +241,7 @@ func Get(c ethdb.Cursor, key []byte, from, to uint32) (*gocroaring.Bitmap, error
 	}
 
 	if len(shards) == 0 {
-		return gocroaring.New(), nil
+		return roaring.New(), nil
 	}
-	return gocroaring.FastOr(shards...), nil
+	return roaring.FastOr(shards...), nil
 }
diff --git a/ethdb/bitmapdb/dbutils_test.go b/ethdb/bitmapdb/dbutils_test.go
index c49c6d4c1c..667afefbae 100644
--- a/ethdb/bitmapdb/dbutils_test.go
+++ b/ethdb/bitmapdb/dbutils_test.go
@@ -4,7 +4,7 @@ import (
 	"context"
 	"testing"
 
-	"github.com/RoaringBitmap/gocroaring"
+	"github.com/RoaringBitmap/roaring"
 	"github.com/ledgerwatch/turbo-geth/common/dbutils"
 	"github.com/ledgerwatch/turbo-geth/ethdb"
 	"github.com/ledgerwatch/turbo-geth/ethdb/bitmapdb"
@@ -26,7 +26,7 @@ func TestSharding(t *testing.T) {
 		k := []byte{1}
 		// Write/Read large bitmap works expected
 		for i := uint32(0); i < 3_000_000; i += 1_000_000 {
-			bm1 := gocroaring.New()
+			bm1 := roaring.New()
 			for j := i; j < i+1_000_000; j += 20 {
 				bm1.AddRange(uint64(j), uint64(j+10))
 			}
@@ -36,7 +36,7 @@ func TestSharding(t *testing.T) {
 
 		fromDb, err := bitmapdb.Get(c, k, 0, 10_000_000)
 		require.NoError(t, err)
-		expect := gocroaring.New()
+		expect := roaring.New()
 		for i := uint32(0); i < 3_000_000; i += 1_000_000 {
 			for j := i; j < i+1_000_000; j += 20 {
 				expect.AddRange(uint64(j), uint64(j+10))
@@ -53,7 +53,7 @@ func TestSharding(t *testing.T) {
 		fromDb, err = bitmapdb.Get(c, k, 0, 10_000_000)
 		require.NoError(t, err)
 
-		expect = gocroaring.New()
+		expect = roaring.New()
 		for i := uint32(0); i < 2_000_000; i += 1_000_000 {
 			for j := i; j < i+1_000_000; j += 20 {
 				expect.AddRange(uint64(j), uint64(j+10))
diff --git a/go.mod b/go.mod
index c2c3e2d748..5bc568a851 100644
--- a/go.mod
+++ b/go.mod
@@ -7,7 +7,7 @@ require (
 	github.com/Azure/azure-storage-blob-go v0.8.0
 	github.com/Azure/go-autorest/autorest/adal v0.8.3 // indirect
 	github.com/JekaMas/notify v0.9.4
-	github.com/RoaringBitmap/gocroaring v0.2.66-0.20200926170445-4232533f8158
+	github.com/RoaringBitmap/roaring v0.5.1
 	github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect
 	github.com/VictoriaMetrics/fastcache v1.5.7
 	github.com/aristanetworks/goarista v0.0.0-20170210015632-ea17b1a17847
diff --git a/go.sum b/go.sum
index 317ef7d930..b09f9e9d45 100644
--- a/go.sum
+++ b/go.sum
@@ -29,8 +29,8 @@ github.com/JekaMas/notify v0.9.4 h1:Ns+DRf9kho8T0yQNSKoZqAnbvO/Hg3KmJCUaoRhL7MM=
 github.com/JekaMas/notify v0.9.4/go.mod h1:KYZd45vBSOYP2/9lY38EjZtvKRZMfgWaJk8bvBxhIYk=
 github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
 github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
-github.com/RoaringBitmap/gocroaring v0.2.66-0.20200926170445-4232533f8158 h1:vUCwVHxgwPjUvknaRJrqW1aoOo/TSj3Wls04zV5uE3E=
-github.com/RoaringBitmap/gocroaring v0.2.66-0.20200926170445-4232533f8158/go.mod h1:UY3cRDn1176ZP/joHjSQFUbD4WiK4j65wzbmXregcUc=
+github.com/RoaringBitmap/roaring v0.5.1 h1:ugdwntNygzk1FZnmtxUr+jM9AYrpU3I3zpt49npDWVo=
+github.com/RoaringBitmap/roaring v0.5.1/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo=
 github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk=
 github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
 github.com/VictoriaMetrics/fastcache v1.5.7 h1:4y6y0G8PRzszQUYIQHHssv/jgPHAb5qQuuDNdCbyAgw=
@@ -116,6 +116,10 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE
 github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
 github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
 github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
+github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2 h1:Ujru1hufTHVb++eG6OuNDKMxZnGIvF6o/u8q/8h2+I4=
+github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2/go.mod h1:/20jfyN9Y5QPEAprSgKAUr+glWDY39ZiUEAYOEv5dsE=
+github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31 h1:gclg6gY70GLy3PbkQ1AERPfmLMMagS60DKF78eWwLn8=
+github.com/glycerine/goconvey v0.0.0-20190410193231-58a59202ab31/go.mod h1:Ogl1Tioa0aV7gstGFO7KhffUsb9M4ydbEbbxpcEDc24=
 github.com/go-gl/gl v0.0.0-20180407155706-68e253793080/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk=
 github.com/go-gl/glfw v0.0.0-20180426074136-46a8d530c326/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
@@ -177,6 +181,8 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
 github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
 github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99 h1:twflg0XRTjwKpxb/jFExr4HGq6on2dEOmnL6FV+fgPw=
+github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
 github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
 github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
@@ -213,6 +219,8 @@ github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGn
 github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
 github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
+github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
 github.com/julienschmidt/httprouter v1.2.0 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g=
 github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
 github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
@@ -266,6 +274,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ
 github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
 github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae h1:VeRdUYdCw49yizlSbMEn2SZ+gT+3IUKx8BqxyQdz+BY=
+github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg=
 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
 github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
@@ -285,6 +295,8 @@ github.com/petar/GoLLRB v0.0.0-20190514000832-33fb24c13b99 h1:KcEvVBAvyHkUdFAygK
 github.com/petar/GoLLRB v0.0.0-20190514000832-33fb24c13b99/go.mod h1:HUpKUBZnpzkdx0kD/+Yfuft+uD3zHGtXF/XJB14TUr4=
 github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 h1:oYW+YCJ1pachXTQmzR3rNLYGGz4g/UgFcjb28p/viDM=
 github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0=
+github.com/philhofer/fwd v1.0.0 h1:UbZqGr5Y38ApvM/V/jEljVxwocdweyH+vmYvRPBnbqQ=
+github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -350,6 +362,8 @@ github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJy
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/tinylib/msgp v1.1.0 h1:9fQd+ICuRIu/ue4vxJZu6/LzxN0HwMds2nq/0cFvxHU=
+github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
 github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
 github.com/tyler-smith/go-bip39 v1.0.2 h1:+t3w+KwLXO6154GNJY+qUtIxLTmFjfUmpguQT1OlOT8=
 github.com/tyler-smith/go-bip39 v1.0.2/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs=
@@ -372,6 +386,8 @@ github.com/valyala/gozstd v1.8.3 h1:nHlS+sCFoNLsZpRPKDviXkhHybaRSUjH2w0P/myYo0I=
 github.com/valyala/gozstd v1.8.3/go.mod h1:y5Ew47GLlP37EkTB+B4s7r6A5rdaeB7ftbl9zoYiIPQ=
 github.com/wcharczuk/go-chart v2.0.1+incompatible h1:0pz39ZAycJFF7ju/1mepnk26RLVLBCWz1STcD3doU0A=
 github.com/wcharczuk/go-chart v2.0.1+incompatible/go.mod h1:PF5tmL4EIx/7Wf+hEkpCqYi5He4u90sw+0+6FhrryuE=
+github.com/willf/bitset v1.1.10 h1:NotGKqX0KwQ72NUzqrjZq5ipPNDQex9lo3WpaS8L2sc=
+github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4=
 github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208 h1:1cngl9mPEoITZG8s8cVcUy5CeIBYhEESkOB7m6Gmkrk=
 github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208/go.mod h1:IotVbo4F+mw0EzQ08zFqg7pK3FebNXpaMsRy2RT+Ees=
 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
-- 
GitLab