Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
N
nebula
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
exchain
nebula
Commits
61d4ae66
Unverified
Commit
61d4ae66
authored
May 19, 2023
by
Adrian Sutton
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
op-node: Add extended peerstore
Supports storing gossip scores for peers.
parent
00cedf57
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
307 additions
and
0 deletions
+307
-0
extended.go
op-node/p2p/store/extended.go
+34
-0
iface.go
op-node/p2p/store/iface.go
+26
-0
scorebook.go
op-node/p2p/store/scorebook.go
+90
-0
scorebook_test.go
op-node/p2p/store/scorebook_test.go
+85
-0
serialize.go
op-node/p2p/store/serialize.go
+27
-0
serialize_test.go
op-node/p2p/store/serialize_test.go
+45
-0
No files found.
op-node/p2p/store/extended.go
0 → 100644
View file @
61d4ae66
package
store
import
(
"context"
"errors"
"fmt"
ds
"github.com/ipfs/go-datastore"
"github.com/libp2p/go-libp2p/core/peerstore"
)
type
extendedStore
struct
{
peerstore
.
Peerstore
peerstore
.
CertifiedAddrBook
*
scoreBook
}
func
NewExtendedPeerstore
(
ctx
context
.
Context
,
ps
peerstore
.
Peerstore
,
store
ds
.
Batching
)
(
ExtendedPeerstore
,
error
)
{
cab
,
ok
:=
peerstore
.
GetCertifiedAddrBook
(
ps
)
if
!
ok
{
return
nil
,
errors
.
New
(
"peerstore should also be a certified address book"
)
}
sb
,
err
:=
newScoreBook
(
ctx
,
store
)
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"create scorebook: %w"
,
err
)
}
return
&
extendedStore
{
Peerstore
:
ps
,
CertifiedAddrBook
:
cab
,
scoreBook
:
sb
,
},
nil
}
var
_
ExtendedPeerstore
=
(
*
extendedStore
)(
nil
)
op-node/p2p/store/iface.go
0 → 100644
View file @
61d4ae66
package
store
import
(
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/core/peerstore"
)
type
PeerScores
struct
{
Gossip
float64
}
// ScoreDatastore defines a type-safe API for getting and setting libp2p peer score information
type
ScoreDatastore
interface
{
// GetPeerScores returns the current scores for the specified peer
GetPeerScores
(
id
peer
.
ID
)
(
PeerScores
,
error
)
// SetGossipScore stores the latest gossip score for a peer
SetGossipScore
(
id
peer
.
ID
,
score
float64
)
error
}
// ExtendedPeerstore defines a type-safe API to work with additional peer metadata based on a libp2p peerstore.Peerstore
type
ExtendedPeerstore
interface
{
peerstore
.
Peerstore
ScoreDatastore
peerstore
.
CertifiedAddrBook
}
op-node/p2p/store/scorebook.go
0 → 100644
View file @
61d4ae66
package
store
import
(
"context"
"errors"
"fmt"
"sync"
lru
"github.com/hashicorp/golang-lru/v2"
ds
"github.com/ipfs/go-datastore"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/multiformats/go-base32"
)
type
scoreBook
struct
{
ctx
context
.
Context
store
ds
.
Batching
cache
*
lru
.
Cache
[
peer
.
ID
,
PeerScores
]
sync
.
RWMutex
}
var
scoresBase
=
ds
.
NewKey
(
"/peers/scores"
)
type
ScoreType
string
const
(
scoreDataV0
=
"0"
scoreCacheSize
=
100
)
func
newScoreBook
(
ctx
context
.
Context
,
store
ds
.
Batching
)
(
*
scoreBook
,
error
)
{
cache
,
err
:=
lru
.
New
[
peer
.
ID
,
PeerScores
](
scoreCacheSize
)
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"creating cache: %w"
,
err
)
}
return
&
scoreBook
{
ctx
:
ctx
,
store
:
store
,
cache
:
cache
,
},
nil
}
func
(
d
*
scoreBook
)
GetPeerScores
(
id
peer
.
ID
)
(
PeerScores
,
error
)
{
d
.
RLock
()
defer
d
.
RUnlock
()
return
d
.
getPeerScoresNoLock
(
id
)
}
func
(
d
*
scoreBook
)
getPeerScoresNoLock
(
id
peer
.
ID
)
(
PeerScores
,
error
)
{
scores
,
ok
:=
d
.
cache
.
Get
(
id
)
if
ok
{
return
scores
,
nil
}
data
,
err
:=
d
.
store
.
Get
(
d
.
ctx
,
scoreKey
(
id
))
if
errors
.
Is
(
err
,
ds
.
ErrNotFound
)
{
return
PeerScores
{},
nil
}
else
if
err
!=
nil
{
return
PeerScores
{},
fmt
.
Errorf
(
"load scores for peer %v: %w"
,
id
,
err
)
}
scores
,
err
=
deserializeScoresV0
(
data
)
if
err
!=
nil
{
return
PeerScores
{},
fmt
.
Errorf
(
"invalid score data for peer %v: %w"
,
id
,
err
)
}
d
.
cache
.
Add
(
id
,
scores
)
return
scores
,
nil
}
func
(
d
*
scoreBook
)
SetGossipScore
(
id
peer
.
ID
,
score
float64
)
error
{
d
.
Lock
()
defer
d
.
Unlock
()
scores
,
err
:=
d
.
getPeerScoresNoLock
(
id
)
if
err
!=
nil
{
return
err
}
scores
.
Gossip
=
score
data
,
err
:=
serializeScoresV0
(
scores
)
if
err
!=
nil
{
return
fmt
.
Errorf
(
"encode scores for peer %v: %w"
,
id
,
err
)
}
err
=
d
.
store
.
Put
(
d
.
ctx
,
scoreKey
(
id
),
data
)
if
err
!=
nil
{
return
fmt
.
Errorf
(
"storing updated scores for peer %v: %w"
,
id
,
err
)
}
d
.
cache
.
Add
(
id
,
scores
)
return
nil
}
func
scoreKey
(
id
peer
.
ID
)
ds
.
Key
{
return
scoresBase
.
ChildString
(
base32
.
RawStdEncoding
.
EncodeToString
([]
byte
(
id
)))
.
ChildString
(
scoreDataV0
)
}
op-node/p2p/store/scorebook_test.go
0 → 100644
View file @
61d4ae66
package
store
import
(
"context"
"testing"
ds
"github.com/ipfs/go-datastore"
"github.com/ipfs/go-datastore/sync"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/libp2p/go-libp2p/p2p/host/peerstore/pstoreds"
"github.com/stretchr/testify/require"
)
func
TestGetEmptyScoreComponents
(
t
*
testing
.
T
)
{
id
:=
peer
.
ID
(
"aaaa"
)
store
:=
createMemoryStore
(
t
)
assertPeerScores
(
t
,
store
,
id
,
PeerScores
{})
}
func
TestRoundTripGossipScore
(
t
*
testing
.
T
)
{
id
:=
peer
.
ID
(
"aaaa"
)
store
:=
createMemoryStore
(
t
)
score
:=
123.45
err
:=
store
.
SetGossipScore
(
id
,
score
)
require
.
NoError
(
t
,
err
)
assertPeerScores
(
t
,
store
,
id
,
PeerScores
{
Gossip
:
score
})
}
func
TestUpdateGossipScore
(
t
*
testing
.
T
)
{
id
:=
peer
.
ID
(
"aaaa"
)
store
:=
createMemoryStore
(
t
)
score
:=
123.45
require
.
NoError
(
t
,
store
.
SetGossipScore
(
id
,
444.223
))
require
.
NoError
(
t
,
store
.
SetGossipScore
(
id
,
score
))
assertPeerScores
(
t
,
store
,
id
,
PeerScores
{
Gossip
:
score
})
}
func
TestStoreScoresForMultiplePeers
(
t
*
testing
.
T
)
{
id1
:=
peer
.
ID
(
"aaaa"
)
id2
:=
peer
.
ID
(
"bbbb"
)
store
:=
createMemoryStore
(
t
)
score1
:=
123.45
score2
:=
453.22
require
.
NoError
(
t
,
store
.
SetGossipScore
(
id1
,
score1
))
require
.
NoError
(
t
,
store
.
SetGossipScore
(
id2
,
score2
))
assertPeerScores
(
t
,
store
,
id1
,
PeerScores
{
Gossip
:
score1
})
assertPeerScores
(
t
,
store
,
id2
,
PeerScores
{
Gossip
:
score2
})
}
func
TestPersistData
(
t
*
testing
.
T
)
{
id
:=
peer
.
ID
(
"aaaa"
)
score
:=
123.45
backingStore
:=
sync
.
MutexWrap
(
ds
.
NewMapDatastore
())
store
:=
createPeerstoreWithBacking
(
t
,
backingStore
)
require
.
NoError
(
t
,
store
.
SetGossipScore
(
id
,
score
))
// Close and recreate a new store from the same backing
require
.
NoError
(
t
,
store
.
Close
())
store
=
createPeerstoreWithBacking
(
t
,
backingStore
)
assertPeerScores
(
t
,
store
,
id
,
PeerScores
{
Gossip
:
score
})
}
func
assertPeerScores
(
t
*
testing
.
T
,
store
ExtendedPeerstore
,
id
peer
.
ID
,
expected
PeerScores
)
{
result
,
err
:=
store
.
GetPeerScores
(
id
)
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
result
,
expected
)
}
func
createMemoryStore
(
t
*
testing
.
T
)
ExtendedPeerstore
{
store
:=
sync
.
MutexWrap
(
ds
.
NewMapDatastore
())
return
createPeerstoreWithBacking
(
t
,
store
)
}
func
createPeerstoreWithBacking
(
t
*
testing
.
T
,
store
*
sync
.
MutexDatastore
)
ExtendedPeerstore
{
ps
,
err
:=
pstoreds
.
NewPeerstore
(
context
.
Background
(),
store
,
pstoreds
.
DefaultOpts
())
require
.
NoError
(
t
,
err
,
"Failed to create peerstore"
)
eps
,
err
:=
NewExtendedPeerstore
(
context
.
Background
(),
ps
,
store
)
require
.
NoError
(
t
,
err
)
return
eps
}
op-node/p2p/store/serialize.go
0 → 100644
View file @
61d4ae66
package
store
import
(
"bytes"
"encoding/binary"
pool
"github.com/libp2p/go-buffer-pool"
)
func
serializeScoresV0
(
scores
PeerScores
)
([]
byte
,
error
)
{
var
b
pool
.
Buffer
err
:=
binary
.
Write
(
&
b
,
binary
.
BigEndian
,
scores
.
Gossip
)
if
err
!=
nil
{
return
nil
,
err
}
return
b
.
Bytes
(),
nil
}
func
deserializeScoresV0
(
data
[]
byte
)
(
PeerScores
,
error
)
{
var
scores
PeerScores
r
:=
bytes
.
NewReader
(
data
)
err
:=
binary
.
Read
(
r
,
binary
.
BigEndian
,
&
scores
.
Gossip
)
if
err
!=
nil
{
return
PeerScores
{},
err
}
return
scores
,
nil
}
op-node/p2p/store/serialize_test.go
0 → 100644
View file @
61d4ae66
package
store
import
(
"testing"
"github.com/status-im/keycard-go/hexutils"
"github.com/stretchr/testify/require"
)
func
TestRoundtripScoresV0
(
t
*
testing
.
T
)
{
scores
:=
PeerScores
{
Gossip
:
1234.52382
,
}
data
,
err
:=
serializeScoresV0
(
scores
)
require
.
NoError
(
t
,
err
)
result
,
err
:=
deserializeScoresV0
(
data
)
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
scores
,
result
)
}
// TestParseHistoricSerializations checks that existing data can still be deserialized
// Adding new fields should not require bumping the version, only removing fields
// A new entry should be added to this test each time any fields are changed to ensure it can always be deserialized
func
TestParseHistoricSerializationsV0
(
t
*
testing
.
T
)
{
tests
:=
[]
struct
{
name
string
data
[]
byte
expected
PeerScores
}{
{
name
:
"GossipOnly"
,
data
:
hexutils
.
HexToBytes
(
"40934A18644523F6"
),
expected
:
PeerScores
{
Gossip
:
1234.52382
},
},
}
for
_
,
test
:=
range
tests
{
test
:=
test
t
.
Run
(
test
.
name
,
func
(
t
*
testing
.
T
)
{
result
,
err
:=
deserializeScoresV0
(
test
.
data
)
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
test
.
expected
,
result
)
})
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment