diff --git a/libs/pidstore/pidstore.go b/libs/pidstore/pidstore.go new file mode 100644 index 0000000000..2d4eb870a8 --- /dev/null +++ b/libs/pidstore/pidstore.go @@ -0,0 +1,67 @@ +package pidstore + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/ipfs/go-datastore" + "github.com/ipfs/go-datastore/namespace" + logging "github.com/ipfs/go-log/v2" + "github.com/libp2p/go-libp2p/core/peer" +) + +var ( + storePrefix = datastore.NewKey("pidstore") + peersKey = datastore.NewKey("peers") + + log = logging.Logger("pidstore") +) + +// PeerIDStore is used to store/load peers to/from disk. +type PeerIDStore struct { + ds datastore.Datastore +} + +// NewPeerIDStore creates a new peer ID store backed by the given datastore. +func NewPeerIDStore(ds datastore.Datastore) *PeerIDStore { + return &PeerIDStore{ + ds: namespace.Wrap(ds, storePrefix), + } +} + +// Load loads the peers from datastore and returns them. +func (p *PeerIDStore) Load(ctx context.Context) ([]peer.ID, error) { + log.Debug("Loading peers") + + bin, err := p.ds.Get(ctx, peersKey) + if err != nil { + return nil, fmt.Errorf("pidstore: loading peers from datastore: %w", err) + } + + var peers []peer.ID + err = json.Unmarshal(bin, &peers) + if err != nil { + return nil, fmt.Errorf("pidstore: unmarshalling peer IDs: %w", err) + } + + log.Infow("Loaded peers from disk", "amount", len(peers)) + return peers, nil +} + +// Put persists the given peer IDs to the datastore. +func (p *PeerIDStore) Put(ctx context.Context, peers []peer.ID) error { + log.Debugw("Persisting peers to disk", "amount", len(peers)) + + bin, err := json.Marshal(peers) + if err != nil { + return fmt.Errorf("pidstore: marshal peerlist: %w", err) + } + + if err = p.ds.Put(ctx, peersKey, bin); err != nil { + return fmt.Errorf("pidstore: error writing to datastore: %w", err) + } + + log.Infow("Persisted peers successfully", "amount", len(peers)) + return nil +} diff --git a/libs/pidstore/pidstore_test.go b/libs/pidstore/pidstore_test.go new file mode 100644 index 0000000000..eafceff3fe --- /dev/null +++ b/libs/pidstore/pidstore_test.go @@ -0,0 +1,59 @@ +package pidstore + +import ( + "context" + "crypto/rand" + "crypto/rsa" + "testing" + "time" + + "github.com/ipfs/go-datastore" + "github.com/ipfs/go-datastore/sync" + "github.com/libp2p/go-libp2p/core/crypto" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestPutLoad(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer t.Cleanup(cancel) + + peerstore := NewPeerIDStore(sync.MutexWrap(datastore.NewMapDatastore())) + + ids, err := generateRandomPeerList(10) + require.NoError(t, err) + + err = peerstore.Put(ctx, ids) + require.NoError(t, err) + + retrievedPeerlist, err := peerstore.Load(ctx) + require.NoError(t, err) + + assert.Equal(t, len(ids), len(retrievedPeerlist)) + assert.Equal(t, ids, retrievedPeerlist) +} + +func generateRandomPeerList(length int) ([]peer.ID, error) { + peerlist := make([]peer.ID, length) + for i := range peerlist { + key, err := rsa.GenerateKey(rand.Reader, 2096) + if err != nil { + return nil, err + } + + _, pubkey, err := crypto.KeyPairFromStdKey(key) + if err != nil { + return nil, err + } + + peerID, err := peer.IDFromPublicKey(pubkey) + if err != nil { + return nil, err + } + + peerlist[i] = peerID + } + + return peerlist, nil +}