From 33aed59b8f7d0974fa6afca5a686fc2b7a75b900 Mon Sep 17 00:00:00 2001 From: Akhil Repala Date: Mon, 1 Sep 2025 16:47:04 -0500 Subject: [PATCH 1/4] feat(ledger): Added friendly string representation of TX outputs for the eras alonzo, babbage, byron, mary and shelley Signed-off-by: Akhil Repala --- ledger/alonzo/alonzo.go | 13 +++++++++++++ ledger/alonzo/alonzo_test.go | 21 +++++++++++++++++++++ ledger/babbage/babbage.go | 13 +++++++++++++ ledger/babbage/babbage_test.go | 21 +++++++++++++++++++++ ledger/byron/byron.go | 8 ++++++++ ledger/byron/byron_test.go | 21 +++++++++++++++++++++ ledger/common/tx.go | 1 + ledger/mary/errors.go | 3 +-- ledger/mary/mary.go | 13 +++++++++++++ ledger/mary/mary_test.go | 32 ++++++++++++++++++++++++++++++++ ledger/shelley/errors.go | 4 ++-- ledger/shelley/shelley.go | 8 ++++++++ ledger/shelley/shelley_test.go | 34 ++++++++++++++++++++++++++++++++++ 13 files changed, 188 insertions(+), 4 deletions(-) create mode 100644 ledger/shelley/shelley_test.go diff --git a/ledger/alonzo/alonzo.go b/ledger/alonzo/alonzo.go index dd4c3693..feb3fb1e 100644 --- a/ledger/alonzo/alonzo.go +++ b/ledger/alonzo/alonzo.go @@ -447,6 +447,19 @@ func (o AlonzoTransactionOutput) Utxorpc() (*utxorpc.TxOutput, error) { nil } +func (o AlonzoTransactionOutput) String() string { + assets := "" + if o.OutputAmount.Assets != nil && len(o.OutputAmount.Assets.Policies()) > 0 { + assets = " assets=..." + } + return fmt.Sprintf( + "(AlonzoTransactionOutput address=%s amount=%d%s)", + o.OutputAddress.String(), + o.OutputAmount.Amount, + assets, + ) +} + type AlonzoRedeemer struct { cbor.StructAsArray Tag common.RedeemerTag diff --git a/ledger/alonzo/alonzo_test.go b/ledger/alonzo/alonzo_test.go index ef4e04f7..36ac40a8 100644 --- a/ledger/alonzo/alonzo_test.go +++ b/ledger/alonzo/alonzo_test.go @@ -17,6 +17,7 @@ package alonzo import ( "math/big" "reflect" + "regexp" "testing" "github.com/blinklabs-io/gouroboros/cbor" @@ -277,3 +278,23 @@ func TestAlonzoRedeemersIter(t *testing.T) { iterIdx++ } } + +func TestAlonzoTransactionOutputString(t *testing.T) { + addr, _ := common.NewAddress( + "addr1qytna5k2fq9ler0fuk45j7zfwv7t2zwhp777nvdjqqfr5tz8ztpwnk8zq5ngetcz5k5mckgkajnygtsra9aej2h3ek5seupmvd", + ) + ma := common.NewMultiAsset[common.MultiAssetTypeOutput]( + map[common.Blake2b224]map[cbor.ByteString]uint64{ + common.NewBlake2b224(make([]byte, 28)): {cbor.NewByteString([]byte("t")): 2}, + }, + ) + out := AlonzoTransactionOutput{ + OutputAddress: addr, + OutputAmount: mary.MaryTransactionOutputValue{Amount: 456, Assets: &ma}, + } + s := out.String() + re := regexp.MustCompile(`^\(AlonzoTransactionOutput address=addr1[0-9a-z]+ amount=456 assets=\.\.\.\)$`) + if !re.MatchString(s) { + t.Fatalf("unexpected string: %s", s) + } +} diff --git a/ledger/babbage/babbage.go b/ledger/babbage/babbage.go index 4723b359..bf2110e5 100644 --- a/ledger/babbage/babbage.go +++ b/ledger/babbage/babbage.go @@ -649,6 +649,19 @@ func (o BabbageTransactionOutput) Utxorpc() (*utxorpc.TxOutput, error) { nil } +func (o BabbageTransactionOutput) String() string { + assets := "" + if o.OutputAmount.Assets != nil && len(o.OutputAmount.Assets.Policies()) > 0 { + assets = " assets=..." + } + return fmt.Sprintf( + "(BabbageTransactionOutput address=%s amount=%d%s)", + o.OutputAddress.String(), + o.OutputAmount.Amount, + assets, + ) +} + type BabbageTransactionWitnessSet struct { cbor.DecodeStoreCbor VkeyWitnesses []common.VkeyWitness `cbor:"0,keyasint,omitempty"` diff --git a/ledger/babbage/babbage_test.go b/ledger/babbage/babbage_test.go index 74a99433..b89a5575 100644 --- a/ledger/babbage/babbage_test.go +++ b/ledger/babbage/babbage_test.go @@ -17,6 +17,7 @@ package babbage import ( "math/big" "reflect" + "regexp" "testing" "github.com/blinklabs-io/gouroboros/cbor" @@ -2970,3 +2971,23 @@ func TestBabbageTransactionOutputToPlutusDataCoinAssets(t *testing.T) { ) } } + +func TestBabbageTransactionOutputString(t *testing.T) { + addr, _ := common.NewAddress( + "addr1qytna5k2fq9ler0fuk45j7zfwv7t2zwhp777nvdjqqfr5tz8ztpwnk8zq5ngetcz5k5mckgkajnygtsra9aej2h3ek5seupmvd", + ) + ma := common.NewMultiAsset[common.MultiAssetTypeOutput]( + map[common.Blake2b224]map[cbor.ByteString]uint64{ + common.NewBlake2b224(make([]byte, 28)): {cbor.NewByteString([]byte("x")): 2}, + }, + ) + out := BabbageTransactionOutput{ + OutputAddress: addr, + OutputAmount: mary.MaryTransactionOutputValue{Amount: 456, Assets: &ma}, + } + s := out.String() + re := regexp.MustCompile(`^\(BabbageTransactionOutput address=addr1[0-9a-z]+ amount=456 assets=\.\.\.\)$`) + if !re.MatchString(s) { + t.Fatalf("unexpected string: %s", s) + } +} diff --git a/ledger/byron/byron.go b/ledger/byron/byron.go index 25939879..3c397c19 100644 --- a/ledger/byron/byron.go +++ b/ledger/byron/byron.go @@ -462,6 +462,14 @@ func (o ByronTransactionOutput) Utxorpc() (*utxorpc.TxOutput, error) { nil } +func (o ByronTransactionOutput) String() string { + return fmt.Sprintf( + "(ByronTransactionOutput address=%s amount=%d)", + o.OutputAddress.String(), + o.OutputAmount, + ) +} + type ByronBlockVersion struct { cbor.StructAsArray Major uint16 diff --git a/ledger/byron/byron_test.go b/ledger/byron/byron_test.go index b2c0c4d9..08f6e070 100644 --- a/ledger/byron/byron_test.go +++ b/ledger/byron/byron_test.go @@ -16,6 +16,7 @@ package byron_test import ( "reflect" + "regexp" "testing" "github.com/blinklabs-io/gouroboros/ledger/byron" @@ -104,3 +105,23 @@ func TestByronTransaction_Utxorpc_Empty(t *testing.T) { t.Errorf("Expected fee = 0, got %d", result.Fee) } } + +func TestByronTransactionOutputString(t *testing.T) { + addr, err := common.NewByronAddressFromParts( + 0, + make([]byte, common.AddressHashSize), + common.ByronAddressAttributes{}, + ) + if err != nil { + t.Fatalf("address: %v", err) + } + out := byron.ByronTransactionOutput{ + OutputAddress: addr, + OutputAmount: 456, + } + s := out.String() + re := regexp.MustCompile(`^\(ByronTransactionOutput address=[1-9A-HJ-NP-Za-km-z]+ amount=456\)$`) + if !re.MatchString(s) { + t.Fatalf("unexpected string: %s", s) + } +} diff --git a/ledger/common/tx.go b/ledger/common/tx.go index 570db12d..93d509d7 100644 --- a/ledger/common/tx.go +++ b/ledger/common/tx.go @@ -77,6 +77,7 @@ type TransactionOutput interface { Utxorpc() (*utxorpc.TxOutput, error) ScriptRef() Script ToPlutusData() data.PlutusData + String() string } type TransactionWitnessSet interface { diff --git a/ledger/mary/errors.go b/ledger/mary/errors.go index 607a35de..8e12b81e 100644 --- a/ledger/mary/errors.go +++ b/ledger/mary/errors.go @@ -15,7 +15,6 @@ package mary import ( - "fmt" "strings" "github.com/blinklabs-io/gouroboros/ledger/common" @@ -28,7 +27,7 @@ type OutputTooBigUtxoError struct { func (e OutputTooBigUtxoError) Error() string { tmpOutputs := make([]string, len(e.Outputs)) for idx, tmpOutput := range e.Outputs { - tmpOutputs[idx] = fmt.Sprintf("%#v", tmpOutput) + tmpOutputs[idx] = tmpOutput.String() } return "output value too large: " + strings.Join(tmpOutputs, ", ") } diff --git a/ledger/mary/mary.go b/ledger/mary/mary.go index 6562d3be..904d4c78 100644 --- a/ledger/mary/mary.go +++ b/ledger/mary/mary.go @@ -490,6 +490,19 @@ func (o MaryTransactionOutput) Utxorpc() (*utxorpc.TxOutput, error) { err } +func (o MaryTransactionOutput) String() string { + assets := "" + if o.OutputAmount.Assets != nil && len(o.OutputAmount.Assets.Policies()) > 0 { + assets = " assets=..." + } + return fmt.Sprintf( + "(MaryTransactionOutput address=%s amount=%d%s)", + o.OutputAddress.String(), + o.OutputAmount.Amount, + assets, + ) +} + type MaryTransactionOutputValue struct { cbor.StructAsArray Amount uint64 diff --git a/ledger/mary/mary_test.go b/ledger/mary/mary_test.go index 6bbf4f3f..a93f8f2e 100644 --- a/ledger/mary/mary_test.go +++ b/ledger/mary/mary_test.go @@ -17,6 +17,7 @@ package mary import ( "encoding/hex" "reflect" + "regexp" "testing" "github.com/blinklabs-io/gouroboros/cbor" @@ -115,3 +116,34 @@ func TestMaryTransactionOutputValueEncodeDecode(t *testing.T) { } } } + +func TestMaryTransactionOutputString(t *testing.T) { + addr, _ := common.NewAddress("addr1qytna5k2fq9ler0fuk45j7zfwv7t2zwhp777nvdjqqfr5tz8ztpwnk8zq5ngetcz5k5mckgkajnygtsra9aej2h3ek5seupmvd") + ma := common.NewMultiAsset[common.MultiAssetTypeOutput]( + map[common.Blake2b224]map[cbor.ByteString]uint64{ + common.NewBlake2b224(make([]byte, 28)): {cbor.NewByteString([]byte("token")): 2}, + }, + ) + out := MaryTransactionOutput{ + OutputAddress: addr, + OutputAmount: MaryTransactionOutputValue{Amount: 456, Assets: &ma}, + } + s := out.String() + re := regexp.MustCompile(`^\(MaryTransactionOutput address=addr1[0-9a-z]+ amount=456 assets=\.\.\.\)$`) + if !re.MatchString(s) { + t.Fatalf("unexpected string: %s", s) + } +} + +func TestMaryOutputTooBigErrorFormatting(t *testing.T) { + addr, _ := common.NewAddress("addr1qytna5k2fq9ler0fuk45j7zfwv7t2zwhp777nvdjqqfr5tz8ztpwnk8zq5ngetcz5k5mckgkajnygtsra9aej2h3ek5seupmvd") + out := &MaryTransactionOutput{ + OutputAddress: addr, + OutputAmount: MaryTransactionOutputValue{Amount: 456}, + } + errStr := OutputTooBigUtxoError{Outputs: []common.TransactionOutput{out}}.Error() + re := regexp.MustCompile(`^output value too large: \(MaryTransactionOutput address=addr1[0-9a-z]+ amount=456\)$`) + if !re.MatchString(errStr) { + t.Fatalf("unexpected error: %s", errStr) + } +} diff --git a/ledger/shelley/errors.go b/ledger/shelley/errors.go index f1343cb6..3da5dc0b 100644 --- a/ledger/shelley/errors.go +++ b/ledger/shelley/errors.go @@ -111,7 +111,7 @@ type OutputTooSmallUtxoError struct { func (e OutputTooSmallUtxoError) Error() string { tmpOutputs := make([]string, len(e.Outputs)) for idx, tmpOutput := range e.Outputs { - tmpOutputs[idx] = fmt.Sprintf("%#v", tmpOutput) + tmpOutputs[idx] = tmpOutput.String() } return "output too small: " + strings.Join(tmpOutputs, ", ") } @@ -123,7 +123,7 @@ type OutputBootAddrAttrsTooBigError struct { func (e OutputBootAddrAttrsTooBigError) Error() string { tmpOutputs := make([]string, len(e.Outputs)) for idx, tmpOutput := range e.Outputs { - tmpOutputs[idx] = fmt.Sprintf("%#v", tmpOutput) + tmpOutputs[idx] = tmpOutput.String() } return "output bootstrap address attributes too big: " + strings.Join( tmpOutputs, diff --git a/ledger/shelley/shelley.go b/ledger/shelley/shelley.go index 7920cfdf..046f0556 100644 --- a/ledger/shelley/shelley.go +++ b/ledger/shelley/shelley.go @@ -444,6 +444,14 @@ func (o ShelleyTransactionOutput) Utxorpc() (*utxorpc.TxOutput, error) { }, nil } +func (o ShelleyTransactionOutput) String() string { + return fmt.Sprintf( + "(ShelleyTransactionOutput address=%s amount=%d)", + o.OutputAddress.String(), + o.OutputAmount, + ) +} + type ShelleyTransactionWitnessSet struct { cbor.DecodeStoreCbor VkeyWitnesses []common.VkeyWitness `cbor:"0,keyasint,omitempty"` diff --git a/ledger/shelley/shelley_test.go b/ledger/shelley/shelley_test.go new file mode 100644 index 00000000..940d372c --- /dev/null +++ b/ledger/shelley/shelley_test.go @@ -0,0 +1,34 @@ +package shelley_test + +import ( + "regexp" + "testing" + + "github.com/blinklabs-io/gouroboros/ledger/common" + "github.com/blinklabs-io/gouroboros/ledger/shelley" +) + +func TestShelleyTransactionOutputString(t *testing.T) { + addr, _ := common.NewAddress("addr1qytna5k2fq9ler0fuk45j7zfwv7t2zwhp777nvdjqqfr5tz8ztpwnk8zq5ngetcz5k5mckgkajnygtsra9aej2h3ek5seupmvd") + out := shelley.ShelleyTransactionOutput{ + OutputAddress: addr, + OutputAmount: 456, + } + s := out.String() + re := regexp.MustCompile(`^\(ShelleyTransactionOutput address=addr1[0-9a-z]+ amount=456\)$`) + if !re.MatchString(s) { + t.Fatalf("unexpected string: %s", s) + } +} + +func TestShelleyOutputTooSmallErrorFormatting(t *testing.T) { + addr, _ := common.NewAddress("addr1qytna5k2fq9ler0fuk45j7zfwv7t2zwhp777nvdjqqfr5tz8ztpwnk8zq5ngetcz5k5mckgkajnygtsra9aej2h3ek5seupmvd") + out := &shelley.ShelleyTransactionOutput{ + OutputAddress: addr, + OutputAmount: 456, + } + errStr := shelley.OutputTooSmallUtxoError{Outputs: []common.TransactionOutput{out}}.Error() + if matched, _ := regexp.MatchString(`^output too small: \(ShelleyTransactionOutput address=addr1[0-9a-z]+ amount=456\)$`, errStr); !matched { + t.Fatalf("unexpected error: %s", errStr) + } +} From be52da669249d147edd4fc55e28ec1dab02aa18c Mon Sep 17 00:00:00 2001 From: Akhil Repala Date: Wed, 3 Sep 2025 00:47:12 -0500 Subject: [PATCH 2/4] feat(ledger): Modifed Check part in all the tests for the exact expected string instead of using a regex Signed-off-by: Akhil Repala --- ledger/alonzo/alonzo_test.go | 5 ++--- ledger/babbage/babbage_test.go | 5 ++--- ledger/byron/byron_test.go | 5 ++--- ledger/mary/mary_test.go | 10 +++++----- ledger/shelley/shelley_test.go | 9 +++++---- 5 files changed, 16 insertions(+), 18 deletions(-) diff --git a/ledger/alonzo/alonzo_test.go b/ledger/alonzo/alonzo_test.go index 36ac40a8..6774931b 100644 --- a/ledger/alonzo/alonzo_test.go +++ b/ledger/alonzo/alonzo_test.go @@ -17,7 +17,6 @@ package alonzo import ( "math/big" "reflect" - "regexp" "testing" "github.com/blinklabs-io/gouroboros/cbor" @@ -293,8 +292,8 @@ func TestAlonzoTransactionOutputString(t *testing.T) { OutputAmount: mary.MaryTransactionOutputValue{Amount: 456, Assets: &ma}, } s := out.String() - re := regexp.MustCompile(`^\(AlonzoTransactionOutput address=addr1[0-9a-z]+ amount=456 assets=\.\.\.\)$`) - if !re.MatchString(s) { + expected := "(AlonzoTransactionOutput address=" + addr.String() + " amount=456 assets=...)" + if s != expected { t.Fatalf("unexpected string: %s", s) } } diff --git a/ledger/babbage/babbage_test.go b/ledger/babbage/babbage_test.go index b89a5575..14e51801 100644 --- a/ledger/babbage/babbage_test.go +++ b/ledger/babbage/babbage_test.go @@ -17,7 +17,6 @@ package babbage import ( "math/big" "reflect" - "regexp" "testing" "github.com/blinklabs-io/gouroboros/cbor" @@ -2986,8 +2985,8 @@ func TestBabbageTransactionOutputString(t *testing.T) { OutputAmount: mary.MaryTransactionOutputValue{Amount: 456, Assets: &ma}, } s := out.String() - re := regexp.MustCompile(`^\(BabbageTransactionOutput address=addr1[0-9a-z]+ amount=456 assets=\.\.\.\)$`) - if !re.MatchString(s) { + expected := "(BabbageTransactionOutput address=" + addr.String() + " amount=456 assets=...)" + if s != expected { t.Fatalf("unexpected string: %s", s) } } diff --git a/ledger/byron/byron_test.go b/ledger/byron/byron_test.go index 08f6e070..dd25c323 100644 --- a/ledger/byron/byron_test.go +++ b/ledger/byron/byron_test.go @@ -16,7 +16,6 @@ package byron_test import ( "reflect" - "regexp" "testing" "github.com/blinklabs-io/gouroboros/ledger/byron" @@ -120,8 +119,8 @@ func TestByronTransactionOutputString(t *testing.T) { OutputAmount: 456, } s := out.String() - re := regexp.MustCompile(`^\(ByronTransactionOutput address=[1-9A-HJ-NP-Za-km-z]+ amount=456\)$`) - if !re.MatchString(s) { + expected := "(ByronTransactionOutput address=" + addr.String() + " amount=456)" + if s != expected { t.Fatalf("unexpected string: %s", s) } } diff --git a/ledger/mary/mary_test.go b/ledger/mary/mary_test.go index a93f8f2e..bde402ec 100644 --- a/ledger/mary/mary_test.go +++ b/ledger/mary/mary_test.go @@ -16,8 +16,8 @@ package mary import ( "encoding/hex" + "fmt" "reflect" - "regexp" "testing" "github.com/blinklabs-io/gouroboros/cbor" @@ -129,8 +129,8 @@ func TestMaryTransactionOutputString(t *testing.T) { OutputAmount: MaryTransactionOutputValue{Amount: 456, Assets: &ma}, } s := out.String() - re := regexp.MustCompile(`^\(MaryTransactionOutput address=addr1[0-9a-z]+ amount=456 assets=\.\.\.\)$`) - if !re.MatchString(s) { + expected := fmt.Sprintf("(MaryTransactionOutput address=%s amount=456 assets=...)", addr.String()) + if s != expected { t.Fatalf("unexpected string: %s", s) } } @@ -142,8 +142,8 @@ func TestMaryOutputTooBigErrorFormatting(t *testing.T) { OutputAmount: MaryTransactionOutputValue{Amount: 456}, } errStr := OutputTooBigUtxoError{Outputs: []common.TransactionOutput{out}}.Error() - re := regexp.MustCompile(`^output value too large: \(MaryTransactionOutput address=addr1[0-9a-z]+ amount=456\)$`) - if !re.MatchString(errStr) { + expected := fmt.Sprintf("output value too large: (MaryTransactionOutput address=%s amount=456)", addr.String()) + if errStr != expected { t.Fatalf("unexpected error: %s", errStr) } } diff --git a/ledger/shelley/shelley_test.go b/ledger/shelley/shelley_test.go index 940d372c..981536a0 100644 --- a/ledger/shelley/shelley_test.go +++ b/ledger/shelley/shelley_test.go @@ -1,7 +1,7 @@ package shelley_test import ( - "regexp" + "fmt" "testing" "github.com/blinklabs-io/gouroboros/ledger/common" @@ -15,8 +15,8 @@ func TestShelleyTransactionOutputString(t *testing.T) { OutputAmount: 456, } s := out.String() - re := regexp.MustCompile(`^\(ShelleyTransactionOutput address=addr1[0-9a-z]+ amount=456\)$`) - if !re.MatchString(s) { + expected := fmt.Sprintf("(ShelleyTransactionOutput address=%s amount=456)", addr.String()) + if s != expected { t.Fatalf("unexpected string: %s", s) } } @@ -28,7 +28,8 @@ func TestShelleyOutputTooSmallErrorFormatting(t *testing.T) { OutputAmount: 456, } errStr := shelley.OutputTooSmallUtxoError{Outputs: []common.TransactionOutput{out}}.Error() - if matched, _ := regexp.MatchString(`^output too small: \(ShelleyTransactionOutput address=addr1[0-9a-z]+ amount=456\)$`, errStr); !matched { + expected := fmt.Sprintf("output too small: (ShelleyTransactionOutput address=%s amount=456)", addr.String()) + if errStr != expected { t.Fatalf("unexpected error: %s", errStr) } } From 33ee0d84174d40ef243e4435a6cddd66c7ebce60 Mon Sep 17 00:00:00 2001 From: Akhil Repala Date: Sun, 7 Sep 2025 17:16:44 -0500 Subject: [PATCH 3/4] feat(ledger): Ensured original address string is preserved in output instead of addr.String() and removed the placeholder for assets Signed-off-by: Akhil Repala --- ledger/alonzo/alonzo.go | 2 +- ledger/alonzo/alonzo_test.go | 10 +++++---- ledger/babbage/babbage.go | 2 +- ledger/babbage/babbage_test.go | 7 +++--- ledger/byron/byron_test.go | 3 ++- ledger/common/common.go | 39 ++++++++++++++++++++++++++++++++++ ledger/mary/mary.go | 2 +- ledger/mary/mary_test.go | 10 +++++---- ledger/shelley/shelley_test.go | 10 +++++---- 9 files changed, 65 insertions(+), 20 deletions(-) diff --git a/ledger/alonzo/alonzo.go b/ledger/alonzo/alonzo.go index feb3fb1e..479c23f5 100644 --- a/ledger/alonzo/alonzo.go +++ b/ledger/alonzo/alonzo.go @@ -450,7 +450,7 @@ func (o AlonzoTransactionOutput) Utxorpc() (*utxorpc.TxOutput, error) { func (o AlonzoTransactionOutput) String() string { assets := "" if o.OutputAmount.Assets != nil && len(o.OutputAmount.Assets.Policies()) > 0 { - assets = " assets=..." + assets = fmt.Sprintf(" assets=%s", o.OutputAmount.Assets.String()) } return fmt.Sprintf( "(AlonzoTransactionOutput address=%s amount=%d%s)", diff --git a/ledger/alonzo/alonzo_test.go b/ledger/alonzo/alonzo_test.go index 6774931b..a3d6d8b7 100644 --- a/ledger/alonzo/alonzo_test.go +++ b/ledger/alonzo/alonzo_test.go @@ -15,6 +15,7 @@ package alonzo import ( + "encoding/hex" "math/big" "reflect" "testing" @@ -279,9 +280,8 @@ func TestAlonzoRedeemersIter(t *testing.T) { } func TestAlonzoTransactionOutputString(t *testing.T) { - addr, _ := common.NewAddress( - "addr1qytna5k2fq9ler0fuk45j7zfwv7t2zwhp777nvdjqqfr5tz8ztpwnk8zq5ngetcz5k5mckgkajnygtsra9aej2h3ek5seupmvd", - ) + addrStr := "addr1qytna5k2fq9ler0fuk45j7zfwv7t2zwhp777nvdjqqfr5tz8ztpwnk8zq5ngetcz5k5mckgkajnygtsra9aej2h3ek5seupmvd" + addr, _ := common.NewAddress(addrStr) ma := common.NewMultiAsset[common.MultiAssetTypeOutput]( map[common.Blake2b224]map[cbor.ByteString]uint64{ common.NewBlake2b224(make([]byte, 28)): {cbor.NewByteString([]byte("t")): 2}, @@ -292,7 +292,9 @@ func TestAlonzoTransactionOutputString(t *testing.T) { OutputAmount: mary.MaryTransactionOutputValue{Amount: 456, Assets: &ma}, } s := out.String() - expected := "(AlonzoTransactionOutput address=" + addr.String() + " amount=456 assets=...)" + policyStr := common.NewBlake2b224(make([]byte, 28)).String() + assetsStr := "[" + policyStr + "." + hex.EncodeToString([]byte("t")) + "=2]" + expected := "(AlonzoTransactionOutput address=" + addrStr + " amount=456 assets=" + assetsStr + ")" if s != expected { t.Fatalf("unexpected string: %s", s) } diff --git a/ledger/babbage/babbage.go b/ledger/babbage/babbage.go index bf2110e5..5988e89b 100644 --- a/ledger/babbage/babbage.go +++ b/ledger/babbage/babbage.go @@ -652,7 +652,7 @@ func (o BabbageTransactionOutput) Utxorpc() (*utxorpc.TxOutput, error) { func (o BabbageTransactionOutput) String() string { assets := "" if o.OutputAmount.Assets != nil && len(o.OutputAmount.Assets.Policies()) > 0 { - assets = " assets=..." + assets = fmt.Sprintf(" assets=%s", o.OutputAmount.Assets.String()) } return fmt.Sprintf( "(BabbageTransactionOutput address=%s amount=%d%s)", diff --git a/ledger/babbage/babbage_test.go b/ledger/babbage/babbage_test.go index 14e51801..c6bd7c63 100644 --- a/ledger/babbage/babbage_test.go +++ b/ledger/babbage/babbage_test.go @@ -2972,9 +2972,8 @@ func TestBabbageTransactionOutputToPlutusDataCoinAssets(t *testing.T) { } func TestBabbageTransactionOutputString(t *testing.T) { - addr, _ := common.NewAddress( - "addr1qytna5k2fq9ler0fuk45j7zfwv7t2zwhp777nvdjqqfr5tz8ztpwnk8zq5ngetcz5k5mckgkajnygtsra9aej2h3ek5seupmvd", - ) + addrStr := "addr1qytna5k2fq9ler0fuk45j7zfwv7t2zwhp777nvdjqqfr5tz8ztpwnk8zq5ngetcz5k5mckgkajnygtsra9aej2h3ek5seupmvd" + addr, _ := common.NewAddress(addrStr) ma := common.NewMultiAsset[common.MultiAssetTypeOutput]( map[common.Blake2b224]map[cbor.ByteString]uint64{ common.NewBlake2b224(make([]byte, 28)): {cbor.NewByteString([]byte("x")): 2}, @@ -2985,7 +2984,7 @@ func TestBabbageTransactionOutputString(t *testing.T) { OutputAmount: mary.MaryTransactionOutputValue{Amount: 456, Assets: &ma}, } s := out.String() - expected := "(BabbageTransactionOutput address=" + addr.String() + " amount=456 assets=...)" + expected := "(BabbageTransactionOutput address=" + addrStr + " amount=456 assets=" + ma.String() + ")" if s != expected { t.Fatalf("unexpected string: %s", s) } diff --git a/ledger/byron/byron_test.go b/ledger/byron/byron_test.go index dd25c323..e577f308 100644 --- a/ledger/byron/byron_test.go +++ b/ledger/byron/byron_test.go @@ -114,12 +114,13 @@ func TestByronTransactionOutputString(t *testing.T) { if err != nil { t.Fatalf("address: %v", err) } + addrStr := addr.String() out := byron.ByronTransactionOutput{ OutputAddress: addr, OutputAmount: 456, } s := out.String() - expected := "(ByronTransactionOutput address=" + addr.String() + " amount=456)" + expected := "(ByronTransactionOutput address=" + addrStr + " amount=456)" if s != expected { t.Fatalf("unexpected string: %s", s) } diff --git a/ledger/common/common.go b/ledger/common/common.go index 9d156176..716d6dcb 100644 --- a/ledger/common/common.go +++ b/ledger/common/common.go @@ -22,6 +22,7 @@ import ( "maps" "math/big" "slices" + "strings" "github.com/blinklabs-io/gouroboros/cbor" "github.com/blinklabs-io/plutigo/data" @@ -325,6 +326,44 @@ func (m *MultiAsset[T]) normalize() map[Blake2b224]map[cbor.ByteString]T { return ret } +// String returns a stable, human-friendly representation of the MultiAsset. +// Output format: [.=, ...] sorted by policyId, then asset name +func (m *MultiAsset[T]) String() string { + if m == nil { + return "[]" + } + norm := m.normalize() + if len(norm) == 0 { + return "[]" + } + + policies := slices.Collect(maps.Keys(norm)) + slices.SortFunc(policies, func(a, b Blake2b224) int { return bytes.Compare(a.Bytes(), b.Bytes()) }) + + var b strings.Builder + b.WriteByte('[') + first := true + for _, pid := range policies { + assets := norm[pid] + names := slices.Collect(maps.Keys(assets)) + slices.SortFunc(names, func(a, b cbor.ByteString) int { return bytes.Compare(a.Bytes(), b.Bytes()) }) + + for _, name := range names { + if !first { + b.WriteString(", ") + } + first = false + b.WriteString(pid.String()) + b.WriteByte('.') + b.WriteString(hex.EncodeToString(name.Bytes())) + b.WriteByte('=') + fmt.Fprintf(&b, "%d", assets[name]) + } + } + b.WriteByte(']') + return b.String() +} + type AssetFingerprint struct { policyId []byte assetName []byte diff --git a/ledger/mary/mary.go b/ledger/mary/mary.go index 904d4c78..cf1d9409 100644 --- a/ledger/mary/mary.go +++ b/ledger/mary/mary.go @@ -493,7 +493,7 @@ func (o MaryTransactionOutput) Utxorpc() (*utxorpc.TxOutput, error) { func (o MaryTransactionOutput) String() string { assets := "" if o.OutputAmount.Assets != nil && len(o.OutputAmount.Assets.Policies()) > 0 { - assets = " assets=..." + assets = fmt.Sprintf(" assets=%s", o.OutputAmount.Assets.String()) } return fmt.Sprintf( "(MaryTransactionOutput address=%s amount=%d%s)", diff --git a/ledger/mary/mary_test.go b/ledger/mary/mary_test.go index bde402ec..ef4ad79c 100644 --- a/ledger/mary/mary_test.go +++ b/ledger/mary/mary_test.go @@ -118,7 +118,8 @@ func TestMaryTransactionOutputValueEncodeDecode(t *testing.T) { } func TestMaryTransactionOutputString(t *testing.T) { - addr, _ := common.NewAddress("addr1qytna5k2fq9ler0fuk45j7zfwv7t2zwhp777nvdjqqfr5tz8ztpwnk8zq5ngetcz5k5mckgkajnygtsra9aej2h3ek5seupmvd") + addrStr := "addr1qytna5k2fq9ler0fuk45j7zfwv7t2zwhp777nvdjqqfr5tz8ztpwnk8zq5ngetcz5k5mckgkajnygtsra9aej2h3ek5seupmvd" + addr, _ := common.NewAddress(addrStr) ma := common.NewMultiAsset[common.MultiAssetTypeOutput]( map[common.Blake2b224]map[cbor.ByteString]uint64{ common.NewBlake2b224(make([]byte, 28)): {cbor.NewByteString([]byte("token")): 2}, @@ -129,20 +130,21 @@ func TestMaryTransactionOutputString(t *testing.T) { OutputAmount: MaryTransactionOutputValue{Amount: 456, Assets: &ma}, } s := out.String() - expected := fmt.Sprintf("(MaryTransactionOutput address=%s amount=456 assets=...)", addr.String()) + expected := fmt.Sprintf("(MaryTransactionOutput address=%s amount=456 assets=%s)", addrStr, ma.String()) if s != expected { t.Fatalf("unexpected string: %s", s) } } func TestMaryOutputTooBigErrorFormatting(t *testing.T) { - addr, _ := common.NewAddress("addr1qytna5k2fq9ler0fuk45j7zfwv7t2zwhp777nvdjqqfr5tz8ztpwnk8zq5ngetcz5k5mckgkajnygtsra9aej2h3ek5seupmvd") + addrStr := "addr1qytna5k2fq9ler0fuk45j7zfwv7t2zwhp777nvdjqqfr5tz8ztpwnk8zq5ngetcz5k5mckgkajnygtsra9aej2h3ek5seupmvd" + addr, _ := common.NewAddress(addrStr) out := &MaryTransactionOutput{ OutputAddress: addr, OutputAmount: MaryTransactionOutputValue{Amount: 456}, } errStr := OutputTooBigUtxoError{Outputs: []common.TransactionOutput{out}}.Error() - expected := fmt.Sprintf("output value too large: (MaryTransactionOutput address=%s amount=456)", addr.String()) + expected := fmt.Sprintf("output value too large: (MaryTransactionOutput address=%s amount=456)", addrStr) if errStr != expected { t.Fatalf("unexpected error: %s", errStr) } diff --git a/ledger/shelley/shelley_test.go b/ledger/shelley/shelley_test.go index 981536a0..dbc2ba67 100644 --- a/ledger/shelley/shelley_test.go +++ b/ledger/shelley/shelley_test.go @@ -9,26 +9,28 @@ import ( ) func TestShelleyTransactionOutputString(t *testing.T) { - addr, _ := common.NewAddress("addr1qytna5k2fq9ler0fuk45j7zfwv7t2zwhp777nvdjqqfr5tz8ztpwnk8zq5ngetcz5k5mckgkajnygtsra9aej2h3ek5seupmvd") + addrStr := "addr1qytna5k2fq9ler0fuk45j7zfwv7t2zwhp777nvdjqqfr5tz8ztpwnk8zq5ngetcz5k5mckgkajnygtsra9aej2h3ek5seupmvd" + addr, _ := common.NewAddress(addrStr) out := shelley.ShelleyTransactionOutput{ OutputAddress: addr, OutputAmount: 456, } s := out.String() - expected := fmt.Sprintf("(ShelleyTransactionOutput address=%s amount=456)", addr.String()) + expected := fmt.Sprintf("(ShelleyTransactionOutput address=%s amount=456)", addrStr) if s != expected { t.Fatalf("unexpected string: %s", s) } } func TestShelleyOutputTooSmallErrorFormatting(t *testing.T) { - addr, _ := common.NewAddress("addr1qytna5k2fq9ler0fuk45j7zfwv7t2zwhp777nvdjqqfr5tz8ztpwnk8zq5ngetcz5k5mckgkajnygtsra9aej2h3ek5seupmvd") + addrStr := "addr1qytna5k2fq9ler0fuk45j7zfwv7t2zwhp777nvdjqqfr5tz8ztpwnk8zq5ngetcz5k5mckgkajnygtsra9aej2h3ek5seupmvd" + addr, _ := common.NewAddress(addrStr) out := &shelley.ShelleyTransactionOutput{ OutputAddress: addr, OutputAmount: 456, } errStr := shelley.OutputTooSmallUtxoError{Outputs: []common.TransactionOutput{out}}.Error() - expected := fmt.Sprintf("output too small: (ShelleyTransactionOutput address=%s amount=456)", addr.String()) + expected := fmt.Sprintf("output too small: (ShelleyTransactionOutput address=%s amount=456)", addrStr) if errStr != expected { t.Fatalf("unexpected error: %s", errStr) } From 57ff1566774ffd1415a2f9e6debcfd7bc39bcffe Mon Sep 17 00:00:00 2001 From: Akhil Repala Date: Sun, 7 Sep 2025 17:29:50 -0500 Subject: [PATCH 4/4] feat(ledger): Replace fmt.Sprintf with with assets=+assets to fix the golangcilint error Signed-off-by: Akhil Repala --- ledger/alonzo/alonzo.go | 6 ++++-- ledger/babbage/babbage.go | 6 ++++-- ledger/mary/mary.go | 6 ++++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/ledger/alonzo/alonzo.go b/ledger/alonzo/alonzo.go index 479c23f5..e1ce1400 100644 --- a/ledger/alonzo/alonzo.go +++ b/ledger/alonzo/alonzo.go @@ -449,8 +449,10 @@ func (o AlonzoTransactionOutput) Utxorpc() (*utxorpc.TxOutput, error) { func (o AlonzoTransactionOutput) String() string { assets := "" - if o.OutputAmount.Assets != nil && len(o.OutputAmount.Assets.Policies()) > 0 { - assets = fmt.Sprintf(" assets=%s", o.OutputAmount.Assets.String()) + if o.OutputAmount.Assets != nil { + if as := o.OutputAmount.Assets.String(); as != "[]" { + assets = " assets=" + as + } } return fmt.Sprintf( "(AlonzoTransactionOutput address=%s amount=%d%s)", diff --git a/ledger/babbage/babbage.go b/ledger/babbage/babbage.go index 5988e89b..8fc155cd 100644 --- a/ledger/babbage/babbage.go +++ b/ledger/babbage/babbage.go @@ -651,8 +651,10 @@ func (o BabbageTransactionOutput) Utxorpc() (*utxorpc.TxOutput, error) { func (o BabbageTransactionOutput) String() string { assets := "" - if o.OutputAmount.Assets != nil && len(o.OutputAmount.Assets.Policies()) > 0 { - assets = fmt.Sprintf(" assets=%s", o.OutputAmount.Assets.String()) + if o.OutputAmount.Assets != nil { + if as := o.OutputAmount.Assets.String(); as != "[]" { + assets = " assets=" + as + } } return fmt.Sprintf( "(BabbageTransactionOutput address=%s amount=%d%s)", diff --git a/ledger/mary/mary.go b/ledger/mary/mary.go index cf1d9409..a7ff2cb9 100644 --- a/ledger/mary/mary.go +++ b/ledger/mary/mary.go @@ -492,8 +492,10 @@ func (o MaryTransactionOutput) Utxorpc() (*utxorpc.TxOutput, error) { func (o MaryTransactionOutput) String() string { assets := "" - if o.OutputAmount.Assets != nil && len(o.OutputAmount.Assets.Policies()) > 0 { - assets = fmt.Sprintf(" assets=%s", o.OutputAmount.Assets.String()) + if o.OutputAmount.Assets != nil { + if as := o.OutputAmount.Assets.String(); as != "[]" { + assets = " assets=" + as + } } return fmt.Sprintf( "(MaryTransactionOutput address=%s amount=%d%s)",