github.com/MetalBlockchain/metalgo@v1.11.9/vms/platformvm/api/static_service.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package api
     5  
     6  import (
     7  	"cmp"
     8  	"errors"
     9  	"fmt"
    10  	"net/http"
    11  
    12  	"github.com/MetalBlockchain/metalgo/ids"
    13  	"github.com/MetalBlockchain/metalgo/utils"
    14  	"github.com/MetalBlockchain/metalgo/utils/formatting"
    15  	"github.com/MetalBlockchain/metalgo/utils/formatting/address"
    16  	"github.com/MetalBlockchain/metalgo/utils/json"
    17  	"github.com/MetalBlockchain/metalgo/utils/math"
    18  	"github.com/MetalBlockchain/metalgo/vms/components/avax"
    19  	"github.com/MetalBlockchain/metalgo/vms/platformvm/genesis"
    20  	"github.com/MetalBlockchain/metalgo/vms/platformvm/signer"
    21  	"github.com/MetalBlockchain/metalgo/vms/platformvm/stakeable"
    22  	"github.com/MetalBlockchain/metalgo/vms/platformvm/txs"
    23  	"github.com/MetalBlockchain/metalgo/vms/platformvm/txs/txheap"
    24  	"github.com/MetalBlockchain/metalgo/vms/secp256k1fx"
    25  )
    26  
    27  // Note that since an Avalanche network has exactly one Platform Chain,
    28  // and the Platform Chain defines the genesis state of the network
    29  // (who is staking, which chains exist, etc.), defining the genesis
    30  // state of the Platform Chain is the same as defining the genesis
    31  // state of the network.
    32  
    33  var (
    34  	errUTXOHasNoValue         = errors.New("genesis UTXO has no value")
    35  	errValidatorHasNoWeight   = errors.New("validator has not weight")
    36  	errValidatorAlreadyExited = errors.New("validator would have already unstaked")
    37  	errStakeOverflow          = errors.New("validator stake exceeds limit")
    38  
    39  	_ utils.Sortable[UTXO] = UTXO{}
    40  )
    41  
    42  // StaticService defines the static API methods exposed by the platform VM
    43  type StaticService struct{}
    44  
    45  // UTXO is a UTXO on the Platform Chain that exists at the chain's genesis.
    46  type UTXO struct {
    47  	Locktime json.Uint64 `json:"locktime"`
    48  	Amount   json.Uint64 `json:"amount"`
    49  	Address  string      `json:"address"`
    50  	Message  string      `json:"message"`
    51  }
    52  
    53  // TODO can we define this on *UTXO?
    54  func (utxo UTXO) Compare(other UTXO) int {
    55  	if locktimeCmp := cmp.Compare(utxo.Locktime, other.Locktime); locktimeCmp != 0 {
    56  		return locktimeCmp
    57  	}
    58  	if amountCmp := cmp.Compare(utxo.Amount, other.Amount); amountCmp != 0 {
    59  		return amountCmp
    60  	}
    61  
    62  	utxoAddr, err := bech32ToID(utxo.Address)
    63  	if err != nil {
    64  		return 0
    65  	}
    66  
    67  	otherAddr, err := bech32ToID(other.Address)
    68  	if err != nil {
    69  		return 0
    70  	}
    71  
    72  	return utxoAddr.Compare(otherAddr)
    73  }
    74  
    75  // TODO: Refactor APIStaker, APIValidators and merge them together for
    76  //       PermissionedValidators + PermissionlessValidators.
    77  
    78  // APIStaker is the representation of a staker sent via APIs.
    79  // [TxID] is the txID of the transaction that added this staker.
    80  // [Amount] is the amount of tokens being staked.
    81  // [StartTime] is the Unix time when they start staking
    82  // [Endtime] is the Unix time repr. of when they are done staking
    83  // [NodeID] is the node ID of the staker
    84  // [Uptime] is the observed uptime of this staker
    85  type Staker struct {
    86  	TxID      ids.ID      `json:"txID"`
    87  	StartTime json.Uint64 `json:"startTime"`
    88  	EndTime   json.Uint64 `json:"endTime"`
    89  	Weight    json.Uint64 `json:"weight"`
    90  	NodeID    ids.NodeID  `json:"nodeID"`
    91  
    92  	// Deprecated: Use Weight instead
    93  	// TODO: remove [StakeAmount] after enough time for dependencies to update
    94  	StakeAmount *json.Uint64 `json:"stakeAmount,omitempty"`
    95  }
    96  
    97  // GenesisValidator should to be used for genesis validators only.
    98  type GenesisValidator Staker
    99  
   100  // Owner is the repr. of a reward owner sent over APIs.
   101  type Owner struct {
   102  	Locktime  json.Uint64 `json:"locktime"`
   103  	Threshold json.Uint32 `json:"threshold"`
   104  	Addresses []string    `json:"addresses"`
   105  }
   106  
   107  // PermissionlessValidator is the repr. of a permissionless validator sent over
   108  // APIs.
   109  type PermissionlessValidator struct {
   110  	Staker
   111  	// Deprecated: RewardOwner has been replaced by ValidationRewardOwner and
   112  	//             DelegationRewardOwner.
   113  	RewardOwner *Owner `json:"rewardOwner,omitempty"`
   114  	// The owner of the rewards from the validation period, if applicable.
   115  	ValidationRewardOwner *Owner `json:"validationRewardOwner,omitempty"`
   116  	// The owner of the rewards from delegations during the validation period,
   117  	// if applicable.
   118  	DelegationRewardOwner  *Owner                    `json:"delegationRewardOwner,omitempty"`
   119  	PotentialReward        *json.Uint64              `json:"potentialReward,omitempty"`
   120  	AccruedDelegateeReward *json.Uint64              `json:"accruedDelegateeReward,omitempty"`
   121  	DelegationFee          json.Float32              `json:"delegationFee"`
   122  	ExactDelegationFee     *json.Uint32              `json:"exactDelegationFee,omitempty"`
   123  	Uptime                 *json.Float32             `json:"uptime,omitempty"`
   124  	Connected              bool                      `json:"connected"`
   125  	Staked                 []UTXO                    `json:"staked,omitempty"`
   126  	Signer                 *signer.ProofOfPossession `json:"signer,omitempty"`
   127  
   128  	// The delegators delegating to this validator
   129  	DelegatorCount  *json.Uint64        `json:"delegatorCount,omitempty"`
   130  	DelegatorWeight *json.Uint64        `json:"delegatorWeight,omitempty"`
   131  	Delegators      *[]PrimaryDelegator `json:"delegators,omitempty"`
   132  }
   133  
   134  // GenesisPermissionlessValidator should to be used for genesis validators only.
   135  type GenesisPermissionlessValidator struct {
   136  	GenesisValidator
   137  	RewardOwner        *Owner                    `json:"rewardOwner,omitempty"`
   138  	DelegationFee      json.Float32              `json:"delegationFee"`
   139  	ExactDelegationFee *json.Uint32              `json:"exactDelegationFee,omitempty"`
   140  	Staked             []UTXO                    `json:"staked,omitempty"`
   141  	Signer             *signer.ProofOfPossession `json:"signer,omitempty"`
   142  }
   143  
   144  // PermissionedValidator is the repr. of a permissioned validator sent over APIs.
   145  type PermissionedValidator struct {
   146  	Staker
   147  	// The owner the staking reward, if applicable, will go to
   148  	Connected bool          `json:"connected"`
   149  	Uptime    *json.Float32 `json:"uptime,omitempty"`
   150  }
   151  
   152  // PrimaryDelegator is the repr. of a primary network delegator sent over APIs.
   153  type PrimaryDelegator struct {
   154  	Staker
   155  	RewardOwner     *Owner       `json:"rewardOwner,omitempty"`
   156  	PotentialReward *json.Uint64 `json:"potentialReward,omitempty"`
   157  }
   158  
   159  // Chain defines a chain that exists
   160  // at the network's genesis.
   161  // [GenesisData] is the initial state of the chain.
   162  // [VMID] is the ID of the VM this chain runs.
   163  // [FxIDs] are the IDs of the Fxs the chain supports.
   164  // [Name] is a human-readable, non-unique name for the chain.
   165  // [SubnetID] is the ID of the subnet that validates the chain
   166  type Chain struct {
   167  	GenesisData string   `json:"genesisData"`
   168  	VMID        ids.ID   `json:"vmID"`
   169  	FxIDs       []ids.ID `json:"fxIDs"`
   170  	Name        string   `json:"name"`
   171  	SubnetID    ids.ID   `json:"subnetID"`
   172  }
   173  
   174  // BuildGenesisArgs are the arguments used to create
   175  // the genesis data of the Platform Chain.
   176  // [NetworkID] is the ID of the network
   177  // [UTXOs] are the UTXOs on the Platform Chain that exist at genesis.
   178  // [Validators] are the validators of the primary network at genesis.
   179  // [Chains] are the chains that exist at genesis.
   180  // [Time] is the Platform Chain's time at network genesis.
   181  type BuildGenesisArgs struct {
   182  	AvaxAssetID   ids.ID                           `json:"avaxAssetID"`
   183  	NetworkID     json.Uint32                      `json:"networkID"`
   184  	UTXOs         []UTXO                           `json:"utxos"`
   185  	Validators    []GenesisPermissionlessValidator `json:"validators"`
   186  	Chains        []Chain                          `json:"chains"`
   187  	Time          json.Uint64                      `json:"time"`
   188  	InitialSupply json.Uint64                      `json:"initialSupply"`
   189  	Message       string                           `json:"message"`
   190  	Encoding      formatting.Encoding              `json:"encoding"`
   191  }
   192  
   193  // BuildGenesisReply is the reply from BuildGenesis
   194  type BuildGenesisReply struct {
   195  	Bytes    string              `json:"bytes"`
   196  	Encoding formatting.Encoding `json:"encoding"`
   197  }
   198  
   199  // bech32ToID takes bech32 address and produces a shortID
   200  func bech32ToID(addrStr string) (ids.ShortID, error) {
   201  	_, addrBytes, err := address.ParseBech32(addrStr)
   202  	if err != nil {
   203  		return ids.ShortID{}, err
   204  	}
   205  	return ids.ToShortID(addrBytes)
   206  }
   207  
   208  // BuildGenesis build the genesis state of the Platform Chain (and thereby the Avalanche network.)
   209  func (*StaticService) BuildGenesis(_ *http.Request, args *BuildGenesisArgs, reply *BuildGenesisReply) error {
   210  	// Specify the UTXOs on the Platform chain that exist at genesis.
   211  	utxos := make([]*genesis.UTXO, 0, len(args.UTXOs))
   212  	for i, apiUTXO := range args.UTXOs {
   213  		if apiUTXO.Amount == 0 {
   214  			return errUTXOHasNoValue
   215  		}
   216  		addrID, err := bech32ToID(apiUTXO.Address)
   217  		if err != nil {
   218  			return err
   219  		}
   220  
   221  		utxo := avax.UTXO{
   222  			UTXOID: avax.UTXOID{
   223  				TxID:        ids.Empty,
   224  				OutputIndex: uint32(i),
   225  			},
   226  			Asset: avax.Asset{ID: args.AvaxAssetID},
   227  			Out: &secp256k1fx.TransferOutput{
   228  				Amt: uint64(apiUTXO.Amount),
   229  				OutputOwners: secp256k1fx.OutputOwners{
   230  					Locktime:  0,
   231  					Threshold: 1,
   232  					Addrs:     []ids.ShortID{addrID},
   233  				},
   234  			},
   235  		}
   236  		if apiUTXO.Locktime > args.Time {
   237  			utxo.Out = &stakeable.LockOut{
   238  				Locktime:        uint64(apiUTXO.Locktime),
   239  				TransferableOut: utxo.Out.(avax.TransferableOut),
   240  			}
   241  		}
   242  		messageBytes, err := formatting.Decode(args.Encoding, apiUTXO.Message)
   243  		if err != nil {
   244  			return fmt.Errorf("problem decoding UTXO message bytes: %w", err)
   245  		}
   246  		utxos = append(utxos, &genesis.UTXO{
   247  			UTXO:    utxo,
   248  			Message: messageBytes,
   249  		})
   250  	}
   251  
   252  	// Specify the validators that are validating the primary network at genesis.
   253  	vdrs := txheap.NewByEndTime()
   254  	for _, vdr := range args.Validators {
   255  		weight := uint64(0)
   256  		stake := make([]*avax.TransferableOutput, len(vdr.Staked))
   257  		utils.Sort(vdr.Staked)
   258  		for i, apiUTXO := range vdr.Staked {
   259  			addrID, err := bech32ToID(apiUTXO.Address)
   260  			if err != nil {
   261  				return err
   262  			}
   263  
   264  			utxo := &avax.TransferableOutput{
   265  				Asset: avax.Asset{ID: args.AvaxAssetID},
   266  				Out: &secp256k1fx.TransferOutput{
   267  					Amt: uint64(apiUTXO.Amount),
   268  					OutputOwners: secp256k1fx.OutputOwners{
   269  						Locktime:  0,
   270  						Threshold: 1,
   271  						Addrs:     []ids.ShortID{addrID},
   272  					},
   273  				},
   274  			}
   275  			if apiUTXO.Locktime > args.Time {
   276  				utxo.Out = &stakeable.LockOut{
   277  					Locktime:        uint64(apiUTXO.Locktime),
   278  					TransferableOut: utxo.Out,
   279  				}
   280  			}
   281  			stake[i] = utxo
   282  
   283  			newWeight, err := math.Add64(weight, uint64(apiUTXO.Amount))
   284  			if err != nil {
   285  				return errStakeOverflow
   286  			}
   287  			weight = newWeight
   288  		}
   289  
   290  		if weight == 0 {
   291  			return errValidatorHasNoWeight
   292  		}
   293  		if uint64(vdr.EndTime) <= uint64(args.Time) {
   294  			return errValidatorAlreadyExited
   295  		}
   296  
   297  		owner := &secp256k1fx.OutputOwners{
   298  			Locktime:  uint64(vdr.RewardOwner.Locktime),
   299  			Threshold: uint32(vdr.RewardOwner.Threshold),
   300  		}
   301  		for _, addrStr := range vdr.RewardOwner.Addresses {
   302  			addrID, err := bech32ToID(addrStr)
   303  			if err != nil {
   304  				return err
   305  			}
   306  			owner.Addrs = append(owner.Addrs, addrID)
   307  		}
   308  		utils.Sort(owner.Addrs)
   309  
   310  		delegationFee := uint32(0)
   311  		if vdr.ExactDelegationFee != nil {
   312  			delegationFee = uint32(*vdr.ExactDelegationFee)
   313  		}
   314  
   315  		var (
   316  			baseTx = txs.BaseTx{BaseTx: avax.BaseTx{
   317  				NetworkID:    uint32(args.NetworkID),
   318  				BlockchainID: ids.Empty,
   319  			}}
   320  			validator = txs.Validator{
   321  				NodeID: vdr.NodeID,
   322  				Start:  uint64(args.Time),
   323  				End:    uint64(vdr.EndTime),
   324  				Wght:   weight,
   325  			}
   326  			tx *txs.Tx
   327  		)
   328  		if vdr.Signer == nil {
   329  			tx = &txs.Tx{Unsigned: &txs.AddValidatorTx{
   330  				BaseTx:           baseTx,
   331  				Validator:        validator,
   332  				StakeOuts:        stake,
   333  				RewardsOwner:     owner,
   334  				DelegationShares: delegationFee,
   335  			}}
   336  		} else {
   337  			tx = &txs.Tx{Unsigned: &txs.AddPermissionlessValidatorTx{
   338  				BaseTx:                baseTx,
   339  				Validator:             validator,
   340  				Signer:                vdr.Signer,
   341  				StakeOuts:             stake,
   342  				ValidatorRewardsOwner: owner,
   343  				DelegatorRewardsOwner: owner,
   344  				DelegationShares:      delegationFee,
   345  			}}
   346  		}
   347  
   348  		if err := tx.Initialize(txs.GenesisCodec); err != nil {
   349  			return err
   350  		}
   351  
   352  		vdrs.Add(tx)
   353  	}
   354  
   355  	// Specify the chains that exist at genesis.
   356  	chains := []*txs.Tx{}
   357  	for _, chain := range args.Chains {
   358  		genesisBytes, err := formatting.Decode(args.Encoding, chain.GenesisData)
   359  		if err != nil {
   360  			return fmt.Errorf("problem decoding chain genesis data: %w", err)
   361  		}
   362  		tx := &txs.Tx{Unsigned: &txs.CreateChainTx{
   363  			BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{
   364  				NetworkID:    uint32(args.NetworkID),
   365  				BlockchainID: ids.Empty,
   366  			}},
   367  			SubnetID:    chain.SubnetID,
   368  			ChainName:   chain.Name,
   369  			VMID:        chain.VMID,
   370  			FxIDs:       chain.FxIDs,
   371  			GenesisData: genesisBytes,
   372  			SubnetAuth:  &secp256k1fx.Input{},
   373  		}}
   374  		if err := tx.Initialize(txs.GenesisCodec); err != nil {
   375  			return err
   376  		}
   377  
   378  		chains = append(chains, tx)
   379  	}
   380  
   381  	validatorTxs := vdrs.List()
   382  
   383  	// genesis holds the genesis state
   384  	g := genesis.Genesis{
   385  		UTXOs:         utxos,
   386  		Validators:    validatorTxs,
   387  		Chains:        chains,
   388  		Timestamp:     uint64(args.Time),
   389  		InitialSupply: uint64(args.InitialSupply),
   390  		Message:       args.Message,
   391  	}
   392  
   393  	// Marshal genesis to bytes
   394  	bytes, err := genesis.Codec.Marshal(genesis.CodecVersion, g)
   395  	if err != nil {
   396  		return fmt.Errorf("couldn't marshal genesis: %w", err)
   397  	}
   398  	reply.Bytes, err = formatting.Encode(args.Encoding, bytes)
   399  	if err != nil {
   400  		return fmt.Errorf("couldn't encode genesis as string: %w", err)
   401  	}
   402  	reply.Encoding = args.Encoding
   403  	return nil
   404  }