mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-10-14 00:59:33 +00:00
New stability test - many tips (#1694)
* Unfinished code. * Update the testnet version to testnet-5. (#1683) * Generalize stability-tests/docker/Dockerfile. (#1685) * Committed for rebasing. * Adds stability-test many-tips, which tests kaspad handling with many tips in the DAG. * Delete manytips_test.go. * Add timeout to the test and create only one RPC client. * Place the spawn before the for loop and remove a redundant condition. Co-authored-by: tal <tal@daglabs.com> Co-authored-by: stasatdaglabs <39559713+stasatdaglabs@users.noreply.github.com>
This commit is contained in:
parent
4df283934a
commit
28a8e96e65
51
stability-tests/many-tips/config.go
Normal file
51
stability-tests/many-tips/config.go
Normal file
@ -0,0 +1,51 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/jessevdk/go-flags"
|
||||
"github.com/kaspanet/kaspad/infrastructure/config"
|
||||
"github.com/kaspanet/kaspad/stability-tests/common"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultLogFilename = "many_tips.log"
|
||||
defaultErrLogFilename = "many_tips_err.log"
|
||||
)
|
||||
|
||||
var (
|
||||
// Default configuration options
|
||||
defaultLogFile = filepath.Join(common.DefaultAppDir, defaultLogFilename)
|
||||
defaultErrLogFile = filepath.Join(common.DefaultAppDir, defaultErrLogFilename)
|
||||
)
|
||||
|
||||
type configFlags struct {
|
||||
LogLevel string `short:"d" long:"loglevel" description:"Set log level {trace, debug, info, warn, error, critical}"`
|
||||
NumberOfBlocks uint64 `short:"n" long:"numblocks" description:"Number of blocks to mine" required:"false"`
|
||||
Profile string `long:"profile" description:"Enable HTTP profiling on given port -- NOTE port must be between 1024 and 65536"`
|
||||
config.NetworkFlags
|
||||
}
|
||||
|
||||
var cfg *configFlags
|
||||
|
||||
func activeConfig() *configFlags {
|
||||
return cfg
|
||||
}
|
||||
|
||||
func parseConfig() error {
|
||||
cfg = &configFlags{}
|
||||
parser := flags.NewParser(cfg, flags.PrintErrors|flags.HelpFlag)
|
||||
_, err := parser.Parse()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = cfg.ResolveNetwork(parser)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
initLog(defaultLogFile, defaultErrLogFile)
|
||||
|
||||
return nil
|
||||
}
|
29
stability-tests/many-tips/log.go
Normal file
29
stability-tests/many-tips/log.go
Normal file
@ -0,0 +1,29 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/infrastructure/logger"
|
||||
"github.com/kaspanet/kaspad/stability-tests/common"
|
||||
"github.com/kaspanet/kaspad/util/panics"
|
||||
"os"
|
||||
)
|
||||
|
||||
var (
|
||||
backendLog = logger.NewBackend()
|
||||
log = backendLog.Logger("MATS")
|
||||
spawn = panics.GoroutineWrapperFunc(log)
|
||||
)
|
||||
|
||||
func initLog(logFile, errLogFile string) {
|
||||
level := logger.LevelDebug
|
||||
if activeConfig().LogLevel != "" {
|
||||
var ok bool
|
||||
level, ok = logger.LevelFromString(activeConfig().LogLevel)
|
||||
if !ok {
|
||||
fmt.Fprintf(os.Stderr, "Log level %s doesn't exists", activeConfig().LogLevel)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
log.SetLevel(level)
|
||||
common.InitBackend(backendLog, logFile, errLogFile)
|
||||
}
|
319
stability-tests/many-tips/main.go
Normal file
319
stability-tests/many-tips/main.go
Normal file
@ -0,0 +1,319 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kaspanet/go-secp256k1"
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/pow"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/util/difficulty"
|
||||
"math"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/kaspanet/kaspad/stability-tests/common"
|
||||
"github.com/kaspanet/kaspad/stability-tests/common/rpc"
|
||||
"github.com/kaspanet/kaspad/util/panics"
|
||||
"github.com/kaspanet/kaspad/util/profiling"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const rpcAddress = "localhost:9000"
|
||||
|
||||
func main() {
|
||||
err := realMain()
|
||||
if err != nil {
|
||||
log.Criticalf("An error occurred: %+v", err)
|
||||
backendLog.Close()
|
||||
os.Exit(1)
|
||||
}
|
||||
backendLog.Close()
|
||||
}
|
||||
|
||||
func realMain() error {
|
||||
defer panics.HandlePanic(log, "many-tips-main", nil)
|
||||
err := parseConfig()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error in parseConfig")
|
||||
}
|
||||
common.UseLogger(backendLog, log.Level())
|
||||
cfg := activeConfig()
|
||||
if cfg.Profile != "" {
|
||||
profiling.Start(cfg.Profile, log)
|
||||
}
|
||||
teardown, err := startNode()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error in startNode")
|
||||
}
|
||||
defer teardown()
|
||||
|
||||
miningAddress, err := generateAddress()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Failed generate a mining address")
|
||||
}
|
||||
rpcClient, err := rpc.ConnectToRPC(&rpc.Config{
|
||||
RPCServer: rpcAddress,
|
||||
}, activeConfig().NetParams())
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error connecting to RPC server")
|
||||
}
|
||||
defer rpcClient.Disconnect()
|
||||
|
||||
// Mine block that its timestamp is one second after the genesis timestamp.
|
||||
blockTemplate, err := rpcClient.GetBlockTemplate(miningAddress.EncodeAddress())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
block, err := appmessage.RPCBlockToDomainBlock(blockTemplate.Block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mutableHeader := block.Header.ToMutable()
|
||||
genesisTimestamp := activeConfig().NetParams().GenesisBlock.Header.TimeInMilliseconds()
|
||||
mutableHeader.SetTimeInMilliseconds(genesisTimestamp + 1000)
|
||||
block.Header = mutableHeader.ToImmutable()
|
||||
solvedBlock := solveBlock(block)
|
||||
_, err = rpcClient.SubmitBlock(solvedBlock)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// mine block at the current time
|
||||
err = mineBlock(rpcClient, miningAddress)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error in mineBlock")
|
||||
}
|
||||
// Mine on top of it 10k tips.
|
||||
numOfTips := 10000
|
||||
err = mineTips(numOfTips, miningAddress, rpcClient)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error in mineTips")
|
||||
}
|
||||
// Mines until the DAG will have only one tip.
|
||||
err = mineLoopUntilHavingOnlyOneTipInDAG(rpcClient, miningAddress)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error in mineLoop")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func startNode() (teardown func(), err error) {
|
||||
log.Infof("Starting node")
|
||||
dataDir, err := common.TempDir("kaspad-datadir")
|
||||
if err != nil {
|
||||
panic(errors.Wrapf(err, "Error in Tempdir"))
|
||||
}
|
||||
log.Infof("kaspad datadir: %s", dataDir)
|
||||
|
||||
kaspadCmd, err := common.StartCmd("KASPAD",
|
||||
"kaspad",
|
||||
common.NetworkCliArgumentFromNetParams(activeConfig().NetParams()),
|
||||
"--appdir", dataDir,
|
||||
"--logdir", dataDir,
|
||||
"--rpclisten", rpcAddress,
|
||||
"--loglevel", "debug",
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
shutdown := uint64(0)
|
||||
|
||||
processesStoppedWg := sync.WaitGroup{}
|
||||
processesStoppedWg.Add(1)
|
||||
spawn("startNode-kaspadCmd.Wait", func() {
|
||||
err := kaspadCmd.Wait()
|
||||
if err != nil {
|
||||
if atomic.LoadUint64(&shutdown) == 0 {
|
||||
panics.Exit(log, fmt.Sprintf("kaspadCmd closed unexpectedly: %s. See logs at: %s", err, dataDir))
|
||||
}
|
||||
if !strings.Contains(err.Error(), "signal: killed") {
|
||||
panics.Exit(log, fmt.Sprintf("kaspadCmd closed with an error: %s. See logs at: %s", err, dataDir))
|
||||
}
|
||||
}
|
||||
processesStoppedWg.Done()
|
||||
})
|
||||
return func() {
|
||||
log.Infof("defer start-node")
|
||||
atomic.StoreUint64(&shutdown, 1)
|
||||
killWithSigterm(kaspadCmd, "kaspadCmd")
|
||||
|
||||
processesStoppedChan := make(chan struct{})
|
||||
spawn("startNode-processStoppedWg.Wait", func() {
|
||||
processesStoppedWg.Wait()
|
||||
processesStoppedChan <- struct{}{}
|
||||
})
|
||||
|
||||
const timeout = 10 * time.Second
|
||||
select {
|
||||
case <-processesStoppedChan:
|
||||
case <-time.After(timeout):
|
||||
panics.Exit(log, fmt.Sprintf("Processes couldn't be closed after %s", timeout))
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
func generateAddress() (util.Address, error) {
|
||||
privateKey, err := secp256k1.GenerateSchnorrKeyPair()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pubKey, err := privateKey.SchnorrPublicKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pubKeySerialized, err := pubKey.Serialize()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return util.NewAddressPublicKey(pubKeySerialized[:], activeConfig().ActiveNetParams.Prefix)
|
||||
}
|
||||
|
||||
func mineBlock(rpcClient *rpc.Client, miningAddress util.Address) error {
|
||||
blockTemplate, err := rpcClient.GetBlockTemplate(miningAddress.EncodeAddress())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
block, err := appmessage.RPCBlockToDomainBlock(blockTemplate.Block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
solvedBlock := solveBlock(block)
|
||||
_, err = rpcClient.SubmitBlock(solvedBlock)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func mineTips(numOfTips int, miningAddress util.Address, rpcClient *rpc.Client) error {
|
||||
blockTemplate, err := rpcClient.GetBlockTemplate(miningAddress.EncodeAddress())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
block, err := appmessage.RPCBlockToDomainBlock(blockTemplate.Block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := 0; i < numOfTips; i++ {
|
||||
solvedBlock := solveBlock(block)
|
||||
_, err = rpcClient.SubmitBlock(solvedBlock)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (i%1000 == 0) && (i != 0) {
|
||||
log.Infof("Mined %d blocks.", i)
|
||||
}
|
||||
}
|
||||
dagInfo, err := rpcClient.GetBlockDAGInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infof("There are %d tips in the DAG", len(dagInfo.TipHashes))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Checks how many blocks were mined and how long it took to get only one tip in the DAG (after having 10k tips in the DAG).
|
||||
func mineLoopUntilHavingOnlyOneTipInDAG(rpcClient *rpc.Client, miningAddress util.Address) error {
|
||||
dagInfo, err := rpcClient.GetBlockDAGInfo()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error in GetBlockDAGInfo")
|
||||
}
|
||||
numOfBlocksBeforeMining := dagInfo.BlockCount
|
||||
|
||||
kaspaMinerCmd, err := common.StartCmd("MINER",
|
||||
"kaspaminer",
|
||||
common.NetworkCliArgumentFromNetParams(activeConfig().NetParams()),
|
||||
"-s", rpcAddress,
|
||||
"--mine-when-not-synced",
|
||||
"--miningaddr", miningAddress.EncodeAddress(),
|
||||
"--target-blocks-per-second=0",
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
startMiningTime := time.Now()
|
||||
shutdown := uint64(0)
|
||||
|
||||
spawn("kaspa-miner-Cmd.Wait", func() {
|
||||
err := kaspaMinerCmd.Wait()
|
||||
if err != nil {
|
||||
if atomic.LoadUint64(&shutdown) == 0 {
|
||||
panics.Exit(log, fmt.Sprintf("minerCmd closed unexpectedly: %s.", err))
|
||||
}
|
||||
if !strings.Contains(err.Error(), "signal: killed") {
|
||||
panics.Exit(log, fmt.Sprintf("minerCmd closed with an error: %s.", err))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
numOfTips, err := getCurrentTipsLength(rpcClient)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Error in getCurrentTipsLength")
|
||||
}
|
||||
hasTimedOut := false
|
||||
spawn("ChecksIfTimeIsUp", func() {
|
||||
timer := time.NewTimer(20 * time.Minute)
|
||||
<-timer.C
|
||||
hasTimedOut = true
|
||||
})
|
||||
for numOfTips > 1 && !hasTimedOut {
|
||||
time.Sleep(1 * time.Second)
|
||||
numOfTips, err = getCurrentTipsLength(rpcClient)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Error in getCurrentTipsLength")
|
||||
}
|
||||
}
|
||||
|
||||
if hasTimedOut {
|
||||
return errors.Errorf("Out of time - the graph still has more than one tip.")
|
||||
}
|
||||
duration := time.Since(startMiningTime)
|
||||
log.Infof("It took %s until there was only one tip in the DAG after having 10k tips.", duration)
|
||||
dagInfo, err = rpcClient.GetBlockDAGInfo()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Failed in GetBlockDAGInfo")
|
||||
}
|
||||
numOfAddedBlocks := dagInfo.BlockCount - numOfBlocksBeforeMining
|
||||
log.Infof("Added %d blocks to reach this.", numOfAddedBlocks)
|
||||
atomic.StoreUint64(&shutdown, 1)
|
||||
killWithSigterm(kaspaMinerCmd, "kaspaMinerCmd")
|
||||
return nil
|
||||
}
|
||||
|
||||
func getCurrentTipsLength(rpcClient *rpc.Client) (int, error) {
|
||||
dagInfo, err := rpcClient.GetBlockDAGInfo()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
log.Infof("Current number of tips is %d", len(dagInfo.TipHashes))
|
||||
return len(dagInfo.TipHashes), nil
|
||||
}
|
||||
|
||||
var latestNonce uint64 = 0 // Use to make the nonce unique.
|
||||
// The nonce is unique for each block in this function.
|
||||
func solveBlock(block *externalapi.DomainBlock) *externalapi.DomainBlock {
|
||||
targetDifficulty := difficulty.CompactToBig(block.Header.Bits())
|
||||
headerForMining := block.Header.ToMutable()
|
||||
maxUInt64 := uint64(math.MaxUint64)
|
||||
for i := latestNonce; i < maxUInt64; i++ {
|
||||
headerForMining.SetNonce(i)
|
||||
if pow.CheckProofOfWorkWithTarget(headerForMining, targetDifficulty) {
|
||||
block.Header = headerForMining.ToImmutable()
|
||||
latestNonce = i + 1
|
||||
return block
|
||||
}
|
||||
}
|
||||
panic("Failed to solve block!")
|
||||
}
|
||||
|
||||
func killWithSigterm(cmd *exec.Cmd, commandName string) {
|
||||
err := cmd.Process.Signal(syscall.SIGTERM)
|
||||
if err != nil {
|
||||
log.Criticalf("Error sending SIGKILL to %s", commandName)
|
||||
}
|
||||
}
|
13
stability-tests/many-tips/run/run.sh
Executable file
13
stability-tests/many-tips/run/run.sh
Executable file
@ -0,0 +1,13 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
many-tips --devnet -n=1000 --profile=7000
|
||||
TEST_EXIT_CODE=$?
|
||||
|
||||
echo "Exit code: $TEST_EXIT_CODE"
|
||||
|
||||
if [ $TEST_EXIT_CODE -eq 0 ]; then
|
||||
echo "many-tips test: PASSED"
|
||||
exit 0
|
||||
fi
|
||||
echo "many-tips test: FAILED"
|
||||
exit 1
|
Loading…
x
Reference in New Issue
Block a user