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  }