mirror of
https://github.com/luxfi/chains.git
synced 2026-09-24 00:26:00 +00:00
A GPU lane's test runs where the host has its device, and fails if it declines there
The tests took a GPU lane that declined the transfers for a lane without a device and skipped it. A device that is there declines too, when its shader is missing or its path regresses, and passed for an absent one. hasDevice now says whether the host has the lane's device: the GPU every Apple silicon Mac has for Metal, and an NVIDIA driver's control device for CUDA. A lane without it is skipped; on a lane with it, the transfers must run, as the evmgpu device test already requires. The docs say a library of another ABI is never sent a block, rather than never called: it still answers the backend queries. Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
+6
-5
@@ -44,8 +44,8 @@
|
||||
// ABIVersion is the go_bridge.h ABI the native build is written to, 7. The
|
||||
// build does not compile against a header that names another, and init reads
|
||||
// the loaded library's gpu_abi_version(): against a library of another ABI,
|
||||
// ExecuteBlock calls nothing and declines every block, so the caller runs them
|
||||
// all on its own EVM. It catches a library built against another ABI number;
|
||||
// ExecuteBlock sends it no block and declines every one, so the caller runs
|
||||
// them all on its own EVM. It catches a library built against another ABI number;
|
||||
// two libraries that report the same number are not told apart.
|
||||
//
|
||||
// Use Health() at startup to see which lanes can run a block: it runs a
|
||||
@@ -75,7 +75,7 @@ var ErrNotLinked = errors.New("cevm: native EVM not linked (rebuild with CGO_ENA
|
||||
// and any batch its value-transfer paths cannot run as the EVM does — a
|
||||
// refusal on balance, nonce, price, block gas limit or revision, a snapshot
|
||||
// row without its hashes, or a device that failed. Against a loaded library
|
||||
// of another ABI, every batch is declined without a call.
|
||||
// of another ABI, every batch is declined without being sent.
|
||||
//
|
||||
// A declined result carries no gas or status a caller may use, so ExecuteBlock
|
||||
// returns none with it.
|
||||
@@ -210,8 +210,9 @@ type BlockContext struct {
|
||||
NumBlobHashes uint32
|
||||
}
|
||||
|
||||
// ABIVersion (cevm_cgo.go / cevm_nocgo.go) is the C ABI this build reads:
|
||||
// go_bridge.h's EVM_GPU_ABI_VERSION, 7. It is one entry, gpu_execute_block,
|
||||
// ABIVersion is the C ABI this build reads: go_bridge.h's EVM_GPU_ABI_VERSION,
|
||||
// 7, in the native build (cevm_cgo.go), and 0 in a build with no library
|
||||
// (cevm_nocgo.go). ABI 7 is one entry, gpu_execute_block,
|
||||
// taking a block context and a state snapshot (CGpuStateAccount) and
|
||||
// answering per-tx gas and status with ok, freed by gpu_free_result; ok=1
|
||||
// only for a result that is the block's. The versioned _v2/_v3/_v4 entries
|
||||
|
||||
@@ -164,8 +164,8 @@ func copyU64(ptr *C.uint64_t, want uint32) []uint64 {
|
||||
//
|
||||
// When the library declines the block (ok=0) ExecuteBlock returns ErrDeclined
|
||||
// and no result: the caller runs the block on its own EVM. A loaded library
|
||||
// of another ABI is never called: ExecuteBlock declines every block it is
|
||||
// given, with an error that wraps ErrDeclined.
|
||||
// of another ABI is never sent a block: ExecuteBlock declines every block it
|
||||
// is given, with an error that wraps ErrDeclined.
|
||||
func ExecuteBlock(backend Backend, numThreads uint32, txs []Transaction, ctx *BlockContext, state []StateAccount) (*BlockResult, error) {
|
||||
if len(txs) == 0 {
|
||||
return &BlockResult{ABIVersion: ABIVersion}, nil
|
||||
|
||||
+39
-31
@@ -4,6 +4,8 @@ package cevm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
@@ -20,9 +22,11 @@ import (
|
||||
// result, and the caller runs it on its own EVM.
|
||||
//
|
||||
// A GPU lane is listed when the library was built with it, whether or not
|
||||
// this host has the device, and without one it declines every block. The ABI
|
||||
// carries nothing else that says a device is there, so a test that needs one
|
||||
// skips on a GPU lane that declines the funded transfers.
|
||||
// this host has the device, and without one it declines every block. A test
|
||||
// that needs a device skips a lane whose device this host does not have
|
||||
// (hasDevice), and on a lane whose device it has, a decline fails: a missing
|
||||
// shader or a device-path regression declines too, and must not pass for an
|
||||
// absent device.
|
||||
|
||||
func TestLibraryABIVersion(t *testing.T) {
|
||||
if got := LibraryABIVersion(); got != ABIVersion {
|
||||
@@ -50,23 +54,20 @@ func TestAvailableBackends_HasCPU(t *testing.T) {
|
||||
}
|
||||
|
||||
// A block of funded plain transfers runs on a GPU lane to TxOK at 21000 gas
|
||||
// each, or is declined there (no device); a CPU lane declines it. No lane
|
||||
// answers anything else.
|
||||
func TestAFundedTransferBlockRunsOnADeviceOrIsDeclined(t *testing.T) {
|
||||
// each; a CPU lane declines it. No lane answers anything else.
|
||||
func TestAFundedTransferBlockRunsOnADeviceAndNowhereElse(t *testing.T) {
|
||||
const n = 4
|
||||
txs, ctx, state := transfers(n)
|
||||
for _, b := range AvailableBackends() {
|
||||
t.Run(BackendName(b), func(t *testing.T) {
|
||||
if isGPU(b) && !hasDevice(b) {
|
||||
t.Skipf("this host has no %s device", BackendName(b))
|
||||
}
|
||||
r, err := ExecuteBlock(b, 0, txs, &ctx, state)
|
||||
if !isGPU(b) {
|
||||
declined(t, r, err)
|
||||
return
|
||||
}
|
||||
if errors.Is(err, ErrDeclined) {
|
||||
declined(t, r, err)
|
||||
t.Logf("%s declined funded transfers: no device on this host", BackendName(b))
|
||||
return
|
||||
}
|
||||
ranTransfers(t, r, err, n)
|
||||
})
|
||||
}
|
||||
@@ -93,10 +94,10 @@ func TestABatchWithCodeIsDeclined(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestHealth runs the Health() battery: a funded plain transfer. A GPU lane
|
||||
// runs it to TxOK at 21000 gas, or declines it on a host without its device;
|
||||
// a CPU lane declines it, because the Go entry runs nothing on the CPU
|
||||
// without a host (go_bridge.h).
|
||||
// TestHealth runs the Health() battery: a funded plain transfer. A GPU
|
||||
// lane whose device this host has runs it to TxOK at 21000 gas; a CPU lane
|
||||
// declines it, because the Go entry runs nothing on the CPU without a host
|
||||
// (go_bridge.h).
|
||||
func TestHealth(t *testing.T) {
|
||||
reports := Health()
|
||||
if len(reports) == 0 {
|
||||
@@ -104,15 +105,12 @@ func TestHealth(t *testing.T) {
|
||||
}
|
||||
for _, r := range reports {
|
||||
switch {
|
||||
case isGPU(r.Backend) && r.OK:
|
||||
if r.GasUsed != 21000 {
|
||||
t.Errorf("Health: GPU lane %q ran the probe at %d gas, want 21000", r.Name, r.GasUsed)
|
||||
}
|
||||
case isGPU(r.Backend) && !hasDevice(r.Backend):
|
||||
t.Logf("Health: GPU lane %q: this host has no device for it (ok=%v err=%v)", r.Name, r.OK, r.Err)
|
||||
case isGPU(r.Backend):
|
||||
if !errors.Is(r.Err, ErrDeclined) {
|
||||
t.Errorf("Health: GPU lane %q: err=%v, want ok at 21000 or declined", r.Name, r.Err)
|
||||
if !r.OK || r.GasUsed != 21000 {
|
||||
t.Errorf("Health: GPU lane %q: ok=%v gas=%d err=%v, want ok at 21000", r.Name, r.OK, r.GasUsed, r.Err)
|
||||
}
|
||||
t.Logf("Health: GPU lane %q declined the probe: no device on this host", r.Name)
|
||||
case r.OK || !errors.Is(r.Err, ErrDeclined):
|
||||
t.Errorf("Health: CPU lane %q: ok=%v err=%v, want declined", r.Name, r.OK, r.Err)
|
||||
}
|
||||
@@ -190,7 +188,7 @@ func TestConcurrent_Stress(t *testing.T) {
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
// TestExecuteBlock_LargeCode: 64 KiB of code crosses the boundary (the
|
||||
// TestExecuteBlock_LargeCode: 48 KB of code crosses the boundary (the
|
||||
// library copies it) and the batch is declined cleanly, never a segfault from
|
||||
// an unchecked uint32 or an unpinned slice.
|
||||
func TestExecuteBlock_LargeCode(t *testing.T) {
|
||||
@@ -335,26 +333,36 @@ func declined(t *testing.T, r *BlockResult, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
// deviceLanes is the GPU lanes on which this host runs a block of funded
|
||||
// transfers. It skips the test when there is none.
|
||||
// deviceLanes is the GPU lanes whose device this host has. It skips the test
|
||||
// when there is none.
|
||||
func deviceLanes(t *testing.T) []Backend {
|
||||
t.Helper()
|
||||
txs, ctx, state := transfers(1)
|
||||
var out []Backend
|
||||
for _, b := range AvailableBackends() {
|
||||
if !isGPU(b) {
|
||||
continue
|
||||
}
|
||||
if _, err := ExecuteBlock(b, 0, txs, &ctx, state); err == nil {
|
||||
if isGPU(b) && hasDevice(b) {
|
||||
out = append(out, b)
|
||||
}
|
||||
}
|
||||
if len(out) == 0 {
|
||||
t.Skip("no GPU lane runs a block on this host: no device")
|
||||
t.Skip("this host has no device for any GPU lane the library offers")
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// hasDevice reports whether this host has the device GPU lane b runs on: the
|
||||
// GPU every Apple silicon Mac has for Metal, and an NVIDIA driver's control
|
||||
// device for CUDA. The library lists a lane it was built with either way.
|
||||
func hasDevice(b Backend) bool {
|
||||
switch b {
|
||||
case GPUMetal:
|
||||
return runtime.GOOS == "darwin" && runtime.GOARCH == "arm64"
|
||||
case GPUCUDA:
|
||||
_, err := os.Stat("/dev/nvidiactl")
|
||||
return err == nil
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// computeBytecode returns deterministic EVM bytecode that does iters
|
||||
// additions then returns.
|
||||
func computeBytecode(iters int) []byte {
|
||||
|
||||
@@ -8,6 +8,8 @@ package parallel
|
||||
import (
|
||||
"errors"
|
||||
"math/big"
|
||||
"os"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/holiman/uint256"
|
||||
@@ -50,8 +52,8 @@ func TestABatchWithCodeIsDeclinedOnEveryBackend(t *testing.T) {
|
||||
// executor: a GPU lane runs it and receipts every transfer at 21000 gas; a
|
||||
// CPU lane, which runs nothing through the Go entry, declines it to the Go
|
||||
// EVM. Neither is an error. A GPU lane is listed when the library was built
|
||||
// with it, whether or not this host has the device, and one without it
|
||||
// declines too: that lane is skipped.
|
||||
// with it, whether or not this host has the device: a lane whose device this
|
||||
// host lacks is skipped, and one whose device it has must run the block.
|
||||
func TestAPlainTransferBlockIsReceiptedOrDeclined(t *testing.T) {
|
||||
const n = 8
|
||||
sdb := newState(t)
|
||||
@@ -69,6 +71,9 @@ func TestAPlainTransferBlockIsReceiptedOrDeclined(t *testing.T) {
|
||||
|
||||
for _, b := range cevm.AvailableBackends() {
|
||||
t.Run(cevm.BackendName(b), func(t *testing.T) {
|
||||
if (b == cevm.GPUMetal || b == cevm.GPUCUDA) && !hasDevice(b) {
|
||||
t.Skipf("this host has no %s device", cevm.BackendName(b))
|
||||
}
|
||||
receipts, err := (&Executor{CevmBackend: b}).run(chainConfig(), header, txs, from, sdb)
|
||||
if err != nil {
|
||||
t.Fatalf("run: %v", err)
|
||||
@@ -80,8 +85,7 @@ func TestAPlainTransferBlockIsReceiptedOrDeclined(t *testing.T) {
|
||||
return
|
||||
}
|
||||
if receipts == nil {
|
||||
t.Skipf("GPU lane %s declined a block of funded plain transfers: no device on this host",
|
||||
cevm.BackendName(b))
|
||||
t.Fatalf("GPU lane %s declined a block of funded plain transfers", cevm.BackendName(b))
|
||||
}
|
||||
if len(receipts) != n {
|
||||
t.Fatalf("%d receipts for %d transactions", len(receipts), n)
|
||||
@@ -94,3 +98,17 @@ func TestAPlainTransferBlockIsReceiptedOrDeclined(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// hasDevice reports whether this host has the device GPU lane b runs on: the
|
||||
// GPU every Apple silicon Mac has for Metal, and an NVIDIA driver's control
|
||||
// device for CUDA. The library lists a lane it was built with either way.
|
||||
func hasDevice(b cevm.Backend) bool {
|
||||
switch b {
|
||||
case cevm.GPUMetal:
|
||||
return runtime.GOOS == "darwin" && runtime.GOARCH == "arm64"
|
||||
case cevm.GPUCUDA:
|
||||
_, err := os.Stat("/dev/nvidiactl")
|
||||
return err == nil
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user