From 2730ee6a7cdf13f82e17d07258ab2adc80cb3280 Mon Sep 17 00:00:00 2001 From: Jonathan Harvey-Buschel Date: Tue, 13 Aug 2024 14:35:05 -0400 Subject: [PATCH 1/5] rpcserver: initial rapid test for uni ID unmarshal --- .golangci.yml | 6 + go.mod | 1 + go.sum | 2 + rpcserver_test.go | 451 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 460 insertions(+) create mode 100644 rpcserver_test.go diff --git a/.golangci.yml b/.golangci.yml index eb161f413..92391711a 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -72,4 +72,10 @@ issues: - unused - deadcode - varcheck + # Ignore errors intentionally not returned in property-based tests. + # Needed here because the inline ignore directive is broken: + # https://github.com/gostaticanalysis/nilerr/issues/8 + - path: rpcserver_test.go + linters: + - nilerr new-from-rev: c723abd3c9db8a6a2f3f1eaa85ce5aefb52c8170 diff --git a/go.mod b/go.mod index e9ed5aa33..f25710b07 100644 --- a/go.mod +++ b/go.mod @@ -201,6 +201,7 @@ require ( modernc.org/strutil v1.2.0 // indirect modernc.org/token v1.1.0 // indirect nhooyr.io/websocket v1.8.7 // indirect + pgregory.net/rapid v1.1.0 sigs.k8s.io/yaml v1.2.0 // indirect ) diff --git a/go.sum b/go.sum index d9159df35..7fae92d46 100644 --- a/go.sum +++ b/go.sum @@ -1169,6 +1169,8 @@ modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g= nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= +pgregory.net/rapid v1.1.0 h1:CMa0sjHSru3puNx+J0MIAuiiEV4N0qj8/cMWGBBCsjw= +pgregory.net/rapid v1.1.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/rpcserver_test.go b/rpcserver_test.go new file mode 100644 index 000000000..37be2e529 --- /dev/null +++ b/rpcserver_test.go @@ -0,0 +1,451 @@ +package taprootassets + +import ( + "bytes" + "crypto/sha256" + "encoding/hex" + "fmt" + "testing" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/lightninglabs/taproot-assets/taprpc/universerpc" + "github.com/lightninglabs/taproot-assets/universe" + "pgregory.net/rapid" +) + +// Result is used to store the output of a fallible function call. +type Result[T any] struct { + res T + err error +} + +// genUniIDField is an interface that is used to compare generated input data +// with unmarshalled data. +type genUniIDField[T any, U universe.Identifier] interface { + // IsValid checks if the generated data should be rejected during + // unmarshal. + IsValid() error + + // IsEqual checks if the generated data is equal to the unmarshalled + // data. + IsEqual(Result[U]) error + + // Inner returns the generated data. + Inner() T + + // ValidInputErrorMsg returns an error message for valid input that + // unmarshal failed on. + ValidInputErrorMsg(error) error + + // InvalidInputErrorMsg returns an error message for an invalid input + // that unmarshal succeeded on. + InvalidInputErrorMsg(error) error +} + +// Compare compares generated input data to unmarshalled data, checking for +// the expected behavior of unmarshalling and data equality. +func Compare[T any, U universe.Identifier](gen genUniIDField[T, U], + res Result[U]) error { + + validGen := gen.IsValid() + + // Unmarshal was expected to fail. + if res.err != nil && validGen != nil { + return nil + } + + // Unmarshal failed on valid input. + if res.err != nil && validGen == nil { + return gen.ValidInputErrorMsg(res.err) + } + + // Unmarshal succeeded on invalid input. + if res.err == nil && validGen != nil { + return gen.InvalidInputErrorMsg(res.err) + } + + // Unmarhsal succeeded on valid input; check equality. + if res.err == nil && validGen == nil { + return gen.IsEqual(res) + } + + // This should be unreachable. + return nil +} + +// genAssetId is generated data used to populate universerpc.ID_AssetId. +type genAssetId struct { + Bytes []byte +} + +func (id genAssetId) Inner() []byte { + return id.Bytes +} + +// NewAssetId creates a new genAssetId instance. +func NewAssetId(t *rapid.T) genAssetId { + var id genAssetId + id.Bytes = rapid.SliceOf(rapid.Byte()).Draw(t, "ID") + + return id +} + +func (id genAssetId) IsValid() error { + // The only valid size for an asset ID is 32 bytes. + idSize := len(id.Bytes) + if idSize != sha256.Size { + return fmt.Errorf("generated asset ID invalid size: %d", idSize) + } + + return nil +} + +func (id genAssetId) IsEqual(other Result[universe.Identifier]) error { + otherBytes := other.res.AssetID[:] + if len(otherBytes) == 0 { + return fmt.Errorf("asset ID bytes not unmarshalled: %v", + id.Inner()) + } + + if !bytes.Equal(id.Bytes, otherBytes) { + return fmt.Errorf("asset ID mismatch: generated %x, "+ + "unmarshalled %x", id.Inner(), otherBytes) + } + + return nil +} + +func (id genAssetId) ValidInputErrorMsg(err error) error { + return fmt.Errorf("unmarshal asset ID bytes failed: %v, %w", + id.Inner(), err) +} + +func (id genAssetId) InvalidInputErrorMsg(err error) error { + return fmt.Errorf("invalid asset ID bytes not rejected: %v, %w", + id.Inner(), id.IsValid()) +} + +var _ genUniIDField[[]byte, universe.Identifier] = (*genAssetId)(nil) + +// genAssetIdStr is generated data used to populate universerpc.ID_AssetIdStr. +type genAssetIdStr struct { + Str string +} + +func (id genAssetIdStr) Inner() string { + return id.Str +} + +// NewAssetIDStr creates a new genAssetIdStr instance. +func NewAssetIDStr(t *rapid.T) genAssetIdStr { + var id genAssetIdStr + id.Str = rapid.String().Draw(t, "ID string") + + return id +} + +func (id genAssetIdStr) IsValid() error { + idSize := len(id.Inner()) + if idSize == 0 { + return fmt.Errorf("asset ID string empty") + } + + // Invalid hex should be rejected. + _, hexErr := hex.DecodeString(id.Inner()) + if hexErr != nil { + return fmt.Errorf("non-hex asset ID string: %w", hexErr) + } + + // The only valid size for a hex-encoded asset ID is 64 bytes. + if idSize != sha256.Size*2 { + return fmt.Errorf("asset ID string invalid size: %d", idSize) + } + + return nil +} + +func (id genAssetIdStr) IsEqual(other Result[universe.Identifier]) error { + otherStr := other.res.AssetID.String() + if len(otherStr) == 0 { + return fmt.Errorf("asset ID string not unmarshalled: "+ + "generated %v", id.Inner()) + } + + if id.Str != otherStr { + return fmt.Errorf("asset ID string mismatch: generated %s, "+ + "unmarshalled %s", id.Inner(), otherStr) + } + + return nil +} + +func (id genAssetIdStr) ValidInputErrorMsg(err error) error { + return fmt.Errorf("unmarshal asset ID string failed: %v, %w", + id.Inner(), err) +} + +func (id genAssetIdStr) InvalidInputErrorMsg(err error) error { + return fmt.Errorf("invalid asset ID string not rejected: %v, %w", + id.Inner(), id.IsValid()) +} + +var _ genUniIDField[string, universe.Identifier] = (*genAssetIdStr)(nil) + +// genGroupKey is generated data used to populate universerpc.ID_GroupKey. +type genGroupKey struct { + Bytes []byte +} + +func (id genGroupKey) Inner() []byte { + return id.Bytes +} + +// NewGroupKey creates a new genGroupKey instance. +func NewGroupKey(t *rapid.T) genGroupKey { + var id genGroupKey + id.Bytes = rapid.SliceOf(rapid.Byte()).Draw(t, "Group key") + + return id +} + +func (id genGroupKey) IsValid() error { + // The only valid size for a group key is 32 or 33 bytes. + idSize := len(id.Bytes) + if idSize != schnorr.PubKeyBytesLen && + idSize != btcec.PubKeyBytesLenCompressed { + + return fmt.Errorf("generated group key invalid size: %d", + idSize) + } + + // The generated key must be valid. + _, keyErr := parseUserKey(id.Bytes) + return keyErr +} + +func (id genGroupKey) IsEqual(otherResult Result[universe.Identifier]) error { + otherKey := otherResult.res.GroupKey + if otherKey == nil { + return fmt.Errorf("group key not unmarshalled: %v", id.Inner()) + } + + // Since we parse the provided key as Schnorr, we must drop the parity + // byte from the generated bytes before comparison. + otherKeyBytes := schnorr.SerializePubKey(otherKey) + idBytes := id.Inner() + if len(id.Inner()) == btcec.PubKeyBytesLenCompressed { + idBytes = idBytes[1:] + } + + if !bytes.Equal(idBytes, otherKeyBytes) { + return fmt.Errorf("group key mismatch: generated %x, "+ + "unmarshalled %x", id.Inner(), otherKeyBytes) + } + + return nil +} + +func (id genGroupKey) ValidInputErrorMsg(err error) error { + return fmt.Errorf("unmarshal group key bytes failed: %x, %w", + id.Inner(), err) +} + +func (id genGroupKey) InvalidInputErrorMsg(err error) error { + return fmt.Errorf("invalid group key bytes not rejected: %x, %w", + id.Inner(), id.IsValid()) +} + +var _ genUniIDField[[]byte, universe.Identifier] = (*genGroupKey)(nil) + +// genGroupKeyStr is generated data used to populate universerpc.ID_GroupKeyStr. +type genGroupKeyStr struct { + Str string +} + +func (id genGroupKeyStr) Inner() string { + return id.Str +} + +// NewGroupKeyStr creates a new genGroupKeyStr instance. +func NewGroupKeyStr(t *rapid.T) genGroupKeyStr { + var id genGroupKeyStr + id.Str = rapid.String().Draw(t, "Group key string") + + return id +} + +func (id genGroupKeyStr) IsValid() error { + idSize := len(id.Inner()) + if idSize == 0 { + return fmt.Errorf("group key string empty") + } + + // Invalid hex should be rejected. + _, hexErr := hex.DecodeString(id.Inner()) + if hexErr != nil { + return fmt.Errorf("non-hex group key string: %w", hexErr) + } + + // The only valid sizes for a group key string is 64 or 66 bytes. + if idSize != schnorr.PubKeyBytesLen*2 && + idSize != btcec.PubKeyBytesLenCompressed*2 { + + return fmt.Errorf("generated group key string invalid size: %d", + idSize) + } + + return nil +} + +func (id genGroupKeyStr) IsEqual( + otherResult Result[universe.Identifier]) error { + + otherKey := otherResult.res.GroupKey + if otherKey == nil { + return fmt.Errorf("group key string not unmarshalled: %v", + id.Inner()) + } + + // Since we parse the provided key as Schnorr, we must drop the parity + // byte from the generated string before comparison. + otherKeyStr := hex.EncodeToString(schnorr.SerializePubKey(otherKey)) + idStr := id.Inner() + if len(id.Inner()) == btcec.PubKeyBytesLenCompressed*2 { + idStr = idStr[2:] + } + + if idStr != otherKeyStr { + return fmt.Errorf("group key string mismatch: generated %s, "+ + "unmarshalled %s", id.Inner(), otherKeyStr) + } + + return nil +} + +func (id genGroupKeyStr) ValidInputErrorMsg(err error) error { + return fmt.Errorf("unmarshal group key string failed: %v, %w", + id.Inner(), err) +} + +func (id genGroupKeyStr) InvalidInputErrorMsg(err error) error { + return fmt.Errorf("invalid group key string not rejected: %v, %w", + id.Inner(), id.IsValid()) +} + +var _ genUniIDField[string, universe.Identifier] = (*genGroupKeyStr)(nil) + +// testUnmarshalUniId tests that UnmarshalUniID correctly unmarshals a +// well-formed rpc ID, and rejects an invalid ID. +func testUnmarshalUniId(t *rapid.T) { + KnownProofTypes := map[universerpc.ProofType]int32{ + universerpc.ProofType_PROOF_TYPE_UNSPECIFIED: 0, + universerpc.ProofType_PROOF_TYPE_ISSUANCE: 1, + universerpc.ProofType_PROOF_TYPE_TRANSFER: 2, + } + + IDBytes := NewAssetId(t) + IDStr := NewAssetIDStr(t) + IDGroupKeyBytes := NewGroupKey(t) + IDGroupKeyStr := NewGroupKeyStr(t) + + IDFieldSelector := rapid.ByteMax(0x5).Draw(t, "ID field selector") + proofType := rapid.Int32().Draw(t, "proofType") + rpcProofType := universerpc.ProofType(proofType) + + uniId := &universerpc.ID{ + ProofType: rpcProofType, + } + + // Set the ID to random data, of a random type. + switch IDFieldSelector { + case 0: + uniId.Id = &universerpc.ID_AssetId{ + AssetId: IDBytes.Inner(), + } + + case 1: + uniId.Id = &universerpc.ID_AssetIdStr{ + AssetIdStr: IDStr.Inner(), + } + + case 2: + uniId.Id = &universerpc.ID_GroupKey{ + GroupKey: IDGroupKeyBytes.Inner(), + } + + case 3: + uniId.Id = &universerpc.ID_GroupKeyStr{ + GroupKeyStr: IDGroupKeyStr.Inner(), + } + + // Empty ID field. + case 4: + + // Empty universe ID. + case 5: + uniId = nil + } + + nativeUniID, err := UnmarshalUniID(uniId) + unmarshalResult := Result[universe.Identifier]{ + res: nativeUniID, + err: err, + } + + // Unmarshalling an unknown proof type should fail. + _, knownProofType := KnownProofTypes[rpcProofType] + if !knownProofType { + if err == nil { + t.Fatalf("unknown proof type not rejected: %v", + rpcProofType) + } + + return + } + + switch IDFieldSelector { + case 0: + if cmpErr := Compare(IDBytes, unmarshalResult); cmpErr != nil { + t.Fatalf("%v", err) + } + + case 1: + if cmpErr := Compare(IDStr, unmarshalResult); cmpErr != nil { + t.Fatalf("%v", err) + } + + case 2: + cmpErr := Compare(IDGroupKeyBytes, unmarshalResult) + if cmpErr != nil { + t.Fatalf("%v", err) + } + + case 3: + cmpErr := Compare(IDGroupKeyStr, unmarshalResult) + if cmpErr != nil { + t.Fatalf("%v", err) + } + + case 4: + if err == nil { + t.Fatalf("unmarshal empty ID not rejected: %v", err) + } + + case 5: + if err == nil { + t.Fatalf("unmarshal ID with empty ID not rejected: %v", + err) + } + } + + // Check equality of the proof type. + if err == nil && int32(nativeUniID.ProofType) != proofType { + t.Fatalf("proof type mismatch: generated %v, unmarshalled %v", + proofType, nativeUniID.ProofType) + } +} + +func TestUnmarshalUniId(t *testing.T) { + rapid.Check(t, testUnmarshalUniId) +} From 9b7c84ea302a592e7d246a6738ed8d5acba048aa Mon Sep 17 00:00:00 2001 From: Jonathan Harvey-Buschel Date: Tue, 13 Aug 2024 14:35:44 -0400 Subject: [PATCH 2/5] rpcserver: add length checks to UnmarshalUniID --- rpcserver.go | 16 ++++++++++-- ...UnmarshalUniId-20240815200615-3628702.fail | 21 +++++++++++++++ ...UnmarshalUniId-20240815200723-3629334.fail | 26 +++++++++++++++++++ 3 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 testdata/rapid/TestUnmarshalUniId/TestUnmarshalUniId-20240815200615-3628702.fail create mode 100644 testdata/rapid/TestUnmarshalUniId/TestUnmarshalUniId-20240815200723-3629334.fail diff --git a/rpcserver.go b/rpcserver.go index 79fae06e4..54cf1a975 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -4670,8 +4670,14 @@ func UnmarshalUniID(rpcID *unirpc.ID) (universe.Identifier, error) { } switch { case rpcID.GetAssetId() != nil: + rpcAssetID := rpcID.GetAssetId() + if len(rpcAssetID) != sha256.Size { + return universe.Identifier{}, fmt.Errorf("asset ID " + + "must be 32 bytes") + } + var assetID asset.ID - copy(assetID[:], rpcID.GetAssetId()) + copy(assetID[:], rpcAssetID) return universe.Identifier{ AssetID: assetID, @@ -4679,7 +4685,13 @@ func UnmarshalUniID(rpcID *unirpc.ID) (universe.Identifier, error) { }, nil case rpcID.GetAssetIdStr() != "": - assetIDBytes, err := hex.DecodeString(rpcID.GetAssetIdStr()) + rpcAssetIDStr := rpcID.GetAssetIdStr() + if len(rpcAssetIDStr) != sha256.Size*2 { + return universe.Identifier{}, fmt.Errorf("asset ID string " + + "must be 64 bytes") + } + + assetIDBytes, err := hex.DecodeString(rpcAssetIDStr) if err != nil { return universe.Identifier{}, err } diff --git a/testdata/rapid/TestUnmarshalUniId/TestUnmarshalUniId-20240815200615-3628702.fail b/testdata/rapid/TestUnmarshalUniId/TestUnmarshalUniId-20240815200615-3628702.fail new file mode 100644 index 000000000..ad03e9e49 --- /dev/null +++ b/testdata/rapid/TestUnmarshalUniId/TestUnmarshalUniId-20240815200615-3628702.fail @@ -0,0 +1,21 @@ +# 2024/08/15 20:06:15.870045 [TestUnmarshalUniId] [rapid] draw ID: []byte{0x0} +# 2024/08/15 20:06:15.870050 [TestUnmarshalUniId] [rapid] draw ID string: "" +# 2024/08/15 20:06:15.870052 [TestUnmarshalUniId] [rapid] draw Group key: []byte{} +# 2024/08/15 20:06:15.870053 [TestUnmarshalUniId] [rapid] draw Group key string: "" +# 2024/08/15 20:06:15.870053 [TestUnmarshalUniId] [rapid] draw ID field selector: 0x0 +# 2024/08/15 20:06:15.870055 [TestUnmarshalUniId] [rapid] draw proofType: 0 +# 2024/08/15 20:06:15.870056 [TestUnmarshalUniId] +# +v0.4.8#17568384081585189385 +0x5555555555555 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 \ No newline at end of file diff --git a/testdata/rapid/TestUnmarshalUniId/TestUnmarshalUniId-20240815200723-3629334.fail b/testdata/rapid/TestUnmarshalUniId/TestUnmarshalUniId-20240815200723-3629334.fail new file mode 100644 index 000000000..0ace329eb --- /dev/null +++ b/testdata/rapid/TestUnmarshalUniId/TestUnmarshalUniId-20240815200723-3629334.fail @@ -0,0 +1,26 @@ +# 2024/08/15 20:07:23.350129 [TestUnmarshalUniId] [rapid] draw ID: []byte{} +# 2024/08/15 20:07:23.350133 [TestUnmarshalUniId] [rapid] draw ID string: "AA" +# 2024/08/15 20:07:23.350135 [TestUnmarshalUniId] [rapid] draw Group key: []byte{} +# 2024/08/15 20:07:23.350136 [TestUnmarshalUniId] [rapid] draw Group key string: "" +# 2024/08/15 20:07:23.350137 [TestUnmarshalUniId] [rapid] draw ID field selector: 0x1 +# 2024/08/15 20:07:23.350138 [TestUnmarshalUniId] [rapid] draw proofType: 0 +# 2024/08/15 20:07:23.350140 [TestUnmarshalUniId] +# +v0.4.8#13202072726014832767 +0x0 +0x5555555555555 +0x0 +0x0 +0x0 +0x5555555555555 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x0 +0x1 +0x0 +0x0 +0x0 \ No newline at end of file From 428506f62b03238128cc6f491d7711c20d22b32f Mon Sep 17 00:00:00 2001 From: Jonathan Harvey-Buschel Date: Fri, 16 Aug 2024 14:37:01 -0400 Subject: [PATCH 3/5] rpcserver: panic test for unmarshalAssetLeaf --- rpcserver_test.go | 153 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 151 insertions(+), 2 deletions(-) diff --git a/rpcserver_test.go b/rpcserver_test.go index 37be2e529..2dcc64666 100644 --- a/rpcserver_test.go +++ b/rpcserver_test.go @@ -9,11 +9,90 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/lightninglabs/taproot-assets/taprpc" "github.com/lightninglabs/taproot-assets/taprpc/universerpc" "github.com/lightninglabs/taproot-assets/universe" "pgregory.net/rapid" ) +// Custom generators. +var ( + ByteSliceGen = rapid.SliceOf(rapid.Byte()) + GenesisInfoGen = rapid.Custom(func(t *rapid.T) taprpc.GenesisInfo { + return taprpc.GenesisInfo{ + GenesisPoint: rapid.String().Draw(t, "genesis_point"), + Name: rapid.String().Draw(t, "name"), + MetaHash: ByteSliceGen.Draw(t, "meta_hash"), + AssetId: ByteSliceGen.Draw(t, "id"), + AssetType: taprpc.AssetType( + rapid.Int32().Draw(t, "asset_type"), + ), + OutputIndex: rapid.Uint32().Draw(t, "output_index"), + } + }) + AssetGroupGen = rapid.Custom(func(t *rapid.T) taprpc.AssetGroup { + return taprpc.AssetGroup{ + RawGroupKey: ByteSliceGen.Draw(t, "raw_group_key"), + TweakedGroupKey: ByteSliceGen.Draw( + t, "tweaked_group_key", + ), + AssetWitness: ByteSliceGen.Draw(t, "asset_witness"), + TapscriptRoot: ByteSliceGen.Draw(t, "tapscript_root"), + } + }) + AnchorInfoGen = rapid.Custom(func(t *rapid.T) taprpc.AnchorInfo { + return taprpc.AnchorInfo{ + AnchorTx: ByteSliceGen.Draw(t, "anchor_tx"), + AnchorBlockHash: rapid.String().Draw( + t, "anchor_block_hash", + ), + AnchorOutpoint: rapid.String().Draw( + t, "anchor_outpoint", + ), + InternalKey: ByteSliceGen.Draw(t, "internal_key"), + MerkleRoot: ByteSliceGen.Draw(t, "merkle_root"), + TapscriptSibling: ByteSliceGen.Draw( + t, "tapscript_sibling", + ), + BlockHeight: rapid.Uint32().Draw(t, "block_height"), + } + }) + PrevInputAssetGen = rapid.Custom( + func(t *rapid.T) taprpc.PrevInputAsset { + + return taprpc.PrevInputAsset{ + AnchorPoint: rapid.String().Draw( + t, "anchor_point", + ), + AssetId: ByteSliceGen.Draw(t, "asset_id"), + ScriptKey: ByteSliceGen.Draw(t, "script_key"), + Amount: rapid.Uint64().Draw(t, "amount"), + } + }) + PrevWitnessGen = rapid.Custom(func(t *rapid.T) taprpc.PrevWitness { + // Leave the split commitment as nil. + return taprpc.PrevWitness{ + PrevId: rapid.Ptr(PrevInputAssetGen, true).Draw( + t, "prev_id", + ), + TxWitness: rapid.SliceOf(ByteSliceGen).Draw( + t, "tx_witnesses", + ), + } + }) + PrevWitnessesGen = rapid.Custom(func(t *rapid.T) []*taprpc.PrevWitness { + witnessGen := rapid.Ptr(PrevWitnessGen, false) + return rapid.SliceOf(witnessGen).Draw(t, "prev_witnesses") + }) + DecDisplayGen = rapid.Custom(func(t *rapid.T) taprpc.DecimalDisplay { + return taprpc.DecimalDisplay{ + DecimalDisplay: rapid.Uint32().Draw( + t, "decimal_display", + ), + } + }) +) + // Result is used to store the output of a fallible function call. type Result[T any] struct { res T @@ -86,7 +165,7 @@ func (id genAssetId) Inner() []byte { // NewAssetId creates a new genAssetId instance. func NewAssetId(t *rapid.T) genAssetId { var id genAssetId - id.Bytes = rapid.SliceOf(rapid.Byte()).Draw(t, "ID") + id.Bytes = ByteSliceGen.Draw(t, "ID") return id } @@ -204,7 +283,7 @@ func (id genGroupKey) Inner() []byte { // NewGroupKey creates a new genGroupKey instance. func NewGroupKey(t *rapid.T) genGroupKey { var id genGroupKey - id.Bytes = rapid.SliceOf(rapid.Byte()).Draw(t, "Group key") + id.Bytes = ByteSliceGen.Draw(t, "Group key") return id } @@ -449,3 +528,73 @@ func testUnmarshalUniId(t *rapid.T) { func TestUnmarshalUniId(t *testing.T) { rapid.Check(t, testUnmarshalUniId) } + +func testUnmarshalAssetLeaf(t *rapid.T) { + // rapid.Make failed on the private gRPC-specific fields of + // taprpc.Asset, so we'll populate only the public fields. + LeafAssetGen := rapid.Custom(func(t *rapid.T) taprpc.Asset { + vers := taprpc.AssetVersion(rapid.Int32().Draw(t, "version")) + genesis := rapid.Ptr(GenesisInfoGen, true).Draw(t, "genesis") + amount := rapid.Uint64().Draw(t, "amount") + lockTime := rapid.Int32().Draw(t, "lock_time") + relativeLockTime := rapid.Int32().Draw(t, "relative_lock_time") + scriptVersion := rapid.Int32().Draw(t, "script_version") + scriptKey := ByteSliceGen.Draw(t, "script_key") + scriptKeyIsLocal := rapid.Bool().Draw(t, "script_key_is_local") + group := rapid.Ptr(AssetGroupGen, true).Draw(t, "asset_group") + chainAnchor := rapid.Ptr(AnchorInfoGen, true).Draw( + t, "chain_anchor", + ) + prevWitnesses := PrevWitnessesGen.Draw(t, "prev_witnesses") + isSpent := rapid.Bool().Draw(t, "is_spent") + leaseOwner := ByteSliceGen.Draw(t, "lease_owner") + leaseExpiry := rapid.Int64().Draw(t, "lease_expiry") + isBurn := rapid.Bool().Draw(t, "is_burn") + scriptKeyDeclaredKnown := rapid.Bool().Draw( + t, "script_key_declared_known", + ) + scriptKeyHasScriptPath := rapid.Bool().Draw( + t, "script_key_has_script_path", + ) + decimalDisplay := rapid.Ptr(DecDisplayGen, true).Draw( + t, "decimal_display", + ) + + return taprpc.Asset{ + Version: vers, + AssetGenesis: genesis, + Amount: amount, + LockTime: lockTime, + RelativeLockTime: relativeLockTime, + ScriptVersion: scriptVersion, + ScriptKey: scriptKey, + ScriptKeyIsLocal: scriptKeyIsLocal, + AssetGroup: group, + ChainAnchor: chainAnchor, + PrevWitnesses: prevWitnesses, + IsSpent: isSpent, + LeaseOwner: leaseOwner, + LeaseExpiry: leaseExpiry, + IsBurn: isBurn, + ScriptKeyDeclaredKnown: scriptKeyDeclaredKnown, + ScriptKeyHasScriptPath: scriptKeyHasScriptPath, + DecimalDisplay: decimalDisplay, + } + }) + + leafGen := rapid.Custom(func(t *rapid.T) universerpc.AssetLeaf { + return universerpc.AssetLeaf{ + Asset: rapid.Ptr(LeafAssetGen, true).Draw(t, "Asset"), + Proof: ByteSliceGen.Draw(t, "Proof"), + } + }) + leaf := rapid.Ptr(leafGen, true).Draw(t, "Leaf") + + // Don't check the unmarshal output, we are only testing if we can + // cause unmarshal to panic. + _, _ = unmarshalAssetLeaf(leaf) +} + +func TestUnmarshalAssetLeaf(t *testing.T) { + rapid.Check(t, testUnmarshalAssetLeaf) +} From 3c85836ebae2500a99eb6ebccb15a56c2772c236 Mon Sep 17 00:00:00 2001 From: Jonathan Harvey-Buschel Date: Wed, 14 Aug 2024 17:21:36 -0400 Subject: [PATCH 4/5] rpcserver: simplify other unmarshal funcs --- rpcserver.go | 127 ++++++++++-------- ...rshalAssetLeaf-20240815223752-3701568.fail | 4 + 2 files changed, 72 insertions(+), 59 deletions(-) create mode 100644 testdata/rapid/TestUnmarshalAssetLeaf/TestUnmarshalAssetLeaf-20240815223752-3701568.fail diff --git a/rpcserver.go b/rpcserver.go index 54cf1a975..9f077e1fc 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -4668,63 +4668,57 @@ func UnmarshalUniID(rpcID *unirpc.ID) (universe.Identifier, error) { return universe.Identifier{}, fmt.Errorf("unable to unmarshal "+ "proof type: %w", err) } + + var ( + assetIDBytes []byte + groupKeyBytes []byte + ) + switch { case rpcID.GetAssetId() != nil: - rpcAssetID := rpcID.GetAssetId() - if len(rpcAssetID) != sha256.Size { + assetIDBytes = rpcID.GetAssetId() + if len(assetIDBytes) != sha256.Size { return universe.Identifier{}, fmt.Errorf("asset ID " + "must be 32 bytes") } - var assetID asset.ID - copy(assetID[:], rpcAssetID) - - return universe.Identifier{ - AssetID: assetID, - ProofType: proofType, - }, nil - case rpcID.GetAssetIdStr() != "": rpcAssetIDStr := rpcID.GetAssetIdStr() if len(rpcAssetIDStr) != sha256.Size*2 { - return universe.Identifier{}, fmt.Errorf("asset ID string " + - "must be 64 bytes") + return universe.Identifier{}, fmt.Errorf("asset ID " + + "string must be 64 chars") } - assetIDBytes, err := hex.DecodeString(rpcAssetIDStr) + assetIDBytes, err = hex.DecodeString(rpcAssetIDStr) if err != nil { return universe.Identifier{}, err } - // TODO(roasbeef): reuse with above - - var assetID asset.ID - copy(assetID[:], assetIDBytes) - - return universe.Identifier{ - AssetID: assetID, - ProofType: proofType, - }, nil - case rpcID.GetGroupKey() != nil: - groupKey, err := parseUserKey(rpcID.GetGroupKey()) + groupKeyBytes = rpcID.GetGroupKey() + + case rpcID.GetGroupKeyStr() != "": + rpcGroupKeyStr := rpcID.GetGroupKeyStr() + groupKeyBytes, err = hex.DecodeString(rpcGroupKeyStr) if err != nil { return universe.Identifier{}, err } + default: + return universe.Identifier{}, fmt.Errorf("no id set") + } + + switch { + case len(assetIDBytes) != 0: + var assetID asset.ID + copy(assetID[:], assetIDBytes) + return universe.Identifier{ - GroupKey: groupKey, + AssetID: assetID, ProofType: proofType, }, nil - case rpcID.GetGroupKeyStr() != "": - groupKeyBytes, err := hex.DecodeString(rpcID.GetGroupKeyStr()) - if err != nil { - return universe.Identifier{}, err - } - - // TODO(roasbeef): reuse with above - + case len(groupKeyBytes) != 0: groupKey, err := parseUserKey(groupKeyBytes) if err != nil { return universe.Identifier{}, err @@ -4736,7 +4730,7 @@ func UnmarshalUniID(rpcID *unirpc.ID) (universe.Identifier, error) { }, nil default: - return universe.Identifier{}, fmt.Errorf("no id set") + return universe.Identifier{}, fmt.Errorf("malformed id") } } @@ -5341,6 +5335,10 @@ func unmarshalUniverseKey(key *unirpc.UniverseKey) (universe.Identifier, // unmarshalAssetLeaf unmarshals an asset leaf from the RPC form. func unmarshalAssetLeaf(leaf *unirpc.AssetLeaf) (*universe.Leaf, error) { + if leaf == nil { + return nil, fmt.Errorf("missing asset leaf") + } + // We'll just pull the asset details from the serialized issuance proof // itself. var proofAsset asset.Asset @@ -5371,6 +5369,10 @@ func unmarshalAssetLeaf(leaf *unirpc.AssetLeaf) (*universe.Leaf, error) { func (r *rpcServer) InsertProof(ctx context.Context, req *unirpc.AssetProof) (*unirpc.AssetProofResponse, error) { + if req == nil { + return nil, fmt.Errorf("missing proof and universe key") + } + universeID, leafKey, err := unmarshalUniverseKey(req.Key) if err != nil { return nil, err @@ -6196,44 +6198,40 @@ func unmarshalAssetSpecifier(req *rfqrpc.AssetSpecifier) (*asset.ID, // give precedence to the asset ID due to its higher level of // specificity. var ( - assetID *asset.ID - + assetIDBytes []byte + assetID *asset.ID groupKeyBytes []byte groupKey *btcec.PublicKey - - err error + err error ) switch { // Parse the asset ID if it's set. case len(req.GetAssetId()) > 0: - var assetIdBytes [32]byte - copy(assetIdBytes[:], req.GetAssetId()) - id := asset.ID(assetIdBytes) - assetID = &id + assetIDBytes = req.GetAssetId() + if len(assetIDBytes) != sha256.Size { + return nil, nil, fmt.Errorf("asset ID must be 32 bytes") + } case len(req.GetAssetIdStr()) > 0: - assetIDBytes, err := hex.DecodeString(req.GetAssetIdStr()) + reqAssetIDStr := req.GetAssetIdStr() + if len(reqAssetIDStr) != sha256.Size*2 { + return nil, nil, fmt.Errorf("asset ID string must be " + + "64 chars") + } + + assetIDBytes, err = hex.DecodeString(reqAssetIDStr) if err != nil { return nil, nil, fmt.Errorf("error decoding asset "+ "ID: %w", err) } - var id asset.ID - copy(id[:], assetIDBytes) - assetID = &id - // Parse the group key if it's set. case len(req.GetGroupKey()) > 0: groupKeyBytes = req.GetGroupKey() - groupKey, err = btcec.ParsePubKey(groupKeyBytes) - if err != nil { - return nil, nil, fmt.Errorf("error parsing group "+ - "key: %w", err) - } case len(req.GetGroupKeyStr()) > 0: - groupKeyBytes, err := hex.DecodeString( + groupKeyBytes, err = hex.DecodeString( req.GetGroupKeyStr(), ) if err != nil { @@ -6241,12 +6239,6 @@ func unmarshalAssetSpecifier(req *rfqrpc.AssetSpecifier) (*asset.ID, "key: %w", err) } - groupKey, err = btcec.ParsePubKey(groupKeyBytes) - if err != nil { - return nil, nil, fmt.Errorf("error parsing group "+ - "key: %w", err) - } - default: // At this point, we know that neither the asset ID nor the // group key are specified. Return an error. @@ -6254,6 +6246,23 @@ func unmarshalAssetSpecifier(req *rfqrpc.AssetSpecifier) (*asset.ID, "key must be specified") } + switch { + case len(assetIDBytes) != 0: + var id asset.ID + copy(id[:], assetIDBytes) + assetID = &id + + case len(groupKeyBytes) != 0: + groupKey, err = parseUserKey(groupKeyBytes) + if err != nil { + return nil, nil, fmt.Errorf("error parsing group "+ + "key: group key: %w", err) + } + + default: + return nil, nil, fmt.Errorf("malformed asset specifier") + } + return assetID, groupKey, nil } diff --git a/testdata/rapid/TestUnmarshalAssetLeaf/TestUnmarshalAssetLeaf-20240815223752-3701568.fail b/testdata/rapid/TestUnmarshalAssetLeaf/TestUnmarshalAssetLeaf-20240815223752-3701568.fail new file mode 100644 index 000000000..c4df2a851 --- /dev/null +++ b/testdata/rapid/TestUnmarshalAssetLeaf/TestUnmarshalAssetLeaf-20240815223752-3701568.fail @@ -0,0 +1,4 @@ +# 2024/08/15 22:37:52.574252 [TestUnmarshalAssetLeaf] [rapid] draw Leaf: (*universerpc.AssetLeaf)(nil) +# +v0.4.8#10744725395972853327 +0x0 \ No newline at end of file From 8b579dab660d8a48163f6a0b85a762af11af6cf2 Mon Sep 17 00:00:00 2001 From: Jonathan Harvey-Buschel Date: Fri, 16 Aug 2024 17:52:04 -0400 Subject: [PATCH 5/5] rpcserver: simpler gRPC generators with rapid fork --- go.mod | 2 + go.sum | 4 +- rpcserver_test.go | 220 ++++++++---------- ...rshalAssetLeaf-20240815223752-3701568.fail | 4 - ...rshalAssetLeaf-20240816174811-4026466.fail | 4 + 5 files changed, 101 insertions(+), 133 deletions(-) delete mode 100644 testdata/rapid/TestUnmarshalAssetLeaf/TestUnmarshalAssetLeaf-20240815223752-3701568.fail create mode 100644 testdata/rapid/TestUnmarshalAssetLeaf/TestUnmarshalAssetLeaf-20240816174811-4026466.fail diff --git a/go.mod b/go.mod index f25710b07..32db90375 100644 --- a/go.mod +++ b/go.mod @@ -208,3 +208,5 @@ require ( // We want to format raw bytes as hex instead of base64. The forked version // allows us to specify that as an option. replace google.golang.org/protobuf => github.com/lightninglabs/protobuf-go-hex-display v1.30.0-hex-display + +replace pgregory.net/rapid v1.1.0 => github.com/chrisseto/rapid v0.0.0-20240815210052-cdeef406c65c // indirect diff --git a/go.sum b/go.sum index 7fae92d46..2eeba6fe7 100644 --- a/go.sum +++ b/go.sum @@ -124,6 +124,8 @@ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chrisseto/rapid v0.0.0-20240815210052-cdeef406c65c h1:GZtcJAFTBCr16eM7ytFwWMg9oLaMsRfSsVyi3lTo+mw= +github.com/chrisseto/rapid v0.0.0-20240815210052-cdeef406c65c/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -1169,8 +1171,6 @@ modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g= nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= -pgregory.net/rapid v1.1.0 h1:CMa0sjHSru3puNx+J0MIAuiiEV4N0qj8/cMWGBBCsjw= -pgregory.net/rapid v1.1.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/rpcserver_test.go b/rpcserver_test.go index 2dcc64666..86b6193e7 100644 --- a/rpcserver_test.go +++ b/rpcserver_test.go @@ -5,6 +5,8 @@ import ( "crypto/sha256" "encoding/hex" "fmt" + "maps" + "reflect" "testing" "github.com/btcsuite/btcd/btcec/v2" @@ -12,85 +14,108 @@ import ( "github.com/lightninglabs/taproot-assets/taprpc" "github.com/lightninglabs/taproot-assets/taprpc/universerpc" "github.com/lightninglabs/taproot-assets/universe" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" "pgregory.net/rapid" ) +type rapidFieldGen = map[string]*rapid.Generator[any] +type rapidFieldMap = map[reflect.Type]rapidFieldGen +type rapidTypeMap = map[reflect.Type]*rapid.Generator[any] + // Custom generators. var ( - ByteSliceGen = rapid.SliceOf(rapid.Byte()) - GenesisInfoGen = rapid.Custom(func(t *rapid.T) taprpc.GenesisInfo { - return taprpc.GenesisInfo{ - GenesisPoint: rapid.String().Draw(t, "genesis_point"), - Name: rapid.String().Draw(t, "name"), - MetaHash: ByteSliceGen.Draw(t, "meta_hash"), - AssetId: ByteSliceGen.Draw(t, "id"), - AssetType: taprpc.AssetType( - rapid.Int32().Draw(t, "asset_type"), - ), - OutputIndex: rapid.Uint32().Draw(t, "output_index"), - } - }) - AssetGroupGen = rapid.Custom(func(t *rapid.T) taprpc.AssetGroup { - return taprpc.AssetGroup{ - RawGroupKey: ByteSliceGen.Draw(t, "raw_group_key"), - TweakedGroupKey: ByteSliceGen.Draw( - t, "tweaked_group_key", - ), - AssetWitness: ByteSliceGen.Draw(t, "asset_witness"), - TapscriptRoot: ByteSliceGen.Draw(t, "tapscript_root"), + ByteSliceGen = rapid.SliceOf(rapid.Byte()) + + // Ignore private gRPC fields of messages, which we don't read when + // unmarshalling and cause issues with rapid.Make(). + ignorePrivateRPCFields = rapidFieldGen{ + "state": rapid.Just(protoimpl.MessageState{}).AsAny(), + "sizeCache": rapid.Just(protoimpl.SizeCache(0)).AsAny(), + "unknownFields": rapid.Just(protoimpl.UnknownFields{}).AsAny(), + } + + // Create a generator config for a gRPC message, which may include + // custom generators or type generator overrides. + genMakeConfig = func(rpcType any, customGens rapidFieldGen, + genOverrides rapidTypeMap) rapid.MakeConfig { + + cfg := rapid.MakeConfig{ + Types: make(rapidTypeMap), + Fields: make(rapidFieldMap), } - }) - AnchorInfoGen = rapid.Custom(func(t *rapid.T) taprpc.AnchorInfo { - return taprpc.AnchorInfo{ - AnchorTx: ByteSliceGen.Draw(t, "anchor_tx"), - AnchorBlockHash: rapid.String().Draw( - t, "anchor_block_hash", - ), - AnchorOutpoint: rapid.String().Draw( - t, "anchor_outpoint", - ), - InternalKey: ByteSliceGen.Draw(t, "internal_key"), - MerkleRoot: ByteSliceGen.Draw(t, "merkle_root"), - TapscriptSibling: ByteSliceGen.Draw( - t, "tapscript_sibling", - ), - BlockHeight: rapid.Uint32().Draw(t, "block_height"), + + // Add custom generators for fields, by field name, to override + // default rapid.Make() behavior. + ignoredFields := maps.Clone(ignorePrivateRPCFields) + for k, v := range customGens { + ignoredFields[k] = v } - }) - PrevInputAssetGen = rapid.Custom( - func(t *rapid.T) taprpc.PrevInputAsset { - - return taprpc.PrevInputAsset{ - AnchorPoint: rapid.String().Draw( - t, "anchor_point", - ), - AssetId: ByteSliceGen.Draw(t, "asset_id"), - ScriptKey: ByteSliceGen.Draw(t, "script_key"), - Amount: rapid.Uint64().Draw(t, "amount"), - } - }) - PrevWitnessGen = rapid.Custom(func(t *rapid.T) taprpc.PrevWitness { - // Leave the split commitment as nil. - return taprpc.PrevWitness{ - PrevId: rapid.Ptr(PrevInputAssetGen, true).Draw( - t, "prev_id", - ), - TxWitness: rapid.SliceOf(ByteSliceGen).Draw( - t, "tx_witnesses", - ), + + cfg.Fields[reflect.TypeOf(rpcType)] = ignoredFields + + // Add custom generators that will override the generators + // rapid.Make() would create for struct member types. + for k, v := range genOverrides { + cfg.Types[k] = v } - }) + + return cfg + } + + GenesisInfoGen = rapid.Ptr(rapid.MakeCustom[taprpc.GenesisInfo]( + genMakeConfig(taprpc.GenesisInfo{}, nil, nil), + ), true) + AssetGroupGen = rapid.Ptr(rapid.MakeCustom[taprpc.AssetGroup]( + genMakeConfig(taprpc.AssetGroup{}, nil, nil), + ), true) + AnchorInfoGen = rapid.Ptr(rapid.MakeCustom[taprpc.AnchorInfo]( + genMakeConfig(taprpc.AnchorInfo{}, nil, nil), + ), true) + PrevInputAssetGen = rapid.MakeCustom[taprpc.PrevInputAsset]( + genMakeConfig(taprpc.PrevInputAsset{}, nil, nil), + ) + + // Leave the split commitment for prev witnesses as nil. + emptySplitCommitmentGen = rapid.Just(taprpc.SplitCommitment{}) + splitCommitPtrGen = rapid.Ptr(emptySplitCommitmentGen, true) + nilSplitCommitment = rapidTypeMap{ + //nolint:lll + reflect.TypeOf(&taprpc.SplitCommitment{}): splitCommitPtrGen.AsAny(), + } + PrevWitnessGen = rapid.MakeCustom[taprpc.PrevWitness]( + genMakeConfig(taprpc.PrevWitness{}, nil, nilSplitCommitment), + ) PrevWitnessesGen = rapid.Custom(func(t *rapid.T) []*taprpc.PrevWitness { witnessGen := rapid.Ptr(PrevWitnessGen, false) return rapid.SliceOf(witnessGen).Draw(t, "prev_witnesses") }) - DecDisplayGen = rapid.Custom(func(t *rapid.T) taprpc.DecimalDisplay { - return taprpc.DecimalDisplay{ - DecimalDisplay: rapid.Uint32().Draw( - t, "decimal_display", - ), - } - }) + DecDisplayGen = rapid.Ptr(rapid.MakeCustom[taprpc.DecimalDisplay]( + genMakeConfig(taprpc.DecimalDisplay{}, nil, nil), + ), true) + + // Set generator overrides for members of taprpc.Asset that are gRPC + // messages. + assetMemberGens = rapidTypeMap{ + reflect.TypeOf(&taprpc.GenesisInfo{}): GenesisInfoGen.AsAny(), + reflect.TypeOf(&taprpc.AssetGroup{}): AssetGroupGen.AsAny(), + reflect.TypeOf(&taprpc.AnchorInfo{}): AnchorInfoGen.AsAny(), + //nolint:lll + reflect.TypeOf([]*taprpc.PrevWitness{}): PrevWitnessesGen.AsAny(), + reflect.TypeOf(&taprpc.DecimalDisplay{}): DecDisplayGen.AsAny(), + } + AssetGen = rapid.MakeCustom[taprpc.Asset]( + genMakeConfig(taprpc.Asset{}, nil, assetMemberGens), + ) + AssetPtrGen = rapid.Ptr(AssetGen, true) + + // Use the custom taprpc.Asset generator for *universerpc.AssetLeaf. + leafMemberGens = rapidTypeMap{ + reflect.TypeOf(&taprpc.Asset{}): AssetPtrGen.AsAny(), + } + AssetLeafGen = rapid.MakeCustom[universerpc.AssetLeaf]( + genMakeConfig(universerpc.AssetLeaf{}, nil, leafMemberGens), + ) + AssetLeafPtrGen = rapid.Ptr(AssetLeafGen, true) ) // Result is used to store the output of a fallible function call. @@ -530,68 +555,9 @@ func TestUnmarshalUniId(t *testing.T) { } func testUnmarshalAssetLeaf(t *rapid.T) { - // rapid.Make failed on the private gRPC-specific fields of - // taprpc.Asset, so we'll populate only the public fields. - LeafAssetGen := rapid.Custom(func(t *rapid.T) taprpc.Asset { - vers := taprpc.AssetVersion(rapid.Int32().Draw(t, "version")) - genesis := rapid.Ptr(GenesisInfoGen, true).Draw(t, "genesis") - amount := rapid.Uint64().Draw(t, "amount") - lockTime := rapid.Int32().Draw(t, "lock_time") - relativeLockTime := rapid.Int32().Draw(t, "relative_lock_time") - scriptVersion := rapid.Int32().Draw(t, "script_version") - scriptKey := ByteSliceGen.Draw(t, "script_key") - scriptKeyIsLocal := rapid.Bool().Draw(t, "script_key_is_local") - group := rapid.Ptr(AssetGroupGen, true).Draw(t, "asset_group") - chainAnchor := rapid.Ptr(AnchorInfoGen, true).Draw( - t, "chain_anchor", - ) - prevWitnesses := PrevWitnessesGen.Draw(t, "prev_witnesses") - isSpent := rapid.Bool().Draw(t, "is_spent") - leaseOwner := ByteSliceGen.Draw(t, "lease_owner") - leaseExpiry := rapid.Int64().Draw(t, "lease_expiry") - isBurn := rapid.Bool().Draw(t, "is_burn") - scriptKeyDeclaredKnown := rapid.Bool().Draw( - t, "script_key_declared_known", - ) - scriptKeyHasScriptPath := rapid.Bool().Draw( - t, "script_key_has_script_path", - ) - decimalDisplay := rapid.Ptr(DecDisplayGen, true).Draw( - t, "decimal_display", - ) - - return taprpc.Asset{ - Version: vers, - AssetGenesis: genesis, - Amount: amount, - LockTime: lockTime, - RelativeLockTime: relativeLockTime, - ScriptVersion: scriptVersion, - ScriptKey: scriptKey, - ScriptKeyIsLocal: scriptKeyIsLocal, - AssetGroup: group, - ChainAnchor: chainAnchor, - PrevWitnesses: prevWitnesses, - IsSpent: isSpent, - LeaseOwner: leaseOwner, - LeaseExpiry: leaseExpiry, - IsBurn: isBurn, - ScriptKeyDeclaredKnown: scriptKeyDeclaredKnown, - ScriptKeyHasScriptPath: scriptKeyHasScriptPath, - DecimalDisplay: decimalDisplay, - } - }) - - leafGen := rapid.Custom(func(t *rapid.T) universerpc.AssetLeaf { - return universerpc.AssetLeaf{ - Asset: rapid.Ptr(LeafAssetGen, true).Draw(t, "Asset"), - Proof: ByteSliceGen.Draw(t, "Proof"), - } - }) - leaf := rapid.Ptr(leafGen, true).Draw(t, "Leaf") - // Don't check the unmarshal output, we are only testing if we can // cause unmarshal to panic. + leaf := AssetLeafPtrGen.Draw(t, "Leaf") _, _ = unmarshalAssetLeaf(leaf) } diff --git a/testdata/rapid/TestUnmarshalAssetLeaf/TestUnmarshalAssetLeaf-20240815223752-3701568.fail b/testdata/rapid/TestUnmarshalAssetLeaf/TestUnmarshalAssetLeaf-20240815223752-3701568.fail deleted file mode 100644 index c4df2a851..000000000 --- a/testdata/rapid/TestUnmarshalAssetLeaf/TestUnmarshalAssetLeaf-20240815223752-3701568.fail +++ /dev/null @@ -1,4 +0,0 @@ -# 2024/08/15 22:37:52.574252 [TestUnmarshalAssetLeaf] [rapid] draw Leaf: (*universerpc.AssetLeaf)(nil) -# -v0.4.8#10744725395972853327 -0x0 \ No newline at end of file diff --git a/testdata/rapid/TestUnmarshalAssetLeaf/TestUnmarshalAssetLeaf-20240816174811-4026466.fail b/testdata/rapid/TestUnmarshalAssetLeaf/TestUnmarshalAssetLeaf-20240816174811-4026466.fail new file mode 100644 index 000000000..2e4f71816 --- /dev/null +++ b/testdata/rapid/TestUnmarshalAssetLeaf/TestUnmarshalAssetLeaf-20240816174811-4026466.fail @@ -0,0 +1,4 @@ +# 2024/08/16 17:48:11.689274 [TestUnmarshalAssetLeaf] [rapid] draw Leaf: (*universerpc.AssetLeaf)(nil) +# +v0.4.8#7630617197267023936 +0x0 \ No newline at end of file