Commit 1f11fab5 authored by acud's avatar acud Committed by GitHub

pull sync: initial version (#193)

* pullsync, puller, pullstorage: initial pull sync protocol implementation
parent 3ab2f7a9
// Copyright 2018 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package bitvector
import (
"errors"
)
var errInvalidLength = errors.New("invalid length")
// BitVector is a convenience object for manipulating and representing bit vectors
type BitVector struct {
len int
b []byte
}
// New creates a new bit vector with the given length
func New(l int) (*BitVector, error) {
return NewFromBytes(make([]byte, l/8+1), l)
}
// NewFromBytes creates a bit vector from the passed byte slice.
//
// Leftmost bit in byte slice becomes leftmost bit in bit vector
func NewFromBytes(b []byte, l int) (*BitVector, error) {
if l <= 0 {
return nil, errInvalidLength
}
if len(b)*8 < l {
return nil, errInvalidLength
}
return &BitVector{
len: l,
b: b,
}, nil
}
// Get gets the corresponding bit, counted from left to right
func (bv *BitVector) Get(i int) bool {
bi := i / 8
return bv.b[bi]&(0x1<<uint(i%8)) != 0
}
// Set sets the bit corresponding to the index in the bitvector, counted from left to right
func (bv *BitVector) set(i int, v bool) {
bi := i / 8
cv := bv.Get(i)
if cv != v {
bv.b[bi] ^= 0x1 << uint8(i%8)
}
}
// Set sets the bit corresponding to the index in the bitvector, counted from left to right
func (bv *BitVector) Set(i int) {
bv.set(i, true)
}
// Unset UNSETS the corresponding bit, counted from left to right
func (bv *BitVector) Unset(i int) {
bv.set(i, false)
}
// SetBytes sets all bits in the bitvector that are set in the argument
//
// The argument must be the same as the bitvector length
func (bv *BitVector) SetBytes(bs []byte) error {
if len(bs) != bv.len {
return errors.New("invalid length")
}
for i := 0; i < bv.len*8; i++ {
bi := i / 8
if bs[bi]&(0x01<<uint(i%8)) > 0 {
bv.set(i, true)
}
}
return nil
}
// UnsetBytes UNSETS all bits in the bitvector that are set in the argument
//
// The argument must be the same as the bitvector length
func (bv *BitVector) UnsetBytes(bs []byte) error {
if len(bs) != bv.len {
return errors.New("invalid length")
}
for i := 0; i < bv.len*8; i++ {
bi := i / 8
if bs[bi]&(0x01<<uint(i%8)) > 0 {
bv.set(i, false)
}
}
return nil
}
// String implements Stringer interface
func (bv *BitVector) String() (s string) {
for i := 0; i < bv.len*8; i++ {
if bv.Get(i) {
s += "1"
} else {
s += "0"
}
}
return s
}
// Bytes retrieves the underlying bytes of the bitvector
func (bv *BitVector) Bytes() []byte {
return bv.b
}
// Copyright 2018 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package bitvector
import "testing"
// TestBitvectorNew checks that enforcements of argument length works in the constructors
func TestBitvectorNew(t *testing.T) {
_, err := New(0)
if err != errInvalidLength {
t.Errorf("expected err %v, got %v", errInvalidLength, err)
}
_, err = NewFromBytes(nil, 0)
if err != errInvalidLength {
t.Errorf("expected err %v, got %v", errInvalidLength, err)
}
_, err = NewFromBytes([]byte{0}, 9)
if err != errInvalidLength {
t.Errorf("expected err %v, got %v", errInvalidLength, err)
}
_, err = NewFromBytes(make([]byte, 8), 8)
if err != nil {
t.Error(err)
}
}
// TestBitvectorGetSet tests correctness of individual Set and Get commands
func TestBitvectorGetSet(t *testing.T) {
for _, length := range []int{
1,
2,
4,
8,
9,
15,
16,
} {
bv, err := New(length)
if err != nil {
t.Errorf("error for length %v: %v", length, err)
}
for i := 0; i < length; i++ {
if bv.Get(i) {
t.Errorf("expected false for element on index %v", i)
}
}
func() {
defer func() {
if err := recover(); err == nil {
t.Errorf("expecting panic")
}
}()
bv.Get(length + 8)
}()
for i := 0; i < length; i++ {
bv.Set(i)
for j := 0; j < length; j++ {
if j == i {
if !bv.Get(j) {
t.Errorf("element on index %v is not set to true", i)
}
} else {
if bv.Get(j) {
t.Errorf("element on index %v is not false", i)
}
}
}
bv.Unset(i)
if bv.Get(i) {
t.Errorf("element on index %v is not set to false", i)
}
}
}
}
// TestBitvectorNewFromBytesGet tests that bit vector is initialized correctly from underlying byte slice
func TestBitvectorNewFromBytesGet(t *testing.T) {
bv, err := NewFromBytes([]byte{8}, 8)
if err != nil {
t.Error(err)
}
if !bv.Get(3) {
t.Fatalf("element 3 is not set to true: state %08b", bv.b[0])
}
}
// TestBitVectorString tests that string representation of bit vector is correct
func TestBitVectorString(t *testing.T) {
b := []byte{0xa5, 0x81}
expect := "1010010110000001"
bv, err := NewFromBytes(b, 2)
if err != nil {
t.Fatal(err)
}
if bv.String() != expect {
t.Fatalf("bitvector string fail: got %s, expect %s", bv.String(), expect)
}
}
// TestBitVectorSetUnsetBytes tests that setting and unsetting by byte slice modifies the bit vector correctly
func TestBitVectorSetBytes(t *testing.T) {
b := []byte{0xff, 0xff}
cb := []byte{0xa5, 0x81}
expectUnset := "0101101001111110"
expectReset := "1111111111111111"
bv, err := NewFromBytes(b, 2)
if err != nil {
t.Fatal(err)
}
err = bv.UnsetBytes(cb)
if err != nil {
t.Fatal(err)
}
if bv.String() != expectUnset {
t.Fatalf("bitvector unset bytes fail: got %s, expect %s", bv.String(), expectUnset)
}
err = bv.SetBytes(cb)
if err != nil {
t.Fatal(err)
}
if bv.String() != expectReset {
t.Fatalf("bitvector reset bytes fail: got %s, expect %s", bv.String(), expectReset)
}
}
......@@ -58,9 +58,11 @@ type Kad struct {
manageC chan struct{} // trigger the manage forever loop to connect to new peers
waitNext map[string]retryInfo // sanction connections to a peer, key is overlay string and value is a retry information
waitNextMu sync.Mutex // synchronize map
logger logging.Logger // logger
quit chan struct{} // quit channel
done chan struct{} // signal that `manage` has quit
peerSig []chan struct{}
peerSigMtx sync.Mutex
logger logging.Logger // logger
quit chan struct{} // quit channel
done chan struct{} // signal that `manage` has quit
}
type retryInfo struct {
......@@ -165,6 +167,8 @@ func (k *Kad) manage() {
k.logger.Debugf("connected to peer: %s old depth: %d new depth: %d", peer, currentDepth, k.NeighborhoodDepth())
k.notifyPeerSig()
select {
case <-k.quit:
return true, false, nil
......@@ -347,6 +351,8 @@ func (k *Kad) Connected(ctx context.Context, addr swarm.Address) error {
k.depth = k.recalcDepth()
k.depthMu.Unlock()
k.notifyPeerSig()
select {
case k.manageC <- struct{}{}:
default:
......@@ -370,6 +376,22 @@ func (k *Kad) Disconnected(addr swarm.Address) {
case k.manageC <- struct{}{}:
default:
}
k.notifyPeerSig()
}
func (k *Kad) notifyPeerSig() {
k.peerSigMtx.Lock()
defer k.peerSigMtx.Unlock()
for _, c := range k.peerSig {
// Every peerSig channel has a buffer capacity of 1,
// so every receiver will get the signal even if the
// select statement has the default case to avoid blocking.
select {
case c <- struct{}{}:
default:
}
}
}
// ClosestPeer returns the closest peer to a given address.
......@@ -408,6 +430,44 @@ func (k *Kad) ClosestPeer(addr swarm.Address) (swarm.Address, error) {
return closest, nil
}
// EachPeer iterates from closest bin to farthest
func (k *Kad) EachPeer(f topology.EachPeerFunc) error {
return k.connectedPeers.EachBin(f)
}
// EachPeerRev iterates from farthest bin to closest
func (k *Kad) EachPeerRev(f topology.EachPeerFunc) error {
return k.connectedPeers.EachBinRev(f)
}
// SubscribePeersChange returns the channel that signals when the connected peers
// set changes. Returned function is safe to be called multiple times.
func (k *Kad) SubscribePeersChange() (c <-chan struct{}, unsubscribe func()) {
channel := make(chan struct{}, 1)
var closeOnce sync.Once
k.peerSigMtx.Lock()
defer k.peerSigMtx.Unlock()
k.peerSig = append(k.peerSig, channel)
unsubscribe = func() {
k.peerSigMtx.Lock()
defer k.peerSigMtx.Unlock()
for i, c := range k.peerSig {
if c == channel {
k.peerSig = append(k.peerSig[:i], k.peerSig[i+1:]...)
break
}
}
closeOnce.Do(func() { close(channel) })
}
return channel, unsubscribe
}
// NeighborhoodDepth returns the current Kademlia depth.
func (k *Kad) NeighborhoodDepth() uint8 {
k.depthMu.RLock()
......
......@@ -501,6 +501,104 @@ func TestClosestPeer(t *testing.T) {
}
}
func TestKademlia_SubscribePeersChange(t *testing.T) {
testSignal := func(t *testing.T, k *kademlia.Kad, c <-chan struct{}) {
t.Helper()
select {
case _, ok := <-c:
if !ok {
t.Error("closed signal channel")
}
case <-time.After(1 * time.Second):
t.Error("timeout")
}
}
t.Run("single subscription", func(t *testing.T) {
base, kad, ab, _, sg := newTestKademlia(nil, nil, nil)
c, u := kad.SubscribePeersChange()
defer u()
addr := test.RandomAddressAt(base, 9)
addOne(t, sg, kad, ab, addr)
testSignal(t, kad, c)
})
t.Run("single subscription, remove peer", func(t *testing.T) {
base, kad, ab, _, sg := newTestKademlia(nil, nil, nil)
c, u := kad.SubscribePeersChange()
defer u()
addr := test.RandomAddressAt(base, 9)
addOne(t, sg, kad, ab, addr)
testSignal(t, kad, c)
removeOne(kad, addr)
testSignal(t, kad, c)
})
t.Run("multiple subscriptions", func(t *testing.T) {
base, kad, ab, _, sg := newTestKademlia(nil, nil, nil)
c1, u1 := kad.SubscribePeersChange()
defer u1()
c2, u2 := kad.SubscribePeersChange()
defer u2()
for i := 0; i < 4; i++ {
addr := test.RandomAddressAt(base, i)
addOne(t, sg, kad, ab, addr)
}
testSignal(t, kad, c1)
testSignal(t, kad, c2)
})
t.Run("multiple changes", func(t *testing.T) {
base, kad, ab, _, sg := newTestKademlia(nil, nil, nil)
c, u := kad.SubscribePeersChange()
defer u()
for i := 0; i < 4; i++ {
addr := test.RandomAddressAt(base, i)
addOne(t, sg, kad, ab, addr)
}
testSignal(t, kad, c)
for i := 0; i < 4; i++ {
addr := test.RandomAddressAt(base, i)
addOne(t, sg, kad, ab, addr)
}
testSignal(t, kad, c)
})
t.Run("no depth change", func(t *testing.T) {
_, kad, _, _, _ := newTestKademlia(nil, nil, nil)
c, u := kad.SubscribePeersChange()
defer u()
select {
case _, ok := <-c:
if !ok {
t.Error("closed signal channel")
}
t.Error("signal received")
case <-time.After(1 * time.Second):
// all fine
}
})
}
func TestMarshal(t *testing.T) {
var (
_, kad, ab, _, signer = newTestKademlia(nil, nil, nil)
......
// Copyright 2020 The Swarm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package mock
import (
"context"
"sync"
"github.com/ethersphere/bee/pkg/swarm"
"github.com/ethersphere/bee/pkg/topology"
)
type AddrTuple struct {
Addr swarm.Address // the peer address
PO uint8 // the po
}
func WithEachPeerRevCalls(addrs ...AddrTuple) Option {
return optionFunc(func(m *Mock) {
for _, a := range addrs {
a := a
m.eachPeerRev = append(m.eachPeerRev, a)
}
})
}
func WithDepth(d uint8) Option {
return optionFunc(func(m *Mock) {
m.depth = d
})
}
func WithDepthCalls(d ...uint8) Option {
return optionFunc(func(m *Mock) {
m.depthReplies = d
})
}
type Mock struct {
mtx sync.Mutex
peers []swarm.Address
eachPeerRev []AddrTuple
depth uint8
depthReplies []uint8
depthCalls int
trigs []chan struct{}
trigMtx sync.Mutex
}
func NewMockKademlia(o ...Option) *Mock {
m := &Mock{}
for _, v := range o {
v.apply(m)
}
return m
}
// AddPeer is called when a peer is added to the topology backlog
// for further processing by connectivity strategy.
func (m *Mock) AddPeer(ctx context.Context, addr swarm.Address) error {
panic("not implemented") // TODO: Implement
}
func (m *Mock) ClosestPeer(addr swarm.Address) (peerAddr swarm.Address, err error) {
panic("not implemented") // TODO: Implement
}
// EachPeer iterates from closest bin to farthest
func (m *Mock) EachPeer(f topology.EachPeerFunc) error {
m.mtx.Lock()
defer m.mtx.Unlock()
for i := len(m.peers) - 1; i > 0; i-- {
stop, _, err := f(m.peers[i], uint8(i))
if stop {
return nil
}
if err != nil {
return err
}
}
return nil
}
// EachPeerRev iterates from farthest bin to closest
func (m *Mock) EachPeerRev(f topology.EachPeerFunc) error {
m.mtx.Lock()
defer m.mtx.Unlock()
for _, v := range m.eachPeerRev {
stop, _, err := f(v.Addr, v.PO)
if stop {
return nil
}
if err != nil {
return err
}
}
return nil
}
func (m *Mock) NeighborhoodDepth() uint8 {
m.mtx.Lock()
defer m.mtx.Unlock()
m.depthCalls++
if len(m.depthReplies) > 0 {
return m.depthReplies[m.depthCalls]
}
return m.depth
}
// Connected is called when a peer dials in.
func (m *Mock) Connected(_ context.Context, addr swarm.Address) error {
m.mtx.Lock()
m.peers = append(m.peers, addr)
m.mtx.Unlock()
m.Trigger()
return nil
}
// Disconnected is called when a peer disconnects.
func (m *Mock) Disconnected(_ swarm.Address) {
m.Trigger()
}
func (m *Mock) SubscribePeersChange() (c <-chan struct{}, unsubscribe func()) {
channel := make(chan struct{}, 1)
var closeOnce sync.Once
m.trigMtx.Lock()
defer m.trigMtx.Unlock()
m.trigs = append(m.trigs, channel)
unsubscribe = func() {
m.trigMtx.Lock()
defer m.trigMtx.Unlock()
for i, c := range m.trigs {
if c == channel {
m.trigs = append(m.trigs[:i], m.trigs[i+1:]...)
break
}
}
closeOnce.Do(func() { close(channel) })
}
return channel, unsubscribe
}
func (m *Mock) Trigger() {
m.trigMtx.Lock()
defer m.trigMtx.Unlock()
for _, c := range m.trigs {
select {
case c <- struct{}{}:
default:
}
}
}
func (m *Mock) ResetPeers() {
m.mtx.Lock()
defer m.mtx.Unlock()
m.peers = nil
m.eachPeerRev = nil
}
func (m *Mock) Close() error {
panic("not implemented") // TODO: Implement
}
type Option interface {
apply(*Mock)
}
type optionFunc func(*Mock)
func (f optionFunc) apply(r *Mock) { f(r) }
......@@ -36,7 +36,7 @@ import (
// function will terminate current and further iterations without errors, and also close the returned channel.
// Make sure that you check the second returned parameter from the channel to stop iteration when its value
// is false.
func (db *DB) SubscribePull(ctx context.Context, bin uint8, since, until uint64) (c <-chan storage.Descriptor, stop func()) {
func (db *DB) SubscribePull(ctx context.Context, bin uint8, since, until uint64) (c <-chan storage.Descriptor, closed <-chan struct{}, stop func()) {
db.metrics.SubscribePull.Inc()
chunkDescriptors := make(chan storage.Descriptor)
......@@ -176,7 +176,7 @@ func (db *DB) SubscribePull(ctx context.Context, bin uint8, since, until uint64)
}
}
return chunkDescriptors, stop
return chunkDescriptors, db.close, stop
}
// LastPullSubscriptionBinID returns chunk bin id of the latest Chunk
......
......@@ -54,7 +54,7 @@ func TestDB_SubscribePull_first(t *testing.T) {
since := chunksInGivenBin + 1
go func() {
ch, stop := db.SubscribePull(context.TODO(), bin, since, 0)
ch, _, stop := db.SubscribePull(context.TODO(), bin, since, 0)
defer stop()
chnk := <-ch
......@@ -100,7 +100,7 @@ func TestDB_SubscribePull(t *testing.T) {
errChan := make(chan error)
for bin := uint8(0); bin <= swarm.MaxPO; bin++ {
ch, stop := db.SubscribePull(ctx, bin, 0, 0)
ch, _, stop := db.SubscribePull(ctx, bin, 0, 0)
defer stop()
// receive and validate addresses from the subscription
......@@ -149,7 +149,7 @@ func TestDB_SubscribePull_multiple(t *testing.T) {
// that all of them will write every address error to errChan
for j := 0; j < subsCount; j++ {
for bin := uint8(0); bin <= swarm.MaxPO; bin++ {
ch, stop := db.SubscribePull(ctx, bin, 0, 0)
ch, _, stop := db.SubscribePull(ctx, bin, 0, 0)
defer stop()
// receive and validate addresses from the subscription
......@@ -237,7 +237,7 @@ func TestDB_SubscribePull_since(t *testing.T) {
if !ok {
continue
}
ch, stop := db.SubscribePull(ctx, bin, since, 0)
ch, _, stop := db.SubscribePull(ctx, bin, since, 0)
defer stop()
// receive and validate addresses from the subscription
......@@ -313,7 +313,7 @@ func TestDB_SubscribePull_until(t *testing.T) {
if !ok {
continue
}
ch, stop := db.SubscribePull(ctx, bin, 0, until)
ch, _, stop := db.SubscribePull(ctx, bin, 0, until)
defer stop()
// receive and validate addresses from the subscription
......@@ -404,7 +404,7 @@ func TestDB_SubscribePull_sinceAndUntil(t *testing.T) {
// skip this bin from testing
continue
}
ch, stop := db.SubscribePull(ctx, bin, since, until)
ch, _, stop := db.SubscribePull(ctx, bin, since, until)
defer stop()
// receive and validate addresses from the subscription
......@@ -491,7 +491,7 @@ func TestDB_SubscribePull_rangeOnRemovedChunks(t *testing.T) {
// ignore this bin if it has only one chunk left
continue
}
ch, stop := db.SubscribePull(ctx, bin, since, until)
ch, _, stop := db.SubscribePull(ctx, bin, since, until)
defer stop()
// the returned channel should be closed
......
......@@ -33,6 +33,9 @@ import (
"github.com/ethersphere/bee/pkg/p2p"
"github.com/ethersphere/bee/pkg/p2p/libp2p"
"github.com/ethersphere/bee/pkg/pingpong"
"github.com/ethersphere/bee/pkg/puller"
"github.com/ethersphere/bee/pkg/pullsync"
"github.com/ethersphere/bee/pkg/pullsync/pullstorage"
"github.com/ethersphere/bee/pkg/pusher"
"github.com/ethersphere/bee/pkg/pushsync"
"github.com/ethersphere/bee/pkg/retrieval"
......@@ -59,6 +62,8 @@ type Bee struct {
localstoreCloser io.Closer
topologyCloser io.Closer
pusherCloser io.Closer
pullerCloser io.Closer
pullSyncCloser io.Closer
}
type Options struct {
......@@ -258,6 +263,27 @@ func NewBee(o Options) (*Bee, error) {
})
b.pusherCloser = pushSyncPusher
pullStorage := pullstorage.New(storer)
pullSync := pullsync.New(pullsync.Options{
Streamer: p2ps,
Storage: pullStorage,
Logger: logger,
})
b.pullSyncCloser = pullSync
if err = p2ps.AddProtocol(pullSync.Protocol()); err != nil {
return nil, fmt.Errorf("pullsync protocol: %w", err)
}
puller := puller.New(puller.Options{
StateStore: stateStore,
Topology: topologyDriver,
PullSync: pullSync,
Logger: logger,
})
b.pullerCloser = puller
var apiService api.Service
if o.APIAddr != "" {
// API server
......@@ -446,6 +472,14 @@ func (b *Bee) Shutdown(ctx context.Context) error {
errs.add(fmt.Errorf("pusher: %w", err))
}
if err := b.pullerCloser.Close(); err != nil {
return fmt.Errorf("puller: %w", err)
}
if err := b.pullSyncCloser.Close(); err != nil {
return fmt.Errorf("pull sync: %w", err)
}
b.p2pCancel()
if err := b.p2pService.Close(); err != nil {
errs.add(fmt.Errorf("p2p server: %w", err))
......
package puller
var (
PeerIntervalKey = peerIntervalKey
Bins = &bins
ShallowBinPeers = &shallowBinPeers
IsSyncing = isSyncing
)
// Copyright 2020 The Swarm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package puller
import (
"context"
"fmt"
"math"
"sync"
"time"
"github.com/ethersphere/bee/pkg/intervalstore"
"github.com/ethersphere/bee/pkg/logging"
"github.com/ethersphere/bee/pkg/pullsync"
"github.com/ethersphere/bee/pkg/storage"
"github.com/ethersphere/bee/pkg/swarm"
"github.com/ethersphere/bee/pkg/topology"
)
var (
bins = uint8(16)
// how many peers per bin do we want to sync with outside of depth
shallowBinPeers = 2
)
type Options struct {
StateStore storage.StateStorer
Topology topology.Driver
PullSync pullsync.Interface
Logger logging.Logger
}
type Puller struct {
mtx sync.Mutex
topology topology.Driver
statestore storage.StateStorer
intervalMtx sync.Mutex
syncer pullsync.Interface
logger logging.Logger
syncPeers []map[string]*syncPeer // index is bin, map key is peer address
syncPeersMtx sync.Mutex
cursors map[string][]uint64
cursorsMtx sync.Mutex
quit chan struct{}
}
func New(o Options) *Puller {
p := &Puller{
statestore: o.StateStore,
topology: o.Topology,
syncer: o.PullSync,
logger: o.Logger,
cursors: make(map[string][]uint64),
syncPeers: make([]map[string]*syncPeer, bins),
quit: make(chan struct{}),
}
for i := uint8(0); i < bins; i++ {
p.syncPeers[i] = make(map[string]*syncPeer)
}
go p.manage()
return p
}
type peer struct {
addr swarm.Address
po uint8
}
func (p *Puller) manage() {
c, unsubscribe := p.topology.SubscribePeersChange()
defer unsubscribe()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
for {
select {
case <-c:
// get all peers from kademlia
// iterate on entire bin at once (get all peers first)
// check how many intervals we synced with all of them
// pick the one with the most
// sync with that one
// if we're already syncing with this peer, make sure
// that we're syncing the correct bins according to depth
depth := p.topology.NeighborhoodDepth()
// we defer the actual start of syncing to get out of the iterator first
var (
peersToSync []peer
peersToRecalc []peer
peersDisconnected = make(map[string]peer)
)
p.syncPeersMtx.Lock()
// make a map of all peers we're syncing with, then remove from it
// the entries we get from kademlia in the iterator, this way we
// know which peers are no longer there anymore (disconnected) thus
// should be removed from the syncPeer bin.
for po, bin := range p.syncPeers {
for peerAddr, v := range bin {
pe := peer{addr: v.address, po: uint8(po)}
peersDisconnected[peerAddr] = pe
}
}
// EachPeerRev in this case will never return an error, since the content of the callback
// never returns an error. In case in the future changes are made to the callback in a
// way that it returns an error - the value must be checked.
_ = p.topology.EachPeerRev(func(peerAddr swarm.Address, po uint8) (stop, jumpToNext bool, err error) {
bp := p.syncPeers[po]
if _, ok := bp[peerAddr.String()]; ok {
delete(peersDisconnected, peerAddr.String())
}
syncing := len(bp)
if po < depth {
// outside of depth, sync peerPO bin only
if _, ok := bp[peerAddr.String()]; !ok {
if syncing < shallowBinPeers {
// peer not syncing yet and we still need more peers in this bin
bp[peerAddr.String()] = newSyncPeer(peerAddr)
peerEntry := peer{addr: peerAddr, po: po}
peersToSync = append(peersToSync, peerEntry)
}
} else {
// already syncing, recalc
peerEntry := peer{addr: peerAddr, po: po}
peersToRecalc = append(peersToRecalc, peerEntry)
}
} else {
// within depth, sync everything >= depth
if _, ok := bp[peerAddr.String()]; !ok {
// we're not syncing with this peer yet, start doing so
bp[peerAddr.String()] = newSyncPeer(peerAddr)
peerEntry := peer{addr: peerAddr, po: po}
peersToSync = append(peersToSync, peerEntry)
} else {
// already syncing, recalc
peerEntry := peer{addr: peerAddr, po: po}
peersToRecalc = append(peersToRecalc, peerEntry)
}
}
return false, false, nil
})
for _, v := range peersToSync {
p.syncPeer(ctx, v.addr, v.po, depth)
}
for _, v := range peersToRecalc {
p.recalcPeer(ctx, v.addr, v.po, depth)
}
for _, v := range peersDisconnected {
p.disconnectPeer(ctx, v.addr, v.po)
}
p.syncPeersMtx.Unlock()
case <-p.quit:
return
}
}
}
func (p *Puller) disconnectPeer(ctx context.Context, peer swarm.Address, po uint8) {
p.logger.Debugf("puller disconnect cleanup peer %s po %d", peer, po)
syncCtx := p.syncPeers[po][peer.String()] // disconnectPeer is called under lock, this is safe
syncCtx.Lock()
defer syncCtx.Unlock()
for _, f := range syncCtx.binCancelFuncs {
f()
}
delete(p.syncPeers[po], peer.String())
}
func (p *Puller) recalcPeer(ctx context.Context, peer swarm.Address, po, d uint8) {
p.logger.Debugf("puller recalculating peer %s po %d depth %d", peer, po, d)
syncCtx := p.syncPeers[po][peer.String()] // recalcPeer is called under lock, this is safe
syncCtx.Lock()
defer syncCtx.Unlock()
p.cursorsMtx.Lock()
c := p.cursors[peer.String()]
p.cursorsMtx.Unlock()
if po >= d {
// within depth
var want, dontWant []uint8
for i := d; i < bins; i++ {
if i == 0 {
continue
}
want = append(want, i)
}
for i := uint8(0); i < d; i++ {
dontWant = append(dontWant, i)
}
for _, bin := range want {
// question: do we want to have the separate cancel funcs per live/hist
// for known whether syncing is running on that bin/stream? could be some race here
if _, ok := syncCtx.binCancelFuncs[bin]; !ok {
// if there's no bin cancel func it means there's no
// sync running on this bin. start syncing both hist and live
cur := c[bin]
binCtx, cancel := context.WithCancel(ctx)
syncCtx.binCancelFuncs[bin] = cancel
if cur > 0 {
go p.histSyncWorker(binCtx, peer, bin, cur)
}
go p.liveSyncWorker(binCtx, peer, bin, cur)
}
}
for _, bin := range dontWant {
if c, ok := syncCtx.binCancelFuncs[bin]; ok {
// we have sync running on this bin, cancel it
c()
delete(syncCtx.binCancelFuncs, bin)
}
}
} else {
// outside of depth
var (
want = po
dontWant = []uint8{0} // never want bin 0
)
for i := uint8(0); i < bins; i++ {
if i == want {
continue
}
dontWant = append(dontWant, i)
}
if _, ok := syncCtx.binCancelFuncs[want]; !ok {
// if there's no bin cancel func it means there's no
// sync running on this bin. start syncing both hist and live
cur := c[want]
binCtx, cancel := context.WithCancel(ctx)
syncCtx.binCancelFuncs[po] = cancel
if cur > 0 {
go p.histSyncWorker(binCtx, peer, want, cur)
}
go p.liveSyncWorker(binCtx, peer, want, cur)
}
for _, bin := range dontWant {
if c, ok := syncCtx.binCancelFuncs[bin]; ok {
// we have sync running on this bin, cancel it
c()
delete(syncCtx.binCancelFuncs, bin)
}
}
}
}
func (p *Puller) syncPeer(ctx context.Context, peer swarm.Address, po, d uint8) {
syncCtx := p.syncPeers[po][peer.String()] // syncPeer is called under lock, so this is safe
syncCtx.Lock()
defer syncCtx.Unlock()
p.cursorsMtx.Lock()
c, ok := p.cursors[peer.String()]
p.cursorsMtx.Unlock()
if !ok {
cursors, err := p.syncer.GetCursors(ctx, peer)
if err != nil {
p.logger.Errorf("error getting cursors: %v", err)
delete(p.syncPeers[po], peer.String())
return
// remove from syncing peers list, trigger channel to find some other peer
// maybe blacklist for some time
}
p.cursorsMtx.Lock()
p.cursors[peer.String()] = cursors
p.cursorsMtx.Unlock()
c = cursors
}
// peer outside depth?
if po < d && po > 0 {
cur, bin := c[po], po
// start just one bin for historical and live
binCtx, cancel := context.WithCancel(ctx)
syncCtx.binCancelFuncs[po] = cancel
if cur > 0 {
go p.histSyncWorker(binCtx, peer, bin, cur) // start historical
}
go p.liveSyncWorker(binCtx, peer, bin, cur) // start live
return
}
for bin, cur := range c {
if bin == 0 || uint8(bin) < d {
continue
}
binCtx, cancel := context.WithCancel(ctx)
syncCtx.binCancelFuncs[uint8(bin)] = cancel
if cur > 0 {
go p.histSyncWorker(binCtx, peer, uint8(bin), cur) // start historical
}
// start live
go p.liveSyncWorker(binCtx, peer, uint8(bin), cur) // start live
}
}
func (p *Puller) histSyncWorker(ctx context.Context, peer swarm.Address, bin uint8, cur uint64) {
p.logger.Debugf("histSyncWorker starting, peer %s bin %d cursor %d", peer, bin, cur)
for {
select {
case <-p.quit:
p.logger.Debugf("histSyncWorker quitting on shutdown. peer %s bin %d cur %d", peer, bin, cur)
return
case <-ctx.Done():
p.logger.Debugf("histSyncWorker context cancelled. peer %s bin %d cur %d", peer, bin, cur)
return
default:
}
s, _, _, err := p.nextPeerInterval(peer, bin)
if err != nil {
p.logger.Debugf("histSyncWorker nextPeerInterval: %v", err)
// wait and retry? this is a local error
// maybe just quit the peer entirely.
// not sure how to do this
<-time.After(30 * time.Second)
continue
}
p.logger.Debugf("histSyncWorker peer %s bin %d next interval %d cursor %d", peer, bin, s, cur)
if s > cur {
p.logger.Debugf("histSyncWorker finished syncing bin %d, cursor %d", bin, cur)
return
}
start := time.Now()
top, err := p.syncer.SyncInterval(ctx, peer, bin, s, cur)
if err != nil {
p.logger.Debugf("histSyncWorker error syncing interval. peer %s, bin %d, cursor %d, err %v", peer.String(), bin, cur, err)
return
}
took := time.Since(start)
p.logger.Tracef("histSyncWorker peer %s bin %d synced interval from %d to %d. took %s", peer, bin, s, top, took)
err = p.addPeerInterval(peer, bin, s, top)
if err != nil {
p.logger.Debugf("error persisting interval for peer, quitting")
return
}
}
}
func (p *Puller) liveSyncWorker(ctx context.Context, peer swarm.Address, bin uint8, cur uint64) {
p.logger.Debugf("liveSyncWorker starting, peer %s bin %d cursor %d", peer, bin, cur)
from := cur + 1
for {
select {
case <-p.quit:
p.logger.Debugf("liveSyncWorker quit on shutdown. peer %s bin %d cur %d", peer, bin, cur)
return
case <-ctx.Done():
p.logger.Debugf("liveSyncWorker context cancelled. peer %s bin %d cur %d", peer, bin, cur)
return
default:
}
p.logger.Tracef("liveSyncWorker peer %s syncing bin %d from %d", peer, bin, from)
top, err := p.syncer.SyncInterval(ctx, peer, bin, from, math.MaxUint64)
if err != nil {
p.logger.Debugf("liveSyncWorker exit on sync error. peer %s bin %d from %d err %v", peer, bin, from, err)
return
}
if top == 0 {
return //TODO need to deal with this somehow. not right
}
p.logger.Tracef("liveSyncWorker peer %s synced bin %d from %d to %d", peer, bin, from, top)
err = p.addPeerInterval(peer, bin, from, top)
if err != nil {
p.logger.Debugf("liveSyncWorker exit on add peer interval. peer %s bin %d from %d err %v", peer, bin, from, err)
return
}
from = top + 1
}
}
func (p *Puller) Close() error {
close(p.quit)
p.syncPeersMtx.Lock()
defer p.syncPeersMtx.Unlock()
for i := uint8(0); i < bins; i++ {
binPeers := p.syncPeers[i]
for _, peer := range binPeers {
peer.Lock()
for _, f := range peer.binCancelFuncs {
f()
}
peer.Unlock()
}
}
return nil
}
func (p *Puller) addPeerInterval(peer swarm.Address, bin uint8, start, end uint64) (err error) {
p.intervalMtx.Lock()
defer p.intervalMtx.Unlock()
peerStreamKey := peerIntervalKey(peer, bin)
i, err := p.getOrCreateInterval(peer, bin)
if err != nil {
return err
}
i.Add(start, end)
return p.statestore.Put(peerStreamKey, i)
}
func (p *Puller) nextPeerInterval(peer swarm.Address, bin uint8) (start, end uint64, empty bool, err error) {
p.intervalMtx.Lock()
defer p.intervalMtx.Unlock()
i, err := p.getOrCreateInterval(peer, bin)
if err != nil {
return 0, 0, false, err
}
start, end, empty = i.Next(0)
return start, end, empty, nil
}
func (p *Puller) getOrCreateInterval(peer swarm.Address, bin uint8) (*intervalstore.Intervals, error) {
p.mtx.Lock()
defer p.mtx.Unlock()
// check that an interval entry exists
key := peerIntervalKey(peer, bin)
i := &intervalstore.Intervals{}
err := p.statestore.Get(key, i)
switch err {
case nil:
case storage.ErrNotFound:
// key interval values are ALWAYS > 0
i = intervalstore.NewIntervals(1)
if err := p.statestore.Put(key, i); err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("get peer interval: %w", err)
}
return i, nil
}
func peerIntervalKey(peer swarm.Address, bin uint8) string {
k := fmt.Sprintf("%s|%d", peer.String(), bin)
return k
}
type syncPeer struct {
address swarm.Address
binCancelFuncs map[uint8]func() // slice of context cancel funcs for historical sync. index is bin
sync.Mutex
}
func newSyncPeer(addr swarm.Address) *syncPeer {
return &syncPeer{
address: addr,
binCancelFuncs: make(map[uint8]func(), bins),
}
}
func isSyncing(p *Puller, addr swarm.Address) bool {
// this is needed for testing purposes in order
// to verify that a peer is no longer syncing on
// disconnect
p.syncPeersMtx.Lock()
defer p.syncPeersMtx.Unlock()
for _, bin := range p.syncPeers {
for peer := range bin {
if addr.String() == peer {
return true
}
}
}
return false
}
// Copyright 2020 The Swarm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package puller_test
import (
"errors"
"io/ioutil"
"math"
"runtime"
"testing"
"time"
"github.com/ethersphere/bee/pkg/intervalstore"
mockk "github.com/ethersphere/bee/pkg/kademlia/mock"
"github.com/ethersphere/bee/pkg/logging"
"github.com/ethersphere/bee/pkg/puller"
mockps "github.com/ethersphere/bee/pkg/pullsync/mock"
"github.com/ethersphere/bee/pkg/statestore/mock"
"github.com/ethersphere/bee/pkg/storage"
"github.com/ethersphere/bee/pkg/swarm"
"github.com/ethersphere/bee/pkg/swarm/test"
)
const max = math.MaxUint64
var (
call = func(b uint8, f, t uint64) c {
return c{b: b, f: f, t: t}
}
reply = mockps.NewReply // alias to make code more readable
)
// test that adding one peer start syncing
// then that adding another peer at the same po
// does not start another syncing session
func TestOneSync(t *testing.T) {
defer func(b uint8, p int) {
*puller.Bins = b
*puller.ShallowBinPeers = p
}(*puller.Bins, *puller.ShallowBinPeers)
*puller.Bins = 3
*puller.ShallowBinPeers = 1
var (
addr = test.RandomAddress()
addr2 = test.RandomAddress()
cursors = []uint64{1000, 1000, 1000}
liveReplies = []uint64{1}
)
puller, _, kad, pullsync := newPuller(opts{
kad: []mockk.Option{
mockk.WithEachPeerRevCalls(
mockk.AddrTuple{Addr: addr, PO: 1},
mockk.AddrTuple{Addr: addr2, PO: 1},
), mockk.WithDepth(2),
},
pullSync: []mockps.Option{mockps.WithCursors(cursors), mockps.WithLiveSyncReplies(liveReplies...)},
})
defer puller.Close()
runtime.Gosched()
time.Sleep(10 * time.Millisecond)
kad.Trigger()
waitCursorsCalled(t, pullsync, addr, false)
waitCursorsCalled(t, pullsync, addr2, true)
waitSyncCalled(t, pullsync, addr, false)
waitSyncCalled(t, pullsync, addr2, true)
}
func TestSyncFlow_PeerOutsideDepth_Live(t *testing.T) {
defer func(b uint8) {
*puller.Bins = b
}(*puller.Bins)
*puller.Bins = 5
addr := test.RandomAddress()
for _, tc := range []struct {
name string // name of test
cursors []uint64 // mocked cursors to be exchanged from peer
liveReplies []uint64
intervals string // expected intervals on pivot
expCalls []c // expected historical sync calls
expLiveCalls []c // expected live sync calls
}{
{
name: "cursor 0, 1 chunk on live", cursors: []uint64{0, 0, 0},
intervals: "[[1 1]]",
liveReplies: []uint64{1},
expLiveCalls: []c{call(1, 1, max), call(1, 2, max)},
},
{
name: "cursor 0 - calls 1-1, 2-5, 6-10", cursors: []uint64{0, 0, 0},
intervals: "[[1 10]]",
liveReplies: []uint64{1, 5, 10},
expLiveCalls: []c{call(1, 1, max), call(1, 2, max), call(1, 6, max), call(1, 11, max)},
},
} {
t.Run(tc.name, func(t *testing.T) {
puller, st, kad, pullsync := newPuller(opts{
kad: []mockk.Option{
mockk.WithEachPeerRevCalls(
mockk.AddrTuple{Addr: addr, PO: 1},
), mockk.WithDepth(2),
},
pullSync: []mockps.Option{mockps.WithCursors(tc.cursors), mockps.WithLiveSyncReplies(tc.liveReplies...)},
})
t.Cleanup(func() {
pullsync.Close()
puller.Close()
})
runtime.Gosched()
time.Sleep(10 * time.Millisecond)
kad.Trigger()
waitCursorsCalled(t, pullsync, addr, false)
waitLiveSyncCalled(t, pullsync, addr, false)
checkCalls(t, tc.expCalls, pullsync.SyncCalls(addr)) // hist always empty
checkCalls(t, tc.expLiveCalls, pullsync.LiveSyncCalls(addr))
// check the intervals
checkIntervals(t, st, addr, tc.intervals, 1)
})
}
}
func TestSyncFlow_PeerOutsideDepth_Historical(t *testing.T) {
defer func(b uint8) {
*puller.Bins = b
}(*puller.Bins)
*puller.Bins = 5
addr := test.RandomAddress()
for _, tc := range []struct {
name string // name of test
cursors []uint64 // mocked cursors to be exchanged from peer
intervals string // expected intervals on pivot
expCalls []c // expected historical sync calls
expLiveCalls []c // expected live sync calls
}{
{
name: "1,1 - 1 call", cursors: []uint64{0, 1, 3}, //the third cursor is to make sure we dont get a request for a bin we dont need
intervals: "[[1 1]]",
expCalls: []c{call(1, 1, 1)},
expLiveCalls: []c{call(1, 2, math.MaxUint64)},
},
{
name: "1,10 - 1 call", cursors: []uint64{0, 10},
intervals: "[[1 10]]",
expCalls: []c{call(1, 1, 10)},
expLiveCalls: []c{call(1, 11, math.MaxUint64)},
},
{
name: "1,50 - 1 call", cursors: []uint64{0, 50},
intervals: "[[1 50]]",
expCalls: []c{call(1, 1, 50)},
expLiveCalls: []c{call(1, 51, math.MaxUint64)},
},
{
name: "1,51 - 2 calls", cursors: []uint64{0, 51},
intervals: "[[1 51]]",
expCalls: []c{call(1, 1, 51), call(1, 51, 51)},
expLiveCalls: []c{call(1, 52, math.MaxUint64)},
},
{
name: "1,100 - 2 calls", cursors: []uint64{130, 100},
intervals: "[[1 100]]",
expCalls: []c{call(1, 1, 100), call(1, 51, 100)},
expLiveCalls: []c{call(1, 101, math.MaxUint64)},
},
{
name: "1,200 - 4 calls", cursors: []uint64{130, 200},
intervals: "[[1 200]]",
expCalls: []c{call(1, 1, 200), call(1, 51, 200), call(1, 101, 200), call(1, 151, 200)},
expLiveCalls: []c{call(1, 201, math.MaxUint64)},
},
} {
t.Run(tc.name, func(t *testing.T) {
puller, st, kad, pullsync := newPuller(opts{
kad: []mockk.Option{
mockk.WithEachPeerRevCalls(
mockk.AddrTuple{Addr: addr, PO: 1},
), mockk.WithDepth(2),
},
pullSync: []mockps.Option{mockps.WithCursors(tc.cursors), mockps.WithAutoReply(), mockps.WithLiveSyncBlock()},
})
defer pullsync.Close()
defer puller.Close()
runtime.Gosched()
time.Sleep(10 * time.Millisecond)
kad.Trigger()
waitCursorsCalled(t, pullsync, addr, false)
waitSyncCalled(t, pullsync, addr, false)
// check historical sync calls
checkCalls(t, tc.expCalls, pullsync.SyncCalls(addr))
checkCalls(t, tc.expLiveCalls, pullsync.LiveSyncCalls(addr))
// check the intervals
checkIntervals(t, st, addr, tc.intervals, 1)
})
}
}
func TestSyncFlow_PeerWithinDepth_Live(t *testing.T) {
defer func(b uint8) {
*puller.Bins = b
}(*puller.Bins)
*puller.Bins = 5
addr := test.RandomAddress()
const max = math.MaxUint64
for _, tc := range []struct {
name string // name of test
cursors []uint64 // mocked cursors to be exchanged from peer
liveReplies []mockps.SyncReply
intervals string // expected intervals on pivot
expCalls []c // expected historical sync calls
expLiveCalls []c // expected live sync calls
}{
{
name: "cursor 0, 1 chunk on live", cursors: []uint64{0, 0, 0, 0, 0},
intervals: "[[1 1]]",
liveReplies: []mockps.SyncReply{reply(2, 1, 1, false), reply(2, 2, 0, true), reply(3, 1, 1, false), reply(3, 2, 0, true), reply(4, 1, 1, false), reply(4, 2, 0, true)},
expLiveCalls: []c{call(2, 1, max), call(2, 2, max), call(3, 1, max), call(3, 2, max), call(4, 1, max), call(4, 2, max)},
},
} {
t.Run(tc.name, func(t *testing.T) {
puller, st, kad, pullsync := newPuller(opts{
kad: []mockk.Option{
mockk.WithEachPeerRevCalls(
mockk.AddrTuple{Addr: addr, PO: 3}, // po is 3, depth is 2, so we're in depth
), mockk.WithDepth(2),
},
pullSync: []mockps.Option{mockps.WithCursors(tc.cursors), mockps.WithLateSyncReply(tc.liveReplies...)},
})
defer pullsync.Close()
defer puller.Close()
runtime.Gosched()
time.Sleep(100 * time.Millisecond)
kad.Trigger()
pullsync.TriggerChange()
waitCursorsCalled(t, pullsync, addr, false)
waitLiveSyncCalled(t, pullsync, addr, false)
time.Sleep(100 * time.Millisecond)
checkCalls(t, tc.expCalls, pullsync.SyncCalls(addr)) // hist always empty
checkCallsUnordered(t, tc.expLiveCalls, pullsync.LiveSyncCalls(addr))
// check the intervals
checkIntervals(t, st, addr, tc.intervals, 2)
})
}
}
func TestPeerDisconnected(t *testing.T) {
cursors := []uint64{0, 0}
addr := test.RandomAddress()
p, _, kad, pullsync := newPuller(opts{
kad: []mockk.Option{
mockk.WithEachPeerRevCalls(
mockk.AddrTuple{Addr: addr, PO: 1},
), mockk.WithDepthCalls(2, 2, 2), // peer moved from out of depth to depth
},
pullSync: []mockps.Option{mockps.WithCursors(cursors), mockps.WithLiveSyncBlock()},
})
t.Cleanup(func() {
p.Close()
pullsync.Close()
})
runtime.Gosched()
time.Sleep(50 * time.Millisecond)
kad.Trigger()
waitCursorsCalled(t, pullsync, addr, false)
waitLiveSyncCalled(t, pullsync, addr, false)
kad.ResetPeers()
kad.Trigger()
time.Sleep(50 * time.Millisecond)
if puller.IsSyncing(p, addr) {
t.Fatalf("peer is syncing but shouldnt")
}
}
func TestDepthChange(t *testing.T) {
defer func(b uint8) {
*puller.Bins = b
}(*puller.Bins)
*puller.Bins = 5
var (
addr = test.RandomAddress()
interval = "[[1 1]]"
)
for _, tc := range []struct {
name string
cursors []uint64
binsSyncing []uint8
binsNotSyncing []uint8
syncReplies []mockps.SyncReply
depths []uint8
}{
{
name: "move peer around", //moves the peer from depth outside of depth then back into depth
cursors: []uint64{0, 0, 0, 0, 0},
binsSyncing: []uint8{3, 4}, binsNotSyncing: []uint8{1, 2},
syncReplies: []mockps.SyncReply{
reply(3, 1, 1, false),
reply(3, 2, 1, true),
reply(4, 1, 1, false),
reply(4, 2, 1, true),
},
depths: []uint8{0, 1, 2, 3, 4, 4, 0, 3},
},
{
name: "peer moves out of depth then back in",
cursors: []uint64{0, 0, 0, 0, 0},
binsNotSyncing: []uint8{1, 2}, // only bins 3,4 are expected to sync
binsSyncing: []uint8{3, 4},
syncReplies: []mockps.SyncReply{
reply(3, 1, 1, false),
reply(3, 2, 1, true),
reply(4, 1, 1, false),
reply(4, 2, 1, true),
},
depths: []uint8{0, 1, 2, 3, 4, 3},
},
{
name: "peer moves out of depth",
cursors: []uint64{0, 0, 0, 0, 0},
binsNotSyncing: []uint8{1, 2, 4}, // only bins 3,4 are expected to sync
binsSyncing: []uint8{3},
syncReplies: []mockps.SyncReply{
reply(3, 1, 1, false),
reply(3, 2, 1, true),
},
depths: []uint8{0, 1, 2, 3, 4},
},
{
name: "peer moves within depth",
cursors: []uint64{0, 0, 0, 0, 0},
binsNotSyncing: []uint8{1, 2}, // only bins 3,4 are expected to sync
binsSyncing: []uint8{3, 4},
depths: []uint8{0, 1, 2, 3},
syncReplies: []mockps.SyncReply{
reply(3, 1, 1, false),
reply(3, 2, 1, true),
reply(4, 1, 1, false),
reply(4, 2, 1, true),
},
},
} {
t.Run(tc.name, func(t *testing.T) {
puller, st, kad, pullsync := newPuller(opts{
kad: []mockk.Option{
mockk.WithEachPeerRevCalls(
mockk.AddrTuple{Addr: addr, PO: 3},
), mockk.WithDepthCalls(tc.depths...), // peer moved from out of depth to depth
},
pullSync: []mockps.Option{mockps.WithCursors(tc.cursors), mockps.WithLateSyncReply(tc.syncReplies...)},
})
defer pullsync.Close()
defer puller.Close()
runtime.Gosched()
time.Sleep(100 * time.Millisecond)
for i := 0; i < len(tc.depths)-1; i++ {
kad.Trigger()
time.Sleep(100 * time.Millisecond)
}
pullsync.TriggerChange()
time.Sleep(100 * time.Millisecond)
// check the intervals
for _, b := range tc.binsSyncing {
checkIntervals(t, st, addr, interval, b) // getting errors here
}
for _, b := range tc.binsNotSyncing {
checkNotFound(t, st, addr, b)
}
})
}
}
func checkIntervals(t *testing.T, s storage.StateStorer, addr swarm.Address, expInterval string, bin uint8) {
t.Helper()
key := puller.PeerIntervalKey(addr, bin)
i := &intervalstore.Intervals{}
err := s.Get(key, i)
if err != nil {
t.Fatalf("error getting interval for bin %d: %v", bin, err)
}
if v := i.String(); v != expInterval {
t.Fatalf("got unexpected interval: %s, want %s bin %d", v, expInterval, bin)
}
}
func checkNotFound(t *testing.T, s storage.StateStorer, addr swarm.Address, bin uint8) {
t.Helper()
key := puller.PeerIntervalKey(addr, bin)
i := &intervalstore.Intervals{}
err := s.Get(key, i)
if err != nil {
if !errors.Is(err, storage.ErrNotFound) {
t.Fatal(err)
}
return
}
if i.String() == "[]" {
return
}
t.Fatalf("wanted error but got none. bin %d", bin)
}
func checkCalls(t *testing.T, expCalls []c, calls []mockps.SyncCall) {
t.Helper()
exp := len(expCalls)
if l := len(calls); l != exp {
t.Fatalf("expected %d calls but got %d. calls: %v", exp, l, calls)
}
// check the calls
for i, v := range expCalls {
if b := calls[i].Bin; b != v.b {
t.Errorf("bin mismatch. got %d want %d index %d", b, v.b, i)
}
if f := calls[i].From; f != v.f {
t.Errorf("from mismatch. got %d want %d index %d", f, v.f, i)
}
if tt := calls[i].To; tt != v.t {
t.Errorf("to mismatch. got %d want %d index %d", tt, v.t, i)
}
}
}
// this is needed since there are several goroutines checking the calls,
// so the call list in the test is no longer expected to be in order
func checkCallsUnordered(t *testing.T, expCalls []c, calls []mockps.SyncCall) {
t.Helper()
exp := len(expCalls)
if l := len(calls); l != exp {
t.Fatalf("expected %d calls but got %d. calls: %v", exp, l, calls)
}
isIn := func(vv c, calls []mockps.SyncCall) bool {
for _, v := range calls {
if v.Bin == vv.b && v.From == vv.f && v.To == vv.t {
return true
}
}
return false
}
for i, v := range expCalls {
if !isIn(v, calls) {
t.Fatalf("call %d not found", i)
}
}
}
// waitSyncCalled waits until SyncInterval is called on the address given.
func waitCursorsCalled(t *testing.T, ps *mockps.PullSyncMock, addr swarm.Address, invert bool) {
t.Helper()
for i := 0; i < 20; i++ {
if v := ps.CursorsCalls(addr); v {
if invert {
t.Fatal("got a call to sync to a peer but shouldnt")
} else {
return
}
}
time.Sleep(50 * time.Millisecond)
}
if invert {
return
}
t.Fatal("timed out waiting for cursors")
}
// waitLiveSyncCalled waits until SyncInterval is called on the address given.
func waitLiveSyncCalled(t *testing.T, ps *mockps.PullSyncMock, addr swarm.Address, invert bool) {
t.Helper()
for i := 0; i < 15; i++ {
v := ps.LiveSyncCalls(addr)
if len(v) > 0 {
if invert {
t.Fatal("got a call to sync to a peer but shouldnt")
} else {
return
}
}
time.Sleep(50 * time.Millisecond)
}
if invert {
return
}
t.Fatal("timed out waiting for sync")
}
// waitSyncCalled waits until SyncInterval is called on the address given.
func waitSyncCalled(t *testing.T, ps *mockps.PullSyncMock, addr swarm.Address, invert bool) {
t.Helper()
for i := 0; i < 15; i++ {
v := ps.SyncCalls(addr)
if len(v) > 0 {
if invert {
t.Fatal("got a call to sync to a peer but shouldnt")
} else {
return
}
}
time.Sleep(50 * time.Millisecond)
}
if invert {
return
}
t.Fatal("timed out waiting for sync")
}
type opts struct {
pullSync []mockps.Option
kad []mockk.Option
}
func newPuller(ops opts) (*puller.Puller, storage.StateStorer, *mockk.Mock, *mockps.PullSyncMock) {
s := mock.NewStateStore()
ps := mockps.NewPullSync(ops.pullSync...)
kad := mockk.NewMockKademlia(ops.kad...)
logger := logging.New(ioutil.Discard, 6)
o := puller.Options{
Topology: kad,
StateStore: s,
PullSync: ps,
Logger: logger,
}
return puller.New(o), s, kad, ps
}
type c struct {
b uint8 //bin
f, t uint64 //from, to
}
// Copyright 2020 The Swarm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package pullsync
// Copyright 2020 The Swarm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package pullsync
// Copyright 2020 The Swarm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package mock
import (
"context"
"io"
"math"
"sync"
"github.com/ethersphere/bee/pkg/pullsync"
"github.com/ethersphere/bee/pkg/swarm"
)
var _ pullsync.Interface = (*PullSyncMock)(nil)
func WithCursors(v []uint64) Option {
return optionFunc(func(p *PullSyncMock) {
p.cursors = v
})
}
// WithAutoReply means that the pull syncer will automatically reply
// to incoming range requests with a top = from+limit.
// This is in order to force the requester to request a subsequent range.
func WithAutoReply() Option {
return optionFunc(func(p *PullSyncMock) {
p.autoReply = true
})
}
// WithLiveSyncBlock makes the protocol mock block on incoming live
// sync requests (identified by the math.MaxUint64 `to` field).
func WithLiveSyncBlock() Option {
return optionFunc(func(p *PullSyncMock) {
p.blockLiveSync = true
})
}
func WithLiveSyncReplies(r ...uint64) Option {
return optionFunc(func(p *PullSyncMock) {
p.liveSyncReplies = r
})
}
func WithLateSyncReply(r ...SyncReply) Option {
return optionFunc(func(p *PullSyncMock) {
p.lateReply = true
p.lateSyncReplies = r
})
}
const limit = 50
type SyncCall struct {
Peer swarm.Address
Bin uint8
From, To uint64
Live bool
}
type SyncReply struct {
bin uint8
from uint64
topmost uint64
block bool
}
func NewReply(bin uint8, from, top uint64, block bool) SyncReply {
return SyncReply{
bin: bin,
from: from,
topmost: top,
block: block,
}
}
type PullSyncMock struct {
mtx sync.Mutex
syncCalls []SyncCall
cursors []uint64
getCursorsPeers []swarm.Address
autoReply bool
blockLiveSync bool
liveSyncReplies []uint64
liveSyncCalls int
lateReply bool
lateCond *sync.Cond
lateChange bool
lateSyncReplies []SyncReply
quit chan struct{}
}
func NewPullSync(opts ...Option) *PullSyncMock {
s := &PullSyncMock{
lateCond: sync.NewCond(new(sync.Mutex)),
quit: make(chan struct{}),
}
for _, v := range opts {
v.apply(s)
}
return s
}
func (p *PullSyncMock) SyncInterval(ctx context.Context, peer swarm.Address, bin uint8, from, to uint64) (topmost uint64, err error) {
isLive := to == math.MaxUint64
call := SyncCall{
Peer: peer,
Bin: bin,
From: from,
To: to,
Live: isLive,
}
p.mtx.Lock()
p.syncCalls = append(p.syncCalls, call)
p.mtx.Unlock()
if isLive && p.lateReply {
p.lateCond.L.Lock()
for !p.lateChange {
p.lateCond.Wait()
}
p.lateCond.L.Unlock()
select {
case <-p.quit:
return 0, context.Canceled
case <-ctx.Done():
return 0, ctx.Err()
default:
}
found := false
var sr SyncReply
p.mtx.Lock()
for i, v := range p.lateSyncReplies {
if v.bin == bin && v.from == from {
sr = v
found = true
p.lateSyncReplies = append(p.lateSyncReplies[:i], p.lateSyncReplies[i+1:]...)
}
}
p.mtx.Unlock()
if found {
if sr.block {
select {
case <-p.quit:
return 0, context.Canceled
case <-ctx.Done():
return 0, ctx.Err()
}
}
return sr.topmost, nil
}
panic("not found")
}
if isLive && p.blockLiveSync {
// don't respond, wait for quit
<-p.quit
return 0, io.EOF
}
if isLive && len(p.liveSyncReplies) > 0 {
if p.liveSyncCalls >= len(p.liveSyncReplies) {
<-p.quit
return
}
p.mtx.Lock()
v := p.liveSyncReplies[p.liveSyncCalls]
p.liveSyncCalls++
p.mtx.Unlock()
return v, nil
}
if p.autoReply {
t := from + limit - 1
// floor to the cursor
if t > to {
t = to
}
return t, nil
}
return to, nil
}
func (p *PullSyncMock) GetCursors(_ context.Context, peer swarm.Address) ([]uint64, error) {
p.mtx.Lock()
defer p.mtx.Unlock()
p.getCursorsPeers = append(p.getCursorsPeers, peer)
return p.cursors, nil
}
func (p *PullSyncMock) SyncCalls(peer swarm.Address) (res []SyncCall) {
p.mtx.Lock()
defer p.mtx.Unlock()
for _, v := range p.syncCalls {
if v.Peer.Equal(peer) && !v.Live {
res = append(res, v)
}
}
return res
}
func (p *PullSyncMock) LiveSyncCalls(peer swarm.Address) (res []SyncCall) {
p.mtx.Lock()
defer p.mtx.Unlock()
for _, v := range p.syncCalls {
if v.Peer.Equal(peer) && v.Live {
res = append(res, v)
}
}
return res
}
func (p *PullSyncMock) CursorsCalls(peer swarm.Address) bool {
p.mtx.Lock()
defer p.mtx.Unlock()
for _, v := range p.getCursorsPeers {
if v.Equal(peer) {
return true
}
}
return false
}
func (p *PullSyncMock) TriggerChange() {
p.lateCond.L.Lock()
p.lateChange = true
p.lateCond.L.Unlock()
p.lateCond.Broadcast()
}
func (p *PullSyncMock) Close() error {
close(p.quit)
p.lateCond.L.Lock()
p.lateChange = true
p.lateCond.L.Unlock()
p.lateCond.Broadcast()
return nil
}
type Option interface {
apply(*PullSyncMock)
}
type optionFunc func(*PullSyncMock)
func (f optionFunc) apply(r *PullSyncMock) { f(r) }
// Copyright 2020 The Swarm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:generate sh -c "protoc -I . -I \"$(go list -f '{{ .Dir }}' -m github.com/gogo/protobuf)/protobuf\" --gogofaster_out=. pullsync.proto"
package pb
// Code generated by protoc-gen-gogo. DO NOT EDIT.
// source: pullsync.proto
package pb
import (
fmt "fmt"
proto "github.com/gogo/protobuf/proto"
io "io"
math "math"
math_bits "math/bits"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
type Syn struct {
}
func (m *Syn) Reset() { *m = Syn{} }
func (m *Syn) String() string { return proto.CompactTextString(m) }
func (*Syn) ProtoMessage() {}
func (*Syn) Descriptor() ([]byte, []int) {
return fileDescriptor_d1dee042cf9c065c, []int{0}
}
func (m *Syn) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
}
func (m *Syn) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
if deterministic {
return xxx_messageInfo_Syn.Marshal(b, m, deterministic)
} else {
b = b[:cap(b)]
n, err := m.MarshalToSizedBuffer(b)
if err != nil {
return nil, err
}
return b[:n], nil
}
}
func (m *Syn) XXX_Merge(src proto.Message) {
xxx_messageInfo_Syn.Merge(m, src)
}
func (m *Syn) XXX_Size() int {
return m.Size()
}
func (m *Syn) XXX_DiscardUnknown() {
xxx_messageInfo_Syn.DiscardUnknown(m)
}
var xxx_messageInfo_Syn proto.InternalMessageInfo
type Ack struct {
Cursors []uint64 `protobuf:"varint,1,rep,packed,name=Cursors,proto3" json:"Cursors,omitempty"`
}
func (m *Ack) Reset() { *m = Ack{} }
func (m *Ack) String() string { return proto.CompactTextString(m) }
func (*Ack) ProtoMessage() {}
func (*Ack) Descriptor() ([]byte, []int) {
return fileDescriptor_d1dee042cf9c065c, []int{1}
}
func (m *Ack) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
}
func (m *Ack) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
if deterministic {
return xxx_messageInfo_Ack.Marshal(b, m, deterministic)
} else {
b = b[:cap(b)]
n, err := m.MarshalToSizedBuffer(b)
if err != nil {
return nil, err
}
return b[:n], nil
}
}
func (m *Ack) XXX_Merge(src proto.Message) {
xxx_messageInfo_Ack.Merge(m, src)
}
func (m *Ack) XXX_Size() int {
return m.Size()
}
func (m *Ack) XXX_DiscardUnknown() {
xxx_messageInfo_Ack.DiscardUnknown(m)
}
var xxx_messageInfo_Ack proto.InternalMessageInfo
func (m *Ack) GetCursors() []uint64 {
if m != nil {
return m.Cursors
}
return nil
}
type GetRange struct {
Bin int32 `protobuf:"varint,1,opt,name=Bin,proto3" json:"Bin,omitempty"`
From uint64 `protobuf:"varint,2,opt,name=From,proto3" json:"From,omitempty"`
To uint64 `protobuf:"varint,3,opt,name=To,proto3" json:"To,omitempty"`
}
func (m *GetRange) Reset() { *m = GetRange{} }
func (m *GetRange) String() string { return proto.CompactTextString(m) }
func (*GetRange) ProtoMessage() {}
func (*GetRange) Descriptor() ([]byte, []int) {
return fileDescriptor_d1dee042cf9c065c, []int{2}
}
func (m *GetRange) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
}
func (m *GetRange) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
if deterministic {
return xxx_messageInfo_GetRange.Marshal(b, m, deterministic)
} else {
b = b[:cap(b)]
n, err := m.MarshalToSizedBuffer(b)
if err != nil {
return nil, err
}
return b[:n], nil
}
}
func (m *GetRange) XXX_Merge(src proto.Message) {
xxx_messageInfo_GetRange.Merge(m, src)
}
func (m *GetRange) XXX_Size() int {
return m.Size()
}
func (m *GetRange) XXX_DiscardUnknown() {
xxx_messageInfo_GetRange.DiscardUnknown(m)
}
var xxx_messageInfo_GetRange proto.InternalMessageInfo
func (m *GetRange) GetBin() int32 {
if m != nil {
return m.Bin
}
return 0
}
func (m *GetRange) GetFrom() uint64 {
if m != nil {
return m.From
}
return 0
}
func (m *GetRange) GetTo() uint64 {
if m != nil {
return m.To
}
return 0
}
type Offer struct {
Topmost uint64 `protobuf:"varint,1,opt,name=Topmost,proto3" json:"Topmost,omitempty"`
Hashes []byte `protobuf:"bytes,2,opt,name=Hashes,proto3" json:"Hashes,omitempty"`
}
func (m *Offer) Reset() { *m = Offer{} }
func (m *Offer) String() string { return proto.CompactTextString(m) }
func (*Offer) ProtoMessage() {}
func (*Offer) Descriptor() ([]byte, []int) {
return fileDescriptor_d1dee042cf9c065c, []int{3}
}
func (m *Offer) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
}
func (m *Offer) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
if deterministic {
return xxx_messageInfo_Offer.Marshal(b, m, deterministic)
} else {
b = b[:cap(b)]
n, err := m.MarshalToSizedBuffer(b)
if err != nil {
return nil, err
}
return b[:n], nil
}
}
func (m *Offer) XXX_Merge(src proto.Message) {
xxx_messageInfo_Offer.Merge(m, src)
}
func (m *Offer) XXX_Size() int {
return m.Size()
}
func (m *Offer) XXX_DiscardUnknown() {
xxx_messageInfo_Offer.DiscardUnknown(m)
}
var xxx_messageInfo_Offer proto.InternalMessageInfo
func (m *Offer) GetTopmost() uint64 {
if m != nil {
return m.Topmost
}
return 0
}
func (m *Offer) GetHashes() []byte {
if m != nil {
return m.Hashes
}
return nil
}
type Want struct {
BitVector []byte `protobuf:"bytes,1,opt,name=BitVector,proto3" json:"BitVector,omitempty"`
}
func (m *Want) Reset() { *m = Want{} }
func (m *Want) String() string { return proto.CompactTextString(m) }
func (*Want) ProtoMessage() {}
func (*Want) Descriptor() ([]byte, []int) {
return fileDescriptor_d1dee042cf9c065c, []int{4}
}
func (m *Want) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
}
func (m *Want) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
if deterministic {
return xxx_messageInfo_Want.Marshal(b, m, deterministic)
} else {
b = b[:cap(b)]
n, err := m.MarshalToSizedBuffer(b)
if err != nil {
return nil, err
}
return b[:n], nil
}
}
func (m *Want) XXX_Merge(src proto.Message) {
xxx_messageInfo_Want.Merge(m, src)
}
func (m *Want) XXX_Size() int {
return m.Size()
}
func (m *Want) XXX_DiscardUnknown() {
xxx_messageInfo_Want.DiscardUnknown(m)
}
var xxx_messageInfo_Want proto.InternalMessageInfo
func (m *Want) GetBitVector() []byte {
if m != nil {
return m.BitVector
}
return nil
}
type Delivery struct {
Address []byte `protobuf:"bytes,1,opt,name=Address,proto3" json:"Address,omitempty"`
Data []byte `protobuf:"bytes,2,opt,name=Data,proto3" json:"Data,omitempty"`
}
func (m *Delivery) Reset() { *m = Delivery{} }
func (m *Delivery) String() string { return proto.CompactTextString(m) }
func (*Delivery) ProtoMessage() {}
func (*Delivery) Descriptor() ([]byte, []int) {
return fileDescriptor_d1dee042cf9c065c, []int{5}
}
func (m *Delivery) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
}
func (m *Delivery) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
if deterministic {
return xxx_messageInfo_Delivery.Marshal(b, m, deterministic)
} else {
b = b[:cap(b)]
n, err := m.MarshalToSizedBuffer(b)
if err != nil {
return nil, err
}
return b[:n], nil
}
}
func (m *Delivery) XXX_Merge(src proto.Message) {
xxx_messageInfo_Delivery.Merge(m, src)
}
func (m *Delivery) XXX_Size() int {
return m.Size()
}
func (m *Delivery) XXX_DiscardUnknown() {
xxx_messageInfo_Delivery.DiscardUnknown(m)
}
var xxx_messageInfo_Delivery proto.InternalMessageInfo
func (m *Delivery) GetAddress() []byte {
if m != nil {
return m.Address
}
return nil
}
func (m *Delivery) GetData() []byte {
if m != nil {
return m.Data
}
return nil
}
func init() {
proto.RegisterType((*Syn)(nil), "pullsync.Syn")
proto.RegisterType((*Ack)(nil), "pullsync.Ack")
proto.RegisterType((*GetRange)(nil), "pullsync.GetRange")
proto.RegisterType((*Offer)(nil), "pullsync.Offer")
proto.RegisterType((*Want)(nil), "pullsync.Want")
proto.RegisterType((*Delivery)(nil), "pullsync.Delivery")
}
func init() { proto.RegisterFile("pullsync.proto", fileDescriptor_d1dee042cf9c065c) }
var fileDescriptor_d1dee042cf9c065c = []byte{
// 270 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x44, 0x90, 0x3f, 0x4f, 0xc3, 0x30,
0x10, 0xc5, 0xeb, 0xfc, 0x29, 0xe1, 0x54, 0x55, 0xc8, 0x03, 0xca, 0x50, 0x99, 0xc8, 0x62, 0xc8,
0xc4, 0xc2, 0x02, 0x1b, 0x0d, 0x15, 0xb0, 0x21, 0x99, 0x08, 0x24, 0x36, 0x37, 0x75, 0x21, 0x22,
0xb5, 0x23, 0xdb, 0x45, 0xca, 0xb7, 0xe0, 0x63, 0x31, 0x76, 0x64, 0x44, 0xc9, 0x17, 0x41, 0xb1,
0x12, 0xb1, 0xbd, 0xdf, 0xd3, 0xdd, 0x7b, 0xa7, 0x83, 0x79, 0xbd, 0xaf, 0x2a, 0xd3, 0xc8, 0xe2,
0xa2, 0xd6, 0xca, 0x2a, 0x1c, 0x8d, 0x4c, 0x43, 0xf0, 0x9f, 0x1a, 0x49, 0xcf, 0xc0, 0x5f, 0x16,
0x1f, 0x38, 0x86, 0xa3, 0xdb, 0xbd, 0x36, 0x4a, 0x9b, 0x18, 0x25, 0x7e, 0x1a, 0xb0, 0x11, 0xe9,
0x0d, 0x44, 0xf7, 0xc2, 0x32, 0x2e, 0xdf, 0x04, 0x3e, 0x01, 0x3f, 0x2b, 0x65, 0x8c, 0x12, 0x94,
0x86, 0xac, 0x97, 0x18, 0x43, 0x70, 0xa7, 0xd5, 0x2e, 0xf6, 0x12, 0x94, 0x06, 0xcc, 0x69, 0x3c,
0x07, 0x2f, 0x57, 0xb1, 0xef, 0x1c, 0x2f, 0x57, 0xf4, 0x1a, 0xc2, 0xc7, 0xed, 0x56, 0xe8, 0xbe,
0x24, 0x57, 0xf5, 0x4e, 0x19, 0xeb, 0x22, 0x02, 0x36, 0x22, 0x3e, 0x85, 0xe9, 0x03, 0x37, 0xef,
0xc2, 0xb8, 0xa0, 0x19, 0x1b, 0x88, 0x9e, 0x43, 0xf0, 0xc2, 0xa5, 0xc5, 0x0b, 0x38, 0xce, 0x4a,
0xfb, 0x2c, 0x0a, 0xab, 0xb4, 0xdb, 0x9d, 0xb1, 0x7f, 0x83, 0x5e, 0x41, 0xb4, 0x12, 0x55, 0xf9,
0x29, 0x74, 0xd3, 0x77, 0x2c, 0x37, 0x1b, 0x2d, 0x8c, 0x19, 0xe6, 0x46, 0xec, 0x4f, 0x5d, 0x71,
0xcb, 0x87, 0x06, 0xa7, 0xb3, 0xc5, 0x77, 0x4b, 0xd0, 0xa1, 0x25, 0xe8, 0xb7, 0x25, 0xe8, 0xab,
0x23, 0x93, 0x43, 0x47, 0x26, 0x3f, 0x1d, 0x99, 0xbc, 0x7a, 0xf5, 0x7a, 0x3d, 0x75, 0x3f, 0xbb,
0xfc, 0x0b, 0x00, 0x00, 0xff, 0xff, 0xa5, 0x17, 0x82, 0xda, 0x45, 0x01, 0x00, 0x00,
}
func (m *Syn) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBuffer(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *Syn) MarshalTo(dAtA []byte) (int, error) {
size := m.Size()
return m.MarshalToSizedBuffer(dAtA[:size])
}
func (m *Syn) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i := len(dAtA)
_ = i
var l int
_ = l
return len(dAtA) - i, nil
}
func (m *Ack) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBuffer(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *Ack) MarshalTo(dAtA []byte) (int, error) {
size := m.Size()
return m.MarshalToSizedBuffer(dAtA[:size])
}
func (m *Ack) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i := len(dAtA)
_ = i
var l int
_ = l
if len(m.Cursors) > 0 {
dAtA2 := make([]byte, len(m.Cursors)*10)
var j1 int
for _, num := range m.Cursors {
for num >= 1<<7 {
dAtA2[j1] = uint8(uint64(num)&0x7f | 0x80)
num >>= 7
j1++
}
dAtA2[j1] = uint8(num)
j1++
}
i -= j1
copy(dAtA[i:], dAtA2[:j1])
i = encodeVarintPullsync(dAtA, i, uint64(j1))
i--
dAtA[i] = 0xa
}
return len(dAtA) - i, nil
}
func (m *GetRange) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBuffer(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *GetRange) MarshalTo(dAtA []byte) (int, error) {
size := m.Size()
return m.MarshalToSizedBuffer(dAtA[:size])
}
func (m *GetRange) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i := len(dAtA)
_ = i
var l int
_ = l
if m.To != 0 {
i = encodeVarintPullsync(dAtA, i, uint64(m.To))
i--
dAtA[i] = 0x18
}
if m.From != 0 {
i = encodeVarintPullsync(dAtA, i, uint64(m.From))
i--
dAtA[i] = 0x10
}
if m.Bin != 0 {
i = encodeVarintPullsync(dAtA, i, uint64(m.Bin))
i--
dAtA[i] = 0x8
}
return len(dAtA) - i, nil
}
func (m *Offer) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBuffer(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *Offer) MarshalTo(dAtA []byte) (int, error) {
size := m.Size()
return m.MarshalToSizedBuffer(dAtA[:size])
}
func (m *Offer) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i := len(dAtA)
_ = i
var l int
_ = l
if len(m.Hashes) > 0 {
i -= len(m.Hashes)
copy(dAtA[i:], m.Hashes)
i = encodeVarintPullsync(dAtA, i, uint64(len(m.Hashes)))
i--
dAtA[i] = 0x12
}
if m.Topmost != 0 {
i = encodeVarintPullsync(dAtA, i, uint64(m.Topmost))
i--
dAtA[i] = 0x8
}
return len(dAtA) - i, nil
}
func (m *Want) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBuffer(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *Want) MarshalTo(dAtA []byte) (int, error) {
size := m.Size()
return m.MarshalToSizedBuffer(dAtA[:size])
}
func (m *Want) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i := len(dAtA)
_ = i
var l int
_ = l
if len(m.BitVector) > 0 {
i -= len(m.BitVector)
copy(dAtA[i:], m.BitVector)
i = encodeVarintPullsync(dAtA, i, uint64(len(m.BitVector)))
i--
dAtA[i] = 0xa
}
return len(dAtA) - i, nil
}
func (m *Delivery) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBuffer(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *Delivery) MarshalTo(dAtA []byte) (int, error) {
size := m.Size()
return m.MarshalToSizedBuffer(dAtA[:size])
}
func (m *Delivery) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i := len(dAtA)
_ = i
var l int
_ = l
if len(m.Data) > 0 {
i -= len(m.Data)
copy(dAtA[i:], m.Data)
i = encodeVarintPullsync(dAtA, i, uint64(len(m.Data)))
i--
dAtA[i] = 0x12
}
if len(m.Address) > 0 {
i -= len(m.Address)
copy(dAtA[i:], m.Address)
i = encodeVarintPullsync(dAtA, i, uint64(len(m.Address)))
i--
dAtA[i] = 0xa
}
return len(dAtA) - i, nil
}
func encodeVarintPullsync(dAtA []byte, offset int, v uint64) int {
offset -= sovPullsync(v)
base := offset
for v >= 1<<7 {
dAtA[offset] = uint8(v&0x7f | 0x80)
v >>= 7
offset++
}
dAtA[offset] = uint8(v)
return base
}
func (m *Syn) Size() (n int) {
if m == nil {
return 0
}
var l int
_ = l
return n
}
func (m *Ack) Size() (n int) {
if m == nil {
return 0
}
var l int
_ = l
if len(m.Cursors) > 0 {
l = 0
for _, e := range m.Cursors {
l += sovPullsync(uint64(e))
}
n += 1 + sovPullsync(uint64(l)) + l
}
return n
}
func (m *GetRange) Size() (n int) {
if m == nil {
return 0
}
var l int
_ = l
if m.Bin != 0 {
n += 1 + sovPullsync(uint64(m.Bin))
}
if m.From != 0 {
n += 1 + sovPullsync(uint64(m.From))
}
if m.To != 0 {
n += 1 + sovPullsync(uint64(m.To))
}
return n
}
func (m *Offer) Size() (n int) {
if m == nil {
return 0
}
var l int
_ = l
if m.Topmost != 0 {
n += 1 + sovPullsync(uint64(m.Topmost))
}
l = len(m.Hashes)
if l > 0 {
n += 1 + l + sovPullsync(uint64(l))
}
return n
}
func (m *Want) Size() (n int) {
if m == nil {
return 0
}
var l int
_ = l
l = len(m.BitVector)
if l > 0 {
n += 1 + l + sovPullsync(uint64(l))
}
return n
}
func (m *Delivery) Size() (n int) {
if m == nil {
return 0
}
var l int
_ = l
l = len(m.Address)
if l > 0 {
n += 1 + l + sovPullsync(uint64(l))
}
l = len(m.Data)
if l > 0 {
n += 1 + l + sovPullsync(uint64(l))
}
return n
}
func sovPullsync(x uint64) (n int) {
return (math_bits.Len64(x|1) + 6) / 7
}
func sozPullsync(x uint64) (n int) {
return sovPullsync(uint64((x << 1) ^ uint64((int64(x) >> 63))))
}
func (m *Syn) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowPullsync
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: Syn: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: Syn: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
default:
iNdEx = preIndex
skippy, err := skipPullsync(dAtA[iNdEx:])
if err != nil {
return err
}
if skippy < 0 {
return ErrInvalidLengthPullsync
}
if (iNdEx + skippy) < 0 {
return ErrInvalidLengthPullsync
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func (m *Ack) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowPullsync
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: Ack: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: Ack: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType == 0 {
var v uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowPullsync
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
v |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
m.Cursors = append(m.Cursors, v)
} else if wireType == 2 {
var packedLen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowPullsync
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
packedLen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if packedLen < 0 {
return ErrInvalidLengthPullsync
}
postIndex := iNdEx + packedLen
if postIndex < 0 {
return ErrInvalidLengthPullsync
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
var elementCount int
var count int
for _, integer := range dAtA[iNdEx:postIndex] {
if integer < 128 {
count++
}
}
elementCount = count
if elementCount != 0 && len(m.Cursors) == 0 {
m.Cursors = make([]uint64, 0, elementCount)
}
for iNdEx < postIndex {
var v uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowPullsync
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
v |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
m.Cursors = append(m.Cursors, v)
}
} else {
return fmt.Errorf("proto: wrong wireType = %d for field Cursors", wireType)
}
default:
iNdEx = preIndex
skippy, err := skipPullsync(dAtA[iNdEx:])
if err != nil {
return err
}
if skippy < 0 {
return ErrInvalidLengthPullsync
}
if (iNdEx + skippy) < 0 {
return ErrInvalidLengthPullsync
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func (m *GetRange) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowPullsync
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: GetRange: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: GetRange: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field Bin", wireType)
}
m.Bin = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowPullsync
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.Bin |= int32(b&0x7F) << shift
if b < 0x80 {
break
}
}
case 2:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field From", wireType)
}
m.From = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowPullsync
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.From |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
case 3:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field To", wireType)
}
m.To = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowPullsync
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.To |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
default:
iNdEx = preIndex
skippy, err := skipPullsync(dAtA[iNdEx:])
if err != nil {
return err
}
if skippy < 0 {
return ErrInvalidLengthPullsync
}
if (iNdEx + skippy) < 0 {
return ErrInvalidLengthPullsync
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func (m *Offer) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowPullsync
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: Offer: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: Offer: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field Topmost", wireType)
}
m.Topmost = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowPullsync
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.Topmost |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
case 2:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Hashes", wireType)
}
var byteLen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowPullsync
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
byteLen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if byteLen < 0 {
return ErrInvalidLengthPullsync
}
postIndex := iNdEx + byteLen
if postIndex < 0 {
return ErrInvalidLengthPullsync
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Hashes = append(m.Hashes[:0], dAtA[iNdEx:postIndex]...)
if m.Hashes == nil {
m.Hashes = []byte{}
}
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipPullsync(dAtA[iNdEx:])
if err != nil {
return err
}
if skippy < 0 {
return ErrInvalidLengthPullsync
}
if (iNdEx + skippy) < 0 {
return ErrInvalidLengthPullsync
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func (m *Want) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowPullsync
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: Want: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: Want: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field BitVector", wireType)
}
var byteLen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowPullsync
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
byteLen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if byteLen < 0 {
return ErrInvalidLengthPullsync
}
postIndex := iNdEx + byteLen
if postIndex < 0 {
return ErrInvalidLengthPullsync
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.BitVector = append(m.BitVector[:0], dAtA[iNdEx:postIndex]...)
if m.BitVector == nil {
m.BitVector = []byte{}
}
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipPullsync(dAtA[iNdEx:])
if err != nil {
return err
}
if skippy < 0 {
return ErrInvalidLengthPullsync
}
if (iNdEx + skippy) < 0 {
return ErrInvalidLengthPullsync
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func (m *Delivery) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowPullsync
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: Delivery: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: Delivery: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Address", wireType)
}
var byteLen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowPullsync
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
byteLen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if byteLen < 0 {
return ErrInvalidLengthPullsync
}
postIndex := iNdEx + byteLen
if postIndex < 0 {
return ErrInvalidLengthPullsync
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Address = append(m.Address[:0], dAtA[iNdEx:postIndex]...)
if m.Address == nil {
m.Address = []byte{}
}
iNdEx = postIndex
case 2:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Data", wireType)
}
var byteLen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowPullsync
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
byteLen |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
if byteLen < 0 {
return ErrInvalidLengthPullsync
}
postIndex := iNdEx + byteLen
if postIndex < 0 {
return ErrInvalidLengthPullsync
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Data = append(m.Data[:0], dAtA[iNdEx:postIndex]...)
if m.Data == nil {
m.Data = []byte{}
}
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipPullsync(dAtA[iNdEx:])
if err != nil {
return err
}
if skippy < 0 {
return ErrInvalidLengthPullsync
}
if (iNdEx + skippy) < 0 {
return ErrInvalidLengthPullsync
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func skipPullsync(dAtA []byte) (n int, err error) {
l := len(dAtA)
iNdEx := 0
depth := 0
for iNdEx < l {
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowPullsync
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
wireType := int(wire & 0x7)
switch wireType {
case 0:
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowPullsync
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
iNdEx++
if dAtA[iNdEx-1] < 0x80 {
break
}
}
case 1:
iNdEx += 8
case 2:
var length int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowPullsync
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
length |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
if length < 0 {
return 0, ErrInvalidLengthPullsync
}
iNdEx += length
case 3:
depth++
case 4:
if depth == 0 {
return 0, ErrUnexpectedEndOfGroupPullsync
}
depth--
case 5:
iNdEx += 4
default:
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
}
if iNdEx < 0 {
return 0, ErrInvalidLengthPullsync
}
if depth == 0 {
return iNdEx, nil
}
}
return 0, io.ErrUnexpectedEOF
}
var (
ErrInvalidLengthPullsync = fmt.Errorf("proto: negative length found during unmarshaling")
ErrIntOverflowPullsync = fmt.Errorf("proto: integer overflow")
ErrUnexpectedEndOfGroupPullsync = fmt.Errorf("proto: unexpected end of group")
)
// Copyright 2020 The Swarm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
syntax = "proto3";
package pullsync;
option go_package = "pb";
message Syn {}
message Ack {
repeated uint64 Cursors = 1;
}
message GetRange {
int32 Bin = 1;
uint64 From = 2;
uint64 To = 3;
}
message Offer {
uint64 Topmost = 1;
bytes Hashes = 2;
}
message Want {
bytes BitVector = 1;
}
message Delivery {
bytes Address = 1;
bytes Data = 2;
}
// Copyright 2020 The Swarm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package pullstorage
// Copyright 2020 The Swarm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package pullstorage
// Copyright 2020 The Swarm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package mock
import (
"context"
"sync"
"github.com/ethersphere/bee/pkg/pullsync/pullstorage"
"github.com/ethersphere/bee/pkg/storage"
"github.com/ethersphere/bee/pkg/swarm"
)
var _ pullstorage.Storer = (*PullStorage)(nil)
type chunksResponse struct {
chunks []swarm.Address
topmost uint64
err error
}
// WithIntervalsResp mocks a desired response when calling IntervalChunks method.
// Different possible responses for subsequent responses in multi-call scenarios
// are possible (i.e. first call yields a,b,c, second call yields d,e,f).
// Mock maintains state of current call using chunksCalls counter.
func WithIntervalsResp(addrs []swarm.Address, top uint64, err error) Option {
return optionFunc(func(p *PullStorage) {
p.intervalChunksResponses = append(p.intervalChunksResponses, chunksResponse{chunks: addrs, topmost: top, err: err})
})
}
// WithChunks mocks the set of chunks that the store is aware of (used in Get and Has calls).
func WithChunks(chs ...swarm.Chunk) Option {
return optionFunc(func(p *PullStorage) {
for _, c := range chs {
p.chunks[c.Address().String()] = c.Data()
}
})
}
// WithEvilChunk allows to inject a malicious chunk (request a certain address
// of a chunk, but get another), in order to mock unsolicited chunk delivery.
func WithEvilChunk(addr swarm.Address, ch swarm.Chunk) Option {
return optionFunc(func(p *PullStorage) {
p.evilAddr = addr
p.evilChunk = ch
})
}
func WithCursors(c []uint64) Option {
return optionFunc(func(p *PullStorage) {
p.cursors = c
})
}
func WithCursorsErr(e error) Option {
return optionFunc(func(p *PullStorage) {
p.cursorsErr = e
})
}
type PullStorage struct {
mtx sync.Mutex
chunksCalls int
putCalls int
setCalls int
chunks map[string][]byte
evilAddr swarm.Address
evilChunk swarm.Chunk
cursors []uint64
cursorsErr error
intervalChunksResponses []chunksResponse
}
// NewPullStorage returns a new PullStorage mock.
func NewPullStorage(opts ...Option) *PullStorage {
s := &PullStorage{
chunks: make(map[string][]byte),
}
for _, v := range opts {
v.apply(s)
}
return s
}
// IntervalChunks returns a set of chunk in a requested interval.
func (s *PullStorage) IntervalChunks(_ context.Context, bin uint8, from, to uint64, limit int) (chunks []swarm.Address, topmost uint64, err error) {
s.mtx.Lock()
defer s.mtx.Unlock()
r := s.intervalChunksResponses[s.chunksCalls]
s.chunksCalls++
return r.chunks, r.topmost, r.err
}
func (s *PullStorage) Cursors(ctx context.Context) (curs []uint64, err error) {
return s.cursors, s.cursorsErr
}
// PutCalls returns the amount of times Put was called.
func (s *PullStorage) PutCalls() int {
s.mtx.Lock()
defer s.mtx.Unlock()
return s.putCalls
}
// SetCalls returns the amount of times Set was called.
func (s *PullStorage) SetCalls() int {
s.mtx.Lock()
defer s.mtx.Unlock()
return s.setCalls
}
// Get chunks.
func (s *PullStorage) Get(_ context.Context, _ storage.ModeGet, addrs ...swarm.Address) (chs []swarm.Chunk, err error) {
for _, a := range addrs {
if s.evilAddr.Equal(a) {
//inject the malicious chunk instead
chs = append(chs, s.evilChunk)
continue
}
if v, ok := s.chunks[a.String()]; ok {
chs = append(chs, swarm.NewChunk(a, v))
} else if !ok {
return nil, storage.ErrNotFound
}
}
return chs, nil
}
// Put chunks.
func (s *PullStorage) Put(_ context.Context, _ storage.ModePut, chs ...swarm.Chunk) error {
s.mtx.Lock()
defer s.mtx.Unlock()
for _, c := range chs {
s.chunks[c.Address().String()] = c.Data()
}
s.putCalls++
return nil
}
// Set chunks.
func (s *PullStorage) Set(ctx context.Context, mode storage.ModeSet, addrs ...swarm.Address) error {
s.mtx.Lock()
defer s.mtx.Unlock()
s.setCalls++
return nil
}
// Has chunks.
func (s *PullStorage) Has(_ context.Context, addr swarm.Address) (bool, error) {
if _, ok := s.chunks[addr.String()]; !ok {
return false, nil
}
return true, nil
}
type Option interface {
apply(*PullStorage)
}
type optionFunc func(*PullStorage)
func (f optionFunc) apply(r *PullStorage) { f(r) }
// Copyright 2020 The Swarm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package pullstorage
import (
"context"
"errors"
"time"
"github.com/ethersphere/bee/pkg/storage"
"github.com/ethersphere/bee/pkg/swarm"
)
var (
_ Storer = (*ps)(nil)
// ErrDbClosed is used to signal the underlying database was closed
ErrDbClosed = errors.New("db closed")
// after how long to return a non-empty batch
batchTimeout = 500 * time.Millisecond
)
// Storer is a thin wrapper around storage.Storer.
// It is used in order to collect and provide information about chunks
// currently present in the local store.
type Storer interface {
// IntervalChunks collects chunk for a requested interval.
IntervalChunks(ctx context.Context, bin uint8, from, to uint64, limit int) (chunks []swarm.Address, topmost uint64, err error)
// Cursors gets the last BinID for every bin in the local storage
Cursors(ctx context.Context) ([]uint64, error)
// Get chunks.
Get(ctx context.Context, mode storage.ModeGet, addrs ...swarm.Address) ([]swarm.Chunk, error)
// Put chunks.
Put(ctx context.Context, mode storage.ModePut, chs ...swarm.Chunk) error
// Set chunks.
Set(ctx context.Context, mode storage.ModeSet, addrs ...swarm.Address) error
// Has chunks.
Has(ctx context.Context, addr swarm.Address) (bool, error)
}
// ps wraps storage.Storer.
type ps struct {
storage.Storer
}
// New returns a new pullstorage Storer instance.
func New(storer storage.Storer) Storer {
return &ps{
Storer: storer,
}
}
// IntervalChunks collects chunk for a requested interval.
func (s *ps) IntervalChunks(ctx context.Context, bin uint8, from, to uint64, limit int) (chs []swarm.Address, topmost uint64, err error) {
// call iterator, iterate either until upper bound or limit reached
// return addresses, topmost is the topmost bin ID
var (
timer *time.Timer
timerC <-chan time.Time
)
ch, dbClosed, stop := s.SubscribePull(ctx, bin, from, to)
defer func(start time.Time) {
stop()
if timer != nil {
timer.Stop()
}
}(time.Now())
var nomore bool
LOOP:
for limit > 0 {
select {
case v, ok := <-ch:
if !ok {
nomore = true
break LOOP
}
chs = append(chs, v.Address)
if v.BinID > topmost {
topmost = v.BinID
}
limit--
if timer == nil {
timer = time.NewTimer(batchTimeout)
} else {
if !timer.Stop() {
<-timer.C
}
timer.Reset(batchTimeout)
}
timerC = timer.C
case <-ctx.Done():
return nil, 0, ctx.Err()
case <-timerC:
// return batch if new chunks are not received after some time
break LOOP
}
}
select {
case <-ctx.Done():
return nil, 0, ctx.Err()
case <-dbClosed:
return nil, 0, ErrDbClosed
default:
}
if nomore {
// end of interval reached. no more chunks so interval is complete
// return requested `to`. it could be that len(chs) == 0 if the interval
// is empty
topmost = to
}
return chs, topmost, nil
}
// Cursors gets the last BinID for every bin in the local storage
func (s *ps) Cursors(ctx context.Context) (curs []uint64, err error) {
curs = make([]uint64, 16)
for i := uint8(0); i < 16; i++ {
binID, err := s.Storer.LastPullSubscriptionBinID(i)
if err != nil {
return nil, err
}
curs[i] = binID
}
return curs, nil
}
// Get chunks.
func (s *ps) Get(ctx context.Context, mode storage.ModeGet, addrs ...swarm.Address) ([]swarm.Chunk, error) {
return s.Storer.GetMulti(ctx, mode, addrs...)
}
// Put chunks.
func (s *ps) Put(ctx context.Context, mode storage.ModePut, chs ...swarm.Chunk) error {
_, err := s.Storer.Put(ctx, mode, chs...)
return err
}
// Copyright 2020 The Swarm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package pullstorage_test
import (
"context"
"crypto/rand"
"errors"
"io/ioutil"
"testing"
"time"
"github.com/ethersphere/bee/pkg/localstore"
"github.com/ethersphere/bee/pkg/logging"
"github.com/ethersphere/bee/pkg/pullsync/pullstorage"
"github.com/ethersphere/bee/pkg/storage"
"github.com/ethersphere/bee/pkg/storage/mock"
stesting "github.com/ethersphere/bee/pkg/storage/testing"
"github.com/ethersphere/bee/pkg/swarm"
)
var (
addrs = []swarm.Address{
swarm.MustParseHexAddress("0001"),
swarm.MustParseHexAddress("0002"),
swarm.MustParseHexAddress("0003"),
swarm.MustParseHexAddress("0004"),
swarm.MustParseHexAddress("0005"),
swarm.MustParseHexAddress("0006"),
}
limit = 5
)
func someAddrs(i ...int) (r []swarm.Address) {
for _, v := range i {
r = append(r, addrs[v])
}
return r
}
func someDescriptors(i ...int) (d []storage.Descriptor) {
for _, v := range i {
d = append(d, storage.Descriptor{Address: addrs[v], BinID: uint64(v + 1)})
}
return d
}
// TestIntervalChunks tests that the IntervalChunks method always returns
// an upper bound of N chunks for a certain range, and the topmost equals:
// - to the To argument of the function (in case there are no chunks in the interval)
// - to the To argument of the function (in case the number of chunks in interval <= N)
// - to BinID of the last chunk in the returned collection in case number of chunks in interval > N
func TestIntervalChunks(t *testing.T) {
// we need to check four cases of the subscribe pull iterator:
// - no chunks in interval
// - less chunks reported than what is in the interval (but interval still intact, probably old chunks GCd)
// - as much chunks as size of interval
// - more chunks than what's in interval (return lower topmost value)
// - less chunks in interval, but since we're at the top of the interval, block and wait for new chunks
for _, tc := range []struct {
desc string
from, to uint64 // request from, to
mockAddrs []int // which addresses should the mock return
addrs []byte // the expected returned chunk address byte slice
topmost uint64 // expected topmost
}{
{desc: "no chunks in interval", from: 0, to: 5, topmost: 5},
{desc: "interval full", from: 0, to: 5, mockAddrs: []int{0, 1, 2, 3, 4}, topmost: 5},
{desc: "some in the middle", from: 0, to: 5, mockAddrs: []int{1, 3}, topmost: 5},
{desc: "at the edges", from: 0, to: 5, mockAddrs: []int{0, 4}, topmost: 5},
{desc: "at the edges and the middle", from: 0, to: 5, mockAddrs: []int{0, 2, 4}, topmost: 5},
{desc: "more than interval", from: 0, to: 5, mockAddrs: []int{0, 1, 2, 3, 4, 5}, topmost: 5},
} {
t.Run(tc.desc, func(t *testing.T) {
b := someAddrs(tc.mockAddrs...)
desc := someDescriptors(tc.mockAddrs...)
ps, _ := newPullStorage(t, mock.WithSubscribePullChunks(desc...))
ctx, cancel := context.WithCancel(context.Background())
addresses, topmost, err := ps.IntervalChunks(ctx, 0, tc.from, tc.to, limit)
if err != nil {
t.Fatal(err)
}
cancel()
checkAinB(t, addresses, b)
if topmost != tc.topmost {
t.Fatalf("expected topmost %d but got %d", tc.topmost, topmost)
}
})
}
}
// Get some descriptor from the chunk channel, then block for a while
// then add more chunks to the subscribe pull iterator and make sure the loop
// exits correctly.
func TestIntervalChunks_GetChunksLater(t *testing.T) {
desc := someDescriptors(0, 2)
ps, db := newPullStorage(t, mock.WithSubscribePullChunks(desc...), mock.WithPartialInterval(true))
go func() {
<-time.After(200 * time.Millisecond)
// add chunks to subscribe pull on the storage mock
db.MorePull(someDescriptors(1, 3, 4)...)
}()
addrs, topmost, err := ps.IntervalChunks(context.Background(), 0, 0, 5, limit)
if err != nil {
t.Fatal(err)
}
if l := len(addrs); l != 5 {
t.Fatalf("want %d addrs but got %d", 5, l)
}
// highest chunk we sent had BinID 5
exp := uint64(5)
if topmost != exp {
t.Fatalf("expected topmost %d but got %d", exp, topmost)
}
}
// Get some descriptors, but then let the iterator time out and return just the stuff we got in the beginning
func TestIntervalChunks_NoChunksLater(t *testing.T) {
desc := someDescriptors(0, 2)
ps, db := newPullStorage(t, mock.WithSubscribePullChunks(desc...), mock.WithPartialInterval(true))
go func() {
<-time.After(600 * time.Millisecond)
// add chunks to subscribe pull on the storage mock
db.MorePull(someDescriptors(1, 3, 4)...)
}()
addrs, topmost, err := ps.IntervalChunks(context.Background(), 0, 0, 5, limit)
if err != nil {
t.Fatal(err)
}
if l := len(addrs); l != 2 {
t.Fatalf("want %d addrs but got %d", 2, l)
}
// highest chunk we sent had BinID 3
exp := uint64(3)
if topmost != exp {
t.Fatalf("expected topmost %d but got %d", exp, topmost)
}
}
func TestIntervalChunks_Blocking(t *testing.T) {
desc := someDescriptors(0, 2)
ps, _ := newPullStorage(t, mock.WithSubscribePullChunks(desc...), mock.WithPartialInterval(true))
ctx, cancel := context.WithCancel(context.Background())
go func() {
<-time.After(100 * time.Millisecond)
cancel()
}()
_, _, err := ps.IntervalChunks(ctx, 0, 0, 5, limit)
if err == nil {
t.Fatal("expected error but got none")
}
if !errors.Is(err, context.Canceled) {
t.Fatal(err)
}
}
func TestIntervalChunks_DbShutdown(t *testing.T) {
ps, db := newPullStorage(t, mock.WithPartialInterval(true))
go func() {
<-time.After(100 * time.Millisecond)
db.Close()
}()
_, _, err := ps.IntervalChunks(context.Background(), 0, 0, 5, limit)
if err == nil {
t.Fatal("expected error but got none")
}
if !errors.Is(err, pullstorage.ErrDbClosed) {
t.Fatal(err)
}
}
// TestIntervalChunks_Localstore is an integration test with a real
// localstore instance.
func TestIntervalChunks_Localstore(t *testing.T) {
fill := func(f, t int) (ints []int) {
for i := f; i <= t; i++ {
ints = append(ints, i)
}
return ints
}
for _, tc := range []struct {
name string
chunks int
f, t uint64
limit int
expect int // chunks
top uint64 // topmost
addrs []int // indexes of the generated chunk slice
}{
{
name: "0-1, expect 1 chunk", // intervals always >0
chunks: 50,
f: 0, t: 1,
limit: 50,
expect: 1, top: 1, addrs: fill(1, 1),
},
{
name: "1-1, expect 1 chunk",
chunks: 50,
f: 1, t: 1,
limit: 50,
expect: 1, top: 1, addrs: fill(1, 1),
},
{
name: "2-2, expect 1 chunk",
chunks: 50,
f: 2, t: 2,
limit: 50,
expect: 1, top: 2, addrs: fill(2, 2),
},
{
name: "0-10, expect 10 chunks", // intervals always >0
chunks: 50,
f: 0, t: 10,
limit: 50,
expect: 10, top: 10, addrs: fill(1, 10),
},
{
name: "1-10, expect 10 chunks",
chunks: 50,
f: 0, t: 10,
limit: 50,
expect: 10, top: 10, addrs: fill(1, 10),
},
{
name: "0-50, expect 50 chunks", // intervals always >0
chunks: 50,
f: 0, t: 50,
limit: 50,
expect: 50, top: 50, addrs: fill(1, 50),
},
{
name: "1-50, expect 50 chunks",
chunks: 50,
f: 1, t: 50,
limit: 50,
expect: 50, top: 50, addrs: fill(1, 50),
},
{
name: "0-60, expect 50 chunks", // hit the limit
chunks: 50,
f: 0, t: 60,
limit: 50,
expect: 50, top: 50, addrs: fill(1, 50),
},
{
name: "1-60, expect 50 chunks", // hit the limit
chunks: 50,
f: 0, t: 60,
limit: 50,
expect: 50, top: 50, addrs: fill(1, 50),
},
} {
t.Run(tc.name, func(t *testing.T) {
base, db := newTestDB(t, nil)
ps := pullstorage.New(db)
var chunks []swarm.Chunk
for i := 1; i <= tc.chunks; {
c := stesting.GenerateTestRandomChunk()
po := swarm.Proximity(c.Address().Bytes(), base)
if po == 1 {
chunks = append(chunks, c)
i++
}
}
ctx := context.Background()
_, err := db.Put(ctx, storage.ModePutUpload, chunks...)
if err != nil {
t.Fatal(err)
}
//always bin 1
chs, topmost, err := ps.IntervalChunks(ctx, 1, tc.f, tc.t, tc.limit)
if err != nil {
t.Fatal(err)
}
checkAddrs := make([]swarm.Address, len(tc.addrs))
for i, v := range tc.addrs {
checkAddrs[i] = chunks[v-1].Address()
}
for i, c := range chs {
if !c.Equal(checkAddrs[i]) {
t.Fatalf("chunk %d address mismatch", i)
}
}
if topmost != tc.top {
t.Fatalf("topmost mismatch, got %d want %d", topmost, tc.top)
}
if l := len(chs); l != tc.expect {
t.Fatalf("expected %d chunks but got %d", tc.expect, l)
}
})
}
}
func newPullStorage(t *testing.T, o ...mock.Option) (pullstorage.Storer, *mock.MockStorer) {
db := mock.NewStorer(o...)
ps := pullstorage.New(db)
return ps, db
}
func newTestDB(t testing.TB, o *localstore.Options) (baseKey []byte, db *localstore.DB) {
t.Helper()
baseKey = make([]byte, 32)
if _, err := rand.Read(baseKey); err != nil {
t.Fatal(err)
}
logger := logging.New(ioutil.Discard, 0)
db, err := localstore.New("", baseKey, o, logger)
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
err := db.Close()
if err != nil {
t.Error(err)
}
})
return baseKey, db
}
// check that every a exists in b
func checkAinB(t *testing.T, a, b []swarm.Address) {
t.Helper()
for _, v := range a {
if !isIn(v, b) {
t.Fatalf("address %s not found in slice %s", v, b)
}
}
}
func isIn(a swarm.Address, b []swarm.Address) bool {
for _, v := range b {
if a.Equal(v) {
return true
}
}
return false
}
// Copyright 2020 The Swarm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package pullsync
import (
"context"
"errors"
"fmt"
"io"
"time"
"github.com/ethersphere/bee/pkg/bitvector"
"github.com/ethersphere/bee/pkg/logging"
"github.com/ethersphere/bee/pkg/p2p"
"github.com/ethersphere/bee/pkg/p2p/protobuf"
"github.com/ethersphere/bee/pkg/pullsync/pb"
"github.com/ethersphere/bee/pkg/pullsync/pullstorage"
"github.com/ethersphere/bee/pkg/storage"
"github.com/ethersphere/bee/pkg/swarm"
)
const (
protocolName = "pullsync"
protocolVersion = "1.0.0"
streamName = "pullsync"
cursorStreamName = "cursors"
)
var (
ErrUnsolicitedChunk = errors.New("peer sent unsolicited chunk")
)
// how many maximum chunks in a batch
var maxPage = 50
type Interface interface {
SyncInterval(ctx context.Context, peer swarm.Address, bin uint8, from, to uint64) (topmost uint64, err error)
GetCursors(ctx context.Context, peer swarm.Address) ([]uint64, error)
}
type Syncer struct {
streamer p2p.Streamer
logger logging.Logger
storage pullstorage.Storer
Interface
io.Closer
}
type Options struct {
Streamer p2p.Streamer
Storage pullstorage.Storer
Logger logging.Logger
}
func New(o Options) *Syncer {
return &Syncer{
streamer: o.Streamer,
storage: o.Storage,
logger: o.Logger,
}
}
func (s *Syncer) Protocol() p2p.ProtocolSpec {
return p2p.ProtocolSpec{
Name: protocolName,
Version: protocolVersion,
StreamSpecs: []p2p.StreamSpec{
{
Name: streamName,
Handler: s.handler,
},
{
Name: cursorStreamName,
Handler: s.cursorHandler,
},
},
}
}
// SyncInterval syncs a requested interval from the given peer.
// It returns the BinID of highest chunk that was synced from the given interval.
// If the requested interval is too large, the downstream peer has the liberty to
// provide less chunks than requested.
func (s *Syncer) SyncInterval(ctx context.Context, peer swarm.Address, bin uint8, from, to uint64) (topmost uint64, err error) {
stream, err := s.streamer.NewStream(ctx, peer, nil, protocolName, protocolVersion, streamName)
if err != nil {
return 0, fmt.Errorf("new stream: %w", err)
}
defer func() {
if err != nil {
_ = stream.FullClose()
return
}
_ = stream.Close()
}()
w, r := protobuf.NewWriterAndReader(stream)
rangeMsg := &pb.GetRange{Bin: int32(bin), From: from, To: to}
if err = w.WriteMsgWithContext(ctx, rangeMsg); err != nil {
return 0, fmt.Errorf("write get range: %w", err)
}
var offer pb.Offer
if err = r.ReadMsgWithContext(ctx, &offer); err != nil {
return 0, fmt.Errorf("read offer: %w", err)
}
if len(offer.Hashes)%swarm.HashSize != 0 {
return 0, fmt.Errorf("inconsistent hash length")
}
// empty interval (no chunks present in interval).
// return the end of the requested range as topmost.
if len(offer.Hashes) == 0 {
return offer.Topmost, nil
}
var (
bvLen = len(offer.Hashes) / swarm.HashSize
wantChunks = make(map[string]struct{})
ctr = 0
)
bv, err := bitvector.New(bvLen)
if err != nil {
return 0, fmt.Errorf("new bitvector: %w", err)
}
for i := 0; i < len(offer.Hashes); i += swarm.HashSize {
a := swarm.NewAddress(offer.Hashes[i : i+swarm.HashSize])
if a.Equal(swarm.ZeroAddress) {
// i'd like to have this around to see we don't see any of these in the logs
s.logger.Errorf("syncer got a zero address hash on offer")
return 0, fmt.Errorf("zero address on offer")
}
have, err := s.storage.Has(ctx, a)
if err != nil {
return 0, fmt.Errorf("storage has: %w", err)
}
if !have {
wantChunks[a.String()] = struct{}{}
ctr++
bv.Set(i / swarm.HashSize)
}
}
wantMsg := &pb.Want{BitVector: bv.Bytes()}
if err = w.WriteMsgWithContext(ctx, wantMsg); err != nil {
return 0, fmt.Errorf("write want: %w", err)
}
// if ctr is zero, it means we don't want any chunk in the batch
// thus, the following loop will not get executed and the method
// returns immediately with the topmost value on the offer, which
// will seal the interval and request the next one
for ; ctr > 0; ctr-- {
var delivery pb.Delivery
if err = r.ReadMsgWithContext(ctx, &delivery); err != nil {
return 0, fmt.Errorf("read delivery: %w", err)
}
addr := swarm.NewAddress(delivery.Address)
if _, ok := wantChunks[addr.String()]; !ok {
return 0, ErrUnsolicitedChunk
}
delete(wantChunks, addr.String())
if err = s.storage.Put(ctx, storage.ModePutSync, swarm.NewChunk(addr, delivery.Data)); err != nil {
return 0, fmt.Errorf("delivery put: %w", err)
}
}
return offer.Topmost, nil
}
// handler handles an incoming request to sync an interval
func (s *Syncer) handler(ctx context.Context, p p2p.Peer, stream p2p.Stream) error {
w, r := protobuf.NewWriterAndReader(stream)
defer stream.Close()
var rn pb.GetRange
if err := r.ReadMsgWithContext(ctx, &rn); err != nil {
return fmt.Errorf("read get range: %w", err)
}
s.logger.Debugf("got range peer %s bin %d from %d to %d", p.Address.String(), rn.Bin, rn.From, rn.To)
// make an offer to the upstream peer in return for the requested range
offer, addrs, err := s.makeOffer(ctx, rn)
if err != nil {
return fmt.Errorf("make offer: %w", err)
}
if err := w.WriteMsgWithContext(ctx, offer); err != nil {
return fmt.Errorf("write offer: %w", err)
}
// we don't have any hashes to offer in this range (the
// interval is empty). nothing more to do
if len(offer.Hashes) == 0 {
return nil
}
var want pb.Want
if err := r.ReadMsgWithContext(ctx, &want); err != nil {
return fmt.Errorf("read want: %w", err)
}
// empty bitvector implies downstream peer does not want
// any chunks (it has them already). mark chunks as synced
if len(want.BitVector) == 0 {
return s.setChunks(ctx, addrs...)
}
chs, err := s.processWant(ctx, offer, &want)
if err != nil {
return fmt.Errorf("process want: %w", err)
}
for _, v := range chs {
deliver := pb.Delivery{Address: v.Address().Bytes(), Data: v.Data()}
if err := w.WriteMsgWithContext(ctx, &deliver); err != nil {
return fmt.Errorf("write delivery: %w", err)
}
}
err = s.setChunks(ctx, addrs...)
if err != nil {
return err
}
time.Sleep(50 * time.Millisecond) //because of test, getting EOF w/o
return nil
}
func (s *Syncer) setChunks(ctx context.Context, addrs ...swarm.Address) error {
return s.storage.Set(ctx, storage.ModeSetSyncPull, addrs...)
}
// makeOffer tries to assemble an offer for a given requested interval.
func (s *Syncer) makeOffer(ctx context.Context, rn pb.GetRange) (o *pb.Offer, addrs []swarm.Address, err error) {
s.logger.Tracef("syncer make offer for bin %d from %d to %d maxpage %d", rn.Bin, rn.From, rn.To, maxPage)
chs, top, err := s.storage.IntervalChunks(ctx, uint8(rn.Bin), rn.From, rn.To, maxPage)
if err != nil {
return o, nil, err
}
o = new(pb.Offer)
o.Topmost = top
o.Hashes = make([]byte, 0)
for _, v := range chs {
o.Hashes = append(o.Hashes, v.Bytes()...)
}
return o, chs, nil
}
// processWant compares a received Want to a sent Offer and returns
// the appropriate chunks from the local store.
func (s *Syncer) processWant(ctx context.Context, o *pb.Offer, w *pb.Want) ([]swarm.Chunk, error) {
l := len(o.Hashes) / swarm.HashSize
bv, err := bitvector.NewFromBytes(w.BitVector, l)
if err != nil {
return nil, err
}
var addrs []swarm.Address
for i := 0; i < len(o.Hashes); i += swarm.HashSize {
if bv.Get(i / swarm.HashSize) {
a := swarm.NewAddress(o.Hashes[i : i+swarm.HashSize])
addrs = append(addrs, a)
}
}
return s.storage.Get(ctx, storage.ModeGetSync, addrs...)
}
func (s *Syncer) GetCursors(ctx context.Context, peer swarm.Address) ([]uint64, error) {
s.logger.Debugf("syncer get cursors from peer %s", peer)
stream, err := s.streamer.NewStream(ctx, peer, nil, protocolName, protocolVersion, cursorStreamName)
if err != nil {
return nil, fmt.Errorf("new stream: %w", err)
}
defer stream.Close()
w, r := protobuf.NewWriterAndReader(stream)
syn := &pb.Syn{}
if err = w.WriteMsgWithContext(ctx, syn); err != nil {
return nil, fmt.Errorf("write syn: %w", err)
}
var ack pb.Ack
if err = r.ReadMsgWithContext(ctx, &ack); err != nil {
return nil, fmt.Errorf("read ack: %w", err)
}
s.logger.Debugf("syncer peer %s cursors %s", peer, ack.Cursors)
return ack.Cursors, nil
}
func (s *Syncer) cursorHandler(ctx context.Context, p p2p.Peer, stream p2p.Stream) error {
w, r := protobuf.NewWriterAndReader(stream)
defer stream.Close()
var syn pb.Syn
if err := r.ReadMsgWithContext(ctx, &syn); err != nil {
return fmt.Errorf("read syn: %w", err)
}
var ack pb.Ack
ints, err := s.storage.Cursors(ctx)
if err != nil {
_ = stream.FullClose()
return err
}
ack.Cursors = ints
if err = w.WriteMsgWithContext(ctx, &ack); err != nil {
return fmt.Errorf("write ack: %w", err)
}
return nil
}
func (s *Syncer) Close() error {
return nil
}
// Copyright 2020 The Swarm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package pullsync_test
import (
"context"
"crypto/rand"
"errors"
"io"
"io/ioutil"
"testing"
"time"
"github.com/ethersphere/bee/pkg/logging"
"github.com/ethersphere/bee/pkg/p2p"
"github.com/ethersphere/bee/pkg/p2p/streamtest"
"github.com/ethersphere/bee/pkg/pullsync"
"github.com/ethersphere/bee/pkg/pullsync/pullstorage/mock"
"github.com/ethersphere/bee/pkg/swarm"
)
var (
addrs = []swarm.Address{
swarm.MustParseHexAddress("0000000000000000000000000000000000000000000000000000000000000001"),
swarm.MustParseHexAddress("0000000000000000000000000000000000000000000000000000000000000002"),
swarm.MustParseHexAddress("0000000000000000000000000000000000000000000000000000000000000003"),
swarm.MustParseHexAddress("0000000000000000000000000000000000000000000000000000000000000004"),
swarm.MustParseHexAddress("0000000000000000000000000000000000000000000000000000000000000005"),
}
chunks []swarm.Chunk
)
func someChunks(i ...int) (c []swarm.Chunk) {
for _, v := range i {
c = append(c, chunks[v])
}
return c
}
func init() {
chunks = make([]swarm.Chunk, 5)
for i := 0; i < 5; i++ {
data := make([]byte, swarm.ChunkSize)
_, _ = rand.Read(data)
chunks[i] = swarm.NewChunk(addrs[i], data)
}
}
// TestIncoming tests that an incoming request for an interval
// is handled correctly when no chunks are available in the interval.
// This means the interval exists but chunks are not there (GCd).
// Expected behavior is that an offer message with the requested
// To value is returned to the requester, but offer.Hashes is empty.
func TestIncoming_WantEmptyInterval(t *testing.T) {
var (
mockTopmost = uint64(5)
ps, serverDb = newPullSync(nil, mock.WithIntervalsResp([]swarm.Address{}, mockTopmost, nil))
recorder = streamtest.New(streamtest.WithProtocols(ps.Protocol()))
psClient, clientDb = newPullSync(recorder)
)
topmost, err := psClient.SyncInterval(context.Background(), swarm.ZeroAddress, 1, 0, 5)
if err != nil {
t.Fatal(err)
}
if topmost != mockTopmost {
t.Fatalf("got offer topmost %d but want %d", topmost, mockTopmost)
}
if clientDb.PutCalls() > 0 {
t.Fatal("too many puts")
}
waitSet(t, serverDb, 0)
}
func TestIncoming_WantNone(t *testing.T) {
var (
mockTopmost = uint64(5)
ps, serverDb = newPullSync(nil, mock.WithIntervalsResp(addrs, mockTopmost, nil), mock.WithChunks(chunks...))
recorder = streamtest.New(streamtest.WithProtocols(ps.Protocol()))
psClient, clientDb = newPullSync(recorder, mock.WithChunks(chunks...))
)
topmost, err := psClient.SyncInterval(context.Background(), swarm.ZeroAddress, 0, 0, 5)
if err != nil {
t.Fatal(err)
}
if topmost != mockTopmost {
t.Fatalf("got offer topmost %d but want %d", topmost, mockTopmost)
}
if clientDb.PutCalls() > 0 {
t.Fatal("too many puts")
}
waitSet(t, serverDb, 1)
}
func TestIncoming_WantOne(t *testing.T) {
var (
mockTopmost = uint64(5)
ps, serverDb = newPullSync(nil, mock.WithIntervalsResp(addrs, mockTopmost, nil), mock.WithChunks(chunks...))
recorder = streamtest.New(streamtest.WithProtocols(ps.Protocol()))
psClient, clientDb = newPullSync(recorder, mock.WithChunks(someChunks(1, 2, 3, 4)...))
)
topmost, err := psClient.SyncInterval(context.Background(), swarm.ZeroAddress, 0, 0, 5)
if err != nil {
t.Fatal(err)
}
if topmost != mockTopmost {
t.Fatalf("got offer topmost %d but want %d", topmost, mockTopmost)
}
// should have all
haveChunks(t, clientDb, addrs...)
if clientDb.PutCalls() > 1 {
t.Fatal("too many puts")
}
waitSet(t, serverDb, 1)
}
func TestIncoming_WantAll(t *testing.T) {
var (
mockTopmost = uint64(5)
ps, serverDb = newPullSync(nil, mock.WithIntervalsResp(addrs, mockTopmost, nil), mock.WithChunks(chunks...))
recorder = streamtest.New(streamtest.WithProtocols(ps.Protocol()))
psClient, clientDb = newPullSync(recorder)
)
topmost, err := psClient.SyncInterval(context.Background(), swarm.ZeroAddress, 0, 0, 5)
if err != nil {
t.Fatal(err)
}
if topmost != mockTopmost {
t.Fatalf("got offer topmost %d but want %d", topmost, mockTopmost)
}
// should have all
haveChunks(t, clientDb, addrs...)
if p := clientDb.PutCalls(); p != 5 {
t.Fatalf("want %d puts but got %d", 5, p)
}
waitSet(t, serverDb, 1)
}
func TestIncoming_UnsolicitedChunk(t *testing.T) {
evilAddr := swarm.MustParseHexAddress("0000000000000000000000000000000000000000000000000000000000000666")
evilData := []byte{0x66, 0x66, 0x66}
evil := swarm.NewChunk(evilAddr, evilData)
var (
mockTopmost = uint64(5)
ps, _ = newPullSync(nil, mock.WithIntervalsResp(addrs, mockTopmost, nil), mock.WithChunks(chunks...), mock.WithEvilChunk(addrs[4], evil))
recorder = streamtest.New(streamtest.WithProtocols(ps.Protocol()))
psClient, _ = newPullSync(recorder)
)
_, err := psClient.SyncInterval(context.Background(), swarm.ZeroAddress, 0, 0, 5)
if !errors.Is(err, pullsync.ErrUnsolicitedChunk) {
t.Fatalf("expected ErrUnsolicitedChunk but got %v", err)
}
}
func TestGetCursors(t *testing.T) {
var (
mockCursors = []uint64{100, 101, 102, 103}
ps, _ = newPullSync(nil, mock.WithCursors(mockCursors))
recorder = streamtest.New(streamtest.WithProtocols(ps.Protocol()))
psClient, _ = newPullSync(recorder)
)
curs, err := psClient.GetCursors(context.Background(), swarm.ZeroAddress)
if err != nil {
t.Fatal(err)
}
if len(curs) != len(mockCursors) {
t.Fatalf("length mismatch got %d want %d", len(curs), len(mockCursors))
}
for i, v := range mockCursors {
if curs[i] != v {
t.Errorf("cursor mismatch. index %d want %d got %d", i, v, curs[i])
}
}
}
func TestGetCursorsError(t *testing.T) {
var (
e = errors.New("erring")
ps, _ = newPullSync(nil, mock.WithCursorsErr(e))
recorder = streamtest.New(streamtest.WithProtocols(ps.Protocol()))
psClient, _ = newPullSync(recorder)
)
_, err := psClient.GetCursors(context.Background(), swarm.ZeroAddress)
if err == nil {
t.Fatal("expected error but got none")
}
if !errors.Is(err, io.EOF) {
t.Fatalf("expect error '%v' but got '%v'", e, err)
}
}
func haveChunks(t *testing.T, s *mock.PullStorage, addrs ...swarm.Address) {
t.Helper()
for _, a := range addrs {
have, err := s.Has(context.Background(), a)
if err != nil {
t.Fatal(err)
}
if !have {
t.Errorf("storage does not have chunk %s", a)
}
}
}
func newPullSync(s p2p.Streamer, o ...mock.Option) (*pullsync.Syncer, *mock.PullStorage) {
storage := mock.NewPullStorage(o...)
logger := logging.New(ioutil.Discard, 0)
return pullsync.New(pullsync.Options{Streamer: s, Storage: storage, Logger: logger}), storage
}
func waitSet(t *testing.T, db *mock.PullStorage, v int) {
time.Sleep(10 * time.Millisecond) // give leeway for the case where v==0
var s int
for i := 0; i < 10; i++ {
s = db.SetCalls()
switch {
case s > v:
t.Fatalf("too many Set calls: got %d want %d", s, v)
case s == v:
return
default:
time.Sleep(10 * time.Millisecond)
}
}
t.Fatalf("timed out waiting for set to be called. got %d calls want %d", s, v)
}
......@@ -88,7 +88,7 @@ func NewDB(path string) (db *DB, err error) {
}
// Put wraps LevelDB Put method to increment metrics counter.
func (db *DB) Put(key []byte, value []byte) (err error) {
func (db *DB) Put(key, value []byte) (err error) {
err = db.ldb.Put(key, value, nil)
if err != nil {
db.metrics.PutFailCounter.Inc()
......
......@@ -17,22 +17,50 @@ import (
var _ storage.Storer = (*MockStorer)(nil)
type MockStorer struct {
store map[string][]byte
modeSet map[string]storage.ModeSet
modeSetMu sync.Mutex
pinnedAddress []swarm.Address // Stores the pinned address
pinnedCounter []uint64 // and its respective counter. These are stored as slices to preserve the order.
pinSetMu sync.Mutex
validator swarm.ChunkValidator
tags *tags.Tags
store map[string][]byte
modeSet map[string]storage.ModeSet
modeSetMu sync.Mutex
pinnedAddress []swarm.Address // Stores the pinned address
pinnedCounter []uint64 // and its respective counter. These are stored as slices to preserve the order.
pinSetMu sync.Mutex
subpull []storage.Descriptor
partialInterval bool
validator swarm.ChunkValidator
tags *tags.Tags
morePull chan struct{}
mtx sync.Mutex
quit chan struct{}
}
func NewStorer() storage.Storer {
return &MockStorer{
func WithSubscribePullChunks(chs ...storage.Descriptor) Option {
return optionFunc(func(m *MockStorer) {
m.subpull = make([]storage.Descriptor, len(chs))
for i, v := range chs {
m.subpull[i] = v
}
})
}
func WithPartialInterval(v bool) Option {
return optionFunc(func(m *MockStorer) {
m.partialInterval = v
})
}
func NewStorer(opts ...Option) *MockStorer {
s := &MockStorer{
store: make(map[string][]byte),
modeSet: make(map[string]storage.ModeSet),
modeSetMu: sync.Mutex{},
morePull: make(chan struct{}),
quit: make(chan struct{}),
}
for _, v := range opts {
v.apply(s)
}
return s
}
func NewValidatingStorer(v swarm.ChunkValidator, tags *tags.Tags) *MockStorer {
......@@ -47,6 +75,9 @@ func NewValidatingStorer(v swarm.ChunkValidator, tags *tags.Tags) *MockStorer {
}
func (m *MockStorer) Get(ctx context.Context, mode storage.ModeGet, addr swarm.Address) (ch swarm.Chunk, err error) {
m.mtx.Lock()
defer m.mtx.Unlock()
v, has := m.store[addr.String()]
if !has {
return nil, storage.ErrNotFound
......@@ -55,6 +86,9 @@ func (m *MockStorer) Get(ctx context.Context, mode storage.ModeGet, addr swarm.A
}
func (m *MockStorer) Put(ctx context.Context, mode storage.ModePut, chs ...swarm.Chunk) (exist []bool, err error) {
m.mtx.Lock()
defer m.mtx.Unlock()
for _, ch := range chs {
if m.validator != nil {
if !m.validator.Validate(ch) {
......@@ -62,7 +96,7 @@ func (m *MockStorer) Put(ctx context.Context, mode storage.ModePut, chs ...swarm
}
}
m.store[ch.Address().String()] = ch.Data()
yes, err := m.Has(ctx, ch.Address())
yes, err := m.has(ctx, ch.Address())
if err != nil {
exist = append(exist, false)
continue
......@@ -81,11 +115,17 @@ func (m *MockStorer) GetMulti(ctx context.Context, mode storage.ModeGet, addrs .
panic("not implemented") // TODO: Implement
}
func (m *MockStorer) Has(ctx context.Context, addr swarm.Address) (yes bool, err error) {
func (m *MockStorer) has(ctx context.Context, addr swarm.Address) (yes bool, err error) {
_, has := m.store[addr.String()]
return has, nil
}
func (m *MockStorer) Has(ctx context.Context, addr swarm.Address) (yes bool, err error) {
m.mtx.Lock()
defer m.mtx.Unlock()
return m.has(ctx, addr)
}
func (m *MockStorer) HasMulti(ctx context.Context, addrs ...swarm.Address) (yes []bool, err error) {
panic("not implemented") // TODO: Implement
}
......@@ -148,8 +188,72 @@ func (m *MockStorer) LastPullSubscriptionBinID(bin uint8) (id uint64, err error)
panic("not implemented") // TODO: Implement
}
func (m *MockStorer) SubscribePull(ctx context.Context, bin uint8, since uint64, until uint64) (c <-chan storage.Descriptor, stop func()) {
panic("not implemented") // TODO: Implement
func (m *MockStorer) SubscribePull(ctx context.Context, bin uint8, since, until uint64) (<-chan storage.Descriptor, <-chan struct{}, func()) {
c := make(chan storage.Descriptor)
done := make(chan struct{})
stop := func() {
close(done)
}
go func() {
defer close(c)
m.mtx.Lock()
for _, ch := range m.subpull {
select {
case c <- ch:
case <-done:
return
case <-ctx.Done():
return
case <-m.quit:
return
}
}
m.mtx.Unlock()
if m.partialInterval {
// block since we're at the top of the bin and waiting for new chunks
select {
case <-done:
return
case <-m.quit:
return
case <-ctx.Done():
return
case <-m.morePull:
}
}
m.mtx.Lock()
defer m.mtx.Unlock()
// iterate on what we have in the iterator
for _, ch := range m.subpull {
select {
case c <- ch:
case <-done:
return
case <-ctx.Done():
return
case <-m.quit:
return
}
}
}()
return c, m.quit, stop
}
func (m *MockStorer) MorePull(d ...storage.Descriptor) {
// clear out what we already have in subpull
m.mtx.Lock()
defer m.mtx.Unlock()
m.subpull = make([]storage.Descriptor, len(d))
for i, v := range d {
m.subpull[i] = v
}
close(m.morePull)
}
func (m *MockStorer) SubscribePush(ctx context.Context) (c <-chan swarm.Chunk, stop func()) {
......@@ -184,5 +288,13 @@ func (m *MockStorer) PinInfo(address swarm.Address) (uint64, error) {
}
func (m *MockStorer) Close() error {
panic("not implemented") // TODO: Implement
close(m.quit)
return nil
}
type Option interface {
apply(*MockStorer)
}
type optionFunc func(*MockStorer)
func (f optionFunc) apply(r *MockStorer) { f(r) }
......@@ -3,9 +3,10 @@ package mock_test
import (
"bytes"
"context"
"github.com/ethersphere/bee/pkg/tags"
"testing"
"github.com/ethersphere/bee/pkg/tags"
"github.com/ethersphere/bee/pkg/storage"
"github.com/ethersphere/bee/pkg/storage/mock"
"github.com/ethersphere/bee/pkg/storage/mock/validator"
......@@ -41,11 +42,10 @@ func TestMockStorer(t *testing.T) {
if chunk, err := s.Get(ctx, storage.ModeGetRequest, keyFound); err != nil {
t.Fatalf("expected no error but got: %v", err)
} else {
if !bytes.Equal(chunk.Data(), valueFound) {
t.Fatalf("expected value %s but got %s", valueFound, chunk.Data())
}
} else if !bytes.Equal(chunk.Data(), valueFound) {
t.Fatalf("expected value %s but got %s", valueFound, chunk.Data())
}
has, err := s.Has(ctx, keyFound)
if err != nil {
t.Fatal(err)
......@@ -83,10 +83,8 @@ func TestMockValidatingStorer(t *testing.T) {
if chunk, err := s.Get(ctx, storage.ModeGetRequest, validAddress); err != nil {
t.Fatalf("got error on get but expected none: %v", err)
} else {
if !bytes.Equal(chunk.Data(), validContent) {
t.Fatal("stored content not identical to input data")
}
} else if !bytes.Equal(chunk.Data(), validContent) {
t.Fatal("stored content not identical to input data")
}
if _, err := s.Get(ctx, storage.ModeGetRequest, invalidAddress); err == nil {
......
......@@ -136,11 +136,10 @@ type Storer interface {
Getter
Putter
GetMulti(ctx context.Context, mode ModeGet, addrs ...swarm.Address) (ch []swarm.Chunk, err error)
Has(ctx context.Context, addr swarm.Address) (yes bool, err error)
HasMulti(ctx context.Context, addrs ...swarm.Address) (yes []bool, err error)
Set(ctx context.Context, mode ModeSet, addrs ...swarm.Address) (err error)
Hasser
Setter
LastPullSubscriptionBinID(bin uint8) (id uint64, err error)
SubscribePull(ctx context.Context, bin uint8, since, until uint64) (c <-chan Descriptor, stop func())
PullSubscriber
SubscribePush(ctx context.Context) (c <-chan swarm.Chunk, stop func())
PinnedChunks(ctx context.Context, cursor swarm.Address) (pinnedChunks []*Pinner, err error)
PinInfo(address swarm.Address) (uint64, error)
......@@ -155,6 +154,19 @@ type Getter interface {
Get(ctx context.Context, mode ModeGet, addr swarm.Address) (ch swarm.Chunk, err error)
}
type Setter interface {
Set(ctx context.Context, mode ModeSet, addrs ...swarm.Address) (err error)
}
type Hasser interface {
Has(ctx context.Context, addr swarm.Address) (yes bool, err error)
HasMulti(ctx context.Context, addrs ...swarm.Address) (yes []bool, err error)
}
type PullSubscriber interface {
SubscribePull(ctx context.Context, bin uint8, since, until uint64) (c <-chan Descriptor, closed <-chan struct{}, stop func())
}
// StateStorer defines methods required to get, set, delete values for different keys
// and close the underlying resources.
type StateStorer interface {
......
......@@ -150,10 +150,29 @@ func (d *driver) Connected(ctx context.Context, addr swarm.Address) error {
return d.AddPeer(ctx, addr)
}
func (d *driver) Disconnected(swarm.Address) {
func (_ *driver) Disconnected(swarm.Address) {
// TODO: implement if necessary
}
func (_ *driver) NeighborhoodDepth() uint8 {
return 0
}
// EachPeer iterates from closest bin to farthest
func (_ *driver) EachPeer(_ topology.EachPeerFunc) error {
panic("not implemented") // TODO: Implement
}
// EachPeerRev iterates from farthest bin to closest
func (_ *driver) EachPeerRev(_ topology.EachPeerFunc) error {
panic("not implemented") // TODO: Implement
}
func (_ *driver) SubscribePeersChange() (c <-chan struct{}, unsubscribe func()) {
//TODO implement if necessary
return c, unsubscribe
}
func (d *driver) MarshalJSON() ([]byte, error) {
var peers []string
for p := range d.receivedPeers {
......
......@@ -79,6 +79,24 @@ func (d *mock) ClosestPeer(addr swarm.Address) (peerAddr swarm.Address, err erro
return d.closestPeer, d.closestPeerErr
}
func (d *mock) SubscribePeersChange() (c <-chan struct{}, unsubscribe func()) {
return c, unsubscribe
}
func (_ *mock) NeighborhoodDepth() uint8 {
return 0
}
// EachPeer iterates from closest bin to farthest
func (_ *mock) EachPeer(_ topology.EachPeerFunc) error {
panic("not implemented") // TODO: Implement
}
// EachPeerRev iterates from farthest bin to closest
func (_ *mock) EachPeerRev(_ topology.EachPeerFunc) error {
panic("not implemented") // TODO: Implement
}
func (d *mock) MarshalJSON() ([]byte, error) {
return d.marshalJSONFunc()
}
......
......@@ -20,7 +20,10 @@ var (
type Driver interface {
PeerAdder
ClosestPeerer
EachPeerer
Notifier
NeighborhoodDepth() uint8
SubscribePeersChange() (c <-chan struct{}, unsubscribe func())
io.Closer
}
......@@ -49,5 +52,12 @@ type ClosestPeerer interface {
ClosestPeer(addr swarm.Address) (peerAddr swarm.Address, err error)
}
type EachPeerer interface {
// EachPeer iterates from closest bin to farthest
EachPeer(EachPeerFunc) error
// EachPeerRev iterates from farthest bin to closest
EachPeerRev(EachPeerFunc) error
}
// EachPeerFunc is a callback that is called with a peer and its PO
type EachPeerFunc func(swarm.Address, uint8) (stop, jumpToNext bool, err error)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment