Ori Newman 765dd170e4
Optimizations and header size reduce hardfork (#1853)
* 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 <>
2021-11-22 09:00:39 +02:00

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)
}
}