Skip to content

Commit 7173b7b

Browse files
authored
feat(ledger): wire up tx validation (#1307)
Signed-off-by: Chris Gianelloni <[email protected]>
1 parent a3f927f commit 7173b7b

File tree

3 files changed

+66
-43
lines changed

3 files changed

+66
-43
lines changed

ledger/common/verify_config.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,15 @@ type VerifyConfig struct {
2828
// SkipBodyHashValidation disables body hash verification in VerifyBlock().
2929
// Useful for scenarios where full block CBOR is unavailable.
3030
SkipBodyHashValidation bool
31+
// SkipTransactionValidation disables transaction validation in VerifyBlock().
32+
// When false (default), LedgerState and ProtocolParameters must be set.
33+
SkipTransactionValidation bool
34+
// LedgerState provides the current ledger state for transaction validation.
35+
// Required if SkipTransactionValidation is false.
36+
LedgerState LedgerState
37+
// ProtocolParameters provides the current protocol parameters for transaction validation.
38+
// Required if SkipTransactionValidation is false.
39+
ProtocolParameters ProtocolParameters
3140
}
3241

3342
// ValidateBlockBodyHash validates the block body hash during parsing.

ledger/verify_block.go

Lines changed: 44 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
package ledger
2020

2121
import (
22-
"bytes"
2322
"encoding/hex"
2423
"errors"
2524
"fmt"
@@ -34,7 +33,6 @@ import (
3433
"github.com/blinklabs-io/gouroboros/ledger/conway"
3534
"github.com/blinklabs-io/gouroboros/ledger/mary"
3635
"github.com/blinklabs-io/gouroboros/ledger/shelley"
37-
"golang.org/x/crypto/blake2b"
3836
)
3937

4038
const (
@@ -300,47 +298,54 @@ func VerifyBlock(
300298
return false, "", 0, 0, errors.New(
301299
"VerifyBlock: block CBOR is required for body hash verification",
302300
)
303-
} else {
304-
var raw []cbor.RawMessage
305-
if _, err := cbor.Decode(rawCbor, &raw); err != nil {
301+
}
302+
era := block.Era()
303+
eraName := era.Name
304+
minLength := 4
305+
if era.Id >= alonzo.EraAlonzo.Id {
306+
minLength = 5
307+
}
308+
if err := common.ValidateBlockBodyHash(rawCbor, expectedBodyHash, eraName, minLength); err != nil {
309+
return false, "", 0, 0, fmt.Errorf(
310+
"VerifyBlock: %w",
311+
err,
312+
)
313+
}
314+
}
315+
316+
// Verify transactions (can be skipped via config)
317+
// Requires LedgerState and ProtocolParameters in config if enabled.
318+
if block.Era() != byron.EraByron && !config.SkipTransactionValidation {
319+
var validationRules []common.UtxoValidationRuleFunc
320+
switch block.Era().Id {
321+
case shelley.EraShelley.Id:
322+
validationRules = shelley.UtxoValidationRules
323+
case allegra.EraAllegra.Id:
324+
validationRules = allegra.UtxoValidationRules
325+
case mary.EraMary.Id:
326+
validationRules = mary.UtxoValidationRules
327+
case alonzo.EraAlonzo.Id:
328+
validationRules = alonzo.UtxoValidationRules
329+
case babbage.EraBabbage.Id:
330+
validationRules = babbage.UtxoValidationRules
331+
case conway.EraConway.Id:
332+
validationRules = conway.UtxoValidationRules
333+
default:
334+
return false, "", 0, 0, fmt.Errorf(
335+
"VerifyBlock: unsupported era for transaction validation %s",
336+
block.Era().Name,
337+
)
338+
}
339+
if config.LedgerState == nil || config.ProtocolParameters == nil {
340+
return false, "", 0, 0, errors.New("VerifyBlock: missing required config field: LedgerState and ProtocolParameters must be set")
341+
}
342+
for _, tx := range block.Transactions() {
343+
if err := common.VerifyTransaction(tx, slot, config.LedgerState, config.ProtocolParameters, validationRules); err != nil {
306344
return false, "", 0, 0, fmt.Errorf(
307-
"VerifyBlock: failed to decode block CBOR for body hash, %w",
345+
"VerifyBlock: transaction validation failed: %w",
308346
err,
309347
)
310348
}
311-
if len(raw) < 4 {
312-
return false, "", 0, 0, errors.New(
313-
"VerifyBlock: invalid block CBOR structure for body hash",
314-
)
315-
}
316-
// Compute body hash as per Cardano spec: blake2b_256(hash_tx || hash_wit || hash_aux || hash_invalid)
317-
emptyInvalidCbor, _ := cbor.Encode(cbor.IndefLengthList([]any{}))
318-
hashInvalidDefault := blake2b.Sum256(emptyInvalidCbor)
319-
var bodyHashes []byte
320-
hashTx := blake2b.Sum256(raw[1])
321-
bodyHashes = append(bodyHashes, hashTx[:]...)
322-
hashWit := blake2b.Sum256(raw[2])
323-
bodyHashes = append(bodyHashes, hashWit[:]...)
324-
hashAux := blake2b.Sum256(raw[3])
325-
bodyHashes = append(bodyHashes, hashAux[:]...)
326-
switch block.Header().(type) {
327-
case *shelley.ShelleyBlockHeader, *allegra.AllegraBlockHeader, *mary.MaryBlockHeader:
328-
bodyHashes = append(bodyHashes, hashInvalidDefault[:]...)
329-
case *alonzo.AlonzoBlockHeader, *babbage.BabbageBlockHeader, *conway.ConwayBlockHeader:
330-
hashInvalid := blake2b.Sum256(raw[4])
331-
bodyHashes = append(bodyHashes, hashInvalid[:]...)
332-
default:
333-
return false, "", 0, 0, fmt.Errorf(
334-
"VerifyBlock: unsupported block type for body hash %T",
335-
block.Header(),
336-
)
337-
}
338-
actualBodyHash := blake2b.Sum256(bodyHashes)
339-
if !bytes.Equal(actualBodyHash[:], expectedBodyHash.Bytes()) {
340-
return false, "", 0, 0, errors.New(
341-
"VerifyBlock: block body hash mismatch",
342-
)
343-
}
344349
}
345350
}
346351

ledger/verify_block_test.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,15 @@ var eraNameMap = map[uint]string{
2626
BlockTypeConway: conway.EraNameConway,
2727
}
2828

29+
// testSkipAllValidationConfig returns a VerifyConfig that skips both body hash and transaction validation.
30+
// Useful for tests that don't provide full CBOR or ledger state.
31+
func testSkipAllValidationConfig() common.VerifyConfig {
32+
return common.VerifyConfig{
33+
SkipBodyHashValidation: true,
34+
SkipTransactionValidation: true,
35+
}
36+
}
37+
2938
// decodeTxBodyBytes extracts transaction body bytes from either []byte or hex string
3039
func decodeTxBodyBytes(t *testing.T, v any) []byte {
3140
t.Helper()
@@ -107,8 +116,8 @@ func decodeTransactionMetadataSet(
107116
}
108117

109118
func TestVerifyBlockBody(t *testing.T) {
110-
// Enable skipping body hash validation for tests that use blocks without stored CBOR
111-
config := common.VerifyConfig{SkipBodyHashValidation: true}
119+
// Skip body hash and transaction validation for tests that use blocks without stored CBOR and don't provide ledger state
120+
config := testSkipAllValidationConfig()
112121

113122
testCases := []struct {
114123
name string
@@ -334,8 +343,8 @@ func TestVerifyBlockBody(t *testing.T) {
334343
}
335344

336345
func TestVerifyBlock_SkipBodyHashValidation(t *testing.T) {
337-
// Enable skipping body hash validation
338-
config := common.VerifyConfig{SkipBodyHashValidation: true}
346+
// Skip body hash and transaction validation
347+
config := testSkipAllValidationConfig()
339348

340349
// This test is lightweight: it constructs a minimal Shelley header/body
341350
// and a block with empty CBOR to simulate missing CBOR scenario.

0 commit comments

Comments
 (0)