Skip to content
This repository has been archived by the owner on Aug 2, 2021. It is now read-only.

Snapshot test #1284

Closed
wants to merge 17 commits into from
Closed
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions swarm/network/simulation/kademlia.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@ package simulation

import (
"context"
"encoding/binary"
"encoding/hex"
"time"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p/enode"
"github.com/ethereum/go-ethereum/p2p/simulations"
"github.com/ethereum/go-ethereum/swarm/network"
)

Expand Down Expand Up @@ -96,3 +98,106 @@ func (s *Simulation) kademlias() (ks map[enode.ID]*network.Kademlia) {
}
return ks
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ideally we should submit kademlia changes as a separate PR tracking as #1298

// WaitTillSnapshotRecreated is blocking until all the connections specified
// in the snapshot are registered in the kademlia.
// It differs from WaitTillHealthy, which waits only until all the kademlias are
// healthy (it might happen even before all the connections are established).
func (s *Simulation) WaitTillSnapshotRecreated(ctx context.Context, snap simulations.Snapshot) error {
expected := getSnapshotConnections(snap.Conns)
ticker := time.NewTicker(150 * time.Millisecond)
defer ticker.Stop()

for {
select {
case <-ctx.Done():
return ctx.Err()
case <-ticker.C:
actual := s.getActualConnections()
if isAllDeployed(expected, actual) {
return nil
}
}
}
}

func (s *Simulation) getActualConnections() (res []uint64) {
kademlias := s.kademlias()
for base, k := range kademlias {
k.EachConn(base[:], 256, func(p *network.Peer, _ int) bool {
res = append(res, getConnectionHash(base, p.ID()))
return true
})
}

// only list those connections that appear twice (both peers should recognize connection as active)
res = removeDuplicatesAndSingletons(res)
return res
}

func getSnapshotConnections(conns []simulations.Conn) (res []uint64) {
for _, c := range conns {
res = append(res, getConnectionHash(c.One, c.Other))
}
return res
}

// returns an integer connection identifier (similar to 8-byte hash)
func getConnectionHash(a, b enode.ID) uint64 {
var h [8]byte
for i := 0; i < 8; i++ {
h[i] = a[i] ^ b[i]
}
res := binary.LittleEndian.Uint64(h[:])
return res
}

// returns true if all connections in expected are listed in actual
func isAllDeployed(expected []uint64, actual []uint64) bool {
if len(expected) == 0 {
return true
}

exp := make([]uint64, len(expected))
copy(exp, expected)
for _, c := range actual {
// remove value c from exp
for i := 0; i < len(exp); i++ {
if exp[i] == c {
exp = removeListElement(exp, i)
if len(exp) == 0 {
return true
}
}
}
}
return len(exp) == 0
}

func removeListElement(arr []uint64, i int) []uint64 {
last := len(arr) - 1
arr[i] = arr[last]
arr = arr[:last]
return arr
}

func removeDuplicatesAndSingletons(arr []uint64) []uint64 {
for i := 0; i < len(arr); {
found := false
for j := i + 1; j < len(arr); j++ {
if arr[i] == arr[j] {
arr = removeListElement(arr, j) // remove duplicate
found = true
break
}
}

if found {
i++
} else {
arr = removeListElement(arr, i) // remove singleton
}
}

return arr
}
163 changes: 163 additions & 0 deletions swarm/network/simulation/kademlia_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,3 +144,166 @@ func createSimServiceMap(discovery bool) map[string]ServiceFunc {
},
}
}

// TestWaitTillSnapshotRecreated tests that we indeed have a network
// configuration specified in the snapshot file, after we wait for it.
//
// First we create a first simulation
// Run it as nodes connected in a ring
// Wait until the network is healthy
// Then we create a snapshot
// With this snapshot we create a new simulation
// Call WaitTillSnapshotRecreated() function and wait until it returns
// Iterate the nodes and check if all the connections are successfully recreated
func TestWaitTillSnapshotRecreated(t *testing.T) {
var err error
sim := New(createSimServiceMap(true))
_, err = sim.AddNodesAndConnectRing(16)
if err != nil {
t.Fatal(err)
}
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
_, err = sim.WaitTillHealthy(ctx)
if err != nil {
t.Fatal(err)
}

originalConnections := sim.getActualConnections()
snap, err := sim.Net.Snapshot()
sim.Close()
if err != nil {
t.Fatal(err)
}

controlSim := New(createSimServiceMap(false))
defer controlSim.Close()
err = controlSim.Net.Load(snap)
if err != nil {
t.Fatal(err)
}
err = controlSim.WaitTillSnapshotRecreated(ctx, *snap)
if err != nil {
t.Fatal(err)
}
controlConnections := controlSim.getActualConnections()

for _, c := range originalConnections {
if !exist(controlConnections, c) {
t.Fatal("connection was not recreated")
}
}
}

// exist returns true if val is found in arr
func exist(arr []uint64, val uint64) bool {
for _, c := range arr {
if c == val {
return true
}
}
return false
}

func TestRemoveDuplicatesAndSingletons(t *testing.T) {
singletons := []uint64{
0x3c127c6f6cb026b0,
0x0f45190d72e71fc5,
0xb0184c02449e0bb6,
0xa85c7b84239c54d3,
0xe3b0c44298fc1c14,
0x9afbf4c8996fb924,
0x27ae41e4649b934c,
0xa495991b7852b855,
}

doubles := []uint64{
0x1b879f878de7fc7a,
0xc6791470521bdab4,
0xdd34b0ee39bbccc6,
0x4d904fbf0f31da10,
0x6403c2560432c8f8,
0x18954e33cf3ad847,
0x90db00e98dc7a8a6,
0x92886b0dfcc1809b,
}

var arr []uint64
arr = append(arr, doubles...)
arr = append(arr, singletons...)
arr = append(arr, doubles...)
arr = removeDuplicatesAndSingletons(arr)

for _, i := range singletons {
if exist(arr, i) {
t.Fatalf("singleton not removed: %d", i)
}
}

for _, i := range doubles {
if !exist(arr, i) {
t.Fatalf("wrong value removed: %d", i)
}
}

for j := 0; j < len(doubles); j++ {
v := doubles[j] + singletons[j]
if exist(arr, v) {
t.Fatalf("non-existing value found, index: %d", j)
}
}
}

func TestIsAllDeployed(t *testing.T) {
a := []uint64{
0x3c127c6f6cb026b0,
0x0f45190d72e71fc5,
0xb0184c02449e0bb6,
0xa85c7b84239c54d3,
0xe3b0c44298fc1c14,
0x9afbf4c8996fb924,
0x27ae41e4649b934c,
0xa495991b7852b855,
}

b := []uint64{
0x1b879f878de7fc7a,
0xc6791470521bdab4,
0xdd34b0ee39bbccc6,
0x4d904fbf0f31da10,
0x6403c2560432c8f8,
0x18954e33cf3ad847,
0x90db00e98dc7a8a6,
0x92886b0dfcc1809b,
}

var c []uint64
c = append(c, a...)
c = append(c, b...)

if !isAllDeployed(a, c) {
t.Fatal("isAllDeployed failed")
}

if !isAllDeployed(b, c) {
t.Fatal("isAllDeployed failed")
}

if isAllDeployed(c, a) {
t.Fatal("isAllDeployed failed: false positive")
}

if isAllDeployed(c, b) {
t.Fatal("isAllDeployed failed: false positive")
}

c = c[2:]

if isAllDeployed(a, c) {
t.Fatal("isAllDeployed failed: false positive")
}

if !isAllDeployed(b, c) {
t.Fatal("isAllDeployed failed")
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... and the positive? :)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this condition remains valid (if i understand your question correctly)

}
Loading