genesis wizard accept file input

This commit is contained in:
wanwiset25 2025-07-28 18:36:41 +07:00
parent e4fd044fa0
commit 4df9699821
7 changed files with 266 additions and 106 deletions

View file

@ -5,6 +5,9 @@ ENV GODEBUG=randseednop=0
RUN apk add make build-base linux-headers
COPY . /builder
RUN cd /builder && make puppeth
RUN cd /builder && make && mv /builder/build/bin/XDC /builder/build/bin/XDC-mainnet
RUN mv /builder/common/constants/constants.go.testnet /builder/common/constants.go
@ -23,6 +26,8 @@ WORKDIR /work
RUN apk add --no-cache bash curl
COPY --from=builder /builder/build/bin/puppeth /usr/bin
COPY --from=builder /builder/build/bin/XDC-local /usr/bin
COPY --from=builder /builder/build/bin/XDC-devnet /usr/bin
COPY --from=builder /builder/build/bin/XDC-testnet /usr/bin
@ -33,7 +38,8 @@ ADD cicd/local /work/local
ADD cicd/devnet /work/devnet
ADD cicd/testnet /work/testnet
ADD cicd/mainnet /work/mainnet
ADD cicd/entry.sh /work/
ADD cicd/entry.sh /work/entry.sh
ADD cicd/puppeth.sh /work/puppeth.sh
# Create an empty pwd file
RUN touch /work/.pwd

2
cicd/puppeth.sh Executable file
View file

@ -0,0 +1,2 @@
#!/bin/bash
puppeth --inpath /app/generated/genesis_input.yml --outpath /app/generated/genesis.json

View file

@ -42,6 +42,16 @@ func main() {
Value: 3,
Usage: "log level to emit to the screen",
},
&cli.StringFlag{
Name: "outpath",
Value: "",
Usage: "output genesis file path",
},
&cli.StringFlag{
Name: "inpath",
Value: "",
Usage: "input file to generate genesis file (optional)",
},
}
app.Action = func(c *cli.Context) error {
// Set up the logger to print everything and the random generator
@ -53,7 +63,7 @@ func main() {
log.Crit("No spaces or hyphens allowed in network name")
}
// Start the wizard and relinquish control
makeWizard(c.String("network")).run()
makeWizard(c.String("network"), c.String("inpath"), c.String("outpath")).run()
return nil
}
app.Run(os.Args)

View file

@ -38,7 +38,8 @@ import (
// config contains all the configurations needed by puppeth that should be saved
// between sessions.
type config struct {
path string // File containing the configuration values
path string // Output file containing the configuration values
inpath string // Input file to read genesis generation parameters (optional)
bootnodes []string // Bootnodes to always connect to by all nodes
ethstats string // Ethstats settings to cache for node deploys
@ -62,6 +63,7 @@ func (c config) flush() {
os.MkdirAll(filepath.Dir(c.path), 0755)
out, _ := json.MarshalIndent(c.Genesis, "", " ")
log.Warn("writing to file", "filename", c.path)
if err := os.WriteFile(c.path, out, 0644); err != nil {
log.Warn("Failed to save puppeth configs", "file", c.path, "err", err)
}

View file

@ -29,6 +29,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/params"
"gopkg.in/yaml.v3"
"context"
"math/big"
@ -43,6 +44,66 @@ import (
"github.com/XinFinOrg/XDPoSChain/rlp"
)
type GenesisInput struct {
Name string
ChainId uint64
Denom string
Period uint64
Epoch uint64
Gap uint64
TimeoutPeriod int
TimeoutSyncThreshold int
V2SwitchBlock uint64
CertThreshold float64
MasternodesOwner common.Address
Masternodes []common.Address
StakingThreshold uint64
RewardYield uint64
FoundationWalletAddress common.Address
}
func NewGenesisInput() *GenesisInput {
return &GenesisInput{
Name: "xdc-custom-network",
ChainId: 5551,
Denom: "xdc",
Period: 2,
Epoch: 900,
Gap: 450,
TimeoutPeriod: 10,
TimeoutSyncThreshold: 3,
V2SwitchBlock: 0,
CertThreshold: 0.667,
StakingThreshold: 10_000_000, // 10M
RewardYield: 10,
FoundationWalletAddress: common.FoudationAddrBinary,
}
}
func (w *wizard) loadGenesisInput() *GenesisInput {
input := NewGenesisInput()
file, err := os.Open(w.conf.inpath)
if err != nil {
log.Warn("Failed to open genesis input file", "err", err)
os.Exit(1)
return nil
}
defer file.Close()
log.Warn("Decoding genesis input file", "path", w.conf.inpath)
log.Warn("File content", "file", file)
decoder := yaml.NewDecoder(file)
if err := decoder.Decode(&input); err != nil {
log.Warn("Failed to decode genesis input file (expect yaml format)", "err", err)
os.Exit(1)
return nil
}
fmt.Println("Generating genesis file with the below input")
fmt.Printf("%+v\n", input)
return input
}
// makeGenesis creates a new genesis struct based on some user input.
func (w *wizard) makeGenesis() {
// Construct a default genesis block
@ -66,7 +127,13 @@ func (w *wizard) makeGenesis() {
fmt.Println(" 2. Clique - proof-of-authority")
fmt.Println(" 3. XDPoS - delegated-proof-of-stake")
choice := w.read()
input := w.loadGenesisInput()
var choice string
if input != nil {
choice = "3"
} else {
choice = w.read()
}
switch {
case choice == "1":
// In case of ethash, we're pretty much done
@ -125,48 +192,75 @@ func (w *wizard) makeGenesis() {
}
fmt.Println()
fmt.Println("How many seconds should blocks take? (default = 2)")
genesis.Config.XDPoS.Period = uint64(w.readDefaultInt(2))
if input != nil {
genesis.Config.XDPoS.Period = input.Period
} else {
genesis.Config.XDPoS.Period = uint64(w.readDefaultInt(2))
}
genesis.Config.XDPoS.V2.CurrentConfig.MinePeriod = int(genesis.Config.XDPoS.Period)
fmt.Println()
fmt.Println("How many Ethers should be rewarded to masternode? (default = 10)")
genesis.Config.XDPoS.Reward = uint64(w.readDefaultInt(10))
fmt.Println()
fmt.Println("Which block number start v2 consesus? (default = 0)")
genesis.Config.XDPoS.V2.SwitchBlock = w.readDefaultBigInt(genesis.Config.XDPoS.V2.SwitchBlock)
if input != nil {
genesis.Config.XDPoS.V2.SwitchBlock = big.NewInt(int64(input.V2SwitchBlock))
} else {
genesis.Config.XDPoS.V2.SwitchBlock = w.readDefaultBigInt(genesis.Config.XDPoS.V2.SwitchBlock)
}
genesis.Config.XDPoS.V2.CurrentConfig.SwitchRound = 0
fmt.Println()
fmt.Println("How long is the v2 timeout period? (default = 10)")
genesis.Config.XDPoS.V2.CurrentConfig.TimeoutPeriod = w.readDefaultInt(10)
if input != nil {
genesis.Config.XDPoS.V2.CurrentConfig.TimeoutPeriod = input.TimeoutPeriod
} else {
genesis.Config.XDPoS.V2.CurrentConfig.TimeoutPeriod = w.readDefaultInt(10)
}
fmt.Println()
fmt.Println("How many v2 timeout reach to send Synchronize message? (default = 3)")
genesis.Config.XDPoS.V2.CurrentConfig.TimeoutSyncThreshold = w.readDefaultInt(3)
if input != nil {
genesis.Config.XDPoS.V2.CurrentConfig.TimeoutSyncThreshold = input.TimeoutSyncThreshold
} else {
genesis.Config.XDPoS.V2.CurrentConfig.TimeoutSyncThreshold = w.readDefaultInt(3)
}
fmt.Println()
fmt.Printf("Proportion of total masternodes v2 vote collection to generate a QC (float value), should be two thirds of masternodes? (default = %f)\n", 0.667)
genesis.Config.XDPoS.V2.CurrentConfig.CertThreshold = w.readDefaultFloat(0.667)
if input != nil {
genesis.Config.XDPoS.V2.CurrentConfig.CertThreshold = input.CertThreshold
} else {
genesis.Config.XDPoS.V2.CurrentConfig.CertThreshold = w.readDefaultFloat(0.667)
}
genesis.Config.XDPoS.V2.CurrentConfig.MaxMasternodes = 108
genesis.Config.XDPoS.V2.AllConfigs[0] = genesis.Config.XDPoS.V2.CurrentConfig
fmt.Println()
fmt.Println("Who own the first masternodes? (mandatory)")
owner := *w.readAddress()
var owner common.Address
if input != nil {
owner = input.MasternodesOwner
} else {
owner = *w.readAddress()
}
// We also need the initial list of signers
fmt.Println()
fmt.Println("Which accounts are allowed to seal (signers)? (mandatory at least one)")
fmt.Println("Which accounts are Masternodes? (mandatory at least one)")
var signers []common.Address
for {
if address := w.readAddress(); address != nil {
signers = append(signers, *address)
continue
if input != nil {
for _, addr := range input.Masternodes {
signers = append(signers, addr)
}
if len(signers) > 0 {
break
} else {
for {
if address := w.readAddress(); address != nil {
signers = append(signers, *address)
continue
}
if len(signers) > 0 {
break
}
}
}
// Sort the signers and embed into the extra-data section
@ -177,6 +271,33 @@ func (w *wizard) makeGenesis() {
}
}
}
fmt.Println()
fmt.Println("How many blocks per epoch? (default = 900)")
if input != nil {
genesis.Config.XDPoS.Epoch = input.Epoch
} else {
genesis.Config.XDPoS.Epoch = uint64(w.readDefaultInt(900))
}
genesis.Config.XDPoS.RewardCheckpoint = genesis.Config.XDPoS.Epoch
fmt.Println()
fmt.Println("How many blocks before checkpoint need to prepare new set of masternodes? (default = 450)")
if input != nil {
genesis.Config.XDPoS.Gap = uint64(input.Gap)
} else {
genesis.Config.XDPoS.Gap = uint64(w.readDefaultInt(450))
}
fmt.Println()
fmt.Println("What is minimum staking threshold to become a Validator? (default = 10M)")
threshold := new(big.Int)
if input != nil {
threshold.SetString("10000000000000000000", 10) // 10M
} else {
threshold.SetString("10000000000000000000", 10) // 10M
}
validatorCap := new(big.Int)
validatorCap.SetString("50000000000000000000000", 10)
var validatorCaps []*big.Int
@ -187,18 +308,29 @@ func (w *wizard) makeGenesis() {
}
fmt.Println()
fmt.Println("How many blocks per epoch? (default = 900)")
epochNumber := uint64(w.readDefaultInt(900))
genesis.Config.XDPoS.Epoch = epochNumber
genesis.Config.XDPoS.RewardCheckpoint = epochNumber
// fmt.Println("How many Ethers should be rewarded to masternode each Epoch? (default = 10)")
fmt.Println("What should be the reward yield of Masternodes in APY% (default = 10)")
yield := uint64(0)
if input != nil {
yield = input.RewardYield
} else {
yield = uint64(w.readDefaultInt(10))
}
blocksPerYear := uint64(31536000 / genesis.Config.XDPoS.Period)
epochsPerYear := blocksPerYear / genesis.Config.XDPoS.Epoch
rewardsPerYear := float64(threshold.Uint64()) * (float64(yield) / float64(100))
rewardPerEpoch := uint64(rewardsPerYear / float64(epochsPerYear))
fmt.Println()
fmt.Println("Calculated Masternode reward per epoch based on yield: ", rewardPerEpoch)
genesis.Config.XDPoS.Reward = rewardPerEpoch
fmt.Println()
fmt.Println("How many blocks before checkpoint need to prepare new set of masternodes? (default = 450)")
genesis.Config.XDPoS.Gap = uint64(w.readDefaultInt(450))
fmt.Println()
fmt.Println("What is foundation wallet address? (default = xdc0000000000000000000000000000000000000068)")
genesis.Config.XDPoS.FoudationWalletAddr = w.readDefaultAddress(common.FoudationAddrBinary)
fmt.Println("What is foundation wallet address (collect 10% of all rewards)? (default = xdc0000000000000000000000000000000000000068)")
if input != nil {
genesis.Config.XDPoS.FoudationWalletAddr = input.FoundationWalletAddress
} else {
genesis.Config.XDPoS.FoudationWalletAddr = w.readDefaultAddress(common.FoudationAddrBinary)
}
// Validator Smart Contract Code
pKey, _ := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
@ -235,21 +367,31 @@ func (w *wizard) makeGenesis() {
fmt.Println()
fmt.Println("Which accounts are allowed to confirm in Foudation MultiSignWallet?")
var owners []common.Address
for {
if address := w.readAddress(); address != nil {
owners = append(owners, *address)
continue
}
if len(owners) > 0 {
break
if input != nil {
owners = append(owners, input.MasternodesOwner)
} else {
for {
if address := w.readAddress(); address != nil {
owners = append(owners, *address)
continue
}
if len(owners) > 0 {
break
}
}
}
fmt.Println()
fmt.Println("How many require for confirm tx in Foudation MultiSignWallet? (default = 2)")
required := int64(w.readDefaultInt(2))
fmt.Println("How many require for confirm tx in Foudation MultiSignWallet? (default = 1)")
var required uint64
if input != nil {
required = 1
} else {
required = uint64(w.readDefaultInt(1))
}
// MultiSigWallet.
multiSignWalletAddr, _, err := multiSignWalletContract.DeployMultiSigWallet(transactOpts, contractBackend, owners, big.NewInt(required))
multiSignWalletAddr, _, err := multiSignWalletContract.DeployMultiSigWallet(transactOpts, contractBackend, owners, big.NewInt(int64(required)))
if err != nil {
fmt.Println("Can't deploy MultiSignWallet SMC")
}
@ -267,7 +409,7 @@ func (w *wizard) makeGenesis() {
}
// Block Signers Smart Contract
blockSignerAddress, _, err := blockSignerContract.DeployBlockSigner(transactOpts, contractBackend, big.NewInt(int64(epochNumber)))
blockSignerAddress, _, err := blockSignerContract.DeployBlockSigner(transactOpts, contractBackend, big.NewInt(int64(genesis.Config.XDPoS.Epoch)))
if err != nil {
fmt.Println("Can't deploy root registry")
}
@ -301,21 +443,31 @@ func (w *wizard) makeGenesis() {
fmt.Println()
fmt.Println("Which accounts are allowed to confirm in Team MultiSignWallet?")
var teams []common.Address
for {
if address := w.readAddress(); address != nil {
teams = append(teams, *address)
continue
}
if len(teams) > 0 {
break
if input != nil {
owners = append(owners, input.MasternodesOwner)
} else {
for {
if address := w.readAddress(); address != nil {
teams = append(teams, *address)
continue
}
if len(teams) > 0 {
break
}
}
}
fmt.Println()
fmt.Println("How many require for confirm tx in Team MultiSignWallet? (default = 2)")
required = int64(w.readDefaultInt(2))
var requiredTeam int64
if input != nil {
requiredTeam = 1
} else {
requiredTeam = int64(w.readDefaultInt(1))
}
// MultiSigWallet.
multiSignWalletTeamAddr, _, err := multiSignWalletContract.DeployMultiSigWallet(transactOpts, contractBackend, teams, big.NewInt(required))
multiSignWalletTeamAddr, _, err := multiSignWalletContract.DeployMultiSigWallet(transactOpts, contractBackend, teams, big.NewInt(requiredTeam))
if err != nil {
fmt.Println("Can't deploy MultiSignWallet SMC")
}
@ -337,32 +489,33 @@ func (w *wizard) makeGenesis() {
Storage: storage,
}
fmt.Println()
fmt.Println("What is swap wallet address for fund 55m XDC?")
swapAddr := *w.readAddress()
baseBalance := big.NewInt(0) // 55m
baseBalance.Add(baseBalance, big.NewInt(55*1000*1000))
baseBalance.Mul(baseBalance, big.NewInt(1000000000000000000))
genesis.Alloc[swapAddr] = types.Account{
Balance: baseBalance,
}
default:
log.Crit("Invalid consensus engine choice", "choice", choice)
}
// Consensus all set, just ask for initial funds and go
fmt.Println()
fmt.Println("Which accounts should be pre-funded? (advisable at least one)")
for {
// Read the address of the account to fund
if address := w.readAddress(); address != nil {
genesis.Alloc[*address] = types.Account{
Balance: new(big.Int).Lsh(big.NewInt(1), 256-7), // 2^256 / 128 (allow many pre-funds without balance overflows)
var addresses []common.Address
if input != nil {
addresses = append(addresses, input.MasternodesOwner)
} else {
for {
if address := w.readAddress(); address != nil {
addresses = append(addresses, *address)
continue
}
continue
break
}
break
}
for _, address := range addresses {
baseBalance := big.NewInt(0) // 21m
baseBalance.Add(baseBalance, big.NewInt(21*1000*1000))
baseBalance.Mul(baseBalance, big.NewInt(1000000000000000000))
genesis.Alloc[address] = types.Account{
Balance: baseBalance,
}
}
// Add a batch of precompile balances to avoid them getting deleted
for i := int64(0); i < 2; i++ {
genesis.Alloc[common.BigToAddress(big.NewInt(i))] = types.Account{Balance: big.NewInt(0)}
@ -370,7 +523,11 @@ func (w *wizard) makeGenesis() {
// Query the user for some custom extras
fmt.Println()
fmt.Println("Specify your chain/network ID if you want an explicit one (default = random)")
genesis.Config.ChainID = new(big.Int).SetUint64(uint64(w.readDefaultInt(rand.Intn(65536))))
if input != nil {
genesis.Config.ChainID = new(big.Int).SetUint64(input.ChainId)
} else {
genesis.Config.ChainID = new(big.Int).SetUint64(uint64(w.readDefaultInt(rand.Intn(65536))))
}
// All done, store the genesis and flush to disk
log.Info("Configured new genesis block")

View file

@ -18,23 +18,31 @@ package main
import (
"bufio"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"sync"
"github.com/XinFinOrg/XDPoSChain/log"
)
// makeWizard creates and returns a new puppeth wizard.
func makeWizard(network string) *wizard {
func makeWizard(network string, inputPath string, outputPath string) *wizard {
conf := config{
Servers: make(map[string][]byte),
}
if inputPath != "" {
conf.inpath = inputPath
}
if outputPath != "" {
conf.path = outputPath
} else {
conf.path = filepath.Join(os.Getenv("HOME"), ".puppeth", network)
}
return &wizard{
network: network,
conf: config{
Servers: make(map[string][]byte),
},
network: network,
conf: conf,
servers: make(map[string]*sshClient),
services: make(map[string][]string),
in: bufio.NewReader(os.Stdin),
@ -44,6 +52,10 @@ func makeWizard(network string) *wizard {
// run displays some useful infos to the user, starting on the journey of
// setting up a new or managing an existing Ethereum private network.
func (w *wizard) run() {
if w.conf.inpath != "" { //don't use interactive mode if run with input option
w.makeGenesis()
return
}
fmt.Println("+-----------------------------------------------------------+")
fmt.Println("| Welcome to puppeth, your Ethereum private network manager |")
fmt.Println("| |")
@ -72,36 +84,6 @@ func (w *wizard) run() {
}
log.Info("Administering Ethereum network", "name", w.network)
// Load initial configurations and connect to all live servers
w.conf.path = filepath.Join(os.Getenv("HOME"), ".puppeth", w.network)
blob, err := os.ReadFile(w.conf.path)
if err != nil {
log.Warn("No previous configurations found", "path", w.conf.path)
} else if err := json.Unmarshal(blob, &w.conf); err != nil {
log.Crit("Previous configuration corrupted", "path", w.conf.path, "err", err)
} else {
// Dial all previously known servers concurrently
var pend sync.WaitGroup
for server, pubkey := range w.conf.Servers {
pend.Add(1)
go func(server string, pubkey []byte) {
defer pend.Done()
log.Info("Dialing previously configured server", "server", server)
client, err := dial(server, pubkey)
if err != nil {
log.Error("Previous server unreachable", "server", server, "err", err)
}
w.lock.Lock()
w.servers[server] = client
w.lock.Unlock()
}(server, pubkey)
}
pend.Wait()
w.networkStats()
}
// Basics done, loop ad infinitum about what to do
for {
fmt.Println()

View file

@ -72,6 +72,7 @@ var (
XDCNativeAddressBinary = HexToAddress("0x0000000000000000000000000000000000000001")
LendingLockAddressBinary = HexToAddress("0x0000000000000000000000000000000000000011")
MintedRecordAddressBinary = HexToAddress("0x000000000000000000000000000000000000009a")
SwapAddressBinary = HexToAddress("0x000000000000000000000000000000000000008e") //confirm Address
)
// Hash represents the 32 byte Keccak256 hash of arbitrary data.