github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/tm2/pkg/bft/types/genesis.go (about)

     1  package types
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"time"
     7  
     8  	"github.com/gnolang/gno/tm2/pkg/amino"
     9  	abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types"
    10  	tmtime "github.com/gnolang/gno/tm2/pkg/bft/types/time"
    11  	"github.com/gnolang/gno/tm2/pkg/crypto"
    12  	"github.com/gnolang/gno/tm2/pkg/errors"
    13  	osm "github.com/gnolang/gno/tm2/pkg/os"
    14  )
    15  
    16  const (
    17  	// MaxChainIDLen is a maximum length of the chain ID.
    18  	MaxChainIDLen = 50
    19  )
    20  
    21  var (
    22  	ErrEmptyChainID                = errors.New("chain ID is empty")
    23  	ErrLongChainID                 = fmt.Errorf("chain ID cannot be longer than %d chars", MaxChainIDLen)
    24  	ErrInvalidGenesisTime          = errors.New("invalid genesis time")
    25  	ErrNoValidators                = errors.New("no validators in set")
    26  	ErrInvalidValidatorVotingPower = errors.New("validator has no voting power")
    27  	ErrInvalidValidatorAddress     = errors.New("invalid validator address")
    28  	ErrValidatorPubKeyMismatch     = errors.New("validator public key and address mismatch")
    29  )
    30  
    31  // ------------------------------------------------------------
    32  // core types for a genesis definition
    33  // NOTE: any changes to the genesis definition should
    34  // be reflected in the documentation:
    35  // docs/tendermint-core/using-tendermint.md
    36  
    37  // GenesisValidator is an initial validator.
    38  type GenesisValidator struct {
    39  	Address Address       `json:"address"`
    40  	PubKey  crypto.PubKey `json:"pub_key"`
    41  	Power   int64         `json:"power"`
    42  	Name    string        `json:"name"`
    43  }
    44  
    45  // GenesisDoc defines the initial conditions for a tendermint blockchain, in particular its validator set.
    46  type GenesisDoc struct {
    47  	GenesisTime     time.Time            `json:"genesis_time"`
    48  	ChainID         string               `json:"chain_id"`
    49  	ConsensusParams abci.ConsensusParams `json:"consensus_params,omitempty"`
    50  	Validators      []GenesisValidator   `json:"validators,omitempty"`
    51  	AppHash         []byte               `json:"app_hash"`
    52  	AppState        interface{}          `json:"app_state,omitempty"`
    53  }
    54  
    55  // SaveAs is a utility method for saving GenensisDoc as a JSON file.
    56  func (genDoc *GenesisDoc) SaveAs(file string) error {
    57  	genDocBytes, err := amino.MarshalJSONIndent(genDoc, "", "  ")
    58  	if err != nil {
    59  		return err
    60  	}
    61  	return osm.WriteFile(file, genDocBytes, 0o644)
    62  }
    63  
    64  // ValidatorHash returns the hash of the validator set contained in the GenesisDoc
    65  func (genDoc *GenesisDoc) ValidatorHash() []byte {
    66  	vals := make([]*Validator, len(genDoc.Validators))
    67  	for i, v := range genDoc.Validators {
    68  		vals[i] = NewValidator(v.PubKey, v.Power)
    69  	}
    70  	vset := NewValidatorSet(vals)
    71  	return vset.Hash()
    72  }
    73  
    74  // Validate validates the genesis doc
    75  func (genDoc *GenesisDoc) Validate() error {
    76  	// Make sure the chain ID is not empty
    77  	if genDoc.ChainID == "" {
    78  		return ErrEmptyChainID
    79  	}
    80  
    81  	// Make sure the chain ID is < max chain ID length
    82  	if len(genDoc.ChainID) > MaxChainIDLen {
    83  		return ErrLongChainID
    84  	}
    85  
    86  	// Make sure the genesis time is valid
    87  	if genDoc.GenesisTime.IsZero() {
    88  		return ErrInvalidGenesisTime
    89  	}
    90  
    91  	// Validate the consensus params
    92  	if consensusParamsErr := ValidateConsensusParams(genDoc.ConsensusParams); consensusParamsErr != nil {
    93  		return consensusParamsErr
    94  	}
    95  
    96  	// Make sure there are validators in the set
    97  	if len(genDoc.Validators) == 0 {
    98  		return ErrNoValidators
    99  	}
   100  
   101  	// Make sure the validators are valid
   102  	for _, v := range genDoc.Validators {
   103  		// Check the voting power
   104  		if v.Power == 0 {
   105  			return fmt.Errorf("%w, %s", ErrInvalidValidatorVotingPower, v.Name)
   106  		}
   107  
   108  		// Check the address
   109  		if v.Address.IsZero() {
   110  			return fmt.Errorf("%w, %s", ErrInvalidValidatorAddress, v.Name)
   111  		}
   112  
   113  		// Check the pub key -> address matching
   114  		if v.PubKey.Address() != v.Address {
   115  			return fmt.Errorf("%w, %s", ErrValidatorPubKeyMismatch, v.Name)
   116  		}
   117  	}
   118  
   119  	return nil
   120  }
   121  
   122  // ValidateAndComplete checks that all necessary fields are present
   123  // and fills in defaults for optional fields left empty
   124  func (genDoc *GenesisDoc) ValidateAndComplete() error {
   125  	if genDoc.ChainID == "" {
   126  		return errors.New("Genesis doc must include non-empty chain_id")
   127  	}
   128  	if len(genDoc.ChainID) > MaxChainIDLen {
   129  		return errors.New("chain_id in genesis doc is too long (max: %d)", MaxChainIDLen)
   130  	}
   131  
   132  	// Start from defaults and fill in consensus params from GenesisDoc.
   133  	genDoc.ConsensusParams = DefaultConsensusParams().Update(genDoc.ConsensusParams)
   134  	if err := ValidateConsensusParams(genDoc.ConsensusParams); err != nil {
   135  		return err
   136  	}
   137  
   138  	for i, v := range genDoc.Validators {
   139  		if v.Power == 0 {
   140  			return errors.New("The genesis file cannot contain validators with no voting power: %v", v)
   141  		}
   142  		if v.Address.IsZero() {
   143  			genDoc.Validators[i].Address = v.PubKey.Address()
   144  		} else if v.PubKey.Address() != v.Address {
   145  			return errors.New("Incorrect address for validator %v in the genesis file, should be %v", v, v.PubKey.Address())
   146  		}
   147  	}
   148  
   149  	if genDoc.GenesisTime.IsZero() {
   150  		genDoc.GenesisTime = tmtime.Now()
   151  	}
   152  
   153  	return nil
   154  }
   155  
   156  // ------------------------------------------------------------
   157  // Make genesis state from file
   158  
   159  // GenesisDocFromJSON unmarshalls JSON data into a GenesisDoc.
   160  func GenesisDocFromJSON(jsonBlob []byte) (*GenesisDoc, error) {
   161  	genDoc := GenesisDoc{}
   162  	err := amino.UnmarshalJSON(jsonBlob, &genDoc)
   163  	if err != nil {
   164  		return nil, err
   165  	}
   166  
   167  	if err := genDoc.ValidateAndComplete(); err != nil {
   168  		return nil, err
   169  	}
   170  
   171  	return &genDoc, err
   172  }
   173  
   174  // GenesisDocFromFile reads JSON data from a file and unmarshalls it into a GenesisDoc.
   175  func GenesisDocFromFile(genDocFile string) (*GenesisDoc, error) {
   176  	jsonBlob, err := os.ReadFile(genDocFile)
   177  	if err != nil {
   178  		return nil, errors.Wrap(err, "Couldn't read GenesisDoc file")
   179  	}
   180  	genDoc, err := GenesisDocFromJSON(jsonBlob)
   181  	if err != nil {
   182  		return nil, errors.Wrap(err, fmt.Sprintf("Error reading GenesisDoc at %v", genDocFile))
   183  	}
   184  	return genDoc, nil
   185  }
   186  
   187  // ----------------------------------------
   188  // Mock AppState (for testing)
   189  
   190  type MockAppState struct {
   191  	AccountOwner string `json:"account_owner"`
   192  }