Go library for Cardano: ledger validation across all eras (Byron→Dijkstra), Ouroboros network protocols, CBOR. Primary agent reference; Claude-specific layer in CLAUDE.md.
| Action | Command |
|---|---|
| Tests + race | make test |
| Lint | make lint |
| Format | make format |
| Conformance | go test -v ./internal/test/conformance/... |
| Build | go build ./... |
All must pass before submitting. 80-col limit (golines), Apache-2.0 header on new files.
ledger/ ledger types + validation
common/ shared interfaces, types, rules
script/ Plutus script context + purpose wrappers (V1/V2/V3)
byron/ standalone legacy format
shelley/ allegra/ mary/ alonzo/ babbage/ conway/ leios/
protocol/ Ouroboros mini-protocols (chainsync, blockfetch,
txsubmission, localstatequery, ...)
cbor/ CBOR helpers (EncMode/DecMode cached globally)
connection/ network connection mgmt
muxer/ protocol multiplexing
kes/ vrf/ consensus/ crypto primitives (used by ledger/verify_*.go)
pipeline/ block processing (decode → validate → apply)
internal/test/ test utilities + conformance harness
Era lineage: Shelley → Allegra → Mary → Alonzo → Babbage → Conway → Dijkstra. Byron is standalone. Leios is not an era — it is a higher-throughput overlay protocol introduced in the Dijkstra era (types in ledger/common/leios*.go, protocols in protocol/leios{fetch,notify,votes}/).
func UtxoValidate{Name}(
tx common.Transaction,
slot uint64,
ls common.LedgerState,
pp common.ProtocolParameters,
) errorReturns nil or a typed error. Later eras often delegate to earlier ones, but not universally — Conway UtxoValidateWithdrawals has its own body. Always grep the function before claiming delegation.
ledger/common/state.go—LedgerStateembedsUtxoState,CertState,SlotState,PoolState,RewardState,GovState; exposesNetworkId(),CostModels().ledger/common/tx.go—Transaction:Hash(),Cbor(),IsValid(),Consumed(),Produced(),Witnesses().
Embeddable helpers:
cbor.StructAsArray— encode struct as array.cbor.DecodeStoreCbor— preserve original bytes. Requires a customUnmarshalCBORthat callsSetCbor(cborData); retrieve via.Cbor().cbor.RawMessage— deferred decode.cbor.ByteString— map-key-safe bytes.cbor.Tag,cbor.RawTag— semantic tags.
Rules:
- Hash from preserved
.Cbor()bytes, never re-encoded. - Map key order uses Cardano rules (e.g. shortLex for language views).
- Indefinite vs definite length matters for some encodings.
EncMode/DecModeare globally cached viasync.Once; don't construct per call.
Blake2b256(redeemers_cbor || datums_cbor || language_views_cbor)
Language-views map, keys sorted shortLex (length-first, then lex):
- PlutusV1 (double-bagged): tag
serialize(serialize(0))=0x4100; paramsserialize(indefinite_list(cost_model))wrapped in bytestring. - PlutusV2/V3: tag
serialize(version)=0x01/0x02; paramsdefinite_list(cost_model)unwrapped.
NativeScript.Evaluate(slot, validityStart, validityEnd, keyHashes) bool at ledger/common/script.go. Types: Pubkey, All, Any, NofK, InvalidBefore, InvalidHereafter.
Two-phase at epoch boundaries:
- Enact proposals ratified in the previous epoch (updates roots).
- Ratify new proposals using updated roots.
Ordering ensures sibling proposals resolve correctly.
All mock fixtures come from github.com/blinklabs-io/ouroboros-mock. Single source of truth across dingo, adder, shai, apollo, and downstream apps. If a fixture is missing, add it upstream and bump the dep — never inline a local mock.
| Need | Package |
|---|---|
| LedgerState, UTxO, pools, pparams, governance, rewards | ouroboros-mock/ledger |
Mock transactions / builders (*MockTransaction) |
ouroboros-mock/ledger |
| Network conversations, connection/handshake mocks | ouroboros-mock (root) |
| Block/tx harnesses, protocol param JSON | ouroboros-mock/fixtures |
NewTransactionBuilder() returns *MockTransaction. The TransactionBuilder interface only exposes WithId, WithInputs, WithOutputs, WithFee, WithTTL, WithMetadata, WithValid, Build. Use the concrete type for WithWithdrawals, WithCollateral, WithReferenceInputs, WithCertificates, WithRequiredSigners, WithScriptDataHash, WithMint, WithValidityIntervalStart.
Use testify: require for fatal, assert for continue-on-fail.
import (
"github.com/stretchr/testify/require"
mockledger "github.com/blinklabs-io/ouroboros-mock/ledger"
)
ls := mockledger.NewLedgerStateBuilder().
WithNetworkId(0).
WithUtxos([]lcommon.Utxo{testUtxo}).
Build()
tx, err := mockledger.NewTransactionBuilder().
WithInputs(in).WithOutputs(out).WithFee(200000).Build()
require.NoError(t, err)| Task | Files |
|---|---|
| Add/fix validation rule | ledger/{era}/rules.go; register in UtxoValidationRules; error in ledger/{era}/errors.go (or common/errors.go if shared); add conformance vector if applicable |
| Transaction body fields | ledger/{era}/shelley.go or era-specific file |
| Witnesses | ledger/common/witness.go |
| Fee calculation | MinFeeTx in ledger/{era}/rules.go |
| Native scripts | ledger/common/script.go (NativeScript and variants) |
| Plutus scripts | ledger/common/script.go (PlutusV1Script, PlutusV2Script, PlutusV3Script); context/purpose wrappers in ledger/common/script/ |
| Script validation | ValidateScriptWitnesses in ledger/common/rules.go; UtxoValidateScriptWitnesses in ledger/{alonzo,babbage,conway}/rules.go |
| Script data hash | TransactionBodyBase.ScriptDataHash() in ledger/common/tx.go; UtxoValidateScriptDataHash in ledger/{alonzo,babbage,conway}/rules.go |
| Certificate types | ledger/common/certs.go (+ CBOR tags, parsing switch, era rule) |
| Governance (Conway) | ledger/conway/gov.go |
| Protocol messages | protocol/{name}/messages.go |
| Protocol client/server | protocol/{name}/{client,server}.go |
| Block verification | ledger/verify_block.go, verify_block_body.go, verify_kes.go, verify_opcert.go; vrf/vrf.go, kes/kes.go |
| New era | ledger/{era}/ + compat exports at ledger/{era}.go |
| Conformance failure | grep rule name in ledger/; cross-ref internal/test/cardano-blueprint/src/ledger/; compare vector JSON in the github.com/blinklabs-io/ouroboros-mock module under conformance/testdata/ (embedded, extracted at test time) |
| Error | Rule | Cause |
|---|---|---|
ExpiredUtxoError |
UtxoValidateTimeToLive |
TTL < current slot |
InputSetEmptyUtxoError |
UtxoValidateInputSetEmptyUtxo |
no inputs |
FeeTooSmallUtxoError |
UtxoValidateFeeTooSmallUtxo |
fee below MinFeeTx() |
BadInputsUtxoError |
UtxoValidateBadInputsUtxo |
input not in UTxO set |
WrongNetworkError |
UtxoValidateWrongNetwork |
output address wrong network |
ValueNotConservedUtxoError |
UtxoValidateValueNotConservedUtxo |
inputs ≠ outputs + fee |
OutputTooSmallUtxoError |
UtxoValidateOutputTooSmallUtxo |
output below min UTxO |
MaxTxSizeUtxoError |
UtxoValidateMaxTxSizeUtxo |
transaction too large |
| Error | Rule | Cause |
|---|---|---|
OutsideValidityIntervalUtxoError |
UtxoValidateValidityInterval |
slot outside validity range |
NativeScriptFailedError |
script evaluation | native script failed |
| Error | Rule | Cause |
|---|---|---|
ExUnitsTooBigUtxoError |
UtxoValidateExUnitsTooBig |
execution units exceed max |
InsufficientCollateralError |
UtxoValidateCollateral |
collateral below required |
CollateralContainsNonAdaError |
UtxoValidateCollateral |
collateral has tokens |
NoCollateralInputsError |
UtxoValidateCollateral |
collateral not specified |
| Error | Rule | Cause |
|---|---|---|
DelegateVoteToUnregisteredDRepError |
UtxoValidateDelegation |
DRep not registered |
StakeCredentialAlreadyRegisteredError |
UtxoValidateDelegation |
re-registering stake key |
PlutusScriptFailedError |
script execution | script returned False |
| Error | Rule | Cause |
|---|---|---|
MissingCostModelError |
script validation | no cost model for Plutus version |
ScriptDataHashMismatchError |
UtxoValidateScriptDataHash |
hash mismatch against witnesses |
MissingVKeyWitnessesError |
UtxoValidateRequiredVKeyWitnesses |
required signature missing |
MalformedReferenceScriptsError |
UtxoValidateMalformedReferenceScripts |
invalid UPLC bytecode |
Error files: ledger/{shelley,allegra,alonzo,babbage,conway,common}/errors.go.
- Reference inputs: resolved but never consumed from UTxO set.
- Collateral: only consumed when
IsValid=false. - Datum lookup: check witness set, inline datums, AND reference inputs.
- Cost models: required per Plutus version used.
- Era delegation is not universal — read the function body. Conway
UtxoValidateWithdrawalshas a custom impl. - Hash from preserved CBOR bytes, not re-encoded data.
DecodeStoreCborrequires a customUnmarshalCBORcallingSetCbor().- Withdrawal amount validation is intentionally disabled (see
NOTE:atconway/rules.goUtxoValidateWithdrawals). Spec requiresamount == balance, notamount > 0; zero-amount withdrawals from zero-balance accounts are spec-valid. Multi-tx balance tracking is deferred. - Read code before claiming "just delegates" or "missing check".
NOTE:comments mark deliberate decisions.
- Do not hallucinate APIs.
UtxoValidateNoDuplicateInputsandDuplicateInputErrordo not exist. TheTransactionBuilderinterface has noWithWithdrawals(the concrete*MockTransactiondoes). - Do not assume delegation. Most Conway rules delegate to Shelley; not all.
- Do not propose Cardano spec checks without verifying the spec. E.g. withdrawal amount must equal the exact reward balance, not just be positive.
- Respect
NOTE:comments — they mark intentional omissions and spec deviations.
README.md— feature checklist, manual testinginternal/test/conformance/README.md— conformance harnessprotocol/PROTOCOL_LIMITS.md— protocol buffer limits