github.com/MetalBlockchain/metalgo@v1.11.9/genesis/config.go (about) 1 // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 package genesis 5 6 import ( 7 "cmp" 8 "encoding/base64" 9 "encoding/hex" 10 "encoding/json" 11 "errors" 12 "fmt" 13 "os" 14 "path/filepath" 15 16 "github.com/MetalBlockchain/metalgo/ids" 17 "github.com/MetalBlockchain/metalgo/utils" 18 "github.com/MetalBlockchain/metalgo/utils/constants" 19 "github.com/MetalBlockchain/metalgo/utils/formatting/address" 20 "github.com/MetalBlockchain/metalgo/utils/math" 21 "github.com/MetalBlockchain/metalgo/vms/platformvm/signer" 22 ) 23 24 var ( 25 _ utils.Sortable[Allocation] = Allocation{} 26 27 errInvalidGenesisJSON = errors.New("could not unmarshal genesis JSON") 28 ) 29 30 type LockedAmount struct { 31 Amount uint64 `json:"amount"` 32 Locktime uint64 `json:"locktime"` 33 } 34 35 type Allocation struct { 36 ETHAddr ids.ShortID `json:"ethAddr"` 37 AVAXAddr ids.ShortID `json:"avaxAddr"` 38 InitialAmount uint64 `json:"initialAmount"` 39 UnlockSchedule []LockedAmount `json:"unlockSchedule"` 40 } 41 42 func (a Allocation) Unparse(networkID uint32) (UnparsedAllocation, error) { 43 ua := UnparsedAllocation{ 44 InitialAmount: a.InitialAmount, 45 UnlockSchedule: a.UnlockSchedule, 46 ETHAddr: "0x" + hex.EncodeToString(a.ETHAddr.Bytes()), 47 } 48 avaxAddr, err := address.Format( 49 "X", 50 constants.GetHRP(networkID), 51 a.AVAXAddr.Bytes(), 52 ) 53 ua.AVAXAddr = avaxAddr 54 return ua, err 55 } 56 57 func (a Allocation) Compare(other Allocation) int { 58 if amountCmp := cmp.Compare(a.InitialAmount, other.InitialAmount); amountCmp != 0 { 59 return amountCmp 60 } 61 return a.AVAXAddr.Compare(other.AVAXAddr) 62 } 63 64 type Staker struct { 65 NodeID ids.NodeID `json:"nodeID"` 66 RewardAddress ids.ShortID `json:"rewardAddress"` 67 DelegationFee uint32 `json:"delegationFee"` 68 Signer *signer.ProofOfPossession `json:"signer,omitempty"` 69 } 70 71 func (s Staker) Unparse(networkID uint32) (UnparsedStaker, error) { 72 avaxAddr, err := address.Format( 73 "X", 74 constants.GetHRP(networkID), 75 s.RewardAddress.Bytes(), 76 ) 77 return UnparsedStaker{ 78 NodeID: s.NodeID, 79 RewardAddress: avaxAddr, 80 DelegationFee: s.DelegationFee, 81 Signer: s.Signer, 82 }, err 83 } 84 85 // Config contains the genesis addresses used to construct a genesis 86 type Config struct { 87 NetworkID uint32 `json:"networkID"` 88 89 Allocations []Allocation `json:"allocations"` 90 91 StartTime uint64 `json:"startTime"` 92 InitialStakeDuration uint64 `json:"initialStakeDuration"` 93 InitialStakeDurationOffset uint64 `json:"initialStakeDurationOffset"` 94 InitialStakedFunds []ids.ShortID `json:"initialStakedFunds"` 95 InitialStakers []Staker `json:"initialStakers"` 96 97 CChainGenesis string `json:"cChainGenesis"` 98 99 Message string `json:"message"` 100 } 101 102 func (c Config) Unparse() (UnparsedConfig, error) { 103 uc := UnparsedConfig{ 104 NetworkID: c.NetworkID, 105 Allocations: make([]UnparsedAllocation, len(c.Allocations)), 106 StartTime: c.StartTime, 107 InitialStakeDuration: c.InitialStakeDuration, 108 InitialStakeDurationOffset: c.InitialStakeDurationOffset, 109 InitialStakedFunds: make([]string, len(c.InitialStakedFunds)), 110 InitialStakers: make([]UnparsedStaker, len(c.InitialStakers)), 111 CChainGenesis: c.CChainGenesis, 112 Message: c.Message, 113 } 114 for i, a := range c.Allocations { 115 ua, err := a.Unparse(uc.NetworkID) 116 if err != nil { 117 return uc, err 118 } 119 uc.Allocations[i] = ua 120 } 121 for i, isa := range c.InitialStakedFunds { 122 avaxAddr, err := address.Format( 123 "X", 124 constants.GetHRP(uc.NetworkID), 125 isa.Bytes(), 126 ) 127 if err != nil { 128 return uc, err 129 } 130 uc.InitialStakedFunds[i] = avaxAddr 131 } 132 for i, is := range c.InitialStakers { 133 uis, err := is.Unparse(c.NetworkID) 134 if err != nil { 135 return uc, err 136 } 137 uc.InitialStakers[i] = uis 138 } 139 140 return uc, nil 141 } 142 143 func (c *Config) InitialSupply() (uint64, error) { 144 initialSupply := uint64(0) 145 for _, allocation := range c.Allocations { 146 newInitialSupply, err := math.Add64(initialSupply, allocation.InitialAmount) 147 if err != nil { 148 return 0, err 149 } 150 for _, unlock := range allocation.UnlockSchedule { 151 newInitialSupply, err = math.Add64(newInitialSupply, unlock.Amount) 152 if err != nil { 153 return 0, err 154 } 155 } 156 initialSupply = newInitialSupply 157 } 158 return initialSupply, nil 159 } 160 161 var ( 162 // MainnetConfig is the config that should be used to generate the mainnet 163 // genesis. 164 MainnetConfig Config 165 166 // TestnetConfig is the config that should be used to generate the testnet 167 // genesis. 168 TahoeConfig Config 169 170 // LocalConfig is the config that should be used to generate a local 171 // genesis. 172 LocalConfig Config 173 ) 174 175 func init() { 176 unparsedMainnetConfig := UnparsedConfig{} 177 unparsedTahoeConfig := UnparsedConfig{} 178 unparsedLocalConfig := UnparsedConfig{} 179 180 err := errors.Join( 181 json.Unmarshal(mainnetGenesisConfigJSON, &unparsedMainnetConfig), 182 json.Unmarshal(tahoeGenesisConfigJSON, &unparsedTahoeConfig), 183 json.Unmarshal(localGenesisConfigJSON, &unparsedLocalConfig), 184 ) 185 if err != nil { 186 panic(err) 187 } 188 189 MainnetConfig, err = unparsedMainnetConfig.Parse() 190 if err != nil { 191 panic(err) 192 } 193 194 TahoeConfig, err = unparsedTahoeConfig.Parse() 195 if err != nil { 196 panic(err) 197 } 198 199 LocalConfig, err = unparsedLocalConfig.Parse() 200 if err != nil { 201 panic(err) 202 } 203 } 204 205 func GetConfig(networkID uint32) *Config { 206 switch networkID { 207 case constants.MainnetID: 208 return &MainnetConfig 209 case constants.TahoeID: 210 return &TahoeConfig 211 case constants.LocalID: 212 return &LocalConfig 213 default: 214 tempConfig := LocalConfig 215 tempConfig.NetworkID = networkID 216 return &tempConfig 217 } 218 } 219 220 // GetConfigFile loads a *Config from a provided filepath. 221 func GetConfigFile(fp string) (*Config, error) { 222 bytes, err := os.ReadFile(filepath.Clean(fp)) 223 if err != nil { 224 return nil, fmt.Errorf("unable to load file %s: %w", fp, err) 225 } 226 return parseGenesisJSONBytesToConfig(bytes) 227 } 228 229 // GetConfigContent loads a *Config from a provided environment variable 230 func GetConfigContent(genesisContent string) (*Config, error) { 231 bytes, err := base64.StdEncoding.DecodeString(genesisContent) 232 if err != nil { 233 return nil, fmt.Errorf("unable to decode base64 content: %w", err) 234 } 235 return parseGenesisJSONBytesToConfig(bytes) 236 } 237 238 func parseGenesisJSONBytesToConfig(bytes []byte) (*Config, error) { 239 var unparsedConfig UnparsedConfig 240 if err := json.Unmarshal(bytes, &unparsedConfig); err != nil { 241 return nil, fmt.Errorf("%w: %w", errInvalidGenesisJSON, err) 242 } 243 244 config, err := unparsedConfig.Parse() 245 if err != nil { 246 return nil, fmt.Errorf("unable to parse config: %w", err) 247 } 248 return &config, nil 249 }