Files
zeekay 7cf87b230b node-unified is the node
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.
2026-09-07 16:15:25 -07:00

148 lines
3.8 KiB
Go

// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package rpcchainvm
import (
"context"
"fmt"
"sync"
"github.com/luxfi/log"
)
// Message represents a notification message type
type Message uint32
const (
_ Message = iota
// PendingTxs indicates pending transactions notification
PendingTxs
)
func (m Message) String() string {
switch m {
case PendingTxs:
return "PendingTxs"
default:
return fmt.Sprintf("Unknown(%d)", m)
}
}
// Subscription is a function that blocks until either the given context is cancelled, or a message is returned.
// It is used to receive messages from a VM such as Pending transactions, state sync completion, etc.
// The function returns the message received, or an error if the context is cancelled.
type Subscription func(ctx context.Context) (Message, error)
type Notifier interface {
Notify(context.Context, Message) error
}
// NotificationForwarder is a component that listens for notifications from a Subscription,
// and forwards them to a Notifier.
// When CheckForEvent is called mid-subscription, it retries the subscription.
// After Notify is called, it waits for CheckForEvent to be called before subscribing again.
type NotificationForwarder struct {
Engine Notifier
Subscribe Subscription
Log log.Logger
lock sync.Mutex
executing sync.WaitGroup
execCtx context.Context
haltExecution context.CancelFunc
abortContext context.CancelFunc
}
func NewNotificationForwarder(
engine Notifier,
subscribe Subscription,
log log.Logger,
) *NotificationForwarder {
nf := &NotificationForwarder{
Engine: engine,
Subscribe: subscribe,
Log: log,
}
nf.start()
return nf
}
func (nf *NotificationForwarder) start() {
// The loop below logs before it does anything else, and it runs on a
// goroutine whose panic no caller can recover. Log is exported and the
// constructor also takes one, so there are two ways to leave it unset;
// answering it here covers both, because this is the one place the loop
// begins. Noop discards what it is given, so a caller that wanted silence
// still gets it.
if nf.Log == nil {
nf.Log = log.Noop()
}
nf.executing.Add(1)
nf.execCtx, nf.haltExecution = context.WithCancel(context.Background())
go nf.run()
}
func (nf *NotificationForwarder) run() {
defer nf.executing.Done()
for nf.execCtx.Err() == nil {
nf.forwardNotification()
}
}
func (nf *NotificationForwarder) forwardNotification() {
ctx := nf.setAndGetContext()
defer nf.cancelContext()
nf.Log.Debug("Subscribing to notifications")
msg, err := nf.Subscribe(ctx)
if err != nil {
nf.Log.Debug("Failed subscribing to notifications", "error", err)
return
}
nf.Log.Debug("Received notification", "msg", msg)
if err := nf.Engine.Notify(ctx, msg); err != nil {
nf.Log.Debug("Failed notifying engine", "error", err)
return
}
// Wait for the context to be cancelled before proceeding to the next subscription,
// in order to subscribe after a block was accepted or a state sync was completed.
<-ctx.Done()
}
// CheckForEvent cancels any outstanding WaitForEvent calls and schedules a new WaitForEvent call.
func (nf *NotificationForwarder) CheckForEvent() {
nf.cancelContext()
}
func (nf *NotificationForwarder) cancelContext() {
nf.lock.Lock()
defer nf.lock.Unlock()
if nf.abortContext != nil {
nf.abortContext()
}
}
func (nf *NotificationForwarder) setAndGetContext() context.Context {
ctx, cancel := context.WithCancel(nf.execCtx)
nf.lock.Lock()
defer nf.lock.Unlock()
nf.abortContext = cancel
return ctx
}
// Close cancels any outstanding WaitForEvent calls and waits for them to return.
// After Close returns, no future WaitForEvent calls will be made by the notification forwarder.
func (nf *NotificationForwarder) Close() {
defer nf.executing.Wait()
nf.haltExecution()
}