negentropy: converting to a more usable api.

This commit is contained in:
fiatjaf 2024-07-20 00:52:58 -03:00
parent b8fe53e295
commit fd1fc8340f
6 changed files with 124 additions and 165 deletions

View File

@ -1,13 +0,0 @@
package negentropy
// Storage defines an interface for storage operations, similar to the abstract class in C++.
type Storage interface {
Insert(uint64, string) error
Seal() error
Size() int
GetItem(i uint64) (Item, error)
Iterate(begin, end int, cb func(item Item, i int) bool) error
FindLowerBound(begin, end int, value Bound) (int, error)
Fingerprint(begin, end int) (Fingerprint, error)
}

View File

@ -3,51 +3,64 @@ package negentropy
import ( import (
"bytes" "bytes"
"encoding/hex" "encoding/hex"
"errors"
"fmt" "fmt"
"math"
"os" "os"
"github.com/nbd-wtf/go-nostr"
) )
const ( const (
ProtocolVersion byte = 0x61 // Version 1 protocolVersion byte = 0x61 // version 1
MaxU64 uint64 = ^uint64(0) maxTimestamp = nostr.Timestamp(math.MaxInt64)
FrameSizeMinLimit uint64 = 4096
) )
type Negentropy struct { type Negentropy struct {
Storage storage Storage
frameSizeLimit uint64 frameSizeLimit uint64
idSize int // in bytes
IsInitiator bool IsInitiator bool
lastTimestampIn uint64 lastTimestampIn nostr.Timestamp
lastTimestampOut uint64 lastTimestampOut nostr.Timestamp
} }
func NewNegentropy(storage Storage, frameSizeLimit uint64) (*Negentropy, error) { func NewNegentropy(storage Storage, frameSizeLimit uint64, IDSize int) (*Negentropy, error) {
if frameSizeLimit != 0 && frameSizeLimit < 4096 { if frameSizeLimit != 0 && frameSizeLimit < 4096 {
return nil, errors.New("frameSizeLimit too small") return nil, fmt.Errorf("frameSizeLimit too small")
}
if IDSize > 32 {
return nil, fmt.Errorf("id size cannot be more than 32, got %d", IDSize)
} }
return &Negentropy{ return &Negentropy{
Storage: storage, storage: storage,
frameSizeLimit: frameSizeLimit, frameSizeLimit: frameSizeLimit,
idSize: IDSize,
}, nil }, nil
} }
func (n *Negentropy) Insert(evt *nostr.Event) {
err := n.storage.Insert(evt.CreatedAt, evt.ID[0:n.idSize*2])
if err != nil {
panic(err)
}
}
func (n *Negentropy) Initiate() ([]byte, error) { func (n *Negentropy) Initiate() ([]byte, error) {
if n.IsInitiator { if n.IsInitiator {
return []byte{}, errors.New("already initiated") return []byte{}, fmt.Errorf("already initiated")
} }
n.IsInitiator = true n.IsInitiator = true
output := make([]byte, 1, 1+n.Storage.Size()*IDSize) output := make([]byte, 1, 1+n.storage.Size()*n.idSize)
output[0] = ProtocolVersion output[0] = protocolVersion
n.SplitRange(0, n.Storage.Size(), Bound{Item: Item{Timestamp: MaxU64}}, &output) n.SplitRange(0, n.storage.Size(), Bound{Item: Item{Timestamp: maxTimestamp}}, &output)
return output, nil return output, nil
} }
func (n *Negentropy) Reconcile(query []byte) ([]byte, error) { func (n *Negentropy) Reconcile(query []byte) ([]byte, error) {
if n.IsInitiator { if n.IsInitiator {
return []byte{}, errors.New("initiator not asking for have/need IDs") return []byte{}, fmt.Errorf("initiator not asking for have/need IDs")
} }
var haveIds, needIds []string var haveIds, needIds []string
@ -66,7 +79,7 @@ func (n *Negentropy) Reconcile(query []byte) ([]byte, error) {
// ReconcileWithIDs when IDs are expected to be returned. // ReconcileWithIDs when IDs are expected to be returned.
func (n *Negentropy) ReconcileWithIDs(query []byte, haveIds, needIds *[]string) ([]byte, error) { func (n *Negentropy) ReconcileWithIDs(query []byte, haveIds, needIds *[]string) ([]byte, error) {
if !n.IsInitiator { if !n.IsInitiator {
return nil, errors.New("non-initiator asking for have/need IDs") return nil, fmt.Errorf("non-initiator asking for have/need IDs")
} }
output, err := n.ReconcileAux(query, haveIds, needIds) output, err := n.ReconcileAux(query, haveIds, needIds)
@ -85,28 +98,28 @@ func (n *Negentropy) ReconcileAux(query []byte, haveIds, needIds *[]string) ([]b
n.lastTimestampIn, n.lastTimestampOut = 0, 0 // Reset for each message n.lastTimestampIn, n.lastTimestampOut = 0, 0 // Reset for each message
var fullOutput []byte var fullOutput []byte
fullOutput = append(fullOutput, ProtocolVersion) fullOutput = append(fullOutput, protocolVersion)
protocolVersion, err := getByte(&query) protocolVersion, err := getByte(&query)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if protocolVersion < 0x60 || protocolVersion > 0x6F { if protocolVersion < 0x60 || protocolVersion > 0x6F {
return nil, errors.New("invalid negentropy protocol version byte") return nil, fmt.Errorf("invalid negentropy protocol version byte")
} }
if protocolVersion != ProtocolVersion { if protocolVersion != protocolVersion {
if n.IsInitiator { if n.IsInitiator {
return nil, errors.New("unsupported negentropy protocol version requested") return nil, fmt.Errorf("unsupported negentropy protocol version requested")
} }
return fullOutput, nil return fullOutput, nil
} }
storageSize := n.Storage.Size() storageSize := n.storage.Size()
var prevBound Bound var prevBound Bound
prevIndex := 0 prevIndex := 0
skip := false skip := false
// Convert the loop to process the query until it's consumed // convert the loop to process the query until it's consumed
for len(query) > 0 { for len(query) > 0 {
var o []byte var o []byte
@ -133,7 +146,7 @@ func (n *Negentropy) ReconcileAux(query []byte, haveIds, needIds *[]string) ([]b
mode := Mode(modeVal) mode := Mode(modeVal)
lower := prevIndex lower := prevIndex
upper, err := n.Storage.FindLowerBound(prevIndex, storageSize, currBound) upper, err := n.storage.FindLowerBound(prevIndex, storageSize, currBound)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -147,7 +160,7 @@ func (n *Negentropy) ReconcileAux(query []byte, haveIds, needIds *[]string) ([]b
if err != nil { if err != nil {
return nil, err return nil, err
} }
ourFingerprint, err := n.Storage.Fingerprint(lower, upper) ourFingerprint, err := n.storage.Fingerprint(lower, upper)
if err != nil { if err != nil {
return nil, err // Handle the error appropriately return nil, err // Handle the error appropriately
} }
@ -160,21 +173,22 @@ func (n *Negentropy) ReconcileAux(query []byte, haveIds, needIds *[]string) ([]b
} }
case IdListMode: case IdListMode:
numIds, err := decodeVarInt(&query) numIds64, err := decodeVarInt(&query)
if err != nil { if err != nil {
return nil, err return nil, err
} }
numIds := int(numIds64)
theirElems := make(map[string]struct{}) theirElems := make(map[string]struct{})
for i := 0; i < numIds; i++ { for i := 0; i < numIds; i++ {
e, err := getBytes(&query, IDSize) e, err := getBytes(&query, n.idSize)
if err != nil { if err != nil {
return nil, err return nil, err
} }
theirElems[hex.EncodeToString(e)] = struct{}{} theirElems[hex.EncodeToString(e)] = struct{}{}
} }
n.Storage.Iterate(lower, upper, func(item Item, _ int) bool { n.storage.Iterate(lower, upper, func(item Item, _ int) bool {
k := item.ID k := item.ID
if _, exists := theirElems[k]; !exists { if _, exists := theirElems[k]; !exists {
if n.IsInitiator { if n.IsInitiator {
@ -195,14 +209,14 @@ func (n *Negentropy) ReconcileAux(query []byte, haveIds, needIds *[]string) ([]b
} else { } else {
doSkip() doSkip()
responseIds := make([]byte, 0, IDSize*n.Storage.Size()) responseIds := make([]byte, 0, n.idSize*n.storage.Size())
responseIdsPtr := &responseIds responseIdsPtr := &responseIds
numResponseIds := 0 numResponseIds := 0
endBound := currBound endBound := currBound
n.Storage.Iterate(lower, upper, func(item Item, index int) bool { n.storage.Iterate(lower, upper, func(item Item, index int) bool {
if n.ExceededFrameSizeLimit(len(fullOutput) + len(*responseIdsPtr)) { if n.ExceededFrameSizeLimit(len(fullOutput) + len(*responseIdsPtr)) {
endBound = *NewBoundWithItem(item) endBound = Bound{item}
upper = index upper = index
return false return false
} }
@ -229,18 +243,18 @@ func (n *Negentropy) ReconcileAux(query []byte, haveIds, needIds *[]string) ([]b
} }
default: default:
return nil, errors.New("unexpected mode") return nil, fmt.Errorf("unexpected mode %d", mode)
} }
// Check if the frame size limit is exceeded // Check if the frame size limit is exceeded
if n.ExceededFrameSizeLimit(len(fullOutput) + len(o)) { if n.ExceededFrameSizeLimit(len(fullOutput) + len(o)) {
// Frame size limit exceeded, handle by encoding a boundary and fingerprint for the remaining range // Frame size limit exceeded, handle by encoding a boundary and fingerprint for the remaining range
remainingFingerprint, err := n.Storage.Fingerprint(upper, storageSize) remainingFingerprint, err := n.storage.Fingerprint(upper, storageSize)
if err != nil { if err != nil {
panic(err) panic(err)
} }
encodedBound, err := n.encodeBound(Bound{Item: Item{Timestamp: MaxU64}}) encodedBound, err := n.encodeBound(Bound{Item: Item{Timestamp: maxTimestamp}})
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -271,11 +285,12 @@ func (n *Negentropy) SplitRange(lower, upper int, upperBound Bound, output *[]by
fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr, err)
panic(err) panic(err)
} }
fmt.Println("upp", upperBound, boundEncoded)
*output = append(*output, boundEncoded...) *output = append(*output, boundEncoded...)
*output = append(*output, encodeVarInt(IdListMode)...) *output = append(*output, encodeVarInt(IdListMode)...)
*output = append(*output, encodeVarInt(numElems)...) *output = append(*output, encodeVarInt(numElems)...)
n.Storage.Iterate(lower, upper, func(item Item, _ int) bool { n.storage.Iterate(lower, upper, func(item Item, _ int) bool {
id, _ := hex.DecodeString(item.ID) id, _ := hex.DecodeString(item.ID)
*output = append(*output, id...) *output = append(*output, id...)
return true return true
@ -290,7 +305,7 @@ func (n *Negentropy) SplitRange(lower, upper int, upperBound Bound, output *[]by
if i < bucketsWithExtra { if i < bucketsWithExtra {
bucketSize++ bucketSize++
} }
ourFingerprint, err := n.Storage.Fingerprint(curr, curr+bucketSize) ourFingerprint, err := n.storage.Fingerprint(curr, curr+bucketSize)
if err != nil { if err != nil {
fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr, err)
panic(err) panic(err)
@ -304,7 +319,7 @@ func (n *Negentropy) SplitRange(lower, upper int, upperBound Bound, output *[]by
} else { } else {
var prevItem, currItem Item var prevItem, currItem Item
n.Storage.Iterate(curr-1, curr+1, func(item Item, index int) bool { n.storage.Iterate(curr-1, curr+1, func(item Item, index int) bool {
if index == curr-1 { if index == curr-1 {
prevItem = item prevItem = item
} else { } else {
@ -313,11 +328,7 @@ func (n *Negentropy) SplitRange(lower, upper int, upperBound Bound, output *[]by
return true return true
}) })
minBound, err := getMinimalBound(prevItem, currItem) minBound := n.getMinimalBound(prevItem, currItem)
if err != nil {
fmt.Fprintln(os.Stderr, err)
panic(err)
}
nextBound = minBound nextBound = minBound
} }
@ -339,21 +350,22 @@ func (n *Negentropy) ExceededFrameSizeLimit(size int) bool {
// Decoding // Decoding
func (n *Negentropy) DecodeTimestampIn(encoded *[]byte) (uint64, error) { func (n *Negentropy) DecodeTimestampIn(encoded *[]byte) (nostr.Timestamp, error) {
t, err := decodeVarInt(encoded) t, err := decodeVarInt(encoded)
if err != nil { if err != nil {
return 0, err return 0, err
} }
timestamp := uint64(t)
timestamp := nostr.Timestamp(t)
if timestamp == 0 { if timestamp == 0 {
timestamp = MaxU64 timestamp = maxTimestamp
} else { } else {
timestamp-- timestamp--
} }
timestamp += n.lastTimestampIn timestamp += n.lastTimestampIn
if timestamp < n.lastTimestampIn { // Check for overflow if timestamp < n.lastTimestampIn { // Check for overflow
timestamp = MaxU64 timestamp = maxTimestamp
} }
n.lastTimestampIn = timestamp n.lastTimestampIn = timestamp
return timestamp, nil return timestamp, nil
@ -375,20 +387,15 @@ func (n *Negentropy) DecodeBound(encoded *[]byte) (Bound, error) {
return Bound{}, err return Bound{}, err
} }
bound, err := NewBound(timestamp, hex.EncodeToString(id)) return Bound{Item{timestamp, hex.EncodeToString(id)}}, nil
if err != nil {
return Bound{}, err
}
return *bound, nil
} }
// Encoding // Encoding
// encodeTimestampOut encodes the given timestamp. // encodeTimestampOut encodes the given timestamp.
func (n *Negentropy) encodeTimestampOut(timestamp uint64) []byte { func (n *Negentropy) encodeTimestampOut(timestamp nostr.Timestamp) []byte {
if timestamp == MaxU64 { if timestamp == maxTimestamp {
n.lastTimestampOut = MaxU64 n.lastTimestampOut = maxTimestamp
return encodeVarInt(0) return encodeVarInt(0)
} }
temp := timestamp temp := timestamp
@ -397,32 +404,27 @@ func (n *Negentropy) encodeTimestampOut(timestamp uint64) []byte {
return encodeVarInt(int(timestamp + 1)) return encodeVarInt(int(timestamp + 1))
} }
// encodeBound encodes the given Bound into a byte slice.
func (n *Negentropy) encodeBound(bound Bound) ([]byte, error) { func (n *Negentropy) encodeBound(bound Bound) ([]byte, error) {
var output []byte var output []byte
t := n.encodeTimestampOut(bound.Item.Timestamp) t := n.encodeTimestampOut(bound.Item.Timestamp)
idlen := encodeVarInt(bound.IDLen) idlen := encodeVarInt(n.idSize)
output = append(output, t...) output = append(output, t...)
output = append(output, idlen...) output = append(output, idlen...)
id := bound.Item.ID id := bound.Item.ID
if len(id) < bound.IDLen {
return nil, errors.New("ID length exceeds bound")
}
output = append(output, id...) output = append(output, id...)
return output, nil return output, nil
} }
func getMinimalBound(prev, curr Item) (Bound, error) { func (n *Negentropy) getMinimalBound(prev, curr Item) Bound {
if curr.Timestamp != prev.Timestamp { if curr.Timestamp != prev.Timestamp {
bound, err := NewBound(curr.Timestamp, "") return Bound{Item{curr.Timestamp, ""}}
return *bound, err
} }
sharedPrefixBytes := 0 sharedPrefixBytes := 0
for i := 0; i < IDSize; i++ { for i := 0; i < n.idSize; i++ {
if curr.ID[i:i+2] != prev.ID[i:i+2] { if curr.ID[i:i+2] != prev.ID[i:i+2] {
break break
} }
@ -430,7 +432,5 @@ func getMinimalBound(prev, curr Item) (Bound, error) {
} }
// sharedPrefixBytes + 1 to include the first differing byte, or the entire ID if identical. // sharedPrefixBytes + 1 to include the first differing byte, or the entire ID if identical.
// Ensure not to exceed the slice's length. return Bound{Item{curr.Timestamp, curr.ID[:sharedPrefixBytes*2+1]}}
bound, err := NewBound(curr.Timestamp, curr.ID[:sharedPrefixBytes*2+1])
return *bound, err
} }

View File

@ -4,18 +4,11 @@ import (
"crypto/sha256" "crypto/sha256"
"encoding/binary" "encoding/binary"
"encoding/hex" "encoding/hex"
"errors"
"math/big" "github.com/nbd-wtf/go-nostr"
) )
const ( const FingerprintSize = 16
IDSize = 32
FingerprintSize = 16
)
var modulo = new(big.Int).Exp(big.NewInt(2), big.NewInt(256), nil)
var ErrBadIDSize = errors.New("bad id size")
type Mode int type Mode int
@ -25,19 +18,22 @@ const (
IdListMode IdListMode
) )
type Storage interface {
Insert(nostr.Timestamp, string) error
Seal() error
IDSize() int
Size() int
Iterate(begin, end int, cb func(item Item, i int) bool) error
FindLowerBound(begin, end int, value Bound) (int, error)
Fingerprint(begin, end int) (Fingerprint, error)
}
type Item struct { type Item struct {
Timestamp uint64 Timestamp nostr.Timestamp
ID string ID string
} }
func NewItem(timestamp uint64, id string) *Item {
return &Item{Timestamp: timestamp, ID: id}
}
func (i Item) Equals(other Item) bool {
return i.Timestamp == other.Timestamp && i.ID == other.ID
}
func (i Item) LessThan(other Item) bool { func (i Item) LessThan(other Item) bool {
if i.Timestamp != other.Timestamp { if i.Timestamp != other.Timestamp {
return i.Timestamp < other.Timestamp return i.Timestamp < other.Timestamp
@ -45,37 +41,7 @@ func (i Item) LessThan(other Item) bool {
return i.ID < other.ID return i.ID < other.ID
} }
type Bound struct { type Bound struct{ Item }
Item Item
IDLen int
}
// NewBound creates a new Bound instance with a timestamp and ID.
// It returns an error if the ID size is incorrect.
func NewBound(timestamp uint64, id string) (*Bound, error) {
b := &Bound{
Item: *NewItem(timestamp, id),
IDLen: len(id),
}
return b, nil
}
// NewBoundWithItem creates a new Bound instance from an existing Item.
func NewBoundWithItem(item Item) *Bound {
return &Bound{
Item: item,
IDLen: len(item.ID),
}
}
// Equals checks if two Bound instances are equal.
func (b Bound) Equals(other Bound) bool {
return b.Item.Equals(other.Item)
}
func (b Bound) LessThan(other Bound) bool {
return b.Item.LessThan(other.Item)
}
type Fingerprint struct { type Fingerprint struct {
Buf [FingerprintSize]byte Buf [FingerprintSize]byte
@ -93,8 +59,8 @@ func (acc *Accumulator) SetToZero() {
acc.Buf = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} acc.Buf = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
} }
func (acc *Accumulator) AddItem(other Item) { func (acc *Accumulator) Add(id string) {
b, _ := hex.DecodeString(other.ID) b, _ := hex.DecodeString(id)
acc.AddBytes(b) acc.AddBytes(b)
} }

View File

@ -17,7 +17,7 @@ func getByte(encoded *[]byte) (byte, error) {
} }
func getBytes(encoded *[]byte, n int) ([]byte, error) { func getBytes(encoded *[]byte, n int) ([]byte, error) {
//fmt.Fprintln(os.Stderr, "getBytes", len(*encoded), n) // fmt.Fprintln(os.Stderr, "getBytes", len(*encoded), n)
if len(*encoded) < n { if len(*encoded) < n {
return nil, errors.New("parse ends prematurely") return nil, errors.New("parse ends prematurely")
} }
@ -39,7 +39,7 @@ func decodeVarInt(encoded *[]byte) (int, error) {
// } // }
//} //}
//return 0, ErrParseEndsPrematurely //return 0, ErrParseEndsPrematurely
res := 0 var res int = 0
for { for {
if len(*encoded) == 0 { if len(*encoded) == 0 {
@ -76,4 +76,4 @@ func encodeVarInt(n int) []byte {
} }
return o return o
} }

View File

@ -2,27 +2,32 @@ package negentropy
import ( import (
"errors" "errors"
"fmt"
"sort" "sort"
"github.com/nbd-wtf/go-nostr"
) )
type Vector struct { type Vector struct {
items []Item items []Item
idSize int
} }
func NewVector() *Vector { func NewVector(idSize int) *Vector {
return &Vector{ return &Vector{
items: make([]Item, 0, 30), items: make([]Item, 0, 30),
idSize: idSize,
} }
} }
func (v *Vector) Insert(createdAt uint64, id string) error { func (v *Vector) Insert(createdAt nostr.Timestamp, id string) error {
// fmt.Fprintln(os.Stderr, "Insert", createdAt, id) // fmt.Fprintln(os.Stderr, "Insert", createdAt, id)
if len(id) != IDSize*2 { if len(id)/2 != v.idSize {
return errors.New("bad id size for added item") return fmt.Errorf("bad id size for added item: expected %d, got %d", v.idSize, len(id)/2)
} }
item := NewItem(createdAt, id)
v.items = append(v.items, *item) item := Item{createdAt, id}
v.items = append(v.items, item)
return nil return nil
} }
@ -32,23 +37,15 @@ func (v *Vector) Seal() error {
}) })
for i := 1; i < len(v.items); i++ { for i := 1; i < len(v.items); i++ {
if v.items[i-1].Equals(v.items[i]) { if v.items[i-1].ID == v.items[i].ID {
return errors.New("duplicate item inserted") return errors.New("duplicate item inserted")
} }
} }
return nil return nil
} }
func (v *Vector) Size() int { func (v *Vector) Size() int { return len(v.items) }
return len(v.items) func (v *Vector) IDSize() int { return v.idSize }
}
func (v *Vector) GetItem(i uint64) (Item, error) {
if i >= uint64(len(v.items)) {
return Item{}, errors.New("index out of bounds")
}
return v.items[i], nil
}
func (v *Vector) Iterate(begin, end int, cb func(Item, int) bool) error { func (v *Vector) Iterate(begin, end int, cb func(Item, int) bool) error {
for i := begin; i < end; i++ { for i := begin; i < end; i++ {
@ -71,7 +68,7 @@ func (v *Vector) Fingerprint(begin, end int) (Fingerprint, error) {
out.SetToZero() out.SetToZero()
if err := v.Iterate(begin, end, func(item Item, _ int) bool { if err := v.Iterate(begin, end, func(item Item, _ int) bool {
out.AddItem(item) out.Add(item.ID)
return true return true
}); err != nil { }); err != nil {
return Fingerprint{}, err return Fingerprint{}, err

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"testing" "testing"
"github.com/nbd-wtf/go-nostr"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -13,13 +14,19 @@ func TestSimple(t *testing.T) {
var n1 *Negentropy var n1 *Negentropy
var n2 *Negentropy var n2 *Negentropy
events := make([]*nostr.Event, 20)
for i := range events {
evt := nostr.Event{Content: fmt.Sprintf("event %d", i+1)}
evt.CreatedAt = nostr.Timestamp(i)
evt.ID = evt.GetID()
events[i] = &evt
}
{ {
n1, _ = NewNegentropy(NewVector(), 1<<16) n1, _ = NewNegentropy(NewVector(32), 1<<16, 32)
n1.Insert(10, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") for i := 2; i < 15; i++ {
n1.Insert(20, "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb") n1.Insert(events[i])
n1.Insert(30, "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc") }
n1.Insert(40, "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd")
n1.Seal()
q, err = n1.Initiate() q, err = n1.Initiate()
if err != nil { if err != nil {
@ -31,11 +38,13 @@ func TestSimple(t *testing.T) {
} }
{ {
n2, _ = NewNegentropy(NewVector(), 1<<16) n2, _ = NewNegentropy(NewVector(32), 1<<16, 32)
n2.Insert(20, "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb") for i := 0; i < 2; i++ {
n2.Insert(30, "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc") n2.Insert(events[i])
n2.Insert(50, "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee") }
n2.Seal() for i := 15; i < 20; i++ {
n2.Insert(events[i])
}
q, err = n2.Reconcile(q) q, err = n2.Reconcile(q)
if err != nil { if err != nil {