mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-03-30 15:08:33 +00:00
208 lines
7.6 KiB
Go
208 lines
7.6 KiB
Go
package integration
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"testing"
|
|
|
|
"github.com/kaspanet/kaspad/domain/consensus/utils/utxo"
|
|
|
|
"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/consensushashing"
|
|
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
|
"github.com/kaspanet/kaspad/domain/consensus/utils/transactionid"
|
|
"github.com/kaspanet/kaspad/domain/consensus/utils/txscript"
|
|
"github.com/kaspanet/kaspad/util"
|
|
)
|
|
|
|
func TestUTXOIndex(t *testing.T) {
|
|
// Setup a single kaspad instance
|
|
harnessParams := &harnessParams{
|
|
p2pAddress: p2pAddress1,
|
|
rpcAddress: rpcAddress1,
|
|
miningAddress: miningAddress1,
|
|
miningAddressPrivateKey: miningAddress1PrivateKey,
|
|
utxoIndex: true,
|
|
}
|
|
kaspad, teardown := setupHarness(t, harnessParams)
|
|
defer teardown()
|
|
|
|
// skip the first block because it's paying to genesis script,
|
|
// which contains no outputs
|
|
mineNextBlock(t, kaspad)
|
|
|
|
// Register for UTXO changes
|
|
const blockAmountToMine = 100
|
|
onUTXOsChangedChan := make(chan *appmessage.UTXOsChangedNotificationMessage, blockAmountToMine)
|
|
err := kaspad.rpcClient.RegisterForUTXOsChangedNotifications([]string{miningAddress1}, func(
|
|
notification *appmessage.UTXOsChangedNotificationMessage) {
|
|
|
|
onUTXOsChangedChan <- notification
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to register for UTXO change notifications: %s", err)
|
|
}
|
|
|
|
// Mine some blocks
|
|
for i := 0; i < blockAmountToMine; i++ {
|
|
mineNextBlock(t, kaspad)
|
|
}
|
|
|
|
// Collect the UTXO and make sure there's nothing in Removed
|
|
// Note that we expect blockAmountToMine-1 messages because
|
|
// the last block won't be accepted until the next block is
|
|
// mined
|
|
var notificationEntries []*appmessage.UTXOsByAddressesEntry
|
|
for i := 0; i < blockAmountToMine; i++ {
|
|
notification := <-onUTXOsChangedChan
|
|
if len(notification.Removed) > 0 {
|
|
t.Fatalf("Unexpectedly received that a UTXO has been removed")
|
|
}
|
|
for _, added := range notification.Added {
|
|
notificationEntries = append(notificationEntries, added)
|
|
}
|
|
}
|
|
|
|
// Submit a few transactions that spends some UTXOs
|
|
const transactionAmountToSpend = 5
|
|
for i := 0; i < transactionAmountToSpend; i++ {
|
|
rpcTransaction := buildTransactionForUTXOIndexTest(t, notificationEntries[i])
|
|
_, err = kaspad.rpcClient.SubmitTransaction(rpcTransaction)
|
|
if err != nil {
|
|
t.Fatalf("Error submitting transaction: %s", err)
|
|
}
|
|
}
|
|
|
|
// Mine a block to include the above transactions
|
|
mineNextBlock(t, kaspad)
|
|
|
|
// Make sure this block removed the UTXOs we spent
|
|
notification := <-onUTXOsChangedChan
|
|
if len(notification.Removed) != transactionAmountToSpend {
|
|
t.Fatalf("Unexpected amount of removed UTXOs. Want: %d, got: %d",
|
|
transactionAmountToSpend, len(notification.Removed))
|
|
}
|
|
for i := 0; i < transactionAmountToSpend; i++ {
|
|
entry := notificationEntries[i]
|
|
|
|
found := false
|
|
for _, removed := range notification.Removed {
|
|
if *removed.Outpoint == *entry.Outpoint {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
t.Fatalf("Missing entry amongst removed UTXOs: %s:%d",
|
|
entry.Outpoint.TransactionID, entry.Outpoint.Index)
|
|
}
|
|
}
|
|
for _, added := range notification.Added {
|
|
notificationEntries = append(notificationEntries, added)
|
|
}
|
|
|
|
// Remove the UTXOs we spent from `notificationEntries`
|
|
notificationEntries = notificationEntries[transactionAmountToSpend:]
|
|
|
|
// Get all the UTXOs and make sure the response is equivalent
|
|
// to the data collected via notifications
|
|
utxosByAddressesResponse, err := kaspad.rpcClient.GetUTXOsByAddresses([]string{miningAddress1})
|
|
if err != nil {
|
|
t.Fatalf("Failed to get UTXOs: %s", err)
|
|
}
|
|
if len(notificationEntries) != len(utxosByAddressesResponse.Entries) {
|
|
t.Fatalf("Unexpected amount of UTXOs. Want: %d, got: %d",
|
|
len(notificationEntries), len(utxosByAddressesResponse.Entries))
|
|
}
|
|
for _, notificationEntry := range notificationEntries {
|
|
var foundResponseEntry *appmessage.UTXOsByAddressesEntry
|
|
for _, responseEntry := range utxosByAddressesResponse.Entries {
|
|
if *notificationEntry.Outpoint == *responseEntry.Outpoint {
|
|
foundResponseEntry = responseEntry
|
|
break
|
|
}
|
|
}
|
|
if foundResponseEntry == nil {
|
|
t.Fatalf("Missing entry in UTXOs response: %s:%d",
|
|
notificationEntry.Outpoint.TransactionID, notificationEntry.Outpoint.Index)
|
|
}
|
|
if notificationEntry.UTXOEntry.Amount != foundResponseEntry.UTXOEntry.Amount {
|
|
t.Fatalf("Unexpected UTXOEntry for outpoint %s:%d. Want: %+v, got: %+v",
|
|
notificationEntry.Outpoint.TransactionID, notificationEntry.Outpoint.Index,
|
|
notificationEntry.UTXOEntry, foundResponseEntry.UTXOEntry)
|
|
}
|
|
if notificationEntry.UTXOEntry.BlockDAAScore != foundResponseEntry.UTXOEntry.BlockDAAScore {
|
|
t.Fatalf("Unexpected UTXOEntry for outpoint %s:%d. Want: %+v, got: %+v",
|
|
notificationEntry.Outpoint.TransactionID, notificationEntry.Outpoint.Index,
|
|
notificationEntry.UTXOEntry, foundResponseEntry.UTXOEntry)
|
|
}
|
|
if notificationEntry.UTXOEntry.IsCoinbase != foundResponseEntry.UTXOEntry.IsCoinbase {
|
|
t.Fatalf("Unexpected UTXOEntry for outpoint %s:%d. Want: %+v, got: %+v",
|
|
notificationEntry.Outpoint.TransactionID, notificationEntry.Outpoint.Index,
|
|
notificationEntry.UTXOEntry, foundResponseEntry.UTXOEntry)
|
|
}
|
|
if *notificationEntry.UTXOEntry.ScriptPublicKey != *foundResponseEntry.UTXOEntry.ScriptPublicKey {
|
|
t.Fatalf("Unexpected UTXOEntry for outpoint %s:%d. Want: %+v, got: %+v",
|
|
notificationEntry.Outpoint.TransactionID, notificationEntry.Outpoint.Index,
|
|
notificationEntry.UTXOEntry, foundResponseEntry.UTXOEntry)
|
|
}
|
|
}
|
|
}
|
|
|
|
func buildTransactionForUTXOIndexTest(t *testing.T, entry *appmessage.UTXOsByAddressesEntry) *appmessage.RPCTransaction {
|
|
transactionIDBytes, err := hex.DecodeString(entry.Outpoint.TransactionID)
|
|
if err != nil {
|
|
t.Fatalf("Error decoding transaction ID: %s", err)
|
|
}
|
|
transactionID, err := transactionid.FromBytes(transactionIDBytes)
|
|
if err != nil {
|
|
t.Fatalf("Error decoding transaction ID: %s", err)
|
|
}
|
|
|
|
txIns := make([]*appmessage.TxIn, 1)
|
|
txIns[0] = appmessage.NewTxIn(appmessage.NewOutpoint(transactionID, entry.Outpoint.Index), []byte{}, 0)
|
|
|
|
payeeAddress, err := util.DecodeAddress(miningAddress1, util.Bech32PrefixKaspaSim)
|
|
if err != nil {
|
|
t.Fatalf("Error decoding payeeAddress: %+v", err)
|
|
}
|
|
toScript, err := txscript.PayToAddrScript(payeeAddress)
|
|
if err != nil {
|
|
t.Fatalf("Error generating script: %+v", err)
|
|
}
|
|
|
|
txOuts := []*appmessage.TxOut{appmessage.NewTxOut(entry.UTXOEntry.Amount-1000, toScript)}
|
|
|
|
fromScriptCode, err := hex.DecodeString(entry.UTXOEntry.ScriptPublicKey.Script)
|
|
if err != nil {
|
|
t.Fatalf("Error decoding script public key: %s", err)
|
|
}
|
|
fromScript := &externalapi.ScriptPublicKey{Script: fromScriptCode, Version: 0}
|
|
fromAmount := entry.UTXOEntry.Amount
|
|
|
|
msgTx := appmessage.NewNativeMsgTx(constants.MaxTransactionVersion, txIns, txOuts)
|
|
|
|
privateKeyBytes, err := hex.DecodeString(miningAddress1PrivateKey)
|
|
if err != nil {
|
|
t.Fatalf("Error decoding private key: %+v", err)
|
|
}
|
|
privateKey, err := secp256k1.DeserializeSchnorrPrivateKeyFromSlice(privateKeyBytes)
|
|
if err != nil {
|
|
t.Fatalf("Error deserializing private key: %+v", err)
|
|
}
|
|
|
|
tx := appmessage.MsgTxToDomainTransaction(msgTx)
|
|
tx.Inputs[0].UTXOEntry = utxo.NewUTXOEntry(fromAmount, fromScript, false, 500)
|
|
|
|
signatureScript, err := txscript.SignatureScript(tx, 0, consensushashing.SigHashAll, privateKey,
|
|
&consensushashing.SighashReusedValues{})
|
|
if err != nil {
|
|
t.Fatalf("Error signing transaction: %+v", err)
|
|
}
|
|
msgTx.TxIn[0].SignatureScript = signatureScript
|
|
|
|
domainTransaction := appmessage.MsgTxToDomainTransaction(msgTx)
|
|
return appmessage.DomainTransactionToRPCTransaction(domainTransaction)
|
|
}
|