Merge branch 'dev' into fix-ExtractAtomicSwapDataPushes

This commit is contained in:
Ori Newman 2023-12-11 16:19:00 +02:00 committed by GitHub
commit 0ab0e16ac9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 448 additions and 52 deletions

View File

@ -23,7 +23,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v2
with:
go-version: 1.19
go-version: 1.21
- name: Build on Linux
if: runner.os == 'Linux'

View File

@ -57,7 +57,7 @@ type sendConfig struct {
DaemonAddress string `long:"daemonaddress" short:"d" description:"Wallet daemon server to connect to"`
ToAddress string `long:"to-address" short:"t" description:"The public address to send Kaspa to" required:"true"`
FromAddresses []string `long:"from-address" short:"a" description:"Specific public address to send Kaspa from. Use multiple times to accept several addresses" required:"false"`
SendAmount float64 `long:"send-amount" short:"v" description:"An amount to send in Kaspa (e.g. 1234.12345678)"`
SendAmount string `long:"send-amount" short:"v" description:"An amount to send in Kaspa (e.g. 1234.12345678)"`
IsSendAll bool `long:"send-all" description:"Send all the Kaspa in the wallet (mutually exclusive with --send-amount)"`
UseExistingChangeAddress bool `long:"use-existing-change-address" short:"u" description:"Will use an existing change address (in case no change address was ever used, it will use a new one)"`
Verbose bool `long:"show-serialized" short:"s" description:"Show a list of hex encoded sent transactions"`
@ -74,7 +74,7 @@ type createUnsignedTransactionConfig struct {
DaemonAddress string `long:"daemonaddress" short:"d" description:"Wallet daemon server to connect to"`
ToAddress string `long:"to-address" short:"t" description:"The public address to send Kaspa to" required:"true"`
FromAddresses []string `long:"from-address" short:"a" description:"Specific public address to send Kaspa from. Use multiple times to accept several addresses" required:"false"`
SendAmount float64 `long:"send-amount" short:"v" description:"An amount to send in Kaspa (e.g. 1234.12345678)"`
SendAmount string `long:"send-amount" short:"v" description:"An amount to send in Kaspa (e.g. 1234.12345678)"`
IsSendAll bool `long:"send-all" description:"Send all the Kaspa in the wallet (mutually exclusive with --send-amount)"`
UseExistingChangeAddress bool `long:"use-existing-change-address" short:"u" description:"Will use an existing change address (in case no change address was ever used, it will use a new one)"`
config.NetworkFlags
@ -296,8 +296,8 @@ func parseCommandLine() (subCommand string, config interface{}) {
}
func validateCreateUnsignedTransactionConf(conf *createUnsignedTransactionConfig) error {
if (!conf.IsSendAll && conf.SendAmount == 0) ||
(conf.IsSendAll && conf.SendAmount > 0) {
if (!conf.IsSendAll && conf.SendAmount == "") ||
(conf.IsSendAll && conf.SendAmount != "") {
return errors.New("exactly one of '--send-amount' or '--all' must be specified")
}
@ -305,8 +305,8 @@ func validateCreateUnsignedTransactionConf(conf *createUnsignedTransactionConfig
}
func validateSendConfig(conf *sendConfig) error {
if (!conf.IsSendAll && conf.SendAmount == 0) ||
(conf.IsSendAll && conf.SendAmount > 0) {
if (!conf.IsSendAll && conf.SendAmount == "") ||
(conf.IsSendAll && conf.SendAmount != "") {
return errors.New("exactly one of '--send-amount' or '--all' must be specified")
}

View File

@ -7,7 +7,7 @@ import (
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/client"
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
"github.com/kaspanet/kaspad/cmd/kaspawallet/utils"
)
func createUnsignedTransaction(conf *createUnsignedTransactionConfig) error {
@ -20,7 +20,12 @@ func createUnsignedTransaction(conf *createUnsignedTransactionConfig) error {
ctx, cancel := context.WithTimeout(context.Background(), daemonTimeout)
defer cancel()
sendAmountSompi := uint64(conf.SendAmount * constants.SompiPerKaspa)
sendAmountSompi, err := utils.KasToSompi(conf.SendAmount)
if err != nil {
return err
}
response, err := daemonClient.CreateUnsignedTransactions(ctx, &pb.CreateUnsignedTransactionsRequest{
From: conf.FromAddresses,
Address: conf.ToAddress,

View File

@ -10,7 +10,7 @@ import (
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
"github.com/kaspanet/kaspad/cmd/kaspawallet/keys"
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
"github.com/kaspanet/kaspad/cmd/kaspawallet/utils"
"github.com/pkg/errors"
)
@ -35,7 +35,11 @@ func send(conf *sendConfig) error {
var sendAmountSompi uint64
if !conf.IsSendAll {
sendAmountSompi = uint64(conf.SendAmount * constants.SompiPerKaspa)
sendAmountSompi, err = utils.KasToSompi(conf.SendAmount)
if err != nil {
return err
}
}
createUnsignedTransactionsResponse, err :=

View File

@ -2,8 +2,13 @@ package utils
import (
"fmt"
"math"
"regexp"
"strconv"
"strings"
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
"github.com/pkg/errors"
)
// FormatKas takes the amount of sompis as uint64, and returns amount of KAS with 8 decimal places
@ -14,3 +19,50 @@ func FormatKas(amount uint64) string {
}
return res
}
// KasToSompi takes in a string representation of the Kas value to convert to Sompi
func KasToSompi(amount string) (uint64, error) {
err := validateKASAmountFormat(amount)
if err != nil {
return 0, err
}
// after validation, amount can only be either an int OR
// a float with an int component and decimal places
parts := strings.Split(amount, ".")
amountStr := ""
if constants.SompiPerKaspa%10 != 0 {
return 0, errors.Errorf("Unable to convert to sompi when SompiPerKaspa is not a multiple of 10")
}
decimalPlaces := int(math.Log10(constants.SompiPerKaspa))
decimalStr := ""
if len(parts) == 2 {
decimalStr = parts[1]
}
amountStr = fmt.Sprintf("%s%-*s", parts[0], decimalPlaces, decimalStr) // Padded with spaces at the end to fill for missing decimals: Sample "0.01234 "
amountStr = strings.ReplaceAll(amountStr, " ", "0") // Make the spaces be 0s. Sample "0.012340000"
convertedAmount, err := strconv.ParseUint(amountStr, 10, 64)
return convertedAmount, err
}
func validateKASAmountFormat(amount string) error {
// Check whether it's an integer, or a float with max 8 digits
match, err := regexp.MatchString("^([1-9]\\d{0,11}|0)(\\.\\d{0,8})?$", amount)
if !match {
return errors.Errorf("Invalid amount")
}
if err != nil {
return err
}
return nil
}

View File

@ -0,0 +1,90 @@
package utils
import "testing"
// Takes in a string representation of the Kas value to convert to Sompi
func TestKasToSompi(t *testing.T) {
type testVector struct {
originalAmount string
convertedAmount uint64
}
validCases := []testVector{
{originalAmount: "0", convertedAmount: 0},
{originalAmount: "1", convertedAmount: 100000000},
{originalAmount: "33184.1489732", convertedAmount: 3318414897320},
{originalAmount: "21.35808032", convertedAmount: 2135808032},
{originalAmount: "184467440737.09551615", convertedAmount: 18446744073709551615},
}
for _, currentTestVector := range validCases {
convertedAmount, err := KasToSompi(currentTestVector.originalAmount)
if err != nil {
t.Error(err)
} else if convertedAmount != currentTestVector.convertedAmount {
t.Errorf("Expected %s, to convert to %d. Got: %d", currentTestVector.originalAmount, currentTestVector.convertedAmount, convertedAmount)
}
}
invalidCases := []string{
"184467440737.09551616", // Bigger than max uint64
"-1",
"a",
"",
}
for _, currentTestVector := range invalidCases {
_, err := KasToSompi(currentTestVector)
if err == nil {
t.Errorf("Expected an error but succeeded validation for test case %s", currentTestVector)
}
}
}
func TestValidateAmountFormat(t *testing.T) {
validCases := []string{
"0",
"1",
"1.0",
"0.1",
"0.12345678",
"111111111111.11111111", // 12 digits to the left of decimal, 8 digits to the right
"184467440737.09551615", // Maximum input that can be represented in sompi later
"184467440737.09551616", // Cannot be represented in sompi, but we'll acccept for "correct format"
"999999999999.99999999", // Cannot be represented in sompi, but we'll acccept for "correct format"
}
for _, testCase := range validCases {
err := validateKASAmountFormat(testCase)
if err != nil {
t.Error(err)
}
}
invalidCases := []string{
"",
"a",
"-1",
"0.123456789", // 9 decimal digits
".1", // decimal but no integer component
"0a", // Extra character
"0000000000000", // 13 zeros
"012", // Int padded with zero
"00.1", // Decimal padded with zeros
"111111111111111111111", // all digits
"111111111111A11111111", // non-period/non-digit where decimal would be
"000000000000.00000000", // all zeros
"kaspa", // all text
}
for _, testCase := range invalidCases {
err := validateKASAmountFormat(testCase)
if err == nil {
t.Errorf("Expected an error but succeeded validation for test case %s", testCase)
}
}
}

View File

@ -43,6 +43,9 @@ type TestConsensus interface {
AddBlock(parentHashes []*externalapi.DomainHash, coinbaseData *externalapi.DomainCoinbaseData,
transactions []*externalapi.DomainTransaction) (*externalapi.DomainHash, *externalapi.VirtualChangeSet, error)
AddBlockOnTips(coinbaseData *externalapi.DomainCoinbaseData,
transactions []*externalapi.DomainTransaction) (*externalapi.DomainHash, *externalapi.VirtualChangeSet, error)
AddUTXOInvalidHeader(parentHashes []*externalapi.DomainHash) (*externalapi.DomainHash, *externalapi.VirtualChangeSet, error)
AddUTXOInvalidBlock(parentHashes []*externalapi.DomainHash) (*externalapi.DomainHash,

View File

@ -69,6 +69,17 @@ func (tc *testConsensus) AddBlock(parentHashes []*externalapi.DomainHash, coinba
return consensushashing.BlockHash(block), virtualChangeSet, nil
}
func (tc *testConsensus) AddBlockOnTips(coinbaseData *externalapi.DomainCoinbaseData,
transactions []*externalapi.DomainTransaction) (*externalapi.DomainHash, *externalapi.VirtualChangeSet, error) {
tips, err := tc.Tips()
if err != nil {
return nil, nil, err
}
return tc.AddBlock(tips, coinbaseData, transactions)
}
func (tc *testConsensus) AddUTXOInvalidHeader(parentHashes []*externalapi.DomainHash) (*externalapi.DomainHash,
*externalapi.VirtualChangeSet, error) {

View File

@ -232,6 +232,8 @@ var MainnetParams = Params{
"seeder4.kaspad.net",
// This DNS seeder is run by Tim
"kaspadns.kaspacalc.net",
// This DNS seeder is run by supertypo
"n-mainnet.kaspa.ws",
},
// DAG parameters

View File

@ -147,16 +147,12 @@ func (btb *blockTemplateBuilder) BuildBlockTemplate(
invalidTxsErr := ruleerrors.ErrInvalidTransactionsInNewBlock{}
if errors.As(err, &invalidTxsErr) {
log.Criticalf("consensusReference.Consensus().BuildBlock returned invalid txs in BuildBlockTemplate")
invalidTxs := make([]*consensusexternalapi.DomainTransaction, 0, len(invalidTxsErr.InvalidTransactions))
for _, tx := range invalidTxsErr.InvalidTransactions {
invalidTxs = append(invalidTxs, tx.Transaction)
}
err = btb.mempool.RemoveTransactions(invalidTxs, true)
err = btb.mempool.RemoveInvalidTransactions(&invalidTxsErr)
if err != nil {
// mempool.RemoveTransactions might return errors in situations that are perfectly fine in this context.
// mempool.RemoveInvalidTransactions might return errors in situations that are perfectly fine in this context.
// TODO: Once the mempool invariants are clear, this should be converted back `return nil, err`:
// https://github.com/kaspanet/kaspad/issues/1553
log.Criticalf("Error from mempool.RemoveTransactions: %+v", err)
log.Criticalf("Error from mempool.RemoveInvalidTransactions: %+v", err)
}
// We can call this recursively without worry because this should almost never happen
return btb.BuildBlockTemplate(coinbaseData)

View File

@ -51,6 +51,7 @@ const (
RejectDifficulty RejectCode = 0x44
RejectImmatureSpend RejectCode = 0x45
RejectBadOrphan RejectCode = 0x64
RejectSpamTx RejectCode = 0x65
)
// Map of reject codes back strings for pretty printing.

View File

@ -1,6 +1,9 @@
package mempool
import (
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
"sync"
"github.com/kaspanet/kaspad/domain/consensusreference"
@ -141,7 +144,57 @@ func (mp *mempool) BlockCandidateTransactions() []*externalapi.DomainTransaction
mp.mtx.RLock()
defer mp.mtx.RUnlock()
return mp.transactionsPool.allReadyTransactions()
readyTxs := mp.transactionsPool.allReadyTransactions()
var candidateTxs []*externalapi.DomainTransaction
var spamTx *externalapi.DomainTransaction
var spamTxNewestUTXODaaScore uint64
for _, tx := range readyTxs {
if len(tx.Outputs) > len(tx.Inputs) {
hasCoinbaseInput := false
for _, input := range tx.Inputs {
if input.UTXOEntry.IsCoinbase() {
hasCoinbaseInput = true
break
}
}
numExtraOuts := len(tx.Outputs) - len(tx.Inputs)
if !hasCoinbaseInput && numExtraOuts > 2 && tx.Fee < uint64(numExtraOuts)*constants.SompiPerKaspa {
log.Debugf("Filtered spam tx %s", consensushashing.TransactionID(tx))
continue
}
if hasCoinbaseInput || tx.Fee > uint64(numExtraOuts)*constants.SompiPerKaspa {
candidateTxs = append(candidateTxs, tx)
} else {
txNewestUTXODaaScore := tx.Inputs[0].UTXOEntry.BlockDAAScore()
for _, input := range tx.Inputs {
if input.UTXOEntry.BlockDAAScore() > txNewestUTXODaaScore {
txNewestUTXODaaScore = input.UTXOEntry.BlockDAAScore()
}
}
if spamTx != nil {
if txNewestUTXODaaScore < spamTxNewestUTXODaaScore {
spamTx = tx
spamTxNewestUTXODaaScore = txNewestUTXODaaScore
}
} else {
spamTx = tx
spamTxNewestUTXODaaScore = txNewestUTXODaaScore
}
}
} else {
candidateTxs = append(candidateTxs, tx)
}
}
if spamTx != nil {
log.Debugf("Adding spam tx candidate %s", consensushashing.TransactionID(spamTx))
candidateTxs = append(candidateTxs, spamTx)
}
return candidateTxs
}
func (mp *mempool) RevalidateHighPriorityTransactions() (validTransactions []*externalapi.DomainTransaction, err error) {
@ -151,11 +204,29 @@ func (mp *mempool) RevalidateHighPriorityTransactions() (validTransactions []*ex
return mp.revalidateHighPriorityTransactions()
}
func (mp *mempool) RemoveTransactions(transactions []*externalapi.DomainTransaction, removeRedeemers bool) error {
func (mp *mempool) RemoveInvalidTransactions(err *ruleerrors.ErrInvalidTransactionsInNewBlock) error {
mp.mtx.Lock()
defer mp.mtx.Unlock()
return mp.removeTransactions(transactions, removeRedeemers)
for _, tx := range err.InvalidTransactions {
ruleErr, success := tx.Error.(ruleerrors.RuleError)
if !success {
continue
}
inner := ruleErr.Unwrap()
removeRedeemers := true
if _, ok := inner.(ruleerrors.ErrMissingTxOut); ok {
removeRedeemers = false
}
err := mp.removeTransaction(consensushashing.TransactionID(tx.Transaction), removeRedeemers)
if err != nil {
return err
}
}
return nil
}
func (mp *mempool) RemoveTransaction(transactionID *externalapi.DomainTransactionID, removeRedeemers bool) error {

View File

@ -2,20 +2,9 @@ package mempool
import (
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
"github.com/kaspanet/kaspad/domain/miningmanager/mempool/model"
)
func (mp *mempool) removeTransactions(transactions []*externalapi.DomainTransaction, removeRedeemers bool) error {
for _, transaction := range transactions {
err := mp.removeTransaction(consensushashing.TransactionID(transaction), removeRedeemers)
if err != nil {
return err
}
}
return nil
}
func (mp *mempool) removeTransaction(transactionID *externalapi.DomainTransactionID, removeRedeemers bool) error {
if _, ok := mp.orphansPool.allOrphans[*transactionID]; ok {
return mp.orphansPool.removeOrphan(transactionID, true)

View File

@ -7,20 +7,85 @@ import (
)
func (mp *mempool) revalidateHighPriorityTransactions() ([]*externalapi.DomainTransaction, error) {
type txNode struct {
children map[externalapi.DomainTransactionID]struct{}
nonVisitedParents int
tx *model.MempoolTransaction
visited bool
}
onEnd := logger.LogAndMeasureExecutionTime(log, "revalidateHighPriorityTransactions")
defer onEnd()
// We revalidate transactions in topological order in case there are dependencies between them
// Naturally transactions point to their dependencies, but since we want to start processing the dependencies
// first, we build the opposite DAG. We initially fill `queue` with transactions with no dependencies.
txDAG := make(map[externalapi.DomainTransactionID]*txNode)
maybeAddNode := func(txID externalapi.DomainTransactionID) *txNode {
if node, ok := txDAG[txID]; ok {
return node
}
node := &txNode{
children: make(map[externalapi.DomainTransactionID]struct{}),
nonVisitedParents: 0,
tx: mp.transactionsPool.highPriorityTransactions[txID],
}
txDAG[txID] = node
return node
}
queue := make([]*txNode, 0, len(mp.transactionsPool.highPriorityTransactions))
for id, transaction := range mp.transactionsPool.highPriorityTransactions {
node := maybeAddNode(id)
parents := make(map[externalapi.DomainTransactionID]struct{})
for _, input := range transaction.Transaction().Inputs {
if _, ok := mp.transactionsPool.highPriorityTransactions[input.PreviousOutpoint.TransactionID]; !ok {
continue
}
parents[input.PreviousOutpoint.TransactionID] = struct{}{} // To avoid duplicate parents, we first add it to a set and then count it
maybeAddNode(input.PreviousOutpoint.TransactionID).children[id] = struct{}{}
}
node.nonVisitedParents = len(parents)
if node.nonVisitedParents == 0 {
queue = append(queue, node)
}
}
validTransactions := []*externalapi.DomainTransaction{}
for _, transaction := range mp.transactionsPool.highPriorityTransactions {
// Now we iterate the DAG in topological order using BFS
for len(queue) > 0 {
var node *txNode
node, queue = queue[0], queue[1:]
if node.visited {
continue
}
node.visited = true
transaction := node.tx
isValid, err := mp.revalidateTransaction(transaction)
if err != nil {
return nil, err
}
if !isValid {
continue
for child := range node.children {
childNode := txDAG[child]
childNode.nonVisitedParents--
if childNode.nonVisitedParents == 0 {
queue = append(queue, txDAG[child])
}
}
validTransactions = append(validTransactions, transaction.Transaction().Clone())
if isValid {
validTransactions = append(validTransactions, transaction.Transaction().Clone())
}
}
return validTransactions, nil
@ -35,7 +100,7 @@ func (mp *mempool) revalidateTransaction(transaction *model.MempoolTransaction)
}
if len(missingParents) > 0 {
log.Debugf("Removing transaction %s, it failed revalidation", transaction.TransactionID())
err := mp.removeTransaction(transaction.TransactionID(), true)
err := mp.removeTransaction(transaction.TransactionID(), false)
if err != nil {
return false, err
}

View File

@ -2,7 +2,6 @@ package mempool
import (
"fmt"
"github.com/kaspanet/kaspad/infrastructure/logger"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"

View File

@ -2,6 +2,7 @@ package mempool
import (
"fmt"
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
@ -44,6 +45,20 @@ func (mp *mempool) validateTransactionInIsolation(transaction *externalapi.Domai
}
func (mp *mempool) validateTransactionInContext(transaction *externalapi.DomainTransaction) error {
hasCoinbaseInput := false
for _, input := range transaction.Inputs {
if input.UTXOEntry.IsCoinbase() {
hasCoinbaseInput = true
break
}
}
numExtraOuts := len(transaction.Outputs) - len(transaction.Inputs)
if !hasCoinbaseInput && numExtraOuts > 2 && transaction.Fee < uint64(numExtraOuts)*constants.SompiPerKaspa {
log.Warnf("Rejected spam tx %s from mempool (%d outputs)", consensushashing.TransactionID(transaction), len(transaction.Outputs))
return transactionRuleError(RejectSpamTx, fmt.Sprintf("Rejected spam tx %s from mempool", consensushashing.TransactionID(transaction)))
}
if !mp.config.AcceptNonStandard {
err := mp.checkTransactionStandardInContext(transaction)
if err != nil {

View File

@ -577,6 +577,72 @@ func TestRevalidateHighPriorityTransactions(t *testing.T) {
})
}
func TestRevalidateHighPriorityTransactionsWithChain(t *testing.T) {
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
consensusConfig.BlockCoinbaseMaturity = 0
factory := consensus.NewFactory()
tc, teardown, err := factory.NewTestConsensus(consensusConfig, "TestRevalidateHighPriorityTransactions")
if err != nil {
t.Fatalf("Failed setting up TestConsensus: %+v", err)
}
defer teardown(false)
miningFactory := miningmanager.NewFactory()
mempoolConfig := mempool.DefaultConfig(&consensusConfig.Params)
tcAsConsensus := tc.(externalapi.Consensus)
tcAsConsensusPointer := &tcAsConsensus
consensusReference := consensusreference.NewConsensusReference(&tcAsConsensusPointer)
miningManager := miningFactory.NewMiningManager(consensusReference, &consensusConfig.Params, mempoolConfig)
const chainSize = 10
chain, err := createTxChain(tc, chainSize)
if err != nil {
t.Fatal(err)
}
_, err = miningManager.ValidateAndInsertTransaction(chain[0], true, false)
if err != nil {
t.Fatal(err)
}
blockHash, _, err := tc.AddBlockOnTips(nil, []*externalapi.DomainTransaction{chain[0].Clone()})
if err != nil {
t.Fatal(err)
}
block, _, err := tc.GetBlock(blockHash)
if err != nil {
t.Fatal(err)
}
_, err = miningManager.HandleNewBlockTransactions(block.Transactions)
if err != nil {
t.Fatal(err)
}
for _, transaction := range chain[1:] {
_, err = miningManager.ValidateAndInsertTransaction(transaction, true, false)
if err != nil {
t.Fatal(err)
}
}
_, _, err = tc.AddBlockOnTips(nil, []*externalapi.DomainTransaction{chain[1].Clone()})
if err != nil {
t.Fatal(err)
}
revalidated, err := miningManager.RevalidateHighPriorityTransactions()
if err != nil {
t.Fatal(err)
}
if len(revalidated) != chainSize-2 {
t.Fatalf("expected %d transactions to revalidate but instead only %d revalidated", chainSize-2, len(revalidated))
}
})
}
// TestModifyBlockTemplate verifies that modifying a block template changes coinbase data correctly.
func TestModifyBlockTemplate(t *testing.T) {
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
@ -904,40 +970,58 @@ func createArraysOfParentAndChildrenTransactions(tc testapi.TestConsensus) ([]*e
func createParentAndChildrenTransactions(tc testapi.TestConsensus) (txParent *externalapi.DomainTransaction,
txChild *externalapi.DomainTransaction, err error) {
chain, err := createTxChain(tc, 2)
if err != nil {
return nil, nil, err
}
return chain[0], chain[1], nil
}
func createTxChain(tc testapi.TestConsensus, numTxs int) ([]*externalapi.DomainTransaction, error) {
// We will add two blocks by consensus before the parent transactions, in order to fund the parent transactions.
tips, err := tc.Tips()
if err != nil {
return nil, nil, err
return nil, err
}
_, _, err = tc.AddBlock(tips, nil, nil)
if err != nil {
return nil, nil, errors.Wrapf(err, "AddBlock: %v", err)
return nil, errors.Wrapf(err, "AddBlock: %v", err)
}
tips, err = tc.Tips()
if err != nil {
return nil, nil, err
return nil, err
}
fundingBlockHashForParent, _, err := tc.AddBlock(tips, nil, nil)
if err != nil {
return nil, nil, errors.Wrap(err, "AddBlock: ")
return nil, errors.Wrap(err, "AddBlock: ")
}
fundingBlockForParent, _, err := tc.GetBlock(fundingBlockHashForParent)
if err != nil {
return nil, nil, errors.Wrap(err, "GetBlock: ")
return nil, errors.Wrap(err, "GetBlock: ")
}
fundingTransactionForParent := fundingBlockForParent.Transactions[transactionhelper.CoinbaseTransactionIndex]
txParent, err = testutils.CreateTransaction(fundingTransactionForParent, 1000)
transactions := make([]*externalapi.DomainTransaction, numTxs)
transactions[0], err = testutils.CreateTransaction(fundingTransactionForParent, 1000)
if err != nil {
return nil, nil, err
return nil, err
}
txChild, err = testutils.CreateTransaction(txParent, 1000)
if err != nil {
return nil, nil, err
txParent := transactions[0]
for i := 1; i < numTxs; i++ {
transactions[i], err = testutils.CreateTransaction(txParent, 1000)
if err != nil {
return nil, err
}
txParent = transactions[i]
}
return txParent, txChild, nil
return transactions, nil
}
func createChildAndParentTxsAndAddParentToConsensus(tc testapi.TestConsensus) (*externalapi.DomainTransaction, error) {

View File

@ -2,6 +2,7 @@ package model
import (
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
)
// Mempool maintains a set of known transactions that
@ -11,7 +12,7 @@ type Mempool interface {
BlockCandidateTransactions() []*externalapi.DomainTransaction
ValidateAndInsertTransaction(transaction *externalapi.DomainTransaction, isHighPriority bool, allowOrphan bool) (
acceptedTransactions []*externalapi.DomainTransaction, err error)
RemoveTransactions(txs []*externalapi.DomainTransaction, removeRedeemers bool) error
RemoveInvalidTransactions(err *ruleerrors.ErrInvalidTransactionsInNewBlock) error
GetTransaction(
transactionID *externalapi.DomainTransactionID,
includeTransactionPool bool,

View File

@ -52,6 +52,8 @@ func (ui *UTXOIndex) Reset() error {
ui.mutex.Lock()
defer ui.mutex.Unlock()
log.Infof("Starting UTXO index reset")
err := ui.store.deleteAll()
if err != nil {
return err
@ -88,7 +90,13 @@ func (ui *UTXOIndex) Reset() error {
}
// This has to be done last to mark that the reset went smoothly and no reset has to be called next time.
return ui.store.updateAndCommitVirtualParentsWithoutTransaction(virtualInfo.ParentHashes)
err = ui.store.updateAndCommitVirtualParentsWithoutTransaction(virtualInfo.ParentHashes)
if err != nil {
return err
}
log.Infof("Finished UTXO index reset")
return nil
}
func (ui *UTXOIndex) isSynced() (bool, error) {

View File

@ -11,7 +11,7 @@ const validCharacters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrs
const (
appMajor uint = 0
appMinor uint = 12
appPatch uint = 13
appPatch uint = 15
)
// appBuild is defined as a variable so it can be overridden during the build