diff --git a/cmd/opera/launcher/config.go b/cmd/opera/launcher/config.go index 6107efbbc..f4f0d9266 100644 --- a/cmd/opera/launcher/config.go +++ b/cmd/opera/launcher/config.go @@ -9,6 +9,7 @@ import ( "path/filepath" "reflect" "strings" + "time" "github.com/Fantom-foundation/lachesis-base/abft" "github.com/Fantom-foundation/lachesis-base/utils/cachescale" @@ -206,10 +207,7 @@ func loadAllConfigs(file string, cfg *config) error { func mayGetGenesisStore(ctx *cli.Context) *genesisstore.Store { switch { case ctx.GlobalIsSet(FakeNetFlag.Name): - _, num, err := parseFakeGen(ctx.GlobalString(FakeNetFlag.Name)) - if err != nil { - log.Crit("Invalid flag", "flag", FakeNetFlag.Name, "err", err) - } + num := getFakeValidatorCount(ctx) return makefakegenesis.FakeGenesisStore(num, futils.ToFtm(1000000000), futils.ToFtm(5000000)) case ctx.GlobalIsSet(GenesisFlag.Name): genesisPath := ctx.GlobalString(GenesisFlag.Name) @@ -271,10 +269,7 @@ func setDataDir(ctx *cli.Context, cfg *node.Config) { case ctx.GlobalIsSet(DataDirFlag.Name): cfg.DataDir = ctx.GlobalString(DataDirFlag.Name) case ctx.GlobalIsSet(FakeNetFlag.Name): - _, num, err := parseFakeGen(ctx.GlobalString(FakeNetFlag.Name)) - if err != nil { - log.Crit("Invalid flag", "flag", FakeNetFlag.Name, "err", err) - } + num := getFakeValidatorCount(ctx) cfg.DataDir = filepath.Join(defaultDataDir, fmt.Sprintf("fakenet-%d", num)) } } @@ -497,11 +492,13 @@ func mayMakeAllConfigs(ctx *cli.Context) (*config, error) { } if ctx.GlobalIsSet(FakeNetFlag.Name) { - _, num, err := parseFakeGen(ctx.GlobalString(FakeNetFlag.Name)) - if err != nil { - return nil, fmt.Errorf("invalid fakenet flag") + // don't wait long in fakenet + cfg.Emitter.EmitIntervals.Max = 10 * time.Second + cfg.Emitter.EmitIntervals.DoublesignProtection = 5 * time.Second + if getFakeValidatorCount(ctx) <= 1 { + // disable self-fork protection if fakenet 1/1 + cfg.Emitter.EmitIntervals.DoublesignProtection = 0 } - cfg.Emitter = emitter.FakeConfig(num) setBootnodes(ctx, []string{}, &cfg.Node) } else { // "asDefault" means set network defaults diff --git a/cmd/opera/launcher/fake.go b/cmd/opera/launcher/fake.go index 7c7140316..9e511c432 100644 --- a/cmd/opera/launcher/fake.go +++ b/cmd/opera/launcher/fake.go @@ -6,7 +6,10 @@ import ( "strconv" "strings" + "github.com/Fantom-foundation/go-opera/inter/validatorpk" "github.com/Fantom-foundation/lachesis-base/inter/idx" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" cli "gopkg.in/urfave/cli.v1" "github.com/Fantom-foundation/go-opera/integration/makefakegenesis" @@ -15,17 +18,34 @@ import ( // FakeNetFlag enables special testnet, where validators are automatically created var FakeNetFlag = cli.StringFlag{ Name: "fakenet", - Usage: "'n/N' - sets coinbase as fake n-th key from genesis of N validators.", + Usage: "'n/N' - sets coinbase as fake n-th key from genesis of [1..N] validators. Use n=0 for non-validator node", +} + +func getFakeValidatorID(ctx *cli.Context) idx.ValidatorID { + id, _, err := parseFakeGen(ctx.GlobalString(FakeNetFlag.Name)) + if err != nil { + log.Crit("Invalid flag", "flag", FakeNetFlag.Name, "err", err) + } + return id } func getFakeValidatorKey(ctx *cli.Context) *ecdsa.PrivateKey { id, _, err := parseFakeGen(ctx.GlobalString(FakeNetFlag.Name)) - if err != nil || id == 0 { - return nil + if err != nil { + log.Crit("Invalid flag", "flag", FakeNetFlag.Name, "err", err) } return makefakegenesis.FakeKey(id) } +func getFakeValidatorCount(ctx *cli.Context) idx.Validator { + _, num, err := parseFakeGen(ctx.GlobalString(FakeNetFlag.Name)) + if err != nil { + log.Crit("Invalid flag", "flag", FakeNetFlag.Name, "err", err) + return 0 + } + return num +} + func parseFakeGen(s string) (id idx.ValidatorID, num idx.Validator, err error) { parts := strings.SplitN(s, "/", 2) if len(parts) != 2 { @@ -41,11 +61,17 @@ func parseFakeGen(s string) (id idx.ValidatorID, num idx.Validator, err error) { id = idx.ValidatorID(u32) u32, err = strconv.ParseUint(parts[1], 10, 32) - num = idx.Validator(u32) - if num < 0 || idx.Validator(id) > num { - err = fmt.Errorf("key-num should be in range from 1 to validators (/), or should be zero for non-validator node") + if err != nil { return } + num = idx.Validator(u32) return } + +func fakeValidatorPubKey(id idx.ValidatorID) validatorpk.PubKey { + return validatorpk.PubKey{ + Raw: crypto.FromECDSAPub(&makefakegenesis.FakeKey(id).PublicKey), + Type: validatorpk.Types.Secp256k1, + } +} diff --git a/cmd/opera/launcher/fake_test.go b/cmd/opera/launcher/fake_test.go index 74da25047..a5c9211f7 100644 --- a/cmd/opera/launcher/fake_test.go +++ b/cmd/opera/launcher/fake_test.go @@ -7,10 +7,8 @@ import ( "testing" "time" - "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" - "github.com/Fantom-foundation/go-opera/integration/makefakegenesis" "github.com/Fantom-foundation/go-opera/inter/validatorpk" ) @@ -101,18 +99,11 @@ To exit, press ctrl-d } } -func readFakeValidator(fakenet string) *validatorpk.PubKey { - n, _, err := parseFakeGen(fakenet) +func readFakeValidator(fakenet string) validatorpk.PubKey { + id, _, err := parseFakeGen(fakenet) if err != nil { panic(err) } - if n < 1 { - return nil - } - - return &validatorpk.PubKey{ - Raw: crypto.FromECDSAPub(&makefakegenesis.FakeKey(n).PublicKey), - Type: validatorpk.Types.Secp256k1, - } + return fakeValidatorPubKey(id) } diff --git a/cmd/opera/launcher/launcher.go b/cmd/opera/launcher/launcher.go index f23690294..10e9b291f 100644 --- a/cmd/opera/launcher/launcher.go +++ b/cmd/opera/launcher/launcher.go @@ -14,13 +14,12 @@ import ( "github.com/ethereum/go-ethereum/console/prompt" "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/log" + evmetrics "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p/discover/discfilter" "github.com/ethereum/go-ethereum/params" "gopkg.in/urfave/cli.v1" - evmetrics "github.com/ethereum/go-ethereum/metrics" - "github.com/Fantom-foundation/go-opera/cmd/opera/launcher/metrics" "github.com/Fantom-foundation/go-opera/cmd/opera/launcher/tracing" "github.com/Fantom-foundation/go-opera/debug" @@ -29,6 +28,7 @@ import ( "github.com/Fantom-foundation/go-opera/gossip" "github.com/Fantom-foundation/go-opera/gossip/emitter" "github.com/Fantom-foundation/go-opera/integration" + "github.com/Fantom-foundation/go-opera/inter/validatorpk" "github.com/Fantom-foundation/go-opera/opera/genesis" "github.com/Fantom-foundation/go-opera/opera/genesisstore" "github.com/Fantom-foundation/go-opera/utils/errlock" @@ -322,9 +322,11 @@ func makeNode(ctx *cli.Context, cfg *config, genesisStore *genesisstore.Store) ( valKeystore := valkeystore.NewDefaultFileKeystore(path.Join(getValKeystoreDir(cfg.Node), "validator")) valPubkey := cfg.Emitter.Validator.PubKey - if key := getFakeValidatorKey(ctx); key != nil && cfg.Emitter.Validator.ID != 0 { + + if ctx.GlobalIsSet(FakeNetFlag.Name) && !valPubkey.Empty() { + key := getFakeValidatorKey(ctx) addFakeValidatorKey(ctx, key, valPubkey, valKeystore) - coinbase := integration.SetAccountKey(stack.AccountManager(), key, "fakepassword") + coinbase := integration.SetAccountKey(stack.AccountManager(), key, validatorpk.FakePassword) log.Info("Unlocked fake validator account", "address", coinbase.Address.Hex()) } diff --git a/cmd/opera/launcher/validator.go b/cmd/opera/launcher/validator.go index 1f128c626..382c4aa94 100644 --- a/cmd/opera/launcher/validator.go +++ b/cmd/opera/launcher/validator.go @@ -7,7 +7,6 @@ import ( "github.com/Fantom-foundation/lachesis-base/inter/idx" "github.com/Fantom-foundation/go-opera/gossip/emitter" - "github.com/Fantom-foundation/go-opera/integration/makefakegenesis" "github.com/Fantom-foundation/go-opera/inter/validatorpk" ) @@ -34,18 +33,14 @@ var validatorPasswordFlag = cli.StringFlag{ func setValidator(ctx *cli.Context, cfg *emitter.Config) error { // Extract the current validator address, new flag overriding legacy one if ctx.GlobalIsSet(FakeNetFlag.Name) { - id, num, err := parseFakeGen(ctx.GlobalString(FakeNetFlag.Name)) - if err != nil { - return err + id := getFakeValidatorID(ctx) + if id != 0 { + if ctx.GlobalIsSet(validatorIDFlag.Name) { + return errors.New("specified validator ID with both --fakenet and --validator.id") + } + cfg.Validator.ID = id + cfg.Validator.PubKey = fakeValidatorPubKey(id) } - - if ctx.GlobalIsSet(validatorIDFlag.Name) && id != 0 { - return errors.New("specified validator ID with both --fakenet and --validator.id") - } - - cfg.Validator.ID = id - validators := makefakegenesis.GetFakeValidators(num) - cfg.Validator.PubKey = validators.Map()[cfg.Validator.ID].PubKey } if ctx.GlobalIsSet(validatorIDFlag.Name) { diff --git a/demo/README.md b/demo/README.md index 598ec616c..c145fb3e0 100755 --- a/demo/README.md +++ b/demo/README.md @@ -1,15 +1,17 @@ # Demo -This directory contains the scripts to run fakenet (private testing network) with N local nodes, +This directory contains the scripts to run fakenet (private testing network) with N+M local nodes, primarily for benchmarking purposes. ## Scripts - start network: `./start.sh`; - stop network: `./stop.sh`; + - run new validator node: `./run-validator.sh 3`; - clean data and logs: `./clean.sh`; -You can specify number of genesis validators by setting N environment variable. +You can specify number of genesis validators by setting N environment variable + and M for additional nodes. ## Balance transfer example diff --git a/demo/_params.sh b/demo/_params.sh index d5299a7d9..1bb6619f3 100755 --- a/demo/_params.sh +++ b/demo/_params.sh @@ -1,17 +1,41 @@ #!/usr/bin/env bash declare -ri N="${N:-3}" -declare -ri M="${M:-2}" +declare -ri M="${M:-0}" declare -r TAG="${TAG:-latest}" PORT_BASE=3000 RPCP_BASE=4000 WSP_BASE=4500 +data_dir() { + local i=$1 + echo "${PWD}/opera$i.datadir" +} + +run_opera_node() { + local i=$1 + local ACC=$(($i+1)) + local DATADIR="$(data_dir $i)" + local PORT=$(($PORT_BASE+$i)) + local RPCP=$(($RPCP_BASE+$i)) + local WSP=$(($WSP_BASE+$i)) + + ../build/demo_opera \ + --datadir=${DATADIR} \ + --fakenet=${ACC}/$N \ + --port=${PORT} \ + --nat extip:127.0.0.1 \ + --http --http.addr="127.0.0.1" --http.port=${RPCP} --http.corsdomain="*" --http.api="eth,debug,net,admin,web3,personal,txpool,ftm,dag" \ + --ws --ws.addr="127.0.0.1" --ws.port=${WSP} --ws.origins="*" --ws.api="eth,debug,net,admin,web3,personal,txpool,ftm,dag" \ + --metrics --metrics.addr=127.0.0.1 --metrics.port=$(($RPCP+1100)) \ + --verbosity=3 --tracing >> opera$i.log 2>&1 +} + attach_and_exec() { local i=$1 - local CMD=$2 - local RPCP=$(($RPCP_BASE+$i)) + local DATADIR="$(data_dir $i)" + local CMD=$2 for attempt in $(seq 40) do @@ -20,7 +44,7 @@ attach_and_exec() { echo " - attempt ${attempt}: " >&2 fi - res=$(../build/demo_opera --exec "${CMD}" attach http://127.0.0.1:${RPCP} 2> /dev/null) + res=$(../build/demo_opera --exec "${CMD}" attach ${DATADIR}/opera.ipc 2> /dev/null) if [ $? -eq 0 ] then #echo "success" >&2 diff --git a/demo/run-validator.sh b/demo/run-validator.sh new file mode 100755 index 000000000..d73d13ade --- /dev/null +++ b/demo/run-validator.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +cd $(dirname $0) +. ./_params.sh + +set -e + +TOTAL=$((N+M)) +i=$1 +DATADIR="$(data_dir $i)" +if [ -d ${DATADIR} ]; then + echo "${DATADIR} already exists!" + exit 1 +fi +mkdir -p "${DATADIR}" + +echo -e "\nStart additional node $i:\n" +run_opera_node $i & +echo -e "\tnode$i ok" + +echo -e "\nConnect to existing nodes:\n" +for ((n=0;n<$TOTAL;n+=1)) +do + enode=$(attach_and_exec $n 'admin.nodeInfo.enode') + echo " p2p address = ${enode}" + + echo " connecting node-$i to node-$n:" + res=$(attach_and_exec $i "admin.addPeer(${enode})") + echo " result = ${res}" +done +sleep 5 + +VPKEY=$(grep "Unlocked validator key" opera$i.log | head -n 1 | sed 's/.* pubkey=\(0x.*\)$/\1/') +VADDR=$(grep "Unlocked fake validator account" opera$i.log | head -n 1 | sed 's/.* address=\(0x.*\)$/\1/') + +echo -e "\nFund new validator acc ${VADDR}:\n" +attach_and_exec 0 "ftm.sendTransaction({from: personal.listAccounts[0], to: \"${VADDR}\", value: web3.toWei(\"510000.0\", \"ftm\")})" +sleep 5 + +echo -e "\nCall SFC to create validator ${VPKEY}:\n" +../build/demo_opera attach "${DATADIR}/opera.ipc" << JS + abi = JSON.parse('[{"constant":false,"inputs":[{"internalType":"bytes","name":"pubkey","type":"bytes"}],"name":"createValidator","outputs":[],"payable":true,"stateMutability":"payable","type":"function"}]'); + sfcc = web3.ftm.contract(abi).at("0xfc00face00000000000000000000000000000000"); + sfcc.createValidator("${VPKEY}", {from:"${VADDR}", value: web3.toWei("500000.0", "ftm")}); +JS diff --git a/demo/start.sh b/demo/start.sh index 763c6a529..c6462ca75 100755 --- a/demo/start.sh +++ b/demo/start.sh @@ -4,45 +4,27 @@ cd $(dirname $0) set -e -echo -e "\nStart $N nodes:\n" +TOTAL=$((N+M)) +echo -e "\nStart ${TOTAL} validators on $N-validator genesis:\n" go build -o ../build/demo_opera ../cmd/opera - rm -f ./transactions.rlp -for ((i=0;i<$N;i+=1)) -do - DATADIR="${PWD}/opera$i.datadir" - mkdir -p ${DATADIR} - - PORT=$(($PORT_BASE+$i)) - RPCP=$(($RPCP_BASE+$i)) - WSP=$(($WSP_BASE+$i)) - ACC=$(($i+1)) - (../build/demo_opera \ - --datadir=${DATADIR} \ - --fakenet=${ACC}/$N \ - --port=${PORT} \ - --nat extip:127.0.0.1 \ - --http --http.addr="127.0.0.1" --http.port=${RPCP} --http.corsdomain="*" --http.api="eth,debug,net,admin,web3,personal,txpool,ftm,dag" \ - --ws --ws.addr="127.0.0.1" --ws.port=${WSP} --ws.origins="*" --ws.api="eth,debug,net,admin,web3,personal,txpool,ftm,dag" \ - --metrics --metrics.addr=127.0.0.1 --metrics.port=$(($RPCP+1100)) \ - --verbosity=3 --tracing >> opera$i.log 2>&1)& +for ((i=0;i<${TOTAL};i+=1)) +do + run_opera_node $i & echo -e "\tnode$i ok" done echo -e "\nConnect nodes to ring:\n" -for ((i=0;i<$N;i+=1)) +for ((i=0;i<${TOTAL};i+=1)) do - for ((n=0;n<$M;n+=1)) - do - j=$(((i+n+1) % N)) + j=$(((i+n+1) % TOTAL)) enode=$(attach_and_exec $j 'admin.nodeInfo.enode') - echo " p2p address = ${enode}" + echo " p2p address = ${enode}" - echo " connecting node-$i to node-$j:" - res=$(attach_and_exec $i "admin.addPeer(${enode})") - echo " result = ${res}" - done + echo " connecting node-$i to node-$j:" + res=$(attach_and_exec $i "admin.addPeer(${enode})") + echo " result = ${res}" done diff --git a/evmcore/apply_fake_genesis.go b/evmcore/apply_fake_genesis.go index 8abd7f2ae..2a1940589 100644 --- a/evmcore/apply_fake_genesis.go +++ b/evmcore/apply_fake_genesis.go @@ -94,7 +94,18 @@ func MustApplyFakeGenesis(statedb *state.StateDB, time inter.Timestamp, balances // FakeKey gets n-th fake private key. func FakeKey(n uint32) *ecdsa.PrivateKey { - var keys = [100]string{ + /* NOTE: hardcoded keys are from legacy (golang 1.17) + func FakeKey(n uint32) *ecdsa.PrivateKey { + reader := rand.New("math/rand".NewSource(int64(n))) + key, err := ecdsa.GenerateKey(crypto.S256(), reader) + if err != nil { + panic(err) + } + return key + } + */ + var keys = [101]string{ + "0x41d3ff12045b73c870529fe44f70dca2745bafbe1698ffc3c8759eef3cfbaee1", "0x163f5f0f9a621d72fedd85ffca3d08d131ab4e812181e0d30ffd1c885d20aac7", "0x3144c0aa4ced56dc15c79b045bc5559a5ac9363d98db6df321fe3847a103740f", "0x04a531f967898df5dbe223b67989b248e23c1c356a3f6717775cccb7fe53482c", @@ -197,10 +208,10 @@ func FakeKey(n uint32) *ecdsa.PrivateKey { "0x4203c438d4e94bf4a595794b5f5c2882f959face730abb7a7b8acb462c8e138d", } - if n == 0 || n > 100 { + if 0 > n || n >= uint32(len(keys)) { panic(errors.New("validator num is out of range")) } - key, _ := crypto.ToECDSA(hexutil.MustDecode(keys[n-1])) + key, _ := crypto.ToECDSA(hexutil.MustDecode(keys[n])) return key } diff --git a/gossip/emitter/config.go b/gossip/emitter/config.go index a56c072d5..c6b64ea55 100644 --- a/gossip/emitter/config.go +++ b/gossip/emitter/config.go @@ -92,15 +92,3 @@ func (cfg EmitIntervals) RandomizeEmitTime(r *rand.Rand) EmitIntervals { } return config } - -// FakeConfig returns the testing configurations for the events emitter. -func FakeConfig(num idx.Validator) Config { - cfg := DefaultConfig() - cfg.EmitIntervals.Max = 10 * time.Second // don't wait long in fakenet - cfg.EmitIntervals.DoublesignProtection = cfg.EmitIntervals.Max / 2 - if num <= 1 { - // disable self-fork protection if fakenet 1/1 - cfg.EmitIntervals.DoublesignProtection = 0 - } - return cfg -}