Skip to content

Commit e5f803e

Browse files
committed
feat: support for querying pool/stake registration certs in validation rules
We only want to collect a deposit on the first pool/stake registration cert for a particular key, so we need to query the ledger state to determine if there are previous certs. Additionally, this commit: * moves the duplicated testLedgerState to a common place as MockLedgerState * adds support for mocking pool/stake registration queries to MockLedgerState Signed-off-by: Aurora Gaffney <[email protected]>
1 parent 2f5c142 commit e5f803e

File tree

12 files changed

+528
-294
lines changed

12 files changed

+528
-294
lines changed

internal/test/helpers.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
// Copyright 2025 Blink Labs Software
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
115
package test
216

317
import (

internal/test/ledger.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// Copyright 2025 Blink Labs Software
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package test
16+
17+
import (
18+
"errors"
19+
20+
"github.com/blinklabs-io/gouroboros/ledger/common"
21+
)
22+
23+
type MockLedgerState struct {
24+
MockNetworkId uint
25+
MockUtxos []common.Utxo
26+
MockStakeRegistration []common.StakeRegistrationCertificate
27+
MockPoolRegistration []common.PoolRegistrationCertificate
28+
}
29+
30+
func (ls MockLedgerState) NetworkId() uint {
31+
return ls.MockNetworkId
32+
}
33+
34+
func (ls MockLedgerState) UtxoById(
35+
id common.TransactionInput,
36+
) (common.Utxo, error) {
37+
for _, tmpUtxo := range ls.MockUtxos {
38+
if id.Index() != tmpUtxo.Id.Index() {
39+
continue
40+
}
41+
if string(id.Id().Bytes()) != string(tmpUtxo.Id.Id().Bytes()) {
42+
continue
43+
}
44+
return tmpUtxo, nil
45+
}
46+
return common.Utxo{}, errors.New("not found")
47+
}
48+
49+
func (ls MockLedgerState) StakeRegistration(stakingKey []byte) ([]common.StakeRegistrationCertificate, error) {
50+
ret := []common.StakeRegistrationCertificate{}
51+
for _, cert := range ls.MockStakeRegistration {
52+
if string(cert.StakeRegistration.Credential) == string(stakingKey) {
53+
ret = append(ret, cert)
54+
}
55+
}
56+
return ret, nil
57+
}
58+
59+
func (ls MockLedgerState) PoolRegistration(poolKeyHash []byte) ([]common.PoolRegistrationCertificate, error) {
60+
ret := []common.PoolRegistrationCertificate{}
61+
for _, cert := range ls.MockPoolRegistration {
62+
if string(common.Blake2b224(cert.Operator).Bytes()) == string(poolKeyHash) {
63+
ret = append(ret, cert)
64+
}
65+
}
66+
return ret, nil
67+
}

ledger/allegra/rules_test.go

Lines changed: 61 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -17,40 +17,16 @@ package allegra_test
1717
import (
1818
"crypto/rand"
1919
"encoding/hex"
20-
"errors"
2120
"testing"
2221

22+
"github.com/blinklabs-io/gouroboros/internal/test"
2323
"github.com/blinklabs-io/gouroboros/ledger/allegra"
2424
"github.com/blinklabs-io/gouroboros/ledger/common"
2525
"github.com/blinklabs-io/gouroboros/ledger/shelley"
2626

2727
"github.com/stretchr/testify/assert"
2828
)
2929

30-
type testLedgerState struct {
31-
networkId uint
32-
utxos []common.Utxo
33-
}
34-
35-
func (ls testLedgerState) NetworkId() uint {
36-
return ls.networkId
37-
}
38-
39-
func (ls testLedgerState) UtxoById(
40-
id common.TransactionInput,
41-
) (common.Utxo, error) {
42-
for _, tmpUtxo := range ls.utxos {
43-
if id.Index() != tmpUtxo.Id.Index() {
44-
continue
45-
}
46-
if string(id.Id().Bytes()) != string(tmpUtxo.Id.Id().Bytes()) {
47-
continue
48-
}
49-
return tmpUtxo, nil
50-
}
51-
return common.Utxo{}, errors.New("not found")
52-
}
53-
5430
func TestUtxoValidateOutsideValidityIntervalUtxo(t *testing.T) {
5531
var testSlot uint64 = 555666777
5632
var testZeroSlot uint64 = 0
@@ -59,7 +35,7 @@ func TestUtxoValidateOutsideValidityIntervalUtxo(t *testing.T) {
5935
TxValidityIntervalStart: testSlot,
6036
},
6137
}
62-
testLedgerState := testLedgerState{}
38+
testLedgerState := test.MockLedgerState{}
6339
testProtocolParams := &allegra.AllegraProtocolParameters{}
6440
var testBeforeSlot uint64 = 555666700
6541
var testAfterSlot uint64 = 555666799
@@ -165,7 +141,7 @@ func TestUtxoValidateInputSetEmptyUtxo(t *testing.T) {
165141
},
166142
},
167143
}
168-
testLedgerState := testLedgerState{}
144+
testLedgerState := test.MockLedgerState{}
169145
testSlot := uint64(0)
170146
testProtocolParams := &allegra.AllegraProtocolParameters{}
171147
// Non-empty
@@ -236,7 +212,7 @@ func TestUtxoValidateFeeTooSmallUtxo(t *testing.T) {
236212
MinFeeB: 53,
237213
},
238214
}
239-
testLedgerState := testLedgerState{}
215+
testLedgerState := test.MockLedgerState{}
240216
testSlot := uint64(0)
241217
// Test helper function
242218
testRun := func(t *testing.T, name string, testFee uint64, validateFunc func(*testing.T, error)) {
@@ -322,8 +298,8 @@ func TestUtxoValidateBadInputsUtxo(t *testing.T) {
322298
testTx := &allegra.AllegraTransaction{
323299
Body: allegra.AllegraTransactionBody{},
324300
}
325-
testLedgerState := testLedgerState{
326-
utxos: []common.Utxo{
301+
testLedgerState := test.MockLedgerState{
302+
MockUtxos: []common.Utxo{
327303
{
328304
Id: testGoodInput,
329305
},
@@ -402,8 +378,8 @@ func TestUtxoValidateWrongNetwork(t *testing.T) {
402378
},
403379
},
404380
}
405-
testLedgerState := testLedgerState{
406-
networkId: common.AddressNetworkMainnet,
381+
testLedgerState := test.MockLedgerState{
382+
MockNetworkId: common.AddressNetworkMainnet,
407383
}
408384
testSlot := uint64(0)
409385
testProtocolParams := &allegra.AllegraProtocolParameters{}
@@ -470,8 +446,8 @@ func TestUtxoValidateWrongNetworkWithdrawal(t *testing.T) {
470446
},
471447
},
472448
}
473-
testLedgerState := testLedgerState{
474-
networkId: common.AddressNetworkMainnet,
449+
testLedgerState := test.MockLedgerState{
450+
MockNetworkId: common.AddressNetworkMainnet,
475451
}
476452
testSlot := uint64(0)
477453
testProtocolParams := &allegra.AllegraProtocolParameters{}
@@ -529,6 +505,8 @@ func TestUtxoValidateValueNotConservedUtxo(t *testing.T) {
529505
var testInputAmount uint64 = 555666777
530506
var testFee uint64 = 123456
531507
var testStakeDeposit uint64 = 2_000_000
508+
var testStakeCred1 = []byte{0x01, 0x23, 0x45}
509+
var testStakeCred2 = []byte{0xab, 0xcd, 0xef}
532510
testOutputExactAmount := testInputAmount - testFee
533511
testOutputUnderAmount := testOutputExactAmount - 999
534512
testOutputOverAmount := testOutputExactAmount + 999
@@ -548,15 +526,22 @@ func TestUtxoValidateValueNotConservedUtxo(t *testing.T) {
548526
},
549527
},
550528
}
551-
testLedgerState := testLedgerState{
552-
utxos: []common.Utxo{
529+
testLedgerState := test.MockLedgerState{
530+
MockUtxos: []common.Utxo{
553531
{
554532
Id: shelley.NewShelleyTransactionInput(testInputTxId, 0),
555533
Output: shelley.ShelleyTransactionOutput{
556534
OutputAmount: testInputAmount,
557535
},
558536
},
559537
},
538+
MockStakeRegistration: []common.StakeRegistrationCertificate{
539+
{
540+
StakeRegistration: common.StakeCredential{
541+
Credential: testStakeCred2,
542+
},
543+
},
544+
},
560545
}
561546
testSlot := uint64(0)
562547
testProtocolParams := &allegra.AllegraProtocolParameters{
@@ -583,15 +568,48 @@ func TestUtxoValidateValueNotConservedUtxo(t *testing.T) {
583568
}
584569
},
585570
)
586-
// Stake registration
571+
// First stake registration
587572
t.Run(
588-
"stake registration",
573+
"first stake registration",
589574
func(t *testing.T) {
590575
testTx.Body.TxOutputs[0].OutputAmount = testOutputExactAmount - testStakeDeposit
591576
testTx.Body.TxCertificates = []common.CertificateWrapper{
592577
{
593-
Type: common.CertificateTypeStakeRegistration,
594-
Certificate: &common.StakeRegistrationCertificate{},
578+
Type: common.CertificateTypeStakeRegistration,
579+
Certificate: &common.StakeRegistrationCertificate{
580+
StakeRegistration: common.StakeCredential{
581+
Credential: testStakeCred1,
582+
},
583+
},
584+
},
585+
}
586+
err := allegra.UtxoValidateValueNotConservedUtxo(
587+
testTx,
588+
testSlot,
589+
testLedgerState,
590+
testProtocolParams,
591+
)
592+
if err != nil {
593+
t.Errorf(
594+
"UtxoValidateValueNotConservedUtxo should succeed when inputs and outputs are balanced\n got error: %v",
595+
err,
596+
)
597+
}
598+
},
599+
)
600+
// Second stake registration
601+
t.Run(
602+
"second stake registration",
603+
func(t *testing.T) {
604+
testTx.Body.TxOutputs[0].OutputAmount = testOutputExactAmount
605+
testTx.Body.TxCertificates = []common.CertificateWrapper{
606+
{
607+
Type: common.CertificateTypeStakeRegistration,
608+
Certificate: &common.StakeRegistrationCertificate{
609+
StakeRegistration: common.StakeCredential{
610+
Credential: testStakeCred2,
611+
},
612+
},
595613
},
596614
}
597615
err := allegra.UtxoValidateValueNotConservedUtxo(
@@ -679,7 +697,7 @@ func TestUtxoValidateOutputTooSmallUtxo(t *testing.T) {
679697
},
680698
},
681699
}
682-
testLedgerState := testLedgerState{}
700+
testLedgerState := test.MockLedgerState{}
683701
testSlot := uint64(0)
684702
testProtocolParams := &allegra.AllegraProtocolParameters{
685703
ShelleyProtocolParameters: shelley.ShelleyProtocolParameters{
@@ -766,7 +784,7 @@ func TestUtxoValidateOutputBootAddrAttrsTooBig(t *testing.T) {
766784
},
767785
},
768786
}
769-
testLedgerState := testLedgerState{}
787+
testLedgerState := test.MockLedgerState{}
770788
testSlot := uint64(0)
771789
testProtocolParams := &allegra.AllegraProtocolParameters{}
772790
// Good
@@ -822,7 +840,7 @@ func TestUtxoValidateMaxTxSizeUtxo(t *testing.T) {
822840
var testMaxTxSizeSmall uint = 2
823841
var testMaxTxSizeLarge uint = 64 * 1024
824842
testTx := &allegra.AllegraTransaction{}
825-
testLedgerState := testLedgerState{}
843+
testLedgerState := test.MockLedgerState{}
826844
testSlot := uint64(0)
827845
testProtocolParams := &allegra.AllegraProtocolParameters{}
828846
// Transaction under limit

0 commit comments

Comments
 (0)