From 7fee22bccdd6e0ba9d2bf3dee706e90dcd41f874 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BF=97=E5=AE=87?= Date: Tue, 16 Mar 2021 17:14:01 +0800 Subject: [PATCH 1/7] Reorder cxspec/locate.go file. --- src/cx/cxspec/locate.go | 114 ++++++++++++++++++++-------------------- 1 file changed, 57 insertions(+), 57 deletions(-) diff --git a/src/cx/cxspec/locate.go b/src/cx/cxspec/locate.go index 49e61c4a9..f31f57729 100644 --- a/src/cx/cxspec/locate.go +++ b/src/cx/cxspec/locate.go @@ -42,6 +42,59 @@ var ( ErrInvalidLocPrefix = errors.New("invalid spec location prefix") ) +// LocateConfig contains flag values for Locate. +type LocateConfig struct { + CXChain string // CX Chain spec location string. + CXTracker string // CX Tracker URL. + + Logger logrus.FieldLogger + HTTPClient *http.Client +} + +// FillDefaults fills LocateConfig with default values. +func (c *LocateConfig) FillDefaults() { + c.CXChain = DefaultSpecLocStr + c.CXTracker = DefaultTrackerURL + c.Logger = logging.MustGetLogger("spec_loc") +} + +// DefaultLocateConfig returns the default LocateConfig set. +func DefaultLocateConfig() LocateConfig { + var lc LocateConfig + lc.FillDefaults() + return lc +} + +// SoftParse parses the OS args for the 'chain' flag. +// It is called 'soft' parse because the existence of non-defined flags does not +// result in failure. +func (c *LocateConfig) SoftParse(args []string) { + if v, ok := obtainFlagValue(args, "chain"); ok { + c.CXChain = v + } + if v, ok := obtainFlagValue(args, "tracker"); ok { + c.CXTracker = v + } +} + +// RegisterFlags ensures that the 'help' menu contains the locate flags and that +// the flags are recognized. +func (c *LocateConfig) RegisterFlags(fs *flag.FlagSet) { + var temp string + fs.StringVar(&temp, "chain", c.CXChain, fmt.Sprintf("cx chain location. Prepend with '%s:' or '%s:' for spec location type.", FileLoc, TrackerLoc)) + fs.StringVar(&temp, "tracker", c.CXTracker, "CX Tracker `URL`.") +} + +// TrackerClient generates a CX Tracker client based on the defined config. +func (c *LocateConfig) TrackerClient() *CXTrackerClient { + return NewCXTrackerClient(c.Logger, c.HTTPClient, c.CXTracker) +} + +// LocateWithConfig locates a spec with a given locate config. +func LocateWithConfig(ctx context.Context, conf *LocateConfig) (ChainSpec, error) { + return Locate(ctx, conf.Logger, conf.TrackerClient(), conf.CXChain) +} + // Locate locates the chain spec given a 'loc' string. // The 'loc' string is to be of format ':'. // * is 'tracker' if undefined. @@ -98,6 +151,10 @@ func Locate(ctx context.Context, log logrus.FieldLogger, tracker *CXTrackerClien } } +/* + << Helper functions >> +*/ + func splitLocString(loc string) (prefix LocPrefix, suffix string, err error) { loc = strings.TrimSpace(loc) if loc == "" { @@ -118,63 +175,6 @@ func splitLocString(loc string) (prefix LocPrefix, suffix string, err error) { return LocPrefix(locParts[0]), locParts[1], nil } -// LocateConfig contains flag values for Locate. -type LocateConfig struct { - CXChain string // CX Chain spec location string. - CXTracker string // CX Tracker URL. - - Logger logrus.FieldLogger - HTTPClient *http.Client -} - -// FillDefaults fills LocateConfig with default values. -func (c *LocateConfig) FillDefaults() { - c.CXChain = DefaultSpecLocStr - c.CXTracker = DefaultTrackerURL - c.Logger = logging.MustGetLogger("spec_loc") -} - -// DefaultLocateConfig returns the default LocateConfig set. -func DefaultLocateConfig() LocateConfig { - var lc LocateConfig - lc.FillDefaults() - return lc -} - -// SoftParse parses the OS args for the 'chain' flag. -// It is called 'soft' parse because the existence of non-defined flags does not -// result in failure. -func (c *LocateConfig) SoftParse(args []string) { - if v, ok := obtainFlagValue(args, "chain"); ok { - c.CXChain = v - } - if v, ok := obtainFlagValue(args, "tracker"); ok { - c.CXTracker = v - } -} - -// RegisterFlags ensures that the 'help' menu contains the locate flags and that -// the flags are recognized. -func (c *LocateConfig) RegisterFlags(fs *flag.FlagSet) { - var temp string - fs.StringVar(&temp, "chain", c.CXChain, fmt.Sprintf("cx chain location. Prepend with '%s:' or '%s:' for spec location type.", FileLoc, TrackerLoc)) - fs.StringVar(&temp, "tracker", c.CXTracker, "CX Tracker `URL`.") -} - -// TrackerClient generates a CX Tracker client based on the defined config. -func (c *LocateConfig) TrackerClient() *CXTrackerClient { - return NewCXTrackerClient(c.Logger, c.HTTPClient, c.CXTracker) -} - -// LocateWithConfig locates a spec with a given locate config. -func LocateWithConfig(ctx context.Context, conf *LocateConfig) (ChainSpec, error) { - return Locate(ctx, conf.Logger, conf.TrackerClient(), conf.CXChain) -} - -/* - << Helper functions >> -*/ - func obtainFlagValue(args []string, key string) (string, bool) { var ( keyPrefix1 = "-" + key From 2b840268302a0bbeb14f8ec50e3e80f659e47f81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BF=97=E5=AE=87?= Date: Tue, 23 Mar 2021 22:52:44 +0800 Subject: [PATCH 2/7] WIP --- src/cx/cxspec/chainspec.go | 287 ++-------------- src/cx/cxspec/cxspecalpha/chainspec_alpha.go | 338 +++++++++++++++++++ src/cx/cxspec/locate.go | 18 +- 3 files changed, 374 insertions(+), 269 deletions(-) create mode 100644 src/cx/cxspec/cxspecalpha/chainspec_alpha.go diff --git a/src/cx/cxspec/chainspec.go b/src/cx/cxspec/chainspec.go index 0787c4b25..70984c330 100644 --- a/src/cx/cxspec/chainspec.go +++ b/src/cx/cxspec/chainspec.go @@ -1,287 +1,52 @@ package cxspec import ( - "encoding/base64" - "encoding/json" "fmt" "strings" - "time" + "unicode" "github.com/skycoin/skycoin/src/cipher" "github.com/skycoin/cx-chains/src/coin" ) -const ( - Era = "cx_alpha" -) - -var progSEnc = base64.StdEncoding - -// ProtocolParams defines the coin's consensus parameters. -type ProtocolParams struct { - UnconfirmedBurnFactor uint32 `json:"unconfirmed_burn_factor"` // Burn factor for an unconfirmed transaction. - UnconfirmedMaxTransactionSize uint32 `json:"unconfirmed_max_transaction_size"` // Maximum size for an unconfirmed transaction. - UnconfirmedMaxDropletPrecision uint8 `json:"unconfirmed_max_droplet_precision"` // Maximum number of decimals allowed for an unconfirmed transaction. - - CreateBlockBurnFactor uint32 `json:"create_block_burn_factor"` // Burn factor to transactions when publishing blocks. - CreateBlockMaxTransactionSize uint32 `json:"create_block_max_transaction_size"` // Maximum size of a transaction when publishing blocks. - CreateBlockMaxDropletPrecision uint8 `json:"create_block_max_droplet_precision"` // Maximum number of decimals allowed for a transaction when publishing blocks. - - MaxBlockTransactionSize uint32 `json:"max_block_transaction_size"` // Maximum total size of transactions when publishing a block. -} - -// DefaultProtocolParams returns default values for ProtocolParams. -func DefaultProtocolParams() ProtocolParams { - return ProtocolParams{ - UnconfirmedBurnFactor: 10, - UnconfirmedMaxTransactionSize: 5 * 1024 * 1024, - UnconfirmedMaxDropletPrecision: 3, - CreateBlockBurnFactor: 10, - CreateBlockMaxTransactionSize: 5 * 1024 * 1024, - CreateBlockMaxDropletPrecision: 3, - MaxBlockTransactionSize: 5 * 1024 * 1024, - } -} - -// NodeParams defines the coin's default node parameters. -// TODO @evanlinjin: In the future, we may use the same network for different cx-chains. -// TODO: If that ever comes to light, we can remove these. -type NodeParams struct { - Port int `json:"port"` // Default port for wire protocol. - WebInterfacePort int `json:"web_interface_port"` // Default port for web interface. - DefaultConnections []string `json:"default_connections"` // Default bootstrapping nodes (trusted). - - /* Parameters for user-generated transactions. */ - UserBurnFactor uint64 `json:"user_burn_factor"` // Inverse fraction of coin hours that must be burned (used when creating transactions). - UserMaxTransactionSize uint32 `json:"user_max_transaction_size"` // Maximum size of user-created transactions (typically equal to the max size of a block). - UserMaxDropletPrecision uint64 `json:"user_max_droplet_precision"` // Decimal precision of droplets (smallest coin unit). -} - -// DefaultNodeParams returns the default values for NodeParams. -func DefaultNodeParams() NodeParams { - return NodeParams{ - Port: 6001, - WebInterfacePort: 6421, - DefaultConnections: []string{ - "127.0.0.1:6001", - }, - UserBurnFactor: 10, - UserMaxTransactionSize: 32 * 1024, - UserMaxDropletPrecision: 3, +// ObtainSpecEra obtains the spec era from a json-parsed result. +func ObtainSpecEra(t map[string]interface{}) string { + s, ok := t["spec_era"].(string) + if !ok { + return "" } -} - -// ChainSpec is... -// Functions: -// - Generate genesis block hash. -// - All checks. -type ChainSpec struct { - SpecEra string `json:"spec_era"` - - ChainPubKey string `json:"chain_pubkey"` // Blockchain public key. - - Protocol ProtocolParams `json:"protocol"` // Params that define the transaction protocol. - Node NodeParams `json:"node"` // Default params for a node of given coin (this may be removed in future eras). - - /* Identity Params */ - CoinName string `json:"coin_name"` // Coin display name (e.g. Skycoin). - CoinTicker string `json:"coin_ticker"` // Coin price ticker (e.g. SKY). - CoinHoursName string `json:"coin_hours_name"` // Coin hours display name (e.g. Skycoin Coin Hours). - CoinHoursTicker string `json:"coin_hours_ticker"` // Coin hours ticker (e.g SCH). - /* Genesis Params */ - GenesisAddr string `json:"genesis_address"` // Genesis address (base58 representation). - GenesisSig string `json:"genesis_signature"` // Genesis signature (hex representation). - GenesisCoinVolume uint64 `json:"genesis_coin_volume"` // Genesis coin volume. - GenesisProgState string `json:"genesis_program_state"` // Initial program state on genesis addr (hex representation). - GenesisTimestamp uint64 `json:"genesis_timestamp"` // Timestamp of genesis block (in seconds, UTC time). - - /* Distribution Params */ - // TODO @evanlinjin: Figure out if these are needed for the time being. - MaxCoinSupply uint64 `json:"max_coin_supply"` // Maximum coin supply. - // InitialUnlockedCount uint64 `json:"initial_unlocked_count"` // Initial number of unlocked addresses. - // UnlockAddressRate uint64 `json:"unlock_address_rate"` // Number of addresses to unlock per time interval. - // UnlockAddressTimeInterval uint64 `json:"unlock_address_time_interval"` // Time interval (in seconds) in which addresses are unlocked. Once the InitialUnlockedCount is exhausted, UnlockAddressRate addresses will be unlocked per UnlockTimeInterval. - // DistributionAddresses []string `json:"distribution_addresses"` // Addresses that receive coins. - - /* post-processed params */ - chainPK cipher.PubKey - genAddr cipher.Address - genSig cipher.Sig - genProgState []byte - // distAddresses []cipher.Address // TODO @evanlinjin: May not be needed. + return s } -// New generates a new chain spec. -func New(coin, ticker string, chainSK cipher.SecKey, genesisAddr cipher.Address, genesisProgState []byte) (*ChainSpec, error) { - coin = strings.ToLower(strings.Replace(coin, " ", "", -1)) - ticker = strings.ToUpper(strings.Replace(ticker, " ", "", -1)) - - spec := &ChainSpec{ - SpecEra: Era, - ChainPubKey: "", // ChainPubKey is generated at a later step via generateAndSignGenesisBlock - Protocol: DefaultProtocolParams(), - Node: DefaultNodeParams(), - - CoinName: coin, - CoinTicker: ticker, - CoinHoursName: fmt.Sprintf("%s coin hours", coin), - CoinHoursTicker: fmt.Sprintf("%sCH", ticker), - - GenesisAddr: genesisAddr.String(), - GenesisSig: "", // GenesisSig is generated at a later step via generateAndSignGenesisBlock - GenesisCoinVolume: 100e12, - GenesisProgState: progSEnc.EncodeToString(genesisProgState), - GenesisTimestamp: uint64(time.Now().UTC().Unix()), - - MaxCoinSupply: 1e8, - } - - // Fill post-processed fields. - if err := postProcess(spec, true); err != nil { - return nil, err - } - - // Generate genesis signature. - if _, err := generateAndSignGenesisBlock(spec, chainSK); err != nil { - return nil, err - } - - return spec, nil +// CoinHoursName generates the coin hours name from given coin name. +func CoinHoursName(coinName string) string { + return fmt.Sprintf("%s coin hours", strings.ToLower(stripWhitespaces(coinName))) } -func (cs *ChainSpec) RawGenesisProgState() []byte { - b, err := progSEnc.DecodeString(cs.GenesisProgState) - if err != nil { - panic(err) - } - return b +// CoinHoursTicker generates the coin hours ticker symbol from given coin ticker. +func CoinHoursTicker(coinTicker string) string { + return fmt.Sprintf("%s_CH", strings.ToUpper(stripWhitespaces(coinTicker))) } -// GenerateGenesisBlock generates a genesis block from the chain spec and verifies it. -// It returns an error if anything fails. -func (cs *ChainSpec) GenerateGenesisBlock() (*coin.Block, error) { - if err := postProcess(cs, false); err != nil { - return nil, err - } - - block, err := generateGenesisBlock(cs) - if err != nil { - return nil, err - } - - if err := cipher.VerifyPubKeySignedHash(cs.chainPK, cs.genSig, block.HashHeader()); err != nil { - return nil, err - } +type TemplatePreparer func() map[string]interface{} - return block, nil -} - -// GenerateAndSignGenesisBlock is used to generate and sign a genesis block. -// Associated fields in chain spec are also updated. -func (cs *ChainSpec) GenerateAndSignGenesisBlock(sk cipher.SecKey) (*coin.Block, error) { - if err := postProcess(cs, true); err != nil { - return nil, err - } - - return generateAndSignGenesisBlock(cs, sk) -} - -// Sign signs the genesis block and fills the chain spec with the resultant data. -// The fields changed are ChainPubKey and GenesisSig (and also the post-processed fields). -func (cs *ChainSpec) Sign(sk cipher.SecKey) error { - if err := postProcess(cs, true); err != nil { - return err - } - - _, err := generateAndSignGenesisBlock(cs, sk) - return err -} - -// ProcessedChainPubKey returns the processed chain public key. -func (cs ChainSpec) ProcessedChainPubKey() cipher.PubKey { - return cs.chainPK -} - -// PrintString prints an indented json representation of the chain spec. -func (cs *ChainSpec) PrintString() string { - b, err := json.MarshalIndent(cs, "", "\t") - if err != nil { - panic(err) - } - - return string(b) -} - -// SpecHash returns the hashed spec object. -func (cs *ChainSpec) SpecHash() cipher.SHA256 { - b, err := json.Marshal(cs) - if err != nil { - panic(err) - } - return cipher.SumSHA256(b) -} - -// postProcess fills post-process fields of chain spec. -// The 'allowEmpty' -func postProcess(cs *ChainSpec, allowEmpty bool) error { - wrapErr := func(name string, err error) error { - return fmt.Errorf("chain spec: failed to post-process '%s': %w", name, err) - } - var err error - - if !allowEmpty || cs.ChainPubKey != "" { - if cs.chainPK, err = cipher.PubKeyFromHex(cs.ChainPubKey); err != nil { - return wrapErr("chain_pubkey", err) +func stripWhitespaces(s string) string { + out := make([]int32, 0, len(s)) + for _, c := range s { + if unicode.IsSpace(c) { + continue } + out = append(out, c) } - if cs.genAddr, err = cipher.DecodeBase58Address(cs.GenesisAddr); err != nil { - return wrapErr("genesis_address", err) - } - if !allowEmpty || cs.GenesisSig != "" { - if cs.genSig, err = cipher.SigFromHex(cs.GenesisSig); err != nil { - return wrapErr("genesis_signature", err) - } - } - if cs.genProgState, err = progSEnc.DecodeString(cs.GenesisProgState); err != nil { - return wrapErr("genesis_prog_state", err) - } - return nil -} -// generateGenesisBlock generates a genesis block from the chain spec with no checks. -func generateGenesisBlock(cs *ChainSpec) (*coin.Block, error) { - block, err := coin.NewGenesisBlock(cs.genAddr, cs.GenesisCoinVolume, cs.GenesisTimestamp, cs.genProgState) - if err != nil { - return nil, fmt.Errorf("chain spec: %w", err) - } - return block, nil + return string(out) } -// generateAndSignGenesisBlock generates and signs the genesis block (using specified fields from chain spec). -// Hence, ChainPubKey and GenesisSig fields are also filled. -func generateAndSignGenesisBlock(cs *ChainSpec, sk cipher.SecKey) (*coin.Block, error) { - block, err := generateGenesisBlock(cs) - if err != nil { - return nil, err - } - - pk, err := cipher.PubKeyFromSecKey(sk) - if err != nil { - return nil, err - } - - cs.chainPK = pk - cs.ChainPubKey = pk.Hex() - - blockSig, err := cipher.SignHash(block.HashHeader(), sk) - if err != nil { - return nil, fmt.Errorf("failed to sign genesis block: %w", err) - } - - cs.genSig = blockSig - cs.GenesisSig = blockSig.Hex() - - return block, nil +type ChainSpec interface { + GenesisProgramState() []byte + GenerateGenesisBlock() (*coin.Block, error) + SpecHash() cipher.SHA256 + String() string } diff --git a/src/cx/cxspec/cxspecalpha/chainspec_alpha.go b/src/cx/cxspec/cxspecalpha/chainspec_alpha.go new file mode 100644 index 000000000..8563fb617 --- /dev/null +++ b/src/cx/cxspec/cxspecalpha/chainspec_alpha.go @@ -0,0 +1,338 @@ +package cxspecalpha + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "strings" + "time" + + "github.com/skycoin/skycoin/src/cipher" + + "github.com/skycoin/cx-chains/src/coin" +) + +const Era = "cx_alpha" + +var progSEnc = base64.StdEncoding + +// ProtocolParams defines the coin's consensus parameters. +type ProtocolParams struct { + UnconfirmedBurnFactor uint32 `json:"unconfirmed_burn_factor"` // Burn factor for an unconfirmed transaction. + UnconfirmedMaxTransactionSize uint32 `json:"unconfirmed_max_transaction_size"` // Maximum size for an unconfirmed transaction. + UnconfirmedMaxDropletPrecision uint8 `json:"unconfirmed_max_droplet_precision"` // Maximum number of decimals allowed for an unconfirmed transaction. + + CreateBlockBurnFactor uint32 `json:"create_block_burn_factor"` // Burn factor to transactions when publishing blocks. + CreateBlockMaxTransactionSize uint32 `json:"create_block_max_transaction_size"` // Maximum size of a transaction when publishing blocks. + CreateBlockMaxDropletPrecision uint8 `json:"create_block_max_droplet_precision"` // Maximum number of decimals allowed for a transaction when publishing blocks. + + MaxBlockTransactionSize uint32 `json:"max_block_transaction_size"` // Maximum total size of transactions when publishing a block. +} + +// DefaultProtocolParams returns default values for ProtocolParams. +func DefaultProtocolParams() ProtocolParams { + return ProtocolParams{ + UnconfirmedBurnFactor: 10, + UnconfirmedMaxTransactionSize: 5 * 1024 * 1024, + UnconfirmedMaxDropletPrecision: 3, + CreateBlockBurnFactor: 10, + CreateBlockMaxTransactionSize: 5 * 1024 * 1024, + CreateBlockMaxDropletPrecision: 3, + MaxBlockTransactionSize: 5 * 1024 * 1024, + } +} + +// NodeParams defines the coin's default node parameters. +// TODO @evanlinjin: In the future, we may use the same network for different cx-chains. +// TODO: If that ever comes to light, we can remove these. +type NodeParams struct { + Port int `json:"port"` // Default port for wire protocol. + WebInterfacePort int `json:"web_interface_port"` // Default port for web interface. + DefaultConnections []string `json:"default_connections"` // Default bootstrapping nodes (trusted). + + /* Parameters for user-generated transactions. */ + UserBurnFactor uint64 `json:"user_burn_factor"` // Inverse fraction of coin hours that must be burned (used when creating transactions). + UserMaxTransactionSize uint32 `json:"user_max_transaction_size"` // Maximum size of user-created transactions (typically equal to the max size of a block). + UserMaxDropletPrecision uint64 `json:"user_max_droplet_precision"` // Decimal precision of droplets (smallest coin unit). +} + +// DefaultNodeParams returns the default values for NodeParams. +func DefaultNodeParams() NodeParams { + return NodeParams{ + Port: 6001, + WebInterfacePort: 6421, + DefaultConnections: []string{ + "127.0.0.1:6001", + }, + UserBurnFactor: 10, + UserMaxTransactionSize: 32 * 1024, + UserMaxDropletPrecision: 3, + } +} + +// ChainSpec represents the chain spec structure of version alpha. +type ChainSpec struct { + SpecEra string `json:"spec_era"` + + ChainPubKey string `json:"chain_pubkey"` // Blockchain public key. + + Protocol ProtocolParams `json:"protocol"` // Params that define the transaction protocol. + Node NodeParams `json:"node"` // Default params for a node of given coin (this may be removed in future eras). + + /* Identity Params */ + CoinName string `json:"coin_name"` // Coin display name (e.g. skycoin). + CoinTicker string `json:"coin_ticker"` // Coin price ticker (e.g. SKY). + + /* Genesis Params */ + GenesisAddr string `json:"genesis_address"` // Genesis address (base58 representation). + GenesisSig string `json:"genesis_signature"` // Genesis signature (hex representation). + GenesisCoinVolume uint64 `json:"genesis_coin_volume"` // Genesis coin volume. + GenesisProgState string `json:"genesis_program_state"` // Initial program state on genesis addr (hex representation). + GenesisTimestamp uint64 `json:"genesis_timestamp"` // Timestamp of genesis block (in seconds, UTC time). + + /* Distribution Params */ + // TODO @evanlinjin: Figure out if these are needed for the time being. + MaxCoinSupply uint64 `json:"max_coin_supply"` // Maximum coin supply. + // InitialUnlockedCount uint64 `json:"initial_unlocked_count"` // Initial number of unlocked addresses. + // UnlockAddressRate uint64 `json:"unlock_address_rate"` // Number of addresses to unlock per time interval. + // UnlockAddressTimeInterval uint64 `json:"unlock_address_time_interval"` // Time interval (in seconds) in which addresses are unlocked. Once the InitialUnlockedCount is exhausted, UnlockAddressRate addresses will be unlocked per UnlockTimeInterval. + // DistributionAddresses []string `json:"distribution_addresses"` // Addresses that receive coins. + + /* post-processed params */ + chainPK cipher.PubKey + genAddr cipher.Address + genSig cipher.Sig + genProgState []byte + // distAddresses []cipher.Address // TODO @evanlinjin: May not be needed. +} + +// New generates a new chain spec. +func New(coin, ticker string, chainSK cipher.SecKey, genesisAddr cipher.Address, genesisProgState []byte) (*ChainSpec, error) { + coin = strings.ToLower(strings.Replace(coin, " ", "", -1)) + ticker = strings.ToUpper(strings.Replace(ticker, " ", "", -1)) + + spec := &ChainSpec{ + SpecEra: Era, + ChainPubKey: "", // ChainPubKey is generated at a later step via generateAndSignGenesisBlock + Protocol: DefaultProtocolParams(), + Node: DefaultNodeParams(), + + CoinName: coin, + CoinTicker: ticker, + + GenesisAddr: genesisAddr.String(), + GenesisSig: "", // GenesisSig is generated at a later step via generateAndSignGenesisBlock + GenesisCoinVolume: 100e12, + GenesisProgState: progSEnc.EncodeToString(genesisProgState), + GenesisTimestamp: uint64(time.Now().UTC().Unix()), + + MaxCoinSupply: 1e8, + } + + // Fill post-processed fields. + if err := postProcess(spec, true); err != nil { + return nil, err + } + + // Generate genesis signature. + if _, err := generateAndSignGenesisBlock(spec, chainSK); err != nil { + return nil, err + } + + return spec, nil +} + +func PrepareTemplate() map[string]interface{} { + t := make(map[string]interface{}) + t["protocol"] = ProtocolParams{} + t["node"] = NodeParams{} + + return t +} + +func ParseTemplate(t map[string]interface{}) (cs *ChainSpec, err error) { + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("failed to extract from raw chain spec: %v", r) + } + }() + + protocolP, ok := t["protocol"].(ProtocolParams) + if !ok { + return nil, fmt.Errorf("internal error: failed to extract 'protocol' field of spec") + } + + nodeP, ok := t["node"].(NodeParams) + if !ok { + return nil, fmt.Errorf("internal error: failed to extract 'node' field of spec") + } + + cs = &ChainSpec{ + SpecEra: t["spec_era"].(string), + ChainPubKey: t["chain_pubkey"].(string), + Protocol: protocolP, + Node: nodeP, + CoinName: t["coin_name"].(string), + CoinTicker: t["coin_ticker"].(string), + GenesisAddr: t["genesis_address"].(string), + GenesisSig: t["genesis_signature"].(string), + GenesisCoinVolume: uint64(t["genesis_coin_volume"].(float64)), + GenesisProgState: t["genesis_program_state"].(string), + GenesisTimestamp: uint64(t["genesis_timestamp"].(float64)), + MaxCoinSupply: uint64(t["max_coin_supply"].(float64)), + // chainPK: cipher.PubKey{}, + // genAddr: cipher.Address{}, + // genSig: cipher.Sig{}, + // genProgState: nil, + } + + if cs.SpecEra != Era { + return nil, fmt.Errorf("internal error: spec error does not match: expected '%s', got '%s'", + Era, cs.SpecEra) + } + + if err = postProcess(cs, false); err != nil { + return nil, fmt.Errorf("invalid chain spec: %w", err) + } + + return cs, err +} + +func (cs *ChainSpec) RawGenesisProgState() []byte { + b, err := progSEnc.DecodeString(cs.GenesisProgState) + if err != nil { + panic(err) + } + return b +} + +// GenerateGenesisBlock generates a genesis block from the chain spec and verifies it. +// It returns an error if anything fails. +func (cs *ChainSpec) GenerateGenesisBlock() (*coin.Block, error) { + if err := postProcess(cs, false); err != nil { + return nil, err + } + + block, err := generateGenesisBlock(cs) + if err != nil { + return nil, err + } + + if err := cipher.VerifyPubKeySignedHash(cs.chainPK, cs.genSig, block.HashHeader()); err != nil { + return nil, err + } + + return block, nil +} + +// GenerateAndSignGenesisBlock is used to generate and sign a genesis block. +// Associated fields in chain spec are also updated. +func (cs *ChainSpec) GenerateAndSignGenesisBlock(sk cipher.SecKey) (*coin.Block, error) { + if err := postProcess(cs, true); err != nil { + return nil, err + } + + return generateAndSignGenesisBlock(cs, sk) +} + +// Sign signs the genesis block and fills the chain spec with the resultant data. +// The fields changed are ChainPubKey and GenesisSig (and also the post-processed fields). +func (cs *ChainSpec) Sign(sk cipher.SecKey) error { + if err := postProcess(cs, true); err != nil { + return err + } + + _, err := generateAndSignGenesisBlock(cs, sk) + return err +} + +// ProcessedChainPubKey returns the processed chain public key. +func (cs ChainSpec) ProcessedChainPubKey() cipher.PubKey { + return cs.chainPK +} + +// PrintString prints an indented json representation of the chain spec. +func (cs *ChainSpec) PrintString() string { + b, err := json.MarshalIndent(cs, "", "\t") + if err != nil { + panic(err) + } + + return string(b) +} + +// SpecHash returns the hashed spec object. +func (cs *ChainSpec) SpecHash() cipher.SHA256 { + b, err := json.Marshal(cs) + if err != nil { + panic(err) + } + return cipher.SumSHA256(b) +} + +/* + <<< Helper functions >>> +*/ + +// postProcess fills post-process fields of chain spec. +// The 'allowEmpty' +func postProcess(cs *ChainSpec, allowEmpty bool) error { + wrapErr := func(name string, err error) error { + return fmt.Errorf("chain spec: failed to post-process '%s': %w", name, err) + } + var err error + + if !allowEmpty || cs.ChainPubKey != "" { + if cs.chainPK, err = cipher.PubKeyFromHex(cs.ChainPubKey); err != nil { + return wrapErr("chain_pubkey", err) + } + } + if cs.genAddr, err = cipher.DecodeBase58Address(cs.GenesisAddr); err != nil { + return wrapErr("genesis_address", err) + } + if !allowEmpty || cs.GenesisSig != "" { + if cs.genSig, err = cipher.SigFromHex(cs.GenesisSig); err != nil { + return wrapErr("genesis_signature", err) + } + } + if cs.genProgState, err = progSEnc.DecodeString(cs.GenesisProgState); err != nil { + return wrapErr("genesis_prog_state", err) + } + return nil +} + +// generateGenesisBlock generates a genesis block from the chain spec with no checks. +func generateGenesisBlock(cs *ChainSpec) (*coin.Block, error) { + block, err := coin.NewGenesisBlock(cs.genAddr, cs.GenesisCoinVolume, cs.GenesisTimestamp, cs.genProgState) + if err != nil { + return nil, fmt.Errorf("chain spec: %w", err) + } + return block, nil +} + +// generateAndSignGenesisBlock generates and signs the genesis block (using specified fields from chain spec). +// Hence, ChainPubKey and GenesisSig fields are also filled. +func generateAndSignGenesisBlock(cs *ChainSpec, sk cipher.SecKey) (*coin.Block, error) { + block, err := generateGenesisBlock(cs) + if err != nil { + return nil, err + } + + pk, err := cipher.PubKeyFromSecKey(sk) + if err != nil { + return nil, err + } + + cs.chainPK = pk + cs.ChainPubKey = pk.Hex() + + blockSig, err := cipher.SignHash(block.HashHeader(), sk) + if err != nil { + return nil, fmt.Errorf("failed to sign genesis block: %w", err) + } + + cs.genSig = blockSig + cs.GenesisSig = blockSig.Hex() + + return block, nil +} diff --git a/src/cx/cxspec/locate.go b/src/cx/cxspec/locate.go index f31f57729..faa39fdd2 100644 --- a/src/cx/cxspec/locate.go +++ b/src/cx/cxspec/locate.go @@ -11,6 +11,8 @@ import ( "github.com/sirupsen/logrus" "github.com/skycoin/skycoin/src/cipher" "github.com/skycoin/skycoin/src/util/logging" + + "github.com/skycoin/cx-chains/src/cx/cxspec/cxspecalpha" ) // LocPrefix determines the location type of the location string. @@ -91,7 +93,7 @@ func (c *LocateConfig) TrackerClient() *CXTrackerClient { } // LocateWithConfig locates a spec with a given locate config. -func LocateWithConfig(ctx context.Context, conf *LocateConfig) (ChainSpec, error) { +func LocateWithConfig(ctx context.Context, conf *LocateConfig) (cxspecalpha.ChainSpec, error) { return Locate(ctx, conf.Logger, conf.TrackerClient(), conf.CXChain) } @@ -101,7 +103,7 @@ func LocateWithConfig(ctx context.Context, conf *LocateConfig) (ChainSpec, error // * either specifies the cx chain's genesis hash (if // is 'tracker') or filepath of the spec file (if // is 'file'). -func Locate(ctx context.Context, log logrus.FieldLogger, tracker *CXTrackerClient, loc string) (ChainSpec, error) { +func Locate(ctx context.Context, log logrus.FieldLogger, tracker *CXTrackerClient, loc string) (cxspecalpha.ChainSpec, error) { // Ensure logger is existent. if log == nil { log = logging.MustGetLogger("cxspec").WithField("func", "Locate") @@ -109,7 +111,7 @@ func Locate(ctx context.Context, log logrus.FieldLogger, tracker *CXTrackerClien prefix, suffix, err := splitLocString(loc) if err != nil { - return ChainSpec{}, err + return cxspecalpha.ChainSpec{}, err } // Check location prefix (LocPrefix). @@ -124,30 +126,30 @@ func Locate(ctx context.Context, log logrus.FieldLogger, tracker *CXTrackerClien case TrackerLoc: // Check that 'tracker' is not nil. if tracker == nil { - return ChainSpec{}, ErrEmptyTracker + return cxspecalpha.ChainSpec{}, ErrEmptyTracker } // Obtain genesis hash from hex string. hash, err := cipher.SHA256FromHex(suffix) if err != nil { - return ChainSpec{}, fmt.Errorf("invalid genesis hash provided '%s': %w", loc, err) + return cxspecalpha.ChainSpec{}, fmt.Errorf("invalid genesis hash provided '%s': %w", loc, err) } // Obtain spec from tracker. signedChainSpec, err := tracker.SpecByGenesisHash(ctx, hash) if err != nil { - return ChainSpec{}, fmt.Errorf("chain spec not of genesis hash not found in tracker: %w", err) + return cxspecalpha.ChainSpec{}, fmt.Errorf("chain spec not of genesis hash not found in tracker: %w", err) } // Verify again (no harm in doing it twice). if err := signedChainSpec.Verify(); err != nil { - return ChainSpec{}, err + return cxspecalpha.ChainSpec{}, err } return signedChainSpec.Spec, nil default: - return ChainSpec{}, fmt.Errorf("%w '%s'", ErrInvalidLocPrefix, prefix) + return cxspecalpha.ChainSpec{}, fmt.Errorf("%w '%s'", ErrInvalidLocPrefix, prefix) } } From 815844ca58efd68c25f1e3a8fbba0454a4a6a753 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BF=97=E5=AE=87?= Date: Sun, 28 Mar 2021 23:54:58 +0800 Subject: [PATCH 3/7] Redefined cxpsec.ChainSpec interface to be more friendly. * Rename 'cxspecalpha' module to 'alpha'. * Move populate logic into cxspec.ChainSpec methods. * Added TrackerAddr field to alpha chain spec. * Added parse logic. --- .../chainspec_alpha.go => alpha/alpha.go} | 249 +++++++++++------- src/cx/cxspec/chainspec.go | 128 ++++++--- src/cx/cxspec/cx_tracker_client.go | 3 +- src/cx/cxspec/locate.go | 18 +- src/cx/cxspec/parse.go | 45 ++++ src/cx/cxspec/populate.go | 78 +----- src/cx/cxspec/signed_chainspec.go | 74 ------ 7 files changed, 309 insertions(+), 286 deletions(-) rename src/cx/cxspec/{cxspecalpha/chainspec_alpha.go => alpha/alpha.go} (64%) create mode 100644 src/cx/cxspec/parse.go delete mode 100644 src/cx/cxspec/signed_chainspec.go diff --git a/src/cx/cxspec/cxspecalpha/chainspec_alpha.go b/src/cx/cxspec/alpha/alpha.go similarity index 64% rename from src/cx/cxspec/cxspecalpha/chainspec_alpha.go rename to src/cx/cxspec/alpha/alpha.go index 8563fb617..ba7be0cef 100644 --- a/src/cx/cxspec/cxspecalpha/chainspec_alpha.go +++ b/src/cx/cxspec/alpha/alpha.go @@ -1,4 +1,4 @@ -package cxspecalpha +package alpha import ( "encoding/base64" @@ -6,10 +6,14 @@ import ( "fmt" "strings" "time" + "unicode" "github.com/skycoin/skycoin/src/cipher" "github.com/skycoin/cx-chains/src/coin" + "github.com/skycoin/cx-chains/src/params" + "github.com/skycoin/cx-chains/src/readable" + "github.com/skycoin/cx-chains/src/skycoin" ) const Era = "cx_alpha" @@ -75,6 +79,7 @@ type ChainSpec struct { SpecEra string `json:"spec_era"` ChainPubKey string `json:"chain_pubkey"` // Blockchain public key. + TrackerAddr string `json:"tracker_addr"` // CX Tracker address. Protocol ProtocolParams `json:"protocol"` // Params that define the transaction protocol. Node NodeParams `json:"node"` // Default params for a node of given coin (this may be removed in future eras). @@ -114,6 +119,7 @@ func New(coin, ticker string, chainSK cipher.SecKey, genesisAddr cipher.Address, spec := &ChainSpec{ SpecEra: Era, ChainPubKey: "", // ChainPubKey is generated at a later step via generateAndSignGenesisBlock + TrackerAddr: "", // TODO @evanlinjin: Have default tracker address. Protocol: DefaultProtocolParams(), Node: DefaultNodeParams(), @@ -135,80 +141,86 @@ func New(coin, ticker string, chainSK cipher.SecKey, genesisAddr cipher.Address, } // Generate genesis signature. - if _, err := generateAndSignGenesisBlock(spec, chainSK); err != nil { + block, err := generateGenesisBlock(spec) + if err != nil { + return nil, err + } + if err := signAndFill(spec, block, chainSK); err != nil { return nil, err } return spec, nil } -func PrepareTemplate() map[string]interface{} { - t := make(map[string]interface{}) - t["protocol"] = ProtocolParams{} - t["node"] = NodeParams{} +func (cs *ChainSpec) RawGenesisProgState() []byte { + b, err := progSEnc.DecodeString(cs.GenesisProgState) + if err != nil { + panic(err) + } + return b +} - return t +// SpecHash returns the hashed spec object. +func (cs *ChainSpec) CXSpecHash() cipher.SHA256 { + b, err := json.Marshal(cs) + if err != nil { + panic(err) + } + return cipher.SumSHA256(b) } -func ParseTemplate(t map[string]interface{}) (cs *ChainSpec, err error) { - defer func() { - if r := recover(); r != nil { - err = fmt.Errorf("failed to extract from raw chain spec: %v", r) - } - }() +// CXSpecEra returns the spec era string. +func (*ChainSpec) CXSpecEra() string { return Era } - protocolP, ok := t["protocol"].(ProtocolParams) - if !ok { - return nil, fmt.Errorf("internal error: failed to extract 'protocol' field of spec") +// String prints an indented json representation of the chain spec. +func (cs *ChainSpec) String() string { + b, err := json.MarshalIndent(cs, "", "\t") + if err != nil { + panic(err) } - nodeP, ok := t["node"].(NodeParams) - if !ok { - return nil, fmt.Errorf("internal error: failed to extract 'node' field of spec") - } + return string(b) +} - cs = &ChainSpec{ - SpecEra: t["spec_era"].(string), - ChainPubKey: t["chain_pubkey"].(string), - Protocol: protocolP, - Node: nodeP, - CoinName: t["coin_name"].(string), - CoinTicker: t["coin_ticker"].(string), - GenesisAddr: t["genesis_address"].(string), - GenesisSig: t["genesis_signature"].(string), - GenesisCoinVolume: uint64(t["genesis_coin_volume"].(float64)), - GenesisProgState: t["genesis_program_state"].(string), - GenesisTimestamp: uint64(t["genesis_timestamp"].(float64)), - MaxCoinSupply: uint64(t["max_coin_supply"].(float64)), - // chainPK: cipher.PubKey{}, - // genAddr: cipher.Address{}, - // genSig: cipher.Sig{}, - // genProgState: nil, +// Finalize finalizes the spec, providing the genesis public key and genesis +// signature. +func (cs *ChainSpec) Finalize(genesisSK cipher.SecKey) error { + if err := postProcess(cs, true); err != nil { + return err } - if cs.SpecEra != Era { - return nil, fmt.Errorf("internal error: spec error does not match: expected '%s', got '%s'", - Era, cs.SpecEra) + block, err := generateGenesisBlock(cs) + if err != nil { + return err } - if err = postProcess(cs, false); err != nil { - return nil, fmt.Errorf("invalid chain spec: %w", err) + if err := signAndFill(cs, block, genesisSK); err != nil { + return err } - return cs, err + return nil } -func (cs *ChainSpec) RawGenesisProgState() []byte { - b, err := progSEnc.DecodeString(cs.GenesisProgState) - if err != nil { - panic(err) +// Check checks whether the spec is valid. +func (cs *ChainSpec) Check() error { + if _, err := cs.ObtainGenesisBlock(); err != nil { + return err } - return b + + // TODO @evanlinjin: Implement more checks. + + return nil } -// GenerateGenesisBlock generates a genesis block from the chain spec and verifies it. +// ObtainCoinName obtains the coin name of the spec. +func (cs *ChainSpec) ObtainCoinName() string { return cs.CoinName } + +// ObtainCoinTicker obtains the coin ticker of the spec. +func (cs *ChainSpec) ObtainCoinTicker() string { return cs.CoinTicker } + +// ObtainGenesisBlock generates a genesis block from the chain spec and verifies it. // It returns an error if anything fails. -func (cs *ChainSpec) GenerateGenesisBlock() (*coin.Block, error) { +func (cs *ChainSpec) ObtainGenesisBlock() (*coin.Block, error) { if err := postProcess(cs, false); err != nil { return nil, err } @@ -225,49 +237,77 @@ func (cs *ChainSpec) GenerateGenesisBlock() (*coin.Block, error) { return block, nil } -// GenerateAndSignGenesisBlock is used to generate and sign a genesis block. -// Associated fields in chain spec are also updated. -func (cs *ChainSpec) GenerateAndSignGenesisBlock(sk cipher.SecKey) (*coin.Block, error) { - if err := postProcess(cs, true); err != nil { - return nil, err - } - - return generateAndSignGenesisBlock(cs, sk) +// ObtainChainPubKey returns the processed chain public key. +func (cs *ChainSpec) ObtainChainPubKey() (cipher.PubKey, error) { + return cipher.PubKeyFromHex(cs.ChainPubKey) } -// Sign signs the genesis block and fills the chain spec with the resultant data. -// The fields changed are ChainPubKey and GenesisSig (and also the post-processed fields). -func (cs *ChainSpec) Sign(sk cipher.SecKey) error { - if err := postProcess(cs, true); err != nil { - return err +// PopulateParamsModule populates the params module within cx chain. +func (cs *ChainSpec) PopulateParamsModule() error { + // TODO @evanlinjin: Figure out distribution. + params.MainNetDistribution = params.Distribution{ + MaxCoinSupply: cs.MaxCoinSupply, + InitialUnlockedCount: 1, + UnlockAddressRate: 0, + UnlockTimeInterval: 0, + Addresses: []string{cs.GenesisAddr}, } + params.UserVerifyTxn = params.VerifyTxn{ + BurnFactor: uint32(cs.Node.UserBurnFactor), + MaxTransactionSize: cs.Node.UserMaxTransactionSize, + MaxDropletPrecision: uint8(cs.Node.UserMaxDropletPrecision), + } + params.InitFromEnv() - _, err := generateAndSignGenesisBlock(cs, sk) - return err -} - -// ProcessedChainPubKey returns the processed chain public key. -func (cs ChainSpec) ProcessedChainPubKey() cipher.PubKey { - return cs.chainPK + return nil } -// PrintString prints an indented json representation of the chain spec. -func (cs *ChainSpec) PrintString() string { - b, err := json.MarshalIndent(cs, "", "\t") +// PopulateNodeConfig populates the node config with values from cx chain spec. +func (cs *ChainSpec) PopulateNodeConfig(conf *skycoin.NodeConfig) error { + genesis, err := cs.ObtainGenesisBlock() if err != nil { - panic(err) + return err + } + peerListURL := fmt.Sprintf("%s/peerlists/%s.txt", cs.TrackerAddr, genesis.HashHeader()) + + conf.CoinName = cs.CoinName + conf.PeerListURL = peerListURL + conf.Port = cs.Node.Port + conf.WebInterfacePort = cs.Node.WebInterfacePort + conf.UnconfirmedVerifyTxn = params.VerifyTxn{ + BurnFactor: cs.Protocol.UnconfirmedBurnFactor, + MaxTransactionSize: cs.Protocol.UnconfirmedMaxTransactionSize, + MaxDropletPrecision: cs.Protocol.UnconfirmedMaxDropletPrecision, + } + conf.CreateBlockVerifyTxn = params.VerifyTxn{ + BurnFactor: cs.Protocol.CreateBlockBurnFactor, + MaxTransactionSize: cs.Protocol.CreateBlockMaxTransactionSize, + MaxDropletPrecision: cs.Protocol.CreateBlockMaxDropletPrecision, + } + conf.MaxBlockTransactionsSize = cs.Protocol.MaxBlockTransactionSize + conf.GenesisSignatureStr = cs.GenesisSig + conf.GenesisAddressStr = cs.GenesisAddr + conf.BlockchainPubkeyStr = cs.ChainPubKey + conf.GenesisTimestamp = cs.GenesisTimestamp + conf.GenesisCoinVolume = cs.GenesisCoinVolume + conf.DefaultConnections = cs.Node.DefaultConnections + + conf.Fiber = readable.FiberConfig{ + Name: cs.CoinName, + DisplayName: cs.CoinName, + Ticker: cs.CoinTicker, + CoinHoursName: coinHoursName(cs.CoinName), + CoinHoursTicker: coinHoursTicker(cs.CoinTicker), + ExplorerURL: "", // TODO @evanlinjin: CX Chain explorer? } - return string(b) -} - -// SpecHash returns the hashed spec object. -func (cs *ChainSpec) SpecHash() cipher.SHA256 { - b, err := json.Marshal(cs) - if err != nil { - panic(err) + if conf.DataDirectory == "" { + conf.DataDirectory = "$HOME/.cxchain/" + cs.CoinName + } else { + conf.DataDirectory = strings.ReplaceAll(conf.DataDirectory, "{coin}", cs.CoinName) } - return cipher.SumSHA256(b) + + return nil } /* @@ -310,29 +350,44 @@ func generateGenesisBlock(cs *ChainSpec) (*coin.Block, error) { return block, nil } -// generateAndSignGenesisBlock generates and signs the genesis block (using specified fields from chain spec). -// Hence, ChainPubKey and GenesisSig fields are also filled. -func generateAndSignGenesisBlock(cs *ChainSpec, sk cipher.SecKey) (*coin.Block, error) { - block, err := generateGenesisBlock(cs) - if err != nil { - return nil, err - } - +// signAndFill fills the chain spec with block and block signature. +func signAndFill(cs *ChainSpec, block *coin.Block, sk cipher.SecKey) error { pk, err := cipher.PubKeyFromSecKey(sk) if err != nil { - return nil, err + return err } - cs.chainPK = pk - cs.ChainPubKey = pk.Hex() - blockSig, err := cipher.SignHash(block.HashHeader(), sk) if err != nil { - return nil, fmt.Errorf("failed to sign genesis block: %w", err) + return fmt.Errorf("failed to sign genesis block: %w", err) } + cs.chainPK = pk + cs.ChainPubKey = pk.Hex() cs.genSig = blockSig cs.GenesisSig = blockSig.Hex() - return block, nil + return nil } + +// coinHoursName generates the coin hours name from given coin name. +func coinHoursName(coinName string) string { + return fmt.Sprintf("%s coin hours", strings.ToLower(stripWhitespaces(coinName))) +} + +// coinHoursTicker generates the coin hours ticker symbol from given coin ticker. +func coinHoursTicker(coinTicker string) string { + return fmt.Sprintf("%s_CH", strings.ToUpper(stripWhitespaces(coinTicker))) +} + +func stripWhitespaces(s string) string { + out := make([]int32, 0, len(s)) + for _, c := range s { + if unicode.IsSpace(c) { + continue + } + out = append(out, c) + } + + return string(out) +} \ No newline at end of file diff --git a/src/cx/cxspec/chainspec.go b/src/cx/cxspec/chainspec.go index 70984c330..53b13b8f8 100644 --- a/src/cx/cxspec/chainspec.go +++ b/src/cx/cxspec/chainspec.go @@ -2,51 +2,119 @@ package cxspec import ( "fmt" - "strings" - "unicode" "github.com/skycoin/skycoin/src/cipher" "github.com/skycoin/cx-chains/src/coin" + "github.com/skycoin/cx-chains/src/cx/cxspec/alpha" + "github.com/skycoin/cx-chains/src/skycoin" ) -// ObtainSpecEra obtains the spec era from a json-parsed result. -func ObtainSpecEra(t map[string]interface{}) string { - s, ok := t["spec_era"].(string) - if !ok { - return "" - } +const ( + specEraFieldName = "spec_era" +) - return s -} +type ChainSpec interface { + CXSpecHash() cipher.SHA256 + CXSpecEra() string + String() string -// CoinHoursName generates the coin hours name from given coin name. -func CoinHoursName(coinName string) string { - return fmt.Sprintf("%s coin hours", strings.ToLower(stripWhitespaces(coinName))) + Finalize(genesisSK cipher.SecKey) error + Check() error + + ObtainCoinName() string + ObtainCoinTicker() string + ObtainGenesisBlock() (*coin.Block, error) + ObtainChainPubKey() (cipher.PubKey, error) + + PopulateParamsModule() error + PopulateNodeConfig(conf *skycoin.NodeConfig) error } -// CoinHoursTicker generates the coin hours ticker symbol from given coin ticker. -func CoinHoursTicker(coinTicker string) string { - return fmt.Sprintf("%s_CH", strings.ToUpper(stripWhitespaces(coinTicker))) +// TemplateSpecGenerator generates a template chain spec. +type TemplateSpecGenerator func() ChainSpec + +// SpecFinalizer finalizes the chain spec. +type SpecFinalizer func(cs ChainSpec) error + +// SignedChainSpec contains a chain spec alongside a valid signature. +type SignedChainSpec struct { + Spec ChainSpec `json:"spec"` + GenesisHash string `json:"genesis_hash,omitempty"` + Sig string `json:"sig"` // hex representation of signature } -type TemplatePreparer func() map[string]interface{} +// MakeSignedChainSpec generates a signed spec from a ChainSpec and secret key. +// Note that the secret key needs to be able to generate the ChainSpec's public +// key to be valid. +func MakeSignedChainSpec(spec ChainSpec, sk cipher.SecKey) (SignedChainSpec, error) { + if err := spec.Check(); err != nil { + return SignedChainSpec{}, fmt.Errorf("spec file failed to pass basic check: %w", err) + } + + genesis, err := spec.ObtainGenesisBlock() + if err != nil { + return SignedChainSpec{}, fmt.Errorf("chain spec failed to generate genesis block: %w", err) + } + + pk, err := cipher.PubKeyFromSecKey(sk) + if err != nil { + return SignedChainSpec{}, err + } + + obtainedPK, err := spec.ObtainChainPubKey() + if err != nil { + return SignedChainSpec{}, fmt.Errorf("cannot obtain chain pk from spec: %w", err) + } + + if pk != obtainedPK { + return SignedChainSpec{}, fmt.Errorf("provided sk does not generate chain pk '%s'", obtainedPK) + } + + sig, err := cipher.SignHash(spec.CXSpecHash(), sk) + if err != nil { + return SignedChainSpec{}, err + } -func stripWhitespaces(s string) string { - out := make([]int32, 0, len(s)) - for _, c := range s { - if unicode.IsSpace(c) { - continue - } - out = append(out, c) + signedSpec := SignedChainSpec{ + Spec: spec, + GenesisHash: genesis.HashHeader().Hex(), + Sig: sig.Hex(), } - return string(out) + return signedSpec, nil } -type ChainSpec interface { - GenesisProgramState() []byte - GenerateGenesisBlock() (*coin.Block, error) - SpecHash() cipher.SHA256 - String() string +// Verify checks the following: +// - Spec is of right era, has valid chain pk, and generates valid genesis block. +// - Signature is valid +func (ss *SignedChainSpec) Verify() error { + const expectedEra = alpha.Era + + if era := ss.Spec.CXSpecEra(); era != expectedEra { + return fmt.Errorf("unexpected chain spec era '%s' (expected '%s')", + era, expectedEra) + } + + if _, err := ss.Spec.ObtainGenesisBlock(); err != nil { + return fmt.Errorf("chain spec failed to generate genesis block: %w", err) + } + + sig, err := cipher.SigFromHex(ss.Sig) + if err != nil { + return fmt.Errorf("failed to decode spec signature: %w", err) + } + + pk, err := ss.Spec.ObtainChainPubKey() + if err != nil { + return fmt.Errorf("failed to obtain chain pk: %w", err) + } + + hash := ss.Spec.CXSpecHash() + + if err := cipher.VerifyPubKeySignedHash(pk, sig, hash); err != nil { + return fmt.Errorf("failed to verify spec signature: %w", err) + } + + return nil } diff --git a/src/cx/cxspec/cx_tracker_client.go b/src/cx/cxspec/cx_tracker_client.go index 2db94a8d1..761754b1a 100644 --- a/src/cx/cxspec/cx_tracker_client.go +++ b/src/cx/cxspec/cx_tracker_client.go @@ -59,7 +59,8 @@ func (c *CXTrackerClient) AllSpecs(ctx context.Context) ([]SignedChainSpec, erro for i, spec := range specs { if err := spec.Verify(); err != nil { - return nil, fmt.Errorf("failed to verify returned spec [%d]%s: %w", i, spec.Spec.ChainPubKey, err) + pk, _ := spec.Spec.ObtainChainPubKey() + return nil, fmt.Errorf("failed to verify returned spec [%d]%s: %w", i, pk.Hex(), err) } } diff --git a/src/cx/cxspec/locate.go b/src/cx/cxspec/locate.go index faa39fdd2..e8c0974ca 100644 --- a/src/cx/cxspec/locate.go +++ b/src/cx/cxspec/locate.go @@ -11,8 +11,6 @@ import ( "github.com/sirupsen/logrus" "github.com/skycoin/skycoin/src/cipher" "github.com/skycoin/skycoin/src/util/logging" - - "github.com/skycoin/cx-chains/src/cx/cxspec/cxspecalpha" ) // LocPrefix determines the location type of the location string. @@ -93,7 +91,7 @@ func (c *LocateConfig) TrackerClient() *CXTrackerClient { } // LocateWithConfig locates a spec with a given locate config. -func LocateWithConfig(ctx context.Context, conf *LocateConfig) (cxspecalpha.ChainSpec, error) { +func LocateWithConfig(ctx context.Context, conf *LocateConfig) (ChainSpec, error) { return Locate(ctx, conf.Logger, conf.TrackerClient(), conf.CXChain) } @@ -103,7 +101,7 @@ func LocateWithConfig(ctx context.Context, conf *LocateConfig) (cxspecalpha.Chai // * either specifies the cx chain's genesis hash (if // is 'tracker') or filepath of the spec file (if // is 'file'). -func Locate(ctx context.Context, log logrus.FieldLogger, tracker *CXTrackerClient, loc string) (cxspecalpha.ChainSpec, error) { +func Locate(ctx context.Context, log logrus.FieldLogger, tracker *CXTrackerClient, loc string) (ChainSpec, error) { // Ensure logger is existent. if log == nil { log = logging.MustGetLogger("cxspec").WithField("func", "Locate") @@ -111,7 +109,7 @@ func Locate(ctx context.Context, log logrus.FieldLogger, tracker *CXTrackerClien prefix, suffix, err := splitLocString(loc) if err != nil { - return cxspecalpha.ChainSpec{}, err + return nil, err } // Check location prefix (LocPrefix). @@ -126,30 +124,30 @@ func Locate(ctx context.Context, log logrus.FieldLogger, tracker *CXTrackerClien case TrackerLoc: // Check that 'tracker' is not nil. if tracker == nil { - return cxspecalpha.ChainSpec{}, ErrEmptyTracker + return nil, ErrEmptyTracker } // Obtain genesis hash from hex string. hash, err := cipher.SHA256FromHex(suffix) if err != nil { - return cxspecalpha.ChainSpec{}, fmt.Errorf("invalid genesis hash provided '%s': %w", loc, err) + return nil, fmt.Errorf("invalid genesis hash provided '%s': %w", loc, err) } // Obtain spec from tracker. signedChainSpec, err := tracker.SpecByGenesisHash(ctx, hash) if err != nil { - return cxspecalpha.ChainSpec{}, fmt.Errorf("chain spec not of genesis hash not found in tracker: %w", err) + return nil, fmt.Errorf("chain spec not of genesis hash not found in tracker: %w", err) } // Verify again (no harm in doing it twice). if err := signedChainSpec.Verify(); err != nil { - return cxspecalpha.ChainSpec{}, err + return nil, err } return signedChainSpec.Spec, nil default: - return cxspecalpha.ChainSpec{}, fmt.Errorf("%w '%s'", ErrInvalidLocPrefix, prefix) + return nil, fmt.Errorf("%w '%s'", ErrInvalidLocPrefix, prefix) } } diff --git a/src/cx/cxspec/parse.go b/src/cx/cxspec/parse.go new file mode 100644 index 000000000..53666fb28 --- /dev/null +++ b/src/cx/cxspec/parse.go @@ -0,0 +1,45 @@ +package cxspec + +import ( + "encoding/json" + "fmt" + + "github.com/skycoin/cx-chains/src/cx/cxspec/alpha" +) + +// Parse parses a chain spec from raw bytes. +func Parse(raw []byte) (ChainSpec, error) { + era, err := ObtainSpecEra(raw) + if err != nil { + return nil, fmt.Errorf("failed to obtain spec era: %s", err) + } + + var v ChainSpec + + switch era { + case alpha.Era: + // TODO @evanlinjin: Ensure this implements ChainSpec. + v = new(alpha.ChainSpec) + default: + return nil, fmt.Errorf("unsupported spec era '%s'", era) + } + + if err := json.Unmarshal(raw, &v); err != nil { + return nil, fmt.Errorf("failed to parse spec of era '%s': %w", era, err) + } + + return v, nil +} + +// ObtainSpecEra obtains the spec era from a raw spec. +func ObtainSpecEra(raw []byte) (string, error) { + var v struct { + SpecEra string `json:"spec_era"` + } + + if err := json.Unmarshal(raw, &v); err != nil { + return "", err + } + + return v.SpecEra, nil +} diff --git a/src/cx/cxspec/populate.go b/src/cx/cxspec/populate.go index 7ec6f2b45..cc7ec1bbe 100644 --- a/src/cx/cxspec/populate.go +++ b/src/cx/cxspec/populate.go @@ -4,7 +4,6 @@ import ( "encoding/json" "fmt" "io/ioutil" - "strings" "time" "github.com/skycoin/cx-chains/src/api" @@ -15,87 +14,18 @@ import ( "github.com/skycoin/cx-chains/src/wallet" ) -// PopulateParamsModule populates the params module within cx chain. -func PopulateParamsModule(cs ChainSpec) { - // TODO @evanlinjin: Figure out distribution. - params.MainNetDistribution = params.Distribution{ - MaxCoinSupply: cs.MaxCoinSupply, - InitialUnlockedCount: 1, - UnlockAddressRate: 0, - UnlockTimeInterval: 0, - Addresses: []string{cs.GenesisAddr}, - } - params.UserVerifyTxn = params.VerifyTxn{ - BurnFactor: uint32(cs.Node.UserBurnFactor), - MaxTransactionSize: cs.Node.UserMaxTransactionSize, - MaxDropletPrecision: uint8(cs.Node.UserMaxDropletPrecision), - } - params.InitFromEnv() -} - -// PopulateNodeConfig populates the node config with values from cx chain spec. -func PopulateNodeConfig(trackerAddr string, spec ChainSpec, conf *skycoin.NodeConfig) error { - if spec.SpecEra != Era { - return fmt.Errorf("unsupported spec era '%s'", spec.SpecEra) - } - - genesis, err := spec.GenerateGenesisBlock() - if err != nil { - return err - } - peerListURL := fmt.Sprintf("%s/peerlists/%s.txt", trackerAddr, genesis.HashHeader()) - - conf.CoinName = spec.CoinName - conf.PeerListURL = peerListURL - conf.Port = spec.Node.Port - conf.WebInterfacePort = spec.Node.WebInterfacePort - conf.UnconfirmedVerifyTxn = params.VerifyTxn{ - BurnFactor: spec.Protocol.UnconfirmedBurnFactor, - MaxTransactionSize: spec.Protocol.UnconfirmedMaxTransactionSize, - MaxDropletPrecision: spec.Protocol.UnconfirmedMaxDropletPrecision, - } - conf.CreateBlockVerifyTxn = params.VerifyTxn{ - BurnFactor: spec.Protocol.CreateBlockBurnFactor, - MaxTransactionSize: spec.Protocol.CreateBlockMaxTransactionSize, - MaxDropletPrecision: spec.Protocol.CreateBlockMaxDropletPrecision, - } - conf.MaxBlockTransactionsSize = spec.Protocol.MaxBlockTransactionSize - conf.GenesisSignatureStr = spec.GenesisSig - conf.GenesisAddressStr = spec.GenesisAddr - conf.BlockchainPubkeyStr = spec.ChainPubKey - conf.GenesisTimestamp = spec.GenesisTimestamp - conf.GenesisCoinVolume = spec.GenesisCoinVolume - conf.DefaultConnections = spec.Node.DefaultConnections - conf.Fiber = readable.FiberConfig{ - Name: spec.CoinName, - DisplayName: spec.CoinName, - Ticker: spec.CoinTicker, - CoinHoursName: spec.CoinHoursName, - CoinHoursTicker: spec.CoinHoursTicker, - ExplorerURL: "", // TODO @evanlinjin: CX Chain explorer? - } - - if conf.DataDirectory == "" { - conf.DataDirectory = "$HOME/.cxchain/" + spec.CoinName - } else { - conf.DataDirectory = strings.ReplaceAll(conf.DataDirectory, "{coin}", spec.CoinName) - } - - return nil -} - // ReadSpecFile reads chain spec from given filename. func ReadSpecFile(filename string) (ChainSpec, error) { b, err := ioutil.ReadFile(filename) if err != nil { - return ChainSpec{}, fmt.Errorf("failed to read chain spec file '%s': %w", filename, err) + return nil, fmt.Errorf("failed to read chain spec file '%s': %w", filename, err) } var spec ChainSpec if err := json.Unmarshal(b, &spec); err != nil { - return ChainSpec{}, fmt.Errorf("chain spec file '%s' is ill-formed: %w", filename, err) + return nil, fmt.Errorf("chain spec file '%s' is ill-formed: %w", filename, err) } - if _, err := spec.GenerateGenesisBlock(); err != nil { - return ChainSpec{}, fmt.Errorf("chain spec file '%s' cannot generate genesis block: %w", filename, err) + if _, err := spec.ObtainGenesisBlock(); err != nil { + return nil, fmt.Errorf("chain spec file '%s' cannot generate genesis block: %w", filename, err) } return spec, nil } diff --git a/src/cx/cxspec/signed_chainspec.go b/src/cx/cxspec/signed_chainspec.go deleted file mode 100644 index 2eaca2d53..000000000 --- a/src/cx/cxspec/signed_chainspec.go +++ /dev/null @@ -1,74 +0,0 @@ -package cxspec - -import ( - "fmt" - - "github.com/skycoin/skycoin/src/cipher" -) - -// SignedChainSpec contains a chain spec alongside a valid signature. -type SignedChainSpec struct { - Spec ChainSpec `json:"spec"` - GenesisHash string `json:"genesis_hash,omitempty"` - Sig string `json:"sig"` // hex representation of signature -} - -// MakeSignedChainSpec generates a signed spec from a ChainSpec and secret key. -// Note that the secret key needs to be able to generate the ChainSpec's public -// key to be valid. -func MakeSignedChainSpec(spec ChainSpec, sk cipher.SecKey) (SignedChainSpec, error) { - genesis, err := spec.GenerateGenesisBlock() - if err != nil { - return SignedChainSpec{}, fmt.Errorf("chain spec failed to generate genesis block: %w", err) - } - - pk, err := cipher.PubKeyFromSecKey(sk) - if err != nil { - return SignedChainSpec{}, err - } - - if pk != spec.ProcessedChainPubKey() { - return SignedChainSpec{}, fmt.Errorf("provided sk does not generate chain pk '%s'", spec.ChainPubKey) - } - - sig, err := cipher.SignHash(spec.SpecHash(), sk) - if err != nil { - return SignedChainSpec{}, err - } - - signedSpec := SignedChainSpec{ - Spec: spec, - GenesisHash: genesis.HashHeader().Hex(), - Sig: sig.Hex(), - } - - return signedSpec, nil -} - -// Verify checks the following: -// - Spec is of right era, has valid chain pk, and generates valid genesis block. -// - Signature is valid -func (ss *SignedChainSpec) Verify() error { - if era := ss.Spec.SpecEra; era != Era { - return fmt.Errorf("unexpected chain spec era '%s' (expected '%s')", - era, Era) - } - - if _, err := ss.Spec.GenerateGenesisBlock(); err != nil { - return fmt.Errorf("chain spec failed to generate genesis block: %w", err) - } - - sig, err := cipher.SigFromHex(ss.Sig) - if err != nil { - return fmt.Errorf("failed to decode spec signature: %w", err) - } - - pk := ss.Spec.ProcessedChainPubKey() - hash := ss.Spec.SpecHash() - - if err := cipher.VerifyPubKeySignedHash(pk, sig, hash); err != nil { - return fmt.Errorf("failed to verify spec signature: %w", err) - } - - return nil -} From b6b38ce9733179d0fbfb05cd386d2f96e518929e Mon Sep 17 00:00:00 2001 From: Evan Lin Date: Mon, 29 Mar 2021 13:23:19 +0800 Subject: [PATCH 4/7] Change execs to work with chain spec logic changes. * Added various extra methods to cxspec.ChainSpec interface. * Ensure execs use spec methods correctly. * Fix compilation issues. --- cmd/cxchain-cli/cmd_genesis.go | 2 +- cmd/cxchain-cli/cmd_new.go | 3 ++- cmd/cxchain-cli/cmd_peers.go | 8 +++++++- cmd/cxchain-cli/cmd_run.go | 24 ++++++++++++++++++++---- cmd/cxchain-cli/cmd_state.go | 16 ++++++++++++++-- cmd/cxchain/cxchain.go | 34 ++++++++++++++++++++++++++-------- src/cx/cxdmsg/api.go | 4 ++-- src/cx/cxspec/alpha/alpha.go | 24 ++++++++++++++++-------- src/cx/cxspec/chainspec.go | 3 +++ src/cx/cxspec/keyspec.go | 5 +++-- 10 files changed, 94 insertions(+), 29 deletions(-) diff --git a/cmd/cxchain-cli/cmd_genesis.go b/cmd/cxchain-cli/cmd_genesis.go index 93e96e0f7..1eaf13532 100644 --- a/cmd/cxchain-cli/cmd_genesis.go +++ b/cmd/cxchain-cli/cmd_genesis.go @@ -58,7 +58,7 @@ func cmdGenesis(args []string) { Fatal("Failed to decode file") } - block, err := cSpec.GenerateGenesisBlock() + block, err := cSpec.ObtainGenesisBlock() if err != nil { log.WithError(err). Fatal("Failed to generate genesis block.") diff --git a/cmd/cxchain-cli/cmd_new.go b/cmd/cxchain-cli/cmd_new.go index df14cb5ea..254d223c6 100644 --- a/cmd/cxchain-cli/cmd_new.go +++ b/cmd/cxchain-cli/cmd_new.go @@ -3,6 +3,7 @@ package main import ( "encoding/json" "flag" + "github.com/skycoin/cx-chains/src/cx/cxspec/alpha" "io/ioutil" "os" "strings" @@ -160,7 +161,7 @@ func cmdNew(args []string) { genAddr := cipher.AddressFromPubKey(genPK) // Generate and write chain spec file. - cSpec, err := cxspec.New(flags.coinName, flags.coinTicker, chainSK, genAddr, genProgState) + cSpec, err := alpha.New(flags.coinName, flags.coinTicker, chainSK, genAddr, genProgState) if err != nil { log.WithError(err). Fatal("Failed to generate chain spec.") diff --git a/cmd/cxchain-cli/cmd_peers.go b/cmd/cxchain-cli/cmd_peers.go index 64413557d..7d4bcb18d 100644 --- a/cmd/cxchain-cli/cmd_peers.go +++ b/cmd/cxchain-cli/cmd_peers.go @@ -18,8 +18,14 @@ func cmdPeers(args []string) { rootCmd := flag.NewFlagSet("cxchain-cli peers", flag.ExitOnError) spec := processSpecFlags(context.Background(), rootCmd, args) + webPort, err := spec.ObtainWebInterfacePort() + if err != nil { + log.WithField("spec_era", spec.CXSpecEra()). + Fatal("Failed to obtain WebInterfacePort") + } + // nodeAddr holds the value parsed from the flags 'node' and 'n' - nodeAddr := fmt.Sprintf("http://127.0.0.1:%d", spec.Node.WebInterfacePort) + nodeAddr := fmt.Sprintf("http://127.0.0.1:%d", webPort) addNodeAddrFlag := func(cmd *flag.FlagSet) { cmd.StringVar(&nodeAddr, "node", nodeAddr, "HTTP API `ADDRESS` of cxchain node") cmd.StringVar(&nodeAddr, "n", nodeAddr, "shorthand for 'node'") diff --git a/cmd/cxchain-cli/cmd_run.go b/cmd/cxchain-cli/cmd_run.go index f3d50e636..a0ce5b499 100644 --- a/cmd/cxchain-cli/cmd_run.go +++ b/cmd/cxchain-cli/cmd_run.go @@ -69,6 +69,18 @@ func processRunFlags(args []string) (runFlags, cxspec.ChainSpec, cipher.SecKey) cmd := flag.NewFlagSet("cxchain-cli run", flag.ExitOnError) spec := processSpecFlags(context.Background(), cmd, args) + webPort, err := spec.ObtainWebInterfacePort() + if err != nil { + log.WithField("spec_era", spec.CXSpecEra()). + Fatal("Failed to obtain web interface port.") + } + + genesisAddr, err := spec.ObtainGenesisAddr() + if err != nil { + log.WithField("spec_era", spec.CXSpecEra()). + Fatal("Failed to obtain genesis address.") + } + f := runFlags{ cmd: cmd, @@ -81,7 +93,7 @@ func processRunFlags(args []string) (runFlags, cxspec.ChainSpec, cipher.SecKey) // TODO @evanlinjin: Find a way to set this later on. // TODO @evanlinjin: This way, we would not need to call '.Locate' so // TODO @evanlinjin: early within processSpecFlags() - nodeAddr: fmt.Sprintf("http://127.0.0.1:%d", spec.Node.WebInterfacePort), + nodeAddr: fmt.Sprintf("http://127.0.0.1:%d", webPort), } f.cmd.Usage = func() { @@ -108,7 +120,7 @@ func processRunFlags(args []string) (runFlags, cxspec.ChainSpec, cipher.SecKey) // Ensure genesis secret key is provided if 'inject' flag is set. var genSK cipher.SecKey if f.inject { - genSK = readRunENVs(cipher.MustDecodeBase58Address(spec.GenesisAddr)) + genSK = readRunENVs(genesisAddr) } // Log stuff. @@ -137,10 +149,14 @@ func cmdRun(args []string) { c := api.NewClient(flags.nodeAddr) // Prepare address. - addr := cipher.MustDecodeBase58Address(spec.GenesisAddr) + genesisAddr, err := spec.ObtainGenesisAddr() + if err != nil { + log.WithField("spec_era", spec.CXSpecEra()). + Fatal("Failed to obtain genesis address.") + } // Parse and run program. - ux, progB, err := PrepareChainProg(cxFilenames, cxRes.CXSources, c, addr, flags.debugLexer, flags.debugProfile) + ux, progB, err := PrepareChainProg(cxFilenames, cxRes.CXSources, c, genesisAddr, flags.debugLexer, flags.debugProfile) if err != nil { log.WithError(err).Fatal("Failed to prepare chain CX program.") } diff --git a/cmd/cxchain-cli/cmd_state.go b/cmd/cxchain-cli/cmd_state.go index e090c8e54..1267ebe5a 100644 --- a/cmd/cxchain-cli/cmd_state.go +++ b/cmd/cxchain-cli/cmd_state.go @@ -29,11 +29,23 @@ func processStateFlags(args []string) (stateFlags, cipher.Address) { cmd := flag.NewFlagSet("cxchain-cli state", flag.ExitOnError) spec := processSpecFlags(context.Background(), cmd, args) + webPort, err := spec.ObtainWebInterfacePort() + if err != nil { + log.WithField("spec_era", spec.CXSpecEra()). + Fatal("Failed to obtain web interface port.") + } + + genesisAddr, err := spec.ObtainGenesisAddr() + if err != nil { + log.WithField("spec_era", spec.CXSpecEra()). + Fatal("Failed to obtain genesis address.") + } + f := stateFlags{ cmd: cmd, MemoryFlags: cxflags.DefaultMemoryFlags(), - nodeAddr: fmt.Sprintf("http://127.0.0.1:%d", spec.Node.WebInterfacePort), - appAddr: cipher.MustDecodeBase58Address(spec.GenesisAddr).String(), + nodeAddr: fmt.Sprintf("http://127.0.0.1:%d", webPort), + appAddr: genesisAddr.String(), } f.cmd.Usage = func() { diff --git a/cmd/cxchain/cxchain.go b/cmd/cxchain/cxchain.go index 284ef7b76..3a19c48f1 100644 --- a/cmd/cxchain/cxchain.go +++ b/cmd/cxchain/cxchain.go @@ -100,14 +100,19 @@ func trackerUpdateLoop(nodeSK cipher.SecKey, nodeTCPAddr string, spec cxspec.Cha client := cxspec.NewCXTrackerClient(log, nil, specFlags.CXTracker) nodePK := cipher.MustPubKeyFromSecKey(nodeSK) - block, err := spec.GenerateGenesisBlock() + block, err := spec.ObtainGenesisBlock() if err != nil { panic(err) // should not happen } hash := block.HashHeader() + chainPK, err := spec.ObtainChainPubKey() + if err != nil { + panic(err) // TODO @evanlinjin: Proper fail and error reporting? + } + // If publisher, ensure spec is registered. - if isPub := nodePK == spec.ProcessedChainPubKey(); isPub { + if isPub := nodePK == chainPK; isPub { signedSpec, err := cxspec.MakeSignedChainSpec(spec, nodeSK) if err != nil { panic(err) // should not happen @@ -161,8 +166,10 @@ func trackerUpdateLoop(nodeSK cipher.SecKey, nodeTCPAddr string, spec cxspec.Cha func main() { // Register and parse flags for cx chain spec. spec := locateSpec() - cxspec.PopulateParamsModule(spec) - log.Info(spec.PrintString()) + if err := spec.PopulateParamsModule(); err != nil { + log.WithError(err).Fatal("Failed to populate params module with spec.") + } + log.Info(spec.String()) // Register additional CLI flags. cmd := flag.CommandLine @@ -180,9 +187,10 @@ func main() { ensureConfMode(&conf) // Node config: Populate node config based on chain spec content. - if err := cxspec.PopulateNodeConfig(specFlags.CXTracker, spec, &conf); err != nil { - log.WithError(err).Fatal("Failed to parse from chain spec file.") + if err := spec.PopulateNodeConfig(&conf); err != nil { + log.WithError(err).Fatal("Failed to populate node config from spec file.") } + // TODO @evanlinjin: Do we need to do something about the cx tracker URL? // Node config: Ensure node keys are set. // - If node secret key is null, randomly generate one. @@ -196,10 +204,15 @@ func main() { } nodePK = cipher.MustPubKeyFromSecKey(nodeSK) + chainPK, err := spec.ObtainChainPubKey() + if err != nil { + log.WithError(err).Fatal("Failed to obtain chain pk from spec.") + } + // Node config: Enable publisher mode if conditions are met. // - Skip if 'forceClient' is set. // - Skip if 'nodePK' is not equal to chain spec's PK. - if !forceClient && nodePK == spec.ProcessedChainPubKey() { + if !forceClient && nodePK == chainPK { conf.BlockchainSeckeyStr = nodeSK.Hex() conf.RunBlockPublisher = true } @@ -264,8 +277,13 @@ func main() { cxdmsg.ServeDmsg(dmsgCtx, dmsgLog, dmsgConf, dmsgAPI) }() + progState, err := spec.ObtainGenesisProgState() + if err != nil { + log.WithError(err).Fatal("Failed to obtain genesis program state from spec.") + } + // Run main daemon. - if err := coin.Run(spec.RawGenesisProgState(), gwCh); err != nil { + if err := coin.Run(progState, gwCh); err != nil { os.Exit(1) } } diff --git a/src/cx/cxdmsg/api.go b/src/cx/cxdmsg/api.go index 9da1120cb..49faaf053 100644 --- a/src/cx/cxdmsg/api.go +++ b/src/cx/cxdmsg/api.go @@ -44,14 +44,14 @@ func (api *API) obtainNodeStats() (NodeStats, error) { return NodeStats{}, err } - genBlock, err := api.ChainSpec.GenerateGenesisBlock() + genBlock, err := api.ChainSpec.ObtainGenesisBlock() if err != nil { return NodeStats{}, err } stats := NodeStats{ CXChainVersion: api.Version, - CXChainSpecEra: api.ChainSpec.SpecEra, + CXChainSpecEra: api.ChainSpec.CXSpecEra(), GenesisBlockHash: cipher.SHA256(genBlock.HashHeader()), PrevBlockHash: cipher.SHA256(chainMeta.HeadBlock.Head.PrevHash), HeadBlockHash: cipher.SHA256(chainMeta.HeadBlock.HashHeader()), diff --git a/src/cx/cxspec/alpha/alpha.go b/src/cx/cxspec/alpha/alpha.go index ba7be0cef..d4216b9f6 100644 --- a/src/cx/cxspec/alpha/alpha.go +++ b/src/cx/cxspec/alpha/alpha.go @@ -152,14 +152,6 @@ func New(coin, ticker string, chainSK cipher.SecKey, genesisAddr cipher.Address, return spec, nil } -func (cs *ChainSpec) RawGenesisProgState() []byte { - b, err := progSEnc.DecodeString(cs.GenesisProgState) - if err != nil { - panic(err) - } - return b -} - // SpecHash returns the hashed spec object. func (cs *ChainSpec) CXSpecHash() cipher.SHA256 { b, err := json.Marshal(cs) @@ -237,11 +229,27 @@ func (cs *ChainSpec) ObtainGenesisBlock() (*coin.Block, error) { return block, nil } +// ObtainGenesisAddr obtains the genesis address. +func (cs *ChainSpec) ObtainGenesisAddr() (cipher.Address, error) { + return cipher.DecodeBase58Address(cs.GenesisAddr) +} + +// ObtainGenesisProgState obtains the genesis program state. +func (cs *ChainSpec) ObtainGenesisProgState() ([]byte, error) { + return progSEnc.DecodeString(cs.GenesisProgState) +} + + // ObtainChainPubKey returns the processed chain public key. func (cs *ChainSpec) ObtainChainPubKey() (cipher.PubKey, error) { return cipher.PubKeyFromHex(cs.ChainPubKey) } +// ObtainWebInterfacePort returns the web interface port. +func (cs *ChainSpec) ObtainWebInterfacePort() (int, error) { + return cs.Node.WebInterfacePort, nil +} + // PopulateParamsModule populates the params module within cx chain. func (cs *ChainSpec) PopulateParamsModule() error { // TODO @evanlinjin: Figure out distribution. diff --git a/src/cx/cxspec/chainspec.go b/src/cx/cxspec/chainspec.go index 53b13b8f8..5433c5a41 100644 --- a/src/cx/cxspec/chainspec.go +++ b/src/cx/cxspec/chainspec.go @@ -25,7 +25,10 @@ type ChainSpec interface { ObtainCoinName() string ObtainCoinTicker() string ObtainGenesisBlock() (*coin.Block, error) + ObtainGenesisAddr() (cipher.Address, error) + ObtainGenesisProgState() ([]byte, error) ObtainChainPubKey() (cipher.PubKey, error) + ObtainWebInterfacePort() (int, error) PopulateParamsModule() error PopulateNodeConfig(conf *skycoin.NodeConfig) error diff --git a/src/cx/cxspec/keyspec.go b/src/cx/cxspec/keyspec.go index 637c9c3eb..7ec0c98a2 100644 --- a/src/cx/cxspec/keyspec.go +++ b/src/cx/cxspec/keyspec.go @@ -2,6 +2,7 @@ package cxspec import ( "fmt" + "github.com/skycoin/cx-chains/src/cx/cxspec/alpha" "github.com/skycoin/skycoin/src/cipher" ) @@ -31,7 +32,7 @@ func KeySpecFromSecKey(keyType string, sk cipher.SecKey, incSK, incAddr bool) Ke addr := cipher.AddressFromPubKey(pk) spec := KeySpec{ - SpecEra: Era, + SpecEra: alpha.Era, // TODO @evanlinjin: Figure out where to define this. Do we also need to change KeySpec to an interface? KeyType: keyType, PubKey: pk.Hex(), SecKey: "", @@ -57,7 +58,7 @@ func KeySpecFromPubKey(keyType string, pk cipher.PubKey, incAddr bool) KeySpec { addr := cipher.AddressFromPubKey(pk) spec := KeySpec{ - SpecEra: Era, + SpecEra: alpha.Era, // TODO @evanlinjin: Figure out where to define this. Do we also need to change KeySpec to an interface? KeyType: keyType, PubKey: pk.Hex(), SecKey: "", From eacec8e3a16c3c6320d60d14db059ddfe3d885f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BF=97=E5=AE=87?= Date: Mon, 29 Mar 2021 22:02:58 +0800 Subject: [PATCH 5/7] Initial work on WrappedChainSpec.. --- src/cx/cxspec/chainspec.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/cx/cxspec/chainspec.go b/src/cx/cxspec/chainspec.go index 53b13b8f8..f6b40354c 100644 --- a/src/cx/cxspec/chainspec.go +++ b/src/cx/cxspec/chainspec.go @@ -1,6 +1,7 @@ package cxspec import ( + "encoding/json" "fmt" "github.com/skycoin/skycoin/src/cipher" @@ -37,6 +38,20 @@ type TemplateSpecGenerator func() ChainSpec // SpecFinalizer finalizes the chain spec. type SpecFinalizer func(cs ChainSpec) error +type WrappedChainSpec struct { ChainSpec } + +// UnmarshalJSON implements json.Unmarshaler +func (ws *WrappedChainSpec) UnmarshalJSON(b []byte) error { + var tempV struct{ + Era string `json:"era"` + } + if err := json.Unmarshal(b, &tempV); err != nil { + return fmt.Errorf("failed to unmarshal into temporary structure: %w", err) + } + + return nil +} + // SignedChainSpec contains a chain spec alongside a valid signature. type SignedChainSpec struct { Spec ChainSpec `json:"spec"` From ebfb89dcfe3c4b96f0a291dd54a60a449439a894 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BF=97=E5=AE=87?= Date: Tue, 30 Mar 2021 18:09:40 +0800 Subject: [PATCH 6/7] Fix cxspec.ReadSpecFile regression. --- cmd/cxchain-cli/cmd_new.go | 16 ++++++++-------- src/cx/cxspec/alpha/alpha.go | 4 ++-- src/cx/cxspec/chainspec.go | 22 ++++++++++++++++++---- src/cx/cxspec/parse.go | 3 +-- src/cx/cxspec/populate.go | 2 +- 5 files changed, 30 insertions(+), 17 deletions(-) diff --git a/cmd/cxchain-cli/cmd_new.go b/cmd/cxchain-cli/cmd_new.go index 254d223c6..0f041a104 100644 --- a/cmd/cxchain-cli/cmd_new.go +++ b/cmd/cxchain-cli/cmd_new.go @@ -3,20 +3,18 @@ package main import ( "encoding/json" "flag" - "github.com/skycoin/cx-chains/src/cx/cxspec/alpha" "io/ioutil" "os" "strings" - "github.com/skycoin/skycoin/src/cipher" - "github.com/skycoin/cx/cxgo/cxlexer" "github.com/skycoin/cx/cxgo/parser" - - "github.com/skycoin/cx-chains/src/cx/cxutil" + "github.com/skycoin/skycoin/src/cipher" "github.com/skycoin/cx-chains/src/cx/cxflags" "github.com/skycoin/cx-chains/src/cx/cxspec" + "github.com/skycoin/cx-chains/src/cx/cxspec/alpha" + "github.com/skycoin/cx-chains/src/cx/cxutil" ) const filePerm = 0644 @@ -28,6 +26,7 @@ type newFlags struct { unifyKeys bool coinName string coinTicker string + trackerAddr string debugLexer bool debugProfile int @@ -48,6 +47,7 @@ func processNewFlags(args []string) newFlags { unifyKeys: false, coinName: "skycoin", coinTicker: "SKY", + trackerAddr: cxspec.DefaultTrackerURL, debugLexer: false, debugProfile: 0, @@ -67,10 +67,10 @@ func processNewFlags(args []string) newFlags { f.cmd.BoolVar(&f.replace, "r", f.replace, "shorthand for 'replace'") f.cmd.BoolVar(&f.unifyKeys, "unify", f.unifyKeys, "whether to use the same keys for genesis and chain") f.cmd.BoolVar(&f.unifyKeys, "u", f.unifyKeys, "shorthand for 'unify'") + f.cmd.StringVar(&f.coinName, "coin", f.coinName, "`NAME` for cx coin") - f.cmd.StringVar(&f.coinName, "c", f.coinName, "shorthand for 'coin'") f.cmd.StringVar(&f.coinTicker, "ticker", f.coinTicker, "`SYMBOL` for cx coin ticker") - f.cmd.StringVar(&f.coinTicker, "t", f.coinTicker, "shorthand for 'ticker'") + f.cmd.StringVar(&f.trackerAddr, "tracker", f.trackerAddr, "`URL` of cx tracker") f.cmd.BoolVar(&f.debugLexer, "debug-lexer", f.debugLexer, "enable lexer debugging by printing all scanner tokens") f.cmd.IntVar(&f.debugProfile, "debug-profile", f.debugProfile, "Enable CPU+MEM profiling and set CPU profiling rate. Visualize .pprof files with 'go get github.com/google/pprof' and 'pprof -http=:8080 file.pprof'") @@ -161,7 +161,7 @@ func cmdNew(args []string) { genAddr := cipher.AddressFromPubKey(genPK) // Generate and write chain spec file. - cSpec, err := alpha.New(flags.coinName, flags.coinTicker, chainSK, genAddr, genProgState) + cSpec, err := alpha.New(flags.coinName, flags.coinTicker, chainSK, flags.trackerAddr, genAddr, genProgState) if err != nil { log.WithError(err). Fatal("Failed to generate chain spec.") diff --git a/src/cx/cxspec/alpha/alpha.go b/src/cx/cxspec/alpha/alpha.go index d4216b9f6..6b7126db3 100644 --- a/src/cx/cxspec/alpha/alpha.go +++ b/src/cx/cxspec/alpha/alpha.go @@ -112,14 +112,14 @@ type ChainSpec struct { } // New generates a new chain spec. -func New(coin, ticker string, chainSK cipher.SecKey, genesisAddr cipher.Address, genesisProgState []byte) (*ChainSpec, error) { +func New(coin, ticker string, chainSK cipher.SecKey, trackerAddr string, genesisAddr cipher.Address, genesisProgState []byte) (*ChainSpec, error) { coin = strings.ToLower(strings.Replace(coin, " ", "", -1)) ticker = strings.ToUpper(strings.Replace(ticker, " ", "", -1)) spec := &ChainSpec{ SpecEra: Era, ChainPubKey: "", // ChainPubKey is generated at a later step via generateAndSignGenesisBlock - TrackerAddr: "", // TODO @evanlinjin: Have default tracker address. + TrackerAddr: trackerAddr, Protocol: DefaultProtocolParams(), Node: DefaultNodeParams(), diff --git a/src/cx/cxspec/chainspec.go b/src/cx/cxspec/chainspec.go index a10814d9a..87dbe8c51 100644 --- a/src/cx/cxspec/chainspec.go +++ b/src/cx/cxspec/chainspec.go @@ -11,6 +11,7 @@ import ( "github.com/skycoin/cx-chains/src/skycoin" ) +// TODO @evanlinjin: Figure out if this is needed. const ( specEraFieldName = "spec_era" ) @@ -41,23 +42,36 @@ type TemplateSpecGenerator func() ChainSpec // SpecFinalizer finalizes the chain spec. type SpecFinalizer func(cs ChainSpec) error +// WrappedChainSpec allows a chain spec to be marshalled/unmarshalled to and +// from raw JSON data. type WrappedChainSpec struct { ChainSpec } // UnmarshalJSON implements json.Unmarshaler func (ws *WrappedChainSpec) UnmarshalJSON(b []byte) error { var tempV struct{ - Era string `json:"era"` + Era string `json:"spec_era"` } if err := json.Unmarshal(b, &tempV); err != nil { return fmt.Errorf("failed to unmarshal into temporary structure: %w", err) } - return nil + var err error + ws.ChainSpec, err = Parse(b) + + return err } +// func (ws *WrappedChainSpec) MarshalJSON() ([]byte, error) { +// if ws.ChainSpec == nil { +// return []byte("null"), nil +// } +// +// return json.Marshal(ws.ChainSpec) +// } + // SignedChainSpec contains a chain spec alongside a valid signature. type SignedChainSpec struct { - Spec ChainSpec `json:"spec"` + Spec WrappedChainSpec `json:"spec"` GenesisHash string `json:"genesis_hash,omitempty"` Sig string `json:"sig"` // hex representation of signature } @@ -95,7 +109,7 @@ func MakeSignedChainSpec(spec ChainSpec, sk cipher.SecKey) (SignedChainSpec, err } signedSpec := SignedChainSpec{ - Spec: spec, + Spec: WrappedChainSpec{ ChainSpec: spec }, GenesisHash: genesis.HashHeader().Hex(), Sig: sig.Hex(), } diff --git a/src/cx/cxspec/parse.go b/src/cx/cxspec/parse.go index 53666fb28..d8523e30d 100644 --- a/src/cx/cxspec/parse.go +++ b/src/cx/cxspec/parse.go @@ -18,8 +18,7 @@ func Parse(raw []byte) (ChainSpec, error) { switch era { case alpha.Era: - // TODO @evanlinjin: Ensure this implements ChainSpec. - v = new(alpha.ChainSpec) + v = &alpha.ChainSpec{} default: return nil, fmt.Errorf("unsupported spec era '%s'", era) } diff --git a/src/cx/cxspec/populate.go b/src/cx/cxspec/populate.go index cc7ec1bbe..f15d6a20a 100644 --- a/src/cx/cxspec/populate.go +++ b/src/cx/cxspec/populate.go @@ -20,7 +20,7 @@ func ReadSpecFile(filename string) (ChainSpec, error) { if err != nil { return nil, fmt.Errorf("failed to read chain spec file '%s': %w", filename, err) } - var spec ChainSpec + var spec WrappedChainSpec if err := json.Unmarshal(b, &spec); err != nil { return nil, fmt.Errorf("chain spec file '%s' is ill-formed: %w", filename, err) } From a49e3d80720d04d062967da75f1c82ff4a0dda31 Mon Sep 17 00:00:00 2001 From: Evan Lin Date: Tue, 30 Mar 2021 20:50:21 +0800 Subject: [PATCH 7/7] Fix spec marshalling regression. --- cmd/cxchain-cli/cmd_genesis.go | 4 ++-- cmd/cxchain-cli/cmd_new.go | 16 ++++++++-------- cmd/cxchain-cli/cmd_post.go | 10 ++++++---- src/cx/cxspec/alpha/alpha.go | 11 +++++------ src/cx/cxspec/chainspec.go | 25 +++++++++++++------------ src/cx/cxspec/keyspec.go | 1 + 6 files changed, 35 insertions(+), 32 deletions(-) diff --git a/cmd/cxchain-cli/cmd_genesis.go b/cmd/cxchain-cli/cmd_genesis.go index 1eaf13532..bd56bd093 100644 --- a/cmd/cxchain-cli/cmd_genesis.go +++ b/cmd/cxchain-cli/cmd_genesis.go @@ -12,7 +12,7 @@ import ( type genesisFlags struct { cmd *flag.FlagSet - + in string } @@ -66,4 +66,4 @@ func cmdGenesis(args []string) { hash := block.HashHeader() fmt.Println(hash.Hex()) -} \ No newline at end of file +} diff --git a/cmd/cxchain-cli/cmd_new.go b/cmd/cxchain-cli/cmd_new.go index 0f041a104..237bdaaf0 100644 --- a/cmd/cxchain-cli/cmd_new.go +++ b/cmd/cxchain-cli/cmd_new.go @@ -22,10 +22,10 @@ const filePerm = 0644 type newFlags struct { cmd *flag.FlagSet - replace bool - unifyKeys bool - coinName string - coinTicker string + replace bool + unifyKeys bool + coinName string + coinTicker string trackerAddr string debugLexer bool @@ -43,10 +43,10 @@ func processNewFlags(args []string) newFlags { f := newFlags{ cmd: flag.NewFlagSet("cxchain-cli new", flag.ExitOnError), - replace: false, - unifyKeys: false, - coinName: "skycoin", - coinTicker: "SKY", + replace: false, + unifyKeys: false, + coinName: "skycoin", + coinTicker: "SKY", trackerAddr: cxspec.DefaultTrackerURL, debugLexer: false, diff --git a/cmd/cxchain-cli/cmd_post.go b/cmd/cxchain-cli/cmd_post.go index b238168ba..5eae5e85f 100644 --- a/cmd/cxchain-cli/cmd_post.go +++ b/cmd/cxchain-cli/cmd_post.go @@ -3,10 +3,12 @@ package main import ( "context" "flag" + "os" + + "github.com/skycoin/skycoin/src/cipher" + "github.com/skycoin/cx-chains/src/cx/cxspec" "github.com/skycoin/cx-chains/src/cx/cxutil" - "github.com/skycoin/skycoin/src/cipher" - "os" ) type postFlags struct { @@ -25,8 +27,8 @@ func processPostFlags(args []string) (postFlags, cipher.SecKey) { specInput: cxspec.DefaultSpecFilepath, signedOutput: "", // empty for no output - dryRun: false, - tracker: cxspec.DefaultTrackerURL, + dryRun: false, + tracker: cxspec.DefaultTrackerURL, } f.cmd.Usage = func() { diff --git a/src/cx/cxspec/alpha/alpha.go b/src/cx/cxspec/alpha/alpha.go index 6b7126db3..cc7a62248 100644 --- a/src/cx/cxspec/alpha/alpha.go +++ b/src/cx/cxspec/alpha/alpha.go @@ -85,8 +85,8 @@ type ChainSpec struct { Node NodeParams `json:"node"` // Default params for a node of given coin (this may be removed in future eras). /* Identity Params */ - CoinName string `json:"coin_name"` // Coin display name (e.g. skycoin). - CoinTicker string `json:"coin_ticker"` // Coin price ticker (e.g. SKY). + CoinName string `json:"coin_name"` // Coin display name (e.g. skycoin). + CoinTicker string `json:"coin_ticker"` // Coin price ticker (e.g. SKY). /* Genesis Params */ GenesisAddr string `json:"genesis_address"` // Genesis address (base58 representation). @@ -123,8 +123,8 @@ func New(coin, ticker string, chainSK cipher.SecKey, trackerAddr string, genesis Protocol: DefaultProtocolParams(), Node: DefaultNodeParams(), - CoinName: coin, - CoinTicker: ticker, + CoinName: coin, + CoinTicker: ticker, GenesisAddr: genesisAddr.String(), GenesisSig: "", // GenesisSig is generated at a later step via generateAndSignGenesisBlock @@ -239,7 +239,6 @@ func (cs *ChainSpec) ObtainGenesisProgState() ([]byte, error) { return progSEnc.DecodeString(cs.GenesisProgState) } - // ObtainChainPubKey returns the processed chain public key. func (cs *ChainSpec) ObtainChainPubKey() (cipher.PubKey, error) { return cipher.PubKeyFromHex(cs.ChainPubKey) @@ -398,4 +397,4 @@ func stripWhitespaces(s string) string { } return string(out) -} \ No newline at end of file +} diff --git a/src/cx/cxspec/chainspec.go b/src/cx/cxspec/chainspec.go index 87dbe8c51..ef1768d1b 100644 --- a/src/cx/cxspec/chainspec.go +++ b/src/cx/cxspec/chainspec.go @@ -44,11 +44,11 @@ type SpecFinalizer func(cs ChainSpec) error // WrappedChainSpec allows a chain spec to be marshalled/unmarshalled to and // from raw JSON data. -type WrappedChainSpec struct { ChainSpec } +type WrappedChainSpec struct{ ChainSpec } // UnmarshalJSON implements json.Unmarshaler func (ws *WrappedChainSpec) UnmarshalJSON(b []byte) error { - var tempV struct{ + var tempV struct { Era string `json:"spec_era"` } if err := json.Unmarshal(b, &tempV); err != nil { @@ -61,19 +61,20 @@ func (ws *WrappedChainSpec) UnmarshalJSON(b []byte) error { return err } -// func (ws *WrappedChainSpec) MarshalJSON() ([]byte, error) { -// if ws.ChainSpec == nil { -// return []byte("null"), nil -// } -// -// return json.Marshal(ws.ChainSpec) -// } +// MarshalJSON implements json.Marshaler +func (ws WrappedChainSpec) MarshalJSON() ([]byte, error) { + if ws.ChainSpec == nil { + return []byte("null"), nil + } + + return json.Marshal(ws.ChainSpec) +} // SignedChainSpec contains a chain spec alongside a valid signature. type SignedChainSpec struct { Spec WrappedChainSpec `json:"spec"` - GenesisHash string `json:"genesis_hash,omitempty"` - Sig string `json:"sig"` // hex representation of signature + GenesisHash string `json:"genesis_hash,omitempty"` + Sig string `json:"sig"` // hex representation of signature } // MakeSignedChainSpec generates a signed spec from a ChainSpec and secret key. @@ -109,7 +110,7 @@ func MakeSignedChainSpec(spec ChainSpec, sk cipher.SecKey) (SignedChainSpec, err } signedSpec := SignedChainSpec{ - Spec: WrappedChainSpec{ ChainSpec: spec }, + Spec: WrappedChainSpec{ChainSpec: spec}, GenesisHash: genesis.HashHeader().Hex(), Sig: sig.Hex(), } diff --git a/src/cx/cxspec/keyspec.go b/src/cx/cxspec/keyspec.go index 7ec0c98a2..9ef893048 100644 --- a/src/cx/cxspec/keyspec.go +++ b/src/cx/cxspec/keyspec.go @@ -2,6 +2,7 @@ package cxspec import ( "fmt" + "github.com/skycoin/cx-chains/src/cx/cxspec/alpha" "github.com/skycoin/skycoin/src/cipher"