Browse Source

addrbook tests

pull/9/head
Jae Kwon 11 years ago
parent
commit
421a9be34c
3 changed files with 326 additions and 154 deletions
  1. +167
    -49
      peer/addrbook.go
  2. +159
    -0
      peer/addrbook_test.go
  3. +0
    -105
      peer/knownaddress.go

+ 167
- 49
peer/addrbook.go View File

@ -9,7 +9,6 @@ import (
"encoding/binary"
"encoding/json"
"fmt"
. "github.com/tendermint/tendermint/binary"
"io"
"math"
"math/rand"
@ -18,24 +17,26 @@ import (
"sync"
"sync/atomic"
"time"
. "github.com/tendermint/tendermint/binary"
)
/* AddrBook - concurrency safe peer address manager */
type AddrBook struct {
filePath string
mtx sync.Mutex
rand *rand.Rand
key [32]byte
addrIndex map[string]*KnownAddress // addr.String() -> KnownAddress
addrNew [newBucketCount]map[string]*KnownAddress
addrOld [oldBucketCount][]*KnownAddress
started int32
shutdown int32
wg sync.WaitGroup
quit chan struct{}
nOld int
nNew int
mtx sync.Mutex
rand *rand.Rand
key [32]byte
addrNewIndex map[string]*knownAddress // addr.String() -> knownAddress
addrNew [newBucketCount]map[string]*knownAddress
addrOld [oldBucketCount][]*knownAddress
started int32
shutdown int32
wg sync.WaitGroup
quit chan struct{}
nOld int
nNew int
}
const (
@ -87,7 +88,7 @@ const (
getAddrPercent = 23
// current version of the on-disk format.
serialisationVersion = 1
serializationVersion = 1
)
// Use Start to begin processing asynchronous address updates.
@ -103,13 +104,13 @@ func NewAddrBook(filePath string) *AddrBook {
// When modifying this, don't forget to update loadFromFile()
func (a *AddrBook) init() {
a.addrIndex = make(map[string]*KnownAddress)
a.addrNewIndex = make(map[string]*knownAddress)
io.ReadFull(crand.Reader, a.key[:])
for i := range a.addrNew {
a.addrNew[i] = make(map[string]*KnownAddress)
a.addrNew[i] = make(map[string]*knownAddress)
}
for i := range a.addrOld {
a.addrOld[i] = make([]*KnownAddress, 0, oldBucketSize)
a.addrOld[i] = make([]*knownAddress, 0, oldBucketSize)
}
}
@ -139,17 +140,17 @@ func (a *AddrBook) AddAddress(addr *NetAddress, src *NetAddress) {
}
func (a *AddrBook) NeedMoreAddresses() bool {
return a.NumAddresses() < needAddressThreshold
return a.Size() < needAddressThreshold
}
func (a *AddrBook) NumAddresses() int {
func (a *AddrBook) Size() int {
a.mtx.Lock()
defer a.mtx.Unlock()
return a.nOld + a.nNew
}
// Pick a new address to connect to.
func (a *AddrBook) PickAddress(class string, newBias int) *KnownAddress {
func (a *AddrBook) PickAddress(class string, newBias int) *knownAddress {
a.mtx.Lock()
defer a.mtx.Unlock()
@ -169,7 +170,7 @@ func (a *AddrBook) PickAddress(class string, newBias int) *KnownAddress {
if (newCorrelation+oldCorrelation)*a.rand.Float64() < oldCorrelation {
// pick random Old bucket.
var bucket []*KnownAddress = nil
var bucket []*knownAddress = nil
for len(bucket) == 0 {
bucket = a.addrOld[a.rand.Intn(len(a.addrOld))]
}
@ -177,7 +178,7 @@ func (a *AddrBook) PickAddress(class string, newBias int) *KnownAddress {
return bucket[a.rand.Intn(len(bucket))]
} else {
// pick random New bucket.
var bucket map[string]*KnownAddress = nil
var bucket map[string]*knownAddress = nil
for len(bucket) == 0 {
bucket = a.addrNew[a.rand.Intn(len(a.addrNew))]
}
@ -197,11 +198,11 @@ func (a *AddrBook) PickAddress(class string, newBias int) *KnownAddress {
func (a *AddrBook) MarkGood(addr *NetAddress) {
a.mtx.Lock()
defer a.mtx.Unlock()
ka := a.addrIndex[addr.String()]
ka := a.addrNewIndex[addr.String()]
if ka == nil {
return
}
ka.MarkAttempt(true)
ka.MarkGood()
if ka.OldBucket == -1 {
a.moveToOld(ka)
}
@ -210,30 +211,40 @@ func (a *AddrBook) MarkGood(addr *NetAddress) {
func (a *AddrBook) MarkAttempt(addr *NetAddress) {
a.mtx.Lock()
defer a.mtx.Unlock()
ka := a.addrIndex[addr.String()]
ka := a.addrNewIndex[addr.String()]
if ka == nil {
return
}
ka.MarkAttempt(false)
ka.MarkAttempt()
}
/* Loading & Saving */
type addrBookJSON struct {
Key [32]byte
AddrNew [newBucketCount]map[string]*KnownAddress
AddrOld [oldBucketCount][]*KnownAddress
NOld int
NNew int
AddrNew [newBucketCount][]*knownAddress
AddrOld [oldBucketCount][]*knownAddress
NumOld int
NumNew int
}
func (a *AddrBook) saveToFile(filePath string) {
// turn a.addrNew into an array like a.addrOld
__addrNew := [newBucketCount][]*knownAddress{}
for i, newBucket := range a.addrNew {
var array []*knownAddress = make([]*knownAddress, 0)
for _, ka := range newBucket {
array = append(array, ka)
}
__addrNew[i] = array
}
aJSON := &addrBookJSON{
Key: a.key,
AddrNew: a.addrNew,
AddrNew: __addrNew,
AddrOld: a.addrOld,
NOld: a.nOld,
NNew: a.nNew,
NumOld: a.nOld,
NumNew: a.nNew,
}
w, err := os.Create(filePath)
@ -271,20 +282,28 @@ func (a *AddrBook) loadFromFile(filePath string) {
panic(fmt.Errorf("error reading %s: %v", filePath, err))
}
// Now we need to initialize self.
// Now we need to restore the fields
// Restore the key
copy(a.key[:], aJSON.Key[:])
a.addrNew = aJSON.AddrNew
// Restore .addrNew
for i, newBucket := range aJSON.AddrNew {
for _, ka := range newBucket {
a.addrNew[i][ka.Addr.String()] = ka
}
}
// Restore .addrOld
for i, oldBucket := range aJSON.AddrOld {
copy(a.addrOld[i], oldBucket)
}
a.nNew = aJSON.NNew
a.nOld = aJSON.NOld
a.addrIndex = make(map[string]*KnownAddress)
// Restore simple fields
a.nNew = aJSON.NumNew
a.nOld = aJSON.NumOld
// Restore addrNewIndex
a.addrNewIndex = make(map[string]*knownAddress)
for _, newBucket := range a.addrNew {
for key, ka := range newBucket {
a.addrIndex[key] = ka
a.addrNewIndex[key] = ka
}
}
}
@ -314,7 +333,7 @@ func (a *AddrBook) addAddress(addr, src *NetAddress) {
}
key := addr.String()
ka := a.addrIndex[key]
ka := a.addrNewIndex[key]
if ka != nil {
// Already added
@ -331,8 +350,8 @@ func (a *AddrBook) addAddress(addr, src *NetAddress) {
return
}
} else {
ka = NewKnownAddress(addr, src)
a.addrIndex[key] = ka
ka = NewknownAddress(addr, src)
a.addrNewIndex[key] = ka
a.nNew++
}
@ -359,16 +378,16 @@ func (a *AddrBook) addAddress(addr, src *NetAddress) {
// Make space in the new buckets by expiring the really bad entries.
// If no bad entries are available we look at a few and remove the oldest.
func (a *AddrBook) expireNew(bucket int) {
var oldest *KnownAddress
var oldest *knownAddress
for k, v := range a.addrNew[bucket] {
// If an entry is bad, throw it away
if v.Bad() {
if v.IsBad() {
log.Tracef("expiring bad address %v", k)
delete(a.addrNew[bucket], k)
v.NewRefs--
if v.NewRefs == 0 {
a.nNew--
delete(a.addrIndex, k)
delete(a.addrNewIndex, k)
}
return
}
@ -388,12 +407,12 @@ func (a *AddrBook) expireNew(bucket int) {
oldest.NewRefs--
if oldest.NewRefs == 0 {
a.nNew--
delete(a.addrIndex, key)
delete(a.addrNewIndex, key)
}
}
}
func (a *AddrBook) moveToOld(ka *KnownAddress) {
func (a *AddrBook) moveToOld(ka *knownAddress) {
// Remove from all new buckets.
// Remember one of those new buckets.
addrKey := ka.Addr.String()
@ -448,7 +467,7 @@ func (a *AddrBook) moveToOld(ka *KnownAddress) {
// Returns the index in old bucket of oldest entry.
func (a *AddrBook) pickOld(bucket int) int {
var oldest *KnownAddress
var oldest *knownAddress
var oldestIndex int
for i, ka := range a.addrOld[bucket] {
if oldest == nil || ka.LastAttempt.Before(oldest.LastAttempt.Time) {
@ -547,3 +566,102 @@ func GroupKey(na *NetAddress) string {
return (&net.IPNet{IP: na.IP, Mask: net.CIDRMask(bits, 128)}).String()
}
/*
knownAddress
tracks information about a known network address that is used
to determine how viable an address is.
*/
type knownAddress struct {
Addr *NetAddress
Src *NetAddress
Attempts UInt32
LastAttempt Time
LastSuccess Time
NewRefs UInt16
OldBucket Int16
}
func NewknownAddress(addr *NetAddress, src *NetAddress) *knownAddress {
return &knownAddress{
Addr: addr,
Src: src,
OldBucket: -1,
LastAttempt: Time{time.Now()},
Attempts: 0,
}
}
func ReadknownAddress(r io.Reader) *knownAddress {
return &knownAddress{
Addr: ReadNetAddress(r),
Src: ReadNetAddress(r),
Attempts: ReadUInt32(r),
LastAttempt: ReadTime(r),
LastSuccess: ReadTime(r),
NewRefs: ReadUInt16(r),
OldBucket: ReadInt16(r),
}
}
func (ka *knownAddress) WriteTo(w io.Writer) (n int64, err error) {
n, err = WriteOnto(ka.Addr, w, n, err)
n, err = WriteOnto(ka.Src, w, n, err)
n, err = WriteOnto(ka.Attempts, w, n, err)
n, err = WriteOnto(ka.LastAttempt, w, n, err)
n, err = WriteOnto(ka.LastSuccess, w, n, err)
n, err = WriteOnto(ka.NewRefs, w, n, err)
n, err = WriteOnto(ka.OldBucket, w, n, err)
return
}
func (ka *knownAddress) MarkAttempt() {
now := Time{time.Now()}
ka.LastAttempt = now
ka.Attempts += 1
}
func (ka *knownAddress) MarkGood() {
now := Time{time.Now()}
ka.LastAttempt = now
ka.Attempts = 0
ka.LastSuccess = now
}
/*
An address is bad if the address in question has not been tried in the last
minute and meets one of the following criteria:
1) It claims to be from the future
2) It hasn't been seen in over a month
3) It has failed at least three times and never succeeded
4) It has failed ten times in the last week
All addresses that meet these criteria are assumed to be worthless and not
worth keeping hold of.
*/
func (ka *knownAddress) IsBad() bool {
// Has been attempted in the last minute --> good
if ka.LastAttempt.Before(time.Now().Add(-1 * time.Minute)) {
return false
}
// Over a month old?
if ka.LastAttempt.After(time.Now().Add(-1 * numMissingDays * time.Hour * 24)) {
return true
}
// Never succeeded?
if ka.LastSuccess.IsZero() && ka.Attempts >= numRetries {
return true
}
// Hasn't succeeded in too long?
if ka.LastSuccess.Before(time.Now().Add(-1*minBadDays*time.Hour*24)) &&
ka.Attempts >= maxFailures {
return true
}
return false
}

+ 159
- 0
peer/addrbook_test.go View File

@ -0,0 +1,159 @@
package peer
import (
"fmt"
"io/ioutil"
"math/rand"
"testing"
)
func createTempFileName(prefix string) string {
f, err := ioutil.TempFile("", prefix)
if err != nil {
panic(err)
}
fname := f.Name()
err = f.Close()
if err != nil {
panic(err)
}
return fname
}
func TestEmpty(t *testing.T) {
fname := createTempFileName("addrbook_test")
// t.Logf("New tempfile name: %v", fname)
// Save an empty book & load it
book := NewAddrBook(fname)
book.saveToFile(fname)
book = NewAddrBook(fname)
book.loadFromFile(fname)
if book.Size() != 0 {
t.Errorf("Expected 0 addresses, found %v", book.Size())
}
}
func randIPv4Address() *NetAddress {
for {
ip := fmt.Sprintf("%v.%v.%v.%v",
rand.Intn(254)+1,
rand.Intn(255),
rand.Intn(255),
rand.Intn(255),
)
port := rand.Intn(65535-1) + 1
addr := NewNetAddressString(fmt.Sprintf("%v:%v", ip, port))
if addr.Routable() {
return addr
}
}
}
func TestSaveAddresses(t *testing.T) {
fname := createTempFileName("addrbook_test")
//t.Logf("New tempfile name: %v", fname)
// Create some random addresses
randAddrs := []struct {
addr *NetAddress
src *NetAddress
}{}
for i := 0; i < 100; i++ {
addr := randIPv4Address()
src := randIPv4Address()
randAddrs = append(randAddrs, struct {
addr *NetAddress
src *NetAddress
}{
addr: addr,
src: src,
})
}
// Create the book & populate & save
book := NewAddrBook(fname)
for _, addrSrc := range randAddrs {
book.AddAddress(addrSrc.addr, addrSrc.src)
}
if book.Size() != 100 {
t.Errorf("Expected 100 addresses, found %v", book.Size())
}
book.saveToFile(fname)
// Reload the book
book = NewAddrBook(fname)
book.loadFromFile(fname)
// Test ...
if book.Size() != 100 {
t.Errorf("Expected 100 addresses, found %v", book.Size())
}
for _, addrSrc := range randAddrs {
addr := addrSrc.addr
src := addrSrc.src
ka := book.addrNewIndex[addr.String()]
if ka == nil {
t.Fatalf("Expected to find KnownAddress %v but wasn't there.", addr)
}
if !(ka.Addr.Equals(addr) && ka.Src.Equals(src)) {
t.Fatalf("KnownAddress doesn't match addr & src")
}
}
}
func TestPromoteToOld(t *testing.T) {
fname := createTempFileName("addrbook_test")
t.Logf("New tempfile name: %v", fname)
// Create some random addresses
randAddrs := []struct {
addr *NetAddress
src *NetAddress
}{}
for i := 0; i < 100; i++ {
addr := randIPv4Address()
src := randIPv4Address()
randAddrs = append(randAddrs, struct {
addr *NetAddress
src *NetAddress
}{
addr: addr,
src: src,
})
}
// Create the book & populate & save
book := NewAddrBook(fname)
for _, addrSrc := range randAddrs {
book.AddAddress(addrSrc.addr, addrSrc.src)
}
// Attempt all addresses.
for _, addrSrc := range randAddrs {
book.MarkAttempt(addrSrc.addr)
}
// Promote half of them
for i, addrSrc := range randAddrs {
if i%2 == 0 {
book.MarkGood(addrSrc.addr)
}
}
book.saveToFile(fname)
// Reload the book
book = NewAddrBook(fname)
book.loadFromFile(fname)
// Test ...
if book.Size() != 100 {
t.Errorf("Expected 100 addresses, found %v", book.Size())
}
// TODO: do more testing :)
}

+ 0
- 105
peer/knownaddress.go View File

@ -1,105 +0,0 @@
package peer
import (
"io"
"time"
. "github.com/tendermint/tendermint/binary"
)
/*
KnownAddress
tracks information about a known network address that is used
to determine how viable an address is.
*/
type KnownAddress struct {
Addr *NetAddress
Src *NetAddress
Attempts UInt32
LastAttempt Time
LastSuccess Time
NewRefs UInt16
OldBucket Int16
}
func NewKnownAddress(addr *NetAddress, src *NetAddress) *KnownAddress {
return &KnownAddress{
Addr: addr,
Src: src,
OldBucket: -1,
LastAttempt: Time{time.Now()},
Attempts: 0,
}
}
func ReadKnownAddress(r io.Reader) *KnownAddress {
return &KnownAddress{
Addr: ReadNetAddress(r),
Src: ReadNetAddress(r),
Attempts: ReadUInt32(r),
LastAttempt: ReadTime(r),
LastSuccess: ReadTime(r),
NewRefs: ReadUInt16(r),
OldBucket: ReadInt16(r),
}
}
func (ka *KnownAddress) WriteTo(w io.Writer) (n int64, err error) {
n, err = WriteOnto(ka.Addr, w, n, err)
n, err = WriteOnto(ka.Src, w, n, err)
n, err = WriteOnto(ka.Attempts, w, n, err)
n, err = WriteOnto(ka.LastAttempt, w, n, err)
n, err = WriteOnto(ka.LastSuccess, w, n, err)
n, err = WriteOnto(ka.NewRefs, w, n, err)
n, err = WriteOnto(ka.OldBucket, w, n, err)
return
}
func (ka *KnownAddress) MarkAttempt(success bool) {
now := Time{time.Now()}
ka.LastAttempt = now
if success {
ka.LastSuccess = now
ka.Attempts = 0
} else {
ka.Attempts += 1
}
}
/*
An address is bad if the address in question has not been tried in the last
minute and meets one of the following criteria:
1) It claims to be from the future
2) It hasn't been seen in over a month
3) It has failed at least three times and never succeeded
4) It has failed ten times in the last week
All addresses that meet these criteria are assumed to be worthless and not
worth keeping hold of.
*/
func (ka *KnownAddress) Bad() bool {
// Has been attempted in the last minute --> good
if ka.LastAttempt.Before(time.Now().Add(-1 * time.Minute)) {
return false
}
// Over a month old?
if ka.LastAttempt.After(time.Now().Add(-1 * numMissingDays * time.Hour * 24)) {
return true
}
// Never succeeded?
if ka.LastSuccess.IsZero() && ka.Attempts >= numRetries {
return true
}
// Hasn't succeeded in too long?
if ka.LastSuccess.Before(time.Now().Add(-1*minBadDays*time.Hour*24)) &&
ka.Attempts >= maxFailures {
return true
}
return false
}

Loading…
Cancel
Save