mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-11-27 15:53:57 +00:00
Merge branch 'dev' into testnet11
This commit is contained in:
commit
a216bd5468
2
.github/workflows/deploy.yaml
vendored
2
.github/workflows/deploy.yaml
vendored
@ -23,7 +23,7 @@ jobs:
|
|||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: 1.19
|
go-version: 1.21
|
||||||
|
|
||||||
- name: Build on Linux
|
- name: Build on Linux
|
||||||
if: runner.os == 'Linux'
|
if: runner.os == 'Linux'
|
||||||
|
|||||||
@ -218,7 +218,7 @@ func RPCTransactionToDomainTransaction(rpcTransaction *RPCTransaction) (*externa
|
|||||||
Outputs: outputs,
|
Outputs: outputs,
|
||||||
LockTime: rpcTransaction.LockTime,
|
LockTime: rpcTransaction.LockTime,
|
||||||
SubnetworkID: *subnetworkID,
|
SubnetworkID: *subnetworkID,
|
||||||
Gas: rpcTransaction.LockTime,
|
Gas: rpcTransaction.Gas,
|
||||||
Payload: payload,
|
Payload: payload,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@ -286,7 +286,7 @@ func DomainTransactionToRPCTransaction(transaction *externalapi.DomainTransactio
|
|||||||
Outputs: outputs,
|
Outputs: outputs,
|
||||||
LockTime: transaction.LockTime,
|
LockTime: transaction.LockTime,
|
||||||
SubnetworkID: subnetworkID,
|
SubnetworkID: subnetworkID,
|
||||||
Gas: transaction.LockTime,
|
Gas: transaction.Gas,
|
||||||
Payload: payload,
|
Payload: payload,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -57,7 +57,7 @@ type sendConfig struct {
|
|||||||
DaemonAddress string `long:"daemonaddress" short:"d" description:"Wallet daemon server to connect to"`
|
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"`
|
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"`
|
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)"`
|
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)"`
|
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"`
|
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"`
|
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"`
|
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"`
|
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)"`
|
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)"`
|
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
|
config.NetworkFlags
|
||||||
@ -296,8 +296,8 @@ func parseCommandLine() (subCommand string, config interface{}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func validateCreateUnsignedTransactionConf(conf *createUnsignedTransactionConfig) error {
|
func validateCreateUnsignedTransactionConf(conf *createUnsignedTransactionConfig) error {
|
||||||
if (!conf.IsSendAll && conf.SendAmount == 0) ||
|
if (!conf.IsSendAll && conf.SendAmount == "") ||
|
||||||
(conf.IsSendAll && conf.SendAmount > 0) {
|
(conf.IsSendAll && conf.SendAmount != "") {
|
||||||
|
|
||||||
return errors.New("exactly one of '--send-amount' or '--all' must be specified")
|
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 {
|
func validateSendConfig(conf *sendConfig) error {
|
||||||
if (!conf.IsSendAll && conf.SendAmount == 0) ||
|
if (!conf.IsSendAll && conf.SendAmount == "") ||
|
||||||
(conf.IsSendAll && conf.SendAmount > 0) {
|
(conf.IsSendAll && conf.SendAmount != "") {
|
||||||
|
|
||||||
return errors.New("exactly one of '--send-amount' or '--all' must be specified")
|
return errors.New("exactly one of '--send-amount' or '--all' must be specified")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import (
|
|||||||
|
|
||||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/client"
|
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/client"
|
||||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
|
"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 {
|
func createUnsignedTransaction(conf *createUnsignedTransactionConfig) error {
|
||||||
@ -20,7 +20,12 @@ func createUnsignedTransaction(conf *createUnsignedTransactionConfig) error {
|
|||||||
ctx, cancel := context.WithTimeout(context.Background(), daemonTimeout)
|
ctx, cancel := context.WithTimeout(context.Background(), daemonTimeout)
|
||||||
defer cancel()
|
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{
|
response, err := daemonClient.CreateUnsignedTransactions(ctx, &pb.CreateUnsignedTransactionsRequest{
|
||||||
From: conf.FromAddresses,
|
From: conf.FromAddresses,
|
||||||
Address: conf.ToAddress,
|
Address: conf.ToAddress,
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import (
|
|||||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
|
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
|
||||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/keys"
|
"github.com/kaspanet/kaspad/cmd/kaspawallet/keys"
|
||||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
|
"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"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -35,7 +35,11 @@ func send(conf *sendConfig) error {
|
|||||||
|
|
||||||
var sendAmountSompi uint64
|
var sendAmountSompi uint64
|
||||||
if !conf.IsSendAll {
|
if !conf.IsSendAll {
|
||||||
sendAmountSompi = uint64(conf.SendAmount * constants.SompiPerKaspa)
|
sendAmountSompi, err = utils.KasToSompi(conf.SendAmount)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
createUnsignedTransactionsResponse, err :=
|
createUnsignedTransactionsResponse, err :=
|
||||||
|
|||||||
@ -2,8 +2,13 @@ package utils
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
"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
|
// 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
|
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
|
||||||
|
}
|
||||||
|
|||||||
90
cmd/kaspawallet/utils/format_kas_test.go
Normal file
90
cmd/kaspawallet/utils/format_kas_test.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -43,6 +43,9 @@ type TestConsensus interface {
|
|||||||
AddBlock(parentHashes []*externalapi.DomainHash, coinbaseData *externalapi.DomainCoinbaseData,
|
AddBlock(parentHashes []*externalapi.DomainHash, coinbaseData *externalapi.DomainCoinbaseData,
|
||||||
transactions []*externalapi.DomainTransaction) (*externalapi.DomainHash, *externalapi.VirtualChangeSet, error)
|
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)
|
AddUTXOInvalidHeader(parentHashes []*externalapi.DomainHash) (*externalapi.DomainHash, *externalapi.VirtualChangeSet, error)
|
||||||
|
|
||||||
AddUTXOInvalidBlock(parentHashes []*externalapi.DomainHash) (*externalapi.DomainHash,
|
AddUTXOInvalidBlock(parentHashes []*externalapi.DomainHash) (*externalapi.DomainHash,
|
||||||
|
|||||||
@ -69,6 +69,17 @@ func (tc *testConsensus) AddBlock(parentHashes []*externalapi.DomainHash, coinba
|
|||||||
return consensushashing.BlockHash(block), virtualChangeSet, nil
|
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,
|
func (tc *testConsensus) AddUTXOInvalidHeader(parentHashes []*externalapi.DomainHash) (*externalapi.DomainHash,
|
||||||
*externalapi.VirtualChangeSet, error) {
|
*externalapi.VirtualChangeSet, error) {
|
||||||
|
|
||||||
|
|||||||
@ -387,7 +387,7 @@ func ExtractAtomicSwapDataPushes(version uint16, scriptPubKey []byte) (*AtomicSw
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(pops) != 20 {
|
if len(pops) != 19 {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
isAtomicSwap := pops[0].opcode.value == OpIf &&
|
isAtomicSwap := pops[0].opcode.value == OpIf &&
|
||||||
@ -403,13 +403,12 @@ func ExtractAtomicSwapDataPushes(version uint16, scriptPubKey []byte) (*AtomicSw
|
|||||||
pops[10].opcode.value == OpElse &&
|
pops[10].opcode.value == OpElse &&
|
||||||
canonicalPush(pops[11]) &&
|
canonicalPush(pops[11]) &&
|
||||||
pops[12].opcode.value == OpCheckLockTimeVerify &&
|
pops[12].opcode.value == OpCheckLockTimeVerify &&
|
||||||
pops[13].opcode.value == OpDrop &&
|
pops[13].opcode.value == OpDup &&
|
||||||
pops[14].opcode.value == OpDup &&
|
pops[14].opcode.value == OpBlake2b &&
|
||||||
pops[15].opcode.value == OpBlake2b &&
|
pops[15].opcode.value == OpData32 &&
|
||||||
pops[16].opcode.value == OpData32 &&
|
pops[16].opcode.value == OpEndIf &&
|
||||||
pops[17].opcode.value == OpEndIf &&
|
pops[17].opcode.value == OpEqualVerify &&
|
||||||
pops[18].opcode.value == OpEqualVerify &&
|
pops[18].opcode.value == OpCheckSig
|
||||||
pops[19].opcode.value == OpCheckSig
|
|
||||||
if !isAtomicSwap {
|
if !isAtomicSwap {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
@ -417,9 +416,9 @@ func ExtractAtomicSwapDataPushes(version uint16, scriptPubKey []byte) (*AtomicSw
|
|||||||
pushes := new(AtomicSwapDataPushes)
|
pushes := new(AtomicSwapDataPushes)
|
||||||
copy(pushes.SecretHash[:], pops[5].data)
|
copy(pushes.SecretHash[:], pops[5].data)
|
||||||
copy(pushes.RecipientBlake2b[:], pops[9].data)
|
copy(pushes.RecipientBlake2b[:], pops[9].data)
|
||||||
copy(pushes.RefundBlake2b[:], pops[16].data)
|
copy(pushes.RefundBlake2b[:], pops[15].data)
|
||||||
if pops[2].data != nil {
|
if pops[2].data != nil {
|
||||||
locktime, err := makeScriptNum(pops[2].data, 5)
|
locktime, err := makeScriptNum(pops[2].data, 8)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
@ -430,7 +429,7 @@ func ExtractAtomicSwapDataPushes(version uint16, scriptPubKey []byte) (*AtomicSw
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
if pops[11].data != nil {
|
if pops[11].data != nil {
|
||||||
locktime, err := makeScriptNum(pops[11].data, 5)
|
locktime, err := makeScriptNum(pops[11].data, 8)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -232,6 +232,8 @@ var MainnetParams = Params{
|
|||||||
"seeder4.kaspad.net",
|
"seeder4.kaspad.net",
|
||||||
// This DNS seeder is run by Tim
|
// This DNS seeder is run by Tim
|
||||||
"kaspadns.kaspacalc.net",
|
"kaspadns.kaspacalc.net",
|
||||||
|
// This DNS seeder is run by supertypo
|
||||||
|
"n-mainnet.kaspa.ws",
|
||||||
},
|
},
|
||||||
|
|
||||||
// DAG parameters
|
// DAG parameters
|
||||||
|
|||||||
@ -147,16 +147,12 @@ func (btb *blockTemplateBuilder) BuildBlockTemplate(
|
|||||||
invalidTxsErr := ruleerrors.ErrInvalidTransactionsInNewBlock{}
|
invalidTxsErr := ruleerrors.ErrInvalidTransactionsInNewBlock{}
|
||||||
if errors.As(err, &invalidTxsErr) {
|
if errors.As(err, &invalidTxsErr) {
|
||||||
log.Criticalf("consensusReference.Consensus().BuildBlock returned invalid txs in BuildBlockTemplate")
|
log.Criticalf("consensusReference.Consensus().BuildBlock returned invalid txs in BuildBlockTemplate")
|
||||||
invalidTxs := make([]*consensusexternalapi.DomainTransaction, 0, len(invalidTxsErr.InvalidTransactions))
|
err = btb.mempool.RemoveInvalidTransactions(&invalidTxsErr)
|
||||||
for _, tx := range invalidTxsErr.InvalidTransactions {
|
|
||||||
invalidTxs = append(invalidTxs, tx.Transaction)
|
|
||||||
}
|
|
||||||
err = btb.mempool.RemoveTransactions(invalidTxs, true)
|
|
||||||
if err != nil {
|
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`:
|
// TODO: Once the mempool invariants are clear, this should be converted back `return nil, err`:
|
||||||
// https://github.com/kaspanet/kaspad/issues/1553
|
// 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
|
// We can call this recursively without worry because this should almost never happen
|
||||||
return btb.BuildBlockTemplate(coinbaseData)
|
return btb.BuildBlockTemplate(coinbaseData)
|
||||||
|
|||||||
@ -51,6 +51,7 @@ const (
|
|||||||
RejectDifficulty RejectCode = 0x44
|
RejectDifficulty RejectCode = 0x44
|
||||||
RejectImmatureSpend RejectCode = 0x45
|
RejectImmatureSpend RejectCode = 0x45
|
||||||
RejectBadOrphan RejectCode = 0x64
|
RejectBadOrphan RejectCode = 0x64
|
||||||
|
RejectSpamTx RejectCode = 0x65
|
||||||
)
|
)
|
||||||
|
|
||||||
// Map of reject codes back strings for pretty printing.
|
// Map of reject codes back strings for pretty printing.
|
||||||
|
|||||||
@ -1,6 +1,9 @@
|
|||||||
package mempool
|
package mempool
|
||||||
|
|
||||||
import (
|
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"
|
"sync"
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/domain/consensusreference"
|
"github.com/kaspanet/kaspad/domain/consensusreference"
|
||||||
@ -141,7 +144,57 @@ func (mp *mempool) BlockCandidateTransactions() []*externalapi.DomainTransaction
|
|||||||
mp.mtx.RLock()
|
mp.mtx.RLock()
|
||||||
defer mp.mtx.RUnlock()
|
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) {
|
func (mp *mempool) RevalidateHighPriorityTransactions() (validTransactions []*externalapi.DomainTransaction, err error) {
|
||||||
@ -151,11 +204,29 @@ func (mp *mempool) RevalidateHighPriorityTransactions() (validTransactions []*ex
|
|||||||
return mp.revalidateHighPriorityTransactions()
|
return mp.revalidateHighPriorityTransactions()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mp *mempool) RemoveTransactions(transactions []*externalapi.DomainTransaction, removeRedeemers bool) error {
|
func (mp *mempool) RemoveInvalidTransactions(err *ruleerrors.ErrInvalidTransactionsInNewBlock) error {
|
||||||
mp.mtx.Lock()
|
mp.mtx.Lock()
|
||||||
defer mp.mtx.Unlock()
|
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 {
|
func (mp *mempool) RemoveTransaction(transactionID *externalapi.DomainTransactionID, removeRedeemers bool) error {
|
||||||
|
|||||||
@ -2,20 +2,9 @@ package mempool
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
|
|
||||||
"github.com/kaspanet/kaspad/domain/miningmanager/mempool/model"
|
"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 {
|
func (mp *mempool) removeTransaction(transactionID *externalapi.DomainTransactionID, removeRedeemers bool) error {
|
||||||
if _, ok := mp.orphansPool.allOrphans[*transactionID]; ok {
|
if _, ok := mp.orphansPool.allOrphans[*transactionID]; ok {
|
||||||
return mp.orphansPool.removeOrphan(transactionID, true)
|
return mp.orphansPool.removeOrphan(transactionID, true)
|
||||||
|
|||||||
@ -7,21 +7,86 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (mp *mempool) revalidateHighPriorityTransactions() ([]*externalapi.DomainTransaction, error) {
|
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")
|
onEnd := logger.LogAndMeasureExecutionTime(log, "revalidateHighPriorityTransactions")
|
||||||
defer onEnd()
|
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{}
|
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)
|
isValid, err := mp.revalidateTransaction(transaction)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if isValid {
|
||||||
validTransactions = append(validTransactions, transaction.Transaction().Clone())
|
validTransactions = append(validTransactions, transaction.Transaction().Clone())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return validTransactions, nil
|
return validTransactions, nil
|
||||||
}
|
}
|
||||||
@ -35,7 +100,7 @@ func (mp *mempool) revalidateTransaction(transaction *model.MempoolTransaction)
|
|||||||
}
|
}
|
||||||
if len(missingParents) > 0 {
|
if len(missingParents) > 0 {
|
||||||
log.Debugf("Removing transaction %s, it failed revalidation", transaction.TransactionID())
|
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 {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,6 @@ package mempool
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/infrastructure/logger"
|
"github.com/kaspanet/kaspad/infrastructure/logger"
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package mempool
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
|
"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 {
|
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 {
|
if !mp.config.AcceptNonStandard {
|
||||||
err := mp.checkTransactionStandardInContext(transaction)
|
err := mp.checkTransactionStandardInContext(transaction)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@ -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.
|
// TestModifyBlockTemplate verifies that modifying a block template changes coinbase data correctly.
|
||||||
func TestModifyBlockTemplate(t *testing.T) {
|
func TestModifyBlockTemplate(t *testing.T) {
|
||||||
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
|
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,
|
func createParentAndChildrenTransactions(tc testapi.TestConsensus) (txParent *externalapi.DomainTransaction,
|
||||||
txChild *externalapi.DomainTransaction, err error) {
|
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.
|
// We will add two blocks by consensus before the parent transactions, in order to fund the parent transactions.
|
||||||
tips, err := tc.Tips()
|
tips, err := tc.Tips()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, err = tc.AddBlock(tips, nil, nil)
|
_, _, err = tc.AddBlock(tips, nil, nil)
|
||||||
if err != 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()
|
tips, err = tc.Tips()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
fundingBlockHashForParent, _, err := tc.AddBlock(tips, nil, nil)
|
fundingBlockHashForParent, _, err := tc.AddBlock(tips, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, errors.Wrap(err, "AddBlock: ")
|
return nil, errors.Wrap(err, "AddBlock: ")
|
||||||
}
|
}
|
||||||
fundingBlockForParent, _, err := tc.GetBlock(fundingBlockHashForParent)
|
fundingBlockForParent, _, err := tc.GetBlock(fundingBlockHashForParent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, errors.Wrap(err, "GetBlock: ")
|
return nil, errors.Wrap(err, "GetBlock: ")
|
||||||
}
|
}
|
||||||
fundingTransactionForParent := fundingBlockForParent.Transactions[transactionhelper.CoinbaseTransactionIndex]
|
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 {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
txChild, err = testutils.CreateTransaction(txParent, 1000)
|
|
||||||
|
txParent := transactions[0]
|
||||||
|
for i := 1; i < numTxs; i++ {
|
||||||
|
transactions[i], err = testutils.CreateTransaction(txParent, 1000)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return txParent, txChild, nil
|
|
||||||
|
txParent = transactions[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
return transactions, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createChildAndParentTxsAndAddParentToConsensus(tc testapi.TestConsensus) (*externalapi.DomainTransaction, error) {
|
func createChildAndParentTxsAndAddParentToConsensus(tc testapi.TestConsensus) (*externalapi.DomainTransaction, error) {
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package model
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||||
|
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Mempool maintains a set of known transactions that
|
// Mempool maintains a set of known transactions that
|
||||||
@ -11,7 +12,7 @@ type Mempool interface {
|
|||||||
BlockCandidateTransactions() []*externalapi.DomainTransaction
|
BlockCandidateTransactions() []*externalapi.DomainTransaction
|
||||||
ValidateAndInsertTransaction(transaction *externalapi.DomainTransaction, isHighPriority bool, allowOrphan bool) (
|
ValidateAndInsertTransaction(transaction *externalapi.DomainTransaction, isHighPriority bool, allowOrphan bool) (
|
||||||
acceptedTransactions []*externalapi.DomainTransaction, err error)
|
acceptedTransactions []*externalapi.DomainTransaction, err error)
|
||||||
RemoveTransactions(txs []*externalapi.DomainTransaction, removeRedeemers bool) error
|
RemoveInvalidTransactions(err *ruleerrors.ErrInvalidTransactionsInNewBlock) error
|
||||||
GetTransaction(
|
GetTransaction(
|
||||||
transactionID *externalapi.DomainTransactionID,
|
transactionID *externalapi.DomainTransactionID,
|
||||||
includeTransactionPool bool,
|
includeTransactionPool bool,
|
||||||
|
|||||||
@ -52,6 +52,8 @@ func (ui *UTXOIndex) Reset() error {
|
|||||||
ui.mutex.Lock()
|
ui.mutex.Lock()
|
||||||
defer ui.mutex.Unlock()
|
defer ui.mutex.Unlock()
|
||||||
|
|
||||||
|
log.Infof("Starting UTXO index reset")
|
||||||
|
|
||||||
err := ui.store.deleteAll()
|
err := ui.store.deleteAll()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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.
|
// 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) {
|
func (ui *UTXOIndex) isSynced() (bool, error) {
|
||||||
|
|||||||
@ -11,7 +11,7 @@ const validCharacters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrs
|
|||||||
const (
|
const (
|
||||||
appMajor uint = 0
|
appMajor uint = 0
|
||||||
appMinor uint = 12
|
appMinor uint = 12
|
||||||
appPatch uint = 13
|
appPatch uint = 15
|
||||||
)
|
)
|
||||||
|
|
||||||
// appBuild is defined as a variable so it can be overridden during the build
|
// appBuild is defined as a variable so it can be overridden during the build
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user