Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: initial framework for validation rules #874

Merged
merged 1 commit into from
Feb 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions ledger/common/rules.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// 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 common

type UtxoValidationRuleFunc func(Transaction, LedgerState, TipState, ProtocolParameters) error
38 changes: 38 additions & 0 deletions ledger/common/state.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// 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 common

import (
pcommon "github.com/blinklabs-io/gouroboros/protocol/common"
)

// UtxoState defines the interface for querying the UTxO state
type UtxoState interface {
UtxosById([]TransactionInput) ([]Utxo, error)
}

// CertState defines the interface for querying the certificate state
type CertState interface{}

// LedgerState defines the interface for querying the ledger
type LedgerState interface {
UtxoState
CertState
}

// TipState defines the interface for querying the current tip
type TipState interface {
Tip() (pcommon.Tip, error)
}
30 changes: 30 additions & 0 deletions ledger/shelley/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// 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 shelley

import "fmt"

type ExpiredUtxoError struct {
Ttl uint64
Slot uint64
}

func (e ExpiredUtxoError) Error() string {
return fmt.Sprintf(
"expired UTxO: TTL %d, slot %d",
e.Ttl,
e.Slot,
)
}
39 changes: 39 additions & 0 deletions ledger/shelley/rules.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// 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 shelley

import (
common "github.com/blinklabs-io/gouroboros/ledger/common"
)

var UtxoValidationRules = []common.UtxoValidationRuleFunc{
UtxoValidateTimeToLive,
}

// UtxoValidateTimeToLive ensures that the current tip slot is not after the specified TTL value
func UtxoValidateTimeToLive(tx common.Transaction, ls common.LedgerState, ts common.TipState, pp common.ProtocolParameters) error {
tip, err := ts.Tip()
if err != nil {
return err
}
ttl := tx.TTL()
if ttl == 0 || ttl >= tip.Point.Slot {
return nil
}
return ExpiredUtxoError{
Ttl: ttl,
Slot: tip.Point.Slot,
}
}
149 changes: 149 additions & 0 deletions ledger/shelley/rules_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
// 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 shelley_test

import (
"testing"

"github.com/blinklabs-io/gouroboros/ledger/common"
"github.com/blinklabs-io/gouroboros/ledger/shelley"
pcommon "github.com/blinklabs-io/gouroboros/protocol/common"

"github.com/stretchr/testify/assert"
)

type testLedgerState struct {
utxos []common.Utxo
}

func (ls testLedgerState) UtxosById(_ []common.TransactionInput) ([]common.Utxo, error) {
return ls.utxos, nil
}

type testTipState struct {
tip pcommon.Tip
}

func (ts testTipState) Tip() (pcommon.Tip, error) {
return ts.tip, nil
}

func TestUtxoValidateTimeToLive(t *testing.T) {
var testSlot uint64 = 555666777
var testZeroSlot uint64 = 0
testTx := &shelley.ShelleyTransaction{
Body: shelley.ShelleyTransactionBody{
Ttl: testSlot,
},
}
testLedgerState := testLedgerState{}
testProtocolParams := &shelley.ShelleyProtocolParameters{}
var testBeforeSlot uint64 = 555666700
var testAfterSlot uint64 = 555666799
// Test helper function
testRun := func(t *testing.T, name string, testTipSlot uint64, validateFunc func(*testing.T, error)) {
t.Run(
name,
func(t *testing.T) {
testTipState := testTipState{
tip: pcommon.Tip{
Point: pcommon.Point{
Slot: testTipSlot,
},
},
}
err := shelley.UtxoValidateTimeToLive(
testTx,
testLedgerState,
testTipState,
testProtocolParams,
)
validateFunc(t, err)
},
)
}
// Slot before TTL
testRun(
t,
"slot before TTL",
testBeforeSlot,
func(t *testing.T, err error) {
if err != nil {
t.Errorf(
"UtxoValidateTimeToLive should succeed when provided a tip slot (%d) before the specified TTL (%d)\n got error: %v",
testBeforeSlot,
testTx.TTL(),
err,
)
}
},
)
// Slot equal to TTL
testRun(
t,
"slot equal to TTL",
testSlot,
func(t *testing.T, err error) {
if err != nil {
t.Errorf(
"UtxoValidateTimeToLive should succeed when provided a tip slot (%d) equal to the specified TTL (%d)\n got error: %v",
testSlot,
testTx.TTL(),
err,
)
}
},
)
// Slot after TTL
testRun(
t,
"slot after TTL",
testAfterSlot,
func(t *testing.T, err error) {
if err == nil {
t.Errorf(
"UtxoValidateTimeToLive should fail when provided a tip slot (%d) after the specified TTL (%d)",
testAfterSlot,
testTx.TTL(),
)
return
}
testErrType := shelley.ExpiredUtxoError{}
assert.IsType(
t,
testErrType,
err,
"did not get expected error type: got %T, wanted %T",
err,
testErrType,
)
},
)
// Zero TTL
testTx.Body.Ttl = testZeroSlot
testRun(
t,
"zero TTL",
testZeroSlot,
func(t *testing.T, err error) {
if err != nil {
t.Errorf(
"UtxoValidateTimeToLive should succeed when provided a zero TTL\n got error: %v",
err,
)
}
},
)
}
8 changes: 2 additions & 6 deletions protocol/chainsync/messages.go
Original file line number Diff line number Diff line change
Expand Up @@ -270,9 +270,5 @@ func NewMsgDone() *MsgDone {
return m
}

type Tip struct {
// Tells the CBOR decoder to convert to/from a struct and a CBOR array
_ struct{} `cbor:",toarray"`
Point common.Point
BlockNumber uint64
}
// Tip is an alias to keep historical code from breaking after moving this elsewhere
type Tip = common.Tip
7 changes: 7 additions & 0 deletions protocol/common/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,10 @@ func (p *Point) MarshalCBOR() ([]byte, error) {
}
return cbor.Encode(data)
}

// Tip represents a Point combined with a block number
type Tip struct {
cbor.StructAsArray
Point Point
BlockNumber uint64
}
Loading