-
Notifications
You must be signed in to change notification settings - Fork 2.4k
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,11 +11,11 @@ import ( | |
"encoding/base32" | ||
"encoding/binary" | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
"io" | ||
"math/rand" | ||
"net" | ||
"os" | ||
"path/filepath" | ||
"strconv" | ||
"strings" | ||
|
@@ -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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
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 | ||
|
@@ -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 | ||
} | ||
} | ||
|
@@ -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 | ||
|
@@ -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")), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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{}), | ||
|
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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing |
||
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, | ||
} | ||
} |
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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, | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,6 +23,11 @@ func appDataDir(goos, appName string, roaming bool) string { | |
return "." | ||
} | ||
|
||
// Fallback to an empty string on js since we do not have a file system. | ||
if goos == "js" { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this case need to be handled elsewhere in practice? |
||
return "" | ||
} | ||
|
||
// The caller really shouldn't prepend the appName with a period, but | ||
// if they do, handle it gracefully by trimming it. | ||
appName = strings.TrimPrefix(appName, ".") | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 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 | ||
|
@@ -62,4 +63,4 @@ retract ( | |
v0.13.0-beta | ||
) | ||
|
||
go 1.17 | ||
go 1.21.2 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IIRC |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing
godoc
comment.