Skip to content

Commit

Permalink
Start implementing network rule validation
Browse files Browse the repository at this point in the history
  • Loading branch information
HerbertJordan committed Dec 20, 2024
1 parent 4846231 commit ba64fe4
Show file tree
Hide file tree
Showing 7 changed files with 213 additions and 15 deletions.
5 changes: 3 additions & 2 deletions gossip/blockproc/drivermodule/driver_txs.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ import (
)

const (
maxAdvanceEpochs = 1 << 16
internalTransactionsGasLimit = opera.MinimumMaxBlockGas / 2
maxAdvanceEpochs = 1 << 16
)

type DriverTxListenerModule struct{}
Expand Down Expand Up @@ -66,7 +67,7 @@ func InternalTxBuilder(statedb state.StateDB) func(calldata []byte, addr common.
if nonce == math.MaxUint64 {
nonce = statedb.GetNonce(common.Address{})
}
tx := types.NewTransaction(nonce, addr, common.Big0, 500_000_000, common.Big0, calldata)
tx := types.NewTransaction(nonce, addr, common.Big0, internalTransactionsGasLimit, common.Big0, calldata)
nonce++
return tx
}
Expand Down
4 changes: 4 additions & 0 deletions integration/makegenesis/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,10 @@ func (b *GenesisBuilder) FinalizeBlockZero(
return common.Hash{}, common.Hash{}, errors.New("block zero already finalized")
}

if err := rules.Validate(); err != nil {
return common.Hash{}, common.Hash{}, fmt.Errorf("invalid rules: %w", err)
}

// construct state root of initial state
b.tmpStateDB.EndBlock(0)
genesisStateRoot := b.tmpStateDB.GetStateHash()
Expand Down
18 changes: 11 additions & 7 deletions opera/marshal.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,19 @@ package opera

import "encoding/json"

func UpdateRules(src Rules, diff []byte) (res Rules, err error) {
func UpdateRules(src Rules, diff []byte) (Rules, error) {
changed := src.Copy()
err = json.Unmarshal(diff, &changed)
err := json.Unmarshal(diff, &changed)
if err != nil {
return src, err
return Rules{}, err
}
// protect readonly fields
res = changed
res.NetworkID = src.NetworkID
res.Name = src.Name
return
changed.NetworkID = src.NetworkID
changed.Name = src.Name

// check validity of the new rules
if err = changed.Validate(); err != nil {
return Rules{}, err
}
return changed, nil
}
5 changes: 3 additions & 2 deletions opera/marshal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ func TestUpdateRules(t *testing.T) {

exp.Dag.MaxParents = 5
exp.Economy.MinGasPrice = big.NewInt(7)
exp.Economy.MinBaseFee = big.NewInt(12)
exp.Blocks.MaxBlockGas = 1000
got, err := UpdateRules(exp, []byte(`{"Dag":{"MaxParents":5},"Economy":{"MinGasPrice":7},"Blocks":{"MaxBlockGas":1000}}`))
got, err := UpdateRules(exp, []byte(`{"Dag":{"MaxParents":5},"Economy":{"MinGasPrice":7},"Blocks":{"MaxBlockGas":2000000000}}`))
require.NoError(err)
require.Equal(exp.String(), got.String(), "mutate fields")
require.Equal(exp.String(), got.String(), "mutate fields") // < this test checks nothing; todo: fix

exp.Dag.MaxParents = 0
got, err = UpdateRules(exp, []byte(`{"Name":"xxx","NetworkID":1,"Dag":{"MaxParents":0}}`))
Expand Down
15 changes: 11 additions & 4 deletions opera/rules.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package opera

import (
"encoding/json"
"math"
"math/big"
"time"

Expand All @@ -26,8 +27,9 @@ const (
llrBit = 1 << 2
sonicBit = 1 << 3

defaultMaxBlockGas = 1_000_000_000
defaultTargetGasRate = 15_000_000 // 15 MGas/s
MinimumMaxBlockGas = 1_000_000_000 // < must be large enough to allow internal transactions to seal blocks
MaximumMaxBlockGas = math.MaxInt64 // < should fit into 64-bit signed integers to avoid parsing errors in third-party libraries
defaultTargetGasRate = 15_000_000 // 15 MGas/s
defaultEventEmitterInterval = 600 * time.Millisecond
)

Expand Down Expand Up @@ -297,7 +299,7 @@ func MainNetRules() Rules {
Epochs: DefaultEpochsRules(),
Economy: DefaultEconomyRules(),
Blocks: BlocksRules{
MaxBlockGas: defaultMaxBlockGas,
MaxBlockGas: MinimumMaxBlockGas,
MaxEmptyBlockSkipPeriod: inter.Timestamp(1 * time.Minute),
},
}
Expand All @@ -312,7 +314,7 @@ func FakeNetRules() Rules {
Epochs: FakeNetEpochsRules(),
Economy: FakeEconomyRules(),
Blocks: BlocksRules{
MaxBlockGas: defaultMaxBlockGas,
MaxBlockGas: MinimumMaxBlockGas,
MaxEmptyBlockSkipPeriod: inter.Timestamp(3 * time.Second),
},
Upgrades: Upgrades{
Expand Down Expand Up @@ -414,9 +416,14 @@ func DefaultGasPowerRules() GasPowerRules {
func (r Rules) Copy() Rules {
cp := r
cp.Economy.MinGasPrice = new(big.Int).Set(r.Economy.MinGasPrice)
cp.Economy.MinBaseFee = new(big.Int).Set(r.Economy.MinBaseFee)
return cp
}

func (r Rules) Validate() error {
return validate(r)
}

func (r Rules) String() string {
b, _ := json.Marshal(&r)
return string(b)
Expand Down
162 changes: 162 additions & 0 deletions opera/validate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
package opera

import (
"errors"
"math/big"
"time"

"github.com/Fantom-foundation/go-opera/inter"
)

var (
maxMinimumGasPrice = new(big.Int).SetUint64(1000 * 1e9) // 1000 Gwei
)

func validate(rules Rules) error {
return errors.Join(
validateDagRules(rules.Dag),
validateEmitterRules(rules.Emitter),
validateEpochsRules(rules.Epochs),
validateBlockRules(rules.Blocks),
validateEconomyRules(rules.Economy),
validateUpgrades(rules.Upgrades),
)
}

func validateDagRules(rules DagRules) error {
var issues []error

if rules.MaxParents < 2 {
issues = append(issues, errors.New("Dag.MaxParents is too low"))
}

if rules.MaxExtraData > 1<<20 { // 1 MB
issues = append(issues, errors.New("Dag.MaxExtraData is too high"))
}

return errors.Join(issues...)
}

func validateEmitterRules(rules EmitterRules) error {

var issues []error
if rules.Interval < inter.Timestamp(100*time.Millisecond) {
issues = append(issues, errors.New("Emitter.Interval is too low"))
}
if rules.Interval > inter.Timestamp(10*time.Second) {
issues = append(issues, errors.New("Emitter.Interval is too high"))
}

if rules.StallThreshold < inter.Timestamp(10*time.Second) {
issues = append(issues, errors.New("Emitter.StallThreshold is too low"))
}

if rules.StalledInterval < inter.Timestamp(10*time.Second) {
issues = append(issues, errors.New("Emitter.StalledInterval is too low"))
}
if rules.StalledInterval > inter.Timestamp(1*time.Minute) {
issues = append(issues, errors.New("Emitter.StalledInterval is too high"))
}

return errors.Join(issues...)
}

func validateEpochsRules(rules EpochsRules) error {
var issues []error

// MaxEpochGas is not restricted. If it is too low, we will have an epoch per block, which is
// not great performance-wise, but it is not invalid. If it is too high, the time limit will
// eventually end a long epoch.

if rules.MaxEpochDuration > inter.Timestamp(24*time.Hour) {
issues = append(issues, errors.New("Epochs.MaxEpochDuration is too high"))
}

return errors.Join(issues...)
}

func validateBlockRules(rules BlocksRules) error {
var issues []error

if rules.MaxBlockGas < MinimumMaxBlockGas {
issues = append(issues, errors.New("MaxBlockGas is too low"))
}
if rules.MaxBlockGas > MaximumMaxBlockGas {
issues = append(issues, errors.New("MaxBlockGas is too high"))
}

// The empty-block skip period is not restricted. There are no too low or too high values.

return errors.Join(issues...)
}

func validateEconomyRules(rules EconomyRules) error {
var issues []error

if rules.MinGasPrice == nil {
issues = append(issues, errors.New("MinGasPrice is nil"))
} else {
if rules.MinGasPrice.Sign() < 0 {
issues = append(issues, errors.New("MinGasPrice is negative"))
}
if rules.MinGasPrice.Cmp(maxMinimumGasPrice) > 0 {
issues = append(issues, errors.New("MinGasPrice is too high"))
}
}

if rules.MinBaseFee == nil {
issues = append(issues, errors.New("MinBaseFee is nil"))
} else {
if rules.MinBaseFee.Sign() < 0 {
issues = append(issues, errors.New("MinBaseFee is negative"))
}
if rules.MinBaseFee.Cmp(maxMinimumGasPrice) > 0 {
issues = append(issues, errors.New("MinBaseFee is too high"))
}
}

// TODO: check BlockMissedSlack

issues = append(issues, validateGasRules(rules.Gas))
issues = append(issues, validateGasPowerRules("Economy.ShortGasPower", rules.ShortGasPower))
issues = append(issues, validateGasPowerRules("Economy.LongGasPower", rules.LongGasPower))

return errors.Join(issues...)
}

func validateGasRules(rules GasRules) error {
var issues []error

// TODO: implement

return errors.Join(issues...)
}

func validateGasPowerRules(prefix string, rules GasPowerRules) error {
var issues []error

// TODO: implement

return errors.Join(issues...)
}

func validateUpgrades(upgrade Upgrades) error {
var issues []error

if upgrade.Llr {
issues = append(issues, errors.New("LLR upgrade is not supported"))
}

if upgrade.Sonic && !upgrade.London {
issues = append(issues, errors.New("Sonic upgrade requires London"))
}
if upgrade.London && !upgrade.Berlin {
issues = append(issues, errors.New("London upgrade requires Berlin"))
}

if !upgrade.Sonic {
issues = append(issues, errors.New("Sonic upgrade is required"))
}

return errors.Join(issues...)
}
19 changes: 19 additions & 0 deletions opera/validate_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package opera

import (
"testing"

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

func TestDefaultRulesAreValid(t *testing.T) {
rules := map[string]Rules{
"mainnet": MainNetRules(),
"fakenet": FakeNetRules(),
}
for name, r := range rules {
t.Run(name, func(t *testing.T) {
require.NoError(t, r.Validate())
})
}
}

0 comments on commit ba64fe4

Please sign in to comment.