github.com/cosmos/cosmos-sdk@v0.50.10/x/genutil/types/genesis.go (about)

     1  package types
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"encoding/json"
     7  	"errors"
     8  	"fmt"
     9  	"io"
    10  	"os"
    11  	"path/filepath"
    12  	"time"
    13  
    14  	cmtjson "github.com/cometbft/cometbft/libs/json"
    15  	cmtproto "github.com/cometbft/cometbft/proto/tendermint/types"
    16  	cmttypes "github.com/cometbft/cometbft/types"
    17  	cmttime "github.com/cometbft/cometbft/types/time"
    18  
    19  	"github.com/cosmos/cosmos-sdk/version"
    20  )
    21  
    22  const (
    23  	// MaxChainIDLen is the maximum length of a chain ID.
    24  	MaxChainIDLen = cmttypes.MaxChainIDLen
    25  )
    26  
    27  // AppGenesis defines the app's genesis.
    28  type AppGenesis struct {
    29  	AppName       string            `json:"app_name"`
    30  	AppVersion    string            `json:"app_version"`
    31  	GenesisTime   time.Time         `json:"genesis_time"`
    32  	ChainID       string            `json:"chain_id"`
    33  	InitialHeight int64             `json:"initial_height"`
    34  	AppHash       []byte            `json:"app_hash"`
    35  	AppState      json.RawMessage   `json:"app_state,omitempty"`
    36  	Consensus     *ConsensusGenesis `json:"consensus,omitempty"`
    37  }
    38  
    39  // NewAppGenesisWithVersion returns a new AppGenesis with the app name and app version already.
    40  func NewAppGenesisWithVersion(chainID string, appState json.RawMessage) *AppGenesis {
    41  	return &AppGenesis{
    42  		AppName:    version.AppName,
    43  		AppVersion: version.Version,
    44  		ChainID:    chainID,
    45  		AppState:   appState,
    46  		Consensus: &ConsensusGenesis{
    47  			Validators: nil,
    48  		},
    49  	}
    50  }
    51  
    52  // ValidateAndComplete performs validation and completes the AppGenesis.
    53  func (ag *AppGenesis) ValidateAndComplete() error {
    54  	if ag.ChainID == "" {
    55  		return errors.New("genesis doc must include non-empty chain_id")
    56  	}
    57  
    58  	if len(ag.ChainID) > MaxChainIDLen {
    59  		return fmt.Errorf("chain_id in genesis doc is too long (max: %d)", MaxChainIDLen)
    60  	}
    61  
    62  	if ag.InitialHeight < 0 {
    63  		return fmt.Errorf("initial_height cannot be negative (got %v)", ag.InitialHeight)
    64  	}
    65  
    66  	if ag.InitialHeight == 0 {
    67  		ag.InitialHeight = 1
    68  	}
    69  
    70  	if ag.GenesisTime.IsZero() {
    71  		ag.GenesisTime = cmttime.Now()
    72  	}
    73  
    74  	if err := ag.Consensus.ValidateAndComplete(); err != nil {
    75  		return err
    76  	}
    77  
    78  	return nil
    79  }
    80  
    81  // SaveAs is a utility method for saving AppGenesis as a JSON file.
    82  func (ag *AppGenesis) SaveAs(file string) error {
    83  	appGenesisBytes, err := json.MarshalIndent(ag, "", "  ")
    84  	if err != nil {
    85  		return err
    86  	}
    87  
    88  	return os.WriteFile(file, appGenesisBytes, 0o600)
    89  }
    90  
    91  // AppGenesisFromReader reads the AppGenesis from the reader.
    92  func AppGenesisFromReader(reader io.Reader) (*AppGenesis, error) {
    93  	jsonBlob, err := io.ReadAll(reader)
    94  	if err != nil {
    95  		return nil, err
    96  	}
    97  
    98  	var appGenesis AppGenesis
    99  	if err := json.Unmarshal(jsonBlob, &appGenesis); err != nil {
   100  		// fallback to CometBFT genesis
   101  		var ctmGenesis cmttypes.GenesisDoc
   102  		if err2 := cmtjson.Unmarshal(jsonBlob, &ctmGenesis); err2 != nil {
   103  			return nil, fmt.Errorf("error unmarshalling AppGenesis: %w\n failed fallback to CometBFT GenDoc: %w", err, err2)
   104  		}
   105  
   106  		appGenesis = AppGenesis{
   107  			AppName: version.AppName,
   108  			// AppVersion is not filled as we do not know it from a CometBFT genesis
   109  			GenesisTime:   ctmGenesis.GenesisTime,
   110  			ChainID:       ctmGenesis.ChainID,
   111  			InitialHeight: ctmGenesis.InitialHeight,
   112  			AppHash:       ctmGenesis.AppHash,
   113  			AppState:      ctmGenesis.AppState,
   114  			Consensus: &ConsensusGenesis{
   115  				Validators: ctmGenesis.Validators,
   116  				Params:     ctmGenesis.ConsensusParams,
   117  			},
   118  		}
   119  	}
   120  
   121  	return &appGenesis, nil
   122  }
   123  
   124  // AppGenesisFromFile reads the AppGenesis from the provided file.
   125  func AppGenesisFromFile(genFile string) (*AppGenesis, error) {
   126  	file, err := os.Open(filepath.Clean(genFile))
   127  	if err != nil {
   128  		return nil, err
   129  	}
   130  
   131  	appGenesis, err := AppGenesisFromReader(bufio.NewReader(file))
   132  	if err != nil {
   133  		return nil, fmt.Errorf("failed to read genesis from file %s: %w", genFile, err)
   134  	}
   135  
   136  	if err := file.Close(); err != nil {
   137  		return nil, err
   138  	}
   139  
   140  	return appGenesis, nil
   141  }
   142  
   143  // --------------------------
   144  // CometBFT Genesis Handling
   145  // --------------------------
   146  
   147  // ToGenesisDoc converts the AppGenesis to a CometBFT GenesisDoc.
   148  func (ag *AppGenesis) ToGenesisDoc() (*cmttypes.GenesisDoc, error) {
   149  	return &cmttypes.GenesisDoc{
   150  		GenesisTime:     ag.GenesisTime,
   151  		ChainID:         ag.ChainID,
   152  		InitialHeight:   ag.InitialHeight,
   153  		AppHash:         ag.AppHash,
   154  		AppState:        ag.AppState,
   155  		Validators:      ag.Consensus.Validators,
   156  		ConsensusParams: ag.Consensus.Params,
   157  	}, nil
   158  }
   159  
   160  // ConsensusGenesis defines the consensus layer's genesis.
   161  // TODO(@julienrbrt) eventually abstract from CometBFT types
   162  type ConsensusGenesis struct {
   163  	Validators []cmttypes.GenesisValidator `json:"validators,omitempty"`
   164  	Params     *cmttypes.ConsensusParams   `json:"params,omitempty"`
   165  }
   166  
   167  // NewConsensusGenesis returns a ConsensusGenesis with given values.
   168  // It takes a proto consensus params so it can called from server export command.
   169  func NewConsensusGenesis(params cmtproto.ConsensusParams, validators []cmttypes.GenesisValidator) *ConsensusGenesis {
   170  	return &ConsensusGenesis{
   171  		Params: &cmttypes.ConsensusParams{
   172  			Block: cmttypes.BlockParams{
   173  				MaxBytes: params.Block.MaxBytes,
   174  				MaxGas:   params.Block.MaxGas,
   175  			},
   176  			Evidence: cmttypes.EvidenceParams{
   177  				MaxAgeNumBlocks: params.Evidence.MaxAgeNumBlocks,
   178  				MaxAgeDuration:  params.Evidence.MaxAgeDuration,
   179  				MaxBytes:        params.Evidence.MaxBytes,
   180  			},
   181  			Validator: cmttypes.ValidatorParams{
   182  				PubKeyTypes: params.Validator.PubKeyTypes,
   183  			},
   184  		},
   185  		Validators: validators,
   186  	}
   187  }
   188  
   189  func (cs *ConsensusGenesis) MarshalJSON() ([]byte, error) {
   190  	type Alias ConsensusGenesis
   191  	return cmtjson.Marshal(&Alias{
   192  		Validators: cs.Validators,
   193  		Params:     cs.Params,
   194  	})
   195  }
   196  
   197  func (cs *ConsensusGenesis) UnmarshalJSON(b []byte) error {
   198  	type Alias ConsensusGenesis
   199  
   200  	result := Alias{}
   201  	if err := cmtjson.Unmarshal(b, &result); err != nil {
   202  		return err
   203  	}
   204  
   205  	cs.Params = result.Params
   206  	cs.Validators = result.Validators
   207  
   208  	return nil
   209  }
   210  
   211  func (cs *ConsensusGenesis) ValidateAndComplete() error {
   212  	if cs == nil {
   213  		return fmt.Errorf("consensus genesis cannot be nil")
   214  	}
   215  
   216  	if cs.Params == nil {
   217  		cs.Params = cmttypes.DefaultConsensusParams()
   218  	} else if err := cs.Params.ValidateBasic(); err != nil {
   219  		return err
   220  	}
   221  
   222  	for i, v := range cs.Validators {
   223  		if v.Power == 0 {
   224  			return fmt.Errorf("the genesis file cannot contain validators with no voting power: %v", v)
   225  		}
   226  		if len(v.Address) > 0 && !bytes.Equal(v.PubKey.Address(), v.Address) {
   227  			return fmt.Errorf("incorrect address for validator %v in the genesis file, should be %v", v, v.PubKey.Address())
   228  		}
   229  		if len(v.Address) == 0 {
   230  			cs.Validators[i].Address = v.PubKey.Address()
   231  		}
   232  	}
   233  
   234  	return nil
   235  }