Files
node/vms/platformvm/platformvm_gpu.go
T
Hanzo AI 1a9dc4fda6 platformvm: wire P-Chain VM to GPU plugin substrate
Mirrors the chains/aivm/aivm_gpu.go + chains/evm/cevm pattern: the GPU
substrate for the P-Chain validator/stake/slashing/epoch transitions is
resolved at PROCESS START via dlopen/dlsym against the lux-gpu-kernels
plugin DSOs. Bridge is FALSE-by-default — the chain continues to drive
consensus through the existing pure-Go path until an operator opts in
by setting LUX_PLATFORMVM_GPU=1. Any launcher error from the GPU falls
back to the Go path WITHOUT panic.

Three new files (plus one round-trip test) decomplect cleanly:

- platformvm_gpu.go (build tag cgo) — exposes GPUBackend with four
  methods (ValidatorSetApply, StakeTransition, SlashingTransition,
  EpochTransition) that dlsym the host launchers
  lux_<backend>_platformvm_<op>. Layout-drift init() asserts every
  struct (PVMValidatorSlot=176, PVMStakeRecord=64, PVMSlashEvidence=80,
  PVMEpochState=160, PVMRoundDescriptor=96, PVMValidatorOp=176,
  PVMStakeOp=64, PVMTransitionResult=192) against the on-device layout
  at ops/platformvm/cuda/platformvm_kernels_common.cuh + the
  authoritative platformvm_gpu_layout.hpp.

- platformvm_gpu_nocgo.go (build tag !cgo) — keeps the public surface
  identical (same struct names, method signatures, constants); every
  method returns ErrGPUNotAvailable.

- backend.go — pure-Go probe driver. Walks the canonical plugin order
  (cuda → hip → metal → vulkan → webgpu) at process start via init(),
  pins the first successful open as ActiveGPUBackend(). AutoBackend()
  is the explicit re-entry point. Search paths cover the operator
  override (LUX_PLATFORMVM_GPU_DIR), shared (LUX_GPU_PLUGIN_DIR), dev
  tree (~/work/lux-private/gpu-kernels/build/*_backend), prefix install
  (/usr/local/lib/lux-gpu), system (/usr/lib/lux-gpu), and the empty
  DYLD/LD_LIBRARY_PATH fallback. GPUEnabled() reads the operator
  opt-in env knob — decomplected from the probe itself so operators
  can inspect a loaded-but-inert backend before flipping the gate.

- platformvm_gpu_test.go — three subtests:
  1. AutoBackend round-trip: dlopen plugin, dlsym four launchers, call
     ValidatorSetApply on a 1-validator (kVOpAdd) fixture, assert
     applied=1 and slot Status = Active|PendingAdd. SKIPs cleanly when
     no plugin is on disk (the production default).
  2. Nil-handle contract: zero-value *GPUBackend returns
     ErrGPUNotAvailable from every method without panic.
  3. Env knob: GPUEnabled() recognises 1/true/yes/TRUE as opt-in;
     anything else (including unset) stays at the false-by-default.

Verified round-trip PASSes against metal, vulkan, and webgpu plugins
on M1 Max (laptop). CUDA / HIP are the linux NVIDIA/AMD paths and
were verified in the spark.local GB10 Blackwell sm_120 micro-bench
session that produced the cited cells (validator_set_apply ~5.5ms,
stake_transition ~3.0ms, slashing_transition ~0.06ms, epoch transition
~7.0ms — gpu-kernels HEAD 4e166de).

Does NOT modify vm.go / block.go / state.go core paths; this is the
bridge surface only. The existing transition functions can opt into
the GPU path later behind the LUX_PLATFORMVM_GPU=1 gate.

All builds succeed: go build, go build -tags cgo, CGO_ENABLED=0 go
build. All existing tests in vms/platformvm pass under both modes —
no regressions.
2026-06-05 15:36:23 -07:00

728 lines
22 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//go:build cgo
// Package platformvm GPU backend — runtime-loaded plugin bridge.
//
// Mirrors the chains/aivm/aivm_gpu.go + chains/evm/cevm pattern: the GPU
// substrate for the P-Chain validator/stake/slashing/epoch transitions is
// resolved at PROCESS START via dlopen/dlsym against the lux-gpu-kernels
// plugin DSOs. This keeps the node module compilable without
// lux-private/gpu-kernels present in the build tree — the plugin is fully
// optional and the chain runs the existing pure-Go path otherwise.
//
// Lookup order (handled by backend.go):
//
// libluxgpu_backend_cuda.so (Linux x86_64 + NVIDIA)
// libluxgpu_backend_hip.so (Linux x86_64 + AMD ROCm)
// libluxgpu_backend_metal.dylib (macOS Apple Silicon / Intel)
// libluxgpu_backend_vulkan.so/.dylib (any Vulkan ICD)
// libluxgpu_backend_webgpu.so/.dylib (Dawn / wgpu-native)
//
// Each plugin exports four extern "C" host launchers per backend:
//
// lux_<backend>_platformvm_validator_set_apply
// lux_<backend>_platformvm_stake_transition
// lux_<backend>_platformvm_slashing_transition
// lux_<backend>_platformvm_epoch_transition
//
// Struct layout matches ops/platformvm/cuda/platformvm_kernels_common.cuh
// and platformvm_gpu_layout.hpp byte-for-byte (asserted by init()).
// Pointer ABI is HOST pointers — the launcher does H2D-upload / dispatch /
// D2H-download internally.
//
// FALSE-by-default: even when cgo is on and a plugin loads, the chain
// runs the existing pure-Go path until an operator explicitly sets
// `LUX_PLATFORMVM_GPU=1`. Any launcher error → fall back to Go path
// WITHOUT panic and emit a warn log.
package platformvm
/*
#cgo darwin LDFLAGS: -ldl
#cgo linux LDFLAGS: -ldl
#include <dlfcn.h>
#include <stdint.h>
#include <stdlib.h>
// Four host-launcher trampolines — invoked by Go via cgo. The function
// pointer is opaque (void*); the cgo bridge casts it to the expected
// C signature and forwards arguments.
//
// All four launcher prototypes are identical across backends (cuda / hip /
// metal / vulkan / webgpu). Argument pointers are HOST pointers; the
// last `void*` is a "stream" handle that is always nullptr on the host-
// pointer ABI backends (metal / vulkan / webgpu) and the cuda/hip stream
// slot from the kernel author's POV.
typedef int (*pvm_validator_set_fn)(
const void* desc,
const void* ops,
void* validators,
void* applied_out,
uint32_t validator_count,
void* stream);
typedef int (*pvm_stake_fn)(
const void* desc,
const void* ops,
void* validators,
void* stake,
void* applied_out,
uint32_t validator_count,
uint32_t stake_count,
void* stream);
typedef int (*pvm_slashing_fn)(
const void* desc,
const void* evidence,
void* validators,
void* slashing,
void* applied_out,
void* total_lo_out,
void* total_hi_out,
uint32_t validator_count,
uint32_t slashing_count,
void* stream);
typedef int (*pvm_epoch_fn)(
const void* desc,
void* validators,
void* stake,
void* slashing,
void* epoch,
void* result,
uint32_t validator_count,
uint32_t stake_count,
uint32_t slashing_count,
void* leaf_scratch,
void* stream);
static int call_pvm_validator_set(void* fn,
const void* desc, const void* ops,
void* validators, void* applied_out,
uint32_t validator_count) {
return ((pvm_validator_set_fn)fn)(desc, ops, validators, applied_out,
validator_count, NULL);
}
static int call_pvm_stake(void* fn,
const void* desc, const void* ops,
void* validators, void* stake, void* applied_out,
uint32_t validator_count, uint32_t stake_count) {
return ((pvm_stake_fn)fn)(desc, ops, validators, stake, applied_out,
validator_count, stake_count, NULL);
}
static int call_pvm_slashing(void* fn,
const void* desc, const void* evidence,
void* validators, void* slashing, void* applied_out,
void* total_lo_out, void* total_hi_out,
uint32_t validator_count, uint32_t slashing_count) {
return ((pvm_slashing_fn)fn)(desc, evidence, validators, slashing,
applied_out, total_lo_out, total_hi_out,
validator_count, slashing_count, NULL);
}
static int call_pvm_epoch(void* fn,
const void* desc, void* validators, void* stake,
void* slashing, void* epoch, void* result,
uint32_t validator_count, uint32_t stake_count,
uint32_t slashing_count, void* leaf_scratch) {
return ((pvm_epoch_fn)fn)(desc, validators, stake, slashing, epoch, result,
validator_count, stake_count, slashing_count,
leaf_scratch, NULL);
}
// dlopen / dlsym wrappers — kept here so backend.go can stay pure Go.
static void* lux_pvm_dlopen(const char* path) {
return dlopen(path, RTLD_NOW | RTLD_LOCAL);
}
static void* lux_pvm_dlsym(void* handle, const char* sym) {
return dlsym(handle, sym);
}
static const char* lux_pvm_dlerror() {
return dlerror();
}
static void lux_pvm_dlclose(void* handle) {
dlclose(handle);
}
*/
import "C"
import (
"errors"
"fmt"
"runtime"
"sync"
"unsafe"
)
// ErrGPUNotAvailable is returned by every GPUBackend method when no plugin
// was loadable at init time. Callers fall through to the existing Go path.
var ErrGPUNotAvailable = errors.New("platformvm: no GPU plugin available")
// GPUBackendKind identifies which lux-gpu-kernels plugin satisfied the
// runtime dlopen probe. AvailableNone is the sentinel "fall through to Go".
type GPUBackendKind uint8
const (
GPUNone GPUBackendKind = 0
GPUCUDA GPUBackendKind = 1
GPUHIP GPUBackendKind = 2
GPUMetal GPUBackendKind = 3
GPUVulkan GPUBackendKind = 4
GPUWebGPU GPUBackendKind = 5
)
// String returns the human-readable name for the backend kind.
func (k GPUBackendKind) String() string {
switch k {
case GPUNone:
return "none"
case GPUCUDA:
return "cuda"
case GPUHIP:
return "hip"
case GPUMetal:
return "metal"
case GPUVulkan:
return "vulkan"
case GPUWebGPU:
return "webgpu"
default:
return fmt.Sprintf("backend(%d)", uint8(k))
}
}
// =============================================================================
// Layout-drift guards — match ops/platformvm/cuda/platformvm_kernels_common.cuh
// + platformvm_gpu_layout.hpp byte-for-byte.
//
// The struct bytes Go hands to C MUST match the on-disk layout file at
// ~/work/lux-private/gpu-kernels/ops/platformvm/op.yaml — every kernel reads
// them via reinterpret_cast. A silent layout shift produces consensus-divergent
// state roots. init() refuses to load if any size drifts.
// =============================================================================
// PVMValidatorSlot mirrors platformvm::gpu::ValidatorSlot (176 bytes).
type PVMValidatorSlot struct {
ValidatorID uint64
Weight uint64
BLSPubkey [48]byte
CoronaPubkey [32]byte // Corona / Corona commitment digest
MLDSAPubkey [32]byte
MLDSAGroth16Root [32]byte
Status uint32
JailUntilEpoch uint32
Occupied uint32
_pad0 uint32
}
// PVMStakeRecord mirrors platformvm::gpu::StakeRecord (64 bytes).
type PVMStakeRecord struct {
DelegatorID uint64
ValidatorID uint64
Amount uint64
LockUntilEpoch uint64
RewardAccumulator uint64
CommissionBPS uint32
Status uint32
EpochBonded uint32
EpochUnbonded uint32
_pad0 uint64
}
// PVMSlashEvidence mirrors platformvm::gpu::SlashEvidence (80 bytes).
type PVMSlashEvidence struct {
ValidatorID uint64
Height uint64
SlashAmount uint64
Kind uint32
Epoch uint32
JailForEpochs uint32
_pad0 uint32
EvidenceDigest [32]byte
_pad1 uint64
}
// PVMEpochState mirrors platformvm::gpu::EpochState (160 bytes).
type PVMEpochState struct {
CurrentEpoch uint64
NextEpochHeight uint64
TotalActiveStake uint64
ActiveValidatorCount uint32
PendingDropCount uint32
ValidatorSetRoot [32]byte
StakeRoot [32]byte
SlashingRoot [32]byte
EpochRoot [32]byte
}
// PVMRoundDescriptor mirrors platformvm::gpu::PVMRoundDescriptor (96 bytes).
type PVMRoundDescriptor struct {
ChainID uint64
Round uint64
TimestampNS uint64
Epoch uint64
Mode uint32
ValidatorOpCount uint32
StakeOpCount uint32
SlashEvidenceCount uint32
ClosingFlag uint32
_pad0 uint32
_pad1 uint64
ParentEpochRoot [32]byte
}
// PVMValidatorOp mirrors platformvm::gpu::ValidatorOp (176 bytes).
type PVMValidatorOp struct {
ValidatorID uint64
Weight uint64
BLSPubkey [48]byte
CoronaPubkey [32]byte
MLDSAPubkey [32]byte
MLDSAGroth16Root [32]byte
Kind uint32
JailUntilEpoch uint32
Epoch uint32
_pad0 uint32
}
// PVMStakeOp mirrors platformvm::gpu::StakeOp (64 bytes).
type PVMStakeOp struct {
DelegatorID uint64
ValidatorID uint64
Amount uint64
LockUntilEpoch uint64
SourceValidatorID uint64
Kind uint32
CommissionBPS uint32
Epoch uint32
_pad0 uint32
_pad1 uint64
}
// PVMTransitionResult mirrors platformvm::gpu::PVMTransitionResult (192 bytes).
type PVMTransitionResult struct {
Status uint32
ValidatorApplyCount uint32
StakeApplyCount uint32
SlashApplyCount uint32
ActiveValidatorCount uint32
PendingDropCount uint32
JailedCount uint32
TombstonedCount uint32
TotalActiveStake uint64
TotalSlashed uint64
TotalRewards uint64
Epoch uint64
ValidatorSetRoot [32]byte
StakeRoot [32]byte
SlashingRoot [32]byte
EpochRoot [32]byte
}
// PlatformVM op-kind constants — must match
// ops/platformvm/cuda/platformvm_kernels_common.cuh.
const (
PVMVOpAdd uint32 = 0
PVMVOpRemove uint32 = 1
PVMVOpUpdateWeight uint32 = 2
PVMVOpJail uint32 = 3
PVMVOpUnjail uint32 = 4
PVMVOpRotateKeys uint32 = 5
PVMSOpBond uint32 = 0
PVMSOpUnbond uint32 = 1
PVMSOpDelegate uint32 = 2
PVMSOpRedelegate uint32 = 3
PVMSOpReward uint32 = 4
PVMSOpCommission uint32 = 5
PVMEvEquivocation uint32 = 0
PVMEvDowntime uint32 = 1
PVMEvInvalidVote uint32 = 2
PVMModeValidator uint32 = 0
PVMModeStake uint32 = 1
PVMModeSlashing uint32 = 2
PVMModeEpoch uint32 = 3
PVMModeFullRound uint32 = 4
PVMStatusActive uint32 = 0x1
PVMStatusJailed uint32 = 0x2
PVMStatusTombstoned uint32 = 0x4
PVMStatusPendingAdd uint32 = 0x8
PVMStatusPendingDrop uint32 = 0x10
PVMStakeStatusActive uint32 = 1
PVMStakeStatusUnbonding uint32 = 2
PVMStakeStatusRetired uint32 = 3
)
// Layout-drift guard — refuse to load if any struct size disagrees with
// the on-device layout. Any disagreement here means Go would write
// garbage at the C boundary.
func init() {
type sz struct {
name string
got uintptr
want uintptr
}
checks := []sz{
{"PVMValidatorSlot", unsafe.Sizeof(PVMValidatorSlot{}), 176},
{"PVMStakeRecord", unsafe.Sizeof(PVMStakeRecord{}), 64},
{"PVMSlashEvidence", unsafe.Sizeof(PVMSlashEvidence{}), 80},
{"PVMEpochState", unsafe.Sizeof(PVMEpochState{}), 160},
{"PVMRoundDescriptor", unsafe.Sizeof(PVMRoundDescriptor{}), 96},
{"PVMValidatorOp", unsafe.Sizeof(PVMValidatorOp{}), 176},
{"PVMStakeOp", unsafe.Sizeof(PVMStakeOp{}), 64},
{"PVMTransitionResult", unsafe.Sizeof(PVMTransitionResult{}), 192},
}
for _, c := range checks {
if c.got != c.want {
panic(fmt.Sprintf(
"platformvm: layout drift — Go sizeof(%s)=%d but on-device layout=%d. "+
"Re-sync vms/platformvm/platformvm_gpu.go against "+
"~/work/lux-private/gpu-kernels/ops/platformvm/cuda/platformvm_kernels_common.cuh.",
c.name, c.got, c.want))
}
}
}
// =============================================================================
// GPUBackend — handle to an open plugin DSO + its four resolved launchers.
// =============================================================================
// GPUBackend is a handle to an open lux-gpu-kernels plugin. Zero value is
// usable (every method returns ErrGPUNotAvailable). The active backend is
// stored at package level by backend.go's init(); call ActiveGPUBackend()
// to retrieve it.
type GPUBackend struct {
mu sync.Mutex
kind GPUBackendKind
handle unsafe.Pointer // dlopen result
path string
fnValidatorSet unsafe.Pointer
fnStake unsafe.Pointer
fnSlashing unsafe.Pointer
fnEpoch unsafe.Pointer
}
// Kind returns which backend satisfied the dlopen probe.
func (b *GPUBackend) Kind() GPUBackendKind {
if b == nil {
return GPUNone
}
return b.kind
}
// Path returns the absolute path of the loaded plugin DSO.
func (b *GPUBackend) Path() string {
if b == nil {
return ""
}
return b.path
}
// IsAvailable reports whether the backend is loaded AND all four host
// launchers were successfully resolved.
func (b *GPUBackend) IsAvailable() bool {
if b == nil || b.handle == nil {
return false
}
return b.fnValidatorSet != nil && b.fnStake != nil &&
b.fnSlashing != nil && b.fnEpoch != nil
}
// openGPUBackend attempts to dlopen `path` and dlsym the four host launchers
// for `kind`. Returns a fully-initialised *GPUBackend on success, or
// (nil, error) when either the dlopen or any dlsym fails.
//
// On dlsym failure the dlopened handle IS dlclose'd before returning so
// we never leak a half-bound plugin.
func openGPUBackend(kind GPUBackendKind, path string) (*GPUBackend, error) {
cpath := C.CString(path)
defer C.free(unsafe.Pointer(cpath))
// Clear any pending error so a stale dlerror() from a previous failed
// dlsym doesn't get mis-attributed to this dlopen call.
C.lux_pvm_dlerror()
handle := C.lux_pvm_dlopen(cpath)
if handle == nil {
return nil, fmt.Errorf("platformvm: dlopen(%s): %s",
path, C.GoString(C.lux_pvm_dlerror()))
}
backendName := kind.String() // cuda / hip / metal / vulkan / webgpu
resolve := func(suffix string) (unsafe.Pointer, error) {
sym := fmt.Sprintf("lux_%s_platformvm_%s", backendName, suffix)
csym := C.CString(sym)
defer C.free(unsafe.Pointer(csym))
C.lux_pvm_dlerror()
ptr := C.lux_pvm_dlsym(handle, csym)
if ptr == nil {
return nil, fmt.Errorf("platformvm: dlsym(%s, %s): %s",
path, sym, C.GoString(C.lux_pvm_dlerror()))
}
return ptr, nil
}
b := &GPUBackend{kind: kind, handle: handle, path: path}
var err error
if b.fnValidatorSet, err = resolve("validator_set_apply"); err != nil {
C.lux_pvm_dlclose(handle)
return nil, err
}
if b.fnStake, err = resolve("stake_transition"); err != nil {
C.lux_pvm_dlclose(handle)
return nil, err
}
if b.fnSlashing, err = resolve("slashing_transition"); err != nil {
C.lux_pvm_dlclose(handle)
return nil, err
}
if b.fnEpoch, err = resolve("epoch_transition"); err != nil {
C.lux_pvm_dlclose(handle)
return nil, err
}
return b, nil
}
// Close releases the dlopen handle. Idempotent — safe to call on a nil
// receiver or an already-closed backend.
func (b *GPUBackend) Close() error {
if b == nil {
return nil
}
b.mu.Lock()
defer b.mu.Unlock()
if b.handle == nil {
return nil
}
C.lux_pvm_dlclose(b.handle)
b.handle = nil
b.fnValidatorSet = nil
b.fnStake = nil
b.fnSlashing = nil
b.fnEpoch = nil
return nil
}
// =============================================================================
// Four host launcher wrappers. Each is a thin cgo trampoline that pins the
// Go-side slice memory (via runtime.KeepAlive) for the duration of the C
// call. The launchers ALWAYS take HOST pointers — no D2H/H2D contract on
// the Go side beyond a defer'd KeepAlive on every input/output buffer.
//
// Error contract: any nonzero return code from the launcher → wrap in a Go
// error. The vm.go opt-in helpers fall back to the Go path on error
// WITHOUT panic and the caller is expected to emit a warn log.
// =============================================================================
// ValidatorSetApply runs the GPU validator-set-apply kernel. `validators` is
// read+written in place; `appliedOut` receives the count of successfully
// applied ops.
func (b *GPUBackend) ValidatorSetApply(
desc *PVMRoundDescriptor,
ops []PVMValidatorOp,
validators []PVMValidatorSlot,
appliedOut *uint32,
) error {
if !b.IsAvailable() {
return ErrGPUNotAvailable
}
if desc == nil || appliedOut == nil {
return errors.New("platformvm: ValidatorSetApply: nil desc or appliedOut")
}
if len(validators) == 0 {
return errors.New("platformvm: ValidatorSetApply: empty validators table")
}
var opsPtr unsafe.Pointer
if len(ops) > 0 {
opsPtr = unsafe.Pointer(&ops[0])
}
rc := C.call_pvm_validator_set(
b.fnValidatorSet,
unsafe.Pointer(desc),
opsPtr,
unsafe.Pointer(&validators[0]),
unsafe.Pointer(appliedOut),
C.uint32_t(len(validators)),
)
runtime.KeepAlive(desc)
runtime.KeepAlive(ops)
runtime.KeepAlive(validators)
runtime.KeepAlive(appliedOut)
if rc != 0 {
return fmt.Errorf("platformvm: %s_platformvm_validator_set_apply returned %d",
b.kind, int(rc))
}
return nil
}
// StakeTransition runs the GPU stake-transition kernel. Reads + writes
// `validators` and `stake` in place; `appliedOut` receives the count.
func (b *GPUBackend) StakeTransition(
desc *PVMRoundDescriptor,
ops []PVMStakeOp,
validators []PVMValidatorSlot,
stake []PVMStakeRecord,
appliedOut *uint32,
) error {
if !b.IsAvailable() {
return ErrGPUNotAvailable
}
if desc == nil || appliedOut == nil {
return errors.New("platformvm: StakeTransition: nil desc or appliedOut")
}
if len(validators) == 0 {
return errors.New("platformvm: StakeTransition: empty validators table")
}
if len(stake) == 0 {
return errors.New("platformvm: StakeTransition: empty stake table")
}
var opsPtr unsafe.Pointer
if len(ops) > 0 {
opsPtr = unsafe.Pointer(&ops[0])
}
rc := C.call_pvm_stake(
b.fnStake,
unsafe.Pointer(desc),
opsPtr,
unsafe.Pointer(&validators[0]),
unsafe.Pointer(&stake[0]),
unsafe.Pointer(appliedOut),
C.uint32_t(len(validators)),
C.uint32_t(len(stake)),
)
runtime.KeepAlive(desc)
runtime.KeepAlive(ops)
runtime.KeepAlive(validators)
runtime.KeepAlive(stake)
runtime.KeepAlive(appliedOut)
if rc != 0 {
return fmt.Errorf("platformvm: %s_platformvm_stake_transition returned %d",
b.kind, int(rc))
}
return nil
}
// SlashingTransition runs the GPU slashing-transition kernel. Reads +
// writes `validators` and `slashing` in place; `appliedOut` receives the
// count, and `totalLoOut`/`totalHiOut` receive the low/high u32 halves of
// the total slashed amount (u64).
func (b *GPUBackend) SlashingTransition(
desc *PVMRoundDescriptor,
evidence []PVMSlashEvidence,
validators []PVMValidatorSlot,
slashing []PVMSlashEvidence,
appliedOut, totalLoOut, totalHiOut *uint32,
) error {
if !b.IsAvailable() {
return ErrGPUNotAvailable
}
if desc == nil || appliedOut == nil || totalLoOut == nil || totalHiOut == nil {
return errors.New("platformvm: SlashingTransition: nil desc or output pointer")
}
if len(validators) == 0 {
return errors.New("platformvm: SlashingTransition: empty validators table")
}
var evidencePtr, slashingPtr unsafe.Pointer
if len(evidence) > 0 {
evidencePtr = unsafe.Pointer(&evidence[0])
}
if len(slashing) > 0 {
slashingPtr = unsafe.Pointer(&slashing[0])
}
rc := C.call_pvm_slashing(
b.fnSlashing,
unsafe.Pointer(desc),
evidencePtr,
unsafe.Pointer(&validators[0]),
slashingPtr,
unsafe.Pointer(appliedOut),
unsafe.Pointer(totalLoOut),
unsafe.Pointer(totalHiOut),
C.uint32_t(len(validators)),
C.uint32_t(len(slashing)),
)
runtime.KeepAlive(desc)
runtime.KeepAlive(evidence)
runtime.KeepAlive(validators)
runtime.KeepAlive(slashing)
runtime.KeepAlive(appliedOut)
runtime.KeepAlive(totalLoOut)
runtime.KeepAlive(totalHiOut)
if rc != 0 {
return fmt.Errorf("platformvm: %s_platformvm_slashing_transition returned %d",
b.kind, int(rc))
}
return nil
}
// EpochTransition runs the GPU epoch-finalisation kernel. Composes the
// per-epoch validator_set_root / stake_root / slashing_root / epoch_root
// (which doubles as pchain_validator_root for Quasar binding).
//
// `leafScratch` is an optional byte arena the kernel uses for keccak leaf
// hashes. If nil or empty the kernel allocates internally. Otherwise it
// must be sized to at least 32 × max(validator_count, stake_count,
// slashing_count) bytes.
func (b *GPUBackend) EpochTransition(
desc *PVMRoundDescriptor,
validators []PVMValidatorSlot,
stake []PVMStakeRecord,
slashing []PVMSlashEvidence,
epoch *PVMEpochState,
result *PVMTransitionResult,
leafScratch []byte,
) error {
if !b.IsAvailable() {
return ErrGPUNotAvailable
}
if desc == nil || epoch == nil || result == nil {
return errors.New("platformvm: EpochTransition: nil desc, epoch, or result")
}
if len(validators) == 0 {
return errors.New("platformvm: EpochTransition: empty validators table")
}
var stakePtr, slashingPtr, scratchPtr unsafe.Pointer
if len(stake) > 0 {
stakePtr = unsafe.Pointer(&stake[0])
}
if len(slashing) > 0 {
slashingPtr = unsafe.Pointer(&slashing[0])
}
if len(leafScratch) > 0 {
scratchPtr = unsafe.Pointer(&leafScratch[0])
}
rc := C.call_pvm_epoch(
b.fnEpoch,
unsafe.Pointer(desc),
unsafe.Pointer(&validators[0]),
stakePtr,
slashingPtr,
unsafe.Pointer(epoch),
unsafe.Pointer(result),
C.uint32_t(len(validators)),
C.uint32_t(len(stake)),
C.uint32_t(len(slashing)),
scratchPtr,
)
runtime.KeepAlive(desc)
runtime.KeepAlive(validators)
runtime.KeepAlive(stake)
runtime.KeepAlive(slashing)
runtime.KeepAlive(epoch)
runtime.KeepAlive(result)
runtime.KeepAlive(leafScratch)
if rc != 0 {
return fmt.Errorf("platformvm: %s_platformvm_epoch_transition returned %d",
b.kind, int(rc))
}
return nil
}