mirror of
https://github.com/luxfi/chains.git
synced 2026-09-24 00:26:00 +00:00
graphvm: say that this chain builds no blocks, and bridgevm: one status type
G-Chain indexes what other chains have already agreed, so it holds no state of its own to agree on and never advances past genesis. That was written down in a comment on its state and nowhere the code could be read from: BuildBlock answered a generic "not implemented", which reads as an omission, and WaitForEvent's comment said it would one day wait for queued schema updates, which invites exactly the latch the other chains just got. Both now name the reason. A block here would carry a query result, which a peer recomputes from state, or a schema, which is local configuration; neither is something the network votes on. A test pins it, so the difference between a chain with nothing to say and a chain with no way to say it stays visible. BridgeRequest.Status was a string whose valid values lived in a comment beside it, while the same lifecycle already had a type and eight constants on BridgeRequestRecord. One concept, two representations, and the one the watcher wrote to was the untyped one. It now uses the type. Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
+11
-11
@@ -249,17 +249,17 @@ type BridgeRequest struct {
|
||||
// the canonical attestation digest binds; SourceChain / DestChain remain the
|
||||
// human labels used to key clients. Nonce is the per-route replay guard
|
||||
// assigned by the source lock and enforced by the destination gateway.
|
||||
SrcChainID uint32 `json:"srcChainId"`
|
||||
DstChainID uint32 `json:"dstChainId"`
|
||||
Nonce uint64 `json:"nonce"`
|
||||
Asset ids.ID `json:"asset"`
|
||||
Amount uint64 `json:"amount"`
|
||||
Recipient []byte `json:"recipient"`
|
||||
SourceTxID ids.ID `json:"sourceTxId"`
|
||||
Confirmations uint32 `json:"confirmations"`
|
||||
Status string `json:"status"` // pending, signing, completed, failed
|
||||
MPCSignatures [][]byte `json:"mpcSignatures"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
SrcChainID uint32 `json:"srcChainId"`
|
||||
DstChainID uint32 `json:"dstChainId"`
|
||||
Nonce uint64 `json:"nonce"`
|
||||
Asset ids.ID `json:"asset"`
|
||||
Amount uint64 `json:"amount"`
|
||||
Recipient []byte `json:"recipient"`
|
||||
SourceTxID ids.ID `json:"sourceTxId"`
|
||||
Confirmations uint32 `json:"confirmations"`
|
||||
Status BridgeRequestStatus `json:"status"`
|
||||
MPCSignatures [][]byte `json:"mpcSignatures"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
|
||||
// ChainClient interface for interacting with different chains
|
||||
|
||||
+1
-1
@@ -202,7 +202,7 @@ func (w *watcher) enqueue(l lock, head uint64) {
|
||||
Recipient: append([]byte(nil), transfer.Recipient[:]...),
|
||||
SourceTxID: l.TxID,
|
||||
Confirmations: uint32(depth),
|
||||
Status: "pending",
|
||||
Status: StatusPending,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
w.vm.mu.Unlock()
|
||||
|
||||
+1
-1
@@ -94,7 +94,7 @@ func readBridgeRequest(o zap.Object) *BridgeRequest {
|
||||
CreatedAt: time.Unix(0, o.Int64(brCreated)).UTC(),
|
||||
SourceChain: string(o.Bytes(brSrcName)),
|
||||
DestChain: string(o.Bytes(brDstName)),
|
||||
Status: string(o.Bytes(brStatus)),
|
||||
Status: BridgeRequestStatus(o.Bytes(brStatus)),
|
||||
Recipient: appendBytes(o.Bytes(brRecip)),
|
||||
MPCSignatures: readSigs(o, brSigLens, brSigBlob),
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package graphvm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TestThisChainBuildsNoBlocks pins the design rather than the omission it looks
|
||||
// like. G-Chain indexes what other chains have already agreed, so it holds no
|
||||
// state of its own to agree on. A block here would carry a query result, which
|
||||
// a peer recomputes from state, or a schema, which is local configuration —
|
||||
// neither is something the network votes on.
|
||||
//
|
||||
// The other chains here had work and no way to tell consensus about it, and were
|
||||
// given a latch. This one has nothing to tell, and a latch would wake a builder
|
||||
// that declines. The test exists so that difference stays visible.
|
||||
func TestThisChainBuildsNoBlocks(t *testing.T) {
|
||||
vm := &VM{}
|
||||
|
||||
_, err := vm.BuildBlock(context.Background())
|
||||
if !errors.Is(err, errReadOnlyChain) {
|
||||
t.Fatalf("BuildBlock must decline for the stated reason, got: %v", err)
|
||||
}
|
||||
|
||||
// And it reports no work, however long anyone waits.
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
|
||||
defer cancel()
|
||||
if _, err := vm.WaitForEvent(ctx); !errors.Is(err, context.DeadlineExceeded) {
|
||||
t.Fatalf("WaitForEvent reported work on a chain that builds nothing: %v", err)
|
||||
}
|
||||
}
|
||||
+16
-6
@@ -39,6 +39,14 @@ var (
|
||||
}
|
||||
|
||||
errNotImplemented = errors.New("not implemented")
|
||||
|
||||
// errReadOnlyChain is what BuildBlock answers. G-Chain indexes what other
|
||||
// chains have already agreed, so it holds no state of its own to agree on
|
||||
// and never advances past genesis. That is the design, not a gap: a block
|
||||
// here would commit a query result, which is derived from state a peer can
|
||||
// recompute, or a schema, which is local configuration. Neither is
|
||||
// something the network has to vote on.
|
||||
errReadOnlyChain = errors.New("graphvm: this chain indexes other chains and builds no blocks")
|
||||
)
|
||||
|
||||
// GConfig contains VM configuration
|
||||
@@ -263,11 +271,12 @@ func (vm *VM) NewHTTPHandler(ctx context.Context) (http.Handler, error) {
|
||||
return &apiHandler{vm: vm}, nil
|
||||
}
|
||||
|
||||
// WaitForEvent blocks until an event occurs that should trigger block building
|
||||
// WaitForEvent waits out the context and reports nothing, because this chain
|
||||
// has nothing to report: it never builds a block, so there is never news of one
|
||||
// to send. A latch here would wake a builder that declines, which is why this
|
||||
// is not the frozen WaitForEvent the other chains had — those had work and no
|
||||
// way to say so. See errReadOnlyChain.
|
||||
func (vm *VM) WaitForEvent(ctx context.Context) (vmcore.Message, error) {
|
||||
// Block until context is cancelled
|
||||
// In production, this would wait for queries/schema updates in queue
|
||||
// CRITICAL: Must block here to avoid notification flood loop in chains/manager.go
|
||||
<-ctx.Done()
|
||||
return vmcore.Message{}, ctx.Err()
|
||||
}
|
||||
@@ -342,9 +351,10 @@ func (vm *VM) CrossChainResponse(ctx context.Context, chainID ids.ID, requestID
|
||||
return nil
|
||||
}
|
||||
|
||||
// BuildBlock implements the chain.ChainVM interface
|
||||
// BuildBlock implements the chain.ChainVM interface, by declining. See
|
||||
// errReadOnlyChain.
|
||||
func (vm *VM) BuildBlock(ctx context.Context) (chain.Block, error) {
|
||||
return nil, errNotImplemented
|
||||
return nil, errReadOnlyChain
|
||||
}
|
||||
|
||||
// ParseBlock implements the chain.ChainVM interface. It decodes the canonical
|
||||
|
||||
Reference in New Issue
Block a user