mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-03-30 15:08:33 +00:00

* Modify DefaultTimeout to 120 seconds A temporary workaround for nodes having trouble to sync (currently the download of pruning point related data during IBD takes more than 30 seconds) * Cache existence in reachability store * Cache block level in the header * Fix IBD indication on submit block * Add hardForkOmitGenesisFromParentsDAAScore logic * Fix NumThreads bug in the wallet * Get rid of ParentsAtLevel header method * Fix a bug in BuildPruningPointProof * Increase race detector timeout * Add cache to BuildPruningPointProof * Add comments and temp comment out go vet * Fix ParentsAtLevel * Dont fill empty parents * Change HardForkOmitGenesisFromParentsDAAScore in fast netsync test * Add --allow-submit-block-when-not-synced in stability tests * Fix TestPruning * Return fast tests * Fix off by one error on kaspawallet * Fetch only one block with trusted data at a time * Update fork DAA score * Don't ban for unexpected message type * Fix tests Co-authored-by: Michael Sutton <mikisiton2@gmail.com> Co-authored-by: Ori Newman <>
305 lines
8.3 KiB
Go
305 lines
8.3 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"github.com/kaspanet/go-secp256k1"
|
|
"github.com/kaspanet/kaspad/app/appmessage"
|
|
"github.com/kaspanet/kaspad/domain/consensus/utils/mining"
|
|
"github.com/kaspanet/kaspad/util"
|
|
"math/rand"
|
|
"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()
|
|
mining.SolveBlock(block, rand.New(rand.NewSource(time.Now().UnixNano())))
|
|
_, err = rpcClient.SubmitBlock(block)
|
|
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",
|
|
"--allow-submit-block-when-not-synced",
|
|
)
|
|
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") {
|
|
// TODO: Panic here and check why sometimes kaspad closes ungracefully
|
|
log.Errorf("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
|
|
}
|
|
mining.SolveBlock(block, rand.New(rand.NewSource(time.Now().UnixNano())))
|
|
_, err = rpcClient.SubmitBlock(block)
|
|
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
|
|
}
|
|
rd := rand.New(rand.NewSource(time.Now().UnixNano()))
|
|
for i := 0; i < numOfTips; i++ {
|
|
mining.SolveBlock(block, rd)
|
|
_, err = rpcClient.SubmitBlock(block)
|
|
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") {
|
|
// TODO: Panic here and check why sometimes miner closes ungracefully
|
|
log.Errorf("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(30 * 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
|
|
}
|
|
|
|
func killWithSigterm(cmd *exec.Cmd, commandName string) {
|
|
err := cmd.Process.Signal(syscall.SIGTERM)
|
|
if err != nil {
|
|
log.Criticalf("Error sending SIGKILL to %s", commandName)
|
|
}
|
|
}
|