github.com/MetalBlockchain/metalgo@v1.11.9/tests/fixture/tmpnet/genesis.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package tmpnet
     5  
     6  import (
     7  	"encoding/json"
     8  	"errors"
     9  	"fmt"
    10  	"math/big"
    11  	"time"
    12  
    13  	"github.com/MetalBlockchain/coreth/core"
    14  	"github.com/MetalBlockchain/coreth/params"
    15  	"github.com/MetalBlockchain/coreth/plugin/evm"
    16  
    17  	"github.com/MetalBlockchain/metalgo/genesis"
    18  	"github.com/MetalBlockchain/metalgo/ids"
    19  	"github.com/MetalBlockchain/metalgo/utils/constants"
    20  	"github.com/MetalBlockchain/metalgo/utils/crypto/secp256k1"
    21  	"github.com/MetalBlockchain/metalgo/utils/formatting/address"
    22  	"github.com/MetalBlockchain/metalgo/utils/units"
    23  	"github.com/MetalBlockchain/metalgo/vms/platformvm/reward"
    24  )
    25  
    26  const (
    27  	defaultGasLimit = uint64(100_000_000) // Gas limit is arbitrary
    28  
    29  	// Arbitrarily large amount of AVAX to fund keys on the X-Chain for testing
    30  	defaultFundedKeyXChainAmount = 30 * units.MegaAvax
    31  )
    32  
    33  var (
    34  	// Arbitrarily large amount of AVAX (10^12) to fund keys on the C-Chain for testing
    35  	defaultFundedKeyCChainAmount = new(big.Int).Exp(big.NewInt(10), big.NewInt(30), nil)
    36  
    37  	errNoKeysForGenesis           = errors.New("no keys to fund for genesis")
    38  	errInvalidNetworkIDForGenesis = errors.New("network ID can't be mainnet, testnet or local network ID for genesis")
    39  	errMissingStakersForGenesis   = errors.New("no stakers provided for genesis")
    40  )
    41  
    42  // Helper type to simplify configuring X-Chain genesis balances
    43  type XChainBalanceMap map[ids.ShortID]uint64
    44  
    45  // Create a genesis struct valid for bootstrapping a test
    46  // network. Note that many of the genesis fields (e.g. reward
    47  // addresses) are randomly generated or hard-coded.
    48  func NewTestGenesis(
    49  	networkID uint32,
    50  	nodes []*Node,
    51  	keysToFund []*secp256k1.PrivateKey,
    52  ) (*genesis.UnparsedConfig, error) {
    53  	// Validate inputs
    54  	switch networkID {
    55  	case constants.TahoeID, constants.MainnetID, constants.LocalID:
    56  		return nil, errInvalidNetworkIDForGenesis
    57  	}
    58  	if len(nodes) == 0 {
    59  		return nil, errMissingStakersForGenesis
    60  	}
    61  	if len(keysToFund) == 0 {
    62  		return nil, errNoKeysForGenesis
    63  	}
    64  
    65  	initialStakers, err := stakersForNodes(networkID, nodes)
    66  	if err != nil {
    67  		return nil, fmt.Errorf("failed to configure stakers for nodes: %w", err)
    68  	}
    69  
    70  	// Address that controls stake doesn't matter -- generate it randomly
    71  	stakeAddress, err := address.Format(
    72  		"X",
    73  		constants.GetHRP(networkID),
    74  		ids.GenerateTestShortID().Bytes(),
    75  	)
    76  	if err != nil {
    77  		return nil, fmt.Errorf("failed to format stake address: %w", err)
    78  	}
    79  
    80  	// Ensure the total stake allows a MegaAvax per staker
    81  	totalStake := uint64(len(initialStakers)) * units.MegaAvax
    82  
    83  	// The eth address is only needed to link pre-mainnet assets. Until that capability
    84  	// becomes necessary for testing, use a bogus address.
    85  	//
    86  	// Reference: https://github.com/MetalBlockchain/metalgo/issues/1365#issuecomment-1511508767
    87  	ethAddress := "0x0000000000000000000000000000000000000000"
    88  
    89  	now := time.Now()
    90  
    91  	config := &genesis.UnparsedConfig{
    92  		NetworkID: networkID,
    93  		Allocations: []genesis.UnparsedAllocation{
    94  			{
    95  				ETHAddr:       ethAddress,
    96  				AVAXAddr:      stakeAddress,
    97  				InitialAmount: 0,
    98  				UnlockSchedule: []genesis.LockedAmount{ // Provides stake to validators
    99  					{
   100  						Amount:   totalStake,
   101  						Locktime: uint64(now.Add(7 * 24 * time.Hour).Unix()), // 1 Week
   102  					},
   103  				},
   104  			},
   105  		},
   106  		StartTime:                  uint64(now.Unix()),
   107  		InitialStakedFunds:         []string{stakeAddress},
   108  		InitialStakeDuration:       365 * 24 * 60 * 60, // 1 year
   109  		InitialStakeDurationOffset: 90 * 60,            // 90 minutes
   110  		Message:                    "hello avalanche!",
   111  		InitialStakers:             initialStakers,
   112  	}
   113  
   114  	// Ensure pre-funded keys have arbitrary large balances on both chains to support testing
   115  	xChainBalances := make(XChainBalanceMap, len(keysToFund))
   116  	cChainBalances := make(core.GenesisAlloc, len(keysToFund))
   117  	for _, key := range keysToFund {
   118  		xChainBalances[key.Address()] = defaultFundedKeyXChainAmount
   119  		cChainBalances[evm.GetEthAddress(key)] = core.GenesisAccount{
   120  			Balance: defaultFundedKeyCChainAmount,
   121  		}
   122  	}
   123  
   124  	// Set X-Chain balances
   125  	for xChainAddress, balance := range xChainBalances {
   126  		avaxAddr, err := address.Format("X", constants.GetHRP(networkID), xChainAddress[:])
   127  		if err != nil {
   128  			return nil, fmt.Errorf("failed to format X-Chain address: %w", err)
   129  		}
   130  		config.Allocations = append(
   131  			config.Allocations,
   132  			genesis.UnparsedAllocation{
   133  				ETHAddr:       ethAddress,
   134  				AVAXAddr:      avaxAddr,
   135  				InitialAmount: balance,
   136  				UnlockSchedule: []genesis.LockedAmount{
   137  					{
   138  						Amount: 20 * units.MegaAvax,
   139  					},
   140  					{
   141  						Amount:   totalStake,
   142  						Locktime: uint64(now.Add(7 * 24 * time.Hour).Unix()), // 1 Week
   143  					},
   144  				},
   145  			},
   146  		)
   147  	}
   148  
   149  	// Define C-Chain genesis
   150  	cChainGenesis := &core.Genesis{
   151  		Config:     params.AvalancheLocalChainConfig,
   152  		Difficulty: big.NewInt(0), // Difficulty is a mandatory field
   153  		GasLimit:   defaultGasLimit,
   154  		Alloc:      cChainBalances,
   155  	}
   156  	cChainGenesisBytes, err := json.Marshal(cChainGenesis)
   157  	if err != nil {
   158  		return nil, fmt.Errorf("failed to marshal C-Chain genesis: %w", err)
   159  	}
   160  	config.CChainGenesis = string(cChainGenesisBytes)
   161  
   162  	return config, nil
   163  }
   164  
   165  // Returns staker configuration for the given set of nodes.
   166  func stakersForNodes(networkID uint32, nodes []*Node) ([]genesis.UnparsedStaker, error) {
   167  	// Give staking rewards for initial validators to a random address. Any testing of staking rewards
   168  	// will be easier to perform with nodes other than the initial validators since the timing of
   169  	// staking can be more easily controlled.
   170  	rewardAddr, err := address.Format("X", constants.GetHRP(networkID), ids.GenerateTestShortID().Bytes())
   171  	if err != nil {
   172  		return nil, fmt.Errorf("failed to format reward address: %w", err)
   173  	}
   174  
   175  	// Configure provided nodes as initial stakers
   176  	initialStakers := make([]genesis.UnparsedStaker, len(nodes))
   177  	for i, node := range nodes {
   178  		pop, err := node.GetProofOfPossession()
   179  		if err != nil {
   180  			return nil, fmt.Errorf("failed to derive proof of possession for node %s: %w", node.NodeID, err)
   181  		}
   182  		initialStakers[i] = genesis.UnparsedStaker{
   183  			NodeID:        node.NodeID,
   184  			RewardAddress: rewardAddr,
   185  			DelegationFee: .01 * reward.PercentDenominator,
   186  			Signer:        pop,
   187  		}
   188  	}
   189  
   190  	return initialStakers, nil
   191  }