Skip to content

Commit

Permalink
fix: prevent Conway TX inputs from being decoded as Shelley TX input
Browse files Browse the repository at this point in the history
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
  • Loading branch information
agaffney committed Jan 23, 2025
1 parent 0b3d6ee commit 8affc29
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 5 deletions.
1 change: 0 additions & 1 deletion cbor/decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{}
Expand Down
27 changes: 27 additions & 0 deletions ledger/conway/conway.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand All @@ -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 {
Expand Down
29 changes: 25 additions & 4 deletions ledger/shelley/shelley.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
}
}

Expand Down
47 changes: 47 additions & 0 deletions ledger/tx_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
}

0 comments on commit 8affc29

Please sign in to comment.