mirror of
https://github.com/luxfi/node.git
synced 2026-10-30 15:24:05 +00:00
The module already called itself github.com/luxfi/node while containing no node in Go: cmd/luxd was twenty-one lines that printed the size of a few types, and the Go column of the matrix measured a VM plugin. A repo that fetches the thing it claims to be has not replaced it, so the Go sources move in — 1515 files, the daemon and everything under it — and the build reaches outside this module for nothing. Rust and C++ sit beside it unchanged and are still measured against it by the differential. That is the point of one repo: the reference implementation and the two ports are the same checkout, so parity is a thing you can run rather than a thing you assert. Verified: builds, and the closure carries no lux-private — checked at build time rather than believed. The daemon starts twelve chains on a local network (P X C Q Z A B G K M F, plus D through its plugin) and reports healthy.
215 lines
4.9 KiB
Go
215 lines
4.9 KiB
Go
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package pubsub
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"github.com/go-json-experiment/json"
|
|
"github.com/gorilla/websocket"
|
|
"github.com/luxfi/log"
|
|
|
|
"github.com/luxfi/node/pubsub/bloom"
|
|
)
|
|
|
|
var (
|
|
ErrFilterNotInitialized = errors.New("filter not initialized")
|
|
ErrAddressLimit = errors.New("address limit exceeded")
|
|
ErrInvalidFilterParam = errors.New("invalid bloom filter params")
|
|
ErrInvalidCommand = errors.New("invalid command")
|
|
_ Filter = (*connection)(nil)
|
|
)
|
|
|
|
type Filter interface {
|
|
Check(addr []byte) bool
|
|
}
|
|
|
|
// connection is a representation of the websocket connection.
|
|
type connection struct {
|
|
s *Server
|
|
|
|
// The websocket connection.
|
|
conn *websocket.Conn
|
|
|
|
// Buffered channel of outbound messages.
|
|
send chan interface{}
|
|
|
|
fp *FilterParam
|
|
|
|
active uint32
|
|
}
|
|
|
|
func (c *connection) Check(addr []byte) bool {
|
|
return c.fp.Check(addr)
|
|
}
|
|
|
|
func (c *connection) isActive() bool {
|
|
active := atomic.LoadUint32(&c.active)
|
|
return active != 0
|
|
}
|
|
|
|
func (c *connection) deactivate() {
|
|
atomic.StoreUint32(&c.active, 0)
|
|
}
|
|
|
|
func (c *connection) Send(msg interface{}) bool {
|
|
if !c.isActive() {
|
|
return false
|
|
}
|
|
select {
|
|
case c.send <- msg:
|
|
return true
|
|
default:
|
|
}
|
|
return false
|
|
}
|
|
|
|
// readPump pumps messages from the websocket connection to the hub.
|
|
//
|
|
// The application runs readPump in a per-connection goroutine. The application
|
|
// ensures that there is at most one reader on a connection by executing all
|
|
// reads from this goroutine.
|
|
func (c *connection) readPump() {
|
|
defer func() {
|
|
c.deactivate()
|
|
c.s.removeConnection(c)
|
|
|
|
// close is called by both the writePump and the readPump so one of them
|
|
// will always error
|
|
_ = c.conn.Close()
|
|
}()
|
|
|
|
c.conn.SetReadLimit(maxMessageSize)
|
|
// SetReadDeadline returns an error if the connection is corrupted
|
|
if err := c.conn.SetReadDeadline(time.Now().Add(pongWait)); err != nil {
|
|
return
|
|
}
|
|
c.conn.SetPongHandler(func(string) error {
|
|
return c.conn.SetReadDeadline(time.Now().Add(pongWait))
|
|
})
|
|
|
|
for {
|
|
err := c.readMessage()
|
|
if err != nil {
|
|
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
|
|
c.s.log.Debug("unexpected close in websockets",
|
|
log.Err(err),
|
|
)
|
|
}
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// writePump pumps messages from the hub to the websocket connection.
|
|
//
|
|
// A goroutine running writePump is started for each connection. The
|
|
// application ensures that there is at most one writer to a connection by
|
|
// executing all writes from this goroutine.
|
|
func (c *connection) writePump() {
|
|
ticker := time.NewTicker(pingPeriod)
|
|
defer func() {
|
|
c.deactivate()
|
|
ticker.Stop()
|
|
c.s.removeConnection(c)
|
|
|
|
// close is called by both the writePump and the readPump so one of them
|
|
// will always error
|
|
_ = c.conn.Close()
|
|
}()
|
|
for {
|
|
select {
|
|
case message, ok := <-c.send:
|
|
if err := c.conn.SetWriteDeadline(time.Now().Add(writeWait)); err != nil {
|
|
c.s.log.Debug("closing the connection",
|
|
log.String("reason", "failed to set the write deadline"),
|
|
log.Err(err),
|
|
)
|
|
return
|
|
}
|
|
if !ok {
|
|
// The hub closed the channel. Attempt to close the connection
|
|
// gracefully.
|
|
_ = c.conn.WriteMessage(websocket.CloseMessage, []byte{})
|
|
return
|
|
}
|
|
|
|
if err := c.conn.WriteJSON(message); err != nil {
|
|
return
|
|
}
|
|
case <-ticker.C:
|
|
if err := c.conn.SetWriteDeadline(time.Now().Add(writeWait)); err != nil {
|
|
c.s.log.Debug("closing the connection",
|
|
log.String("reason", "failed to set the write deadline"),
|
|
log.Err(err),
|
|
)
|
|
return
|
|
}
|
|
if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *connection) readMessage() error {
|
|
_, r, err := c.conn.NextReader()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cmd := &Command{}
|
|
err = json.UnmarshalRead(r, cmd)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
switch {
|
|
case cmd.NewBloom != nil:
|
|
err = c.handleNewBloom(cmd.NewBloom)
|
|
case cmd.NewSet != nil:
|
|
c.handleNewSet(cmd.NewSet)
|
|
case cmd.AddAddresses != nil:
|
|
err = c.handleAddAddresses(cmd.AddAddresses)
|
|
default:
|
|
err = ErrInvalidCommand
|
|
}
|
|
if err != nil {
|
|
c.Send(&errorMsg{
|
|
Error: err.Error(),
|
|
})
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (c *connection) handleNewBloom(cmd *NewBloom) error {
|
|
if !cmd.IsParamsValid() {
|
|
return ErrInvalidFilterParam
|
|
}
|
|
filter, err := bloom.New(int(cmd.MaxElements), float64(cmd.CollisionProb), MaxBytes)
|
|
if err != nil {
|
|
return fmt.Errorf("bloom filter creation failed %w", err)
|
|
}
|
|
c.fp.SetFilter(filter)
|
|
return nil
|
|
}
|
|
|
|
func (c *connection) handleNewSet(_ *NewSet) {
|
|
c.fp.NewSet()
|
|
}
|
|
|
|
func (c *connection) handleAddAddresses(cmd *AddAddresses) error {
|
|
if err := cmd.parseAddresses(); err != nil {
|
|
return fmt.Errorf("address parse failed %w", err)
|
|
}
|
|
err := c.fp.Add(cmd.addressIds...)
|
|
if err != nil {
|
|
return fmt.Errorf("address append failed %w", err)
|
|
}
|
|
c.s.subscribedConnections.Add(c)
|
|
return nil
|
|
}
|