Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Browser Support (Neutrino) #2124

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
45 changes: 26 additions & 19 deletions addrmgr/addrmanager.go
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")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing godoc comment.


// store is a basic storage interface. Either using the file system or localStorage in the browser.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

godoc string should be wrapped to 80 char columns

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")),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the store also be accepted as an optional argument so it can be properly initialized in the WASM/browser context?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah scratch that, I see the build tag usage below!

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
@@ -0,0 +1,43 @@
//go:build !js && !wasm

package addrmgr

import (
"io"
"os"
)

type Store struct {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing godoc comments.

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
@@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

}

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
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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we can just fold in the code into the project so we don't need to add a new extertnal dep? It could live in an internal package.

I checked it out, and looks like a pretty minimal set of helper funcs.

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC go.mod doesn't handle minor version specification

4 changes: 4 additions & 0 deletions go.sum
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