mirror of
https://github.com/luxfi/vm.git
synced 2026-10-30 15:13:40 +00:00
Replaces copy(id[:], wireBytes) — which silently zero-pads/truncates a wrong-length field into a different ID — with the canonical length-validating ids.ToID/ToNodeID constructors, on both the server handlers (handleInitialize + 5 block-ID handlers) and the client response decoders (Initialize/BuildBlock/ParseBlock/GetBlock). Also rejects a negative MaxBlocksNum before it sizes a slice cap. Drops a dead nodeID var. Co-authored-by: Hanzo Dev <[email protected]>
396 lines
9.8 KiB
Go
396 lines
9.8 KiB
Go
//go:build !grpc
|
|
|
|
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package rpc
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httputil"
|
|
"net/url"
|
|
"time"
|
|
|
|
zapwire "github.com/luxfi/api/zap"
|
|
"github.com/luxfi/database"
|
|
"github.com/luxfi/ids"
|
|
"github.com/luxfi/log"
|
|
"github.com/luxfi/metric"
|
|
"github.com/luxfi/resource"
|
|
"github.com/luxfi/vm/chain"
|
|
"github.com/luxfi/vm/rpc/runtime"
|
|
)
|
|
|
|
// ZAPClient implements the VM interface over ZAP transport
|
|
type ZAPClient struct {
|
|
conn *zapwire.Conn
|
|
logger log.Logger
|
|
|
|
runtime runtime.Stopper
|
|
pid int
|
|
processTracker resource.ProcessTracker
|
|
|
|
// Cached state from Initialize
|
|
lastAcceptedID ids.ID
|
|
}
|
|
|
|
// NewZAPClient creates a new ZAP-based VM client
|
|
func NewZAPClient(
|
|
conn *zapwire.Conn,
|
|
stopper runtime.Stopper,
|
|
pid int,
|
|
processTracker resource.ProcessTracker,
|
|
metricsGatherer metric.MultiGatherer,
|
|
logger log.Logger,
|
|
) *ZAPClient {
|
|
return &ZAPClient{
|
|
conn: conn,
|
|
runtime: stopper,
|
|
pid: pid,
|
|
processTracker: processTracker,
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
// Initialize implements chain.ChainVM
|
|
func (c *ZAPClient) Initialize(
|
|
ctx context.Context,
|
|
chainCtx interface{},
|
|
db interface{},
|
|
genesisBytes, upgradeBytes, configBytes []byte,
|
|
msgChan interface{},
|
|
fxs []interface{},
|
|
sender interface{},
|
|
) error {
|
|
req := &zapwire.InitializeRequest{
|
|
GenesisBytes: genesisBytes,
|
|
UpgradeBytes: upgradeBytes,
|
|
ConfigBytes: configBytes,
|
|
}
|
|
|
|
buf := zapwire.GetBuffer()
|
|
defer zapwire.PutBuffer(buf)
|
|
req.Encode(buf)
|
|
|
|
respType, respData, err := c.conn.Call(ctx, zapwire.MsgInitialize, buf.Bytes())
|
|
if err != nil {
|
|
return fmt.Errorf("zap initialize: %w", err)
|
|
}
|
|
|
|
if respType&^zapwire.MsgResponseFlag != zapwire.MsgInitialize {
|
|
return fmt.Errorf("unexpected response type: %d", respType)
|
|
}
|
|
|
|
resp := &zapwire.InitializeResponse{}
|
|
if err := resp.Decode(zapwire.NewReader(respData)); err != nil {
|
|
return fmt.Errorf("zap decode initialize response: %w", err)
|
|
}
|
|
|
|
lastAcceptedID, err := ids.ToID(resp.LastAcceptedID)
|
|
if err != nil {
|
|
return fmt.Errorf("zap initialize: invalid lastAcceptedID: %w", err)
|
|
}
|
|
c.lastAcceptedID = lastAcceptedID
|
|
|
|
c.logger.Info("VM initialized via ZAP",
|
|
"height", resp.Height,
|
|
"lastAcceptedID", c.lastAcceptedID,
|
|
)
|
|
|
|
return nil
|
|
}
|
|
|
|
// Shutdown implements chain.ChainVM
|
|
func (c *ZAPClient) Shutdown(ctx context.Context) error {
|
|
_, _, err := c.conn.Call(ctx, zapwire.MsgShutdown, nil)
|
|
if err != nil {
|
|
c.logger.Warn("ZAP shutdown error", "error", err)
|
|
}
|
|
c.conn.Close()
|
|
c.runtime.Stop(ctx)
|
|
return err
|
|
}
|
|
|
|
// SetState implements chain.ChainVM
|
|
func (c *ZAPClient) SetState(ctx context.Context, state uint32) error {
|
|
req := &zapwire.SetStateRequest{
|
|
State: zapwire.State(state),
|
|
}
|
|
|
|
buf := zapwire.GetBuffer()
|
|
defer zapwire.PutBuffer(buf)
|
|
req.Encode(buf)
|
|
|
|
_, _, err := c.conn.Call(ctx, zapwire.MsgSetState, buf.Bytes())
|
|
return err
|
|
}
|
|
|
|
// Version implements chain.ChainVM
|
|
func (c *ZAPClient) Version(ctx context.Context) (string, error) {
|
|
_, respData, err := c.conn.Call(ctx, zapwire.MsgVersion, nil)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
resp := &zapwire.VersionResponse{}
|
|
if err := resp.Decode(zapwire.NewReader(respData)); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return resp.Version, nil
|
|
}
|
|
|
|
// BuildBlock implements chain.ChainVM
|
|
func (c *ZAPClient) BuildBlock(ctx context.Context) (chain.Block, error) {
|
|
log.Info("[ZAP CLIENT] BuildBlock calling server")
|
|
_, respData, err := c.conn.Call(ctx, zapwire.MsgBuildBlock, nil)
|
|
if err != nil {
|
|
log.Error("[ZAP CLIENT] BuildBlock call error", "error", err)
|
|
return nil, err
|
|
}
|
|
|
|
resp := &zapwire.BlockResponse{}
|
|
if err := resp.Decode(zapwire.NewReader(respData)); err != nil {
|
|
log.Error("[ZAP CLIENT] BuildBlock decode error", "error", err)
|
|
return nil, err
|
|
}
|
|
|
|
log.Info("[ZAP CLIENT] BuildBlock response",
|
|
"respIDLen", len(resp.ID), "respParentIDLen", len(resp.ParentID),
|
|
"height", resp.Height, "bytesLen", len(resp.Bytes), "err", resp.Err)
|
|
|
|
if resp.Err != zapwire.ErrorUnspecified {
|
|
return nil, errorFromZAP(resp.Err)
|
|
}
|
|
|
|
id, err := ids.ToID(resp.ID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("zap buildblock: invalid id: %w", err)
|
|
}
|
|
parentID, err := ids.ToID(resp.ParentID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("zap buildblock: invalid parentID: %w", err)
|
|
}
|
|
|
|
log.Info("[ZAP CLIENT] BuildBlock returning", "id", id, "parentID", parentID, "height", resp.Height)
|
|
|
|
return &zapBlock{
|
|
client: c,
|
|
id: id,
|
|
parentID: parentID,
|
|
bytes: resp.Bytes,
|
|
height: resp.Height,
|
|
timestamp: time.Unix(0, resp.Timestamp),
|
|
}, nil
|
|
}
|
|
|
|
// ParseBlock implements chain.ChainVM
|
|
func (c *ZAPClient) ParseBlock(ctx context.Context, blockBytes []byte) (chain.Block, error) {
|
|
req := &zapwire.ParseBlockRequest{
|
|
Bytes: blockBytes,
|
|
}
|
|
|
|
buf := zapwire.GetBuffer()
|
|
defer zapwire.PutBuffer(buf)
|
|
req.Encode(buf)
|
|
|
|
_, respData, err := c.conn.Call(ctx, zapwire.MsgParseBlock, buf.Bytes())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resp := &zapwire.BlockResponse{}
|
|
if err := resp.Decode(zapwire.NewReader(respData)); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if resp.Err != zapwire.ErrorUnspecified {
|
|
return nil, errorFromZAP(resp.Err)
|
|
}
|
|
|
|
id, err := ids.ToID(resp.ID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("zap parseblock: invalid id: %w", err)
|
|
}
|
|
parentID, err := ids.ToID(resp.ParentID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("zap parseblock: invalid parentID: %w", err)
|
|
}
|
|
|
|
return &zapBlock{
|
|
client: c,
|
|
id: id,
|
|
parentID: parentID,
|
|
bytes: blockBytes, // Use original bytes
|
|
height: resp.Height,
|
|
timestamp: time.Unix(0, resp.Timestamp),
|
|
}, nil
|
|
}
|
|
|
|
// GetBlock implements chain.ChainVM
|
|
func (c *ZAPClient) GetBlock(ctx context.Context, blkID ids.ID) (chain.Block, error) {
|
|
req := &zapwire.GetBlockRequest{
|
|
ID: blkID[:],
|
|
}
|
|
|
|
buf := zapwire.GetBuffer()
|
|
defer zapwire.PutBuffer(buf)
|
|
req.Encode(buf)
|
|
|
|
_, respData, err := c.conn.Call(ctx, zapwire.MsgGetBlock, buf.Bytes())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resp := &zapwire.BlockResponse{}
|
|
if err := resp.Decode(zapwire.NewReader(respData)); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if resp.Err != zapwire.ErrorUnspecified {
|
|
return nil, errorFromZAP(resp.Err)
|
|
}
|
|
|
|
parentID, err := ids.ToID(resp.ParentID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("zap getblock: invalid parentID: %w", err)
|
|
}
|
|
|
|
return &zapBlock{
|
|
client: c,
|
|
id: blkID,
|
|
parentID: parentID,
|
|
bytes: resp.Bytes,
|
|
height: resp.Height,
|
|
timestamp: time.Unix(0, resp.Timestamp),
|
|
}, nil
|
|
}
|
|
|
|
// SetPreference implements chain.ChainVM
|
|
func (c *ZAPClient) SetPreference(ctx context.Context, blkID ids.ID) error {
|
|
req := &zapwire.SetPreferenceRequest{
|
|
ID: blkID[:],
|
|
}
|
|
|
|
buf := zapwire.GetBuffer()
|
|
defer zapwire.PutBuffer(buf)
|
|
req.Encode(buf)
|
|
|
|
_, _, err := c.conn.Call(ctx, zapwire.MsgSetPreference, buf.Bytes())
|
|
return err
|
|
}
|
|
|
|
// LastAccepted implements chain.ChainVM
|
|
func (c *ZAPClient) LastAccepted(ctx context.Context) (ids.ID, error) {
|
|
return c.lastAcceptedID, nil
|
|
}
|
|
|
|
// CreateHandlers returns HTTP handlers for the VM's API endpoints
|
|
func (c *ZAPClient) CreateHandlers(ctx context.Context) (map[string]http.Handler, error) {
|
|
c.logger.Info("ZAPClient.CreateHandlers called")
|
|
|
|
_, respData, err := c.conn.Call(ctx, zapwire.MsgCreateHandlers, nil)
|
|
if err != nil {
|
|
c.logger.Error("CreateHandlers call failed", "error", err)
|
|
return nil, err
|
|
}
|
|
|
|
resp := &zapwire.CreateHandlersResponse{}
|
|
if err := resp.Decode(zapwire.NewReader(respData)); err != nil {
|
|
c.logger.Error("CreateHandlers decode failed", "error", err)
|
|
return nil, err
|
|
}
|
|
|
|
c.logger.Info("CreateHandlers response", "count", len(resp.Handlers))
|
|
|
|
handlers := make(map[string]http.Handler, len(resp.Handlers))
|
|
for _, h := range resp.Handlers {
|
|
// Create a reverse proxy to the VM's HTTP server
|
|
targetURL, err := url.Parse("http://" + h.ServerAddr)
|
|
if err != nil {
|
|
c.logger.Error("Failed to parse handler URL", "prefix", h.Prefix, "addr", h.ServerAddr, "error", err)
|
|
return nil, err
|
|
}
|
|
|
|
proxy := httputil.NewSingleHostReverseProxy(targetURL)
|
|
handlers[h.Prefix] = proxy
|
|
|
|
c.logger.Info("Created HTTP handler", "prefix", h.Prefix, "target", targetURL.String())
|
|
}
|
|
|
|
return handlers, nil
|
|
}
|
|
|
|
// zapBlock implements chain.Block
|
|
type zapBlock struct {
|
|
client *ZAPClient
|
|
id ids.ID
|
|
parentID ids.ID
|
|
bytes []byte
|
|
height uint64
|
|
timestamp time.Time
|
|
}
|
|
|
|
func (b *zapBlock) ID() ids.ID { return b.id }
|
|
func (b *zapBlock) Parent() ids.ID { return b.parentID }
|
|
func (b *zapBlock) ParentID() ids.ID { return b.parentID }
|
|
func (b *zapBlock) Bytes() []byte { return b.bytes }
|
|
func (b *zapBlock) Height() uint64 { return b.height }
|
|
func (b *zapBlock) Timestamp() time.Time { return b.timestamp }
|
|
func (b *zapBlock) Status() uint8 { return uint8(chain.Processing) }
|
|
|
|
func (b *zapBlock) Verify(ctx context.Context) error {
|
|
req := &zapwire.BlockVerifyRequest{
|
|
Bytes: b.bytes,
|
|
HasPChainHeight: false,
|
|
}
|
|
|
|
buf := zapwire.GetBuffer()
|
|
defer zapwire.PutBuffer(buf)
|
|
req.Encode(buf)
|
|
|
|
_, _, err := b.client.conn.Call(ctx, zapwire.MsgBlockVerify, buf.Bytes())
|
|
return err
|
|
}
|
|
|
|
func (b *zapBlock) Accept(ctx context.Context) error {
|
|
req := &zapwire.BlockAcceptRequest{
|
|
ID: b.id[:],
|
|
}
|
|
|
|
buf := zapwire.GetBuffer()
|
|
defer zapwire.PutBuffer(buf)
|
|
req.Encode(buf)
|
|
|
|
_, _, err := b.client.conn.Call(ctx, zapwire.MsgBlockAccept, buf.Bytes())
|
|
return err
|
|
}
|
|
|
|
func (b *zapBlock) Reject(ctx context.Context) error {
|
|
req := &zapwire.BlockRejectRequest{
|
|
ID: b.id[:],
|
|
}
|
|
|
|
buf := zapwire.GetBuffer()
|
|
defer zapwire.PutBuffer(buf)
|
|
req.Encode(buf)
|
|
|
|
_, _, err := b.client.conn.Call(ctx, zapwire.MsgBlockReject, buf.Bytes())
|
|
return err
|
|
}
|
|
|
|
func errorFromZAP(err zapwire.Error) error {
|
|
switch err {
|
|
case zapwire.ErrorUnspecified:
|
|
return nil
|
|
case zapwire.ErrorClosed:
|
|
return database.ErrClosed
|
|
case zapwire.ErrorNotFound:
|
|
return database.ErrNotFound
|
|
default:
|
|
return fmt.Errorf("zap error: %d", err)
|
|
}
|
|
}
|