Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(runtime/v2): bring back concurrent export genesis in v2 #21554

Merged
merged 14 commits into from
Sep 24, 2024

Conversation

akhilkumarpilli
Copy link
Contributor

@akhilkumarpilli akhilkumarpilli commented Sep 5, 2024

Description

Closes: #21303


Author Checklist

All items are required. Please add a note to the item if the item is not applicable and
please add links to any relevant follow up issues.

I have...

  • included the correct type prefix in the PR title, you can find examples of the prefixes below:
  • confirmed ! in the type prefix if API or client breaking change
  • targeted the correct branch (see PR Targeting)
  • provided a link to the relevant issue or specification
  • reviewed "Files changed" and left comments if necessary
  • included the necessary unit and integration tests
  • added a changelog entry to CHANGELOG.md
  • updated the relevant documentation or specification, including comments for documenting Go code
  • confirmed all CI checks have passed

Reviewers Checklist

All items are required. Please add a note if the item is not applicable and please add
your handle next to the items reviewed if you only reviewed selected items.

Please see Pull Request Reviewer section in the contributing guide for more information on how to review a pull request.

I have...

  • confirmed the correct type prefix in the PR title
  • confirmed all author checklist items have been addressed
  • reviewed state machine logic, API design and naming, documentation is accurate, tests and test coverage

Summary by CodeRabbit

  • New Features

    • Enhanced the genesis export functionality to ensure accuracy based on the application state at a specified version.
    • Introduced asynchronous processing for exporting genesis data, improving performance and allowing concurrent module exports.
  • Bug Fixes

    • Improved error handling during the genesis export process, ensuring that failures are reported more effectively and simplifying the control flow.

Copy link
Contributor

coderabbitai bot commented Sep 5, 2024

Walkthrough

Walkthrough

The changes involve modifying the ExportGenesis and ExportGenesisForModules functions to enhance the genesis export process by incorporating state retrieval at a specified version and enabling concurrent execution. The ExportGenesis function now retrieves the application state at a specific version before exporting, while ExportGenesisForModules has been refactored to support asynchronous operations, improving error handling and performance.

Changes

Files Change Summary
runtime/v2/builder.go Updated ExportGenesis to retrieve application state at a specified version before exporting genesis, enhancing the accuracy of the export process.
runtime/v2/manager.go Refactored ExportGenesisForModules to accept new parameters for asynchronous execution, using goroutines to manage module exports concurrently and improve error handling.

Assessment against linked issues

Objective Addressed Explanation
Bring back concurrent export of genesis on v2 (#21303)
Change ModuleManager to accept stf as param (#21303)

Possibly related PRs

  • refactor(stf): remove RunWithCtx #21739: The changes in the InitGenesis function to return a store.WriterMap are related to the modifications in the ExportGenesis method, as both involve handling state management during the genesis process.

Suggested labels

C:server/v2, C:server/v2 stf, C:server/v2 appmanager, C:server/v2 cometbft

Suggested reviewers

  • sontrinh16
  • facundomedica
  • hieuvubk
  • testinginprod

Recent review details

Configuration used: .coderabbit.yml
Review profile: CHILL

Commits

Files that changed from the base of the PR and between 4d1a612 and 943a6fe.

Files selected for processing (1)
  • runtime/v2/builder.go (1 hunks)
Files skipped from review as they are similar to previous changes (1)
  • runtime/v2/builder.go

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

Share
Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    -- I pushed a fix in commit <commit_id>, please review it.
    -- Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    -- @coderabbitai generate unit testing code for this file.
    -- @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    -- @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    -- @coderabbitai read src/utils.ts and generate unit testing code.
    -- @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    -- @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@github-actions github-actions bot added C:server/v2 Issues related to server/v2 C:server/v2 stf labels Sep 5, 2024
runtime/v2/manager.go Fixed Show fixed Hide fixed
runtime/v2/manager.go Fixed Show fixed Hide fixed
@github-actions github-actions bot removed C:server/v2 Issues related to server/v2 C:server/v2 stf labels Sep 5, 2024
@akhilkumarpilli akhilkumarpilli changed the title (WIP) fix: bring back concurrent export genesis in v2 fix: bring back concurrent export genesis in v2 Sep 5, 2024
runtime/v2/manager.go Fixed Show fixed Hide fixed
@akhilkumarpilli akhilkumarpilli marked this pull request as ready for review September 5, 2024 13:34
Copy link
Contributor

github-actions bot commented Sep 5, 2024

@akhilkumarpilli your pull request is missing a changelog!

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Review details

Configuration used: .coderabbit.yml
Review profile: CHILL

Commits

Files that changed from the base of the PR and between 6b20ef7 and d4ca906.

Files selected for processing (2)
  • runtime/v2/builder.go (1 hunks)
  • runtime/v2/manager.go (4 hunks)
Additional context used
Path-based instructions (2)
runtime/v2/builder.go (1)

Pattern **/*.go: Review the Golang code for conformity with the Uber Golang style guide, highlighting any deviations.

runtime/v2/manager.go (1)

Pattern **/*.go: Review the Golang code for conformity with the Uber Golang style guide, highlighting any deviations.

GitHub Check: CodeQL
runtime/v2/manager.go

[notice] 231-242: Spawning a Go routine
Spawning a Go routine may be a possible source of non-determinism

Additional comments not posted (1)
runtime/v2/manager.go (1)

Line range hint 198-253: Review of ExportGenesisForModules Function Changes

The changes to the ExportGenesisForModules function introduce concurrency through goroutines, which can enhance performance but also bring challenges such as non-determinism and potential race conditions. Here are some specific observations and suggestions:

  1. Function Signature Change: The addition of appStf *stf.STF[T] and state store.ReaderMap to the function signature is a significant change. Ensure that all calls to this function across the codebase have been updated to pass these new parameters.

  2. Concurrency with Goroutines: The use of goroutines to handle exports concurrently is a major shift towards improving performance. However, it's crucial to ensure that the shared resources, particularly state, are handled safely to avoid race conditions.

  3. Error Handling: The structured approach to error handling within goroutines is commendable. It ensures that errors from individual module exports are captured and handled appropriately.

  4. Potential Non-Determinism: As noted by the security bot, the introduction of goroutines could lead to non-deterministic behavior, especially in a distributed system like a blockchain. It's important to ensure that the order of operations and the state consistency are preserved across different executions.

  5. Testing: Given the complexity introduced by these changes, comprehensive testing is crucial. Consider adding more unit tests and integration tests to cover concurrent executions and error scenarios.

Overall, the refactoring enhances the function's capability to handle asynchronous operations and improves its error handling. However, careful attention must be paid to concurrency issues and ensuring deterministic behavior.

Consider verifying the deterministic behavior and thread safety through additional tests or static analysis tools. If you need assistance with setting up tests or further analysis, please let me know.

Tools
GitHub Check: CodeQL

[notice] 231-242: Spawning a Go routine
Spawning a Go routine may be a possible source of non-determinism

Comment on lines 177 to 182
state, err := a.app.db.StateAt(version)
if err != nil {
return nil, fmt.Errorf("failed to export genesis: %w", err)
}

genesisJson, err := a.app.moduleManager.ExportGenesisForModules(ctx, a.app.stf, state)
Copy link
Contributor

Choose a reason for hiding this comment

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

Approve changes to ExportGenesis with a suggestion for enhanced error handling.

The modifications to ExportGenesis function are well-aligned with the PR's objectives to restore concurrent export functionality and address the race condition. The addition of state retrieval at a specified version enhances the robustness and accuracy of the export process.

Suggestion:
Consider adding more specific error messages for different failure points within the function to aid in debugging and maintenance.

runtime/v2/builder.go Show resolved Hide resolved
runtime/v2/manager.go Show resolved Hide resolved
@@ -194,6 +195,8 @@ func (m *MM[T]) InitGenesisJSON(
// ExportGenesisForModules performs export genesis functionality for modules
func (m *MM[T]) ExportGenesisForModules(
ctx context.Context,
appStf *stf.STF[T],
Copy link
Member

Choose a reason for hiding this comment

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

Can we not leak stf in the module manager API?

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Outside diff range, codebase verification and nitpick comments (1)
runtime/v2/manager.go (1)

Line range hint 199-255: Approve the refactoring with a minor nitpick.

The refactoring of the ExportGenesisForModules function to support asynchronous execution using goroutines and channels is a good approach to improve performance. The changes handle errors and collect results from the goroutines correctly. The use of the genesisResult type and the ModuleI interface improves modularity and reusability.

However, consider renaming moduleI to module for better readability:

-		var moduleI ModuleI
+		var module ModuleI
		if module, hasGenesis := mod.(appmodulev2.HasGenesis); hasGenesis {
-			moduleI = module.(ModuleI)
+			module = module.(ModuleI)
		} else if module, hasABCIGenesis := mod.(appmodulev2.HasABCIGenesis); hasABCIGenesis {
-			moduleI = module.(ModuleI)
+			module = module.(ModuleI)
		} else {
			continue
		}

Overall, the changes align with the PR objectives and the AI-generated summary.

Review details

Configuration used: .coderabbit.yml
Review profile: CHILL

Commits

Files that changed from the base of the PR and between a91e7de and 2964763.

Files selected for processing (1)
  • runtime/v2/manager.go (4 hunks)
Additional context used
Path-based instructions (1)
runtime/v2/manager.go (1)

Pattern **/*.go: Review the Golang code for conformity with the Uber Golang style guide, highlighting any deviations.

Additional comments not posted (2)
runtime/v2/manager.go (2)

233-243: Skip the static analysis flag.

The static analysis tool flagged the use of goroutines as a possible source of non-determinism. However, the usage here is valid and does not introduce non-determinism as the goroutines are used to enable concurrent execution of independent tasks and the results are collected deterministically using channels.


Line range hint 260-269: LGTM!

The checkModulesExists function is implemented correctly.

@julienrbrt julienrbrt mentioned this pull request Sep 16, 2024
12 tasks
@julienrbrt
Copy link
Member

julienrbrt commented Sep 19, 2024

@akhilkumarpilli, could you base yourself on this branch: #21739?
Then genesis handling will change after the above.
Maybe no other change than the manager change is needed after

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Review details

Configuration used: .coderabbit.yml
Review profile: CHILL

Commits

Files that changed from the base of the PR and between 2964763 and 1dce1ee.

Files selected for processing (3)
  • runtime/v2/builder.go (1 hunks)
  • runtime/v2/manager.go (4 hunks)
  • server/v2/appmanager/appmanager.go (1 hunks)
Additional context used
Path-based instructions (3)
runtime/v2/builder.go (1)

Pattern **/*.go: Review the Golang code for conformity with the Uber Golang style guide, highlighting any deviations.

runtime/v2/manager.go (1)

Pattern **/*.go: Review the Golang code for conformity with the Uber Golang style guide, highlighting any deviations.

server/v2/appmanager/appmanager.go (1)

Pattern **/*.go: Review the Golang code for conformity with the Uber Golang style guide, highlighting any deviations.

Additional comments not posted (4)
server/v2/appmanager/appmanager.go (2)

92-93: LGTM!

The addition of the guard clause to check if exportGenesis is nil at the start of the function is a good practice. It simplifies the control flow, improves readability, and ensures that the function fails fast if exportGenesis is not set, preventing potential nil pointer dereferences.


Line range hint 96-100: Looks good!

The changes made to the ExportGenesis function, such as directly calling exportGenesis and handling its errors in a straightforward manner, improve the clarity and maintainability of the code. The removal of the state retrieval from the database simplifies the function and focuses on the core functionality of exporting the genesis state. These changes align with the AI-generated summary and enhance the overall readability and maintainability of the code.

runtime/v2/builder.go (1)

176-181: Approve changes to ExportGenesis with a minor suggestion for improved error handling.

The modifications to the ExportGenesis function align well with the PR's objectives of restoring concurrent export functionality and addressing the race condition. Retrieving the state at the specified version ensures that the genesis export is based on the most current state, enhancing the reliability of the exported data.

The error handling for the state retrieval failure provides more context, improving the debuggability of the genesis export process. The approach of fetching the state here, instead of modifying the ExportGenesis arguments, maintains backward compatibility, as mentioned in the past review comments.

Minor Suggestion:
Consider wrapping the error returned by ExportGenesisForModules with more context about the failure, similar to how it's done for the state retrieval error. This will provide even more clarity during debugging.

runtime/v2/manager.go (1)

232-242: Ensure Thread-Safety of Concurrent State Access

The use of goroutines to export genesis data concurrently is beneficial for performance. However, calling appStf.RunWithCtx concurrently with the shared state may introduce data races or inconsistent state if state or its underlying components are not safe for concurrent access.

Verify that store.ReaderMap and any underlying stores support concurrent read operations without the risk of data races.

To check if store.ReaderMap is thread-safe, you can run the following script:

Comment on lines 198 to 199
appStf appmanager.StateTransitionFunction[T],
state store.ReaderMap,
Copy link
Contributor

Choose a reason for hiding this comment

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

Avoid Leaking Internal Interfaces in Module Manager API

Passing appStf appmanager.StateTransitionFunction[T] and state store.ReaderMap directly into the ExportGenesisForModules function exposes internal interfaces and implementation details at the module manager level. This can lead to tight coupling and makes it harder to maintain and refactor the code in the future.

Consider abstracting these dependencies or providing a higher-level interface that encapsulates the state transition functionality without exposing the internal types.

@github-actions github-actions bot removed C:server/v2 Issues related to server/v2 C:server/v2 appmanager labels Sep 20, 2024
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Review details

Configuration used: .coderabbit.yml
Review profile: CHILL

Commits

Files that changed from the base of the PR and between 1dce1ee and 8e0c4c9.

Files selected for processing (2)
  • runtime/v2/builder.go (1 hunks)
  • runtime/v2/manager.go (4 hunks)
Files skipped from review as they are similar to previous changes (1)
  • runtime/v2/builder.go
Additional context used
Path-based instructions (1)
runtime/v2/manager.go (1)

Pattern **/*.go: Review the Golang code for conformity with the Uber Golang style guide, highlighting any deviations.

Additional comments not posted (2)
runtime/v2/manager.go (2)

209-212: Improved encapsulation with genesisResult struct.

The introduction of the genesisResult struct provides a cleaner way to encapsulate the exported genesis data and any errors that may occur during the export process. This improves code readability and maintainability.


Line range hint 218-243: Verify the correctness of the goroutine implementation.

The use of goroutines and channels allows for concurrent execution of the ExportGenesis method for each module, potentially improving performance. However, spawning goroutines can be a source of non-determinism if not implemented correctly.

To verify the correctness of the goroutine implementation, consider the following:

  • Ensure that the goroutines are properly synchronized and do not introduce any race conditions.
  • Verify that the goroutines are properly terminated and do not leak resources.
  • Test the implementation thoroughly to ensure that it behaves as expected under different scenarios, such as when a module's ExportGenesis method returns an error.

You can use the following script to search for potential synchronization issues:

Comment on lines +245 to +252
genesisData := make(map[string]json.RawMessage)
for moduleName := range channels {
res := <-channels[moduleName]
if res.err != nil {
return nil, fmt.Errorf("genesis export error in %s: %w", moduleName, res.err)
}

genesisData[moduleName] = res
genesisData[moduleName] = res.bz
Copy link
Contributor

Choose a reason for hiding this comment

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

Use a deterministic order when collecting genesis data.

Collecting the exported genesis data from the channels using a range loop over the channels map may result in non-deterministic ordering. This is because the order of iteration over a map is not guaranteed to be the same across different runs.

To ensure deterministic ordering, consider the following:

  • Create a sorted slice of module names based on the keys of the channels map.
  • Iterate over the sorted slice of module names to collect the exported genesis data from the channels in a deterministic order.

Here's an example of how you can modify the code to achieve deterministic ordering:

 genesisData := make(map[string]json.RawMessage)
-for moduleName := range channels {
+moduleNames := make([]string, 0, len(channels))
+for moduleName := range channels {
+    moduleNames = append(moduleNames, moduleName)
+}
+sort.Strings(moduleNames)
+for _, moduleName := range moduleNames {
     res := <-channels[moduleName]
     if res.err != nil {
         return nil, fmt.Errorf("genesis export error in %s: %w", moduleName, res.err)
     }
     genesisData[moduleName] = res.bz
 }
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
genesisData := make(map[string]json.RawMessage)
for moduleName := range channels {
res := <-channels[moduleName]
if res.err != nil {
return nil, fmt.Errorf("genesis export error in %s: %w", moduleName, res.err)
}
genesisData[moduleName] = res
genesisData[moduleName] = res.bz
genesisData := make(map[string]json.RawMessage)
moduleNames := make([]string, 0, len(channels))
for moduleName := range channels {
moduleNames = append(moduleNames, moduleName)
}
sort.Strings(moduleNames)
for _, moduleName := range moduleNames {
res := <-channels[moduleName]
if res.err != nil {
return nil, fmt.Errorf("genesis export error in %s: %w", moduleName, res.err)
}
genesisData[moduleName] = res.bz

runtime/v2/manager.go Fixed Show fixed Hide fixed
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Outside diff range and nitpick comments (1)
runtime/v2/manager.go (1)

245-252: Use a deterministic order when collecting genesis data.

Collecting the exported genesis data from the channels using a range loop over the channels map may result in non-deterministic ordering. This is because the order of iteration over a map is not guaranteed to be the same across different runs.

To ensure deterministic ordering, consider the following:

  • Create a sorted slice of module names based on the keys of the channels map.
  • Iterate over the sorted slice of module names to collect the exported genesis data from the channels in a deterministic order.

Here's an example of how you can modify the code to achieve deterministic ordering:

 genesisData := make(map[string]json.RawMessage)
-for moduleName := range channels {
+moduleNames := make([]string, 0, len(channels))
+for moduleName := range channels {
+    moduleNames = append(moduleNames, moduleName)
+}
+sort.Strings(moduleNames)
+for _, moduleName := range moduleNames {
     res := <-channels[moduleName]
     if res.err != nil {
         return nil, fmt.Errorf("genesis export error in %s: %w", moduleName, res.err)
     }
     genesisData[moduleName] = res.bz
 }
Review details

Configuration used: .coderabbit.yml
Review profile: CHILL

Commits

Files that changed from the base of the PR and between 8e0c4c9 and 4d1a612.

Files selected for processing (2)
  • runtime/v2/builder.go (1 hunks)
  • runtime/v2/manager.go (4 hunks)
Files skipped from review as they are similar to previous changes (1)
  • runtime/v2/builder.go
Additional context used
Path-based instructions (1)
runtime/v2/manager.go (1)

Pattern **/*.go: Review the Golang code for conformity with the Uber Golang style guide, highlighting any deviations.

Additional comments not posted (2)
runtime/v2/manager.go (2)

Line range hint 198-256: Excellent refactoring of the ExportGenesisForModules function!

The changes introduce several improvements:

  1. Asynchronous execution enables concurrent processing of module exports, enhancing performance.
  2. Better error handling with the genesisResult struct provides clearer visibility into module-specific issues.
  3. State isolation through the stateFactory parameter ensures each module operates on its own context, improving robustness.
  4. Modularity allows for targeted exports of specific modules, providing fine-grained control.

24-28: Appropriate addition of import statements.

The new import statements, "cosmossdk.io/core/store" and "cosmossdk.io/runtime/v2/services", are necessary to support the refactored ExportGenesisForModules function:

  • "cosmossdk.io/core/store" provides the store.WriterMap type used in the stateFactory parameter.
  • "cosmossdk.io/runtime/v2/services" offers the services.NewGenesisContext function used to create a new genesisContext for each module's export operation.

These import statements serve a clear purpose and are correctly added to facilitate the changes made in the ExportGenesisForModules function.

Comment on lines +231 to +242
go func(moduleI ModuleI, ch chan genesisResult) {
genesisCtx := services.NewGenesisContext(stateFactory())
_, _ = genesisCtx.Run(ctx, func(ctx context.Context) error {
jm, err := moduleI.ExportGenesis(ctx)
if err != nil {
ch <- genesisResult{nil, err}
return err
}
ch <- genesisResult{jm, nil}
return nil
})
}(moduleI, channels[moduleName])

Check notice

Code scanning / CodeQL

Spawning a Go routine Note

Spawning a Go routine may be a possible source of non-determinism
@julienrbrt julienrbrt added backport/v0.52.x PR scheduled for inclusion in the v0.52's next stable release and removed backport/v0.52.x PR scheduled for inclusion in the v0.52's next stable release labels Sep 23, 2024
@julienrbrt julienrbrt changed the title fix: bring back concurrent export genesis in v2 fix(runtime/v2): bring back concurrent export genesis in v2 Sep 23, 2024
@julienrbrt julienrbrt added this pull request to the merge queue Sep 24, 2024
Merged via the queue into main with commit 0611fbc Sep 24, 2024
73 of 74 checks passed
@julienrbrt julienrbrt deleted the akhil/concurrent-export-genesis branch September 24, 2024 09:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Feature]: bring back concurrent export of genesis on v2.
5 participants