Files
node/vms/proposervm/block/parse_test.go
T
Hanzo AI 4c6c1affba prep for v1.30.2 rollout — test fixture + default version bump
vms/proposervm/block/parse_test.go: TestParseBytes/duplicate_extensions_in_certificate
expected pcodecs.ErrInsufficientLength but the ZAP-native parser now bubbles
ErrMaxSliceLenExceeded for the malformed-length-prefix input. Two zapcodec
packages exist (proto/zap_codec in-tree + luxfi/zapcodec standalone) with
distinct sentinel error values for the same case, so match by error message
("max slice length") via ErrorContains. Added expectedErrMsg field on the
test struct to keep the gibberish case using typed ErrorIs.

version/constants.go: defaultMinor 28→30, defaultPatch 0→2. Without ldflags
the binary was reporting luxd/1.28.0 even though latest tag is v1.30.1.
v1.30.2 is the rollout candidate this becomes after CI tags.
2026-06-06 17:25:10 -07:00

236 lines
9.2 KiB
Go

// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package block
import (
"crypto"
"encoding/hex"
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/luxfi/ids"
"github.com/luxfi/node/staking"
"github.com/luxfi/node/vms/pcodecs"
)
func TestParseBlocks(t *testing.T) {
parentID := ids.ID{1}
timestamp := time.Unix(123, 0)
pChainHeight := uint64(2)
innerBlockBytes := []byte{3}
chainID := ids.ID{4}
tlsCert, err := staking.NewTLSCert()
require.NoError(t, err)
cert, err := staking.ParseCertificate(tlsCert.Leaf.Raw)
require.NoError(t, err)
key := tlsCert.PrivateKey.(crypto.Signer)
signedBlock, err := Build(
parentID,
timestamp,
pChainHeight,
Epoch{},
cert,
innerBlockBytes,
chainID,
key,
)
require.NoError(t, err)
signedBlockBytes := signedBlock.Bytes()
malformedBlockBytes := make([]byte, len(signedBlockBytes)-1)
copy(malformedBlockBytes, signedBlockBytes)
for _, testCase := range []struct {
name string
input [][]byte
output []ParseResult
}{
{
name: "ValidThenInvalid",
input: [][]byte{signedBlockBytes, malformedBlockBytes},
output: []ParseResult{{Block: &statelessBlock{bytes: signedBlockBytes}}, {Err: pcodecs.ErrInsufficientLength}},
},
{
name: "InvalidThenValid",
input: [][]byte{malformedBlockBytes, signedBlockBytes},
output: []ParseResult{{Err: pcodecs.ErrInsufficientLength}, {Block: &statelessBlock{bytes: signedBlockBytes}}},
},
} {
t.Run(testCase.name, func(t *testing.T) {
results := ParseBlocks(testCase.input, chainID)
for i := range testCase.output {
if testCase.output[i].Block == nil {
require.Nil(t, results[i].Block)
require.ErrorIs(t, results[i].Err, testCase.output[i].Err)
} else {
require.Equal(t, testCase.output[i].Block.Bytes(), results[i].Block.Bytes())
require.NoError(t, results[i].Err)
}
}
})
}
}
func TestParse(t *testing.T) {
parentID := ids.ID{1}
timestamp := time.Unix(123, 0)
pChainHeight := uint64(2)
innerBlockBytes := []byte{3}
chainID := ids.ID{4}
tlsCert, err := staking.NewTLSCert()
require.NoError(t, err)
cert, err := staking.ParseCertificate(tlsCert.Leaf.Raw)
require.NoError(t, err)
key := tlsCert.PrivateKey.(crypto.Signer)
signedBlock, err := Build(
parentID,
timestamp,
pChainHeight,
Epoch{},
cert,
innerBlockBytes,
chainID,
key,
)
require.NoError(t, err)
unsignedBlock, err := BuildUnsigned(parentID, timestamp, pChainHeight, Epoch{}, innerBlockBytes)
require.NoError(t, err)
signedWithoutCertBlockIntf, err := BuildUnsigned(parentID, timestamp, pChainHeight, Epoch{}, innerBlockBytes)
require.NoError(t, err)
signedWithoutCertBlock := signedWithoutCertBlockIntf.(*statelessBlock)
signedWithoutCertBlock.Signature = []byte{5}
signedWithoutCertBlock.bytes, err = Codec.Marshal(CodecVersion, &signedWithoutCertBlockIntf)
require.NoError(t, err)
optionBlock, err := BuildOption(parentID, innerBlockBytes)
require.NoError(t, err)
tests := []struct {
name string
block Block
chainID ids.ID
expectedErr error
}{
{
name: "correct chainID",
block: signedBlock,
chainID: chainID,
expectedErr: nil,
},
{
name: "invalid chainID",
block: signedBlock,
chainID: ids.ID{5},
expectedErr: staking.ErrECDSAVerificationFailure,
},
{
name: "unsigned block",
block: unsignedBlock,
chainID: chainID,
expectedErr: nil,
},
{
name: "invalid signature",
block: signedWithoutCertBlockIntf,
chainID: chainID,
expectedErr: errUnexpectedSignature,
},
{
name: "option block",
block: optionBlock,
chainID: chainID,
expectedErr: nil,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
require := require.New(t)
blockBytes := test.block.Bytes()
parsedBlockWithoutVerification, err := ParseWithoutVerification(blockBytes)
require.NoError(err)
equal(require, test.block, parsedBlockWithoutVerification)
parsedBlock, err := Parse(blockBytes, test.chainID)
require.ErrorIs(err, test.expectedErr)
if test.expectedErr == nil {
equal(require, test.block, parsedBlock)
}
})
}
}
func TestParseBytes(t *testing.T) {
chainID := ids.ID{4}
tests := []struct {
name string
hex string
expectedErr error
expectedErrMsg string // when set, match by ErrorContains instead of ErrorIs
}{
{
// Malformed cert: extension count length-prefix overflows int32. Under the
// ZAP-native parser this trips the max-slice-length guard. The exact sentinel
// can vary between proto/zap_codec and the standalone luxfi/zapcodec module,
// so match on the canonical error message instead of a typed sentinel.
name: "duplicate extensions in certificate",
hex: "0000000000000100000000000000000000000000000000000000000000000000000000000000000000000000007b0000000000000002000004bd308204b9308202a1a003020102020100300d06092a864886f70d01010b050030003020170d3939313233313030303030305a180f32313232303830333233323835335a300030820222300d06092a864886f70d01010105000382020f003082020a0282020100c2b2de1c16924d9b9254a0d5b80a4bc5f9beaa4f4f40a0e4efb69eb9b55d7d37f8c82328c237d7c5b451f5427b487284fa3f365f9caa53c7fcfef8d7a461d743bd7d88129f2da62b877ebe9d6feabf1bd12923e6c12321382c782fc3bb6b6cb4986a937a1edc3814f4e621e1a62053deea8c7649e43edd97ab6b56315b00d9ab5026bb9c31fb042dc574ba83c54e720e0120fcba2e8a66b77839be3ece0d4a6383ef3f76aac952b49a15b65e18674cd1340c32cecbcbaf80ae45be001366cb56836575fb0ab51ea44bf7278817e99b6b180fdd110a49831a132968489822c56692161bbd372cf89d9b8ee5a734cff15303b3a960ee78d79e76662a701941d9ec084429f26707f767e9b1d43241c0e4f96655d95c1f4f4aa00add78eff6bf0a6982766a035bf0b465786632c5bb240788ca0fdf032d8815899353ea4bec5848fd30118711e5b356bde8a0da074cc25709623225e734ff5bd0cf65c40d9fd8fccf746d8f8f35145bcebcf378d2b086e57d78b11e84f47fa467c4d037f92bff6dd4e934e0189b58193f24c4222ffb72b5c06361cf68ca64345bc3e230cc0f40063ad5f45b1659c643662996328c2eeddcd760d6f7c9cbae081ccc065844f7ea78c858564a408979764de882793706acc67d88092790dff567ed914b03355330932616a0f26f994b963791f0b1dbd8df979db86d1ea490700a3120293c3c2b10bef10203010001a33c303a300e0603551d0f0101ff0404030204b030130603551d25040c300a06082b0601050507030230130603551d25040c300a06082b06010505070302300d06092a864886f70d01010b05000382020100a21a0d73ec9ef4eb39f810557ac70b0b775772b8bae5f42c98565bc50b5b2c57317aa9cb1da12f55d0aac7bb36a00cd4fd0d7384c4efa284b53520c5a3c4b8a65240b393eeab02c802ea146c0728c3481c9e8d3aaad9d4dd7607103dcfaa96da83460adbe18174ed5b71bde7b0a93d4fb52234a9ff54e3fd25c5b74790dfb090f2e59dc5907357f510cc3a0b70ccdb87aee214def794b316224f318b471ffa13b66e44b467670e881cb1628c99c048a503376d9b6d7b8eef2e7be47ff7d5c1d56221f4cf7fa2519b594cb5917815c64dc75d8d281bcc99b5a12899b08f2ca0f189857b64a1afc5963337f3dd6e79390e85221569f6dbbb13aadce06a3dfb5032f0cc454809627872cd7cd0cea5eba187723f07652c8abc3fc42bd62136fc66287f2cc19a7cb416923ad1862d7f820b55cacb65e43731cb6df780e2651e457a3438456aeeeb278ad9c0ad2e760f6c1cbe276eeb621c8a4e609b5f2d902beb3212e3e45df99497021ff536d0b56390c5d785a8bf7909f6b61bdc705d7d92ae22f58e7b075f164a0450d82d8286bf449072751636ab5185f59f518b845a75d112d6f7b65223479202cff67635e2ad88106bc8a0cc9352d87c5b182ac19a4680a958d814a093acf46730f87da0df6926291d02590f215041b44a0a1a32eeb3a52cddabc3d256689bace18a8d85e644cf9137cce3718f7caac1cb16ae06e874f4c701000000010300000200b8e3a4d9a4394bac714cb597f5ba1a81865185e35c782d0317e7abc0b52d49ff8e10f787bedf86f08148e3dbd2d2d478caa2a2893d31db7d5ee51339883fe84d3004440f16cb3797a7fab0f627d3ebd79217e995488e785cd6bb7b96b9d306f8109daa9cfc4162f9839f60fb965bcb3b56a5fa787549c153a4c80027398f73a617b90b7f24f437b140cd3ac832c0b75ec98b9423b275782988a9fd426937b8f82fbb0e88a622934643fb6335c1a080a4d13125544b04585d5f5295be7cd2c8be364246ea3d5df3e837b39a85074575a1fa2f4799050460110bdfb20795c8a9172a20f61b95e1c5c43eccd0c2c155b67385366142c63409cb3fb488e7aba6c8930f7f151abf1c24a54bd21c3f7a06856ea9db35beddecb30d2c61f533a3d0590bdbb438c6f2a2286dfc3c71b383354f0abad72771c2cc3687b50c2298783e53857cf26058ed78d0c1cf53786eb8d006a058ee3c85a7b2b836b5d03ef782709ce8f2725548e557b3de45a395a669a15f1d910e97015d22ac70020cab7e2531e8b1f739b023b49e742203e9e19a7fe0053826a9a2fe2e118d3b83498c2cb308573202ad41aa4a390aee4b6b5dd2164e5c5cd1b5f68b7d5632cf7dbb9a9139663c9aac53a74b2c6fc73cad80e228a186ba027f6f32f0182d62503e04fcced385f2e7d2e11c00940622ebd533b4d144689082f9777e5b16c36f9af9066e0ad6564d43",
expectedErrMsg: "max slice length",
},
{
name: "gibberish",
hex: "000102030405",
expectedErr: pcodecs.ErrUnknownVersion,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
require := require.New(t)
bytes, err := hex.DecodeString(test.hex)
require.NoError(err)
_, err = Parse(bytes, chainID)
if test.expectedErrMsg != "" {
require.ErrorContains(err, test.expectedErrMsg)
} else {
require.ErrorIs(err, test.expectedErr)
}
})
}
}
func TestParseHeader(t *testing.T) {
require := require.New(t)
chainID := ids.ID{1}
parentID := ids.ID{2}
bodyID := ids.ID{3}
builtHeader, err := BuildHeader(
chainID,
parentID,
bodyID,
)
require.NoError(err)
builtHeaderBytes := builtHeader.Bytes()
parsedHeader, err := ParseHeader(builtHeaderBytes)
require.NoError(err)
equalHeader(require, builtHeader, parsedHeader)
}