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

209 lines
4.5 KiB
Go

// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package rpcchainvm
import (
"context"
"sync"
"sync/atomic"
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/luxfi/log"
)
type notifier func(_ context.Context, msg Message) error
func (n notifier) Notify(ctx context.Context, msg Message) error {
return n(ctx, msg)
}
func TestNotifier(t *testing.T) {
var wg sync.WaitGroup
wg.Add(1)
notifier := notifier(func(_ context.Context, msg Message) error {
defer wg.Done()
require.Equal(t, PendingTxs, msg)
return nil
})
c := make(chan Message)
subscriber := func(ctx context.Context) (Message, error) {
select {
case <-ctx.Done():
return 0, ctx.Err()
case msg := <-c:
return msg, nil
}
}
nf := NewNotificationForwarder(
Notifier(notifier),
subscriber,
&log.NoLog{})
defer nf.Close()
c <- PendingTxs
wg.Wait()
}
func TestNotifierStopWhileSubscribing(_ *testing.T) {
notifier := notifier(func(ctx context.Context, _ Message) error {
<-ctx.Done()
return nil
})
var subscribed sync.WaitGroup
subscribed.Add(1)
subscribe := func(ctx context.Context) (Message, error) {
subscribed.Done()
<-ctx.Done()
return 0, nil
}
nf := NewNotificationForwarder(
Notifier(notifier),
subscribe,
&log.NoLog{})
subscribed.Wait()
nf.Close()
}
func TestNotifierWaitForPrefChangeAfterNotify(t *testing.T) {
var notifiedCount uint32
engine := Notifier(notifier(func(_ context.Context, _ Message) error {
atomic.AddUint32(&notifiedCount, 1)
return nil
}))
subscribe := func(context.Context) (Message, error) {
return 0, nil
}
nf := NewNotificationForwarder(engine, subscribe, &log.NoLog{})
defer nf.Close()
require.Eventually(t, func() bool {
return atomic.LoadUint32(&notifiedCount) == 1
}, time.Minute, 10*time.Millisecond)
require.Never(t, func() bool {
return atomic.LoadUint32(&notifiedCount) != 1
}, time.Millisecond*100, 10*time.Millisecond)
nf.CheckForEvent()
require.Eventually(t, func() bool {
return atomic.LoadUint32(&notifiedCount) == 2
}, time.Minute, 10*time.Millisecond)
require.Never(t, func() bool {
return atomic.LoadUint32(&notifiedCount) != 2
}, time.Millisecond*100, 10*time.Millisecond)
}
func TestNotifierReSubscribeAtPrefChange(t *testing.T) {
notifications := make(chan Message)
engine := Notifier(notifier(func(ctx context.Context, msg Message) error {
select {
case <-ctx.Done():
return ctx.Err()
case notifications <- msg:
}
<-ctx.Done()
return ctx.Err()
}))
messages := make(chan Message)
signal := make(chan struct{})
subscriber := func(ctx context.Context) (Message, error) {
select {
case <-signal:
default:
close(signal)
<-ctx.Done()
return 0, ctx.Err()
}
select {
case <-ctx.Done():
return 0, ctx.Err()
case msg := <-messages:
return msg, nil
}
}
nf := NewNotificationForwarder(engine, subscriber, &log.NoLog{})
defer nf.Close()
select {
case <-signal:
nf.CheckForEvent()
case <-time.After(time.Minute):
require.FailNow(t, "Timed out waiting for the notification forwarder to subscribe")
}
select {
case messages <- PendingTxs:
case <-time.After(time.Minute):
require.FailNow(t, "Timed out waiting to send message to notification forwarder")
}
select {
case msg := <-notifications:
require.Equal(t, PendingTxs, msg)
case <-time.After(time.Minute):
require.FailNow(t, "Timed out waiting for notification forwarder to forward message")
}
}
// TestNotifierWithoutLoggerDoesNotPanic pins the forwarder to surviving an
// unset Log.
//
// The constructor spawns the forwarding goroutine itself, so a caller cannot
// set Log afterwards — passing nil is fatal at the instant of construction.
// And forwardNotification logs before it does anything else, on a goroutine
// whose panic no caller can recover, so the nil dereference takes the whole
// process with it rather than failing one VM.
func TestNotifierWithoutLoggerDoesNotPanic(t *testing.T) {
var wg sync.WaitGroup
wg.Add(1)
notifier := notifier(func(_ context.Context, msg Message) error {
defer wg.Done()
require.Equal(t, PendingTxs, msg)
return nil
})
c := make(chan Message)
subscriber := func(ctx context.Context) (Message, error) {
select {
case <-ctx.Done():
return 0, ctx.Err()
case msg := <-c:
return msg, nil
}
}
// No logger. The forwarder still forwards.
nf := NewNotificationForwarder(Notifier(notifier), subscriber, nil)
defer nf.Close()
c <- PendingTxs
wg.Wait()
require.NotNil(t, nf.Log, "an unset Log must be answered before the loop starts")
}