Skip to content

Commit 910b0da

Browse files
authored
perf(ledger): preallocate tx input/output slices (#1253)
Also, use CBOR major type (map vs list) to determine transaction processing path. Signed-off-by: Chris Gianelloni <[email protected]>
1 parent 47e67cc commit 910b0da

File tree

5 files changed

+95
-60
lines changed

5 files changed

+95
-60
lines changed

ledger/alonzo/alonzo.go

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -233,17 +233,18 @@ func (b *AlonzoTransactionBody) UnmarshalCBOR(cborData []byte) error {
233233
}
234234

235235
func (b *AlonzoTransactionBody) Inputs() []common.TransactionInput {
236-
ret := []common.TransactionInput{}
237-
for _, input := range b.TxInputs.Items() {
238-
ret = append(ret, input)
236+
items := b.TxInputs.Items()
237+
ret := make([]common.TransactionInput, len(items))
238+
for i, input := range items {
239+
ret[i] = input
239240
}
240241
return ret
241242
}
242243

243244
func (b *AlonzoTransactionBody) Outputs() []common.TransactionOutput {
244-
ret := []common.TransactionOutput{}
245-
for _, output := range b.TxOutputs {
246-
ret = append(ret, &output)
245+
ret := make([]common.TransactionOutput, len(b.TxOutputs))
246+
for i := range b.TxOutputs {
247+
ret[i] = &b.TxOutputs[i]
247248
}
248249
return ret
249250
}
@@ -292,9 +293,10 @@ func (b *AlonzoTransactionBody) AssetMint() *common.MultiAsset[common.MultiAsset
292293
}
293294

294295
func (b *AlonzoTransactionBody) Collateral() []common.TransactionInput {
295-
ret := []common.TransactionInput{}
296-
for _, collateral := range b.TxCollateral.Items() {
297-
ret = append(ret, collateral)
296+
items := b.TxCollateral.Items()
297+
ret := make([]common.TransactionInput, len(items))
298+
for i, collateral := range items {
299+
ret[i] = collateral
298300
}
299301
return ret
300302
}

ledger/babbage/babbage.go

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -276,17 +276,18 @@ func (b *BabbageTransactionBody) Id() common.Blake2b256 {
276276
}
277277

278278
func (b *BabbageTransactionBody) Inputs() []common.TransactionInput {
279-
ret := []common.TransactionInput{}
280-
for _, input := range b.TxInputs.Items() {
281-
ret = append(ret, input)
279+
items := b.TxInputs.Items()
280+
ret := make([]common.TransactionInput, len(items))
281+
for i, input := range items {
282+
ret[i] = input
282283
}
283284
return ret
284285
}
285286

286287
func (b *BabbageTransactionBody) Outputs() []common.TransactionOutput {
287-
ret := []common.TransactionOutput{}
288-
for _, output := range b.TxOutputs {
289-
ret = append(ret, &output)
288+
ret := make([]common.TransactionOutput, len(b.TxOutputs))
289+
for i := range b.TxOutputs {
290+
ret[i] = &b.TxOutputs[i]
290291
}
291292
return ret
292293
}
@@ -335,9 +336,10 @@ func (b *BabbageTransactionBody) AssetMint() *common.MultiAsset[common.MultiAsse
335336
}
336337

337338
func (b *BabbageTransactionBody) Collateral() []common.TransactionInput {
338-
ret := []common.TransactionInput{}
339-
for _, collateral := range b.TxCollateral.Items() {
340-
ret = append(ret, collateral)
339+
items := b.TxCollateral.Items()
340+
ret := make([]common.TransactionInput, len(items))
341+
for i, collateral := range items {
342+
ret[i] = collateral
341343
}
342344
return ret
343345
}
@@ -351,9 +353,10 @@ func (b *BabbageTransactionBody) ScriptDataHash() *common.Blake2b256 {
351353
}
352354

353355
func (b *BabbageTransactionBody) ReferenceInputs() []common.TransactionInput {
354-
ret := []common.TransactionInput{}
355-
for _, input := range b.TxReferenceInputs.Items() {
356-
ret = append(ret, &input)
356+
items := b.TxReferenceInputs.Items()
357+
ret := make([]common.TransactionInput, len(items))
358+
for i := range items {
359+
ret[i] = &items[i]
357360
}
358361
return ret
359362
}

ledger/shelley/shelley.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -239,17 +239,18 @@ func (b *ShelleyTransactionBody) UnmarshalCBOR(cborData []byte) error {
239239
}
240240

241241
func (b *ShelleyTransactionBody) Inputs() []common.TransactionInput {
242-
ret := []common.TransactionInput{}
243-
for _, input := range b.TxInputs.Items() {
244-
ret = append(ret, input)
242+
items := b.TxInputs.Items()
243+
ret := make([]common.TransactionInput, len(items))
244+
for i, input := range items {
245+
ret[i] = input
245246
}
246247
return ret
247248
}
248249

249250
func (b *ShelleyTransactionBody) Outputs() []common.TransactionOutput {
250-
ret := []common.TransactionOutput{}
251-
for _, output := range b.TxOutputs {
252-
ret = append(ret, &output)
251+
ret := make([]common.TransactionOutput, len(b.TxOutputs))
252+
for i := range b.TxOutputs {
253+
ret[i] = &b.TxOutputs[i]
253254
}
254255
return ret
255256
}

ledger/tx.go

Lines changed: 55 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -80,46 +80,69 @@ func NewTransactionBodyFromCbor(
8080
// NewTransactionOutputFromCbor attempts to parse the provided arbitrary CBOR data as a transaction output from
8181
// each of the eras, returning the first one that we can successfully decode
8282
func NewTransactionOutputFromCbor(data []byte) (TransactionOutput, error) {
83-
if txOut, err := NewByronTransactionOutputFromCbor(data); err == nil {
84-
return txOut, nil
83+
if len(data) == 0 {
84+
return nil, errors.New("unknown transaction output type")
8585
}
86-
if txOut, err := NewShelleyTransactionOutputFromCbor(data); err == nil {
87-
return txOut, nil
88-
}
89-
if txOut, err := NewMaryTransactionOutputFromCbor(data); err == nil {
90-
return txOut, nil
91-
}
92-
if txOut, err := NewAlonzoTransactionOutputFromCbor(data); err == nil {
93-
return txOut, nil
94-
}
95-
if txOut, err := NewBabbageTransactionOutputFromCbor(data); err == nil {
96-
return txOut, nil
86+
// Check CBOR major type: 4 = array (Byron, Shelley, Mary, Alonzo), 5 = map (Babbage+)
87+
majorType := data[0] >> 5
88+
switch majorType {
89+
case 4:
90+
// Try array-encoded outputs: Byron, Shelley, Mary, Alonzo
91+
if txOut, err := NewByronTransactionOutputFromCbor(data); err == nil {
92+
return txOut, nil
93+
}
94+
if txOut, err := NewShelleyTransactionOutputFromCbor(data); err == nil {
95+
return txOut, nil
96+
}
97+
if txOut, err := NewMaryTransactionOutputFromCbor(data); err == nil {
98+
return txOut, nil
99+
}
100+
if txOut, err := NewAlonzoTransactionOutputFromCbor(data); err == nil {
101+
return txOut, nil
102+
}
103+
case 5:
104+
// Try map-encoded outputs: Babbage+
105+
if txOut, err := NewBabbageTransactionOutputFromCbor(data); err == nil {
106+
return txOut, nil
107+
}
97108
}
98109
return nil, errors.New("unknown transaction output type")
99110
}
100111

101112
func DetermineTransactionType(data []byte) (uint, error) {
102-
if _, err := NewByronTransactionFromCbor(data); err == nil {
103-
return TxTypeByron, nil
104-
}
105-
if _, err := NewShelleyTransactionFromCbor(data); err == nil {
106-
return TxTypeShelley, nil
107-
}
108-
if _, err := NewAllegraTransactionFromCbor(data); err == nil {
109-
return TxTypeAllegra, nil
113+
if len(data) == 0 {
114+
return 0, errors.New("unknown transaction type")
110115
}
111-
if _, err := NewMaryTransactionFromCbor(data); err == nil {
112-
return TxTypeMary, nil
113-
}
114-
if _, err := NewAlonzoTransactionFromCbor(data); err == nil {
115-
return TxTypeAlonzo, nil
116-
}
117-
if _, err := NewBabbageTransactionFromCbor(data); err == nil {
118-
return TxTypeBabbage, nil
119-
}
120-
if _, err := NewConwayTransactionFromCbor(data); err == nil {
121-
return TxTypeConway, nil
116+
// Fast path: check first byte to distinguish Byron from post-Shelley eras
117+
firstByte := data[0]
118+
switch firstByte {
119+
case 0x83: // Byron, Shelley, Allegra, and Mary transactions are 3-element arrays
120+
if _, err := NewByronTransactionFromCbor(data); err == nil {
121+
return TxTypeByron, nil
122+
}
123+
if _, err := NewShelleyTransactionFromCbor(data); err == nil {
124+
return TxTypeShelley, nil
125+
}
126+
if _, err := NewAllegraTransactionFromCbor(data); err == nil {
127+
return TxTypeAllegra, nil
128+
}
129+
if _, err := NewMaryTransactionFromCbor(data); err == nil {
130+
return TxTypeMary, nil
131+
}
132+
case 0x84: // Alonzo+ transactions are 4-element arrays
133+
// Try most recent eras first for better performance
134+
if _, err := NewConwayTransactionFromCbor(data); err == nil {
135+
return TxTypeConway, nil
136+
}
137+
if _, err := NewBabbageTransactionFromCbor(data); err == nil {
138+
return TxTypeBabbage, nil
139+
}
140+
if _, err := NewAlonzoTransactionFromCbor(data); err == nil {
141+
return TxTypeAlonzo, nil
142+
}
122143
}
144+
145+
// Fallback: try Leios in case our assumptions are wrong
123146
if _, err := NewLeiosTransactionFromCbor(data); err == nil {
124147
return TxTypeLeios, nil
125148
}

ledger/tx_test.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2023 Blink Labs Software
1+
// Copyright 2025 Blink Labs Software
22
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.
@@ -154,6 +154,9 @@ func BenchmarkTransactionSerialization_Byron(b *testing.B) {
154154
if err != nil {
155155
b.Fatal(err)
156156
}
157+
if tx == nil {
158+
b.Fatal("tx is nil")
159+
}
157160
b.ResetTimer()
158161
for i := 0; i < b.N; i++ {
159162
_ = tx.Cbor()
@@ -177,6 +180,9 @@ func BenchmarkTransactionSerialization_Conway(b *testing.B) {
177180
if err != nil {
178181
b.Fatal(err)
179182
}
183+
if tx == nil {
184+
b.Fatal("tx is nil")
185+
}
180186
b.ResetTimer()
181187
for i := 0; i < b.N; i++ {
182188
_ = tx.Cbor()

0 commit comments

Comments
 (0)