func (b *Balloon) AddBulk(eventBulkDigest []hashing.Digest) ([]*Snapshot, []*storage.Mutation, error) {
	b.Lock()
	defer b.Unlock()
	initialVersion := b.version
	b.version += uint64(len(eventBulkDigest))
	var historyDigests []hashing.Digest
	var historyMutations []*storage.Mutation
	var historyErr error
	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
		historyDigests, historyMutations, historyErr = b.historyTree.AddBulk(eventBulkDigest, initialVersion)
		wg.Done()
	}()
	hyperDigest, mutations, hyperErr := b.hyperTree.AddBulk(eventBulkDigest, initialVersion)
	wg.Wait()
	if historyErr != nil {
		return nil, nil, historyErr
	}
	if hyperErr != nil {
		return nil, nil, hyperErr
	}
	mutations = append(mutations, historyMutations...)
	snapshotBulk := make([]*Snapshot, 0)
	for i, _ := range eventBulkDigest {
		snapshotBulk = append(snapshotBulk, &Snapshot{
			EventDigest:   eventBulkDigest[i],
			HistoryDigest: historyDigests[i],
			HyperDigest:   hyperDigest,
			Version:       initialVersion + uint64(i),
		})
	}
	return snapshotBulk, mutations, nil
}
AddBulk funcion inserts a bulk of event hashes into the history and hyper trees, creates an
array of snapshots with these insertions results, and returns the array of snapshots along
with certain mutations to do to the persistent storage.
package balloon
import (
	"errors"
	"fmt"
	"sync"
	"github.com/bbva/qed/balloon/history"
	"github.com/bbva/qed/balloon/hyper"
	"github.com/bbva/qed/crypto/hashing"
	"github.com/bbva/qed/log"
	"github.com/bbva/qed/storage"
	"github.com/bbva/qed/util"
)
var (
	BalloonVersionKey = []byte("version")
)
type Balloon struct {
	version uint64
	hasherF func() hashing.Hasher
	store   storage.Store
	historyTree *history.HistoryTree
	hyperTree   *hyper.HyperTree
	sync.RWMutex
	log log.Logger
}
func NewBalloon(store storage.Store, hasherF func() hashing.Hasher) (*Balloon, error) {
	return NewBalloonWithLogger(store, hasherF, log.L())
}
func NewBalloonWithLogger(store storage.Store, hasherF func() hashing.Hasher, logger log.Logger) (*Balloon, error) {
	historyTree := history.NewHistoryTreeWithLogger(hasherF, store, 300, logger.Named("history"))
	batchCache := hyper.NewBatchCache(hyper.DefaultBatchLevels)
	hyperTree := hyper.NewHyperTreeWithLogger(hasherF, store, batchCache, logger.Named("hyper"))
	balloon := &Balloon{
		version:     0,
		hasherF:     hasherF,
		store:       store,
		historyTree: historyTree,
		hyperTree:   hyperTree,
		log:         logger,
	}
	err := balloon.RefreshVersion()
	if err != nil {
		return nil, err
	}
	return balloon, nil
}
type Snapshot struct {
	EventDigest   hashing.Digest
	HistoryDigest hashing.Digest
	HyperDigest   hashing.Digest
	Version       uint64
}
type Verifiable interface {
	Verify(key []byte, expectedDigest hashing.Digest) bool
}
type MembershipProof struct {
	Exists         bool
	HyperProof     *hyper.QueryProof
	HistoryProof   *history.MembershipProof
	CurrentVersion uint64
	QueryVersion   uint64
	ActualVersion  uint64
	KeyDigest      hashing.Digest
	Hasher         hashing.Hasher
}
func NewMembershipProof(exists bool, hyperProof *hyper.QueryProof, historyProof *history.MembershipProof, currentVersion, queryVersion, actualVersion uint64, keyDigest hashing.Digest, Hasher hashing.Hasher) *MembershipProof {
	return &MembershipProof{
		exists,
		hyperProof,
		historyProof,
		currentVersion,
		queryVersion,
		actualVersion,
		keyDigest,
		Hasher,
	}
}
func (p MembershipProof) DigestVerify(digest hashing.Digest, snapshot *Snapshot) bool {
	if p.HyperProof == nil || p.HistoryProof == nil {
		return false
	}
	hyperCorrect := p.HyperProof.Verify(digest, snapshot.HyperDigest)
	if p.Exists {
		if p.ActualVersion <= p.QueryVersion {
			historyCorrect := p.HistoryProof.Verify(digest, snapshot.HistoryDigest)
			return hyperCorrect && historyCorrect
		}
	}
	return hyperCorrect
}
func (p MembershipProof) Verify(event []byte, snapshot *Snapshot) bool {
	return p.DigestVerify(p.Hasher.Do(event), snapshot)
}
type IncrementalProof struct {
	Start, End uint64
	AuditPath  history.AuditPath
	Hasher     hashing.Hasher
}
func NewIncrementalProof(start, end uint64, auditPath history.AuditPath, hasher hashing.Hasher) *IncrementalProof {
	return &IncrementalProof{
		start,
		end,
		auditPath,
		hasher,
	}
}
func (p IncrementalProof) Verify(snapshotStart, snapshotEnd *Snapshot) bool {
	ip := history.NewIncrementalProof(p.Start, p.End, p.AuditPath, p.Hasher)
	return ip.Verify(snapshotStart.HistoryDigest, snapshotEnd.HistoryDigest)
}
func (b *Balloon) Version() uint64 {
	return b.version
}
func (b *Balloon) RefreshVersion() error {
	kv, err := b.store.GetLast(storage.HistoryTable)
	if err != nil {
		if err != storage.ErrKeyNotFound {
			return err
		}
	} else {
		b.version = util.BytesAsUint64(kv.Key[:8]) + 1
	}
	return nil
}
func (b *Balloon) Add(eventDigest hashing.Digest) (*Snapshot, []*storage.Mutation, error) {
	b.Lock()
	defer b.Unlock()
	version := b.version
	b.version++
	var historyDigest hashing.Digest
	var historyMutations []*storage.Mutation
	var historyErr error
	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
		historyDigest, historyMutations, historyErr = b.historyTree.Add(eventDigest, version)
		wg.Done()
	}()
	hyperDigest, mutations, hyperErr := b.hyperTree.Add(eventDigest, version)
	wg.Wait()
	if historyErr != nil {
		return nil, nil, historyErr
	}
	if hyperErr != nil {
		return nil, nil, hyperErr
	}
	mutations = append(mutations, historyMutations...)
	snapshot := &Snapshot{
		EventDigest:   eventDigest,
		HistoryDigest: historyDigest,
		HyperDigest:   hyperDigest,
		Version:       version,
	}
	return snapshot, mutations, nil
}