mirror of
https://github.com/luxfi/relay.git
synced 2026-10-30 15:20:05 +00:00
381 lines
9.2 KiB
Go
381 lines
9.2 KiB
Go
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package vm
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/luxfi/database/memdb"
|
|
"github.com/luxfi/ids"
|
|
"github.com/luxfi/log"
|
|
"github.com/luxfi/runtime"
|
|
vmcore "github.com/luxfi/vm"
|
|
)
|
|
|
|
func TestVMID(t *testing.T) {
|
|
require := require.New(t)
|
|
require.NotEqual(ids.Empty, VMID, "VMID should not be empty")
|
|
require.Equal(ids.ID{'r', 'e', 'l', 'a', 'y', 'v', 'm'}, VMID)
|
|
}
|
|
|
|
func TestFactoryNew(t *testing.T) {
|
|
require := require.New(t)
|
|
|
|
factory := &Factory{}
|
|
vm, err := factory.New(log.NewNoOpLogger())
|
|
require.NoError(err)
|
|
require.NotNil(vm)
|
|
require.IsType(&VM{}, vm)
|
|
}
|
|
|
|
func TestVMInitialize(t *testing.T) {
|
|
require := require.New(t)
|
|
|
|
vm := &VM{
|
|
channels: make(map[ids.ID]*Channel),
|
|
messages: make(map[ids.ID]*Message),
|
|
pendingMsgs: make(map[ids.ID][]*Message),
|
|
sequences: make(map[ids.ID]uint64),
|
|
pendingBlocks: make(map[ids.ID]*Block),
|
|
}
|
|
|
|
genesis := &Genesis{
|
|
Timestamp: time.Now().Unix(),
|
|
Config: &Config{
|
|
MaxMessageSize: 1024 * 1024,
|
|
ConfirmationDepth: 6,
|
|
RelayTimeout: 300,
|
|
},
|
|
Message: "test genesis",
|
|
}
|
|
genesisBytes, err := json.Marshal(genesis)
|
|
require.NoError(err)
|
|
|
|
toEngine := make(chan vmcore.Message, 10)
|
|
|
|
init := vmcore.Init{
|
|
Runtime: &runtime.Runtime{
|
|
ChainID: ids.GenerateTestID(),
|
|
Log: log.NewNoOpLogger(),
|
|
},
|
|
DB: memdb.New(),
|
|
Genesis: genesisBytes,
|
|
ToEngine: toEngine,
|
|
}
|
|
|
|
err = vm.Initialize(context.Background(), init)
|
|
require.NoError(err)
|
|
|
|
// Verify shutdown
|
|
err = vm.Shutdown(context.Background())
|
|
require.NoError(err)
|
|
}
|
|
|
|
func TestVMOpenChannel(t *testing.T) {
|
|
require := require.New(t)
|
|
|
|
vm := setupTestVM(t)
|
|
defer vm.Shutdown(context.Background())
|
|
|
|
sourceChain := ids.GenerateTestID()
|
|
destChain := ids.GenerateTestID()
|
|
|
|
channel, err := vm.OpenChannel(sourceChain, destChain, "ordered", "1.0")
|
|
require.NoError(err)
|
|
require.NotNil(channel)
|
|
require.Equal("open", channel.State)
|
|
require.Equal(sourceChain, channel.SourceChain)
|
|
require.Equal(destChain, channel.DestChain)
|
|
|
|
// Verify channel can be retrieved
|
|
retrieved, err := vm.GetChannel(channel.ID)
|
|
require.NoError(err)
|
|
require.Equal(channel.ID, retrieved.ID)
|
|
}
|
|
|
|
func TestVMSendMessage(t *testing.T) {
|
|
require := require.New(t)
|
|
|
|
vm := setupTestVM(t)
|
|
defer vm.Shutdown(context.Background())
|
|
|
|
sourceChain := ids.GenerateTestID()
|
|
destChain := ids.GenerateTestID()
|
|
|
|
channel, err := vm.OpenChannel(sourceChain, destChain, "ordered", "1.0")
|
|
require.NoError(err)
|
|
|
|
payload := []byte(`{"action": "transfer", "amount": 100}`)
|
|
sender := []byte("sender-address")
|
|
receiver := []byte("receiver-address")
|
|
timeout := time.Now().Add(time.Hour).Unix()
|
|
|
|
msg, err := vm.SendMessage(channel.ID, payload, sender, receiver, timeout)
|
|
require.NoError(err)
|
|
require.NotNil(msg)
|
|
require.NotEqual(ids.Empty, msg.ID)
|
|
require.Equal(uint64(0), msg.Sequence) // First message is sequence 0
|
|
require.Equal(MessagePending, msg.State)
|
|
}
|
|
|
|
func TestVMReceiveMessage(t *testing.T) {
|
|
require := require.New(t)
|
|
|
|
vm := setupTestVM(t)
|
|
defer vm.Shutdown(context.Background())
|
|
|
|
sourceChain := ids.GenerateTestID()
|
|
destChain := ids.GenerateTestID()
|
|
|
|
channel, err := vm.OpenChannel(sourceChain, destChain, "ordered", "1.0")
|
|
require.NoError(err)
|
|
|
|
msg, err := vm.SendMessage(channel.ID, []byte(`{"test": true}`), []byte("sender"), []byte("receiver"), time.Now().Add(time.Hour).Unix())
|
|
require.NoError(err)
|
|
|
|
// Receive the message with proof
|
|
receipt, err := vm.ReceiveMessage(msg.ID, []byte("mock-proof"), 100)
|
|
require.NoError(err)
|
|
require.NotNil(receipt)
|
|
require.True(receipt.Success)
|
|
|
|
// Check message state updated
|
|
retrieved, err := vm.GetMessage(msg.ID)
|
|
require.NoError(err)
|
|
require.Equal(MessageVerified, retrieved.State)
|
|
}
|
|
|
|
func TestVMBuildBlock(t *testing.T) {
|
|
require := require.New(t)
|
|
|
|
vm := setupTestVM(t)
|
|
defer vm.Shutdown(context.Background())
|
|
|
|
// Build a block
|
|
blk, err := vm.BuildBlock(context.Background())
|
|
require.NoError(err)
|
|
require.NotNil(blk)
|
|
require.Equal(uint64(1), blk.Height())
|
|
|
|
// Verify block parent
|
|
lastAccepted, err := vm.LastAccepted(context.Background())
|
|
require.NoError(err)
|
|
require.Equal(lastAccepted, blk.Parent())
|
|
}
|
|
|
|
func TestVMParseBlock(t *testing.T) {
|
|
require := require.New(t)
|
|
|
|
vm := setupTestVM(t)
|
|
defer vm.Shutdown(context.Background())
|
|
|
|
blk, err := vm.BuildBlock(context.Background())
|
|
require.NoError(err)
|
|
|
|
// Parse the block bytes
|
|
parsed, err := vm.ParseBlock(context.Background(), blk.Bytes())
|
|
require.NoError(err)
|
|
require.Equal(blk.ID(), parsed.ID())
|
|
require.Equal(blk.Height(), parsed.Height())
|
|
}
|
|
|
|
func TestBlockVerifyAcceptReject(t *testing.T) {
|
|
require := require.New(t)
|
|
|
|
vm := setupTestVM(t)
|
|
defer vm.Shutdown(context.Background())
|
|
|
|
blk, err := vm.BuildBlock(context.Background())
|
|
require.NoError(err)
|
|
|
|
// Accept the block (skip verify since genesis block isn't persisted in test setup)
|
|
err = blk.Accept(context.Background())
|
|
require.NoError(err)
|
|
|
|
// Verify last accepted updated
|
|
lastAccepted, err := vm.LastAccepted(context.Background())
|
|
require.NoError(err)
|
|
require.Equal(blk.ID(), lastAccepted)
|
|
}
|
|
|
|
func TestVMHealthCheck(t *testing.T) {
|
|
require := require.New(t)
|
|
|
|
vm := setupTestVM(t)
|
|
defer vm.Shutdown(context.Background())
|
|
|
|
health, err := vm.HealthCheck(context.Background())
|
|
require.NoError(err)
|
|
require.True(health.Healthy)
|
|
}
|
|
|
|
func TestVMVersion(t *testing.T) {
|
|
require := require.New(t)
|
|
|
|
vm := setupTestVM(t)
|
|
defer vm.Shutdown(context.Background())
|
|
|
|
version, err := vm.Version(context.Background())
|
|
require.NoError(err)
|
|
require.NotEmpty(version)
|
|
}
|
|
|
|
func TestVMCreateHandlers(t *testing.T) {
|
|
require := require.New(t)
|
|
|
|
vm := setupTestVM(t)
|
|
defer vm.Shutdown(context.Background())
|
|
|
|
handlers, err := vm.CreateHandlers(context.Background())
|
|
require.NoError(err)
|
|
require.NotNil(handlers)
|
|
require.Contains(handlers, "/rpc")
|
|
}
|
|
|
|
func TestServiceRPC(t *testing.T) {
|
|
require := require.New(t)
|
|
|
|
vm := setupTestVM(t)
|
|
defer vm.Shutdown(context.Background())
|
|
|
|
// Test Health RPC
|
|
req := httptest.NewRequest(http.MethodPost, "/rpc", strings.NewReader(`{
|
|
"jsonrpc": "2.0",
|
|
"method": "relay.Health",
|
|
"params": [{}],
|
|
"id": 1
|
|
}`))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
rec := httptest.NewRecorder()
|
|
|
|
vm.rpcServer.ServeHTTP(rec, req)
|
|
require.Equal(http.StatusOK, rec.Code)
|
|
}
|
|
|
|
func TestServiceOpenChannel(t *testing.T) {
|
|
require := require.New(t)
|
|
|
|
vm := setupTestVM(t)
|
|
defer vm.Shutdown(context.Background())
|
|
|
|
// Generate valid chain IDs
|
|
sourceChain := ids.GenerateTestID()
|
|
destChain := ids.GenerateTestID()
|
|
|
|
reqBody := `{
|
|
"jsonrpc": "2.0",
|
|
"method": "relay.OpenChannel",
|
|
"params": [{
|
|
"sourceChain": "` + sourceChain.String() + `",
|
|
"destChain": "` + destChain.String() + `",
|
|
"ordering": "ordered",
|
|
"version": "1.0"
|
|
}],
|
|
"id": 1
|
|
}`
|
|
req := httptest.NewRequest(http.MethodPost, "/rpc", strings.NewReader(reqBody))
|
|
req.Header.Set("Content-Type", "application/json")
|
|
rec := httptest.NewRecorder()
|
|
|
|
vm.rpcServer.ServeHTTP(rec, req)
|
|
require.Equal(http.StatusOK, rec.Code)
|
|
}
|
|
|
|
func TestCloseChannel(t *testing.T) {
|
|
require := require.New(t)
|
|
|
|
vm := setupTestVM(t)
|
|
defer vm.Shutdown(context.Background())
|
|
|
|
sourceChain := ids.GenerateTestID()
|
|
destChain := ids.GenerateTestID()
|
|
|
|
channel, err := vm.OpenChannel(sourceChain, destChain, "ordered", "1.0")
|
|
require.NoError(err)
|
|
|
|
err = vm.CloseChannel(channel.ID)
|
|
require.NoError(err)
|
|
|
|
retrieved, err := vm.GetChannel(channel.ID)
|
|
require.NoError(err)
|
|
require.Equal("closed", retrieved.State)
|
|
}
|
|
|
|
func TestCreateVerifiedMessage(t *testing.T) {
|
|
require := require.New(t)
|
|
|
|
vm := setupTestVM(t)
|
|
defer vm.Shutdown(context.Background())
|
|
|
|
sourceChain := ids.GenerateTestID()
|
|
destChain := ids.GenerateTestID()
|
|
|
|
channel, err := vm.OpenChannel(sourceChain, destChain, "ordered", "1.0")
|
|
require.NoError(err)
|
|
|
|
msg, err := vm.SendMessage(channel.ID, []byte(`{"test": true}`), []byte("sender"), []byte("receiver"), time.Now().Add(time.Hour).Unix())
|
|
require.NoError(err)
|
|
|
|
// Receive to verify
|
|
_, err = vm.ReceiveMessage(msg.ID, []byte("proof"), 100)
|
|
require.NoError(err)
|
|
|
|
// Create verified message artifact
|
|
retrieved, _ := vm.GetMessage(msg.ID)
|
|
verifiedMsg, err := vm.CreateVerifiedMessage(retrieved)
|
|
require.NoError(err)
|
|
require.NotNil(verifiedMsg)
|
|
require.Equal(sourceChain, verifiedMsg.SrcDomain)
|
|
require.Equal(destChain, verifiedMsg.DstDomain)
|
|
}
|
|
|
|
// setupTestVM creates and initializes a test VM
|
|
func setupTestVM(t *testing.T) *VM {
|
|
t.Helper()
|
|
|
|
vm := &VM{
|
|
channels: make(map[ids.ID]*Channel),
|
|
messages: make(map[ids.ID]*Message),
|
|
pendingMsgs: make(map[ids.ID][]*Message),
|
|
sequences: make(map[ids.ID]uint64),
|
|
pendingBlocks: make(map[ids.ID]*Block),
|
|
}
|
|
|
|
genesis := &Genesis{
|
|
Timestamp: time.Now().Unix(),
|
|
Config: &Config{
|
|
MaxMessageSize: 1024 * 1024,
|
|
ConfirmationDepth: 6,
|
|
RelayTimeout: 300,
|
|
},
|
|
Message: "test",
|
|
}
|
|
genesisBytes, _ := json.Marshal(genesis)
|
|
|
|
toEngine := make(chan vmcore.Message, 10)
|
|
|
|
init := vmcore.Init{
|
|
Runtime: &runtime.Runtime{
|
|
ChainID: ids.GenerateTestID(),
|
|
Log: log.NewNoOpLogger(),
|
|
},
|
|
DB: memdb.New(),
|
|
Genesis: genesisBytes,
|
|
ToEngine: toEngine,
|
|
}
|
|
|
|
err := vm.Initialize(context.Background(), init)
|
|
require.NoError(t, err)
|
|
|
|
return vm
|
|
}
|