Skip to content

Commit 10b804d

Browse files
authored
feat: Alonzo era 'Utxo' validation rules (#922)
Fixes #878 Signed-off-by: Aurora Gaffney <[email protected]>
1 parent 1e5ee87 commit 10b804d

File tree

4 files changed

+1583
-2
lines changed

4 files changed

+1583
-2
lines changed

ledger/alonzo/errors.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
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 alonzo
16+
17+
import (
18+
"fmt"
19+
20+
"github.com/blinklabs-io/gouroboros/ledger/common"
21+
)
22+
23+
type ExUnitsTooBigUtxoError struct {
24+
TotalExUnits common.ExUnits
25+
MaxTxExUnits common.ExUnits
26+
}
27+
28+
func (e ExUnitsTooBigUtxoError) Error() string {
29+
return fmt.Sprintf(
30+
"ExUnits too big: total %d/%d steps/memory, maximum %d/%d steps/memory",
31+
e.TotalExUnits.Steps,
32+
e.TotalExUnits.Memory,
33+
e.MaxTxExUnits.Steps,
34+
e.MaxTxExUnits.Memory,
35+
)
36+
}
37+
38+
type InsufficientCollateralError struct {
39+
Provided uint64
40+
Required uint64
41+
}
42+
43+
func (e InsufficientCollateralError) Error() string {
44+
return fmt.Sprintf(
45+
"insufficient collateral: provided %d, required %d",
46+
e.Provided,
47+
e.Required,
48+
)
49+
}
50+
51+
type CollateralContainsNonAdaError struct {
52+
Provided uint64
53+
}
54+
55+
func (e CollateralContainsNonAdaError) Error() string {
56+
return fmt.Sprintf(
57+
"collateral contains non-ADA: provided %d",
58+
e.Provided,
59+
)
60+
}
61+
62+
type NoCollateralInputsError struct{}
63+
64+
func (NoCollateralInputsError) Error() string {
65+
return "no collateral inputs"
66+
}

ledger/alonzo/rules.go

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
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 alonzo
16+
17+
import (
18+
"errors"
19+
20+
"github.com/blinklabs-io/gouroboros/cbor"
21+
"github.com/blinklabs-io/gouroboros/ledger/allegra"
22+
"github.com/blinklabs-io/gouroboros/ledger/common"
23+
"github.com/blinklabs-io/gouroboros/ledger/mary"
24+
"github.com/blinklabs-io/gouroboros/ledger/shelley"
25+
)
26+
27+
var UtxoValidationRules = []common.UtxoValidationRuleFunc{
28+
UtxoValidateOutsideValidityIntervalUtxo,
29+
UtxoValidateInputSetEmptyUtxo,
30+
UtxoValidateFeeTooSmallUtxo,
31+
UtxoValidateInsufficientCollateral,
32+
UtxoValidateCollateralContainsNonAda,
33+
UtxoValidateNoCollateralInputs,
34+
UtxoValidateBadInputsUtxo,
35+
UtxoValidateValueNotConservedUtxo,
36+
UtxoValidateOutputTooSmallUtxo,
37+
UtxoValidateOutputTooBigUtxo,
38+
UtxoValidateOutputBootAddrAttrsTooBig,
39+
UtxoValidateWrongNetwork,
40+
UtxoValidateWrongNetworkWithdrawal,
41+
UtxoValidateMaxTxSizeUtxo,
42+
UtxoValidateExUnitsTooBigUtxo,
43+
}
44+
45+
// UtxoValidateOutputTooBigUtxo ensures that transaction output values are not too large
46+
func UtxoValidateOutputTooBigUtxo(tx common.Transaction, slot uint64, _ common.LedgerState, pp common.ProtocolParameters) error {
47+
tmpPparams, ok := pp.(*AlonzoProtocolParameters)
48+
if !ok {
49+
return errors.New("pparams are not expected type")
50+
}
51+
badOutputs := []common.TransactionOutput{}
52+
for _, txOutput := range tx.Outputs() {
53+
tmpOutput, ok := txOutput.(*AlonzoTransactionOutput)
54+
if !ok {
55+
return errors.New("transaction output is not expected type")
56+
}
57+
outputValBytes, err := cbor.Encode(tmpOutput.OutputAmount)
58+
if err != nil {
59+
return err
60+
}
61+
if uint(len(outputValBytes)) <= tmpPparams.MaxValueSize {
62+
continue
63+
}
64+
badOutputs = append(badOutputs, tmpOutput)
65+
}
66+
if len(badOutputs) == 0 {
67+
return nil
68+
}
69+
return mary.OutputTooBigUtxoError{
70+
Outputs: badOutputs,
71+
}
72+
}
73+
74+
// UtxoValidateExUnitsTooBigUtxo ensures that ExUnits for a transaction do not exceed the maximum specified via protocol parameters
75+
func UtxoValidateExUnitsTooBigUtxo(tx common.Transaction, slot uint64, ls common.LedgerState, pp common.ProtocolParameters) error {
76+
tmpPparams, ok := pp.(*AlonzoProtocolParameters)
77+
if !ok {
78+
return errors.New("pparams are not expected type")
79+
}
80+
tmpTx, ok := tx.(*AlonzoTransaction)
81+
if !ok {
82+
return errors.New("transaction is not expected type")
83+
}
84+
var totalSteps, totalMemory uint64
85+
for _, redeemer := range tmpTx.WitnessSet.WsRedeemers {
86+
totalSteps += redeemer.ExUnits.Steps
87+
totalMemory += redeemer.ExUnits.Memory
88+
}
89+
if totalSteps <= tmpPparams.MaxTxExUnits.Steps && totalMemory <= tmpPparams.MaxTxExUnits.Memory {
90+
return nil
91+
}
92+
return ExUnitsTooBigUtxoError{
93+
TotalExUnits: common.ExUnits{
94+
Memory: totalMemory,
95+
Steps: totalSteps,
96+
},
97+
MaxTxExUnits: tmpPparams.MaxTxExUnits,
98+
}
99+
}
100+
101+
func UtxoValidateOutsideValidityIntervalUtxo(tx common.Transaction, slot uint64, ls common.LedgerState, pp common.ProtocolParameters) error {
102+
return allegra.UtxoValidateOutsideValidityIntervalUtxo(tx, slot, ls, pp)
103+
}
104+
105+
func UtxoValidateInputSetEmptyUtxo(tx common.Transaction, slot uint64, ls common.LedgerState, pp common.ProtocolParameters) error {
106+
return shelley.UtxoValidateInputSetEmptyUtxo(tx, slot, ls, pp)
107+
}
108+
109+
func UtxoValidateFeeTooSmallUtxo(tx common.Transaction, slot uint64, ls common.LedgerState, pp common.ProtocolParameters) error {
110+
tmpPparams, ok := pp.(*AlonzoProtocolParameters)
111+
if !ok {
112+
return errors.New("pparams are not expected type")
113+
}
114+
return shelley.UtxoValidateFeeTooSmallUtxo(tx, slot, ls, &tmpPparams.ShelleyProtocolParameters)
115+
}
116+
117+
// UtxoValidateInsufficientCollateral ensures that there is sufficient collateral provided
118+
func UtxoValidateInsufficientCollateral(tx common.Transaction, slot uint64, ls common.LedgerState, pp common.ProtocolParameters) error {
119+
tmpPparams, ok := pp.(*AlonzoProtocolParameters)
120+
if !ok {
121+
return errors.New("pparams are not expected type")
122+
}
123+
tmpTx, ok := tx.(*AlonzoTransaction)
124+
if !ok {
125+
return errors.New("transaction is not expected type")
126+
}
127+
// There's nothing to check if there are no redeemers
128+
if len(tmpTx.WitnessSet.WsRedeemers) == 0 {
129+
return nil
130+
}
131+
var totalCollateral uint64
132+
for _, collateralInput := range tx.Collateral() {
133+
utxo, err := ls.UtxoById(collateralInput)
134+
if err != nil {
135+
return err
136+
}
137+
totalCollateral += utxo.Output.Amount()
138+
}
139+
minCollateral := tmpTx.Fee() * uint64(tmpPparams.CollateralPercentage) / 100
140+
if totalCollateral >= minCollateral {
141+
return nil
142+
}
143+
return InsufficientCollateralError{
144+
Provided: totalCollateral,
145+
Required: minCollateral,
146+
}
147+
}
148+
149+
// UtxoValidateCollateralContainsNonAda ensures that collateral inputs don't contain non-ADA
150+
func UtxoValidateCollateralContainsNonAda(tx common.Transaction, slot uint64, ls common.LedgerState, pp common.ProtocolParameters) error {
151+
tmpTx, ok := tx.(*AlonzoTransaction)
152+
if !ok {
153+
return errors.New("transaction is not expected type")
154+
}
155+
// There's nothing to check if there are no redeemers
156+
if len(tmpTx.WitnessSet.WsRedeemers) == 0 {
157+
return nil
158+
}
159+
badOutputs := []common.TransactionOutput{}
160+
var totalCollateral uint64
161+
for _, collateralInput := range tx.Collateral() {
162+
utxo, err := ls.UtxoById(collateralInput)
163+
if err != nil {
164+
return err
165+
}
166+
totalCollateral += utxo.Output.Amount()
167+
if utxo.Output.Assets() == nil {
168+
continue
169+
}
170+
badOutputs = append(badOutputs, utxo.Output)
171+
}
172+
if len(badOutputs) == 0 {
173+
return nil
174+
}
175+
return CollateralContainsNonAdaError{
176+
Provided: totalCollateral,
177+
}
178+
}
179+
180+
// UtxoValidateNoCollateralInputs ensures that collateral inputs are provided when redeemers are present
181+
func UtxoValidateNoCollateralInputs(tx common.Transaction, slot uint64, ls common.LedgerState, pp common.ProtocolParameters) error {
182+
tmpTx, ok := tx.(*AlonzoTransaction)
183+
if !ok {
184+
return errors.New("transaction is not expected type")
185+
}
186+
// There's nothing to check if there are no redeemers
187+
if len(tmpTx.WitnessSet.WsRedeemers) == 0 {
188+
return nil
189+
}
190+
if len(tx.Collateral()) > 0 {
191+
return nil
192+
}
193+
return NoCollateralInputsError{}
194+
}
195+
func UtxoValidateBadInputsUtxo(tx common.Transaction, slot uint64, ls common.LedgerState, pp common.ProtocolParameters) error {
196+
return shelley.UtxoValidateBadInputsUtxo(tx, slot, ls, pp)
197+
}
198+
199+
func UtxoValidateValueNotConservedUtxo(tx common.Transaction, slot uint64, ls common.LedgerState, pp common.ProtocolParameters) error {
200+
tmpPparams, ok := pp.(*AlonzoProtocolParameters)
201+
if !ok {
202+
return errors.New("pparams are not expected type")
203+
}
204+
return shelley.UtxoValidateValueNotConservedUtxo(tx, slot, ls, &tmpPparams.ShelleyProtocolParameters)
205+
}
206+
207+
func UtxoValidateOutputTooSmallUtxo(tx common.Transaction, slot uint64, ls common.LedgerState, pp common.ProtocolParameters) error {
208+
tmpPparams, ok := pp.(*AlonzoProtocolParameters)
209+
if !ok {
210+
return errors.New("pparams are not expected type")
211+
}
212+
return shelley.UtxoValidateOutputTooSmallUtxo(tx, slot, ls, &tmpPparams.ShelleyProtocolParameters)
213+
}
214+
215+
func UtxoValidateOutputBootAddrAttrsTooBig(tx common.Transaction, slot uint64, ls common.LedgerState, pp common.ProtocolParameters) error {
216+
return shelley.UtxoValidateOutputBootAddrAttrsTooBig(tx, slot, ls, pp)
217+
}
218+
219+
func UtxoValidateWrongNetwork(tx common.Transaction, slot uint64, ls common.LedgerState, pp common.ProtocolParameters) error {
220+
return shelley.UtxoValidateWrongNetwork(tx, slot, ls, pp)
221+
}
222+
223+
func UtxoValidateWrongNetworkWithdrawal(tx common.Transaction, slot uint64, ls common.LedgerState, pp common.ProtocolParameters) error {
224+
return shelley.UtxoValidateWrongNetworkWithdrawal(tx, slot, ls, pp)
225+
}
226+
227+
func UtxoValidateMaxTxSizeUtxo(tx common.Transaction, slot uint64, ls common.LedgerState, pp common.ProtocolParameters) error {
228+
tmpPparams, ok := pp.(*AlonzoProtocolParameters)
229+
if !ok {
230+
return errors.New("pparams are not expected type")
231+
}
232+
return shelley.UtxoValidateMaxTxSizeUtxo(tx, slot, ls, &tmpPparams.ShelleyProtocolParameters)
233+
}

0 commit comments

Comments
 (0)