Skip to content

Commit a33de47

Browse files
authored
refactor: allow easier iteration of redeemers (#1127)
This also moves the Conway redeemer key/value types to common so they can be reused for iteration Fixes #1126 Signed-off-by: Aurora Gaffney <[email protected]>
1 parent 519c705 commit a33de47

File tree

8 files changed

+318
-39
lines changed

8 files changed

+318
-39
lines changed

ledger/alonzo/alonzo.go

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ package alonzo
1717
import (
1818
"encoding/json"
1919
"fmt"
20+
"iter"
2021
"math/big"
22+
"slices"
2123

2224
"github.com/blinklabs-io/gouroboros/cbor"
2325
"github.com/blinklabs-io/gouroboros/ledger/common"
@@ -453,6 +455,40 @@ type AlonzoRedeemer struct {
453455

454456
type AlonzoRedeemers []AlonzoRedeemer
455457

458+
func (r AlonzoRedeemers) Iter() iter.Seq2[common.RedeemerKey, common.RedeemerValue] {
459+
return func(yield func(common.RedeemerKey, common.RedeemerValue) bool) {
460+
// Sort redeemers
461+
sorted := make([]AlonzoRedeemer, len(r))
462+
copy(sorted, r)
463+
slices.SortFunc(
464+
sorted,
465+
func(a, b AlonzoRedeemer) int {
466+
if a.Tag < b.Tag || (a.Tag == b.Tag && a.Index < b.Index) {
467+
return -1
468+
}
469+
if a.Tag > b.Tag || (a.Tag == b.Tag && a.Index > b.Index) {
470+
return 1
471+
}
472+
return 0
473+
},
474+
)
475+
// Yield keys
476+
for _, redeemer := range sorted {
477+
tmpKey := common.RedeemerKey{
478+
Tag: redeemer.Tag,
479+
Index: redeemer.Index,
480+
}
481+
tmpVal := common.RedeemerValue{
482+
Data: redeemer.Data,
483+
ExUnits: redeemer.ExUnits,
484+
}
485+
if !yield(tmpKey, tmpVal) {
486+
return
487+
}
488+
}
489+
}
490+
}
491+
456492
func (r AlonzoRedeemers) Indexes(tag common.RedeemerTag) []uint {
457493
ret := []uint{}
458494
for _, redeemer := range r {
@@ -466,13 +502,16 @@ func (r AlonzoRedeemers) Indexes(tag common.RedeemerTag) []uint {
466502
func (r AlonzoRedeemers) Value(
467503
index uint,
468504
tag common.RedeemerTag,
469-
) (cbor.LazyValue, common.ExUnits) {
505+
) common.RedeemerValue {
470506
for _, redeemer := range r {
471507
if redeemer.Tag == tag && uint(redeemer.Index) == index {
472-
return redeemer.Data, redeemer.ExUnits
508+
return common.RedeemerValue{
509+
Data: redeemer.Data,
510+
ExUnits: redeemer.ExUnits,
511+
}
473512
}
474513
}
475-
return cbor.LazyValue{}, common.ExUnits{}
514+
return common.RedeemerValue{}
476515
}
477516

478517
type AlonzoTransactionWitnessSet struct {

ledger/alonzo/alonzo_test.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,3 +164,84 @@ func TestAlonzoTransactionOutputToPlutusDataCoinAssets(t *testing.T) {
164164
t.Fatalf("did not get expected PlutusData\n got: %#v\n wanted: %#v", tmpData, expectedData)
165165
}
166166
}
167+
168+
func TestAlonzoRedeemersIter(t *testing.T) {
169+
testRedeemers := AlonzoRedeemers{
170+
{
171+
Tag: common.RedeemerTagMint,
172+
Index: 2,
173+
ExUnits: common.ExUnits{
174+
Memory: 1111,
175+
Steps: 2222,
176+
},
177+
},
178+
{
179+
Tag: common.RedeemerTagMint,
180+
Index: 0,
181+
ExUnits: common.ExUnits{
182+
Memory: 1111,
183+
Steps: 0,
184+
},
185+
},
186+
{
187+
Tag: common.RedeemerTagSpend,
188+
Index: 4,
189+
ExUnits: common.ExUnits{
190+
Memory: 0,
191+
Steps: 4444,
192+
},
193+
},
194+
}
195+
expectedOrder := []struct {
196+
Key common.RedeemerKey
197+
Value common.RedeemerValue
198+
}{
199+
{
200+
Key: common.RedeemerKey{
201+
Tag: common.RedeemerTagSpend,
202+
Index: 4,
203+
},
204+
Value: common.RedeemerValue{
205+
ExUnits: common.ExUnits{
206+
Memory: 0,
207+
Steps: 4444,
208+
},
209+
},
210+
},
211+
{
212+
Key: common.RedeemerKey{
213+
Tag: common.RedeemerTagMint,
214+
Index: 0,
215+
},
216+
Value: common.RedeemerValue{
217+
ExUnits: common.ExUnits{
218+
Memory: 1111,
219+
Steps: 0,
220+
},
221+
},
222+
},
223+
{
224+
Key: common.RedeemerKey{
225+
Tag: common.RedeemerTagMint,
226+
Index: 2,
227+
},
228+
Value: common.RedeemerValue{
229+
ExUnits: common.ExUnits{
230+
Memory: 1111,
231+
Steps: 2222,
232+
},
233+
},
234+
},
235+
}
236+
iterIdx := 0
237+
for key, val := range testRedeemers.Iter() {
238+
expected := expectedOrder[iterIdx]
239+
if !reflect.DeepEqual(key, expected.Key) {
240+
t.Fatalf("did not get expected key: got %#v, wanted %#v", key, expected.Key)
241+
}
242+
if !reflect.DeepEqual(val, expected.Value) {
243+
t.Fatalf("did not get expected value: got %#v, wanted %#v", val, expected.Value)
244+
}
245+
iterIdx++
246+
}
247+
}

ledger/common/redeemer.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Copyright 2025 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 common
16+
17+
import (
18+
"github.com/blinklabs-io/gouroboros/cbor"
19+
)
20+
21+
type RedeemerTag uint8
22+
23+
const (
24+
RedeemerTagSpend RedeemerTag = 0
25+
RedeemerTagMint RedeemerTag = 1
26+
RedeemerTagCert RedeemerTag = 2
27+
RedeemerTagReward RedeemerTag = 3
28+
RedeemerTagVoting RedeemerTag = 4
29+
RedeemerTagProposing RedeemerTag = 5
30+
)
31+
32+
type RedeemerKey struct {
33+
cbor.StructAsArray
34+
Tag RedeemerTag
35+
Index uint32
36+
}
37+
38+
type RedeemerValue struct {
39+
cbor.StructAsArray
40+
Data cbor.LazyValue
41+
ExUnits ExUnits
42+
}

ledger/common/tx.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
package common
1616

1717
import (
18+
"iter"
19+
1820
"github.com/blinklabs-io/gouroboros/cbor"
1921
"github.com/blinklabs-io/plutigo/data"
2022
utxorpc "github.com/utxorpc/go-codegen/utxorpc/v1alpha/cardano"
@@ -90,7 +92,8 @@ type TransactionWitnessSet interface {
9092

9193
type TransactionWitnessRedeemers interface {
9294
Indexes(RedeemerTag) []uint
93-
Value(uint, RedeemerTag) (cbor.LazyValue, ExUnits)
95+
Value(uint, RedeemerTag) RedeemerValue
96+
Iter() iter.Seq2[RedeemerKey, RedeemerValue]
9497
}
9598

9699
type Utxo struct {

ledger/common/witness.go

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,6 @@ import (
1818
"github.com/blinklabs-io/gouroboros/cbor"
1919
)
2020

21-
type RedeemerTag uint8
22-
23-
const (
24-
RedeemerTagSpend RedeemerTag = 0
25-
RedeemerTagMint RedeemerTag = 1
26-
RedeemerTagCert RedeemerTag = 2
27-
RedeemerTagReward RedeemerTag = 3
28-
RedeemerTagVoting RedeemerTag = 4
29-
RedeemerTagProposing RedeemerTag = 5
30-
)
31-
3221
type VkeyWitness struct {
3322
cbor.StructAsArray
3423
Vkey []byte

ledger/conway/conway.go

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ package conway
1717
import (
1818
"errors"
1919
"fmt"
20+
"iter"
21+
"maps"
22+
"slices"
2023

2124
"github.com/blinklabs-io/gouroboros/cbor"
2225
"github.com/blinklabs-io/gouroboros/ledger/alonzo"
@@ -154,20 +157,8 @@ func (h *ConwayBlockHeader) Era() common.Era {
154157
return EraConway
155158
}
156159

157-
type ConwayRedeemerKey struct {
158-
cbor.StructAsArray
159-
Tag common.RedeemerTag
160-
Index uint32
161-
}
162-
163-
type ConwayRedeemerValue struct {
164-
cbor.StructAsArray
165-
Data cbor.LazyValue
166-
ExUnits common.ExUnits
167-
}
168-
169160
type ConwayRedeemers struct {
170-
Redeemers map[ConwayRedeemerKey]ConwayRedeemerValue
161+
Redeemers map[common.RedeemerKey]common.RedeemerValue
171162
legacyRedeemers alonzo.AlonzoRedeemers
172163
legacy bool
173164
}
@@ -190,6 +181,32 @@ func (r *ConwayRedeemers) MarshalCBOR() ([]byte, error) {
190181
return cbor.Encode(r.Redeemers)
191182
}
192183

184+
func (r ConwayRedeemers) Iter() iter.Seq2[common.RedeemerKey, common.RedeemerValue] {
185+
return func(yield func(common.RedeemerKey, common.RedeemerValue) bool) {
186+
// Sort redeemers
187+
sorted := slices.Collect(maps.Keys(r.Redeemers))
188+
slices.SortFunc(
189+
sorted,
190+
func(a, b common.RedeemerKey) int {
191+
if a.Tag < b.Tag || (a.Tag == b.Tag && a.Index < b.Index) {
192+
return -1
193+
}
194+
if a.Tag > b.Tag || (a.Tag == b.Tag && a.Index > b.Index) {
195+
return 1
196+
}
197+
return 0
198+
},
199+
)
200+
// Yield keys
201+
for _, redeemerKey := range sorted {
202+
tmpVal := r.Redeemers[redeemerKey]
203+
if !yield(redeemerKey, tmpVal) {
204+
return
205+
}
206+
}
207+
}
208+
}
209+
193210
func (r ConwayRedeemers) Indexes(tag common.RedeemerTag) []uint {
194211
if r.legacy {
195212
return r.legacyRedeemers.Indexes(tag)
@@ -206,18 +223,18 @@ func (r ConwayRedeemers) Indexes(tag common.RedeemerTag) []uint {
206223
func (r ConwayRedeemers) Value(
207224
index uint,
208225
tag common.RedeemerTag,
209-
) (cbor.LazyValue, common.ExUnits) {
226+
) common.RedeemerValue {
210227
if r.legacy {
211228
return r.legacyRedeemers.Value(index, tag)
212229
}
213-
redeemer, ok := r.Redeemers[ConwayRedeemerKey{
230+
redeemerVal, ok := r.Redeemers[common.RedeemerKey{
214231
Tag: tag,
215232
Index: uint32(index), // #nosec G115
216233
}]
217234
if ok {
218-
return redeemer.Data, redeemer.ExUnits
235+
return redeemerVal
219236
}
220-
return cbor.LazyValue{}, common.ExUnits{}
237+
return common.RedeemerValue{}
221238
}
222239

223240
type ConwayTransactionWitnessSet struct {

0 commit comments

Comments
 (0)