From bb8719bf891a5ac5529218846230449eb547e97a Mon Sep 17 00:00:00 2001 From: danwt <30197399+danwt@users.noreply.github.com> Date: Fri, 13 Dec 2024 15:12:41 +0000 Subject: [PATCH 01/25] basically implement it --- block/executor.go | 8 +- block/manager.go | 13 +++ block/produce.go | 11 ++- dofraud/do_fraud.go | 212 +++++++++++++++++++++++++++++++++++++++++++ dofraud/example.json | 40 ++++++++ dofraud/z_test.go | 33 +++++++ 6 files changed, 315 insertions(+), 2 deletions(-) create mode 100644 dofraud/do_fraud.go create mode 100644 dofraud/example.json create mode 100644 dofraud/z_test.go diff --git a/block/executor.go b/block/executor.go index f3a1421c5..ae845949d 100644 --- a/block/executor.go +++ b/block/executor.go @@ -4,6 +4,7 @@ import ( "errors" "time" + "github.com/dymensionxyz/dymint/dofraud" proto2 "github.com/gogo/protobuf/proto" "go.uber.org/multierr" @@ -24,7 +25,7 @@ const minBlockMaxBytes = 10000 type ExecutorI interface { InitChain(genesis *tmtypes.GenesisDoc, genesisChecksum string, valset []*tmtypes.Validator) (*abci.ResponseInitChain, error) - CreateBlock(height uint64, lastCommit *types.Commit, lastHeaderHash, nextSeqHash [32]byte, state *types.State, maxBlockDataSizeBytes uint64) *types.Block + CreateBlock(height uint64, lastCommit *types.Commit, lastHeaderHash, nextSeqHash [32]byte, state *types.State, maxBlockDataSizeBytes uint64, fraud *dofraud.Cmd) *types.Block Commit(state *types.State, block *types.Block, resp *tmstate.ABCIResponses) ([]byte, int64, error) GetAppInfo() (*abci.ResponseInfo, error) ExecuteBlock(block *types.Block) (*tmstate.ABCIResponses, error) @@ -143,6 +144,7 @@ func (e *Executor) CreateBlock( lastHeaderHash, nextSeqHash [32]byte, state *types.State, maxBlockDataSizeBytes uint64, + fraud *dofraud.Cmd, // optional fraud, for testing ) *types.Block { maxBlockDataSizeBytes = min(maxBlockDataSizeBytes, uint64(max(minBlockMaxBytes, state.ConsensusParams.Block.MaxBytes))) //nolint:gosec // MaxBytes is always positive mempoolTxs := e.mempool.ReapMaxBytesMaxGas(int64(maxBlockDataSizeBytes), state.ConsensusParams.Block.MaxGas) //nolint:gosec // size is always positive and falls in int64 @@ -175,6 +177,10 @@ func (e *Executor) CreateBlock( copy(block.Header.DataHash[:], types.GetDataHash(block)) copy(block.Header.SequencerHash[:], state.GetProposerHash()) copy(block.Header.NextSequencersHash[:], nextSeqHash[:]) + + if fraud != nil { + block = dofraud.ApplyFraud(*fraud, block) + } return block } diff --git a/block/manager.go b/block/manager.go index 61d74a6ab..30f2aff61 100644 --- a/block/manager.go +++ b/block/manager.go @@ -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" @@ -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 + + // frauds for testing + fraudCmds dofraud.Cmds } // NewManager creates new block Manager. @@ -209,11 +214,19 @@ func NewManager( m.SettlementValidator = NewSettlementValidator(m.logger, m) + fn := "foo" + frauds, err := dofraud.Load(fn) + if err != nil && !errors.Is(err, os.ErrNotExist) { + return nil, fmt.Errorf("load frauds: %w", err) + } + m.fraudCmds = frauds + return m, nil } // Start starts the block manager. func (m *Manager) Start(ctx context.Context) error { + m.Ctx, m.Cancel = context.WithCancel(ctx) // Check if InitChain flow is needed if m.State.IsGenesis() { diff --git a/block/produce.go b/block/produce.go index 9a67fe77b..5e57455c2 100644 --- a/block/produce.go +++ b/block/produce.go @@ -6,6 +6,7 @@ import ( "fmt" "time" + "github.com/dymensionxyz/dymint/dofraud" "github.com/dymensionxyz/gerr-cosmos/gerrc" "github.com/dymensionxyz/dymint/node/events" @@ -236,8 +237,16 @@ func (m *Manager) produceBlock(opts ProduceBlockOptions) (*types.Block, *types.C proposerHashForBlock = *opts.NextProposerHash } + var fraud *dofraud.Cmd + { + f, ok := m.Conf.Frauds[newHeight] + if ok { + fraud = &f + } + } + // dequeue consensus messages for the new sequencers while creating a new block - block = m.Executor.CreateBlock(newHeight, lastCommit, lastHeaderHash, proposerHashForBlock, m.State, maxBlockDataSize) + block = m.Executor.CreateBlock(newHeight, lastCommit, lastHeaderHash, proposerHashForBlock, m.State, maxBlockDataSize, fraud) // 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 { diff --git a/dofraud/do_fraud.go b/dofraud/do_fraud.go new file mode 100644 index 000000000..de0321636 --- /dev/null +++ b/dofraud/do_fraud.go @@ -0,0 +1,212 @@ +package dofraud + +import ( + "encoding/json" + "io" + "os" + + "github.com/dymensionxyz/dymint/types" +) + +type diskPair struct { + Height uint64 + Cmd diskCmd +} + +type disk struct { + Pairs []diskPair +} + +type diskCmd struct { + HeaderVersionBlock uint64 `json:",omitempty"` + HeaderVersionApp uint64 `json:",omitempty"` + HeaderChainID string `json:",omitempty"` + HeaderHeight uint64 `json:",omitempty"` + HeaderTime int64 `json:",omitempty"` + HeaderLastHeaderHash string `json:",omitempty"` + HeaderDataHash string `json:",omitempty"` + HeaderConsensusHash string `json:",omitempty"` + HeaderAppHash string `json:",omitempty"` + HeaderLastResultsHash string `json:",omitempty"` + HeaderProposerAddr string `json:",omitempty"` + HeaderLastCommitHash string `json:",omitempty"` + HeaderSequencerHash string `json:",omitempty"` + HeaderNextSequencerHash string `json:",omitempty"` + Data struct{} `json:",omitempty"` // TODO: + LastCommit struct{} `json:",omitempty"` // TODO: +} + +func Load(fn string) (Cmds, error) { + file, err := os.Open(fn) + if err != nil { + return nil, err + } + defer file.Close() + + data, err := io.ReadAll(file) + if err != nil { + return nil, err + } + + var d disk + err = json.Unmarshal(data, &d) + if err != nil { + return nil, err + } + + frauds := []FraudType{} + for _, pair := range d.Pairs { + frauds := []FraudType{} + if pair.Cmd.HeaderVersionBlock != 0 { + frauds = append(frauds, HeaderVersionBlock) + } + if pair.Cmd.HeaderVersionApp != 0 { + frauds = append(frauds, HeaderVersionApp) + } + if pair.Cmd.HeaderChainID != "" { + frauds = append(frauds, HeaderChainID) + } + if pair.Cmd.HeaderHeight != 0 { + frauds = append(frauds, HeaderHeight) + } + if pair.Cmd.HeaderTime != 0 { + frauds = append(frauds, HeaderTime) + } + if pair.Cmd.HeaderLastHeaderHash != "" { + frauds = append(frauds, HeaderLastHeaderHash) + } + if pair.Cmd.HeaderDataHash != "" { + frauds = append(frauds, HeaderDataHash) + } + if pair.Cmd.HeaderConsensusHash != "" { + frauds = append(frauds, HeaderConsensusHash) + } + if pair.Cmd.HeaderAppHash != "" { + frauds = append(frauds, HeaderAppHash) + } + if pair.Cmd.HeaderLastResultsHash != "" { + frauds = append(frauds, HeaderLastResultsHash) + } + if pair.Cmd.HeaderProposerAddr != "" { + frauds = append(frauds, HeaderProposerAddr) + } + if pair.Cmd.HeaderLastCommitHash != "" { + frauds = append(frauds, HeaderLastCommitHash) + } + if pair.Cmd.HeaderSequencerHash != "" { + frauds = append(frauds, HeaderSequencerHash) + } + if pair.Cmd.HeaderNextSequencerHash != "" { + frauds = append(frauds, HeaderNextSequencerHash) + } + // TODO: Data and LastCommit + } + + cmds := make(Cmds) + for _, pair := range d.Pairs { + cmd := Cmd{ + Block: &types.Block{ + Header: types.Header{ + Version: types.Version{ + Block: pair.Cmd.HeaderVersionBlock, + App: pair.Cmd.HeaderVersionApp, + }, + ChainID: pair.Cmd.HeaderChainID, + Height: pair.Cmd.HeaderHeight, + Time: pair.Cmd.HeaderTime, + LastHeaderHash: parseHash(pair.Cmd.HeaderLastHeaderHash), + DataHash: parseHash(pair.Cmd.HeaderDataHash), + ConsensusHash: parseHash(pair.Cmd.HeaderConsensusHash), + AppHash: parseHash(pair.Cmd.HeaderAppHash), + LastResultsHash: parseHash(pair.Cmd.HeaderLastResultsHash), + ProposerAddress: []byte(pair.Cmd.HeaderProposerAddr), + LastCommitHash: parseHash(pair.Cmd.HeaderLastCommitHash), + SequencerHash: parseHash(pair.Cmd.HeaderSequencerHash), + NextSequencersHash: parseHash(pair.Cmd.HeaderNextSequencerHash), + }, + // Data and LastCommit fields need to be populated as per your requirements + }, + frauds: frauds, + } + cmds[pair.Height] = cmd + } + + return cmds, nil +} + +func parseHash(hashStr string) [32]byte { + var hash [32]byte + copy(hash[:], hashStr) + return hash +} + +type FraudType int + +const ( + None = iota + HeaderVersionBlock + HeaderVersionApp + HeaderChainID + HeaderHeight + HeaderTime + HeaderLastHeaderHash + HeaderDataHash + HeaderConsensusHash + HeaderAppHash + HeaderLastResultsHash + HeaderProposerAddr + HeaderLastCommitHash + HeaderSequencerHash + HeaderNextSequencerHash + Data + LastCommit +) + +// height -> cmd +type Cmds map[uint64]Cmd + +type Cmd struct { + *types.Block + frauds []FraudType +} + +func ApplyFraud(cmd Cmd, b *types.Block) *types.Block { + for _, fraud := range cmd.frauds { + 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: + } + } + return b +} diff --git a/dofraud/example.json b/dofraud/example.json new file mode 100644 index 000000000..3ab988367 --- /dev/null +++ b/dofraud/example.json @@ -0,0 +1,40 @@ +{ + "Pairs": [ + { + "Height": 10, + "Cmd": { + "HeaderVersionBlock": 1, + "HeaderChainID": "chain-1", + "Data": {}, + "LastCommit": {} + } + }, + { + "Height": 20, + "Cmd": { + "HeaderVersionApp": 2, + "HeaderHeight": 100, + "Data": {}, + "LastCommit": {} + } + }, + { + "Height": 30, + "Cmd": { + "HeaderTime": 1234567890, + "HeaderDataHash": "datahash", + "Data": {}, + "LastCommit": {} + } + }, + { + "Height": 40, + "Cmd": { + "HeaderDataHash": "datahash", + "HeaderNextSequencerHash": "foobar", + "Data": {}, + "LastCommit": {} + } + } + ] +} \ No newline at end of file diff --git a/dofraud/z_test.go b/dofraud/z_test.go new file mode 100644 index 000000000..27d8fa6d3 --- /dev/null +++ b/dofraud/z_test.go @@ -0,0 +1,33 @@ +package dofraud + +import ( + "encoding/json" + "os" + "testing" +) + +func TestDoFraud(t *testing.T) { + const fn = "/Users/danwt/Documents/dym/aaa-dym-notes/all_tasks/tasks/202412_testing_playground/frauds.json" + + // Generate a few example diskPairs + examples := disk{ + Pairs: []diskPair{ + {Height: 10, Cmd: diskCmd{HeaderVersionBlock: 1, HeaderChainID: "chain-1"}}, + {Height: 20, Cmd: diskCmd{HeaderVersionApp: 2, HeaderHeight: 100}}, + {Height: 30, Cmd: diskCmd{HeaderTime: 1234567890, HeaderDataHash: "datahash"}}, + {Height: 40, Cmd: diskCmd{HeaderNextSequencerHash: "foobar", HeaderDataHash: "datahash"}}, + }, + } + + // Marshal the examples to JSON + data, err := json.MarshalIndent(examples, "", " ") + if err != nil { + t.Fatalf("Failed to marshal examples: %v", err) + } + + // Write the JSON data to the specified file + err = os.WriteFile(fn, data, 0644) + if err != nil { + t.Fatalf("Failed to write examples to file: %v", err) + } +} From 3e6606dea0336bccbf62a9617991ff9f017d29fd Mon Sep 17 00:00:00 2001 From: danwt <30197399+danwt@users.noreply.github.com> Date: Fri, 13 Dec 2024 15:22:00 +0000 Subject: [PATCH 02/25] builds --- block/executor.go | 9 ++------- block/produce.go | 13 +++++-------- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/block/executor.go b/block/executor.go index ae845949d..ddaf86042 100644 --- a/block/executor.go +++ b/block/executor.go @@ -4,7 +4,6 @@ import ( "errors" "time" - "github.com/dymensionxyz/dymint/dofraud" proto2 "github.com/gogo/protobuf/proto" "go.uber.org/multierr" @@ -25,7 +24,7 @@ const minBlockMaxBytes = 10000 type ExecutorI interface { InitChain(genesis *tmtypes.GenesisDoc, genesisChecksum string, valset []*tmtypes.Validator) (*abci.ResponseInitChain, error) - CreateBlock(height uint64, lastCommit *types.Commit, lastHeaderHash, nextSeqHash [32]byte, state *types.State, maxBlockDataSizeBytes uint64, fraud *dofraud.Cmd) *types.Block + CreateBlock(height uint64, lastCommit *types.Commit, lastHeaderHash, nextSeqHash [32]byte, state *types.State, maxBlockDataSizeBytes uint64) *types.Block Commit(state *types.State, block *types.Block, resp *tmstate.ABCIResponses) ([]byte, int64, error) GetAppInfo() (*abci.ResponseInfo, error) ExecuteBlock(block *types.Block) (*tmstate.ABCIResponses, error) @@ -130,7 +129,7 @@ func (e *Executor) InitChain(genesis *tmtypes.GenesisDoc, genesisChecksum string Version: &tmproto.VersionParams{ AppVersion: params.Version.AppVersion, }, - }, Validators: valUpdates, + }, Validators: valUpdates, AppStateBytes: genesis.AppState, InitialHeight: genesis.InitialHeight, GenesisChecksum: genesisChecksum, @@ -144,7 +143,6 @@ func (e *Executor) CreateBlock( lastHeaderHash, nextSeqHash [32]byte, state *types.State, maxBlockDataSizeBytes uint64, - fraud *dofraud.Cmd, // optional fraud, for testing ) *types.Block { maxBlockDataSizeBytes = min(maxBlockDataSizeBytes, uint64(max(minBlockMaxBytes, state.ConsensusParams.Block.MaxBytes))) //nolint:gosec // MaxBytes is always positive mempoolTxs := e.mempool.ReapMaxBytesMaxGas(int64(maxBlockDataSizeBytes), state.ConsensusParams.Block.MaxGas) //nolint:gosec // size is always positive and falls in int64 @@ -178,9 +176,6 @@ func (e *Executor) CreateBlock( copy(block.Header.SequencerHash[:], state.GetProposerHash()) copy(block.Header.NextSequencersHash[:], nextSeqHash[:]) - if fraud != nil { - block = dofraud.ApplyFraud(*fraud, block) - } return block } diff --git a/block/produce.go b/block/produce.go index 5e57455c2..35e934b1e 100644 --- a/block/produce.go +++ b/block/produce.go @@ -237,16 +237,13 @@ func (m *Manager) produceBlock(opts ProduceBlockOptions) (*types.Block, *types.C proposerHashForBlock = *opts.NextProposerHash } - var fraud *dofraud.Cmd - { - f, ok := m.Conf.Frauds[newHeight] - if ok { - fraud = &f - } + // dequeue consensus messages for the new sequencers while creating a new block + block = m.Executor.CreateBlock(newHeight, lastCommit, lastHeaderHash, proposerHashForBlock, m.State, maxBlockDataSize) + f, ok := m.fraudCmds[newHeight] + if ok { + block = dofraud.ApplyFraud(f, block) } - // dequeue consensus messages for the new sequencers while creating a new block - block = m.Executor.CreateBlock(newHeight, lastCommit, lastHeaderHash, proposerHashForBlock, m.State, maxBlockDataSize, fraud) // 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 { From 57763e36d8fe8f08aacecf4f1c00974dba656b73 Mon Sep 17 00:00:00 2001 From: danwt <30197399+danwt@users.noreply.github.com> Date: Fri, 13 Dec 2024 15:23:27 +0000 Subject: [PATCH 03/25] add omit empty --- block/executor.go | 2 +- config/config.go | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/block/executor.go b/block/executor.go index ddaf86042..85e1e227a 100644 --- a/block/executor.go +++ b/block/executor.go @@ -129,7 +129,7 @@ func (e *Executor) InitChain(genesis *tmtypes.GenesisDoc, genesisChecksum string Version: &tmproto.VersionParams{ AppVersion: params.Version.AppVersion, }, - }, Validators: valUpdates, + }, Validators: valUpdates, AppStateBytes: genesis.AppState, InitialHeight: genesis.InitialHeight, GenesisChecksum: genesisChecksum, diff --git a/config/config.go b/config/config.go index c19c58277..a7eb22b79 100644 --- a/config/config.go +++ b/config/config.go @@ -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. From f55c4a88c3ba5b3d0be3757387d941f4f2448ff2 Mon Sep 17 00:00:00 2001 From: danwt <30197399+danwt@users.noreply.github.com> Date: Fri, 13 Dec 2024 15:24:36 +0000 Subject: [PATCH 04/25] use specific file --- block/manager.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/block/manager.go b/block/manager.go index 30f2aff61..2a699ce73 100644 --- a/block/manager.go +++ b/block/manager.go @@ -214,10 +214,12 @@ func NewManager( m.SettlementValidator = NewSettlementValidator(m.logger, m) - fn := "foo" - frauds, err := dofraud.Load(fn) - if err != nil && !errors.Is(err, os.ErrNotExist) { - return nil, fmt.Errorf("load frauds: %w", err) + frauds, err := dofraud.Load(conf.FraudCmdsPath) + if err != nil { + if !errors.Is(err, os.ErrNotExist) { + return nil, fmt.Errorf("load frauds: %w", err) + } + logger.Info("No frauds file found", "path", conf.FraudCmdsPath) } m.fraudCmds = frauds From 289ecd3a8c2f4d3d50e90c42cbb27cab91eae09c Mon Sep 17 00:00:00 2001 From: danwt <30197399+danwt@users.noreply.github.com> Date: Mon, 16 Dec 2024 10:41:40 +0000 Subject: [PATCH 05/25] fraud cp --- dofraud/do_fraud.go | 53 ++++++++++++++++++++++++++------------------- dofraud/z_test.go | 5 +---- 2 files changed, 32 insertions(+), 26 deletions(-) diff --git a/dofraud/do_fraud.go b/dofraud/do_fraud.go index de0321636..83e02bfb8 100644 --- a/dofraud/do_fraud.go +++ b/dofraud/do_fraud.go @@ -17,7 +17,38 @@ type disk struct { Pairs []diskPair } +type FraudType int +type FraudVariant int + +const ( + NoneType = iota + HeaderVersionBlock + HeaderVersionApp + HeaderChainID + HeaderHeight + HeaderTime + HeaderLastHeaderHash + HeaderDataHash + HeaderConsensusHash + HeaderAppHash + HeaderLastResultsHash + HeaderProposerAddr + HeaderLastCommitHash + HeaderSequencerHash + HeaderNextSequencerHash + Data + LastCommit +) + +const ( + NoneVariant = iota + Produce + DA + Gossip +) + type diskCmd struct { + Variant string `json:",omitempty"` HeaderVersionBlock uint64 `json:",omitempty"` HeaderVersionApp uint64 `json:",omitempty"` HeaderChainID string `json:",omitempty"` @@ -140,28 +171,6 @@ func parseHash(hashStr string) [32]byte { return hash } -type FraudType int - -const ( - None = iota - HeaderVersionBlock - HeaderVersionApp - HeaderChainID - HeaderHeight - HeaderTime - HeaderLastHeaderHash - HeaderDataHash - HeaderConsensusHash - HeaderAppHash - HeaderLastResultsHash - HeaderProposerAddr - HeaderLastCommitHash - HeaderSequencerHash - HeaderNextSequencerHash - Data - LastCommit -) - // height -> cmd type Cmds map[uint64]Cmd diff --git a/dofraud/z_test.go b/dofraud/z_test.go index 27d8fa6d3..0f40e692d 100644 --- a/dofraud/z_test.go +++ b/dofraud/z_test.go @@ -12,10 +12,7 @@ func TestDoFraud(t *testing.T) { // Generate a few example diskPairs examples := disk{ Pairs: []diskPair{ - {Height: 10, Cmd: diskCmd{HeaderVersionBlock: 1, HeaderChainID: "chain-1"}}, - {Height: 20, Cmd: diskCmd{HeaderVersionApp: 2, HeaderHeight: 100}}, - {Height: 30, Cmd: diskCmd{HeaderTime: 1234567890, HeaderDataHash: "datahash"}}, - {Height: 40, Cmd: diskCmd{HeaderNextSequencerHash: "foobar", HeaderDataHash: "datahash"}}, + {Height: 33, Cmd: diskCmd{HeaderProposerAddr: "1092381209381923809182391823098129038"}}, }, } From fd26f2610de65a3a1af6f9c1d2ca71a026b7ee0a Mon Sep 17 00:00:00 2001 From: danwt <30197399+danwt@users.noreply.github.com> Date: Mon, 16 Dec 2024 11:10:37 +0000 Subject: [PATCH 06/25] impl --- dofraud/disk.go | 165 +++++++++++++++++++++++++++++++++++++++++ dofraud/do_fraud.go | 174 +++++++------------------------------------- dofraud/go.doc.go | 2 + dofraud/z_test.go | 7 +- 4 files changed, 198 insertions(+), 150 deletions(-) create mode 100644 dofraud/disk.go create mode 100644 dofraud/go.doc.go diff --git a/dofraud/disk.go b/dofraud/disk.go new file mode 100644 index 000000000..bbfaef911 --- /dev/null +++ b/dofraud/disk.go @@ -0,0 +1,165 @@ +package dofraud + +import ( + "encoding/json" + "github.com/dymensionxyz/dymint/types" + "io" + "os" + "slices" + "strings" +) + +type disk struct { + Instances []diskInstance `json:",omitempty"` +} + +type diskInstance struct { + Height uint64 + Variant string + Block diskBlock +} + +type diskBlock struct { + HeaderVersionBlock uint64 `json:",omitempty"` + HeaderVersionApp uint64 `json:",omitempty"` + HeaderChainID string `json:",omitempty"` + HeaderHeight uint64 `json:",omitempty"` + HeaderTime int64 `json:",omitempty"` + HeaderLastHeaderHash string `json:",omitempty"` + HeaderDataHash string `json:",omitempty"` + HeaderConsensusHash string `json:",omitempty"` + HeaderAppHash string `json:",omitempty"` + HeaderLastResultsHash string `json:",omitempty"` + HeaderProposerAddr string `json:",omitempty"` + HeaderLastCommitHash string `json:",omitempty"` + HeaderSequencerHash string `json:",omitempty"` + HeaderNextSequencerHash string `json:",omitempty"` + Data struct{} `json:",omitempty"` // TODO: + LastCommit struct{} `json:",omitempty"` // TODO: +} + +func Load(fn string) (*Frauds, error) { + file, err := os.Open(fn) + if err != nil { + return nil, err + } + defer file.Close() + + data, err := io.ReadAll(file) + if err != nil { + return nil, err + } + + var d disk + err = json.Unmarshal(data, &d) + if err != nil { + return nil, err + } + + ret := Frauds{} + for _, pair := range d.Instances { + cmd := Cmd{ + Block: &types.Block{}, + } + key := key{pair.Height, parseVariant(pair.Variant)} + if pair.Block.HeaderVersionBlock != 0 { + frauds = append(frauds, HeaderVersionBlock) + } + if pair.Block.HeaderVersionApp != 0 { + frauds = append(frauds, HeaderVersionApp) + } + if pair.Block.HeaderChainID != "" { + frauds = append(frauds, HeaderChainID) + } + if pair.Block.HeaderHeight != 0 { + frauds = append(frauds, HeaderHeight) + } + if pair.Block.HeaderTime != 0 { + frauds = append(frauds, HeaderTime) + } + if pair.Block.HeaderLastHeaderHash != "" { + frauds = append(frauds, HeaderLastHeaderHash) + } + if pair.Block.HeaderDataHash != "" { + frauds = append(frauds, HeaderDataHash) + } + if pair.Block.HeaderConsensusHash != "" { + frauds = append(frauds, HeaderConsensusHash) + } + if pair.Block.HeaderAppHash != "" { + frauds = append(frauds, HeaderAppHash) + } + if pair.Block.HeaderLastResultsHash != "" { + frauds = append(frauds, HeaderLastResultsHash) + } + if pair.Block.HeaderProposerAddr != "" { + frauds = append(frauds, HeaderProposerAddr) + } + if pair.Block.HeaderLastCommitHash != "" { + frauds = append(frauds, HeaderLastCommitHash) + } + if pair.Block.HeaderSequencerHash != "" { + frauds = append(frauds, HeaderSequencerHash) + } + if pair.Block.HeaderNextSequencerHash != "" { + frauds = append(frauds, HeaderNextSequencerHash) + } + // TODO: Data and LastCommit + } + + cmds := make(Cmds) + for _, pair := range d.Instances { + cmd := Cmd{ + Block: &types.Block{ + Header: types.Header{ + Version: types.Version{ + Block: pair.Block.HeaderVersionBlock, + App: pair.Block.HeaderVersionApp, + }, + ChainID: pair.Block.HeaderChainID, + Height: pair.Block.HeaderHeight, + Time: pair.Block.HeaderTime, + LastHeaderHash: parseHash(pair.Block.HeaderLastHeaderHash), + DataHash: parseHash(pair.Block.HeaderDataHash), + ConsensusHash: parseHash(pair.Block.HeaderConsensusHash), + AppHash: parseHash(pair.Block.HeaderAppHash), + LastResultsHash: parseHash(pair.Block.HeaderLastResultsHash), + ProposerAddress: []byte(pair.Block.HeaderProposerAddr), + LastCommitHash: parseHash(pair.Block.HeaderLastCommitHash), + SequencerHash: parseHash(pair.Block.HeaderSequencerHash), + NextSequencersHash: parseHash(pair.Block.HeaderNextSequencerHash), + }, + // Data and LastCommit fields need to be populated as per your requirements + }, + ts: frauds, + } + cmds[pair.Height] = cmd + } + + return cmds, nil +} + +func parseHash(hashStr string) [32]byte { + var hash [32]byte + copy(hash[:], hashStr) + return hash +} + +func parseVariants(s string) []int { + ret := []int{} + s = strings.ToLower(s) + if strings.Contains(s, ",") { + l := strings.Split(s, ",") + for _, v := range l { + sub := parseVariants(v) + ret = append(ret, sub...) + } + return ret + } + if s == "da" { + return []int{DA} + } + if s == "gossip" { + return []int{DA} + } +} diff --git a/dofraud/do_fraud.go b/dofraud/do_fraud.go index 83e02bfb8..4a7f5ef61 100644 --- a/dofraud/do_fraud.go +++ b/dofraud/do_fraud.go @@ -1,25 +1,22 @@ package dofraud import ( - "encoding/json" - "io" - "os" + "fmt" "github.com/dymensionxyz/dymint/types" ) -type diskPair struct { - Height uint64 - Cmd diskCmd -} - -type disk struct { - Pairs []diskPair -} - -type FraudType int type FraudVariant int +type FraudType int +// Variant +const ( + NoneVariant = iota + DA + Gossip +) + +// Type const ( NoneType = iota HeaderVersionBlock @@ -40,147 +37,31 @@ const ( LastCommit ) -const ( - NoneVariant = iota - Produce - DA - Gossip -) - -type diskCmd struct { - Variant string `json:",omitempty"` - HeaderVersionBlock uint64 `json:",omitempty"` - HeaderVersionApp uint64 `json:",omitempty"` - HeaderChainID string `json:",omitempty"` - HeaderHeight uint64 `json:",omitempty"` - HeaderTime int64 `json:",omitempty"` - HeaderLastHeaderHash string `json:",omitempty"` - HeaderDataHash string `json:",omitempty"` - HeaderConsensusHash string `json:",omitempty"` - HeaderAppHash string `json:",omitempty"` - HeaderLastResultsHash string `json:",omitempty"` - HeaderProposerAddr string `json:",omitempty"` - HeaderLastCommitHash string `json:",omitempty"` - HeaderSequencerHash string `json:",omitempty"` - HeaderNextSequencerHash string `json:",omitempty"` - Data struct{} `json:",omitempty"` // TODO: - LastCommit struct{} `json:",omitempty"` // TODO: +type Cmd struct { + *types.Block + ts []FraudType } -func Load(fn string) (Cmds, error) { - file, err := os.Open(fn) - if err != nil { - return nil, err - } - defer file.Close() - - data, err := io.ReadAll(file) - if err != nil { - return nil, err - } - - var d disk - err = json.Unmarshal(data, &d) - if err != nil { - return nil, err - } - - frauds := []FraudType{} - for _, pair := range d.Pairs { - frauds := []FraudType{} - if pair.Cmd.HeaderVersionBlock != 0 { - frauds = append(frauds, HeaderVersionBlock) - } - if pair.Cmd.HeaderVersionApp != 0 { - frauds = append(frauds, HeaderVersionApp) - } - if pair.Cmd.HeaderChainID != "" { - frauds = append(frauds, HeaderChainID) - } - if pair.Cmd.HeaderHeight != 0 { - frauds = append(frauds, HeaderHeight) - } - if pair.Cmd.HeaderTime != 0 { - frauds = append(frauds, HeaderTime) - } - if pair.Cmd.HeaderLastHeaderHash != "" { - frauds = append(frauds, HeaderLastHeaderHash) - } - if pair.Cmd.HeaderDataHash != "" { - frauds = append(frauds, HeaderDataHash) - } - if pair.Cmd.HeaderConsensusHash != "" { - frauds = append(frauds, HeaderConsensusHash) - } - if pair.Cmd.HeaderAppHash != "" { - frauds = append(frauds, HeaderAppHash) - } - if pair.Cmd.HeaderLastResultsHash != "" { - frauds = append(frauds, HeaderLastResultsHash) - } - if pair.Cmd.HeaderProposerAddr != "" { - frauds = append(frauds, HeaderProposerAddr) - } - if pair.Cmd.HeaderLastCommitHash != "" { - frauds = append(frauds, HeaderLastCommitHash) - } - if pair.Cmd.HeaderSequencerHash != "" { - frauds = append(frauds, HeaderSequencerHash) - } - if pair.Cmd.HeaderNextSequencerHash != "" { - frauds = append(frauds, HeaderNextSequencerHash) - } - // TODO: Data and LastCommit - } - - cmds := make(Cmds) - for _, pair := range d.Pairs { - cmd := Cmd{ - Block: &types.Block{ - Header: types.Header{ - Version: types.Version{ - Block: pair.Cmd.HeaderVersionBlock, - App: pair.Cmd.HeaderVersionApp, - }, - ChainID: pair.Cmd.HeaderChainID, - Height: pair.Cmd.HeaderHeight, - Time: pair.Cmd.HeaderTime, - LastHeaderHash: parseHash(pair.Cmd.HeaderLastHeaderHash), - DataHash: parseHash(pair.Cmd.HeaderDataHash), - ConsensusHash: parseHash(pair.Cmd.HeaderConsensusHash), - AppHash: parseHash(pair.Cmd.HeaderAppHash), - LastResultsHash: parseHash(pair.Cmd.HeaderLastResultsHash), - ProposerAddress: []byte(pair.Cmd.HeaderProposerAddr), - LastCommitHash: parseHash(pair.Cmd.HeaderLastCommitHash), - SequencerHash: parseHash(pair.Cmd.HeaderSequencerHash), - NextSequencersHash: parseHash(pair.Cmd.HeaderNextSequencerHash), - }, - // Data and LastCommit fields need to be populated as per your requirements - }, - frauds: frauds, - } - cmds[pair.Height] = cmd - } - - return cmds, nil +type Frauds struct { + frauds map[string]Cmd } -func parseHash(hashStr string) [32]byte { - var hash [32]byte - copy(hash[:], hashStr) - return hash +type key struct { + height uint64 + variant FraudVariant } -// height -> cmd -type Cmds map[uint64]Cmd - -type Cmd struct { - *types.Block - frauds []FraudType +func (k key) String() string { + return fmt.Sprintf("%d:%d", k.height, k.variant) } -func ApplyFraud(cmd Cmd, b *types.Block) *types.Block { - for _, fraud := range cmd.frauds { +func (f *Frauds) Apply(height uint64, fraudVariant FraudVariant, b *types.Block) { + cmd, ok := f.frauds[key{height, fraudVariant}.String()] + if !ok { + return + } + + for _, fraud := range cmd.ts { switch fraud { case HeaderVersionBlock: b.Header.Version.Block = cmd.Header.Version.Block @@ -217,5 +98,4 @@ func ApplyFraud(cmd Cmd, b *types.Block) *types.Block { default: } } - return b } diff --git a/dofraud/go.doc.go b/dofraud/go.doc.go new file mode 100644 index 000000000..dd6b2ab19 --- /dev/null +++ b/dofraud/go.doc.go @@ -0,0 +1,2 @@ +// Packet dofraud is for testing fraud scenarios. +package dofraud diff --git a/dofraud/z_test.go b/dofraud/z_test.go index 0f40e692d..74df48520 100644 --- a/dofraud/z_test.go +++ b/dofraud/z_test.go @@ -7,12 +7,13 @@ import ( ) func TestDoFraud(t *testing.T) { - const fn = "/Users/danwt/Documents/dym/aaa-dym-notes/all_tasks/tasks/202412_testing_playground/frauds.json" + t.Skip("Not a real test, just handy for quickly generating an example json.") + const fn = "/Users/danwt/Documents/dym/aaa-dym-notes/all_tasks/tasks/202412_testing_playground/ts.json" // Generate a few example diskPairs examples := disk{ - Pairs: []diskPair{ - {Height: 33, Cmd: diskCmd{HeaderProposerAddr: "1092381209381923809182391823098129038"}}, + Instances: []diskInstance{ + {Height: 10, Block: diskBlock{HeaderProposerAddr: "1092381209381923809182391823098129038"}}, }, } From b5831e37cd11838e566d1e4f4fcb4a6b8829a294 Mon Sep 17 00:00:00 2001 From: danwt <30197399+danwt@users.noreply.github.com> Date: Mon, 16 Dec 2024 11:24:08 +0000 Subject: [PATCH 07/25] cp --- dofraud/disk.go | 131 ++++++++++++++-------------------- dofraud/do_fraud.go | 1 + dofraud/example.json | 40 ----------- dofraud/testdata/example.json | 22 ++++++ dofraud/z_test.go | 29 +++++--- 5 files changed, 96 insertions(+), 127 deletions(-) delete mode 100644 dofraud/example.json create mode 100644 dofraud/testdata/example.json diff --git a/dofraud/disk.go b/dofraud/disk.go index bbfaef911..4b6657ae8 100644 --- a/dofraud/disk.go +++ b/dofraud/disk.go @@ -2,11 +2,12 @@ package dofraud import ( "encoding/json" - "github.com/dymensionxyz/dymint/types" "io" "os" "slices" "strings" + + "github.com/dymensionxyz/dymint/types" ) type disk struct { @@ -14,9 +15,9 @@ type disk struct { } type diskInstance struct { - Height uint64 - Variant string - Block diskBlock + Height uint64 + Variants string + Block diskBlock } type diskBlock struct { @@ -57,86 +58,62 @@ func Load(fn string) (*Frauds, error) { } ret := Frauds{} - for _, pair := range d.Instances { + ret.frauds = make(map[string]Cmd) + for _, ins := range d.Instances { cmd := Cmd{ - Block: &types.Block{}, + Block: &types.Block{ + Header: types.Header{Version: types.Version{}}, + }, } - key := key{pair.Height, parseVariant(pair.Variant)} - if pair.Block.HeaderVersionBlock != 0 { - frauds = append(frauds, HeaderVersionBlock) + if ins.Block.HeaderVersionBlock != 0 { + cmd.Block.Header.Version.Block = ins.Block.HeaderVersionBlock } - if pair.Block.HeaderVersionApp != 0 { - frauds = append(frauds, HeaderVersionApp) + if ins.Block.HeaderVersionApp != 0 { + cmd.Block.Header.Version.App = ins.Block.HeaderVersionApp } - if pair.Block.HeaderChainID != "" { - frauds = append(frauds, HeaderChainID) + if ins.Block.HeaderChainID != "" { + cmd.Block.Header.ChainID = ins.Block.HeaderChainID } - if pair.Block.HeaderHeight != 0 { - frauds = append(frauds, HeaderHeight) + if ins.Block.HeaderHeight != 0 { + cmd.Block.Header.Height = ins.Block.HeaderHeight } - if pair.Block.HeaderTime != 0 { - frauds = append(frauds, HeaderTime) + if ins.Block.HeaderTime != 0 { + cmd.Block.Header.Time = ins.Block.HeaderTime } - if pair.Block.HeaderLastHeaderHash != "" { - frauds = append(frauds, HeaderLastHeaderHash) + if ins.Block.HeaderLastHeaderHash != "" { + cmd.Block.Header.LastHeaderHash = parseHash(ins.Block.HeaderLastHeaderHash) } - if pair.Block.HeaderDataHash != "" { - frauds = append(frauds, HeaderDataHash) + if ins.Block.HeaderDataHash != "" { + cmd.Block.Header.DataHash = parseHash(ins.Block.HeaderDataHash) } - if pair.Block.HeaderConsensusHash != "" { - frauds = append(frauds, HeaderConsensusHash) + if ins.Block.HeaderConsensusHash != "" { + cmd.Block.Header.ConsensusHash = parseHash(ins.Block.HeaderConsensusHash) } - if pair.Block.HeaderAppHash != "" { - frauds = append(frauds, HeaderAppHash) + if ins.Block.HeaderAppHash != "" { + cmd.Block.Header.AppHash = parseHash(ins.Block.HeaderAppHash) } - if pair.Block.HeaderLastResultsHash != "" { - frauds = append(frauds, HeaderLastResultsHash) + if ins.Block.HeaderLastResultsHash != "" { + cmd.Block.Header.LastResultsHash = parseHash(ins.Block.HeaderLastResultsHash) } - if pair.Block.HeaderProposerAddr != "" { - frauds = append(frauds, HeaderProposerAddr) + if ins.Block.HeaderProposerAddr != "" { + cmd.Block.Header.ProposerAddress = []byte(ins.Block.HeaderProposerAddr) } - if pair.Block.HeaderLastCommitHash != "" { - frauds = append(frauds, HeaderLastCommitHash) + if ins.Block.HeaderLastCommitHash != "" { + cmd.Block.Header.LastCommitHash = parseHash(ins.Block.HeaderLastCommitHash) } - if pair.Block.HeaderSequencerHash != "" { - frauds = append(frauds, HeaderSequencerHash) + if ins.Block.HeaderSequencerHash != "" { + cmd.Block.Header.SequencerHash = parseHash(ins.Block.HeaderSequencerHash) } - if pair.Block.HeaderNextSequencerHash != "" { - frauds = append(frauds, HeaderNextSequencerHash) + if ins.Block.HeaderNextSequencerHash != "" { + cmd.Block.Header.NextSequencersHash = parseHash(ins.Block.HeaderNextSequencerHash) } // TODO: Data and LastCommit - } - - cmds := make(Cmds) - for _, pair := range d.Instances { - cmd := Cmd{ - Block: &types.Block{ - Header: types.Header{ - Version: types.Version{ - Block: pair.Block.HeaderVersionBlock, - App: pair.Block.HeaderVersionApp, - }, - ChainID: pair.Block.HeaderChainID, - Height: pair.Block.HeaderHeight, - Time: pair.Block.HeaderTime, - LastHeaderHash: parseHash(pair.Block.HeaderLastHeaderHash), - DataHash: parseHash(pair.Block.HeaderDataHash), - ConsensusHash: parseHash(pair.Block.HeaderConsensusHash), - AppHash: parseHash(pair.Block.HeaderAppHash), - LastResultsHash: parseHash(pair.Block.HeaderLastResultsHash), - ProposerAddress: []byte(pair.Block.HeaderProposerAddr), - LastCommitHash: parseHash(pair.Block.HeaderLastCommitHash), - SequencerHash: parseHash(pair.Block.HeaderSequencerHash), - NextSequencersHash: parseHash(pair.Block.HeaderNextSequencerHash), - }, - // Data and LastCommit fields need to be populated as per your requirements - }, - ts: frauds, + vs := parseVariants(ins.Variants) + for _, v := range vs { + ret.frauds[key{ins.Height, v}.String()] = cmd } - cmds[pair.Height] = cmd } - - return cmds, nil + return &ret, nil } func parseHash(hashStr string) [32]byte { @@ -145,21 +122,17 @@ func parseHash(hashStr string) [32]byte { return hash } -func parseVariants(s string) []int { - ret := []int{} - s = strings.ToLower(s) - if strings.Contains(s, ",") { - l := strings.Split(s, ",") - for _, v := range l { - sub := parseVariants(v) - ret = append(ret, sub...) - } - return ret +func parseVariants(s string) []FraudVariant { + ret := []FraudVariant{} + l := strings.Split(s, ",") + if slices.Contains(l, "da") { + ret = append(ret, DA) } - if s == "da" { - return []int{DA} + if slices.Contains(l, "gossip") { + ret = append(ret, Gossip) } - if s == "gossip" { - return []int{DA} + if slices.Contains(l, "produce") { + ret = append(ret, Produce) } + return ret } diff --git a/dofraud/do_fraud.go b/dofraud/do_fraud.go index 4a7f5ef61..9253d5429 100644 --- a/dofraud/do_fraud.go +++ b/dofraud/do_fraud.go @@ -14,6 +14,7 @@ const ( NoneVariant = iota DA Gossip + Produce ) // Type diff --git a/dofraud/example.json b/dofraud/example.json deleted file mode 100644 index 3ab988367..000000000 --- a/dofraud/example.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "Pairs": [ - { - "Height": 10, - "Cmd": { - "HeaderVersionBlock": 1, - "HeaderChainID": "chain-1", - "Data": {}, - "LastCommit": {} - } - }, - { - "Height": 20, - "Cmd": { - "HeaderVersionApp": 2, - "HeaderHeight": 100, - "Data": {}, - "LastCommit": {} - } - }, - { - "Height": 30, - "Cmd": { - "HeaderTime": 1234567890, - "HeaderDataHash": "datahash", - "Data": {}, - "LastCommit": {} - } - }, - { - "Height": 40, - "Cmd": { - "HeaderDataHash": "datahash", - "HeaderNextSequencerHash": "foobar", - "Data": {}, - "LastCommit": {} - } - } - ] -} \ No newline at end of file diff --git a/dofraud/testdata/example.json b/dofraud/testdata/example.json new file mode 100644 index 000000000..4c0f7d374 --- /dev/null +++ b/dofraud/testdata/example.json @@ -0,0 +1,22 @@ +{ + "Instances": [ + { + "Height": 10, + "Variants": "da,gossip", + "Block": { + "HeaderProposerAddr": "1092381209381923809182391823098129038", + "Data": {}, + "LastCommit": {} + } + }, + { + "Height": 22, + "Variants": "da", + "Block": { + "HeaderNextSequencerHash": "1092381209381923809182391823098129038", + "Data": {}, + "LastCommit": {} + } + } + ] +} \ No newline at end of file diff --git a/dofraud/z_test.go b/dofraud/z_test.go index 74df48520..0450f4da7 100644 --- a/dofraud/z_test.go +++ b/dofraud/z_test.go @@ -1,31 +1,44 @@ package dofraud import ( + _ "embed" "encoding/json" "os" "testing" + + "github.com/stretchr/testify/require" ) -func TestDoFraud(t *testing.T) { - t.Skip("Not a real test, just handy for quickly generating an example json.") - const fn = "/Users/danwt/Documents/dym/aaa-dym-notes/all_tasks/tasks/202412_testing_playground/ts.json" +// TODO: path +const fp = "/Users/danwt/Documents/dym/d-dymint/dofraud/testdata/example.json" + +//go:embed testdata/example.json +var testData []byte + +func TestGenerateJson(t *testing.T) { + //t.Skip("Not a real test, just handy for quickly generating an example json.") - // Generate a few example diskPairs examples := disk{ Instances: []diskInstance{ - {Height: 10, Block: diskBlock{HeaderProposerAddr: "1092381209381923809182391823098129038"}}, + {Height: 10, Variants: "da,gossip", Block: diskBlock{HeaderProposerAddr: "1092381209381923809182391823098129038"}}, + {Height: 22, Variants: "da", Block: diskBlock{HeaderNextSequencerHash: "1092381209381923809182391823098129038"}}, }, } - // Marshal the examples to JSON data, err := json.MarshalIndent(examples, "", " ") if err != nil { t.Fatalf("Failed to marshal examples: %v", err) } - // Write the JSON data to the specified file - err = os.WriteFile(fn, data, 0644) + err = os.WriteFile(fp, data, 0644) if err != nil { t.Fatalf("Failed to write examples to file: %v", err) } } + +func TestParseJson(t *testing.T) { + fraud, err := Load(fp) + require.NoError(t, err) + _, ok := fraud.frauds["10:1"] + require.True(t, ok) +} From 60ac0e3caa926dac56abcf998436279a6295be8e Mon Sep 17 00:00:00 2001 From: danwt <30197399+danwt@users.noreply.github.com> Date: Mon, 16 Dec 2024 11:26:00 +0000 Subject: [PATCH 08/25] cp --- block/manager.go | 6 +++--- block/produce.go | 5 +---- dofraud/{do_fraud.go => apply.go} | 0 dofraud/disk.go | 10 +++++----- 4 files changed, 9 insertions(+), 12 deletions(-) rename dofraud/{do_fraud.go => apply.go} (100%) diff --git a/block/manager.go b/block/manager.go index 2a699ce73..0b808cca6 100644 --- a/block/manager.go +++ b/block/manager.go @@ -129,8 +129,8 @@ 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 - // frauds for testing - fraudCmds dofraud.Cmds + // for testing + fraudSim dofraud.Frauds } // NewManager creates new block Manager. @@ -221,7 +221,7 @@ func NewManager( } logger.Info("No frauds file found", "path", conf.FraudCmdsPath) } - m.fraudCmds = frauds + m.fraudSim = frauds return m, nil } diff --git a/block/produce.go b/block/produce.go index 35e934b1e..ff315c279 100644 --- a/block/produce.go +++ b/block/produce.go @@ -239,10 +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) - f, ok := m.fraudCmds[newHeight] - if ok { - block = dofraud.ApplyFraud(f, block) - } + m.fraudSim.Apply(newHeight, dofraud.Produce, block) // this cannot happen if there are any sequencer set updates // AllowEmpty should be always true in this case diff --git a/dofraud/do_fraud.go b/dofraud/apply.go similarity index 100% rename from dofraud/do_fraud.go rename to dofraud/apply.go diff --git a/dofraud/disk.go b/dofraud/disk.go index 4b6657ae8..0c2289f97 100644 --- a/dofraud/disk.go +++ b/dofraud/disk.go @@ -39,22 +39,22 @@ type diskBlock struct { LastCommit struct{} `json:",omitempty"` // TODO: } -func Load(fn string) (*Frauds, error) { +func Load(fn string) (Frauds, error) { file, err := os.Open(fn) if err != nil { - return nil, err + return Frauds{}, err } defer file.Close() data, err := io.ReadAll(file) if err != nil { - return nil, err + return Frauds{}, err } var d disk err = json.Unmarshal(data, &d) if err != nil { - return nil, err + return Frauds{}, err } ret := Frauds{} @@ -113,7 +113,7 @@ func Load(fn string) (*Frauds, error) { ret.frauds[key{ins.Height, v}.String()] = cmd } } - return &ret, nil + return ret, nil } func parseHash(hashStr string) [32]byte { From 32f89eb9d3693326c6b1fb2278e3ef3464ca89c0 Mon Sep 17 00:00:00 2001 From: danwt <30197399+danwt@users.noreply.github.com> Date: Mon, 16 Dec 2024 11:28:10 +0000 Subject: [PATCH 09/25] add logging --- block/produce.go | 2 +- dofraud/apply.go | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/block/produce.go b/block/produce.go index ff315c279..b53915b83 100644 --- a/block/produce.go +++ b/block/produce.go @@ -239,7 +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) - m.fraudSim.Apply(newHeight, dofraud.Produce, block) + m.fraudSim.Apply(m.logger, newHeight, dofraud.Produce, block) // this cannot happen if there are any sequencer set updates // AllowEmpty should be always true in this case diff --git a/dofraud/apply.go b/dofraud/apply.go index 9253d5429..776a1f24c 100644 --- a/dofraud/apply.go +++ b/dofraud/apply.go @@ -56,7 +56,7 @@ func (k key) String() string { return fmt.Sprintf("%d:%d", k.height, k.variant) } -func (f *Frauds) Apply(height uint64, fraudVariant FraudVariant, b *types.Block) { +func (f *Frauds) Apply(log types.Logger, height uint64, fraudVariant FraudVariant, b *types.Block) { cmd, ok := f.frauds[key{height, fraudVariant}.String()] if !ok { return @@ -99,4 +99,6 @@ func (f *Frauds) Apply(height uint64, fraudVariant FraudVariant, b *types.Block) default: } } + + log.Info("Applied fraud.", "height", height, "variant", fraudVariant, "types", cmd.ts) } From e4db3c1dc478f969bc42e9c50946ac0cb20ad9f9 Mon Sep 17 00:00:00 2001 From: danwt <30197399+danwt@users.noreply.github.com> Date: Mon, 16 Dec 2024 12:39:32 +0000 Subject: [PATCH 10/25] pre add da/gossip functionlity From 0b0ab22f1f48fd5d8a74b06000d5ee3a17bc80c0 Mon Sep 17 00:00:00 2001 From: danwt <30197399+danwt@users.noreply.github.com> Date: Mon, 16 Dec 2024 12:52:40 +0000 Subject: [PATCH 11/25] lets test again --- block/p2p.go | 2 ++ block/produce.go | 12 +++++++++++- block/submit.go | 10 ++++++++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/block/p2p.go b/block/p2p.go index 6dcae3c5e..c756a43ce 100644 --- a/block/p2p.go +++ b/block/p2p.go @@ -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" @@ -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) + commit = *m.fraudBlockAndCommit(dofraud.Gossip, block.Header.Height, &block) gossipedBlock := p2p.BlockData{Block: block, Commit: commit} gossipedBlockBytes, err := gossipedBlock.MarshalBinary() if err != nil { diff --git a/block/produce.go b/block/produce.go index b53915b83..00b0f31df 100644 --- a/block/produce.go +++ b/block/produce.go @@ -239,7 +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) - m.fraudSim.Apply(m.logger, newHeight, dofraud.Produce, block) + _, _ = m.fraudBlockAndCommit(dofraud.Produce, newHeight, block) // this cannot happen if there are any sequencer set updates // AllowEmpty should be always true in this case @@ -351,3 +351,13 @@ func getHeaderHashAndCommit(store store.Store, height uint64) ([32]byte, *types. } return lastBlock.Header.Hash(), lastCommit, nil } + +// modifies the block, returns new commit +func (m *Manager) fraudBlockAndCommit(variant dofraud.FraudVariant, h uint64, b *types.Block) *types.Commit { + 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) + } + return comm +} diff --git a/block/submit.go b/block/submit.go index 3ee4e2dc4..c68f44374 100644 --- a/block/submit.go +++ b/block/submit.go @@ -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" @@ -240,6 +241,7 @@ func (m *Manager) CreateBatch(maxBatchSize uint64, startHeight uint64, endHeight } func (m *Manager) SubmitBatch(batch *types.Batch) error { + m.applyFraudsToBatch(batch) resultSubmitToDA := m.DAClient.SubmitBatch(batch) if resultSubmitToDA.Code != da.StatusSuccess { return fmt.Errorf("da client submit batch: %s: %w", resultSubmitToDA.Message, resultSubmitToDA.Error) @@ -326,3 +328,11 @@ func UpdateBatchSubmissionGauges(skewBytes uint64, skewBlocks uint64, skewTime t types.RollappPendingSubmissionsSkewBlocks.Set(float64(skewBlocks)) types.RollappPendingSubmissionsSkewTimeMinutes.Set(float64(skewTime.Minutes())) } + +// (if frauds are specified) +func (m *Manager) applyFraudsToBatch(batch *types.Batch) { + for i, block := range batch.Blocks { + comm := m.fraudBlockAndCommit(dofraud.DA, block.Header.Height, block) + batch.Commits[i] = comm + } +} From bc14d4d96bf9d8f2cfc572247eba67f2fa59d373 Mon Sep 17 00:00:00 2001 From: danwt <30197399+danwt@users.noreply.github.com> Date: Mon, 16 Dec 2024 13:01:56 +0000 Subject: [PATCH 12/25] tests pass --- block/produce.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/block/produce.go b/block/produce.go index 00b0f31df..f98e48418 100644 --- a/block/produce.go +++ b/block/produce.go @@ -239,7 +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) - _, _ = m.fraudBlockAndCommit(dofraud.Produce, newHeight, block) + m.fraudBlockAndCommit(dofraud.Produce, newHeight, block) // this cannot happen if there are any sequencer set updates // AllowEmpty should be always true in this case From adf331562b6f4c81113c969e3c2d127228b6202f Mon Sep 17 00:00:00 2001 From: danwt <30197399+danwt@users.noreply.github.com> Date: Mon, 16 Dec 2024 13:02:58 +0000 Subject: [PATCH 13/25] diagnostici --- block/manager.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/block/manager.go b/block/manager.go index 0b808cca6..1ec24d170 100644 --- a/block/manager.go +++ b/block/manager.go @@ -219,7 +219,9 @@ func NewManager( if !errors.Is(err, os.ErrNotExist) { return nil, fmt.Errorf("load frauds: %w", err) } - logger.Info("No frauds file found", "path", conf.FraudCmdsPath) + logger.Info("Did not load fraud tests - frauds file not found", "path", conf.FraudCmdsPath) + } else { + logger.Info("Loaded frauds.") } m.fraudSim = frauds From da26ce5e7275daf1bfc7cedefccb81d036528c46 Mon Sep 17 00:00:00 2001 From: danwt <30197399+danwt@users.noreply.github.com> Date: Mon, 16 Dec 2024 17:08:24 +0000 Subject: [PATCH 14/25] cp --- dofraud/disk.go | 14 ++++++++++++++ dofraud/testdata/example.json | 15 ++------------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/dofraud/disk.go b/dofraud/disk.go index 0c2289f97..183050a99 100644 --- a/dofraud/disk.go +++ b/dofraud/disk.go @@ -67,45 +67,59 @@ func Load(fn string) (Frauds, error) { } if ins.Block.HeaderVersionBlock != 0 { cmd.Block.Header.Version.Block = ins.Block.HeaderVersionBlock + cmd.ts = append(cmd.ts, HeaderVersionBlock) } if ins.Block.HeaderVersionApp != 0 { cmd.Block.Header.Version.App = ins.Block.HeaderVersionApp + cmd.ts = append(cmd.ts, HeaderVersionApp) } if ins.Block.HeaderChainID != "" { cmd.Block.Header.ChainID = ins.Block.HeaderChainID + cmd.ts = append(cmd.ts, HeaderChainID) } if ins.Block.HeaderHeight != 0 { cmd.Block.Header.Height = ins.Block.HeaderHeight + cmd.ts = append(cmd.ts, HeaderHeight) } if ins.Block.HeaderTime != 0 { cmd.Block.Header.Time = ins.Block.HeaderTime + cmd.ts = append(cmd.ts, HeaderTime) } if ins.Block.HeaderLastHeaderHash != "" { cmd.Block.Header.LastHeaderHash = parseHash(ins.Block.HeaderLastHeaderHash) + cmd.ts = append(cmd.ts, HeaderLastHeaderHash) } if ins.Block.HeaderDataHash != "" { cmd.Block.Header.DataHash = parseHash(ins.Block.HeaderDataHash) + cmd.ts = append(cmd.ts, HeaderDataHash) } if ins.Block.HeaderConsensusHash != "" { cmd.Block.Header.ConsensusHash = parseHash(ins.Block.HeaderConsensusHash) + cmd.ts = append(cmd.ts, HeaderConsensusHash) } if ins.Block.HeaderAppHash != "" { cmd.Block.Header.AppHash = parseHash(ins.Block.HeaderAppHash) + cmd.ts = append(cmd.ts, HeaderAppHash) } if ins.Block.HeaderLastResultsHash != "" { cmd.Block.Header.LastResultsHash = parseHash(ins.Block.HeaderLastResultsHash) + cmd.ts = append(cmd.ts, HeaderLastResultsHash) } if ins.Block.HeaderProposerAddr != "" { cmd.Block.Header.ProposerAddress = []byte(ins.Block.HeaderProposerAddr) + cmd.ts = append(cmd.ts, HeaderProposerAddr) } if ins.Block.HeaderLastCommitHash != "" { cmd.Block.Header.LastCommitHash = parseHash(ins.Block.HeaderLastCommitHash) + cmd.ts = append(cmd.ts, HeaderLastCommitHash) } if ins.Block.HeaderSequencerHash != "" { cmd.Block.Header.SequencerHash = parseHash(ins.Block.HeaderSequencerHash) + cmd.ts = append(cmd.ts, HeaderSequencerHash) } if ins.Block.HeaderNextSequencerHash != "" { cmd.Block.Header.NextSequencersHash = parseHash(ins.Block.HeaderNextSequencerHash) + cmd.ts = append(cmd.ts, HeaderNextSequencerHash) } // TODO: Data and LastCommit vs := parseVariants(ins.Variants) diff --git a/dofraud/testdata/example.json b/dofraud/testdata/example.json index 4c0f7d374..321a06645 100644 --- a/dofraud/testdata/example.json +++ b/dofraud/testdata/example.json @@ -1,21 +1,10 @@ { "Instances": [ { - "Height": 10, + "Height": 40, "Variants": "da,gossip", "Block": { - "HeaderProposerAddr": "1092381209381923809182391823098129038", - "Data": {}, - "LastCommit": {} - } - }, - { - "Height": 22, - "Variants": "da", - "Block": { - "HeaderNextSequencerHash": "1092381209381923809182391823098129038", - "Data": {}, - "LastCommit": {} + "HeaderProposerAddr": "1092381209381923809182391823098129038" } } ] From 4bd76dc173931e5e9700e591f6f97a098632d6c8 Mon Sep 17 00:00:00 2001 From: danwt <30197399+danwt@users.noreply.github.com> Date: Mon, 16 Dec 2024 17:34:38 +0000 Subject: [PATCH 15/25] double check --- dofraud/apply.go | 4 ++-- dofraud/testdata/example.json | 15 +++++++++++++-- dofraud/z_test.go | 3 ++- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/dofraud/apply.go b/dofraud/apply.go index 776a1f24c..f4aac7e53 100644 --- a/dofraud/apply.go +++ b/dofraud/apply.go @@ -6,8 +6,8 @@ import ( "github.com/dymensionxyz/dymint/types" ) -type FraudVariant int -type FraudType int +type FraudVariant = int +type FraudType = int // Variant const ( diff --git a/dofraud/testdata/example.json b/dofraud/testdata/example.json index 321a06645..4c0f7d374 100644 --- a/dofraud/testdata/example.json +++ b/dofraud/testdata/example.json @@ -1,10 +1,21 @@ { "Instances": [ { - "Height": 40, + "Height": 10, "Variants": "da,gossip", "Block": { - "HeaderProposerAddr": "1092381209381923809182391823098129038" + "HeaderProposerAddr": "1092381209381923809182391823098129038", + "Data": {}, + "LastCommit": {} + } + }, + { + "Height": 22, + "Variants": "da", + "Block": { + "HeaderNextSequencerHash": "1092381209381923809182391823098129038", + "Data": {}, + "LastCommit": {} } } ] diff --git a/dofraud/z_test.go b/dofraud/z_test.go index 0450f4da7..7e221d07f 100644 --- a/dofraud/z_test.go +++ b/dofraud/z_test.go @@ -39,6 +39,7 @@ func TestGenerateJson(t *testing.T) { func TestParseJson(t *testing.T) { fraud, err := Load(fp) require.NoError(t, err) - _, ok := fraud.frauds["10:1"] + cmd, ok := fraud.frauds[key{10, DA}.String()] require.True(t, ok) + require.Contains(t, cmd.ts, HeaderProposerAddr) } From 3f0b4e51d5689ab5fe992a8800cfb899bd05a88e Mon Sep 17 00:00:00 2001 From: danwt <30197399+danwt@users.noreply.github.com> Date: Mon, 16 Dec 2024 18:47:22 +0000 Subject: [PATCH 16/25] lfg --- dofraud/doc.go | 4 ++++ dofraud/go.doc.go | 2 -- dofraud/testdata/example.json | 25 +++++++++++++++++++++++-- dofraud/z_test.go | 24 +++++++++++++++--------- 4 files changed, 42 insertions(+), 13 deletions(-) create mode 100644 dofraud/doc.go delete mode 100644 dofraud/go.doc.go diff --git a/dofraud/doc.go b/dofraud/doc.go new file mode 100644 index 000000000..fee5306ee --- /dev/null +++ b/dofraud/doc.go @@ -0,0 +1,4 @@ +// Package dofraud is for injecting frauds, for testing. +// User should pass a path to a json file containing frauds to dymint.toml +// Can choose between 3 variants of frauds: DA, Gossip, Produce +package dofraud diff --git a/dofraud/go.doc.go b/dofraud/go.doc.go deleted file mode 100644 index dd6b2ab19..000000000 --- a/dofraud/go.doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// Packet dofraud is for testing fraud scenarios. -package dofraud diff --git a/dofraud/testdata/example.json b/dofraud/testdata/example.json index 4c0f7d374..7711dc0c4 100644 --- a/dofraud/testdata/example.json +++ b/dofraud/testdata/example.json @@ -10,13 +10,34 @@ } }, { - "Height": 22, - "Variants": "da", + "Height": 44, + "Variants": "da,gossip,produce", "Block": { + "HeaderDataHash": "1290830918230981239812903819823909123", + "HeaderLastResultsHash": "129038120938120938120938120938120938", "HeaderNextSequencerHash": "1092381209381923809182391823098129038", "Data": {}, "LastCommit": {} } + }, + { + "Height": 105, + "Variants": "gossip", + "Block": { + "HeaderTime": 1734374656000000000, + "HeaderNextSequencerHash": "1092381209381923809182391823098129038", + "Data": {}, + "LastCommit": {} + } + }, + { + "Height": 120, + "Variants": "gossip", + "Block": { + "HeaderVersionApp": 3, + "Data": {}, + "LastCommit": {} + } } ] } \ No newline at end of file diff --git a/dofraud/z_test.go b/dofraud/z_test.go index 7e221d07f..ccf16e325 100644 --- a/dofraud/z_test.go +++ b/dofraud/z_test.go @@ -9,31 +9,37 @@ import ( "github.com/stretchr/testify/require" ) -// TODO: path const fp = "/Users/danwt/Documents/dym/d-dymint/dofraud/testdata/example.json" //go:embed testdata/example.json var testData []byte func TestGenerateJson(t *testing.T) { - //t.Skip("Not a real test, just handy for quickly generating an example json.") + t.Skip("Not a real test, just handy for quickly generating an example json.") examples := disk{ Instances: []diskInstance{ {Height: 10, Variants: "da,gossip", Block: diskBlock{HeaderProposerAddr: "1092381209381923809182391823098129038"}}, - {Height: 22, Variants: "da", Block: diskBlock{HeaderNextSequencerHash: "1092381209381923809182391823098129038"}}, + {Height: 44, Variants: "da,gossip,produce", Block: diskBlock{ + HeaderNextSequencerHash: "1092381209381923809182391823098129038", + HeaderDataHash: "1290830918230981239812903819823909123", + HeaderLastResultsHash: "129038120938120938120938120938120938", + }}, + {Height: 105, Variants: "gossip", Block: diskBlock{ + HeaderNextSequencerHash: "1092381209381923809182391823098129038", + HeaderTime: 1734374656000000000, + }}, + {Height: 120, Variants: "gossip", Block: diskBlock{ + HeaderVersionApp: 3, + }}, }, } data, err := json.MarshalIndent(examples, "", " ") - if err != nil { - t.Fatalf("Failed to marshal examples: %v", err) - } + require.NoError(t, err) err = os.WriteFile(fp, data, 0644) - if err != nil { - t.Fatalf("Failed to write examples to file: %v", err) - } + require.NoError(t, err) } func TestParseJson(t *testing.T) { From 76dfeae248af00b5ba15e956fb690383db912b4c Mon Sep 17 00:00:00 2001 From: danwt <30197399+danwt@users.noreply.github.com> Date: Mon, 16 Dec 2024 18:50:16 +0000 Subject: [PATCH 17/25] lfg --- dofraud/apply.go | 1 + dofraud/disk.go | 40 ++++++++++++++++++++--------------- dofraud/testdata/example.json | 16 ++++---------- 3 files changed, 28 insertions(+), 29 deletions(-) diff --git a/dofraud/apply.go b/dofraud/apply.go index f4aac7e53..1df83fd83 100644 --- a/dofraud/apply.go +++ b/dofraud/apply.go @@ -43,6 +43,7 @@ type Cmd struct { ts []FraudType } +// The possibilites are simple, just change type Frauds struct { frauds map[string]Cmd } diff --git a/dofraud/disk.go b/dofraud/disk.go index 183050a99..43d8682b4 100644 --- a/dofraud/disk.go +++ b/dofraud/disk.go @@ -8,6 +8,7 @@ import ( "strings" "github.com/dymensionxyz/dymint/types" + "github.com/dymensionxyz/gerr-cosmos/gerrc" ) type disk struct { @@ -21,22 +22,22 @@ type diskInstance struct { } type diskBlock struct { - HeaderVersionBlock uint64 `json:",omitempty"` - HeaderVersionApp uint64 `json:",omitempty"` - HeaderChainID string `json:",omitempty"` - HeaderHeight uint64 `json:",omitempty"` - HeaderTime int64 `json:",omitempty"` - HeaderLastHeaderHash string `json:",omitempty"` - HeaderDataHash string `json:",omitempty"` - HeaderConsensusHash string `json:",omitempty"` - HeaderAppHash string `json:",omitempty"` - HeaderLastResultsHash string `json:",omitempty"` - HeaderProposerAddr string `json:",omitempty"` - HeaderLastCommitHash string `json:",omitempty"` - HeaderSequencerHash string `json:",omitempty"` - HeaderNextSequencerHash string `json:",omitempty"` - Data struct{} `json:",omitempty"` // TODO: - LastCommit struct{} `json:",omitempty"` // TODO: + HeaderVersionBlock uint64 `json:",omitempty"` + HeaderVersionApp uint64 `json:",omitempty"` + HeaderChainID string `json:",omitempty"` + HeaderHeight uint64 `json:",omitempty"` + HeaderTime int64 `json:",omitempty"` + HeaderLastHeaderHash string `json:",omitempty"` + HeaderDataHash string `json:",omitempty"` + HeaderConsensusHash string `json:",omitempty"` + HeaderAppHash string `json:",omitempty"` + HeaderLastResultsHash string `json:",omitempty"` + HeaderProposerAddr string `json:",omitempty"` + HeaderLastCommitHash string `json:",omitempty"` + HeaderSequencerHash string `json:",omitempty"` + HeaderNextSequencerHash string `json:",omitempty"` + Data *struct{} `json:",omitempty"` // TODO: + LastCommit *struct{} `json:",omitempty"` // TODO: } func Load(fn string) (Frauds, error) { @@ -121,7 +122,12 @@ func Load(fn string) (Frauds, error) { cmd.Block.Header.NextSequencersHash = parseHash(ins.Block.HeaderNextSequencerHash) cmd.ts = append(cmd.ts, HeaderNextSequencerHash) } - // TODO: Data and LastCommit + if ins.Block.Data != nil { + return Frauds{}, gerrc.ErrUnimplemented.Wrap("block data") + } + if ins.Block.LastCommit != nil { + return Frauds{}, gerrc.ErrUnimplemented.Wrap("last commit") + } vs := parseVariants(ins.Variants) for _, v := range vs { ret.frauds[key{ins.Height, v}.String()] = cmd diff --git a/dofraud/testdata/example.json b/dofraud/testdata/example.json index 7711dc0c4..b7800bb6c 100644 --- a/dofraud/testdata/example.json +++ b/dofraud/testdata/example.json @@ -4,9 +4,7 @@ "Height": 10, "Variants": "da,gossip", "Block": { - "HeaderProposerAddr": "1092381209381923809182391823098129038", - "Data": {}, - "LastCommit": {} + "HeaderProposerAddr": "1092381209381923809182391823098129038" } }, { @@ -15,9 +13,7 @@ "Block": { "HeaderDataHash": "1290830918230981239812903819823909123", "HeaderLastResultsHash": "129038120938120938120938120938120938", - "HeaderNextSequencerHash": "1092381209381923809182391823098129038", - "Data": {}, - "LastCommit": {} + "HeaderNextSequencerHash": "1092381209381923809182391823098129038" } }, { @@ -25,18 +21,14 @@ "Variants": "gossip", "Block": { "HeaderTime": 1734374656000000000, - "HeaderNextSequencerHash": "1092381209381923809182391823098129038", - "Data": {}, - "LastCommit": {} + "HeaderNextSequencerHash": "1092381209381923809182391823098129038" } }, { "Height": 120, "Variants": "gossip", "Block": { - "HeaderVersionApp": 3, - "Data": {}, - "LastCommit": {} + "HeaderVersionApp": 3 } } ] From cd066887c0a3d956440c3c5f669f758d13f820ab Mon Sep 17 00:00:00 2001 From: danwt <30197399+danwt@users.noreply.github.com> Date: Mon, 16 Dec 2024 18:54:38 +0000 Subject: [PATCH 18/25] cp --- block/executor.go | 1 - block/manager.go | 20 ++++++++++++++------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/block/executor.go b/block/executor.go index 85e1e227a..f3a1421c5 100644 --- a/block/executor.go +++ b/block/executor.go @@ -175,7 +175,6 @@ func (e *Executor) CreateBlock( copy(block.Header.DataHash[:], types.GetDataHash(block)) copy(block.Header.SequencerHash[:], state.GetProposerHash()) copy(block.Header.NextSequencersHash[:], nextSeqHash[:]) - return block } diff --git a/block/manager.go b/block/manager.go index 1ec24d170..8c843442b 100644 --- a/block/manager.go +++ b/block/manager.go @@ -214,18 +214,26 @@ func NewManager( m.SettlementValidator = NewSettlementValidator(m.logger, m) - frauds, err := dofraud.Load(conf.FraudCmdsPath) + 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 nil, fmt.Errorf("load frauds: %w", err) + return fmt.Errorf("load frauds: %w", err) } - logger.Info("Did not load fraud tests - frauds file not found", "path", conf.FraudCmdsPath) + m.logger.Info("Did not load fraud tests - frauds file not found", "path", path) } else { - logger.Info("Loaded frauds.") + m.logger.Info("Loaded frauds.") } m.fraudSim = frauds - - return m, nil + return nil } // Start starts the block manager. From f5baf998d88417285ee322e92a50793c6c3c0a3e Mon Sep 17 00:00:00 2001 From: danwt <30197399+danwt@users.noreply.github.com> Date: Mon, 16 Dec 2024 19:00:33 +0000 Subject: [PATCH 19/25] clean --- block/p2p.go | 2 +- block/produce.go | 18 ++++++++++-------- block/submit.go | 14 ++++++-------- dofraud/apply.go | 6 ++++-- 4 files changed, 21 insertions(+), 19 deletions(-) diff --git a/block/p2p.go b/block/p2p.go index c756a43ce..a327d7e76 100644 --- a/block/p2p.go +++ b/block/p2p.go @@ -67,7 +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) - commit = *m.fraudBlockAndCommit(dofraud.Gossip, block.Header.Height, &block) + m.fraudBlockAndCommit(dofraud.Gossip, block.Header.Height, &block, &commit) gossipedBlock := p2p.BlockData{Block: block, Commit: commit} gossipedBlockBytes, err := gossipedBlock.MarshalBinary() if err != nil { diff --git a/block/produce.go b/block/produce.go index f98e48418..f7588204b 100644 --- a/block/produce.go +++ b/block/produce.go @@ -239,7 +239,6 @@ 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) - m.fraudBlockAndCommit(dofraud.Produce, newHeight, block) // this cannot happen if there are any sequencer set updates // AllowEmpty should be always true in this case @@ -251,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.fraudBlockAndCommit(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))) @@ -352,12 +352,14 @@ func getHeaderHashAndCommit(store store.Store, height uint64) ([32]byte, *types. return lastBlock.Header.Hash(), lastCommit, nil } -// modifies the block, returns new commit -func (m *Manager) fraudBlockAndCommit(variant dofraud.FraudVariant, h uint64, b *types.Block) *types.Commit { - 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) +// if a fraud is specified, apply it (modify block, commit) +func (m *Manager) fraudBlockAndCommit(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 + } } - return comm } diff --git a/block/submit.go b/block/submit.go index c68f44374..50266b70e 100644 --- a/block/submit.go +++ b/block/submit.go @@ -44,13 +44,13 @@ func (m *Manager) SubmitLoop(ctx context.Context, func SubmitLoopInner( ctx context.Context, logger types.Logger, - bytesProduced chan int, // a channel of block and commit bytes produced - maxSkewTime time.Duration, // max time between last submitted block and last produced block allowed. if this threshold is reached block production is stopped. + bytesProduced chan int, // a channel of block and commit bytes produced + maxSkewTime time.Duration, // max time between last submitted block and last produced block allowed. if this threshold is reached block production is stopped. unsubmittedBlocksNum func() uint64, // func that returns the amount of non-submitted blocks - unsubmittedBlocksBytes func() int, // func that returns bytes from non-submitted blocks + unsubmittedBlocksBytes func() int, // func that returns bytes from non-submitted blocks batchSkewTime func() time.Duration, // func that returns measured time between last submitted block and last produced block - maxBatchSubmitTime time.Duration, // max time to allow between batches - maxBatchSubmitBytes uint64, // max size of serialised batch in bytes + maxBatchSubmitTime time.Duration, // max time to allow between batches + maxBatchSubmitBytes uint64, // max size of serialised batch in bytes createAndSubmitBatch func(maxSizeBytes uint64) (bytes uint64, err error), ) error { eg, ctx := errgroup.WithContext(ctx) @@ -329,10 +329,8 @@ func UpdateBatchSubmissionGauges(skewBytes uint64, skewBlocks uint64, skewTime t types.RollappPendingSubmissionsSkewTimeMinutes.Set(float64(skewTime.Minutes())) } -// (if frauds are specified) func (m *Manager) applyFraudsToBatch(batch *types.Batch) { for i, block := range batch.Blocks { - comm := m.fraudBlockAndCommit(dofraud.DA, block.Header.Height, block) - batch.Commits[i] = comm + m.fraudBlockAndCommit(dofraud.DA, block.Header.Height, block, batch.Commits[i]) } } diff --git a/dofraud/apply.go b/dofraud/apply.go index 1df83fd83..7947ba9b6 100644 --- a/dofraud/apply.go +++ b/dofraud/apply.go @@ -57,10 +57,11 @@ func (k key) String() string { return fmt.Sprintf("%d:%d", k.height, k.variant) } -func (f *Frauds) Apply(log types.Logger, height uint64, fraudVariant FraudVariant, b *types.Block) { +// 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 + return false } for _, fraud := range cmd.ts { @@ -102,4 +103,5 @@ func (f *Frauds) Apply(log types.Logger, height uint64, fraudVariant FraudVarian } log.Info("Applied fraud.", "height", height, "variant", fraudVariant, "types", cmd.ts) + return true } From 593fe750a1728c3be81a64281978f561741c7716 Mon Sep 17 00:00:00 2001 From: danwt <30197399+danwt@users.noreply.github.com> Date: Mon, 16 Dec 2024 19:03:08 +0000 Subject: [PATCH 20/25] rename --- block/p2p.go | 2 +- block/produce.go | 4 ++-- block/submit.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/block/p2p.go b/block/p2p.go index a327d7e76..d1b0c57c8 100644 --- a/block/p2p.go +++ b/block/p2p.go @@ -67,7 +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.fraudBlockAndCommit(dofraud.Gossip, block.Header.Height, &block, &commit) + m.doFraud(dofraud.Gossip, block.Header.Height, &block, &commit) gossipedBlock := p2p.BlockData{Block: block, Commit: commit} gossipedBlockBytes, err := gossipedBlock.MarshalBinary() if err != nil { diff --git a/block/produce.go b/block/produce.go index f7588204b..bfc479065 100644 --- a/block/produce.go +++ b/block/produce.go @@ -250,7 +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.fraudBlockAndCommit(dofraud.Produce, newHeight, block, commit) + 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))) @@ -353,7 +353,7 @@ func getHeaderHashAndCommit(store store.Store, height uint64) ([32]byte, *types. } // if a fraud is specified, apply it (modify block, commit) -func (m *Manager) fraudBlockAndCommit(variant dofraud.FraudVariant, h uint64, b *types.Block, c *types.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 { diff --git a/block/submit.go b/block/submit.go index 50266b70e..c6ca14538 100644 --- a/block/submit.go +++ b/block/submit.go @@ -331,6 +331,6 @@ func UpdateBatchSubmissionGauges(skewBytes uint64, skewBlocks uint64, skewTime t func (m *Manager) applyFraudsToBatch(batch *types.Batch) { for i, block := range batch.Blocks { - m.fraudBlockAndCommit(dofraud.DA, block.Header.Height, block, batch.Commits[i]) + m.doFraud(dofraud.DA, block.Header.Height, block, batch.Commits[i]) } } From 5368d9c63876f79a0b63d786a5b65c184fb34ff4 Mon Sep 17 00:00:00 2001 From: danwt <30197399+danwt@users.noreply.github.com> Date: Mon, 16 Dec 2024 19:17:12 +0000 Subject: [PATCH 21/25] fac --- block/p2p.go | 2 +- block/submit.go | 29 ++++++++++++++++++++++------- dofraud/apply.go | 5 +++++ types/batch.go | 7 +++++++ types/block.go | 2 ++ 5 files changed, 37 insertions(+), 8 deletions(-) diff --git a/block/p2p.go b/block/p2p.go index d1b0c57c8..3887bf52b 100644 --- a/block/p2p.go +++ b/block/p2p.go @@ -67,7 +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) + 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 { diff --git a/block/submit.go b/block/submit.go index c6ca14538..2caa7f390 100644 --- a/block/submit.go +++ b/block/submit.go @@ -44,13 +44,13 @@ func (m *Manager) SubmitLoop(ctx context.Context, func SubmitLoopInner( ctx context.Context, logger types.Logger, - bytesProduced chan int, // a channel of block and commit bytes produced - maxSkewTime time.Duration, // max time between last submitted block and last produced block allowed. if this threshold is reached block production is stopped. + bytesProduced chan int, // a channel of block and commit bytes produced + maxSkewTime time.Duration, // max time between last submitted block and last produced block allowed. if this threshold is reached block production is stopped. unsubmittedBlocksNum func() uint64, // func that returns the amount of non-submitted blocks - unsubmittedBlocksBytes func() int, // func that returns bytes from non-submitted blocks + unsubmittedBlocksBytes func() int, // func that returns bytes from non-submitted blocks batchSkewTime func() time.Duration, // func that returns measured time between last submitted block and last produced block - maxBatchSubmitTime time.Duration, // max time to allow between batches - maxBatchSubmitBytes uint64, // max size of serialised batch in bytes + maxBatchSubmitTime time.Duration, // max time to allow between batches + maxBatchSubmitBytes uint64, // max size of serialised batch in bytes createAndSubmitBatch func(maxSizeBytes uint64) (bytes uint64, err error), ) error { eg, ctx := errgroup.WithContext(ctx) @@ -241,8 +241,22 @@ func (m *Manager) CreateBatch(maxBatchSize uint64, startHeight uint64, endHeight } func (m *Manager) SubmitBatch(batch *types.Batch) error { - m.applyFraudsToBatch(batch) - resultSubmitToDA := m.DAClient.SubmitBatch(batch) + var 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) } @@ -329,6 +343,7 @@ func UpdateBatchSubmissionGauges(skewBytes uint64, skewBlocks uint64, skewTime t 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]) diff --git a/dofraud/apply.go b/dofraud/apply.go index 7947ba9b6..2b0a167f4 100644 --- a/dofraud/apply.go +++ b/dofraud/apply.go @@ -57,6 +57,11 @@ 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()] diff --git a/types/batch.go b/types/batch.go index 14d486539..1e9fcb91a 100644 --- a/types/batch.go +++ b/types/batch.go @@ -53,3 +53,10 @@ func (b Batch) SizeBlockAndCommitBytes() int { func (b Batch) SizeBytes() int { return b.ToProto().Size() } + +func (b Batch) Clone() (*Batch, error) { + p := b.ToProto() + ret := &Batch{} + err := ret.FromProto(p) + return ret, err +} diff --git a/types/block.go b/types/block.go index e6d2c1673..08fb70e10 100644 --- a/types/block.go +++ b/types/block.go @@ -130,3 +130,5 @@ func GetDataHash(block *Block) []byte { } return abciData.Hash() } + +func \ No newline at end of file From 22f676e8ff05e999011623252f50f22fdbaad3b5 Mon Sep 17 00:00:00 2001 From: danwt <30197399+danwt@users.noreply.github.com> Date: Mon, 16 Dec 2024 19:18:46 +0000 Subject: [PATCH 22/25] confirm tests pass --- types/block.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/types/block.go b/types/block.go index 08fb70e10..e6d2c1673 100644 --- a/types/block.go +++ b/types/block.go @@ -130,5 +130,3 @@ func GetDataHash(block *Block) []byte { } return abciData.Hash() } - -func \ No newline at end of file From caa286b4bbc7c90b972bbc98a528861108848039 Mon Sep 17 00:00:00 2001 From: danwt <30197399+danwt@users.noreply.github.com> Date: Mon, 16 Dec 2024 19:22:47 +0000 Subject: [PATCH 23/25] linter --- dofraud/disk.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dofraud/disk.go b/dofraud/disk.go index 43d8682b4..1f7e6e4f9 100644 --- a/dofraud/disk.go +++ b/dofraud/disk.go @@ -41,11 +41,11 @@ type diskBlock struct { } func Load(fn string) (Frauds, error) { - file, err := os.Open(fn) + file, err := os.Open(fn) //nolint:gosec if err != nil { return Frauds{}, err } - defer file.Close() + defer file.Close() //nolint:errcheck data, err := io.ReadAll(file) if err != nil { From 89b757ca6847f0dcfc7f2cd66f93179ea398b003 Mon Sep 17 00:00:00 2001 From: danwt <30197399+danwt@users.noreply.github.com> Date: Tue, 17 Dec 2024 09:25:58 +0000 Subject: [PATCH 24/25] format --- block/manager.go | 1 - block/submit.go | 2 +- dofraud/apply.go | 6 ++++-- dofraud/z_test.go | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/block/manager.go b/block/manager.go index 8c843442b..ed5e09062 100644 --- a/block/manager.go +++ b/block/manager.go @@ -238,7 +238,6 @@ func (m *Manager) loadFraud(path string) error { // Start starts the block manager. func (m *Manager) Start(ctx context.Context) error { - m.Ctx, m.Cancel = context.WithCancel(ctx) // Check if InitChain flow is needed if m.State.IsGenesis() { diff --git a/block/submit.go b/block/submit.go index 2caa7f390..8900b7270 100644 --- a/block/submit.go +++ b/block/submit.go @@ -241,7 +241,7 @@ func (m *Manager) CreateBatch(maxBatchSize uint64, startHeight uint64, endHeight } func (m *Manager) SubmitBatch(batch *types.Batch) error { - var daBatch = 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 diff --git a/dofraud/apply.go b/dofraud/apply.go index 2b0a167f4..4185a6248 100644 --- a/dofraud/apply.go +++ b/dofraud/apply.go @@ -6,8 +6,10 @@ import ( "github.com/dymensionxyz/dymint/types" ) -type FraudVariant = int -type FraudType = int +type ( + FraudVariant = int + FraudType = int +) // Variant const ( diff --git a/dofraud/z_test.go b/dofraud/z_test.go index ccf16e325..e83594957 100644 --- a/dofraud/z_test.go +++ b/dofraud/z_test.go @@ -38,7 +38,7 @@ func TestGenerateJson(t *testing.T) { data, err := json.MarshalIndent(examples, "", " ") require.NoError(t, err) - err = os.WriteFile(fp, data, 0644) + err = os.WriteFile(fp, data, 0o644) require.NoError(t, err) } From 0b6d3a81ec22e5f4ac7ad34d8d2fcdb09fee32c5 Mon Sep 17 00:00:00 2001 From: danwt <30197399+danwt@users.noreply.github.com> Date: Tue, 17 Dec 2024 09:27:57 +0000 Subject: [PATCH 25/25] format --- dofraud/disk.go | 5 ++++- dofraud/z_test.go | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/dofraud/disk.go b/dofraud/disk.go index 1f7e6e4f9..bdfa5da0c 100644 --- a/dofraud/disk.go +++ b/dofraud/disk.go @@ -51,9 +51,12 @@ func Load(fn string) (Frauds, error) { if err != nil { return Frauds{}, err } + return loadBz(data) +} +func loadBz(bz []byte) (Frauds, error) { var d disk - err = json.Unmarshal(data, &d) + err := json.Unmarshal(bz, &d) if err != nil { return Frauds{}, err } diff --git a/dofraud/z_test.go b/dofraud/z_test.go index e83594957..540f603d0 100644 --- a/dofraud/z_test.go +++ b/dofraud/z_test.go @@ -9,7 +9,7 @@ import ( "github.com/stretchr/testify/require" ) -const fp = "/Users/danwt/Documents/dym/d-dymint/dofraud/testdata/example.json" +const fp = "testdata/example.json" //go:embed testdata/example.json var testData []byte @@ -43,7 +43,7 @@ func TestGenerateJson(t *testing.T) { } func TestParseJson(t *testing.T) { - fraud, err := Load(fp) + fraud, err := loadBz(testData) require.NoError(t, err) cmd, ok := fraud.frauds[key{10, DA}.String()] require.True(t, ok)