From 162dc1651c53e29fd4b01eb49ad57433bad6aba6 Mon Sep 17 00:00:00 2001 From: Jasmina Malicevic Date: Wed, 14 May 2025 13:11:36 +0200 Subject: [PATCH 01/15] Set default value of blob params to 0 --- types/params.go | 3 ++- types/params_test.go | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/types/params.go b/types/params.go index e9daf5da83f..5c143438b47 100644 --- a/types/params.go +++ b/types/params.go @@ -119,7 +119,8 @@ func DefaultBlockParams() BlockParams { // DefaultBlobParams returns a default BlobParams. func DefaultBlobParams() BlobParams { return BlobParams{ - MaxBytes: 819200, // 800kB + MaxBytes: 0, // 0 bytes means no blob. To use the blob feature + // there needs to be a government proposal to set this to some other value } } diff --git a/types/params_test.go b/types/params_test.go index 875fcb673b4..5dfc9bda2b3 100644 --- a/types/params_test.go +++ b/types/params_test.go @@ -149,6 +149,16 @@ func TestConsensusParamsUpdate(t *testing.T) { }, makeParams(100, 200, 1, 300, 50, valSecp256k1, 0), }, + // upadate blob params + { + makeParams(1, 2, 1, 3, 0, valEd25519, 0), + &cmtproto.ConsensusParams{ + Blob: &cmtproto.BlobParams{ + MaxBytes: 100, + }, + }, + makeParams(1, 2, 100, 3, 0, valEd25519, 0), + }, } for _, tc := range testCases { From 2874a8139ccdc10e8f761003c70562782bd5bc0e Mon Sep 17 00:00:00 2001 From: Jasmina Malicevic Date: Wed, 14 May 2025 18:41:34 +0200 Subject: [PATCH 02/15] Added maxBytes param update to e2e --- test/e2e/app/app.go | 54 ++++++++++++++++++++++++++++------ test/e2e/generator/generate.go | 7 +++++ test/e2e/node/config.go | 2 ++ test/e2e/pkg/manifest.go | 5 +++- test/e2e/pkg/testnet.go | 9 ++++++ test/e2e/runner/setup.go | 1 + 6 files changed, 68 insertions(+), 10 deletions(-) diff --git a/test/e2e/app/app.go b/test/e2e/app/app.go index 7a5cf074349..de1a175e0aa 100644 --- a/test/e2e/app/app.go +++ b/test/e2e/app/app.go @@ -24,6 +24,7 @@ import ( "github.com/cometbft/cometbft/libs/protoio" cryptoproto "github.com/cometbft/cometbft/proto/tendermint/crypto" cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" + cmttypes "github.com/cometbft/cometbft/types" "github.com/cometbft/cometbft/version" ) @@ -36,6 +37,7 @@ const ( suffixChainID string = "ChainID" suffixVoteExtHeight string = "VoteExtensionsHeight" suffixInitialHeight string = "InitialHeight" + suffixBlobMaxBytes string = "BlobMaxBytes" ) // Application is an ABCI application for use by end-to-end tests. It is a @@ -111,6 +113,10 @@ type Config struct { // -1 denotes it is set at genesis. // 0 denotes it is set at InitChain. VoteExtensionsUpdateHeight int64 `toml:"vote_extensions_update_height"` + + // BlobMaxBytesUpdateHeight configures the height at which the + // blob max bytes is set to something other than 0 + BlobMaxBytesUpdateHeight int64 `toml:"blob_max_bytes_update_height"` } func DefaultConfig(dir string) *Config { @@ -129,7 +135,10 @@ func DefaultConfig(dir string) *Config { // 2 - blob is empty ([]byte{}) // 3 - blob is variable length string that contains "BLOBXXX" where XXX is height multiplied by 0x80 in hex // 4 - blob is the size of MaxBlobSizeBytes and contains height truncated into two bytes repeated. -func blobOracle(height int64) ([]byte, bool) { +func blobOracle(height int64, blobMaxBytesUpdateHeight int64) ([]byte, bool) { + if height <= blobMaxBytesUpdateHeight+2 { + return nil, false + } switch height % 4 { case 1: truncatedHeight := byte(height % 0x100) @@ -158,14 +167,14 @@ func isBlob(blob []byte) bool { } // CreateBlob creates a new blob for a proposal at this height. -func CreateBlob(height int64) ([]byte, bool) { - return blobOracle(height) +func CreateBlob(height int64, blobMaxBytesUpdateHeight int64) ([]byte, bool) { + return blobOracle(height, blobMaxBytesUpdateHeight) } -func VerifyBlob(height int64, blob []byte) bool { +func VerifyBlob(height int64, blob []byte, blobMaxBytesUpdateHeight int64) bool { // The application might have internal checks that ensures a blob is valid. // In this test application, we know what a valid blob should look like. - validBlob, exist := blobOracle(height) + validBlob, exist := blobOracle(height, blobMaxBytesUpdateHeight) if !exist { // The application received a blob at a height where no blob should be. return len(blob) == 0 @@ -183,6 +192,7 @@ func NewApplication(cfg *Config) (*Application, error) { if err != nil { return nil, err } + fmt.Println("XXXX ", cfg.BlobMaxBytesUpdateHeight) blobCache := make(map[int64][]byte) return &Application{ logger: log.NewTMLogger(log.NewSyncWriter(os.Stdout)), @@ -204,6 +214,26 @@ func (app *Application) Info(context.Context, *abci.RequestInfo) (*abci.Response }, nil } +// Expeected to be called with params set +func (app *Application) updateBlobMaxBytes(currentHeight int64, params *cmtproto.ConsensusParams) *cmtproto.ConsensusParams { + if params == nil { + params = &cmtproto.ConsensusParams{} + } + if app.cfg.BlobMaxBytesUpdateHeight == currentHeight { + app.logger.Info("updating blob max bytes on the fly", + "current_height", currentHeight, + "blob_max_bytes_update_height", app.cfg.BlobMaxBytesUpdateHeight) + params = &cmtproto.ConsensusParams{ + Blob: &cmtproto.BlobParams{ + MaxBytes: cmttypes.MaxBlobSizeBytes, + }, + } + app.logger.Info("updating blob max bytes in app_state", "height", app.cfg.BlobMaxBytesUpdateHeight) + app.state.Set(prefixReservedKey+suffixBlobMaxBytes, strconv.FormatInt(app.cfg.BlobMaxBytesUpdateHeight, 10)) + } + return params +} + func (app *Application) updateVoteExtensionEnableHeight(currentHeight int64) *cmtproto.ConsensusParams { var params *cmtproto.ConsensusParams if app.cfg.VoteExtensionsUpdateHeight == currentHeight { @@ -281,6 +311,10 @@ func (app *Application) CheckTx(_ context.Context, req *abci.RequestCheckTx) (*a // It returns a boolean indicating if a blob exists (either retrieved from the simaulted network or from the // local cache) and the blob itself. func (app *Application) GetBlob(height int64) ([]byte, bool, error) { + if app.cfg.BlobMaxBytesUpdateHeight > height { + // Blob max bytes is still 0 so we cannot send a blob + return nil, false, nil + } // First check the local cache if blob, found := app.blobCache[height]; found { if !isBlob(blob) { @@ -291,7 +325,7 @@ func (app *Application) GetBlob(height int64) ([]byte, bool, error) { } // If not found, reach out to other peers and retrieve it through them. time.Sleep(100 * time.Millisecond) // Add simulated network delay - blobFromPeer, exist := blobOracle(height) + blobFromPeer, exist := blobOracle(height, app.cfg.BlobMaxBytesUpdateHeight) if exist && !isBlob(blobFromPeer) { return nil, false, nil } @@ -321,7 +355,7 @@ func (app *Application) FinalizeBlock(_ context.Context, req *abci.RequestFinali if err != nil { return nil, fmt.Errorf("could not fetch blob for height %d, %s", req.Height, err.Error()) } - if exists && !VerifyBlob(req.Height, blob) { + if exists && !VerifyBlob(req.Height, blob, app.cfg.BlobMaxBytesUpdateHeight) { return nil, fmt.Errorf("blob verification failed for height %d", req.Height) } @@ -348,6 +382,8 @@ func (app *Application) FinalizeBlock(_ context.Context, req *abci.RequestFinali params := app.updateVoteExtensionEnableHeight(req.Height) + params = app.updateBlobMaxBytes(req.Height, params) + if app.cfg.FinalizeBlockDelay != 0 { time.Sleep(app.cfg.FinalizeBlockDelay) } @@ -528,7 +564,7 @@ func (app *Application) PrepareProposal( txs = append(txs, tx) } // Generate blob for the current height. - blob, exists := CreateBlob(req.Height) + blob, exists := CreateBlob(req.Height, app.cfg.BlobMaxBytesUpdateHeight) if app.cfg.PrepareProposalDelay != 0 { time.Sleep(app.cfg.PrepareProposalDelay) @@ -551,7 +587,7 @@ func (app *Application) ProcessProposal(_ context.Context, req *abci.RequestProc fmt.Println("BLOB: ", req.Blob) - if !VerifyBlob(req.Height, req.Blob) { + if !VerifyBlob(req.Height, req.Blob, app.cfg.BlobMaxBytesUpdateHeight) { app.logger.Error("invalid blob, rejecting proposal", "received height", req.Height, "received", req.Blob) return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}, nil } diff --git a/test/e2e/generator/generate.go b/test/e2e/generator/generate.go index bc64501d4fe..c6c1707f081 100644 --- a/test/e2e/generator/generate.go +++ b/test/e2e/generator/generate.go @@ -62,6 +62,7 @@ var ( voteExtensionUpdateHeight = uniformChoice{int64(-1), int64(0), int64(1)} // -1: genesis, 0: InitChain, 1: (use offset) voteExtensionEnabled = weightedChoice{true: 3, false: 1} voteExtensionHeightOffset = uniformChoice{int64(0), int64(10), int64(100)} + blobMaxBytesUpdateHeight = uniformChoice{int64(-1), int64(0), int64(100)} ) type generateConfig struct { @@ -157,6 +158,12 @@ func generateTestnet(r *rand.Rand, opt map[string]interface{}, upgradeVersion st manifest.VoteExtensionsEnableHeight = baseHeight + voteExtensionHeightOffset.Choose(r).(int64) } + manifest.BlobMaxBytesUpdateHeight = blobMaxBytesUpdateHeight.Choose(r).(int64) + + if manifest.BlobMaxBytesUpdateHeight != -1 && manifest.BlobMaxBytesUpdateHeight < manifest.InitialHeight { + manifest.BlobMaxBytesUpdateHeight = manifest.InitialHeight + manifest.BlobMaxBytesUpdateHeight + } + var numSeeds, numValidators, numFulls, numLightClients int switch opt["topology"].(string) { case "single": diff --git a/test/e2e/node/config.go b/test/e2e/node/config.go index f06ddc80d3d..00b964caf08 100644 --- a/test/e2e/node/config.go +++ b/test/e2e/node/config.go @@ -26,6 +26,7 @@ type Config struct { KeyType string `toml:"key_type"` VoteExtensionsEnableHeight int64 `toml:"vote_extensions_enable_height"` VoteExtensionsUpdateHeight int64 `toml:"vote_extensions_update_height"` + BlobMaxBytesUpdateHeight int64 `toml:"blob_max_bytes_update_height"` } // App extracts out the application specific configuration parameters @@ -39,6 +40,7 @@ func (cfg *Config) App() *app.Config { PersistInterval: cfg.PersistInterval, VoteExtensionsEnableHeight: cfg.VoteExtensionsEnableHeight, VoteExtensionsUpdateHeight: cfg.VoteExtensionsUpdateHeight, + BlobMaxBytesUpdateHeight: cfg.BlobMaxBytesUpdateHeight, } } diff --git a/test/e2e/pkg/manifest.go b/test/e2e/pkg/manifest.go index d86fd113d1a..0f8c8e81196 100644 --- a/test/e2e/pkg/manifest.go +++ b/test/e2e/pkg/manifest.go @@ -101,7 +101,7 @@ type Manifest struct { // BlobMaxBytes specifies the maximum size in bytes of a blob. This // value will be written to the genesis file of all nodes. - BlobMaxBytes int64 `toml:"blob_max_bytes"` + //BlobMaxBytes int64 `toml:"blob_max_bytes"` // VoteExtensionsEnableHeight configures the first height during which // the chain will use and require vote extension data to be present @@ -116,6 +116,9 @@ type Manifest struct { // Maximum number of peers to which the node gossips transactions ExperimentalMaxGossipConnectionsToPersistentPeers uint `toml:"experimental_max_gossip_connections_to_persistent_peers"` ExperimentalMaxGossipConnectionsToNonPersistentPeers uint `toml:"experimental_max_gossip_connections_to_non_persistent_peers"` + + // Height at which we set the max blob size to be something other than 0 + BlobMaxBytesUpdateHeight int64 `toml:"blob_max_bytes_update_height"` } // ManifestNode represents a node in a testnet manifest. diff --git a/test/e2e/pkg/testnet.go b/test/e2e/pkg/testnet.go index 7d67e0ec3e1..a0d0e830872 100644 --- a/test/e2e/pkg/testnet.go +++ b/test/e2e/pkg/testnet.go @@ -98,6 +98,7 @@ type Testnet struct { VoteExtensionsUpdateHeight int64 ExperimentalMaxGossipConnectionsToPersistentPeers uint ExperimentalMaxGossipConnectionsToNonPersistentPeers uint + BlobMaxBytesUpdateHeight int64 } // Node represents a CometBFT node in a testnet. @@ -182,13 +183,17 @@ func NewTestnetFromManifest(manifest Manifest, file string, ifd InfrastructureDa VoteExtensionsUpdateHeight: manifest.VoteExtensionsUpdateHeight, ExperimentalMaxGossipConnectionsToPersistentPeers: manifest.ExperimentalMaxGossipConnectionsToPersistentPeers, ExperimentalMaxGossipConnectionsToNonPersistentPeers: manifest.ExperimentalMaxGossipConnectionsToNonPersistentPeers, + BlobMaxBytesUpdateHeight: manifest.BlobMaxBytesUpdateHeight, } if len(manifest.KeyType) != 0 { testnet.KeyType = manifest.KeyType } if manifest.InitialHeight > 0 { testnet.InitialHeight = manifest.InitialHeight + } else { + testnet.BlobMaxBytesUpdateHeight = testnet.BlobMaxBytesUpdateHeight + testnet.InitialHeight } + if testnet.ABCIProtocol == "" { testnet.ABCIProtocol = string(ProtocolBuiltin) } @@ -391,6 +396,10 @@ func (t Testnet) Validate() error { ) } } + + if !(t.BlobMaxBytesUpdateHeight == -1 || t.BlobMaxBytesUpdateHeight == t.InitialHeight || t.BlobMaxBytesUpdateHeight == t.InitialHeight+100) { + return fmt.Errorf("the value of BlobMaxBytesUpdateHeight must be either -1 (disabled) , 0 (InitChain) or 100(height 100): %d ", t.BlobMaxBytesUpdateHeight+t.InitialHeight) + } for _, node := range t.Nodes { if err := node.Validate(t); err != nil { return fmt.Errorf("invalid node %q: %w", node.Name, err) diff --git a/test/e2e/runner/setup.go b/test/e2e/runner/setup.go index f9eb1239914..a10923cf45f 100644 --- a/test/e2e/runner/setup.go +++ b/test/e2e/runner/setup.go @@ -292,6 +292,7 @@ func MakeAppConfig(node *e2e.Node) ([]byte, error) { "finalize_block_delay": node.Testnet.FinalizeBlockDelay, "vote_extensions_enable_height": node.Testnet.VoteExtensionsEnableHeight, "vote_extensions_update_height": node.Testnet.VoteExtensionsUpdateHeight, + "blob_max_bytes_update_height": node.Testnet.BlobMaxBytesUpdateHeight, } switch node.ABCIProtocol { case e2e.ProtocolUNIX: From b70dff3bf6311dc4872a1bff82ac16ab0c48e6d2 Mon Sep 17 00:00:00 2001 From: Jasmina Malicevic Date: Wed, 14 May 2025 19:34:38 +0200 Subject: [PATCH 03/15] Update ci.toml with proper blob_max_bytes value and fix check --- test/e2e/networks/ci.toml | 1 + test/e2e/pkg/testnet.go | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/test/e2e/networks/ci.toml b/test/e2e/networks/ci.toml index 8b191e62ab8..ba0ca0bf354 100644 --- a/test/e2e/networks/ci.toml +++ b/test/e2e/networks/ci.toml @@ -5,6 +5,7 @@ ipv6 = true initial_height = 1000 vote_extensions_update_height = -1 vote_extensions_enable_height = 1000 +blob_max_bytes_update_height = 1000 evidence = 5 initial_state = { initial01 = "a", initial02 = "b", initial03 = "c" } prepare_proposal_delay = "100ms" diff --git a/test/e2e/pkg/testnet.go b/test/e2e/pkg/testnet.go index a0d0e830872..2ffec41d75d 100644 --- a/test/e2e/pkg/testnet.go +++ b/test/e2e/pkg/testnet.go @@ -191,7 +191,9 @@ func NewTestnetFromManifest(manifest Manifest, file string, ifd InfrastructureDa if manifest.InitialHeight > 0 { testnet.InitialHeight = manifest.InitialHeight } else { - testnet.BlobMaxBytesUpdateHeight = testnet.BlobMaxBytesUpdateHeight + testnet.InitialHeight + if testnet.BlobMaxBytesUpdateHeight != -1 { + testnet.BlobMaxBytesUpdateHeight = testnet.BlobMaxBytesUpdateHeight + testnet.InitialHeight + } } if testnet.ABCIProtocol == "" { @@ -398,7 +400,7 @@ func (t Testnet) Validate() error { } if !(t.BlobMaxBytesUpdateHeight == -1 || t.BlobMaxBytesUpdateHeight == t.InitialHeight || t.BlobMaxBytesUpdateHeight == t.InitialHeight+100) { - return fmt.Errorf("the value of BlobMaxBytesUpdateHeight must be either -1 (disabled) , 0 (InitChain) or 100(height 100): %d ", t.BlobMaxBytesUpdateHeight+t.InitialHeight) + return fmt.Errorf("the value of BlobMaxBytesUpdateHeight must be either -1 (disabled) , 0 (InitChain) or 100(height 100): %d ", t.BlobMaxBytesUpdateHeight) } for _, node := range t.Nodes { if err := node.Validate(t); err != nil { From 6e31f892c66788247577b8af2e68e56133daa324 Mon Sep 17 00:00:00 2001 From: Jasmina Malicevic Date: Wed, 14 May 2025 22:58:23 +0200 Subject: [PATCH 04/15] fixed edge case when blobs are disabled, lowered the height they are activated at --- test/e2e/app/app.go | 8 ++++---- test/e2e/generator/generate.go | 2 +- test/e2e/pkg/testnet.go | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/test/e2e/app/app.go b/test/e2e/app/app.go index de1a175e0aa..65b66d0f767 100644 --- a/test/e2e/app/app.go +++ b/test/e2e/app/app.go @@ -136,7 +136,7 @@ func DefaultConfig(dir string) *Config { // 3 - blob is variable length string that contains "BLOBXXX" where XXX is height multiplied by 0x80 in hex // 4 - blob is the size of MaxBlobSizeBytes and contains height truncated into two bytes repeated. func blobOracle(height int64, blobMaxBytesUpdateHeight int64) ([]byte, bool) { - if height <= blobMaxBytesUpdateHeight+2 { + if height <= blobMaxBytesUpdateHeight+2 || blobMaxBytesUpdateHeight == -1 { return nil, false } switch height % 4 { @@ -192,7 +192,6 @@ func NewApplication(cfg *Config) (*Application, error) { if err != nil { return nil, err } - fmt.Println("XXXX ", cfg.BlobMaxBytesUpdateHeight) blobCache := make(map[int64][]byte) return &Application{ logger: log.NewTMLogger(log.NewSyncWriter(os.Stdout)), @@ -219,7 +218,7 @@ func (app *Application) updateBlobMaxBytes(currentHeight int64, params *cmtproto if params == nil { params = &cmtproto.ConsensusParams{} } - if app.cfg.BlobMaxBytesUpdateHeight == currentHeight { + if app.cfg.BlobMaxBytesUpdateHeight != 1 && app.cfg.BlobMaxBytesUpdateHeight == currentHeight { app.logger.Info("updating blob max bytes on the fly", "current_height", currentHeight, "blob_max_bytes_update_height", app.cfg.BlobMaxBytesUpdateHeight) @@ -267,6 +266,7 @@ func (app *Application) InitChain(_ context.Context, req *abci.RequestInitChain) app.state.Set(prefixReservedKey+suffixVoteExtHeight, strconv.FormatInt(req.ConsensusParams.Abci.VoteExtensionsEnableHeight, 10)) app.logger.Info("setting initial height in app_state", "initial_height", req.InitialHeight) app.state.Set(prefixReservedKey+suffixInitialHeight, strconv.FormatInt(req.InitialHeight, 10)) + app.state.Set(prefixReservedKey+suffixBlobMaxBytes, strconv.FormatInt(req.ConsensusParams.Blob.MaxBytes, 10)) // Get validators from genesis if req.Validators != nil { for _, val := range req.Validators { @@ -311,7 +311,7 @@ func (app *Application) CheckTx(_ context.Context, req *abci.RequestCheckTx) (*a // It returns a boolean indicating if a blob exists (either retrieved from the simaulted network or from the // local cache) and the blob itself. func (app *Application) GetBlob(height int64) ([]byte, bool, error) { - if app.cfg.BlobMaxBytesUpdateHeight > height { + if height <= app.cfg.BlobMaxBytesUpdateHeight+2 || app.cfg.BlobMaxBytesUpdateHeight == -1 { // Blob max bytes is still 0 so we cannot send a blob return nil, false, nil } diff --git a/test/e2e/generator/generate.go b/test/e2e/generator/generate.go index c6c1707f081..08639569b4b 100644 --- a/test/e2e/generator/generate.go +++ b/test/e2e/generator/generate.go @@ -62,7 +62,7 @@ var ( voteExtensionUpdateHeight = uniformChoice{int64(-1), int64(0), int64(1)} // -1: genesis, 0: InitChain, 1: (use offset) voteExtensionEnabled = weightedChoice{true: 3, false: 1} voteExtensionHeightOffset = uniformChoice{int64(0), int64(10), int64(100)} - blobMaxBytesUpdateHeight = uniformChoice{int64(-1), int64(0), int64(100)} + blobMaxBytesUpdateHeight = uniformChoice{int64(-1), int64(0), int64(10)} ) type generateConfig struct { diff --git a/test/e2e/pkg/testnet.go b/test/e2e/pkg/testnet.go index 2ffec41d75d..1c18f1bd515 100644 --- a/test/e2e/pkg/testnet.go +++ b/test/e2e/pkg/testnet.go @@ -399,8 +399,8 @@ func (t Testnet) Validate() error { } } - if !(t.BlobMaxBytesUpdateHeight == -1 || t.BlobMaxBytesUpdateHeight == t.InitialHeight || t.BlobMaxBytesUpdateHeight == t.InitialHeight+100) { - return fmt.Errorf("the value of BlobMaxBytesUpdateHeight must be either -1 (disabled) , 0 (InitChain) or 100(height 100): %d ", t.BlobMaxBytesUpdateHeight) + if !(t.BlobMaxBytesUpdateHeight == -1 || t.BlobMaxBytesUpdateHeight == t.InitialHeight || t.BlobMaxBytesUpdateHeight == t.InitialHeight+10) { + return fmt.Errorf("the value of BlobMaxBytesUpdateHeight must be either -1 (disabled) , 0 (InitChain) or 10(height 10): %d ", t.BlobMaxBytesUpdateHeight) } for _, node := range t.Nodes { if err := node.Validate(t); err != nil { From 1b82ab4bb3db0fb5b81eb0915c3ade4ad0db9135 Mon Sep 17 00:00:00 2001 From: Jasmina Malicevic Date: Thu, 15 May 2025 13:15:16 +0200 Subject: [PATCH 05/15] Minor fixes to address initial comments --- test/e2e/app/app.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/test/e2e/app/app.go b/test/e2e/app/app.go index 65b66d0f767..cab573373e2 100644 --- a/test/e2e/app/app.go +++ b/test/e2e/app/app.go @@ -136,7 +136,7 @@ func DefaultConfig(dir string) *Config { // 3 - blob is variable length string that contains "BLOBXXX" where XXX is height multiplied by 0x80 in hex // 4 - blob is the size of MaxBlobSizeBytes and contains height truncated into two bytes repeated. func blobOracle(height int64, blobMaxBytesUpdateHeight int64) ([]byte, bool) { - if height <= blobMaxBytesUpdateHeight+2 || blobMaxBytesUpdateHeight == -1 { + if height <= blobMaxBytesUpdateHeight || blobMaxBytesUpdateHeight == -1 { return nil, false } switch height % 4 { @@ -222,11 +222,10 @@ func (app *Application) updateBlobMaxBytes(currentHeight int64, params *cmtproto app.logger.Info("updating blob max bytes on the fly", "current_height", currentHeight, "blob_max_bytes_update_height", app.cfg.BlobMaxBytesUpdateHeight) - params = &cmtproto.ConsensusParams{ - Blob: &cmtproto.BlobParams{ - MaxBytes: cmttypes.MaxBlobSizeBytes, - }, + params.Blob = &cmtproto.BlobParams{ + MaxBytes: cmttypes.MaxBlobSizeBytes, } + app.logger.Info("updating blob max bytes in app_state", "height", app.cfg.BlobMaxBytesUpdateHeight) app.state.Set(prefixReservedKey+suffixBlobMaxBytes, strconv.FormatInt(app.cfg.BlobMaxBytesUpdateHeight, 10)) } @@ -311,7 +310,7 @@ func (app *Application) CheckTx(_ context.Context, req *abci.RequestCheckTx) (*a // It returns a boolean indicating if a blob exists (either retrieved from the simaulted network or from the // local cache) and the blob itself. func (app *Application) GetBlob(height int64) ([]byte, bool, error) { - if height <= app.cfg.BlobMaxBytesUpdateHeight+2 || app.cfg.BlobMaxBytesUpdateHeight == -1 { + if height <= app.cfg.BlobMaxBytesUpdateHeight || app.cfg.BlobMaxBytesUpdateHeight == -1 { // Blob max bytes is still 0 so we cannot send a blob return nil, false, nil } From 7b709514c3b284dc57ce931022b57f4abfcaa4cc Mon Sep 17 00:00:00 2001 From: Jasmina Malicevic Date: Fri, 16 May 2025 14:47:38 +0200 Subject: [PATCH 06/15] BlobMaxBytes update logic is the same as VE --- test/e2e/app/app.go | 111 ++++++++++++++++++++++----------- test/e2e/generator/generate.go | 12 +++- test/e2e/networks/ci.toml | 1 + test/e2e/node/config.go | 2 + test/e2e/pkg/manifest.go | 15 +++-- test/e2e/pkg/testnet.go | 21 +++++-- test/e2e/runner/setup.go | 8 ++- test/e2e/tests/app_test.go | 27 ++++++++ 8 files changed, 142 insertions(+), 55 deletions(-) diff --git a/test/e2e/app/app.go b/test/e2e/app/app.go index cab573373e2..31c00cf5d23 100644 --- a/test/e2e/app/app.go +++ b/test/e2e/app/app.go @@ -115,8 +115,14 @@ type Config struct { VoteExtensionsUpdateHeight int64 `toml:"vote_extensions_update_height"` // BlobMaxBytesUpdateHeight configures the height at which the - // blob max bytes is set to something other than 0 + // blob max bytes consensus parameters are updated + // -1 means the max_bytes value is set at genesis + // 0 means the max_bytes value is set at InitChain + // >0 means the max_bytes value is set at the given height BlobMaxBytesUpdateHeight int64 `toml:"blob_max_bytes_update_height"` + + // BlobMaxBytes is the values of max bytes for blobs + BlobMaxBytes int64 `toml:"blob_max_bytes"` } func DefaultConfig(dir string) *Config { @@ -136,9 +142,7 @@ func DefaultConfig(dir string) *Config { // 3 - blob is variable length string that contains "BLOBXXX" where XXX is height multiplied by 0x80 in hex // 4 - blob is the size of MaxBlobSizeBytes and contains height truncated into two bytes repeated. func blobOracle(height int64, blobMaxBytesUpdateHeight int64) ([]byte, bool) { - if height <= blobMaxBytesUpdateHeight || blobMaxBytesUpdateHeight == -1 { - return nil, false - } + switch height % 4 { case 1: truncatedHeight := byte(height % 0x100) @@ -218,16 +222,16 @@ func (app *Application) updateBlobMaxBytes(currentHeight int64, params *cmtproto if params == nil { params = &cmtproto.ConsensusParams{} } - if app.cfg.BlobMaxBytesUpdateHeight != 1 && app.cfg.BlobMaxBytesUpdateHeight == currentHeight { + if app.cfg.BlobMaxBytesUpdateHeight == currentHeight { app.logger.Info("updating blob max bytes on the fly", "current_height", currentHeight, "blob_max_bytes_update_height", app.cfg.BlobMaxBytesUpdateHeight) params.Blob = &cmtproto.BlobParams{ - MaxBytes: cmttypes.MaxBlobSizeBytes, + MaxBytes: app.cfg.BlobMaxBytes, } - app.logger.Info("updating blob max bytes in app_state", "height", app.cfg.BlobMaxBytesUpdateHeight) - app.state.Set(prefixReservedKey+suffixBlobMaxBytes, strconv.FormatInt(app.cfg.BlobMaxBytesUpdateHeight, 10)) + app.logger.Info("updating blob max bytes in app_state", "height", app.cfg.BlobMaxBytes) + app.state.Set(prefixReservedKey+suffixBlobMaxBytes, strconv.FormatInt(app.cfg.BlobMaxBytes, 10)) } return params } @@ -252,6 +256,7 @@ func (app *Application) updateVoteExtensionEnableHeight(currentHeight int64) *cm // Info implements ABCI. func (app *Application) InitChain(_ context.Context, req *abci.RequestInitChain) (*abci.ResponseInitChain, error) { var err error + app.state.initialHeight = uint64(req.InitialHeight) if len(req.AppStateBytes) > 0 { err = app.state.Import(0, req.AppStateBytes) @@ -278,6 +283,8 @@ func (app *Application) InitChain(_ context.Context, req *abci.RequestInitChain) params := app.updateVoteExtensionEnableHeight(0) + params = app.updateBlobMaxBytes(0, params) + resp := &abci.ResponseInitChain{ ConsensusParams: params, AppHash: app.state.GetHash(), @@ -310,7 +317,7 @@ func (app *Application) CheckTx(_ context.Context, req *abci.RequestCheckTx) (*a // It returns a boolean indicating if a blob exists (either retrieved from the simaulted network or from the // local cache) and the blob itself. func (app *Application) GetBlob(height int64) ([]byte, bool, error) { - if height <= app.cfg.BlobMaxBytesUpdateHeight || app.cfg.BlobMaxBytesUpdateHeight == -1 { + if !app.checkBlobHeight(height, "getBlob") { // Blob max bytes is still 0 so we cannot send a blob return nil, false, nil } @@ -350,18 +357,20 @@ func (app *Application) FinalizeBlock(_ context.Context, req *abci.RequestFinali } // Verify blob. - blob, exists, err := app.GetBlob(req.Height) - if err != nil { - return nil, fmt.Errorf("could not fetch blob for height %d, %s", req.Height, err.Error()) - } - if exists && !VerifyBlob(req.Height, blob, app.cfg.BlobMaxBytesUpdateHeight) { - return nil, fmt.Errorf("blob verification failed for height %d", req.Height) - } + if app.checkBlobHeight(req.Height, "FinalizeBlock") { + blob, exists, err := app.GetBlob(req.Height) + if err != nil { + return nil, fmt.Errorf("could not fetch blob for height %d, %s", req.Height, err.Error()) + } + if exists && !VerifyBlob(req.Height, blob, app.cfg.BlobMaxBytesUpdateHeight) { + return nil, fmt.Errorf("blob verification failed for height %d", req.Height) + } - // This is a short-term cache so we delete the entry after we received it. - delete(app.blobCache, req.Height) - if len(app.blobCache) != 0 { - app.logger.Error("blob cache should be empty", "size", len(app.blobCache)) + // This is a short-term cache so we delete the entry after we received it. + delete(app.blobCache, req.Height) + if len(app.blobCache) != 0 { + app.logger.Error("blob cache should be empty", "size", len(app.blobCache)) + } } for _, ev := range req.Misbehavior { @@ -562,9 +571,12 @@ func (app *Application) PrepareProposal( // Coherence: No need to call parseTx, as the check is stateless and has been performed by CheckTx txs = append(txs, tx) } + var blob []byte + var exists bool // Generate blob for the current height. - blob, exists := CreateBlob(req.Height, app.cfg.BlobMaxBytesUpdateHeight) - + if app.checkBlobHeight(req.Height, "prepare_proposal") { + blob, exists = CreateBlob(req.Height, app.cfg.BlobMaxBytesUpdateHeight) + } if app.cfg.PrepareProposalDelay != 0 { time.Sleep(app.cfg.PrepareProposalDelay) } @@ -583,23 +595,23 @@ func (app *Application) PrepareProposal( func (app *Application) ProcessProposal(_ context.Context, req *abci.RequestProcessProposal) (*abci.ResponseProcessProposal, error) { r := &abci.Request{Value: &abci.Request_ProcessProposal{ProcessProposal: &abci.RequestProcessProposal{}}} app.logger.Info("ABCIRequest", "request", r) + if app.checkBlobHeight(req.Height, "ProcessProposal") { + fmt.Println("BLOB: ", req.Blob) - fmt.Println("BLOB: ", req.Blob) - - if !VerifyBlob(req.Height, req.Blob, app.cfg.BlobMaxBytesUpdateHeight) { - app.logger.Error("invalid blob, rejecting proposal", "received height", req.Height, "received", req.Blob) - return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}, nil - } - // Blob verification - if len(req.Blob) != 0 { - // Proposal will be accepted by us and blob exists and valid, so we store it in the cache. - app.blobCache[req.Height] = req.Blob - } else { - // Proposal will be accepted by us and there is no blob. - // We make a note of it in the cache so we can inform FinalizeBlock. A real application will have better methods for this. - app.blobCache[req.Height] = noBlobBytes() + if !VerifyBlob(req.Height, req.Blob, app.cfg.BlobMaxBytesUpdateHeight) { + app.logger.Error("invalid blob, rejecting proposal", "received height", req.Height, "received", req.Blob) + return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}, nil + } + // Blob verification + if len(req.Blob) != 0 { + // Proposal will be accepted by us and blob exists and valid, so we store it in the cache. + app.blobCache[req.Height] = req.Blob + } else { + // Proposal will be accepted by us and there is no blob. + // We make a note of it in the cache so we can inform FinalizeBlock. A real application will have better methods for this. + app.blobCache[req.Height] = noBlobBytes() + } } - _, areExtensionsEnabled := app.checkHeightAndExtensions(true, req.Height, "ProcessProposal") for _, tx := range req.Txs { @@ -721,6 +733,31 @@ func (app *Application) getAppHeight() int64 { return appHeight + 1 } +func (app *Application) checkBlobHeight(height int64, callsite string) bool { + appHeight := app.getAppHeight() + if height != appHeight { + panic(fmt.Errorf( + "got unexpected height in %s request; expected %d, actual %d", + callsite, appHeight, height, + )) + } + if appHeight <= app.cfg.BlobMaxBytesUpdateHeight { + return false + } + blobMaxBytesStr := app.state.Get(prefixReservedKey + suffixBlobMaxBytes) + if len(blobMaxBytesStr) == 0 { + panic("blob max bytes not set in database") + } + blobMaxBytes, err := strconv.ParseInt(blobMaxBytesStr, 10, 64) + if err != nil { + panic(fmt.Errorf("malformed blob max bytes %q in database", blobMaxBytesStr)) + } + if blobMaxBytes == 0 { + return false + } + return true +} + func (app *Application) checkHeightAndExtensions(isPrepareProcessProposal bool, height int64, callsite string) (int64, bool) { appHeight := app.getAppHeight() if height != appHeight { diff --git a/test/e2e/generator/generate.go b/test/e2e/generator/generate.go index 08639569b4b..5687648cf7c 100644 --- a/test/e2e/generator/generate.go +++ b/test/e2e/generator/generate.go @@ -11,6 +11,7 @@ import ( "github.com/Masterminds/semver/v3" e2e "github.com/cometbft/cometbft/test/e2e/pkg" + "github.com/cometbft/cometbft/types" "github.com/cometbft/cometbft/version" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing/object" @@ -62,7 +63,8 @@ var ( voteExtensionUpdateHeight = uniformChoice{int64(-1), int64(0), int64(1)} // -1: genesis, 0: InitChain, 1: (use offset) voteExtensionEnabled = weightedChoice{true: 3, false: 1} voteExtensionHeightOffset = uniformChoice{int64(0), int64(10), int64(100)} - blobMaxBytesUpdateHeight = uniformChoice{int64(-1), int64(0), int64(10)} + blobMaxBytesUpdateHeight = uniformChoice{int64(-1), int64(0), int64(1)} + blobHeightOffset = uniformChoice{int64(0), int64(10), int64(100)} ) type generateConfig struct { @@ -160,10 +162,14 @@ func generateTestnet(r *rand.Rand, opt map[string]interface{}, upgradeVersion st manifest.BlobMaxBytesUpdateHeight = blobMaxBytesUpdateHeight.Choose(r).(int64) - if manifest.BlobMaxBytesUpdateHeight != -1 && manifest.BlobMaxBytesUpdateHeight < manifest.InitialHeight { - manifest.BlobMaxBytesUpdateHeight = manifest.InitialHeight + manifest.BlobMaxBytesUpdateHeight + if manifest.BlobMaxBytesUpdateHeight == 1 { + manifest.BlobMaxBytesUpdateHeight = manifest.InitialHeight + blobHeightOffset.Choose(r).(int64) + manifest.BlobMaxBytes = types.MaxBlobSizeBytes } + if manifest.BlobMaxBytesUpdateHeight == 0 { + manifest.BlobMaxBytes = types.MaxBlobSizeBytes + } var numSeeds, numValidators, numFulls, numLightClients int switch opt["topology"].(string) { case "single": diff --git a/test/e2e/networks/ci.toml b/test/e2e/networks/ci.toml index ba0ca0bf354..502e8ee470d 100644 --- a/test/e2e/networks/ci.toml +++ b/test/e2e/networks/ci.toml @@ -6,6 +6,7 @@ initial_height = 1000 vote_extensions_update_height = -1 vote_extensions_enable_height = 1000 blob_max_bytes_update_height = 1000 +blob_max_bytes = 819200 evidence = 5 initial_state = { initial01 = "a", initial02 = "b", initial03 = "c" } prepare_proposal_delay = "100ms" diff --git a/test/e2e/node/config.go b/test/e2e/node/config.go index 00b964caf08..f2cec36df76 100644 --- a/test/e2e/node/config.go +++ b/test/e2e/node/config.go @@ -27,6 +27,7 @@ type Config struct { VoteExtensionsEnableHeight int64 `toml:"vote_extensions_enable_height"` VoteExtensionsUpdateHeight int64 `toml:"vote_extensions_update_height"` BlobMaxBytesUpdateHeight int64 `toml:"blob_max_bytes_update_height"` + BlobMaxBytes int64 `toml:"blob_max_bytes"` } // App extracts out the application specific configuration parameters @@ -41,6 +42,7 @@ func (cfg *Config) App() *app.Config { VoteExtensionsEnableHeight: cfg.VoteExtensionsEnableHeight, VoteExtensionsUpdateHeight: cfg.VoteExtensionsUpdateHeight, BlobMaxBytesUpdateHeight: cfg.BlobMaxBytesUpdateHeight, + BlobMaxBytes: cfg.BlobMaxBytes, } } diff --git a/test/e2e/pkg/manifest.go b/test/e2e/pkg/manifest.go index 0f8c8e81196..a0960229952 100644 --- a/test/e2e/pkg/manifest.go +++ b/test/e2e/pkg/manifest.go @@ -98,11 +98,6 @@ type Manifest struct { // BlockMaxBytes specifies the maximum size in bytes of a block. This // value will be written to the genesis file of all nodes. BlockMaxBytes int64 `toml:"block_max_bytes"` - - // BlobMaxBytes specifies the maximum size in bytes of a blob. This - // value will be written to the genesis file of all nodes. - //BlobMaxBytes int64 `toml:"blob_max_bytes"` - // VoteExtensionsEnableHeight configures the first height during which // the chain will use and require vote extension data to be present // in precommit messages. @@ -117,8 +112,16 @@ type Manifest struct { ExperimentalMaxGossipConnectionsToPersistentPeers uint `toml:"experimental_max_gossip_connections_to_persistent_peers"` ExperimentalMaxGossipConnectionsToNonPersistentPeers uint `toml:"experimental_max_gossip_connections_to_non_persistent_peers"` - // Height at which we set the max blob size to be something other than 0 + // BlobMaxBytesUpdateHeight configures the height at which the + // blob max bytes consensus parameters are updated + // -1 means the max_bytes value is set at genesis + // 0 means the max_bytes value is set at InitChain + // >0 means the max_bytes value is set at the given height BlobMaxBytesUpdateHeight int64 `toml:"blob_max_bytes_update_height"` + + // BlobMaxBytes specifies the maximum size in bytes of a blob. This + // value will be written to the genesis file of all nodes. + BlobMaxBytes int64 `toml:"blob_max_bytes"` } // ManifestNode represents a node in a testnet manifest. diff --git a/test/e2e/pkg/testnet.go b/test/e2e/pkg/testnet.go index 1c18f1bd515..eed30c5645c 100644 --- a/test/e2e/pkg/testnet.go +++ b/test/e2e/pkg/testnet.go @@ -184,16 +184,13 @@ func NewTestnetFromManifest(manifest Manifest, file string, ifd InfrastructureDa ExperimentalMaxGossipConnectionsToPersistentPeers: manifest.ExperimentalMaxGossipConnectionsToPersistentPeers, ExperimentalMaxGossipConnectionsToNonPersistentPeers: manifest.ExperimentalMaxGossipConnectionsToNonPersistentPeers, BlobMaxBytesUpdateHeight: manifest.BlobMaxBytesUpdateHeight, + BlobMaxBytes: manifest.BlobMaxBytes, } if len(manifest.KeyType) != 0 { testnet.KeyType = manifest.KeyType } if manifest.InitialHeight > 0 { testnet.InitialHeight = manifest.InitialHeight - } else { - if testnet.BlobMaxBytesUpdateHeight != -1 { - testnet.BlobMaxBytesUpdateHeight = testnet.BlobMaxBytesUpdateHeight + testnet.InitialHeight - } } if testnet.ABCIProtocol == "" { @@ -367,6 +364,9 @@ func (t Testnet) Validate() error { if t.BlobMaxBytes > types.MaxBlobSizeBytes { return fmt.Errorf("value of BlobMaxBytes cannot be higher than %d", types.MaxBlobSizeBytes) } + if t.BlobMaxBytes < 0 { + return fmt.Errorf("value of BlobMaxBytes cannot be less than 0: %d", types.MaxBlobSizeBytes) + } if t.VoteExtensionsUpdateHeight < -1 { return fmt.Errorf("value of VoteExtensionsUpdateHeight must be positive, 0 (InitChain), "+ "or -1 (Genesis); update height %d", t.VoteExtensionsUpdateHeight) @@ -398,10 +398,19 @@ func (t Testnet) Validate() error { ) } } + if t.BlobMaxBytesUpdateHeight < -1 { + return fmt.Errorf("value of BlobMaxBytesUpdateHeight must be positive, 0 (InitChain), "+ + "or -1 (Genesis); update height %d", t.BlobMaxBytesUpdateHeight) + } - if !(t.BlobMaxBytesUpdateHeight == -1 || t.BlobMaxBytesUpdateHeight == t.InitialHeight || t.BlobMaxBytesUpdateHeight == t.InitialHeight+10) { - return fmt.Errorf("the value of BlobMaxBytesUpdateHeight must be either -1 (disabled) , 0 (InitChain) or 10(height 10): %d ", t.BlobMaxBytesUpdateHeight) + if t.BlobMaxBytesUpdateHeight > 0 && t.BlobMaxBytesUpdateHeight < t.InitialHeight { + return fmt.Errorf("a value of BlobMaxBytesUpdateHeight greater than 0 "+ + "must not be less than InitialHeight; "+ + "update height %d, initial height %d", + t.BlobMaxBytesUpdateHeight, t.InitialHeight, + ) } + for _, node := range t.Nodes { if err := node.Validate(t); err != nil { return fmt.Errorf("invalid node %q: %w", node.Name, err) diff --git a/test/e2e/runner/setup.go b/test/e2e/runner/setup.go index a10923cf45f..34a3094a378 100644 --- a/test/e2e/runner/setup.go +++ b/test/e2e/runner/setup.go @@ -140,12 +140,13 @@ func MakeGenesis(testnet *e2e.Testnet) (types.GenesisDoc, error) { if testnet.BlockMaxBytes != 0 { genesis.ConsensusParams.Block.MaxBytes = testnet.BlockMaxBytes } - if testnet.BlobMaxBytes != 0 { - genesis.ConsensusParams.Blob.MaxBytes = testnet.BlobMaxBytes - } if testnet.VoteExtensionsUpdateHeight == -1 { genesis.ConsensusParams.ABCI.VoteExtensionsEnableHeight = testnet.VoteExtensionsEnableHeight } + // If this is not explicitly set it defaults to 0 which means blobs are disabled + if testnet.BlobMaxBytesUpdateHeight == -1 { + genesis.ConsensusParams.Blob.MaxBytes = testnet.BlobMaxBytes + } for validator, power := range testnet.Validators { genesis.Validators = append(genesis.Validators, types.GenesisValidator{ Name: validator.Name, @@ -293,6 +294,7 @@ func MakeAppConfig(node *e2e.Node) ([]byte, error) { "vote_extensions_enable_height": node.Testnet.VoteExtensionsEnableHeight, "vote_extensions_update_height": node.Testnet.VoteExtensionsUpdateHeight, "blob_max_bytes_update_height": node.Testnet.BlobMaxBytesUpdateHeight, + "blob_max_bytes": node.Testnet.BlobMaxBytes, } switch node.ABCIProtocol { case e2e.ProtocolUNIX: diff --git a/test/e2e/tests/app_test.go b/test/e2e/tests/app_test.go index d4f0f91561e..7090cc3e718 100644 --- a/test/e2e/tests/app_test.go +++ b/test/e2e/tests/app_test.go @@ -105,6 +105,33 @@ func TestApp_Tx(t *testing.T) { }) } +func TestApp_Blob(t *testing.T) { + testNode(t, func(t *testing.T, node e2e.Node) { + client, err := node.Client() + require.NoError(t, err) + info, err := client.ABCIInfo(ctx) + require.NoError(t, err) + + // This special value should have been created by way of vote extensions + resp, err := client.ABCIQuery(ctx, "", []byte("reservedTxKey_"+"BlobMaxBytes")) + require.NoError(t, err) + + // if extensions are not enabled on the network, we should expect + // the app to have any extension value set (via a normal tx). + if node.Testnet.BlobMaxBytesUpdateHeight != 0 && + info.Response.LastBlockHeight > node.Testnet.BlobMaxBytesUpdateHeight { + valString := string(resp.Response.Value) + assert.NotEmpty(t, valString, "expected blob max bytes to be set") + blobMaxBytes, err := strconv.ParseInt(valString, 10, 64) + if err != nil { + t.Fatalf("failed to parse blob max bytes: %v", err) + } + require.NoError(t, err) + require.Equal(t, node.Testnet.BlobMaxBytes, blobMaxBytes) + } + }) +} + func TestApp_VoteExtensions(t *testing.T) { testNode(t, func(t *testing.T, node e2e.Node) { client, err := node.Client() From b9c5795214cafa6a6c1f709d514178cc2dae046c Mon Sep 17 00:00:00 2001 From: Jasmina Malicevic Date: Fri, 16 May 2025 14:57:56 +0200 Subject: [PATCH 07/15] Removed printf --- test/e2e/app/app.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/app/app.go b/test/e2e/app/app.go index 31c00cf5d23..afe8c584253 100644 --- a/test/e2e/app/app.go +++ b/test/e2e/app/app.go @@ -596,7 +596,7 @@ func (app *Application) ProcessProposal(_ context.Context, req *abci.RequestProc r := &abci.Request{Value: &abci.Request_ProcessProposal{ProcessProposal: &abci.RequestProcessProposal{}}} app.logger.Info("ABCIRequest", "request", r) if app.checkBlobHeight(req.Height, "ProcessProposal") { - fmt.Println("BLOB: ", req.Blob) + app.logger.Debug("Blob: ", req.Blob) if !VerifyBlob(req.Height, req.Blob, app.cfg.BlobMaxBytesUpdateHeight) { app.logger.Error("invalid blob, rejecting proposal", "received height", req.Height, "received", req.Blob) From 08b7d0cf5dafb792d7de0f3d260b2bf12a927809 Mon Sep 17 00:00:00 2001 From: Jasmina Malicevic Date: Fri, 16 May 2025 14:59:21 +0200 Subject: [PATCH 08/15] Moved BlobMaxBytes var close to BlobMaxBytesUpdateHeight --- test/e2e/pkg/testnet.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/pkg/testnet.go b/test/e2e/pkg/testnet.go index eed30c5645c..ef78bf51311 100644 --- a/test/e2e/pkg/testnet.go +++ b/test/e2e/pkg/testnet.go @@ -93,12 +93,12 @@ type Testnet struct { LogFormat string Prometheus bool BlockMaxBytes int64 - BlobMaxBytes int64 VoteExtensionsEnableHeight int64 VoteExtensionsUpdateHeight int64 ExperimentalMaxGossipConnectionsToPersistentPeers uint ExperimentalMaxGossipConnectionsToNonPersistentPeers uint BlobMaxBytesUpdateHeight int64 + BlobMaxBytes int64 } // Node represents a CometBFT node in a testnet. From dab4aa4b66a1d282d0246891699f8efcf19e5a13 Mon Sep 17 00:00:00 2001 From: Jasmina Malicevic Date: Fri, 16 May 2025 18:21:53 +0200 Subject: [PATCH 09/15] Do not use blob in UTs --- consensus/byzantine_test.go | 2 +- test/e2e/app/app.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/consensus/byzantine_test.go b/consensus/byzantine_test.go index 9b21cc2b385..d5f748b12eb 100644 --- a/consensus/byzantine_test.go +++ b/consensus/byzantine_test.go @@ -304,7 +304,7 @@ func TestByzantineConflictingProposalsWithPartition(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - app := newKVStoreWithBlob + app := newKVStore css, cleanup := randConsensusNet(t, N, "consensus_byzantine_test", newMockTickerFunc(false), app) defer cleanup() diff --git a/test/e2e/app/app.go b/test/e2e/app/app.go index afe8c584253..4e25d14049d 100644 --- a/test/e2e/app/app.go +++ b/test/e2e/app/app.go @@ -143,7 +143,7 @@ func DefaultConfig(dir string) *Config { // 4 - blob is the size of MaxBlobSizeBytes and contains height truncated into two bytes repeated. func blobOracle(height int64, blobMaxBytesUpdateHeight int64) ([]byte, bool) { - switch height % 4 { + switch height % 5 { case 1: truncatedHeight := byte(height % 0x100) data := bytes.Repeat([]byte{truncatedHeight}, 8) From 03c2bae89b5dbf75a27bb8e0443635d545e984fe Mon Sep 17 00:00:00 2001 From: Jasmina Malicevic Date: Fri, 16 May 2025 18:32:19 +0200 Subject: [PATCH 10/15] Fix UT to use blobs and set maxBlobBytes to 800kb --- consensus/byzantine_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/consensus/byzantine_test.go b/consensus/byzantine_test.go index d5f748b12eb..113c723d495 100644 --- a/consensus/byzantine_test.go +++ b/consensus/byzantine_test.go @@ -304,7 +304,7 @@ func TestByzantineConflictingProposalsWithPartition(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - app := newKVStore + app := newKVStoreWithBlob css, cleanup := randConsensusNet(t, N, "consensus_byzantine_test", newMockTickerFunc(false), app) defer cleanup() @@ -353,6 +353,7 @@ func TestByzantineConflictingProposalsWithPartition(t *testing.T) { require.NoError(t, err) conR := NewReactor(css[i], true) // so we don't start the consensus states + conR.conS.state.ConsensusParams.Blob.MaxBytes = types.MaxBlobSizeBytes conR.SetLogger(logger.With("validator", i)) conR.SetEventBus(eventBus) From 1448486b2b4398246e39b939b87a8b9840ef800b Mon Sep 17 00:00:00 2001 From: Jasmina Malicevic Date: Fri, 16 May 2025 18:45:06 +0200 Subject: [PATCH 11/15] Fix typo --- test/e2e/app/app.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/app/app.go b/test/e2e/app/app.go index 4e25d14049d..2557e8af000 100644 --- a/test/e2e/app/app.go +++ b/test/e2e/app/app.go @@ -217,7 +217,7 @@ func (app *Application) Info(context.Context, *abci.RequestInfo) (*abci.Response }, nil } -// Expeected to be called with params set +// Expected to be called with params set func (app *Application) updateBlobMaxBytes(currentHeight int64, params *cmtproto.ConsensusParams) *cmtproto.ConsensusParams { if params == nil { params = &cmtproto.ConsensusParams{} From 368043645bd8541f7b2c77519b5ca23b6546c8c5 Mon Sep 17 00:00:00 2001 From: Jasmina Malicevic Date: Mon, 19 May 2025 17:43:08 +0200 Subject: [PATCH 12/15] Applied review comments --- test/e2e/app/app.go | 61 ++++++++++++++++++++++----------------------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/test/e2e/app/app.go b/test/e2e/app/app.go index 2557e8af000..e7d4a219e97 100644 --- a/test/e2e/app/app.go +++ b/test/e2e/app/app.go @@ -135,13 +135,13 @@ func DefaultConfig(dir string) *Config { // blobOracle can tell you the expected blob on a specific height. // It can be used to add blobs to proposals or simulate network peer responses. -// Blob properties: height modulo 4 +// Blob properties: height modulo 5 // 0 - no blob // 1 - blob is exactly 8 bytes long and contains the height truncated into a byte 8 times // 2 - blob is empty ([]byte{}) // 3 - blob is variable length string that contains "BLOBXXX" where XXX is height multiplied by 0x80 in hex // 4 - blob is the size of MaxBlobSizeBytes and contains height truncated into two bytes repeated. -func blobOracle(height int64, blobMaxBytesUpdateHeight int64) ([]byte, bool) { +func blobOracle(height int64) ([]byte, bool) { switch height % 5 { case 1: @@ -154,7 +154,7 @@ func blobOracle(height int64, blobMaxBytesUpdateHeight int64) ([]byte, bool) { return []byte(fmt.Sprintf("BLOB%x", height*0x80)), true case 4: truncatedHeight := byte(height % 0x10000) - data := bytes.Repeat([]byte{truncatedHeight}, cmttypes.MaxBlobSizeBytes/4) + data := bytes.Repeat([]byte{truncatedHeight}, cmttypes.MaxBlobSizeBytes) return data, true } return nil, false @@ -171,14 +171,14 @@ func isBlob(blob []byte) bool { } // CreateBlob creates a new blob for a proposal at this height. -func CreateBlob(height int64, blobMaxBytesUpdateHeight int64) ([]byte, bool) { - return blobOracle(height, blobMaxBytesUpdateHeight) +func CreateBlob(height int64) ([]byte, bool) { + return blobOracle(height) } -func VerifyBlob(height int64, blob []byte, blobMaxBytesUpdateHeight int64) bool { +func VerifyBlob(height int64, blob []byte) bool { // The application might have internal checks that ensures a blob is valid. // In this test application, we know what a valid blob should look like. - validBlob, exist := blobOracle(height, blobMaxBytesUpdateHeight) + validBlob, exist := blobOracle(height) if !exist { // The application received a blob at a height where no blob should be. return len(blob) == 0 @@ -219,13 +219,14 @@ func (app *Application) Info(context.Context, *abci.RequestInfo) (*abci.Response // Expected to be called with params set func (app *Application) updateBlobMaxBytes(currentHeight int64, params *cmtproto.ConsensusParams) *cmtproto.ConsensusParams { - if params == nil { - params = &cmtproto.ConsensusParams{} - } + if app.cfg.BlobMaxBytesUpdateHeight == currentHeight { app.logger.Info("updating blob max bytes on the fly", "current_height", currentHeight, "blob_max_bytes_update_height", app.cfg.BlobMaxBytesUpdateHeight) + if params == nil { + params = &cmtproto.ConsensusParams{} + } params.Blob = &cmtproto.BlobParams{ MaxBytes: app.cfg.BlobMaxBytes, } @@ -317,7 +318,7 @@ func (app *Application) CheckTx(_ context.Context, req *abci.RequestCheckTx) (*a // It returns a boolean indicating if a blob exists (either retrieved from the simaulted network or from the // local cache) and the blob itself. func (app *Application) GetBlob(height int64) ([]byte, bool, error) { - if !app.checkBlobHeight(height, "getBlob") { + if !app.checkBlobEnabled("getBlob") { // Blob max bytes is still 0 so we cannot send a blob return nil, false, nil } @@ -331,7 +332,7 @@ func (app *Application) GetBlob(height int64) ([]byte, bool, error) { } // If not found, reach out to other peers and retrieve it through them. time.Sleep(100 * time.Millisecond) // Add simulated network delay - blobFromPeer, exist := blobOracle(height, app.cfg.BlobMaxBytesUpdateHeight) + blobFromPeer, exist := blobOracle(height) if exist && !isBlob(blobFromPeer) { return nil, false, nil } @@ -357,12 +358,12 @@ func (app *Application) FinalizeBlock(_ context.Context, req *abci.RequestFinali } // Verify blob. - if app.checkBlobHeight(req.Height, "FinalizeBlock") { + if app.checkBlobEnabled("FinalizeBlock") { blob, exists, err := app.GetBlob(req.Height) if err != nil { return nil, fmt.Errorf("could not fetch blob for height %d, %s", req.Height, err.Error()) } - if exists && !VerifyBlob(req.Height, blob, app.cfg.BlobMaxBytesUpdateHeight) { + if exists && !VerifyBlob(req.Height, blob) { return nil, fmt.Errorf("blob verification failed for height %d", req.Height) } @@ -574,8 +575,11 @@ func (app *Application) PrepareProposal( var blob []byte var exists bool // Generate blob for the current height. - if app.checkBlobHeight(req.Height, "prepare_proposal") { - blob, exists = CreateBlob(req.Height, app.cfg.BlobMaxBytesUpdateHeight) + if app.checkBlobEnabled("prepare_proposal") { + blob, exists = CreateBlob(req.Height) + if len(blob) != 0 { + app.logger.Debug("Received blob", "Blob: ", blob[min(len(blob)-1, 10)]) + } } if app.cfg.PrepareProposalDelay != 0 { time.Sleep(app.cfg.PrepareProposalDelay) @@ -584,7 +588,7 @@ func (app *Application) PrepareProposal( if !exists { return &abci.ResponsePrepareProposal{Txs: txs}, nil } - fmt.Println("BLOB_1: ", blob) + return &abci.ResponsePrepareProposal{Txs: txs, Blob: blob}, nil } @@ -595,15 +599,15 @@ func (app *Application) PrepareProposal( func (app *Application) ProcessProposal(_ context.Context, req *abci.RequestProcessProposal) (*abci.ResponseProcessProposal, error) { r := &abci.Request{Value: &abci.Request_ProcessProposal{ProcessProposal: &abci.RequestProcessProposal{}}} app.logger.Info("ABCIRequest", "request", r) - if app.checkBlobHeight(req.Height, "ProcessProposal") { - app.logger.Debug("Blob: ", req.Blob) + if app.checkBlobEnabled("ProcessProposal") { - if !VerifyBlob(req.Height, req.Blob, app.cfg.BlobMaxBytesUpdateHeight) { + if !VerifyBlob(req.Height, req.Blob) { app.logger.Error("invalid blob, rejecting proposal", "received height", req.Height, "received", req.Blob) return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}, nil } // Blob verification if len(req.Blob) != 0 { + app.logger.Debug("Received blob", "Blob: ", req.Blob[min(len(req.Blob)-1, 10)]) // Proposal will be accepted by us and blob exists and valid, so we store it in the cache. app.blobCache[req.Height] = req.Blob } else { @@ -611,6 +615,11 @@ func (app *Application) ProcessProposal(_ context.Context, req *abci.RequestProc // We make a note of it in the cache so we can inform FinalizeBlock. A real application will have better methods for this. app.blobCache[req.Height] = noBlobBytes() } + } else { + if len(req.Blob) != 0 { + app.logger.Error("received a blob when blobs are disabled, rejecting proposal", "received height", req.Height, "received", req.Blob) + return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}, nil + } } _, areExtensionsEnabled := app.checkHeightAndExtensions(true, req.Height, "ProcessProposal") @@ -733,17 +742,7 @@ func (app *Application) getAppHeight() int64 { return appHeight + 1 } -func (app *Application) checkBlobHeight(height int64, callsite string) bool { - appHeight := app.getAppHeight() - if height != appHeight { - panic(fmt.Errorf( - "got unexpected height in %s request; expected %d, actual %d", - callsite, appHeight, height, - )) - } - if appHeight <= app.cfg.BlobMaxBytesUpdateHeight { - return false - } +func (app *Application) checkBlobEnabled(callsite string) bool { blobMaxBytesStr := app.state.Get(prefixReservedKey + suffixBlobMaxBytes) if len(blobMaxBytesStr) == 0 { panic("blob max bytes not set in database") From 6ade5da8e6b85c323924fb7fa43554b4568ae63f Mon Sep 17 00:00:00 2001 From: Jasmina Malicevic Date: Tue, 20 May 2025 12:43:50 +0200 Subject: [PATCH 13/15] Fix UTs --- consensus/common_test.go | 10 +++++++--- consensus/reactor_test.go | 2 +- consensus/state_test.go | 8 ++++---- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/consensus/common_test.go b/consensus/common_test.go index e96e49e312d..802b8cfaa5f 100644 --- a/consensus/common_test.go +++ b/consensus/common_test.go @@ -513,13 +513,13 @@ func loadPrivValidator(config *cfg.Config) *privval.FilePV { } func randState(nValidators int) (*State, []*validatorStub) { - return randStateWithApp(nValidators, kvstore.NewInMemoryApplication()) + return randStateWithApp(nValidators, kvstore.NewInMemoryApplication(), false) } func randStateWithBlob(nValidators int) (*State, []*validatorStub) { app := kvstore.NewInMemoryApplication() app.SetGenerateBlobs() - return randStateWithApp(nValidators, app) + return randStateWithApp(nValidators, app, true) } func randStateWithAppWithHeight( @@ -531,8 +531,11 @@ func randStateWithAppWithHeight( c.ABCI.VoteExtensionsEnableHeight = height return randStateWithAppImpl(nValidators, app, c) } -func randStateWithApp(nValidators int, app abci.Application) (*State, []*validatorStub) { +func randStateWithApp(nValidators int, app abci.Application, blob bool) (*State, []*validatorStub) { c := test.ConsensusParams() + if blob { + c.Blob.MaxBytes = 100 + } return randStateWithAppImpl(nValidators, app, c) } @@ -877,6 +880,7 @@ func randConsensusNetWithPeers( appFunc func(string) abci.Application, ) ([]*State, *types.GenesisDoc, *cfg.Config, cleanupFunc) { c := test.ConsensusParams() + c.Blob.MaxBytes = 100 genDoc, privVals := randGenesisDoc(nValidators, false, testMinPower, c) css := make([]*State, nPeers) logger := consensusLogger() diff --git a/consensus/reactor_test.go b/consensus/reactor_test.go index 815e0906bef..9767a6b0b35 100644 --- a/consensus/reactor_test.go +++ b/consensus/reactor_test.go @@ -421,8 +421,8 @@ func TestReactorRecordsVotesAndBlockPartsAndBlobParts(t *testing.T) { n := 4 css, _, _, cleanup := randConsensusNetWithPeers(t, n, n, "consensus_reactor_test", newMockTickerFunc(true), newPersistentKVStoreWithPathAndBlob) - defer cleanup() + reactors, blocksSubs, eventBuses := startConsensusNet(t, css, n) defer stopConsensusNet(log.TestingLogger(), reactors, eventBuses) diff --git a/consensus/state_test.go b/consensus/state_test.go index 1aae3d0651d..44d5e6794c7 100644 --- a/consensus/state_test.go +++ b/consensus/state_test.go @@ -1655,7 +1655,7 @@ func TestProcessProposalAccept(t *testing.T) { } m.On("ProcessProposal", mock.Anything, mock.Anything).Return(&abci.ResponseProcessProposal{Status: status}, nil) m.On("PrepareProposal", mock.Anything, mock.Anything).Return(&abci.ResponsePrepareProposal{}, nil).Maybe() - cs1, _ := randStateWithApp(4, m) + cs1, _ := randStateWithApp(4, m, false) height, round := cs1.Height, cs1.Round proposalCh := subscribe(cs1.eventBus, types.EventQueryCompleteProposal) @@ -1796,7 +1796,7 @@ func TestVerifyVoteExtensionNotCalledOnAbsentPrecommit(t *testing.T) { }, nil) m.On("FinalizeBlock", mock.Anything, mock.Anything).Return(&abci.ResponseFinalizeBlock{}, nil).Maybe() m.On("Commit", mock.Anything, mock.Anything).Return(&abci.ResponseCommit{}, nil).Maybe() - cs1, vss := randStateWithApp(4, m) + cs1, vss := randStateWithApp(4, m, false) height, round := cs1.Height, cs1.Round cs1.state.ConsensusParams.ABCI.VoteExtensionsEnableHeight = cs1.Height @@ -1890,7 +1890,7 @@ func TestPrepareProposalReceivesVoteExtensions(t *testing.T) { m.On("Commit", mock.Anything, mock.Anything).Return(&abci.ResponseCommit{}, nil).Maybe() m.On("FinalizeBlock", mock.Anything, mock.Anything).Return(&abci.ResponseFinalizeBlock{}, nil) - cs1, vss := randStateWithApp(4, m) + cs1, vss := randStateWithApp(4, m, false) height, round := cs1.Height, cs1.Round newRoundCh := subscribe(cs1.eventBus, types.EventQueryNewRound) @@ -1994,7 +1994,7 @@ func TestFinalizeBlockCalled(t *testing.T) { m.On("FinalizeBlock", mock.Anything, mock.Anything).Return(r, nil).Maybe() m.On("Commit", mock.Anything, mock.Anything).Return(&abci.ResponseCommit{}, nil).Maybe() - cs1, vss := randStateWithApp(4, m) + cs1, vss := randStateWithApp(4, m, false) height, round := cs1.Height, cs1.Round proposalCh := subscribe(cs1.eventBus, types.EventQueryCompleteProposal) From be25865342bd8f9409bb30d9feece96f59b793c6 Mon Sep 17 00:00:00 2001 From: Jasmina Malicevic Date: Tue, 20 May 2025 12:47:57 +0200 Subject: [PATCH 14/15] Update comment Co-authored-by: Sergio Mena --- test/e2e/app/app.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/app/app.go b/test/e2e/app/app.go index e7d4a219e97..65dd1003042 100644 --- a/test/e2e/app/app.go +++ b/test/e2e/app/app.go @@ -319,7 +319,7 @@ func (app *Application) CheckTx(_ context.Context, req *abci.RequestCheckTx) (*a // local cache) and the blob itself. func (app *Application) GetBlob(height int64) ([]byte, bool, error) { if !app.checkBlobEnabled("getBlob") { - // Blob max bytes is still 0 so we cannot send a blob + // Blob max bytes is 0 so we cannot send a blob return nil, false, nil } // First check the local cache From 217f9b7175310978052a4e061b9f98c09b5ee6ba Mon Sep 17 00:00:00 2001 From: Jasmina Malicevic Date: Tue, 20 May 2025 12:48:13 +0200 Subject: [PATCH 15/15] Update comment Co-authored-by: Sergio Mena --- test/e2e/app/app.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/app/app.go b/test/e2e/app/app.go index 65dd1003042..bc3336a17d5 100644 --- a/test/e2e/app/app.go +++ b/test/e2e/app/app.go @@ -121,7 +121,7 @@ type Config struct { // >0 means the max_bytes value is set at the given height BlobMaxBytesUpdateHeight int64 `toml:"blob_max_bytes_update_height"` - // BlobMaxBytes is the values of max bytes for blobs + // BlobMaxBytes is the value of max bytes for blobs BlobMaxBytes int64 `toml:"blob_max_bytes"` }