Skip to content

Commit

Permalink
feat(addrmgr): use localStorage instead of file-system on js
Browse files Browse the repository at this point in the history
  • Loading branch information
linden committed Feb 18, 2024
1 parent a096a22 commit 36e494c
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 20 deletions.
45 changes: 26 additions & 19 deletions addrmgr/addrmanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ import (
"encoding/base32"
"encoding/binary"
"encoding/json"
"errors"
"fmt"
"io"
"math/rand"
"net"
"os"
"path/filepath"
"strconv"
"strings"
Expand All @@ -27,11 +27,21 @@ import (
"github.com/btcsuite/btcd/wire"
)

var ErrNotExist = errors.New("store does not exist")

// store is a basic storage interface. Either using the file system or localStorage in the browser.
type store interface {
Reader() (io.ReadCloser, error)
Writer() (io.WriteCloser, error)
Remove() error
String() string
}

// AddrManager provides a concurrency safe address manager for caching potential
// peers on the bitcoin network.
type AddrManager struct {
mtx sync.RWMutex
peersFile string
store store
lookupFunc func(string) ([]net.IP, error)
rand *rand.Rand
key [32]byte
Expand Down Expand Up @@ -407,15 +417,15 @@ func (a *AddrManager) savePeers() {
}
}

w, err := os.Create(a.peersFile)
w, err := a.store.Writer()
if err != nil {
log.Errorf("Error opening file %s: %v", a.peersFile, err)
log.Errorf("Error opening store %s: %v", a.store, err)
return
}
enc := json.NewEncoder(w)
defer w.Close()
if err := enc.Encode(&sam); err != nil {
log.Errorf("Failed to encode file %s: %v", a.peersFile, err)
log.Errorf("Failed to encode peers %s: %v", a.store, err)
return
}
}
Expand All @@ -426,38 +436,35 @@ func (a *AddrManager) loadPeers() {
a.mtx.Lock()
defer a.mtx.Unlock()

err := a.deserializePeers(a.peersFile)
err := a.deserializePeers()
if err != nil {
log.Errorf("Failed to parse file %s: %v", a.peersFile, err)
log.Errorf("Failed to parse store %s: %v", a.store, err)
// if it is invalid we nuke the old one unconditionally.
err = os.Remove(a.peersFile)
err = a.store.Remove()
if err != nil {
log.Warnf("Failed to remove corrupt peers file %s: %v",
a.peersFile, err)
log.Warnf("Failed to remove corrupt peers %s: %v", a.store, err)
}
a.reset()
return
}
log.Infof("Loaded %d addresses from file '%s'", a.numAddresses(), a.peersFile)
log.Infof("Loaded %d addresses from store '%s'", a.numAddresses(), a.store)
}

func (a *AddrManager) deserializePeers(filePath string) error {

_, err := os.Stat(filePath)
if os.IsNotExist(err) {
func (a *AddrManager) deserializePeers() error {
r, err := a.store.Reader()
if errors.Is(err, ErrNotExist) {
return nil
}
r, err := os.Open(filePath)
if err != nil {
return fmt.Errorf("%s error opening file: %v", filePath, err)
return fmt.Errorf("error opening store: %v", err)
}
defer r.Close()

var sam serializedAddrManager
dec := json.NewDecoder(r)
err = dec.Decode(&sam)
if err != nil {
return fmt.Errorf("error reading %s: %v", filePath, err)
return fmt.Errorf("error reading %s: %v", a.store, err)
}

// Since decoding JSON is backwards compatible (i.e., only decodes
Expand Down Expand Up @@ -1206,7 +1213,7 @@ func (a *AddrManager) GetBestLocalAddress(remoteAddr *wire.NetAddressV2) *wire.N
// Use Start to begin processing asynchronous address updates.
func New(dataDir string, lookupFunc func(string) ([]net.IP, error)) *AddrManager {
am := AddrManager{
peersFile: filepath.Join(dataDir, "peers.json"),
store: NewStore(filepath.Join(dataDir, "peers.json")),
lookupFunc: lookupFunc,
rand: rand.New(rand.NewSource(time.Now().UnixNano())),
quit: make(chan struct{}),
Expand Down
43 changes: 43 additions & 0 deletions addrmgr/addrstore.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//go:build !js && !wasm

package addrmgr

import (
"io"
"os"
)

type Store struct {
path string
}

func (s *Store) Reader() (io.ReadCloser, error) {
// Open the file.
r, err := os.Open(s.path)

// Convert into a generic error.
if os.IsNotExist(err) {
return nil, ErrNotExist
}

return r, err
}

func (s *Store) Writer() (io.WriteCloser, error) {
// Create or open the file.
return os.Create(s.path)
}

func (s *Store) Remove() error {
return os.Remove(s.path)
}

func (s *Store) String() string {
return s.path
}

func NewStore(path string) *Store {
return &Store{
path: path,
}
}
87 changes: 87 additions & 0 deletions addrmgr/addrstore_js.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
//go:build js && wasm

package addrmgr

import (
"bytes"
"errors"
"io"
"strings"

"github.com/linden/localstorage"
)

type Store struct {
path string
}

func (s *Store) Reader() (io.ReadCloser, error) {
// Get the value from localStorage.
val := localstorage.Get(s.path)

// Convert into a generic error.
if val == "" {
return nil, ErrNotExist
}

// Create a new buffer storing our value.
buf := bytes.NewBufferString(val)

// Create a NOP closer, we have nothing to do upon close.
return io.NopCloser(buf), nil
}

func (s *Store) Writer() (io.WriteCloser, error) {
// Create a new writer.
return newWriter(s.path), nil
}

func (s *Store) Remove() error {
// Remove the key/value from localStorage.
localstorage.Remove(s.path)

return nil
}

func (s *Store) String() string {
return s.path
}

func NewStore(path string) *Store {
return &Store{
path: path,
}
}

// writer updates the localStorage on write.
type writer struct {
path string
closed bool
builder strings.Builder
}

func (w *writer) Write(p []byte) (int, error) {
if w.closed {
return 0, errors.New("writer already closed")
}

// Write the bytes to our string builder.
n, _ := w.builder.Write(p)

// Update the localStorage value.
localstorage.Set(w.path, w.builder.String())

// Return the length written,
return n, nil
}

func (w *writer) Close() error {
w.closed = true
return nil
}

func newWriter(path string) *writer {
return &writer{
path: path,
}
}
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ require (
github.com/gorilla/websocket v1.5.0
github.com/jessevdk/go-flags v1.4.0
github.com/jrick/logrotate v1.0.0
github.com/linden/localstorage v0.0.0-20231117043609-5d94f0a86609
github.com/stretchr/testify v1.8.4
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
Expand Down Expand Up @@ -62,4 +63,4 @@ retract (
v0.13.0-beta
)

go 1.17
go 1.21.2
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ github.com/jrick/logrotate v1.0.0 h1:lQ1bL/n9mBNeIXoTUoYRlK4dHuNJVofX9oWqBtPnSzI
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23 h1:FOOIBWrEkLgmlgGfMuZT83xIwfPDxEI2OHu6xUmJMFE=
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
github.com/linden/localstorage v0.0.0-20231116131807-4984ea9234ae h1:aShKD5W7/reF6oTj+JsQP/Yf/7B3sYiqx6K9tIp6KFI=
github.com/linden/localstorage v0.0.0-20231116131807-4984ea9234ae/go.mod h1:uQBC250C4YSyzxNly4CR02PT2VKoO3H9zlJXbUXVLl8=
github.com/linden/localstorage v0.0.0-20231117043609-5d94f0a86609 h1:oRjHnrgw7Jo+B0MokX+2L7ugroptSEhvJpc3RenpZL8=
github.com/linden/localstorage v0.0.0-20231117043609-5d94f0a86609/go.mod h1:uQBC250C4YSyzxNly4CR02PT2VKoO3H9zlJXbUXVLl8=
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
Expand Down

0 comments on commit 36e494c

Please sign in to comment.