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: allow sim fraud #1284

Open
wants to merge 25 commits into
base: main
Choose a base branch
from
24 changes: 24 additions & 0 deletions block/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import (
"context"
"errors"
"fmt"
"os"
"sync"
"sync/atomic"

"github.com/dymensionxyz/dymint/dofraud"
"github.com/dymensionxyz/gerr-cosmos/gerrc"
"golang.org/x/sync/errgroup"

Expand Down Expand Up @@ -126,6 +128,9 @@ type Manager struct {

// validates all non-finalized state updates from settlement, checking there is consistency between DA and P2P blocks, and the information in the state update.
SettlementValidator *SettlementValidator

// for testing
fraudSim dofraud.Frauds
}

// NewManager creates new block Manager.
Expand Down Expand Up @@ -209,9 +214,28 @@ func NewManager(

m.SettlementValidator = NewSettlementValidator(m.logger, m)

err = m.loadFraud(conf.FraudCmdsPath)
if err != nil {
return nil, fmt.Errorf("load frauds: %w", err)
}

return m, nil
}

func (m *Manager) loadFraud(path string) error {
frauds, err := dofraud.Load(path)
if err != nil {
if !errors.Is(err, os.ErrNotExist) {
return fmt.Errorf("load frauds: %w", err)
}
m.logger.Info("Did not load fraud tests - frauds file not found", "path", path)
} else {
m.logger.Info("Loaded frauds.")
}
m.fraudSim = frauds
return nil
}

// Start starts the block manager.
func (m *Manager) Start(ctx context.Context) error {
m.Ctx, m.Cancel = context.WithCancel(ctx)
Expand Down
2 changes: 2 additions & 0 deletions block/p2p.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"

"github.com/dymensionxyz/dymint/dofraud"
"github.com/dymensionxyz/dymint/p2p"
"github.com/dymensionxyz/dymint/types"
"github.com/tendermint/tendermint/libs/pubsub"
Expand Down Expand Up @@ -66,6 +67,7 @@ func (m *Manager) OnReceivedBlock(event pubsub.Message) {
// gossipBlock sends created blocks by the sequencer to full-nodes using P2P gossipSub
func (m *Manager) gossipBlock(ctx context.Context, block types.Block, commit types.Commit) error {
m.logger.Info("Gossipping block", "height", block.Header.Height)
m.doFraud(dofraud.Gossip, block.Header.Height, &block, &commit) // TODO: technically ought to clone here, but block,commit aren't used afterwards except for managing production rate, fine for MVP
gossipedBlock := p2p.BlockData{Block: block, Commit: commit}
gossipedBlockBytes, err := gossipedBlock.MarshalBinary()
if err != nil {
Expand Down
15 changes: 15 additions & 0 deletions block/produce.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"time"

"github.com/dymensionxyz/dymint/dofraud"
"github.com/dymensionxyz/gerr-cosmos/gerrc"

"github.com/dymensionxyz/dymint/node/events"
Expand Down Expand Up @@ -238,6 +239,7 @@ func (m *Manager) produceBlock(opts ProduceBlockOptions) (*types.Block, *types.C

// dequeue consensus messages for the new sequencers while creating a new block
block = m.Executor.CreateBlock(newHeight, lastCommit, lastHeaderHash, proposerHashForBlock, m.State, maxBlockDataSize)

// this cannot happen if there are any sequencer set updates
// AllowEmpty should be always true in this case
if !opts.AllowEmpty && len(block.Data.Txs) == 0 {
Expand All @@ -248,6 +250,7 @@ func (m *Manager) produceBlock(opts ProduceBlockOptions) (*types.Block, *types.C
if err != nil {
return nil, nil, fmt.Errorf("create commit: %w: %w", err, ErrNonRecoverable)
}
m.doFraud(dofraud.Produce, newHeight, block, commit)

m.logger.Info("Block created.", "height", newHeight, "num_tx", len(block.Data.Txs), "size", block.SizeBytes()+commit.SizeBytes())
types.RollappBlockSizeBytesGauge.Set(float64(len(block.Data.Txs)))
Expand Down Expand Up @@ -348,3 +351,15 @@ func getHeaderHashAndCommit(store store.Store, height uint64) ([32]byte, *types.
}
return lastBlock.Header.Hash(), lastCommit, nil
}

// if a fraud is specified, apply it (modify block, commit)
func (m *Manager) doFraud(variant dofraud.FraudVariant, h uint64, b *types.Block, c *types.Commit) {
if m.fraudSim.Apply(m.logger, h, variant, b) {
comm, err := m.createCommit(b)
if err != nil {
m.logger.Error("Fraud block, create commit.", "err", err)
} else {
*c = *comm
}
}
}
25 changes: 24 additions & 1 deletion block/submit.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"sync/atomic"
"time"

"github.com/dymensionxyz/dymint/dofraud"
"github.com/dymensionxyz/gerr-cosmos/gerrc"
"github.com/tendermint/tendermint/libs/pubsub"
"golang.org/x/sync/errgroup"
Expand Down Expand Up @@ -240,7 +241,22 @@ func (m *Manager) CreateBatch(maxBatchSize uint64, startHeight uint64, endHeight
}

func (m *Manager) SubmitBatch(batch *types.Batch) error {
resultSubmitToDA := m.DAClient.SubmitBatch(batch)
daBatch := batch
// a little optimized to avoid expensive clone on every batch
// only clone and apply frauds if a fraud is actually specified
// if fraud is specified, it's only for the DA, not for the SL
for _, b := range batch.Blocks {
if m.fraudSim.Has(b.Header.Height, dofraud.DA) {
var err error
daBatch, err = batch.Clone()
if err != nil {
return fmt.Errorf("deep clone batch: %w", err)
}
m.applyFraudsToBatch(daBatch)
break
}
}
resultSubmitToDA := m.DAClient.SubmitBatch(daBatch)
if resultSubmitToDA.Code != da.StatusSuccess {
return fmt.Errorf("da client submit batch: %s: %w", resultSubmitToDA.Message, resultSubmitToDA.Error)
}
Expand Down Expand Up @@ -326,3 +342,10 @@ func UpdateBatchSubmissionGauges(skewBytes uint64, skewBlocks uint64, skewTime t
types.RollappPendingSubmissionsSkewBlocks.Set(float64(skewBlocks))
types.RollappPendingSubmissionsSkewTimeMinutes.Set(float64(skewTime.Minutes()))
}

// applies frauds to a clone of the batch, if necessary, returns same batch or
func (m *Manager) applyFraudsToBatch(batch *types.Batch) {
for i, block := range batch.Blocks {
m.doFraud(dofraud.DA, block.Header.Height, block, batch.Commits[i])
}
}
2 changes: 2 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ type BlockManagerConfig struct {
BatchSubmitBytes uint64 `mapstructure:"batch_submit_bytes"`
// SequencerSetUpdateInterval defines the interval at which to fetch sequencer updates from the settlement layer
SequencerSetUpdateInterval time.Duration `mapstructure:"sequencer_update_interval"`
// File path to configure frauds for testing
FraudCmdsPath string `mapstructure:"fraud_cmds_path,omitempty"`
}

// GetViperConfig reads configuration parameters from Viper instance.
Expand Down
114 changes: 114 additions & 0 deletions dofraud/apply.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package dofraud

import (
"fmt"

"github.com/dymensionxyz/dymint/types"
)

type (
FraudVariant = int
FraudType = int
)

// Variant
const (
NoneVariant = iota
DA
Gossip
Produce
)

// Type
const (
NoneType = iota
HeaderVersionBlock
HeaderVersionApp
HeaderChainID
HeaderHeight
HeaderTime
HeaderLastHeaderHash
HeaderDataHash
HeaderConsensusHash
HeaderAppHash
HeaderLastResultsHash
HeaderProposerAddr
HeaderLastCommitHash
HeaderSequencerHash
HeaderNextSequencerHash
Data
LastCommit
)

type Cmd struct {
*types.Block
ts []FraudType
}

// The possibilites are simple, just change
type Frauds struct {
frauds map[string]Cmd
}

type key struct {
height uint64
variant FraudVariant
}

func (k key) String() string {
return fmt.Sprintf("%d:%d", k.height, k.variant)
}

func (f *Frauds) Has(height uint64, variant FraudVariant) bool {
_, ok := f.frauds[key{height, variant}.String()]
return ok
}

// apply any loaded frauds, no-op if none
func (f *Frauds) Apply(log types.Logger, height uint64, fraudVariant FraudVariant, b *types.Block) bool {
cmd, ok := f.frauds[key{height, fraudVariant}.String()]
if !ok {
return false
}

for _, fraud := range cmd.ts {
switch fraud {
case HeaderVersionBlock:
b.Header.Version.Block = cmd.Header.Version.Block
case HeaderVersionApp:
b.Header.Version.App = cmd.Header.Version.App
case HeaderChainID:
b.Header.ChainID = cmd.Header.ChainID
case HeaderHeight:
b.Header.Height = cmd.Header.Height
case HeaderTime:
b.Header.Time = cmd.Header.Time
case HeaderLastHeaderHash:
b.Header.LastHeaderHash = cmd.Header.LastHeaderHash
case HeaderDataHash:
b.Header.DataHash = cmd.Header.DataHash
case HeaderConsensusHash:
b.Header.ConsensusHash = cmd.Header.ConsensusHash
case HeaderAppHash:
b.Header.AppHash = cmd.Header.AppHash
case HeaderLastResultsHash:
b.Header.LastResultsHash = cmd.Header.LastResultsHash
case HeaderProposerAddr:
b.Header.ProposerAddress = cmd.Header.ProposerAddress
case HeaderLastCommitHash:
b.Header.LastCommitHash = cmd.Header.LastCommitHash
case HeaderSequencerHash:
b.Header.SequencerHash = cmd.Header.SequencerHash
case HeaderNextSequencerHash:
b.Header.NextSequencersHash = cmd.Header.NextSequencersHash
case Data:
b.Data = cmd.Data
case LastCommit:
b.LastCommit = cmd.LastCommit
default:
}
}

log.Info("Applied fraud.", "height", height, "variant", fraudVariant, "types", cmd.ts)
return true
}
Loading
Loading