-
Notifications
You must be signed in to change notification settings - Fork 118
Expand file tree
/
Copy pathcreate.go
More file actions
574 lines (531 loc) · 19.9 KB
/
create.go
File metadata and controls
574 lines (531 loc) · 19.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
// Copyright (C) 2022, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package blockchaincmd
import (
"encoding/json"
"errors"
"fmt"
"os"
"sort"
"strconv"
"strings"
"unicode"
"github.com/ava-labs/avalanche-cli/cmd/flags"
"github.com/ava-labs/avalanche-cli/pkg/cobrautils"
"github.com/ava-labs/avalanche-cli/pkg/constants"
"github.com/ava-labs/avalanche-cli/pkg/interchain"
"github.com/ava-labs/avalanche-cli/pkg/key"
"github.com/ava-labs/avalanche-cli/pkg/metrics"
"github.com/ava-labs/avalanche-cli/pkg/models"
"github.com/ava-labs/avalanche-cli/pkg/utils"
"github.com/ava-labs/avalanche-cli/pkg/ux"
"github.com/ava-labs/avalanche-cli/pkg/vm"
"github.com/ava-labs/avalanchego/utils/formatting/address"
"github.com/ethereum/go-ethereum/common"
"github.com/spf13/cobra"
"golang.org/x/mod/semver"
)
const (
forceFlag = "force"
latest = "latest"
preRelease = "pre-release"
)
type CreateFlags struct {
useSubnetEvm bool
useCustomVM bool
chainID uint64
tokenSymbol string
useTestDefaults bool
useProductionDefaults bool
useWarp bool
useICM bool
vmVersion string
useLatestReleasedVMVersion bool
useLatestPreReleasedVMVersion bool
useExternalGasToken bool
addICMRegistryToGenesis bool
proofOfStake bool
proofOfAuthority bool
rewardBasisPoints uint64
validatorManagerOwner string
proxyContractOwner string
enableDebugging bool
useACP99 bool
}
var (
createFlags CreateFlags
forceCreate bool
genesisPath string
vmFile string
useRepo bool
sovereign bool
errEmptyBlockchainName = errors.New("invalid empty name")
errIllegalNameCharacter = errors.New("illegal name character: only letters, no special characters allowed")
errMutuallyExlusiveVersionOptions = errors.New("version flags --latest,--pre-release,vm-version are mutually exclusive")
errMutuallyExclusiveVMConfigOptions = errors.New("--genesis flag disables --evm-chain-id,--evm-defaults,--production-defaults,--test-defaults")
errMutuallyExlusiveValidatorManagementOptions = errors.New("validator management type flags --proof-of-authority,--proof-of-stake are mutually exclusive")
errSOVFlagsOnly = errors.New("flags --proof-of-authority, --proof-of-stake, --poa-manager-owner --proxy-contract-owner are only applicable to Subnet Only Validator (SOV) blockchains")
)
// avalanche blockchain create
func newCreateCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "create [blockchainName]",
Short: "Create a new blockchain configuration",
Long: `The blockchain create command builds a new genesis file to configure your Blockchain.
By default, the command runs an interactive wizard. It walks you through
all the steps you need to create your first Blockchain.
The tool supports deploying Subnet-EVM, and custom VMs. You
can create a custom, user-generated genesis with a custom VM by providing
the path to your genesis and VM binaries with the --genesis and --vm flags.
By default, running the command with a blockchainName that already exists
causes the command to fail. If you'd like to overwrite an existing
configuration, pass the -f flag.`,
Args: cobrautils.ExactArgs(1),
RunE: createBlockchainConfig,
PersistentPostRun: handlePostRun,
}
cmd.Flags().StringVar(&genesisPath, "genesis", "", "file path of genesis to use")
cmd.Flags().BoolVar(&createFlags.useSubnetEvm, "evm", false, "use the Subnet-EVM as the base template")
cmd.Flags().BoolVar(&createFlags.useCustomVM, "custom", false, "use a custom VM template")
cmd.Flags().StringVar(&createFlags.vmVersion, "vm-version", "", "version of Subnet-EVM template to use")
cmd.Flags().BoolVar(&createFlags.useLatestPreReleasedVMVersion, preRelease, false, "use latest Subnet-EVM pre-released version, takes precedence over --vm-version")
cmd.Flags().BoolVar(&createFlags.useLatestReleasedVMVersion, latest, false, "use latest Subnet-EVM released version, takes precedence over --vm-version")
cmd.Flags().Uint64Var(&createFlags.chainID, "evm-chain-id", 0, "chain ID to use with Subnet-EVM")
cmd.Flags().StringVar(&createFlags.tokenSymbol, "evm-token", "", "token symbol to use with Subnet-EVM")
cmd.Flags().BoolVar(&createFlags.useProductionDefaults, "evm-defaults", false, "deprecation notice: use '--production-defaults'")
cmd.Flags().BoolVar(&createFlags.useProductionDefaults, "production-defaults", false, "use default production settings for your blockchain")
cmd.Flags().BoolVar(&createFlags.useTestDefaults, "test-defaults", false, "use default test settings for your blockchain")
cmd.Flags().BoolVarP(&forceCreate, forceFlag, "f", false, "overwrite the existing configuration if one exists")
cmd.Flags().StringVar(&vmFile, "vm", "", "file path of custom vm to use. alias to custom-vm-path")
cmd.Flags().StringVar(&vmFile, "custom-vm-path", "", "file path of custom vm to use")
cmd.Flags().StringVar(&customVMRepoURL, "custom-vm-repo-url", "", "custom vm repository url")
cmd.Flags().StringVar(&customVMBranch, "custom-vm-branch", "", "custom vm branch or commit")
cmd.Flags().StringVar(&customVMBuildScript, "custom-vm-build-script", "", "custom vm build-script")
cmd.Flags().BoolVar(&useRepo, "from-github-repo", false, "generate custom VM binary from github repository")
cmd.Flags().BoolVar(&createFlags.useWarp, "warp", true, "generate a vm with warp support (needed for ICM)")
cmd.Flags().BoolVar(&createFlags.useICM, "teleporter", false, "interoperate with other blockchains using ICM")
cmd.Flags().BoolVar(&createFlags.useICM, "icm", false, "interoperate with other blockchains using ICM")
cmd.Flags().BoolVar(&createFlags.useExternalGasToken, "external-gas-token", false, "use a gas token from another blockchain")
cmd.Flags().BoolVar(&createFlags.addICMRegistryToGenesis, "icm-registry-at-genesis", false, "setup ICM registry smart contract on genesis [experimental]")
cmd.Flags().BoolVar(&createFlags.proofOfAuthority, "proof-of-authority", false, "use proof of authority(PoA) for validator management")
cmd.Flags().BoolVar(&createFlags.proofOfStake, "proof-of-stake", false, "use proof of stake(PoS) for validator management")
cmd.Flags().StringVar(&createFlags.validatorManagerOwner, "validator-manager-owner", "", "EVM address that controls Validator Manager Owner")
cmd.Flags().StringVar(&createFlags.proxyContractOwner, "proxy-contract-owner", "", "EVM address that controls ProxyAdmin for TransparentProxy of ValidatorManager contract")
cmd.Flags().BoolVar(&sovereign, "sovereign", true, "set to false if creating non-sovereign blockchain")
cmd.Flags().Uint64Var(&createFlags.rewardBasisPoints, "reward-basis-points", 100, "(PoS only) reward basis points for PoS Reward Calculator")
cmd.Flags().BoolVar(&createFlags.enableDebugging, "debug", true, "enable blockchain debugging")
cmd.Flags().BoolVar(&createFlags.useACP99, "acp99", true, "use ACP99 contracts instead of v1.0.0 for validator managers")
return cmd
}
func CallCreate(
cmd *cobra.Command,
blockchainName string,
forceCreateParam bool,
genesisPathParam string,
useSubnetEvmParam bool,
useCustomParam bool,
vmVersionParam string,
evmChainIDParam uint64,
tokenSymbolParam string,
useProductionDefaultsParam bool,
useTestDefaultsParam bool,
useLatestReleasedVMVersionParam bool,
useLatestPreReleasedVMVersionParam bool,
customVMRepoURLParam string,
customVMBranchParam string,
customVMBuildScriptParam string,
) error {
forceCreate = forceCreateParam
genesisPath = genesisPathParam
createFlags.useSubnetEvm = useSubnetEvmParam
createFlags.vmVersion = vmVersionParam
createFlags.chainID = evmChainIDParam
createFlags.tokenSymbol = tokenSymbolParam
createFlags.useProductionDefaults = useProductionDefaultsParam
createFlags.useTestDefaults = useTestDefaultsParam
createFlags.useLatestReleasedVMVersion = useLatestReleasedVMVersionParam
createFlags.useLatestPreReleasedVMVersion = useLatestPreReleasedVMVersionParam
createFlags.useCustomVM = useCustomParam
customVMRepoURL = customVMRepoURLParam
customVMBranch = customVMBranchParam
customVMBuildScript = customVMBuildScriptParam
return createBlockchainConfig(cmd, []string{blockchainName})
}
// override postrun function from root.go, so that we don't double send metrics for the same command
func handlePostRun(_ *cobra.Command, _ []string) {}
func createBlockchainConfig(cmd *cobra.Command, args []string) error {
blockchainName := args[0]
if app.GenesisExists(blockchainName) && !forceCreate {
return errors.New("configuration already exists. Use --" + forceFlag + " parameter to overwrite")
}
if err := checkInvalidSubnetNames(blockchainName); err != nil {
return fmt.Errorf("blockchain name %q is invalid: %w", blockchainName, err)
}
// version flags exclusiveness
if !flags.EnsureMutuallyExclusive([]bool{
createFlags.useLatestReleasedVMVersion,
createFlags.useLatestPreReleasedVMVersion,
createFlags.vmVersion != "",
}) {
return errMutuallyExlusiveVersionOptions
}
defaultsKind := vm.NoDefaults
if createFlags.useTestDefaults {
defaultsKind = vm.TestDefaults
}
if createFlags.useProductionDefaults {
defaultsKind = vm.ProductionDefaults
}
// genesis flags exclusiveness
if genesisPath != "" && (createFlags.chainID != 0 || defaultsKind != vm.NoDefaults) {
return errMutuallyExclusiveVMConfigOptions
}
// if given custom repo info, assumes custom VM
if vmFile != "" || customVMRepoURL != "" || customVMBranch != "" || customVMBuildScript != "" {
createFlags.useCustomVM = true
}
// vm type exclusiveness
if !flags.EnsureMutuallyExclusive([]bool{createFlags.useSubnetEvm, createFlags.useCustomVM}) {
return errors.New("flags --evm,--custom are mutually exclusive")
}
if !sovereign {
if createFlags.proofOfAuthority || createFlags.proofOfStake || createFlags.validatorManagerOwner != "" || createFlags.proxyContractOwner != "" {
return errSOVFlagsOnly
}
}
// validator management type exclusiveness
if !flags.EnsureMutuallyExclusive([]bool{createFlags.proofOfAuthority, createFlags.proofOfStake}) {
return errMutuallyExlusiveValidatorManagementOptions
}
if createFlags.rewardBasisPoints == 0 && createFlags.proofOfStake {
return fmt.Errorf("reward basis points cannot be zero")
}
// get vm kind
vmType, err := vm.PromptVMType(app, createFlags.useSubnetEvm, createFlags.useCustomVM)
if err != nil {
return err
}
var (
genesisBytes []byte
useICMFlag *bool
deployICM bool
useExternalGasToken bool
)
// get ICM flag as a pointer (3 values: undef/true/false)
flagName := "teleporter"
if flag := cmd.Flags().Lookup(flagName); flag != nil && flag.Changed {
useICMFlag = &createFlags.useICM
}
flagName = "icm"
if flag := cmd.Flags().Lookup(flagName); flag != nil && flag.Changed {
useICMFlag = &createFlags.useICM
}
// get ICM info
icmInfo, err := interchain.GetICMInfo(app)
if err != nil {
return err
}
sc := &models.Sidecar{}
if sovereign {
if err = promptValidatorManagementType(app, sc); err != nil {
return err
}
}
if vmType == models.SubnetEvm {
if sovereign {
if err := setSidecarValidatorManageOwner(sc, createFlags); err != nil {
return err
}
}
if genesisPath == "" {
// Default
defaultsKind, err = vm.PromptDefaults(app, defaultsKind)
if err != nil {
return err
}
}
// get vm version
vmVersion := createFlags.vmVersion
if vmVersion == "" && (createFlags.useLatestReleasedVMVersion || defaultsKind != vm.NoDefaults) {
vmVersion = latest
}
if createFlags.useLatestPreReleasedVMVersion {
vmVersion = preRelease
}
if vmVersion != latest && vmVersion != preRelease && vmVersion != "" && !semver.IsValid(vmVersion) {
return fmt.Errorf("invalid version string, should be semantic version (ex: v1.1.1): %s", vmVersion)
}
vmVersion, err = vm.PromptSubnetEVMVersion(app, vmVersion)
if err != nil {
return err
}
var tokenSymbol string
if genesisPath != "" {
if evmCompatibleGenesis, err := utils.FileIsSubnetEVMGenesis(genesisPath); err != nil {
return err
} else if !evmCompatibleGenesis {
return fmt.Errorf("the provided genesis file has no proper Subnet-EVM format")
}
tokenSymbol, err = vm.PromptTokenSymbol(app, createFlags.tokenSymbol)
if err != nil {
return err
}
deployICM, err = vm.PromptInterop(app, useICMFlag, defaultsKind, false)
if err != nil {
return err
}
ux.Logger.PrintToUser("importing genesis for blockchain %s", blockchainName)
genesisBytes, err = os.ReadFile(genesisPath)
if err != nil {
return err
}
} else {
var params vm.SubnetEVMGenesisParams
params, tokenSymbol, err = vm.PromptSubnetEVMGenesisParams(
app,
sc,
vmVersion,
createFlags.chainID,
createFlags.tokenSymbol,
blockchainName,
useICMFlag,
defaultsKind,
createFlags.useWarp,
createFlags.useExternalGasToken,
)
if err != nil {
return err
}
deployICM = params.UseICM
useExternalGasToken = params.UseExternalGasToken
genesisBytes, err = vm.CreateEVMGenesis(
app,
params,
icmInfo,
createFlags.addICMRegistryToGenesis,
sc.ProxyContractOwner,
createFlags.rewardBasisPoints,
createFlags.useACP99,
)
if err != nil {
return err
}
}
if sc, err = vm.CreateEvmSidecar(
sc,
app,
blockchainName,
vmVersion,
tokenSymbol,
true,
sovereign,
createFlags.useACP99,
); err != nil {
return err
}
} else {
if genesisPath == "" {
genesisPath, err = app.Prompt.CaptureExistingFilepath("Enter path to custom genesis")
if err != nil {
return err
}
}
genesisBytes, err = os.ReadFile(genesisPath)
if err != nil {
return err
}
var tokenSymbol string
if evmCompatibleGenesis := utils.ByteSliceIsSubnetEvmGenesis(genesisBytes); evmCompatibleGenesis {
if sovereign {
if err := setSidecarValidatorManageOwner(sc, createFlags); err != nil {
return err
}
}
tokenSymbol, err = vm.PromptTokenSymbol(app, createFlags.tokenSymbol)
if err != nil {
return err
}
deployICM, err = vm.PromptInterop(app, useICMFlag, defaultsKind, false)
if err != nil {
return err
}
}
if sc, err = vm.CreateCustomSidecar(
sc,
app,
blockchainName,
useRepo,
customVMRepoURL,
customVMBranch,
customVMBuildScript,
vmFile,
tokenSymbol,
sovereign,
); err != nil {
return err
}
}
if deployICM || useExternalGasToken {
sc.TeleporterReady = true
sc.RunRelayer = true // TODO: remove this once deploy asks if deploying relayer
sc.ExternalToken = useExternalGasToken
sc.TeleporterKey = constants.ICMKeyName
sc.TeleporterVersion = icmInfo.Version
if genesisPath != "" {
if evmCompatibleGenesis, err := utils.FileIsSubnetEVMGenesis(genesisPath); err != nil {
return err
} else if evmCompatibleGenesis {
// evm genesis file was given. make appropriate checks and customizations for ICM
genesisBytes, err = addSubnetEVMGenesisPrefundedAddress(
genesisBytes,
icmInfo.FundedAddress,
icmInfo.FundedBalance.String(),
)
if err != nil {
return err
}
}
}
}
if err = app.WriteGenesisFile(blockchainName, genesisBytes); err != nil {
return err
}
// subnet-evm check based on genesis
// covers both subnet-evm vms and custom vms
if hasSubnetEVMGenesis, _, err := app.HasSubnetEVMGenesis(blockchainName); err != nil {
return err
} else if hasSubnetEVMGenesis {
if createFlags.enableDebugging {
if err := SetBlockchainConf(
blockchainName,
vm.EvmDebugConfig,
constants.ChainConfigFileName,
); err != nil {
return err
}
} else {
if err := SetBlockchainConf(
blockchainName,
vm.EvmNonDebugConfig,
constants.ChainConfigFileName,
); err != nil {
return err
}
}
}
if err = app.CreateSidecar(sc); err != nil {
return err
}
if vmType == models.SubnetEvm {
err = sendMetrics(cmd, vmType.RepoName(), blockchainName)
if err != nil {
return err
}
}
ux.Logger.GreenCheckmarkToUser("Successfully created blockchain configuration")
ux.Logger.PrintToUser("Run 'avalanche blockchain describe' to view all created addresses and what their roles are")
return nil
}
func addSubnetEVMGenesisPrefundedAddress(genesisBytes []byte, address string, balance string) ([]byte, error) {
var genesisMap map[string]interface{}
if err := json.Unmarshal(genesisBytes, &genesisMap); err != nil {
return nil, err
}
allocI, ok := genesisMap["alloc"]
if !ok {
return nil, fmt.Errorf("alloc field not found on genesis")
}
alloc, ok := allocI.(map[string]interface{})
if !ok {
return nil, fmt.Errorf("expected genesis alloc field to be map[string]interface, found %T", allocI)
}
trimmedAddress := strings.TrimPrefix(address, "0x")
alloc[trimmedAddress] = map[string]interface{}{
"balance": balance,
}
genesisMap["alloc"] = alloc
return json.MarshalIndent(genesisMap, "", " ")
}
func sendMetrics(cmd *cobra.Command, repoName, blockchainName string) error {
flags := make(map[string]string)
flags[constants.SubnetType] = repoName
genesis, err := app.LoadEvmGenesis(blockchainName)
if err != nil {
return err
}
conf := genesis.Config.GenesisPrecompiles
precompiles := make([]string, 0, 6)
for precompileName := range conf {
precompileTag := "precompile-" + precompileName
flags[precompileTag] = precompileName
precompiles = append(precompiles, precompileName)
}
numAirdropAddresses := len(genesis.Alloc)
for address := range genesis.Alloc {
if address.String() != vm.PrefundedEwoqAddress.String() {
precompileTag := "precompile-" + constants.CustomAirdrop
flags[precompileTag] = constants.CustomAirdrop
precompiles = append(precompiles, constants.CustomAirdrop)
break
}
}
sort.Strings(precompiles)
precompilesJoined := strings.Join(precompiles, ",")
flags[constants.PrecompileType] = precompilesJoined
flags[constants.NumberOfAirdrops] = strconv.Itoa(numAirdropAddresses)
metrics.HandleTracking(cmd, constants.MetricsSubnetCreateCommand, app, flags)
return nil
}
func validateValidatorManagerOwnerFlag(input string) error {
// check that flag value is not P Chain or X Chain address
_, _, _, err := address.Parse(input)
if err == nil {
return fmt.Errorf("validator manager owner has to be EVM address (in 0x format)")
}
// if flag value is a key name, we get the C Chain address of the key and set it as the value of
// the validator manager address
if !common.IsHexAddress(input) {
k, err := key.LoadSoft(models.UndefinedNetwork.ID, app.GetKeyPath(input))
if err != nil {
return err
}
createFlags.validatorManagerOwner = k.C()
}
return nil
}
func checkInvalidSubnetNames(name string) error {
if name == "" {
return errEmptyBlockchainName
}
// this is currently exactly the same code as in avalanchego/vms/platformvm/create_chain_tx.go
for _, r := range name {
if r > unicode.MaxASCII || !(unicode.IsLetter(r) || unicode.IsNumber(r) || r == ' ') {
return errIllegalNameCharacter
}
}
return nil
}
func setSidecarValidatorManageOwner(sc *models.Sidecar, createFlags CreateFlags) error {
var err error
if createFlags.validatorManagerOwner == "" {
createFlags.validatorManagerOwner, err = getValidatorContractManagerAddr()
if err != nil {
return err
}
}
if err := validateValidatorManagerOwnerFlag(createFlags.validatorManagerOwner); err != nil {
return err
}
sc.ValidatorManagerOwner = createFlags.validatorManagerOwner
ux.Logger.GreenCheckmarkToUser("Validator Manager Contract owner address %s", createFlags.validatorManagerOwner)
// use the validator manager owner as the transparent proxy contract owner unless specified via cmd flag
if createFlags.proxyContractOwner != "" {
if err = validateValidatorManagerOwnerFlag(createFlags.proxyContractOwner); err != nil {
return err
}
sc.ProxyContractOwner = createFlags.proxyContractOwner
} else {
sc.ProxyContractOwner = sc.ValidatorManagerOwner
}
return nil
}