diff --git a/internal/test/ledger/ledger.go b/internal/test/ledger/ledger.go new file mode 100644 index 00000000..bc4a9543 --- /dev/null +++ b/internal/test/ledger/ledger.go @@ -0,0 +1,67 @@ +// Copyright 2025 Blink Labs Software +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package test_ledger + +import ( + "errors" + + "github.com/blinklabs-io/gouroboros/ledger/common" +) + +type MockLedgerState struct { + MockNetworkId uint + MockUtxos []common.Utxo + MockStakeRegistration []common.StakeRegistrationCertificate + MockPoolRegistration []common.PoolRegistrationCertificate +} + +func (ls MockLedgerState) NetworkId() uint { + return ls.MockNetworkId +} + +func (ls MockLedgerState) UtxoById( + id common.TransactionInput, +) (common.Utxo, error) { + for _, tmpUtxo := range ls.MockUtxos { + if id.Index() != tmpUtxo.Id.Index() { + continue + } + if string(id.Id().Bytes()) != string(tmpUtxo.Id.Id().Bytes()) { + continue + } + return tmpUtxo, nil + } + return common.Utxo{}, errors.New("not found") +} + +func (ls MockLedgerState) StakeRegistration(stakingKey []byte) ([]common.StakeRegistrationCertificate, error) { + ret := []common.StakeRegistrationCertificate{} + for _, cert := range ls.MockStakeRegistration { + if string(cert.StakeRegistration.Credential) == string(stakingKey) { + ret = append(ret, cert) + } + } + return ret, nil +} + +func (ls MockLedgerState) PoolRegistration(poolKeyHash []byte) ([]common.PoolRegistrationCertificate, error) { + ret := []common.PoolRegistrationCertificate{} + for _, cert := range ls.MockPoolRegistration { + if string(common.Blake2b224(cert.Operator).Bytes()) == string(poolKeyHash) { + ret = append(ret, cert) + } + } + return ret, nil +} diff --git a/ledger/allegra/rules_test.go b/ledger/allegra/rules_test.go index 6f460030..2fb08e5b 100644 --- a/ledger/allegra/rules_test.go +++ b/ledger/allegra/rules_test.go @@ -17,9 +17,9 @@ package allegra_test import ( "crypto/rand" "encoding/hex" - "errors" "testing" + test "github.com/blinklabs-io/gouroboros/internal/test/ledger" "github.com/blinklabs-io/gouroboros/ledger/allegra" "github.com/blinklabs-io/gouroboros/ledger/common" "github.com/blinklabs-io/gouroboros/ledger/shelley" @@ -27,30 +27,6 @@ import ( "github.com/stretchr/testify/assert" ) -type testLedgerState struct { - networkId uint - utxos []common.Utxo -} - -func (ls testLedgerState) NetworkId() uint { - return ls.networkId -} - -func (ls testLedgerState) UtxoById( - id common.TransactionInput, -) (common.Utxo, error) { - for _, tmpUtxo := range ls.utxos { - if id.Index() != tmpUtxo.Id.Index() { - continue - } - if string(id.Id().Bytes()) != string(tmpUtxo.Id.Id().Bytes()) { - continue - } - return tmpUtxo, nil - } - return common.Utxo{}, errors.New("not found") -} - func TestUtxoValidateOutsideValidityIntervalUtxo(t *testing.T) { var testSlot uint64 = 555666777 var testZeroSlot uint64 = 0 @@ -59,7 +35,7 @@ func TestUtxoValidateOutsideValidityIntervalUtxo(t *testing.T) { TxValidityIntervalStart: testSlot, }, } - testLedgerState := testLedgerState{} + testLedgerState := test.MockLedgerState{} testProtocolParams := &allegra.AllegraProtocolParameters{} var testBeforeSlot uint64 = 555666700 var testAfterSlot uint64 = 555666799 @@ -165,7 +141,7 @@ func TestUtxoValidateInputSetEmptyUtxo(t *testing.T) { }, }, } - testLedgerState := testLedgerState{} + testLedgerState := test.MockLedgerState{} testSlot := uint64(0) testProtocolParams := &allegra.AllegraProtocolParameters{} // Non-empty @@ -236,7 +212,7 @@ func TestUtxoValidateFeeTooSmallUtxo(t *testing.T) { MinFeeB: 53, }, } - testLedgerState := testLedgerState{} + testLedgerState := test.MockLedgerState{} testSlot := uint64(0) // Test helper function testRun := func(t *testing.T, name string, testFee uint64, validateFunc func(*testing.T, error)) { @@ -322,8 +298,8 @@ func TestUtxoValidateBadInputsUtxo(t *testing.T) { testTx := &allegra.AllegraTransaction{ Body: allegra.AllegraTransactionBody{}, } - testLedgerState := testLedgerState{ - utxos: []common.Utxo{ + testLedgerState := test.MockLedgerState{ + MockUtxos: []common.Utxo{ { Id: testGoodInput, }, @@ -402,8 +378,8 @@ func TestUtxoValidateWrongNetwork(t *testing.T) { }, }, } - testLedgerState := testLedgerState{ - networkId: common.AddressNetworkMainnet, + testLedgerState := test.MockLedgerState{ + MockNetworkId: common.AddressNetworkMainnet, } testSlot := uint64(0) testProtocolParams := &allegra.AllegraProtocolParameters{} @@ -470,8 +446,8 @@ func TestUtxoValidateWrongNetworkWithdrawal(t *testing.T) { }, }, } - testLedgerState := testLedgerState{ - networkId: common.AddressNetworkMainnet, + testLedgerState := test.MockLedgerState{ + MockNetworkId: common.AddressNetworkMainnet, } testSlot := uint64(0) testProtocolParams := &allegra.AllegraProtocolParameters{} @@ -529,6 +505,8 @@ func TestUtxoValidateValueNotConservedUtxo(t *testing.T) { var testInputAmount uint64 = 555666777 var testFee uint64 = 123456 var testStakeDeposit uint64 = 2_000_000 + var testStakeCred1 = []byte{0x01, 0x23, 0x45} + var testStakeCred2 = []byte{0xab, 0xcd, 0xef} testOutputExactAmount := testInputAmount - testFee testOutputUnderAmount := testOutputExactAmount - 999 testOutputOverAmount := testOutputExactAmount + 999 @@ -548,8 +526,8 @@ func TestUtxoValidateValueNotConservedUtxo(t *testing.T) { }, }, } - testLedgerState := testLedgerState{ - utxos: []common.Utxo{ + testLedgerState := test.MockLedgerState{ + MockUtxos: []common.Utxo{ { Id: shelley.NewShelleyTransactionInput(testInputTxId, 0), Output: shelley.ShelleyTransactionOutput{ @@ -557,6 +535,13 @@ func TestUtxoValidateValueNotConservedUtxo(t *testing.T) { }, }, }, + MockStakeRegistration: []common.StakeRegistrationCertificate{ + { + StakeRegistration: common.StakeCredential{ + Credential: testStakeCred2, + }, + }, + }, } testSlot := uint64(0) testProtocolParams := &allegra.AllegraProtocolParameters{ @@ -583,15 +568,48 @@ func TestUtxoValidateValueNotConservedUtxo(t *testing.T) { } }, ) - // Stake registration + // First stake registration t.Run( - "stake registration", + "first stake registration", func(t *testing.T) { testTx.Body.TxOutputs[0].OutputAmount = testOutputExactAmount - testStakeDeposit testTx.Body.TxCertificates = []common.CertificateWrapper{ { - Type: common.CertificateTypeStakeRegistration, - Certificate: &common.StakeRegistrationCertificate{}, + Type: common.CertificateTypeStakeRegistration, + Certificate: &common.StakeRegistrationCertificate{ + StakeRegistration: common.StakeCredential{ + Credential: testStakeCred1, + }, + }, + }, + } + 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, + ) + } + }, + ) + // Second stake registration + t.Run( + "second stake registration", + func(t *testing.T) { + testTx.Body.TxOutputs[0].OutputAmount = testOutputExactAmount + testTx.Body.TxCertificates = []common.CertificateWrapper{ + { + Type: common.CertificateTypeStakeRegistration, + Certificate: &common.StakeRegistrationCertificate{ + StakeRegistration: common.StakeCredential{ + Credential: testStakeCred2, + }, + }, }, } err := allegra.UtxoValidateValueNotConservedUtxo( @@ -679,7 +697,7 @@ func TestUtxoValidateOutputTooSmallUtxo(t *testing.T) { }, }, } - testLedgerState := testLedgerState{} + testLedgerState := test.MockLedgerState{} testSlot := uint64(0) testProtocolParams := &allegra.AllegraProtocolParameters{ ShelleyProtocolParameters: shelley.ShelleyProtocolParameters{ @@ -766,7 +784,7 @@ func TestUtxoValidateOutputBootAddrAttrsTooBig(t *testing.T) { }, }, } - testLedgerState := testLedgerState{} + testLedgerState := test.MockLedgerState{} testSlot := uint64(0) testProtocolParams := &allegra.AllegraProtocolParameters{} // Good @@ -822,7 +840,7 @@ func TestUtxoValidateMaxTxSizeUtxo(t *testing.T) { var testMaxTxSizeSmall uint = 2 var testMaxTxSizeLarge uint = 64 * 1024 testTx := &allegra.AllegraTransaction{} - testLedgerState := testLedgerState{} + testLedgerState := test.MockLedgerState{} testSlot := uint64(0) testProtocolParams := &allegra.AllegraProtocolParameters{} // Transaction under limit diff --git a/ledger/alonzo/rules_test.go b/ledger/alonzo/rules_test.go index 8e5f1cd9..8c9e9039 100644 --- a/ledger/alonzo/rules_test.go +++ b/ledger/alonzo/rules_test.go @@ -17,10 +17,10 @@ package alonzo_test import ( "crypto/rand" "encoding/hex" - "fmt" "testing" "github.com/blinklabs-io/gouroboros/cbor" + test "github.com/blinklabs-io/gouroboros/internal/test/ledger" "github.com/blinklabs-io/gouroboros/ledger/allegra" "github.com/blinklabs-io/gouroboros/ledger/alonzo" "github.com/blinklabs-io/gouroboros/ledger/common" @@ -30,28 +30,6 @@ import ( "github.com/stretchr/testify/assert" ) -type testLedgerState struct { - networkId uint - utxos []common.Utxo -} - -func (ls testLedgerState) NetworkId() uint { - return ls.networkId -} - -func (ls testLedgerState) UtxoById(id common.TransactionInput) (common.Utxo, error) { - for _, tmpUtxo := range ls.utxos { - if id.Index() != tmpUtxo.Id.Index() { - continue - } - if string(id.Id().Bytes()) != string(tmpUtxo.Id.Id().Bytes()) { - continue - } - return tmpUtxo, nil - } - return common.Utxo{}, fmt.Errorf("not found") -} - func TestUtxoValidateOutsideValidityIntervalUtxo(t *testing.T) { var testSlot uint64 = 555666777 var testZeroSlot uint64 = 0 @@ -64,7 +42,7 @@ func TestUtxoValidateOutsideValidityIntervalUtxo(t *testing.T) { }, }, } - testLedgerState := testLedgerState{} + testLedgerState := test.MockLedgerState{} testProtocolParams := &alonzo.AlonzoProtocolParameters{} var testBeforeSlot uint64 = 555666700 var testAfterSlot uint64 = 555666799 @@ -174,7 +152,7 @@ func TestUtxoValidateInputSetEmptyUtxo(t *testing.T) { }, }, } - testLedgerState := testLedgerState{} + testLedgerState := test.MockLedgerState{} testSlot := uint64(0) testProtocolParams := &alonzo.AlonzoProtocolParameters{} // Non-empty @@ -253,7 +231,7 @@ func TestUtxoValidateFeeTooSmallUtxo(t *testing.T) { }, }, } - testLedgerState := testLedgerState{} + testLedgerState := test.MockLedgerState{} testSlot := uint64(0) // Test helper function testRun := func(t *testing.T, name string, testFee uint64, validateFunc func(*testing.T, error)) { @@ -339,8 +317,8 @@ func TestUtxoValidateBadInputsUtxo(t *testing.T) { testTx := &alonzo.AlonzoTransaction{ Body: alonzo.AlonzoTransactionBody{}, } - testLedgerState := testLedgerState{ - utxos: []common.Utxo{ + testLedgerState := test.MockLedgerState{ + MockUtxos: []common.Utxo{ { Id: testGoodInput, }, @@ -415,8 +393,8 @@ func TestUtxoValidateWrongNetwork(t *testing.T) { }, }, } - testLedgerState := testLedgerState{ - networkId: common.AddressNetworkMainnet, + testLedgerState := test.MockLedgerState{ + MockNetworkId: common.AddressNetworkMainnet, } testSlot := uint64(0) testProtocolParams := &alonzo.AlonzoProtocolParameters{} @@ -483,8 +461,8 @@ func TestUtxoValidateWrongNetworkWithdrawal(t *testing.T) { }, }, } - testLedgerState := testLedgerState{ - networkId: common.AddressNetworkMainnet, + testLedgerState := test.MockLedgerState{ + MockNetworkId: common.AddressNetworkMainnet, } testSlot := uint64(0) testProtocolParams := &alonzo.AlonzoProtocolParameters{} @@ -542,6 +520,8 @@ func TestUtxoValidateValueNotConservedUtxo(t *testing.T) { var testInputAmount uint64 = 555666777 var testFee uint64 = 123456 var testStakeDeposit uint64 = 2_000_000 + var testStakeCred1 = []byte{0x01, 0x23, 0x45} + var testStakeCred2 = []byte{0xab, 0xcd, 0xef} testOutputExactAmount := testInputAmount - testFee testOutputUnderAmount := testOutputExactAmount - 999 testOutputOverAmount := testOutputExactAmount + 999 @@ -565,8 +545,8 @@ func TestUtxoValidateValueNotConservedUtxo(t *testing.T) { }, }, } - testLedgerState := testLedgerState{ - utxos: []common.Utxo{ + testLedgerState := test.MockLedgerState{ + MockUtxos: []common.Utxo{ { Id: shelley.NewShelleyTransactionInput(testInputTxId, 0), Output: shelley.ShelleyTransactionOutput{ @@ -574,6 +554,13 @@ func TestUtxoValidateValueNotConservedUtxo(t *testing.T) { }, }, }, + MockStakeRegistration: []common.StakeRegistrationCertificate{ + { + StakeRegistration: common.StakeCredential{ + Credential: testStakeCred2, + }, + }, + }, } testSlot := uint64(0) testProtocolParams := &alonzo.AlonzoProtocolParameters{ @@ -604,15 +591,48 @@ func TestUtxoValidateValueNotConservedUtxo(t *testing.T) { } }, ) - // Stake registration + // First stake registration t.Run( - "stake registration", + "first 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{}, + Type: common.CertificateTypeStakeRegistration, + Certificate: &common.StakeRegistrationCertificate{ + StakeRegistration: common.StakeCredential{ + Credential: testStakeCred1, + }, + }, + }, + } + 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, + ) + } + }, + ) + // Second stake registration + t.Run( + "second stake registration", + func(t *testing.T) { + testTx.Body.TxOutputs[0].OutputAmount.Amount = testOutputExactAmount + testTx.Body.TxCertificates = []common.CertificateWrapper{ + { + Type: common.CertificateTypeStakeRegistration, + Certificate: &common.StakeRegistrationCertificate{ + StakeRegistration: common.StakeCredential{ + Credential: testStakeCred2, + }, + }, }, } err := alonzo.UtxoValidateValueNotConservedUtxo( @@ -698,7 +718,7 @@ func TestUtxoValidateOutputTooSmallUtxo(t *testing.T) { }, }, } - testLedgerState := testLedgerState{} + testLedgerState := test.MockLedgerState{} testSlot := uint64(0) testProtocolParams := &alonzo.AlonzoProtocolParameters{ MaryProtocolParameters: mary.MaryProtocolParameters{ @@ -791,7 +811,7 @@ func TestUtxoValidateOutputTooBigUtxo(t *testing.T) { }, }, } - testLedgerState := testLedgerState{} + testLedgerState := test.MockLedgerState{} testSlot := uint64(0) testProtocolParams := &alonzo.AlonzoProtocolParameters{ MaxValueSize: 4000, @@ -872,7 +892,7 @@ func TestUtxoValidateOutputBootAddrAttrsTooBig(t *testing.T) { }, }, } - testLedgerState := testLedgerState{} + testLedgerState := test.MockLedgerState{} testSlot := uint64(0) testProtocolParams := &alonzo.AlonzoProtocolParameters{} // Good @@ -928,7 +948,7 @@ func TestUtxoValidateMaxTxSizeUtxo(t *testing.T) { var testMaxTxSizeSmall uint = 2 var testMaxTxSizeLarge uint = 64 * 1024 testTx := &alonzo.AlonzoTransaction{} - testLedgerState := testLedgerState{} + testLedgerState := test.MockLedgerState{} testSlot := uint64(0) testProtocolParams := &alonzo.AlonzoProtocolParameters{} // Transaction under limit @@ -1002,8 +1022,8 @@ func TestUtxoValidateInsufficientCollateral(t *testing.T) { }, }, } - testLedgerState := testLedgerState{ - utxos: []common.Utxo{ + testLedgerState := test.MockLedgerState{ + MockUtxos: []common.Utxo{ { Id: shelley.NewShelleyTransactionInput(testInputTxId, 0), Output: shelley.ShelleyTransactionOutput{ @@ -1091,8 +1111,8 @@ func TestUtxoValidateCollateralContainsNonAda(t *testing.T) { tmpMultiAsset := common.NewMultiAsset[common.MultiAssetTypeOutput]( map[common.Blake2b224]map[cbor.ByteString]uint64{}, ) - testLedgerState := testLedgerState{ - utxos: []common.Utxo{ + testLedgerState := test.MockLedgerState{ + MockUtxos: []common.Utxo{ { Id: shelley.NewShelleyTransactionInput(testInputTxId, 0), Output: shelley.ShelleyTransactionOutput{ @@ -1178,8 +1198,8 @@ func TestUtxoValidateNoCollateralInputs(t *testing.T) { }, }, } - testLedgerState := testLedgerState{ - utxos: []common.Utxo{ + testLedgerState := test.MockLedgerState{ + MockUtxos: []common.Utxo{ { Id: shelley.NewShelleyTransactionInput(testInputTxId, 0), Output: shelley.ShelleyTransactionOutput{ @@ -1256,7 +1276,7 @@ func TestUtxoValidateExUnitsTooBigUtxo(t *testing.T) { testTx := &alonzo.AlonzoTransaction{ WitnessSet: alonzo.AlonzoTransactionWitnessSet{}, } - testLedgerState := testLedgerState{} + testLedgerState := test.MockLedgerState{} testSlot := uint64(0) testProtocolParams := &alonzo.AlonzoProtocolParameters{ MaxTxExUnits: common.ExUnits{ diff --git a/ledger/babbage/rules.go b/ledger/babbage/rules.go index 116e849b..a47ed27b 100644 --- a/ledger/babbage/rules.go +++ b/ledger/babbage/rules.go @@ -203,11 +203,23 @@ func UtxoValidateValueNotConservedUtxo(tx common.Transaction, slot uint64, ls co } producedValue += tx.Fee() for _, cert := range tx.Certificates() { - switch cert.(type) { + switch tmpCert := cert.(type) { case *common.PoolRegistrationCertificate: - producedValue += uint64(tmpPparams.PoolDeposit) + certs, err := ls.PoolRegistration(common.Blake2b224(tmpCert.Operator).Bytes()) + if err != nil { + return err + } + if len(certs) == 0 { + producedValue += uint64(tmpPparams.PoolDeposit) + } case *common.StakeRegistrationCertificate: - producedValue += uint64(tmpPparams.KeyDeposit) + certs, err := ls.StakeRegistration(tmpCert.StakeRegistration.Credential) + if err != nil { + return err + } + if len(certs) == 0 { + producedValue += uint64(tmpPparams.KeyDeposit) + } } } if consumedValue == producedValue { diff --git a/ledger/babbage/rules_test.go b/ledger/babbage/rules_test.go index 1565aa15..0a5fa160 100644 --- a/ledger/babbage/rules_test.go +++ b/ledger/babbage/rules_test.go @@ -17,10 +17,10 @@ package babbage_test import ( "crypto/rand" "encoding/hex" - "fmt" "testing" "github.com/blinklabs-io/gouroboros/cbor" + test "github.com/blinklabs-io/gouroboros/internal/test/ledger" "github.com/blinklabs-io/gouroboros/ledger/allegra" "github.com/blinklabs-io/gouroboros/ledger/alonzo" "github.com/blinklabs-io/gouroboros/ledger/babbage" @@ -31,28 +31,6 @@ import ( "github.com/stretchr/testify/assert" ) -type testLedgerState struct { - networkId uint - utxos []common.Utxo -} - -func (ls testLedgerState) NetworkId() uint { - return ls.networkId -} - -func (ls testLedgerState) UtxoById(id common.TransactionInput) (common.Utxo, error) { - for _, tmpUtxo := range ls.utxos { - if id.Index() != tmpUtxo.Id.Index() { - continue - } - if string(id.Id().Bytes()) != string(tmpUtxo.Id.Id().Bytes()) { - continue - } - return tmpUtxo, nil - } - return common.Utxo{}, fmt.Errorf("not found") -} - func TestUtxoValidateOutsideValidityIntervalUtxo(t *testing.T) { var testSlot uint64 = 555666777 var testZeroSlot uint64 = 0 @@ -67,7 +45,7 @@ func TestUtxoValidateOutsideValidityIntervalUtxo(t *testing.T) { }, }, } - testLedgerState := testLedgerState{} + testLedgerState := test.MockLedgerState{} testProtocolParams := &babbage.BabbageProtocolParameters{} var testBeforeSlot uint64 = 555666700 var testAfterSlot uint64 = 555666799 @@ -179,7 +157,7 @@ func TestUtxoValidateInputSetEmptyUtxo(t *testing.T) { }, }, } - testLedgerState := testLedgerState{} + testLedgerState := test.MockLedgerState{} testSlot := uint64(0) testProtocolParams := &babbage.BabbageProtocolParameters{} // Non-empty @@ -254,7 +232,7 @@ func TestUtxoValidateFeeTooSmallUtxo(t *testing.T) { MinFeeA: 7, MinFeeB: 53, } - testLedgerState := testLedgerState{} + testLedgerState := test.MockLedgerState{} testSlot := uint64(0) // Test helper function testRun := func(t *testing.T, name string, testFee uint64, validateFunc func(*testing.T, error)) { @@ -340,8 +318,8 @@ func TestUtxoValidateBadInputsUtxo(t *testing.T) { testTx := &babbage.BabbageTransaction{ Body: babbage.BabbageTransactionBody{}, } - testLedgerState := testLedgerState{ - utxos: []common.Utxo{ + testLedgerState := test.MockLedgerState{ + MockUtxos: []common.Utxo{ { Id: testGoodInput, }, @@ -416,8 +394,8 @@ func TestUtxoValidateWrongNetwork(t *testing.T) { }, }, } - testLedgerState := testLedgerState{ - networkId: common.AddressNetworkMainnet, + testLedgerState := test.MockLedgerState{ + MockNetworkId: common.AddressNetworkMainnet, } testSlot := uint64(0) testProtocolParams := &babbage.BabbageProtocolParameters{} @@ -486,8 +464,8 @@ func TestUtxoValidateWrongNetworkWithdrawal(t *testing.T) { }, }, } - testLedgerState := testLedgerState{ - networkId: common.AddressNetworkMainnet, + testLedgerState := test.MockLedgerState{ + MockNetworkId: common.AddressNetworkMainnet, } testSlot := uint64(0) testProtocolParams := &babbage.BabbageProtocolParameters{} @@ -545,6 +523,8 @@ func TestUtxoValidateValueNotConservedUtxo(t *testing.T) { var testInputAmount uint64 = 555666777 var testFee uint64 = 123456 var testStakeDeposit uint64 = 2_000_000 + var testStakeCred1 = []byte{0x01, 0x23, 0x45} + var testStakeCred2 = []byte{0xab, 0xcd, 0xef} testOutputExactAmount := testInputAmount - testFee testOutputUnderAmount := testOutputExactAmount - 999 testOutputOverAmount := testOutputExactAmount + 999 @@ -570,8 +550,8 @@ func TestUtxoValidateValueNotConservedUtxo(t *testing.T) { }, }, } - testLedgerState := testLedgerState{ - utxos: []common.Utxo{ + testLedgerState := test.MockLedgerState{ + MockUtxos: []common.Utxo{ { Id: shelley.NewShelleyTransactionInput(testInputTxId, 0), Output: shelley.ShelleyTransactionOutput{ @@ -579,6 +559,13 @@ func TestUtxoValidateValueNotConservedUtxo(t *testing.T) { }, }, }, + MockStakeRegistration: []common.StakeRegistrationCertificate{ + { + StakeRegistration: common.StakeCredential{ + Credential: testStakeCred2, + }, + }, + }, } testSlot := uint64(0) testProtocolParams := &babbage.BabbageProtocolParameters{ @@ -603,15 +590,48 @@ func TestUtxoValidateValueNotConservedUtxo(t *testing.T) { } }, ) - // Stake registration + // First stake registration t.Run( - "stake registration", + "first 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{}, + Type: common.CertificateTypeStakeRegistration, + Certificate: &common.StakeRegistrationCertificate{ + StakeRegistration: common.StakeCredential{ + Credential: testStakeCred1, + }, + }, + }, + } + 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, + ) + } + }, + ) + // Second stake registration + t.Run( + "second stake registration", + func(t *testing.T) { + testTx.Body.TxOutputs[0].OutputAmount.Amount = testOutputExactAmount + testTx.Body.TxCertificates = []common.CertificateWrapper{ + { + Type: common.CertificateTypeStakeRegistration, + Certificate: &common.StakeRegistrationCertificate{ + StakeRegistration: common.StakeCredential{ + Credential: testStakeCred2, + }, + }, }, } err := babbage.UtxoValidateValueNotConservedUtxo( @@ -697,7 +717,7 @@ func TestUtxoValidateOutputTooSmallUtxo(t *testing.T) { }, }, } - testLedgerState := testLedgerState{} + testLedgerState := test.MockLedgerState{} testSlot := uint64(0) testProtocolParams := &babbage.BabbageProtocolParameters{ AdaPerUtxoByte: 50, @@ -784,7 +804,7 @@ func TestUtxoValidateOutputTooBigUtxo(t *testing.T) { }, }, } - testLedgerState := testLedgerState{} + testLedgerState := test.MockLedgerState{} testSlot := uint64(0) testProtocolParams := &babbage.BabbageProtocolParameters{ MaxValueSize: 4000, @@ -865,7 +885,7 @@ func TestUtxoValidateOutputBootAddrAttrsTooBig(t *testing.T) { }, }, } - testLedgerState := testLedgerState{} + testLedgerState := test.MockLedgerState{} testSlot := uint64(0) testProtocolParams := &babbage.BabbageProtocolParameters{} // Good @@ -921,7 +941,7 @@ func TestUtxoValidateMaxTxSizeUtxo(t *testing.T) { var testMaxTxSizeSmall uint = 2 var testMaxTxSizeLarge uint = 64 * 1024 testTx := &babbage.BabbageTransaction{} - testLedgerState := testLedgerState{} + testLedgerState := test.MockLedgerState{} testSlot := uint64(0) testProtocolParams := &babbage.BabbageProtocolParameters{} // Transaction under limit @@ -999,8 +1019,8 @@ func TestUtxoValidateInsufficientCollateral(t *testing.T) { }, }, } - testLedgerState := testLedgerState{ - utxos: []common.Utxo{ + testLedgerState := test.MockLedgerState{ + MockUtxos: []common.Utxo{ { Id: shelley.NewShelleyTransactionInput(testInputTxId, 0), Output: shelley.ShelleyTransactionOutput{ @@ -1090,8 +1110,8 @@ func TestUtxoValidateCollateralContainsNonAda(t *testing.T) { tmpMultiAsset := common.NewMultiAsset[common.MultiAssetTypeOutput]( map[common.Blake2b224]map[cbor.ByteString]uint64{}, ) - testLedgerState := testLedgerState{ - utxos: []common.Utxo{ + testLedgerState := test.MockLedgerState{ + MockUtxos: []common.Utxo{ { Id: shelley.NewShelleyTransactionInput(testInputTxId, 0), Output: shelley.ShelleyTransactionOutput{ @@ -1179,8 +1199,8 @@ func TestUtxoValidateNoCollateralInputs(t *testing.T) { }, }, } - testLedgerState := testLedgerState{ - utxos: []common.Utxo{ + testLedgerState := test.MockLedgerState{ + MockUtxos: []common.Utxo{ { Id: shelley.NewShelleyTransactionInput(testInputTxId, 0), Output: shelley.ShelleyTransactionOutput{ @@ -1257,7 +1277,7 @@ func TestUtxoValidateExUnitsTooBigUtxo(t *testing.T) { testTx := &babbage.BabbageTransaction{ WitnessSet: babbage.BabbageTransactionWitnessSet{}, } - testLedgerState := testLedgerState{} + testLedgerState := test.MockLedgerState{} testSlot := uint64(0) testProtocolParams := &babbage.BabbageProtocolParameters{ MaxTxExUnits: common.ExUnits{ @@ -1334,8 +1354,8 @@ func TestUtxoValidateCollateralEqBalance(t *testing.T) { }, }, } - testLedgerState := testLedgerState{ - utxos: []common.Utxo{ + testLedgerState := test.MockLedgerState{ + MockUtxos: []common.Utxo{ { Id: shelley.NewShelleyTransactionInput( testInputTxId, @@ -1411,7 +1431,7 @@ func TestUtxoValidateTooManyCollateralInputs(t *testing.T) { testTx := &babbage.BabbageTransaction{ Body: babbage.BabbageTransactionBody{}, } - testLedgerState := testLedgerState{} + testLedgerState := test.MockLedgerState{} testSlot := uint64(0) testProtocolParams := &babbage.BabbageProtocolParameters{ MaxCollateralInputs: 1, diff --git a/ledger/common/state.go b/ledger/common/state.go index 1c051afe..d75f8354 100644 --- a/ledger/common/state.go +++ b/ledger/common/state.go @@ -24,7 +24,10 @@ type UtxoState interface { } // CertState defines the interface for querying the certificate state -type CertState interface{} +type CertState interface { + StakeRegistration([]byte) ([]StakeRegistrationCertificate, error) + PoolRegistration([]byte) ([]PoolRegistrationCertificate, error) +} // LedgerState defines the interface for querying the ledger type LedgerState interface { diff --git a/ledger/conway/rules.go b/ledger/conway/rules.go index a00c4edd..3d659b80 100644 --- a/ledger/conway/rules.go +++ b/ledger/conway/rules.go @@ -199,11 +199,23 @@ func UtxoValidateValueNotConservedUtxo(tx common.Transaction, slot uint64, ls co } producedValue += tx.Fee() for _, cert := range tx.Certificates() { - switch cert.(type) { + switch tmpCert := cert.(type) { case *common.PoolRegistrationCertificate: - producedValue += uint64(tmpPparams.PoolDeposit) + certs, err := ls.PoolRegistration(common.Blake2b224(tmpCert.Operator).Bytes()) + if err != nil { + return err + } + if len(certs) == 0 { + producedValue += uint64(tmpPparams.PoolDeposit) + } case *common.StakeRegistrationCertificate: - producedValue += uint64(tmpPparams.KeyDeposit) + certs, err := ls.StakeRegistration(tmpCert.StakeRegistration.Credential) + if err != nil { + return err + } + if len(certs) == 0 { + producedValue += uint64(tmpPparams.KeyDeposit) + } } } if consumedValue == producedValue { diff --git a/ledger/conway/rules_test.go b/ledger/conway/rules_test.go index 82ed9f8c..345f9115 100644 --- a/ledger/conway/rules_test.go +++ b/ledger/conway/rules_test.go @@ -17,10 +17,10 @@ package conway_test import ( "crypto/rand" "encoding/hex" - "fmt" "testing" "github.com/blinklabs-io/gouroboros/cbor" + test "github.com/blinklabs-io/gouroboros/internal/test/ledger" "github.com/blinklabs-io/gouroboros/ledger/allegra" "github.com/blinklabs-io/gouroboros/ledger/alonzo" "github.com/blinklabs-io/gouroboros/ledger/babbage" @@ -32,28 +32,6 @@ import ( "github.com/stretchr/testify/assert" ) -type testLedgerState struct { - networkId uint - utxos []common.Utxo -} - -func (ls testLedgerState) NetworkId() uint { - return ls.networkId -} - -func (ls testLedgerState) UtxoById(id common.TransactionInput) (common.Utxo, error) { - for _, tmpUtxo := range ls.utxos { - if id.Index() != tmpUtxo.Id.Index() { - continue - } - if string(id.Id().Bytes()) != string(tmpUtxo.Id.Id().Bytes()) { - continue - } - return tmpUtxo, nil - } - return common.Utxo{}, fmt.Errorf("not found") -} - func TestUtxoValidateOutsideValidityIntervalUtxo(t *testing.T) { var testSlot uint64 = 555666777 var testZeroSlot uint64 = 0 @@ -70,7 +48,7 @@ func TestUtxoValidateOutsideValidityIntervalUtxo(t *testing.T) { }, }, } - testLedgerState := testLedgerState{} + testLedgerState := test.MockLedgerState{} testProtocolParams := &conway.ConwayProtocolParameters{} var testBeforeSlot uint64 = 555666700 var testAfterSlot uint64 = 555666799 @@ -174,7 +152,7 @@ func TestUtxoValidateInputSetEmptyUtxo(t *testing.T) { ), }, } - testLedgerState := testLedgerState{} + testLedgerState := test.MockLedgerState{} testSlot := uint64(0) testProtocolParams := &conway.ConwayProtocolParameters{} // Non-empty @@ -251,7 +229,7 @@ func TestUtxoValidateFeeTooSmallUtxo(t *testing.T) { MinFeeA: 7, MinFeeB: 53, } - testLedgerState := testLedgerState{} + testLedgerState := test.MockLedgerState{} testSlot := uint64(0) // Test helper function testRun := func(t *testing.T, name string, testFee uint64, validateFunc func(*testing.T, error)) { @@ -337,8 +315,8 @@ func TestUtxoValidateBadInputsUtxo(t *testing.T) { testTx := &conway.ConwayTransaction{ Body: conway.ConwayTransactionBody{}, } - testLedgerState := testLedgerState{ - utxos: []common.Utxo{ + testLedgerState := test.MockLedgerState{ + MockUtxos: []common.Utxo{ { Id: testGoodInput, }, @@ -415,8 +393,8 @@ func TestUtxoValidateWrongNetwork(t *testing.T) { }, }, } - testLedgerState := testLedgerState{ - networkId: common.AddressNetworkMainnet, + testLedgerState := test.MockLedgerState{ + MockNetworkId: common.AddressNetworkMainnet, } testSlot := uint64(0) testProtocolParams := &conway.ConwayProtocolParameters{} @@ -487,8 +465,8 @@ func TestUtxoValidateWrongNetworkWithdrawal(t *testing.T) { }, }, } - testLedgerState := testLedgerState{ - networkId: common.AddressNetworkMainnet, + testLedgerState := test.MockLedgerState{ + MockNetworkId: common.AddressNetworkMainnet, } testSlot := uint64(0) testProtocolParams := &conway.ConwayProtocolParameters{} @@ -546,6 +524,8 @@ func TestUtxoValidateValueNotConservedUtxo(t *testing.T) { var testInputAmount uint64 = 555666777 var testFee uint64 = 123456 var testStakeDeposit uint64 = 2_000_000 + var testStakeCred1 = []byte{0x01, 0x23, 0x45} + var testStakeCred2 = []byte{0xab, 0xcd, 0xef} testOutputExactAmount := testInputAmount - testFee testOutputUnderAmount := testOutputExactAmount - 999 testOutputOverAmount := testOutputExactAmount + 999 @@ -573,8 +553,8 @@ func TestUtxoValidateValueNotConservedUtxo(t *testing.T) { }, }, } - testLedgerState := testLedgerState{ - utxos: []common.Utxo{ + testLedgerState := test.MockLedgerState{ + MockUtxos: []common.Utxo{ { Id: shelley.NewShelleyTransactionInput(testInputTxId, 0), Output: shelley.ShelleyTransactionOutput{ @@ -582,6 +562,13 @@ func TestUtxoValidateValueNotConservedUtxo(t *testing.T) { }, }, }, + MockStakeRegistration: []common.StakeRegistrationCertificate{ + { + StakeRegistration: common.StakeCredential{ + Credential: testStakeCred2, + }, + }, + }, } testSlot := uint64(0) testProtocolParams := &conway.ConwayProtocolParameters{ @@ -606,15 +593,48 @@ func TestUtxoValidateValueNotConservedUtxo(t *testing.T) { } }, ) - // Stake registration + // First stake registration t.Run( - "stake registration", + "first 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{}, + Type: common.CertificateTypeStakeRegistration, + Certificate: &common.StakeRegistrationCertificate{ + StakeRegistration: common.StakeCredential{ + Credential: testStakeCred1, + }, + }, + }, + } + 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, + ) + } + }, + ) + // Second stake registration + t.Run( + "second stake registration", + func(t *testing.T) { + testTx.Body.TxOutputs[0].OutputAmount.Amount = testOutputExactAmount + testTx.Body.TxCertificates = []common.CertificateWrapper{ + { + Type: common.CertificateTypeStakeRegistration, + Certificate: &common.StakeRegistrationCertificate{ + StakeRegistration: common.StakeCredential{ + Credential: testStakeCred2, + }, + }, }, } err := conway.UtxoValidateValueNotConservedUtxo( @@ -702,7 +722,7 @@ func TestUtxoValidateOutputTooSmallUtxo(t *testing.T) { }, }, } - testLedgerState := testLedgerState{} + testLedgerState := test.MockLedgerState{} testSlot := uint64(0) testProtocolParams := &conway.ConwayProtocolParameters{ AdaPerUtxoByte: 50, @@ -791,7 +811,7 @@ func TestUtxoValidateOutputTooBigUtxo(t *testing.T) { }, }, } - testLedgerState := testLedgerState{} + testLedgerState := test.MockLedgerState{} testSlot := uint64(0) testProtocolParams := &conway.ConwayProtocolParameters{ MaxValueSize: 4000, @@ -874,7 +894,7 @@ func TestUtxoValidateOutputBootAddrAttrsTooBig(t *testing.T) { }, }, } - testLedgerState := testLedgerState{} + testLedgerState := test.MockLedgerState{} testSlot := uint64(0) testProtocolParams := &conway.ConwayProtocolParameters{} // Good @@ -930,7 +950,7 @@ func TestUtxoValidateMaxTxSizeUtxo(t *testing.T) { var testMaxTxSizeSmall uint = 2 var testMaxTxSizeLarge uint = 64 * 1024 testTx := &conway.ConwayTransaction{} - testLedgerState := testLedgerState{} + testLedgerState := test.MockLedgerState{} testSlot := uint64(0) testProtocolParams := &conway.ConwayProtocolParameters{} // Transaction under limit @@ -1010,8 +1030,8 @@ func TestUtxoValidateInsufficientCollateral(t *testing.T) { }, }, } - testLedgerState := testLedgerState{ - utxos: []common.Utxo{ + testLedgerState := test.MockLedgerState{ + MockUtxos: []common.Utxo{ { Id: shelley.NewShelleyTransactionInput(testInputTxId, 0), Output: shelley.ShelleyTransactionOutput{ @@ -1101,8 +1121,8 @@ func TestUtxoValidateCollateralContainsNonAda(t *testing.T) { tmpMultiAsset := common.NewMultiAsset[common.MultiAssetTypeOutput]( map[common.Blake2b224]map[cbor.ByteString]uint64{}, ) - testLedgerState := testLedgerState{ - utxos: []common.Utxo{ + testLedgerState := test.MockLedgerState{ + MockUtxos: []common.Utxo{ { Id: shelley.NewShelleyTransactionInput(testInputTxId, 0), Output: shelley.ShelleyTransactionOutput{ @@ -1190,8 +1210,8 @@ func TestUtxoValidateNoCollateralInputs(t *testing.T) { }, }, } - testLedgerState := testLedgerState{ - utxos: []common.Utxo{ + testLedgerState := test.MockLedgerState{ + MockUtxos: []common.Utxo{ { Id: shelley.NewShelleyTransactionInput(testInputTxId, 0), Output: shelley.ShelleyTransactionOutput{ @@ -1268,7 +1288,7 @@ func TestUtxoValidateExUnitsTooBigUtxo(t *testing.T) { testTx := &conway.ConwayTransaction{ WitnessSet: conway.ConwayTransactionWitnessSet{}, } - testLedgerState := testLedgerState{} + testLedgerState := test.MockLedgerState{} testSlot := uint64(0) testProtocolParams := &conway.ConwayProtocolParameters{ MaxTxExUnits: common.ExUnits{ @@ -1338,7 +1358,7 @@ func TestUtxoValidateDisjointRefInputs(t *testing.T) { testTx := &conway.ConwayTransaction{ Body: conway.ConwayTransactionBody{}, } - testLedgerState := testLedgerState{} + testLedgerState := test.MockLedgerState{} testSlot := uint64(0) testProtocolParams := &conway.ConwayProtocolParameters{} // Non-disjoint ref inputs @@ -1422,8 +1442,8 @@ func TestUtxoValidateCollateralEqBalance(t *testing.T) { }, }, } - testLedgerState := testLedgerState{ - utxos: []common.Utxo{ + testLedgerState := test.MockLedgerState{ + MockUtxos: []common.Utxo{ { Id: shelley.NewShelleyTransactionInput( testInputTxId, @@ -1499,7 +1519,7 @@ func TestUtxoValidateTooManyCollateralInputs(t *testing.T) { testTx := &conway.ConwayTransaction{ Body: conway.ConwayTransactionBody{}, } - testLedgerState := testLedgerState{} + testLedgerState := test.MockLedgerState{} testSlot := uint64(0) testProtocolParams := &conway.ConwayProtocolParameters{ MaxCollateralInputs: 1, diff --git a/ledger/mary/rules_test.go b/ledger/mary/rules_test.go index 24eefcf7..9808105c 100644 --- a/ledger/mary/rules_test.go +++ b/ledger/mary/rules_test.go @@ -17,10 +17,10 @@ package mary_test import ( "crypto/rand" "encoding/hex" - "errors" "testing" "github.com/blinklabs-io/gouroboros/cbor" + test "github.com/blinklabs-io/gouroboros/internal/test/ledger" "github.com/blinklabs-io/gouroboros/ledger/allegra" "github.com/blinklabs-io/gouroboros/ledger/common" "github.com/blinklabs-io/gouroboros/ledger/mary" @@ -29,30 +29,6 @@ import ( "github.com/stretchr/testify/assert" ) -type testLedgerState struct { - networkId uint - utxos []common.Utxo -} - -func (ls testLedgerState) NetworkId() uint { - return ls.networkId -} - -func (ls testLedgerState) UtxoById( - id common.TransactionInput, -) (common.Utxo, error) { - for _, tmpUtxo := range ls.utxos { - if id.Index() != tmpUtxo.Id.Index() { - continue - } - if string(id.Id().Bytes()) != string(tmpUtxo.Id.Id().Bytes()) { - continue - } - return tmpUtxo, nil - } - return common.Utxo{}, errors.New("not found") -} - func TestUtxoValidateOutsideValidityIntervalUtxo(t *testing.T) { var testSlot uint64 = 555666777 var testZeroSlot uint64 = 0 @@ -63,7 +39,7 @@ func TestUtxoValidateOutsideValidityIntervalUtxo(t *testing.T) { }, }, } - testLedgerState := testLedgerState{} + testLedgerState := test.MockLedgerState{} testProtocolParams := &mary.MaryProtocolParameters{} var testBeforeSlot uint64 = 555666700 var testAfterSlot uint64 = 555666799 @@ -171,7 +147,7 @@ func TestUtxoValidateInputSetEmptyUtxo(t *testing.T) { }, }, } - testLedgerState := testLedgerState{} + testLedgerState := test.MockLedgerState{} testSlot := uint64(0) testProtocolParams := &mary.MaryProtocolParameters{} // Non-empty @@ -246,7 +222,7 @@ func TestUtxoValidateFeeTooSmallUtxo(t *testing.T) { }, }, } - testLedgerState := testLedgerState{} + testLedgerState := test.MockLedgerState{} testSlot := uint64(0) // Test helper function testRun := func(t *testing.T, name string, testFee uint64, validateFunc func(*testing.T, error)) { @@ -332,8 +308,8 @@ func TestUtxoValidateBadInputsUtxo(t *testing.T) { testTx := &mary.MaryTransaction{ Body: mary.MaryTransactionBody{}, } - testLedgerState := testLedgerState{ - utxos: []common.Utxo{ + testLedgerState := test.MockLedgerState{ + MockUtxos: []common.Utxo{ { Id: testGoodInput, }, @@ -412,8 +388,8 @@ func TestUtxoValidateWrongNetwork(t *testing.T) { }, }, } - testLedgerState := testLedgerState{ - networkId: common.AddressNetworkMainnet, + testLedgerState := test.MockLedgerState{ + MockNetworkId: common.AddressNetworkMainnet, } testSlot := uint64(0) testProtocolParams := &mary.MaryProtocolParameters{} @@ -482,8 +458,8 @@ func TestUtxoValidateWrongNetworkWithdrawal(t *testing.T) { }, }, } - testLedgerState := testLedgerState{ - networkId: common.AddressNetworkMainnet, + testLedgerState := test.MockLedgerState{ + MockNetworkId: common.AddressNetworkMainnet, } testSlot := uint64(0) testProtocolParams := &mary.MaryProtocolParameters{} @@ -541,6 +517,8 @@ func TestUtxoValidateValueNotConservedUtxo(t *testing.T) { var testInputAmount uint64 = 555666777 var testFee uint64 = 123456 var testStakeDeposit uint64 = 2_000_000 + var testStakeCred1 = []byte{0x01, 0x23, 0x45} + var testStakeCred2 = []byte{0xab, 0xcd, 0xef} testOutputExactAmount := testInputAmount - testFee testOutputUnderAmount := testOutputExactAmount - 999 testOutputOverAmount := testOutputExactAmount + 999 @@ -565,8 +543,8 @@ func TestUtxoValidateValueNotConservedUtxo(t *testing.T) { }, }, } - testLedgerState := testLedgerState{ - utxos: []common.Utxo{ + testLedgerState := test.MockLedgerState{ + MockUtxos: []common.Utxo{ { Id: shelley.NewShelleyTransactionInput(testInputTxId, 0), Output: shelley.ShelleyTransactionOutput{ @@ -574,6 +552,13 @@ func TestUtxoValidateValueNotConservedUtxo(t *testing.T) { }, }, }, + MockStakeRegistration: []common.StakeRegistrationCertificate{ + { + StakeRegistration: common.StakeCredential{ + Credential: testStakeCred2, + }, + }, + }, } testSlot := uint64(0) testProtocolParams := &mary.MaryProtocolParameters{ @@ -602,15 +587,48 @@ func TestUtxoValidateValueNotConservedUtxo(t *testing.T) { } }, ) - // Stake registration + // First stake registration t.Run( - "stake registration", + "first 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{}, + Type: common.CertificateTypeStakeRegistration, + Certificate: &common.StakeRegistrationCertificate{ + StakeRegistration: common.StakeCredential{ + Credential: testStakeCred1, + }, + }, + }, + } + 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, + ) + } + }, + ) + // Second stake registration + t.Run( + "second stake registration", + func(t *testing.T) { + testTx.Body.TxOutputs[0].OutputAmount.Amount = testOutputExactAmount + testTx.Body.TxCertificates = []common.CertificateWrapper{ + { + Type: common.CertificateTypeStakeRegistration, + Certificate: &common.StakeRegistrationCertificate{ + StakeRegistration: common.StakeCredential{ + Credential: testStakeCred2, + }, + }, }, } err := mary.UtxoValidateValueNotConservedUtxo( @@ -696,7 +714,7 @@ func TestUtxoValidateOutputTooSmallUtxo(t *testing.T) { }, }, } - testLedgerState := testLedgerState{} + testLedgerState := test.MockLedgerState{} testSlot := uint64(0) testProtocolParams := &mary.MaryProtocolParameters{ AllegraProtocolParameters: allegra.AllegraProtocolParameters{ @@ -789,7 +807,7 @@ func TestUtxoValidateOutputTooBigUtxo(t *testing.T) { }, }, } - testLedgerState := testLedgerState{} + testLedgerState := test.MockLedgerState{} testSlot := uint64(0) testProtocolParams := &mary.MaryProtocolParameters{} // Good @@ -870,7 +888,7 @@ func TestUtxoValidateOutputBootAddrAttrsTooBig(t *testing.T) { }, }, } - testLedgerState := testLedgerState{} + testLedgerState := test.MockLedgerState{} testSlot := uint64(0) testProtocolParams := &mary.MaryProtocolParameters{} // Good @@ -926,7 +944,7 @@ func TestUtxoValidateMaxTxSizeUtxo(t *testing.T) { var testMaxTxSizeSmall uint = 2 var testMaxTxSizeLarge uint = 64 * 1024 testTx := &mary.MaryTransaction{} - testLedgerState := testLedgerState{} + testLedgerState := test.MockLedgerState{} testSlot := uint64(0) testProtocolParams := &mary.MaryProtocolParameters{} // Transaction under limit diff --git a/ledger/shelley/rules.go b/ledger/shelley/rules.go index 9f2e3785..bd940bfe 100644 --- a/ledger/shelley/rules.go +++ b/ledger/shelley/rules.go @@ -188,11 +188,23 @@ func UtxoValidateValueNotConservedUtxo( } producedValue += tx.Fee() for _, cert := range tx.Certificates() { - switch cert.(type) { + switch tmpCert := cert.(type) { case *common.PoolRegistrationCertificate: - producedValue += uint64(tmpPparams.PoolDeposit) + certs, err := ls.PoolRegistration(common.Blake2b224(tmpCert.Operator).Bytes()) + if err != nil { + return err + } + if len(certs) == 0 { + producedValue += uint64(tmpPparams.PoolDeposit) + } case *common.StakeRegistrationCertificate: - producedValue += uint64(tmpPparams.KeyDeposit) + certs, err := ls.StakeRegistration(tmpCert.StakeRegistration.Credential) + if err != nil { + return err + } + if len(certs) == 0 { + producedValue += uint64(tmpPparams.KeyDeposit) + } } } if consumedValue == producedValue { diff --git a/ledger/shelley/rules_test.go b/ledger/shelley/rules_test.go index 49855d31..7f395853 100644 --- a/ledger/shelley/rules_test.go +++ b/ledger/shelley/rules_test.go @@ -17,39 +17,15 @@ package shelley_test import ( "crypto/rand" "encoding/hex" - "errors" "testing" + test "github.com/blinklabs-io/gouroboros/internal/test/ledger" "github.com/blinklabs-io/gouroboros/ledger/common" "github.com/blinklabs-io/gouroboros/ledger/shelley" "github.com/stretchr/testify/assert" ) -type testLedgerState struct { - networkId uint - utxos []common.Utxo -} - -func (ls testLedgerState) NetworkId() uint { - return ls.networkId -} - -func (ls testLedgerState) UtxoById( - id common.TransactionInput, -) (common.Utxo, error) { - for _, tmpUtxo := range ls.utxos { - if id.Index() != tmpUtxo.Id.Index() { - continue - } - if string(id.Id().Bytes()) != string(tmpUtxo.Id.Id().Bytes()) { - continue - } - return tmpUtxo, nil - } - return common.Utxo{}, errors.New("not found") -} - func TestUtxoValidateTimeToLive(t *testing.T) { var testSlot uint64 = 555666777 var testZeroSlot uint64 = 0 @@ -58,7 +34,7 @@ func TestUtxoValidateTimeToLive(t *testing.T) { Ttl: testSlot, }, } - testLedgerState := testLedgerState{} + testLedgerState := test.MockLedgerState{} testProtocolParams := &shelley.ShelleyProtocolParameters{} var testBeforeSlot uint64 = 555666700 var testAfterSlot uint64 = 555666799 @@ -162,7 +138,7 @@ func TestUtxoValidateInputSetEmptyUtxo(t *testing.T) { ), }, } - testLedgerState := testLedgerState{} + testLedgerState := test.MockLedgerState{} testSlot := uint64(0) testProtocolParams := &shelley.ShelleyProtocolParameters{} // Non-empty @@ -229,7 +205,7 @@ func TestUtxoValidateFeeTooSmallUtxo(t *testing.T) { MinFeeA: 7, MinFeeB: 53, } - testLedgerState := testLedgerState{} + testLedgerState := test.MockLedgerState{} testSlot := uint64(0) // Test helper function testRun := func(t *testing.T, name string, testFee uint64, validateFunc func(*testing.T, error)) { @@ -315,8 +291,8 @@ func TestUtxoValidateBadInputsUtxo(t *testing.T) { testTx := &shelley.ShelleyTransaction{ Body: shelley.ShelleyTransactionBody{}, } - testLedgerState := testLedgerState{ - utxos: []common.Utxo{ + testLedgerState := test.MockLedgerState{ + MockUtxos: []common.Utxo{ { Id: testGoodInput, }, @@ -393,8 +369,8 @@ func TestUtxoValidateWrongNetwork(t *testing.T) { }, }, } - testLedgerState := testLedgerState{ - networkId: common.AddressNetworkMainnet, + testLedgerState := test.MockLedgerState{ + MockNetworkId: common.AddressNetworkMainnet, } testSlot := uint64(0) testProtocolParams := &shelley.ShelleyProtocolParameters{} @@ -459,8 +435,8 @@ func TestUtxoValidateWrongNetworkWithdrawal(t *testing.T) { TxWithdrawals: map[*common.Address]uint64{}, }, } - testLedgerState := testLedgerState{ - networkId: common.AddressNetworkMainnet, + testLedgerState := test.MockLedgerState{ + MockNetworkId: common.AddressNetworkMainnet, } testSlot := uint64(0) testProtocolParams := &shelley.ShelleyProtocolParameters{} @@ -518,6 +494,8 @@ func TestUtxoValidateValueNotConservedUtxo(t *testing.T) { var testInputAmount uint64 = 555666777 var testFee uint64 = 123456 var testStakeDeposit uint64 = 2_000_000 + var testStakeCred1 = []byte{0x01, 0x23, 0x45} + var testStakeCred2 = []byte{0xab, 0xcd, 0xef} testOutputExactAmount := testInputAmount - testFee testOutputUnderAmount := testOutputExactAmount - 999 testOutputOverAmount := testOutputExactAmount + 999 @@ -535,8 +513,8 @@ func TestUtxoValidateValueNotConservedUtxo(t *testing.T) { }, }, } - testLedgerState := testLedgerState{ - utxos: []common.Utxo{ + testLedgerState := test.MockLedgerState{ + MockUtxos: []common.Utxo{ { Id: shelley.NewShelleyTransactionInput(testInputTxId, 0), Output: shelley.ShelleyTransactionOutput{ @@ -544,6 +522,13 @@ func TestUtxoValidateValueNotConservedUtxo(t *testing.T) { }, }, }, + MockStakeRegistration: []common.StakeRegistrationCertificate{ + { + StakeRegistration: common.StakeCredential{ + Credential: testStakeCred2, + }, + }, + }, } testSlot := uint64(0) testProtocolParams := &shelley.ShelleyProtocolParameters{ @@ -568,15 +553,48 @@ func TestUtxoValidateValueNotConservedUtxo(t *testing.T) { } }, ) - // Stake registration + // First stake registration t.Run( - "stake registration", + "first stake registration", func(t *testing.T) { testTx.Body.TxOutputs[0].OutputAmount = testOutputExactAmount - testStakeDeposit testTx.Body.TxCertificates = []common.CertificateWrapper{ { - Type: common.CertificateTypeStakeRegistration, - Certificate: &common.StakeRegistrationCertificate{}, + Type: common.CertificateTypeStakeRegistration, + Certificate: &common.StakeRegistrationCertificate{ + StakeRegistration: common.StakeCredential{ + Credential: testStakeCred1, + }, + }, + }, + } + 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, + ) + } + }, + ) + // Second stake registration + t.Run( + "second stake registration", + func(t *testing.T) { + testTx.Body.TxOutputs[0].OutputAmount = testOutputExactAmount + testTx.Body.TxCertificates = []common.CertificateWrapper{ + { + Type: common.CertificateTypeStakeRegistration, + Certificate: &common.StakeRegistrationCertificate{ + StakeRegistration: common.StakeCredential{ + Credential: testStakeCred2, + }, + }, }, } err := shelley.UtxoValidateValueNotConservedUtxo( @@ -662,7 +680,7 @@ func TestUtxoValidateOutputTooSmallUtxo(t *testing.T) { }, }, } - testLedgerState := testLedgerState{} + testLedgerState := test.MockLedgerState{} testSlot := uint64(0) testProtocolParams := &shelley.ShelleyProtocolParameters{ MinUtxoValue: 100000, @@ -745,7 +763,7 @@ func TestUtxoValidateOutputBootAddrAttrsTooBig(t *testing.T) { }, }, } - testLedgerState := testLedgerState{} + testLedgerState := test.MockLedgerState{} testSlot := uint64(0) testProtocolParams := &shelley.ShelleyProtocolParameters{} // Good @@ -801,7 +819,7 @@ func TestUtxoValidateMaxTxSizeUtxo(t *testing.T) { var testMaxTxSizeSmall uint = 2 var testMaxTxSizeLarge uint = 64 * 1024 testTx := &shelley.ShelleyTransaction{} - testLedgerState := testLedgerState{} + testLedgerState := test.MockLedgerState{} testSlot := uint64(0) testProtocolParams := &shelley.ShelleyProtocolParameters{} // Transaction under limit