peer: knownInventory, sentNonces - use generic lru
While here, also rename and generalize limitMap and apply to other maps which need to be bounded.
This commit is contained in:
parent
e2d9cf4b55
commit
875b51c9fb
8 changed files with 34 additions and 606 deletions
1
go.mod
1
go.mod
|
@ -8,6 +8,7 @@ require (
|
||||||
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792
|
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792
|
||||||
github.com/btcsuite/winsvc v1.0.0
|
github.com/btcsuite/winsvc v1.0.0
|
||||||
github.com/davecgh/go-spew v1.1.1
|
github.com/davecgh/go-spew v1.1.1
|
||||||
|
github.com/decred/dcrd/lru v1.0.0
|
||||||
github.com/jessevdk/go-flags v1.4.0
|
github.com/jessevdk/go-flags v1.4.0
|
||||||
github.com/jrick/logrotate v1.0.0
|
github.com/jrick/logrotate v1.0.0
|
||||||
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37
|
golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -24,6 +24,8 @@ github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46f
|
||||||
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/decred/dcrd/lru v1.0.0 h1:Kbsb1SFDsIlaupWPwsPp+dkxiBY1frcS07PCPgotKz8=
|
||||||
|
github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218=
|
||||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||||
|
|
|
@ -147,6 +147,25 @@ type peerSyncState struct {
|
||||||
requestedBlocks map[chainhash.Hash]struct{}
|
requestedBlocks map[chainhash.Hash]struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// limitAdd is a helper function for maps that require a maximum limit by
|
||||||
|
// evicting a random value if adding the new value would cause it to
|
||||||
|
// overflow the maximum allowed.
|
||||||
|
func limitAdd(m map[chainhash.Hash]struct{}, hash chainhash.Hash, limit int) {
|
||||||
|
if len(m)+1 > limit {
|
||||||
|
// Remove a random entry from the map. For most compilers, Go's
|
||||||
|
// range statement iterates starting at a random item although
|
||||||
|
// that is not 100% guaranteed by the spec. The iteration order
|
||||||
|
// is not important here because an adversary would have to be
|
||||||
|
// able to pull off preimage attacks on the hashing function in
|
||||||
|
// order to target eviction of specific entries anyways.
|
||||||
|
for txHash := range m {
|
||||||
|
delete(m, txHash)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m[hash] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
// SyncManager is used to communicate block related messages with peers. The
|
// SyncManager is used to communicate block related messages with peers. The
|
||||||
// SyncManager is started as by executing Start() in a goroutine. Once started,
|
// SyncManager is started as by executing Start() in a goroutine. Once started,
|
||||||
// it selects peers to sync from and starts the initial block download. Once the
|
// it selects peers to sync from and starts the initial block download. Once the
|
||||||
|
@ -579,8 +598,7 @@ func (sm *SyncManager) handleTxMsg(tmsg *txMsg) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Do not request this transaction again until a new block
|
// Do not request this transaction again until a new block
|
||||||
// has been processed.
|
// has been processed.
|
||||||
sm.rejectedTxns[*txHash] = struct{}{}
|
limitAdd(sm.rejectedTxns, *txHash, maxRejectedTxns)
|
||||||
sm.limitMap(sm.rejectedTxns, maxRejectedTxns)
|
|
||||||
|
|
||||||
// When the error is a rule error, it means the transaction was
|
// When the error is a rule error, it means the transaction was
|
||||||
// simply rejected as opposed to something actually going wrong,
|
// simply rejected as opposed to something actually going wrong,
|
||||||
|
@ -1202,9 +1220,8 @@ func (sm *SyncManager) handleInvMsg(imsg *invMsg) {
|
||||||
// Request the block if there is not already a pending
|
// Request the block if there is not already a pending
|
||||||
// request.
|
// request.
|
||||||
if _, exists := sm.requestedBlocks[iv.Hash]; !exists {
|
if _, exists := sm.requestedBlocks[iv.Hash]; !exists {
|
||||||
sm.requestedBlocks[iv.Hash] = struct{}{}
|
limitAdd(sm.requestedBlocks, iv.Hash, maxRequestedBlocks)
|
||||||
sm.limitMap(sm.requestedBlocks, maxRequestedBlocks)
|
limitAdd(state.requestedBlocks, iv.Hash, maxRequestedBlocks)
|
||||||
state.requestedBlocks[iv.Hash] = struct{}{}
|
|
||||||
|
|
||||||
if peer.IsWitnessEnabled() {
|
if peer.IsWitnessEnabled() {
|
||||||
iv.Type = wire.InvTypeWitnessBlock
|
iv.Type = wire.InvTypeWitnessBlock
|
||||||
|
@ -1220,9 +1237,8 @@ func (sm *SyncManager) handleInvMsg(imsg *invMsg) {
|
||||||
// Request the transaction if there is not already a
|
// Request the transaction if there is not already a
|
||||||
// pending request.
|
// pending request.
|
||||||
if _, exists := sm.requestedTxns[iv.Hash]; !exists {
|
if _, exists := sm.requestedTxns[iv.Hash]; !exists {
|
||||||
sm.requestedTxns[iv.Hash] = struct{}{}
|
limitAdd(sm.requestedTxns, iv.Hash, maxRequestedTxns)
|
||||||
sm.limitMap(sm.requestedTxns, maxRequestedTxns)
|
limitAdd(state.requestedTxns, iv.Hash, maxRequestedTxns)
|
||||||
state.requestedTxns[iv.Hash] = struct{}{}
|
|
||||||
|
|
||||||
// If the peer is capable, request the txn
|
// If the peer is capable, request the txn
|
||||||
// including all witness data.
|
// including all witness data.
|
||||||
|
@ -1245,24 +1261,6 @@ func (sm *SyncManager) handleInvMsg(imsg *invMsg) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// limitMap is a helper function for maps that require a maximum limit by
|
|
||||||
// evicting a random transaction if adding a new value would cause it to
|
|
||||||
// overflow the maximum allowed.
|
|
||||||
func (sm *SyncManager) limitMap(m map[chainhash.Hash]struct{}, limit int) {
|
|
||||||
if len(m)+1 > limit {
|
|
||||||
// Remove a random entry from the map. For most compilers, Go's
|
|
||||||
// range statement iterates starting at a random item although
|
|
||||||
// that is not 100% guaranteed by the spec. The iteration order
|
|
||||||
// is not important here because an adversary would have to be
|
|
||||||
// able to pull off preimage attacks on the hashing function in
|
|
||||||
// order to target eviction of specific entries anyways.
|
|
||||||
for txHash := range m {
|
|
||||||
delete(m, txHash)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// blockHandler is the main handler for the sync manager. It must be run as a
|
// blockHandler is the main handler for the sync manager. It must be run as a
|
||||||
// goroutine. It processes block and inv messages in a separate goroutine
|
// goroutine. It processes block and inv messages in a separate goroutine
|
||||||
// from the peer handlers so the block (MsgBlock) messages are handled by a
|
// from the peer handlers so the block (MsgBlock) messages are handled by a
|
||||||
|
|
|
@ -1,127 +0,0 @@
|
||||||
// Copyright (c) 2013-2015 The btcsuite developers
|
|
||||||
// Use of this source code is governed by an ISC
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package peer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"container/list"
|
|
||||||
"fmt"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/wire"
|
|
||||||
)
|
|
||||||
|
|
||||||
// mruInventoryMap provides a concurrency safe map that is limited to a maximum
|
|
||||||
// number of items with eviction for the oldest entry when the limit is
|
|
||||||
// exceeded.
|
|
||||||
type mruInventoryMap struct {
|
|
||||||
invMtx sync.Mutex
|
|
||||||
invMap map[wire.InvVect]*list.Element // nearly O(1) lookups
|
|
||||||
invList *list.List // O(1) insert, update, delete
|
|
||||||
limit uint
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the map as a human-readable string.
|
|
||||||
//
|
|
||||||
// This function is safe for concurrent access.
|
|
||||||
func (m *mruInventoryMap) String() string {
|
|
||||||
m.invMtx.Lock()
|
|
||||||
defer m.invMtx.Unlock()
|
|
||||||
|
|
||||||
lastEntryNum := len(m.invMap) - 1
|
|
||||||
curEntry := 0
|
|
||||||
buf := bytes.NewBufferString("[")
|
|
||||||
for iv := range m.invMap {
|
|
||||||
buf.WriteString(fmt.Sprintf("%v", iv))
|
|
||||||
if curEntry < lastEntryNum {
|
|
||||||
buf.WriteString(", ")
|
|
||||||
}
|
|
||||||
curEntry++
|
|
||||||
}
|
|
||||||
buf.WriteString("]")
|
|
||||||
|
|
||||||
return fmt.Sprintf("<%d>%s", m.limit, buf.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exists returns whether or not the passed inventory item is in the map.
|
|
||||||
//
|
|
||||||
// This function is safe for concurrent access.
|
|
||||||
func (m *mruInventoryMap) Exists(iv *wire.InvVect) bool {
|
|
||||||
m.invMtx.Lock()
|
|
||||||
_, exists := m.invMap[*iv]
|
|
||||||
m.invMtx.Unlock()
|
|
||||||
|
|
||||||
return exists
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add adds the passed inventory to the map and handles eviction of the oldest
|
|
||||||
// item if adding the new item would exceed the max limit. Adding an existing
|
|
||||||
// item makes it the most recently used item.
|
|
||||||
//
|
|
||||||
// This function is safe for concurrent access.
|
|
||||||
func (m *mruInventoryMap) Add(iv *wire.InvVect) {
|
|
||||||
m.invMtx.Lock()
|
|
||||||
defer m.invMtx.Unlock()
|
|
||||||
|
|
||||||
// When the limit is zero, nothing can be added to the map, so just
|
|
||||||
// return.
|
|
||||||
if m.limit == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// When the entry already exists move it to the front of the list
|
|
||||||
// thereby marking it most recently used.
|
|
||||||
if node, exists := m.invMap[*iv]; exists {
|
|
||||||
m.invList.MoveToFront(node)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Evict the least recently used entry (back of the list) if the the new
|
|
||||||
// entry would exceed the size limit for the map. Also reuse the list
|
|
||||||
// node so a new one doesn't have to be allocated.
|
|
||||||
if uint(len(m.invMap))+1 > m.limit {
|
|
||||||
node := m.invList.Back()
|
|
||||||
lru := node.Value.(*wire.InvVect)
|
|
||||||
|
|
||||||
// Evict least recently used item.
|
|
||||||
delete(m.invMap, *lru)
|
|
||||||
|
|
||||||
// Reuse the list node of the item that was just evicted for the
|
|
||||||
// new item.
|
|
||||||
node.Value = iv
|
|
||||||
m.invList.MoveToFront(node)
|
|
||||||
m.invMap[*iv] = node
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// The limit hasn't been reached yet, so just add the new item.
|
|
||||||
node := m.invList.PushFront(iv)
|
|
||||||
m.invMap[*iv] = node
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete deletes the passed inventory item from the map (if it exists).
|
|
||||||
//
|
|
||||||
// This function is safe for concurrent access.
|
|
||||||
func (m *mruInventoryMap) Delete(iv *wire.InvVect) {
|
|
||||||
m.invMtx.Lock()
|
|
||||||
if node, exists := m.invMap[*iv]; exists {
|
|
||||||
m.invList.Remove(node)
|
|
||||||
delete(m.invMap, *iv)
|
|
||||||
}
|
|
||||||
m.invMtx.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// newMruInventoryMap returns a new inventory map that is limited to the number
|
|
||||||
// of entries specified by limit. When the number of entries exceeds the limit,
|
|
||||||
// the oldest (least recently used) entry will be removed to make room for the
|
|
||||||
// new entry.
|
|
||||||
func newMruInventoryMap(limit uint) *mruInventoryMap {
|
|
||||||
m := mruInventoryMap{
|
|
||||||
invMap: make(map[wire.InvVect]*list.Element),
|
|
||||||
invList: list.New(),
|
|
||||||
limit: limit,
|
|
||||||
}
|
|
||||||
return &m
|
|
||||||
}
|
|
|
@ -1,170 +0,0 @@
|
||||||
// Copyright (c) 2013-2016 The btcsuite developers
|
|
||||||
// Use of this source code is governed by an ISC
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package peer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
|
||||||
"github.com/btcsuite/btcd/wire"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TestMruInventoryMap ensures the MruInventoryMap behaves as expected including
|
|
||||||
// limiting, eviction of least-recently used entries, specific entry removal,
|
|
||||||
// and existence tests.
|
|
||||||
func TestMruInventoryMap(t *testing.T) {
|
|
||||||
// Create a bunch of fake inventory vectors to use in testing the mru
|
|
||||||
// inventory code.
|
|
||||||
numInvVects := 10
|
|
||||||
invVects := make([]*wire.InvVect, 0, numInvVects)
|
|
||||||
for i := 0; i < numInvVects; i++ {
|
|
||||||
hash := &chainhash.Hash{byte(i)}
|
|
||||||
iv := wire.NewInvVect(wire.InvTypeBlock, hash)
|
|
||||||
invVects = append(invVects, iv)
|
|
||||||
}
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
limit int
|
|
||||||
}{
|
|
||||||
{name: "limit 0", limit: 0},
|
|
||||||
{name: "limit 1", limit: 1},
|
|
||||||
{name: "limit 5", limit: 5},
|
|
||||||
{name: "limit 7", limit: 7},
|
|
||||||
{name: "limit one less than available", limit: numInvVects - 1},
|
|
||||||
{name: "limit all available", limit: numInvVects},
|
|
||||||
}
|
|
||||||
|
|
||||||
testLoop:
|
|
||||||
for i, test := range tests {
|
|
||||||
// Create a new mru inventory map limited by the specified test
|
|
||||||
// limit and add all of the test inventory vectors. This will
|
|
||||||
// cause evicition since there are more test inventory vectors
|
|
||||||
// than the limits.
|
|
||||||
mruInvMap := newMruInventoryMap(uint(test.limit))
|
|
||||||
for j := 0; j < numInvVects; j++ {
|
|
||||||
mruInvMap.Add(invVects[j])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure the limited number of most recent entries in the
|
|
||||||
// inventory vector list exist.
|
|
||||||
for j := numInvVects - test.limit; j < numInvVects; j++ {
|
|
||||||
if !mruInvMap.Exists(invVects[j]) {
|
|
||||||
t.Errorf("Exists #%d (%s) entry %s does not "+
|
|
||||||
"exist", i, test.name, *invVects[j])
|
|
||||||
continue testLoop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure the entries before the limited number of most recent
|
|
||||||
// entries in the inventory vector list do not exist.
|
|
||||||
for j := 0; j < numInvVects-test.limit; j++ {
|
|
||||||
if mruInvMap.Exists(invVects[j]) {
|
|
||||||
t.Errorf("Exists #%d (%s) entry %s exists", i,
|
|
||||||
test.name, *invVects[j])
|
|
||||||
continue testLoop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Readd the entry that should currently be the least-recently
|
|
||||||
// used entry so it becomes the most-recently used entry, then
|
|
||||||
// force an eviction by adding an entry that doesn't exist and
|
|
||||||
// ensure the evicted entry is the new least-recently used
|
|
||||||
// entry.
|
|
||||||
//
|
|
||||||
// This check needs at least 2 entries.
|
|
||||||
if test.limit > 1 {
|
|
||||||
origLruIndex := numInvVects - test.limit
|
|
||||||
mruInvMap.Add(invVects[origLruIndex])
|
|
||||||
|
|
||||||
iv := wire.NewInvVect(wire.InvTypeBlock,
|
|
||||||
&chainhash.Hash{0x00, 0x01})
|
|
||||||
mruInvMap.Add(iv)
|
|
||||||
|
|
||||||
// Ensure the original lru entry still exists since it
|
|
||||||
// was updated and should've have become the mru entry.
|
|
||||||
if !mruInvMap.Exists(invVects[origLruIndex]) {
|
|
||||||
t.Errorf("MRU #%d (%s) entry %s does not exist",
|
|
||||||
i, test.name, *invVects[origLruIndex])
|
|
||||||
continue testLoop
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure the entry that should've become the new lru
|
|
||||||
// entry was evicted.
|
|
||||||
newLruIndex := origLruIndex + 1
|
|
||||||
if mruInvMap.Exists(invVects[newLruIndex]) {
|
|
||||||
t.Errorf("MRU #%d (%s) entry %s exists", i,
|
|
||||||
test.name, *invVects[newLruIndex])
|
|
||||||
continue testLoop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete all of the entries in the inventory vector list,
|
|
||||||
// including those that don't exist in the map, and ensure they
|
|
||||||
// no longer exist.
|
|
||||||
for j := 0; j < numInvVects; j++ {
|
|
||||||
mruInvMap.Delete(invVects[j])
|
|
||||||
if mruInvMap.Exists(invVects[j]) {
|
|
||||||
t.Errorf("Delete #%d (%s) entry %s exists", i,
|
|
||||||
test.name, *invVects[j])
|
|
||||||
continue testLoop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestMruInventoryMapStringer tests the stringized output for the
|
|
||||||
// MruInventoryMap type.
|
|
||||||
func TestMruInventoryMapStringer(t *testing.T) {
|
|
||||||
// Create a couple of fake inventory vectors to use in testing the mru
|
|
||||||
// inventory stringer code.
|
|
||||||
hash1 := &chainhash.Hash{0x01}
|
|
||||||
hash2 := &chainhash.Hash{0x02}
|
|
||||||
iv1 := wire.NewInvVect(wire.InvTypeBlock, hash1)
|
|
||||||
iv2 := wire.NewInvVect(wire.InvTypeBlock, hash2)
|
|
||||||
|
|
||||||
// Create new mru inventory map and add the inventory vectors.
|
|
||||||
mruInvMap := newMruInventoryMap(uint(2))
|
|
||||||
mruInvMap.Add(iv1)
|
|
||||||
mruInvMap.Add(iv2)
|
|
||||||
|
|
||||||
// Ensure the stringer gives the expected result. Since map iteration
|
|
||||||
// is not ordered, either entry could be first, so account for both
|
|
||||||
// cases.
|
|
||||||
wantStr1 := fmt.Sprintf("<%d>[%s, %s]", 2, *iv1, *iv2)
|
|
||||||
wantStr2 := fmt.Sprintf("<%d>[%s, %s]", 2, *iv2, *iv1)
|
|
||||||
gotStr := mruInvMap.String()
|
|
||||||
if gotStr != wantStr1 && gotStr != wantStr2 {
|
|
||||||
t.Fatalf("unexpected string representation - got %q, want %q "+
|
|
||||||
"or %q", gotStr, wantStr1, wantStr2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// BenchmarkMruInventoryList performs basic benchmarks on the most recently
|
|
||||||
// used inventory handling.
|
|
||||||
func BenchmarkMruInventoryList(b *testing.B) {
|
|
||||||
// Create a bunch of fake inventory vectors to use in benchmarking
|
|
||||||
// the mru inventory code.
|
|
||||||
b.StopTimer()
|
|
||||||
numInvVects := 100000
|
|
||||||
invVects := make([]*wire.InvVect, 0, numInvVects)
|
|
||||||
for i := 0; i < numInvVects; i++ {
|
|
||||||
hashBytes := make([]byte, chainhash.HashSize)
|
|
||||||
rand.Read(hashBytes)
|
|
||||||
hash, _ := chainhash.NewHash(hashBytes)
|
|
||||||
iv := wire.NewInvVect(wire.InvTypeBlock, hash)
|
|
||||||
invVects = append(invVects, iv)
|
|
||||||
}
|
|
||||||
b.StartTimer()
|
|
||||||
|
|
||||||
// Benchmark the add plus evicition code.
|
|
||||||
limit := 20000
|
|
||||||
mruInvMap := newMruInventoryMap(uint(limit))
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
mruInvMap.Add(invVects[i%numInvVects])
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,125 +0,0 @@
|
||||||
// Copyright (c) 2015 The btcsuite developers
|
|
||||||
// Use of this source code is governed by an ISC
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package peer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"container/list"
|
|
||||||
"fmt"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
// mruNonceMap provides a concurrency safe map that is limited to a maximum
|
|
||||||
// number of items with eviction for the oldest entry when the limit is
|
|
||||||
// exceeded.
|
|
||||||
type mruNonceMap struct {
|
|
||||||
mtx sync.Mutex
|
|
||||||
nonceMap map[uint64]*list.Element // nearly O(1) lookups
|
|
||||||
nonceList *list.List // O(1) insert, update, delete
|
|
||||||
limit uint
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the map as a human-readable string.
|
|
||||||
//
|
|
||||||
// This function is safe for concurrent access.
|
|
||||||
func (m *mruNonceMap) String() string {
|
|
||||||
m.mtx.Lock()
|
|
||||||
defer m.mtx.Unlock()
|
|
||||||
|
|
||||||
lastEntryNum := len(m.nonceMap) - 1
|
|
||||||
curEntry := 0
|
|
||||||
buf := bytes.NewBufferString("[")
|
|
||||||
for nonce := range m.nonceMap {
|
|
||||||
buf.WriteString(fmt.Sprintf("%d", nonce))
|
|
||||||
if curEntry < lastEntryNum {
|
|
||||||
buf.WriteString(", ")
|
|
||||||
}
|
|
||||||
curEntry++
|
|
||||||
}
|
|
||||||
buf.WriteString("]")
|
|
||||||
|
|
||||||
return fmt.Sprintf("<%d>%s", m.limit, buf.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exists returns whether or not the passed nonce is in the map.
|
|
||||||
//
|
|
||||||
// This function is safe for concurrent access.
|
|
||||||
func (m *mruNonceMap) Exists(nonce uint64) bool {
|
|
||||||
m.mtx.Lock()
|
|
||||||
_, exists := m.nonceMap[nonce]
|
|
||||||
m.mtx.Unlock()
|
|
||||||
|
|
||||||
return exists
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add adds the passed nonce to the map and handles eviction of the oldest item
|
|
||||||
// if adding the new item would exceed the max limit. Adding an existing item
|
|
||||||
// makes it the most recently used item.
|
|
||||||
//
|
|
||||||
// This function is safe for concurrent access.
|
|
||||||
func (m *mruNonceMap) Add(nonce uint64) {
|
|
||||||
m.mtx.Lock()
|
|
||||||
defer m.mtx.Unlock()
|
|
||||||
|
|
||||||
// When the limit is zero, nothing can be added to the map, so just
|
|
||||||
// return.
|
|
||||||
if m.limit == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// When the entry already exists move it to the front of the list
|
|
||||||
// thereby marking it most recently used.
|
|
||||||
if node, exists := m.nonceMap[nonce]; exists {
|
|
||||||
m.nonceList.MoveToFront(node)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Evict the least recently used entry (back of the list) if the the new
|
|
||||||
// entry would exceed the size limit for the map. Also reuse the list
|
|
||||||
// node so a new one doesn't have to be allocated.
|
|
||||||
if uint(len(m.nonceMap))+1 > m.limit {
|
|
||||||
node := m.nonceList.Back()
|
|
||||||
lru := node.Value.(uint64)
|
|
||||||
|
|
||||||
// Evict least recently used item.
|
|
||||||
delete(m.nonceMap, lru)
|
|
||||||
|
|
||||||
// Reuse the list node of the item that was just evicted for the
|
|
||||||
// new item.
|
|
||||||
node.Value = nonce
|
|
||||||
m.nonceList.MoveToFront(node)
|
|
||||||
m.nonceMap[nonce] = node
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// The limit hasn't been reached yet, so just add the new item.
|
|
||||||
node := m.nonceList.PushFront(nonce)
|
|
||||||
m.nonceMap[nonce] = node
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete deletes the passed nonce from the map (if it exists).
|
|
||||||
//
|
|
||||||
// This function is safe for concurrent access.
|
|
||||||
func (m *mruNonceMap) Delete(nonce uint64) {
|
|
||||||
m.mtx.Lock()
|
|
||||||
if node, exists := m.nonceMap[nonce]; exists {
|
|
||||||
m.nonceList.Remove(node)
|
|
||||||
delete(m.nonceMap, nonce)
|
|
||||||
}
|
|
||||||
m.mtx.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// newMruNonceMap returns a new nonce map that is limited to the number of
|
|
||||||
// entries specified by limit. When the number of entries exceeds the limit,
|
|
||||||
// the oldest (least recently used) entry will be removed to make room for the
|
|
||||||
// new entry.
|
|
||||||
func newMruNonceMap(limit uint) *mruNonceMap {
|
|
||||||
m := mruNonceMap{
|
|
||||||
nonceMap: make(map[uint64]*list.Element),
|
|
||||||
nonceList: list.New(),
|
|
||||||
limit: limit,
|
|
||||||
}
|
|
||||||
return &m
|
|
||||||
}
|
|
|
@ -1,152 +0,0 @@
|
||||||
// Copyright (c) 2015 The btcsuite developers
|
|
||||||
// Use of this source code is governed by an ISC
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package peer
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TestMruNonceMap ensures the mruNonceMap behaves as expected including
|
|
||||||
// limiting, eviction of least-recently used entries, specific entry removal,
|
|
||||||
// and existence tests.
|
|
||||||
func TestMruNonceMap(t *testing.T) {
|
|
||||||
// Create a bunch of fake nonces to use in testing the mru nonce code.
|
|
||||||
numNonces := 10
|
|
||||||
nonces := make([]uint64, 0, numNonces)
|
|
||||||
for i := 0; i < numNonces; i++ {
|
|
||||||
nonces = append(nonces, uint64(i))
|
|
||||||
}
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
limit int
|
|
||||||
}{
|
|
||||||
{name: "limit 0", limit: 0},
|
|
||||||
{name: "limit 1", limit: 1},
|
|
||||||
{name: "limit 5", limit: 5},
|
|
||||||
{name: "limit 7", limit: 7},
|
|
||||||
{name: "limit one less than available", limit: numNonces - 1},
|
|
||||||
{name: "limit all available", limit: numNonces},
|
|
||||||
}
|
|
||||||
|
|
||||||
testLoop:
|
|
||||||
for i, test := range tests {
|
|
||||||
// Create a new mru nonce map limited by the specified test
|
|
||||||
// limit and add all of the test nonces. This will cause
|
|
||||||
// evicition since there are more test nonces than the limits.
|
|
||||||
mruNonceMap := newMruNonceMap(uint(test.limit))
|
|
||||||
for j := 0; j < numNonces; j++ {
|
|
||||||
mruNonceMap.Add(nonces[j])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure the limited number of most recent entries in the list
|
|
||||||
// exist.
|
|
||||||
for j := numNonces - test.limit; j < numNonces; j++ {
|
|
||||||
if !mruNonceMap.Exists(nonces[j]) {
|
|
||||||
t.Errorf("Exists #%d (%s) entry %d does not "+
|
|
||||||
"exist", i, test.name, nonces[j])
|
|
||||||
continue testLoop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure the entries before the limited number of most recent
|
|
||||||
// entries in the list do not exist.
|
|
||||||
for j := 0; j < numNonces-test.limit; j++ {
|
|
||||||
if mruNonceMap.Exists(nonces[j]) {
|
|
||||||
t.Errorf("Exists #%d (%s) entry %d exists", i,
|
|
||||||
test.name, nonces[j])
|
|
||||||
continue testLoop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Readd the entry that should currently be the least-recently
|
|
||||||
// used entry so it becomes the most-recently used entry, then
|
|
||||||
// force an eviction by adding an entry that doesn't exist and
|
|
||||||
// ensure the evicted entry is the new least-recently used
|
|
||||||
// entry.
|
|
||||||
//
|
|
||||||
// This check needs at least 2 entries.
|
|
||||||
if test.limit > 1 {
|
|
||||||
origLruIndex := numNonces - test.limit
|
|
||||||
mruNonceMap.Add(nonces[origLruIndex])
|
|
||||||
|
|
||||||
mruNonceMap.Add(uint64(numNonces) + 1)
|
|
||||||
|
|
||||||
// Ensure the original lru entry still exists since it
|
|
||||||
// was updated and should've have become the mru entry.
|
|
||||||
if !mruNonceMap.Exists(nonces[origLruIndex]) {
|
|
||||||
t.Errorf("MRU #%d (%s) entry %d does not exist",
|
|
||||||
i, test.name, nonces[origLruIndex])
|
|
||||||
continue testLoop
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure the entry that should've become the new lru
|
|
||||||
// entry was evicted.
|
|
||||||
newLruIndex := origLruIndex + 1
|
|
||||||
if mruNonceMap.Exists(nonces[newLruIndex]) {
|
|
||||||
t.Errorf("MRU #%d (%s) entry %d exists", i,
|
|
||||||
test.name, nonces[newLruIndex])
|
|
||||||
continue testLoop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete all of the entries in the list, including those that
|
|
||||||
// don't exist in the map, and ensure they no longer exist.
|
|
||||||
for j := 0; j < numNonces; j++ {
|
|
||||||
mruNonceMap.Delete(nonces[j])
|
|
||||||
if mruNonceMap.Exists(nonces[j]) {
|
|
||||||
t.Errorf("Delete #%d (%s) entry %d exists", i,
|
|
||||||
test.name, nonces[j])
|
|
||||||
continue testLoop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestMruNonceMapStringer tests the stringized output for the mruNonceMap type.
|
|
||||||
func TestMruNonceMapStringer(t *testing.T) {
|
|
||||||
// Create a couple of fake nonces to use in testing the mru nonce
|
|
||||||
// stringer code.
|
|
||||||
nonce1 := uint64(10)
|
|
||||||
nonce2 := uint64(20)
|
|
||||||
|
|
||||||
// Create new mru nonce map and add the nonces.
|
|
||||||
mruNonceMap := newMruNonceMap(uint(2))
|
|
||||||
mruNonceMap.Add(nonce1)
|
|
||||||
mruNonceMap.Add(nonce2)
|
|
||||||
|
|
||||||
// Ensure the stringer gives the expected result. Since map iteration
|
|
||||||
// is not ordered, either entry could be first, so account for both
|
|
||||||
// cases.
|
|
||||||
wantStr1 := fmt.Sprintf("<%d>[%d, %d]", 2, nonce1, nonce2)
|
|
||||||
wantStr2 := fmt.Sprintf("<%d>[%d, %d]", 2, nonce2, nonce1)
|
|
||||||
gotStr := mruNonceMap.String()
|
|
||||||
if gotStr != wantStr1 && gotStr != wantStr2 {
|
|
||||||
t.Fatalf("unexpected string representation - got %q, want %q "+
|
|
||||||
"or %q", gotStr, wantStr1, wantStr2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// BenchmarkMruNonceList performs basic benchmarks on the most recently used
|
|
||||||
// nonce handling.
|
|
||||||
func BenchmarkMruNonceList(b *testing.B) {
|
|
||||||
// Create a bunch of fake nonces to use in benchmarking the mru nonce
|
|
||||||
// code.
|
|
||||||
b.StopTimer()
|
|
||||||
numNonces := 100000
|
|
||||||
nonces := make([]uint64, 0, numNonces)
|
|
||||||
for i := 0; i < numNonces; i++ {
|
|
||||||
nonces = append(nonces, uint64(i))
|
|
||||||
}
|
|
||||||
b.StartTimer()
|
|
||||||
|
|
||||||
// Benchmark the add plus evicition code.
|
|
||||||
limit := 20000
|
|
||||||
mruNonceMap := newMruNonceMap(uint(limit))
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
mruNonceMap.Add(nonces[i%numNonces])
|
|
||||||
}
|
|
||||||
}
|
|
13
peer/peer.go
13
peer/peer.go
|
@ -24,6 +24,7 @@ import (
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
"github.com/btcsuite/go-socks/socks"
|
"github.com/btcsuite/go-socks/socks"
|
||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
"github.com/decred/dcrd/lru"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -82,7 +83,7 @@ var (
|
||||||
|
|
||||||
// sentNonces houses the unique nonces that are generated when pushing
|
// sentNonces houses the unique nonces that are generated when pushing
|
||||||
// version messages that are used to detect self connections.
|
// version messages that are used to detect self connections.
|
||||||
sentNonces = newMruNonceMap(50)
|
sentNonces = lru.NewCache(50)
|
||||||
|
|
||||||
// allowSelfConns is only used to allow the tests to bypass the self
|
// allowSelfConns is only used to allow the tests to bypass the self
|
||||||
// connection detecting and disconnect logic since they intentionally
|
// connection detecting and disconnect logic since they intentionally
|
||||||
|
@ -450,7 +451,7 @@ type Peer struct {
|
||||||
|
|
||||||
wireEncoding wire.MessageEncoding
|
wireEncoding wire.MessageEncoding
|
||||||
|
|
||||||
knownInventory *mruInventoryMap
|
knownInventory lru.Cache
|
||||||
prevGetBlocksMtx sync.Mutex
|
prevGetBlocksMtx sync.Mutex
|
||||||
prevGetBlocksBegin *chainhash.Hash
|
prevGetBlocksBegin *chainhash.Hash
|
||||||
prevGetBlocksStop *chainhash.Hash
|
prevGetBlocksStop *chainhash.Hash
|
||||||
|
@ -1626,7 +1627,7 @@ out:
|
||||||
|
|
||||||
// Don't send inventory that became known after
|
// Don't send inventory that became known after
|
||||||
// the initial check.
|
// the initial check.
|
||||||
if p.knownInventory.Exists(iv) {
|
if p.knownInventory.Contains(iv) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1832,7 +1833,7 @@ func (p *Peer) QueueMessageWithEncoding(msg wire.Message, doneChan chan<- struct
|
||||||
func (p *Peer) QueueInventory(invVect *wire.InvVect) {
|
func (p *Peer) QueueInventory(invVect *wire.InvVect) {
|
||||||
// Don't add the inventory to the send queue if the peer is already
|
// Don't add the inventory to the send queue if the peer is already
|
||||||
// known to have it.
|
// known to have it.
|
||||||
if p.knownInventory.Exists(invVect) {
|
if p.knownInventory.Contains(invVect) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1891,7 +1892,7 @@ func (p *Peer) readRemoteVersionMsg() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detect self connections.
|
// Detect self connections.
|
||||||
if !allowSelfConns && sentNonces.Exists(msg.Nonce) {
|
if !allowSelfConns && sentNonces.Contains(msg.Nonce) {
|
||||||
return errors.New("disconnecting peer connected to self")
|
return errors.New("disconnecting peer connected to self")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2224,7 +2225,7 @@ func newPeerBase(origCfg *Config, inbound bool) *Peer {
|
||||||
p := Peer{
|
p := Peer{
|
||||||
inbound: inbound,
|
inbound: inbound,
|
||||||
wireEncoding: wire.BaseEncoding,
|
wireEncoding: wire.BaseEncoding,
|
||||||
knownInventory: newMruInventoryMap(maxKnownInventory),
|
knownInventory: lru.NewCache(maxKnownInventory),
|
||||||
stallControl: make(chan stallControlMsg, 1), // nonblocking sync
|
stallControl: make(chan stallControlMsg, 1), // nonblocking sync
|
||||||
outputQueue: make(chan outMsg, outputBufferSize),
|
outputQueue: make(chan outMsg, outputBufferSize),
|
||||||
sendQueue: make(chan outMsg, 1), // nonblocking sync
|
sendQueue: make(chan outMsg, 1), // nonblocking sync
|
||||||
|
|
Loading…
Reference in a new issue