Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,69 @@ graph TD
- **State Management**: Manages blockchain state including validators and consensus parameters.
- **P2P Communication**: Implements transaction gossip across the network.
- **RPC Endpoints**: Provides compatible API endpoints for clients to interact with.
- **Genesis DA Height Auto-Configuration**: Automatically configures the Data Availability (DA) start height for new chains at genesis by querying the DA node, reducing manual configuration steps for non-aggregator nodes.

## ABCI Compatibility

This adapter achieves compatibility with ABCI by calling the appropriate methods on the ABCI application during the execution lifecycle. It implements the necessary interfaces to ensure that transactions are processed correctly, blocks are finalized, and state is committed.

Note, that because of the nature of ev-node (single proposer), **Vote Extensions are not supported**. The adapter will not call the `VoteExtensions` methods on the ABCI application, and any logic related to vote extensions should be handled separately or not used at all.

## Genesis DA Height Auto-Configuration

When starting a new chain at genesis, `ev-abci` can automatically configure the Data Availability (DA) start height to optimize the startup process. This feature works by:

1. **Detecting Genesis Conditions**: The system checks if the chain is starting at genesis (initial height = 1) and the DA start height is not manually configured (set to 0).

2. **Node Type Verification**: The optimization only applies to non-aggregator nodes, as aggregator nodes have different DA requirements.

3. **Automatic DA Height Retrieval**: When conditions are met, the system queries the DA node using the `GetGenesisDaHeight` RPC method to obtain the appropriate starting DA height.

4. **Seamless Configuration**: The retrieved DA height is automatically applied to the node configuration, eliminating the need for manual DA height configuration in genesis scenarios.

### Benefits

- **Reduced Configuration Complexity**: Operators don't need to manually determine and set the DA start height for new chains.
- **Optimized Startup**: Automatically uses the correct DA height, preventing unnecessary synchronization of old DA blocks.
- **Error Prevention**: Eliminates misconfiguration issues related to DA start height settings.

### Conditions for Activation

The genesis DA height auto-configuration activates only when **all** of the following conditions are met:

- The chain is at genesis (initial height = 1)
- The DA start height is unset (configured as 0)
- The node is **not** an aggregator
- The DA node supports the `GetGenesisDaHeight` RPC method

If any condition is not met, the system logs the reason and continues with the existing configuration without modification.

### Example Usage

When starting a new chain with `ev-abci`, the system will automatically attempt to optimize the DA configuration:

```bash
# Starting a new chain at genesis (no manual DA configuration needed)
./app start --rollkit.node.aggregator=false

# System logs will show:
# INFO attempting to prefill genesis start DA height from node
# INFO successfully retrieved genesis DA height from node genesis_da_height=12345
# INFO genesis start DA height has been automatically set start_height=12345
```

For cases where auto-configuration is skipped:

```bash
# Aggregator nodes skip the optimization
./app start --rollkit.node.aggregator=true
# LOG: skipping genesis DA height prefill: node is an aggregator

# Chains with pre-configured DA height skip the optimization
./app start --rollkit.da.start_height=100
# LOG: skipping genesis DA height prefill: DA start height already set start_height=100
```

## Installation

```bash
Expand Down
141 changes: 141 additions & 0 deletions server/genesis_da_height_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package server

import (
"context"
"testing"
"time"

"cosmossdk.io/log"

Check failure on line 8 in server/genesis_da_height_test.go

View workflow job for this annotation

GitHub Actions / lint / golangci-lint

File is not properly formatted (gci)
"github.com/evstack/ev-node/pkg/config"
"github.com/evstack/ev-node/pkg/genesis"
"github.com/stretchr/testify/assert"
)

func TestPrefillGenesisDaHeight(t *testing.T) {
ctx := context.Background()
logger := log.NewNopLogger()

// Create test rollkit genesis at height 1
rollkitGenesis := genesis.NewGenesis(
"test-chain",
1, // Initial height 1 = at genesis
time.Now(),
[]byte("test-sequencer-addr"),
)

tests := []struct {
name string
cfg *config.Config
migrationGenesis *evolveMigrationGenesis
rollkitGenesis *genesis.Genesis
expectSkip bool
expectReason string
}{
{
name: "should skip when node is aggregator",
cfg: &config.Config{
Node: config.NodeConfig{Aggregator: true},
DA: config.DAConfig{StartHeight: 0},
},
rollkitGenesis: &rollkitGenesis,
expectSkip: true,
expectReason: "node is aggregator",
},
{
name: "should skip when DA start height already set",
cfg: &config.Config{
Node: config.NodeConfig{Aggregator: false},
DA: config.DAConfig{StartHeight: 100},
},
rollkitGenesis: &rollkitGenesis,
expectSkip: true,
expectReason: "DA start height already set",
},
{
name: "should skip when migration scenario",
cfg: &config.Config{
Node: config.NodeConfig{Aggregator: false},
DA: config.DAConfig{StartHeight: 0},
},
migrationGenesis: &evolveMigrationGenesis{
ChainID: "test-chain",
InitialHeight: 1,
},
rollkitGenesis: &rollkitGenesis,
expectSkip: true,
expectReason: "migration scenario",
},
{
name: "should skip when not at genesis",
cfg: &config.Config{
Node: config.NodeConfig{Aggregator: false},
DA: config.DAConfig{StartHeight: 0},
},
rollkitGenesis: &genesis.Genesis{
ChainID: "test-chain",
InitialHeight: 100, // Not at genesis
StartTime: time.Now(),
ProposerAddress: []byte("test-sequencer-addr"),
},
expectSkip: true,
expectReason: "not at genesis",
},
{
name: "should attempt prefill when conditions are met",
cfg: &config.Config{
Node: config.NodeConfig{Aggregator: false},
DA: config.DAConfig{StartHeight: 0},
},
rollkitGenesis: &rollkitGenesis,
expectSkip: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Reset config for each test
originalStartHeight := tt.cfg.DA.StartHeight

// Call the prefill function
err := prefillGenesisDaHeight(
ctx,
tt.cfg,
nil, // DA client is nil, but we expect early returns for most tests
tt.rollkitGenesis,
tt.migrationGenesis,
logger,
)

if tt.expectSkip {
// For skip scenarios, the config should not be modified
assert.Equal(t, originalStartHeight, tt.cfg.DA.StartHeight,
"DA start height should not be modified when skipping")

// Error should be nil for skip scenarios
assert.NoError(t, err, "should not return error for valid skip scenarios")
} else {
// For non-skip scenarios with nil DA client, we expect an error
// since getGenesisDaHeight will fail with "not yet implemented"
assert.Error(t, err, "should return error when GetGenesisDaHeight is not available")
assert.Contains(t, err.Error(), "not yet implemented",
"error should indicate GetGenesisDaHeight is not yet implemented")

// Config should not be modified when RPC fails
assert.Equal(t, originalStartHeight, tt.cfg.DA.StartHeight,
"DA start height should not be modified when RPC fails")
}
})
}
}

func TestGetGenesisDaHeight(t *testing.T) {
ctx := context.Background()

// Test that the function returns the expected error until the RPC is implemented
height, err := getGenesisDaHeight(ctx, nil)

assert.Error(t, err, "should return error when GetGenesisDaHeight RPC is not available")
assert.Contains(t, err.Error(), "not yet implemented",
"error should indicate the method is not yet implemented")
assert.Equal(t, uint64(0), height, "height should be 0 when error occurs")
}
102 changes: 102 additions & 0 deletions server/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,16 @@ func setupNodeAndExecutor(
return nil, nil, cleanupFn, fmt.Errorf("failed to create DA client: %w", err)
}

// Pre-fill genesis start DA height when possible
// This optimization fetches the first DA height from the node when:
// 1. The chain is at genesis (not a migration scenario and initial height is 1)
// 2. The genesis start DA height is unset (0)
// 3. The node is not an aggregator
if err := prefillGenesisDaHeight(ctx, &rollkitcfg, daClient, rollkitGenesis, migrationGenesis, sdkLogger); err != nil {
sdkLogger.Error("failed to prefill genesis DA height", "error", err)
// Don't fail startup for this optimization, just log the error
}

singleMetrics, err := single.NopMetrics()
if err != nil {
return nil, nil, cleanupFn, err
Expand Down Expand Up @@ -715,3 +725,95 @@ func createEvolveGenesisFromCometBFT(cmtGenDoc *cmttypes.GenesisDoc) *genesis.Ge

return &rollkitGenesis
}

// prefillGenesisDaHeight automatically sets the genesis start DA height when possible.
// This optimization fetches the first DA height from the node and sets it in the configuration
// when the chain is at genesis, the DA start height is unset, and the node is not an aggregator.
//
// Parameters:
// - ctx: Context for the operation
// - cfg: Rollkit configuration (will be modified in-place if conditions are met)
// - daClient: DA client to query for genesis DA height
// - rollkitGenesis: The rollkit genesis information
// - migrationGenesis: Migration genesis (nil if not a migration scenario)
// - logger: Logger for information and error messages
//
// This function will not cause startup to fail if it encounters errors; errors are logged
// and the startup continues with the original configuration.
func prefillGenesisDaHeight(
ctx context.Context,
cfg *config.Config,
daClient *jsonrpc.Client,
rollkitGenesis *genesis.Genesis,
migrationGenesis *evolveMigrationGenesis,
logger log.Logger,
) error {
// Only proceed if node is NOT an aggregator
if cfg.Node.Aggregator {
logger.Debug("skipping genesis DA height prefill: node is an aggregator")
return nil
}

// Only proceed if DA start height is unset (0)
if cfg.DA.StartHeight != 0 {
logger.Debug("skipping genesis DA height prefill: DA start height already set",
"start_height", cfg.DA.StartHeight)
return nil
}

// Only proceed if this is not a migration scenario and we're at genesis
if migrationGenesis != nil {
logger.Debug("skipping genesis DA height prefill: migration scenario detected")
return nil
}

// Check if we're at genesis (initial height is 1)
if rollkitGenesis.InitialHeight != 1 {
logger.Debug("skipping genesis DA height prefill: not at genesis",
"initial_height", rollkitGenesis.InitialHeight)
return nil
}

logger.Info("attempting to prefill genesis start DA height from node")

// Call GetGenesisDaHeight RPC method
// Note: This method is expected to be available in the ev-node RPC interface
// as mentioned in the issue description
genesisDaHeight, err := getGenesisDaHeight(ctx, daClient)
if err != nil {
return fmt.Errorf("failed to get genesis DA height from node: %w", err)
}

if genesisDaHeight == 0 {
logger.Warn("genesis DA height returned from node is 0, not updating configuration")
return nil
}

logger.Info("successfully retrieved genesis DA height from node",
"genesis_da_height", genesisDaHeight)

// Update the configuration
cfg.DA.StartHeight = genesisDaHeight

logger.Info("genesis start DA height has been automatically set",
"start_height", genesisDaHeight)

return nil
}

// getGenesisDaHeight calls the GetGenesisDaHeight RPC method on the DA node.
// This is a wrapper function that handles the RPC call to retrieve the first
// DA height from the node, as mentioned in issue ev-node/pull/2614.
func getGenesisDaHeight(ctx context.Context, daClient *jsonrpc.Client) (uint64, error) {
// TODO: Once the GetGenesisDaHeight RPC method is available in the ev-node
// jsonrpc API (as mentioned in ev-node/pull/2614), this implementation should
// be updated to make the actual RPC call.
//
// The expected implementation would be:
// if methodExists(&daClient.DA, "GetGenesisDaHeight") {
// return daClient.DA.GetGenesisDaHeight(ctx)
// }
//
// For now, we return an informative error that doesn't break startup
return 0, fmt.Errorf("GetGenesisDaHeight RPC method is not yet implemented in ev-node jsonrpc API - see ev-node/pull/2614")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

implement it.

}
Loading