Skip to content

Commit 543fcbc

Browse files
authored
feat(ledger): Add friendly string representation of TX outputs (#1174)
Signed-off-by: Akhil Repala <[email protected]>
1 parent 5ae56b1 commit 543fcbc

File tree

14 files changed

+237
-4
lines changed

14 files changed

+237
-4
lines changed

ledger/alonzo/alonzo.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,21 @@ func (o AlonzoTransactionOutput) Utxorpc() (*utxorpc.TxOutput, error) {
445445
nil
446446
}
447447

448+
func (o AlonzoTransactionOutput) String() string {
449+
assets := ""
450+
if o.OutputAmount.Assets != nil {
451+
if as := o.OutputAmount.Assets.String(); as != "[]" {
452+
assets = " assets=" + as
453+
}
454+
}
455+
return fmt.Sprintf(
456+
"(AlonzoTransactionOutput address=%s amount=%d%s)",
457+
o.OutputAddress.String(),
458+
o.OutputAmount.Amount,
459+
assets,
460+
)
461+
}
462+
448463
type AlonzoRedeemer struct {
449464
cbor.StructAsArray
450465
Tag common.RedeemerTag

ledger/alonzo/alonzo_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package alonzo
1616

1717
import (
18+
"encoding/hex"
1819
"math/big"
1920
"reflect"
2021
"testing"
@@ -277,3 +278,24 @@ func TestAlonzoRedeemersIter(t *testing.T) {
277278
iterIdx++
278279
}
279280
}
281+
282+
func TestAlonzoTransactionOutputString(t *testing.T) {
283+
addrStr := "addr1qytna5k2fq9ler0fuk45j7zfwv7t2zwhp777nvdjqqfr5tz8ztpwnk8zq5ngetcz5k5mckgkajnygtsra9aej2h3ek5seupmvd"
284+
addr, _ := common.NewAddress(addrStr)
285+
ma := common.NewMultiAsset[common.MultiAssetTypeOutput](
286+
map[common.Blake2b224]map[cbor.ByteString]uint64{
287+
common.NewBlake2b224(make([]byte, 28)): {cbor.NewByteString([]byte("t")): 2},
288+
},
289+
)
290+
out := AlonzoTransactionOutput{
291+
OutputAddress: addr,
292+
OutputAmount: mary.MaryTransactionOutputValue{Amount: 456, Assets: &ma},
293+
}
294+
s := out.String()
295+
policyStr := common.NewBlake2b224(make([]byte, 28)).String()
296+
assetsStr := "[" + policyStr + "." + hex.EncodeToString([]byte("t")) + "=2]"
297+
expected := "(AlonzoTransactionOutput address=" + addrStr + " amount=456 assets=" + assetsStr + ")"
298+
if s != expected {
299+
t.Fatalf("unexpected string: %s", s)
300+
}
301+
}

ledger/babbage/babbage.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -647,6 +647,21 @@ func (o BabbageTransactionOutput) Utxorpc() (*utxorpc.TxOutput, error) {
647647
nil
648648
}
649649

650+
func (o BabbageTransactionOutput) String() string {
651+
assets := ""
652+
if o.OutputAmount.Assets != nil {
653+
if as := o.OutputAmount.Assets.String(); as != "[]" {
654+
assets = " assets=" + as
655+
}
656+
}
657+
return fmt.Sprintf(
658+
"(BabbageTransactionOutput address=%s amount=%d%s)",
659+
o.OutputAddress.String(),
660+
o.OutputAmount.Amount,
661+
assets,
662+
)
663+
}
664+
650665
type BabbageTransactionWitnessSet struct {
651666
cbor.DecodeStoreCbor
652667
VkeyWitnesses []common.VkeyWitness `cbor:"0,keyasint,omitempty"`

ledger/babbage/babbage_test.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2970,3 +2970,22 @@ func TestBabbageTransactionOutputToPlutusDataCoinAssets(t *testing.T) {
29702970
)
29712971
}
29722972
}
2973+
2974+
func TestBabbageTransactionOutputString(t *testing.T) {
2975+
addrStr := "addr1qytna5k2fq9ler0fuk45j7zfwv7t2zwhp777nvdjqqfr5tz8ztpwnk8zq5ngetcz5k5mckgkajnygtsra9aej2h3ek5seupmvd"
2976+
addr, _ := common.NewAddress(addrStr)
2977+
ma := common.NewMultiAsset[common.MultiAssetTypeOutput](
2978+
map[common.Blake2b224]map[cbor.ByteString]uint64{
2979+
common.NewBlake2b224(make([]byte, 28)): {cbor.NewByteString([]byte("x")): 2},
2980+
},
2981+
)
2982+
out := BabbageTransactionOutput{
2983+
OutputAddress: addr,
2984+
OutputAmount: mary.MaryTransactionOutputValue{Amount: 456, Assets: &ma},
2985+
}
2986+
s := out.String()
2987+
expected := "(BabbageTransactionOutput address=" + addrStr + " amount=456 assets=" + ma.String() + ")"
2988+
if s != expected {
2989+
t.Fatalf("unexpected string: %s", s)
2990+
}
2991+
}

ledger/byron/byron.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,14 @@ func (o ByronTransactionOutput) Utxorpc() (*utxorpc.TxOutput, error) {
489489
nil
490490
}
491491

492+
func (o ByronTransactionOutput) String() string {
493+
return fmt.Sprintf(
494+
"(ByronTransactionOutput address=%s amount=%d)",
495+
o.OutputAddress.String(),
496+
o.OutputAmount,
497+
)
498+
}
499+
492500
type ByronBlockVersion struct {
493501
cbor.StructAsArray
494502
Major uint16

ledger/byron/byron_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,3 +104,24 @@ func TestByronTransaction_Utxorpc_Empty(t *testing.T) {
104104
t.Errorf("Expected fee = 0, got %d", result.Fee)
105105
}
106106
}
107+
108+
func TestByronTransactionOutputString(t *testing.T) {
109+
addr, err := common.NewByronAddressFromParts(
110+
0,
111+
make([]byte, common.AddressHashSize),
112+
common.ByronAddressAttributes{},
113+
)
114+
if err != nil {
115+
t.Fatalf("address: %v", err)
116+
}
117+
addrStr := addr.String()
118+
out := byron.ByronTransactionOutput{
119+
OutputAddress: addr,
120+
OutputAmount: 456,
121+
}
122+
s := out.String()
123+
expected := "(ByronTransactionOutput address=" + addrStr + " amount=456)"
124+
if s != expected {
125+
t.Fatalf("unexpected string: %s", s)
126+
}
127+
}

ledger/common/common.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"maps"
2323
"math/big"
2424
"slices"
25+
"strings"
2526

2627
"github.com/blinklabs-io/gouroboros/cbor"
2728
"github.com/blinklabs-io/plutigo/data"
@@ -325,6 +326,44 @@ func (m *MultiAsset[T]) normalize() map[Blake2b224]map[cbor.ByteString]T {
325326
return ret
326327
}
327328

329+
// String returns a stable, human-friendly representation of the MultiAsset.
330+
// Output format: [<policyId>.<assetNameHex>=<amount>, ...] sorted by policyId, then asset name
331+
func (m *MultiAsset[T]) String() string {
332+
if m == nil {
333+
return "[]"
334+
}
335+
norm := m.normalize()
336+
if len(norm) == 0 {
337+
return "[]"
338+
}
339+
340+
policies := slices.Collect(maps.Keys(norm))
341+
slices.SortFunc(policies, func(a, b Blake2b224) int { return bytes.Compare(a.Bytes(), b.Bytes()) })
342+
343+
var b strings.Builder
344+
b.WriteByte('[')
345+
first := true
346+
for _, pid := range policies {
347+
assets := norm[pid]
348+
names := slices.Collect(maps.Keys(assets))
349+
slices.SortFunc(names, func(a, b cbor.ByteString) int { return bytes.Compare(a.Bytes(), b.Bytes()) })
350+
351+
for _, name := range names {
352+
if !first {
353+
b.WriteString(", ")
354+
}
355+
first = false
356+
b.WriteString(pid.String())
357+
b.WriteByte('.')
358+
b.WriteString(hex.EncodeToString(name.Bytes()))
359+
b.WriteByte('=')
360+
fmt.Fprintf(&b, "%d", assets[name])
361+
}
362+
}
363+
b.WriteByte(']')
364+
return b.String()
365+
}
366+
328367
type AssetFingerprint struct {
329368
policyId []byte
330369
assetName []byte

ledger/common/tx.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ type TransactionOutput interface {
7777
Utxorpc() (*utxorpc.TxOutput, error)
7878
ScriptRef() Script
7979
ToPlutusData() data.PlutusData
80+
String() string
8081
}
8182

8283
type TransactionWitnessSet interface {

ledger/mary/errors.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
package mary
1616

1717
import (
18-
"fmt"
1918
"strings"
2019

2120
"github.com/blinklabs-io/gouroboros/ledger/common"
@@ -28,7 +27,7 @@ type OutputTooBigUtxoError struct {
2827
func (e OutputTooBigUtxoError) Error() string {
2928
tmpOutputs := make([]string, len(e.Outputs))
3029
for idx, tmpOutput := range e.Outputs {
31-
tmpOutputs[idx] = fmt.Sprintf("%#v", tmpOutput)
30+
tmpOutputs[idx] = tmpOutput.String()
3231
}
3332
return "output value too large: " + strings.Join(tmpOutputs, ", ")
3433
}

ledger/mary/mary.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,21 @@ func (o MaryTransactionOutput) Utxorpc() (*utxorpc.TxOutput, error) {
529529
err
530530
}
531531

532+
func (o MaryTransactionOutput) String() string {
533+
assets := ""
534+
if o.OutputAmount.Assets != nil {
535+
if as := o.OutputAmount.Assets.String(); as != "[]" {
536+
assets = " assets=" + as
537+
}
538+
}
539+
return fmt.Sprintf(
540+
"(MaryTransactionOutput address=%s amount=%d%s)",
541+
o.OutputAddress.String(),
542+
o.OutputAmount.Amount,
543+
assets,
544+
)
545+
}
546+
532547
type MaryTransactionOutputValue struct {
533548
cbor.StructAsArray
534549
Amount uint64

0 commit comments

Comments
 (0)