diff --git a/cmd/node/main.go b/cmd/node/main.go index 27048d245..0c31f3188 100644 --- a/cmd/node/main.go +++ b/cmd/node/main.go @@ -172,6 +172,14 @@ func loadConfig() *config.Config { "external address:port to announce to other peers (automatically guessed if empty)") flag.String("vochainGenesis", "", "use alternative genesis file for the vochain") + flag.String("vochainGenesisChainID", "", + "override ChainID in genesis for the vochain") + flag.Int64("vochainGenesisInitialHeight", 0, + "override InitialHeight in genesis for the vochain") + flag.String("vochainGenesisAppHash", "", + "override AppHash in genesis for the vochain") + flag.Int64("vochainEndOfChain", 0, + "height at which this node will refuse adding new blocks to the chain") flag.String("vochainLogLevel", "disabled", "tendermint node log level (debug, info, error, disabled)") flag.StringSlice("vochainPeers", []string{}, diff --git a/cmd/tools/vochaininspector/inspector.go b/cmd/tools/vochaininspector/inspector.go index 5215797be..7df0aec2b 100644 --- a/cmd/tools/vochaininspector/inspector.go +++ b/cmd/tools/vochaininspector/inspector.go @@ -20,7 +20,6 @@ import ( "go.vocdoni.io/dvote/statedb" "go.vocdoni.io/dvote/util" "go.vocdoni.io/dvote/vochain" - "go.vocdoni.io/dvote/vochain/genesis" "go.vocdoni.io/dvote/vochain/state" "go.vocdoni.io/dvote/vochain/vochaininfo" "go.vocdoni.io/proto/build/go/models" @@ -62,7 +61,7 @@ func main() { if blockHeight == 0 { log.Fatal("listProcess requires a height value") } - path := filepath.Join(dataDir, "data", "vcstate") + path := filepath.Join(dataDir, vochain.StateDataDir) log.Infof("opening state database path %s", path) listStateProcesses(int64(blockHeight), path) @@ -70,7 +69,7 @@ func main() { if blockHeight == 0 { log.Fatal("listVotes requires a height value") } - path := filepath.Join(dataDir, "data", "vcstate") + path := filepath.Join(dataDir, vochain.StateDataDir) log.Infof("opening state database path %s", path) listStateVotes(pid, int64(blockHeight), path) @@ -84,7 +83,7 @@ func main() { if blockHeight == 0 { log.Fatal("stateGraph requires a height value") } - path := filepath.Join(dataDir, "data", "vcstate") + path := filepath.Join(dataDir, vochain.StateDataDir) graphVizMainTree(int64(blockHeight), path) case "sync": @@ -191,8 +190,7 @@ func newVochain(network, dataDir string) *vochain.BaseApplication { } log.Infof("external ip address %s", cfg.PublicAddr) // Create the vochain node - genesisBytes := genesis.Genesis[network].Genesis.Marshal() - return vochain.NewVochain(cfg, genesisBytes) + return vochain.NewVochain(cfg) } func listBlockVotes(_ int64, _ string) { diff --git a/config/config.go b/config/config.go index 1bec18521..ad4d40dc0 100644 --- a/config/config.go +++ b/config/config.go @@ -92,6 +92,12 @@ type VochainCfg struct { DBType string // Genesis path where the genesis file is stored Genesis string + // GenesisChainID overrides ChainID in hardcoded genesis + GenesisChainID string + // GenesisInitialHeight overrides InitialHeight in hardcoded genesis + GenesisInitialHeight int64 + // GenesisAppHash overrides AppHash in hardcoded genesis + GenesisAppHash string // Peers peers with which the node tries to connect Peers []string // Seeds seeds with which the node tries to connect @@ -105,6 +111,8 @@ type VochainCfg struct { PrivValidatorListenAddr string // NoWaitSync if enabled the Vochain synchronization won't be blocking NoWaitSync bool + // EndOfChain is the height at which this node will refuse adding new blocks to the chain + EndOfChain int64 // MempoolSize is the size of the mempool MempoolSize int // SkipPreviousOffchainData if enabled, the node will skip downloading the previous off-chain data to the current block diff --git a/config/defaults.go b/config/defaults.go index 511829456..bdb052494 100644 --- a/config/defaults.go +++ b/config/defaults.go @@ -3,4 +3,29 @@ package config // These consts are defaults used in VochainCfg const ( DefaultMinerTargetBlockTimeSeconds = 10 + DefaultCometBFTPath = "cometbft" + DefaultGenesisPath = DefaultCometBFTPath + "/config/genesis.json" ) + +// DefaultSeedNodes is a map indexed by network name +var DefaultSeedNodes = map[string][]string{ + // testsuite test network + "test": { + "3c3765494e758ae7baccb1f5b0661755302ddc47@seed:26656", + }, + // Development network + "dev": { + "7440a5b086e16620ce7b13198479016aa2b07988@seed.dev.vocdoni.net:26656", + }, + + // Staging network + "stage": { + "588133b8309363a2a852e853424251cd6e8c5330@seed.stg.vocdoni.net:26656", + }, + + // LTS production network + "lts": { + "32acbdcda649fbcd35775f1dd8653206d940eee4@seed1.lts.vocdoni.net:26656", + "02bfac9bd98bf25429d12edc50552cca5e975080@seed2.lts.vocdoni.net:26656", + }, +} diff --git a/config/forks.go b/config/forks.go index 3e411771b..f7586dfad 100644 --- a/config/forks.go +++ b/config/forks.go @@ -1,30 +1,50 @@ package config +import ( + "sync" +) + // ForksCfg allows applying softforks at specified heights type ForksCfg struct { VoceremonyForkBlock uint32 NullifierFromZkProof uint32 + EndOfChain uint32 } -// Forks is a map of chainIDs -var Forks = map[string]*ForksCfg{ - "vocdoni/DEV/29": { - VoceremonyForkBlock: 217200, // estimated 2023-12-05T11:33:31.426638381Z - }, - "vocdoni/STAGE/9": { - VoceremonyForkBlock: 250000, // estimated 2023-12-11T12:09:00.917676214Z - NullifierFromZkProof: 439000, // estimated 2024-01-03T12:09:30.009477164Z - }, - "vocdoni/LTS/1.2": { - VoceremonyForkBlock: 400200, // estimated 2023-12-12T09:09:31.511245938Z - NullifierFromZkProof: 575800, // estimated 2024-01-03T12:09:30.009477164Z - }, -} +var ( + mu sync.RWMutex + // forks is a map of chainIDs to their respective ForksCfg + forks = map[string]*ForksCfg{ + "vocdoni/TEST/1.2": { + EndOfChain: 100, + }, + "vocdoni/DEV/29": { + VoceremonyForkBlock: 217200, // estimated 2023-12-05T11:33:31.426638381Z + }, + "vocdoni/STAGE/9": { + VoceremonyForkBlock: 250000, // estimated 2023-12-11T12:09:00.917676214Z + NullifierFromZkProof: 439000, // estimated 2024-01-03T12:09:30.009477164Z + }, + "vocdoni/LTS/1.2": { + VoceremonyForkBlock: 400200, // estimated 2023-12-12T09:09:31.511245938Z + NullifierFromZkProof: 575800, // estimated 2024-01-03T12:09:30.009477164Z + }, + } +) // ForksForChainID returns the ForksCfg of chainID, if found, or an empty ForksCfg otherwise func ForksForChainID(chainID string) *ForksCfg { - if cfg, found := Forks[chainID]; found { + mu.RLock() + defer mu.RUnlock() + if cfg, found := forks[chainID]; found { return cfg } return &ForksCfg{} } + +// SetForksForChainID sets the ForksCfg of chainID +func SetForksForChainID(chainID string, forksCfg *ForksCfg) { + mu.Lock() + defer mu.Unlock() + forks[chainID] = forksCfg +} diff --git a/dockerfiles/testsuite/README.md b/dockerfiles/testsuite/README.md index 30ef9fa33..53d221ade 100644 --- a/dockerfiles/testsuite/README.md +++ b/dockerfiles/testsuite/README.md @@ -21,8 +21,6 @@ the testnet is composed of: * four [miners](https://docs.vocdoni.io/architecture/services/vochain.html#miner) (aka [validator nodes](https://docs.tendermint.com/master/nodes/#validators) in tendermint jargon) * one [gateway](https://docs.vocdoni.io/architecture/components.html#gateway) -the `genesis.json` file lists the public keys of all the miners, since vochain is a Proof-of-Authority. - the seed node will serve to bootstrap the network: it'll just wait for incoming connections from other nodes, and provide them a list of peers which they can connect to. the miners will first connect to the seed node, get the list of peers, and connect to each other. when there are at least 3 miners online, they can reach consensus and start producing blocks. diff --git a/dockerfiles/testsuite/docker-compose.yml b/dockerfiles/testsuite/docker-compose.yml index d92a9a0a9..a32a1f22a 100644 --- a/dockerfiles/testsuite/docker-compose.yml +++ b/dockerfiles/testsuite/docker-compose.yml @@ -10,8 +10,7 @@ services: - blockchain volumes: - data-seed:/app/run/ - - ${COMPOSE_HOST_PATH:-.}/genesis.json:/app/misc/genesis.json - - /tmp/.vochain-zkCircuits/:/app/run/dev/zkCircuits/ + - /tmp/.vochain-zkCircuits/:/app/run/test/zkCircuits/ - gocoverage-seed:/app/run/gocoverage environment: - GOCOVERDIR=/app/run/gocoverage @@ -24,8 +23,7 @@ services: - blockchain volumes: - data-miner0:/app/run/ - - ${COMPOSE_HOST_PATH:-.}/genesis.json:/app/misc/genesis.json - - /tmp/.vochain-zkCircuits/:/app/run/dev/zkCircuits/ + - /tmp/.vochain-zkCircuits/:/app/run/test/zkCircuits/ - gocoverage-miner0:/app/run/gocoverage environment: - GOCOVERDIR=/app/run/gocoverage @@ -38,8 +36,7 @@ services: - blockchain volumes: - data-miner1:/app/run/ - - ${COMPOSE_HOST_PATH:-.}/genesis.json:/app/misc/genesis.json - - /tmp/.vochain-zkCircuits/:/app/run/dev/zkCircuits/ + - /tmp/.vochain-zkCircuits/:/app/run/test/zkCircuits/ - gocoverage-miner1:/app/run/gocoverage environment: - GOCOVERDIR=/app/run/gocoverage @@ -52,8 +49,7 @@ services: - blockchain volumes: - data-miner2:/app/run/ - - ${COMPOSE_HOST_PATH:-.}/genesis.json:/app/misc/genesis.json - - /tmp/.vochain-zkCircuits/:/app/run/dev/zkCircuits/ + - /tmp/.vochain-zkCircuits/:/app/run/test/zkCircuits/ - gocoverage-miner2:/app/run/gocoverage environment: - GOCOVERDIR=/app/run/gocoverage @@ -66,8 +62,7 @@ services: - blockchain volumes: - data-miner3:/app/run/ - - ${COMPOSE_HOST_PATH:-.}/genesis.json:/app/misc/genesis.json - - /tmp/.vochain-zkCircuits/:/app/run/dev/zkCircuits/ + - /tmp/.vochain-zkCircuits/:/app/run/test/zkCircuits/ - gocoverage-miner3:/app/run/gocoverage environment: - GOCOVERDIR=/app/run/gocoverage @@ -82,8 +77,7 @@ services: - blockchain volumes: - data-gateway0:/app/run/ - - ${COMPOSE_HOST_PATH:-.}/genesis.json:/app/misc/genesis.json - - /tmp/.vochain-zkCircuits/:/app/run/dev/zkCircuits/ + - /tmp/.vochain-zkCircuits/:/app/run/test/zkCircuits/ - gocoverage-gateway0:/app/run/gocoverage environment: - GOCOVERDIR=/app/run/gocoverage @@ -163,8 +157,7 @@ services: - blockchain volumes: - data-gatewaySync:/app/run/ - - ${COMPOSE_HOST_PATH:-.}/genesis.json:/app/misc/genesis.json - - /tmp/.vochain-zkCircuits/:/app/run/dev/zkCircuits/ + - /tmp/.vochain-zkCircuits/:/app/run/test/zkCircuits/ - gocoverage-gatewaySync:/app/run/gocoverage environment: - GOCOVERDIR=/app/run/gocoverage diff --git a/dockerfiles/testsuite/env.gateway0 b/dockerfiles/testsuite/env.gateway0 index 2b8432bf1..99e1555c8 100755 --- a/dockerfiles/testsuite/env.gateway0 +++ b/dockerfiles/testsuite/env.gateway0 @@ -1,5 +1,6 @@ VOCDONI_DATADIR=/app/run VOCDONI_MODE=gateway +VOCDONI_CHAIN=test VOCDONI_LOGLEVEL=debug VOCDONI_VOCHAIN_LOGLEVEL=error VOCDONI_DEV=True @@ -8,11 +9,9 @@ VOCDONI_ENABLERPC=True VOCDONI_ENABLEFAUCETWITHAMOUNT=100000 VOCDONI_VOCHAIN_PUBLICADDR=gateway0:26656 VOCDONI_VOCHAIN_SEEDS=3c3765494e758ae7baccb1f5b0661755302ddc47@seed:26656 -VOCDONI_VOCHAIN_GENESIS=/app/misc/genesis.json VOCDONI_VOCHAIN_NOWAITSYNC=True VOCDONI_METRICS_ENABLED=True VOCDONI_METRICS_REFRESHINTERVAL=5 -VOCDONI_CHAIN=dev VOCDONI_SIGNINGKEY=e0f1412b86d6ca9f2b318f1d243ef50be23d315a2e6c1c3035bc72d44c8b2f90 # 0x88a499cEf9D1330111b41360173967c9C1bf703f VOCDONI_ARCHIVEURL=none VOCDONI_VOCHAIN_STATESYNCENABLED=false diff --git a/dockerfiles/testsuite/env.gatewaySync b/dockerfiles/testsuite/env.gatewaySync index 49e01d5e6..6fc74085a 100644 --- a/dockerfiles/testsuite/env.gatewaySync +++ b/dockerfiles/testsuite/env.gatewaySync @@ -1,5 +1,6 @@ VOCDONI_DATADIR=/app/run VOCDONI_MODE=gateway +VOCDONI_CHAIN=test VOCDONI_LOGLEVEL=debug VOCDONI_VOCHAIN_LOGLEVEL=info VOCDONI_DEV=True @@ -8,11 +9,9 @@ VOCDONI_ENABLERPC=True VOCDONI_ENABLEFAUCETWITHAMOUNT=100000 VOCDONI_VOCHAIN_PUBLICADDR=gatewaySync:26656 VOCDONI_VOCHAIN_SEEDS=3c3765494e758ae7baccb1f5b0661755302ddc47@seed:26656 -VOCDONI_VOCHAIN_GENESIS=/app/misc/genesis.json VOCDONI_VOCHAIN_NOWAITSYNC=True VOCDONI_METRICS_ENABLED=True VOCDONI_METRICS_REFRESHINTERVAL=5 -VOCDONI_CHAIN=dev VOCDONI_SIGNINGKEY=f50be23d315a2e6c1c30e0f1412b86d6ca9f2b318f1d243e35bc72d44c8b2f90 VOCDONI_ARCHIVEURL=none VOCDONI_VOCHAIN_SNAPSHOTINTERVAL=3 diff --git a/dockerfiles/testsuite/env.miner0 b/dockerfiles/testsuite/env.miner0 index 5e3d449fe..a720a0263 100755 --- a/dockerfiles/testsuite/env.miner0 +++ b/dockerfiles/testsuite/env.miner0 @@ -1,11 +1,11 @@ VOCDONI_DATADIR=/app/run VOCDONI_MODE=miner +VOCDONI_CHAIN=test VOCDONI_LOGLEVEL=debug VOCDONI_VOCHAIN_LOGLEVEL=error VOCDONI_DEV=True VOCDONI_VOCHAIN_PUBLICADDR=miner0:26656 VOCDONI_VOCHAIN_SEEDS=3c3765494e758ae7baccb1f5b0661755302ddc47@seed:26656 -VOCDONI_VOCHAIN_GENESIS=/app/misc/genesis.json VOCDONI_VOCHAIN_MINERKEY=cda909c34901c137e12bb7d0afbcb9d1c8abc66f03862a42344b1f509d1ae4ce VOCDONI_METRICS_ENABLED=True VOCDONI_METRICS_REFRESHINTERVAL=5 diff --git a/dockerfiles/testsuite/env.miner1 b/dockerfiles/testsuite/env.miner1 index 3904bea35..2c25d2770 100755 --- a/dockerfiles/testsuite/env.miner1 +++ b/dockerfiles/testsuite/env.miner1 @@ -1,10 +1,10 @@ VOCDONI_DATADIR=/app/run VOCDONI_MODE=miner +VOCDONI_CHAIN=test VOCDONI_LOGLEVEL=info VOCDONI_DEV=True VOCDONI_VOCHAIN_PUBLICADDR=miner1:26656 VOCDONI_VOCHAIN_SEEDS=3c3765494e758ae7baccb1f5b0661755302ddc47@seed:26656 -VOCDONI_VOCHAIN_GENESIS=/app/misc/genesis.json VOCDONI_VOCHAIN_MINERKEY=d52a488fa1511a07778cc94ed9d8130fb255537783ea7c669f38292b4f53ac4f VOCDONI_METRICS_ENABLED=True VOCDONI_METRICS_REFRESHINTERVAL=5 diff --git a/dockerfiles/testsuite/env.miner2 b/dockerfiles/testsuite/env.miner2 index 8dc742042..3aa25579b 100755 --- a/dockerfiles/testsuite/env.miner2 +++ b/dockerfiles/testsuite/env.miner2 @@ -1,10 +1,10 @@ VOCDONI_DATADIR=/app/run VOCDONI_MODE=miner +VOCDONI_CHAIN=test VOCDONI_LOGLEVEL=info VOCDONI_DEV=True VOCDONI_VOCHAIN_PUBLICADDR=miner2:26656 VOCDONI_VOCHAIN_SEEDS=3c3765494e758ae7baccb1f5b0661755302ddc47@seed:26656 -VOCDONI_VOCHAIN_GENESIS=/app/misc/genesis.json VOCDONI_VOCHAIN_MINERKEY=e06976e5eaf3f147e12763eb140e3d5c2ed16a6fa747d787d8b92ca961fa7dc4 VOCDONI_METRICS_ENABLED=True VOCDONI_METRICS_REFRESHINTERVAL=5 diff --git a/dockerfiles/testsuite/env.miner3 b/dockerfiles/testsuite/env.miner3 index 44ff46f30..8fdaade9b 100755 --- a/dockerfiles/testsuite/env.miner3 +++ b/dockerfiles/testsuite/env.miner3 @@ -1,10 +1,10 @@ VOCDONI_DATADIR=/app/run VOCDONI_MODE=miner +VOCDONI_CHAIN=test VOCDONI_LOGLEVEL=info VOCDONI_DEV=True VOCDONI_VOCHAIN_PUBLICADDR=miner3:26656 VOCDONI_VOCHAIN_SEEDS=3c3765494e758ae7baccb1f5b0661755302ddc47@seed:26656 -VOCDONI_VOCHAIN_GENESIS=/app/misc/genesis.json VOCDONI_VOCHAIN_MINERKEY=b8d258559adee836a43e964badf541ec106cc68a01f989d3c3c9a030ae3945a6 VOCDONI_METRICS_ENABLED=True VOCDONI_METRICS_REFRESHINTERVAL=5 diff --git a/dockerfiles/testsuite/env.seed b/dockerfiles/testsuite/env.seed index 7482b0e65..d17736e19 100755 --- a/dockerfiles/testsuite/env.seed +++ b/dockerfiles/testsuite/env.seed @@ -1,10 +1,10 @@ VOCDONI_DATADIR=/app/run VOCDONI_MODE=seed +VOCDONI_CHAIN=test VOCDONI_LOGLEVEL=debug VOCDONI_DEV=True VOCDONI_VOCHAIN_PUBLICADDR=seed:26656 VOCDONI_VOCHAIN_LOGLEVEL=info -VOCDONI_VOCHAIN_GENESIS=/app/misc/genesis.json VOCDONI_VOCHAIN_NODEKEY=0x2060e20d1f0894d6b23901bce3f20f26107baf0335451ad75ef27b14e4fc56ae050a65ae3883c379b70d811d6e12db2fe1e3a5cf0cae4d03dbbbfebc68601bdd VOCDONI_METRICS_ENABLED=True VOCDONI_METRICS_REFRESHINTERVAL=5 diff --git a/dockerfiles/testsuite/genesis.json b/dockerfiles/testsuite/genesis.json deleted file mode 100755 index bc7ab735a..000000000 --- a/dockerfiles/testsuite/genesis.json +++ /dev/null @@ -1,80 +0,0 @@ -{ - "genesis_time": "2023-02-28T22:40:43.668920539Z", - "chain_id": "test-chain-1", - "initial_height": "0", - "consensus_params": { - "block": { - "max_bytes": "2097152", - "max_gas": "-1" - }, - "evidence": { - "max_age_num_blocks": "100000", - "max_age_duration": "10000", - "max_bytes": "1048576" - }, - "validator": { - "pub_key_types": [ - "secp256k1" - ] - }, - "version": { - "app_version": "0" - } - }, - "app_hash": "", - "app_state": { - "max_election_size": 1000000, - "network_capacity": 10000, - "validators": [ - { - "signer_address": "6bcc92be71d48cfe3f2f5042f157ad978f3e8117", - "consensus_pub_key": "038faa051e8a726597549bb057f1d296947bb54378443ec8fce030001ece678e14", - "power": 10, - "key_index": 1, - "name": "validator1" - }, - { - "signer_address": "032faef5d0f2c76bbd804215e822a5203e83385d", - "consensus_pub_key": "03cf8d0d1afa561e01145a275d1e41ed1a6d652361509a4c93dfc6488fdf5eca38", - "power": 10, - "key_index": 2, - "name": "validator2" - }, - { - "signer_address": "1f00b2ee957af530d44c8ceb1fecdd07c5702ad7", - "consensus_pub_key": "031802916d945239a39a9a8ee3e2eb3fb91ee324ccdfd73659f482e644892b796f", - "power": 10, - "key_index": 3, - "name": "validator3" - }, - { - "signer_address": "8992487e178fdcdc0dad95174e2a6d6229b3719c", - "consensus_pub_key": "02a790726e98978b0ca2cde3a09cbb1af1b298191f46e051b86bcb1854deb58478", - "power": 10, - "key_index": 4, - "name": "validator4" - } - ], - "accounts":[ - { - "address":"0x88a499cEf9D1330111b41360173967c9C1bf703f", - "balance":1000000000000 - } - ], - "tx_cost": { - "Tx_SetProcessStatus": 1, - "Tx_SetProcessCensus": 1, - "Tx_SetProcessResults": 1, - "Tx_SetProcessQuestionIndex": 1, - "Tx_RegisterKey": 1, - "Tx_NewProcess": 10, - "Tx_SendTokens": 2, - "Tx_CreateAccount": 2, - "Tx_SetAccountInfoURI": 2, - "Tx_AddDelegateForAccount": 2, - "Tx_DelDelegateForAccount": 2, - "Tx_CollectFaucet": 1, - "Tx_SetAccountValidator": 100 - } - } -} diff --git a/service/vochain.go b/service/vochain.go index 33c2dd5cf..a231d947d 100644 --- a/service/vochain.go +++ b/service/vochain.go @@ -1,18 +1,18 @@ package service import ( - "bytes" "fmt" "net" "os" + "path/filepath" "strings" "time" - "go.vocdoni.io/dvote/crypto/ethereum" + "go.vocdoni.io/dvote/config" "go.vocdoni.io/dvote/log" "go.vocdoni.io/dvote/util" "go.vocdoni.io/dvote/vochain" - vocdoniGenesis "go.vocdoni.io/dvote/vochain/genesis" + "go.vocdoni.io/dvote/vochain/genesis" "go.vocdoni.io/dvote/vochain/vochaininfo" ) @@ -48,57 +48,54 @@ func (vs *VocdoniService) Vochain() error { "address", vs.Config.PublicAddr, "listenHost", vs.Config.P2PListen) - // Genesis file - var genesisBytes []byte - var err error - // If genesis file from flag, prioritize it + // If genesis flag defined, use file as-is, never overwrite it, and fail if it doesn't exist if len(vs.Config.Genesis) > 0 { log.Infof("using custom genesis from %s", vs.Config.Genesis) if _, err := os.Stat(vs.Config.Genesis); os.IsNotExist(err) { return err } - if genesisBytes, err = os.ReadFile(vs.Config.Genesis); err != nil { - return err - } - } else { // If genesis flag not defined, use a hardcoded or local genesis - genesisBytes, err = os.ReadFile(vs.Config.DataDir + "/config/genesis.json") + } else { // If genesis flag not defined, use local or hardcoded genesis + vs.Config.Genesis = filepath.Join(vs.Config.DataDir, config.DefaultGenesisPath) - if err == nil { // If genesis file found - log.Info("found genesis file") + if _, err := os.Stat(vs.Config.Genesis); err != nil { + if os.IsNotExist(err) { + log.Info("genesis file does not exist, will use hardcoded genesis") + } else { + return fmt.Errorf("unexpected error reading genesis: %w", err) + } + } else { + log.Infof("found local genesis file at %s", vs.Config.Genesis) + loadedGenesis, err := genesis.LoadFromFile(vs.Config.Genesis) + if err != nil { + return fmt.Errorf("couldn't parse local genesis file: %w", err) + } + hardcodedGenesis := genesis.HardcodedWithOverrides(vs.Config.Network, + vs.Config.GenesisChainID, + vs.Config.GenesisInitialHeight, + vs.Config.GenesisAppHash) // compare genesis - if !bytes.Equal(ethereum.HashRaw(genesisBytes), vocdoniGenesis.Genesis[vs.Config.Network].Genesis.Hash()) { - // if auto-update genesis enabled, delete local genesis and use hardcoded genesis - if vocdoniGenesis.Genesis[vs.Config.Network].AutoUpdateGenesis || vs.Config.Dev { - log.Warn("new genesis found, cleaning and restarting Vochain") - if err = os.RemoveAll(vs.Config.DataDir); err != nil { + if loadedGenesis.ChainID != hardcodedGenesis.ChainID { + log.Warnf("local genesis ChainID (%s) differs from hardcoded (%s)", loadedGenesis.ChainID, hardcodedGenesis.ChainID) + if hardcodedGenesis.InitialHeight > 1 { + log.Warnf("new hardcoded genesis creates a chain %q starting at block %d (i.e. on top of current %q), wiping out cometbft datadir", + hardcodedGenesis.ChainID, hardcodedGenesis.InitialHeight, loadedGenesis.ChainID) + if err = os.RemoveAll(filepath.Join(vs.Config.DataDir, config.DefaultCometBFTPath)); err != nil { return err } - if _, ok := vocdoniGenesis.Genesis[vs.Config.Network]; !ok { - err = fmt.Errorf("cannot find a valid genesis for the %q network", vs.Config.Network) + } else { // hardcodedGenesis.InitialHeight <= 0 + log.Warnf("new hardcoded genesis creates a chain %q from scratch, wiping out current chain %q datadir", + hardcodedGenesis.ChainID, loadedGenesis.ChainID) + if err = os.RemoveAll(vs.Config.DataDir); err != nil { return err } - genesisBytes = vocdoniGenesis.Genesis[vs.Config.Network].Genesis.Marshal() - } else { - log.Warn("local and hardcoded genesis are different, risk of potential consensus failure") } - } else { - log.Info("local and factory genesis match") - } - } else { // If genesis file not found - if !os.IsNotExist(err) { - return err - } - log.Info("genesis file does not exist, using factory") - if _, ok := vocdoniGenesis.Genesis[vs.Config.Network]; !ok { - err = fmt.Errorf("cannot find a valid genesis for the %s network", vs.Config.Network) - return err + } - genesisBytes = vocdoniGenesis.Genesis[vs.Config.Network].Genesis.Marshal() } } // Create the vochain node - vs.App = vochain.NewVochain(vs.Config, genesisBytes) + vs.App = vochain.NewVochain(vs.Config) return nil } diff --git a/vochain/app.go b/vochain/app.go index 66b95d55b..1e412179b 100644 --- a/vochain/app.go +++ b/vochain/app.go @@ -20,6 +20,7 @@ import ( "go.vocdoni.io/dvote/snapshot" "go.vocdoni.io/dvote/test/testcommon/testutil" "go.vocdoni.io/dvote/types" + "go.vocdoni.io/dvote/vochain/genesis" "go.vocdoni.io/dvote/vochain/ist" vstate "go.vocdoni.io/dvote/vochain/state" "go.vocdoni.io/dvote/vochain/transaction" @@ -91,7 +92,7 @@ type BaseApplication struct { chainID string dataDir string dbType string - genesisInfo *comettypes.GenesisDoc + genesisDoc *genesis.Doc // lastDeliverTxResponse is used to store the last DeliverTxResponse, so validators // can skip block re-execution on FinalizeBlock call. @@ -169,7 +170,7 @@ func NewBaseApplication(vochainCfg *config.VochainCfg) (*BaseApplication, error) dataDir: vochainCfg.DataDir, dbType: vochainCfg.DBType, snapshotInterval: vochainCfg.SnapshotInterval, - genesisInfo: &comettypes.GenesisDoc{}, + genesisDoc: &genesis.Doc{}, }, nil } @@ -387,9 +388,9 @@ func (app *BaseApplication) MempoolDeleteTx(txID [32]byte) { } } -// Genesis returns the tendermint genesis information -func (app *BaseApplication) Genesis() *comettypes.GenesisDoc { - return app.genesisInfo +// Genesis returns the genesis used by the app (and cometbft) +func (app *BaseApplication) Genesis() *genesis.Doc { + return app.genesisDoc } // SetZkCircuit ensures the global ZkCircuit is the correct for a chain that implements forks diff --git a/vochain/appsetup.go b/vochain/appsetup.go index d29e206bf..0430e8b4a 100644 --- a/vochain/appsetup.go +++ b/vochain/appsetup.go @@ -20,9 +20,9 @@ const ( ) // SetNode initializes the cometbft consensus node service and client. -func (app *BaseApplication) SetNode(vochaincfg *config.VochainCfg, genesis []byte) error { +func (app *BaseApplication) SetNode(vochaincfg *config.VochainCfg) error { var err error - if app.Node, err = newTendermint(app, vochaincfg, genesis); err != nil { + if app.Node, err = newTendermint(app, vochaincfg); err != nil { return fmt.Errorf("could not set tendermint node service: %s", err) } if vochaincfg.IsSeedNode { @@ -30,11 +30,6 @@ func (app *BaseApplication) SetNode(vochaincfg *config.VochainCfg, genesis []byt } // Note that cometcli.New logs any error rather than returning it. app.NodeClient = cometcli.New(app.Node) - nodeGenesis, err := app.NodeClient.Genesis(context.TODO()) - if err != nil { - return err - } - app.genesisInfo = nodeGenesis.Genesis return nil } diff --git a/vochain/apputils.go b/vochain/apputils.go index ba7cdfeee..2e6f13e12 100644 --- a/vochain/apputils.go +++ b/vochain/apputils.go @@ -21,7 +21,6 @@ import ( cometp2p "github.com/cometbft/cometbft/p2p" cometprivval "github.com/cometbft/cometbft/privval" cometrpchttp "github.com/cometbft/cometbft/rpc/client/http" - comettypes "github.com/cometbft/cometbft/types" ethcommon "github.com/ethereum/go-ethereum/common" ) @@ -91,49 +90,11 @@ func GenerateFaucetPackage(from *ethereum.SignKeys, to ethcommon.Address, amount // NewTemplateGenesisFile creates a genesis file with the given number of validators and its private keys. // Also includes faucet account. // The genesis document is returned. -func NewTemplateGenesisFile(dir string, validators int) (*comettypes.GenesisDoc, error) { - gd := comettypes.GenesisDoc{} +func NewTemplateGenesisFile(dir string, validators int) (*genesis.Doc, error) { + gd := genesis.HardcodedForNetwork("test") gd.ChainID = "test-chain-1" gd.GenesisTime = time.Now() gd.InitialHeight = 0 - gd.ConsensusParams = comettypes.DefaultConsensusParams() - gd.ConsensusParams.Block.MaxBytes = 5242880 - gd.ConsensusParams.Block.MaxGas = -1 - gd.ConsensusParams.Evidence.MaxAgeNumBlocks = 100000 - gd.ConsensusParams.Evidence.MaxAgeDuration = 10000 - gd.ConsensusParams.Validator.PubKeyTypes = []string{"secp256k1"} - - // Create validators - appStateValidators := []genesis.AppStateValidators{} - for i := 0; i < validators; i++ { - nodeDir := filepath.Join(dir, fmt.Sprintf("node%d", i)) - if err := os.MkdirAll(nodeDir, 0o700); err != nil { - return nil, err - } - pk := crypto256k1.GenPrivKey() - privKeyHex := hex.EncodeToString(pk.Bytes()) - pv, err := NewPrivateValidator(privKeyHex, - filepath.Join(nodeDir, "priv_validator_key.json"), - filepath.Join(nodeDir, "priv_validator_state.json"), - ) - if err != nil { - return nil, fmt.Errorf("cannot create validator key and state: (%v)", err) - } - pv.Save() - if err := os.WriteFile(filepath.Join(nodeDir, "hex_priv_key"), []byte(privKeyHex), 0o600); err != nil { - return nil, err - } - signer := ethereum.SignKeys{} - if err := signer.AddHexKey(hex.EncodeToString(pv.Key.PrivKey.Bytes())); err != nil { - return nil, err - } - appStateValidators = append(appStateValidators, genesis.AppStateValidators{ - Address: signer.Address().Bytes(), - PubKey: pv.Key.PubKey.Bytes(), - Power: 10, - KeyIndex: uint8(i + 1), // zero is reserved for disabling validator key keeper capabilities - }) - } // Faucet faucet := ethereum.SignKeys{} @@ -168,7 +129,6 @@ func NewTemplateGenesisFile(dir string, validators int) (*comettypes.GenesisDoc, // Build genesis app state and create genesis file appState := genesis.AppState{ - Validators: appStateValidators, Accounts: []genesis.Account{ { Address: faucet.Address().Bytes(), @@ -178,12 +138,45 @@ func NewTemplateGenesisFile(dir string, validators int) (*comettypes.GenesisDoc, TxCost: genesis.TransactionCosts{}, } appState.MaxElectionSize = 100000 + + // Create validators + for i := 0; i < validators; i++ { + nodeDir := filepath.Join(dir, fmt.Sprintf("node%d", i)) + if err := os.MkdirAll(nodeDir, 0o700); err != nil { + return nil, err + } + pk := crypto256k1.GenPrivKey() + privKeyHex := hex.EncodeToString(pk.Bytes()) + pv, err := NewPrivateValidator(privKeyHex, + filepath.Join(nodeDir, "priv_validator_key.json"), + filepath.Join(nodeDir, "priv_validator_state.json"), + ) + if err != nil { + return nil, fmt.Errorf("cannot create validator key and state: (%v)", err) + } + pv.Save() + if err := os.WriteFile(filepath.Join(nodeDir, "hex_priv_key"), []byte(privKeyHex), 0o600); err != nil { + return nil, err + } + signer := ethereum.SignKeys{} + if err := signer.AddHexKey(hex.EncodeToString(pv.Key.PrivKey.Bytes())); err != nil { + return nil, err + } + appState.Validators = append(appState.Validators, genesis.AppStateValidators{ + Address: signer.Address().Bytes(), + PubKey: pv.Key.PubKey.Bytes(), + Power: 10, + Name: fmt.Sprintf("validator%d", i), + KeyIndex: uint8(i + 1), // zero is reserved for disabling validator key keeper capabilities + }) + } + appStateBytes, err := json.Marshal(appState) if err != nil { return nil, err } gd.AppState = appStateBytes - return &gd, gd.SaveAs(filepath.Join(dir, "genesis.json")) + return gd, gd.SaveAs(filepath.Join(dir, "genesis.json")) } // newCometRPCClient sets up a new cometbft RPC client diff --git a/vochain/cometbft.go b/vochain/cometbft.go index 9ced5e90b..8c15d6c5d 100644 --- a/vochain/cometbft.go +++ b/vochain/cometbft.go @@ -20,6 +20,7 @@ import ( crypto256k1 "github.com/cometbft/cometbft/crypto/secp256k1" comettypes "github.com/cometbft/cometbft/types" ethcommon "github.com/ethereum/go-ethereum/common" + "go.vocdoni.io/dvote/config" "go.vocdoni.io/dvote/crypto/ethereum" "go.vocdoni.io/dvote/log" "go.vocdoni.io/dvote/snapshot" @@ -36,22 +37,36 @@ import ( // The returned AppVersion will be included in the Header of every block. // Tendermint expects LastBlockAppHash and LastBlockHeight to be updated during Commit, // ensuring that Commit is never called twice for the same block height. -// -// We use this method to initialize some state variables. func (app *BaseApplication) Info(_ context.Context, req *cometabcitypes.InfoRequest) (*cometabcitypes.InfoResponse, error) { + // TODO: move SetElectionPriceCalc() somewhere else, also deduplicating from InitChain + if err := app.State.SetElectionPriceCalc(); err != nil { + return nil, fmt.Errorf("cannot set election price calc: %w", err) + } + + log.Infow("cometbft info", "cometVersion", req.Version, "p2pVersion", + req.P2PVersion, "blockVersion", req.BlockVersion) + + appHash := app.State.CommittedHash() lastHeight, err := app.State.LastHeight() if err != nil { return nil, fmt.Errorf("cannot get State.LastHeight: %w", err) } - app.State.SetHeight(lastHeight) - appHash := app.State.CommittedHash() - if err := app.State.SetElectionPriceCalc(); err != nil { - return nil, fmt.Errorf("cannot set election price calc: %w", err) + if lastHeight > 0 && lastHeight < uint32(app.Genesis().InitialHeight) { + // when the genesis has InitialHeight > 1, means we're doing a chain bump + // and we need cometbft to trigger InitChain, which only happens when the app reports lastHeight=0 + log.Warnf("genesis initial height (%d) > our state lastHeight (%d), creating new chain on top of current one", + app.Genesis().InitialHeight, lastHeight) + + if app.Genesis().InitialHeight != int64(lastHeight+1) { + log.Warn("genesis initial height is not lastHeight+1") + } + if !bytes.Equal(appHash, app.Genesis().AppHash) { + log.Fatalf("our current appHash (%x) doesn't match the genesis initial AppHash (%x), aborting...", appHash, app.Genesis().AppHash) + } + lastHeight = 0 } // print some basic version info about tendermint components - log.Infow("cometbft info", "cometVersion", req.Version, "p2pVersion", - req.P2PVersion, "blockVersion", req.BlockVersion) log.Infow("telling cometbft our state", "LastBlockHeight", lastHeight, "LastBlockAppHash", hex.EncodeToString(appHash), @@ -68,13 +83,33 @@ func (app *BaseApplication) Info(_ context.Context, // Tendermint will use the validators loaded in the genesis file. func (app *BaseApplication) InitChain(_ context.Context, req *cometabcitypes.InitChainRequest) (*cometabcitypes.InitChainResponse, error) { + // if our State is already initialized, but cometbft is calling InitChain + // it means the ChainID was bumped and cometbft is starting a chain on top of previous one. + // Skip all the init, just pass the current validator set and AppHash to cometbft + lastHeight, err := app.State.LastHeight() + if err != nil { + return nil, fmt.Errorf("cannot get State.LastHeight: %w", err) + } + if lastHeight > 0 { + // pass current validators to cometbft + validators, err := app.State.Validators(false) + if err != nil { + return nil, fmt.Errorf("cannot get validators: %w", err) + } + + return &cometabcitypes.InitChainResponse{ + Validators: validatorUpdate(validators), + AppHash: app.State.CommittedHash(), + }, nil + } + // setting the app initial state with validators, height = 0 and empty apphash // unmarshal app state from genesis var genesisAppState genesis.AppState - err := json.Unmarshal(req.AppStateBytes, &genesisAppState) - if err != nil { + if err := json.Unmarshal(req.AppStateBytes, &genesisAppState); err != nil { return nil, fmt.Errorf("cannot unmarshal app state bytes: %w", err) } + // create accounts for _, acc := range genesisAppState.Accounts { addr := ethcommon.BytesToAddress(acc.Address) @@ -88,9 +123,8 @@ func (app *BaseApplication) InitChain(_ context.Context, } log.Infow("created account", "addr", addr.Hex(), "tokens", acc.Balance) } - // get validators - // TODO pau: unify this code with the one on apputils.go that essentially does the same - tendermintValidators := []cometabcitypes.ValidatorUpdate{} + + // add validators for i := 0; i < len(genesisAppState.Validators); i++ { log.Infow("add genesis validator", "signingAddress", genesisAppState.Validators[i].Address.String(), @@ -107,7 +141,7 @@ func (app *BaseApplication) InitChain(_ context.Context, Name: genesisAppState.Validators[i].Name, ValidatorAddress: crypto256k1.PubKey(genesisAppState.Validators[i].PubKey).Address().Bytes(), } - if err = app.State.AddValidator(v); err != nil { + if err := app.State.AddValidator(v); err != nil { return nil, fmt.Errorf("cannot add validator %s: %w", log.FormatProto(v), err) } addr := ethcommon.BytesToAddress(v.Address) @@ -116,12 +150,6 @@ func (app *BaseApplication) InitChain(_ context.Context, return nil, fmt.Errorf("cannot create validator acount %x: %w", addr, err) } } - tendermintValidators = append(tendermintValidators, - cometabcitypes.UpdateValidator( - genesisAppState.Validators[i].PubKey, - int64(genesisAppState.Validators[i].Power), - crypto256k1.KeyType, - )) } myValidator, err := app.State.Validator(app.NodeAddress, false) if err != nil { @@ -174,8 +202,15 @@ func (app *BaseApplication) InitChain(_ context.Context, if _, err = app.State.Save(); err != nil { return nil, fmt.Errorf("cannot save state: %w", err) } + + // pass current validators to cometbft + validators, err := app.State.Validators(false) + if err != nil { + return nil, fmt.Errorf("cannot get validators: %w", err) + } + return &cometabcitypes.InitChainResponse{ - Validators: tendermintValidators, + Validators: validatorUpdate(validators), AppHash: hash, }, nil } @@ -462,6 +497,16 @@ func (app *BaseApplication) ProcessProposal(_ context.Context, if req == nil { return nil, fmt.Errorf("nil request") } + if f := config.ForksForChainID(app.ChainID()); f.EndOfChain > 0 && + req.Height > int64(f.EndOfChain) { + log.Errorf("reached end of chain %s at height %d, rejecting new block for height %d", app.ChainID(), f.EndOfChain, req.Height) + log.Errorf("now you can deploy a new chain on top, with a different ChainID, InitialHeight %d and AppHash %x", + app.State.CurrentHeight()+1, app.State.CommittedHash()) + return &cometabcitypes.ProcessProposalResponse{ + Status: cometabcitypes.PROCESS_PROPOSAL_STATUS_REJECT, + }, nil + } + // Check if the node is a validator, if not, just accept the proposal and return (nothing to say) validator, err := app.State.Validator(app.NodeAddress, true) if err != nil { diff --git a/vochain/genesis/genesis.go b/vochain/genesis/genesis.go index a0e3f0aec..2b2aebe97 100644 --- a/vochain/genesis/genesis.go +++ b/vochain/genesis/genesis.go @@ -1,106 +1,140 @@ package genesis import ( + "encoding/json" "time" + comettypes "github.com/cometbft/cometbft/types" + + "go.vocdoni.io/dvote/crypto/ethereum" + "go.vocdoni.io/dvote/log" "go.vocdoni.io/dvote/types" ) -// Genesis is a map containing the default Genesis details -var Genesis = map[string]Vochain{ +// genesis is a map containing the default GenesisDoc for each network +var genesis = map[string]comettypes.GenesisDoc{ + // testsuite test network + "test": testGenesis, + // Development network - "dev": { - AutoUpdateGenesis: true, - SeedNodes: []string{ - "7440a5b086e16620ce7b13198479016aa2b07988@seed.dev.vocdoni.net:26656", - }, - StateSync: map[string]StateSyncParams{ - "vocdoni/DEV/32": { - TrustHeight: 10000, - TrustHash: types.HexStringToHexBytes("0x2b430478c7867dc078c0380b81838d75358db7c8b65bfaf84ade85448a0abd54"), - }, - }, - Genesis: &devGenesis, - }, + "dev": devGenesis, // Staging network - "stage": { - AutoUpdateGenesis: true, - SeedNodes: []string{ - "588133b8309363a2a852e853424251cd6e8c5330@seed.stg.vocdoni.net:26656", - }, - StateSync: map[string]StateSyncParams{ - "Vocdoni/STAGE/11": { - TrustHeight: 150000, - TrustHash: types.HexStringToHexBytes("0xd964cd5ec4704d3b3e1864c174edd1331044926bb2e6d3fe0b239b1c59329ff2"), - }, - }, - Genesis: &stageGenesis, - }, + "stage": stageGenesis, // LTS production network - "lts": { - AutoUpdateGenesis: false, - SeedNodes: []string{ - "32acbdcda649fbcd35775f1dd8653206d940eee4@seed1.lts.vocdoni.net:26656", - "02bfac9bd98bf25429d12edc50552cca5e975080@seed2.lts.vocdoni.net:26656", - }, - StateSync: map[string]StateSyncParams{ - "Vocdoni/LTS/1.2": { - TrustHeight: 1000000, - TrustHash: types.HexStringToHexBytes("0xd782c4a8e889a12fb326dd7f098336756f4238169a603501ae4a2b2f88c19db9"), - }, - }, - Genesis: <sGenesis, - }, + "lts": ltsGenesis, } -var devGenesis = Doc{ - GenesisTime: time.Date(2024, time.April, 4, 1, 0, 0, 0, time.UTC), - ChainID: "vocdoni/DEV/33", - ConsensusParams: &ConsensusParams{ - Block: BlockParams{ - MaxBytes: 2097152, - MaxGas: -1, +var testGenesis = comettypes.GenesisDoc{ + GenesisTime: time.Date(2024, time.May, 7, 1, 0, 0, 0, time.UTC), + ChainID: "vocdoni/TEST/1", + InitialHeight: 1, + ConsensusParams: &comettypes.ConsensusParams{ + Block: DefaultBlockParams(), + Evidence: comettypes.DefaultEvidenceParams(), + Validator: DefaultValidatorParams(), + Version: comettypes.VersionParams{ + App: 1, }, - Evidence: EvidenceParams{ - MaxAgeNumBlocks: 100000, - MaxAgeDuration: 10000, + }, + AppState: jsonRawMessage(AppState{ + MaxElectionSize: 1000000, + NetworkCapacity: 10000, + Validators: []AppStateValidators{ + { // 0 + Address: ethereumAddrFromPubKey("038faa051e8a726597549bb057f1d296947bb54378443ec8fce030001ece678e14"), + PubKey: types.HexStringToHexBytes("038faa051e8a726597549bb057f1d296947bb54378443ec8fce030001ece678e14"), + Power: 10, + Name: "validator1", + KeyIndex: 1, + }, + { // 1 + Address: ethereumAddrFromPubKey("03cf8d0d1afa561e01145a275d1e41ed1a6d652361509a4c93dfc6488fdf5eca38"), + PubKey: types.HexStringToHexBytes("03cf8d0d1afa561e01145a275d1e41ed1a6d652361509a4c93dfc6488fdf5eca38"), + Power: 10, + Name: "validator2", + KeyIndex: 2, + }, + { // 2 + Address: ethereumAddrFromPubKey("031802916d945239a39a9a8ee3e2eb3fb91ee324ccdfd73659f482e644892b796f"), + PubKey: types.HexStringToHexBytes("031802916d945239a39a9a8ee3e2eb3fb91ee324ccdfd73659f482e644892b796f"), + Power: 10, + Name: "validator3", + KeyIndex: 3, + }, + { // 3 + Address: ethereumAddrFromPubKey("02a790726e98978b0ca2cde3a09cbb1af1b298191f46e051b86bcb1854deb58478"), + PubKey: types.HexStringToHexBytes("02a790726e98978b0ca2cde3a09cbb1af1b298191f46e051b86bcb1854deb58478"), + Power: 10, + Name: "validator4", + KeyIndex: 4, + }, }, - Validator: ValidatorParams{ - PubKeyTypes: []string{"secp256k1"}, + Accounts: []Account{ + { // faucet + Address: types.HexStringToHexBytes("0x88a499cEf9D1330111b41360173967c9C1bf703f"), + Balance: 1000000000000, + }, }, - Version: VersionParams{ - AppVersion: 1, + TxCost: TransactionCosts{ + SetProcessStatus: 1, + SetProcessCensus: 1, + SetProcessQuestionIndex: 1, + RegisterKey: 1, + NewProcess: 10, + SendTokens: 2, + SetAccountInfoURI: 2, + CreateAccount: 2, + AddDelegateForAccount: 2, + DelDelegateForAccount: 2, + CollectFaucet: 1, + SetAccountSIK: 1, + DelAccountSIK: 1, + SetAccountValidator: 100, + }, + }), +} + +var devGenesis = comettypes.GenesisDoc{ + GenesisTime: time.Date(2024, time.April, 4, 1, 0, 0, 0, time.UTC), + ChainID: "vocdoni/DEV/33", + InitialHeight: 1, + ConsensusParams: &comettypes.ConsensusParams{ + Block: DefaultBlockParams(), + Evidence: comettypes.DefaultEvidenceParams(), + Validator: DefaultValidatorParams(), + Version: comettypes.VersionParams{ + App: 1, }, }, - AppState: AppState{ + AppState: jsonRawMessage(AppState{ MaxElectionSize: 100000, NetworkCapacity: 20000, Validators: []AppStateValidators{ { // 0 - Address: types.HexStringToHexBytes("04cc36be85a0a6e2bfd09295396625e6302d7c60"), + Address: ethereumAddrFromPubKey("03c61c8399828b0c5644455e43c946979272dc3ca0859267f798268802303015f7"), PubKey: types.HexStringToHexBytes("03c61c8399828b0c5644455e43c946979272dc3ca0859267f798268802303015f7"), Power: 10, Name: "", KeyIndex: 1, }, { // 1 - Address: types.HexStringToHexBytes("fc095a35338d96503b6fd1010475e45a3545fc25"), + Address: ethereumAddrFromPubKey("0383fe95c5fddee9932ef0f77c180c3c5d0357dba566f2ee77de666a64d9d8c2a6"), PubKey: types.HexStringToHexBytes("0383fe95c5fddee9932ef0f77c180c3c5d0357dba566f2ee77de666a64d9d8c2a6"), Power: 10, Name: "", KeyIndex: 2, }, { // 2 - Address: types.HexStringToHexBytes("a9b1008f17654b36f2a9abd29323c53d344415a0"), + Address: ethereumAddrFromPubKey("03503c0872bdcd804b1635cf187577ca1caddbbb14ec8eb3af68579fe4bedcf071"), PubKey: types.HexStringToHexBytes("03503c0872bdcd804b1635cf187577ca1caddbbb14ec8eb3af68579fe4bedcf071"), Power: 10, Name: "", KeyIndex: 3, }, { // 3 - Address: types.HexStringToHexBytes("234120598e3fcfcfae5d969254d371248b0cf8d1"), + Address: ethereumAddrFromPubKey("02159b8dd9b1cea02cd0ff78ae26dc8aa4efc65f46511537d8550fe1ce407100c3"), PubKey: types.HexStringToHexBytes("02159b8dd9b1cea02cd0ff78ae26dc8aa4efc65f46511537d8550fe1ce407100c3"), Power: 10, Name: "", @@ -137,80 +171,69 @@ var devGenesis = Doc{ DelAccountSIK: 1, SetAccountValidator: 10000, }, - }, + }), } -var stageGenesis = Doc{ - GenesisTime: time.Date(2024, time.January, 30, 1, 0, 0, 0, time.UTC), - ChainID: "vocdoni/STAGE/11", - ConsensusParams: &ConsensusParams{ - Block: BlockParams{ - MaxBytes: 2097152, - MaxGas: -1, - }, - Evidence: EvidenceParams{ - MaxAgeNumBlocks: 100000, - MaxAgeDuration: 10000, - }, - Validator: ValidatorParams{ - PubKeyTypes: []string{"secp256k1"}, - }, - Version: VersionParams{ - AppVersion: 1, +var stageGenesis = comettypes.GenesisDoc{ + GenesisTime: time.Date(2024, time.January, 30, 1, 0, 0, 0, time.UTC), + ChainID: "vocdoni/STAGE/11", + InitialHeight: 1, + ConsensusParams: &comettypes.ConsensusParams{ + Block: DefaultBlockParams(), + Evidence: comettypes.DefaultEvidenceParams(), + Validator: DefaultValidatorParams(), + Version: comettypes.VersionParams{ + App: 1, }, }, - AppState: AppState{ + AppState: jsonRawMessage(AppState{ MaxElectionSize: 500000, NetworkCapacity: 10000, Validators: []AppStateValidators{ { // 0 - Address: types.HexStringToHexBytes("321d141cf1fcb41d7844af611b5347afc380a03f"), + Address: ethereumAddrFromPubKey("02420b2ee645b9509453cd3b99a6bd8e5e10c1d746fb0bb0ac5af79aba19bb9784"), PubKey: types.HexStringToHexBytes("02420b2ee645b9509453cd3b99a6bd8e5e10c1d746fb0bb0ac5af79aba19bb9784"), Power: 10, Name: "vocdoni1", KeyIndex: 1, }, { // 1 - Address: types.HexStringToHexBytes("5e6c49d98ff3b90ca46387d7c583d20cf99f29bd"), - PubKey: types.HexStringToHexBytes("03e6c55195825f9736ce8a4553913bbadb26c7f094540e06aed9ccda0e6e26050d"), - Power: 10, - Name: "vocdoni2", - KeyIndex: 0, + Address: ethereumAddrFromPubKey("03e6c55195825f9736ce8a4553913bbadb26c7f094540e06aed9ccda0e6e26050d"), + PubKey: types.HexStringToHexBytes("03e6c55195825f9736ce8a4553913bbadb26c7f094540e06aed9ccda0e6e26050d"), + Power: 10, + Name: "vocdoni2", }, { // 2 - Address: types.HexStringToHexBytes("9d4c46f7485036faea5f15c3034e9e864b9415b5"), - PubKey: types.HexStringToHexBytes("03cb39e1132eee0b25ec75d7dad1f2885460f9b2f200d108a923b78e648b783839"), - Power: 10, - Name: "vocdoni3", - KeyIndex: 0, + Address: ethereumAddrFromPubKey("03cb39e1132eee0b25ec75d7dad1f2885460f9b2f200d108a923b78e648b783839"), + PubKey: types.HexStringToHexBytes("03cb39e1132eee0b25ec75d7dad1f2885460f9b2f200d108a923b78e648b783839"), + Power: 10, + Name: "vocdoni3", }, { // 3 - Address: types.HexStringToHexBytes("52d74938f81569aba46f384c8108c370b5403585"), + Address: ethereumAddrFromPubKey("03f6c246831a524e8214e9ceb61d3da2c3c4dbee09bcbe5d9d9878aaa085764d65"), PubKey: types.HexStringToHexBytes("03f6c246831a524e8214e9ceb61d3da2c3c4dbee09bcbe5d9d9878aaa085764d65"), Power: 10, Name: "vocdoni4", KeyIndex: 2, }, { // 4 - Address: types.HexStringToHexBytes("ad6ff21ccfb31002adc52714043e37da1b555b15"), - PubKey: types.HexStringToHexBytes("02fd283ff5760958b4e59eac6b0647ed002669ef2862eb9361251376160aa72fe5"), - Power: 10, - Name: "vocdoni5", - KeyIndex: 0, + Address: ethereumAddrFromPubKey("02fd283ff5760958b4e59eac6b0647ed002669ef2862eb9361251376160aa72fe5"), + PubKey: types.HexStringToHexBytes("02fd283ff5760958b4e59eac6b0647ed002669ef2862eb9361251376160aa72fe5"), + Power: 10, + Name: "vocdoni5", }, { // 5 - Address: types.HexStringToHexBytes("8367a1488c3afda043a2a602c13f01d801d0270e"), + Address: ethereumAddrFromPubKey("03369a8c595c70526baf8528b908591ec286e910b10796c3d6dfca0ef76a645167"), PubKey: types.HexStringToHexBytes("03369a8c595c70526baf8528b908591ec286e910b10796c3d6dfca0ef76a645167"), Power: 10, Name: "vocdoni6", KeyIndex: 3, }, { // 6 - Address: types.HexStringToHexBytes("4146598ff76009f45903958c4c7a3195683b2f61"), - PubKey: types.HexStringToHexBytes("02b5005aeefdb8bb196d308df3fba157a7c1e84966f899a9def6aa97b086bc87e7"), - Power: 10, - Name: "vocdoni7", - KeyIndex: 0, + Address: ethereumAddrFromPubKey("02b5005aeefdb8bb196d308df3fba157a7c1e84966f899a9def6aa97b086bc87e7"), + PubKey: types.HexStringToHexBytes("02b5005aeefdb8bb196d308df3fba157a7c1e84966f899a9def6aa97b086bc87e7"), + Power: 10, + Name: "vocdoni7", }, }, Accounts: []Account{ @@ -235,90 +258,78 @@ var stageGenesis = Doc{ DelAccountSIK: 1, SetAccountValidator: 500000, }, - }, + }), } -var ltsGenesis = Doc{ - GenesisTime: time.Date(2023, time.October, 24, 9, 0, 0, 0, time.UTC), - ChainID: "vocdoni/LTS/1.2", - ConsensusParams: &ConsensusParams{ - Block: BlockParams{ - MaxBytes: 2097152, - MaxGas: -1, - }, - Evidence: EvidenceParams{ - MaxAgeNumBlocks: 100000, - MaxAgeDuration: 10000, - }, - Validator: ValidatorParams{ - PubKeyTypes: []string{"secp256k1"}, - }, - Version: VersionParams{ - AppVersion: 0, +var ltsGenesis = comettypes.GenesisDoc{ + GenesisTime: time.Date(2024, time.April, 24, 9, 0, 0, 0, time.UTC), + ChainID: "vocdoni/LTS/1.2", + InitialHeight: 1, + ConsensusParams: &comettypes.ConsensusParams{ + Block: DefaultBlockParams(), + Evidence: comettypes.DefaultEvidenceParams(), + Validator: DefaultValidatorParams(), + Version: comettypes.VersionParams{ + App: 0, }, }, - AppState: AppState{ + AppState: jsonRawMessage(AppState{ MaxElectionSize: 1000000, NetworkCapacity: 5000, Validators: []AppStateValidators{ { // 0 - Address: types.HexStringToHexBytes("8a67aa6e63ea24a029fade79b93f39aa2f935608"), + Address: ethereumAddrFromPubKey("024e3fbcd7e1516ebbc332519a3602e39753c6dd49c46df307c1e60b976f0b29a5"), PubKey: types.HexStringToHexBytes("024e3fbcd7e1516ebbc332519a3602e39753c6dd49c46df307c1e60b976f0b29a5"), Power: 10, Name: "vocdoni-validator0", KeyIndex: 1, }, { // 1 - Address: types.HexStringToHexBytes("dd47c5e9db1be4f9c6fac3474b9d9aec5c00ecdd"), - PubKey: types.HexStringToHexBytes("02364db3aedf05ffbf25e67e81de971f3a9965b9e1a2d066af06b634ba5c959152"), - Power: 10, - Name: "vocdoni-validator1", - KeyIndex: 0, + Address: ethereumAddrFromPubKey("02364db3aedf05ffbf25e67e81de971f3a9965b9e1a2d066af06b634ba5c959152"), + PubKey: types.HexStringToHexBytes("02364db3aedf05ffbf25e67e81de971f3a9965b9e1a2d066af06b634ba5c959152"), + Power: 10, + Name: "vocdoni-validator1", }, { // 2 - Address: types.HexStringToHexBytes("6bc0fe0ac7e7371294e3c2d39b0e1337b9757193"), - PubKey: types.HexStringToHexBytes("037a2e3b3e7ae07cb75dbc73aff9c39b403e0ec58b596cf03fe99a27555285ef73"), - Power: 10, - Name: "vocdoni-validator2", - KeyIndex: 0, + Address: ethereumAddrFromPubKey("037a2e3b3e7ae07cb75dbc73aff9c39b403e0ec58b596cf03fe99a27555285ef73"), + PubKey: types.HexStringToHexBytes("037a2e3b3e7ae07cb75dbc73aff9c39b403e0ec58b596cf03fe99a27555285ef73"), + Power: 10, + Name: "vocdoni-validator2", }, { // 3 - Address: types.HexStringToHexBytes("d863a79bb3c019941de5ebfc10a136bbfbbc2982"), + Address: ethereumAddrFromPubKey("03553d1b75cdda0a49136417daee453c3a00ed75af64ec6aa20476cf227dfd946c"), PubKey: types.HexStringToHexBytes("03553d1b75cdda0a49136417daee453c3a00ed75af64ec6aa20476cf227dfd946c"), Power: 10, Name: "vocdoni-validator3", KeyIndex: 2, }, { // 4 - Address: types.HexStringToHexBytes("d16a9fe63456ea0b3706a2855b98a3a20f10e308"), - PubKey: types.HexStringToHexBytes("036e25b61605a04ef3cf5829e73a2c9db4a4b0958a8a6be0895c3df19b69e7ad45"), - Power: 10, - Name: "vocdoni-validator4", - KeyIndex: 0, + Address: ethereumAddrFromPubKey("036e25b61605a04ef3cf5829e73a2c9db4a4b0958a8a6be0895c3df19b69e7ad45"), + PubKey: types.HexStringToHexBytes("036e25b61605a04ef3cf5829e73a2c9db4a4b0958a8a6be0895c3df19b69e7ad45"), + Power: 10, + Name: "vocdoni-validator4", }, { // 5 - Address: types.HexStringToHexBytes("4ece41dd2b0f0ddd4ddef9fa83ad6d973c98a48c"), + Address: ethereumAddrFromPubKey("027b034a05be20113cdf39eff609c5265d1575c5510bf3fcc611e6da0bed6d30b4"), PubKey: types.HexStringToHexBytes("027b034a05be20113cdf39eff609c5265d1575c5510bf3fcc611e6da0bed6d30b4"), Power: 10, Name: "vocdoni-validator5", KeyIndex: 3, }, { // 6 - Address: types.HexStringToHexBytes("2b617bad95bb36805512c76a02144c778d3cda20"), - PubKey: types.HexStringToHexBytes("034105acd3392dffcfe08a7a2e1c48fb4f52c7f4cdce4477474afc0ddff023ec2d"), - Power: 10, - Name: "vocdoni-validator6", - KeyIndex: 0, + Address: ethereumAddrFromPubKey("034105acd3392dffcfe08a7a2e1c48fb4f52c7f4cdce4477474afc0ddff023ec2d"), + PubKey: types.HexStringToHexBytes("034105acd3392dffcfe08a7a2e1c48fb4f52c7f4cdce4477474afc0ddff023ec2d"), + Power: 10, + Name: "vocdoni-validator6", }, { // 7 - Address: types.HexStringToHexBytes("4945fd40d29870a931561b26a30a529081ded677"), - PubKey: types.HexStringToHexBytes("038276c348971ef9d8b11abaf0cdce50e6cb89bd0f87df14301ef02d46db09db6d"), - Power: 10, - Name: "vocdoni-validator7", - KeyIndex: 0, + Address: ethereumAddrFromPubKey("038276c348971ef9d8b11abaf0cdce50e6cb89bd0f87df14301ef02d46db09db6d"), + PubKey: types.HexStringToHexBytes("038276c348971ef9d8b11abaf0cdce50e6cb89bd0f87df14301ef02d46db09db6d"), + Power: 10, + Name: "vocdoni-validator7", }, { // 8 - Address: types.HexStringToHexBytes("34868fa6ef1b001b830a5a19a06c69f62f622410"), + Address: ethereumAddrFromPubKey("02a94d4a25c0281980af65d014ce72d34b0aba6e5dff362da8b34c31e8b93b26a9"), PubKey: types.HexStringToHexBytes("02a94d4a25c0281980af65d014ce72d34b0aba6e5dff362da8b34c31e8b93b26a9"), Power: 10, Name: "vocdoni-validator8", @@ -351,14 +362,93 @@ var ltsGenesis = Doc{ DelAccountSIK: 1, SetAccountValidator: 500000, }, - }, + }), +} + +// DefaultBlockParams returns different defaults than upstream DefaultBlockParams: +// MaxBytes = 2 megabytes, and MaxGas = -1 +func DefaultBlockParams() comettypes.BlockParams { + return comettypes.BlockParams{ + MaxBytes: 2097152, + MaxGas: -1, + } +} + +// DefaultValidatorParams returns different defaults than upstream DefaultValidatorParams: +// allows only secp256k1 pubkeys. +func DefaultValidatorParams() comettypes.ValidatorParams { + return comettypes.ValidatorParams{ + PubKeyTypes: []string{comettypes.ABCIPubKeyTypeSecp256k1}, + } } // AvailableNetworks returns the list of hardcoded networks func AvailableNetworks() []string { networks := []string{} - for k := range Genesis { + for k := range genesis { networks = append(networks, k) } return networks } + +// HardcodedForNetwork returns the hardcoded genesis.Doc of a specific network. Panics if not found +func HardcodedForNetwork(network string) *Doc { + g, ok := genesis[network] + if !ok { + panic("no hardcoded genesis found for current network") + } + if err := g.ValidateAndComplete(); err != nil { + panic("hardcoded genesis is invalid") + } + return &Doc{g} +} + +// HardcodedWithOverrides gets the hardcoded genesis.Doc of a specific network (panics if not found) +// and overrides ChainID, InitialHeight and AppHash before returning it. +func HardcodedWithOverrides(network, chainID string, initialHeight int64, appHash string) *Doc { + g := HardcodedForNetwork(network) + if chainID != "" { + g.ChainID = chainID + log.Warnf("overriding genesis ChainID=%q", chainID) + } + if initialHeight > 0 { + g.InitialHeight = initialHeight + log.Warnf("overriding genesis InitialHeight=%d", initialHeight) + } + if appHash != "" { + g.AppHash = []byte(types.HexStringToHexBytes(appHash)) + log.Warnf("overriding genesis AppHash=%q", appHash) + } + return g +} + +// LoadFromFile loads and unmarshals a genesis.json, returning a genesis.Doc +func LoadFromFile(path string) (*Doc, error) { + gd, err := comettypes.GenesisDocFromFile(path) + if err != nil { + return nil, err + } + return &Doc{*gd}, nil +} + +// ethereumAddrFromPubKey converts a hex string to a ethcommon.Address and returns its Bytes(). +// It strips a leading '0x' or '0X' if found, for backwards compatibility. +// Panics if the string is not a valid hex string. +func ethereumAddrFromPubKey(hexString string) []byte { + addr, err := ethereum.AddrFromPublicKey(types.HexStringToHexBytes(hexString)) + if err != nil { + panic(err) + } + return addr.Bytes() +} + +// jsonRawMessage marshals the appState into a json.RawMessage. +// Panics on error. +func jsonRawMessage(appState AppState) json.RawMessage { + jrm, err := json.Marshal(appState) + if err != nil { + // must never happen + panic(err) + } + return jrm +} diff --git a/vochain/genesis/genesis_test.go b/vochain/genesis/genesis_test.go new file mode 100644 index 000000000..03b3218ca --- /dev/null +++ b/vochain/genesis/genesis_test.go @@ -0,0 +1,23 @@ +package genesis + +import ( + "path/filepath" + "testing" + + qt "github.com/frankban/quicktest" +) + +func TestSaveAsAndLoad(t *testing.T) { + file := filepath.Join(t.TempDir(), "genesis.json") + g := HardcodedForNetwork("test") + t.Logf("%+v", g) + err := g.SaveAs(file) + qt.Assert(t, err, qt.IsNil) + + f, err := LoadFromFile(file) + qt.Assert(t, err, qt.IsNil) + t.Logf("%+v", f) + t.Logf("%+v", g.ConsensusParams) + t.Logf("%+v", f.ConsensusParams) + qt.Assert(t, g.Hash(), qt.DeepEquals, f.Hash()) +} diff --git a/vochain/genesis/types.go b/vochain/genesis/types.go index 479a50da0..19622f8bc 100644 --- a/vochain/genesis/types.go +++ b/vochain/genesis/types.go @@ -2,58 +2,17 @@ package genesis import ( "encoding/json" - "strconv" - "time" + comethash "github.com/cometbft/cometbft/crypto/tmhash" comettypes "github.com/cometbft/cometbft/types" - "go.vocdoni.io/dvote/crypto/ethereum" "go.vocdoni.io/dvote/types" ) -// Vochain is a struct containing the genesis details. -type Vochain struct { - AutoUpdateGenesis bool - SeedNodes []string - StateSync map[string]StateSyncParams - Genesis *Doc -} - -// The genesis app state types are copied from -// github.com/cometbft/cometbft/types, for the sake of making this package -// lightweight and not have it import heavy indirect dependencies like grpc or -// crypto/*. - -// Doc defines the initial conditions for a Vocdoni blockchain. -// It is mostly a wrapper around the Tendermint GenesisDoc. +// Doc is a wrapper around the CometBFT GenesisDoc, +// that adds some useful methods like Hash type Doc struct { - GenesisTime time.Time `json:"genesis_time"` - ChainID string `json:"chain_id"` - ConsensusParams *ConsensusParams `json:"consensus_params,omitempty"` - AppHash types.HexBytes `json:"app_hash"` - AppState AppState `json:"app_state,omitempty"` -} - -// TendermintDoc returns the Tendermint GenesisDoc from the Vocdoni genesis.Doc. -func (g *Doc) TendermintDoc() comettypes.GenesisDoc { - appState, err := json.Marshal(g.AppState) - if err != nil { - // must never happen - panic(err) - } - return comettypes.GenesisDoc{ - GenesisTime: g.GenesisTime, - ChainID: g.ChainID, - ConsensusParams: &comettypes.ConsensusParams{ - Block: comettypes.BlockParams{ - MaxBytes: int64(g.ConsensusParams.Block.MaxBytes), - MaxGas: int64(g.ConsensusParams.Block.MaxGas), - }, - }, - Validators: []comettypes.GenesisValidator{}, - AppHash: []byte(g.AppHash), - AppState: appState, - } + comettypes.GenesisDoc } // Marshal returns the JSON encoding of the genesis.Doc. @@ -71,50 +30,11 @@ func (g *Doc) Hash() []byte { if err != nil { panic(err) } - return ethereum.HashRaw(data) -} - -// ConsensusParams defines the consensus critical parameters that determine the -// validity of blocks. This comes from Tendermint. -type ConsensusParams struct { - Block BlockParams `json:"block"` - Evidence EvidenceParams `json:"evidence"` - Validator ValidatorParams `json:"validator"` - Version VersionParams `json:"version"` -} - -// BlockParams define limits on the block size and gas plus minimum time -// between blocks. This comes from Tendermint. -type BlockParams struct { - MaxBytes StringifiedInt64 `json:"max_bytes"` - MaxGas StringifiedInt64 `json:"max_gas"` -} - -// EvidenceParams define limits on max evidence age and max duration -type EvidenceParams struct { - MaxAgeNumBlocks StringifiedInt64 `json:"max_age_num_blocks"` - // only accept new evidence more recent than this - MaxAgeDuration StringifiedInt64 `json:"max_age_duration"` -} - -// ValidatorParams define the validator key -type ValidatorParams struct { - PubKeyTypes []string `json:"pub_key_types"` -} - -// VersionParams define the version app information -type VersionParams struct { - AppVersion StringifiedInt64 `json:"app_version"` + return comethash.Sum(data) } // ________________________ GENESIS APP STATE ________________________ -// Account represents an account in the genesis app state -type Account struct { - Address types.HexBytes `json:"address"` - Balance uint64 `json:"balance"` -} - // AppState is the main application state in the genesis file. type AppState struct { Validators []AppStateValidators `json:"validators"` @@ -133,33 +53,8 @@ type AppStateValidators struct { KeyIndex uint8 `json:"key_index"` } -// StateSyncParams define the parameters used by StateSync -type StateSyncParams struct { - TrustHeight int64 - TrustHash types.HexBytes -} - -// StringifiedInt64 is a wrapper around int64 that marshals/unmarshals as a string. -// This is a dirty non-sense workaround. Blame Tendermint not me. -// For some (unknown) reason Tendermint requires the integer values to be strings in -// the JSON genesis file. -type StringifiedInt64 int64 - -// MarshalJSON implements the json.Marshaler interface. -func (i StringifiedInt64) MarshalJSON() ([]byte, error) { - return json.Marshal(strconv.FormatInt(int64(i), 10)) -} - -// UnmarshalJSON implements the json.Unmarshaler interface. -func (i *StringifiedInt64) UnmarshalJSON(data []byte) error { - var s string - if err := json.Unmarshal(data, &s); err != nil { - return err - } - v, err := strconv.ParseInt(s, 10, 64) - if err != nil { - return err - } - *i = StringifiedInt64(v) - return nil +// Account represents an account in the genesis app state +type Account struct { + Address types.HexBytes `json:"address"` + Balance uint64 `json:"balance"` } diff --git a/vochain/start.go b/vochain/start.go index c8c1616ad..2867da418 100644 --- a/vochain/start.go +++ b/vochain/start.go @@ -4,7 +4,6 @@ package vochain import ( "context" "encoding/hex" - "encoding/json" "fmt" "os" "path/filepath" @@ -14,7 +13,7 @@ import ( "go.vocdoni.io/dvote/config" "go.vocdoni.io/dvote/crypto/ethereum" "go.vocdoni.io/dvote/crypto/zk/circuit" - vocdoniGenesis "go.vocdoni.io/dvote/vochain/genesis" + "go.vocdoni.io/dvote/vochain/genesis" cometconfig "github.com/cometbft/cometbft/config" cometp2p "github.com/cometbft/cometbft/p2p" @@ -25,16 +24,15 @@ import ( ) // NewVochain starts a node with an ABCI application -func NewVochain(vochaincfg *config.VochainCfg, genesis []byte) *BaseApplication { +func NewVochain(vochaincfg *config.VochainCfg) *BaseApplication { + migrateLegacyDirs(vochaincfg.DataDir) // creating new vochain app - c := *vochaincfg - c.DataDir = filepath.Join(vochaincfg.DataDir, "data") - app, err := NewBaseApplication(&c) + app, err := NewBaseApplication(vochaincfg) if err != nil { log.Fatalf("cannot initialize vochain application: %s", err) } log.Info("creating tendermint node and application") - err = app.SetNode(vochaincfg, genesis) + err = app.SetNode(vochaincfg) if err != nil { log.Fatal(err) } @@ -46,16 +44,16 @@ func NewVochain(vochaincfg *config.VochainCfg, genesis []byte) *BaseApplication } // newTendermint creates a new tendermint node attached to the given ABCI app -func newTendermint(app *BaseApplication, - localConfig *config.VochainCfg, genesis []byte) (*cometnode.Node, error) { +func newTendermint(app *BaseApplication, localConfig *config.VochainCfg) (*cometnode.Node, error) { var err error tconfig := cometconfig.DefaultConfig() - tconfig.SetRoot(localConfig.DataDir) - if err := os.MkdirAll(filepath.Join(localConfig.DataDir, "config"), 0750); err != nil { + + tconfig.SetRoot(filepath.Join(localConfig.DataDir, config.DefaultCometBFTPath)) + if err := os.MkdirAll(filepath.Join(tconfig.RootDir, cometconfig.DefaultConfigDir), 0o750); err != nil { log.Fatal(err) } - if err := os.MkdirAll(filepath.Join(localConfig.DataDir, "data"), 0750); err != nil { + if err := os.MkdirAll(filepath.Join(tconfig.RootDir, cometconfig.DefaultDataDir), 0o750); err != nil { log.Fatal(err) } @@ -69,9 +67,10 @@ func newTendermint(app *BaseApplication, tconfig.P2P.AddrBookStrict = false } tconfig.P2P.Seeds = strings.Trim(strings.Join(localConfig.Seeds, ","), "[]\"") - if _, ok := vocdoniGenesis.Genesis[localConfig.Network]; len(tconfig.P2P.Seeds) < 8 && - !localConfig.IsSeedNode && ok { - tconfig.P2P.Seeds = strings.Join(vocdoniGenesis.Genesis[localConfig.Network].SeedNodes, ",") + if len(tconfig.P2P.Seeds) < 8 && !localConfig.IsSeedNode { + if seeds, ok := config.DefaultSeedNodes[localConfig.Network]; ok { + tconfig.P2P.Seeds = strings.Join(seeds, ",") + } } if len(tconfig.P2P.Seeds) > 0 { log.Infof("seed nodes: %s", tconfig.P2P.Seeds) @@ -149,8 +148,8 @@ func newTendermint(app *BaseApplication, } // if also no Seeds specified, fallback to genesis - if _, ok := vocdoniGenesis.Genesis[localConfig.Network]; ok { - return replacePorts(vocdoniGenesis.Genesis[localConfig.Network].SeedNodes) + if seeds, ok := config.DefaultSeedNodes[localConfig.Network]; ok { + return replacePorts(seeds) } return nil @@ -167,8 +166,8 @@ func newTendermint(app *BaseApplication, tconfig.StateSync.TrustHeight = localConfig.StateSyncTrustHeight tconfig.StateSync.TrustHash = localConfig.StateSyncTrustHash - // If StateSync is enabled but parameters are empty, try our best to populate them - // first try to fetch params from remote API endpoint + // If StateSync is enabled but parameters are empty, populate them + // fetching params from remote API endpoint if localConfig.StateSyncFetchParamsFromRPC && tconfig.StateSync.TrustHeight == 0 && tconfig.StateSync.TrustHash == "" { tconfig.StateSync.TrustHeight, tconfig.StateSync.TrustHash = func() (int64, string) { @@ -196,18 +195,6 @@ func newTendermint(app *BaseApplication, return status.SyncInfo.LatestBlockHeight, status.SyncInfo.LatestBlockHash.String() }() } - - // if still empty, fallback to hardcoded params, if defined for the current network & chainID - if tconfig.StateSync.TrustHeight == 0 && tconfig.StateSync.TrustHash == "" { - if g, ok := vocdoniGenesis.Genesis[localConfig.Network]; ok { - if statesync, ok := g.StateSync[g.Genesis.ChainID]; ok { - tconfig.StateSync.TrustHeight = statesync.TrustHeight - tconfig.StateSync.TrustHash = statesync.TrustHash.String() - log.Infow("using hardcoded statesync params", - "height", tconfig.StateSync.TrustHeight, "hash", tconfig.StateSync.TrustHash) - } - } - } } tconfig.RPC.ListenAddress = "tcp://0.0.0.0:26657" @@ -215,13 +202,28 @@ func newTendermint(app *BaseApplication, // tmdbBackend defaults to goleveldb, but switches to cleveldb if // -tags=cleveldb is used. See tmdb_*.go. tconfig.DBBackend = string(tmdbBackend) - if localConfig.Genesis != "" { - tconfig.Genesis = localConfig.Genesis + tconfig.Genesis = localConfig.Genesis + + if _, err := os.Stat(tconfig.Genesis); os.IsNotExist(err) { + log.Infof("writing comet genesis to %s", tconfig.Genesis) + if err := genesis.HardcodedWithOverrides(localConfig.Network, + localConfig.GenesisChainID, + localConfig.GenesisInitialHeight, + localConfig.GenesisAppHash).SaveAs(tconfig.Genesis); err != nil { + return nil, fmt.Errorf("cannot write genesis file: %w", err) + } } - if err := tconfig.ValidateBasic(); err != nil { - return nil, fmt.Errorf("config is invalid: %w", err) + // We need to load the genesis already, + // to fetch chain_id in order to make Replay work, since signatures depend on it. + // and also to check InitialHeight to decide what to reply when cometbft ask for Info() + loadedGenesis, err := genesis.LoadFromFile(tconfig.GenesisFile()) + if err != nil { + return nil, fmt.Errorf("cannot load genesis file: %w", err) } + log.Infow("loaded genesis file", "genesis", tconfig.GenesisFile(), "chainID", loadedGenesis.ChainID) + app.genesisDoc = loadedGenesis + app.SetChainID(loadedGenesis.ChainID) // read or create local private validator pv, err := NewPrivateValidator( @@ -256,25 +258,6 @@ func newTendermint(app *BaseApplication, return nil, fmt.Errorf("cannot create or load node key: %w", err) } } - log.Infow("vochain initialized", - "db-backend", tconfig.DBBackend, - "publicKey", hex.EncodeToString(pv.Key.PubKey.Bytes()), - "accountAddr", app.NodeAddress, - "validatorAddr", pv.Key.PubKey.Address(), - "external-address", tconfig.P2P.ExternalAddress, - "nodeId", nodeKey.ID(), - "seed", tconfig.P2P.SeedMode) - - // read or create genesis file - if _, err := os.Stat(tconfig.GenesisFile()); err == nil { - log.Infof("found genesis file %s", tconfig.GenesisFile()) - } else { - log.Debugf("loaded genesis: %s", string(genesis)) - if err := os.WriteFile(tconfig.GenesisFile(), genesis, 0o600); err != nil { - return nil, err - } - log.Infof("new genesis created, stored at %s", tconfig.GenesisFile()) - } if localConfig.TendermintMetrics { tconfig.Instrumentation = &cometconfig.InstrumentationConfig{ @@ -285,28 +268,35 @@ func newTendermint(app *BaseApplication, } } - // We need to fetch chain_id in order to make Replay work, - // since signatures depend on it. - type genesisChainID struct { - ChainID string `json:"chain_id"` - } - genesisData, err := os.ReadFile(tconfig.GenesisFile()) - if err != nil { - return nil, fmt.Errorf("cannot read genesis file: %w", err) - } - genesisCID := &genesisChainID{} - if err := json.Unmarshal(genesisData, genesisCID); err != nil { - return nil, fmt.Errorf("cannot unmarshal genesis file for fetching chainID") + if err := tconfig.ValidateBasic(); err != nil { + return nil, fmt.Errorf("config is invalid: %w", err) } - log.Infow("genesis file", "genesis", tconfig.GenesisFile(), "chainID", genesisCID.ChainID) - app.SetChainID(genesisCID.ChainID) + + log.Infow("vochain initialized", + "db-backend", tconfig.DBBackend, + "publicKey", hex.EncodeToString(pv.Key.PubKey.Bytes()), + "accountAddr", app.NodeAddress, + "validatorAddr", pv.Key.PubKey.Address(), + "external-address", tconfig.P2P.ExternalAddress, + "nodeId", nodeKey.ID(), + "seed", tconfig.P2P.SeedMode) // the chain might need additional ZkCircuits, now that we know the chainID ensure they are downloaded now, // to avoid delays at beginBlock during a fork - if err := circuit.DownloadArtifactsForChainID(genesisCID.ChainID); err != nil { + if err := circuit.DownloadArtifactsForChainID(app.ChainID()); err != nil { return nil, fmt.Errorf("cannot download zk circuits for chainID: %w", err) } + // if EndOfChain cmdline flag was passed, override ForksCfg + if localConfig.EndOfChain > 0 { + forks := config.ForksForChainID(app.ChainID()) + forks.EndOfChain = uint32(localConfig.EndOfChain) + config.SetForksForChainID(app.ChainID(), forks) + } + if config.ForksForChainID(app.ChainID()).EndOfChain > 0 { + log.Warnf("this node will stop chain %s at height %d", app.ChainID(), config.ForksForChainID(app.ChainID()).EndOfChain) + } + // assign the default tendermint methods app.SetDefaultMethods() node, err := cometnode.NewNode( @@ -327,3 +317,41 @@ func newTendermint(app *BaseApplication, return node, nil } + +// migrateLegacyDirs handles the legacy paths used before commit "refactor genesis package" +// cometbft data is now separated from vcstate and snapshots, so we need to migrate current deployments +func migrateLegacyDirs(dataDir string) { + // first move our dirs out, so they are not mixed with cometbft data + // * vochain/data/vcstate -> vochain/vcstate + // * vochain/data/snapshots -> vochain/snapshots + migrateLegacyPath(StateDataDir, filepath.Join(dataDir, cometconfig.DefaultDataDir), dataDir) + migrateLegacyPath(SnapshotsDataDir, filepath.Join(dataDir, cometconfig.DefaultDataDir), dataDir) + // then move cometbft dirs into its own subdir + // * vochain/config -> vochain/cometbft/config + // * vochain/data -> vochain/cometbft/data + migrateLegacyPath(cometconfig.DefaultConfigDir, dataDir, filepath.Join(dataDir, config.DefaultCometBFTPath)) + migrateLegacyPath(cometconfig.DefaultDataDir, dataDir, filepath.Join(dataDir, config.DefaultCometBFTPath)) +} + +// migrateLegacyPath moves dir from oldRoot into newRoot, +// only if it exists on oldRoot and not on newRoot +func migrateLegacyPath(dir, oldRoot, newRoot string) { + oldpath := filepath.Join(oldRoot, dir) + newpath := filepath.Join(newRoot, dir) + if _, err := os.Stat(oldpath); os.IsNotExist(err) { + return + } + if _, err := os.Stat(newpath); !os.IsNotExist(err) { + log.Warnf("found legacy dir %s but also new dir %s, leaving untouched, you need to resolve migration manually", oldpath, newpath) + return + } + if err := os.MkdirAll(newRoot, 0o750); err != nil { + log.Errorw(err, fmt.Sprintf("migrating legacy dirs, couldn't create %s", newRoot)) + return + } + if err := os.Rename(oldpath, newpath); err != nil { + log.Errorw(err, fmt.Sprintf("couldn't migrate legacy dir %s -> %s", oldpath, newpath)) + return + } + log.Warnf("migrated legacy dir %s -> %s", oldpath, newpath) +} diff --git a/vochain/state/state.go b/vochain/state/state.go index 91c1a1cba..ba598d993 100644 --- a/vochain/state/state.go +++ b/vochain/state/state.go @@ -353,7 +353,7 @@ func (v *State) LastHeight() (uint32, error) { return v.store.Version() } -// CurrentHeight returns the current state height (block count). +// CurrentHeight returns the height of the current (not committed) block. func (v *State) CurrentHeight() uint32 { return v.currentHeight.Load() }