Skip to content

Commit d4718bc

Browse files
Merge pull request #719 from ava-labs/genesis-config-flag
Support Custom Genesis Config for Non-Standard NetworkIDs
2 parents 2c39947 + 5e99ba5 commit d4718bc

File tree

7 files changed

+541
-67
lines changed

7 files changed

+541
-67
lines changed

genesis/config.go

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ package genesis
66
import (
77
"encoding/hex"
88
"encoding/json"
9+
"fmt"
10+
"io/ioutil"
11+
"path"
912

1013
"github.com/ava-labs/avalanchego/ids"
1114
"github.com/ava-labs/avalanchego/utils/constants"
@@ -75,7 +78,7 @@ type Config struct {
7578
StartTime uint64 `json:"startTime"`
7679
InitialStakeDuration uint64 `json:"initialStakeDuration"`
7780
InitialStakeDurationOffset uint64 `json:"initialStakeDurationOffset"`
78-
InitialStakedFunds []ids.ShortID `json:"unitialStakedFunds"`
81+
InitialStakedFunds []ids.ShortID `json:"initialStakedFunds"`
7982
InitialStakers []Staker `json:"initialStakers"`
8083

8184
CChainGenesis string `json:"cChainGenesis"`
@@ -205,3 +208,24 @@ func GetConfig(networkID uint32) *Config {
205208
return &tempConfig
206209
}
207210
}
211+
212+
// GetConfigFile loads a *Config from a provided
213+
// filepath.
214+
func GetConfigFile(filepath string) (*Config, error) {
215+
b, err := ioutil.ReadFile(path.Clean(filepath))
216+
if err != nil {
217+
return nil, fmt.Errorf("unable to load file %s: %w", filepath, err)
218+
}
219+
220+
var unparsedConfig UnparsedConfig
221+
if err := json.Unmarshal(b, &unparsedConfig); err != nil {
222+
return nil, fmt.Errorf("could not unmarshal JSON: %w", err)
223+
}
224+
225+
config, err := unparsedConfig.Parse()
226+
if err != nil {
227+
return nil, fmt.Errorf("unable to parse config: %w", err)
228+
}
229+
230+
return &config, nil
231+
}

genesis/genesis.go

Lines changed: 165 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -28,18 +28,179 @@ import (
2828
)
2929

3030
const (
31-
defaultEncoding = formatting.Hex
32-
codecVersion = 0
31+
defaultEncoding = formatting.Hex
32+
codecVersion = 0
33+
configChainIDAlias = "X"
3334
)
3435

36+
// validateInitialStakedFunds ensures all staked
37+
// funds have allocations and that all staked
38+
// funds are unique.
39+
//
40+
// This function assumes that NetworkID in *Config has already
41+
// been checked for correctness.
42+
func validateInitialStakedFunds(config *Config) error {
43+
if len(config.InitialStakedFunds) == 0 {
44+
return errors.New("initial staked funds cannot be empty")
45+
}
46+
47+
allocationSet := ids.ShortSet{}
48+
initialStakedFundsSet := ids.ShortSet{}
49+
for _, allocation := range config.Allocations {
50+
// It is ok to have duplicates as different
51+
// ethAddrs could claim to the same avaxAddr.
52+
allocationSet.Add(allocation.AVAXAddr)
53+
}
54+
55+
for _, staker := range config.InitialStakedFunds {
56+
if initialStakedFundsSet.Contains(staker) {
57+
avaxAddr, err := formatting.FormatAddress(
58+
configChainIDAlias,
59+
constants.GetHRP(config.NetworkID),
60+
staker.Bytes(),
61+
)
62+
if err != nil {
63+
return fmt.Errorf(
64+
"unable to format address from %s",
65+
staker.String(),
66+
)
67+
}
68+
69+
return fmt.Errorf(
70+
"address %s is duplicated in initial staked funds",
71+
avaxAddr,
72+
)
73+
}
74+
initialStakedFundsSet.Add(staker)
75+
76+
if !allocationSet.Contains(staker) {
77+
avaxAddr, err := formatting.FormatAddress(
78+
configChainIDAlias,
79+
constants.GetHRP(config.NetworkID),
80+
staker.Bytes(),
81+
)
82+
if err != nil {
83+
return fmt.Errorf(
84+
"unable to format address from %s",
85+
staker.String(),
86+
)
87+
}
88+
89+
return fmt.Errorf(
90+
"address %s does not have an allocation to stake",
91+
avaxAddr,
92+
)
93+
}
94+
}
95+
96+
return nil
97+
}
98+
99+
// validateConfig returns an error if the provided
100+
// *Config is not considered valid.
101+
func validateConfig(networkID uint32, config *Config) error {
102+
if networkID != config.NetworkID {
103+
return fmt.Errorf(
104+
"networkID %d specified but genesis config contains networkID %d",
105+
networkID,
106+
config.NetworkID,
107+
)
108+
}
109+
110+
initialSupply, err := config.InitialSupply()
111+
switch {
112+
case err != nil:
113+
return fmt.Errorf("unable to calculate initial supply: %w", err)
114+
case initialSupply == 0:
115+
return errors.New("initial supply must be > 0")
116+
}
117+
118+
startTime := time.Unix(int64(config.StartTime), 0)
119+
if time.Since(startTime) < 0 {
120+
return fmt.Errorf(
121+
"start time cannot be in the future: %s",
122+
startTime,
123+
)
124+
}
125+
126+
// We don't impose any restrictions on the minimum
127+
// stake duration to enable complex testing configurations
128+
// but recommend setting a minimum duration of at least
129+
// 15 minutes.
130+
if config.InitialStakeDuration == 0 {
131+
return errors.New("initial stake duration must be > 0")
132+
}
133+
134+
if len(config.InitialStakers) == 0 {
135+
return errors.New("initial stakers must be > 0")
136+
}
137+
138+
offsetTimeRequired := config.InitialStakeDurationOffset * uint64(len(config.InitialStakers)-1)
139+
if offsetTimeRequired > config.InitialStakeDuration {
140+
return fmt.Errorf(
141+
"initial stake duration is %d but need at least %d with offset of %d",
142+
config.InitialStakeDuration,
143+
offsetTimeRequired,
144+
config.InitialStakeDurationOffset,
145+
)
146+
}
147+
148+
if err := validateInitialStakedFunds(config); err != nil {
149+
return fmt.Errorf("initial staked funds validation failed: %w", err)
150+
}
151+
152+
if len(config.CChainGenesis) == 0 {
153+
return errors.New("C-Chain genesis cannot be empty")
154+
}
155+
156+
return nil
157+
}
158+
35159
// Genesis returns the genesis data of the Platform Chain.
36160
//
37161
// Since an Avalanche network has exactly one Platform Chain, and the Platform
38162
// Chain defines the genesis state of the network (who is staking, which chains
39163
// exist, etc.), defining the genesis state of the Platform Chain is the same as
40164
// defining the genesis state of the network.
41165
//
42-
// The ID of the new network is [networkID].
166+
// Genesis accepts:
167+
// 1) The ID of the new network. [networkID]
168+
// 2) The location of a custom genesis config to load. [filepath]
169+
//
170+
// If [filepath] is empty or the given network ID is Mainnet, Testnet, or Local, loads the
171+
// network genesis state from predefined configs. If [filepath] is non-empty and networkID
172+
// isn't Mainnet, Testnet, or Local, loads the network genesis data from the config at [filepath].
173+
//
174+
// Genesis returns:
175+
// 1) The byte representation of the genesis state of the platform chain
176+
// (ie the genesis state of the network)
177+
// 2) The asset ID of AVAX
178+
func Genesis(networkID uint32, filepath string) ([]byte, ids.ID, error) {
179+
config := GetConfig(networkID)
180+
if len(filepath) > 0 {
181+
switch networkID {
182+
case constants.MainnetID, constants.TestnetID, constants.LocalID:
183+
return nil, ids.ID{}, fmt.Errorf(
184+
"cannot override genesis config for standard network %s (%d)",
185+
constants.NetworkName(networkID),
186+
networkID,
187+
)
188+
}
189+
190+
customConfig, err := GetConfigFile(filepath)
191+
if err != nil {
192+
return nil, ids.ID{}, fmt.Errorf("unable to load provided genesis config at %s: %w", filepath, err)
193+
}
194+
195+
config = customConfig
196+
}
197+
198+
if err := validateConfig(networkID, config); err != nil {
199+
return nil, ids.ID{}, fmt.Errorf("genesis config validation failed: %w", err)
200+
}
201+
202+
return FromConfig(config)
203+
}
43204

44205
// FromConfig returns:
45206
// 1) The byte representation of the genesis state of the platform chain
@@ -313,20 +474,8 @@ func splitAllocations(allocations []Allocation, numSplits int) [][]Allocation {
313474
return append(allNodeAllocations, currentNodeAllocation)
314475
}
315476

316-
// Genesis returns:
317-
// 1) The byte representation of the genesis state of the platform chain
318-
// (ie the genesis state of the network)
319-
// 2) The asset ID of AVAX
320-
func Genesis(networkID uint32) ([]byte, ids.ID, error) {
321-
return FromConfig(GetConfig(networkID))
322-
}
323-
324477
// VMGenesis ...
325-
func VMGenesis(networkID uint32, vmID ids.ID) (*platformvm.Tx, error) {
326-
genesisBytes, _, err := Genesis(networkID)
327-
if err != nil {
328-
return nil, err
329-
}
478+
func VMGenesis(genesisBytes []byte, vmID ids.ID) (*platformvm.Tx, error) {
330479
genesis := platformvm.Genesis{}
331480
if _, err := platformvm.GenesisCodec.Unmarshal(genesisBytes, &genesis); err != nil {
332481
return nil, fmt.Errorf("couldn't unmarshal genesis bytes due to: %w", err)

0 commit comments

Comments
 (0)