Files
zeekayandHanzo Dev 29cdd69b95 vm: a latch, so a VM can tell consensus there is work
Consensus calls WaitForEvent and builds nothing until it returns. Four VMs
answered it with <-ctx.Done(), which never returns, so BuildBlock was never
called and those chains could not leave genesis however much work they accepted.
The work was there; the news of it was not.

A Latch is that news. Signalling twice before the signal is taken is the same as
signalling once, since what a builder needs to know is that there is work, not
how much — a burst of submissions coalesces into one build rather than flooding
the engine, which is the concern those VMs were answering by never returning at
all. The zero value is ready to use, so whatever owns the pending work can hold
one without being constructed around it.

Living here rather than in each VM means one implementation of it instead of one
per chain.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-25 12:01:38 -07:00

51 lines
1.4 KiB
Go

// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package vm
import (
"context"
"sync"
)
// A Latch is how a VM tells consensus there is work to build a block from.
//
// Consensus calls WaitForEvent and builds nothing until it returns. A VM that
// waits only on the context therefore never builds, and its chain never leaves
// genesis however much work it has accepted — the work is there, the news of it
// is not.
//
// Signalling twice before the signal is taken is the same as signalling once:
// what a builder needs to know is that there is work, not how much. The zero
// value is ready to use.
type Latch struct {
once sync.Once
c chan struct{}
}
func (l *Latch) ch() chan struct{} {
l.once.Do(func() { l.c = make(chan struct{}, 1) })
return l.c
}
// Signal reports that there is work. It never blocks, so it is safe to call
// while holding a lock — though callers should prefer to release first, since
// the point is to let a builder run.
func (l *Latch) Signal() {
select {
case l.ch() <- struct{}{}:
default:
}
}
// WaitForEvent blocks until there is work or the context ends, which is exactly
// what the ChainVM method of the same name owes consensus. A VM delegates to it.
func (l *Latch) WaitForEvent(ctx context.Context) (Message, error) {
select {
case <-ctx.Done():
return Message{}, ctx.Err()
case <-l.ch():
return Message{Type: PendingTxs}, nil
}
}