[NOD-1597] Implement a UTXO index (#1221)

* [NOD-1579] Rename AcceptedTxIDs to AcceptedTransactionIDs.

* [NOD-1579] Add InsertBlockResult to ValidateAndInsertBlock results.

* [NOD-1593] Rename InsertBlockResult to BlockInsertionResult.

* [NOD-1593] Add SelectedParentChainChanges to AddBlockToVirtual's result.

* [NOD-1593] Implement findSelectedParentChainChanges.

* [NOD-1593] Implement TestFindSelectedParentChainChanges.

* [NOD-1593] Fix a string.

* [NOD-1593] Finish implementing TestFindSelectedParentChainChanges.

* [NOD-1593] Fix merge errors.

* [NOD-1597] Begin implementing UTXOIndex.

* [NOD-1597] Connect UTXOIndex to RPC.

* [NOD-1597] Connect Consensus to UTXOIndex.

* [NOD-1597] Add AcceptanceData to BlockInfo.

* [NOD-1597] Implement UTXOIndex.Update().

* [NOD-1597] Implement add(), remove(), and discard() in utxoIndexStore.

* [NOD-1597] Add error cases to add() and remove().

* [NOD-1597] Add special cases to add() and remove().

* [NOD-1597] Implement commit.

* [NOD-1597] Add a mutex around UTXOIndex.Update().

* [NOD-1597] Return changes to the UTXO from Update().

* [NOD-1597] Add NotifyUTXOsChangedRequestMessage and related structs.

* [NOD-1597] Implement HandleNotifyUTXOsChanged.

* [NOD-1597] Begin implementing TestUTXOIndex.

* [NOD-1597] Implement RegisterForUTXOsChangedNotifications.

* [NOD-1597] Fix bad transaction.ID usage.

* [NOD-1597] Implement convertUTXOChangesToUTXOsChangedNotification.

* [NOD-1597] Make UTXOsChangedNotificationMessage.Removed UTXOsByAddressesEntry instead of just RPCOutpoint so that the client can discern which address was the UTXO removed for.

* [NOD-1597] Collect outpoints in TestUTXOIndex.

* [NOD-1597] Rename RPC stuff.

* [NOD-1597] Add messages for GetUTXOsByAddresses.

* [NOD-1597] Implement HandleGetUTXOsByAddresses.

* [NOD-1597] Implement GetUTXOsByAddresses.

* [NOD-1597] Implement UTXOs().

* [NOD-1597] Implement getUTXOOutpointEntryPairs().

* [NOD-1597] Expand TestUTXOIndex.

* [NOD-1597] Convert SubmitTransaction to use RPCTransaction instead of MsgTx.

* [NOD-1597] Finish implementing TestUTXOIndex.

* [NOD-1597] Add messages for GetVirtualSelectedParentBlueScore.

* [NOD-1597] Implement HandleGetVirtualSelectedParentBlueScore and GetVirtualSelectedParentBlueScore.

* [NOD-1597] Implement TestVirtualSelectedParentBlueScore.

* [NOD-1597] Implement NotifyVirtualSelectedParentBlueScoreChanged.

* [NOD-1597] Expand TestVirtualSelectedParentBlueScore.

* [NOD-1597] Implement notifyVirtualSelectedParentBlueScoreChanged.

* [NOD-1597] Make go lint happy.

* [NOD-1593] Fix merge errors.

* [NOD-1593] Rename findSelectedParentChainChanges to calculateSelectedParentChainChanges.

* [NOD-1593] Expand TestCalculateSelectedParentChainChanges.

* [NOD-1597] Add logs to utxoindex.go.

* [NOD-1597] Add logs to utxoindex/store.go.

* [NOD-1597] Add logs to RPCManager.NotifyXXX functions.

* [NOD-1597] Ignore transactions that aren't accepted.

* [NOD-1597] Use GetBlockAcceptanceData instead of GetBlockInfo.

* [NOD-1597] Convert scriptPublicKey to string directly, instead of using hex.

* [NOD-1597] Add a comment.

* [NOD-1597] Guard against calling utxoindex methods when utxoindex is turned off.

* [NOD-1597] Add lock to UTXOs.

* [NOD-1597] Guard against calls to getUTXOOutpointEntryPairs when staging isn't empty.
This commit is contained in:
stasatdaglabs 2020-12-20 17:24:56 +02:00 committed by GitHub
parent 843edc4ba5
commit 053bb351b5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
59 changed files with 4517 additions and 985 deletions

View File

@ -1,7 +1,11 @@
package appmessage
import (
"encoding/hex"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/utils/hashes"
"github.com/kaspanet/kaspad/domain/consensus/utils/subnetworks"
"github.com/kaspanet/kaspad/domain/consensus/utils/transactionid"
"github.com/kaspanet/kaspad/util/mstime"
)
@ -153,3 +157,113 @@ func outpointToDomainOutpoint(outpoint *Outpoint) *externalapi.DomainOutpoint {
Index: outpoint.Index,
}
}
// RPCTransactionToDomainTransaction converts RPCTransactions to DomainTransactions
func RPCTransactionToDomainTransaction(rpcTransaction *RPCTransaction) (*externalapi.DomainTransaction, error) {
inputs := make([]*externalapi.DomainTransactionInput, len(rpcTransaction.Inputs))
for i, input := range rpcTransaction.Inputs {
transactionIDBytes, err := hex.DecodeString(input.PreviousOutpoint.TransactionID)
if err != nil {
return nil, err
}
transactionID, err := transactionid.FromBytes(transactionIDBytes)
if err != nil {
return nil, err
}
previousOutpoint := &externalapi.DomainOutpoint{
TransactionID: *transactionID,
Index: input.PreviousOutpoint.Index,
}
signatureScript, err := hex.DecodeString(input.SignatureScript)
if err != nil {
return nil, err
}
inputs[i] = &externalapi.DomainTransactionInput{
PreviousOutpoint: *previousOutpoint,
SignatureScript: signatureScript,
Sequence: input.Sequence,
}
}
outputs := make([]*externalapi.DomainTransactionOutput, len(rpcTransaction.Outputs))
for i, output := range rpcTransaction.Outputs {
scriptPublicKey, err := hex.DecodeString(output.ScriptPubKey)
if err != nil {
return nil, err
}
outputs[i] = &externalapi.DomainTransactionOutput{
Value: output.Amount,
ScriptPublicKey: scriptPublicKey,
}
}
subnetworkIDBytes, err := hex.DecodeString(rpcTransaction.SubnetworkID)
if err != nil {
return nil, err
}
subnetworkID, err := subnetworks.FromBytes(subnetworkIDBytes)
if err != nil {
return nil, err
}
payloadHashBytes, err := hex.DecodeString(rpcTransaction.PayloadHash)
if err != nil {
return nil, err
}
payloadHash, err := hashes.FromBytes(payloadHashBytes)
if err != nil {
return nil, err
}
payload, err := hex.DecodeString(rpcTransaction.Payload)
if err != nil {
return nil, err
}
return &externalapi.DomainTransaction{
Version: rpcTransaction.Version,
Inputs: inputs,
Outputs: outputs,
LockTime: rpcTransaction.LockTime,
SubnetworkID: *subnetworkID,
Gas: rpcTransaction.LockTime,
PayloadHash: *payloadHash,
Payload: payload,
}, nil
}
// DomainTransactionToRPCTransaction converts DomainTransactions to RPCTransactions
func DomainTransactionToRPCTransaction(transaction *externalapi.DomainTransaction) *RPCTransaction {
inputs := make([]*RPCTransactionInput, len(transaction.Inputs))
for i, input := range transaction.Inputs {
transactionID := hex.EncodeToString(input.PreviousOutpoint.TransactionID[:])
previousOutpoint := &RPCOutpoint{
TransactionID: transactionID,
Index: input.PreviousOutpoint.Index,
}
signatureScript := hex.EncodeToString(input.SignatureScript)
inputs[i] = &RPCTransactionInput{
PreviousOutpoint: previousOutpoint,
SignatureScript: signatureScript,
Sequence: input.Sequence,
}
}
outputs := make([]*RPCTransactionOutput, len(transaction.Outputs))
for i, output := range transaction.Outputs {
scriptPublicKey := hex.EncodeToString(output.ScriptPublicKey)
outputs[i] = &RPCTransactionOutput{
Amount: output.Value,
ScriptPubKey: scriptPublicKey,
}
}
subnetworkID := hex.EncodeToString(transaction.SubnetworkID[:])
payloadHash := hex.EncodeToString(transaction.PayloadHash[:])
payload := hex.EncodeToString(transaction.Payload)
return &RPCTransaction{
Version: transaction.Version,
Inputs: inputs,
Outputs: outputs,
LockTime: transaction.LockTime,
SubnetworkID: subnetworkID,
Gas: transaction.LockTime,
PayloadHash: payloadHash,
Payload: payload,
}
}

View File

@ -106,6 +106,16 @@ const (
CmdShutDownResponseMessage
CmdGetHeadersRequestMessage
CmdGetHeadersResponseMessage
CmdNotifyUTXOsChangedRequestMessage
CmdNotifyUTXOsChangedResponseMessage
CmdUTXOsChangedNotificationMessage
CmdGetUTXOsByAddressesRequestMessage
CmdGetUTXOsByAddressesResponseMessage
CmdGetVirtualSelectedParentBlueScoreRequestMessage
CmdGetVirtualSelectedParentBlueScoreResponseMessage
CmdNotifyVirtualSelectedParentBlueScoreChangedRequestMessage
CmdNotifyVirtualSelectedParentBlueScoreChangedResponseMessage
CmdVirtualSelectedParentBlueScoreChangedNotificationMessage
)
// ProtocolMessageCommandToString maps all MessageCommands to their string representation
@ -139,53 +149,63 @@ var ProtocolMessageCommandToString = map[MessageCommand]string{
// RPCMessageCommandToString maps all MessageCommands to their string representation
var RPCMessageCommandToString = map[MessageCommand]string{
CmdGetCurrentNetworkRequestMessage: "GetCurrentNetworkRequest",
CmdGetCurrentNetworkResponseMessage: "GetCurrentNetworkResponse",
CmdSubmitBlockRequestMessage: "SubmitBlockRequest",
CmdSubmitBlockResponseMessage: "SubmitBlockResponse",
CmdGetBlockTemplateRequestMessage: "GetBlockTemplateRequest",
CmdGetBlockTemplateResponseMessage: "GetBlockTemplateResponse",
CmdGetBlockTemplateTransactionMessage: "CmdGetBlockTemplateTransaction",
CmdNotifyBlockAddedRequestMessage: "NotifyBlockAddedRequest",
CmdNotifyBlockAddedResponseMessage: "NotifyBlockAddedResponse",
CmdBlockAddedNotificationMessage: "BlockAddedNotification",
CmdGetPeerAddressesRequestMessage: "GetPeerAddressesRequest",
CmdGetPeerAddressesResponseMessage: "GetPeerAddressesResponse",
CmdGetSelectedTipHashRequestMessage: "GetSelectedTipHashRequest",
CmdGetSelectedTipHashResponseMessage: "GetSelectedTipHashResponse",
CmdGetMempoolEntryRequestMessage: "GetMempoolEntryRequest",
CmdGetMempoolEntryResponseMessage: "GetMempoolEntryResponse",
CmdGetConnectedPeerInfoRequestMessage: "GetConnectedPeerInfoRequest",
CmdGetConnectedPeerInfoResponseMessage: "GetConnectedPeerInfoResponse",
CmdAddPeerRequestMessage: "AddPeerRequest",
CmdAddPeerResponseMessage: "AddPeerResponse",
CmdSubmitTransactionRequestMessage: "SubmitTransactionRequest",
CmdSubmitTransactionResponseMessage: "SubmitTransactionResponse",
CmdNotifyChainChangedRequestMessage: "NotifyChainChangedRequest",
CmdNotifyChainChangedResponseMessage: "NotifyChainChangedResponse",
CmdChainChangedNotificationMessage: "ChainChangedNotification",
CmdGetBlockRequestMessage: "GetBlockRequest",
CmdGetBlockResponseMessage: "GetBlockResponse",
CmdGetSubnetworkRequestMessage: "GetSubnetworkRequest",
CmdGetSubnetworkResponseMessage: "GetSubnetworkResponse",
CmdGetChainFromBlockRequestMessage: "GetChainFromBlockRequest",
CmdGetChainFromBlockResponseMessage: "GetChainFromBlockResponse",
CmdGetBlocksRequestMessage: "GetBlocksRequest",
CmdGetBlocksResponseMessage: "GetBlocksResponse",
CmdGetBlockCountRequestMessage: "GetBlockCountRequest",
CmdGetBlockCountResponseMessage: "GetBlockCountResponse",
CmdGetBlockDAGInfoRequestMessage: "GetBlockDAGInfoRequest",
CmdGetBlockDAGInfoResponseMessage: "GetBlockDAGInfoResponse",
CmdResolveFinalityConflictRequestMessage: "ResolveFinalityConflictRequest",
CmdResolveFinalityConflictResponseMessage: "ResolveFinalityConflictResponse",
CmdNotifyFinalityConflictsRequestMessage: "NotifyFinalityConflictsRequest",
CmdNotifyFinalityConflictsResponseMessage: "NotifyFinalityConflictsResponse",
CmdFinalityConflictNotificationMessage: "FinalityConflictNotification",
CmdFinalityConflictResolvedNotificationMessage: "FinalityConflictResolvedNotification",
CmdGetMempoolEntriesRequestMessage: "GetMempoolEntriesRequestMessage",
CmdGetMempoolEntriesResponseMessage: "GetMempoolEntriesResponseMessage",
CmdGetHeadersRequestMessage: "GetHeadersRequest",
CmdGetHeadersResponseMessage: "GetHeadersResponse",
CmdGetCurrentNetworkRequestMessage: "GetCurrentNetworkRequest",
CmdGetCurrentNetworkResponseMessage: "GetCurrentNetworkResponse",
CmdSubmitBlockRequestMessage: "SubmitBlockRequest",
CmdSubmitBlockResponseMessage: "SubmitBlockResponse",
CmdGetBlockTemplateRequestMessage: "GetBlockTemplateRequest",
CmdGetBlockTemplateResponseMessage: "GetBlockTemplateResponse",
CmdGetBlockTemplateTransactionMessage: "CmdGetBlockTemplateTransaction",
CmdNotifyBlockAddedRequestMessage: "NotifyBlockAddedRequest",
CmdNotifyBlockAddedResponseMessage: "NotifyBlockAddedResponse",
CmdBlockAddedNotificationMessage: "BlockAddedNotification",
CmdGetPeerAddressesRequestMessage: "GetPeerAddressesRequest",
CmdGetPeerAddressesResponseMessage: "GetPeerAddressesResponse",
CmdGetSelectedTipHashRequestMessage: "GetSelectedTipHashRequest",
CmdGetSelectedTipHashResponseMessage: "GetSelectedTipHashResponse",
CmdGetMempoolEntryRequestMessage: "GetMempoolEntryRequest",
CmdGetMempoolEntryResponseMessage: "GetMempoolEntryResponse",
CmdGetConnectedPeerInfoRequestMessage: "GetConnectedPeerInfoRequest",
CmdGetConnectedPeerInfoResponseMessage: "GetConnectedPeerInfoResponse",
CmdAddPeerRequestMessage: "AddPeerRequest",
CmdAddPeerResponseMessage: "AddPeerResponse",
CmdSubmitTransactionRequestMessage: "SubmitTransactionRequest",
CmdSubmitTransactionResponseMessage: "SubmitTransactionResponse",
CmdNotifyChainChangedRequestMessage: "NotifyChainChangedRequest",
CmdNotifyChainChangedResponseMessage: "NotifyChainChangedResponse",
CmdChainChangedNotificationMessage: "ChainChangedNotification",
CmdGetBlockRequestMessage: "GetBlockRequest",
CmdGetBlockResponseMessage: "GetBlockResponse",
CmdGetSubnetworkRequestMessage: "GetSubnetworkRequest",
CmdGetSubnetworkResponseMessage: "GetSubnetworkResponse",
CmdGetChainFromBlockRequestMessage: "GetChainFromBlockRequest",
CmdGetChainFromBlockResponseMessage: "GetChainFromBlockResponse",
CmdGetBlocksRequestMessage: "GetBlocksRequest",
CmdGetBlocksResponseMessage: "GetBlocksResponse",
CmdGetBlockCountRequestMessage: "GetBlockCountRequest",
CmdGetBlockCountResponseMessage: "GetBlockCountResponse",
CmdGetBlockDAGInfoRequestMessage: "GetBlockDAGInfoRequest",
CmdGetBlockDAGInfoResponseMessage: "GetBlockDAGInfoResponse",
CmdResolveFinalityConflictRequestMessage: "ResolveFinalityConflictRequest",
CmdResolveFinalityConflictResponseMessage: "ResolveFinalityConflictResponse",
CmdNotifyFinalityConflictsRequestMessage: "NotifyFinalityConflictsRequest",
CmdNotifyFinalityConflictsResponseMessage: "NotifyFinalityConflictsResponse",
CmdFinalityConflictNotificationMessage: "FinalityConflictNotification",
CmdFinalityConflictResolvedNotificationMessage: "FinalityConflictResolvedNotification",
CmdGetMempoolEntriesRequestMessage: "GetMempoolEntriesRequest",
CmdGetMempoolEntriesResponseMessage: "GetMempoolEntriesResponse",
CmdGetHeadersRequestMessage: "GetHeadersRequest",
CmdGetHeadersResponseMessage: "GetHeadersResponse",
CmdNotifyUTXOsChangedRequestMessage: "NotifyUTXOsChangedRequest",
CmdNotifyUTXOsChangedResponseMessage: "NotifyUTXOsChangedResponse",
CmdUTXOsChangedNotificationMessage: "UTXOsChangedNotification",
CmdGetUTXOsByAddressesRequestMessage: "GetUTXOsByAddressesRequest",
CmdGetUTXOsByAddressesResponseMessage: "GetUTXOsByAddressesResponse",
CmdGetVirtualSelectedParentBlueScoreRequestMessage: "GetVirtualSelectedParentBlueScoreRequest",
CmdGetVirtualSelectedParentBlueScoreResponseMessage: "GetVirtualSelectedParentBlueScoreResponse",
CmdNotifyVirtualSelectedParentBlueScoreChangedRequestMessage: "NotifyVirtualSelectedParentBlueScoreChangedRequest",
CmdNotifyVirtualSelectedParentBlueScoreChangedResponseMessage: "NotifyVirtualSelectedParentBlueScoreChangedResponse",
CmdVirtualSelectedParentBlueScoreChangedNotificationMessage: "VirtualSelectedParentBlueScoreChangedNotification",
}
// Message is an interface that describes a kaspa message. A type that

View File

@ -0,0 +1,41 @@
package appmessage
// GetUTXOsByAddressesRequestMessage is an appmessage corresponding to
// its respective RPC message
type GetUTXOsByAddressesRequestMessage struct {
baseMessage
Addresses []string
}
// Command returns the protocol command string for the message
func (msg *GetUTXOsByAddressesRequestMessage) Command() MessageCommand {
return CmdGetUTXOsByAddressesRequestMessage
}
// NewGetUTXOsByAddressesRequestMessage returns a instance of the message
func NewGetUTXOsByAddressesRequestMessage(addresses []string) *GetUTXOsByAddressesRequestMessage {
return &GetUTXOsByAddressesRequestMessage{
Addresses: addresses,
}
}
// GetUTXOsByAddressesResponseMessage is an appmessage corresponding to
// its respective RPC message
type GetUTXOsByAddressesResponseMessage struct {
baseMessage
Entries []*UTXOsByAddressesEntry
Error *RPCError
}
// Command returns the protocol command string for the message
func (msg *GetUTXOsByAddressesResponseMessage) Command() MessageCommand {
return CmdGetUTXOsByAddressesResponseMessage
}
// NewGetUTXOsByAddressesResponseMessage returns a instance of the message
func NewGetUTXOsByAddressesResponseMessage(entries []*UTXOsByAddressesEntry) *GetUTXOsByAddressesResponseMessage {
return &GetUTXOsByAddressesResponseMessage{
Entries: entries,
}
}

View File

@ -0,0 +1,38 @@
package appmessage
// GetVirtualSelectedParentBlueScoreRequestMessage is an appmessage corresponding to
// its respective RPC message
type GetVirtualSelectedParentBlueScoreRequestMessage struct {
baseMessage
}
// Command returns the protocol command string for the message
func (msg *GetVirtualSelectedParentBlueScoreRequestMessage) Command() MessageCommand {
return CmdGetVirtualSelectedParentBlueScoreRequestMessage
}
// NewGetVirtualSelectedParentBlueScoreRequestMessage returns a instance of the message
func NewGetVirtualSelectedParentBlueScoreRequestMessage() *GetVirtualSelectedParentBlueScoreRequestMessage {
return &GetVirtualSelectedParentBlueScoreRequestMessage{}
}
// GetVirtualSelectedParentBlueScoreResponseMessage is an appmessage corresponding to
// its respective RPC message
type GetVirtualSelectedParentBlueScoreResponseMessage struct {
baseMessage
BlueScore uint64
Error *RPCError
}
// Command returns the protocol command string for the message
func (msg *GetVirtualSelectedParentBlueScoreResponseMessage) Command() MessageCommand {
return CmdGetVirtualSelectedParentBlueScoreResponseMessage
}
// NewGetVirtualSelectedParentBlueScoreResponseMessage returns a instance of the message
func NewGetVirtualSelectedParentBlueScoreResponseMessage(blueScore uint64) *GetVirtualSelectedParentBlueScoreResponseMessage {
return &GetVirtualSelectedParentBlueScoreResponseMessage{
BlueScore: blueScore,
}
}

View File

@ -0,0 +1,62 @@
package appmessage
// NotifyUTXOsChangedRequestMessage is an appmessage corresponding to
// its respective RPC message
type NotifyUTXOsChangedRequestMessage struct {
baseMessage
Addresses []string
}
// Command returns the protocol command string for the message
func (msg *NotifyUTXOsChangedRequestMessage) Command() MessageCommand {
return CmdNotifyUTXOsChangedRequestMessage
}
// NewNotifyUTXOsChangedRequestMessage returns a instance of the message
func NewNotifyUTXOsChangedRequestMessage(addresses []string) *NotifyUTXOsChangedRequestMessage {
return &NotifyUTXOsChangedRequestMessage{
Addresses: addresses,
}
}
// NotifyUTXOsChangedResponseMessage is an appmessage corresponding to
// its respective RPC message
type NotifyUTXOsChangedResponseMessage struct {
baseMessage
Error *RPCError
}
// Command returns the protocol command string for the message
func (msg *NotifyUTXOsChangedResponseMessage) Command() MessageCommand {
return CmdNotifyUTXOsChangedResponseMessage
}
// NewNotifyUTXOsChangedResponseMessage returns a instance of the message
func NewNotifyUTXOsChangedResponseMessage() *NotifyUTXOsChangedResponseMessage {
return &NotifyUTXOsChangedResponseMessage{}
}
// UTXOsChangedNotificationMessage is an appmessage corresponding to
// its respective RPC message
type UTXOsChangedNotificationMessage struct {
baseMessage
Added []*UTXOsByAddressesEntry
Removed []*UTXOsByAddressesEntry
}
// UTXOsByAddressesEntry represents a UTXO of some address
type UTXOsByAddressesEntry struct {
Address string
Outpoint *RPCOutpoint
UTXOEntry *RPCUTXOEntry
}
// Command returns the protocol command string for the message
func (msg *UTXOsChangedNotificationMessage) Command() MessageCommand {
return CmdUTXOsChangedNotificationMessage
}
// NewUTXOsChangedNotificationMessage returns a instance of the message
func NewUTXOsChangedNotificationMessage() *UTXOsChangedNotificationMessage {
return &UTXOsChangedNotificationMessage{}
}

View File

@ -0,0 +1,55 @@
package appmessage
// NotifyVirtualSelectedParentBlueScoreChangedRequestMessage is an appmessage corresponding to
// its respective RPC message
type NotifyVirtualSelectedParentBlueScoreChangedRequestMessage struct {
baseMessage
}
// Command returns the protocol command string for the message
func (msg *NotifyVirtualSelectedParentBlueScoreChangedRequestMessage) Command() MessageCommand {
return CmdNotifyVirtualSelectedParentBlueScoreChangedRequestMessage
}
// NewNotifyVirtualSelectedParentBlueScoreChangedRequestMessage returns a instance of the message
func NewNotifyVirtualSelectedParentBlueScoreChangedRequestMessage() *NotifyVirtualSelectedParentBlueScoreChangedRequestMessage {
return &NotifyVirtualSelectedParentBlueScoreChangedRequestMessage{}
}
// NotifyVirtualSelectedParentBlueScoreChangedResponseMessage is an appmessage corresponding to
// its respective RPC message
type NotifyVirtualSelectedParentBlueScoreChangedResponseMessage struct {
baseMessage
Error *RPCError
}
// Command returns the protocol command string for the message
func (msg *NotifyVirtualSelectedParentBlueScoreChangedResponseMessage) Command() MessageCommand {
return CmdNotifyVirtualSelectedParentBlueScoreChangedResponseMessage
}
// NewNotifyVirtualSelectedParentBlueScoreChangedResponseMessage returns a instance of the message
func NewNotifyVirtualSelectedParentBlueScoreChangedResponseMessage() *NotifyVirtualSelectedParentBlueScoreChangedResponseMessage {
return &NotifyVirtualSelectedParentBlueScoreChangedResponseMessage{}
}
// VirtualSelectedParentBlueScoreChangedNotificationMessage is an appmessage corresponding to
// its respective RPC message
type VirtualSelectedParentBlueScoreChangedNotificationMessage struct {
baseMessage
VirtualSelectedParentBlueScore uint64
}
// Command returns the protocol command string for the message
func (msg *VirtualSelectedParentBlueScoreChangedNotificationMessage) Command() MessageCommand {
return CmdVirtualSelectedParentBlueScoreChangedNotificationMessage
}
// NewVirtualSelectedParentBlueScoreChangedNotificationMessage returns a instance of the message
func NewVirtualSelectedParentBlueScoreChangedNotificationMessage(
virtualSelectedParentBlueScore uint64) *VirtualSelectedParentBlueScoreChangedNotificationMessage {
return &VirtualSelectedParentBlueScoreChangedNotificationMessage{
VirtualSelectedParentBlueScore: virtualSelectedParentBlueScore,
}
}

View File

@ -4,7 +4,7 @@ package appmessage
// its respective RPC message
type SubmitTransactionRequestMessage struct {
baseMessage
Transaction *MsgTx
Transaction *RPCTransaction
}
// Command returns the protocol command string for the message
@ -13,7 +13,7 @@ func (msg *SubmitTransactionRequestMessage) Command() MessageCommand {
}
// NewSubmitTransactionRequestMessage returns a instance of the message
func NewSubmitTransactionRequestMessage(transaction *MsgTx) *SubmitTransactionRequestMessage {
func NewSubmitTransactionRequestMessage(transaction *RPCTransaction) *SubmitTransactionRequestMessage {
return &SubmitTransactionRequestMessage{
Transaction: transaction,
}
@ -23,7 +23,7 @@ func NewSubmitTransactionRequestMessage(transaction *MsgTx) *SubmitTransactionRe
// its respective RPC message
type SubmitTransactionResponseMessage struct {
baseMessage
TxID string
TransactionID string
Error *RPCError
}
@ -34,8 +34,52 @@ func (msg *SubmitTransactionResponseMessage) Command() MessageCommand {
}
// NewSubmitTransactionResponseMessage returns a instance of the message
func NewSubmitTransactionResponseMessage(txID string) *SubmitTransactionResponseMessage {
func NewSubmitTransactionResponseMessage(transactionID string) *SubmitTransactionResponseMessage {
return &SubmitTransactionResponseMessage{
TxID: txID,
TransactionID: transactionID,
}
}
// RPCTransaction is a kaspad transaction representation meant to be
// used over RPC
type RPCTransaction struct {
Version int32
Inputs []*RPCTransactionInput
Outputs []*RPCTransactionOutput
LockTime uint64
SubnetworkID string
Gas uint64
PayloadHash string
Payload string
}
// RPCTransactionInput is a kaspad transaction input representation
// meant to be used over RPC
type RPCTransactionInput struct {
PreviousOutpoint *RPCOutpoint
SignatureScript string
Sequence uint64
}
// RPCTransactionOutput is a kaspad transaction output representation
// meant to be used over RPC
type RPCTransactionOutput struct {
Amount uint64
ScriptPubKey string
}
// RPCOutpoint is a kaspad outpoint representation meant to be used
// over RPC
type RPCOutpoint struct {
TransactionID string
Index uint32
}
// RPCUTXOEntry is a kaspad utxo entry representation meant to be used
// over RPC
type RPCUTXOEntry struct {
Amount uint64
ScriptPubKey string
BlockBlueScore uint64
IsCoinbase bool
}

View File

@ -2,6 +2,7 @@ package app
import (
"fmt"
"github.com/kaspanet/kaspad/domain/utxoindex"
"sync/atomic"
infrastructuredatabase "github.com/kaspanet/kaspad/infrastructure/db/database"
@ -93,6 +94,12 @@ func NewComponentManager(cfg *config.Config, db infrastructuredatabase.Database,
return nil, err
}
var utxoIndex *utxoindex.UTXOIndex
if cfg.UTXOIndex {
utxoIndex = utxoindex.New(domain.Consensus(), db)
log.Infof("UTXO index started")
}
connectionManager, err := connmanager.New(cfg, netAdapter, addressManager)
if err != nil {
return nil, err
@ -101,7 +108,7 @@ func NewComponentManager(cfg *config.Config, db infrastructuredatabase.Database,
if err != nil {
return nil, err
}
rpcManager := setupRPC(cfg, domain, netAdapter, protocolManager, connectionManager, addressManager, interrupt)
rpcManager := setupRPC(cfg, domain, netAdapter, protocolManager, connectionManager, addressManager, utxoIndex, interrupt)
return &ComponentManager{
cfg: cfg,
@ -121,11 +128,20 @@ func setupRPC(
protocolManager *protocol.Manager,
connectionManager *connmanager.ConnectionManager,
addressManager *addressmanager.AddressManager,
utxoIndex *utxoindex.UTXOIndex,
shutDownChan chan<- struct{},
) *rpc.Manager {
rpcManager := rpc.NewManager(
cfg, domain, netAdapter, protocolManager, connectionManager, addressManager, shutDownChan)
cfg,
domain,
netAdapter,
protocolManager,
connectionManager,
addressManager,
utxoIndex,
shutDownChan,
)
protocolManager.SetOnBlockAddedToDAGHandler(rpcManager.NotifyBlockAddedToDAG)
return rpcManager

View File

@ -17,7 +17,9 @@ import (
// OnNewBlock updates the mempool after a new block arrival, and
// relays newly unorphaned transactions and possibly rebroadcast
// manually added transactions when not in IBD.
func (f *FlowContext) OnNewBlock(block *externalapi.DomainBlock) error {
func (f *FlowContext) OnNewBlock(block *externalapi.DomainBlock,
blockInsertionResult *externalapi.BlockInsertionResult) error {
hash := consensushashing.BlockHash(block)
log.Debugf("OnNewBlock start for block %s", hash)
defer log.Debugf("OnNewBlock end for block %s", hash)
@ -37,7 +39,7 @@ func (f *FlowContext) OnNewBlock(block *externalapi.DomainBlock) error {
if f.onBlockAddedToDAGHandler != nil {
log.Tracef("OnNewBlock: calling f.onBlockAddedToDAGHandler for block %s", hash)
err := f.onBlockAddedToDAGHandler(newBlock)
err := f.onBlockAddedToDAGHandler(newBlock, blockInsertionResult)
if err != nil {
return err
}
@ -89,7 +91,7 @@ func (f *FlowContext) SharedRequestedBlocks() *blockrelay.SharedRequestedBlocks
// AddBlock adds the given block to the DAG and propagates it.
func (f *FlowContext) AddBlock(block *externalapi.DomainBlock) error {
_, err := f.Domain().Consensus().ValidateAndInsertBlock(block)
blockInsertionResult, err := f.Domain().Consensus().ValidateAndInsertBlock(block)
if err != nil {
if errors.As(err, &ruleerrors.RuleError{}) {
log.Infof("Validation failed for block %s: %s", consensushashing.BlockHash(block), err)
@ -97,7 +99,7 @@ func (f *FlowContext) AddBlock(block *externalapi.DomainBlock) error {
}
return err
}
err = f.OnNewBlock(block)
err = f.OnNewBlock(block, blockInsertionResult)
if err != nil {
return err
}

View File

@ -20,7 +20,7 @@ import (
// OnBlockAddedToDAGHandler is a handler function that's triggered
// when a block is added to the DAG
type OnBlockAddedToDAGHandler func(block *externalapi.DomainBlock) error
type OnBlockAddedToDAGHandler func(block *externalapi.DomainBlock, blockInsertionResult *externalapi.BlockInsertionResult) error
// OnTransactionAddedToMempoolHandler is a handler function that's triggered
// when a transaction is added to the mempool

View File

@ -25,7 +25,7 @@ type RelayInvsContext interface {
Domain() domain.Domain
Config() *config.Config
NetAdapter() *netadapter.NetAdapter
OnNewBlock(block *externalapi.DomainBlock) error
OnNewBlock(block *externalapi.DomainBlock, blockInsertionResult *externalapi.BlockInsertionResult) error
SharedRequestedBlocks() *SharedRequestedBlocks
Broadcast(message appmessage.Message) error
AddOrphan(orphanBlock *externalapi.DomainBlock)
@ -102,7 +102,7 @@ func (flow *handleRelayInvsFlow) start() error {
}
log.Debugf("Processing block %s", inv.Hash)
missingParents, err := flow.processBlock(block)
missingParents, blockInsertionResult, err := flow.processBlock(block)
if err != nil {
return err
}
@ -121,7 +121,7 @@ func (flow *handleRelayInvsFlow) start() error {
return err
}
log.Infof("Accepted block %s via relay", inv.Hash)
err = flow.OnNewBlock(block)
err = flow.OnNewBlock(block, blockInsertionResult)
if err != nil {
return err
}
@ -199,22 +199,22 @@ func (flow *handleRelayInvsFlow) readMsgBlock() (msgBlock *appmessage.MsgBlock,
}
}
func (flow *handleRelayInvsFlow) processBlock(block *externalapi.DomainBlock) ([]*externalapi.DomainHash, error) {
func (flow *handleRelayInvsFlow) processBlock(block *externalapi.DomainBlock) ([]*externalapi.DomainHash, *externalapi.BlockInsertionResult, error) {
blockHash := consensushashing.BlockHash(block)
_, err := flow.Domain().Consensus().ValidateAndInsertBlock(block)
blockInsertionResult, err := flow.Domain().Consensus().ValidateAndInsertBlock(block)
if err != nil {
if !errors.As(err, &ruleerrors.RuleError{}) {
return nil, errors.Wrapf(err, "failed to process block %s", blockHash)
return nil, nil, errors.Wrapf(err, "failed to process block %s", blockHash)
}
missingParentsError := &ruleerrors.ErrMissingParents{}
if errors.As(err, missingParentsError) {
return missingParentsError.MissingParentHashes, nil
return missingParentsError.MissingParentHashes, nil, nil
}
log.Warnf("Rejected block %s from %s: %s", blockHash, flow.peer, err)
return nil, protocolerrors.Wrapf(true, err, "got invalid block %s from relay", blockHash)
return nil, nil, protocolerrors.Wrapf(true, err, "got invalid block %s from relay", blockHash)
}
return nil, nil
return nil, blockInsertionResult, nil
}
func (flow *handleRelayInvsFlow) relayBlock(block *externalapi.DomainBlock) error {

View File

@ -283,11 +283,11 @@ func (flow *handleRelayInvsFlow) syncMissingBlockBodies(highHash *externalapi.Do
return protocolerrors.Errorf(true, "expected block %s but got %s", expectedHash, blockHash)
}
_, err = flow.Domain().Consensus().ValidateAndInsertBlock(block)
blockInsertionResult, err := flow.Domain().Consensus().ValidateAndInsertBlock(block)
if err != nil {
return protocolerrors.ConvertToBanningProtocolErrorIfRuleError(err, "invalid block %s", blockHash)
}
err = flow.OnNewBlock(block)
err = flow.OnNewBlock(block, blockInsertionResult)
if err != nil {
return err
}

View File

@ -6,7 +6,9 @@ import (
"github.com/kaspanet/kaspad/app/rpc/rpccontext"
"github.com/kaspanet/kaspad/domain"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/utxoindex"
"github.com/kaspanet/kaspad/infrastructure/config"
"github.com/kaspanet/kaspad/infrastructure/logger"
"github.com/kaspanet/kaspad/infrastructure/network/addressmanager"
"github.com/kaspanet/kaspad/infrastructure/network/connmanager"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter"
@ -25,6 +27,7 @@ func NewManager(
protocolManager *protocol.Manager,
connectionManager *connmanager.ConnectionManager,
addressManager *addressmanager.AddressManager,
utxoIndex *utxoindex.UTXOIndex,
shutDownChan chan<- struct{}) *Manager {
manager := Manager{
@ -35,6 +38,7 @@ func NewManager(
protocolManager,
connectionManager,
addressManager,
utxoIndex,
shutDownChan,
),
}
@ -44,19 +48,63 @@ func NewManager(
}
// NotifyBlockAddedToDAG notifies the manager that a block has been added to the DAG
func (m *Manager) NotifyBlockAddedToDAG(block *externalapi.DomainBlock) error {
notification := appmessage.NewBlockAddedNotificationMessage(appmessage.DomainBlockToMsgBlock(block))
return m.context.NotificationManager.NotifyBlockAdded(notification)
func (m *Manager) NotifyBlockAddedToDAG(block *externalapi.DomainBlock, blockInsertionResult *externalapi.BlockInsertionResult) error {
onEnd := logger.LogAndMeasureExecutionTime(log, "RPCManager.NotifyBlockAddedToDAG")
defer onEnd()
if m.context.Config.UTXOIndex {
err := m.notifyUTXOsChanged(blockInsertionResult)
if err != nil {
return err
}
}
err := m.notifyVirtualSelectedParentBlueScoreChanged()
if err != nil {
return err
}
blockAddedNotification := appmessage.NewBlockAddedNotificationMessage(appmessage.DomainBlockToMsgBlock(block))
return m.context.NotificationManager.NotifyBlockAdded(blockAddedNotification)
}
// NotifyFinalityConflict notifies the manager that there's a finality conflict in the DAG
func (m *Manager) NotifyFinalityConflict(violatingBlockHash string) error {
onEnd := logger.LogAndMeasureExecutionTime(log, "RPCManager.NotifyFinalityConflict")
defer onEnd()
notification := appmessage.NewFinalityConflictNotificationMessage(violatingBlockHash)
return m.context.NotificationManager.NotifyFinalityConflict(notification)
}
// NotifyFinalityConflictResolved notifies the manager that a finality conflict in the DAG has been resolved
func (m *Manager) NotifyFinalityConflictResolved(finalityBlockHash string) error {
onEnd := logger.LogAndMeasureExecutionTime(log, "RPCManager.NotifyFinalityConflictResolved")
defer onEnd()
notification := appmessage.NewFinalityConflictResolvedNotificationMessage(finalityBlockHash)
return m.context.NotificationManager.NotifyFinalityConflictResolved(notification)
}
func (m *Manager) notifyUTXOsChanged(blockInsertionResult *externalapi.BlockInsertionResult) error {
onEnd := logger.LogAndMeasureExecutionTime(log, "RPCManager.NotifyUTXOsChanged")
defer onEnd()
utxoIndexChanges, err := m.context.UTXOIndex.Update(blockInsertionResult.SelectedParentChainChanges)
if err != nil {
return err
}
return m.context.NotificationManager.NotifyUTXOsChanged(utxoIndexChanges)
}
func (m *Manager) notifyVirtualSelectedParentBlueScoreChanged() error {
onEnd := logger.LogAndMeasureExecutionTime(log, "RPCManager.NotifyVirtualSelectedParentBlueScoreChanged")
defer onEnd()
virtualInfo, err := m.context.Domain.Consensus().GetVirtualInfo()
if err != nil {
return err
}
notification := appmessage.NewVirtualSelectedParentBlueScoreChangedNotificationMessage(virtualInfo.BlueScore)
return m.context.NotificationManager.NotifyVirtualSelectedParentBlueScoreChanged(notification)
}

View File

@ -12,28 +12,32 @@ import (
type handler func(context *rpccontext.Context, router *router.Router, request appmessage.Message) (appmessage.Message, error)
var handlers = map[appmessage.MessageCommand]handler{
appmessage.CmdGetCurrentNetworkRequestMessage: rpchandlers.HandleGetCurrentNetwork,
appmessage.CmdSubmitBlockRequestMessage: rpchandlers.HandleSubmitBlock,
appmessage.CmdGetBlockTemplateRequestMessage: rpchandlers.HandleGetBlockTemplate,
appmessage.CmdNotifyBlockAddedRequestMessage: rpchandlers.HandleNotifyBlockAdded,
appmessage.CmdGetPeerAddressesRequestMessage: rpchandlers.HandleGetPeerAddresses,
appmessage.CmdGetSelectedTipHashRequestMessage: rpchandlers.HandleGetSelectedTipHash,
appmessage.CmdGetMempoolEntryRequestMessage: rpchandlers.HandleGetMempoolEntry,
appmessage.CmdGetConnectedPeerInfoRequestMessage: rpchandlers.HandleGetConnectedPeerInfo,
appmessage.CmdAddPeerRequestMessage: rpchandlers.HandleAddPeer,
appmessage.CmdSubmitTransactionRequestMessage: rpchandlers.HandleSubmitTransaction,
appmessage.CmdNotifyChainChangedRequestMessage: rpchandlers.HandleNotifyChainChanged,
appmessage.CmdGetBlockRequestMessage: rpchandlers.HandleGetBlock,
appmessage.CmdGetSubnetworkRequestMessage: rpchandlers.HandleGetSubnetwork,
appmessage.CmdGetChainFromBlockRequestMessage: rpchandlers.HandleGetChainFromBlock,
appmessage.CmdGetBlocksRequestMessage: rpchandlers.HandleGetBlocks,
appmessage.CmdGetBlockCountRequestMessage: rpchandlers.HandleGetBlockCount,
appmessage.CmdGetBlockDAGInfoRequestMessage: rpchandlers.HandleGetBlockDAGInfo,
appmessage.CmdResolveFinalityConflictRequestMessage: rpchandlers.HandleResolveFinalityConflict,
appmessage.CmdNotifyFinalityConflictsRequestMessage: rpchandlers.HandleNotifyFinalityConflicts,
appmessage.CmdGetMempoolEntriesRequestMessage: rpchandlers.HandleGetMempoolEntries,
appmessage.CmdShutDownRequestMessage: rpchandlers.HandleShutDown,
appmessage.CmdGetHeadersRequestMessage: rpchandlers.HandleGetHeaders,
appmessage.CmdGetCurrentNetworkRequestMessage: rpchandlers.HandleGetCurrentNetwork,
appmessage.CmdSubmitBlockRequestMessage: rpchandlers.HandleSubmitBlock,
appmessage.CmdGetBlockTemplateRequestMessage: rpchandlers.HandleGetBlockTemplate,
appmessage.CmdNotifyBlockAddedRequestMessage: rpchandlers.HandleNotifyBlockAdded,
appmessage.CmdGetPeerAddressesRequestMessage: rpchandlers.HandleGetPeerAddresses,
appmessage.CmdGetSelectedTipHashRequestMessage: rpchandlers.HandleGetSelectedTipHash,
appmessage.CmdGetMempoolEntryRequestMessage: rpchandlers.HandleGetMempoolEntry,
appmessage.CmdGetConnectedPeerInfoRequestMessage: rpchandlers.HandleGetConnectedPeerInfo,
appmessage.CmdAddPeerRequestMessage: rpchandlers.HandleAddPeer,
appmessage.CmdSubmitTransactionRequestMessage: rpchandlers.HandleSubmitTransaction,
appmessage.CmdNotifyChainChangedRequestMessage: rpchandlers.HandleNotifyChainChanged,
appmessage.CmdGetBlockRequestMessage: rpchandlers.HandleGetBlock,
appmessage.CmdGetSubnetworkRequestMessage: rpchandlers.HandleGetSubnetwork,
appmessage.CmdGetChainFromBlockRequestMessage: rpchandlers.HandleGetChainFromBlock,
appmessage.CmdGetBlocksRequestMessage: rpchandlers.HandleGetBlocks,
appmessage.CmdGetBlockCountRequestMessage: rpchandlers.HandleGetBlockCount,
appmessage.CmdGetBlockDAGInfoRequestMessage: rpchandlers.HandleGetBlockDAGInfo,
appmessage.CmdResolveFinalityConflictRequestMessage: rpchandlers.HandleResolveFinalityConflict,
appmessage.CmdNotifyFinalityConflictsRequestMessage: rpchandlers.HandleNotifyFinalityConflicts,
appmessage.CmdGetMempoolEntriesRequestMessage: rpchandlers.HandleGetMempoolEntries,
appmessage.CmdShutDownRequestMessage: rpchandlers.HandleShutDown,
appmessage.CmdGetHeadersRequestMessage: rpchandlers.HandleGetHeaders,
appmessage.CmdNotifyUTXOsChangedRequestMessage: rpchandlers.HandleNotifyUTXOsChanged,
appmessage.CmdGetUTXOsByAddressesRequestMessage: rpchandlers.HandleGetUTXOsByAddresses,
appmessage.CmdGetVirtualSelectedParentBlueScoreRequestMessage: rpchandlers.HandleGetVirtualSelectedParentBlueScore,
appmessage.CmdNotifyVirtualSelectedParentBlueScoreChangedRequestMessage: rpchandlers.HandleNotifyVirtualSelectedParentBlueScoreChanged,
}
func (m *Manager) routerInitializer(router *router.Router, netConnection *netadapter.NetConnection) {

View File

@ -3,6 +3,7 @@ package rpccontext
import (
"github.com/kaspanet/kaspad/app/protocol"
"github.com/kaspanet/kaspad/domain"
"github.com/kaspanet/kaspad/domain/utxoindex"
"github.com/kaspanet/kaspad/infrastructure/config"
"github.com/kaspanet/kaspad/infrastructure/network/addressmanager"
"github.com/kaspanet/kaspad/infrastructure/network/connmanager"
@ -17,6 +18,7 @@ type Context struct {
ProtocolManager *protocol.Manager
ConnectionManager *connmanager.ConnectionManager
AddressManager *addressmanager.AddressManager
UTXOIndex *utxoindex.UTXOIndex
ShutDownChan chan<- struct{}
NotificationManager *NotificationManager
@ -29,6 +31,7 @@ func NewContext(cfg *config.Config,
protocolManager *protocol.Manager,
connectionManager *connmanager.ConnectionManager,
addressManager *addressmanager.AddressManager,
utxoIndex *utxoindex.UTXOIndex,
shutDownChan chan<- struct{}) *Context {
context := &Context{
@ -38,8 +41,10 @@ func NewContext(cfg *config.Config,
ProtocolManager: protocolManager,
ConnectionManager: connectionManager,
AddressManager: addressManager,
UTXOIndex: utxoIndex,
ShutDownChan: shutDownChan,
}
context.NotificationManager = NewNotificationManager()
return context
}

View File

@ -1,7 +1,9 @@
package rpccontext
import (
"encoding/hex"
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/domain/utxoindex"
routerpkg "github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
"github.com/pkg/errors"
"sync"
@ -13,12 +15,23 @@ type NotificationManager struct {
listeners map[*routerpkg.Router]*NotificationListener
}
// UTXOsChangedNotificationAddress represents a kaspad address.
// This type is meant to be used in UTXOsChanged notifications
type UTXOsChangedNotificationAddress struct {
Address string
ScriptPublicKeyString utxoindex.ScriptPublicKeyString
}
// NotificationListener represents a registered RPC notification listener
type NotificationListener struct {
propagateBlockAddedNotifications bool
propagateChainChangedNotifications bool
propagateFinalityConflictNotifications bool
propagateFinalityConflictResolvedNotifications bool
propagateBlockAddedNotifications bool
propagateChainChangedNotifications bool
propagateFinalityConflictNotifications bool
propagateFinalityConflictResolvedNotifications bool
propagateUTXOsChangedNotifications bool
propagateVirtualSelectedParentBlueScoreChangedNotifications bool
propagateUTXOsChangedNotificationAddresses []*UTXOsChangedNotificationAddress
}
// NewNotificationManager creates a new NotificationManager
@ -123,12 +136,58 @@ func (nm *NotificationManager) NotifyFinalityConflictResolved(notification *appm
return nil
}
// NotifyUTXOsChanged notifies the notification manager that UTXOs have been changed
func (nm *NotificationManager) NotifyUTXOsChanged(utxoChanges *utxoindex.UTXOChanges) error {
nm.RLock()
defer nm.RUnlock()
for router, listener := range nm.listeners {
if listener.propagateUTXOsChangedNotifications {
// Filter utxoChanges and create a notification
notification := listener.convertUTXOChangesToUTXOsChangedNotification(utxoChanges)
// Don't send the notification if it's empty
if len(notification.Added) == 0 && len(notification.Removed) == 0 {
continue
}
// Enqueue the notification
err := router.OutgoingRoute().Enqueue(notification)
if err != nil {
return err
}
}
}
return nil
}
// NotifyVirtualSelectedParentBlueScoreChanged notifies the notification manager that the DAG's
// virtual selected parent blue score has changed
func (nm *NotificationManager) NotifyVirtualSelectedParentBlueScoreChanged(
notification *appmessage.VirtualSelectedParentBlueScoreChangedNotificationMessage) error {
nm.RLock()
defer nm.RUnlock()
for router, listener := range nm.listeners {
if listener.propagateVirtualSelectedParentBlueScoreChangedNotifications {
err := router.OutgoingRoute().Enqueue(notification)
if err != nil {
return err
}
}
}
return nil
}
func newNotificationListener() *NotificationListener {
return &NotificationListener{
propagateBlockAddedNotifications: false,
propagateChainChangedNotifications: false,
propagateFinalityConflictNotifications: false,
propagateFinalityConflictResolvedNotifications: false,
propagateBlockAddedNotifications: false,
propagateChainChangedNotifications: false,
propagateFinalityConflictNotifications: false,
propagateFinalityConflictResolvedNotifications: false,
propagateUTXOsChangedNotifications: false,
propagateVirtualSelectedParentBlueScoreChangedNotifications: false,
}
}
@ -155,3 +214,40 @@ func (nl *NotificationListener) PropagateFinalityConflictNotifications() {
func (nl *NotificationListener) PropagateFinalityConflictResolvedNotifications() {
nl.propagateFinalityConflictResolvedNotifications = true
}
// PropagateUTXOsChangedNotifications instructs the listener to send UTXOs changed notifications
// to the remote listener
func (nl *NotificationListener) PropagateUTXOsChangedNotifications(addresses []*UTXOsChangedNotificationAddress) {
nl.propagateUTXOsChangedNotifications = true
nl.propagateUTXOsChangedNotificationAddresses = addresses
}
func (nl *NotificationListener) convertUTXOChangesToUTXOsChangedNotification(
utxoChanges *utxoindex.UTXOChanges) *appmessage.UTXOsChangedNotificationMessage {
notification := &appmessage.UTXOsChangedNotificationMessage{}
for _, listenerAddress := range nl.propagateUTXOsChangedNotificationAddresses {
listenerScriptPublicKeyString := listenerAddress.ScriptPublicKeyString
if addedPairs, ok := utxoChanges.Added[listenerScriptPublicKeyString]; ok {
notification.Added = ConvertUTXOOutpointEntryPairsToUTXOsByAddressesEntries(listenerAddress.Address, addedPairs)
}
if removedOutpoints, ok := utxoChanges.Removed[listenerScriptPublicKeyString]; ok {
for outpoint := range removedOutpoints {
notification.Removed = append(notification.Removed, &appmessage.UTXOsByAddressesEntry{
Address: listenerAddress.Address,
Outpoint: &appmessage.RPCOutpoint{
TransactionID: hex.EncodeToString(outpoint.TransactionID[:]),
Index: outpoint.Index,
},
})
}
}
}
return notification
}
// PropagateVirtualSelectedParentBlueScoreChangedNotifications instructs the listener to send
// virtual selected parent blue score notifications to the remote listener
func (nl *NotificationListener) PropagateVirtualSelectedParentBlueScoreChangedNotifications() {
nl.propagateVirtualSelectedParentBlueScoreChangedNotifications = true
}

View File

@ -0,0 +1,29 @@
package rpccontext
import (
"encoding/hex"
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/domain/utxoindex"
)
// ConvertUTXOOutpointEntryPairsToUTXOsByAddressesEntries converts
// UTXOOutpointEntryPairs to a slice of UTXOsByAddressesEntry
func ConvertUTXOOutpointEntryPairsToUTXOsByAddressesEntries(address string, pairs utxoindex.UTXOOutpointEntryPairs) []*appmessage.UTXOsByAddressesEntry {
utxosByAddressesEntries := make([]*appmessage.UTXOsByAddressesEntry, 0, len(pairs))
for outpoint, utxoEntry := range pairs {
utxosByAddressesEntries = append(utxosByAddressesEntries, &appmessage.UTXOsByAddressesEntry{
Address: address,
Outpoint: &appmessage.RPCOutpoint{
TransactionID: hex.EncodeToString(outpoint.TransactionID[:]),
Index: outpoint.Index,
},
UTXOEntry: &appmessage.RPCUTXOEntry{
Amount: utxoEntry.Amount(),
ScriptPubKey: hex.EncodeToString(utxoEntry.ScriptPublicKey()),
BlockBlueScore: utxoEntry.BlockBlueScore(),
IsCoinbase: utxoEntry.IsCoinbase(),
},
})
}
return utxosByAddressesEntries
}

View File

@ -0,0 +1,45 @@
package rpchandlers
import (
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/app/rpc/rpccontext"
"github.com/kaspanet/kaspad/domain/consensus/utils/txscript"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
"github.com/kaspanet/kaspad/util"
)
// HandleGetUTXOsByAddresses handles the respectively named RPC command
func HandleGetUTXOsByAddresses(context *rpccontext.Context, _ *router.Router, request appmessage.Message) (appmessage.Message, error) {
if !context.Config.UTXOIndex {
errorMessage := &appmessage.GetUTXOsByAddressesResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("Method unavailable when kaspad is run without --utxoindex")
return errorMessage, nil
}
getUTXOsByAddressesRequest := request.(*appmessage.GetUTXOsByAddressesRequestMessage)
allEntries := make([]*appmessage.UTXOsByAddressesEntry, 0)
for _, addressString := range getUTXOsByAddressesRequest.Addresses {
address, err := util.DecodeAddress(addressString, context.Config.ActiveNetParams.Prefix)
if err != nil {
errorMessage := &appmessage.GetUTXOsByAddressesResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("Could not decode address '%s': %s", addressString, err)
return errorMessage, nil
}
scriptPublicKey, err := txscript.PayToAddrScript(address)
if err != nil {
errorMessage := &appmessage.GetUTXOsByAddressesResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("Could not create a scriptPublicKey for address '%s': %s", addressString, err)
return errorMessage, nil
}
utxoOutpointEntryPairs, err := context.UTXOIndex.UTXOs(scriptPublicKey)
if err != nil {
return nil, err
}
entries := rpccontext.ConvertUTXOOutpointEntryPairsToUTXOsByAddressesEntries(addressString, utxoOutpointEntryPairs)
allEntries = append(allEntries, entries...)
}
response := appmessage.NewGetUTXOsByAddressesResponseMessage(allEntries)
return response, nil
}

View File

@ -0,0 +1,16 @@
package rpchandlers
import (
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/app/rpc/rpccontext"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
)
// HandleGetVirtualSelectedParentBlueScore handles the respectively named RPC command
func HandleGetVirtualSelectedParentBlueScore(context *rpccontext.Context, _ *router.Router, _ appmessage.Message) (appmessage.Message, error) {
virtualInfo, err := context.Domain.Consensus().GetVirtualInfo()
if err != nil {
return nil, err
}
return appmessage.NewGetVirtualSelectedParentBlueScoreResponseMessage(virtualInfo.BlueScore), nil
}

View File

@ -0,0 +1,51 @@
package rpchandlers
import (
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/app/rpc/rpccontext"
"github.com/kaspanet/kaspad/domain/consensus/utils/txscript"
"github.com/kaspanet/kaspad/domain/utxoindex"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
"github.com/kaspanet/kaspad/util"
)
// HandleNotifyUTXOsChanged handles the respectively named RPC command
func HandleNotifyUTXOsChanged(context *rpccontext.Context, router *router.Router, request appmessage.Message) (appmessage.Message, error) {
if !context.Config.UTXOIndex {
errorMessage := appmessage.NewNotifyUTXOsChangedResponseMessage()
errorMessage.Error = appmessage.RPCErrorf("Method unavailable when kaspad is run without --utxoindex")
return errorMessage, nil
}
notifyUTXOsChangedRequest := request.(*appmessage.NotifyUTXOsChangedRequestMessage)
addresses := make([]*rpccontext.UTXOsChangedNotificationAddress, len(notifyUTXOsChangedRequest.Addresses))
for i, addressString := range notifyUTXOsChangedRequest.Addresses {
address, err := util.DecodeAddress(addressString, context.Config.ActiveNetParams.Prefix)
if err != nil {
errorMessage := appmessage.NewNotifyUTXOsChangedResponseMessage()
errorMessage.Error = appmessage.RPCErrorf("Could not decode address '%s': %s", addressString, err)
return errorMessage, nil
}
scriptPublicKey, err := txscript.PayToAddrScript(address)
if err != nil {
errorMessage := appmessage.NewNotifyUTXOsChangedResponseMessage()
errorMessage.Error = appmessage.RPCErrorf("Could not create a scriptPublicKey for address '%s': %s", addressString, err)
return errorMessage, nil
}
scriptPublicKeyString := utxoindex.ConvertScriptPublicKeyToString(scriptPublicKey)
addresses[i] = &rpccontext.UTXOsChangedNotificationAddress{
Address: addressString,
ScriptPublicKeyString: scriptPublicKeyString,
}
}
listener, err := context.NotificationManager.Listener(router)
if err != nil {
return nil, err
}
listener.PropagateUTXOsChangedNotifications(addresses)
response := appmessage.NewNotifyUTXOsChangedResponseMessage()
return response, nil
}

View File

@ -0,0 +1,19 @@
package rpchandlers
import (
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/app/rpc/rpccontext"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
)
// HandleNotifyVirtualSelectedParentBlueScoreChanged handles the respectively named RPC command
func HandleNotifyVirtualSelectedParentBlueScoreChanged(context *rpccontext.Context, router *router.Router, _ appmessage.Message) (appmessage.Message, error) {
listener, err := context.NotificationManager.Listener(router)
if err != nil {
return nil, err
}
listener.PropagateVirtualSelectedParentBlueScoreChangedNotifications()
response := appmessage.NewNotifyVirtualSelectedParentBlueScoreChangedResponseMessage()
return response, nil
}

View File

@ -13,9 +13,15 @@ import (
func HandleSubmitTransaction(context *rpccontext.Context, _ *router.Router, request appmessage.Message) (appmessage.Message, error) {
submitTransactionRequest := request.(*appmessage.SubmitTransactionRequestMessage)
domainTransaction := appmessage.MsgTxToDomainTransaction(submitTransactionRequest.Transaction)
domainTransaction, err := appmessage.RPCTransactionToDomainTransaction(submitTransactionRequest.Transaction)
if err != nil {
errorMessage := &appmessage.SubmitTransactionResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("Could not parse transaction: %s", err)
return errorMessage, nil
}
transactionID := consensushashing.TransactionID(domainTransaction)
err := context.ProtocolManager.AddTransaction(domainTransaction)
err = context.ProtocolManager.AddTransaction(domainTransaction)
if err != nil {
if !errors.As(err, &mempool.RuleError{}) {
return nil, err

View File

@ -141,6 +141,13 @@ func (s *consensus) GetBlockInfo(blockHash *externalapi.DomainHash) (*externalap
return blockInfo, nil
}
func (s *consensus) GetBlockAcceptanceData(blockHash *externalapi.DomainHash) (externalapi.AcceptanceData, error) {
s.lock.Lock()
defer s.lock.Unlock()
return s.acceptanceDataStore.Get(s.databaseContext, blockHash)
}
func (s *consensus) GetHashesBetween(lowHash, highHash *externalapi.DomainHash) ([]*externalapi.DomainHash, error) {
s.lock.Lock()
defer s.lock.Unlock()
@ -196,10 +203,16 @@ func (s *consensus) GetVirtualSelectedParent() (*externalapi.DomainBlock, error)
}
func (s *consensus) Tips() ([]*externalapi.DomainHash, error) {
s.lock.Lock()
defer s.lock.Unlock()
return s.consensusStateStore.Tips(s.databaseContext)
}
func (s *consensus) GetVirtualInfo() (*externalapi.VirtualInfo, error) {
s.lock.Lock()
defer s.lock.Unlock()
blockRelations, err := s.blockRelationStore.BlockRelation(s.databaseContext, model.VirtualBlockHash)
if err != nil {
return nil, err
@ -212,11 +225,16 @@ func (s *consensus) GetVirtualInfo() (*externalapi.VirtualInfo, error) {
if err != nil {
return nil, err
}
virtualGHOSTDAGData, err := s.ghostdagDataStore.Get(s.databaseContext, model.VirtualBlockHash)
if err != nil {
return nil, err
}
return &externalapi.VirtualInfo{
ParentHashes: blockRelations.Parents,
Bits: bits,
PastMedianTime: pastMedianTime,
BlueScore: virtualGHOSTDAGData.BlueScore(),
}, nil
}

View File

@ -1,9 +1,11 @@
package serialization
import "github.com/kaspanet/kaspad/domain/consensus/model"
import (
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
)
// DomainAcceptanceDataToDbAcceptanceData converts model.AcceptanceData to DbAcceptanceData
func DomainAcceptanceDataToDbAcceptanceData(domainAcceptanceData model.AcceptanceData) *DbAcceptanceData {
func DomainAcceptanceDataToDbAcceptanceData(domainAcceptanceData externalapi.AcceptanceData) *DbAcceptanceData {
dbBlockAcceptanceData := make([]*DbBlockAcceptanceData, len(domainAcceptanceData))
for i, blockAcceptanceData := range domainAcceptanceData {
dbTransactionAcceptanceData := make([]*DbTransactionAcceptanceData,
@ -29,10 +31,10 @@ func DomainAcceptanceDataToDbAcceptanceData(domainAcceptanceData model.Acceptanc
}
// DbAcceptanceDataToDomainAcceptanceData converts DbAcceptanceData to model.AcceptanceData
func DbAcceptanceDataToDomainAcceptanceData(dbAcceptanceData *DbAcceptanceData) (model.AcceptanceData, error) {
domainAcceptanceData := make(model.AcceptanceData, len(dbAcceptanceData.BlockAcceptanceData))
func DbAcceptanceDataToDomainAcceptanceData(dbAcceptanceData *DbAcceptanceData) (externalapi.AcceptanceData, error) {
domainAcceptanceData := make(externalapi.AcceptanceData, len(dbAcceptanceData.BlockAcceptanceData))
for i, dbBlockAcceptanceData := range dbAcceptanceData.BlockAcceptanceData {
domainTransactionAcceptanceData := make([]*model.TransactionAcceptanceData,
domainTransactionAcceptanceData := make([]*externalapi.TransactionAcceptanceData,
len(dbBlockAcceptanceData.TransactionAcceptanceData))
for j, dbTransactionAcceptanceData := range dbBlockAcceptanceData.TransactionAcceptanceData {
@ -40,14 +42,14 @@ func DbAcceptanceDataToDomainAcceptanceData(dbAcceptanceData *DbAcceptanceData)
if err != nil {
return nil, err
}
domainTransactionAcceptanceData[j] = &model.TransactionAcceptanceData{
domainTransactionAcceptanceData[j] = &externalapi.TransactionAcceptanceData{
Transaction: domainTransaction,
Fee: dbTransactionAcceptanceData.Fee,
IsAccepted: dbTransactionAcceptanceData.IsAccepted,
}
}
domainAcceptanceData[i] = &model.BlockAcceptanceData{
domainAcceptanceData[i] = &externalapi.BlockAcceptanceData{
TransactionAcceptanceData: domainTransactionAcceptanceData,
}
}

View File

@ -13,7 +13,7 @@ var bucket = dbkeys.MakeBucket([]byte("acceptance-data"))
// acceptanceDataStore represents a store of AcceptanceData
type acceptanceDataStore struct {
staging map[externalapi.DomainHash]model.AcceptanceData
staging map[externalapi.DomainHash]externalapi.AcceptanceData
toDelete map[externalapi.DomainHash]struct{}
cache *lrucache.LRUCache
}
@ -21,14 +21,14 @@ type acceptanceDataStore struct {
// New instantiates a new AcceptanceDataStore
func New(cacheSize int) model.AcceptanceDataStore {
return &acceptanceDataStore{
staging: make(map[externalapi.DomainHash]model.AcceptanceData),
staging: make(map[externalapi.DomainHash]externalapi.AcceptanceData),
toDelete: make(map[externalapi.DomainHash]struct{}),
cache: lrucache.New(cacheSize),
}
}
// Stage stages the given acceptanceData for the given blockHash
func (ads *acceptanceDataStore) Stage(blockHash *externalapi.DomainHash, acceptanceData model.AcceptanceData) {
func (ads *acceptanceDataStore) Stage(blockHash *externalapi.DomainHash, acceptanceData externalapi.AcceptanceData) {
ads.staging[*blockHash] = acceptanceData.Clone()
}
@ -37,7 +37,7 @@ func (ads *acceptanceDataStore) IsStaged() bool {
}
func (ads *acceptanceDataStore) Discard() {
ads.staging = make(map[externalapi.DomainHash]model.AcceptanceData)
ads.staging = make(map[externalapi.DomainHash]externalapi.AcceptanceData)
ads.toDelete = make(map[externalapi.DomainHash]struct{})
}
@ -67,13 +67,13 @@ func (ads *acceptanceDataStore) Commit(dbTx model.DBTransaction) error {
}
// Get gets the acceptanceData associated with the given blockHash
func (ads *acceptanceDataStore) Get(dbContext model.DBReader, blockHash *externalapi.DomainHash) (model.AcceptanceData, error) {
func (ads *acceptanceDataStore) Get(dbContext model.DBReader, blockHash *externalapi.DomainHash) (externalapi.AcceptanceData, error) {
if acceptanceData, ok := ads.staging[*blockHash]; ok {
return acceptanceData.Clone(), nil
}
if acceptanceData, ok := ads.cache.Get(blockHash); ok {
return acceptanceData.(model.AcceptanceData).Clone(), nil
return acceptanceData.(externalapi.AcceptanceData).Clone(), nil
}
acceptanceDataBytes, err := dbContext.Get(ads.hashAsKey(blockHash))
@ -98,12 +98,12 @@ func (ads *acceptanceDataStore) Delete(blockHash *externalapi.DomainHash) {
ads.toDelete[*blockHash] = struct{}{}
}
func (ads *acceptanceDataStore) serializeAcceptanceData(acceptanceData model.AcceptanceData) ([]byte, error) {
func (ads *acceptanceDataStore) serializeAcceptanceData(acceptanceData externalapi.AcceptanceData) ([]byte, error) {
dbAcceptanceData := serialization.DomainAcceptanceDataToDbAcceptanceData(acceptanceData)
return proto.Marshal(dbAcceptanceData)
}
func (ads *acceptanceDataStore) deserializeAcceptanceData(acceptanceDataBytes []byte) (model.AcceptanceData, error) {
func (ads *acceptanceDataStore) deserializeAcceptanceData(acceptanceDataBytes []byte) (externalapi.AcceptanceData, error) {
dbAcceptanceData := &serialization.DbAcceptanceData{}
err := proto.Unmarshal(acceptanceDataBytes, dbAcceptanceData)
if err != nil {

View File

@ -1,6 +1,4 @@
package model
import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
package externalapi
// AcceptanceData stores data about which transactions were accepted by a block.
// It's ordered in the same way as the block merge set blues.
@ -42,7 +40,7 @@ func (bad *BlockAcceptanceData) Clone() *BlockAcceptanceData {
// TransactionAcceptanceData stores a transaction together with an indication
// if it was accepted or not by some block
type TransactionAcceptanceData struct {
Transaction *externalapi.DomainTransaction
Transaction *DomainTransaction
Fee uint64
IsAccepted bool
}

View File

@ -9,6 +9,7 @@ type Consensus interface {
GetBlock(blockHash *DomainHash) (*DomainBlock, error)
GetBlockHeader(blockHash *DomainHash) (*DomainBlockHeader, error)
GetBlockInfo(blockHash *DomainHash) (*BlockInfo, error)
GetBlockAcceptanceData(blockHash *DomainHash) (AcceptanceData, error)
GetHashesBetween(lowHash, highHash *DomainHash) ([]*DomainHash, error)
GetMissingBlockBodyHashes(highHash *DomainHash) ([]*DomainHash, error)

View File

@ -5,4 +5,5 @@ type VirtualInfo struct {
ParentHashes []*DomainHash
Bits uint32
PastMedianTime int64
BlueScore uint64
}

View File

@ -5,8 +5,8 @@ import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
// AcceptanceDataStore represents a store of AcceptanceData
type AcceptanceDataStore interface {
Store
Stage(blockHash *externalapi.DomainHash, acceptanceData AcceptanceData)
Stage(blockHash *externalapi.DomainHash, acceptanceData externalapi.AcceptanceData)
IsStaged() bool
Get(dbContext DBReader, blockHash *externalapi.DomainHash) (AcceptanceData, error)
Get(dbContext DBReader, blockHash *externalapi.DomainHash) (externalapi.AcceptanceData, error)
Delete(blockHash *externalapi.DomainHash)
}

View File

@ -8,5 +8,5 @@ type ConsensusStateManager interface {
PopulateTransactionWithUTXOEntries(transaction *externalapi.DomainTransaction) error
UpdatePruningPoint(newPruningPoint *externalapi.DomainBlock, serializedUTXOSet []byte) error
RestorePastUTXOSetIterator(blockHash *externalapi.DomainHash) (ReadOnlyUTXOSetIterator, error)
CalculatePastUTXOAndAcceptanceData(blockHash *externalapi.DomainHash) (UTXODiff, AcceptanceData, Multiset, error)
CalculatePastUTXOAndAcceptanceData(blockHash *externalapi.DomainHash) (UTXODiff, externalapi.AcceptanceData, Multiset, error)
}

View File

@ -182,7 +182,7 @@ func (bb *blockBuilder) newBlockAcceptedIDMerkleRoot() (*externalapi.DomainHash,
return bb.calculateAcceptedIDMerkleRoot(newBlockAcceptanceData)
}
func (bb *blockBuilder) calculateAcceptedIDMerkleRoot(acceptanceData model.AcceptanceData) (*externalapi.DomainHash, error) {
func (bb *blockBuilder) calculateAcceptedIDMerkleRoot(acceptanceData externalapi.AcceptanceData) (*externalapi.DomainHash, error) {
var acceptedTransactions []*externalapi.DomainTransaction
for _, blockAcceptanceData := range acceptanceData {
for _, transactionAcceptance := range blockAcceptanceData.TransactionAcceptanceData {

View File

@ -40,7 +40,7 @@ func (bb *testBlockBuilder) BuildBlockWithParents(parentHashes []*externalapi.Do
}
func (bb *testBlockBuilder) buildHeaderWithParents(parentHashes []*externalapi.DomainHash,
transactions []*externalapi.DomainTransaction, acceptanceData model.AcceptanceData, multiset model.Multiset) (
transactions []*externalapi.DomainTransaction, acceptanceData externalapi.AcceptanceData, multiset model.Multiset) (
*externalapi.DomainBlockHeader, error) {
timeInMilliseconds, err := bb.minBlockTime(tempBlockHash)

View File

@ -65,7 +65,7 @@ func (c *coinbaseManager) ExpectedCoinbaseTransaction(blockHash *externalapi.Dom
// coinbaseOutputForBlueBlock calculates the output that should go into the coinbase transaction of blueBlock
// If blueBlock gets no fee - returns nil for txOut
func (c *coinbaseManager) coinbaseOutputForBlueBlock(blueBlock *externalapi.DomainHash,
blockAcceptanceData *model.BlockAcceptanceData) (*externalapi.DomainTransactionOutput, bool, error) {
blockAcceptanceData *externalapi.BlockAcceptanceData) (*externalapi.DomainTransactionOutput, bool, error) {
totalFees := uint64(0)
for _, txAcceptanceData := range blockAcceptanceData.TransactionAcceptanceData {

View File

@ -13,7 +13,7 @@ import (
)
func (csm *consensusStateManager) CalculatePastUTXOAndAcceptanceData(blockHash *externalapi.DomainHash) (
model.UTXODiff, model.AcceptanceData, model.Multiset, error) {
model.UTXODiff, externalapi.AcceptanceData, model.Multiset, error) {
log.Tracef("CalculatePastUTXOAndAcceptanceData start for block %s", blockHash)
defer log.Tracef("CalculatePastUTXOAndAcceptanceData end for block %s", blockHash)
@ -21,7 +21,7 @@ func (csm *consensusStateManager) CalculatePastUTXOAndAcceptanceData(blockHash *
if *blockHash == *csm.genesisHash {
log.Tracef("Block %s is the genesis. By definition, "+
"it has an empty UTXO diff, empty acceptance data, and a blank multiset", blockHash)
return utxo.NewUTXODiff(), model.AcceptanceData{}, multiset.New(), nil
return utxo.NewUTXODiff(), externalapi.AcceptanceData{}, multiset.New(), nil
}
blockGHOSTDAGData, err := csm.ghostdagDataStore.Get(csm.databaseContext, blockHash)
@ -109,7 +109,7 @@ func (csm *consensusStateManager) restorePastUTXO(blockHash *externalapi.DomainH
func (csm *consensusStateManager) applyBlueBlocks(blockHash *externalapi.DomainHash,
selectedParentPastUTXODiff model.MutableUTXODiff, ghostdagData model.BlockGHOSTDAGData) (
model.AcceptanceData, model.MutableUTXODiff, error) {
externalapi.AcceptanceData, model.MutableUTXODiff, error) {
log.Tracef("applyBlueBlocks start for block %s", blockHash)
defer log.Tracef("applyBlueBlocks end for block %s", blockHash)
@ -125,15 +125,15 @@ func (csm *consensusStateManager) applyBlueBlocks(blockHash *externalapi.DomainH
}
log.Tracef("The past median time for block %s is: %d", blockHash, selectedParentMedianTime)
multiblockAcceptanceData := make(model.AcceptanceData, len(blueBlocks))
multiblockAcceptanceData := make(externalapi.AcceptanceData, len(blueBlocks))
accumulatedUTXODiff := selectedParentPastUTXODiff
accumulatedMass := uint64(0)
for i, blueBlock := range blueBlocks {
blueBlockHash := consensushashing.BlockHash(blueBlock)
log.Tracef("Applying blue block %s", blueBlockHash)
blockAcceptanceData := &model.BlockAcceptanceData{
TransactionAcceptanceData: make([]*model.TransactionAcceptanceData, len(blueBlock.Transactions)),
blockAcceptanceData := &externalapi.BlockAcceptanceData{
TransactionAcceptanceData: make([]*externalapi.TransactionAcceptanceData, len(blueBlock.Transactions)),
}
isSelectedParent := i == 0
log.Tracef("Is blue block %s the selected parent: %t", blueBlockHash, isSelectedParent)
@ -153,7 +153,7 @@ func (csm *consensusStateManager) applyBlueBlocks(blockHash *externalapi.DomainH
log.Tracef("Transaction %s in block %s isAccepted: %t, fee: %d",
transactionID, blueBlockHash, isAccepted, transaction.Fee)
blockAcceptanceData.TransactionAcceptanceData[j] = &model.TransactionAcceptanceData{
blockAcceptanceData.TransactionAcceptanceData[j] = &externalapi.TransactionAcceptanceData{
Transaction: transaction,
Fee: transaction.Fee,
IsAccepted: isAccepted,

View File

@ -10,7 +10,7 @@ import (
)
func (csm *consensusStateManager) calculateMultiset(
acceptanceData model.AcceptanceData, blockGHOSTDAGData model.BlockGHOSTDAGData) (model.Multiset, error) {
acceptanceData externalapi.AcceptanceData, blockGHOSTDAGData model.BlockGHOSTDAGData) (model.Multiset, error) {
log.Tracef("calculateMultiset start for block with selected parent %s", blockGHOSTDAGData.SelectedParent())
defer log.Tracef("calculateMultiset end for block with selected parent %s", blockGHOSTDAGData.SelectedParent())

View File

@ -18,7 +18,7 @@ import (
)
func (csm *consensusStateManager) verifyUTXO(block *externalapi.DomainBlock, blockHash *externalapi.DomainHash,
pastUTXODiff model.UTXODiff, acceptanceData model.AcceptanceData, multiset model.Multiset) error {
pastUTXODiff model.UTXODiff, acceptanceData externalapi.AcceptanceData, multiset model.Multiset) error {
log.Tracef("verifyUTXO start for block %s", blockHash)
defer log.Tracef("verifyUTXO end for block %s", blockHash)
@ -97,7 +97,7 @@ func (csm *consensusStateManager) validateBlockTransactionsAgainstPastUTXO(block
}
func (csm *consensusStateManager) validateAcceptedIDMerkleRoot(block *externalapi.DomainBlock,
blockHash *externalapi.DomainHash, acceptanceData model.AcceptanceData) error {
blockHash *externalapi.DomainHash, acceptanceData externalapi.AcceptanceData) error {
log.Tracef("validateAcceptedIDMerkleRoot start for block %s", blockHash)
defer log.Tracef("validateAcceptedIDMerkleRoot end for block %s", blockHash)
@ -127,7 +127,7 @@ func (csm *consensusStateManager) validateUTXOCommitment(
return nil
}
func calculateAcceptedIDMerkleRoot(multiblockAcceptanceData model.AcceptanceData) *externalapi.DomainHash {
func calculateAcceptedIDMerkleRoot(multiblockAcceptanceData externalapi.AcceptanceData) *externalapi.DomainHash {
log.Tracef("calculateAcceptedIDMerkleRoot start")
defer log.Tracef("calculateAcceptedIDMerkleRoot end")

11
domain/utxoindex/log.go Normal file
View File

@ -0,0 +1,11 @@
// Copyright (c) 2016 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package utxoindex
import (
"github.com/kaspanet/kaspad/infrastructure/logger"
)
var log, _ = logger.Get(logger.SubsystemTags.INDX)

33
domain/utxoindex/model.go Normal file
View File

@ -0,0 +1,33 @@
package utxoindex
import (
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
)
// ScriptPublicKeyString is a script public key represented as a string
// We use this type rather than just a byte slice because Go maps don't
// support slices as keys. See: UTXOChanges
type ScriptPublicKeyString string
// UTXOOutpointEntryPairs is a map between UTXO outpoints to UTXO entries
type UTXOOutpointEntryPairs map[externalapi.DomainOutpoint]externalapi.UTXOEntry
// UTXOOutpoints is a set of UTXO outpoints
type UTXOOutpoints map[externalapi.DomainOutpoint]interface{}
// UTXOChanges is the set of changes made to the UTXO index after
// a successful update
type UTXOChanges struct {
Added map[ScriptPublicKeyString]UTXOOutpointEntryPairs
Removed map[ScriptPublicKeyString]UTXOOutpoints
}
// ConvertScriptPublicKeyToString converts the given scriptPublicKey to a string
func ConvertScriptPublicKeyToString(scriptPublicKey []byte) ScriptPublicKeyString {
return ScriptPublicKeyString(scriptPublicKey)
}
// ConvertStringToScriptPublicKey converts the given string to a scriptPublicKey byte slice
func ConvertStringToScriptPublicKey(string ScriptPublicKeyString) []byte {
return []byte(string)
}

255
domain/utxoindex/store.go Normal file
View File

@ -0,0 +1,255 @@
package utxoindex
import (
"github.com/golang/protobuf/proto"
"github.com/kaspanet/kaspad/domain/consensus/database/serialization"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/infrastructure/db/database"
"github.com/kaspanet/kaspad/infrastructure/logger"
"github.com/pkg/errors"
)
var utxoIndexBucket = database.MakeBucket([]byte("utxo-index"))
type utxoIndexStore struct {
database database.Database
toAdd map[ScriptPublicKeyString]UTXOOutpointEntryPairs
toRemove map[ScriptPublicKeyString]UTXOOutpoints
}
func newUTXOIndexStore(database database.Database) *utxoIndexStore {
return &utxoIndexStore{
database: database,
toAdd: make(map[ScriptPublicKeyString]UTXOOutpointEntryPairs),
toRemove: make(map[ScriptPublicKeyString]UTXOOutpoints),
}
}
func (uis *utxoIndexStore) add(scriptPublicKey []byte, outpoint *externalapi.DomainOutpoint, utxoEntry *externalapi.UTXOEntry) error {
key := ConvertScriptPublicKeyToString(scriptPublicKey)
log.Tracef("Adding outpoint %s:%d to scriptPublicKey %s",
outpoint.TransactionID, outpoint.Index, key)
// If the outpoint exists in `toRemove` simply remove it from there and return
if toRemoveOutpointsOfKey, ok := uis.toRemove[key]; ok {
if _, ok := toRemoveOutpointsOfKey[*outpoint]; ok {
log.Tracef("Outpoint %s:%d exists in `toRemove`. Deleting it from there",
outpoint.TransactionID, outpoint.Index)
delete(toRemoveOutpointsOfKey, *outpoint)
return nil
}
}
// Create a UTXOOutpointEntryPairs entry in `toAdd` if it doesn't exist
if _, ok := uis.toAdd[key]; !ok {
log.Tracef("Creating key %s in `toAdd`", key)
uis.toAdd[key] = make(UTXOOutpointEntryPairs)
}
// Return an error if the outpoint already exists in `toAdd`
toAddPairsOfKey := uis.toAdd[key]
if _, ok := toAddPairsOfKey[*outpoint]; ok {
return errors.Errorf("cannot add outpoint %s because it's being added already", outpoint)
}
toAddPairsOfKey[*outpoint] = *utxoEntry
log.Tracef("Added outpoint %s:%d to scriptPublicKey %s",
outpoint.TransactionID, outpoint.Index, key)
return nil
}
func (uis *utxoIndexStore) remove(scriptPublicKey []byte, outpoint *externalapi.DomainOutpoint) error {
key := ConvertScriptPublicKeyToString(scriptPublicKey)
log.Tracef("Removing outpoint %s:%d from scriptPublicKey %s",
outpoint.TransactionID, outpoint.Index, key)
// If the outpoint exists in `toAdd` simply remove it from there and return
if toAddPairsOfKey, ok := uis.toAdd[key]; ok {
if _, ok := toAddPairsOfKey[*outpoint]; ok {
log.Tracef("Outpoint %s:%d exists in `toAdd`. Deleting it from there",
outpoint.TransactionID, outpoint.Index)
delete(toAddPairsOfKey, *outpoint)
return nil
}
}
// Create a UTXOOutpoints entry in `toRemove` if it doesn't exist
if _, ok := uis.toRemove[key]; !ok {
log.Tracef("Creating key %s in `toRemove`", key)
uis.toRemove[key] = make(UTXOOutpoints)
}
// Return an error if the outpoint already exists in `toRemove`
toRemoveOutpointsOfKey := uis.toRemove[key]
if _, ok := toRemoveOutpointsOfKey[*outpoint]; ok {
return errors.Errorf("cannot remove outpoint %s because it's being removed already", outpoint)
}
toRemoveOutpointsOfKey[*outpoint] = struct{}{}
log.Tracef("Removed outpoint %s:%d from scriptPublicKey %s",
outpoint.TransactionID, outpoint.Index, key)
return nil
}
func (uis *utxoIndexStore) discard() {
uis.toAdd = make(map[ScriptPublicKeyString]UTXOOutpointEntryPairs)
uis.toRemove = make(map[ScriptPublicKeyString]UTXOOutpoints)
}
func (uis *utxoIndexStore) commit() error {
onEnd := logger.LogAndMeasureExecutionTime(log, "utxoIndexStore.commit")
defer onEnd()
dbTransaction, err := uis.database.Begin()
if err != nil {
return err
}
defer dbTransaction.RollbackUnlessClosed()
for scriptPublicKeyString, toRemoveOutpointsOfKey := range uis.toRemove {
scriptPublicKey := ConvertStringToScriptPublicKey(scriptPublicKeyString)
bucket := uis.bucketForScriptPublicKey(scriptPublicKey)
for outpointToRemove := range toRemoveOutpointsOfKey {
key, err := uis.convertOutpointToKey(bucket, &outpointToRemove)
if err != nil {
return err
}
err = dbTransaction.Delete(key)
if err != nil {
return err
}
}
}
for scriptPublicKeyString, toAddUTXOOutpointEntryPairs := range uis.toAdd {
scriptPublicKey := ConvertStringToScriptPublicKey(scriptPublicKeyString)
bucket := uis.bucketForScriptPublicKey(scriptPublicKey)
for outpointToAdd, utxoEntryToAdd := range toAddUTXOOutpointEntryPairs {
key, err := uis.convertOutpointToKey(bucket, &outpointToAdd)
if err != nil {
return err
}
serializedUTXOEntry, err := uis.serializeUTXOEntry(utxoEntryToAdd)
if err != nil {
return err
}
err = dbTransaction.Put(key, serializedUTXOEntry)
if err != nil {
return err
}
}
}
err = dbTransaction.Commit()
if err != nil {
return err
}
uis.discard()
return nil
}
func (uis *utxoIndexStore) bucketForScriptPublicKey(scriptPublicKey []byte) *database.Bucket {
return utxoIndexBucket.Bucket(scriptPublicKey)
}
func (uis *utxoIndexStore) convertOutpointToKey(bucket *database.Bucket, outpoint *externalapi.DomainOutpoint) (*database.Key, error) {
serializedOutpoint, err := uis.serializeOutpoint(outpoint)
if err != nil {
return nil, err
}
return bucket.Key(serializedOutpoint), nil
}
func (uis *utxoIndexStore) convertKeyToOutpoint(key *database.Key) (*externalapi.DomainOutpoint, error) {
serializedOutpoint := key.Suffix()
return uis.deserializeOutpoint(serializedOutpoint)
}
func (uis *utxoIndexStore) serializeOutpoint(outpoint *externalapi.DomainOutpoint) ([]byte, error) {
dbOutpoint := serialization.DomainOutpointToDbOutpoint(outpoint)
return proto.Marshal(dbOutpoint)
}
func (uis *utxoIndexStore) deserializeOutpoint(serializedOutpoint []byte) (*externalapi.DomainOutpoint, error) {
var dbOutpoint serialization.DbOutpoint
err := proto.Unmarshal(serializedOutpoint, &dbOutpoint)
if err != nil {
return nil, err
}
return serialization.DbOutpointToDomainOutpoint(&dbOutpoint)
}
func (uis *utxoIndexStore) serializeUTXOEntry(utxoEntry externalapi.UTXOEntry) ([]byte, error) {
dbUTXOEntry := serialization.UTXOEntryToDBUTXOEntry(utxoEntry)
return proto.Marshal(dbUTXOEntry)
}
func (uis *utxoIndexStore) deserializeUTXOEntry(serializedUTXOEntry []byte) (externalapi.UTXOEntry, error) {
var dbUTXOEntry serialization.DbUtxoEntry
err := proto.Unmarshal(serializedUTXOEntry, &dbUTXOEntry)
if err != nil {
return nil, err
}
return serialization.DBUTXOEntryToUTXOEntry(&dbUTXOEntry), nil
}
func (uis *utxoIndexStore) stagedData() (
toAdd map[ScriptPublicKeyString]UTXOOutpointEntryPairs,
toRemove map[ScriptPublicKeyString]UTXOOutpoints) {
toAddClone := make(map[ScriptPublicKeyString]UTXOOutpointEntryPairs, len(uis.toAdd))
for scriptPublicKeyString, toAddUTXOOutpointEntryPairs := range uis.toAdd {
toAddUTXOOutpointEntryPairsClone := make(UTXOOutpointEntryPairs, len(toAddUTXOOutpointEntryPairs))
for outpoint, utxoEntry := range toAddUTXOOutpointEntryPairs {
toAddUTXOOutpointEntryPairsClone[outpoint] = utxoEntry
}
toAddClone[scriptPublicKeyString] = toAddUTXOOutpointEntryPairsClone
}
toRemoveClone := make(map[ScriptPublicKeyString]UTXOOutpoints, len(uis.toRemove))
for scriptPublicKeyString, toRemoveOutpoints := range uis.toRemove {
toRemoveOutpointsClone := make(UTXOOutpoints, len(toRemoveOutpoints))
for outpoint := range toRemoveOutpoints {
toRemoveOutpointsClone[outpoint] = struct{}{}
}
toRemoveClone[scriptPublicKeyString] = toRemoveOutpointsClone
}
return toAddClone, toRemoveClone
}
func (uis *utxoIndexStore) getUTXOOutpointEntryPairs(scriptPublicKey []byte) (UTXOOutpointEntryPairs, error) {
if len(uis.toAdd) > 0 || len(uis.toRemove) > 0 {
return nil, errors.Errorf("cannot get utxo outpoint entry pairs while staging isn't empty")
}
bucket := uis.bucketForScriptPublicKey(scriptPublicKey)
cursor, err := uis.database.Cursor(bucket)
if err != nil {
return nil, err
}
utxoOutpointEntryPairs := make(UTXOOutpointEntryPairs)
for cursor.Next() {
key, err := cursor.Key()
if err != nil {
return nil, err
}
outpoint, err := uis.convertKeyToOutpoint(key)
if err != nil {
return nil, err
}
serializedUTXOEntry, err := cursor.Value()
if err != nil {
return nil, err
}
utxoEntry, err := uis.deserializeUTXOEntry(serializedUTXOEntry)
if err != nil {
return nil, err
}
utxoOutpointEntryPairs[*outpoint] = utxoEntry
}
return utxoOutpointEntryPairs, nil
}

View File

@ -0,0 +1,168 @@
package utxoindex
import (
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
"github.com/kaspanet/kaspad/domain/consensus/utils/transactionhelper"
"github.com/kaspanet/kaspad/domain/consensus/utils/utxo"
"github.com/kaspanet/kaspad/infrastructure/db/database"
"github.com/kaspanet/kaspad/infrastructure/logger"
"sync"
)
// UTXOIndex maintains an index between transaction scriptPublicKeys
// and UTXOs
type UTXOIndex struct {
consensus externalapi.Consensus
store *utxoIndexStore
mutex sync.Mutex
}
// New creates a new UTXO index
func New(consensus externalapi.Consensus, database database.Database) *UTXOIndex {
store := newUTXOIndexStore(database)
return &UTXOIndex{
consensus: consensus,
store: store,
}
}
// Update updates the UTXO index with the given DAG selected parent chain changes
func (ui *UTXOIndex) Update(chainChanges *externalapi.SelectedParentChainChanges) (*UTXOChanges, error) {
onEnd := logger.LogAndMeasureExecutionTime(log, "UTXOIndex.Update")
defer onEnd()
ui.mutex.Lock()
defer ui.mutex.Unlock()
log.Tracef("Updating UTXO index with chainChanges: %+v", chainChanges)
for _, removedBlockHash := range chainChanges.Removed {
err := ui.removeBlock(removedBlockHash)
if err != nil {
return nil, err
}
}
for _, addedBlockHash := range chainChanges.Added {
err := ui.addBlock(addedBlockHash)
if err != nil {
return nil, err
}
}
added, removed := ui.store.stagedData()
utxoIndexChanges := &UTXOChanges{
Added: added,
Removed: removed,
}
err := ui.store.commit()
if err != nil {
return nil, err
}
log.Tracef("UTXO index updated with the UTXOChanged: %+v", utxoIndexChanges)
return utxoIndexChanges, nil
}
func (ui *UTXOIndex) addBlock(blockHash *externalapi.DomainHash) error {
log.Tracef("Adding block %s to UTXO index", blockHash)
acceptanceData, err := ui.consensus.GetBlockAcceptanceData(blockHash)
if err != nil {
return err
}
blockInfo, err := ui.consensus.GetBlockInfo(blockHash)
if err != nil {
return err
}
for _, blockAcceptanceData := range acceptanceData {
for _, transactionAcceptanceData := range blockAcceptanceData.TransactionAcceptanceData {
if !transactionAcceptanceData.IsAccepted {
continue
}
err := ui.addTransaction(transactionAcceptanceData.Transaction, blockInfo.BlueScore)
if err != nil {
return err
}
}
}
return nil
}
func (ui *UTXOIndex) removeBlock(blockHash *externalapi.DomainHash) error {
log.Tracef("Removing block %s from UTXO index", blockHash)
acceptanceData, err := ui.consensus.GetBlockAcceptanceData(blockHash)
if err != nil {
return err
}
for _, blockAcceptanceData := range acceptanceData {
for _, transactionAcceptanceData := range blockAcceptanceData.TransactionAcceptanceData {
if !transactionAcceptanceData.IsAccepted {
continue
}
err := ui.removeTransaction(transactionAcceptanceData.Transaction)
if err != nil {
return err
}
}
}
return nil
}
func (ui *UTXOIndex) addTransaction(transaction *externalapi.DomainTransaction, blockBlueScore uint64) error {
transactionID := consensushashing.TransactionID(transaction)
log.Tracef("Adding transaction %s to UTXO index", transactionID)
isCoinbase := transactionhelper.IsCoinBase(transaction)
for _, transactionInput := range transaction.Inputs {
log.Tracef("Removing outpoint %s:%d from UTXO index",
transactionInput.PreviousOutpoint.TransactionID, transactionInput.PreviousOutpoint.Index)
err := ui.store.remove(transactionInput.UTXOEntry.ScriptPublicKey(), &transactionInput.PreviousOutpoint)
if err != nil {
return err
}
}
for index, transactionOutput := range transaction.Outputs {
log.Tracef("Adding outpoint %s:%d to UTXO index", transactionID, index)
outpoint := externalapi.NewDomainOutpoint(transactionID, uint32(index))
utxoEntry := utxo.NewUTXOEntry(transactionOutput.Value, transactionOutput.ScriptPublicKey, isCoinbase, blockBlueScore)
err := ui.store.add(transactionOutput.ScriptPublicKey, outpoint, &utxoEntry)
if err != nil {
return err
}
}
return nil
}
func (ui *UTXOIndex) removeTransaction(transaction *externalapi.DomainTransaction) error {
transactionID := consensushashing.TransactionID(transaction)
log.Tracef("Removing transaction %s from UTXO index", transactionID)
for index, transactionOutput := range transaction.Outputs {
log.Tracef("Removing outpoint %s:%d from UTXO index", transactionID, index)
outpoint := externalapi.NewDomainOutpoint(transactionID, uint32(index))
err := ui.store.remove(transactionOutput.ScriptPublicKey, outpoint)
if err != nil {
return err
}
}
for _, transactionInput := range transaction.Inputs {
log.Tracef("Adding outpoint %s:%d to UTXO index",
transactionInput.PreviousOutpoint.TransactionID, transactionInput.PreviousOutpoint.Index)
err := ui.store.add(transactionInput.UTXOEntry.ScriptPublicKey(), &transactionInput.PreviousOutpoint, &transactionInput.UTXOEntry)
if err != nil {
return err
}
}
return nil
}
// UTXOs returns all the UTXOs for the given scriptPublicKey
func (ui *UTXOIndex) UTXOs(scriptPublicKey []byte) (UTXOOutpointEntryPairs, error) {
onEnd := logger.LogAndMeasureExecutionTime(log, "UTXOIndex.UTXOs")
defer onEnd()
ui.mutex.Lock()
defer ui.mutex.Unlock()
return ui.store.getUTXOOutpointEntryPairs(scriptPublicKey)
}

View File

@ -123,6 +123,7 @@ type Flags struct {
RejectNonStd bool `long:"rejectnonstd" description:"Reject non-standard transactions regardless of the default settings for the active network."`
ResetDatabase bool `long:"reset-db" description:"Reset database before starting node. It's needed when switching between subnetworks."`
MaxUTXOCacheSize uint64 `long:"maxutxocachesize" description:"Max size of loaded UTXO into ram from the disk in bytes"`
UTXOIndex bool `long:"utxoindex" description:"Enable the UTXO index"`
NetworkFlags
ServiceOptions *ServiceOptions
}

View File

@ -77,8 +77,18 @@ message KaspadMessage {
GetMempoolEntriesResponseMessage getMempoolEntriesResponse = 1044;
ShutDownRequestMessage shutDownRequest = 1045;
ShutDownResponseMessage shutDownResponse = 1046;
GetHeadersRequestMessage getHeadersRequest = 10347;
GetHeadersRequestMessage getHeadersRequest = 1047;
GetHeadersResponseMessage getHeadersResponse = 1048;
NotifyUtxosChangedRequestMessage notifyUtxosChangedRequest = 1049;
NotifyUtxosChangedResponseMessage notifyUtxosChangedResponse = 1050;
UtxosChangedNotificationMessage utxosChangedNotification = 1051;
GetUtxosByAddressesRequestMessage getUtxosByAddressesRequest = 1052;
GetUtxosByAddressesResponseMessage getUtxosByAddressesResponse = 1053;
GetVirtualSelectedParentBlueScoreRequestMessage getVirtualSelectedParentBlueScoreRequest = 1054;
GetVirtualSelectedParentBlueScoreResponseMessage getVirtualSelectedParentBlueScoreResponse = 1055;
NotifyVirtualSelectedParentBlueScoreChangedRequestMessage notifyVirtualSelectedParentBlueScoreChangedRequest = 1056;
NotifyVirtualSelectedParentBlueScoreChangedResponseMessage notifyVirtualSelectedParentBlueScoreChangedResponse = 1057;
VirtualSelectedParentBlueScoreChangedNotificationMessage virtualSelectedParentBlueScoreChangedNotification = 1058;
}
}
@ -412,11 +422,12 @@ message AddPeerResponseMessage{
}
message SubmitTransactionRequestMessage{
TransactionMessage transaction = 1;
RpcTransaction transaction = 1;
}
message SubmitTransactionResponseMessage{
string txId = 1;
string transactionId = 1;
RPCError error = 1000;
}
@ -612,6 +623,89 @@ message GetHeadersResponseMessage{
RPCError error = 1000;
}
message NotifyUtxosChangedRequestMessage {
repeated string addresses = 1;
}
message NotifyUtxosChangedResponseMessage {
RPCError error = 1000;
}
message UtxosChangedNotificationMessage {
repeated UtxosByAddressesEntry added = 1;
repeated UtxosByAddressesEntry removed = 2;
}
message UtxosByAddressesEntry {
string address = 1;
RpcOutpoint outpoint = 2;
RpcUtxoEntry utxoEntry = 3;
}
message RpcTransaction {
int32 version = 1;
repeated RpcTransactionInput inputs = 2;
repeated RpcTransactionOutput outputs = 3;
uint64 lockTime = 4;
string subnetworkId = 5;
uint64 gas = 6;
string payloadHash = 7;
string payload = 8;
}
message RpcTransactionInput {
RpcOutpoint previousOutpoint = 1;
string signatureScript = 2;
uint64 sequence = 3;
}
message RpcTransactionOutput {
uint64 amount = 1;
string scriptPubKey = 2;
}
message RpcOutpoint {
string transactionId = 1;
uint32 index = 2;
}
message RpcUtxoEntry {
uint64 amount = 1;
string scriptPubKey = 2;
uint64 blockBlueScore = 3;
bool isCoinbase = 4;
}
message GetUtxosByAddressesRequestMessage {
repeated string addresses = 1;
}
message GetUtxosByAddressesResponseMessage {
repeated UtxosByAddressesEntry entries = 1;
RPCError error = 1000;
}
message GetVirtualSelectedParentBlueScoreRequestMessage {
}
message GetVirtualSelectedParentBlueScoreResponseMessage {
uint64 blueScore = 1;
RPCError error = 1000;
}
message NotifyVirtualSelectedParentBlueScoreChangedRequestMessage {
}
message NotifyVirtualSelectedParentBlueScoreChangedResponseMessage {
RPCError error = 1000;
}
message VirtualSelectedParentBlueScoreChangedNotificationMessage {
uint64 virtualSelectedParentBlueScore = 1;
}
service RPC {
rpc MessageStream (stream KaspadMessage) returns (stream KaspadMessage) {}
}

View File

@ -0,0 +1,48 @@
package protowire
import "github.com/kaspanet/kaspad/app/appmessage"
func (x *KaspadMessage_GetUtxosByAddressesRequest) toAppMessage() (appmessage.Message, error) {
return &appmessage.GetUTXOsByAddressesRequestMessage{
Addresses: x.GetUtxosByAddressesRequest.Addresses,
}, nil
}
func (x *KaspadMessage_GetUtxosByAddressesRequest) fromAppMessage(message *appmessage.GetUTXOsByAddressesRequestMessage) error {
x.GetUtxosByAddressesRequest = &GetUtxosByAddressesRequestMessage{
Addresses: message.Addresses,
}
return nil
}
func (x *KaspadMessage_GetUtxosByAddressesResponse) toAppMessage() (appmessage.Message, error) {
var err *appmessage.RPCError
if x.GetUtxosByAddressesResponse.Error != nil {
err = &appmessage.RPCError{Message: x.GetUtxosByAddressesResponse.Error.Message}
}
entries := make([]*appmessage.UTXOsByAddressesEntry, len(x.GetUtxosByAddressesResponse.Entries))
for i, entry := range x.GetUtxosByAddressesResponse.Entries {
entries[i] = entry.toAppMessage()
}
return &appmessage.GetUTXOsByAddressesResponseMessage{
Entries: entries,
Error: err,
}, nil
}
func (x *KaspadMessage_GetUtxosByAddressesResponse) fromAppMessage(message *appmessage.GetUTXOsByAddressesResponseMessage) error {
var err *RPCError
if message.Error != nil {
err = &RPCError{Message: message.Error.Message}
}
entries := make([]*UtxosByAddressesEntry, len(message.Entries))
for i, entry := range message.Entries {
entries[i] = &UtxosByAddressesEntry{}
entries[i].fromAppMessage(entry)
}
x.GetUtxosByAddressesResponse = &GetUtxosByAddressesResponseMessage{
Entries: entries,
Error: err,
}
return nil
}

View File

@ -0,0 +1,37 @@
package protowire
import (
"github.com/kaspanet/kaspad/app/appmessage"
)
func (x *KaspadMessage_GetVirtualSelectedParentBlueScoreRequest) toAppMessage() (appmessage.Message, error) {
return &appmessage.GetVirtualSelectedParentBlueScoreRequestMessage{}, nil
}
func (x *KaspadMessage_GetVirtualSelectedParentBlueScoreRequest) fromAppMessage(message *appmessage.GetVirtualSelectedParentBlueScoreRequestMessage) error {
x.GetVirtualSelectedParentBlueScoreRequest = &GetVirtualSelectedParentBlueScoreRequestMessage{}
return nil
}
func (x *KaspadMessage_GetVirtualSelectedParentBlueScoreResponse) toAppMessage() (appmessage.Message, error) {
var err *appmessage.RPCError
if x.GetVirtualSelectedParentBlueScoreResponse.Error != nil {
err = &appmessage.RPCError{Message: x.GetVirtualSelectedParentBlueScoreResponse.Error.Message}
}
return &appmessage.GetVirtualSelectedParentBlueScoreResponseMessage{
BlueScore: x.GetVirtualSelectedParentBlueScoreResponse.BlueScore,
Error: err,
}, nil
}
func (x *KaspadMessage_GetVirtualSelectedParentBlueScoreResponse) fromAppMessage(message *appmessage.GetVirtualSelectedParentBlueScoreResponseMessage) error {
var err *RPCError
if message.Error != nil {
err = &RPCError{Message: message.Error.Message}
}
x.GetVirtualSelectedParentBlueScoreResponse = &GetVirtualSelectedParentBlueScoreResponseMessage{
BlueScore: message.BlueScore,
Error: err,
}
return nil
}

View File

@ -0,0 +1,118 @@
package protowire
import (
"github.com/kaspanet/kaspad/app/appmessage"
)
func (x *KaspadMessage_NotifyUtxosChangedRequest) toAppMessage() (appmessage.Message, error) {
return &appmessage.NotifyUTXOsChangedRequestMessage{
Addresses: x.NotifyUtxosChangedRequest.Addresses,
}, nil
}
func (x *KaspadMessage_NotifyUtxosChangedRequest) fromAppMessage(message *appmessage.NotifyUTXOsChangedRequestMessage) error {
x.NotifyUtxosChangedRequest = &NotifyUtxosChangedRequestMessage{
Addresses: message.Addresses,
}
return nil
}
func (x *KaspadMessage_NotifyUtxosChangedResponse) toAppMessage() (appmessage.Message, error) {
var err *appmessage.RPCError
if x.NotifyUtxosChangedResponse.Error != nil {
err = &appmessage.RPCError{Message: x.NotifyUtxosChangedResponse.Error.Message}
}
return &appmessage.NotifyUTXOsChangedResponseMessage{
Error: err,
}, nil
}
func (x *KaspadMessage_NotifyUtxosChangedResponse) fromAppMessage(message *appmessage.NotifyUTXOsChangedResponseMessage) error {
var err *RPCError
if message.Error != nil {
err = &RPCError{Message: message.Error.Message}
}
x.NotifyUtxosChangedResponse = &NotifyUtxosChangedResponseMessage{
Error: err,
}
return nil
}
func (x *KaspadMessage_UtxosChangedNotification) toAppMessage() (appmessage.Message, error) {
added := make([]*appmessage.UTXOsByAddressesEntry, len(x.UtxosChangedNotification.Added))
for i, entry := range x.UtxosChangedNotification.Added {
added[i] = entry.toAppMessage()
}
removed := make([]*appmessage.UTXOsByAddressesEntry, len(x.UtxosChangedNotification.Removed))
for i, entry := range x.UtxosChangedNotification.Removed {
removed[i] = entry.toAppMessage()
}
return &appmessage.UTXOsChangedNotificationMessage{
Added: added,
Removed: removed,
}, nil
}
func (x *KaspadMessage_UtxosChangedNotification) fromAppMessage(message *appmessage.UTXOsChangedNotificationMessage) error {
added := make([]*UtxosByAddressesEntry, len(message.Added))
for i, entry := range message.Added {
added[i] = &UtxosByAddressesEntry{}
added[i].fromAppMessage(entry)
}
removed := make([]*UtxosByAddressesEntry, len(message.Removed))
for i, entry := range message.Removed {
removed[i] = &UtxosByAddressesEntry{}
removed[i].fromAppMessage(entry)
}
x.UtxosChangedNotification = &UtxosChangedNotificationMessage{
Added: added,
Removed: removed,
}
return nil
}
func (x *UtxosByAddressesEntry) toAppMessage() *appmessage.UTXOsByAddressesEntry {
outpoint := &appmessage.RPCOutpoint{
TransactionID: x.Outpoint.TransactionId,
Index: x.Outpoint.Index,
}
var utxoEntry *appmessage.RPCUTXOEntry
if x.UtxoEntry != nil {
utxoEntry = &appmessage.RPCUTXOEntry{
Amount: x.UtxoEntry.Amount,
ScriptPubKey: x.UtxoEntry.ScriptPubKey,
BlockBlueScore: x.UtxoEntry.BlockBlueScore,
IsCoinbase: x.UtxoEntry.IsCoinbase,
}
}
return &appmessage.UTXOsByAddressesEntry{
Address: x.Address,
Outpoint: outpoint,
UTXOEntry: utxoEntry,
}
}
func (x *UtxosByAddressesEntry) fromAppMessage(entry *appmessage.UTXOsByAddressesEntry) {
outpoint := &RpcOutpoint{
TransactionId: entry.Outpoint.TransactionID,
Index: entry.Outpoint.Index,
}
var utxoEntry *RpcUtxoEntry
if entry.UTXOEntry != nil {
utxoEntry = &RpcUtxoEntry{
Amount: entry.UTXOEntry.Amount,
ScriptPubKey: entry.UTXOEntry.ScriptPubKey,
BlockBlueScore: entry.UTXOEntry.BlockBlueScore,
IsCoinbase: entry.UTXOEntry.IsCoinbase,
}
}
*x = UtxosByAddressesEntry{
Address: entry.Address,
Outpoint: outpoint,
UtxoEntry: utxoEntry,
}
}

View File

@ -0,0 +1,46 @@
package protowire
import "github.com/kaspanet/kaspad/app/appmessage"
func (x *KaspadMessage_NotifyVirtualSelectedParentBlueScoreChangedRequest) toAppMessage() (appmessage.Message, error) {
return &appmessage.NotifyVirtualSelectedParentBlueScoreChangedRequestMessage{}, nil
}
func (x *KaspadMessage_NotifyVirtualSelectedParentBlueScoreChangedRequest) fromAppMessage(_ *appmessage.NotifyVirtualSelectedParentBlueScoreChangedRequestMessage) error {
x.NotifyVirtualSelectedParentBlueScoreChangedRequest = &NotifyVirtualSelectedParentBlueScoreChangedRequestMessage{}
return nil
}
func (x *KaspadMessage_NotifyVirtualSelectedParentBlueScoreChangedResponse) toAppMessage() (appmessage.Message, error) {
var err *appmessage.RPCError
if x.NotifyVirtualSelectedParentBlueScoreChangedResponse.Error != nil {
err = &appmessage.RPCError{Message: x.NotifyVirtualSelectedParentBlueScoreChangedResponse.Error.Message}
}
return &appmessage.NotifyVirtualSelectedParentBlueScoreChangedResponseMessage{
Error: err,
}, nil
}
func (x *KaspadMessage_NotifyVirtualSelectedParentBlueScoreChangedResponse) fromAppMessage(message *appmessage.NotifyVirtualSelectedParentBlueScoreChangedResponseMessage) error {
var err *RPCError
if message.Error != nil {
err = &RPCError{Message: message.Error.Message}
}
x.NotifyVirtualSelectedParentBlueScoreChangedResponse = &NotifyVirtualSelectedParentBlueScoreChangedResponseMessage{
Error: err,
}
return nil
}
func (x *KaspadMessage_VirtualSelectedParentBlueScoreChangedNotification) toAppMessage() (appmessage.Message, error) {
return &appmessage.VirtualSelectedParentBlueScoreChangedNotificationMessage{
VirtualSelectedParentBlueScore: x.VirtualSelectedParentBlueScoreChangedNotification.VirtualSelectedParentBlueScore,
}, nil
}
func (x *KaspadMessage_VirtualSelectedParentBlueScoreChangedNotification) fromAppMessage(message *appmessage.VirtualSelectedParentBlueScoreChangedNotificationMessage) error {
x.VirtualSelectedParentBlueScoreChangedNotification = &VirtualSelectedParentBlueScoreChangedNotificationMessage{
VirtualSelectedParentBlueScore: message.VirtualSelectedParentBlueScore,
}
return nil
}

View File

@ -1,20 +1,19 @@
package protowire
import "github.com/kaspanet/kaspad/app/appmessage"
import (
"github.com/kaspanet/kaspad/app/appmessage"
)
func (x *KaspadMessage_SubmitTransactionRequest) toAppMessage() (appmessage.Message, error) {
msgTx, err := x.SubmitTransactionRequest.Transaction.toAppMessage()
if err != nil {
return nil, err
}
rpcTransaction := x.SubmitTransactionRequest.Transaction.toAppMessage()
return &appmessage.SubmitTransactionRequestMessage{
Transaction: msgTx.(*appmessage.MsgTx),
Transaction: rpcTransaction,
}, nil
}
func (x *KaspadMessage_SubmitTransactionRequest) fromAppMessage(message *appmessage.SubmitTransactionRequestMessage) error {
x.SubmitTransactionRequest = &SubmitTransactionRequestMessage{
Transaction: &TransactionMessage{},
Transaction: &RpcTransaction{},
}
x.SubmitTransactionRequest.Transaction.fromAppMessage(message.Transaction)
return nil
@ -26,8 +25,8 @@ func (x *KaspadMessage_SubmitTransactionResponse) toAppMessage() (appmessage.Mes
err = &appmessage.RPCError{Message: x.SubmitTransactionResponse.Error.Message}
}
return &appmessage.SubmitTransactionResponseMessage{
TxID: x.SubmitTransactionResponse.TxId,
Error: err,
TransactionID: x.SubmitTransactionResponse.TransactionId,
Error: err,
}, nil
}
@ -37,8 +36,72 @@ func (x *KaspadMessage_SubmitTransactionResponse) fromAppMessage(message *appmes
err = &RPCError{Message: message.Error.Message}
}
x.SubmitTransactionResponse = &SubmitTransactionResponseMessage{
TxId: message.TxID,
Error: err,
TransactionId: message.TransactionID,
Error: err,
}
return nil
}
func (x *RpcTransaction) toAppMessage() *appmessage.RPCTransaction {
inputs := make([]*appmessage.RPCTransactionInput, len(x.Inputs))
for i, input := range x.Inputs {
previousOutpoint := &appmessage.RPCOutpoint{
TransactionID: input.PreviousOutpoint.TransactionId,
Index: input.PreviousOutpoint.Index,
}
inputs[i] = &appmessage.RPCTransactionInput{
PreviousOutpoint: previousOutpoint,
SignatureScript: input.SignatureScript,
Sequence: input.Sequence,
}
}
outputs := make([]*appmessage.RPCTransactionOutput, len(x.Outputs))
for i, output := range x.Outputs {
outputs[i] = &appmessage.RPCTransactionOutput{
Amount: output.Amount,
ScriptPubKey: output.ScriptPubKey,
}
}
return &appmessage.RPCTransaction{
Version: x.Version,
Inputs: inputs,
Outputs: outputs,
LockTime: x.LockTime,
SubnetworkID: x.SubnetworkId,
Gas: x.Gas,
PayloadHash: x.PayloadHash,
Payload: x.Payload,
}
}
func (x *RpcTransaction) fromAppMessage(transaction *appmessage.RPCTransaction) {
inputs := make([]*RpcTransactionInput, len(transaction.Inputs))
for i, input := range transaction.Inputs {
previousOutpoint := &RpcOutpoint{
TransactionId: input.PreviousOutpoint.TransactionID,
Index: input.PreviousOutpoint.Index,
}
inputs[i] = &RpcTransactionInput{
PreviousOutpoint: previousOutpoint,
SignatureScript: input.SignatureScript,
Sequence: input.Sequence,
}
}
outputs := make([]*RpcTransactionOutput, len(transaction.Outputs))
for i, output := range transaction.Outputs {
outputs[i] = &RpcTransactionOutput{
Amount: output.Amount,
ScriptPubKey: output.ScriptPubKey,
}
}
*x = RpcTransaction{
Version: transaction.Version,
Inputs: inputs,
Outputs: outputs,
LockTime: transaction.LockTime,
SubnetworkId: transaction.SubnetworkID,
Gas: transaction.Gas,
PayloadHash: transaction.PayloadHash,
Payload: transaction.Payload,
}
}

View File

@ -575,6 +575,76 @@ func toRPCPayload(message appmessage.Message) (isKaspadMessage_Payload, error) {
return nil, err
}
return payload, nil
case *appmessage.NotifyUTXOsChangedRequestMessage:
payload := new(KaspadMessage_NotifyUtxosChangedRequest)
err := payload.fromAppMessage(message)
if err != nil {
return nil, err
}
return payload, nil
case *appmessage.NotifyUTXOsChangedResponseMessage:
payload := new(KaspadMessage_NotifyUtxosChangedResponse)
err := payload.fromAppMessage(message)
if err != nil {
return nil, err
}
return payload, nil
case *appmessage.UTXOsChangedNotificationMessage:
payload := new(KaspadMessage_UtxosChangedNotification)
err := payload.fromAppMessage(message)
if err != nil {
return nil, err
}
return payload, nil
case *appmessage.GetUTXOsByAddressesRequestMessage:
payload := new(KaspadMessage_GetUtxosByAddressesRequest)
err := payload.fromAppMessage(message)
if err != nil {
return nil, err
}
return payload, nil
case *appmessage.GetUTXOsByAddressesResponseMessage:
payload := new(KaspadMessage_GetUtxosByAddressesResponse)
err := payload.fromAppMessage(message)
if err != nil {
return nil, err
}
return payload, nil
case *appmessage.GetVirtualSelectedParentBlueScoreRequestMessage:
payload := new(KaspadMessage_GetVirtualSelectedParentBlueScoreRequest)
err := payload.fromAppMessage(message)
if err != nil {
return nil, err
}
return payload, nil
case *appmessage.GetVirtualSelectedParentBlueScoreResponseMessage:
payload := new(KaspadMessage_GetVirtualSelectedParentBlueScoreResponse)
err := payload.fromAppMessage(message)
if err != nil {
return nil, err
}
return payload, nil
case *appmessage.NotifyVirtualSelectedParentBlueScoreChangedRequestMessage:
payload := new(KaspadMessage_NotifyVirtualSelectedParentBlueScoreChangedRequest)
err := payload.fromAppMessage(message)
if err != nil {
return nil, err
}
return payload, nil
case *appmessage.NotifyVirtualSelectedParentBlueScoreChangedResponseMessage:
payload := new(KaspadMessage_NotifyVirtualSelectedParentBlueScoreChangedResponse)
err := payload.fromAppMessage(message)
if err != nil {
return nil, err
}
return payload, nil
case *appmessage.VirtualSelectedParentBlueScoreChangedNotificationMessage:
payload := new(KaspadMessage_VirtualSelectedParentBlueScoreChangedNotification)
err := payload.fromAppMessage(message)
if err != nil {
return nil, err
}
return payload, nil
default:
return nil, nil
}

View File

@ -0,0 +1,20 @@
package rpcclient
import "github.com/kaspanet/kaspad/app/appmessage"
// GetUTXOsByAddresses sends an RPC request respective to the function's name and returns the RPC server's response
func (c *RPCClient) GetUTXOsByAddresses(addresses []string) (*appmessage.GetUTXOsByAddressesResponseMessage, error) {
err := c.rpcRouter.outgoingRoute().Enqueue(appmessage.NewGetUTXOsByAddressesRequestMessage(addresses))
if err != nil {
return nil, err
}
response, err := c.route(appmessage.CmdGetUTXOsByAddressesResponseMessage).DequeueWithTimeout(c.timeout)
if err != nil {
return nil, err
}
getUTXOsByAddressesResponse := response.(*appmessage.GetUTXOsByAddressesResponseMessage)
if getUTXOsByAddressesResponse.Error != nil {
return nil, c.convertRPCError(getUTXOsByAddressesResponse.Error)
}
return getUTXOsByAddressesResponse, nil
}

View File

@ -0,0 +1,20 @@
package rpcclient
import "github.com/kaspanet/kaspad/app/appmessage"
// GetVirtualSelectedParentBlueScore sends an RPC request respective to the function's name and returns the RPC server's response
func (c *RPCClient) GetVirtualSelectedParentBlueScore() (*appmessage.GetVirtualSelectedParentBlueScoreResponseMessage, error) {
err := c.rpcRouter.outgoingRoute().Enqueue(appmessage.NewGetVirtualSelectedParentBlueScoreRequestMessage())
if err != nil {
return nil, err
}
response, err := c.route(appmessage.CmdGetVirtualSelectedParentBlueScoreResponseMessage).DequeueWithTimeout(c.timeout)
if err != nil {
return nil, err
}
getVirtualSelectedParentBlueScoreResponse := response.(*appmessage.GetVirtualSelectedParentBlueScoreResponseMessage)
if getVirtualSelectedParentBlueScoreResponse.Error != nil {
return nil, c.convertRPCError(getVirtualSelectedParentBlueScoreResponse.Error)
}
return getVirtualSelectedParentBlueScoreResponse, nil
}

View File

@ -0,0 +1,40 @@
package rpcclient
import (
"github.com/kaspanet/kaspad/app/appmessage"
routerpkg "github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
"github.com/pkg/errors"
)
// RegisterForUTXOsChangedNotifications sends an RPC request respective to the function's name and returns the RPC server's response.
// Additionally, it starts listening for the appropriate notification using the given handler function
func (c *RPCClient) RegisterForUTXOsChangedNotifications(addresses []string,
onUTXOsChanged func(notification *appmessage.UTXOsChangedNotificationMessage)) error {
err := c.rpcRouter.outgoingRoute().Enqueue(appmessage.NewNotifyUTXOsChangedRequestMessage(addresses))
if err != nil {
return err
}
response, err := c.route(appmessage.CmdNotifyUTXOsChangedResponseMessage).DequeueWithTimeout(c.timeout)
if err != nil {
return err
}
notifyUTXOsChangedResponse := response.(*appmessage.NotifyUTXOsChangedResponseMessage)
if notifyUTXOsChangedResponse.Error != nil {
return c.convertRPCError(notifyUTXOsChangedResponse.Error)
}
spawn("RegisterForUTXOsChangedNotifications", func() {
for {
notification, err := c.route(appmessage.CmdUTXOsChangedNotificationMessage).Dequeue()
if err != nil {
if errors.Is(err, routerpkg.ErrRouteClosed) {
break
}
panic(err)
}
UTXOsChangedNotification := notification.(*appmessage.UTXOsChangedNotificationMessage)
onUTXOsChanged(UTXOsChangedNotification)
}
})
return nil
}

View File

@ -0,0 +1,41 @@
package rpcclient
import (
"github.com/kaspanet/kaspad/app/appmessage"
routerpkg "github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
"github.com/pkg/errors"
)
// RegisterForVirtualSelectedParentBlueScoreChangedNotifications sends an RPC request respective to the function's
// name and returns the RPC server's response. Additionally, it starts listening for the appropriate notification
// using the given handler function
func (c *RPCClient) RegisterForVirtualSelectedParentBlueScoreChangedNotifications(
onVirtualSelectedParentBlueScoreChanged func(notification *appmessage.VirtualSelectedParentBlueScoreChangedNotificationMessage)) error {
err := c.rpcRouter.outgoingRoute().Enqueue(appmessage.NewNotifyVirtualSelectedParentBlueScoreChangedRequestMessage())
if err != nil {
return err
}
response, err := c.route(appmessage.CmdNotifyVirtualSelectedParentBlueScoreChangedResponseMessage).DequeueWithTimeout(c.timeout)
if err != nil {
return err
}
notifyVirtualSelectedParentBlueScoreChangedResponse := response.(*appmessage.NotifyVirtualSelectedParentBlueScoreChangedResponseMessage)
if notifyVirtualSelectedParentBlueScoreChangedResponse.Error != nil {
return c.convertRPCError(notifyVirtualSelectedParentBlueScoreChangedResponse.Error)
}
spawn("RegisterForVirtualSelectedParentBlueScoreChangedNotifications", func() {
for {
notification, err := c.route(appmessage.CmdVirtualSelectedParentBlueScoreChangedNotificationMessage).Dequeue()
if err != nil {
if errors.Is(err, routerpkg.ErrRouteClosed) {
break
}
panic(err)
}
VirtualSelectedParentBlueScoreChangedNotification := notification.(*appmessage.VirtualSelectedParentBlueScoreChangedNotificationMessage)
onVirtualSelectedParentBlueScoreChanged(VirtualSelectedParentBlueScoreChangedNotification)
}
})
return nil
}

View File

@ -5,8 +5,8 @@ import (
)
// SubmitTransaction sends an RPC request respective to the function's name and returns the RPC server's response
func (c *RPCClient) SubmitTransaction(msgTx *appmessage.MsgTx) (*appmessage.SubmitTransactionResponseMessage, error) {
err := c.rpcRouter.outgoingRoute().Enqueue(appmessage.NewSubmitTransactionRequestMessage(msgTx))
func (c *RPCClient) SubmitTransaction(transaction *appmessage.RPCTransaction) (*appmessage.SubmitTransactionResponseMessage, error) {
err := c.rpcRouter.outgoingRoute().Enqueue(appmessage.NewSubmitTransactionRequestMessage(transaction))
if err != nil {
return nil, err
}

View File

@ -35,6 +35,7 @@ func setConfig(t *testing.T, harness *appHarness) {
harness.config.DataDir = randomDirectory(t)
harness.config.Listeners = []string{harness.p2pAddress}
harness.config.RPCListeners = []string{harness.rpcAddress}
harness.config.UTXOIndex = harness.utxoIndex
}
func commonConfig() *config.Config {

View File

@ -21,6 +21,7 @@ type appHarness struct {
miningAddressPrivateKey string
config *config.Config
database database.Database
utxoIndex bool
}
type harnessParams struct {
@ -28,6 +29,7 @@ type harnessParams struct {
rpcAddress string
miningAddress string
miningAddressPrivateKey string
utxoIndex bool
}
// setupHarness creates a single appHarness with given parameters
@ -37,6 +39,7 @@ func setupHarness(t *testing.T, params *harnessParams) (harness *appHarness, tea
rpcAddress: params.rpcAddress,
miningAddress: params.miningAddress,
miningAddressPrivateKey: params.miningAddressPrivateKey,
utxoIndex: params.utxoIndex,
}
setConfig(t, harness)

View File

@ -42,12 +42,14 @@ func TestTxRelay(t *testing.T) {
waitForPayeeToReceiveBlock(t, payeeBlockAddedChan)
}
tx := generateTx(t, secondBlock.Transactions[transactionhelper.CoinbaseTransactionIndex], payer, payee)
response, err := payer.rpcClient.SubmitTransaction(tx)
msgTx := generateTx(t, secondBlock.Transactions[transactionhelper.CoinbaseTransactionIndex], payer, payee)
domainTransaction := appmessage.MsgTxToDomainTransaction(msgTx)
rpcTransaction := appmessage.DomainTransactionToRPCTransaction(domainTransaction)
response, err := payer.rpcClient.SubmitTransaction(rpcTransaction)
if err != nil {
t.Fatalf("Error submitting transaction: %+v", err)
}
txID := response.TxID
txID := response.TransactionID
txAddedToMempoolChan := make(chan struct{})

View File

@ -0,0 +1,192 @@
package integration
import (
"encoding/hex"
"github.com/kaspanet/go-secp256k1"
"github.com/kaspanet/kaspad/app/appmessage"
"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"
"testing"
)
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-1; 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 transaction
mineNextBlock(t, kaspad)
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)
}
// Mine another block to accept the above block
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 != *foundResponseEntry.UTXOEntry {
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{})
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)}
fromScript, err := hex.DecodeString(entry.UTXOEntry.ScriptPubKey)
if err != nil {
t.Fatalf("Error decoding script public key: %s", err)
}
msgTx := appmessage.NewNativeMsgTx(constants.TransactionVersion, txIns, txOuts)
privateKeyBytes, err := hex.DecodeString(miningAddress1PrivateKey)
if err != nil {
t.Fatalf("Error decoding private key: %+v", err)
}
privateKey, err := secp256k1.DeserializePrivateKeyFromSlice(privateKeyBytes)
if err != nil {
t.Fatalf("Error deserializing private key: %+v", err)
}
signatureScript, err := txscript.SignatureScript(appmessage.MsgTxToDomainTransaction(msgTx), 0,
fromScript, txscript.SigHashAll, privateKey)
if err != nil {
t.Fatalf("Error signing transaction: %+v", err)
}
msgTx.TxIn[0].SignatureScript = signatureScript
domainTransaction := appmessage.MsgTxToDomainTransaction(msgTx)
return appmessage.DomainTransactionToRPCTransaction(domainTransaction)
}

View File

@ -0,0 +1,62 @@
package integration
import (
"github.com/kaspanet/kaspad/app/appmessage"
"testing"
)
func TestVirtualSelectedParentBlueScore(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()
// Make sure that the initial blue score is 1
response, err := kaspad.rpcClient.GetVirtualSelectedParentBlueScore()
if err != nil {
t.Fatalf("Error getting virtual selected parent blue score: %s", err)
}
if response.BlueScore != 1 {
t.Fatalf("Unexpected virtual selected parent blue score. Want: %d, got: %d",
1, response.BlueScore)
}
// Register to virtual selected parent blue score changes
onVirtualSelectedParentBlueScoreChangedChan := make(chan *appmessage.VirtualSelectedParentBlueScoreChangedNotificationMessage)
err = kaspad.rpcClient.RegisterForVirtualSelectedParentBlueScoreChangedNotifications(
func(notification *appmessage.VirtualSelectedParentBlueScoreChangedNotificationMessage) {
onVirtualSelectedParentBlueScoreChangedChan <- notification
})
if err != nil {
t.Fatalf("Failed to register for virtual selected parent "+
"blue score change notifications: %s", err)
}
// Mine some blocks and make sure that the notifications
// report correct blue score values
const blockAmountToMine = 100
for i := 0; i < blockAmountToMine; i++ {
mineNextBlock(t, kaspad)
notification := <-onVirtualSelectedParentBlueScoreChangedChan
if notification.VirtualSelectedParentBlueScore != 2+uint64(i) {
t.Fatalf("Unexpected virtual selected parent blue score. Want: %d, got: %d",
2+uint64(i), notification.VirtualSelectedParentBlueScore)
}
}
// Make sure that the blue score after all that mining is as expected
response, err = kaspad.rpcClient.GetVirtualSelectedParentBlueScore()
if err != nil {
t.Fatalf("Error getting virtual selected parent blue score: %s", err)
}
if response.BlueScore != 1+blockAmountToMine {
t.Fatalf("Unexpected virtual selected parent blue score. Want: %d, got: %d",
1+blockAmountToMine, response.BlueScore)
}
}