Skip to content

Commit d7c029a

Browse files
authored
Restore blockchain import file command (ava-labs#3003)
* Restore blockchain import file command Restores the avalanche blockchain import file command that was removed in the APM cleanup. This command allows users to import blockchain configurations from JSON export files. * change version file * remove unused flags
1 parent 38d4c97 commit d7c029a

4 files changed

Lines changed: 250 additions & 1 deletion

File tree

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.9.4
1+
1.9.5

cmd/blockchaincmd/export_test.go

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
// Copyright (C) 2025, Ava Labs, Inc. All rights reserved.
2+
// See the file LICENSE for licensing terms.
3+
package blockchaincmd
4+
5+
import (
6+
"encoding/json"
7+
"io"
8+
"os"
9+
"path/filepath"
10+
"testing"
11+
12+
"github.com/ava-labs/avalanche-cli/internal/mocks"
13+
"github.com/ava-labs/avalanche-cli/pkg/application"
14+
"github.com/ava-labs/avalanche-cli/pkg/constants"
15+
"github.com/ava-labs/avalanche-cli/pkg/prompts"
16+
"github.com/ava-labs/avalanche-cli/pkg/ux"
17+
"github.com/ava-labs/avalanche-cli/pkg/vm"
18+
"github.com/ava-labs/avalanchego/utils/logging"
19+
"github.com/stretchr/testify/mock"
20+
"github.com/stretchr/testify/require"
21+
)
22+
23+
func TestExportImportSubnet(t *testing.T) {
24+
testDir := t.TempDir()
25+
require := require.New(t)
26+
testSubnet := "testSubnet"
27+
vmVersion := "v0.9.99"
28+
testSubnetEVMCompat := []byte("{\"rpcChainVMProtocolVersion\": {\"v0.9.99\": 18}}")
29+
30+
app = application.New()
31+
32+
mockAppDownloader := mocks.Downloader{}
33+
mockAppDownloader.On("Download", mock.Anything).Return(testSubnetEVMCompat, nil)
34+
35+
app.Setup(testDir, logging.NoLog{}, nil, "", prompts.NewPrompter(), &mockAppDownloader, nil)
36+
ux.NewUserLog(logging.NoLog{}, io.Discard)
37+
38+
subnetEvmGenesisPath := "tests/e2e/assets/test_subnet_evm_genesis.json"
39+
genBytes, err := os.ReadFile("../../" + subnetEvmGenesisPath)
40+
require.NoError(err)
41+
sc, err := vm.CreateEvmSidecar(
42+
nil,
43+
app,
44+
testSubnet,
45+
vmVersion,
46+
"Test",
47+
false,
48+
true,
49+
true,
50+
)
51+
require.NoError(err)
52+
err = app.WriteGenesisFile(testSubnet, genBytes)
53+
require.NoError(err)
54+
err = app.CreateSidecar(sc)
55+
require.NoError(err)
56+
57+
exportOutputDir := filepath.Join(testDir, "output")
58+
err = os.MkdirAll(exportOutputDir, constants.DefaultPerms755)
59+
require.NoError(err)
60+
exportOutput = filepath.Join(exportOutputDir, testSubnet)
61+
defer func() {
62+
exportOutput = ""
63+
app = nil
64+
}()
65+
globalNetworkFlags.UseLocal = true
66+
err = exportSubnet(nil, []string{"this-does-not-exist-should-fail"})
67+
require.Error(err)
68+
69+
err = exportSubnet(nil, []string{testSubnet})
70+
require.NoError(err)
71+
require.FileExists(exportOutput)
72+
sidecarFile := filepath.Join(app.GetBaseDir(), constants.SubnetDir, testSubnet, constants.SidecarFileName)
73+
orig, err := os.ReadFile(sidecarFile)
74+
require.NoError(err)
75+
76+
var control map[string]interface{}
77+
err = json.Unmarshal(orig, &control)
78+
require.NoError(err)
79+
require.Equal(control["Name"], testSubnet)
80+
require.Equal(control["VM"], "Subnet-EVM")
81+
require.Equal(control["VMVersion"], vmVersion)
82+
require.Equal(control["Subnet"], testSubnet)
83+
require.Equal(control["TokenName"], "Test Token")
84+
require.Equal(control["TokenSymbol"], "Test")
85+
require.Equal(control["Version"], constants.SidecarVersion)
86+
require.Equal(control["Networks"], nil)
87+
88+
err = os.Remove(sidecarFile)
89+
require.NoError(err)
90+
91+
err = importFile(nil, []string{"this-does-also-not-exist-import-should-fail"})
92+
require.ErrorIs(err, os.ErrNotExist)
93+
err = importFile(nil, []string{exportOutput})
94+
require.ErrorContains(err, "blockchain already exists")
95+
genFile := filepath.Join(app.GetBaseDir(), constants.SubnetDir, testSubnet, constants.GenesisFileName)
96+
err = os.Remove(genFile)
97+
require.NoError(err)
98+
err = importFile(nil, []string{exportOutput})
99+
require.NoError(err)
100+
}

cmd/blockchaincmd/import.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ or importing from blockchains running public networks
1919
(e.g. created manually or with the deprecated subnet-cli)`,
2020
RunE: cobrautils.CommandSuiteUsage,
2121
}
22+
// blockchain import file
23+
cmd.AddCommand(newImportFileCmd())
2224
// blockchain import public
2325
cmd.AddCommand(newImportPublicCmd())
2426
return cmd

cmd/blockchaincmd/import_file.go

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
// Copyright (C) 2025, Ava Labs, Inc. All rights reserved.
2+
// See the file LICENSE for licensing terms.
3+
package blockchaincmd
4+
5+
import (
6+
"encoding/json"
7+
"errors"
8+
"fmt"
9+
"os"
10+
11+
"github.com/ava-labs/avalanche-cli/pkg/cobrautils"
12+
"github.com/ava-labs/avalanche-cli/pkg/models"
13+
"github.com/ava-labs/avalanche-cli/pkg/ux"
14+
"github.com/ava-labs/avalanche-cli/pkg/vm"
15+
"github.com/spf13/cobra"
16+
)
17+
18+
// avalanche blockchain import file
19+
func newImportFileCmd() *cobra.Command {
20+
cmd := &cobra.Command{
21+
Use: "file [blockchainPath]",
22+
Short: "Import an existing blockchain config",
23+
RunE: importFile,
24+
Args: cobrautils.MaximumNArgs(1),
25+
Long: `The blockchain import file command will import a blockchain configuration from a file.
26+
27+
You can optionally provide the path as a command-line argument.
28+
Alternatively, running the command without any arguments triggers an interactive wizard.
29+
By default, an imported blockchain doesn't overwrite an existing blockchain with the same name.
30+
To allow overwrites, provide the --force flag.`,
31+
}
32+
cmd.Flags().BoolVarP(
33+
&overwriteImport,
34+
"force",
35+
"f",
36+
false,
37+
"overwrite the existing configuration if one exists",
38+
)
39+
return cmd
40+
}
41+
42+
func importFile(_ *cobra.Command, args []string) error {
43+
var (
44+
importPath string
45+
err error
46+
)
47+
if len(args) == 1 {
48+
importPath = args[0]
49+
}
50+
51+
if importPath == "" {
52+
promptStr := "Select the file to import your blockchain from"
53+
importPath, err = app.Prompt.CaptureExistingFilepath(promptStr)
54+
if err != nil {
55+
return err
56+
}
57+
}
58+
59+
importFileBytes, err := os.ReadFile(importPath)
60+
if err != nil {
61+
return err
62+
}
63+
64+
importable := models.Exportable{}
65+
err = json.Unmarshal(importFileBytes, &importable)
66+
if err != nil {
67+
return err
68+
}
69+
70+
blockchainName := importable.Sidecar.Name
71+
if blockchainName == "" {
72+
return errors.New("export data is malformed: missing blockchain name")
73+
}
74+
75+
if app.GenesisExists(blockchainName) && !overwriteImport {
76+
return errors.New("blockchain already exists. Use --" + forceFlag + " parameter to overwrite")
77+
}
78+
79+
if importable.Sidecar.VM == models.CustomVM {
80+
if importable.Sidecar.CustomVMRepoURL == "" {
81+
return fmt.Errorf("repository url must be defined for custom vm import")
82+
}
83+
if importable.Sidecar.CustomVMBranch == "" {
84+
return fmt.Errorf("repository branch must be defined for custom vm import")
85+
}
86+
if importable.Sidecar.CustomVMBuildScript == "" {
87+
return fmt.Errorf("build script must be defined for custom vm import")
88+
}
89+
90+
if err := vm.BuildCustomVM(app, &importable.Sidecar); err != nil {
91+
return err
92+
}
93+
94+
vmPath := app.GetCustomVMPath(blockchainName)
95+
rpcVersion, err := vm.GetVMBinaryProtocolVersion(vmPath)
96+
if err != nil {
97+
return fmt.Errorf("unable to get custom binary RPC version: %w", err)
98+
}
99+
if rpcVersion != importable.Sidecar.RPCVersion {
100+
return fmt.Errorf("RPC version mismatch between sidecar and vm binary (%d vs %d)", importable.Sidecar.RPCVersion, rpcVersion)
101+
}
102+
}
103+
104+
if err := app.WriteGenesisFile(blockchainName, importable.Genesis); err != nil {
105+
return err
106+
}
107+
108+
if importable.NodeConfig != nil {
109+
if err := app.WriteAvagoNodeConfigFile(blockchainName, importable.NodeConfig); err != nil {
110+
return err
111+
}
112+
} else {
113+
_ = os.RemoveAll(app.GetAvagoNodeConfigPath(blockchainName))
114+
}
115+
116+
if importable.ChainConfig != nil {
117+
if err := app.WriteChainConfigFile(blockchainName, importable.ChainConfig); err != nil {
118+
return err
119+
}
120+
} else {
121+
_ = os.RemoveAll(app.GetChainConfigPath(blockchainName))
122+
}
123+
124+
if importable.SubnetConfig != nil {
125+
if err := app.WriteAvagoSubnetConfigFile(blockchainName, importable.SubnetConfig); err != nil {
126+
return err
127+
}
128+
} else {
129+
_ = os.RemoveAll(app.GetAvagoSubnetConfigPath(blockchainName))
130+
}
131+
132+
if importable.NetworkUpgrades != nil {
133+
if err := app.WriteNetworkUpgradesFile(blockchainName, importable.NetworkUpgrades); err != nil {
134+
return err
135+
}
136+
} else {
137+
_ = os.RemoveAll(app.GetUpgradeBytesFilepath(blockchainName))
138+
}
139+
140+
if err := app.CreateSidecar(&importable.Sidecar); err != nil {
141+
return err
142+
}
143+
144+
ux.Logger.PrintToUser("Blockchain imported successfully")
145+
146+
return nil
147+
}

0 commit comments

Comments
 (0)