Skip to content

Commit bbca805

Browse files
authored
fix: prevent Conway TX inputs from being decoded as Shelley TX input (#840)
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
1 parent 32bb7d7 commit bbca805

File tree

4 files changed

+99
-5
lines changed

4 files changed

+99
-5
lines changed

cbor/decode.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,6 @@ func DecodeGeneric(cborData []byte, dest interface{}) error {
129129
// destination object
130130
if valueDest.Kind() != reflect.Pointer ||
131131
valueDest.Elem().Kind() != reflect.Struct {
132-
decodeGenericTypeCacheMutex.Unlock()
133132
return fmt.Errorf("destination must be a pointer to a struct")
134133
}
135134
destTypeFields := []reflect.StructField{}

ledger/conway/conway.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,8 +199,27 @@ func (t *ConwayTransactionWitnessSet) UnmarshalCBOR(cborData []byte) error {
199199
return t.UnmarshalCbor(cborData, t)
200200
}
201201

202+
type ConwayTransactionInputSet struct {
203+
items []shelley.ShelleyTransactionInput
204+
}
205+
206+
func (s *ConwayTransactionInputSet) UnmarshalCBOR(data []byte) error {
207+
// This overrides the Shelley behavior that explicitly disallowed tag-wrapped sets
208+
var tmpData []shelley.ShelleyTransactionInput
209+
if _, err := cbor.Decode(data, &tmpData); err != nil {
210+
return err
211+
}
212+
s.items = tmpData
213+
return nil
214+
}
215+
216+
func (s *ConwayTransactionInputSet) Items() []shelley.ShelleyTransactionInput {
217+
return s.items
218+
}
219+
202220
type ConwayTransactionBody struct {
203221
babbage.BabbageTransactionBody
222+
TxInputs ConwayTransactionInputSet `cbor:"0,keyasint,omitempty"`
204223
TxVotingProcedures common.VotingProcedures `cbor:"19,keyasint,omitempty"`
205224
TxProposalProcedures []common.ProposalProcedure `cbor:"20,keyasint,omitempty"`
206225
TxCurrentTreasuryValue int64 `cbor:"21,keyasint,omitempty"`
@@ -211,6 +230,14 @@ func (b *ConwayTransactionBody) UnmarshalCBOR(cborData []byte) error {
211230
return b.UnmarshalCbor(cborData, b)
212231
}
213232

233+
func (b *ConwayTransactionBody) Inputs() []common.TransactionInput {
234+
ret := []common.TransactionInput{}
235+
for _, input := range b.TxInputs.Items() {
236+
ret = append(ret, input)
237+
}
238+
return ret
239+
}
240+
214241
func (b *ConwayTransactionBody) ProtocolParameterUpdates() (uint64, map[common.Blake2b224]common.ProtocolParameterUpdate) {
215242
updateMap := make(map[common.Blake2b224]common.ProtocolParameterUpdate)
216243
for k, v := range b.Update.ProtocolParamUpdates {

ledger/shelley/shelley.go

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ func (h *ShelleyBlockHeader) Era() common.Era {
193193
type ShelleyTransactionBody struct {
194194
cbor.DecodeStoreCbor
195195
hash string
196-
TxInputs []ShelleyTransactionInput `cbor:"0,keyasint,omitempty"`
196+
TxInputs ShelleyTransactionInputSet `cbor:"0,keyasint,omitempty"`
197197
TxOutputs []ShelleyTransactionOutput `cbor:"1,keyasint,omitempty"`
198198
TxFee uint64 `cbor:"2,keyasint,omitempty"`
199199
Ttl uint64 `cbor:"3,keyasint,omitempty"`
@@ -221,7 +221,7 @@ func (b *ShelleyTransactionBody) Hash() string {
221221

222222
func (b *ShelleyTransactionBody) Inputs() []common.TransactionInput {
223223
ret := []common.TransactionInput{}
224-
for _, input := range b.TxInputs {
224+
for _, input := range b.TxInputs.Items() {
225225
ret = append(ret, input)
226226
}
227227
return ret
@@ -357,6 +357,29 @@ func (b *ShelleyTransactionBody) Utxorpc() *utxorpc.Tx {
357357
return tx
358358
}
359359

360+
type ShelleyTransactionInputSet struct {
361+
items []ShelleyTransactionInput
362+
}
363+
364+
func (s *ShelleyTransactionInputSet) UnmarshalCBOR(data []byte) error {
365+
// Make sure this isn't a tag-wrapped set
366+
// This is needed to prevent Conway+ TXs from being decoded as an earlier type
367+
var tmpTag cbor.RawTag
368+
if _, err := cbor.Decode(data, &tmpTag); err == nil {
369+
return fmt.Errorf("did not expect CBOR tag")
370+
}
371+
var tmpData []ShelleyTransactionInput
372+
if _, err := cbor.Decode(data, &tmpData); err != nil {
373+
return err
374+
}
375+
s.items = tmpData
376+
return nil
377+
}
378+
379+
func (s *ShelleyTransactionInputSet) Items() []ShelleyTransactionInput {
380+
return s.items
381+
}
382+
360383
type ShelleyTransactionInput struct {
361384
cbor.StructAsArray
362385
TxId common.Blake2b256
@@ -386,8 +409,6 @@ func (i ShelleyTransactionInput) Utxorpc() *utxorpc.TxInput {
386409
return &utxorpc.TxInput{
387410
TxHash: i.TxId.Bytes(),
388411
OutputIndex: i.OutputIndex,
389-
// AsOutput: i.AsOutput,
390-
// Redeemer: i.Redeemer,
391412
}
392413
}
393414

ledger/tx_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Copyright 2023 Blink Labs Software
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package ledger_test
16+
17+
import (
18+
"encoding/hex"
19+
"testing"
20+
21+
"github.com/blinklabs-io/gouroboros/ledger"
22+
)
23+
24+
func TestDetermineTransactionType(t *testing.T) {
25+
testDefs := []struct {
26+
txCborHex string
27+
expectedTxType uint
28+
}{
29+
{
30+
txCborHex: "84a500d9010281825820279184037d249e397d97293738370756da559718fcdefae9924834840046b37b01018282583900923d4b64e1d730a4baf3e6dc433a9686983940f458363f37aad7a1a9568b72f85522e4a17d44a45cd021b9741b55d7cbc635c911625b015e1a00a9867082583900923d4b64e1d730a4baf3e6dc433a9686983940f458363f37aad7a1a9568b72f85522e4a17d44a45cd021b9741b55d7cbc635c911625b015e1b00000001267d7b04021a0002938d031a04e304e70800a100d9010281825820b829480e5d5827d2e1bd7c89176a5ca125c30812e54be7dbdf5c47c835a17f3d5840b13a76e7f2b19cde216fcad55ceeeb489ebab3dcf63ef1539ac4f535dece00411ee55c9b8188ef04b4aa3c72586e4a0ec9b89949367d7270fdddad3b18731403f5f6",
31+
expectedTxType: 6,
32+
},
33+
}
34+
for _, testDef := range testDefs {
35+
txCbor, err := hex.DecodeString(testDef.txCborHex)
36+
if err != nil {
37+
t.Fatalf("unexpected error: %s", err)
38+
}
39+
tmpTxType, err := ledger.DetermineTransactionType(txCbor)
40+
if err != nil {
41+
t.Fatalf("unexpected error: %s", err)
42+
}
43+
if tmpTxType != testDef.expectedTxType {
44+
t.Fatalf("did not get expected TX type: got %d, wanted %d", tmpTxType, testDef.expectedTxType)
45+
}
46+
}
47+
}

0 commit comments

Comments
 (0)