Skip to content

Commit

Permalink
rpcserver: initial rapid test for uni ID unmarshal
Browse files Browse the repository at this point in the history
  • Loading branch information
jharveyb committed Aug 13, 2024
1 parent a8d8b5a commit 549d5f9
Show file tree
Hide file tree
Showing 3 changed files with 309 additions and 0 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
)

Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
306 changes: 306 additions & 0 deletions rpcserver_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,306 @@
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/asset"
"github.com/lightninglabs/taproot-assets/fn"
"github.com/lightninglabs/taproot-assets/taprpc"
"github.com/lightninglabs/taproot-assets/taprpc/universerpc"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
"pgregory.net/rapid"
)

func formatProtoJSON(resp proto.Message) (string, error) {
jsonBytes, err := taprpc.ProtoJSONMarshalOpts.Marshal(resp)
if err != nil {
return "", err
}

return string(jsonBytes), nil
}

func toJSON(t *rapid.T, resp proto.Message) string {
jsonStr, err := formatProtoJSON(resp)
require.NoError(t, err)

return jsonStr
}

func testUnmarshalUniId(t *rapid.T) {
KnownProofTypes := fn.NewSet(
universerpc.ProofType_PROOF_TYPE_UNSPECIFIED,
universerpc.ProofType_PROOF_TYPE_ISSUANCE,
universerpc.ProofType_PROOF_TYPE_TRANSFER,
)

KnownProofTypesInt := fn.NewSet(
int32(universerpc.ProofType_PROOF_TYPE_UNSPECIFIED),
int32(universerpc.ProofType_PROOF_TYPE_ISSUANCE),
int32(universerpc.ProofType_PROOF_TYPE_TRANSFER),
)

genBytes := rapid.SliceOf(rapid.Byte())

IDBytes := genBytes.Draw(t, "ID")
IDStr := rapid.String().Draw(t, "ID string")
IDFieldSelector := rapid.ByteMax(0x5).Draw(t, "ID field selector")
proofType := rapid.Int32().Draw(t, "proofType")
rpcProofType := universerpc.ProofType(proofType)

lenIDBytes := len(IDBytes)
lenIDStr := len(IDStr)

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,
}

case 1:
uniId.Id = &universerpc.ID_AssetIdStr{
AssetIdStr: IDStr,
}

case 2:
uniId.Id = &universerpc.ID_GroupKey{
GroupKey: IDBytes,
}

case 3:
uniId.Id = &universerpc.ID_GroupKeyStr{
GroupKeyStr: IDStr,
}

// Empty ID field.
case 4:

// Empty universe ID.
case 5:
uniId = nil
}

nativeUniID, err := UnmarshalUniID(uniId)

// Unmarhsalling failure is acceptable if part of the input was nil,
// or the input was the wrong size.
if err != nil {
if !KnownProofTypes.Contains(rpcProofType) {
return
}

switch int(IDFieldSelector) {
case 0:
if lenIDBytes == sha256.Size {
t.Fatalf("unmarshal asset ID bytes failed: %v", err)
}

case 1:

if lenIDStr == 0 {
return
}

// Invalid hex should cause an error.
_, hexErr := hex.DecodeString(IDStr)
if hexErr != nil {
return
}

if lenIDStr == sha256.Size*2 {
t.Fatalf("unmarshal asset ID string failed: %v", err)
}

case 2:
if lenIDBytes == sha256.Size ||
lenIDBytes == btcec.PubKeyBytesLenCompressed {

_, keyErr := parseUserKey(IDBytes)
if keyErr != nil {
return
}

t.Fatalf("unmarshal group key bytes failed: %v", err)
}

case 3:
if lenIDStr == 0 {
return
}

// Invalid hex should cause an error.
keyBytes, hexErr := hex.DecodeString(IDStr)
if hexErr != nil {
return
}

if lenIDStr == sha256.Size*2 ||
lenIDStr == btcec.PubKeyBytesLenCompressed*2 {

_, keyErr := parseUserKey(keyBytes)
if keyErr != nil {
return
}

t.Fatalf("unmarshal group key string failed: %v", err)
}

// Unmarshalling an empty universe ID, or an ID with an empty
// ID field, should fail.
case 4, 5:
}

return
}

// Unmarshalling an unknown proof type should fail.
if !KnownProofTypes.Contains(rpcProofType) {
t.Fatalf("unknown proof type not rejected: %v", rpcProofType)
}

// Asset IDs must be 32 bytes (or 64 hex chars). Group keys
// must be 32 or 33 bytes (64 or 66 chars).
switch int(IDFieldSelector) {
case 0:
if lenIDBytes != sha256.Size {
t.Fatalf("invalid asset ID not "+
"rejected: generated %v",
IDBytes)
}

case 1:
if lenIDStr != sha256.Size*2 {
t.Fatalf("invalid asset ID string "+
"not rejected: generated %v",
IDStr)
}

case 2:
if lenIDBytes != sha256.Size &&
lenIDBytes != btcec.PubKeyBytesLenCompressed {

t.Fatalf("invalid group key not "+
"rejected: generated %v",
IDBytes)
}

case 3:
if lenIDStr != sha256.Size*2 &&
lenIDStr != btcec.PubKeyBytesLenCompressed*2 {

t.Fatalf("invalid group key string "+
"not rejected: generated %v",
IDStr)
}

case 4:
t.Fatalf("unmarshal empty ID not rejected: %v", err)

case 5:
t.Fatalf("unmarshal ID with empty ID not rejected: %v",
err)
}

// Check equality of the proof type.
proofTypeInt := int32(nativeUniID.ProofType)
if KnownProofTypesInt.Contains(proofTypeInt) && proofTypeInt != proofType {
t.Fatalf("proof type mismatch: generated %v, unmarshalled %v",
proofType, int32(nativeUniID.ProofType))
}

// Check equality of the ID field.
switch IDFieldSelector {
case 0:
if len(nativeUniID.AssetID) == 0 {
t.Fatalf("asset ID bytes not unmarshalled: generated %v",
IDBytes)
}

if !bytes.Equal(nativeUniID.AssetID[:], IDBytes) {
t.Fatalf("asset ID mismatch: generated %x, "+
"unmarshalled %x", IDBytes,
nativeUniID.AssetID[:])
}

case 1:
if len(nativeUniID.AssetID) == 0 {
t.Fatalf("asset ID string not unmarshalled: generated %v",
IDBytes)
}

if nativeUniID.AssetID.String() != IDStr {
t.Fatalf("asset ID string mismatch: generated %s, "+
"unmarshalled %s, %v, %v", IDStr,
nativeUniID.AssetID.String(),
nativeUniID, err)
}

case 2:
if nativeUniID.GroupKey == nil {
t.Fatalf("group key not unmarshalled: generated %v",
IDBytes)
}

// We ignore the leading sign byte for keys passsed as
// compressed.
groupKeyBytes := schnorr.SerializePubKey(nativeUniID.GroupKey)
switch len(IDBytes) {
case 32:
if !bytes.Equal(groupKeyBytes, IDBytes) {
t.Fatalf("group key mismatch: generated %x, "+
"unmarshalled %x", IDBytes,
groupKeyBytes[1:])
}

case 33:
if !bytes.Equal(groupKeyBytes, IDBytes[1:]) {
t.Fatalf("group key mismatch: generated %x, "+
"unmarshalled %x", IDBytes,
groupKeyBytes)
}
}

case 3:
if nativeUniID.GroupKey == nil {
t.Fatalf("group key not unmarshalled: generated %v",
IDBytes)
}

groupKeyBytes := nativeUniID.GroupKey.SerializeCompressed()
groupKeyStr := hex.EncodeToString(groupKeyBytes)

switch len(IDStr) {
case 64:
if groupKeyStr[2:] != IDStr {
t.Fatalf("group key string mismatch: "+
"generated %s, "+
"unmarshalled %s", IDStr,
groupKeyStr[2:])
}

case 66:
if groupKeyStr != IDStr {
t.Fatalf("group key string mismatch: "+
"generated %s, "+
"unmarshalled %s", IDStr,
groupKeyStr)
}
}
}
}

func TestUnmarshalUniId(t *testing.T) {
rapid.Check(t, testUnmarshalUniId)
}

0 comments on commit 549d5f9

Please sign in to comment.