@@ -21,6 +21,9 @@ import (
2121 "strings"
2222
2323 "github.com/blinklabs-io/gouroboros/cbor"
24+ "github.com/blinklabs-io/gouroboros/ledger/byron"
25+ "github.com/blinklabs-io/gouroboros/ledger/common"
26+ "github.com/blinklabs-io/gouroboros/ledger/shelley"
2427)
2528
2629const (
@@ -42,20 +45,88 @@ const (
4245 UtxoFailureWrongNetworkWithdrawal = 9
4346 UtxoFailureOutputBootAddrAttrsTooBig = 10
4447 UtxoFailureTriesToForgeAda = 11
45- UtxoFailureOutputTooBigUtxo = 12
46- UtxoFailureInsufficientCollateral = 13
47- UtxoFailureScriptsNotPaidUtxo = 14
48- UtxoFailureExUnitsTooBigUtxo = 15
49- UtxoFailureCollateralContainsNonAda = 16
48+ UtxoFailureInsufficientCollateral = 12
5049 UtxoFailureWrongNetworkInTxBody = 17
5150 UtxoFailureOutsideForecast = 18
5251 UtxoFailureTooManyCollateralInputs = 19
5352 UtxoFailureNoCollateralInputs = 20
5453)
5554
55+ // Era-specific constants for errors that differ between Cardano eras
56+ const (
57+ // Alonzo era error constants
58+ UtxoFailureOutputTooBigUtxoAlonzo = 12
59+ UtxoFailureScriptsNotPaidUtxoAlonzo = 14
60+ UtxoFailureExUnitsTooBigUtxoAlonzo = 15
61+ UtxoFailureCollateralContainsNonAdaAlonzo = 16
62+
63+ // Babbage era error constants (same as Alonzo for these errors)
64+ UtxoFailureOutputTooBigUtxoBabbage = 12
65+ UtxoFailureScriptsNotPaidUtxoBabbage = 14
66+ UtxoFailureExUnitsTooBigUtxoBabbage = 15
67+ UtxoFailureCollateralContainsNonAdaBabbage = 16
68+
69+ // Conway era error constants
70+ UtxoFailureOutputTooBigUtxoConway = 11
71+ UtxoFailureScriptsNotPaidUtxoConway = 13
72+ UtxoFailureExUnitsTooBigUtxoConway = 14
73+ UtxoFailureCollateralContainsNonAdaConway = 15
74+ )
75+
5676// Helper type to make the code a little cleaner
5777type NewErrorFromCborFunc func ([]byte ) (error , error )
5878
79+ // getEraSpecificUtxoFailureConstants returns the correct error constants for the given era
80+ func getEraSpecificUtxoFailureConstants (eraId uint8 ) (map [int ]any , int , int , int , int ) {
81+ baseMap := map [int ]any {
82+ UtxoFailureBadInputsUtxo : & BadInputsUtxo {},
83+ UtxoFailureOutsideValidityIntervalUtxo : & OutsideValidityIntervalUtxo {},
84+ UtxoFailureMaxTxSizeUtxo : & MaxTxSizeUtxo {},
85+ UtxoFailureInputSetEmpty : & InputSetEmptyUtxo {},
86+ UtxoFailureFeeTooSmallUtxo : & FeeTooSmallUtxo {},
87+ UtxoFailureValueNotConservedUtxo : & ValueNotConservedUtxo {},
88+ UtxoFailureOutputTooSmallUtxo : & OutputTooSmallUtxo {},
89+ UtxoFailureUtxosFailure : & UtxosFailure {},
90+ UtxoFailureWrongNetwork : & WrongNetwork {},
91+ UtxoFailureWrongNetworkWithdrawal : & WrongNetworkWithdrawal {},
92+ UtxoFailureOutputBootAddrAttrsTooBig : & OutputBootAddrAttrsTooBig {},
93+ UtxoFailureTriesToForgeAda : & TriesToForgeADA {},
94+ UtxoFailureInsufficientCollateral : & InsufficientCollateral {},
95+ UtxoFailureWrongNetworkInTxBody : & WrongNetworkInTxBody {},
96+ UtxoFailureOutsideForecast : & OutsideForecast {},
97+ UtxoFailureTooManyCollateralInputs : & TooManyCollateralInputs {},
98+ UtxoFailureNoCollateralInputs : & NoCollateralInputs {},
99+ }
100+
101+ switch eraId {
102+ case EraIdAlonzo :
103+ baseMap [UtxoFailureOutputTooBigUtxoAlonzo ] = & OutputTooBigUtxo {}
104+ baseMap [UtxoFailureScriptsNotPaidUtxoAlonzo ] = & ScriptsNotPaidUtxo {}
105+ baseMap [UtxoFailureExUnitsTooBigUtxoAlonzo ] = & ExUnitsTooBigUtxo {}
106+ baseMap [UtxoFailureCollateralContainsNonAdaAlonzo ] = & CollateralContainsNonADA {}
107+ return baseMap , UtxoFailureOutputTooBigUtxoAlonzo , UtxoFailureScriptsNotPaidUtxoAlonzo , UtxoFailureExUnitsTooBigUtxoAlonzo , UtxoFailureCollateralContainsNonAdaAlonzo
108+ case EraIdBabbage :
109+ baseMap [UtxoFailureOutputTooBigUtxoBabbage ] = & OutputTooBigUtxo {}
110+ baseMap [UtxoFailureScriptsNotPaidUtxoBabbage ] = & ScriptsNotPaidUtxo {}
111+ baseMap [UtxoFailureExUnitsTooBigUtxoBabbage ] = & ExUnitsTooBigUtxo {}
112+ baseMap [UtxoFailureCollateralContainsNonAdaBabbage ] = & CollateralContainsNonADA {}
113+ return baseMap , UtxoFailureOutputTooBigUtxoBabbage , UtxoFailureScriptsNotPaidUtxoBabbage , UtxoFailureExUnitsTooBigUtxoBabbage , UtxoFailureCollateralContainsNonAdaBabbage
114+ case EraIdConway :
115+ baseMap [UtxoFailureOutputTooBigUtxoConway ] = & OutputTooBigUtxo {}
116+ baseMap [UtxoFailureScriptsNotPaidUtxoConway ] = & ScriptsNotPaidUtxo {}
117+ baseMap [UtxoFailureExUnitsTooBigUtxoConway ] = & ExUnitsTooBigUtxo {}
118+ baseMap [UtxoFailureCollateralContainsNonAdaConway ] = & CollateralContainsNonADA {}
119+ return baseMap , UtxoFailureOutputTooBigUtxoConway , UtxoFailureScriptsNotPaidUtxoConway , UtxoFailureExUnitsTooBigUtxoConway , UtxoFailureCollateralContainsNonAdaConway
120+ default :
121+ // For other eras (Byron, Shelley, Allegra, Mary), use Conway constants as fallback
122+ baseMap [UtxoFailureOutputTooBigUtxoConway ] = & OutputTooBigUtxo {}
123+ baseMap [UtxoFailureScriptsNotPaidUtxoConway ] = & ScriptsNotPaidUtxo {}
124+ baseMap [UtxoFailureExUnitsTooBigUtxoConway ] = & ExUnitsTooBigUtxo {}
125+ baseMap [UtxoFailureCollateralContainsNonAdaConway ] = & CollateralContainsNonADA {}
126+ return baseMap , UtxoFailureOutputTooBigUtxoConway , UtxoFailureScriptsNotPaidUtxoConway , UtxoFailureExUnitsTooBigUtxoConway , UtxoFailureCollateralContainsNonAdaConway
127+ }
128+ }
129+
59130func NewGenericErrorFromCbor (cborData []byte ) (error , error ) {
60131 newErr := & GenericError {}
61132 if _ , err := cbor .Decode (cborData , newErr ); err != nil {
@@ -262,32 +333,8 @@ func (e *UtxoFailure) UnmarshalCBOR(data []byte) error {
262333 return err
263334 }
264335 e .Era = tmpData .Era
265- newErr , err := cbor .DecodeById (
266- tmpData .Err ,
267- map [int ]any {
268- UtxoFailureBadInputsUtxo : & BadInputsUtxo {},
269- UtxoFailureOutsideValidityIntervalUtxo : & OutsideValidityIntervalUtxo {},
270- UtxoFailureMaxTxSizeUtxo : & MaxTxSizeUtxo {},
271- UtxoFailureInputSetEmpty : & InputSetEmptyUtxo {},
272- UtxoFailureFeeTooSmallUtxo : & FeeTooSmallUtxo {},
273- UtxoFailureValueNotConservedUtxo : & ValueNotConservedUtxo {},
274- UtxoFailureOutputTooSmallUtxo : & OutputTooSmallUtxo {},
275- UtxoFailureUtxosFailure : & UtxosFailure {},
276- UtxoFailureWrongNetwork : & WrongNetwork {},
277- UtxoFailureWrongNetworkWithdrawal : & WrongNetworkWithdrawal {},
278- UtxoFailureOutputBootAddrAttrsTooBig : & OutputBootAddrAttrsTooBig {},
279- UtxoFailureTriesToForgeAda : & TriesToForgeADA {},
280- UtxoFailureOutputTooBigUtxo : & OutputTooBigUtxo {},
281- UtxoFailureInsufficientCollateral : & InsufficientCollateral {},
282- UtxoFailureScriptsNotPaidUtxo : & ScriptsNotPaidUtxo {},
283- UtxoFailureExUnitsTooBigUtxo : & ExUnitsTooBigUtxo {},
284- UtxoFailureCollateralContainsNonAda : & CollateralContainsNonADA {},
285- UtxoFailureWrongNetworkInTxBody : & WrongNetworkInTxBody {},
286- UtxoFailureOutsideForecast : & OutsideForecast {},
287- UtxoFailureTooManyCollateralInputs : & TooManyCollateralInputs {},
288- UtxoFailureNoCollateralInputs : & NoCollateralInputs {},
289- },
290- )
336+ errorMap , _ , _ , _ , _ := getEraSpecificUtxoFailureConstants (tmpData .Era )
337+ newErr , err := cbor .DecodeById (tmpData .Err , errorMap )
291338 if err != nil {
292339 newErr , err = NewGenericErrorFromCbor (tmpData .Err )
293340 if err != nil {
@@ -534,14 +581,147 @@ func (e *InsufficientCollateral) Error() string {
534581 )
535582}
536583
584+ // ScriptsNotPaidUtxo represents the ScriptsNotPaidUTxO error from cardano-ledger.
585+ // Haskell: ScriptsNotPaidUTxO !(UTxO era) where UTxO era = Map TxIn TxOut
586+ // CBOR: [14, utxo_map]
537587type ScriptsNotPaidUtxo struct {
538588 UtxoFailureErrorBase
539- // TODO: determine content/structure of this value (#847)
540- Value cbor.Value
589+ Utxos []common.Utxo // Each Utxo contains Id (input) and Output
590+ }
591+
592+ func (e * ScriptsNotPaidUtxo ) MarshalCBOR () ([]byte , error ) {
593+ // Use era-specific constant - we'll use Conway as default since it has the most recent structure
594+ // In practice, this should be set when the error is created, but we provide a sensible fallback
595+ constantToUse := UtxoFailureScriptsNotPaidUtxoConway
596+ if e .Type != 0 {
597+ constantToUse = int (e .Type )
598+ }
599+ // Bounds check to prevent integer overflow
600+ if constantToUse < 0 || constantToUse > 255 {
601+ return nil , fmt .Errorf ("ScriptsNotPaidUtxo: invalid constructor index %d (must be 0-255)" , constantToUse )
602+ }
603+ e .Type = uint8 (constantToUse )
604+
605+ utxoMap := make (
606+ map [common.TransactionInput ]common.TransactionOutput ,
607+ len (e .Utxos ),
608+ )
609+ for _ , u := range e .Utxos {
610+ // Return error for nil entries instead of silently skipping
611+ if u .Id == nil || u .Output == nil {
612+ return nil , errors .New (
613+ "ScriptsNotPaidUtxo: cannot marshal UTxO with nil Id or Output" ,
614+ )
615+ }
616+ utxoMap [u .Id ] = u .Output
617+ }
618+ arr := []any {constantToUse , utxoMap }
619+ return cbor .Encode (arr )
620+ }
621+
622+ func (e * ScriptsNotPaidUtxo ) UnmarshalCBOR (data []byte ) error {
623+ type tScriptsNotPaidUtxo struct {
624+ cbor.StructAsArray
625+ ConstructorIdx uint64
626+ UtxoMapCbor cbor.RawMessage
627+ }
628+ var tmp tScriptsNotPaidUtxo
629+ if _ , err := cbor .Decode (data , & tmp ); err != nil {
630+ return fmt .Errorf ("failed to decode ScriptsNotPaidUtxo: %w" , err )
631+ }
632+
633+ // Check if the constructor index matches any valid era-specific constant
634+ validConstructors := []int {
635+ UtxoFailureScriptsNotPaidUtxoAlonzo ,
636+ UtxoFailureScriptsNotPaidUtxoBabbage ,
637+ UtxoFailureScriptsNotPaidUtxoConway ,
638+ }
639+
640+ isValid := false
641+ for _ , valid := range validConstructors {
642+ // Bounds check to prevent integer overflow
643+ if valid < 0 || valid > 65535 {
644+ continue // Skip invalid constants
645+ }
646+ if tmp .ConstructorIdx == uint64 (valid ) {
647+ isValid = true
648+ break
649+ }
650+ }
651+
652+ if ! isValid {
653+ return fmt .Errorf (
654+ "ScriptsNotPaidUtxo: expected one of constructor indices %v, got %d" ,
655+ validConstructors ,
656+ tmp .ConstructorIdx ,
657+ )
658+ }
659+
660+ // Set the struct tag to match the decoded constructor
661+ // Bounds check to prevent integer overflow
662+ if tmp .ConstructorIdx > 255 {
663+ return fmt .Errorf ("ScriptsNotPaidUtxo: constructor index %d exceeds uint8 range (0-255)" , tmp .ConstructorIdx )
664+ }
665+ e .Type = uint8 (tmp .ConstructorIdx )
666+
667+ // For era-agnostic decoding, we need to handle the map structure carefully
668+ // Since we can't use cbor.RawMessage as map keys, we'll decode to a concrete type first
669+ // and then convert to era-agnostic types. Try different era input types until one works.
670+
671+ // Try Shelley-family transaction inputs first (most common from Shelley onwards)
672+ var shelleyUtxoMap map [shelley.ShelleyTransactionInput ]cbor.RawMessage
673+ if _ , err := cbor .Decode (tmp .UtxoMapCbor , & shelleyUtxoMap ); err == nil {
674+ // Successfully decoded as Shelley-family inputs
675+ e .Utxos = make ([]common.Utxo , 0 , len (shelleyUtxoMap ))
676+ for input , outputCbor := range shelleyUtxoMap {
677+ // Decode output using era-agnostic function (handles all eras)
678+ output , err := NewTransactionOutputFromCbor (outputCbor )
679+ if err != nil {
680+ return fmt .Errorf (
681+ "failed to decode transaction output: %w" ,
682+ err ,
683+ )
684+ }
685+
686+ e .Utxos = append (e .Utxos , common.Utxo {
687+ Id : input ,
688+ Output : output ,
689+ })
690+ }
691+ return nil
692+ }
693+
694+ // Try Byron transaction inputs (for Byron era)
695+ var byronUtxoMap map [byron.ByronTransactionInput ]cbor.RawMessage
696+ if _ , err := cbor .Decode (tmp .UtxoMapCbor , & byronUtxoMap ); err == nil {
697+ // Successfully decoded as Byron inputs
698+ e .Utxos = make ([]common.Utxo , 0 , len (byronUtxoMap ))
699+ for input , outputCbor := range byronUtxoMap {
700+ // Decode output using era-agnostic function (handles all eras)
701+ output , err := NewTransactionOutputFromCbor (outputCbor )
702+ if err != nil {
703+ return fmt .Errorf (
704+ "failed to decode transaction output: %w" ,
705+ err ,
706+ )
707+ }
708+
709+ e .Utxos = append (e .Utxos , common.Utxo {
710+ Id : input ,
711+ Output : output ,
712+ })
713+ }
714+ return nil
715+ }
716+
717+ // If both failed, return an error
718+ return errors .New (
719+ "failed to decode UTxO map as either Shelley-family or Byron transaction inputs" ,
720+ )
541721}
542722
543723func (e * ScriptsNotPaidUtxo ) Error () string {
544- return fmt .Sprintf ("ScriptsNotPaidUtxo (%v )" , e . Value . Value ( ))
724+ return fmt .Sprintf ("ScriptsNotPaidUtxo (%d UTxOs )" , len ( e . Utxos ))
545725}
546726
547727type ExUnitsTooBigUtxo struct {
0 commit comments