ceremonyclient/node/execution/intrinsics/hypergraph/hypergraph_serialization_fuzz_test.go
Cassandra Heart cec73cd940
v2.1.0.1 (#441)
wire issue for proxy worker init - h/t dogeanger
moved injection from implicit to explicit for hypergraph operations
bundle address resolution for multi-tx token interactions
genesis shard init bug
message subscriber for app -> global flow bug
shard store save bug
2025-09-30 15:42:34 -05:00

710 lines
18 KiB
Go

package hypergraph_test
import (
"bytes"
"encoding/binary"
"math/big"
"testing"
"github.com/stretchr/testify/require"
hgcrdt "source.quilibrium.com/quilibrium/monorepo/hypergraph"
"source.quilibrium.com/quilibrium/monorepo/node/execution/intrinsics/hypergraph"
)
// TODO[2.1.1]: Vertex add serialization fuzz test
// FuzzVertexRemoveSerialization tests VertexRemove serialization/deserialization
func FuzzVertexRemoveSerialization(f *testing.F) {
// Add seed corpus
f.Add(make([]byte, 32), make([]byte, 32), make([]byte, 114))
f.Add(bytes.Repeat([]byte{0x55}, 32), bytes.Repeat([]byte{0xbb}, 32), make([]byte, 114))
f.Fuzz(func(t *testing.T, domain, dataAddress, signature []byte) {
if len(domain) != 32 || len(dataAddress) != 32 {
return
}
// Create vertex remove with fuzz data
vertexRemove := &hypergraph.VertexRemove{}
copy(vertexRemove.Domain[:], domain)
copy(vertexRemove.DataAddress[:], dataAddress)
vertexRemove.Signature = signature
// Serialize
data, err := vertexRemove.ToBytes()
if err != nil {
return
}
// Deserialize
vertexRemove2 := &hypergraph.VertexRemove{}
err = vertexRemove2.FromBytes(
data,
nil,
nil,
nil,
)
// If deserialization succeeds, verify round-trip
if err == nil {
if len(vertexRemove.Domain) != 0 || len(vertexRemove2.Domain) != 0 {
require.Equal(t, vertexRemove.Domain, vertexRemove2.Domain)
}
if len(vertexRemove.DataAddress) != 0 || len(vertexRemove2.DataAddress) != 0 {
require.Equal(t, vertexRemove.DataAddress, vertexRemove2.DataAddress)
}
if len(vertexRemove.Signature) != 0 || len(vertexRemove2.Signature) != 0 {
require.Equal(t, vertexRemove.Signature, vertexRemove2.Signature)
}
}
})
}
// FuzzHyperedgeAddSerialization tests HyperedgeAdd serialization/deserialization
func FuzzHyperedgeAddSerialization(f *testing.F) {
// Add seed corpus
f.Add(make([]byte, 32), make([]byte, 32), make([]byte, 32), make([]byte, 114))
f.Fuzz(func(t *testing.T, domain, hyperedgeDomain, hyperedgeDataAddr, signature []byte) {
if len(domain) != 32 || len(hyperedgeDomain) != 32 || len(hyperedgeDataAddr) != 32 {
return
}
// Create hyperedge
var heDomain [32]byte
var heDataAddr [32]byte
copy(heDomain[:], hyperedgeDomain)
copy(heDataAddr[:], hyperedgeDataAddr)
he := hgcrdt.NewHyperedge(heDomain, heDataAddr)
// Add some vertices as extrinsics
for i := 0; i < 3; i++ {
vertexDomain := [32]byte{byte(i)}
vertexDataAddr := [32]byte{byte(i + 10)}
vertex := hgcrdt.NewVertex(vertexDomain, vertexDataAddr, make([]byte, 74), big.NewInt(74))
he.AddExtrinsic(vertex)
}
// Create hyperedge add with fuzz data
hyperedgeAdd := &hypergraph.HyperedgeAdd{}
copy(hyperedgeAdd.Domain[:], domain)
hyperedgeAdd.Value = he
hyperedgeAdd.Signature = signature
// Serialize
data, err := hyperedgeAdd.ToBytes()
if err != nil {
return
}
// Deserialize
hyperedgeAdd2 := &hypergraph.HyperedgeAdd{}
err = hyperedgeAdd2.FromBytes(
data,
nil,
nil,
nil,
nil,
)
// If deserialization succeeds, verify round-trip
if err == nil {
if len(hyperedgeAdd.Domain) != 0 || len(hyperedgeAdd2.Domain) != 0 {
require.Equal(t, hyperedgeAdd.Domain, hyperedgeAdd2.Domain)
}
if len(hyperedgeAdd.Signature) != 0 || len(hyperedgeAdd2.Signature) != 0 {
require.Equal(t, hyperedgeAdd.Signature, hyperedgeAdd2.Signature)
}
if hyperedgeAdd.Value != nil && hyperedgeAdd2.Value != nil {
require.Equal(t, hyperedgeAdd.Value.GetID(), hyperedgeAdd2.Value.GetID())
}
}
})
}
// FuzzHyperedgeRemoveSerialization tests HyperedgeRemove serialization/deserialization
func FuzzHyperedgeRemoveSerialization(f *testing.F) {
// Add seed corpus
f.Add(make([]byte, 32), make([]byte, 32), make([]byte, 32), make([]byte, 114))
f.Fuzz(func(t *testing.T, domain, hyperedgeDomain, hyperedgeDataAddr, signature []byte) {
if len(domain) != 32 || len(hyperedgeDomain) != 32 || len(hyperedgeDataAddr) != 32 {
return
}
// Create hyperedge
var heDomain [32]byte
var heDataAddr [32]byte
copy(heDomain[:], hyperedgeDomain)
copy(heDataAddr[:], hyperedgeDataAddr)
he := hgcrdt.NewHyperedge(heDomain, heDataAddr)
// Create hyperedge remove with fuzz data
hyperedgeRemove := &hypergraph.HyperedgeRemove{}
copy(hyperedgeRemove.Domain[:], domain)
hyperedgeRemove.Value = he
hyperedgeRemove.Signature = signature
// Serialize
data, err := hyperedgeRemove.ToBytes()
if err != nil {
return
}
// Deserialize
hyperedgeRemove2 := &hypergraph.HyperedgeRemove{}
err = hyperedgeRemove2.FromBytes(
data,
nil,
nil,
nil,
)
// If deserialization succeeds, verify round-trip
if err == nil {
if len(hyperedgeRemove.Domain) != 0 || len(hyperedgeRemove2.Domain) != 0 {
require.Equal(t, hyperedgeRemove.Domain, hyperedgeRemove2.Domain)
}
if len(hyperedgeRemove.Signature) != 0 || len(hyperedgeRemove2.Signature) != 0 {
require.Equal(t, hyperedgeRemove.Signature, hyperedgeRemove2.Signature)
}
if hyperedgeRemove.Value != nil && hyperedgeRemove2.Value != nil {
require.Equal(t, hyperedgeRemove.Value.GetID(), hyperedgeRemove2.Value.GetID())
}
}
})
}
// FuzzHypergraphDeployArgumentsSerialization tests HypergraphDeployArguments serialization
func FuzzHypergraphDeployArgumentsSerialization(f *testing.F) {
// Add seed corpus
f.Add(make([]byte, 57), make([]byte, 57), []byte("test schema"))
f.Add([]byte{}, []byte{}, []byte{})
f.Add(make([]byte, 100), make([]byte, 100), bytes.Repeat([]byte("schema"), 100))
f.Fuzz(func(t *testing.T, readPubKey, writePubKey, rdfSchema []byte) {
if len(rdfSchema) > 10000 { // Limit schema size
return
}
// Create deploy arguments with fuzz data
deploy := &hypergraph.HypergraphDeployArguments{
Config: &hypergraph.HypergraphIntrinsicConfiguration{
ReadPublicKey: readPubKey,
WritePublicKey: writePubKey,
},
RDFSchema: rdfSchema,
}
// Serialize
data, err := deploy.DeployToBytes()
if err != nil {
return
}
// Deserialize
deploy2, err := hypergraph.DeployFromBytes(data)
// If deserialization succeeds, verify round-trip
if err == nil && deploy2 != nil && deploy2.Config != nil {
require.Equal(t, deploy.Config.ReadPublicKey, deploy2.Config.ReadPublicKey)
require.Equal(t, deploy.Config.WritePublicKey, deploy2.Config.WritePublicKey)
require.Equal(t, deploy.RDFSchema, deploy2.RDFSchema)
}
})
}
// FuzzHypergraphOperationTypeDetection tests operation type detection
func FuzzHypergraphOperationTypeDetection(f *testing.F) {
// Add various type prefixes
f.Add(uint32(hypergraph.HypergraphDeployType), []byte{})
f.Add(uint32(hypergraph.VertexAddType), []byte{})
f.Add(uint32(hypergraph.VertexRemoveType), []byte{})
f.Add(uint32(hypergraph.HyperedgeAddType), []byte{})
f.Add(uint32(hypergraph.HyperedgeRemoveType), []byte{})
f.Add(uint32(999999), []byte{}) // Invalid type
f.Add(uint32(0), make([]byte, 100))
f.Fuzz(func(t *testing.T, typePrefix uint32, additionalData []byte) {
// Create data with type prefix
buf := new(bytes.Buffer)
binary.Write(buf, binary.BigEndian, typePrefix)
buf.Write(additionalData)
data := buf.Bytes()
// Try deserializing based on type
switch typePrefix {
case hypergraph.HypergraphDeployType:
_, _ = hypergraph.DeployFromBytes(data)
case hypergraph.VertexAddType:
obj := &hypergraph.VertexAdd{}
_ = obj.FromBytes(
data,
nil,
nil,
nil,
nil,
nil,
)
case hypergraph.VertexRemoveType:
obj := &hypergraph.VertexRemove{}
_ = obj.FromBytes(
data,
nil,
nil,
nil,
)
case hypergraph.HyperedgeAddType:
obj := &hypergraph.HyperedgeAdd{}
_ = obj.FromBytes(
data,
nil,
nil,
nil,
nil,
)
case hypergraph.HyperedgeRemoveType:
obj := &hypergraph.HyperedgeRemove{}
_ = obj.FromBytes(
data,
nil,
nil,
nil,
)
}
// Test that wrong types fail appropriately
if typePrefix != hypergraph.VertexAddType && len(data) >= 4 {
obj := &hypergraph.VertexAdd{}
err := obj.FromBytes(
data,
nil,
nil,
nil,
nil,
nil,
)
if typePrefix >= hypergraph.VertexAddType && typePrefix <= hypergraph.HyperedgeRemoveType {
// Should fail for wrong but valid type
require.Error(t, err)
}
}
})
}
// FuzzDeserializationRobustness tests deserialization with completely random data
func FuzzDeserializationRobustness(f *testing.F) {
// Add various malformed inputs
f.Add([]byte{})
f.Add([]byte{0x00})
f.Add([]byte{0x00, 0x00, 0x00, 0x01}) // Just type prefix
f.Add([]byte{0xff, 0xff, 0xff, 0xff}) // Invalid type
f.Add(bytes.Repeat([]byte{0x00}, 1000))
f.Add(bytes.Repeat([]byte{0xff}, 1000))
// Add some structured but potentially malformed data
for i := uint32(1); i <= 5; i++ {
buf := new(bytes.Buffer)
binary.Write(buf, binary.BigEndian, i)
buf.Write(bytes.Repeat([]byte{0x41}, 100))
f.Add(buf.Bytes())
}
f.Fuzz(func(t *testing.T, data []byte) {
// Test all deserialization functions with random data
// They should either succeed or fail gracefully without panicking
// Test deploy
_, _ = hypergraph.DeployFromBytes(data)
// Test vertex operations
vertexAdd := &hypergraph.VertexAdd{}
_ = vertexAdd.FromBytes(
data,
nil,
nil,
nil,
nil,
nil,
)
vertexRemove := &hypergraph.VertexRemove{}
_ = vertexRemove.FromBytes(
data,
nil,
nil,
nil,
)
// Test hyperedge operations
hyperedgeAdd := &hypergraph.HyperedgeAdd{}
_ = hyperedgeAdd.FromBytes(
data,
nil,
nil,
nil,
nil,
)
hyperedgeRemove := &hypergraph.HyperedgeRemove{}
_ = hyperedgeRemove.FromBytes(
data,
nil,
nil,
nil,
)
})
}
func FuzzVertexRemove_Deserialization(f *testing.F) {
// Add valid case
validVertexRemove := &hypergraph.VertexRemove{
Domain: [32]byte{1, 2, 3},
DataAddress: [32]byte{4, 5, 6},
Signature: make([]byte, 114),
}
validData, _ := validVertexRemove.ToBytes()
f.Add(validData)
// Add truncated data
for i := 0; i < len(validData) && i < 50; i++ {
f.Add(validData[:i])
}
f.Fuzz(func(t *testing.T, data []byte) {
if len(data) > 1000000 {
t.Skip("Skipping very large input")
}
vertexRemove := &hypergraph.VertexRemove{}
_ = vertexRemove.FromBytes(
data,
nil,
nil,
nil,
) // Should not panic
})
}
func FuzzHyperedgeAdd_Deserialization(f *testing.F) {
// Add valid case
he := hgcrdt.NewHyperedge([32]byte{1}, [32]byte{2})
validHyperedgeAdd := &hypergraph.HyperedgeAdd{
Domain: [32]byte{3, 4, 5},
Value: he,
Signature: make([]byte, 114),
}
validData, _ := validHyperedgeAdd.ToBytes()
f.Add(validData)
// Add truncated data
for i := 0; i < len(validData) && i < 100; i++ {
f.Add(validData[:i])
}
f.Fuzz(func(t *testing.T, data []byte) {
if len(data) > 1000000 {
t.Skip("Skipping very large input")
}
hyperedgeAdd := &hypergraph.HyperedgeAdd{}
_ = hyperedgeAdd.FromBytes(
data,
nil,
nil,
nil,
nil,
) // Should not panic
})
}
func FuzzHyperedgeRemove_Deserialization(f *testing.F) {
// Add valid case
he := hgcrdt.NewHyperedge([32]byte{1}, [32]byte{2})
validHyperedgeRemove := &hypergraph.HyperedgeRemove{
Domain: [32]byte{3, 4, 5},
Value: he,
Signature: make([]byte, 114),
}
validData, _ := validHyperedgeRemove.ToBytes()
f.Add(validData)
// Add truncated data
for i := 0; i < len(validData) && i < 100; i++ {
f.Add(validData[:i])
}
f.Fuzz(func(t *testing.T, data []byte) {
if len(data) > 1000000 {
t.Skip("Skipping very large input")
}
hyperedgeRemove := &hypergraph.HyperedgeRemove{}
_ = hyperedgeRemove.FromBytes(
data,
nil,
nil,
nil,
) // Should not panic
})
}
func FuzzHypergraphDeploy_Deserialization(f *testing.F) {
// Add valid case
validDeploy := &hypergraph.HypergraphDeployArguments{
Config: &hypergraph.HypergraphIntrinsicConfiguration{
ReadPublicKey: make([]byte, 57),
WritePublicKey: make([]byte, 57),
},
RDFSchema: []byte("test schema"),
}
validData, _ := validDeploy.DeployToBytes()
f.Add(validData)
// Add truncated data
for i := 0; i < len(validData) && i < 50; i++ {
f.Add(validData[:i])
}
// Add invalid key lengths
f.Add([]byte{0x00, 0x00, 0x00, 0x04, // Type prefix
0x00, 0x00, 0x00, 0x10, // Invalid read key length (16 instead of 57)
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10}) // 16 bytes
f.Fuzz(func(t *testing.T, data []byte) {
if len(data) > 1000000 {
t.Skip("Skipping very large input")
}
_, _ = hypergraph.DeployFromBytes(data) // Should not panic
})
}
func FuzzMixedTypeDeserialization(f *testing.F) {
// Add valid prefixes for each type
types := []uint32{
hypergraph.HypergraphDeployType,
hypergraph.VertexAddType,
hypergraph.VertexRemoveType,
hypergraph.HyperedgeAddType,
hypergraph.HyperedgeRemoveType,
}
for _, typ := range types {
data := make([]byte, 4)
data[0] = byte(typ >> 24)
data[1] = byte(typ >> 16)
data[2] = byte(typ >> 8)
data[3] = byte(typ)
f.Add(data)
}
f.Fuzz(func(t *testing.T, data []byte) {
if len(data) > 1000000 {
t.Skip("Skipping very large input")
}
// Try deserializing as each type - should handle gracefully
_, _ = hypergraph.DeployFromBytes(data)
_ = (&hypergraph.VertexAdd{}).FromBytes(
data,
nil,
nil,
nil,
nil,
nil,
)
_ = (&hypergraph.VertexRemove{}).FromBytes(
data,
nil,
nil,
nil,
)
_ = (&hypergraph.HyperedgeAdd{}).FromBytes(
data,
nil,
nil,
nil,
nil,
)
_ = (&hypergraph.HyperedgeRemove{}).FromBytes(
data,
nil,
nil,
nil,
)
})
}
func FuzzTreeDataHandling(f *testing.F) {
// Add various tree data sizes
f.Add([]byte{}, uint32(0))
f.Add(make([]byte, 100), uint32(100))
f.Add(make([]byte, 1000), uint32(1000))
f.Add(make([]byte, 100), uint32(1000000)) // Length mismatch
f.Fuzz(func(t *testing.T, treeData []byte, declaredLength uint32) {
// Create vertex add with potentially mismatched tree data
buf := new(bytes.Buffer)
// Write type
binary.Write(buf, binary.BigEndian, uint32(hypergraph.VertexAddType))
// Write domain and data address
buf.Write(make([]byte, 32))
buf.Write(make([]byte, 32))
// Write tree data with declared length
binary.Write(buf, binary.BigEndian, declaredLength)
buf.Write(treeData) // May not match declared length
// Write signature length and data
binary.Write(buf, binary.BigEndian, uint32(114))
buf.Write(make([]byte, 114))
// Try to deserialize
vertexAdd := &hypergraph.VertexAdd{}
_ = vertexAdd.FromBytes(
buf.Bytes(),
nil,
nil,
nil,
nil,
nil,
)
})
}
func FuzzHyperedgeExtrinsicsHandling(f *testing.F) {
// Add various numbers of extrinsics
f.Add(uint32(0), uint32(0))
f.Add(uint32(1), uint32(100))
f.Add(uint32(10), uint32(1000))
f.Add(uint32(1000), uint32(100)) // Many extrinsics, small data
f.Fuzz(func(t *testing.T, numExtrinsics, extrinsicDataSize uint32) {
// Limit to reasonable values
if numExtrinsics > 1000 || extrinsicDataSize > 10000 {
return
}
// Create hyperedge with fuzz number of extrinsics
buf := new(bytes.Buffer)
// Write type
binary.Write(buf, binary.BigEndian, uint32(hypergraph.HyperedgeAddType))
// Write domain
buf.Write(make([]byte, 32))
// Write hyperedge data
// This would include the extrinsics tree serialization
// For fuzzing, we'll write simplified data
binary.Write(buf, binary.BigEndian, numExtrinsics)
for i := uint32(0); i < min32(numExtrinsics, 100); i++ {
buf.Write(make([]byte, min32(extrinsicDataSize, 1000)))
}
// Write signature
binary.Write(buf, binary.BigEndian, uint32(114))
buf.Write(make([]byte, 114))
// Try to deserialize
hyperedgeAdd := &hypergraph.HyperedgeAdd{}
_ = hyperedgeAdd.FromBytes(
buf.Bytes(),
nil,
nil,
nil,
nil,
)
})
}
func FuzzLengthOverflows(f *testing.F) {
// Add cases with large length values
f.Add(uint32(0xFFFFFFFF), uint32(0xFFFFFFFF))
f.Add(uint32(1<<31), uint32(1<<31))
f.Add(uint32(1000000), uint32(1000000))
f.Fuzz(func(t *testing.T, len1, len2 uint32) {
// Create data with potentially overflowing lengths
buf := new(bytes.Buffer)
// Write type prefix
binary.Write(buf, binary.BigEndian, uint32(hypergraph.VertexAddType))
// Write domain and data address
buf.Write(make([]byte, 32))
buf.Write(make([]byte, 32))
// Write tree data with large length
binary.Write(buf, binary.BigEndian, len1)
// Don't actually write that much data
buf.Write(make([]byte, min(int(len1), 100)))
// Write signature length
binary.Write(buf, binary.BigEndian, len2)
buf.Write(make([]byte, min(int(len2), 100)))
// Try to deserialize - should handle gracefully
vertexAdd := &hypergraph.VertexAdd{}
_ = vertexAdd.FromBytes(
buf.Bytes(),
nil,
nil,
nil,
nil,
nil,
)
})
}
func FuzzInvalidPublicKeyLengths(f *testing.F) {
// Add cases with invalid public key lengths
f.Add(uint32(0), uint32(0))
f.Add(uint32(1), uint32(57))
f.Add(uint32(57), uint32(1))
f.Add(uint32(100), uint32(100))
f.Add(uint32(0xFFFFFFFF), uint32(57))
f.Fuzz(func(t *testing.T, readKeyLen, writeKeyLen uint32) {
// Create deploy data with invalid key lengths
buf := new(bytes.Buffer)
// Write type prefix
binary.Write(buf, binary.BigEndian, uint32(hypergraph.HypergraphDeployType))
// Write read public key with invalid length
binary.Write(buf, binary.BigEndian, readKeyLen)
buf.Write(make([]byte, min(int(readKeyLen), 1000)))
// Write write public key with invalid length
binary.Write(buf, binary.BigEndian, writeKeyLen)
buf.Write(make([]byte, min(int(writeKeyLen), 1000)))
// Write schema
binary.Write(buf, binary.BigEndian, uint32(10))
buf.Write([]byte("test schema"))
// Try to deserialize - should handle gracefully
_, _ = hypergraph.DeployFromBytes(buf.Bytes())
})
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
func min32(a, b uint32) uint32 {
if a < b {
return a
}
return b
}