mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-06-12 09:52:14 +02:00
This commit does two things: - removes the concept of allow / deny. Having this in place was a minor optimization and removing it makes the solution simpler. - changes the job dependency tracking to track sets of abstact parent jobs rather than individual parent jobs. As a note, the purpose of the ValidationBarrier is that it allows us to launch gossip validation jobs in goroutines while still ensuring that the validation order of these goroutines is adhered to when it comes to validating ChannelAnnouncement _before_ ChannelUpdate and _before_ NodeAnnouncement.
294 lines
8.2 KiB
Go
294 lines
8.2 KiB
Go
package graph_test
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/lightningnetwork/lnd/graph"
|
|
"github.com/lightningnetwork/lnd/lnwire"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// TestValidationBarrierSemaphore checks basic properties of the validation
|
|
// barrier's semaphore wrt. enqueuing/dequeuing.
|
|
func TestValidationBarrierSemaphore(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
const (
|
|
numTasks = 8
|
|
numPendingTasks = 8
|
|
timeout = 50 * time.Millisecond
|
|
)
|
|
|
|
quit := make(chan struct{})
|
|
barrier := graph.NewValidationBarrier(numTasks, quit)
|
|
|
|
// Saturate the semaphore with jobs.
|
|
for i := 0; i < numTasks; i++ {
|
|
barrier.InitJobDependencies(nil)
|
|
}
|
|
|
|
// Spawn additional tasks that will signal completion when added.
|
|
jobAdded := make(chan struct{})
|
|
for i := 0; i < numPendingTasks; i++ {
|
|
go func() {
|
|
barrier.InitJobDependencies(nil)
|
|
jobAdded <- struct{}{}
|
|
}()
|
|
}
|
|
|
|
// Check that no jobs are added while semaphore is full.
|
|
select {
|
|
case <-time.After(timeout):
|
|
// Expected since no slots open.
|
|
case <-jobAdded:
|
|
t.Fatalf("job should not have been added")
|
|
}
|
|
|
|
// Complete jobs one at a time and verify that they get added.
|
|
for i := 0; i < numPendingTasks; i++ {
|
|
barrier.CompleteJob()
|
|
|
|
select {
|
|
case <-time.After(timeout):
|
|
t.Fatalf("timeout waiting for job to be added")
|
|
case <-jobAdded:
|
|
// Expected since one slot opened up.
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestValidationBarrierQuit checks that pending validation tasks will return an
|
|
// error from WaitForDependants if the barrier's quit signal is canceled.
|
|
func TestValidationBarrierQuit(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
const (
|
|
numTasks = 8
|
|
timeout = 50 * time.Millisecond
|
|
)
|
|
|
|
quit := make(chan struct{})
|
|
barrier := graph.NewValidationBarrier(2*numTasks, quit)
|
|
|
|
// Create a set of unique channel announcements that we will prep for
|
|
// validation.
|
|
anns := make([]*lnwire.ChannelAnnouncement1, 0, numTasks)
|
|
parentJobIDs := make([]graph.JobID, 0, numTasks)
|
|
for i := 0; i < numTasks; i++ {
|
|
anns = append(anns, &lnwire.ChannelAnnouncement1{
|
|
ShortChannelID: lnwire.NewShortChanIDFromInt(uint64(i)),
|
|
NodeID1: nodeIDFromInt(uint64(2 * i)),
|
|
NodeID2: nodeIDFromInt(uint64(2*i + 1)),
|
|
})
|
|
parentJobID, err := barrier.InitJobDependencies(anns[i])
|
|
require.NoError(t, err)
|
|
|
|
parentJobIDs = append(parentJobIDs, parentJobID)
|
|
}
|
|
|
|
// Create a set of channel updates, that must wait until their
|
|
// associated channel announcement has been verified.
|
|
chanUpds := make([]*lnwire.ChannelUpdate1, 0, numTasks)
|
|
childJobIDs := make([]graph.JobID, 0, numTasks)
|
|
for i := 0; i < numTasks; i++ {
|
|
chanUpds = append(chanUpds, &lnwire.ChannelUpdate1{
|
|
ShortChannelID: lnwire.NewShortChanIDFromInt(uint64(i)),
|
|
})
|
|
childJob, err := barrier.InitJobDependencies(chanUpds[i])
|
|
require.NoError(t, err)
|
|
|
|
childJobIDs = append(childJobIDs, childJob)
|
|
}
|
|
|
|
// Spawn additional tasks that will send the error returned after
|
|
// waiting for the announcements to finish. In the background, we will
|
|
// iteratively queue the channel updates, which will send back the error
|
|
// returned from waiting.
|
|
jobErrs := make(chan error)
|
|
for i := 0; i < numTasks; i++ {
|
|
go func(ii int) {
|
|
jobErrs <- barrier.WaitForParents(
|
|
childJobIDs[ii], chanUpds[ii],
|
|
)
|
|
}(i)
|
|
}
|
|
|
|
// Check that no jobs are added while semaphore is full.
|
|
select {
|
|
case <-time.After(timeout):
|
|
// Expected since no slots open.
|
|
case <-jobErrs:
|
|
t.Fatalf("job should not have been signaled")
|
|
}
|
|
|
|
// Complete the first half of jobs, one at a time, verifying that they
|
|
// get signaled. Then, quit the barrier and check that all others exit
|
|
// with the correct error.
|
|
for i := 0; i < numTasks; i++ {
|
|
switch {
|
|
case i < numTasks/2:
|
|
err := barrier.SignalDependents(
|
|
anns[i], parentJobIDs[i],
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
barrier.CompleteJob()
|
|
|
|
// At midpoint, quit the validation barrier.
|
|
case i == numTasks/2:
|
|
close(quit)
|
|
}
|
|
|
|
var err error
|
|
select {
|
|
case <-time.After(timeout):
|
|
t.Fatalf("timeout waiting for job to be signaled")
|
|
case err = <-jobErrs:
|
|
}
|
|
|
|
switch {
|
|
// First half should return without failure.
|
|
case i < numTasks/2 && err != nil:
|
|
t.Fatalf("unexpected failure while waiting: %v", err)
|
|
|
|
// Last half should return the shutdown error.
|
|
case i >= numTasks/2 && !graph.IsError(
|
|
err, graph.ErrVBarrierShuttingDown,
|
|
):
|
|
t.Fatalf("expected failure after quitting: want %v, "+
|
|
"got %v", graph.ErrVBarrierShuttingDown, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestValidationBarrierParentJobsClear tests that creating two parent jobs for
|
|
// ChannelUpdate / NodeAnnouncement will pause child jobs until the set of
|
|
// parent jobs has cleared.
|
|
func TestValidationBarrierParentJobsClear(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
const (
|
|
numTasks = 8
|
|
timeout = time.Second
|
|
)
|
|
|
|
quit := make(chan struct{})
|
|
barrier := graph.NewValidationBarrier(numTasks, quit)
|
|
|
|
sharedScid := lnwire.NewShortChanIDFromInt(0)
|
|
sharedNodeID := nodeIDFromInt(0)
|
|
|
|
// Create a set of gossip messages that depend on each other. ann1 and
|
|
// ann2 share the ShortChannelID field. ann1 and ann3 share both the
|
|
// ShortChannelID field and the NodeID1 field. These shared values let
|
|
// us test the "set" properties of the ValidationBarrier.
|
|
ann1 := &lnwire.ChannelAnnouncement1{
|
|
ShortChannelID: sharedScid,
|
|
NodeID1: sharedNodeID,
|
|
NodeID2: nodeIDFromInt(1),
|
|
}
|
|
parentID1, err := barrier.InitJobDependencies(ann1)
|
|
require.NoError(t, err)
|
|
|
|
ann2 := &lnwire.ChannelAnnouncement1{
|
|
ShortChannelID: sharedScid,
|
|
NodeID1: nodeIDFromInt(2),
|
|
NodeID2: nodeIDFromInt(3),
|
|
}
|
|
parentID2, err := barrier.InitJobDependencies(ann2)
|
|
require.NoError(t, err)
|
|
|
|
ann3 := &lnwire.ChannelAnnouncement1{
|
|
ShortChannelID: sharedScid,
|
|
NodeID1: sharedNodeID,
|
|
NodeID2: nodeIDFromInt(10),
|
|
}
|
|
parentID3, err := barrier.InitJobDependencies(ann3)
|
|
require.NoError(t, err)
|
|
|
|
// Create the ChannelUpdate & NodeAnnouncement messages.
|
|
upd1 := &lnwire.ChannelUpdate1{
|
|
ShortChannelID: sharedScid,
|
|
}
|
|
childID1, err := barrier.InitJobDependencies(upd1)
|
|
require.NoError(t, err)
|
|
|
|
node1 := &lnwire.NodeAnnouncement{
|
|
NodeID: sharedNodeID,
|
|
}
|
|
childID2, err := barrier.InitJobDependencies(node1)
|
|
require.NoError(t, err)
|
|
|
|
run := func(vb *graph.ValidationBarrier, childJobID graph.JobID,
|
|
job interface{}, resp chan error, start chan error) {
|
|
|
|
close(start)
|
|
|
|
err := vb.WaitForParents(childJobID, job)
|
|
resp <- err
|
|
}
|
|
|
|
errChan := make(chan error, 2)
|
|
|
|
startChan1 := make(chan error, 1)
|
|
startChan2 := make(chan error, 1)
|
|
|
|
go run(barrier, childID1, upd1, errChan, startChan1)
|
|
go run(barrier, childID2, node1, errChan, startChan2)
|
|
|
|
// Wait for the start signal since we are testing the case where the
|
|
// parent jobs only complete _after_ the child jobs have called. Note
|
|
// that there is technically an edge case where we receive the start
|
|
// signal and call SignalDependents before WaitForParents can actually
|
|
// be called in the goroutine launched above. In this case, which
|
|
// arises due to our inability to control precisely when these VB
|
|
// methods are scheduled (as they are in different goroutines), the
|
|
// test should still pass as we want to test that validation jobs are
|
|
// completing and not stalling. In other words, this issue with the
|
|
// test itself is good as it actually randomizes some of the ordering,
|
|
// occasionally. This tests that the VB is robust against ordering /
|
|
// concurrency issues.
|
|
select {
|
|
case <-startChan1:
|
|
case <-time.After(timeout):
|
|
t.Fatal("timed out waiting for startChan1")
|
|
}
|
|
|
|
select {
|
|
case <-startChan2:
|
|
case <-time.After(timeout):
|
|
t.Fatal("timed out waiting for startChan2")
|
|
}
|
|
|
|
// Now we can call SignalDependents for our parent jobs.
|
|
err = barrier.SignalDependents(ann1, parentID1)
|
|
require.NoError(t, err)
|
|
|
|
err = barrier.SignalDependents(ann2, parentID2)
|
|
require.NoError(t, err)
|
|
|
|
err = barrier.SignalDependents(ann3, parentID3)
|
|
require.NoError(t, err)
|
|
|
|
select {
|
|
case <-errChan:
|
|
case <-time.After(timeout):
|
|
t.Fatal("unexpected timeout waiting for first error signal")
|
|
}
|
|
|
|
select {
|
|
case <-errChan:
|
|
case <-time.After(timeout):
|
|
t.Fatal("unexpected timeout waiting for second error signal")
|
|
}
|
|
}
|
|
|
|
// nodeIDFromInt creates a node ID by writing a uint64 to the first 8 bytes.
|
|
func nodeIDFromInt(i uint64) [33]byte {
|
|
var nodeID [33]byte
|
|
binary.BigEndian.PutUint64(nodeID[:8], i)
|
|
return nodeID
|
|
}
|