diff --git a/pkg/tbtc/marshaling_test.go b/pkg/tbtc/marshaling_test.go index 32b6977f0a..5fa3692918 100644 --- a/pkg/tbtc/marshaling_test.go +++ b/pkg/tbtc/marshaling_test.go @@ -231,6 +231,22 @@ func TestCoordinationMessage_MarshalingRoundtrip(t *testing.T) { SweepTxFee: big.NewInt(8000), }, }, + "with reservation anchor proposal": { + proposal: &ReservationAnchorProposal{ + DepositFundingTxHash: parseHash("709b55bd3da0f5a838125bd0ee20c5bfdd7caba173912d4281cae816b79a201b"), + DepositFundingOutputIndex: 2, + RequestNonce: 7, + AnchorTxFee: big.NewInt(1500), + }, + }, + "with reservation reanchor proposal": { + proposal: &ReservationReanchorProposal{ + ReservationKey: big.NewInt(424242), + RequestNonce: 4, + TargetWalletPublicKeyHash: toByte20("f87eb7ec3b15a3fdd7b57754d765694b3e0b4bf4"), + ReanchorTxFee: big.NewInt(1200), + }, + }, } walletPublicKeyHash := toByte20("aa768412ceed10bd423c025542ca90071f9fb62d") @@ -402,6 +418,64 @@ func TestFuzzCoordinationMessage_MarshalingRoundtrip_WithMovedFundsSweepProposal } } +func TestFuzzCoordinationMessage_MarshalingRoundtrip_WithReservationAnchorProposal(t *testing.T) { + for i := 0; i < 10; i++ { + var ( + senderID group.MemberIndex + coordinationBlock uint64 + walletPublicKeyHash [20]byte + proposal ReservationAnchorProposal + ) + + f := fuzz.New().NilChance(0.1). + NumElements(0, 512). + Funcs(pbutils.FuzzFuncs()...) + + f.Fuzz(&senderID) + f.Fuzz(&coordinationBlock) + f.Fuzz(&walletPublicKeyHash) + f.Fuzz(&proposal) + + coordinationMsg := &coordinationMessage{ + senderID: senderID, + coordinationBlock: coordinationBlock, + walletPublicKeyHash: walletPublicKeyHash, + proposal: &proposal, + } + + _ = pbutils.RoundTrip(coordinationMsg, &coordinationMessage{}) + } +} + +func TestFuzzCoordinationMessage_MarshalingRoundtrip_WithReservationReanchorProposal(t *testing.T) { + for i := 0; i < 10; i++ { + var ( + senderID group.MemberIndex + coordinationBlock uint64 + walletPublicKeyHash [20]byte + proposal ReservationReanchorProposal + ) + + f := fuzz.New().NilChance(0.1). + NumElements(0, 512). + Funcs(pbutils.FuzzFuncs()...) + + f.Fuzz(&senderID) + f.Fuzz(&coordinationBlock) + f.Fuzz(&walletPublicKeyHash) + f.Fuzz(&proposal) + + coordinationMsg := &coordinationMessage{ + senderID: senderID, + coordinationBlock: coordinationBlock, + walletPublicKeyHash: walletPublicKeyHash, + proposal: &proposal, + } + + _ = pbutils.RoundTrip(coordinationMsg, &coordinationMessage{}) + } +} + func TestFuzzCoordinationMessage_MarshalingRoundtrip_WithNoopProposal(t *testing.T) { for i := 0; i < 10; i++ { var ( diff --git a/pkg/tbtc/reservation_test.go b/pkg/tbtc/reservation_test.go index 899d9a144c..d0649d9329 100644 --- a/pkg/tbtc/reservation_test.go +++ b/pkg/tbtc/reservation_test.go @@ -137,7 +137,7 @@ func TestReservationProposals_MarshalingRoundtrip(t *testing.T) { roundtrip(reanchorProposal, &ReservationReanchorProposal{}) } -func TestReservationProposals_UnmarshalRejectsMissingIntegers(t *testing.T) { +func TestReservationProposals_UnmarshalRejectsInvalidFields(t *testing.T) { tests := map[string]struct { actionType WalletActionType payload []byte @@ -201,6 +201,36 @@ func TestReservationProposals_UnmarshalRejectsMissingIntegers(t *testing.T) { }), expectedError: "cannot unmarshal proposal payload: [invalid re-anchor transaction fee byte length: [9]]", }, + "anchor zero fee marshaled through Marshal is rejected as missing": { + actionType: ActionReservationAnchor, + payload: marshalThroughProposal(t, &ReservationAnchorProposal{ + DepositFundingTxHash: bitcoin.Hash{0x01, 0x02}, + DepositFundingOutputIndex: 3, + RequestNonce: 1, + AnchorTxFee: big.NewInt(0), + }), + expectedError: "cannot unmarshal proposal payload: [anchor transaction fee is required]", + }, + "re-anchor zero reservation key marshaled through Marshal is rejected as missing": { + actionType: ActionReservationReanchor, + payload: marshalThroughProposal(t, &ReservationReanchorProposal{ + ReservationKey: big.NewInt(0), + RequestNonce: 3, + TargetWalletPublicKeyHash: [20]byte{0xaa, 0xbb}, + ReanchorTxFee: big.NewInt(1700), + }), + expectedError: "cannot unmarshal proposal payload: [reservation key is required]", + }, + "re-anchor zero fee marshaled through Marshal is rejected as missing": { + actionType: ActionReservationReanchor, + payload: marshalThroughProposal(t, &ReservationReanchorProposal{ + ReservationKey: big.NewInt(54321), + RequestNonce: 3, + TargetWalletPublicKeyHash: [20]byte{0xaa, 0xbb}, + ReanchorTxFee: big.NewInt(0), + }), + expectedError: "cannot unmarshal proposal payload: [re-anchor transaction fee is required]", + }, } for testName, test := range tests { @@ -230,6 +260,19 @@ func marshalPb(t *testing.T, msg proto.Message) []byte { return data } +// marshalThroughProposal marshals a CoordinationProposal via its own Marshal +// method, for use as a test fixture payload. Unlike marshalPb, this exercises +// the proposal's real wire-encoding path (e.g. *big.Int.Bytes()) rather than +// hand-constructing the protobuf message directly. +func marshalThroughProposal(t *testing.T, proposal CoordinationProposal) []byte { + t.Helper() + data, err := proposal.Marshal() + if err != nil { + t.Fatal(err) + } + return data +} + func signReservationTransaction( t *testing.T, builder *bitcoin.TransactionBuilder,