Skip to content

Commit ca8546e

Browse files
committed
feat(ledger): validate metadata during rule checks
Signed-off-by: Chris Gianelloni <wolf31o2@blinklabs.io>
1 parent 7524697 commit ca8546e

File tree

12 files changed

+480
-0
lines changed

12 files changed

+480
-0
lines changed

ledger/allegra/errors.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ package allegra
1616

1717
import (
1818
"fmt"
19+
20+
"github.com/blinklabs-io/gouroboros/ledger/common"
1921
)
2022

2123
type OutsideValidityIntervalUtxoError struct {
@@ -30,3 +32,38 @@ func (e OutsideValidityIntervalUtxoError) Error() string {
3032
e.Slot,
3133
)
3234
}
35+
36+
type MissingTransactionMetadataError struct {
37+
Hash common.Blake2b256
38+
}
39+
40+
func (e MissingTransactionMetadataError) Error() string {
41+
return fmt.Sprintf(
42+
"missing transaction metadata: body declares hash %x but no metadata provided",
43+
e.Hash,
44+
)
45+
}
46+
47+
type MissingTransactionAuxiliaryDataHashError struct {
48+
Hash common.Blake2b256
49+
}
50+
51+
func (e MissingTransactionAuxiliaryDataHashError) Error() string {
52+
return fmt.Sprintf(
53+
"missing transaction auxiliary data hash: metadata provided with hash %x but body has no hash",
54+
e.Hash,
55+
)
56+
}
57+
58+
type ConflictingMetadataHashError struct {
59+
Supplied common.Blake2b256
60+
Expected common.Blake2b256
61+
}
62+
63+
func (e ConflictingMetadataHashError) Error() string {
64+
return fmt.Sprintf(
65+
"conflicting metadata hash: body declares %x, actual metadata hash is %x",
66+
e.Supplied,
67+
e.Expected,
68+
)
69+
}

ledger/allegra/rules.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
)
2323

2424
var UtxoValidationRules = []common.UtxoValidationRuleFunc{
25+
UtxoValidateMetadata,
2526
UtxoValidateOutsideValidityIntervalUtxo,
2627
UtxoValidateInputSetEmptyUtxo,
2728
UtxoValidateFeeTooSmallUtxo,
@@ -167,3 +168,46 @@ func UtxoValidateMaxTxSizeUtxo(
167168
tmpPparams,
168169
)
169170
}
171+
172+
// UtxoValidateMetadata validates that auxiliary data (metadata) matches the hash in transaction body
173+
// This is a cheap structural check that should run before expensive ledger lookups
174+
func UtxoValidateMetadata(
175+
tx common.Transaction,
176+
slot uint64,
177+
ls common.LedgerState,
178+
pp common.ProtocolParameters,
179+
) error {
180+
bodyAuxDataHash := tx.AuxDataHash()
181+
txAuxData := tx.Metadata()
182+
183+
// Case 1: Neither body hash nor aux data present - OK
184+
if bodyAuxDataHash == nil && txAuxData == nil {
185+
return nil
186+
}
187+
188+
// Case 2: Body has hash but no aux data provided - error
189+
if bodyAuxDataHash != nil && txAuxData == nil {
190+
return MissingTransactionMetadataError{
191+
Hash: *bodyAuxDataHash,
192+
}
193+
}
194+
195+
// Case 3: Aux data provided but body has no hash - error
196+
if bodyAuxDataHash == nil && txAuxData != nil {
197+
actualHash := common.Blake2b256(txAuxData.Cbor())
198+
return MissingTransactionAuxiliaryDataHashError{
199+
Hash: actualHash,
200+
}
201+
}
202+
203+
// Case 4: Both present - verify hash matches
204+
actualHash := common.Blake2b256(txAuxData.Cbor())
205+
if *bodyAuxDataHash != actualHash {
206+
return ConflictingMetadataHashError{
207+
Supplied: *bodyAuxDataHash,
208+
Expected: actualHash,
209+
}
210+
}
211+
212+
return nil
213+
}

ledger/alonzo/errors.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,38 @@ type NoCollateralInputsError struct{}
6464
func (NoCollateralInputsError) Error() string {
6565
return "no collateral inputs"
6666
}
67+
68+
type MissingTransactionMetadataError struct {
69+
Hash common.Blake2b256
70+
}
71+
72+
func (e MissingTransactionMetadataError) Error() string {
73+
return fmt.Sprintf(
74+
"missing transaction metadata: body declares hash %x but no metadata provided",
75+
e.Hash,
76+
)
77+
}
78+
79+
type MissingTransactionAuxiliaryDataHashError struct {
80+
Hash common.Blake2b256
81+
}
82+
83+
func (e MissingTransactionAuxiliaryDataHashError) Error() string {
84+
return fmt.Sprintf(
85+
"missing transaction auxiliary data hash: metadata provided with hash %x but body has no hash",
86+
e.Hash,
87+
)
88+
}
89+
90+
type ConflictingMetadataHashError struct {
91+
Supplied common.Blake2b256
92+
Expected common.Blake2b256
93+
}
94+
95+
func (e ConflictingMetadataHashError) Error() string {
96+
return fmt.Sprintf(
97+
"conflicting metadata hash: body declares %x, actual metadata hash is %x",
98+
e.Supplied,
99+
e.Expected,
100+
)
101+
}

ledger/alonzo/rules.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
)
2626

2727
var UtxoValidationRules = []common.UtxoValidationRuleFunc{
28+
UtxoValidateMetadata,
2829
UtxoValidateOutsideValidityIntervalUtxo,
2930
UtxoValidateInputSetEmptyUtxo,
3031
UtxoValidateFeeTooSmallUtxo,
@@ -428,3 +429,46 @@ func MinCoinTxOut(
428429
minCoinTxOut := uint64(tmpPparams.MinUtxoValue)
429430
return minCoinTxOut, nil
430431
}
432+
433+
// UtxoValidateMetadata validates that auxiliary data (metadata) matches the hash in transaction body
434+
// This is a cheap structural check that should run before expensive ledger lookups
435+
func UtxoValidateMetadata(
436+
tx common.Transaction,
437+
slot uint64,
438+
ls common.LedgerState,
439+
pp common.ProtocolParameters,
440+
) error {
441+
bodyAuxDataHash := tx.AuxDataHash()
442+
txAuxData := tx.Metadata()
443+
444+
// Case 1: Neither body hash nor aux data present - OK
445+
if bodyAuxDataHash == nil && txAuxData == nil {
446+
return nil
447+
}
448+
449+
// Case 2: Body has hash but no aux data provided - error
450+
if bodyAuxDataHash != nil && txAuxData == nil {
451+
return MissingTransactionMetadataError{
452+
Hash: *bodyAuxDataHash,
453+
}
454+
}
455+
456+
// Case 3: Aux data provided but body has no hash - error
457+
if bodyAuxDataHash == nil && txAuxData != nil {
458+
actualHash := common.Blake2b256(txAuxData.Cbor())
459+
return MissingTransactionAuxiliaryDataHashError{
460+
Hash: actualHash,
461+
}
462+
}
463+
464+
// Case 4: Both present - verify hash matches
465+
actualHash := common.Blake2b256(txAuxData.Cbor())
466+
if *bodyAuxDataHash != actualHash {
467+
return ConflictingMetadataHashError{
468+
Supplied: *bodyAuxDataHash,
469+
Expected: actualHash,
470+
}
471+
}
472+
473+
return nil
474+
}

ledger/babbage/errors.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ package babbage
1616

1717
import (
1818
"fmt"
19+
20+
"github.com/blinklabs-io/gouroboros/ledger/common"
1921
)
2022

2123
type TooManyCollateralInputsError struct {
@@ -43,3 +45,38 @@ func (e IncorrectTotalCollateralFieldError) Error() string {
4345
e.TotalCollateral,
4446
)
4547
}
48+
49+
type MissingTransactionMetadataError struct {
50+
Hash common.Blake2b256
51+
}
52+
53+
func (e MissingTransactionMetadataError) Error() string {
54+
return fmt.Sprintf(
55+
"missing transaction metadata: auxiliary data hash %s",
56+
e.Hash,
57+
)
58+
}
59+
60+
type MissingTransactionAuxiliaryDataHashError struct {
61+
Hash common.Blake2b256
62+
}
63+
64+
func (e MissingTransactionAuxiliaryDataHashError) Error() string {
65+
return fmt.Sprintf(
66+
"missing auxiliary data hash in transaction body: metadata hash %s",
67+
e.Hash,
68+
)
69+
}
70+
71+
type ConflictingMetadataHashError struct {
72+
Supplied common.Blake2b256
73+
Expected common.Blake2b256
74+
}
75+
76+
func (e ConflictingMetadataHashError) Error() string {
77+
return fmt.Sprintf(
78+
"metadata hash mismatch: supplied %s expected %s",
79+
e.Supplied,
80+
e.Expected,
81+
)
82+
}

ledger/babbage/rules.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
)
2727

2828
var UtxoValidationRules = []common.UtxoValidationRuleFunc{
29+
UtxoValidateMetadata,
2930
UtxoValidateOutsideValidityIntervalUtxo,
3031
UtxoValidateInputSetEmptyUtxo,
3132
UtxoValidateFeeTooSmallUtxo,
@@ -499,3 +500,46 @@ func MinCoinTxOut(
499500
minCoinTxOut := tmpPparams.AdaPerUtxoByte * uint64(len(txOutBytes))
500501
return minCoinTxOut, nil
501502
}
503+
504+
// UtxoValidateMetadata validates that auxiliary data (metadata) matches the hash in transaction body
505+
// This is a cheap structural check that should run before expensive ledger lookups
506+
func UtxoValidateMetadata(
507+
tx common.Transaction,
508+
slot uint64,
509+
ls common.LedgerState,
510+
pp common.ProtocolParameters,
511+
) error {
512+
bodyAuxDataHash := tx.AuxDataHash()
513+
txAuxData := tx.Metadata()
514+
515+
// Case 1: Neither body hash nor aux data present - OK
516+
if bodyAuxDataHash == nil && txAuxData == nil {
517+
return nil
518+
}
519+
520+
// Case 2: Body has hash but no aux data provided - error
521+
if bodyAuxDataHash != nil && txAuxData == nil {
522+
return MissingTransactionMetadataError{
523+
Hash: *bodyAuxDataHash,
524+
}
525+
}
526+
527+
// Case 3: Aux data provided but body has no hash - error
528+
if bodyAuxDataHash == nil && txAuxData != nil {
529+
actualHash := common.Blake2b256(txAuxData.Cbor())
530+
return MissingTransactionAuxiliaryDataHashError{
531+
Hash: actualHash,
532+
}
533+
}
534+
535+
// Case 4: Both present - verify hash matches
536+
actualHash := common.Blake2b256(txAuxData.Cbor())
537+
if *bodyAuxDataHash != actualHash {
538+
return ConflictingMetadataHashError{
539+
Supplied: *bodyAuxDataHash,
540+
Expected: actualHash,
541+
}
542+
}
543+
544+
return nil
545+
}

ledger/conway/errors.go

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

1717
import (
18+
"fmt"
1819
"strings"
1920

2021
"github.com/blinklabs-io/gouroboros/ledger/common"
@@ -31,3 +32,38 @@ func (e NonDisjointRefInputsError) Error() string {
3132
}
3233
return "non-disjoint reference inputs: " + strings.Join(tmpInputs, ", ")
3334
}
35+
36+
type MissingTransactionMetadataError struct {
37+
Hash common.Blake2b256
38+
}
39+
40+
func (e MissingTransactionMetadataError) Error() string {
41+
return fmt.Sprintf(
42+
"missing transaction metadata: auxiliary data hash %s",
43+
e.Hash,
44+
)
45+
}
46+
47+
type MissingTransactionAuxiliaryDataHashError struct {
48+
Hash common.Blake2b256
49+
}
50+
51+
func (e MissingTransactionAuxiliaryDataHashError) Error() string {
52+
return fmt.Sprintf(
53+
"missing auxiliary data hash in transaction body: metadata hash %s",
54+
e.Hash,
55+
)
56+
}
57+
58+
type ConflictingMetadataHashError struct {
59+
Supplied common.Blake2b256
60+
Expected common.Blake2b256
61+
}
62+
63+
func (e ConflictingMetadataHashError) Error() string {
64+
return fmt.Sprintf(
65+
"metadata hash mismatch: supplied %s expected %s",
66+
e.Supplied,
67+
e.Expected,
68+
)
69+
}

0 commit comments

Comments
 (0)