Skip to content
Merged
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
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.9.4
1.9.5
100 changes: 100 additions & 0 deletions cmd/blockchaincmd/export_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Copyright (C) 2025, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package blockchaincmd

import (
"encoding/json"
"io"
"os"
"path/filepath"
"testing"

"github.com/ava-labs/avalanche-cli/internal/mocks"
"github.com/ava-labs/avalanche-cli/pkg/application"
"github.com/ava-labs/avalanche-cli/pkg/constants"
"github.com/ava-labs/avalanche-cli/pkg/prompts"
"github.com/ava-labs/avalanche-cli/pkg/ux"
"github.com/ava-labs/avalanche-cli/pkg/vm"
"github.com/ava-labs/avalanchego/utils/logging"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)

func TestExportImportSubnet(t *testing.T) {
testDir := t.TempDir()
require := require.New(t)
testSubnet := "testSubnet"
vmVersion := "v0.9.99"
testSubnetEVMCompat := []byte("{\"rpcChainVMProtocolVersion\": {\"v0.9.99\": 18}}")

app = application.New()

mockAppDownloader := mocks.Downloader{}
mockAppDownloader.On("Download", mock.Anything).Return(testSubnetEVMCompat, nil)

app.Setup(testDir, logging.NoLog{}, nil, "", prompts.NewPrompter(), &mockAppDownloader, nil)
ux.NewUserLog(logging.NoLog{}, io.Discard)

subnetEvmGenesisPath := "tests/e2e/assets/test_subnet_evm_genesis.json"
genBytes, err := os.ReadFile("../../" + subnetEvmGenesisPath)
require.NoError(err)
sc, err := vm.CreateEvmSidecar(
nil,
app,
testSubnet,
vmVersion,
"Test",
false,
true,
true,
)
require.NoError(err)
err = app.WriteGenesisFile(testSubnet, genBytes)
require.NoError(err)
err = app.CreateSidecar(sc)
require.NoError(err)

exportOutputDir := filepath.Join(testDir, "output")
err = os.MkdirAll(exportOutputDir, constants.DefaultPerms755)
require.NoError(err)
exportOutput = filepath.Join(exportOutputDir, testSubnet)
defer func() {
exportOutput = ""
app = nil
}()
globalNetworkFlags.UseLocal = true
err = exportSubnet(nil, []string{"this-does-not-exist-should-fail"})
require.Error(err)

err = exportSubnet(nil, []string{testSubnet})
require.NoError(err)
require.FileExists(exportOutput)
sidecarFile := filepath.Join(app.GetBaseDir(), constants.SubnetDir, testSubnet, constants.SidecarFileName)
orig, err := os.ReadFile(sidecarFile)
require.NoError(err)

var control map[string]interface{}
err = json.Unmarshal(orig, &control)
require.NoError(err)
require.Equal(control["Name"], testSubnet)
require.Equal(control["VM"], "Subnet-EVM")
require.Equal(control["VMVersion"], vmVersion)
require.Equal(control["Subnet"], testSubnet)
require.Equal(control["TokenName"], "Test Token")
require.Equal(control["TokenSymbol"], "Test")
require.Equal(control["Version"], constants.SidecarVersion)
require.Equal(control["Networks"], nil)

err = os.Remove(sidecarFile)
require.NoError(err)

err = importFile(nil, []string{"this-does-also-not-exist-import-should-fail"})
require.ErrorIs(err, os.ErrNotExist)
err = importFile(nil, []string{exportOutput})
require.ErrorContains(err, "blockchain already exists")
genFile := filepath.Join(app.GetBaseDir(), constants.SubnetDir, testSubnet, constants.GenesisFileName)
err = os.Remove(genFile)
require.NoError(err)
err = importFile(nil, []string{exportOutput})
require.NoError(err)
}
2 changes: 2 additions & 0 deletions cmd/blockchaincmd/import.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ or importing from blockchains running public networks
(e.g. created manually or with the deprecated subnet-cli)`,
RunE: cobrautils.CommandSuiteUsage,
}
// blockchain import file
cmd.AddCommand(newImportFileCmd())
// blockchain import public
cmd.AddCommand(newImportPublicCmd())
return cmd
Expand Down
147 changes: 147 additions & 0 deletions cmd/blockchaincmd/import_file.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
// Copyright (C) 2025, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package blockchaincmd

import (
"encoding/json"
"errors"
"fmt"
"os"

"github.com/ava-labs/avalanche-cli/pkg/cobrautils"
"github.com/ava-labs/avalanche-cli/pkg/models"
"github.com/ava-labs/avalanche-cli/pkg/ux"
"github.com/ava-labs/avalanche-cli/pkg/vm"
"github.com/spf13/cobra"
)

// avalanche blockchain import file
func newImportFileCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "file [blockchainPath]",
Short: "Import an existing blockchain config",
RunE: importFile,
Args: cobrautils.MaximumNArgs(1),
Long: `The blockchain import file command will import a blockchain configuration from a file.

You can optionally provide the path as a command-line argument.
Alternatively, running the command without any arguments triggers an interactive wizard.
By default, an imported blockchain doesn't overwrite an existing blockchain with the same name.
To allow overwrites, provide the --force flag.`,
}
cmd.Flags().BoolVarP(
&overwriteImport,
"force",
"f",
false,
"overwrite the existing configuration if one exists",
)
return cmd
}

func importFile(_ *cobra.Command, args []string) error {
var (
importPath string
err error
)
if len(args) == 1 {
importPath = args[0]
}

if importPath == "" {
promptStr := "Select the file to import your blockchain from"
importPath, err = app.Prompt.CaptureExistingFilepath(promptStr)
if err != nil {
return err
}
}

importFileBytes, err := os.ReadFile(importPath)
if err != nil {
return err
}

importable := models.Exportable{}
err = json.Unmarshal(importFileBytes, &importable)
if err != nil {
return err
}

blockchainName := importable.Sidecar.Name
if blockchainName == "" {
return errors.New("export data is malformed: missing blockchain name")
}

if app.GenesisExists(blockchainName) && !overwriteImport {
return errors.New("blockchain already exists. Use --" + forceFlag + " parameter to overwrite")
}

if importable.Sidecar.VM == models.CustomVM {
if importable.Sidecar.CustomVMRepoURL == "" {
return fmt.Errorf("repository url must be defined for custom vm import")
}
if importable.Sidecar.CustomVMBranch == "" {
return fmt.Errorf("repository branch must be defined for custom vm import")
}
if importable.Sidecar.CustomVMBuildScript == "" {
return fmt.Errorf("build script must be defined for custom vm import")
}

if err := vm.BuildCustomVM(app, &importable.Sidecar); err != nil {
return err
}

vmPath := app.GetCustomVMPath(blockchainName)
rpcVersion, err := vm.GetVMBinaryProtocolVersion(vmPath)
if err != nil {
return fmt.Errorf("unable to get custom binary RPC version: %w", err)
}
if rpcVersion != importable.Sidecar.RPCVersion {
return fmt.Errorf("RPC version mismatch between sidecar and vm binary (%d vs %d)", importable.Sidecar.RPCVersion, rpcVersion)
}
}

if err := app.WriteGenesisFile(blockchainName, importable.Genesis); err != nil {
return err
}

if importable.NodeConfig != nil {
if err := app.WriteAvagoNodeConfigFile(blockchainName, importable.NodeConfig); err != nil {
return err
}
} else {
_ = os.RemoveAll(app.GetAvagoNodeConfigPath(blockchainName))
}

if importable.ChainConfig != nil {
if err := app.WriteChainConfigFile(blockchainName, importable.ChainConfig); err != nil {
return err
}
} else {
_ = os.RemoveAll(app.GetChainConfigPath(blockchainName))
}

if importable.SubnetConfig != nil {
if err := app.WriteAvagoSubnetConfigFile(blockchainName, importable.SubnetConfig); err != nil {
return err
}
} else {
_ = os.RemoveAll(app.GetAvagoSubnetConfigPath(blockchainName))
}

if importable.NetworkUpgrades != nil {
if err := app.WriteNetworkUpgradesFile(blockchainName, importable.NetworkUpgrades); err != nil {
return err
}
} else {
_ = os.RemoveAll(app.GetUpgradeBytesFilepath(blockchainName))
}

if err := app.CreateSidecar(&importable.Sidecar); err != nil {
return err
}

ux.Logger.PrintToUser("Blockchain imported successfully")

return nil
}
Loading