From 30636c926a780eb131ffce09c41f0e7656a1ad07 Mon Sep 17 00:00:00 2001 From: Aurora Gaffney Date: Wed, 5 Mar 2025 15:22:41 -0500 Subject: [PATCH] feat: account for stake/pool deposits in validation rules Signed-off-by: Aurora Gaffney --- ledger/allegra/rules.go | 11 +++++++++- ledger/allegra/rules_test.go | 32 +++++++++++++++++++++++++++- ledger/alonzo/rules.go | 6 +++++- ledger/alonzo/rules_test.go | 36 ++++++++++++++++++++++++++++++- ledger/babbage/rules.go | 41 +++++++++++++++++++++++++++++++++++- ledger/babbage/rules_test.go | 30 +++++++++++++++++++++++++- ledger/conway/rules.go | 41 +++++++++++++++++++++++++++++++++++- ledger/conway/rules_test.go | 30 +++++++++++++++++++++++++- ledger/mary/rules.go | 11 +++++++++- ledger/mary/rules_test.go | 34 +++++++++++++++++++++++++++++- ledger/shelley/rules.go | 14 +++++++++++- ledger/shelley/rules_test.go | 30 +++++++++++++++++++++++++- 12 files changed, 304 insertions(+), 12 deletions(-) diff --git a/ledger/allegra/rules.go b/ledger/allegra/rules.go index ff7ea87b..a53000d2 100644 --- a/ledger/allegra/rules.go +++ b/ledger/allegra/rules.go @@ -111,7 +111,16 @@ func UtxoValidateValueNotConservedUtxo( ls common.LedgerState, pp common.ProtocolParameters, ) error { - return shelley.UtxoValidateValueNotConservedUtxo(tx, slot, ls, pp) + tmpPparams, ok := pp.(*AllegraProtocolParameters) + if !ok { + return errors.New("pparams are not expected type") + } + return shelley.UtxoValidateValueNotConservedUtxo( + tx, + slot, + ls, + &tmpPparams.ShelleyProtocolParameters, + ) } func UtxoValidateOutputTooSmallUtxo( diff --git a/ledger/allegra/rules_test.go b/ledger/allegra/rules_test.go index d24ee4ca..6f460030 100644 --- a/ledger/allegra/rules_test.go +++ b/ledger/allegra/rules_test.go @@ -528,6 +528,7 @@ func TestUtxoValidateValueNotConservedUtxo(t *testing.T) { testInputTxId := "d228b482a1aae768e4a796380f49e021d9c21f70d3c12cb186b188dedfc0ee22" var testInputAmount uint64 = 555666777 var testFee uint64 = 123456 + var testStakeDeposit uint64 = 2_000_000 testOutputExactAmount := testInputAmount - testFee testOutputUnderAmount := testOutputExactAmount - 999 testOutputOverAmount := testOutputExactAmount + 999 @@ -558,7 +559,11 @@ func TestUtxoValidateValueNotConservedUtxo(t *testing.T) { }, } testSlot := uint64(0) - testProtocolParams := &allegra.AllegraProtocolParameters{} + testProtocolParams := &allegra.AllegraProtocolParameters{ + ShelleyProtocolParameters: shelley.ShelleyProtocolParameters{ + KeyDeposit: uint(testStakeDeposit), + }, + } // Exact amount t.Run( "exact amount", @@ -578,6 +583,31 @@ func TestUtxoValidateValueNotConservedUtxo(t *testing.T) { } }, ) + // Stake registration + t.Run( + "stake registration", + func(t *testing.T) { + testTx.Body.TxOutputs[0].OutputAmount = testOutputExactAmount - testStakeDeposit + testTx.Body.TxCertificates = []common.CertificateWrapper{ + { + Type: common.CertificateTypeStakeRegistration, + Certificate: &common.StakeRegistrationCertificate{}, + }, + } + err := allegra.UtxoValidateValueNotConservedUtxo( + testTx, + testSlot, + testLedgerState, + testProtocolParams, + ) + if err != nil { + t.Errorf( + "UtxoValidateValueNotConservedUtxo should succeed when inputs and outputs are balanced\n got error: %v", + err, + ) + } + }, + ) // Output too low t.Run( "output too low", diff --git a/ledger/alonzo/rules.go b/ledger/alonzo/rules.go index 59e798bd..0923b50a 100644 --- a/ledger/alonzo/rules.go +++ b/ledger/alonzo/rules.go @@ -198,7 +198,11 @@ func UtxoValidateBadInputsUtxo(tx common.Transaction, slot uint64, ls common.Led } func UtxoValidateValueNotConservedUtxo(tx common.Transaction, slot uint64, ls common.LedgerState, pp common.ProtocolParameters) error { - return shelley.UtxoValidateValueNotConservedUtxo(tx, slot, ls, pp) + tmpPparams, ok := pp.(*AlonzoProtocolParameters) + if !ok { + return errors.New("pparams are not expected type") + } + return shelley.UtxoValidateValueNotConservedUtxo(tx, slot, ls, &tmpPparams.ShelleyProtocolParameters) } func UtxoValidateOutputTooSmallUtxo(tx common.Transaction, slot uint64, ls common.LedgerState, pp common.ProtocolParameters) error { diff --git a/ledger/alonzo/rules_test.go b/ledger/alonzo/rules_test.go index 50bb1055..8e5f1cd9 100644 --- a/ledger/alonzo/rules_test.go +++ b/ledger/alonzo/rules_test.go @@ -541,6 +541,7 @@ func TestUtxoValidateValueNotConservedUtxo(t *testing.T) { testInputTxId := "d228b482a1aae768e4a796380f49e021d9c21f70d3c12cb186b188dedfc0ee22" var testInputAmount uint64 = 555666777 var testFee uint64 = 123456 + var testStakeDeposit uint64 = 2_000_000 testOutputExactAmount := testInputAmount - testFee testOutputUnderAmount := testOutputExactAmount - 999 testOutputOverAmount := testOutputExactAmount + 999 @@ -575,7 +576,15 @@ func TestUtxoValidateValueNotConservedUtxo(t *testing.T) { }, } testSlot := uint64(0) - testProtocolParams := &alonzo.AlonzoProtocolParameters{} + testProtocolParams := &alonzo.AlonzoProtocolParameters{ + MaryProtocolParameters: mary.MaryProtocolParameters{ + AllegraProtocolParameters: allegra.AllegraProtocolParameters{ + ShelleyProtocolParameters: shelley.ShelleyProtocolParameters{ + KeyDeposit: uint(testStakeDeposit), + }, + }, + }, + } // Exact amount t.Run( "exact amount", @@ -595,6 +604,31 @@ func TestUtxoValidateValueNotConservedUtxo(t *testing.T) { } }, ) + // Stake registration + t.Run( + "stake registration", + func(t *testing.T) { + testTx.Body.TxOutputs[0].OutputAmount.Amount = testOutputExactAmount - testStakeDeposit + testTx.Body.TxCertificates = []common.CertificateWrapper{ + { + Type: common.CertificateTypeStakeRegistration, + Certificate: &common.StakeRegistrationCertificate{}, + }, + } + err := alonzo.UtxoValidateValueNotConservedUtxo( + testTx, + testSlot, + testLedgerState, + testProtocolParams, + ) + if err != nil { + t.Errorf( + "UtxoValidateValueNotConservedUtxo should succeed when inputs and outputs are balanced\n got error: %v", + err, + ) + } + }, + ) // Output too low t.Run( "output too low", diff --git a/ledger/babbage/rules.go b/ledger/babbage/rules.go index 64dd5be5..116e849b 100644 --- a/ledger/babbage/rules.go +++ b/ledger/babbage/rules.go @@ -177,7 +177,46 @@ func UtxoValidateBadInputsUtxo(tx common.Transaction, slot uint64, ls common.Led } func UtxoValidateValueNotConservedUtxo(tx common.Transaction, slot uint64, ls common.LedgerState, pp common.ProtocolParameters) error { - return shelley.UtxoValidateValueNotConservedUtxo(tx, slot, ls, pp) + tmpPparams, ok := pp.(*BabbageProtocolParameters) + if !ok { + return errors.New("pparams are not expected type") + } + // Calculate consumed value + // consumed = value from input(s) + withdrawals + refunds(?) + var consumedValue uint64 + for _, tmpInput := range tx.Inputs() { + tmpUtxo, err := ls.UtxoById(tmpInput) + // Ignore errors fetching the UTxO and exclude it from calculations + if err != nil { + continue + } + consumedValue += tmpUtxo.Output.Amount() + } + for _, tmpWithdrawalAmount := range tx.Withdrawals() { + consumedValue += tmpWithdrawalAmount + } + // Calculate produced value + // produced = value from output(s) + fee + deposits + var producedValue uint64 + for _, tmpOutput := range tx.Outputs() { + producedValue += tmpOutput.Amount() + } + producedValue += tx.Fee() + for _, cert := range tx.Certificates() { + switch cert.(type) { + case *common.PoolRegistrationCertificate: + producedValue += uint64(tmpPparams.PoolDeposit) + case *common.StakeRegistrationCertificate: + producedValue += uint64(tmpPparams.KeyDeposit) + } + } + if consumedValue == producedValue { + return nil + } + return shelley.ValueNotConservedUtxoError{ + Consumed: consumedValue, + Produced: producedValue, + } } func UtxoValidateOutputTooSmallUtxo(tx common.Transaction, slot uint64, ls common.LedgerState, pp common.ProtocolParameters) error { diff --git a/ledger/babbage/rules_test.go b/ledger/babbage/rules_test.go index 69e0578a..1565aa15 100644 --- a/ledger/babbage/rules_test.go +++ b/ledger/babbage/rules_test.go @@ -544,6 +544,7 @@ func TestUtxoValidateValueNotConservedUtxo(t *testing.T) { testInputTxId := "d228b482a1aae768e4a796380f49e021d9c21f70d3c12cb186b188dedfc0ee22" var testInputAmount uint64 = 555666777 var testFee uint64 = 123456 + var testStakeDeposit uint64 = 2_000_000 testOutputExactAmount := testInputAmount - testFee testOutputUnderAmount := testOutputExactAmount - 999 testOutputOverAmount := testOutputExactAmount + 999 @@ -580,7 +581,9 @@ func TestUtxoValidateValueNotConservedUtxo(t *testing.T) { }, } testSlot := uint64(0) - testProtocolParams := &babbage.BabbageProtocolParameters{} + testProtocolParams := &babbage.BabbageProtocolParameters{ + KeyDeposit: uint(testStakeDeposit), + } // Exact amount t.Run( "exact amount", @@ -600,6 +603,31 @@ func TestUtxoValidateValueNotConservedUtxo(t *testing.T) { } }, ) + // Stake registration + t.Run( + "stake registration", + func(t *testing.T) { + testTx.Body.TxOutputs[0].OutputAmount.Amount = testOutputExactAmount - testStakeDeposit + testTx.Body.TxCertificates = []common.CertificateWrapper{ + { + Type: common.CertificateTypeStakeRegistration, + Certificate: &common.StakeRegistrationCertificate{}, + }, + } + err := babbage.UtxoValidateValueNotConservedUtxo( + testTx, + testSlot, + testLedgerState, + testProtocolParams, + ) + if err != nil { + t.Errorf( + "UtxoValidateValueNotConservedUtxo should succeed when inputs and outputs are balanced\n got error: %v", + err, + ) + } + }, + ) // Output too low t.Run( "output too low", diff --git a/ledger/conway/rules.go b/ledger/conway/rules.go index 84686c06..a00c4edd 100644 --- a/ledger/conway/rules.go +++ b/ledger/conway/rules.go @@ -173,7 +173,46 @@ func UtxoValidateBadInputsUtxo(tx common.Transaction, slot uint64, ls common.Led } func UtxoValidateValueNotConservedUtxo(tx common.Transaction, slot uint64, ls common.LedgerState, pp common.ProtocolParameters) error { - return shelley.UtxoValidateValueNotConservedUtxo(tx, slot, ls, pp) + tmpPparams, ok := pp.(*ConwayProtocolParameters) + if !ok { + return errors.New("pparams are not expected type") + } + // Calculate consumed value + // consumed = value from input(s) + withdrawals + refunds(?) + var consumedValue uint64 + for _, tmpInput := range tx.Inputs() { + tmpUtxo, err := ls.UtxoById(tmpInput) + // Ignore errors fetching the UTxO and exclude it from calculations + if err != nil { + continue + } + consumedValue += tmpUtxo.Output.Amount() + } + for _, tmpWithdrawalAmount := range tx.Withdrawals() { + consumedValue += tmpWithdrawalAmount + } + // Calculate produced value + // produced = value from output(s) + fee + deposits + var producedValue uint64 + for _, tmpOutput := range tx.Outputs() { + producedValue += tmpOutput.Amount() + } + producedValue += tx.Fee() + for _, cert := range tx.Certificates() { + switch cert.(type) { + case *common.PoolRegistrationCertificate: + producedValue += uint64(tmpPparams.PoolDeposit) + case *common.StakeRegistrationCertificate: + producedValue += uint64(tmpPparams.KeyDeposit) + } + } + if consumedValue == producedValue { + return nil + } + return shelley.ValueNotConservedUtxoError{ + Consumed: consumedValue, + Produced: producedValue, + } } func UtxoValidateOutputTooSmallUtxo(tx common.Transaction, slot uint64, ls common.LedgerState, pp common.ProtocolParameters) error { diff --git a/ledger/conway/rules_test.go b/ledger/conway/rules_test.go index 869152e5..82ed9f8c 100644 --- a/ledger/conway/rules_test.go +++ b/ledger/conway/rules_test.go @@ -545,6 +545,7 @@ func TestUtxoValidateValueNotConservedUtxo(t *testing.T) { testInputTxId := "d228b482a1aae768e4a796380f49e021d9c21f70d3c12cb186b188dedfc0ee22" var testInputAmount uint64 = 555666777 var testFee uint64 = 123456 + var testStakeDeposit uint64 = 2_000_000 testOutputExactAmount := testInputAmount - testFee testOutputUnderAmount := testOutputExactAmount - 999 testOutputOverAmount := testOutputExactAmount + 999 @@ -583,7 +584,9 @@ func TestUtxoValidateValueNotConservedUtxo(t *testing.T) { }, } testSlot := uint64(0) - testProtocolParams := &conway.ConwayProtocolParameters{} + testProtocolParams := &conway.ConwayProtocolParameters{ + KeyDeposit: uint(testStakeDeposit), + } // Exact amount t.Run( "exact amount", @@ -603,6 +606,31 @@ func TestUtxoValidateValueNotConservedUtxo(t *testing.T) { } }, ) + // Stake registration + t.Run( + "stake registration", + func(t *testing.T) { + testTx.Body.TxOutputs[0].OutputAmount.Amount = testOutputExactAmount - testStakeDeposit + testTx.Body.TxCertificates = []common.CertificateWrapper{ + { + Type: common.CertificateTypeStakeRegistration, + Certificate: &common.StakeRegistrationCertificate{}, + }, + } + err := conway.UtxoValidateValueNotConservedUtxo( + testTx, + testSlot, + testLedgerState, + testProtocolParams, + ) + if err != nil { + t.Errorf( + "UtxoValidateValueNotConservedUtxo should succeed when inputs and outputs are balanced\n got error: %v", + err, + ) + } + }, + ) // Output too low t.Run( "output too low", diff --git a/ledger/mary/rules.go b/ledger/mary/rules.go index 970bd0f4..7a79800e 100644 --- a/ledger/mary/rules.go +++ b/ledger/mary/rules.go @@ -140,7 +140,16 @@ func UtxoValidateValueNotConservedUtxo( ls common.LedgerState, pp common.ProtocolParameters, ) error { - return shelley.UtxoValidateValueNotConservedUtxo(tx, slot, ls, pp) + tmpPparams, ok := pp.(*MaryProtocolParameters) + if !ok { + return errors.New("pparams are not expected type") + } + return shelley.UtxoValidateValueNotConservedUtxo( + tx, + slot, + ls, + &tmpPparams.ShelleyProtocolParameters, + ) } func UtxoValidateOutputTooSmallUtxo( diff --git a/ledger/mary/rules_test.go b/ledger/mary/rules_test.go index 7d5af093..24eefcf7 100644 --- a/ledger/mary/rules_test.go +++ b/ledger/mary/rules_test.go @@ -540,6 +540,7 @@ func TestUtxoValidateValueNotConservedUtxo(t *testing.T) { testInputTxId := "d228b482a1aae768e4a796380f49e021d9c21f70d3c12cb186b188dedfc0ee22" var testInputAmount uint64 = 555666777 var testFee uint64 = 123456 + var testStakeDeposit uint64 = 2_000_000 testOutputExactAmount := testInputAmount - testFee testOutputUnderAmount := testOutputExactAmount - 999 testOutputOverAmount := testOutputExactAmount + 999 @@ -575,7 +576,13 @@ func TestUtxoValidateValueNotConservedUtxo(t *testing.T) { }, } testSlot := uint64(0) - testProtocolParams := &mary.MaryProtocolParameters{} + testProtocolParams := &mary.MaryProtocolParameters{ + AllegraProtocolParameters: allegra.AllegraProtocolParameters{ + ShelleyProtocolParameters: shelley.ShelleyProtocolParameters{ + KeyDeposit: uint(testStakeDeposit), + }, + }, + } // Exact amount t.Run( "exact amount", @@ -595,6 +602,31 @@ func TestUtxoValidateValueNotConservedUtxo(t *testing.T) { } }, ) + // Stake registration + t.Run( + "stake registration", + func(t *testing.T) { + testTx.Body.TxOutputs[0].OutputAmount.Amount = testOutputExactAmount - testStakeDeposit + testTx.Body.TxCertificates = []common.CertificateWrapper{ + { + Type: common.CertificateTypeStakeRegistration, + Certificate: &common.StakeRegistrationCertificate{}, + }, + } + err := mary.UtxoValidateValueNotConservedUtxo( + testTx, + testSlot, + testLedgerState, + testProtocolParams, + ) + if err != nil { + t.Errorf( + "UtxoValidateValueNotConservedUtxo should succeed when inputs and outputs are balanced\n got error: %v", + err, + ) + } + }, + ) // Output too low t.Run( "output too low", diff --git a/ledger/shelley/rules.go b/ledger/shelley/rules.go index ef924eb3..9f2e3785 100644 --- a/ledger/shelley/rules.go +++ b/ledger/shelley/rules.go @@ -162,6 +162,10 @@ func UtxoValidateValueNotConservedUtxo( ls common.LedgerState, pp common.ProtocolParameters, ) error { + tmpPparams, ok := pp.(*ShelleyProtocolParameters) + if !ok { + return errors.New("pparams are not expected type") + } // Calculate consumed value // consumed = value from input(s) + withdrawals + refunds(?) var consumedValue uint64 @@ -177,12 +181,20 @@ func UtxoValidateValueNotConservedUtxo( consumedValue += tmpWithdrawalAmount } // Calculate produced value - // produced = value from output(s) + fee + deposits(?) + // produced = value from output(s) + fee + deposits var producedValue uint64 for _, tmpOutput := range tx.Outputs() { producedValue += tmpOutput.Amount() } producedValue += tx.Fee() + for _, cert := range tx.Certificates() { + switch cert.(type) { + case *common.PoolRegistrationCertificate: + producedValue += uint64(tmpPparams.PoolDeposit) + case *common.StakeRegistrationCertificate: + producedValue += uint64(tmpPparams.KeyDeposit) + } + } if consumedValue == producedValue { return nil } diff --git a/ledger/shelley/rules_test.go b/ledger/shelley/rules_test.go index 736ab163..49855d31 100644 --- a/ledger/shelley/rules_test.go +++ b/ledger/shelley/rules_test.go @@ -517,6 +517,7 @@ func TestUtxoValidateValueNotConservedUtxo(t *testing.T) { testInputTxId := "d228b482a1aae768e4a796380f49e021d9c21f70d3c12cb186b188dedfc0ee22" var testInputAmount uint64 = 555666777 var testFee uint64 = 123456 + var testStakeDeposit uint64 = 2_000_000 testOutputExactAmount := testInputAmount - testFee testOutputUnderAmount := testOutputExactAmount - 999 testOutputOverAmount := testOutputExactAmount + 999 @@ -545,7 +546,9 @@ func TestUtxoValidateValueNotConservedUtxo(t *testing.T) { }, } testSlot := uint64(0) - testProtocolParams := &shelley.ShelleyProtocolParameters{} + testProtocolParams := &shelley.ShelleyProtocolParameters{ + KeyDeposit: uint(testStakeDeposit), + } // Exact amount t.Run( "exact amount", @@ -565,6 +568,31 @@ func TestUtxoValidateValueNotConservedUtxo(t *testing.T) { } }, ) + // Stake registration + t.Run( + "stake registration", + func(t *testing.T) { + testTx.Body.TxOutputs[0].OutputAmount = testOutputExactAmount - testStakeDeposit + testTx.Body.TxCertificates = []common.CertificateWrapper{ + { + Type: common.CertificateTypeStakeRegistration, + Certificate: &common.StakeRegistrationCertificate{}, + }, + } + err := shelley.UtxoValidateValueNotConservedUtxo( + testTx, + testSlot, + testLedgerState, + testProtocolParams, + ) + if err != nil { + t.Errorf( + "UtxoValidateValueNotConservedUtxo should succeed when inputs and outputs are balanced\n got error: %v", + err, + ) + } + }, + ) // Output too low t.Run( "output too low",