Skip to content

Commit 2876b3c

Browse files
authored
feat: initial framework for validation rules (#874)
Signed-off-by: Aurora Gaffney <[email protected]>
1 parent 3f1ba19 commit 2876b3c

File tree

7 files changed

+282
-6
lines changed

7 files changed

+282
-6
lines changed

ledger/common/rules.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +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+
15+
package common
16+
17+
type UtxoValidationRuleFunc func(Transaction, LedgerState, TipState, ProtocolParameters) error

ledger/common/state.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
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 common
16+
17+
import (
18+
pcommon "github.com/blinklabs-io/gouroboros/protocol/common"
19+
)
20+
21+
// UtxoState defines the interface for querying the UTxO state
22+
type UtxoState interface {
23+
UtxosById([]TransactionInput) ([]Utxo, error)
24+
}
25+
26+
// CertState defines the interface for querying the certificate state
27+
type CertState interface{}
28+
29+
// LedgerState defines the interface for querying the ledger
30+
type LedgerState interface {
31+
UtxoState
32+
CertState
33+
}
34+
35+
// TipState defines the interface for querying the current tip
36+
type TipState interface {
37+
Tip() (pcommon.Tip, error)
38+
}

ledger/shelley/errors.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
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 shelley
16+
17+
import "fmt"
18+
19+
type ExpiredUtxoError struct {
20+
Ttl uint64
21+
Slot uint64
22+
}
23+
24+
func (e ExpiredUtxoError) Error() string {
25+
return fmt.Sprintf(
26+
"expired UTxO: TTL %d, slot %d",
27+
e.Ttl,
28+
e.Slot,
29+
)
30+
}

ledger/shelley/rules.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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 shelley
16+
17+
import (
18+
common "github.com/blinklabs-io/gouroboros/ledger/common"
19+
)
20+
21+
var UtxoValidationRules = []common.UtxoValidationRuleFunc{
22+
UtxoValidateTimeToLive,
23+
}
24+
25+
// UtxoValidateTimeToLive ensures that the current tip slot is not after the specified TTL value
26+
func UtxoValidateTimeToLive(tx common.Transaction, ls common.LedgerState, ts common.TipState, pp common.ProtocolParameters) error {
27+
tip, err := ts.Tip()
28+
if err != nil {
29+
return err
30+
}
31+
ttl := tx.TTL()
32+
if ttl == 0 || ttl >= tip.Point.Slot {
33+
return nil
34+
}
35+
return ExpiredUtxoError{
36+
Ttl: ttl,
37+
Slot: tip.Point.Slot,
38+
}
39+
}

ledger/shelley/rules_test.go

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
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 shelley_test
16+
17+
import (
18+
"testing"
19+
20+
"github.com/blinklabs-io/gouroboros/ledger/common"
21+
"github.com/blinklabs-io/gouroboros/ledger/shelley"
22+
pcommon "github.com/blinklabs-io/gouroboros/protocol/common"
23+
24+
"github.com/stretchr/testify/assert"
25+
)
26+
27+
type testLedgerState struct {
28+
utxos []common.Utxo
29+
}
30+
31+
func (ls testLedgerState) UtxosById(_ []common.TransactionInput) ([]common.Utxo, error) {
32+
return ls.utxos, nil
33+
}
34+
35+
type testTipState struct {
36+
tip pcommon.Tip
37+
}
38+
39+
func (ts testTipState) Tip() (pcommon.Tip, error) {
40+
return ts.tip, nil
41+
}
42+
43+
func TestUtxoValidateTimeToLive(t *testing.T) {
44+
var testSlot uint64 = 555666777
45+
var testZeroSlot uint64 = 0
46+
testTx := &shelley.ShelleyTransaction{
47+
Body: shelley.ShelleyTransactionBody{
48+
Ttl: testSlot,
49+
},
50+
}
51+
testLedgerState := testLedgerState{}
52+
testProtocolParams := &shelley.ShelleyProtocolParameters{}
53+
var testBeforeSlot uint64 = 555666700
54+
var testAfterSlot uint64 = 555666799
55+
// Test helper function
56+
testRun := func(t *testing.T, name string, testTipSlot uint64, validateFunc func(*testing.T, error)) {
57+
t.Run(
58+
name,
59+
func(t *testing.T) {
60+
testTipState := testTipState{
61+
tip: pcommon.Tip{
62+
Point: pcommon.Point{
63+
Slot: testTipSlot,
64+
},
65+
},
66+
}
67+
err := shelley.UtxoValidateTimeToLive(
68+
testTx,
69+
testLedgerState,
70+
testTipState,
71+
testProtocolParams,
72+
)
73+
validateFunc(t, err)
74+
},
75+
)
76+
}
77+
// Slot before TTL
78+
testRun(
79+
t,
80+
"slot before TTL",
81+
testBeforeSlot,
82+
func(t *testing.T, err error) {
83+
if err != nil {
84+
t.Errorf(
85+
"UtxoValidateTimeToLive should succeed when provided a tip slot (%d) before the specified TTL (%d)\n got error: %v",
86+
testBeforeSlot,
87+
testTx.TTL(),
88+
err,
89+
)
90+
}
91+
},
92+
)
93+
// Slot equal to TTL
94+
testRun(
95+
t,
96+
"slot equal to TTL",
97+
testSlot,
98+
func(t *testing.T, err error) {
99+
if err != nil {
100+
t.Errorf(
101+
"UtxoValidateTimeToLive should succeed when provided a tip slot (%d) equal to the specified TTL (%d)\n got error: %v",
102+
testSlot,
103+
testTx.TTL(),
104+
err,
105+
)
106+
}
107+
},
108+
)
109+
// Slot after TTL
110+
testRun(
111+
t,
112+
"slot after TTL",
113+
testAfterSlot,
114+
func(t *testing.T, err error) {
115+
if err == nil {
116+
t.Errorf(
117+
"UtxoValidateTimeToLive should fail when provided a tip slot (%d) after the specified TTL (%d)",
118+
testAfterSlot,
119+
testTx.TTL(),
120+
)
121+
return
122+
}
123+
testErrType := shelley.ExpiredUtxoError{}
124+
assert.IsType(
125+
t,
126+
testErrType,
127+
err,
128+
"did not get expected error type: got %T, wanted %T",
129+
err,
130+
testErrType,
131+
)
132+
},
133+
)
134+
// Zero TTL
135+
testTx.Body.Ttl = testZeroSlot
136+
testRun(
137+
t,
138+
"zero TTL",
139+
testZeroSlot,
140+
func(t *testing.T, err error) {
141+
if err != nil {
142+
t.Errorf(
143+
"UtxoValidateTimeToLive should succeed when provided a zero TTL\n got error: %v",
144+
err,
145+
)
146+
}
147+
},
148+
)
149+
}

protocol/chainsync/messages.go

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -270,9 +270,5 @@ func NewMsgDone() *MsgDone {
270270
return m
271271
}
272272

273-
type Tip struct {
274-
// Tells the CBOR decoder to convert to/from a struct and a CBOR array
275-
_ struct{} `cbor:",toarray"`
276-
Point common.Point
277-
BlockNumber uint64
278-
}
273+
// Tip is an alias to keep historical code from breaking after moving this elsewhere
274+
type Tip = common.Tip

protocol/common/types.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,10 @@ func (p *Point) MarshalCBOR() ([]byte, error) {
6666
}
6767
return cbor.Encode(data)
6868
}
69+
70+
// Tip represents a Point combined with a block number
71+
type Tip struct {
72+
cbor.StructAsArray
73+
Point Point
74+
BlockNumber uint64
75+
}

0 commit comments

Comments
 (0)