conformance

package
v0.66.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 21, 2026 License: Apache-2.0 Imports: 17 Imported by: 0

README

Ledger Rules Conformance Tests

This package runs the Amaru ledger rules conformance vectors against Dingo's ledger implementation. The shared harness and embedded test data live in github.com/blinklabs-io/ouroboros-mock/conformance; this package provides DingoStateManager, an adapter that drives Dingo's database and ledger packages so every vector runs against a clean state. DingoStateManager only ever talks to the database through *gorm.DB, so the same adapter runs against an in-memory SQLite backend (the default, no setup required), a real PostgreSQL backend (see PostgreSQL backend), or a real MySQL backend (see MySQL backend).

What the vectors cover

The vectors exercise Conway era ledger rules:

  • UTxO validation — inputs, outputs, fees, collateral
  • Certificate processing — stake, pool, DRep, committee
  • Governance — proposals, voting, enactment
  • Script execution — native scripts, Plutus V1/V2/V3

Running the tests

Run the full suite:

go test ./internal/test/conformance/

Run with verbose vector-level output (useful when investigating a failure):

go test -v ./internal/test/conformance/ -run TestRulesConformanceVectors

Run the variant that reports per-vector pass/fail statistics:

go test -v ./internal/test/conformance/ -run TestRulesConformanceVectorsWithResults

Run a single vector by substring match (delegated by the harness):

go test -v ./internal/test/conformance/ -run TestRulesConformanceVectors -vector <name>

PostgreSQL backend

By default the tests use an in-memory SQLite database and need no setup. A second, build-tag-gated variant runs the identical harness against a real PostgreSQL database, using the same dingo_extra_plugins build tag as database/plugin/metadata/postgres (the actual Postgres metadata store plugin) and the same POSTGRES_HOST/PORT/USER/PASSWORD/DATABASE/SSLMODE environment variables that plugin's tests and CI's go-test-linux job already use.

Bring up a local Postgres and run it:

docker compose -f internal/test/conformance/docker-compose.yml up -d

POSTGRES_HOST=localhost POSTGRES_PORT=5432 POSTGRES_USER=postgres \
POSTGRES_PASSWORD=postgres POSTGRES_DATABASE=dingo_test \
  go test -tags dingo_extra_plugins -v ./internal/test/conformance/... -run Postgres

Without a POSTGRES_PASSWORD or POSTGRES_DSN set, both Postgres tests skip (they never fail a plain go test ./...). CI's go-test-linux job already runs a postgres:16 service with those exact env vars, so the Postgres variant runs automatically as part of the existing tagged go test -race ./... step.

Schema isolation. database/plugin/metadata/postgres's own tests connect to the same dingo_test database. Since go test ./... runs different packages as separate, concurrent processes, sharing the default public schema would let the two suites race on the same tables. NewDingoPostgresStateManager migrates into a dedicated conformance schema instead (via CREATE SCHEMA IF NOT EXISTS + SET search_path, with the connection pool pinned to one connection so every statement lands on the session the search_path was set on).

TestRulesConformanceVectorsWithResultsPostgres runs the SQLite and Postgres harnesses in the same test and compares vector counts instead of asserting a hardcoded number, so the two runs should exercise the identical vector count with identical pass counts, and the comparison stays correct even as the embedded ouroboros-mock vector corpus grows or shrinks.

MySQL backend

Same idea as the PostgreSQL backend above, using the same dingo_extra_plugins build tag as database/plugin/metadata/mysql and the same MYSQL_HOST/PORT environment variables that plugin's tests and CI's go-test-linux job already use.

Bring up a local MySQL and run it:

docker compose -f internal/test/conformance/docker-compose.yml up -d mysql

MYSQL_HOST=localhost MYSQL_PORT=3306 MYSQL_ROOT_PASSWORD=mysql \
  go test -tags dingo_extra_plugins -v ./internal/test/conformance/... -run Mysql

Without a MYSQL_ROOT_PASSWORD or MYSQL_DSN set, both MySQL tests skip. CI's go-test-linux job already runs a mysql:8 service and sets MYSQL_ROOT_PASSWORD, so the MySQL variant runs automatically as part of the existing tagged go test -race ./... step.

Database isolation. MySQL has no schema/database distinction the way Postgres does — a MySQL "schema" is a database. database/plugin/metadata/mysql's own tests connect to the shared dingo_test database with a user the official mysql image's bootstrap grants access to only that database, so this suite can't reuse that user to carve out an isolated namespace the way the Postgres one does with CREATE SCHEMA. Instead, NewDingoMysqlStateManager authenticates as root (the one account guaranteed to have CREATE DATABASE privileges) and migrates into its own dedicated dingo_conformance_test database. This is why the MySQL tests key off MYSQL_ROOT_PASSWORD specifically rather than the MYSQL_PASSWORD the plugin's own tests use.

TestRulesConformanceVectorsWithResultsMysql follows the same count-comparison approach as the Postgres variant, for the same reason.

When to run them

Conformance tests are mandatory after every ledger-affecting change, not just once at the end of a branch. Specifically, run them after any edit under ledger/, database/plugin/metadata/, database/models/, or any dependency bump of gouroboros, plutigo, or ouroboros-mock. A regression here almost always indicates a correctness bug that CI on unit tests will miss.

Cross-repo change cascades that must re-run this suite:

Changed repo Must run conformance tests in
plutigo plutigo → gouroboros → dingo
gouroboros gouroboros → dingo
dingo dingo

How it works

  1. The test extracts embedded vectors from ouroboros-mock/conformance into a temp directory (ExtractEmbeddedTestdata).
  2. A fresh DingoStateManager spins up an in-memory SQLite database and applies Dingo's GORM migrations.
  3. The harness (conformance.NewHarness) walks every vector, feeding transactions through the state manager and comparing expected vs. actual ledger state after each step.
  4. RunAllVectors fails the Go test on any vector mismatch; RunAllVectorsWithResults returns structured pass/fail counts instead so progress can be tracked.

Files

File Purpose
conformance_test.go Go test entry points, SQLite backend (TestRulesConformanceVectors, …WithResults)
conformance_postgres_test.go Go test entry points, PostgreSQL backend (dingo_extra_plugins build tag)
conformance_mysql_test.go Go test entry points, MySQL backend (dingo_extra_plugins build tag)
state_manager.go DingoStateManager — implements conformance.StateManager against Dingo's DB/ledger
state_manager_postgres.go NewDingoPostgresStateManager — same DingoStateManager, Postgres connection (dingo_extra_plugins build tag)
state_manager_mysql.go NewDingoMysqlStateManager — same DingoStateManager, MySQL connection (dingo_extra_plugins build tag)
state_provider.go State-query adapters used by the harness
docker-compose.yml Local PostgreSQL and MySQL for the SQL-backed tests

Updating vectors

The vectors themselves are embedded in ouroboros-mock, not in this repo. To update the corpus, bump the ouroboros-mock dependency in go.mod and re-run the suite. Do not add or mutate vectors locally.

Debugging a failing vector

  1. Re-run the failing vector in isolation with -v so the harness prints per-step diagnostics.
  2. Check whether the failure is Dingo-side (ledger logic) or state-manager-side (state_manager.go mapping between common.* types and Dingo's GORM models). State-manager bugs usually surface as the same vector failing identically across multiple eras; ledger bugs are usually era-specific.
  3. If the upstream vector itself looks wrong, file an issue against blinklabs-io/ouroboros-mock rather than patching around it here.

Documentation

Overview

Package conformance provides a DingoStateManager that implements the ouroboros-mock conformance.StateManager interface using dingo's database and ledger packages with an in-memory SQLite database.

Index

Constants

This section is empty.

Variables

View Source
var ErrNotFound = errors.New("conformance: not found")

ErrNotFound is returned when a requested item is not found

Functions

This section is empty.

Types

type DingoStateManager

type DingoStateManager struct {
	// contains filtered or unexported fields
}

DingoStateManager implements conformance.StateManager using dingo's database with an in-memory SQLite backend for testing.

func NewDingoStateManager

func NewDingoStateManager() (*DingoStateManager, error)

NewDingoStateManager creates a new DingoStateManager with an in-memory SQLite database.

func (*DingoStateManager) ApplyTransaction

func (m *DingoStateManager) ApplyTransaction(
	tx common.Transaction,
	slot uint64,
) error

ApplyTransaction implements conformance.StateManager.ApplyTransaction.

func (*DingoStateManager) Close

func (m *DingoStateManager) Close() error

Close closes the database connection.

func (*DingoStateManager) GetGovernanceState

func (m *DingoStateManager) GetGovernanceState() *conformance.GovernanceState

GetGovernanceState implements conformance.StateManager.GetGovernanceState.

func (*DingoStateManager) GetProtocolParameters

func (m *DingoStateManager) GetProtocolParameters() common.ProtocolParameters

GetProtocolParameters implements conformance.StateManager.GetProtocolParameters.

func (*DingoStateManager) GetStateProvider

func (m *DingoStateManager) GetStateProvider() conformance.StateProvider

GetStateProvider implements conformance.StateManager.GetStateProvider.

func (*DingoStateManager) LoadInitialState

LoadInitialState implements conformance.StateManager.LoadInitialState.

func (*DingoStateManager) ProcessEpochBoundary

func (m *DingoStateManager) ProcessEpochBoundary(newEpoch uint64) error

ProcessEpochBoundary implements conformance.StateManager.ProcessEpochBoundary.

func (*DingoStateManager) Reset

func (m *DingoStateManager) Reset() error

Reset implements conformance.StateManager.Reset.

func (*DingoStateManager) SetRewardBalances

func (m *DingoStateManager) SetRewardBalances(
	balances map[common.Blake2b224]uint64,
)

SetRewardBalances implements conformance.StateManager.SetRewardBalances.

type DingoStateProvider

type DingoStateProvider struct {
	// contains filtered or unexported fields
}

DingoStateProvider implements conformance.StateProvider by wrapping DingoStateManager to satisfy all gouroboros state interfaces.

func NewDingoStateProvider

func NewDingoStateProvider(manager *DingoStateManager) *DingoStateProvider

NewDingoStateProvider creates a new DingoStateProvider.

func (*DingoStateProvider) CalculateRewards

CalculateRewards calculates rewards for the given epoch

func (*DingoStateProvider) CommitteeMember

func (p *DingoStateProvider) CommitteeMember(
	coldKey common.Blake2b224,
) (*common.CommitteeMember, error)

CommitteeMember looks up a constitutional committee member by credential hash

func (*DingoStateProvider) CommitteeMembers

func (p *DingoStateProvider) CommitteeMembers() ([]common.CommitteeMember, error)

CommitteeMembers returns all committee members

func (*DingoStateProvider) Constitution

func (p *DingoStateProvider) Constitution() (*common.Constitution, error)

Constitution returns the current constitution

func (*DingoStateProvider) CostModels

CostModels returns which Plutus language versions have cost models defined. CostModel values are empty markers (struct{} upstream).

func (*DingoStateProvider) DRepRegistration

func (p *DingoStateProvider) DRepRegistration(
	credential common.Blake2b224,
) (*common.DRepRegistration, error)

DRepRegistration looks up a DRep registration by credential hash

func (*DingoStateProvider) DRepRegistrations

func (p *DingoStateProvider) DRepRegistrations() ([]common.DRepRegistration, error)

DRepRegistrations returns all DRep registrations

func (*DingoStateProvider) GetAdaPots

func (p *DingoStateProvider) GetAdaPots() common.AdaPots

GetAdaPots returns the current ADA pots

func (*DingoStateProvider) GetRewardSnapshot

func (p *DingoStateProvider) GetRewardSnapshot(
	epoch uint64,
) (common.RewardSnapshot, error)

GetRewardSnapshot returns the stake snapshot for reward calculation

func (*DingoStateProvider) GovActionById

func (p *DingoStateProvider) GovActionById(
	id common.GovActionId,
) (*common.GovActionState, error)

GovActionById looks up a governance action by its ID

func (*DingoStateProvider) GovActionExists

func (p *DingoStateProvider) GovActionExists(id common.GovActionId) bool

GovActionExists checks if a governance action exists

func (*DingoStateProvider) IsPoolRegistered

func (p *DingoStateProvider) IsPoolRegistered(
	poolKeyHash common.PoolKeyHash,
) bool

IsPoolRegistered checks if a pool is currently registered

func (*DingoStateProvider) IsRewardAccountRegistered

func (p *DingoStateProvider) IsRewardAccountRegistered(
	cred common.Credential,
) bool

IsRewardAccountRegistered checks if a reward account is registered

func (*DingoStateProvider) IsStakeCredentialRegistered

func (p *DingoStateProvider) IsStakeCredentialRegistered(
	cred common.Credential,
) bool

IsStakeCredentialRegistered checks if a stake credential is currently registered

func (*DingoStateProvider) IsVrfKeyInUse

func (p *DingoStateProvider) IsVrfKeyInUse(
	vrfKeyHash common.Blake2b256,
) (bool, common.PoolKeyHash, error)

IsVrfKeyInUse checks if a VRF key hash is registered by another pool. Conformance tests don't currently test VRF key uniqueness.

func (*DingoStateProvider) NetworkId

func (p *DingoStateProvider) NetworkId() uint

NetworkId returns the network identifier

func (*DingoStateProvider) PoolCurrentState

func (p *DingoStateProvider) PoolCurrentState(
	poolKeyHash common.PoolKeyHash,
) (*common.PoolRegistrationCertificate, *uint64, error)

PoolCurrentState returns the current state of a pool

func (*DingoStateProvider) RewardAccountBalance

func (p *DingoStateProvider) RewardAccountBalance(
	cred common.Credential,
) (*uint64, error)

RewardAccountBalance returns the current reward balance for a stake credential

func (*DingoStateProvider) SlotToTime

func (p *DingoStateProvider) SlotToTime(slot uint64) (time.Time, error)

SlotToTime converts a slot number to a time

func (*DingoStateProvider) StakeRegistration

func (p *DingoStateProvider) StakeRegistration(
	stakingKey []byte,
) ([]common.StakeRegistrationCertificate, error)

StakeRegistration looks up stake registrations by staking key

func (*DingoStateProvider) TimeToSlot

func (p *DingoStateProvider) TimeToSlot(t time.Time) (uint64, error)

TimeToSlot converts a time to a slot number

func (*DingoStateProvider) TreasuryValue

func (p *DingoStateProvider) TreasuryValue() (uint64, error)

TreasuryValue returns the current treasury value

func (*DingoStateProvider) UpdateAdaPots

func (p *DingoStateProvider) UpdateAdaPots(pots common.AdaPots) error

UpdateAdaPots updates the ADA pots

func (*DingoStateProvider) UtxoById

UtxoById looks up a UTxO by transaction input

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL