From 8affc297e61541bd20e923e3778faf66ddc878b4 Mon Sep 17 00:00:00 2001 From: Aurora Gaffney Date: Thu, 23 Jan 2025 16:18:57 -0500 Subject: [PATCH] fix: prevent Conway TX inputs from being decoded as Shelley TX input Conway-era TX inputs using CBOR tag 258 (sets) could be implicitly decoded by earlier eras, but this doesn't match the behavior of cardano-node. We explicitly prevent this from happening to allow proper identification of TX types --- cbor/decode.go | 1 - ledger/conway/conway.go | 27 ++++++++++++++++++++++ ledger/shelley/shelley.go | 29 ++++++++++++++++++++---- ledger/tx_test.go | 47 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 99 insertions(+), 5 deletions(-) create mode 100644 ledger/tx_test.go diff --git a/cbor/decode.go b/cbor/decode.go index f967ceb6..d015fc1f 100644 --- a/cbor/decode.go +++ b/cbor/decode.go @@ -129,7 +129,6 @@ func DecodeGeneric(cborData []byte, dest interface{}) error { // destination object if valueDest.Kind() != reflect.Pointer || valueDest.Elem().Kind() != reflect.Struct { - decodeGenericTypeCacheMutex.Unlock() return fmt.Errorf("destination must be a pointer to a struct") } destTypeFields := []reflect.StructField{} diff --git a/ledger/conway/conway.go b/ledger/conway/conway.go index e65e076d..4653de17 100644 --- a/ledger/conway/conway.go +++ b/ledger/conway/conway.go @@ -199,8 +199,27 @@ func (t *ConwayTransactionWitnessSet) UnmarshalCBOR(cborData []byte) error { return t.UnmarshalCbor(cborData, t) } +type ConwayTransactionInputSet struct { + items []shelley.ShelleyTransactionInput +} + +func (s *ConwayTransactionInputSet) UnmarshalCBOR(data []byte) error { + // This overrides the Shelley behavior that explicitly disallowed tag-wrapped sets + var tmpData []shelley.ShelleyTransactionInput + if _, err := cbor.Decode(data, &tmpData); err != nil { + return err + } + s.items = tmpData + return nil +} + +func (s *ConwayTransactionInputSet) Items() []shelley.ShelleyTransactionInput { + return s.items +} + type ConwayTransactionBody struct { babbage.BabbageTransactionBody + TxInputs ConwayTransactionInputSet `cbor:"0,keyasint,omitempty"` TxVotingProcedures common.VotingProcedures `cbor:"19,keyasint,omitempty"` TxProposalProcedures []common.ProposalProcedure `cbor:"20,keyasint,omitempty"` TxCurrentTreasuryValue int64 `cbor:"21,keyasint,omitempty"` @@ -211,6 +230,14 @@ func (b *ConwayTransactionBody) UnmarshalCBOR(cborData []byte) error { return b.UnmarshalCbor(cborData, b) } +func (b *ConwayTransactionBody) Inputs() []common.TransactionInput { + ret := []common.TransactionInput{} + for _, input := range b.TxInputs.Items() { + ret = append(ret, input) + } + return ret +} + func (b *ConwayTransactionBody) ProtocolParameterUpdates() (uint64, map[common.Blake2b224]common.ProtocolParameterUpdate) { updateMap := make(map[common.Blake2b224]common.ProtocolParameterUpdate) for k, v := range b.Update.ProtocolParamUpdates { diff --git a/ledger/shelley/shelley.go b/ledger/shelley/shelley.go index 798ca198..dd8d3dca 100644 --- a/ledger/shelley/shelley.go +++ b/ledger/shelley/shelley.go @@ -193,7 +193,7 @@ func (h *ShelleyBlockHeader) Era() common.Era { type ShelleyTransactionBody struct { cbor.DecodeStoreCbor hash string - TxInputs []ShelleyTransactionInput `cbor:"0,keyasint,omitempty"` + TxInputs ShelleyTransactionInputSet `cbor:"0,keyasint,omitempty"` TxOutputs []ShelleyTransactionOutput `cbor:"1,keyasint,omitempty"` TxFee uint64 `cbor:"2,keyasint,omitempty"` Ttl uint64 `cbor:"3,keyasint,omitempty"` @@ -221,7 +221,7 @@ func (b *ShelleyTransactionBody) Hash() string { func (b *ShelleyTransactionBody) Inputs() []common.TransactionInput { ret := []common.TransactionInput{} - for _, input := range b.TxInputs { + for _, input := range b.TxInputs.Items() { ret = append(ret, input) } return ret @@ -357,6 +357,29 @@ func (b *ShelleyTransactionBody) Utxorpc() *utxorpc.Tx { return tx } +type ShelleyTransactionInputSet struct { + items []ShelleyTransactionInput +} + +func (s *ShelleyTransactionInputSet) UnmarshalCBOR(data []byte) error { + // Make sure this isn't a tag-wrapped set + // This is needed to prevent Conway+ TXs from being decoded as an earlier type + var tmpTag cbor.RawTag + if _, err := cbor.Decode(data, &tmpTag); err == nil { + return fmt.Errorf("did not expect CBOR tag") + } + var tmpData []ShelleyTransactionInput + if _, err := cbor.Decode(data, &tmpData); err != nil { + return err + } + s.items = tmpData + return nil +} + +func (s *ShelleyTransactionInputSet) Items() []ShelleyTransactionInput { + return s.items +} + type ShelleyTransactionInput struct { cbor.StructAsArray TxId common.Blake2b256 @@ -386,8 +409,6 @@ func (i ShelleyTransactionInput) Utxorpc() *utxorpc.TxInput { return &utxorpc.TxInput{ TxHash: i.TxId.Bytes(), OutputIndex: i.OutputIndex, - // AsOutput: i.AsOutput, - // Redeemer: i.Redeemer, } } diff --git a/ledger/tx_test.go b/ledger/tx_test.go new file mode 100644 index 00000000..be6d3e8f --- /dev/null +++ b/ledger/tx_test.go @@ -0,0 +1,47 @@ +// Copyright 2023 Blink Labs Software +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ledger_test + +import ( + "encoding/hex" + "testing" + + "github.com/blinklabs-io/gouroboros/ledger" +) + +func TestDetermineTransactionType(t *testing.T) { + testDefs := []struct { + txCborHex string + expectedTxType uint + }{ + { + txCborHex: "84a500d9010281825820279184037d249e397d97293738370756da559718fcdefae9924834840046b37b01018282583900923d4b64e1d730a4baf3e6dc433a9686983940f458363f37aad7a1a9568b72f85522e4a17d44a45cd021b9741b55d7cbc635c911625b015e1a00a9867082583900923d4b64e1d730a4baf3e6dc433a9686983940f458363f37aad7a1a9568b72f85522e4a17d44a45cd021b9741b55d7cbc635c911625b015e1b00000001267d7b04021a0002938d031a04e304e70800a100d9010281825820b829480e5d5827d2e1bd7c89176a5ca125c30812e54be7dbdf5c47c835a17f3d5840b13a76e7f2b19cde216fcad55ceeeb489ebab3dcf63ef1539ac4f535dece00411ee55c9b8188ef04b4aa3c72586e4a0ec9b89949367d7270fdddad3b18731403f5f6", + expectedTxType: 6, + }, + } + for _, testDef := range testDefs { + txCbor, err := hex.DecodeString(testDef.txCborHex) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + tmpTxType, err := ledger.DetermineTransactionType(txCbor) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + if tmpTxType != testDef.expectedTxType { + t.Fatalf("did not get expected TX type: got %d, wanted %d", tmpTxType, testDef.expectedTxType) + } + } +}