mirror of
https://github.com/kaspanet/kaspad.git
synced 2026-02-23 11:58:22 +00:00
Compare commits
22 Commits
v0.8.5-dev
...
v0.8.6-dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3916534a7e | ||
|
|
0561347ff1 | ||
|
|
fb11981da1 | ||
|
|
1742e76af7 | ||
|
|
ddfe376388 | ||
|
|
52da65077a | ||
|
|
ed85f09742 | ||
|
|
819ec9f2a7 | ||
|
|
7ea8a72a9e | ||
|
|
ca04c049ab | ||
|
|
9a17198e7d | ||
|
|
756f40c59a | ||
|
|
6a03d31f98 | ||
|
|
319ab6cfcd | ||
|
|
abef96e3de | ||
|
|
2e0bc0f8c4 | ||
|
|
acf5423c63 | ||
|
|
effb545d20 | ||
|
|
ad9c213a06 | ||
|
|
a4adbabf96 | ||
|
|
799eb7515c | ||
|
|
0769705b37 |
@@ -5,3 +5,5 @@ coverage:
|
||||
project:
|
||||
default:
|
||||
informational: true
|
||||
ignore:
|
||||
- "**/*.pb.go" # Ignore all auto generated protobuf structures.
|
||||
@@ -3,6 +3,7 @@ package appmessage
|
||||
import (
|
||||
"encoding/hex"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/blockheader"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/utxo"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/subnetworks"
|
||||
@@ -268,3 +269,49 @@ func DomainTransactionToRPCTransaction(transaction *externalapi.DomainTransactio
|
||||
Payload: payload,
|
||||
}
|
||||
}
|
||||
|
||||
// OutpointAndUTXOEntryPairsToDomainOutpointAndUTXOEntryPairs converts
|
||||
// OutpointAndUTXOEntryPairs to domain OutpointAndUTXOEntryPairs
|
||||
func OutpointAndUTXOEntryPairsToDomainOutpointAndUTXOEntryPairs(
|
||||
outpointAndUTXOEntryPairs []*OutpointAndUTXOEntryPair) []*externalapi.OutpointAndUTXOEntryPair {
|
||||
|
||||
domainOutpointAndUTXOEntryPairs := make([]*externalapi.OutpointAndUTXOEntryPair, len(outpointAndUTXOEntryPairs))
|
||||
for i, outpointAndUTXOEntryPair := range outpointAndUTXOEntryPairs {
|
||||
domainOutpointAndUTXOEntryPairs[i] = &externalapi.OutpointAndUTXOEntryPair{
|
||||
Outpoint: &externalapi.DomainOutpoint{
|
||||
TransactionID: outpointAndUTXOEntryPair.Outpoint.TxID,
|
||||
Index: outpointAndUTXOEntryPair.Outpoint.Index,
|
||||
},
|
||||
UTXOEntry: utxo.NewUTXOEntry(
|
||||
outpointAndUTXOEntryPair.UTXOEntry.Amount,
|
||||
outpointAndUTXOEntryPair.UTXOEntry.ScriptPublicKey,
|
||||
outpointAndUTXOEntryPair.UTXOEntry.IsCoinbase,
|
||||
outpointAndUTXOEntryPair.UTXOEntry.BlockBlueScore,
|
||||
),
|
||||
}
|
||||
}
|
||||
return domainOutpointAndUTXOEntryPairs
|
||||
}
|
||||
|
||||
// DomainOutpointAndUTXOEntryPairsToOutpointAndUTXOEntryPairs converts
|
||||
// domain OutpointAndUTXOEntryPairs to OutpointAndUTXOEntryPairs
|
||||
func DomainOutpointAndUTXOEntryPairsToOutpointAndUTXOEntryPairs(
|
||||
outpointAndUTXOEntryPairs []*externalapi.OutpointAndUTXOEntryPair) []*OutpointAndUTXOEntryPair {
|
||||
|
||||
domainOutpointAndUTXOEntryPairs := make([]*OutpointAndUTXOEntryPair, len(outpointAndUTXOEntryPairs))
|
||||
for i, outpointAndUTXOEntryPair := range outpointAndUTXOEntryPairs {
|
||||
domainOutpointAndUTXOEntryPairs[i] = &OutpointAndUTXOEntryPair{
|
||||
Outpoint: &Outpoint{
|
||||
TxID: outpointAndUTXOEntryPair.Outpoint.TransactionID,
|
||||
Index: outpointAndUTXOEntryPair.Outpoint.Index,
|
||||
},
|
||||
UTXOEntry: &UTXOEntry{
|
||||
Amount: outpointAndUTXOEntryPair.UTXOEntry.Amount(),
|
||||
ScriptPublicKey: outpointAndUTXOEntryPair.UTXOEntry.ScriptPublicKey(),
|
||||
IsCoinbase: outpointAndUTXOEntryPair.UTXOEntry.IsCoinbase(),
|
||||
BlockBlueScore: outpointAndUTXOEntryPair.UTXOEntry.BlockBlueScore(),
|
||||
},
|
||||
}
|
||||
}
|
||||
return domainOutpointAndUTXOEntryPairs
|
||||
}
|
||||
|
||||
@@ -51,17 +51,17 @@ const (
|
||||
CmdReject
|
||||
CmdHeader
|
||||
CmdRequestNextHeaders
|
||||
CmdRequestIBDRootUTXOSetAndBlock
|
||||
CmdIBDRootUTXOSetChunk
|
||||
CmdRequestPruningPointUTXOSetAndBlock
|
||||
CmdPruningPointUTXOSetChunk
|
||||
CmdRequestIBDBlocks
|
||||
CmdIBDRootNotFound
|
||||
CmdRequestIBDRootHash
|
||||
CmdIBDRootHash
|
||||
CmdUnexpectedPruningPoint
|
||||
CmdRequestPruningPointHash
|
||||
CmdPruningPointHash
|
||||
CmdIBDBlockLocator
|
||||
CmdIBDBlockLocatorHighestHash
|
||||
CmdBlockHeaders
|
||||
CmdRequestNextIBDRootUTXOSetChunk
|
||||
CmdDoneIBDRootUTXOSetChunks
|
||||
CmdRequestNextPruningPointUTXOSetChunk
|
||||
CmdDonePruningPointUTXOSetChunks
|
||||
|
||||
// rpc
|
||||
CmdGetCurrentNetworkRequestMessage
|
||||
@@ -127,38 +127,38 @@ const (
|
||||
|
||||
// ProtocolMessageCommandToString maps all MessageCommands to their string representation
|
||||
var ProtocolMessageCommandToString = map[MessageCommand]string{
|
||||
CmdVersion: "Version",
|
||||
CmdVerAck: "VerAck",
|
||||
CmdRequestAddresses: "RequestAddresses",
|
||||
CmdAddresses: "Addresses",
|
||||
CmdRequestHeaders: "RequestHeaders",
|
||||
CmdBlock: "Block",
|
||||
CmdTx: "Tx",
|
||||
CmdPing: "Ping",
|
||||
CmdPong: "Pong",
|
||||
CmdRequestBlockLocator: "RequestBlockLocator",
|
||||
CmdBlockLocator: "BlockLocator",
|
||||
CmdInvRelayBlock: "InvRelayBlock",
|
||||
CmdRequestRelayBlocks: "RequestRelayBlocks",
|
||||
CmdInvTransaction: "InvTransaction",
|
||||
CmdRequestTransactions: "RequestTransactions",
|
||||
CmdIBDBlock: "IBDBlock",
|
||||
CmdDoneHeaders: "DoneHeaders",
|
||||
CmdTransactionNotFound: "TransactionNotFound",
|
||||
CmdReject: "Reject",
|
||||
CmdHeader: "Header",
|
||||
CmdRequestNextHeaders: "RequestNextHeaders",
|
||||
CmdRequestIBDRootUTXOSetAndBlock: "RequestPruningUTXOSetAndBlock",
|
||||
CmdIBDRootUTXOSetChunk: "IBDRootUTXOSetChunk",
|
||||
CmdRequestIBDBlocks: "RequestIBDBlocks",
|
||||
CmdIBDRootNotFound: "IBDRootNotFound",
|
||||
CmdRequestIBDRootHash: "IBDRequestIBDRootHash",
|
||||
CmdIBDRootHash: "IBDIBDRootHash",
|
||||
CmdIBDBlockLocator: "IBDBlockLocator",
|
||||
CmdIBDBlockLocatorHighestHash: "IBDBlockLocatorHighestHash",
|
||||
CmdBlockHeaders: "BlockHeaders",
|
||||
CmdRequestNextIBDRootUTXOSetChunk: "RequestNextIBDRootUTXOSetChunk",
|
||||
CmdDoneIBDRootUTXOSetChunks: "DoneIBDRootUTXOSetChunks",
|
||||
CmdVersion: "Version",
|
||||
CmdVerAck: "VerAck",
|
||||
CmdRequestAddresses: "RequestAddresses",
|
||||
CmdAddresses: "Addresses",
|
||||
CmdRequestHeaders: "RequestHeaders",
|
||||
CmdBlock: "Block",
|
||||
CmdTx: "Tx",
|
||||
CmdPing: "Ping",
|
||||
CmdPong: "Pong",
|
||||
CmdRequestBlockLocator: "RequestBlockLocator",
|
||||
CmdBlockLocator: "BlockLocator",
|
||||
CmdInvRelayBlock: "InvRelayBlock",
|
||||
CmdRequestRelayBlocks: "RequestRelayBlocks",
|
||||
CmdInvTransaction: "InvTransaction",
|
||||
CmdRequestTransactions: "RequestTransactions",
|
||||
CmdIBDBlock: "IBDBlock",
|
||||
CmdDoneHeaders: "DoneHeaders",
|
||||
CmdTransactionNotFound: "TransactionNotFound",
|
||||
CmdReject: "Reject",
|
||||
CmdHeader: "Header",
|
||||
CmdRequestNextHeaders: "RequestNextHeaders",
|
||||
CmdRequestPruningPointUTXOSetAndBlock: "RequestPruningPointUTXOSetAndBlock",
|
||||
CmdPruningPointUTXOSetChunk: "PruningPointUTXOSetChunk",
|
||||
CmdRequestIBDBlocks: "RequestIBDBlocks",
|
||||
CmdUnexpectedPruningPoint: "UnexpectedPruningPoint",
|
||||
CmdRequestPruningPointHash: "RequestPruningPointHashHash",
|
||||
CmdPruningPointHash: "PruningPointHash",
|
||||
CmdIBDBlockLocator: "IBDBlockLocator",
|
||||
CmdIBDBlockLocatorHighestHash: "IBDBlockLocatorHighestHash",
|
||||
CmdBlockHeaders: "BlockHeaders",
|
||||
CmdRequestNextPruningPointUTXOSetChunk: "RequestNextPruningPointUTXOSetChunk",
|
||||
CmdDonePruningPointUTXOSetChunks: "DonePruningPointUTXOSetChunks",
|
||||
}
|
||||
|
||||
// RPCMessageCommandToString maps all MessageCommands to their string representation
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
package appmessage
|
||||
|
||||
// MsgIBDRootNotFound implements the Message interface and represents a kaspa
|
||||
// IBDRootNotFound message. It is used to notify the IBD root that was requested
|
||||
// by other peer was not found.
|
||||
//
|
||||
// This message has no payload.
|
||||
type MsgIBDRootNotFound struct {
|
||||
baseMessage
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
// of the Message interface implementation.
|
||||
func (msg *MsgIBDRootNotFound) Command() MessageCommand {
|
||||
return CmdIBDRootNotFound
|
||||
}
|
||||
|
||||
// NewMsgIBDRootNotFound returns a new kaspa IBDRootNotFound message that conforms to the
|
||||
// Message interface.
|
||||
func NewMsgIBDRootNotFound() *MsgIBDRootNotFound {
|
||||
return &MsgIBDRootNotFound{}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package appmessage
|
||||
|
||||
// MsgDoneIBDRootUTXOSetChunks represents a kaspa DoneIBDRootUTXOSetChunks message
|
||||
type MsgDoneIBDRootUTXOSetChunks struct {
|
||||
baseMessage
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message
|
||||
func (msg *MsgDoneIBDRootUTXOSetChunks) Command() MessageCommand {
|
||||
return CmdDoneIBDRootUTXOSetChunks
|
||||
}
|
||||
|
||||
// NewMsgDoneIBDRootUTXOSetChunks returns a new MsgDoneIBDRootUTXOSetChunks.
|
||||
func NewMsgDoneIBDRootUTXOSetChunks() *MsgDoneIBDRootUTXOSetChunks {
|
||||
return &MsgDoneIBDRootUTXOSetChunks{}
|
||||
}
|
||||
16
app/appmessage/p2p_msgdonepruningpointutxosetchunks.go
Normal file
16
app/appmessage/p2p_msgdonepruningpointutxosetchunks.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package appmessage
|
||||
|
||||
// MsgDonePruningPointUTXOSetChunks represents a kaspa DonePruningPointUTXOSetChunks message
|
||||
type MsgDonePruningPointUTXOSetChunks struct {
|
||||
baseMessage
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message
|
||||
func (msg *MsgDonePruningPointUTXOSetChunks) Command() MessageCommand {
|
||||
return CmdDonePruningPointUTXOSetChunks
|
||||
}
|
||||
|
||||
// NewMsgDonePruningPointUTXOSetChunks returns a new MsgDonePruningPointUTXOSetChunks.
|
||||
func NewMsgDonePruningPointUTXOSetChunks() *MsgDonePruningPointUTXOSetChunks {
|
||||
return &MsgDonePruningPointUTXOSetChunks{}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
)
|
||||
|
||||
// MsgIBDRootHashMessage implements the Message interface and represents a kaspa
|
||||
// IBDRootHash message. It is used as a reply to IBD root hash requests.
|
||||
type MsgIBDRootHashMessage struct {
|
||||
baseMessage
|
||||
Hash *externalapi.DomainHash
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
// of the Message interface implementation.
|
||||
func (msg *MsgIBDRootHashMessage) Command() MessageCommand {
|
||||
return CmdIBDRootHash
|
||||
}
|
||||
|
||||
// NewMsgIBDRootHashMessage returns a new kaspa IBDRootHash message that conforms to
|
||||
// the Message interface. See MsgIBDRootHashMessage for details.
|
||||
func NewMsgIBDRootHashMessage(hash *externalapi.DomainHash) *MsgIBDRootHashMessage {
|
||||
return &MsgIBDRootHashMessage{
|
||||
Hash: hash,
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package appmessage
|
||||
|
||||
// MsgIBDRootUTXOSetChunk represents a kaspa IBDRootUTXOSetChunk message
|
||||
type MsgIBDRootUTXOSetChunk struct {
|
||||
baseMessage
|
||||
Chunk []byte
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message
|
||||
func (msg *MsgIBDRootUTXOSetChunk) Command() MessageCommand {
|
||||
return CmdIBDRootUTXOSetChunk
|
||||
}
|
||||
|
||||
// NewMsgIBDRootUTXOSetChunk returns a new MsgIBDRootUTXOSetChunk.
|
||||
func NewMsgIBDRootUTXOSetChunk(chunk []byte) *MsgIBDRootUTXOSetChunk {
|
||||
return &MsgIBDRootUTXOSetChunk{
|
||||
Chunk: chunk,
|
||||
}
|
||||
}
|
||||
23
app/appmessage/p2p_msgpruningpointhash.go
Normal file
23
app/appmessage/p2p_msgpruningpointhash.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
)
|
||||
|
||||
// MsgPruningPointHashMessage represents a kaspa PruningPointHash message
|
||||
type MsgPruningPointHashMessage struct {
|
||||
baseMessage
|
||||
Hash *externalapi.DomainHash
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message
|
||||
func (msg *MsgPruningPointHashMessage) Command() MessageCommand {
|
||||
return CmdPruningPointHash
|
||||
}
|
||||
|
||||
// NewPruningPointHashMessage returns a new kaspa PruningPointHash message
|
||||
func NewPruningPointHashMessage(hash *externalapi.DomainHash) *MsgPruningPointHashMessage {
|
||||
return &MsgPruningPointHashMessage{
|
||||
Hash: hash,
|
||||
}
|
||||
}
|
||||
36
app/appmessage/p2p_msgpruningpointutxosetchunk.go
Normal file
36
app/appmessage/p2p_msgpruningpointutxosetchunk.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package appmessage
|
||||
|
||||
import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
|
||||
// MsgPruningPointUTXOSetChunk represents a kaspa PruningPointUTXOSetChunk message
|
||||
type MsgPruningPointUTXOSetChunk struct {
|
||||
baseMessage
|
||||
OutpointAndUTXOEntryPairs []*OutpointAndUTXOEntryPair
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message
|
||||
func (msg *MsgPruningPointUTXOSetChunk) Command() MessageCommand {
|
||||
return CmdPruningPointUTXOSetChunk
|
||||
}
|
||||
|
||||
// NewMsgPruningPointUTXOSetChunk returns a new MsgPruningPointUTXOSetChunk.
|
||||
func NewMsgPruningPointUTXOSetChunk(outpointAndUTXOEntryPairs []*OutpointAndUTXOEntryPair) *MsgPruningPointUTXOSetChunk {
|
||||
return &MsgPruningPointUTXOSetChunk{
|
||||
OutpointAndUTXOEntryPairs: outpointAndUTXOEntryPairs,
|
||||
}
|
||||
}
|
||||
|
||||
// OutpointAndUTXOEntryPair is an outpoint along with its
|
||||
// respective UTXO entry
|
||||
type OutpointAndUTXOEntryPair struct {
|
||||
Outpoint *Outpoint
|
||||
UTXOEntry *UTXOEntry
|
||||
}
|
||||
|
||||
// UTXOEntry houses details about an individual transaction output in a UTXO
|
||||
type UTXOEntry struct {
|
||||
Amount uint64
|
||||
ScriptPublicKey *externalapi.ScriptPublicKey
|
||||
BlockBlueScore uint64
|
||||
IsCoinbase bool
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
)
|
||||
|
||||
// MsgRequestIBDRootUTXOSetAndBlock implements the Message interface and represents a kaspa
|
||||
// RequestIBDRootUTXOSetAndBlock message. It is used to request the UTXO set and block body
|
||||
// of the IBD root block.
|
||||
type MsgRequestIBDRootUTXOSetAndBlock struct {
|
||||
baseMessage
|
||||
IBDRoot *externalapi.DomainHash
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
// of the Message interface implementation.
|
||||
func (msg *MsgRequestIBDRootUTXOSetAndBlock) Command() MessageCommand {
|
||||
return CmdRequestIBDRootUTXOSetAndBlock
|
||||
}
|
||||
|
||||
// NewMsgRequestIBDRootUTXOSetAndBlock returns a new MsgRequestIBDRootUTXOSetAndBlock.
|
||||
func NewMsgRequestIBDRootUTXOSetAndBlock(ibdRoot *externalapi.DomainHash) *MsgRequestIBDRootUTXOSetAndBlock {
|
||||
return &MsgRequestIBDRootUTXOSetAndBlock{
|
||||
IBDRoot: ibdRoot,
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package appmessage
|
||||
|
||||
// MsgRequestNextIBDRootUTXOSetChunk represents a kaspa RequestNextIBDRootUTXOSetChunk message
|
||||
type MsgRequestNextIBDRootUTXOSetChunk struct {
|
||||
baseMessage
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message
|
||||
func (msg *MsgRequestNextIBDRootUTXOSetChunk) Command() MessageCommand {
|
||||
return CmdRequestNextIBDRootUTXOSetChunk
|
||||
}
|
||||
|
||||
// NewMsgRequestNextIBDRootUTXOSetChunk returns a new MsgRequestNextIBDRootUTXOSetChunk.
|
||||
func NewMsgRequestNextIBDRootUTXOSetChunk() *MsgRequestNextIBDRootUTXOSetChunk {
|
||||
return &MsgRequestNextIBDRootUTXOSetChunk{}
|
||||
}
|
||||
16
app/appmessage/p2p_msgrequestnextpruningpointutxosetchunk.go
Normal file
16
app/appmessage/p2p_msgrequestnextpruningpointutxosetchunk.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package appmessage
|
||||
|
||||
// MsgRequestNextPruningPointUTXOSetChunk represents a kaspa RequestNextPruningPointUTXOSetChunk message
|
||||
type MsgRequestNextPruningPointUTXOSetChunk struct {
|
||||
baseMessage
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message
|
||||
func (msg *MsgRequestNextPruningPointUTXOSetChunk) Command() MessageCommand {
|
||||
return CmdRequestNextPruningPointUTXOSetChunk
|
||||
}
|
||||
|
||||
// NewMsgRequestNextPruningPointUTXOSetChunk returns a new MsgRequestNextPruningPointUTXOSetChunk.
|
||||
func NewMsgRequestNextPruningPointUTXOSetChunk() *MsgRequestNextPruningPointUTXOSetChunk {
|
||||
return &MsgRequestNextPruningPointUTXOSetChunk{}
|
||||
}
|
||||
23
app/appmessage/p2p_msgrequestpruningpointutxosetandblock.go
Normal file
23
app/appmessage/p2p_msgrequestpruningpointutxosetandblock.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
)
|
||||
|
||||
// MsgRequestPruningPointUTXOSetAndBlock represents a kaspa RequestPruningPointUTXOSetAndBlock message
|
||||
type MsgRequestPruningPointUTXOSetAndBlock struct {
|
||||
baseMessage
|
||||
PruningPointHash *externalapi.DomainHash
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message
|
||||
func (msg *MsgRequestPruningPointUTXOSetAndBlock) Command() MessageCommand {
|
||||
return CmdRequestPruningPointUTXOSetAndBlock
|
||||
}
|
||||
|
||||
// NewMsgRequestPruningPointUTXOSetAndBlock returns a new MsgRequestPruningPointUTXOSetAndBlock
|
||||
func NewMsgRequestPruningPointUTXOSetAndBlock(pruningPointHash *externalapi.DomainHash) *MsgRequestPruningPointUTXOSetAndBlock {
|
||||
return &MsgRequestPruningPointUTXOSetAndBlock{
|
||||
PruningPointHash: pruningPointHash,
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package appmessage
|
||||
|
||||
// MsgRequestIBDRootHashMessage implements the Message interface and represents a kaspa
|
||||
// MsgRequestIBDRootHashMessage message. It is used to request the IBD root hash
|
||||
// from a peer during IBD.
|
||||
//
|
||||
// This message has no payload.
|
||||
type MsgRequestIBDRootHashMessage struct {
|
||||
baseMessage
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
// of the Message interface implementation.
|
||||
func (msg *MsgRequestIBDRootHashMessage) Command() MessageCommand {
|
||||
return CmdRequestIBDRootHash
|
||||
}
|
||||
|
||||
// NewMsgRequestIBDRootHashMessage returns a new kaspa RequestIBDRootHash message that conforms to the
|
||||
// Message interface.
|
||||
func NewMsgRequestIBDRootHashMessage() *MsgRequestIBDRootHashMessage {
|
||||
return &MsgRequestIBDRootHashMessage{}
|
||||
}
|
||||
16
app/appmessage/p2p_requestpruningpointhash.go
Normal file
16
app/appmessage/p2p_requestpruningpointhash.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package appmessage
|
||||
|
||||
// MsgRequestPruningPointHashMessage represents a kaspa RequestPruningPointHashMessage message
|
||||
type MsgRequestPruningPointHashMessage struct {
|
||||
baseMessage
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message
|
||||
func (msg *MsgRequestPruningPointHashMessage) Command() MessageCommand {
|
||||
return CmdRequestPruningPointHash
|
||||
}
|
||||
|
||||
// NewMsgRequestPruningPointHashMessage returns a new kaspa RequestPruningPointHash message
|
||||
func NewMsgRequestPruningPointHashMessage() *MsgRequestPruningPointHashMessage {
|
||||
return &MsgRequestPruningPointHashMessage{}
|
||||
}
|
||||
16
app/appmessage/p2p_unexpectedpruningpoint.go
Normal file
16
app/appmessage/p2p_unexpectedpruningpoint.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package appmessage
|
||||
|
||||
// MsgUnexpectedPruningPoint represents a kaspa UnexpectedPruningPoint message
|
||||
type MsgUnexpectedPruningPoint struct {
|
||||
baseMessage
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message
|
||||
func (msg *MsgUnexpectedPruningPoint) Command() MessageCommand {
|
||||
return CmdUnexpectedPruningPoint
|
||||
}
|
||||
|
||||
// NewMsgUnexpectedPruningPoint returns a new kaspa UnexpectedPruningPoint message
|
||||
func NewMsgUnexpectedPruningPoint() *MsgUnexpectedPruningPoint {
|
||||
return &MsgUnexpectedPruningPoint{}
|
||||
}
|
||||
@@ -5,7 +5,6 @@ package appmessage
|
||||
type GetBlockRequestMessage struct {
|
||||
baseMessage
|
||||
Hash string
|
||||
SubnetworkID string
|
||||
IncludeTransactionVerboseData bool
|
||||
}
|
||||
|
||||
@@ -15,10 +14,9 @@ func (msg *GetBlockRequestMessage) Command() MessageCommand {
|
||||
}
|
||||
|
||||
// NewGetBlockRequestMessage returns a instance of the message
|
||||
func NewGetBlockRequestMessage(hash string, subnetworkID string, includeTransactionVerboseData bool) *GetBlockRequestMessage {
|
||||
func NewGetBlockRequestMessage(hash string, includeTransactionVerboseData bool) *GetBlockRequestMessage {
|
||||
return &GetBlockRequestMessage{
|
||||
Hash: hash,
|
||||
SubnetworkID: subnetworkID,
|
||||
IncludeTransactionVerboseData: includeTransactionVerboseData,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/hashset"
|
||||
"github.com/kaspanet/kaspad/infrastructure/logger"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@@ -160,3 +162,49 @@ func (f *FlowContext) unorphanBlock(orphanHash externalapi.DomainHash) (*externa
|
||||
log.Infof("Unorphaned block %s", orphanHash)
|
||||
return blockInsertionResult, true, nil
|
||||
}
|
||||
|
||||
// GetOrphanRoots returns the roots of the missing ancestors DAG of the given orphan
|
||||
func (f *FlowContext) GetOrphanRoots(orphan *externalapi.DomainHash) ([]*externalapi.DomainHash, bool, error) {
|
||||
onEnd := logger.LogAndMeasureExecutionTime(log, "GetOrphanRoots")
|
||||
defer onEnd()
|
||||
|
||||
f.orphansMutex.RLock()
|
||||
defer f.orphansMutex.RUnlock()
|
||||
|
||||
_, ok := f.orphans[*orphan]
|
||||
if !ok {
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
queue := []*externalapi.DomainHash{orphan}
|
||||
addedToQueueSet := hashset.New()
|
||||
addedToQueueSet.Add(orphan)
|
||||
|
||||
roots := []*externalapi.DomainHash{}
|
||||
for len(queue) > 0 {
|
||||
var current *externalapi.DomainHash
|
||||
current, queue = queue[0], queue[1:]
|
||||
|
||||
block, ok := f.orphans[*current]
|
||||
if !ok {
|
||||
blockInfo, err := f.domain.Consensus().GetBlockInfo(current)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
if !blockInfo.Exists || blockInfo.BlockStatus == externalapi.StatusHeaderOnly {
|
||||
roots = append(roots, current)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
for _, parent := range block.Header.ParentHashes() {
|
||||
if !addedToQueueSet.Contains(parent) {
|
||||
queue = append(queue, parent)
|
||||
addedToQueueSet.Add(parent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return roots, true, nil
|
||||
}
|
||||
|
||||
@@ -5,14 +5,12 @@ import (
|
||||
"github.com/kaspanet/kaspad/app/protocol/common"
|
||||
peerpkg "github.com/kaspanet/kaspad/app/protocol/peer"
|
||||
"github.com/kaspanet/kaspad/app/protocol/protocolerrors"
|
||||
"github.com/kaspanet/kaspad/infrastructure/config"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/addressmanager"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
||||
)
|
||||
|
||||
// ReceiveAddressesContext is the interface for the context needed for the ReceiveAddresses flow.
|
||||
type ReceiveAddressesContext interface {
|
||||
Config() *config.Config
|
||||
AddressManager() *addressmanager.AddressManager
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package addressexchange
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/app/protocol/protocolerrors"
|
||||
"math/rand"
|
||||
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
@@ -17,16 +16,11 @@ type SendAddressesContext interface {
|
||||
// SendAddresses sends addresses to a peer that requests it.
|
||||
func SendAddresses(context SendAddressesContext, incomingRoute *router.Route, outgoingRoute *router.Route) error {
|
||||
for {
|
||||
message, err := incomingRoute.Dequeue()
|
||||
_, err := incomingRoute.Dequeue()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, ok := message.(*appmessage.MsgRequestAddresses)
|
||||
if !ok {
|
||||
return protocolerrors.Errorf(true, "unexpected message. "+
|
||||
"Expected: %s, got: %s", appmessage.CmdRequestAddresses, message.Command())
|
||||
}
|
||||
addresses := context.AddressManager().Addresses()
|
||||
msgAddresses := appmessage.NewMsgAddresses(shuffleAddresses(addresses))
|
||||
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
package blockrelay
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/domain"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
||||
)
|
||||
|
||||
// HandleIBDRootHashRequestsFlowContext is the interface for the context needed for the handleIBDRootHashRequestsFlow flow.
|
||||
type HandleIBDRootHashRequestsFlowContext interface {
|
||||
Domain() domain.Domain
|
||||
}
|
||||
|
||||
type handleIBDRootHashRequestsFlow struct {
|
||||
HandleIBDRootHashRequestsFlowContext
|
||||
incomingRoute, outgoingRoute *router.Route
|
||||
}
|
||||
|
||||
// HandleIBDRootHashRequests listens to appmessage.MsgRequestIBDRootHashMessage messages and sends
|
||||
// the IBD root hash as response.
|
||||
func HandleIBDRootHashRequests(context HandleIBDRootHashRequestsFlowContext, incomingRoute,
|
||||
outgoingRoute *router.Route) error {
|
||||
flow := &handleIBDRootHashRequestsFlow{
|
||||
HandleIBDRootHashRequestsFlowContext: context,
|
||||
incomingRoute: incomingRoute,
|
||||
outgoingRoute: outgoingRoute,
|
||||
}
|
||||
|
||||
return flow.start()
|
||||
}
|
||||
|
||||
func (flow *handleIBDRootHashRequestsFlow) start() error {
|
||||
for {
|
||||
_, err := flow.incomingRoute.Dequeue()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debugf("Got request for IBD root hash")
|
||||
|
||||
pruningPoint, err := flow.Domain().Consensus().PruningPoint()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = flow.outgoingRoute.Enqueue(appmessage.NewMsgIBDRootHashMessage(pruningPoint))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debugf("Sent IBD root hash %s", pruningPoint)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package blockrelay
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/domain"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
||||
)
|
||||
|
||||
// HandlePruningPointHashRequestsFlowContext is the interface for the context needed for the handlePruningPointHashRequestsFlow flow.
|
||||
type HandlePruningPointHashRequestsFlowContext interface {
|
||||
Domain() domain.Domain
|
||||
}
|
||||
|
||||
type handlePruningPointHashRequestsFlow struct {
|
||||
HandlePruningPointHashRequestsFlowContext
|
||||
incomingRoute, outgoingRoute *router.Route
|
||||
}
|
||||
|
||||
// HandlePruningPointHashRequests listens to appmessage.MsgRequestPruningPointHashMessage messages and sends
|
||||
// the pruning point hash as response.
|
||||
func HandlePruningPointHashRequests(context HandlePruningPointHashRequestsFlowContext, incomingRoute,
|
||||
outgoingRoute *router.Route) error {
|
||||
flow := &handlePruningPointHashRequestsFlow{
|
||||
HandlePruningPointHashRequestsFlowContext: context,
|
||||
incomingRoute: incomingRoute,
|
||||
outgoingRoute: outgoingRoute,
|
||||
}
|
||||
|
||||
return flow.start()
|
||||
}
|
||||
|
||||
func (flow *handlePruningPointHashRequestsFlow) start() error {
|
||||
for {
|
||||
_, err := flow.incomingRoute.Dequeue()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debugf("Got request for a pruning point hash")
|
||||
|
||||
pruningPoint, err := flow.Domain().Consensus().PruningPoint()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = flow.outgoingRoute.Enqueue(appmessage.NewPruningPointHashMessage(pruningPoint))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debugf("Sent pruning point hash %s", pruningPoint)
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
|
||||
"github.com/kaspanet/kaspad/infrastructure/config"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
@@ -24,11 +23,11 @@ var orphanResolutionRange uint32 = 5
|
||||
type RelayInvsContext interface {
|
||||
Domain() domain.Domain
|
||||
Config() *config.Config
|
||||
NetAdapter() *netadapter.NetAdapter
|
||||
OnNewBlock(block *externalapi.DomainBlock, blockInsertionResult *externalapi.BlockInsertionResult) error
|
||||
SharedRequestedBlocks() *SharedRequestedBlocks
|
||||
Broadcast(message appmessage.Message) error
|
||||
AddOrphan(orphanBlock *externalapi.DomainBlock)
|
||||
GetOrphanRoots(orphanHash *externalapi.DomainHash) ([]*externalapi.DomainHash, bool, error)
|
||||
IsOrphan(blockHash *externalapi.DomainHash) bool
|
||||
IsIBDRunning() bool
|
||||
TrySetIBDRunning(ibdPeer *peerpkg.Peer) bool
|
||||
@@ -71,7 +70,7 @@ func (flow *handleRelayInvsFlow) start() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if blockInfo.Exists {
|
||||
if blockInfo.Exists && blockInfo.BlockStatus != externalapi.StatusHeaderOnly {
|
||||
if blockInfo.BlockStatus == externalapi.StatusInvalid {
|
||||
return protocolerrors.Errorf(true, "sent inv of an invalid block %s",
|
||||
inv.Hash)
|
||||
@@ -81,7 +80,11 @@ func (flow *handleRelayInvsFlow) start() error {
|
||||
}
|
||||
|
||||
if flow.IsOrphan(inv.Hash) {
|
||||
log.Debugf("Block %s is a known orphan. continuing...", inv.Hash)
|
||||
log.Debugf("Block %s is a known orphan. Requesting its missing ancestors", inv.Hash)
|
||||
err := flow.AddOrphanRootsToQueue(inv.Hash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -104,10 +107,14 @@ func (flow *handleRelayInvsFlow) start() error {
|
||||
log.Debugf("Processing block %s", inv.Hash)
|
||||
missingParents, blockInsertionResult, err := flow.processBlock(block)
|
||||
if err != nil {
|
||||
if errors.Is(err, ruleerrors.ErrDuplicateBlock) {
|
||||
log.Infof("Ignoring duplicate block %s", inv.Hash)
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
if len(missingParents) > 0 {
|
||||
log.Debugf("Block %s contains orphans: %s", inv.Hash, missingParents)
|
||||
log.Debugf("Block %s is orphan and has missing parents: %s", inv.Hash, missingParents)
|
||||
err := flow.processOrphan(block, missingParents)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -238,9 +245,10 @@ func (flow *handleRelayInvsFlow) processOrphan(block *externalapi.DomainBlock, m
|
||||
}
|
||||
if isBlockInOrphanResolutionRange {
|
||||
log.Debugf("Block %s is within orphan resolution range. "+
|
||||
"Adding it to the orphan set and requesting its missing parents", blockHash)
|
||||
flow.addToOrphanSetAndRequestMissingParents(block, missingParents)
|
||||
return nil
|
||||
"Adding it to the orphan set", blockHash)
|
||||
flow.AddOrphan(block)
|
||||
log.Debugf("Requesting block %s missing ancestors", blockHash)
|
||||
return flow.AddOrphanRootsToQueue(blockHash)
|
||||
}
|
||||
|
||||
// Start IBD unless we already are in IBD
|
||||
@@ -277,13 +285,25 @@ func (flow *handleRelayInvsFlow) isBlockInOrphanResolutionRange(blockHash *exter
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (flow *handleRelayInvsFlow) addToOrphanSetAndRequestMissingParents(
|
||||
block *externalapi.DomainBlock, missingParents []*externalapi.DomainHash) {
|
||||
|
||||
flow.AddOrphan(block)
|
||||
invMessages := make([]*appmessage.MsgInvRelayBlock, len(missingParents))
|
||||
for i, missingParent := range missingParents {
|
||||
invMessages[i] = appmessage.NewMsgInvBlock(missingParent)
|
||||
func (flow *handleRelayInvsFlow) AddOrphanRootsToQueue(orphan *externalapi.DomainHash) error {
|
||||
orphanRoots, orphanExists, err := flow.GetOrphanRoots(orphan)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !orphanExists {
|
||||
log.Infof("Orphan block %s was missing from the orphan pool while requesting for its roots. This "+
|
||||
"probably happened because it was randomly evicted immediately after it was added.", orphan)
|
||||
}
|
||||
|
||||
log.Infof("Block %s has %d missing ancestors. Adding them to the invs queue...", orphan, len(orphanRoots))
|
||||
|
||||
invMessages := make([]*appmessage.MsgInvRelayBlock, len(orphanRoots))
|
||||
for i, root := range orphanRoots {
|
||||
log.Debugf("Adding block %s missing ancestor %s to the invs queue", orphan, root)
|
||||
invMessages[i] = appmessage.NewMsgInvBlock(root)
|
||||
}
|
||||
|
||||
flow.invsQueue = append(invMessages, flow.invsQueue...)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,117 +0,0 @@
|
||||
package blockrelay
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/app/protocol/common"
|
||||
"github.com/kaspanet/kaspad/app/protocol/protocolerrors"
|
||||
"github.com/kaspanet/kaspad/domain"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
|
||||
"github.com/kaspanet/kaspad/infrastructure/logger"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
||||
)
|
||||
|
||||
// HandleRequestIBDRootUTXOSetAndBlockContext is the interface for the context needed for the HandleRequestIBDRootUTXOSetAndBlock flow.
|
||||
type HandleRequestIBDRootUTXOSetAndBlockContext interface {
|
||||
Domain() domain.Domain
|
||||
}
|
||||
|
||||
type handleRequestIBDRootUTXOSetAndBlockFlow struct {
|
||||
HandleRequestIBDRootUTXOSetAndBlockContext
|
||||
incomingRoute, outgoingRoute *router.Route
|
||||
}
|
||||
|
||||
// HandleRequestIBDRootUTXOSetAndBlock listens to appmessage.MsgRequestIBDRootUTXOSetAndBlock messages and sends
|
||||
// the IBD root UTXO set and block body.
|
||||
func HandleRequestIBDRootUTXOSetAndBlock(context HandleRequestIBDRootUTXOSetAndBlockContext, incomingRoute,
|
||||
outgoingRoute *router.Route) error {
|
||||
flow := &handleRequestIBDRootUTXOSetAndBlockFlow{
|
||||
HandleRequestIBDRootUTXOSetAndBlockContext: context,
|
||||
incomingRoute: incomingRoute,
|
||||
outgoingRoute: outgoingRoute,
|
||||
}
|
||||
|
||||
return flow.start()
|
||||
}
|
||||
|
||||
func (flow *handleRequestIBDRootUTXOSetAndBlockFlow) start() error {
|
||||
for {
|
||||
message, err := flow.incomingRoute.Dequeue()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
msgRequestIBDRootUTXOSetAndBlock, ok := message.(*appmessage.MsgRequestIBDRootUTXOSetAndBlock)
|
||||
if !ok {
|
||||
return protocolerrors.Errorf(true, "received unexpected message type. "+
|
||||
"expected: %s, got: %s", appmessage.CmdRequestIBDRootUTXOSetAndBlock, message.Command())
|
||||
}
|
||||
|
||||
finishMeasuring := logger.LogAndMeasureExecutionTime(log, "handleRequestIBDRootUTXOSetAndBlockFlow")
|
||||
log.Debugf("Got request for IBDRoot UTXOSet and Block")
|
||||
|
||||
serializedUTXOSet, err := flow.Domain().Consensus().GetPruningPointUTXOSet(msgRequestIBDRootUTXOSetAndBlock.IBDRoot)
|
||||
if err != nil {
|
||||
if errors.Is(err, ruleerrors.ErrWrongPruningPointHash) {
|
||||
err = flow.outgoingRoute.Enqueue(appmessage.NewMsgIBDRootNotFound())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
}
|
||||
log.Debugf("Retrieved utxo set for pruning block %s", msgRequestIBDRootUTXOSetAndBlock.IBDRoot)
|
||||
|
||||
block, err := flow.Domain().Consensus().GetBlock(msgRequestIBDRootUTXOSetAndBlock.IBDRoot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debugf("Retrieved pruning block %s", msgRequestIBDRootUTXOSetAndBlock.IBDRoot)
|
||||
|
||||
err = flow.outgoingRoute.Enqueue(appmessage.NewMsgIBDBlock(appmessage.DomainBlockToMsgBlock(block)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Send the UTXO set in `step`-sized chunks
|
||||
const step = 1024 * 1024 // 1MB
|
||||
offset := 0
|
||||
chunksSent := 0
|
||||
for offset < len(serializedUTXOSet) {
|
||||
var chunk []byte
|
||||
if offset+step < len(serializedUTXOSet) {
|
||||
chunk = serializedUTXOSet[offset : offset+step]
|
||||
} else {
|
||||
chunk = serializedUTXOSet[offset:]
|
||||
}
|
||||
|
||||
err = flow.outgoingRoute.Enqueue(appmessage.NewMsgIBDRootUTXOSetChunk(chunk))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
offset += step
|
||||
chunksSent++
|
||||
|
||||
// Wait for the peer to request more chunks every `ibdBatchSize` chunks
|
||||
if chunksSent%ibdBatchSize == 0 {
|
||||
message, err := flow.incomingRoute.DequeueWithTimeout(common.DefaultTimeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, ok := message.(*appmessage.MsgRequestNextIBDRootUTXOSetChunk)
|
||||
if !ok {
|
||||
return protocolerrors.Errorf(true, "received unexpected message type. "+
|
||||
"expected: %s, got: %s", appmessage.CmdRequestNextIBDRootUTXOSetChunk, message.Command())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = flow.outgoingRoute.Enqueue(appmessage.NewMsgDoneIBDRootUTXOSetChunks())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
finishMeasuring()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
package blockrelay
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/app/protocol/common"
|
||||
"github.com/kaspanet/kaspad/app/protocol/protocolerrors"
|
||||
"github.com/kaspanet/kaspad/domain"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
|
||||
"github.com/kaspanet/kaspad/infrastructure/logger"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
||||
)
|
||||
|
||||
// HandleRequestPruningPointUTXOSetAndBlockContext is the interface for the context needed for the HandleRequestPruningPointUTXOSetAndBlock flow.
|
||||
type HandleRequestPruningPointUTXOSetAndBlockContext interface {
|
||||
Domain() domain.Domain
|
||||
}
|
||||
|
||||
type handleRequestPruningPointUTXOSetAndBlockFlow struct {
|
||||
HandleRequestPruningPointUTXOSetAndBlockContext
|
||||
incomingRoute, outgoingRoute *router.Route
|
||||
}
|
||||
|
||||
// HandleRequestPruningPointUTXOSetAndBlock listens to appmessage.MsgRequestPruningPointUTXOSetAndBlock messages and sends
|
||||
// the pruning point UTXO set and block body.
|
||||
func HandleRequestPruningPointUTXOSetAndBlock(context HandleRequestPruningPointUTXOSetAndBlockContext, incomingRoute,
|
||||
outgoingRoute *router.Route) error {
|
||||
flow := &handleRequestPruningPointUTXOSetAndBlockFlow{
|
||||
HandleRequestPruningPointUTXOSetAndBlockContext: context,
|
||||
incomingRoute: incomingRoute,
|
||||
outgoingRoute: outgoingRoute,
|
||||
}
|
||||
|
||||
return flow.start()
|
||||
}
|
||||
|
||||
func (flow *handleRequestPruningPointUTXOSetAndBlockFlow) start() error {
|
||||
for {
|
||||
msgRequestPruningPointUTXOSetAndBlock, err := flow.waitForRequestPruningPointUTXOSetAndBlockMessages()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = flow.handleRequestPruningPointUTXOSetAndBlockMessage(msgRequestPruningPointUTXOSetAndBlock)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (flow *handleRequestPruningPointUTXOSetAndBlockFlow) handleRequestPruningPointUTXOSetAndBlockMessage(
|
||||
msgRequestPruningPointUTXOSetAndBlock *appmessage.MsgRequestPruningPointUTXOSetAndBlock) error {
|
||||
|
||||
onEnd := logger.LogAndMeasureExecutionTime(log, "handleRequestPruningPointUTXOSetAndBlockFlow")
|
||||
defer onEnd()
|
||||
|
||||
log.Debugf("Got request for PruningPointHash UTXOSet and Block")
|
||||
|
||||
err := flow.sendPruningPointBlock(msgRequestPruningPointUTXOSetAndBlock)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return flow.sendPruningPointUTXOSet(msgRequestPruningPointUTXOSetAndBlock)
|
||||
}
|
||||
|
||||
func (flow *handleRequestPruningPointUTXOSetAndBlockFlow) waitForRequestPruningPointUTXOSetAndBlockMessages() (
|
||||
*appmessage.MsgRequestPruningPointUTXOSetAndBlock, error) {
|
||||
|
||||
message, err := flow.incomingRoute.Dequeue()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
msgRequestPruningPointUTXOSetAndBlock, ok := message.(*appmessage.MsgRequestPruningPointUTXOSetAndBlock)
|
||||
if !ok {
|
||||
return nil, protocolerrors.Errorf(true, "received unexpected message type. "+
|
||||
"expected: %s, got: %s", appmessage.CmdRequestPruningPointUTXOSetAndBlock, message.Command())
|
||||
}
|
||||
return msgRequestPruningPointUTXOSetAndBlock, nil
|
||||
}
|
||||
|
||||
func (flow *handleRequestPruningPointUTXOSetAndBlockFlow) sendPruningPointBlock(
|
||||
msgRequestPruningPointUTXOSetAndBlock *appmessage.MsgRequestPruningPointUTXOSetAndBlock) error {
|
||||
|
||||
block, err := flow.Domain().Consensus().GetBlock(msgRequestPruningPointUTXOSetAndBlock.PruningPointHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debugf("Retrieved pruning block %s", msgRequestPruningPointUTXOSetAndBlock.PruningPointHash)
|
||||
|
||||
return flow.outgoingRoute.Enqueue(appmessage.NewMsgIBDBlock(appmessage.DomainBlockToMsgBlock(block)))
|
||||
}
|
||||
|
||||
func (flow *handleRequestPruningPointUTXOSetAndBlockFlow) sendPruningPointUTXOSet(
|
||||
msgRequestPruningPointUTXOSetAndBlock *appmessage.MsgRequestPruningPointUTXOSetAndBlock) error {
|
||||
|
||||
// Send the UTXO set in `step`-sized chunks
|
||||
const step = 1000
|
||||
var fromOutpoint *externalapi.DomainOutpoint
|
||||
chunksSent := 0
|
||||
for {
|
||||
pruningPointUTXOs, err := flow.Domain().Consensus().GetPruningPointUTXOs(
|
||||
msgRequestPruningPointUTXOSetAndBlock.PruningPointHash, fromOutpoint, step)
|
||||
if err != nil {
|
||||
if errors.Is(err, ruleerrors.ErrWrongPruningPointHash) {
|
||||
return flow.outgoingRoute.Enqueue(appmessage.NewMsgUnexpectedPruningPoint())
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf("Retrieved %d UTXOs for pruning block %s",
|
||||
len(pruningPointUTXOs), msgRequestPruningPointUTXOSetAndBlock.PruningPointHash)
|
||||
|
||||
outpointAndUTXOEntryPairs :=
|
||||
appmessage.DomainOutpointAndUTXOEntryPairsToOutpointAndUTXOEntryPairs(pruningPointUTXOs)
|
||||
err = flow.outgoingRoute.Enqueue(appmessage.NewMsgPruningPointUTXOSetChunk(outpointAndUTXOEntryPairs))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(pruningPointUTXOs) < step {
|
||||
log.Debugf("Finished sending UTXOs for pruning block %s",
|
||||
msgRequestPruningPointUTXOSetAndBlock.PruningPointHash)
|
||||
|
||||
return flow.outgoingRoute.Enqueue(appmessage.NewMsgDonePruningPointUTXOSetChunks())
|
||||
}
|
||||
|
||||
fromOutpoint = pruningPointUTXOs[len(pruningPointUTXOs)-1].Outpoint
|
||||
chunksSent++
|
||||
|
||||
// Wait for the peer to request more chunks every `ibdBatchSize` chunks
|
||||
if chunksSent%ibdBatchSize == 0 {
|
||||
message, err := flow.incomingRoute.DequeueWithTimeout(common.DefaultTimeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, ok := message.(*appmessage.MsgRequestNextPruningPointUTXOSetChunk)
|
||||
if !ok {
|
||||
return protocolerrors.Errorf(true, "received unexpected message type. "+
|
||||
"expected: %s, got: %s", appmessage.CmdRequestNextPruningPointUTXOSetChunk, message.Command())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
package blockrelay
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/infrastructure/logger"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/kaspanet/kaspad/infrastructure/logger"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
@@ -278,9 +280,13 @@ func (flow *handleRelayInvsFlow) processHeader(msgBlockHeader *appmessage.MsgBlo
|
||||
if !errors.As(err, &ruleerrors.RuleError{}) {
|
||||
return errors.Wrapf(err, "failed to process header %s during IBD", blockHash)
|
||||
}
|
||||
log.Infof("Rejected block header %s from %s during IBD: %s", blockHash, flow.peer, err)
|
||||
|
||||
return protocolerrors.Wrapf(true, err, "got invalid block %s during IBD", blockHash)
|
||||
if errors.Is(err, ruleerrors.ErrDuplicateBlock) {
|
||||
log.Debugf("Skipping block header %s as it is a duplicate", blockHash)
|
||||
} else {
|
||||
log.Infof("Rejected block header %s from %s during IBD: %s", blockHash, flow.peer, err)
|
||||
return protocolerrors.Wrapf(true, err, "got invalid block header %s during IBD", blockHash)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -288,7 +294,7 @@ func (flow *handleRelayInvsFlow) processHeader(msgBlockHeader *appmessage.MsgBlo
|
||||
|
||||
func (flow *handleRelayInvsFlow) syncPruningPointUTXOSet() (bool, error) {
|
||||
log.Debugf("Checking if a new pruning point is available")
|
||||
err := flow.outgoingRoute.Enqueue(appmessage.NewMsgRequestIBDRootHashMessage())
|
||||
err := flow.outgoingRoute.Enqueue(appmessage.NewMsgRequestPruningPointHashMessage())
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@@ -296,36 +302,40 @@ func (flow *handleRelayInvsFlow) syncPruningPointUTXOSet() (bool, error) {
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
msgIBDRootHash, ok := message.(*appmessage.MsgIBDRootHashMessage)
|
||||
msgPruningPointHash, ok := message.(*appmessage.MsgPruningPointHashMessage)
|
||||
if !ok {
|
||||
return false, protocolerrors.Errorf(true, "received unexpected message type. "+
|
||||
"expected: %s, got: %s", appmessage.CmdIBDRootHash, message.Command())
|
||||
"expected: %s, got: %s", appmessage.CmdPruningPointHash, message.Command())
|
||||
}
|
||||
|
||||
blockInfo, err := flow.Domain().Consensus().GetBlockInfo(msgIBDRootHash.Hash)
|
||||
blockInfo, err := flow.Domain().Consensus().GetBlockInfo(msgPruningPointHash.Hash)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if !blockInfo.Exists {
|
||||
return false, errors.Errorf("The pruning point header is missing")
|
||||
}
|
||||
|
||||
if blockInfo.BlockStatus != externalapi.StatusHeaderOnly {
|
||||
log.Debugf("Already has the block data of the new suggested pruning point %s", msgIBDRootHash.Hash)
|
||||
log.Debugf("Already has the block data of the new suggested pruning point %s", msgPruningPointHash.Hash)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
log.Infof("Checking if the suggested pruning point %s is compatible to the node DAG", msgIBDRootHash.Hash)
|
||||
isValid, err := flow.Domain().Consensus().IsValidPruningPoint(msgIBDRootHash.Hash)
|
||||
log.Infof("Checking if the suggested pruning point %s is compatible to the node DAG", msgPruningPointHash.Hash)
|
||||
isValid, err := flow.Domain().Consensus().IsValidPruningPoint(msgPruningPointHash.Hash)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if !isValid {
|
||||
log.Infof("The suggested pruning point %s is incompatible to this node DAG, so stopping IBD with this"+
|
||||
" peer", msgIBDRootHash.Hash)
|
||||
" peer", msgPruningPointHash.Hash)
|
||||
return false, nil
|
||||
}
|
||||
|
||||
log.Info("Fetching the pruning point UTXO set")
|
||||
succeed, err := flow.fetchMissingUTXOSet(msgIBDRootHash.Hash)
|
||||
succeed, err := flow.fetchMissingUTXOSet(msgPruningPointHash.Hash)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@@ -339,94 +349,118 @@ func (flow *handleRelayInvsFlow) syncPruningPointUTXOSet() (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (flow *handleRelayInvsFlow) fetchMissingUTXOSet(ibdRootHash *externalapi.DomainHash) (succeed bool, err error) {
|
||||
err = flow.outgoingRoute.Enqueue(appmessage.NewMsgRequestIBDRootUTXOSetAndBlock(ibdRootHash))
|
||||
func (flow *handleRelayInvsFlow) fetchMissingUTXOSet(pruningPointHash *externalapi.DomainHash) (succeed bool, err error) {
|
||||
defer func() {
|
||||
err := flow.Domain().Consensus().ClearImportedPruningPointData()
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("failed to clear imported pruning point data: %s", err))
|
||||
}
|
||||
}()
|
||||
|
||||
err = flow.outgoingRoute.Enqueue(appmessage.NewMsgRequestPruningPointUTXOSetAndBlock(pruningPointHash))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
utxoSet, block, found, err := flow.receiveIBDRootUTXOSetAndBlock()
|
||||
block, err := flow.receivePruningPointBlock()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if !found {
|
||||
receivedAll, err := flow.receiveAndInsertPruningPointUTXOSet(pruningPointHash)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !receivedAll {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
err = flow.Domain().Consensus().ValidateAndInsertPruningPoint(block, utxoSet)
|
||||
err = flow.Domain().Consensus().ValidateAndInsertImportedPruningPoint(block)
|
||||
if err != nil {
|
||||
// TODO: Find a better way to deal with finality conflicts.
|
||||
if errors.Is(err, ruleerrors.ErrSuggestedPruningViolatesFinality) {
|
||||
return false, nil
|
||||
}
|
||||
return false, protocolerrors.ConvertToBanningProtocolErrorIfRuleError(err, "error with IBD root UTXO set")
|
||||
return false, protocolerrors.ConvertToBanningProtocolErrorIfRuleError(err, "error with pruning point UTXO set")
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (flow *handleRelayInvsFlow) receiveIBDRootUTXOSetAndBlock() ([]byte, *externalapi.DomainBlock, bool, error) {
|
||||
onEnd := logger.LogAndMeasureExecutionTime(log, "receiveIBDRootUTXOSetAndBlock")
|
||||
func (flow *handleRelayInvsFlow) receivePruningPointBlock() (*externalapi.DomainBlock, error) {
|
||||
onEnd := logger.LogAndMeasureExecutionTime(log, "receivePruningPointBlock")
|
||||
defer onEnd()
|
||||
|
||||
message, err := flow.dequeueIncomingMessageAndSkipInvs(common.DefaultTimeout)
|
||||
if err != nil {
|
||||
return nil, nil, false, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var block *externalapi.DomainBlock
|
||||
switch message := message.(type) {
|
||||
case *appmessage.MsgIBDBlock:
|
||||
block = appmessage.MsgBlockToDomainBlock(message.MsgBlock)
|
||||
case *appmessage.MsgIBDRootNotFound:
|
||||
return nil, nil, false, nil
|
||||
default:
|
||||
return nil, nil, false,
|
||||
protocolerrors.Errorf(true, "received unexpected message type. "+
|
||||
"expected: %s or %s, got: %s",
|
||||
appmessage.CmdIBDBlock, appmessage.CmdIBDRootNotFound, message.Command(),
|
||||
)
|
||||
ibdBlockMessage, ok := message.(*appmessage.MsgIBDBlock)
|
||||
if !ok {
|
||||
return nil, protocolerrors.Errorf(true, "received unexpected message type. "+
|
||||
"expected: %s, got: %s", appmessage.CmdIBDBlock, message.Command())
|
||||
}
|
||||
log.Debugf("Received IBD root block %s", consensushashing.BlockHash(block))
|
||||
block := appmessage.MsgBlockToDomainBlock(ibdBlockMessage.MsgBlock)
|
||||
|
||||
log.Debugf("Received pruning point block %s", consensushashing.BlockHash(block))
|
||||
|
||||
return block, nil
|
||||
}
|
||||
|
||||
func (flow *handleRelayInvsFlow) receiveAndInsertPruningPointUTXOSet(
|
||||
pruningPointHash *externalapi.DomainHash) (bool, error) {
|
||||
|
||||
onEnd := logger.LogAndMeasureExecutionTime(log, "receiveAndInsertPruningPointUTXOSet")
|
||||
defer onEnd()
|
||||
|
||||
serializedUTXOSet := []byte{}
|
||||
receivedAllChunks := false
|
||||
receivedChunkCount := 0
|
||||
for !receivedAllChunks {
|
||||
receivedUTXOCount := 0
|
||||
for {
|
||||
message, err := flow.dequeueIncomingMessageAndSkipInvs(common.DefaultTimeout)
|
||||
if err != nil {
|
||||
return nil, nil, false, err
|
||||
return false, err
|
||||
}
|
||||
|
||||
switch message := message.(type) {
|
||||
case *appmessage.MsgIBDRootUTXOSetChunk:
|
||||
serializedUTXOSet = append(serializedUTXOSet, message.Chunk...)
|
||||
case *appmessage.MsgDoneIBDRootUTXOSetChunks:
|
||||
receivedAllChunks = true
|
||||
default:
|
||||
return nil, nil, false,
|
||||
protocolerrors.Errorf(true, "received unexpected message type. "+
|
||||
"expected: %s or %s, got: %s",
|
||||
appmessage.CmdIBDRootUTXOSetChunk, appmessage.CmdDoneIBDRootUTXOSetChunks, message.Command(),
|
||||
)
|
||||
}
|
||||
case *appmessage.MsgPruningPointUTXOSetChunk:
|
||||
receivedUTXOCount += len(message.OutpointAndUTXOEntryPairs)
|
||||
domainOutpointAndUTXOEntryPairs :=
|
||||
appmessage.OutpointAndUTXOEntryPairsToDomainOutpointAndUTXOEntryPairs(message.OutpointAndUTXOEntryPairs)
|
||||
|
||||
receivedChunkCount++
|
||||
if !receivedAllChunks && receivedChunkCount%ibdBatchSize == 0 {
|
||||
log.Debugf("Received %d UTXO set chunks so far, totaling in %d bytes",
|
||||
receivedChunkCount, len(serializedUTXOSet))
|
||||
|
||||
requestNextIBDRootUTXOSetChunkMessage := appmessage.NewMsgRequestNextIBDRootUTXOSetChunk()
|
||||
err := flow.outgoingRoute.Enqueue(requestNextIBDRootUTXOSetChunkMessage)
|
||||
err := flow.Domain().Consensus().AppendImportedPruningPointUTXOs(domainOutpointAndUTXOEntryPairs)
|
||||
if err != nil {
|
||||
return nil, nil, false, err
|
||||
return false, err
|
||||
}
|
||||
|
||||
receivedChunkCount++
|
||||
if receivedChunkCount%ibdBatchSize == 0 {
|
||||
log.Debugf("Received %d UTXO set chunks so far, totaling in %d UTXOs",
|
||||
receivedChunkCount, receivedUTXOCount)
|
||||
|
||||
requestNextPruningPointUTXOSetChunkMessage := appmessage.NewMsgRequestNextPruningPointUTXOSetChunk()
|
||||
err := flow.outgoingRoute.Enqueue(requestNextPruningPointUTXOSetChunkMessage)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
case *appmessage.MsgDonePruningPointUTXOSetChunks:
|
||||
log.Debugf("Finished receiving the UTXO set. Total UTXOs: %d", receivedUTXOCount)
|
||||
return true, nil
|
||||
|
||||
case *appmessage.MsgUnexpectedPruningPoint:
|
||||
log.Debugf("Could not receive the next UTXO chunk because the pruning point %s "+
|
||||
"is no longer the pruning point of peer %s", pruningPointHash, flow.peer)
|
||||
return false, nil
|
||||
|
||||
default:
|
||||
return false, protocolerrors.Errorf(true, "received unexpected message type. "+
|
||||
"expected: %s or %s or %s, got: %s", appmessage.CmdPruningPointUTXOSetChunk,
|
||||
appmessage.CmdDonePruningPointUTXOSetChunks, appmessage.CmdUnexpectedPruningPoint, message.Command(),
|
||||
)
|
||||
}
|
||||
}
|
||||
log.Debugf("Finished receiving the UTXO set. Total bytes: %d", len(serializedUTXOSet))
|
||||
|
||||
return serializedUTXOSet, block, true, nil
|
||||
}
|
||||
|
||||
func (flow *handleRelayInvsFlow) syncMissingBlockBodies(highHash *externalapi.DomainHash) error {
|
||||
@@ -468,6 +502,10 @@ func (flow *handleRelayInvsFlow) syncMissingBlockBodies(highHash *externalapi.Do
|
||||
|
||||
blockInsertionResult, err := flow.Domain().Consensus().ValidateAndInsertBlock(block)
|
||||
if err != nil {
|
||||
if errors.Is(err, ruleerrors.ErrDuplicateBlock) {
|
||||
log.Debugf("Skipping IBD Block %s as it has already been added to the DAG", blockHash)
|
||||
continue
|
||||
}
|
||||
return protocolerrors.ConvertToBanningProtocolErrorIfRuleError(err, "invalid block %s", blockHash)
|
||||
}
|
||||
err = flow.OnNewBlock(block, blockInsertionResult)
|
||||
|
||||
@@ -82,7 +82,7 @@ func HandleHandshake(context HandleHandshakeContext, netConnection *netadapter.N
|
||||
|
||||
err := context.AddToPeers(peer)
|
||||
if err != nil {
|
||||
if errors.As(err, &common.ErrPeerWithSameIDExists) {
|
||||
if errors.Is(err, common.ErrPeerWithSameIDExists) {
|
||||
return nil, protocolerrors.Wrap(false, err, "peer already exists")
|
||||
}
|
||||
return nil, err
|
||||
|
||||
23
app/protocol/flows/testing/common_test.go
Normal file
23
app/protocol/flows/testing/common_test.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package testing
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/app/protocol/protocolerrors"
|
||||
"github.com/pkg/errors"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func checkFlowError(t *testing.T, err error, isProtocolError bool, shouldBan bool, contains string) {
|
||||
pErr := &protocolerrors.ProtocolError{}
|
||||
if errors.As(err, &pErr) != isProtocolError {
|
||||
t.Fatalf("Unexepcted error %+v", err)
|
||||
}
|
||||
|
||||
if pErr.ShouldBan != shouldBan {
|
||||
t.Fatalf("Exepcted shouldBan %t but got %t", shouldBan, pErr.ShouldBan)
|
||||
}
|
||||
|
||||
if !strings.Contains(err.Error(), contains) {
|
||||
t.Fatalf("Unexpected error. Expected error to contain '%s' but got: %+v", contains, err)
|
||||
}
|
||||
}
|
||||
986
app/protocol/flows/testing/handle_relay_invs_test.go
Normal file
986
app/protocol/flows/testing/handle_relay_invs_test.go
Normal file
@@ -0,0 +1,986 @@
|
||||
package testing
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/app/protocol/flows/blockrelay"
|
||||
peerpkg "github.com/kaspanet/kaspad/app/protocol/peer"
|
||||
"github.com/kaspanet/kaspad/domain"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/blockheader"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/testutils"
|
||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||
"github.com/kaspanet/kaspad/domain/miningmanager"
|
||||
"github.com/kaspanet/kaspad/infrastructure/config"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
||||
"github.com/kaspanet/kaspad/util/mstime"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var orphanBlock = &externalapi.DomainBlock{
|
||||
Header: blockheader.NewImmutableBlockHeader(
|
||||
constants.MaxBlockVersion,
|
||||
[]*externalapi.DomainHash{unknownBlockHash},
|
||||
&externalapi.DomainHash{},
|
||||
&externalapi.DomainHash{},
|
||||
&externalapi.DomainHash{},
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
),
|
||||
}
|
||||
|
||||
var validPruningPointBlock = &externalapi.DomainBlock{
|
||||
Header: blockheader.NewImmutableBlockHeader(
|
||||
constants.MaxBlockVersion,
|
||||
[]*externalapi.DomainHash{externalapi.NewDomainHashFromByteArray(&[externalapi.DomainHashSize]byte{1})},
|
||||
&externalapi.DomainHash{},
|
||||
&externalapi.DomainHash{},
|
||||
&externalapi.DomainHash{},
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
),
|
||||
}
|
||||
|
||||
var invalidPruningPointBlock = &externalapi.DomainBlock{
|
||||
Header: blockheader.NewImmutableBlockHeader(
|
||||
constants.MaxBlockVersion,
|
||||
[]*externalapi.DomainHash{externalapi.NewDomainHashFromByteArray(&[externalapi.DomainHashSize]byte{2})},
|
||||
&externalapi.DomainHash{},
|
||||
&externalapi.DomainHash{},
|
||||
&externalapi.DomainHash{},
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
),
|
||||
}
|
||||
|
||||
var unexpectedIBDBlock = &externalapi.DomainBlock{
|
||||
Header: blockheader.NewImmutableBlockHeader(
|
||||
constants.MaxBlockVersion,
|
||||
[]*externalapi.DomainHash{externalapi.NewDomainHashFromByteArray(&[externalapi.DomainHashSize]byte{3})},
|
||||
&externalapi.DomainHash{},
|
||||
&externalapi.DomainHash{},
|
||||
&externalapi.DomainHash{},
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
),
|
||||
}
|
||||
|
||||
var invalidBlock = &externalapi.DomainBlock{
|
||||
Header: blockheader.NewImmutableBlockHeader(
|
||||
constants.MaxBlockVersion,
|
||||
[]*externalapi.DomainHash{externalapi.NewDomainHashFromByteArray(&[externalapi.DomainHashSize]byte{4})},
|
||||
&externalapi.DomainHash{},
|
||||
&externalapi.DomainHash{},
|
||||
&externalapi.DomainHash{},
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
),
|
||||
}
|
||||
|
||||
var unknownBlockHash = externalapi.NewDomainHashFromByteArray(&[externalapi.DomainHashSize]byte{1})
|
||||
var knownInvalidBlockHash = externalapi.NewDomainHashFromByteArray(&[externalapi.DomainHashSize]byte{2})
|
||||
var validPruningPointHash = consensushashing.BlockHash(validPruningPointBlock)
|
||||
var invalidBlockHash = consensushashing.BlockHash(invalidBlock)
|
||||
var invalidPruningPointHash = consensushashing.BlockHash(invalidPruningPointBlock)
|
||||
var orphanBlockHash = consensushashing.BlockHash(orphanBlock)
|
||||
|
||||
var fakeRelayInvsContextMap = map[externalapi.DomainHash]*externalapi.BlockInfo{
|
||||
*knownInvalidBlockHash: {
|
||||
Exists: true,
|
||||
BlockStatus: externalapi.StatusInvalid,
|
||||
},
|
||||
*validPruningPointHash: {
|
||||
Exists: true,
|
||||
BlockStatus: externalapi.StatusHeaderOnly,
|
||||
},
|
||||
*invalidPruningPointHash: {
|
||||
Exists: true,
|
||||
BlockStatus: externalapi.StatusHeaderOnly,
|
||||
},
|
||||
}
|
||||
|
||||
type fakeRelayInvsContext struct {
|
||||
params *dagconfig.Params
|
||||
askedOrphanBlockInfo bool
|
||||
}
|
||||
|
||||
func (f *fakeRelayInvsContext) BuildBlock(coinbaseData *externalapi.DomainCoinbaseData, transactions []*externalapi.DomainTransaction) (*externalapi.DomainBlock, error) {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
func (f *fakeRelayInvsContext) ValidateAndInsertBlock(block *externalapi.DomainBlock) (*externalapi.BlockInsertionResult, error) {
|
||||
hash := consensushashing.BlockHash(block)
|
||||
if hash.Equal(orphanBlockHash) {
|
||||
return nil, ruleerrors.NewErrMissingParents(orphanBlock.Header.ParentHashes())
|
||||
}
|
||||
|
||||
if hash.Equal(invalidBlockHash) {
|
||||
return nil, ruleerrors.ErrBadMerkleRoot
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (f *fakeRelayInvsContext) ValidateTransactionAndPopulateWithConsensusData(transaction *externalapi.DomainTransaction) error {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
func (f *fakeRelayInvsContext) GetBlock(blockHash *externalapi.DomainHash) (*externalapi.DomainBlock, error) {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
func (f *fakeRelayInvsContext) GetBlockHeader(blockHash *externalapi.DomainHash) (externalapi.BlockHeader, error) {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
func (f *fakeRelayInvsContext) GetBlockInfo(blockHash *externalapi.DomainHash) (*externalapi.BlockInfo, error) {
|
||||
if info, ok := fakeRelayInvsContextMap[*blockHash]; ok {
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// Second time we ask for orphan block it's in the end of IBD, to
|
||||
// check if the IBD has finished.
|
||||
// Since we don't actually process headers, we just say the orphan
|
||||
// exists in the second time we're asked about it to indicate IBD
|
||||
// has finished.
|
||||
if blockHash.Equal(orphanBlockHash) {
|
||||
if f.askedOrphanBlockInfo {
|
||||
return &externalapi.BlockInfo{Exists: true}, nil
|
||||
}
|
||||
f.askedOrphanBlockInfo = true
|
||||
}
|
||||
|
||||
return &externalapi.BlockInfo{
|
||||
Exists: false,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (f *fakeRelayInvsContext) GetBlockAcceptanceData(blockHash *externalapi.DomainHash) (externalapi.AcceptanceData, error) {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
func (f *fakeRelayInvsContext) GetHashesBetween(lowHash, highHash *externalapi.DomainHash, maxBlueScoreDifference uint64) ([]*externalapi.DomainHash, error) {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
func (f *fakeRelayInvsContext) GetMissingBlockBodyHashes(highHash *externalapi.DomainHash) ([]*externalapi.DomainHash, error) {
|
||||
// This is done so we can test getting invalid block during IBD.
|
||||
return []*externalapi.DomainHash{invalidBlockHash}, nil
|
||||
}
|
||||
|
||||
func (f *fakeRelayInvsContext) GetPruningPointUTXOs(expectedPruningPointHash *externalapi.DomainHash, fromOutpoint *externalapi.DomainOutpoint, limit int) ([]*externalapi.OutpointAndUTXOEntryPair, error) {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
func (f *fakeRelayInvsContext) PruningPoint() (*externalapi.DomainHash, error) {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
func (f *fakeRelayInvsContext) ClearImportedPruningPointData() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *fakeRelayInvsContext) AppendImportedPruningPointUTXOs(outpointAndUTXOEntryPairs []*externalapi.OutpointAndUTXOEntryPair) error {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
func (f *fakeRelayInvsContext) ValidateAndInsertImportedPruningPoint(newPruningPoint *externalapi.DomainBlock) error {
|
||||
if consensushashing.BlockHash(newPruningPoint).Equal(invalidPruningPointHash) {
|
||||
return ruleerrors.ErrBadMerkleRoot
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *fakeRelayInvsContext) GetVirtualSelectedParent() (*externalapi.DomainHash, error) {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
func (f *fakeRelayInvsContext) CreateBlockLocator(lowHash, highHash *externalapi.DomainHash, limit uint32) (externalapi.BlockLocator, error) {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
func (f *fakeRelayInvsContext) CreateHeadersSelectedChainBlockLocator(lowHash, highHash *externalapi.DomainHash) (externalapi.BlockLocator, error) {
|
||||
return externalapi.BlockLocator{
|
||||
f.params.GenesisHash,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (f *fakeRelayInvsContext) CreateFullHeadersSelectedChainBlockLocator() (externalapi.BlockLocator, error) {
|
||||
return externalapi.BlockLocator{
|
||||
f.params.GenesisHash,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (f *fakeRelayInvsContext) GetSyncInfo() (*externalapi.SyncInfo, error) {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
func (f *fakeRelayInvsContext) Tips() ([]*externalapi.DomainHash, error) {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
func (f *fakeRelayInvsContext) GetVirtualInfo() (*externalapi.VirtualInfo, error) {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
func (f *fakeRelayInvsContext) IsValidPruningPoint(blockHash *externalapi.DomainHash) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (f *fakeRelayInvsContext) GetVirtualSelectedParentChainFromBlock(blockHash *externalapi.DomainHash) (*externalapi.SelectedChainPath, error) {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
func (f *fakeRelayInvsContext) IsInSelectedParentChainOf(blockHashA *externalapi.DomainHash, blockHashB *externalapi.DomainHash) (bool, error) {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
func (f *fakeRelayInvsContext) GetHeadersSelectedTip() (*externalapi.DomainHash, error) {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
func (f *fakeRelayInvsContext) MiningManager() miningmanager.MiningManager {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
func (f *fakeRelayInvsContext) Consensus() externalapi.Consensus {
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *fakeRelayInvsContext) Domain() domain.Domain {
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *fakeRelayInvsContext) Config() *config.Config {
|
||||
return &config.Config{
|
||||
Flags: &config.Flags{
|
||||
NetworkFlags: config.NetworkFlags{
|
||||
ActiveNetParams: f.params,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (f *fakeRelayInvsContext) OnNewBlock(block *externalapi.DomainBlock, blockInsertionResult *externalapi.BlockInsertionResult) error {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
func (f *fakeRelayInvsContext) SharedRequestedBlocks() *blockrelay.SharedRequestedBlocks {
|
||||
return blockrelay.NewSharedRequestedBlocks()
|
||||
}
|
||||
|
||||
func (f *fakeRelayInvsContext) Broadcast(message appmessage.Message) error {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
func (f *fakeRelayInvsContext) AddOrphan(orphanBlock *externalapi.DomainBlock) {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
func (f *fakeRelayInvsContext) GetOrphanRoots(orphanHash *externalapi.DomainHash) ([]*externalapi.DomainHash, bool, error) {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
func (f *fakeRelayInvsContext) IsOrphan(blockHash *externalapi.DomainHash) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (f *fakeRelayInvsContext) IsIBDRunning() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (f *fakeRelayInvsContext) TrySetIBDRunning(ibdPeer *peerpkg.Peer) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (f *fakeRelayInvsContext) UnsetIBDRunning() {
|
||||
}
|
||||
|
||||
func TestHandleRelayInvsErrors(t *testing.T) {
|
||||
triggerIBD := func(incomingRoute, outgoingRoute *router.Route) {
|
||||
err := incomingRoute.Enqueue(appmessage.NewMsgInvBlock(consensushashing.BlockHash(orphanBlock)))
|
||||
if err != nil {
|
||||
t.Fatalf("Enqueue: %+v", err)
|
||||
}
|
||||
|
||||
msg, err := outgoingRoute.DequeueWithTimeout(time.Second)
|
||||
if err != nil {
|
||||
t.Fatalf("DequeueWithTimeout: %+v", err)
|
||||
}
|
||||
_ = msg.(*appmessage.MsgRequestRelayBlocks)
|
||||
|
||||
err = incomingRoute.Enqueue(appmessage.DomainBlockToMsgBlock(orphanBlock))
|
||||
if err != nil {
|
||||
t.Fatalf("Enqueue: %+v", err)
|
||||
}
|
||||
|
||||
msg, err = outgoingRoute.DequeueWithTimeout(time.Second)
|
||||
if err != nil {
|
||||
t.Fatalf("DequeueWithTimeout: %+v", err)
|
||||
}
|
||||
_ = msg.(*appmessage.MsgRequestBlockLocator)
|
||||
|
||||
err = incomingRoute.Enqueue(appmessage.NewMsgBlockLocator(nil))
|
||||
if err != nil {
|
||||
t.Fatalf("Enqueue: %+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
funcToExecute func(incomingRoute, outgoingRoute *router.Route)
|
||||
expectsProtocolError bool
|
||||
expectsBan bool
|
||||
expectsErrToContain string
|
||||
}{
|
||||
{
|
||||
name: "sending unexpected message type",
|
||||
funcToExecute: func(incomingRoute, outgoingRoute *router.Route) {
|
||||
err := incomingRoute.Enqueue(appmessage.NewMsgBlockLocator(nil))
|
||||
if err != nil {
|
||||
t.Fatalf("Enqueue: %+v", err)
|
||||
}
|
||||
},
|
||||
expectsProtocolError: true,
|
||||
expectsBan: true,
|
||||
expectsErrToContain: "message in the block relay handleRelayInvsFlow while expecting an inv message",
|
||||
},
|
||||
{
|
||||
name: "sending a known invalid inv",
|
||||
funcToExecute: func(incomingRoute, outgoingRoute *router.Route) {
|
||||
err := incomingRoute.Enqueue(appmessage.NewMsgInvBlock(knownInvalidBlockHash))
|
||||
if err != nil {
|
||||
t.Fatalf("Enqueue: %+v", err)
|
||||
}
|
||||
},
|
||||
expectsProtocolError: true,
|
||||
expectsBan: true,
|
||||
expectsErrToContain: "sent inv of an invalid block",
|
||||
},
|
||||
{
|
||||
name: "sending unrequested block",
|
||||
funcToExecute: func(incomingRoute, outgoingRoute *router.Route) {
|
||||
err := incomingRoute.Enqueue(appmessage.NewMsgInvBlock(unknownBlockHash))
|
||||
if err != nil {
|
||||
t.Fatalf("Enqueue: %+v", err)
|
||||
}
|
||||
|
||||
msg, err := outgoingRoute.DequeueWithTimeout(time.Second)
|
||||
if err != nil {
|
||||
t.Fatalf("DequeueWithTimeout: %+v", err)
|
||||
}
|
||||
_ = msg.(*appmessage.MsgRequestRelayBlocks)
|
||||
|
||||
err = incomingRoute.Enqueue(appmessage.NewMsgBlock(&appmessage.MsgBlockHeader{
|
||||
Version: 0,
|
||||
ParentHashes: nil,
|
||||
HashMerkleRoot: &externalapi.DomainHash{},
|
||||
AcceptedIDMerkleRoot: &externalapi.DomainHash{},
|
||||
UTXOCommitment: &externalapi.DomainHash{},
|
||||
Timestamp: mstime.Time{},
|
||||
Bits: 0,
|
||||
Nonce: 0,
|
||||
}))
|
||||
if err != nil {
|
||||
t.Fatalf("Enqueue: %+v", err)
|
||||
}
|
||||
},
|
||||
expectsProtocolError: true,
|
||||
expectsBan: true,
|
||||
expectsErrToContain: "got unrequested block",
|
||||
},
|
||||
{
|
||||
name: "sending invalid block",
|
||||
funcToExecute: func(incomingRoute, outgoingRoute *router.Route) {
|
||||
err := incomingRoute.Enqueue(appmessage.NewMsgInvBlock(invalidBlockHash))
|
||||
if err != nil {
|
||||
t.Fatalf("Enqueue: %+v", err)
|
||||
}
|
||||
|
||||
msg, err := outgoingRoute.DequeueWithTimeout(time.Second)
|
||||
if err != nil {
|
||||
t.Fatalf("DequeueWithTimeout: %+v", err)
|
||||
}
|
||||
_ = msg.(*appmessage.MsgRequestRelayBlocks)
|
||||
|
||||
err = incomingRoute.Enqueue(appmessage.DomainBlockToMsgBlock(invalidBlock))
|
||||
if err != nil {
|
||||
t.Fatalf("Enqueue: %+v", err)
|
||||
}
|
||||
},
|
||||
expectsProtocolError: true,
|
||||
expectsBan: true,
|
||||
expectsErrToContain: "got invalid block",
|
||||
},
|
||||
{
|
||||
name: "sending unexpected message instead of block locator",
|
||||
funcToExecute: func(incomingRoute, outgoingRoute *router.Route) {
|
||||
err := incomingRoute.Enqueue(appmessage.NewMsgInvBlock(consensushashing.BlockHash(orphanBlock)))
|
||||
if err != nil {
|
||||
t.Fatalf("Enqueue: %+v", err)
|
||||
}
|
||||
|
||||
msg, err := outgoingRoute.DequeueWithTimeout(time.Second)
|
||||
if err != nil {
|
||||
t.Fatalf("DequeueWithTimeout: %+v", err)
|
||||
}
|
||||
_ = msg.(*appmessage.MsgRequestRelayBlocks)
|
||||
|
||||
err = incomingRoute.Enqueue(appmessage.DomainBlockToMsgBlock(orphanBlock))
|
||||
if err != nil {
|
||||
t.Fatalf("Enqueue: %+v", err)
|
||||
}
|
||||
|
||||
msg, err = outgoingRoute.DequeueWithTimeout(time.Second)
|
||||
if err != nil {
|
||||
t.Fatalf("DequeueWithTimeout: %+v", err)
|
||||
}
|
||||
_ = msg.(*appmessage.MsgRequestBlockLocator)
|
||||
|
||||
// Sending a block while expected a block locator
|
||||
err = incomingRoute.Enqueue(appmessage.DomainBlockToMsgBlock(orphanBlock))
|
||||
if err != nil {
|
||||
t.Fatalf("Enqueue: %+v", err)
|
||||
}
|
||||
},
|
||||
expectsProtocolError: true,
|
||||
expectsBan: true,
|
||||
expectsErrToContain: fmt.Sprintf("received unexpected message type. expected: %s",
|
||||
appmessage.CmdBlockLocator),
|
||||
},
|
||||
{
|
||||
name: "sending unknown highest hash",
|
||||
funcToExecute: func(incomingRoute, outgoingRoute *router.Route) {
|
||||
triggerIBD(incomingRoute, outgoingRoute)
|
||||
|
||||
msg, err := outgoingRoute.DequeueWithTimeout(time.Second)
|
||||
if err != nil {
|
||||
t.Fatalf("DequeueWithTimeout: %+v", err)
|
||||
}
|
||||
_ = msg.(*appmessage.MsgIBDBlockLocator)
|
||||
|
||||
err = incomingRoute.Enqueue(appmessage.NewMsgIBDBlockLocatorHighestHash(unknownBlockHash))
|
||||
if err != nil {
|
||||
t.Fatalf("Enqueue: %+v", err)
|
||||
}
|
||||
},
|
||||
expectsProtocolError: true,
|
||||
expectsBan: true,
|
||||
expectsErrToContain: "is not in the original blockLocator",
|
||||
},
|
||||
{
|
||||
name: "sending unexpected type instead of highest hash",
|
||||
funcToExecute: func(incomingRoute, outgoingRoute *router.Route) {
|
||||
triggerIBD(incomingRoute, outgoingRoute)
|
||||
|
||||
msg, err := outgoingRoute.DequeueWithTimeout(time.Second)
|
||||
if err != nil {
|
||||
t.Fatalf("DequeueWithTimeout: %+v", err)
|
||||
}
|
||||
_ = msg.(*appmessage.MsgIBDBlockLocator)
|
||||
|
||||
err = incomingRoute.Enqueue(appmessage.NewMsgBlockLocator(nil))
|
||||
if err != nil {
|
||||
t.Fatalf("Enqueue: %+v", err)
|
||||
}
|
||||
},
|
||||
expectsProtocolError: true,
|
||||
expectsBan: true,
|
||||
expectsErrToContain: fmt.Sprintf("received unexpected message type. expected: %s",
|
||||
appmessage.CmdIBDBlockLocatorHighestHash),
|
||||
},
|
||||
{
|
||||
name: "sending unexpected type instead of a header",
|
||||
funcToExecute: func(incomingRoute, outgoingRoute *router.Route) {
|
||||
triggerIBD(incomingRoute, outgoingRoute)
|
||||
|
||||
msg, err := outgoingRoute.DequeueWithTimeout(time.Second)
|
||||
if err != nil {
|
||||
t.Fatalf("DequeueWithTimeout: %+v", err)
|
||||
}
|
||||
|
||||
highestHash := msg.(*appmessage.MsgIBDBlockLocator).BlockLocatorHashes[0]
|
||||
err = incomingRoute.Enqueue(appmessage.NewMsgIBDBlockLocatorHighestHash(highestHash))
|
||||
if err != nil {
|
||||
t.Fatalf("Enqueue: %+v", err)
|
||||
}
|
||||
|
||||
msg, err = outgoingRoute.DequeueWithTimeout(time.Second)
|
||||
if err != nil {
|
||||
t.Fatalf("DequeueWithTimeout: %+v", err)
|
||||
}
|
||||
_ = msg.(*appmessage.MsgRequestHeaders)
|
||||
|
||||
// Sending unrequested block locator
|
||||
err = incomingRoute.Enqueue(appmessage.NewMsgBlockLocator(nil))
|
||||
if err != nil {
|
||||
t.Fatalf("Enqueue: %+v", err)
|
||||
}
|
||||
},
|
||||
expectsProtocolError: true,
|
||||
expectsBan: true,
|
||||
expectsErrToContain: fmt.Sprintf("received unexpected message type. expected: %s or %s",
|
||||
appmessage.CmdHeader, appmessage.CmdDoneHeaders),
|
||||
},
|
||||
{
|
||||
name: "sending unexpected type instead of pruning point hash",
|
||||
funcToExecute: func(incomingRoute, outgoingRoute *router.Route) {
|
||||
triggerIBD(incomingRoute, outgoingRoute)
|
||||
|
||||
msg, err := outgoingRoute.DequeueWithTimeout(time.Second)
|
||||
if err != nil {
|
||||
t.Fatalf("DequeueWithTimeout: %+v", err)
|
||||
}
|
||||
|
||||
highestHash := msg.(*appmessage.MsgIBDBlockLocator).BlockLocatorHashes[0]
|
||||
err = incomingRoute.Enqueue(appmessage.NewMsgIBDBlockLocatorHighestHash(highestHash))
|
||||
if err != nil {
|
||||
t.Fatalf("Enqueue: %+v", err)
|
||||
}
|
||||
|
||||
msg, err = outgoingRoute.DequeueWithTimeout(time.Second)
|
||||
if err != nil {
|
||||
t.Fatalf("DequeueWithTimeout: %+v", err)
|
||||
}
|
||||
_ = msg.(*appmessage.MsgRequestHeaders)
|
||||
|
||||
err = incomingRoute.Enqueue(appmessage.NewMsgDoneHeaders())
|
||||
if err != nil {
|
||||
t.Fatalf("Enqueue: %+v", err)
|
||||
}
|
||||
|
||||
msg, err = outgoingRoute.DequeueWithTimeout(time.Second)
|
||||
if err != nil {
|
||||
t.Fatalf("DequeueWithTimeout: %+v", err)
|
||||
}
|
||||
_ = msg.(*appmessage.MsgRequestPruningPointHashMessage)
|
||||
|
||||
// Sending unrequested block locator
|
||||
err = incomingRoute.Enqueue(appmessage.NewMsgBlockLocator(nil))
|
||||
if err != nil {
|
||||
t.Fatalf("Enqueue: %+v", err)
|
||||
}
|
||||
},
|
||||
expectsProtocolError: true,
|
||||
expectsBan: true,
|
||||
expectsErrToContain: fmt.Sprintf("received unexpected message type. expected: %s",
|
||||
appmessage.CmdPruningPointHash),
|
||||
},
|
||||
{
|
||||
name: "sending unexpected type instead of pruning point block",
|
||||
funcToExecute: func(incomingRoute, outgoingRoute *router.Route) {
|
||||
triggerIBD(incomingRoute, outgoingRoute)
|
||||
|
||||
msg, err := outgoingRoute.DequeueWithTimeout(time.Second)
|
||||
if err != nil {
|
||||
t.Fatalf("DequeueWithTimeout: %+v", err)
|
||||
}
|
||||
|
||||
highestHash := msg.(*appmessage.MsgIBDBlockLocator).BlockLocatorHashes[0]
|
||||
err = incomingRoute.Enqueue(appmessage.NewMsgIBDBlockLocatorHighestHash(highestHash))
|
||||
if err != nil {
|
||||
t.Fatalf("Enqueue: %+v", err)
|
||||
}
|
||||
|
||||
msg, err = outgoingRoute.DequeueWithTimeout(time.Second)
|
||||
if err != nil {
|
||||
t.Fatalf("DequeueWithTimeout: %+v", err)
|
||||
}
|
||||
_ = msg.(*appmessage.MsgRequestHeaders)
|
||||
|
||||
err = incomingRoute.Enqueue(appmessage.NewMsgDoneHeaders())
|
||||
if err != nil {
|
||||
t.Fatalf("Enqueue: %+v", err)
|
||||
}
|
||||
|
||||
msg, err = outgoingRoute.DequeueWithTimeout(time.Second)
|
||||
if err != nil {
|
||||
t.Fatalf("DequeueWithTimeout: %+v", err)
|
||||
}
|
||||
_ = msg.(*appmessage.MsgRequestPruningPointHashMessage)
|
||||
|
||||
err = incomingRoute.Enqueue(appmessage.NewPruningPointHashMessage(validPruningPointHash))
|
||||
if err != nil {
|
||||
t.Fatalf("Enqueue: %+v", err)
|
||||
}
|
||||
|
||||
msg, err = outgoingRoute.DequeueWithTimeout(time.Second)
|
||||
if err != nil {
|
||||
t.Fatalf("DequeueWithTimeout: %+v", err)
|
||||
}
|
||||
_ = msg.(*appmessage.MsgRequestPruningPointUTXOSetAndBlock)
|
||||
|
||||
// Sending unrequested block locator
|
||||
err = incomingRoute.Enqueue(appmessage.NewMsgBlockLocator(nil))
|
||||
if err != nil {
|
||||
t.Fatalf("Enqueue: %+v", err)
|
||||
}
|
||||
},
|
||||
expectsProtocolError: true,
|
||||
expectsBan: true,
|
||||
expectsErrToContain: fmt.Sprintf("received unexpected message type. expected: %s",
|
||||
appmessage.CmdIBDBlock),
|
||||
},
|
||||
{
|
||||
name: "sending unexpected type instead of UTXO chunk",
|
||||
funcToExecute: func(incomingRoute, outgoingRoute *router.Route) {
|
||||
triggerIBD(incomingRoute, outgoingRoute)
|
||||
|
||||
msg, err := outgoingRoute.DequeueWithTimeout(time.Second)
|
||||
if err != nil {
|
||||
t.Fatalf("DequeueWithTimeout: %+v", err)
|
||||
}
|
||||
|
||||
highestHash := msg.(*appmessage.MsgIBDBlockLocator).BlockLocatorHashes[0]
|
||||
err = incomingRoute.Enqueue(appmessage.NewMsgIBDBlockLocatorHighestHash(highestHash))
|
||||
if err != nil {
|
||||
t.Fatalf("Enqueue: %+v", err)
|
||||
}
|
||||
|
||||
msg, err = outgoingRoute.DequeueWithTimeout(time.Second)
|
||||
if err != nil {
|
||||
t.Fatalf("DequeueWithTimeout: %+v", err)
|
||||
}
|
||||
_ = msg.(*appmessage.MsgRequestHeaders)
|
||||
|
||||
err = incomingRoute.Enqueue(appmessage.NewMsgDoneHeaders())
|
||||
if err != nil {
|
||||
t.Fatalf("Enqueue: %+v", err)
|
||||
}
|
||||
|
||||
msg, err = outgoingRoute.DequeueWithTimeout(time.Second)
|
||||
if err != nil {
|
||||
t.Fatalf("DequeueWithTimeout: %+v", err)
|
||||
}
|
||||
_ = msg.(*appmessage.MsgRequestPruningPointHashMessage)
|
||||
|
||||
err = incomingRoute.Enqueue(appmessage.NewPruningPointHashMessage(validPruningPointHash))
|
||||
if err != nil {
|
||||
t.Fatalf("Enqueue: %+v", err)
|
||||
}
|
||||
|
||||
msg, err = outgoingRoute.DequeueWithTimeout(time.Second)
|
||||
if err != nil {
|
||||
t.Fatalf("DequeueWithTimeout: %+v", err)
|
||||
}
|
||||
_ = msg.(*appmessage.MsgRequestPruningPointUTXOSetAndBlock)
|
||||
|
||||
err = incomingRoute.Enqueue(appmessage.NewMsgIBDBlock(appmessage.DomainBlockToMsgBlock(validPruningPointBlock)))
|
||||
if err != nil {
|
||||
t.Fatalf("Enqueue: %+v", err)
|
||||
}
|
||||
|
||||
// Sending unrequested block locator
|
||||
err = incomingRoute.Enqueue(appmessage.NewMsgBlockLocator(nil))
|
||||
if err != nil {
|
||||
t.Fatalf("Enqueue: %+v", err)
|
||||
}
|
||||
},
|
||||
expectsProtocolError: true,
|
||||
expectsBan: true,
|
||||
expectsErrToContain: fmt.Sprintf("received unexpected message type. "+
|
||||
"expected: %s or %s or %s", appmessage.CmdPruningPointUTXOSetChunk,
|
||||
appmessage.CmdDonePruningPointUTXOSetChunks, appmessage.CmdUnexpectedPruningPoint),
|
||||
},
|
||||
{
|
||||
name: "sending invalid pruning point",
|
||||
funcToExecute: func(incomingRoute, outgoingRoute *router.Route) {
|
||||
triggerIBD(incomingRoute, outgoingRoute)
|
||||
|
||||
msg, err := outgoingRoute.DequeueWithTimeout(time.Second)
|
||||
if err != nil {
|
||||
t.Fatalf("DequeueWithTimeout: %+v", err)
|
||||
}
|
||||
|
||||
highestHash := msg.(*appmessage.MsgIBDBlockLocator).BlockLocatorHashes[0]
|
||||
err = incomingRoute.Enqueue(appmessage.NewMsgIBDBlockLocatorHighestHash(highestHash))
|
||||
if err != nil {
|
||||
t.Fatalf("Enqueue: %+v", err)
|
||||
}
|
||||
|
||||
msg, err = outgoingRoute.DequeueWithTimeout(time.Second)
|
||||
if err != nil {
|
||||
t.Fatalf("DequeueWithTimeout: %+v", err)
|
||||
}
|
||||
_ = msg.(*appmessage.MsgRequestHeaders)
|
||||
|
||||
err = incomingRoute.Enqueue(appmessage.NewMsgDoneHeaders())
|
||||
if err != nil {
|
||||
t.Fatalf("Enqueue: %+v", err)
|
||||
}
|
||||
|
||||
msg, err = outgoingRoute.DequeueWithTimeout(time.Second)
|
||||
if err != nil {
|
||||
t.Fatalf("DequeueWithTimeout: %+v", err)
|
||||
}
|
||||
_ = msg.(*appmessage.MsgRequestPruningPointHashMessage)
|
||||
|
||||
err = incomingRoute.Enqueue(appmessage.NewPruningPointHashMessage(invalidPruningPointHash))
|
||||
if err != nil {
|
||||
t.Fatalf("Enqueue: %+v", err)
|
||||
}
|
||||
|
||||
msg, err = outgoingRoute.DequeueWithTimeout(time.Second)
|
||||
if err != nil {
|
||||
t.Fatalf("DequeueWithTimeout: %+v", err)
|
||||
}
|
||||
_ = msg.(*appmessage.MsgRequestPruningPointUTXOSetAndBlock)
|
||||
|
||||
err = incomingRoute.Enqueue(appmessage.NewMsgIBDBlock(appmessage.DomainBlockToMsgBlock(invalidPruningPointBlock)))
|
||||
if err != nil {
|
||||
t.Fatalf("Enqueue: %+v", err)
|
||||
}
|
||||
|
||||
err = incomingRoute.Enqueue(appmessage.NewMsgDonePruningPointUTXOSetChunks())
|
||||
if err != nil {
|
||||
t.Fatalf("Enqueue: %+v", err)
|
||||
}
|
||||
},
|
||||
expectsProtocolError: true,
|
||||
expectsBan: true,
|
||||
expectsErrToContain: "error with pruning point UTXO set",
|
||||
},
|
||||
{
|
||||
name: "sending unexpected type instead of IBD block",
|
||||
funcToExecute: func(incomingRoute, outgoingRoute *router.Route) {
|
||||
triggerIBD(incomingRoute, outgoingRoute)
|
||||
|
||||
msg, err := outgoingRoute.DequeueWithTimeout(time.Second)
|
||||
if err != nil {
|
||||
t.Fatalf("DequeueWithTimeout: %+v", err)
|
||||
}
|
||||
|
||||
highestHash := msg.(*appmessage.MsgIBDBlockLocator).BlockLocatorHashes[0]
|
||||
err = incomingRoute.Enqueue(appmessage.NewMsgIBDBlockLocatorHighestHash(highestHash))
|
||||
if err != nil {
|
||||
t.Fatalf("Enqueue: %+v", err)
|
||||
}
|
||||
|
||||
msg, err = outgoingRoute.DequeueWithTimeout(time.Second)
|
||||
if err != nil {
|
||||
t.Fatalf("DequeueWithTimeout: %+v", err)
|
||||
}
|
||||
_ = msg.(*appmessage.MsgRequestHeaders)
|
||||
|
||||
err = incomingRoute.Enqueue(appmessage.NewMsgDoneHeaders())
|
||||
if err != nil {
|
||||
t.Fatalf("Enqueue: %+v", err)
|
||||
}
|
||||
|
||||
msg, err = outgoingRoute.DequeueWithTimeout(time.Second)
|
||||
if err != nil {
|
||||
t.Fatalf("DequeueWithTimeout: %+v", err)
|
||||
}
|
||||
_ = msg.(*appmessage.MsgRequestPruningPointHashMessage)
|
||||
|
||||
err = incomingRoute.Enqueue(appmessage.NewPruningPointHashMessage(validPruningPointHash))
|
||||
if err != nil {
|
||||
t.Fatalf("Enqueue: %+v", err)
|
||||
}
|
||||
|
||||
msg, err = outgoingRoute.DequeueWithTimeout(time.Second)
|
||||
if err != nil {
|
||||
t.Fatalf("DequeueWithTimeout: %+v", err)
|
||||
}
|
||||
_ = msg.(*appmessage.MsgRequestPruningPointUTXOSetAndBlock)
|
||||
|
||||
err = incomingRoute.Enqueue(appmessage.NewMsgIBDBlock(appmessage.DomainBlockToMsgBlock(validPruningPointBlock)))
|
||||
if err != nil {
|
||||
t.Fatalf("Enqueue: %+v", err)
|
||||
}
|
||||
|
||||
err = incomingRoute.Enqueue(appmessage.NewMsgDonePruningPointUTXOSetChunks())
|
||||
if err != nil {
|
||||
t.Fatalf("Enqueue: %+v", err)
|
||||
}
|
||||
|
||||
msg, err = outgoingRoute.DequeueWithTimeout(time.Second)
|
||||
if err != nil {
|
||||
t.Fatalf("DequeueWithTimeout: %+v", err)
|
||||
}
|
||||
_ = msg.(*appmessage.MsgRequestIBDBlocks)
|
||||
|
||||
// Sending unrequested block locator
|
||||
err = incomingRoute.Enqueue(appmessage.NewMsgBlockLocator(nil))
|
||||
if err != nil {
|
||||
t.Fatalf("Enqueue: %+v", err)
|
||||
}
|
||||
},
|
||||
expectsProtocolError: true,
|
||||
expectsBan: true,
|
||||
expectsErrToContain: fmt.Sprintf("received unexpected message type. expected: %s",
|
||||
appmessage.CmdIBDBlock),
|
||||
},
|
||||
{
|
||||
name: "sending unexpected IBD block",
|
||||
funcToExecute: func(incomingRoute, outgoingRoute *router.Route) {
|
||||
triggerIBD(incomingRoute, outgoingRoute)
|
||||
|
||||
msg, err := outgoingRoute.DequeueWithTimeout(time.Second)
|
||||
if err != nil {
|
||||
t.Fatalf("DequeueWithTimeout: %+v", err)
|
||||
}
|
||||
|
||||
highestHash := msg.(*appmessage.MsgIBDBlockLocator).BlockLocatorHashes[0]
|
||||
err = incomingRoute.Enqueue(appmessage.NewMsgIBDBlockLocatorHighestHash(highestHash))
|
||||
if err != nil {
|
||||
t.Fatalf("Enqueue: %+v", err)
|
||||
}
|
||||
|
||||
msg, err = outgoingRoute.DequeueWithTimeout(time.Second)
|
||||
if err != nil {
|
||||
t.Fatalf("DequeueWithTimeout: %+v", err)
|
||||
}
|
||||
_ = msg.(*appmessage.MsgRequestHeaders)
|
||||
|
||||
err = incomingRoute.Enqueue(appmessage.NewMsgDoneHeaders())
|
||||
if err != nil {
|
||||
t.Fatalf("Enqueue: %+v", err)
|
||||
}
|
||||
|
||||
msg, err = outgoingRoute.DequeueWithTimeout(time.Second)
|
||||
if err != nil {
|
||||
t.Fatalf("DequeueWithTimeout: %+v", err)
|
||||
}
|
||||
_ = msg.(*appmessage.MsgRequestPruningPointHashMessage)
|
||||
|
||||
err = incomingRoute.Enqueue(appmessage.NewPruningPointHashMessage(validPruningPointHash))
|
||||
if err != nil {
|
||||
t.Fatalf("Enqueue: %+v", err)
|
||||
}
|
||||
|
||||
msg, err = outgoingRoute.DequeueWithTimeout(time.Second)
|
||||
if err != nil {
|
||||
t.Fatalf("DequeueWithTimeout: %+v", err)
|
||||
}
|
||||
_ = msg.(*appmessage.MsgRequestPruningPointUTXOSetAndBlock)
|
||||
|
||||
err = incomingRoute.Enqueue(appmessage.NewMsgIBDBlock(appmessage.DomainBlockToMsgBlock(validPruningPointBlock)))
|
||||
if err != nil {
|
||||
t.Fatalf("Enqueue: %+v", err)
|
||||
}
|
||||
|
||||
err = incomingRoute.Enqueue(appmessage.NewMsgDonePruningPointUTXOSetChunks())
|
||||
if err != nil {
|
||||
t.Fatalf("Enqueue: %+v", err)
|
||||
}
|
||||
|
||||
msg, err = outgoingRoute.DequeueWithTimeout(time.Second)
|
||||
if err != nil {
|
||||
t.Fatalf("DequeueWithTimeout: %+v", err)
|
||||
}
|
||||
_ = msg.(*appmessage.MsgRequestIBDBlocks)
|
||||
|
||||
err = incomingRoute.Enqueue(appmessage.NewMsgIBDBlock(appmessage.DomainBlockToMsgBlock(unexpectedIBDBlock)))
|
||||
if err != nil {
|
||||
t.Fatalf("Enqueue: %+v", err)
|
||||
}
|
||||
},
|
||||
expectsProtocolError: true,
|
||||
expectsBan: true,
|
||||
expectsErrToContain: "expected block",
|
||||
},
|
||||
{
|
||||
name: "sending invalid IBD block",
|
||||
funcToExecute: func(incomingRoute, outgoingRoute *router.Route) {
|
||||
triggerIBD(incomingRoute, outgoingRoute)
|
||||
|
||||
msg, err := outgoingRoute.DequeueWithTimeout(time.Second)
|
||||
if err != nil {
|
||||
t.Fatalf("DequeueWithTimeout: %+v", err)
|
||||
}
|
||||
|
||||
highestHash := msg.(*appmessage.MsgIBDBlockLocator).BlockLocatorHashes[0]
|
||||
err = incomingRoute.Enqueue(appmessage.NewMsgIBDBlockLocatorHighestHash(highestHash))
|
||||
if err != nil {
|
||||
t.Fatalf("Enqueue: %+v", err)
|
||||
}
|
||||
|
||||
msg, err = outgoingRoute.DequeueWithTimeout(time.Second)
|
||||
if err != nil {
|
||||
t.Fatalf("DequeueWithTimeout: %+v", err)
|
||||
}
|
||||
_ = msg.(*appmessage.MsgRequestHeaders)
|
||||
|
||||
err = incomingRoute.Enqueue(appmessage.NewMsgDoneHeaders())
|
||||
if err != nil {
|
||||
t.Fatalf("Enqueue: %+v", err)
|
||||
}
|
||||
|
||||
msg, err = outgoingRoute.DequeueWithTimeout(time.Second)
|
||||
if err != nil {
|
||||
t.Fatalf("DequeueWithTimeout: %+v", err)
|
||||
}
|
||||
_ = msg.(*appmessage.MsgRequestPruningPointHashMessage)
|
||||
|
||||
err = incomingRoute.Enqueue(appmessage.NewPruningPointHashMessage(validPruningPointHash))
|
||||
if err != nil {
|
||||
t.Fatalf("Enqueue: %+v", err)
|
||||
}
|
||||
|
||||
msg, err = outgoingRoute.DequeueWithTimeout(time.Second)
|
||||
if err != nil {
|
||||
t.Fatalf("DequeueWithTimeout: %+v", err)
|
||||
}
|
||||
_ = msg.(*appmessage.MsgRequestPruningPointUTXOSetAndBlock)
|
||||
|
||||
err = incomingRoute.Enqueue(appmessage.NewMsgIBDBlock(appmessage.DomainBlockToMsgBlock(validPruningPointBlock)))
|
||||
if err != nil {
|
||||
t.Fatalf("Enqueue: %+v", err)
|
||||
}
|
||||
|
||||
err = incomingRoute.Enqueue(appmessage.NewMsgDonePruningPointUTXOSetChunks())
|
||||
if err != nil {
|
||||
t.Fatalf("Enqueue: %+v", err)
|
||||
}
|
||||
|
||||
msg, err = outgoingRoute.DequeueWithTimeout(time.Second)
|
||||
if err != nil {
|
||||
t.Fatalf("DequeueWithTimeout: %+v", err)
|
||||
}
|
||||
_ = msg.(*appmessage.MsgRequestIBDBlocks)
|
||||
|
||||
err = incomingRoute.Enqueue(appmessage.NewMsgIBDBlock(appmessage.DomainBlockToMsgBlock(invalidBlock)))
|
||||
if err != nil {
|
||||
t.Fatalf("Enqueue: %+v", err)
|
||||
}
|
||||
},
|
||||
expectsProtocolError: true,
|
||||
expectsBan: true,
|
||||
expectsErrToContain: "invalid block",
|
||||
},
|
||||
}
|
||||
|
||||
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
|
||||
for _, test := range tests {
|
||||
incomingRoute := router.NewRoute()
|
||||
outgoingRoute := router.NewRoute()
|
||||
peer := peerpkg.New(nil)
|
||||
errChan := make(chan error)
|
||||
go func() {
|
||||
errChan <- blockrelay.HandleRelayInvs(&fakeRelayInvsContext{
|
||||
params: params,
|
||||
}, incomingRoute, outgoingRoute, peer)
|
||||
}()
|
||||
|
||||
test.funcToExecute(incomingRoute, outgoingRoute)
|
||||
|
||||
select {
|
||||
case err := <-errChan:
|
||||
checkFlowError(t, err, test.expectsProtocolError, test.expectsBan, test.expectsErrToContain)
|
||||
case <-time.After(time.Second):
|
||||
t.Fatalf("timed out after %s", time.Second)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
50
app/protocol/flows/testing/receiveaddresses_test.go
Normal file
50
app/protocol/flows/testing/receiveaddresses_test.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package testing
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/app/protocol/flows/addressexchange"
|
||||
peerpkg "github.com/kaspanet/kaspad/app/protocol/peer"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/testutils"
|
||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/addressmanager"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type fakeReceiveAddressesContext struct{}
|
||||
|
||||
func (f fakeReceiveAddressesContext) AddressManager() *addressmanager.AddressManager {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestReceiveAddressesErrors(t *testing.T) {
|
||||
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
|
||||
incomingRoute := router.NewRoute()
|
||||
outgoingRoute := router.NewRoute()
|
||||
peer := peerpkg.New(nil)
|
||||
errChan := make(chan error)
|
||||
go func() {
|
||||
errChan <- addressexchange.ReceiveAddresses(fakeReceiveAddressesContext{}, incomingRoute, outgoingRoute, peer)
|
||||
}()
|
||||
|
||||
_, err := outgoingRoute.DequeueWithTimeout(time.Second)
|
||||
if err != nil {
|
||||
t.Fatalf("DequeueWithTimeout: %+v", err)
|
||||
}
|
||||
|
||||
// Sending addressmanager.GetAddressesMax+1 addresses should trigger a ban
|
||||
err = incomingRoute.Enqueue(appmessage.NewMsgAddresses(make([]*appmessage.NetAddress,
|
||||
addressmanager.GetAddressesMax+1)))
|
||||
if err != nil {
|
||||
t.Fatalf("Enqueue: %+v", err)
|
||||
}
|
||||
|
||||
select {
|
||||
case err := <-errChan:
|
||||
checkFlowError(t, err, true, true, "address count exceeded")
|
||||
case <-time.After(time.Second):
|
||||
t.Fatalf("timed out after %s", time.Second)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -44,6 +44,7 @@ func (m *Manager) routerInitializer(router *routerpkg.Router, netConnection *net
|
||||
panic(err)
|
||||
}
|
||||
if isBanned {
|
||||
log.Infof("Peer %s is banned. Disconnecting...", netConnection)
|
||||
netConnection.Disconnect()
|
||||
return
|
||||
}
|
||||
@@ -87,6 +88,7 @@ func (m *Manager) handleError(err error, netConnection *netadapter.NetConnection
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
log.Debugf("Disconnecting from %s (reason: %s)", netConnection, protocolErr.Cause)
|
||||
netConnection.Disconnect()
|
||||
return
|
||||
}
|
||||
@@ -135,9 +137,9 @@ func (m *Manager) registerBlockRelayFlows(router *routerpkg.Router, isStopping *
|
||||
return []*flow{
|
||||
m.registerFlow("HandleRelayInvs", router, []appmessage.MessageCommand{
|
||||
appmessage.CmdInvRelayBlock, appmessage.CmdBlock, appmessage.CmdBlockLocator, appmessage.CmdIBDBlock,
|
||||
appmessage.CmdDoneHeaders, appmessage.CmdIBDRootNotFound, appmessage.CmdIBDRootUTXOSetChunk,
|
||||
appmessage.CmdBlockHeaders, appmessage.CmdIBDRootHash, appmessage.CmdIBDBlockLocatorHighestHash,
|
||||
appmessage.CmdDoneIBDRootUTXOSetChunks},
|
||||
appmessage.CmdDoneHeaders, appmessage.CmdUnexpectedPruningPoint, appmessage.CmdPruningPointUTXOSetChunk,
|
||||
appmessage.CmdBlockHeaders, appmessage.CmdPruningPointHash, appmessage.CmdIBDBlockLocatorHighestHash,
|
||||
appmessage.CmdDonePruningPointUTXOSetChunks},
|
||||
isStopping, errChan, func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return blockrelay.HandleRelayInvs(m.context, incomingRoute,
|
||||
outgoingRoute, peer)
|
||||
@@ -164,11 +166,11 @@ func (m *Manager) registerBlockRelayFlows(router *routerpkg.Router, isStopping *
|
||||
},
|
||||
),
|
||||
|
||||
m.registerFlow("HandleRequestIBDRootUTXOSetAndBlock", router,
|
||||
[]appmessage.MessageCommand{appmessage.CmdRequestIBDRootUTXOSetAndBlock,
|
||||
appmessage.CmdRequestNextIBDRootUTXOSetChunk}, isStopping, errChan,
|
||||
m.registerFlow("HandleRequestPruningPointUTXOSetAndBlock", router,
|
||||
[]appmessage.MessageCommand{appmessage.CmdRequestPruningPointUTXOSetAndBlock,
|
||||
appmessage.CmdRequestNextPruningPointUTXOSetChunk}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return blockrelay.HandleRequestIBDRootUTXOSetAndBlock(m.context, incomingRoute, outgoingRoute)
|
||||
return blockrelay.HandleRequestPruningPointUTXOSetAndBlock(m.context, incomingRoute, outgoingRoute)
|
||||
},
|
||||
),
|
||||
|
||||
@@ -179,10 +181,10 @@ func (m *Manager) registerBlockRelayFlows(router *routerpkg.Router, isStopping *
|
||||
},
|
||||
),
|
||||
|
||||
m.registerFlow("HandleIBDRootHashRequests", router,
|
||||
[]appmessage.MessageCommand{appmessage.CmdRequestIBDRootHash}, isStopping, errChan,
|
||||
m.registerFlow("HandlePruningPointHashRequests", router,
|
||||
[]appmessage.MessageCommand{appmessage.CmdRequestPruningPointHash}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return blockrelay.HandleIBDRootHashRequests(m.context, incomingRoute, outgoingRoute)
|
||||
return blockrelay.HandlePruningPointHashRequests(m.context, incomingRoute, outgoingRoute)
|
||||
},
|
||||
),
|
||||
|
||||
|
||||
@@ -19,4 +19,9 @@ go vet -composites=false $FLAGS ./...
|
||||
|
||||
go build $FLAGS -o kaspad .
|
||||
|
||||
go test $FLAGS ./...
|
||||
if [ -n "${NO_PARALLEL}" ]
|
||||
then
|
||||
go test -parallel=1 $FLAGS ./...
|
||||
else
|
||||
go test $FLAGS ./...
|
||||
fi
|
||||
171
cmd/kaspactl/command_parser.go
Normal file
171
cmd/kaspactl/command_parser.go
Normal file
@@ -0,0 +1,171 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/server/grpcserver/protowire"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func parseCommand(args []string, commandDescs []*commandDescription) (*protowire.KaspadMessage, error) {
|
||||
commandName, parameterStrings := args[0], args[1:]
|
||||
|
||||
var commandDesc *commandDescription
|
||||
for _, cd := range commandDescs {
|
||||
if cd.name == commandName {
|
||||
commandDesc = cd
|
||||
break
|
||||
}
|
||||
}
|
||||
if commandDesc == nil {
|
||||
return nil, errors.Errorf("unknown command: %s. Use --list-commands to list all commands", commandName)
|
||||
}
|
||||
if len(parameterStrings) != len(commandDesc.parameters) {
|
||||
return nil, errors.Errorf("command '%s' expects %d parameters but got %d",
|
||||
commandName, len(commandDesc.parameters), len(parameterStrings))
|
||||
}
|
||||
|
||||
commandValue := reflect.New(unwrapCommandType(commandDesc.typeof))
|
||||
for i, parameterDesc := range commandDesc.parameters {
|
||||
parameterValue, err := stringToValue(parameterDesc, parameterStrings[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
setField(commandValue, parameterValue, parameterDesc)
|
||||
}
|
||||
|
||||
return generateKaspadMessage(commandValue, commandDesc)
|
||||
}
|
||||
|
||||
func setField(commandValue reflect.Value, parameterValue reflect.Value, parameterDesc *parameterDescription) {
|
||||
parameterField := commandValue.Elem().FieldByName(parameterDesc.name)
|
||||
|
||||
parameterField.Set(parameterValue)
|
||||
}
|
||||
|
||||
func stringToValue(parameterDesc *parameterDescription, valueStr string) (reflect.Value, error) {
|
||||
var value interface{}
|
||||
var err error
|
||||
switch parameterDesc.typeof.Kind() {
|
||||
case reflect.Bool:
|
||||
value, err = strconv.ParseBool(valueStr)
|
||||
if err != nil {
|
||||
return reflect.Value{}, errors.WithStack(err)
|
||||
}
|
||||
case reflect.Int8:
|
||||
var valueInt64 int64
|
||||
valueInt64, err = strconv.ParseInt(valueStr, 10, 8)
|
||||
if err != nil {
|
||||
return reflect.Value{}, errors.WithStack(err)
|
||||
}
|
||||
value = int8(valueInt64)
|
||||
case reflect.Int16:
|
||||
var valueInt64 int64
|
||||
valueInt64, err = strconv.ParseInt(valueStr, 10, 16)
|
||||
if err != nil {
|
||||
return reflect.Value{}, errors.WithStack(err)
|
||||
}
|
||||
value = int16(valueInt64)
|
||||
case reflect.Int32:
|
||||
var valueInt64 int64
|
||||
valueInt64, err = strconv.ParseInt(valueStr, 10, 32)
|
||||
if err != nil {
|
||||
return reflect.Value{}, errors.WithStack(err)
|
||||
}
|
||||
value = int32(valueInt64)
|
||||
case reflect.Int64:
|
||||
value, err = strconv.ParseInt(valueStr, 10, 64)
|
||||
if err != nil {
|
||||
return reflect.Value{}, errors.WithStack(err)
|
||||
}
|
||||
case reflect.Uint8:
|
||||
var valueUInt64 uint64
|
||||
valueUInt64, err = strconv.ParseUint(valueStr, 10, 8)
|
||||
if err != nil {
|
||||
return reflect.Value{}, errors.WithStack(err)
|
||||
}
|
||||
value = uint8(valueUInt64)
|
||||
case reflect.Uint16:
|
||||
var valueUInt64 uint64
|
||||
valueUInt64, err = strconv.ParseUint(valueStr, 10, 16)
|
||||
if err != nil {
|
||||
return reflect.Value{}, errors.WithStack(err)
|
||||
}
|
||||
value = uint16(valueUInt64)
|
||||
case reflect.Uint32:
|
||||
var valueUInt64 uint64
|
||||
valueUInt64, err = strconv.ParseUint(valueStr, 10, 32)
|
||||
if err != nil {
|
||||
return reflect.Value{}, errors.WithStack(err)
|
||||
}
|
||||
value = uint32(valueUInt64)
|
||||
case reflect.Uint64:
|
||||
value, err = strconv.ParseUint(valueStr, 10, 64)
|
||||
if err != nil {
|
||||
return reflect.Value{}, errors.WithStack(err)
|
||||
}
|
||||
case reflect.Float32:
|
||||
var valueFloat64 float64
|
||||
valueFloat64, err = strconv.ParseFloat(valueStr, 32)
|
||||
if err != nil {
|
||||
return reflect.Value{}, errors.WithStack(err)
|
||||
}
|
||||
value = float32(valueFloat64)
|
||||
case reflect.Float64:
|
||||
value, err = strconv.ParseFloat(valueStr, 64)
|
||||
if err != nil {
|
||||
return reflect.Value{}, errors.WithStack(err)
|
||||
}
|
||||
case reflect.String:
|
||||
value = valueStr
|
||||
case reflect.Struct:
|
||||
pointer := reflect.New(parameterDesc.typeof) // create pointer to this type
|
||||
fieldInterface := pointer.Interface().(proto.Message)
|
||||
err := protojson.Unmarshal([]byte(valueStr), fieldInterface)
|
||||
if err != nil {
|
||||
return reflect.Value{}, errors.WithStack(err)
|
||||
}
|
||||
// Unpointer the value once it's ready
|
||||
fieldInterfaceValue := reflect.ValueOf(fieldInterface)
|
||||
value = fieldInterfaceValue.Elem().Interface()
|
||||
case reflect.Ptr:
|
||||
dummyParameterDesc := ¶meterDescription{
|
||||
name: "valuePointedTo",
|
||||
typeof: parameterDesc.typeof.Elem(),
|
||||
}
|
||||
valuePointedTo, err := stringToValue(dummyParameterDesc, valueStr)
|
||||
if err != nil {
|
||||
return reflect.Value{}, errors.WithStack(err)
|
||||
}
|
||||
pointer := pointerToValue(valuePointedTo)
|
||||
|
||||
value = pointer.Interface()
|
||||
|
||||
// Int and uint are not supported because their size is platform-dependant
|
||||
case reflect.Int,
|
||||
reflect.Uint,
|
||||
// Other types are not supported simply because they are not used in any command right now
|
||||
// but support can be added if and when needed
|
||||
reflect.Slice,
|
||||
reflect.Func,
|
||||
reflect.Interface,
|
||||
reflect.Map,
|
||||
reflect.UnsafePointer,
|
||||
reflect.Invalid,
|
||||
reflect.Uintptr,
|
||||
reflect.Complex64,
|
||||
reflect.Complex128,
|
||||
reflect.Array,
|
||||
reflect.Chan:
|
||||
fallthrough
|
||||
default:
|
||||
return reflect.Value{},
|
||||
errors.Errorf("Unsupported type '%s' for parameter '%s'", parameterDesc.typeof.Kind(), parameterDesc.name)
|
||||
}
|
||||
|
||||
return reflect.ValueOf(value), nil
|
||||
}
|
||||
87
cmd/kaspactl/commands.go
Normal file
87
cmd/kaspactl/commands.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/server/grpcserver/protowire"
|
||||
)
|
||||
|
||||
var commandTypes = []reflect.Type{
|
||||
reflect.TypeOf(protowire.KaspadMessage_AddPeerRequest{}),
|
||||
reflect.TypeOf(protowire.KaspadMessage_GetConnectedPeerInfoRequest{}),
|
||||
reflect.TypeOf(protowire.KaspadMessage_GetPeerAddressesRequest{}),
|
||||
reflect.TypeOf(protowire.KaspadMessage_GetCurrentNetworkRequest{}),
|
||||
|
||||
reflect.TypeOf(protowire.KaspadMessage_GetBlockRequest{}),
|
||||
reflect.TypeOf(protowire.KaspadMessage_GetBlocksRequest{}),
|
||||
reflect.TypeOf(protowire.KaspadMessage_GetHeadersRequest{}),
|
||||
reflect.TypeOf(protowire.KaspadMessage_GetBlockCountRequest{}),
|
||||
reflect.TypeOf(protowire.KaspadMessage_GetBlockDagInfoRequest{}),
|
||||
reflect.TypeOf(protowire.KaspadMessage_GetSelectedTipHashRequest{}),
|
||||
reflect.TypeOf(protowire.KaspadMessage_GetVirtualSelectedParentBlueScoreRequest{}),
|
||||
reflect.TypeOf(protowire.KaspadMessage_GetVirtualSelectedParentChainFromBlockRequest{}),
|
||||
reflect.TypeOf(protowire.KaspadMessage_ResolveFinalityConflictRequest{}),
|
||||
|
||||
reflect.TypeOf(protowire.KaspadMessage_GetBlockTemplateRequest{}),
|
||||
reflect.TypeOf(protowire.KaspadMessage_SubmitBlockRequest{}),
|
||||
|
||||
reflect.TypeOf(protowire.KaspadMessage_GetMempoolEntryRequest{}),
|
||||
reflect.TypeOf(protowire.KaspadMessage_GetMempoolEntriesRequest{}),
|
||||
reflect.TypeOf(protowire.KaspadMessage_SubmitTransactionRequest{}),
|
||||
|
||||
reflect.TypeOf(protowire.KaspadMessage_GetUtxosByAddressesRequest{}),
|
||||
}
|
||||
|
||||
type commandDescription struct {
|
||||
name string
|
||||
parameters []*parameterDescription
|
||||
typeof reflect.Type
|
||||
}
|
||||
|
||||
type parameterDescription struct {
|
||||
name string
|
||||
typeof reflect.Type
|
||||
}
|
||||
|
||||
func commandDescriptions() []*commandDescription {
|
||||
commandDescriptions := make([]*commandDescription, len(commandTypes))
|
||||
|
||||
for i, commandTypeWrapped := range commandTypes {
|
||||
commandType := unwrapCommandType(commandTypeWrapped)
|
||||
|
||||
name := strings.TrimSuffix(commandType.Name(), "RequestMessage")
|
||||
numFields := commandType.NumField()
|
||||
|
||||
var parameters []*parameterDescription
|
||||
for i := 0; i < numFields; i++ {
|
||||
field := commandType.Field(i)
|
||||
|
||||
if !isFieldExported(field) {
|
||||
continue
|
||||
}
|
||||
|
||||
parameters = append(parameters, ¶meterDescription{
|
||||
name: field.Name,
|
||||
typeof: field.Type,
|
||||
})
|
||||
}
|
||||
commandDescriptions[i] = &commandDescription{
|
||||
name: name,
|
||||
parameters: parameters,
|
||||
typeof: commandTypeWrapped,
|
||||
}
|
||||
}
|
||||
|
||||
return commandDescriptions
|
||||
}
|
||||
|
||||
func (cd *commandDescription) help() string {
|
||||
sb := &strings.Builder{}
|
||||
sb.WriteString(cd.name)
|
||||
for _, parameter := range cd.parameters {
|
||||
_, _ = fmt.Fprintf(sb, " [%s]", parameter.name)
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
@@ -12,9 +12,11 @@ var (
|
||||
)
|
||||
|
||||
type configFlags struct {
|
||||
RPCServer string `short:"s" long:"rpcserver" description:"RPC server to connect to"`
|
||||
Timeout uint64 `short:"t" long:"timeout" description:"Timeout for the request (in seconds)"`
|
||||
RequestJSON string `description:"The request in JSON format"`
|
||||
RPCServer string `short:"s" long:"rpcserver" description:"RPC server to connect to"`
|
||||
Timeout uint64 `short:"t" long:"timeout" description:"Timeout for the request (in seconds)"`
|
||||
RequestJSON string `short:"j" long:"json" description:"The request in JSON format"`
|
||||
ListCommands bool `short:"l" long:"list-commands" description:"List all commands and exit"`
|
||||
CommandAndParameters []string
|
||||
config.NetworkFlags
|
||||
}
|
||||
|
||||
@@ -23,21 +25,29 @@ func parseConfig() (*configFlags, error) {
|
||||
RPCServer: defaultRPCServer,
|
||||
Timeout: defaultTimeout,
|
||||
}
|
||||
parser := flags.NewParser(cfg, flags.PrintErrors|flags.HelpFlag)
|
||||
args, err := parser.Parse()
|
||||
parser := flags.NewParser(cfg, flags.HelpFlag)
|
||||
parser.Usage = "kaspactl [OPTIONS] [COMMAND] [COMMAND PARAMETERS].\n\nCommand can be supplied only if --json is not used." +
|
||||
"\n\nUse `kaspactl --list-commands` to get a list of all commands and their parameters"
|
||||
remainingArgs, err := parser.Parse()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if cfg.ListCommands {
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
err = cfg.ResolveNetwork(parser)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(args) != 1 {
|
||||
return nil, errors.New("the last parameter must be the request in JSON format")
|
||||
cfg.CommandAndParameters = remainingArgs
|
||||
if len(cfg.CommandAndParameters) == 0 && cfg.RequestJSON == "" ||
|
||||
len(cfg.CommandAndParameters) > 0 && cfg.RequestJSON != "" {
|
||||
|
||||
return nil, errors.New("Exactly one of --json or a command must be specified")
|
||||
}
|
||||
cfg.RequestJSON = args[0]
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
@@ -2,9 +2,14 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/rpcclient/grpcclient"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/server/grpcserver/protowire"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/rpcclient/grpcclient"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -12,6 +17,10 @@ func main() {
|
||||
if err != nil {
|
||||
printErrorAndExit(fmt.Sprintf("error parsing command-line arguments: %s", err))
|
||||
}
|
||||
if cfg.ListCommands {
|
||||
printAllCommands()
|
||||
return
|
||||
}
|
||||
|
||||
rpcAddress, err := cfg.NetParams().NormalizeRPCServerAddress(cfg.RPCServer)
|
||||
if err != nil {
|
||||
@@ -23,28 +32,69 @@ func main() {
|
||||
}
|
||||
defer client.Disconnect()
|
||||
|
||||
var responseString string
|
||||
done := make(chan struct{})
|
||||
responseChan := make(chan string)
|
||||
|
||||
go func() {
|
||||
requestString := cfg.RequestJSON
|
||||
var err error
|
||||
responseString, err = client.PostJSON(requestString)
|
||||
if err != nil {
|
||||
printErrorAndExit(fmt.Sprintf("error posting the request to the RPC server: %s", err))
|
||||
}
|
||||
done <- struct{}{}
|
||||
}()
|
||||
if cfg.RequestJSON != "" {
|
||||
go postJSON(cfg, client, responseChan)
|
||||
} else {
|
||||
go postCommand(cfg, client, responseChan)
|
||||
}
|
||||
|
||||
timeout := time.Duration(cfg.Timeout) * time.Second
|
||||
select {
|
||||
case <-done:
|
||||
fmt.Println(responseString)
|
||||
case responseString := <-responseChan:
|
||||
prettyResponseString := prettifyResponse(responseString)
|
||||
fmt.Println(prettyResponseString)
|
||||
case <-time.After(timeout):
|
||||
printErrorAndExit(fmt.Sprintf("timeout of %s has been exceeded", timeout))
|
||||
}
|
||||
}
|
||||
|
||||
func printAllCommands() {
|
||||
requestDescs := commandDescriptions()
|
||||
for _, requestDesc := range requestDescs {
|
||||
fmt.Printf("\t%s\n", requestDesc.help())
|
||||
}
|
||||
}
|
||||
|
||||
func postCommand(cfg *configFlags, client *grpcclient.GRPCClient, responseChan chan string) {
|
||||
message, err := parseCommand(cfg.CommandAndParameters, commandDescriptions())
|
||||
if err != nil {
|
||||
printErrorAndExit(fmt.Sprintf("error parsing command: %s", err))
|
||||
}
|
||||
|
||||
response, err := client.Post(message)
|
||||
if err != nil {
|
||||
printErrorAndExit(fmt.Sprintf("error posting the request to the RPC server: %s", err))
|
||||
}
|
||||
responseBytes, err := protojson.Marshal(response)
|
||||
if err != nil {
|
||||
printErrorAndExit(errors.Wrapf(err, "error parsing the response from the RPC server").Error())
|
||||
}
|
||||
|
||||
responseChan <- string(responseBytes)
|
||||
}
|
||||
|
||||
func postJSON(cfg *configFlags, client *grpcclient.GRPCClient, doneChan chan string) {
|
||||
responseString, err := client.PostJSON(cfg.RequestJSON)
|
||||
if err != nil {
|
||||
printErrorAndExit(fmt.Sprintf("error posting the request to the RPC server: %s", err))
|
||||
}
|
||||
doneChan <- responseString
|
||||
}
|
||||
|
||||
func prettifyResponse(response string) string {
|
||||
kaspadMessage := &protowire.KaspadMessage{}
|
||||
err := protojson.Unmarshal([]byte(response), kaspadMessage)
|
||||
if err != nil {
|
||||
printErrorAndExit(fmt.Sprintf("error parsing the response from the RPC server: %s", err))
|
||||
}
|
||||
|
||||
marshalOptions := &protojson.MarshalOptions{}
|
||||
marshalOptions.Indent = " "
|
||||
return marshalOptions.Format(kaspadMessage)
|
||||
}
|
||||
|
||||
func printErrorAndExit(message string) {
|
||||
fmt.Fprintf(os.Stderr, fmt.Sprintf("%s\n", message))
|
||||
os.Exit(1)
|
||||
|
||||
46
cmd/kaspactl/reflection_helpers.go
Normal file
46
cmd/kaspactl/reflection_helpers.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unicode"
|
||||
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/server/grpcserver/protowire"
|
||||
)
|
||||
|
||||
// protobuf generates the command types with two types:
|
||||
// 1. A concrete type that holds the fields of the command bearing the name of the command with `RequestMessage` as suffix
|
||||
// 2. A wrapper that implements isKaspadMessage_Payload, having a single field pointing to the concrete command
|
||||
// bearing the name of the command with `KaspadMessage_` prefix and `Request` suffix
|
||||
|
||||
// unwrapCommandType converts a reflect.Type signifying a wrapper type into the concrete request type
|
||||
func unwrapCommandType(requestTypeWrapped reflect.Type) reflect.Type {
|
||||
return requestTypeWrapped.Field(0).Type.Elem()
|
||||
}
|
||||
|
||||
// unwrapCommandValue convertes a reflect.Value of a pointer to a wrapped command into a concrete command
|
||||
func unwrapCommandValue(commandValueWrapped reflect.Value) reflect.Value {
|
||||
return commandValueWrapped.Elem().Field(0)
|
||||
}
|
||||
|
||||
// isFieldExported returns true if the given field is exported.
|
||||
// Currently the only way to check this is to check if the first rune in the field's name is upper case.
|
||||
func isFieldExported(field reflect.StructField) bool {
|
||||
return unicode.IsUpper(rune(field.Name[0]))
|
||||
}
|
||||
|
||||
// generateKaspadMessage generates a wrapped KaspadMessage with the given `commandValue`
|
||||
func generateKaspadMessage(commandValue reflect.Value, commandDesc *commandDescription) (*protowire.KaspadMessage, error) {
|
||||
commandWrapper := reflect.New(commandDesc.typeof)
|
||||
unwrapCommandValue(commandWrapper).Set(commandValue)
|
||||
|
||||
kaspadMessage := reflect.New(reflect.TypeOf(protowire.KaspadMessage{}))
|
||||
kaspadMessage.Elem().FieldByName("Payload").Set(commandWrapper)
|
||||
return kaspadMessage.Interface().(*protowire.KaspadMessage), nil
|
||||
}
|
||||
|
||||
// pointerToValue returns a reflect.Value that represents a pointer to the given value
|
||||
func pointerToValue(valuePointedTo reflect.Value) reflect.Value {
|
||||
pointer := reflect.New(valuePointedTo.Type())
|
||||
pointer.Elem().Set(valuePointedTo)
|
||||
return pointer
|
||||
}
|
||||
@@ -18,7 +18,7 @@ COPY go.sum .
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN ./build_and_test.sh
|
||||
RUN NO_PARALLEL=1 ./build_and_test.sh
|
||||
|
||||
# --- multistage docker build: stage #2: runtime image
|
||||
FROM alpine
|
||||
|
||||
@@ -3,8 +3,7 @@ package consensus
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/kaspanet/kaspad/infrastructure/db/database"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/database"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
|
||||
@@ -200,7 +199,9 @@ func (s *consensus) GetMissingBlockBodyHashes(highHash *externalapi.DomainHash)
|
||||
return s.syncManager.GetMissingBlockBodyHashes(highHash)
|
||||
}
|
||||
|
||||
func (s *consensus) GetPruningPointUTXOSet(expectedPruningPointHash *externalapi.DomainHash) ([]byte, error) {
|
||||
func (s *consensus) GetPruningPointUTXOs(expectedPruningPointHash *externalapi.DomainHash,
|
||||
fromOutpoint *externalapi.DomainOutpoint, limit int) ([]*externalapi.OutpointAndUTXOEntryPair, error) {
|
||||
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
@@ -215,11 +216,11 @@ func (s *consensus) GetPruningPointUTXOSet(expectedPruningPointHash *externalapi
|
||||
pruningPointHash)
|
||||
}
|
||||
|
||||
serializedUTXOSet, err := s.pruningStore.PruningPointSerializedUTXOSet(s.databaseContext)
|
||||
pruningPointUTXOs, err := s.pruningStore.PruningPointUTXOs(s.databaseContext, fromOutpoint, limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return serializedUTXOSet, nil
|
||||
return pruningPointUTXOs, nil
|
||||
}
|
||||
|
||||
func (s *consensus) PruningPoint() (*externalapi.DomainHash, error) {
|
||||
@@ -229,11 +230,25 @@ func (s *consensus) PruningPoint() (*externalapi.DomainHash, error) {
|
||||
return s.pruningStore.PruningPoint(s.databaseContext)
|
||||
}
|
||||
|
||||
func (s *consensus) ValidateAndInsertPruningPoint(newPruningPoint *externalapi.DomainBlock, serializedUTXOSet []byte) error {
|
||||
func (s *consensus) ClearImportedPruningPointData() error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
return s.blockProcessor.ValidateAndInsertPruningPoint(newPruningPoint, serializedUTXOSet)
|
||||
return s.pruningManager.ClearImportedPruningPointData()
|
||||
}
|
||||
|
||||
func (s *consensus) AppendImportedPruningPointUTXOs(outpointAndUTXOEntryPairs []*externalapi.OutpointAndUTXOEntryPair) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
return s.pruningManager.AppendImportedPruningPointUTXOs(outpointAndUTXOEntryPairs)
|
||||
}
|
||||
|
||||
func (s *consensus) ValidateAndInsertImportedPruningPoint(newPruningPoint *externalapi.DomainBlock) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
return s.blockProcessor.ValidateAndInsertImportedPruningPoint(newPruningPoint)
|
||||
}
|
||||
|
||||
func (s *consensus) GetVirtualSelectedParent() (*externalapi.DomainHash, error) {
|
||||
|
||||
@@ -6,9 +6,18 @@ import (
|
||||
)
|
||||
|
||||
func dbBucketToDatabaseBucket(bucket model.DBBucket) *database.Bucket {
|
||||
if bucket, ok := bucket.(dbBucket); ok {
|
||||
return bucket.bucket
|
||||
}
|
||||
// This assumes that MakeBucket(src).Path() == src. which is not promised anywhere.
|
||||
return database.MakeBucket(bucket.Path())
|
||||
}
|
||||
|
||||
// MakeBucket creates a new Bucket using the given path of buckets.
|
||||
func MakeBucket(path []byte) model.DBBucket {
|
||||
return dbBucket{bucket: database.MakeBucket(path)}
|
||||
}
|
||||
|
||||
type dbBucket struct {
|
||||
bucket *database.Bucket
|
||||
}
|
||||
@@ -26,5 +35,5 @@ func (d dbBucket) Path() []byte {
|
||||
}
|
||||
|
||||
func newDBBucket(bucket *database.Bucket) model.DBBucket {
|
||||
return &dbBucket{bucket: bucket}
|
||||
return dbBucket{bucket: bucket}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,14 @@ func (dbw *dbManager) Has(key model.DBKey) (bool, error) {
|
||||
return dbw.db.Has(dbKeyToDatabaseKey(key))
|
||||
}
|
||||
|
||||
func (dbw *dbManager) Put(key model.DBKey, value []byte) error {
|
||||
return dbw.db.Put(dbKeyToDatabaseKey(key), value)
|
||||
}
|
||||
|
||||
func (dbw *dbManager) Delete(key model.DBKey) error {
|
||||
return dbw.db.Delete(dbKeyToDatabaseKey(key))
|
||||
}
|
||||
|
||||
func (dbw *dbManager) Cursor(bucket model.DBBucket) (model.DBCursor, error) {
|
||||
cursor, err := dbw.db.Cursor(dbBucketToDatabaseBucket(bucket))
|
||||
if err != nil {
|
||||
|
||||
14
domain/consensus/database/errors.go
Normal file
14
domain/consensus/database/errors.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/infrastructure/db/database"
|
||||
)
|
||||
|
||||
// ErrNotFound denotes that the requested item was not
|
||||
// found in the database.
|
||||
var ErrNotFound = database.ErrNotFound
|
||||
|
||||
// IsNotFoundError checks whether an error is an ErrNotFound.
|
||||
func IsNotFoundError(err error) bool {
|
||||
return database.IsNotFoundError(err)
|
||||
}
|
||||
@@ -6,6 +6,12 @@ import (
|
||||
)
|
||||
|
||||
func dbKeyToDatabaseKey(key model.DBKey) *database.Key {
|
||||
if key, ok := key.(dbKey); ok {
|
||||
return key.key
|
||||
}
|
||||
if key, ok := key.(*dbKey); ok {
|
||||
return key.key
|
||||
}
|
||||
return dbBucketToDatabaseBucket(key.Bucket()).Key(key.Suffix())
|
||||
}
|
||||
|
||||
@@ -26,5 +32,5 @@ func (d dbKey) Suffix() []byte {
|
||||
}
|
||||
|
||||
func newDBKey(key *database.Key) model.DBKey {
|
||||
return &dbKey{key: key}
|
||||
return dbKey{key: key}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ func utxoCollectionToDBUTXOCollection(utxoCollection model.UTXOCollection) ([]*D
|
||||
items := make([]*DbUtxoCollectionItem, utxoCollection.Len())
|
||||
i := 0
|
||||
utxoIterator := utxoCollection.Iterator()
|
||||
for utxoIterator.Next() {
|
||||
for ok := utxoIterator.First(); ok; ok = utxoIterator.Next() {
|
||||
outpoint, entry, err := utxoIterator.Get()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
package acceptancedatastore
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus/database"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/database/serialization"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/dbkeys"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/lrucache"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
var bucket = dbkeys.MakeBucket([]byte("acceptance-data"))
|
||||
var bucket = database.MakeBucket([]byte("acceptance-data"))
|
||||
|
||||
// acceptanceDataStore represents a store of AcceptanceData
|
||||
type acceptanceDataStore struct {
|
||||
|
||||
@@ -2,15 +2,15 @@ package blockheaderstore
|
||||
|
||||
import (
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/database"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/database/serialization"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/dbkeys"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/lrucache"
|
||||
)
|
||||
|
||||
var bucket = dbkeys.MakeBucket([]byte("block-headers"))
|
||||
var countKey = dbkeys.MakeBucket().Key([]byte("block-headers-count"))
|
||||
var bucket = database.MakeBucket([]byte("block-headers"))
|
||||
var countKey = database.MakeBucket(nil).Key([]byte("block-headers-count"))
|
||||
|
||||
// blockHeaderStore represents a store of blocks
|
||||
type blockHeaderStore struct {
|
||||
|
||||
@@ -2,14 +2,14 @@ package blockrelationstore
|
||||
|
||||
import (
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/database"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/database/serialization"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/dbkeys"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/lrucache"
|
||||
)
|
||||
|
||||
var bucket = dbkeys.MakeBucket([]byte("block-relations"))
|
||||
var bucket = database.MakeBucket([]byte("block-relations"))
|
||||
|
||||
// blockRelationStore represents a store of BlockRelations
|
||||
type blockRelationStore struct {
|
||||
|
||||
@@ -2,14 +2,14 @@ package blockstatusstore
|
||||
|
||||
import (
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/database"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/database/serialization"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/dbkeys"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/lrucache"
|
||||
)
|
||||
|
||||
var bucket = dbkeys.MakeBucket([]byte("block-statuses"))
|
||||
var bucket = database.MakeBucket([]byte("block-statuses"))
|
||||
|
||||
// blockStatusStore represents a store of BlockStatuses
|
||||
type blockStatusStore struct {
|
||||
|
||||
@@ -2,15 +2,15 @@ package blockstore
|
||||
|
||||
import (
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/database"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/database/serialization"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/dbkeys"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/lrucache"
|
||||
)
|
||||
|
||||
var bucket = dbkeys.MakeBucket([]byte("blocks"))
|
||||
var countKey = dbkeys.MakeBucket().Key([]byte("blocks-count"))
|
||||
var bucket = database.MakeBucket([]byte("blocks"))
|
||||
var countKey = database.MakeBucket(nil).Key([]byte("blocks-count"))
|
||||
|
||||
// blockStore represents a store of blocks
|
||||
type blockStore struct {
|
||||
|
||||
@@ -11,7 +11,6 @@ type consensusStateStore struct {
|
||||
tipsStaging []*externalapi.DomainHash
|
||||
virtualDiffParentsStaging []*externalapi.DomainHash
|
||||
virtualUTXODiffStaging model.UTXODiff
|
||||
virtualUTXOSetStaging model.UTXOCollection
|
||||
|
||||
virtualUTXOSetCache *utxolrucache.LRUCache
|
||||
|
||||
@@ -30,7 +29,6 @@ func (css *consensusStateStore) Discard() {
|
||||
css.tipsStaging = nil
|
||||
css.virtualUTXODiffStaging = nil
|
||||
css.virtualDiffParentsStaging = nil
|
||||
css.virtualUTXOSetStaging = nil
|
||||
}
|
||||
|
||||
func (css *consensusStateStore) Commit(dbTx model.DBTransaction) error {
|
||||
@@ -48,11 +46,6 @@ func (css *consensusStateStore) Commit(dbTx model.DBTransaction) error {
|
||||
return err
|
||||
}
|
||||
|
||||
err = css.commitVirtualUTXOSet(dbTx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
css.Discard()
|
||||
|
||||
return nil
|
||||
|
||||
@@ -2,13 +2,13 @@ package consensusstatestore
|
||||
|
||||
import (
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/database"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/database/serialization"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/dbkeys"
|
||||
)
|
||||
|
||||
var tipsKey = dbkeys.MakeBucket().Key([]byte("tips"))
|
||||
var tipsKey = database.MakeBucket(nil).Key([]byte("tips"))
|
||||
|
||||
func (css *consensusStateStore) Tips(dbContext model.DBReader) ([]*externalapi.DomainHash, error) {
|
||||
if css.tipsStaging != nil {
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
package consensusstatestore
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus/database"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/dbkeys"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/utxo"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var utxoSetBucket = dbkeys.MakeBucket([]byte("virtual-utxo-set"))
|
||||
var utxoSetBucket = database.MakeBucket([]byte("virtual-utxo-set"))
|
||||
|
||||
func utxoKey(outpoint *externalapi.DomainOutpoint) (model.DBKey, error) {
|
||||
serializedOutpoint, err := serializeOutpoint(outpoint)
|
||||
@@ -19,22 +19,25 @@ func utxoKey(outpoint *externalapi.DomainOutpoint) (model.DBKey, error) {
|
||||
return utxoSetBucket.Key(serializedOutpoint), nil
|
||||
}
|
||||
|
||||
func (css *consensusStateStore) StageVirtualUTXODiff(virtualUTXODiff model.UTXODiff) error {
|
||||
if css.virtualUTXOSetStaging != nil {
|
||||
return errors.New("cannot stage virtual UTXO diff while virtual UTXO set is staged")
|
||||
}
|
||||
|
||||
func (css *consensusStateStore) StageVirtualUTXODiff(virtualUTXODiff model.UTXODiff) {
|
||||
css.virtualUTXODiffStaging = virtualUTXODiff
|
||||
return nil
|
||||
}
|
||||
|
||||
func (css *consensusStateStore) commitVirtualUTXODiff(dbTx model.DBTransaction) error {
|
||||
hadStartedImportingPruningPointUTXOSet, err := css.HadStartedImportingPruningPointUTXOSet(dbTx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if hadStartedImportingPruningPointUTXOSet {
|
||||
return errors.New("cannot commit virtual UTXO diff after starting to import the pruning point UTXO set")
|
||||
}
|
||||
|
||||
if css.virtualUTXODiffStaging == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
toRemoveIterator := css.virtualUTXODiffStaging.ToRemove().Iterator()
|
||||
for toRemoveIterator.Next() {
|
||||
for ok := toRemoveIterator.First(); ok; ok = toRemoveIterator.Next() {
|
||||
toRemoveOutpoint, _, err := toRemoveIterator.Get()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -53,7 +56,7 @@ func (css *consensusStateStore) commitVirtualUTXODiff(dbTx model.DBTransaction)
|
||||
}
|
||||
|
||||
toAddIterator := css.virtualUTXODiffStaging.ToAdd().Iterator()
|
||||
for toAddIterator.Next() {
|
||||
for ok := toAddIterator.First(); ok; ok = toAddIterator.Next() {
|
||||
toAddOutpoint, toAddEntry, err := toAddIterator.Get()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -80,63 +83,9 @@ func (css *consensusStateStore) commitVirtualUTXODiff(dbTx model.DBTransaction)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (css *consensusStateStore) commitVirtualUTXOSet(dbTx model.DBTransaction) error {
|
||||
if css.virtualUTXOSetStaging == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Clear the existing virtual utxo set in database before adding the new one
|
||||
cursor, err := dbTx.Cursor(utxoSetBucket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for cursor.Next() {
|
||||
key, err := cursor.Key()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = dbTx.Delete(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Now put the new virtualUTXOSet into the database
|
||||
css.virtualUTXOSetCache.Clear()
|
||||
iterator := css.virtualUTXOSetStaging.Iterator()
|
||||
for iterator.Next() {
|
||||
outpoint, utxoEntry, err := iterator.Get()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
css.virtualUTXOSetCache.Add(outpoint, utxoEntry)
|
||||
dbKey, err := utxoKey(outpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
serializedEntry, err := serializeUTXOEntry(utxoEntry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = dbTx.Put(dbKey, serializedEntry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Note: we don't discard the staging here since that's
|
||||
// being done at the end of Commit()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (css *consensusStateStore) UTXOByOutpoint(dbContext model.DBReader, outpoint *externalapi.DomainOutpoint) (
|
||||
externalapi.UTXOEntry, error) {
|
||||
|
||||
if css.virtualUTXOSetStaging != nil {
|
||||
return css.utxoByOutpointFromStagedVirtualUTXOSet(outpoint)
|
||||
}
|
||||
|
||||
return css.utxoByOutpointFromStagedVirtualUTXODiff(dbContext, outpoint)
|
||||
}
|
||||
|
||||
@@ -176,20 +125,7 @@ func (css *consensusStateStore) utxoByOutpointFromStagedVirtualUTXODiff(dbContex
|
||||
return entry, nil
|
||||
}
|
||||
|
||||
func (css *consensusStateStore) utxoByOutpointFromStagedVirtualUTXOSet(outpoint *externalapi.DomainOutpoint) (
|
||||
externalapi.UTXOEntry, error) {
|
||||
if utxoEntry, ok := css.virtualUTXOSetStaging.Get(outpoint); ok {
|
||||
return utxoEntry, nil
|
||||
}
|
||||
|
||||
return nil, errors.Errorf("outpoint was not found")
|
||||
}
|
||||
|
||||
func (css *consensusStateStore) HasUTXOByOutpoint(dbContext model.DBReader, outpoint *externalapi.DomainOutpoint) (bool, error) {
|
||||
if css.virtualUTXOSetStaging != nil {
|
||||
return css.hasUTXOByOutpointFromStagedVirtualUTXOSet(outpoint), nil
|
||||
}
|
||||
|
||||
return css.hasUTXOByOutpointFromStagedVirtualUTXODiff(dbContext, outpoint)
|
||||
}
|
||||
|
||||
@@ -213,10 +149,6 @@ func (css *consensusStateStore) hasUTXOByOutpointFromStagedVirtualUTXODiff(dbCon
|
||||
return dbContext.Has(key)
|
||||
}
|
||||
|
||||
func (css *consensusStateStore) hasUTXOByOutpointFromStagedVirtualUTXOSet(outpoint *externalapi.DomainOutpoint) bool {
|
||||
return css.virtualUTXOSetStaging.Contains(outpoint)
|
||||
}
|
||||
|
||||
func (css *consensusStateStore) VirtualUTXOSetIterator(dbContext model.DBReader) (model.ReadOnlyUTXOSetIterator, error) {
|
||||
cursor, err := dbContext.Cursor(utxoSetBucket)
|
||||
if err != nil {
|
||||
@@ -239,6 +171,10 @@ func newCursorUTXOSetIterator(cursor model.DBCursor) model.ReadOnlyUTXOSetIterat
|
||||
return &utxoSetIterator{cursor: cursor}
|
||||
}
|
||||
|
||||
func (u utxoSetIterator) First() bool {
|
||||
return u.cursor.First()
|
||||
}
|
||||
|
||||
func (u utxoSetIterator) Next() bool {
|
||||
return u.cursor.Next()
|
||||
}
|
||||
@@ -266,25 +202,3 @@ func (u utxoSetIterator) Get() (outpoint *externalapi.DomainOutpoint, utxoEntry
|
||||
|
||||
return outpoint, utxoEntry, nil
|
||||
}
|
||||
|
||||
func (css *consensusStateStore) StageVirtualUTXOSet(virtualUTXOSetIterator model.ReadOnlyUTXOSetIterator) error {
|
||||
if css.virtualUTXODiffStaging != nil {
|
||||
return errors.New("cannot stage virtual UTXO set while virtual UTXO diff is staged")
|
||||
}
|
||||
|
||||
utxoMap := make(map[externalapi.DomainOutpoint]externalapi.UTXOEntry)
|
||||
for virtualUTXOSetIterator.Next() {
|
||||
outpoint, entry, err := virtualUTXOSetIterator.Get()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, exists := utxoMap[*outpoint]; exists {
|
||||
return errors.Errorf("outpoint %s is found more than once in the given iterator", outpoint)
|
||||
}
|
||||
utxoMap[*outpoint] = entry
|
||||
}
|
||||
css.virtualUTXOSetStaging = utxo.NewUTXOCollection(utxoMap)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2,13 +2,13 @@ package consensusstatestore
|
||||
|
||||
import (
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/database"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/database/serialization"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/dbkeys"
|
||||
)
|
||||
|
||||
var virtualDiffParentsKey = dbkeys.MakeBucket().Key([]byte("virtual-diff-parents"))
|
||||
var virtualDiffParentsKey = database.MakeBucket(nil).Key([]byte("virtual-diff-parents"))
|
||||
|
||||
func (css *consensusStateStore) VirtualDiffParents(dbContext model.DBReader) ([]*externalapi.DomainHash, error) {
|
||||
if css.virtualDiffParentsStaging != nil {
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
package consensusstatestore
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus/database"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var importingPruningPointUTXOSetKey = database.MakeBucket(nil).Key([]byte("importing-pruning-point-utxo-set"))
|
||||
|
||||
func (css *consensusStateStore) StartImportingPruningPointUTXOSet(dbContext model.DBWriter) error {
|
||||
return dbContext.Put(importingPruningPointUTXOSetKey, []byte{0})
|
||||
}
|
||||
|
||||
func (css *consensusStateStore) HadStartedImportingPruningPointUTXOSet(dbContext model.DBWriter) (bool, error) {
|
||||
return dbContext.Has(importingPruningPointUTXOSetKey)
|
||||
}
|
||||
|
||||
func (css *consensusStateStore) FinishImportingPruningPointUTXOSet(dbContext model.DBWriter) error {
|
||||
return dbContext.Delete(importingPruningPointUTXOSetKey)
|
||||
}
|
||||
|
||||
func (css *consensusStateStore) ImportPruningPointUTXOSetIntoVirtualUTXOSet(dbContext model.DBWriter,
|
||||
pruningPointUTXOSetIterator model.ReadOnlyUTXOSetIterator) error {
|
||||
|
||||
if css.virtualUTXODiffStaging != nil {
|
||||
return errors.New("cannot import virtual UTXO set while virtual UTXO diff is staged")
|
||||
}
|
||||
|
||||
hadStartedImportingPruningPointUTXOSet, err := css.HadStartedImportingPruningPointUTXOSet(dbContext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !hadStartedImportingPruningPointUTXOSet {
|
||||
return errors.New("cannot import pruning point UTXO set " +
|
||||
"without calling StartImportingPruningPointUTXOSet first")
|
||||
}
|
||||
|
||||
// Clear the cache
|
||||
css.virtualUTXOSetCache.Clear()
|
||||
|
||||
// Delete all the old UTXOs from the database
|
||||
deleteCursor, err := dbContext.Cursor(utxoSetBucket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for ok := deleteCursor.First(); ok; ok = deleteCursor.Next() {
|
||||
key, err := deleteCursor.Key()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = dbContext.Delete(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Insert all the new UTXOs into the database
|
||||
for ok := pruningPointUTXOSetIterator.First(); ok; ok = pruningPointUTXOSetIterator.Next() {
|
||||
outpoint, entry, err := pruningPointUTXOSetIterator.Get()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
key, err := utxoKey(outpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
serializedUTXOEntry, err := serializeUTXOEntry(entry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = dbContext.Put(key, serializedUTXOEntry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
package finalitystore
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus/database"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/dbkeys"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/lrucache"
|
||||
)
|
||||
|
||||
var bucket = dbkeys.MakeBucket([]byte("finality-points"))
|
||||
var bucket = database.MakeBucket([]byte("finality-points"))
|
||||
|
||||
type finalityStore struct {
|
||||
staging map[externalapi.DomainHash]*externalapi.DomainHash
|
||||
|
||||
@@ -2,14 +2,14 @@ package ghostdagdatastore
|
||||
|
||||
import (
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/database"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/database/serialization"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/dbkeys"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/lrucache"
|
||||
)
|
||||
|
||||
var bucket = dbkeys.MakeBucket([]byte("block-ghostdag-data"))
|
||||
var bucket = database.MakeBucket([]byte("block-ghostdag-data"))
|
||||
|
||||
// ghostdagDataStore represents a store of BlockGHOSTDAGData
|
||||
type ghostdagDataStore struct {
|
||||
|
||||
@@ -2,19 +2,18 @@ package headersselectedchainstore
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/database"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/database/binaryserialization"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/dbkeys"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/lrucache"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/lrucacheuint64tohash"
|
||||
"github.com/kaspanet/kaspad/infrastructure/db/database"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var bucketChainBlockHashByIndex = dbkeys.MakeBucket([]byte("chain-block-hash-by-index"))
|
||||
var bucketChainBlockIndexByHash = dbkeys.MakeBucket([]byte("chain-block-index-by-hash"))
|
||||
var highestChainBlockIndexKey = dbkeys.MakeBucket().Key([]byte("highest-chain-block-index"))
|
||||
var bucketChainBlockHashByIndex = database.MakeBucket([]byte("chain-block-hash-by-index"))
|
||||
var bucketChainBlockIndexByHash = database.MakeBucket([]byte("chain-block-index-by-hash"))
|
||||
var highestChainBlockIndexKey = database.MakeBucket(nil).Key([]byte("highest-chain-block-index"))
|
||||
|
||||
type headersSelectedChainStore struct {
|
||||
stagingAddedByHash map[externalapi.DomainHash]uint64
|
||||
|
||||
@@ -2,13 +2,13 @@ package headersselectedtipstore
|
||||
|
||||
import (
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/database"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/database/serialization"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/dbkeys"
|
||||
)
|
||||
|
||||
var headerSelectedTipKey = dbkeys.MakeBucket().Key([]byte("headers-selected-tip"))
|
||||
var headerSelectedTipKey = database.MakeBucket(nil).Key([]byte("headers-selected-tip"))
|
||||
|
||||
type headerSelectedTipStore struct {
|
||||
staging *externalapi.DomainHash
|
||||
|
||||
@@ -2,14 +2,14 @@ package multisetstore
|
||||
|
||||
import (
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/database"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/database/serialization"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/dbkeys"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/lrucache"
|
||||
)
|
||||
|
||||
var bucket = dbkeys.MakeBucket([]byte("multisets"))
|
||||
var bucket = database.MakeBucket([]byte("multisets"))
|
||||
|
||||
// multisetStore represents a store of Multisets
|
||||
type multisetStore struct {
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
package pruningstore
|
||||
|
||||
import (
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/database"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/database/serialization"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
)
|
||||
|
||||
var importedPruningPointUTXOsBucket = database.MakeBucket([]byte("imported-pruning-point-utxos"))
|
||||
var importedPruningPointMultiset = database.MakeBucket(nil).Key([]byte("imported-pruning-point-multiset"))
|
||||
|
||||
func (ps *pruningStore) ClearImportedPruningPointUTXOs(dbContext model.DBWriter) error {
|
||||
cursor, err := dbContext.Cursor(importedPruningPointUTXOsBucket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for ok := cursor.First(); ok; ok = cursor.Next() {
|
||||
key, err := cursor.Key()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = dbContext.Delete(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ps *pruningStore) AppendImportedPruningPointUTXOs(dbTx model.DBTransaction,
|
||||
outpointAndUTXOEntryPairs []*externalapi.OutpointAndUTXOEntryPair) error {
|
||||
|
||||
for _, outpointAndUTXOEntryPair := range outpointAndUTXOEntryPairs {
|
||||
key, err := ps.importedPruningPointUTXOKey(outpointAndUTXOEntryPair.Outpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
serializedUTXOEntry, err := serializeUTXOEntry(outpointAndUTXOEntryPair.UTXOEntry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = dbTx.Put(key, serializedUTXOEntry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ps *pruningStore) ImportedPruningPointUTXOIterator(dbContext model.DBReader) (model.ReadOnlyUTXOSetIterator, error) {
|
||||
cursor, err := dbContext.Cursor(importedPruningPointUTXOsBucket)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ps.newCursorUTXOSetIterator(cursor), nil
|
||||
}
|
||||
|
||||
type utxoSetIterator struct {
|
||||
cursor model.DBCursor
|
||||
}
|
||||
|
||||
func (ps *pruningStore) newCursorUTXOSetIterator(cursor model.DBCursor) model.ReadOnlyUTXOSetIterator {
|
||||
return &utxoSetIterator{cursor: cursor}
|
||||
}
|
||||
|
||||
func (u *utxoSetIterator) First() bool {
|
||||
return u.cursor.First()
|
||||
}
|
||||
|
||||
func (u *utxoSetIterator) Next() bool {
|
||||
return u.cursor.Next()
|
||||
}
|
||||
|
||||
func (u *utxoSetIterator) Get() (outpoint *externalapi.DomainOutpoint, utxoEntry externalapi.UTXOEntry, err error) {
|
||||
key, err := u.cursor.Key()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
utxoEntryBytes, err := u.cursor.Value()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
outpoint, err = deserializeOutpoint(key.Suffix())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
utxoEntry, err = deserializeUTXOEntry(utxoEntryBytes)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return outpoint, utxoEntry, nil
|
||||
}
|
||||
|
||||
func (ps *pruningStore) importedPruningPointUTXOKey(outpoint *externalapi.DomainOutpoint) (model.DBKey, error) {
|
||||
serializedOutpoint, err := serializeOutpoint(outpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return importedPruningPointUTXOsBucket.Key(serializedOutpoint), nil
|
||||
}
|
||||
|
||||
func serializeOutpoint(outpoint *externalapi.DomainOutpoint) ([]byte, error) {
|
||||
return proto.Marshal(serialization.DomainOutpointToDbOutpoint(outpoint))
|
||||
}
|
||||
|
||||
func serializeUTXOEntry(entry externalapi.UTXOEntry) ([]byte, error) {
|
||||
return proto.Marshal(serialization.UTXOEntryToDBUTXOEntry(entry))
|
||||
}
|
||||
|
||||
func deserializeOutpoint(outpointBytes []byte) (*externalapi.DomainOutpoint, error) {
|
||||
dbOutpoint := &serialization.DbOutpoint{}
|
||||
err := proto.Unmarshal(outpointBytes, dbOutpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return serialization.DbOutpointToDomainOutpoint(dbOutpoint)
|
||||
}
|
||||
|
||||
func deserializeUTXOEntry(entryBytes []byte) (externalapi.UTXOEntry, error) {
|
||||
dbEntry := &serialization.DbUtxoEntry{}
|
||||
err := proto.Unmarshal(entryBytes, dbEntry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return serialization.DBUTXOEntryToUTXOEntry(dbEntry)
|
||||
}
|
||||
|
||||
func (ps *pruningStore) ClearImportedPruningPointMultiset(dbContext model.DBWriter) error {
|
||||
return dbContext.Delete(importedPruningPointMultiset)
|
||||
}
|
||||
|
||||
func (ps *pruningStore) ImportedPruningPointMultiset(dbContext model.DBReader) (model.Multiset, error) {
|
||||
multisetBytes, err := dbContext.Get(importedPruningPointMultiset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ps.deserializeMultiset(multisetBytes)
|
||||
}
|
||||
|
||||
func (ps *pruningStore) UpdateImportedPruningPointMultiset(dbTx model.DBTransaction, multiset model.Multiset) error {
|
||||
multisetBytes, err := ps.serializeMultiset(multiset)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return dbTx.Put(importedPruningPointMultiset, multisetBytes)
|
||||
}
|
||||
|
||||
func (ps *pruningStore) serializeMultiset(multiset model.Multiset) ([]byte, error) {
|
||||
return proto.Marshal(serialization.MultisetToDBMultiset(multiset))
|
||||
}
|
||||
|
||||
func (ps *pruningStore) deserializeMultiset(multisetBytes []byte) (model.Multiset, error) {
|
||||
dbMultiset := &serialization.DbMultiset{}
|
||||
err := proto.Unmarshal(multisetBytes, dbMultiset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return serialization.DBMultisetToMultiset(dbMultiset)
|
||||
}
|
||||
|
||||
func (ps *pruningStore) CommitImportedPruningPointUTXOSet(dbContext model.DBWriter) error {
|
||||
// Delete all the old UTXOs from the database
|
||||
deleteCursor, err := dbContext.Cursor(pruningPointUTXOSetBucket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for ok := deleteCursor.First(); ok; ok = deleteCursor.Next() {
|
||||
key, err := deleteCursor.Key()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = dbContext.Delete(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Insert all the new UTXOs into the database
|
||||
insertCursor, err := dbContext.Cursor(importedPruningPointUTXOsBucket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for ok := insertCursor.First(); ok; ok = insertCursor.Next() {
|
||||
importedPruningPointUTXOSetKey, err := insertCursor.Key()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pruningPointUTXOSetKey := pruningPointUTXOSetBucket.Key(importedPruningPointUTXOSetKey.Suffix())
|
||||
|
||||
serializedUTXOEntry, err := insertCursor.Value()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = dbContext.Put(pruningPointUTXOSetKey, serializedUTXOEntry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -2,23 +2,30 @@ package pruningstore
|
||||
|
||||
import (
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/database"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/database/serialization"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/dbkeys"
|
||||
)
|
||||
|
||||
var pruningBlockHashKey = dbkeys.MakeBucket().Key([]byte("pruning-block-hash"))
|
||||
var candidatePruningPointHashKey = dbkeys.MakeBucket().Key([]byte("candidate-pruning-point-hash"))
|
||||
var pruningSerializedUTXOSetKey = dbkeys.MakeBucket().Key([]byte("pruning-utxo-set"))
|
||||
var pruningBlockHashKey = database.MakeBucket(nil).Key([]byte("pruning-block-hash"))
|
||||
var candidatePruningPointHashKey = database.MakeBucket(nil).Key([]byte("candidate-pruning-point-hash"))
|
||||
var pruningPointUTXOSetBucket = database.MakeBucket([]byte("pruning-point-utxo-set"))
|
||||
var updatingPruningPointUTXOSetKey = database.MakeBucket(nil).Key([]byte("updating-pruning-point-utxo-set"))
|
||||
|
||||
// pruningStore represents a store for the current pruning state
|
||||
type pruningStore struct {
|
||||
pruningPointStaging *externalapi.DomainHash
|
||||
pruningPointCandidateStaging *externalapi.DomainHash
|
||||
serializedUTXOSetStaging []byte
|
||||
pruningPointCandidateCache *externalapi.DomainHash
|
||||
pruningPointCache *externalapi.DomainHash
|
||||
pruningPointCandidateStaging *externalapi.DomainHash
|
||||
pruningPointCandidateCache *externalapi.DomainHash
|
||||
|
||||
startUpdatingPruningPointUTXOSetStaging bool
|
||||
}
|
||||
|
||||
// New instantiates a new PruningStore
|
||||
func New() model.PruningStore {
|
||||
return &pruningStore{}
|
||||
}
|
||||
|
||||
func (ps *pruningStore) StagePruningPointCandidate(candidate *externalapi.DomainHash) {
|
||||
@@ -59,24 +66,18 @@ func (ps *pruningStore) HasPruningPointCandidate(dbContext model.DBReader) (bool
|
||||
return dbContext.Has(candidatePruningPointHashKey)
|
||||
}
|
||||
|
||||
// New instantiates a new PruningStore
|
||||
func New() model.PruningStore {
|
||||
return &pruningStore{}
|
||||
}
|
||||
|
||||
// Stage stages the pruning state
|
||||
func (ps *pruningStore) StagePruningPoint(pruningPointBlockHash *externalapi.DomainHash, pruningPointUTXOSetBytes []byte) {
|
||||
func (ps *pruningStore) StagePruningPoint(pruningPointBlockHash *externalapi.DomainHash) {
|
||||
ps.pruningPointStaging = pruningPointBlockHash
|
||||
ps.serializedUTXOSetStaging = pruningPointUTXOSetBytes
|
||||
}
|
||||
|
||||
func (ps *pruningStore) IsStaged() bool {
|
||||
return ps.pruningPointStaging != nil || ps.serializedUTXOSetStaging != nil
|
||||
return ps.pruningPointStaging != nil || ps.startUpdatingPruningPointUTXOSetStaging
|
||||
}
|
||||
|
||||
func (ps *pruningStore) Discard() {
|
||||
ps.pruningPointStaging = nil
|
||||
ps.serializedUTXOSetStaging = nil
|
||||
ps.startUpdatingPruningPointUTXOSetStaging = false
|
||||
}
|
||||
|
||||
func (ps *pruningStore) Commit(dbTx model.DBTransaction) error {
|
||||
@@ -104,12 +105,8 @@ func (ps *pruningStore) Commit(dbTx model.DBTransaction) error {
|
||||
ps.pruningPointCandidateCache = ps.pruningPointCandidateStaging
|
||||
}
|
||||
|
||||
if ps.serializedUTXOSetStaging != nil {
|
||||
utxoSetBytes, err := ps.serializeUTXOSetBytes(ps.serializedUTXOSetStaging)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = dbTx.Put(pruningSerializedUTXOSetKey, utxoSetBytes)
|
||||
if ps.startUpdatingPruningPointUTXOSetStaging {
|
||||
err := dbTx.Put(updatingPruningPointUTXOSetKey, []byte{0})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -119,6 +116,49 @@ func (ps *pruningStore) Commit(dbTx model.DBTransaction) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ps *pruningStore) UpdatePruningPointUTXOSet(dbContext model.DBWriter,
|
||||
utxoSetIterator model.ReadOnlyUTXOSetIterator) error {
|
||||
|
||||
// Delete all the old UTXOs from the database
|
||||
deleteCursor, err := dbContext.Cursor(pruningPointUTXOSetBucket)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for ok := deleteCursor.First(); ok; ok = deleteCursor.Next() {
|
||||
key, err := deleteCursor.Key()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = dbContext.Delete(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Insert all the new UTXOs into the database
|
||||
for ok := utxoSetIterator.First(); ok; ok = utxoSetIterator.Next() {
|
||||
outpoint, entry, err := utxoSetIterator.Get()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
serializedOutpoint, err := serializeOutpoint(outpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key := pruningPointUTXOSetBucket.Key(serializedOutpoint)
|
||||
serializedUTXOEntry, err := serializeUTXOEntry(entry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = dbContext.Put(key, serializedUTXOEntry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// PruningPoint gets the current pruning point
|
||||
func (ps *pruningStore) PruningPoint(dbContext model.DBReader) (*externalapi.DomainHash, error) {
|
||||
if ps.pruningPointStaging != nil {
|
||||
@@ -142,24 +182,6 @@ func (ps *pruningStore) PruningPoint(dbContext model.DBReader) (*externalapi.Dom
|
||||
return pruningPoint, nil
|
||||
}
|
||||
|
||||
// PruningPointSerializedUTXOSet returns the serialized UTXO set of the current pruning point
|
||||
func (ps *pruningStore) PruningPointSerializedUTXOSet(dbContext model.DBReader) ([]byte, error) {
|
||||
if ps.serializedUTXOSetStaging != nil {
|
||||
return ps.serializedUTXOSetStaging, nil
|
||||
}
|
||||
|
||||
dbPruningPointUTXOSetBytes, err := dbContext.Get(pruningSerializedUTXOSetKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pruningPointUTXOSet, err := ps.deserializeUTXOSetBytes(dbPruningPointUTXOSetBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pruningPointUTXOSet, nil
|
||||
}
|
||||
|
||||
func (ps *pruningStore) serializeHash(hash *externalapi.DomainHash) ([]byte, error) {
|
||||
return proto.Marshal(serialization.DomainHashToDbHash(hash))
|
||||
}
|
||||
@@ -199,3 +221,51 @@ func (ps *pruningStore) HasPruningPoint(dbContext model.DBReader) (bool, error)
|
||||
|
||||
return dbContext.Has(pruningBlockHashKey)
|
||||
}
|
||||
|
||||
func (ps *pruningStore) PruningPointUTXOs(dbContext model.DBReader,
|
||||
fromOutpoint *externalapi.DomainOutpoint, limit int) ([]*externalapi.OutpointAndUTXOEntryPair, error) {
|
||||
|
||||
cursor, err := dbContext.Cursor(pruningPointUTXOSetBucket)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if fromOutpoint != nil {
|
||||
serializedFromOutpoint, err := serializeOutpoint(fromOutpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
seekKey := pruningPointUTXOSetBucket.Key(serializedFromOutpoint)
|
||||
err = cursor.Seek(seekKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
pruningPointUTXOIterator := ps.newCursorUTXOSetIterator(cursor)
|
||||
|
||||
outpointAndUTXOEntryPairs := make([]*externalapi.OutpointAndUTXOEntryPair, 0, limit)
|
||||
for len(outpointAndUTXOEntryPairs) < limit && pruningPointUTXOIterator.Next() {
|
||||
outpoint, utxoEntry, err := pruningPointUTXOIterator.Get()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
outpointAndUTXOEntryPairs = append(outpointAndUTXOEntryPairs, &externalapi.OutpointAndUTXOEntryPair{
|
||||
Outpoint: outpoint,
|
||||
UTXOEntry: utxoEntry,
|
||||
})
|
||||
}
|
||||
return outpointAndUTXOEntryPairs, nil
|
||||
}
|
||||
|
||||
func (ps *pruningStore) StageStartUpdatingPruningPointUTXOSet() {
|
||||
ps.startUpdatingPruningPointUTXOSetStaging = true
|
||||
}
|
||||
|
||||
func (ps *pruningStore) HadStartedUpdatingPruningPointUTXOSet(dbContext model.DBWriter) (bool, error) {
|
||||
return dbContext.Has(updatingPruningPointUTXOSetKey)
|
||||
}
|
||||
|
||||
func (ps *pruningStore) FinishUpdatingPruningPointUTXOSet(dbContext model.DBWriter) error {
|
||||
return dbContext.Delete(updatingPruningPointUTXOSetKey)
|
||||
}
|
||||
|
||||
@@ -2,15 +2,15 @@ package reachabilitydatastore
|
||||
|
||||
import (
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/database"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/database/serialization"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/dbkeys"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/lrucache"
|
||||
)
|
||||
|
||||
var reachabilityDataBucket = dbkeys.MakeBucket([]byte("reachability-data"))
|
||||
var reachabilityReindexRootKey = dbkeys.MakeBucket().Key([]byte("reachability-reindex-root"))
|
||||
var reachabilityDataBucket = database.MakeBucket([]byte("reachability-data"))
|
||||
var reachabilityReindexRootKey = database.MakeBucket(nil).Key([]byte("reachability-reindex-root"))
|
||||
|
||||
// reachabilityDataStore represents a store of ReachabilityData
|
||||
type reachabilityDataStore struct {
|
||||
|
||||
@@ -2,16 +2,16 @@ package utxodiffstore
|
||||
|
||||
import (
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/database"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/database/serialization"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/dbkeys"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/lrucache"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var utxoDiffBucket = dbkeys.MakeBucket([]byte("utxo-diffs"))
|
||||
var utxoDiffChildBucket = dbkeys.MakeBucket([]byte("utxo-diff-children"))
|
||||
var utxoDiffBucket = database.MakeBucket([]byte("utxo-diffs"))
|
||||
var utxoDiffChildBucket = database.MakeBucket([]byte("utxo-diff-children"))
|
||||
|
||||
// utxoDiffStore represents a store of UTXODiffs
|
||||
type utxoDiffStore struct {
|
||||
|
||||
@@ -357,6 +357,19 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, db infrastructuredat
|
||||
}
|
||||
}
|
||||
|
||||
err = consensusStateManager.RecoverUTXOIfRequired()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = pruningManager.ClearImportedPruningPointData()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = pruningManager.UpdatePruningPointUTXOSetIfRequired()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +45,8 @@ type DBReader interface {
|
||||
|
||||
// DBWriter is an interface to write to the database
|
||||
type DBWriter interface {
|
||||
DBReader
|
||||
|
||||
// Put sets the value for the given key. It overwrites
|
||||
// any previous value for that key.
|
||||
Put(key DBKey, value []byte) error
|
||||
@@ -58,7 +60,6 @@ type DBWriter interface {
|
||||
// access that requires an open database transaction
|
||||
type DBTransaction interface {
|
||||
DBWriter
|
||||
DBReader
|
||||
|
||||
// Rollback rolls back whatever changes were made to the
|
||||
// database within this transaction.
|
||||
@@ -77,7 +78,7 @@ type DBTransaction interface {
|
||||
// DBManager defines the interface of a database that can begin
|
||||
// transactions and read data.
|
||||
type DBManager interface {
|
||||
DBReader
|
||||
DBWriter
|
||||
|
||||
// Begin begins a new database transaction.
|
||||
Begin() (DBTransaction, error)
|
||||
|
||||
@@ -13,9 +13,11 @@ type Consensus interface {
|
||||
|
||||
GetHashesBetween(lowHash, highHash *DomainHash, maxBlueScoreDifference uint64) ([]*DomainHash, error)
|
||||
GetMissingBlockBodyHashes(highHash *DomainHash) ([]*DomainHash, error)
|
||||
GetPruningPointUTXOSet(expectedPruningPointHash *DomainHash) ([]byte, error)
|
||||
GetPruningPointUTXOs(expectedPruningPointHash *DomainHash, fromOutpoint *DomainOutpoint, limit int) ([]*OutpointAndUTXOEntryPair, error)
|
||||
PruningPoint() (*DomainHash, error)
|
||||
ValidateAndInsertPruningPoint(newPruningPoint *DomainBlock, serializedUTXOSet []byte) error
|
||||
ClearImportedPruningPointData() error
|
||||
AppendImportedPruningPointUTXOs(outpointAndUTXOEntryPairs []*OutpointAndUTXOEntryPair) error
|
||||
ValidateAndInsertImportedPruningPoint(newPruningPoint *DomainBlock) error
|
||||
GetVirtualSelectedParent() (*DomainHash, error)
|
||||
CreateBlockLocator(lowHash, highHash *DomainHash, limit uint32) (BlockLocator, error)
|
||||
CreateHeadersSelectedChainBlockLocator(lowHash, highHash *DomainHash) (BlockLocator, error)
|
||||
|
||||
@@ -11,3 +11,10 @@ type UTXOEntry interface {
|
||||
IsCoinbase() bool
|
||||
Equal(other UTXOEntry) bool
|
||||
}
|
||||
|
||||
// OutpointAndUTXOEntryPair is an outpoint along with its
|
||||
// respective UTXO entry
|
||||
type OutpointAndUTXOEntryPair struct {
|
||||
Outpoint *DomainOutpoint
|
||||
UTXOEntry UTXOEntry
|
||||
}
|
||||
|
||||
@@ -7,8 +7,7 @@ type ConsensusStateStore interface {
|
||||
Store
|
||||
IsStaged() bool
|
||||
|
||||
StageVirtualUTXODiff(virtualUTXODiff UTXODiff) error
|
||||
StageVirtualUTXOSet(virtualUTXOSetIterator ReadOnlyUTXOSetIterator) error
|
||||
StageVirtualUTXODiff(virtualUTXODiff UTXODiff)
|
||||
UTXOByOutpoint(dbContext DBReader, outpoint *externalapi.DomainOutpoint) (externalapi.UTXOEntry, error)
|
||||
HasUTXOByOutpoint(dbContext DBReader, outpoint *externalapi.DomainOutpoint) (bool, error)
|
||||
VirtualUTXOSetIterator(dbContext DBReader) (ReadOnlyUTXOSetIterator, error)
|
||||
@@ -18,4 +17,9 @@ type ConsensusStateStore interface {
|
||||
|
||||
StageTips(tipHashes []*externalapi.DomainHash)
|
||||
Tips(dbContext DBReader) ([]*externalapi.DomainHash, error)
|
||||
|
||||
StartImportingPruningPointUTXOSet(dbContext DBWriter) error
|
||||
HadStartedImportingPruningPointUTXOSet(dbContext DBWriter) (bool, error)
|
||||
ImportPruningPointUTXOSetIntoVirtualUTXOSet(dbContext DBWriter, pruningPointUTXOSetIterator ReadOnlyUTXOSetIterator) error
|
||||
FinishImportingPruningPointUTXOSet(dbContext DBWriter) error
|
||||
}
|
||||
|
||||
@@ -5,12 +5,25 @@ import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
// PruningStore represents a store for the current pruning state
|
||||
type PruningStore interface {
|
||||
Store
|
||||
StagePruningPoint(pruningPointBlockHash *externalapi.DomainHash, pruningPointUTXOSetBytes []byte)
|
||||
StagePruningPoint(pruningPointBlockHash *externalapi.DomainHash)
|
||||
StagePruningPointCandidate(candidate *externalapi.DomainHash)
|
||||
IsStaged() bool
|
||||
PruningPointCandidate(dbContext DBReader) (*externalapi.DomainHash, error)
|
||||
HasPruningPointCandidate(dbContext DBReader) (bool, error)
|
||||
PruningPoint(dbContext DBReader) (*externalapi.DomainHash, error)
|
||||
HasPruningPoint(dbContext DBReader) (bool, error)
|
||||
PruningPointSerializedUTXOSet(dbContext DBReader) ([]byte, error)
|
||||
|
||||
StageStartUpdatingPruningPointUTXOSet()
|
||||
HadStartedUpdatingPruningPointUTXOSet(dbContext DBWriter) (bool, error)
|
||||
FinishUpdatingPruningPointUTXOSet(dbContext DBWriter) error
|
||||
UpdatePruningPointUTXOSet(dbContext DBWriter, utxoSetIterator ReadOnlyUTXOSetIterator) error
|
||||
|
||||
ClearImportedPruningPointUTXOs(dbContext DBWriter) error
|
||||
AppendImportedPruningPointUTXOs(dbTx DBTransaction, outpointAndUTXOEntryPairs []*externalapi.OutpointAndUTXOEntryPair) error
|
||||
ImportedPruningPointUTXOIterator(dbContext DBReader) (ReadOnlyUTXOSetIterator, error)
|
||||
ClearImportedPruningPointMultiset(dbContext DBWriter) error
|
||||
ImportedPruningPointMultiset(dbContext DBReader) (Multiset, error)
|
||||
UpdateImportedPruningPointMultiset(dbTx DBTransaction, multiset Multiset) error
|
||||
CommitImportedPruningPointUTXOSet(dbContext DBWriter) error
|
||||
PruningPointUTXOs(dbContext DBReader, fromOutpoint *externalapi.DomainOutpoint, limit int) ([]*externalapi.OutpointAndUTXOEntryPair, error)
|
||||
}
|
||||
|
||||
@@ -5,5 +5,5 @@ import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
// BlockProcessor is responsible for processing incoming blocks
|
||||
type BlockProcessor interface {
|
||||
ValidateAndInsertBlock(block *externalapi.DomainBlock) (*externalapi.BlockInsertionResult, error)
|
||||
ValidateAndInsertPruningPoint(newPruningPoint *externalapi.DomainBlock, serializedUTXOSet []byte) error
|
||||
ValidateAndInsertImportedPruningPoint(newPruningPoint *externalapi.DomainBlock) error
|
||||
}
|
||||
|
||||
@@ -6,8 +6,9 @@ import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
type ConsensusStateManager interface {
|
||||
AddBlock(blockHash *externalapi.DomainHash) (*externalapi.SelectedChainPath, error)
|
||||
PopulateTransactionWithUTXOEntries(transaction *externalapi.DomainTransaction) error
|
||||
UpdatePruningPoint(newPruningPoint *externalapi.DomainBlock, serializedUTXOSet []byte) error
|
||||
ImportPruningPoint(newPruningPoint *externalapi.DomainBlock) error
|
||||
RestorePastUTXOSetIterator(blockHash *externalapi.DomainHash) (ReadOnlyUTXOSetIterator, error)
|
||||
CalculatePastUTXOAndAcceptanceData(blockHash *externalapi.DomainHash) (UTXODiff, externalapi.AcceptanceData, Multiset, error)
|
||||
GetVirtualSelectedParentChainFromBlock(blockHash *externalapi.DomainHash) (*externalapi.SelectedChainPath, error)
|
||||
RecoverUTXOIfRequired() error
|
||||
}
|
||||
|
||||
@@ -6,4 +6,7 @@ import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
type PruningManager interface {
|
||||
UpdatePruningPointByVirtual() error
|
||||
IsValidPruningPoint(blockHash *externalapi.DomainHash) (bool, error)
|
||||
ClearImportedPruningPointData() error
|
||||
AppendImportedPruningPointUTXOs(outpointAndUTXOEntryPairs []*externalapi.OutpointAndUTXOEntryPair) error
|
||||
UpdatePruningPointUTXOSetIfRequired() error
|
||||
}
|
||||
|
||||
@@ -9,5 +9,5 @@ import (
|
||||
type TransactionValidator interface {
|
||||
ValidateTransactionInIsolation(transaction *externalapi.DomainTransaction) error
|
||||
ValidateTransactionInContextAndPopulateMassAndFee(tx *externalapi.DomainTransaction,
|
||||
povTransactionHash *externalapi.DomainHash, selectedParentMedianTime int64) error
|
||||
povBlockHash *externalapi.DomainHash, selectedParentMedianTime int64) error
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ type ReadOnlyUTXOSet interface {
|
||||
// ReadOnlyUTXOSetIterator is an iterator over all entries in a
|
||||
// ReadOnlyUTXOSet
|
||||
type ReadOnlyUTXOSetIterator interface {
|
||||
First() bool
|
||||
Next() bool
|
||||
Get() (outpoint *externalapi.DomainOutpoint, utxoEntry externalapi.UTXOEntry, err error)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package blockbuilder_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/testutils"
|
||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func TestBuildBlockErrorCases(t *testing.T) {
|
||||
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
|
||||
factory := consensus.NewFactory()
|
||||
testConsensus, teardown, err := factory.NewTestConsensus(
|
||||
params, false, "TestBlockBuilderErrorCases")
|
||||
if err != nil {
|
||||
t.Fatalf("Error initializing consensus for: %+v", err)
|
||||
}
|
||||
defer teardown(false)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
coinbaseData *externalapi.DomainCoinbaseData
|
||||
transactions []*externalapi.DomainTransaction
|
||||
expectedErrorType error
|
||||
}{
|
||||
{
|
||||
"scriptPublicKey too long",
|
||||
&externalapi.DomainCoinbaseData{
|
||||
ScriptPublicKey: &externalapi.ScriptPublicKey{
|
||||
Script: make([]byte, params.CoinbasePayloadScriptPublicKeyMaxLength+1),
|
||||
Version: 0,
|
||||
},
|
||||
ExtraData: nil,
|
||||
},
|
||||
nil,
|
||||
ruleerrors.ErrBadCoinbasePayloadLen,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
_, err = testConsensus.BlockBuilder().BuildBlock(test.coinbaseData, test.transactions)
|
||||
if err == nil {
|
||||
t.Errorf("%s: No error from BuildBlock", test.name)
|
||||
return
|
||||
}
|
||||
if test.expectedErrorType != nil && !errors.Is(err, test.expectedErrorType) {
|
||||
t.Errorf("%s: Expected error '%s', but got '%s'", test.name, test.expectedErrorType, err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -137,9 +137,9 @@ func (bp *blockProcessor) ValidateAndInsertBlock(block *externalapi.DomainBlock)
|
||||
return bp.validateAndInsertBlock(block, false)
|
||||
}
|
||||
|
||||
func (bp *blockProcessor) ValidateAndInsertPruningPoint(newPruningPoint *externalapi.DomainBlock, serializedUTXOSet []byte) error {
|
||||
onEnd := logger.LogAndMeasureExecutionTime(log, "ValidateAndInsertPruningPoint")
|
||||
func (bp *blockProcessor) ValidateAndInsertImportedPruningPoint(newPruningPoint *externalapi.DomainBlock) error {
|
||||
onEnd := logger.LogAndMeasureExecutionTime(log, "ValidateAndInsertImportedPruningPoint")
|
||||
defer onEnd()
|
||||
|
||||
return bp.validateAndInsertPruningPoint(newPruningPoint, serializedUTXOSet)
|
||||
return bp.validateAndInsertImportedPruningPoint(newPruningPoint)
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ func (bp *blockProcessor) validateAndInsertBlock(block *externalapi.DomainBlock,
|
||||
if !isHeaderOnlyBlock {
|
||||
// There's no need to update the consensus state manager when
|
||||
// processing the pruning point since it was already handled
|
||||
// in consensusStateManager.UpdatePruningPoint
|
||||
// in consensusStateManager.ImportPruningPoint
|
||||
if !isPruningPoint {
|
||||
// Attempt to add the block to the virtual
|
||||
selectedParentChainChanges, err = bp.consensusStateManager.AddBlock(blockHash)
|
||||
@@ -117,6 +117,11 @@ func (bp *blockProcessor) validateAndInsertBlock(block *externalapi.DomainBlock,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = bp.pruningManager.UpdatePruningPointUTXOSetIfRequired()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Debug(logger.NewLogClosure(func() string {
|
||||
hashrate := difficulty.GetHashrateString(difficulty.CompactToBig(block.Header.Bits()), bp.targetTimePerBlock)
|
||||
return fmt.Sprintf("Block %s validated and inserted, network hashrate: %s", blockHash, hashrate)
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (bp *blockProcessor) validateAndInsertPruningPoint(newPruningPoint *externalapi.DomainBlock, serializedUTXOSet []byte) error {
|
||||
func (bp *blockProcessor) validateAndInsertImportedPruningPoint(newPruningPoint *externalapi.DomainBlock) error {
|
||||
log.Info("Checking that the given pruning point is the expected pruning point")
|
||||
|
||||
newPruningPointHash := consensushashing.BlockHash(newPruningPoint)
|
||||
@@ -29,7 +29,7 @@ func (bp *blockProcessor) validateAndInsertPruningPoint(newPruningPoint *externa
|
||||
}
|
||||
|
||||
log.Infof("Updating consensus state manager according to the new pruning point %s", newPruningPointHash)
|
||||
err = bp.consensusStateManager.UpdatePruningPoint(newPruningPoint, serializedUTXOSet)
|
||||
err = bp.consensusStateManager.ImportPruningPoint(newPruningPoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,686 @@
|
||||
package blockprocessor_test
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/testapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/testutils"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/txscript"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/utxo"
|
||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||
"github.com/pkg/errors"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func addBlock(tcSyncer, tcSyncee testapi.TestConsensus, parentHashes []*externalapi.DomainHash, t *testing.T) *externalapi.DomainHash {
|
||||
block, _, err := tcSyncer.BuildBlockWithParents(parentHashes, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("BuildBlockWithParents: %+v", err)
|
||||
}
|
||||
|
||||
_, err = tcSyncer.ValidateAndInsertBlock(block)
|
||||
if err != nil {
|
||||
t.Fatalf("ValidateAndInsertBlock: %+v", err)
|
||||
}
|
||||
|
||||
_, err = tcSyncee.ValidateAndInsertBlock(&externalapi.DomainBlock{
|
||||
Header: block.Header,
|
||||
Transactions: nil,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("ValidateAndInsertBlock: %+v", err)
|
||||
}
|
||||
|
||||
return consensushashing.BlockHash(block)
|
||||
}
|
||||
|
||||
func TestValidateAndInsertImportedPruningPoint(t *testing.T) {
|
||||
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
|
||||
// This is done to reduce the pruning depth to 6 blocks
|
||||
finalityDepth := 3
|
||||
params.FinalityDuration = time.Duration(finalityDepth) * params.TargetTimePerBlock
|
||||
params.K = 0
|
||||
|
||||
factory := consensus.NewFactory()
|
||||
|
||||
tcSyncer, teardownSyncer, err := factory.NewTestConsensus(params, false, "TestValidateAndInsertPruningPointSyncer")
|
||||
if err != nil {
|
||||
t.Fatalf("Error setting up tcSyncer: %+v", err)
|
||||
}
|
||||
defer teardownSyncer(false)
|
||||
|
||||
tcSyncee, teardownSyncee, err := factory.NewTestConsensus(params, false, "TestValidateAndInsertPruningPointSyncee")
|
||||
if err != nil {
|
||||
t.Fatalf("Error setting up tcSyncee: %+v", err)
|
||||
}
|
||||
defer teardownSyncee(false)
|
||||
|
||||
tipHash := params.GenesisHash
|
||||
for i := 0; i < finalityDepth-2; i++ {
|
||||
tipHash = addBlock(tcSyncer, tcSyncee, []*externalapi.DomainHash{tipHash}, t)
|
||||
}
|
||||
|
||||
// Add block in the anticone of the pruning point to test such situation
|
||||
pruningPointAnticoneBlock := addBlock(tcSyncer, tcSyncee, []*externalapi.DomainHash{tipHash}, t)
|
||||
tipHash = addBlock(tcSyncer, tcSyncee, []*externalapi.DomainHash{tipHash}, t)
|
||||
nextPruningPoint := addBlock(tcSyncer, tcSyncee, []*externalapi.DomainHash{tipHash}, t)
|
||||
|
||||
tipHash = addBlock(tcSyncer, tcSyncee, []*externalapi.DomainHash{pruningPointAnticoneBlock, nextPruningPoint}, t)
|
||||
|
||||
// Add blocks until the pruning point changes
|
||||
for {
|
||||
tipHash = addBlock(tcSyncer, tcSyncee, []*externalapi.DomainHash{tipHash}, t)
|
||||
|
||||
pruningPoint, err := tcSyncer.PruningPoint()
|
||||
if err != nil {
|
||||
t.Fatalf("PruningPoint: %+v", err)
|
||||
}
|
||||
|
||||
if !pruningPoint.Equal(params.GenesisHash) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
pruningPoint, err := tcSyncer.PruningPoint()
|
||||
if err != nil {
|
||||
t.Fatalf("PruningPoint: %+v", err)
|
||||
}
|
||||
|
||||
if !pruningPoint.Equal(nextPruningPoint) {
|
||||
t.Fatalf("Unexpected pruning point %s", pruningPoint)
|
||||
}
|
||||
|
||||
pruningPointUTXOs, err := tcSyncer.GetPruningPointUTXOs(pruningPoint, nil, 1000)
|
||||
if err != nil {
|
||||
t.Fatalf("GetPruningPointUTXOs: %+v", err)
|
||||
}
|
||||
err = tcSyncee.AppendImportedPruningPointUTXOs(pruningPointUTXOs)
|
||||
if err != nil {
|
||||
t.Fatalf("AppendImportedPruningPointUTXOs: %+v", err)
|
||||
}
|
||||
|
||||
tip, err := tcSyncer.GetBlock(tipHash)
|
||||
if err != nil {
|
||||
t.Fatalf("GetBlock: %+v", err)
|
||||
}
|
||||
|
||||
// Check that ValidateAndInsertImportedPruningPoint fails for invalid pruning point
|
||||
err = tcSyncee.ValidateAndInsertImportedPruningPoint(tip)
|
||||
if !errors.Is(err, ruleerrors.ErrUnexpectedPruningPoint) {
|
||||
t.Fatalf("Unexpected error: %+v", err)
|
||||
}
|
||||
|
||||
pruningPointBlock, err := tcSyncer.GetBlock(pruningPoint)
|
||||
if err != nil {
|
||||
t.Fatalf("GetBlock: %+v", err)
|
||||
}
|
||||
|
||||
invalidPruningPointBlock := pruningPointBlock.Clone()
|
||||
invalidPruningPointBlock.Transactions[0].Version += 1
|
||||
|
||||
// Check that ValidateAndInsertImportedPruningPoint fails for invalid block
|
||||
err = tcSyncee.ValidateAndInsertImportedPruningPoint(invalidPruningPointBlock)
|
||||
if !errors.Is(err, ruleerrors.ErrBadMerkleRoot) {
|
||||
t.Fatalf("Unexpected error: %+v", err)
|
||||
}
|
||||
|
||||
err = tcSyncee.ClearImportedPruningPointData()
|
||||
if err != nil {
|
||||
t.Fatalf("ClearImportedPruningPointData: %+v", err)
|
||||
}
|
||||
err = tcSyncee.AppendImportedPruningPointUTXOs(makeFakeUTXOs())
|
||||
if err != nil {
|
||||
t.Fatalf("AppendImportedPruningPointUTXOs: %+v", err)
|
||||
}
|
||||
|
||||
// Check that ValidateAndInsertImportedPruningPoint fails if the UTXO commitment doesn't fit the provided UTXO set.
|
||||
err = tcSyncee.ValidateAndInsertImportedPruningPoint(pruningPointBlock)
|
||||
if !errors.Is(err, ruleerrors.ErrBadPruningPointUTXOSet) {
|
||||
t.Fatalf("Unexpected error: %+v", err)
|
||||
}
|
||||
|
||||
err = tcSyncee.ClearImportedPruningPointData()
|
||||
if err != nil {
|
||||
t.Fatalf("ClearImportedPruningPointData: %+v", err)
|
||||
}
|
||||
err = tcSyncee.AppendImportedPruningPointUTXOs(pruningPointUTXOs)
|
||||
if err != nil {
|
||||
t.Fatalf("AppendImportedPruningPointUTXOs: %+v", err)
|
||||
}
|
||||
|
||||
// Check that ValidateAndInsertImportedPruningPoint works given the right arguments.
|
||||
err = tcSyncee.ValidateAndInsertImportedPruningPoint(pruningPointBlock)
|
||||
if err != nil {
|
||||
t.Fatalf("ValidateAndInsertImportedPruningPoint: %+v", err)
|
||||
}
|
||||
|
||||
virtualSelectedParent, err := tcSyncer.GetVirtualSelectedParent()
|
||||
if err != nil {
|
||||
t.Fatalf("GetVirtualSelectedParent: %+v", err)
|
||||
}
|
||||
|
||||
missingBlockBodyHashes, err := tcSyncee.GetMissingBlockBodyHashes(virtualSelectedParent)
|
||||
if err != nil {
|
||||
t.Fatalf("GetMissingBlockBodyHashes: %+v", err)
|
||||
}
|
||||
|
||||
for _, missingHash := range missingBlockBodyHashes {
|
||||
block, err := tcSyncer.GetBlock(missingHash)
|
||||
if err != nil {
|
||||
t.Fatalf("GetBlock: %+v", err)
|
||||
}
|
||||
|
||||
_, err = tcSyncee.ValidateAndInsertBlock(block)
|
||||
if err != nil {
|
||||
t.Fatalf("ValidateAndInsertBlock: %+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
synceeTips, err := tcSyncee.Tips()
|
||||
if err != nil {
|
||||
t.Fatalf("Tips: %+v", err)
|
||||
}
|
||||
|
||||
syncerTips, err := tcSyncer.Tips()
|
||||
if err != nil {
|
||||
t.Fatalf("Tips: %+v", err)
|
||||
}
|
||||
|
||||
if !externalapi.HashesEqual(synceeTips, syncerTips) {
|
||||
t.Fatalf("Syncee's tips are %s while syncer's are %s", synceeTips, syncerTips)
|
||||
}
|
||||
|
||||
synceePruningPoint, err := tcSyncee.PruningPoint()
|
||||
if err != nil {
|
||||
t.Fatalf("PruningPoint: %+v", err)
|
||||
}
|
||||
|
||||
if !synceePruningPoint.Equal(pruningPoint) {
|
||||
t.Fatalf("The syncee pruning point has not changed as exepcted")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestValidateAndInsertPruningPointWithSideBlocks makes sure that when a node applies a UTXO-Set downloaded during
|
||||
// IBD, while it already has a non-empty UTXO-Set originating from blocks mined on top of genesis - the resulting
|
||||
// UTXO set is correct
|
||||
func TestValidateAndInsertPruningPointWithSideBlocks(t *testing.T) {
|
||||
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
|
||||
// This is done to reduce the pruning depth to 6 blocks
|
||||
finalityDepth := 3
|
||||
params.FinalityDuration = time.Duration(finalityDepth) * params.TargetTimePerBlock
|
||||
params.K = 0
|
||||
|
||||
factory := consensus.NewFactory()
|
||||
|
||||
tcSyncer, teardownSyncer, err := factory.NewTestConsensus(params, false, "TestValidateAndInsertPruningPointSyncer")
|
||||
if err != nil {
|
||||
t.Fatalf("Error setting up tcSyncer: %+v", err)
|
||||
}
|
||||
defer teardownSyncer(false)
|
||||
|
||||
tcSyncee, teardownSyncee, err := factory.NewTestConsensus(params, false, "TestValidateAndInsertPruningPointSyncee")
|
||||
if err != nil {
|
||||
t.Fatalf("Error setting up tcSyncee: %+v", err)
|
||||
}
|
||||
defer teardownSyncee(false)
|
||||
|
||||
// Mine 2 block in the syncee on top of genesis
|
||||
side, _, err := tcSyncee.AddBlock([]*externalapi.DomainHash{params.GenesisHash}, &externalapi.DomainCoinbaseData{ScriptPublicKey: &externalapi.ScriptPublicKey{}, ExtraData: []byte{1, 2}}, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, _, err = tcSyncee.AddBlock([]*externalapi.DomainHash{side}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tipHash := params.GenesisHash
|
||||
for i := 0; i < finalityDepth-2; i++ {
|
||||
tipHash = addBlock(tcSyncer, tcSyncee, []*externalapi.DomainHash{tipHash}, t)
|
||||
}
|
||||
|
||||
tipHash = addBlock(tcSyncer, tcSyncee, []*externalapi.DomainHash{tipHash}, t)
|
||||
nextPruningPoint := addBlock(tcSyncer, tcSyncee, []*externalapi.DomainHash{tipHash}, t)
|
||||
|
||||
tipHash = addBlock(tcSyncer, tcSyncee, []*externalapi.DomainHash{nextPruningPoint}, t)
|
||||
|
||||
// Add blocks until the pruning point changes
|
||||
for {
|
||||
tipHash = addBlock(tcSyncer, tcSyncee, []*externalapi.DomainHash{tipHash}, t)
|
||||
|
||||
pruningPoint, err := tcSyncer.PruningPoint()
|
||||
if err != nil {
|
||||
t.Fatalf("PruningPoint: %+v", err)
|
||||
}
|
||||
|
||||
if !pruningPoint.Equal(params.GenesisHash) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
pruningPoint, err := tcSyncer.PruningPoint()
|
||||
if err != nil {
|
||||
t.Fatalf("PruningPoint: %+v", err)
|
||||
}
|
||||
|
||||
if !pruningPoint.Equal(nextPruningPoint) {
|
||||
t.Fatalf("Unexpected pruning point %s", pruningPoint)
|
||||
}
|
||||
|
||||
pruningPointUTXOs, err := tcSyncer.GetPruningPointUTXOs(pruningPoint, nil, 1000)
|
||||
if err != nil {
|
||||
t.Fatalf("GetPruningPointUTXOs: %+v", err)
|
||||
}
|
||||
err = tcSyncee.AppendImportedPruningPointUTXOs(pruningPointUTXOs)
|
||||
if err != nil {
|
||||
t.Fatalf("AppendImportedPruningPointUTXOs: %+v", err)
|
||||
}
|
||||
|
||||
// Check that ValidateAndInsertPruningPoint works.
|
||||
pruningPointBlock, err := tcSyncer.GetBlock(pruningPoint)
|
||||
if err != nil {
|
||||
t.Fatalf("GetBlock: %+v", err)
|
||||
}
|
||||
err = tcSyncee.ValidateAndInsertImportedPruningPoint(pruningPointBlock)
|
||||
if err != nil {
|
||||
t.Fatalf("ValidateAndInsertPruningPoint: %+v", err)
|
||||
}
|
||||
|
||||
// Insert the rest of the blocks atop pruning point
|
||||
virtualSelectedParent, err := tcSyncer.GetVirtualSelectedParent()
|
||||
if err != nil {
|
||||
t.Fatalf("GetVirtualSelectedParent: %+v", err)
|
||||
}
|
||||
|
||||
missingBlockBodyHashes, err := tcSyncee.GetMissingBlockBodyHashes(virtualSelectedParent)
|
||||
if err != nil {
|
||||
t.Fatalf("GetMissingBlockBodyHashes: %+v", err)
|
||||
}
|
||||
|
||||
for _, missingHash := range missingBlockBodyHashes {
|
||||
block, err := tcSyncer.GetBlock(missingHash)
|
||||
if err != nil {
|
||||
t.Fatalf("GetBlock: %+v", err)
|
||||
}
|
||||
|
||||
_, err = tcSyncee.ValidateAndInsertBlock(block)
|
||||
if err != nil {
|
||||
t.Fatalf("ValidateAndInsertBlock: %+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that syncee and syncer tips are equal
|
||||
synceeTips, err := tcSyncee.Tips()
|
||||
if err != nil {
|
||||
t.Fatalf("Tips: %+v", err)
|
||||
}
|
||||
|
||||
syncerTips, err := tcSyncer.Tips()
|
||||
if err != nil {
|
||||
t.Fatalf("Tips: %+v", err)
|
||||
}
|
||||
|
||||
if !externalapi.HashesEqual(synceeTips, syncerTips) {
|
||||
t.Fatalf("Syncee's tips are %s while syncer's are %s", synceeTips, syncerTips)
|
||||
}
|
||||
|
||||
// Verify that syncee and syncer pruning points are equal
|
||||
synceePruningPoint, err := tcSyncee.PruningPoint()
|
||||
if err != nil {
|
||||
t.Fatalf("PruningPoint: %+v", err)
|
||||
}
|
||||
|
||||
if !synceePruningPoint.Equal(pruningPoint) {
|
||||
t.Fatalf("The syncee pruning point has not changed as exepcted")
|
||||
}
|
||||
|
||||
pruningPointOld := pruningPoint
|
||||
|
||||
// Add blocks until the pruning point moves, and verify it moved to the same point on both syncer and syncee
|
||||
for {
|
||||
block, _, err := tcSyncer.BuildBlockWithParents([]*externalapi.DomainHash{tipHash}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("BuildBlockWithParents: %+v", err)
|
||||
}
|
||||
|
||||
_, err = tcSyncer.ValidateAndInsertBlock(block)
|
||||
if err != nil {
|
||||
t.Fatalf("ValidateAndInsertBlock: %+v", err)
|
||||
}
|
||||
|
||||
_, err = tcSyncee.ValidateAndInsertBlock(block)
|
||||
if err != nil {
|
||||
t.Fatalf("ValidateAndInsertBlock: %+v", err)
|
||||
}
|
||||
|
||||
tipHash = consensushashing.BlockHash(block)
|
||||
|
||||
pruningPoint, err = tcSyncer.PruningPoint()
|
||||
if err != nil {
|
||||
t.Fatalf("PruningPoint: %+v", err)
|
||||
}
|
||||
|
||||
if !pruningPoint.Equal(pruningPointOld) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
synceePruningPoint, err = tcSyncee.PruningPoint()
|
||||
if err != nil {
|
||||
t.Fatalf("PruningPoint: %+v", err)
|
||||
}
|
||||
|
||||
if !synceePruningPoint.Equal(pruningPoint) {
|
||||
t.Fatalf("The syncee pruning point(%s) is not equal to syncer pruning point (%s) after it moved. "+
|
||||
"pruning point before move: %s", synceePruningPoint, pruningPoint, pruningPointOld)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func makeFakeUTXOs() []*externalapi.OutpointAndUTXOEntryPair {
|
||||
return []*externalapi.OutpointAndUTXOEntryPair{
|
||||
{
|
||||
Outpoint: &externalapi.DomainOutpoint{
|
||||
TransactionID: externalapi.DomainTransactionID{},
|
||||
Index: 0,
|
||||
},
|
||||
UTXOEntry: utxo.NewUTXOEntry(
|
||||
0,
|
||||
&externalapi.ScriptPublicKey{
|
||||
Script: nil,
|
||||
Version: 0,
|
||||
},
|
||||
false,
|
||||
0,
|
||||
),
|
||||
},
|
||||
{
|
||||
Outpoint: &externalapi.DomainOutpoint{
|
||||
TransactionID: externalapi.DomainTransactionID{},
|
||||
Index: 1,
|
||||
},
|
||||
UTXOEntry: utxo.NewUTXOEntry(
|
||||
2,
|
||||
&externalapi.ScriptPublicKey{
|
||||
Script: nil,
|
||||
Version: 0,
|
||||
},
|
||||
true,
|
||||
3,
|
||||
),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPruningPointUTXOs(t *testing.T) {
|
||||
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
|
||||
// This is done to reduce the pruning depth to 8 blocks
|
||||
finalityDepth := 4
|
||||
params.FinalityDuration = time.Duration(finalityDepth) * params.TargetTimePerBlock
|
||||
params.K = 0
|
||||
|
||||
params.BlockCoinbaseMaturity = 0
|
||||
|
||||
factory := consensus.NewFactory()
|
||||
testConsensus, teardown, err := factory.NewTestConsensus(params, false, "TestGetPruningPointUTXOs")
|
||||
if err != nil {
|
||||
t.Fatalf("Error setting up testConsensus: %+v", err)
|
||||
}
|
||||
defer teardown(false)
|
||||
|
||||
// Create a block that accepts the genesis coinbase so that we won't have script problems down the line
|
||||
emptyCoinbase := &externalapi.DomainCoinbaseData{
|
||||
ScriptPublicKey: &externalapi.ScriptPublicKey{
|
||||
Script: nil,
|
||||
Version: 0,
|
||||
},
|
||||
}
|
||||
blockAboveGeneis, err := testConsensus.BuildBlock(emptyCoinbase, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Error building block above genesis: %+v", err)
|
||||
}
|
||||
_, err = testConsensus.ValidateAndInsertBlock(blockAboveGeneis)
|
||||
if err != nil {
|
||||
t.Fatalf("Error validating and inserting block above genesis: %+v", err)
|
||||
}
|
||||
|
||||
// Create a block whose coinbase we could spend
|
||||
scriptPublicKey, redeemScript := testutils.OpTrueScript()
|
||||
coinbaseData := &externalapi.DomainCoinbaseData{ScriptPublicKey: scriptPublicKey}
|
||||
blockWithSpendableCoinbase, err := testConsensus.BuildBlock(coinbaseData, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Error building block with spendable coinbase: %+v", err)
|
||||
}
|
||||
_, err = testConsensus.ValidateAndInsertBlock(blockWithSpendableCoinbase)
|
||||
if err != nil {
|
||||
t.Fatalf("Error validating and inserting block with spendable coinbase: %+v", err)
|
||||
}
|
||||
|
||||
// Create a transaction that adds a lot of UTXOs to the UTXO set
|
||||
transactionToSpend := blockWithSpendableCoinbase.Transactions[0]
|
||||
signatureScript, err := txscript.PayToScriptHashSignatureScript(redeemScript, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating signature script: %+v", err)
|
||||
}
|
||||
input := &externalapi.DomainTransactionInput{
|
||||
PreviousOutpoint: externalapi.DomainOutpoint{
|
||||
TransactionID: *consensushashing.TransactionID(transactionToSpend),
|
||||
Index: 0,
|
||||
},
|
||||
SignatureScript: signatureScript,
|
||||
Sequence: constants.MaxTxInSequenceNum,
|
||||
}
|
||||
|
||||
outputs := make([]*externalapi.DomainTransactionOutput, 1125)
|
||||
for i := 0; i < len(outputs); i++ {
|
||||
outputs[i] = &externalapi.DomainTransactionOutput{
|
||||
ScriptPublicKey: scriptPublicKey,
|
||||
Value: 10000,
|
||||
}
|
||||
}
|
||||
spendingTransaction := &externalapi.DomainTransaction{
|
||||
Version: constants.MaxTransactionVersion,
|
||||
Inputs: []*externalapi.DomainTransactionInput{input},
|
||||
Outputs: outputs,
|
||||
Payload: []byte{},
|
||||
}
|
||||
|
||||
// Create a block with that includes the above transaction
|
||||
includingBlock, err := testConsensus.BuildBlock(emptyCoinbase, []*externalapi.DomainTransaction{spendingTransaction})
|
||||
if err != nil {
|
||||
t.Fatalf("Error building including block: %+v", err)
|
||||
}
|
||||
_, err = testConsensus.ValidateAndInsertBlock(includingBlock)
|
||||
if err != nil {
|
||||
t.Fatalf("Error validating and inserting including block: %+v", err)
|
||||
}
|
||||
|
||||
// Add enough blocks to move the pruning point
|
||||
for {
|
||||
block, err := testConsensus.BuildBlock(emptyCoinbase, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Error building block: %+v", err)
|
||||
}
|
||||
_, err = testConsensus.ValidateAndInsertBlock(block)
|
||||
if err != nil {
|
||||
t.Fatalf("Error validating and inserting block: %+v", err)
|
||||
}
|
||||
|
||||
pruningPoint, err := testConsensus.PruningPoint()
|
||||
if err != nil {
|
||||
t.Fatalf("Error getting the pruning point: %+v", err)
|
||||
}
|
||||
if !pruningPoint.Equal(params.GenesisHash) {
|
||||
break
|
||||
}
|
||||
}
|
||||
pruningPoint, err := testConsensus.PruningPoint()
|
||||
if err != nil {
|
||||
t.Fatalf("Error getting the pruning point: %+v", err)
|
||||
}
|
||||
|
||||
// Get pruning point UTXOs in a loop
|
||||
var allOutpointAndUTXOEntryPairs []*externalapi.OutpointAndUTXOEntryPair
|
||||
step := 100
|
||||
var fromOutpoint *externalapi.DomainOutpoint
|
||||
for {
|
||||
outpointAndUTXOEntryPairs, err := testConsensus.GetPruningPointUTXOs(pruningPoint, fromOutpoint, step)
|
||||
if err != nil {
|
||||
t.Fatalf("Error getting pruning point UTXOs: %+v", err)
|
||||
}
|
||||
allOutpointAndUTXOEntryPairs = append(allOutpointAndUTXOEntryPairs, outpointAndUTXOEntryPairs...)
|
||||
fromOutpoint = outpointAndUTXOEntryPairs[len(outpointAndUTXOEntryPairs)-1].Outpoint
|
||||
|
||||
if len(outpointAndUTXOEntryPairs) < step {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure the length of the UTXOs is exactly spendingTransaction.Outputs + 2 coinbase outputs
|
||||
if len(allOutpointAndUTXOEntryPairs) != len(outputs)+2 {
|
||||
t.Fatalf("Returned an unexpected amount of UTXOs. "+
|
||||
"Want: %d, got: %d", len(outputs)+2, len(allOutpointAndUTXOEntryPairs))
|
||||
}
|
||||
|
||||
// Make sure all spendingTransaction.Outputs are in the returned UTXOs
|
||||
spendingTransactionID := consensushashing.TransactionID(spendingTransaction)
|
||||
for i := range outputs {
|
||||
found := false
|
||||
for _, outpointAndUTXOEntryPair := range allOutpointAndUTXOEntryPairs {
|
||||
outpoint := outpointAndUTXOEntryPair.Outpoint
|
||||
if outpoint.TransactionID == *spendingTransactionID && outpoint.Index == uint32(i) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Fatalf("Outpoint %s:%d not found amongst the returned UTXOs", spendingTransactionID, i)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkGetPruningPointUTXOs(b *testing.B) {
|
||||
params := dagconfig.DevnetParams
|
||||
|
||||
// This is done to reduce the pruning depth to 200 blocks
|
||||
finalityDepth := 100
|
||||
params.FinalityDuration = time.Duration(finalityDepth) * params.TargetTimePerBlock
|
||||
params.K = 0
|
||||
|
||||
params.SkipProofOfWork = true
|
||||
params.BlockCoinbaseMaturity = 0
|
||||
|
||||
factory := consensus.NewFactory()
|
||||
testConsensus, teardown, err := factory.NewTestConsensus(¶ms, false, "TestGetPruningPointUTXOs")
|
||||
if err != nil {
|
||||
b.Fatalf("Error setting up testConsensus: %+v", err)
|
||||
}
|
||||
defer teardown(false)
|
||||
|
||||
// Create a block whose coinbase we could spend
|
||||
scriptPublicKey, redeemScript := testutils.OpTrueScript()
|
||||
coinbaseData := &externalapi.DomainCoinbaseData{ScriptPublicKey: scriptPublicKey}
|
||||
blockWithSpendableCoinbase, err := testConsensus.BuildBlock(coinbaseData, nil)
|
||||
if err != nil {
|
||||
b.Fatalf("Error building block with spendable coinbase: %+v", err)
|
||||
}
|
||||
_, err = testConsensus.ValidateAndInsertBlock(blockWithSpendableCoinbase)
|
||||
if err != nil {
|
||||
b.Fatalf("Error validating and inserting block with spendable coinbase: %+v", err)
|
||||
}
|
||||
|
||||
addBlockWithLotsOfOutputs := func(b *testing.B, transactionToSpend *externalapi.DomainTransaction) *externalapi.DomainBlock {
|
||||
// Create a transaction that adds a lot of UTXOs to the UTXO set
|
||||
signatureScript, err := txscript.PayToScriptHashSignatureScript(redeemScript, nil)
|
||||
if err != nil {
|
||||
b.Fatalf("Error creating signature script: %+v", err)
|
||||
}
|
||||
input := &externalapi.DomainTransactionInput{
|
||||
PreviousOutpoint: externalapi.DomainOutpoint{
|
||||
TransactionID: *consensushashing.TransactionID(transactionToSpend),
|
||||
Index: 0,
|
||||
},
|
||||
SignatureScript: signatureScript,
|
||||
Sequence: constants.MaxTxInSequenceNum,
|
||||
}
|
||||
outputs := make([]*externalapi.DomainTransactionOutput, 1125)
|
||||
for i := 0; i < len(outputs); i++ {
|
||||
outputs[i] = &externalapi.DomainTransactionOutput{
|
||||
ScriptPublicKey: scriptPublicKey,
|
||||
Value: 10000,
|
||||
}
|
||||
}
|
||||
transaction := &externalapi.DomainTransaction{
|
||||
Version: constants.MaxTransactionVersion,
|
||||
Inputs: []*externalapi.DomainTransactionInput{input},
|
||||
Outputs: outputs,
|
||||
Payload: []byte{},
|
||||
}
|
||||
|
||||
// Create a block that includes the above transaction
|
||||
block, err := testConsensus.BuildBlock(coinbaseData, []*externalapi.DomainTransaction{transaction})
|
||||
if err != nil {
|
||||
b.Fatalf("Error building block: %+v", err)
|
||||
}
|
||||
_, err = testConsensus.ValidateAndInsertBlock(block)
|
||||
if err != nil {
|
||||
b.Fatalf("Error validating and inserting block: %+v", err)
|
||||
}
|
||||
|
||||
return block
|
||||
}
|
||||
|
||||
// Add finalityDepth blocks, each containing lots of outputs
|
||||
tip := blockWithSpendableCoinbase
|
||||
for i := 0; i < finalityDepth; i++ {
|
||||
tip = addBlockWithLotsOfOutputs(b, tip.Transactions[0])
|
||||
}
|
||||
|
||||
// Add enough blocks to move the pruning point
|
||||
for {
|
||||
block, err := testConsensus.BuildBlock(coinbaseData, nil)
|
||||
if err != nil {
|
||||
b.Fatalf("Error building block: %+v", err)
|
||||
}
|
||||
_, err = testConsensus.ValidateAndInsertBlock(block)
|
||||
if err != nil {
|
||||
b.Fatalf("Error validating and inserting block: %+v", err)
|
||||
}
|
||||
|
||||
pruningPoint, err := testConsensus.PruningPoint()
|
||||
if err != nil {
|
||||
b.Fatalf("Error getting the pruning point: %+v", err)
|
||||
}
|
||||
if !pruningPoint.Equal(params.GenesisHash) {
|
||||
break
|
||||
}
|
||||
}
|
||||
pruningPoint, err := testConsensus.PruningPoint()
|
||||
if err != nil {
|
||||
b.Fatalf("Error getting the pruning point: %+v", err)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
// Get pruning point UTXOs in a loop
|
||||
step := 100
|
||||
var fromOutpoint *externalapi.DomainOutpoint
|
||||
for {
|
||||
outpointAndUTXOEntryPairs, err := testConsensus.GetPruningPointUTXOs(pruningPoint, fromOutpoint, step)
|
||||
if err != nil {
|
||||
b.Fatalf("Error getting pruning point UTXOs: %+v", err)
|
||||
}
|
||||
fromOutpoint = outpointAndUTXOEntryPairs[len(outpointAndUTXOEntryPairs)-1].Outpoint
|
||||
|
||||
if len(outpointAndUTXOEntryPairs) < step {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
type coinbaseManager struct {
|
||||
subsidyReductionInterval uint64
|
||||
baseSubsidy uint64
|
||||
coinbasePayloadScriptPublicKeyMaxLength uint64
|
||||
coinbasePayloadScriptPublicKeyMaxLength uint8
|
||||
|
||||
databaseContext model.DBReader
|
||||
ghostdagDataStore model.GHOSTDAGDataStore
|
||||
@@ -129,7 +129,7 @@ func New(
|
||||
|
||||
subsidyReductionInterval uint64,
|
||||
baseSubsidy uint64,
|
||||
coinbasePayloadScriptPublicKeyMaxLength uint64,
|
||||
coinbasePayloadScriptPublicKeyMaxLength uint8,
|
||||
|
||||
ghostdagDataStore model.GHOSTDAGDataStore,
|
||||
acceptanceDataStore model.AcceptanceDataStore) model.CoinbaseManager {
|
||||
|
||||
@@ -2,7 +2,6 @@ package coinbasemanager
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"math"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
|
||||
@@ -17,16 +16,13 @@ const lengthOfVersionScriptPubKey = uint16Len
|
||||
// serializeCoinbasePayload builds the coinbase payload based on the provided scriptPubKey and extra data.
|
||||
func (c *coinbaseManager) serializeCoinbasePayload(blueScore uint64, coinbaseData *externalapi.DomainCoinbaseData) ([]byte, error) {
|
||||
scriptLengthOfScriptPubKey := len(coinbaseData.ScriptPublicKey.Script)
|
||||
if uint64(scriptLengthOfScriptPubKey) > c.coinbasePayloadScriptPublicKeyMaxLength {
|
||||
if scriptLengthOfScriptPubKey > int(c.coinbasePayloadScriptPublicKeyMaxLength) {
|
||||
return nil, errors.Wrapf(ruleerrors.ErrBadCoinbasePayloadLen, "coinbase's payload script public key is "+
|
||||
"longer than the max allowed length of %d", c.coinbasePayloadScriptPublicKeyMaxLength)
|
||||
}
|
||||
|
||||
payload := make([]byte, uint64Len+lengthOfVersionScriptPubKey+lengthOfscriptPubKeyLength+scriptLengthOfScriptPubKey+len(coinbaseData.ExtraData))
|
||||
binary.LittleEndian.PutUint64(payload[:uint64Len], blueScore)
|
||||
if len(coinbaseData.ScriptPublicKey.Script) > math.MaxUint8 {
|
||||
return nil, errors.Errorf("script public key is bigger than %d", math.MaxUint8)
|
||||
}
|
||||
|
||||
payload[uint64Len] = uint8(coinbaseData.ScriptPublicKey.Version)
|
||||
payload[uint64Len+lengthOfVersionScriptPubKey] = uint8(len(coinbaseData.ScriptPublicKey.Script))
|
||||
@@ -49,7 +45,7 @@ func (c *coinbaseManager) ExtractCoinbaseDataAndBlueScore(coinbaseTx *externalap
|
||||
scriptPubKeyVersion := uint16(coinbaseTx.Payload[uint64Len])
|
||||
scriptPubKeyScriptLength := coinbaseTx.Payload[uint64Len+lengthOfVersionScriptPubKey]
|
||||
|
||||
if uint64(scriptPubKeyScriptLength) > c.coinbasePayloadScriptPublicKeyMaxLength {
|
||||
if scriptPubKeyScriptLength > c.coinbasePayloadScriptPublicKeyMaxLength {
|
||||
return 0, nil, errors.Wrapf(ruleerrors.ErrBadCoinbasePayloadLen, "coinbase's payload script public key is "+
|
||||
"longer than the max allowed length of %d", c.coinbasePayloadScriptPublicKeyMaxLength)
|
||||
}
|
||||
|
||||
@@ -10,7 +10,8 @@ import (
|
||||
// current virtual. This process may result in a new virtual block
|
||||
// getting created
|
||||
func (csm *consensusStateManager) AddBlock(blockHash *externalapi.DomainHash) (*externalapi.SelectedChainPath, error) {
|
||||
logger.LogAndMeasureExecutionTime(log, "csm.AddBlock")
|
||||
onEnd := logger.LogAndMeasureExecutionTime(log, "csm.AddBlock")
|
||||
defer onEnd()
|
||||
|
||||
log.Debugf("Resolving whether the block %s is the next virtual selected parent", blockHash)
|
||||
isCandidateToBeNextVirtualSelectedParent, err := csm.isCandidateToBeNextVirtualSelectedParent(blockHash)
|
||||
|
||||
@@ -89,7 +89,7 @@ func checkBlockUTXOCommitment(t *testing.T, consensus testapi.TestConsensus, blo
|
||||
|
||||
// Build a Multiset
|
||||
ms := multiset.New()
|
||||
for utxoSetIterator.Next() {
|
||||
for ok := utxoSetIterator.First(); ok; ok = utxoSetIterator.Next() {
|
||||
outpoint, entry, err := utxoSetIterator.Get()
|
||||
if err != nil {
|
||||
t.Fatalf("Error getting from UTXOSet iterator: %+v", err)
|
||||
|
||||
@@ -0,0 +1,218 @@
|
||||
package consensusstatemanager
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
|
||||
"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/logger"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (csm *consensusStateManager) ImportPruningPoint(newPruningPoint *externalapi.DomainBlock) error {
|
||||
onEnd := logger.LogAndMeasureExecutionTime(log, "ImportPruningPoint")
|
||||
defer onEnd()
|
||||
|
||||
err := csm.importPruningPoint(newPruningPoint)
|
||||
if err != nil {
|
||||
csm.discardImportedPruningPointUTXOSetChanges()
|
||||
return err
|
||||
}
|
||||
|
||||
return csm.applyImportedPruningPointUTXOSet()
|
||||
}
|
||||
|
||||
func (csm *consensusStateManager) importPruningPoint(newPruningPoint *externalapi.DomainBlock) error {
|
||||
log.Debugf("importPruningPoint start")
|
||||
defer log.Debugf("importPruningPoint end")
|
||||
|
||||
newPruningPointHash := consensushashing.BlockHash(newPruningPoint)
|
||||
|
||||
// We ignore the shouldSendNotification return value because we always want to send finality conflict notification
|
||||
// in case the new pruning point violates finality
|
||||
isViolatingFinality, _, err := csm.isViolatingFinality(newPruningPointHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if isViolatingFinality {
|
||||
log.Warnf("Finality Violation Detected! The suggest pruning point %s violates finality!", newPruningPointHash)
|
||||
return errors.Wrapf(ruleerrors.ErrSuggestedPruningViolatesFinality, "%s cannot be a pruning point because "+
|
||||
"it violates finality", newPruningPointHash)
|
||||
}
|
||||
|
||||
importedPruningPointMultiset, err := csm.pruningStore.ImportedPruningPointMultiset(csm.databaseContext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newPruningPointHeader, err := csm.blockHeaderStore.BlockHeader(csm.databaseContext, newPruningPointHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debugf("The UTXO commitment of the pruning point: %s",
|
||||
newPruningPointHeader.UTXOCommitment())
|
||||
|
||||
if !newPruningPointHeader.UTXOCommitment().Equal(importedPruningPointMultiset.Hash()) {
|
||||
return errors.Wrapf(ruleerrors.ErrBadPruningPointUTXOSet, "the expected multiset hash of the pruning "+
|
||||
"point UTXO set is %s but got %s", newPruningPointHeader.UTXOCommitment(), *importedPruningPointMultiset.Hash())
|
||||
}
|
||||
log.Debugf("The new pruning point UTXO commitment validation passed")
|
||||
|
||||
log.Debugf("Staging the the pruning point as the only DAG tip")
|
||||
newTips := []*externalapi.DomainHash{newPruningPointHash}
|
||||
csm.consensusStateStore.StageTips(newTips)
|
||||
|
||||
log.Debugf("Setting the pruning point as the only virtual parent")
|
||||
err = csm.dagTopologyManager.SetParents(model.VirtualBlockHash, newTips)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("Calculating GHOSTDAG for the new virtual")
|
||||
err = csm.ghostdagManager.GHOSTDAG(model.VirtualBlockHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("Deleting all existing virtual diff parents")
|
||||
csm.consensusStateStore.StageVirtualDiffParents(nil)
|
||||
|
||||
log.Debugf("Updating the new pruning point to be the new virtual diff parent with an empty diff")
|
||||
err = csm.stageDiff(newPruningPointHash, utxo.NewUTXODiff(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("Staging the new pruning point")
|
||||
csm.pruningStore.StagePruningPoint(newPruningPointHash)
|
||||
|
||||
log.Debugf("Populating the pruning point with UTXO entries")
|
||||
importedPruningPointUTXOIterator, err := csm.pruningStore.ImportedPruningPointUTXOIterator(csm.databaseContext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Clone the pruningPoint block here because validateBlockTransactionsAgainstPastUTXO
|
||||
// assumes that the block UTXOEntries are pre-filled during further validations
|
||||
newPruningPointClone := newPruningPoint.Clone()
|
||||
err = csm.populateTransactionWithUTXOEntriesFromUTXOSet(newPruningPointClone, importedPruningPointUTXOIterator)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Before we manually mark the new pruning point as valid, we validate that all of its transactions are valid
|
||||
// against the provided UTXO set.
|
||||
log.Debugf("Validating that the pruning point is UTXO valid")
|
||||
newPruningPointSelectedParentMedianTime, err := csm.pastMedianTimeManager.PastMedianTime(newPruningPointHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Tracef("The past median time of pruning block %s is %d",
|
||||
newPruningPointHash, newPruningPointSelectedParentMedianTime)
|
||||
|
||||
for i, transaction := range newPruningPointClone.Transactions {
|
||||
transactionID := consensushashing.TransactionID(transaction)
|
||||
log.Tracef("Validating transaction %s in pruning block %s against "+
|
||||
"the pruning point's past UTXO", transactionID, newPruningPointHash)
|
||||
if i == transactionhelper.CoinbaseTransactionIndex {
|
||||
log.Tracef("Skipping transaction %s because it is the coinbase", transactionID)
|
||||
continue
|
||||
}
|
||||
log.Tracef("Validating transaction %s and populating it with mass and fee", transactionID)
|
||||
err = csm.transactionValidator.ValidateTransactionInContextAndPopulateMassAndFee(
|
||||
transaction, newPruningPointHash, newPruningPointSelectedParentMedianTime)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Tracef("Validation against the pruning point's past UTXO "+
|
||||
"passed for transaction %s", transactionID)
|
||||
}
|
||||
|
||||
log.Debugf("Staging the new pruning point as %s", externalapi.StatusUTXOValid)
|
||||
csm.blockStatusStore.Stage(newPruningPointHash, externalapi.StatusUTXOValid)
|
||||
|
||||
log.Debugf("Staging the new pruning point multiset")
|
||||
csm.multisetStore.Stage(newPruningPointHash, importedPruningPointMultiset)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (csm *consensusStateManager) discardImportedPruningPointUTXOSetChanges() {
|
||||
for _, store := range csm.stores {
|
||||
store.Discard()
|
||||
}
|
||||
}
|
||||
|
||||
func (csm *consensusStateManager) applyImportedPruningPointUTXOSet() error {
|
||||
dbTx, err := csm.databaseContext.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, store := range csm.stores {
|
||||
err = store.Commit(dbTx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf("Starting to import virtual UTXO set and pruning point utxo set")
|
||||
err = csm.consensusStateStore.StartImportingPruningPointUTXOSet(dbTx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("Committing all staged data for imported pruning point")
|
||||
err = dbTx.Commit()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return csm.importVirtualUTXOSetAndPruningPointUTXOSet()
|
||||
}
|
||||
|
||||
func (csm *consensusStateManager) importVirtualUTXOSetAndPruningPointUTXOSet() error {
|
||||
onEnd := logger.LogAndMeasureExecutionTime(log, "importVirtualUTXOSetAndPruningPointUTXOSet")
|
||||
defer onEnd()
|
||||
|
||||
log.Debugf("Getting an iterator into the imported pruning point utxo set")
|
||||
pruningPointUTXOSetIterator, err := csm.pruningStore.ImportedPruningPointUTXOIterator(csm.databaseContext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("Importing the virtual UTXO set")
|
||||
err = csm.consensusStateStore.ImportPruningPointUTXOSetIntoVirtualUTXOSet(csm.databaseContext, pruningPointUTXOSetIterator)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("Importing the new pruning point UTXO set")
|
||||
err = csm.pruningStore.CommitImportedPruningPointUTXOSet(csm.databaseContext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("Finishing to import virtual UTXO set and pruning point UTXO set")
|
||||
return csm.consensusStateStore.FinishImportingPruningPointUTXOSet(csm.databaseContext)
|
||||
}
|
||||
|
||||
func (csm *consensusStateManager) RecoverUTXOIfRequired() error {
|
||||
hadStartedImportingPruningPointUTXOSet, err := csm.consensusStateStore.HadStartedImportingPruningPointUTXOSet(csm.databaseContext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !hadStartedImportingPruningPointUTXOSet {
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Warnf("Unimported pruning point UTXO set detected. Attempting to recover...")
|
||||
err = csm.importVirtualUTXOSetAndPruningPointUTXOSet()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Warnf("Unimported UTXO set successfully recovered")
|
||||
return nil
|
||||
}
|
||||
@@ -75,3 +75,49 @@ func (csm *consensusStateManager) populateTransactionWithUTXOEntriesFromVirtualO
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (csm *consensusStateManager) populateTransactionWithUTXOEntriesFromUTXOSet(
|
||||
pruningPoint *externalapi.DomainBlock, iterator model.ReadOnlyUTXOSetIterator) error {
|
||||
|
||||
// Collect the required outpoints from the block
|
||||
outpointsForPopulation := make(map[externalapi.DomainOutpoint]interface{})
|
||||
for _, transaction := range pruningPoint.Transactions {
|
||||
for _, input := range transaction.Inputs {
|
||||
outpointsForPopulation[input.PreviousOutpoint] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// Collect the UTXO entries from the iterator
|
||||
outpointsToUTXOEntries := make(map[externalapi.DomainOutpoint]externalapi.UTXOEntry, len(outpointsForPopulation))
|
||||
for ok := iterator.First(); ok; ok = iterator.Next() {
|
||||
outpoint, utxoEntry, err := iterator.Get()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
outpointValue := *outpoint
|
||||
if _, ok := outpointsForPopulation[outpointValue]; ok {
|
||||
outpointsToUTXOEntries[outpointValue] = utxoEntry
|
||||
}
|
||||
if len(outpointsForPopulation) == len(outpointsToUTXOEntries) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Populate the block with the collected UTXO entries
|
||||
var missingOutpoints []*externalapi.DomainOutpoint
|
||||
for _, transaction := range pruningPoint.Transactions {
|
||||
for _, input := range transaction.Inputs {
|
||||
utxoEntry, ok := outpointsToUTXOEntries[input.PreviousOutpoint]
|
||||
if !ok {
|
||||
missingOutpoints = append(missingOutpoints, &input.PreviousOutpoint)
|
||||
continue
|
||||
}
|
||||
input.UTXOEntry = utxoEntry
|
||||
}
|
||||
}
|
||||
|
||||
if len(missingOutpoints) > 0 {
|
||||
return ruleerrors.NewErrMissingTxOut(missingOutpoints)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,174 +0,0 @@
|
||||
package consensusstatemanager
|
||||
|
||||
import (
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/serialization"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/utxo"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/utxoserialization"
|
||||
"github.com/kaspanet/kaspad/infrastructure/logger"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (csm *consensusStateManager) UpdatePruningPoint(newPruningPoint *externalapi.DomainBlock, serializedUTXOSet []byte) error {
|
||||
onEnd := logger.LogAndMeasureExecutionTime(log, "UpdatePruningPoint")
|
||||
defer onEnd()
|
||||
|
||||
err := csm.updatePruningPoint(newPruningPoint, serializedUTXOSet)
|
||||
if err != nil {
|
||||
csm.discardSetPruningPointUTXOSetChanges()
|
||||
return err
|
||||
}
|
||||
|
||||
return csm.commitSetPruningPointUTXOSetAll()
|
||||
}
|
||||
|
||||
func (csm *consensusStateManager) updatePruningPoint(newPruningPoint *externalapi.DomainBlock, serializedUTXOSet []byte) error {
|
||||
log.Debugf("updatePruningPoint start")
|
||||
defer log.Debugf("updatePruningPoint end")
|
||||
|
||||
newPruningPointHash := consensushashing.BlockHash(newPruningPoint)
|
||||
|
||||
// We ignore the shouldSendNotification return value because we always want to send finality conflict notification
|
||||
// in case the new pruning point violates finality
|
||||
isViolatingFinality, _, err := csm.isViolatingFinality(newPruningPointHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if isViolatingFinality {
|
||||
log.Warnf("Finality Violation Detected! The suggest pruning point %s violates finality!", newPruningPointHash)
|
||||
return errors.Wrapf(ruleerrors.ErrSuggestedPruningViolatesFinality, "%s cannot be a pruning point because "+
|
||||
"it violates finality", newPruningPointHash)
|
||||
}
|
||||
|
||||
protoUTXOSet := &utxoserialization.ProtoUTXOSet{}
|
||||
err = proto.Unmarshal(serializedUTXOSet, protoUTXOSet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
utxoSetMultiSet, err := utxoserialization.CalculateMultisetFromProtoUTXOSet(protoUTXOSet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debugf("Calculated multiset for given UTXO set: %s", utxoSetMultiSet.Hash())
|
||||
|
||||
newPruningPointHeader, err := csm.blockHeaderStore.BlockHeader(csm.databaseContext, newPruningPointHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debugf("The UTXO commitment of the pruning point: %s",
|
||||
newPruningPointHeader.UTXOCommitment())
|
||||
|
||||
if !newPruningPointHeader.UTXOCommitment().Equal(utxoSetMultiSet.Hash()) {
|
||||
return errors.Wrapf(ruleerrors.ErrBadPruningPointUTXOSet, "the expected multiset hash of the pruning "+
|
||||
"point UTXO set is %s but got %s", newPruningPointHeader.UTXOCommitment(), *utxoSetMultiSet.Hash())
|
||||
}
|
||||
log.Debugf("The new pruning point UTXO commitment validation passed")
|
||||
|
||||
newTips := []*externalapi.DomainHash{newPruningPointHash}
|
||||
|
||||
log.Debugf("Staging the the pruning point as the only DAG tip")
|
||||
csm.consensusStateStore.StageTips(newTips)
|
||||
|
||||
log.Debugf("Setting the pruning point as the only virtual parent")
|
||||
err = csm.dagTopologyManager.SetParents(model.VirtualBlockHash, newTips)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("Calculating GHOSTDAG for the new virtual")
|
||||
err = csm.ghostdagManager.GHOSTDAG(model.VirtualBlockHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("Staging the virtual UTXO set")
|
||||
err = csm.consensusStateStore.StageVirtualUTXOSet(protoUTXOSetToReadOnlyUTXOSetIterator(protoUTXOSet))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("Deleting all the existing virtual diff parents")
|
||||
csm.consensusStateStore.StageVirtualDiffParents(nil)
|
||||
|
||||
log.Debugf("Updating the new pruning point to be the new virtual diff parent with an empty diff")
|
||||
err = csm.stageDiff(newPruningPointHash, utxo.NewUTXODiff(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("Staging the new pruning point and its UTXO set")
|
||||
csm.pruningStore.StagePruningPoint(newPruningPointHash, serializedUTXOSet)
|
||||
|
||||
// Before we manually mark the new pruning point as valid, we validate that all of its transactions are valid
|
||||
// against the provided UTXO set.
|
||||
log.Debugf("Validating that the pruning point is UTXO valid")
|
||||
|
||||
// validateBlockTransactionsAgainstPastUTXO pre-fills the block's transactions inputs, which
|
||||
// are assumed to not be pre-filled during further validations.
|
||||
// Therefore - clone newPruningPoint before passing it to validateBlockTransactionsAgainstPastUTXO
|
||||
err = csm.validateBlockTransactionsAgainstPastUTXO(newPruningPoint.Clone(), utxo.NewUTXODiff())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("Staging the new pruning point as %s", externalapi.StatusUTXOValid)
|
||||
csm.blockStatusStore.Stage(newPruningPointHash, externalapi.StatusUTXOValid)
|
||||
|
||||
log.Debugf("Staging the new pruning point multiset")
|
||||
csm.multisetStore.Stage(newPruningPointHash, utxoSetMultiSet)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (csm *consensusStateManager) discardSetPruningPointUTXOSetChanges() {
|
||||
for _, store := range csm.stores {
|
||||
store.Discard()
|
||||
}
|
||||
}
|
||||
|
||||
func (csm *consensusStateManager) commitSetPruningPointUTXOSetAll() error {
|
||||
dbTx, err := csm.databaseContext.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, store := range csm.stores {
|
||||
err = store.Commit(dbTx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return dbTx.Commit()
|
||||
}
|
||||
|
||||
type protoUTXOSetIterator struct {
|
||||
utxoSet *utxoserialization.ProtoUTXOSet
|
||||
index int
|
||||
}
|
||||
|
||||
func (p *protoUTXOSetIterator) Next() bool {
|
||||
p.index++
|
||||
return p.index < len(p.utxoSet.Utxos)
|
||||
}
|
||||
|
||||
func (p *protoUTXOSetIterator) Get() (outpoint *externalapi.DomainOutpoint, utxoEntry externalapi.UTXOEntry, err error) {
|
||||
entry, outpoint, err := utxo.DeserializeUTXO(p.utxoSet.Utxos[p.index].EntryOutpointPair)
|
||||
if err != nil {
|
||||
if serialization.IsMalformedError(err) {
|
||||
return nil, nil, errors.Wrapf(ruleerrors.ErrMalformedUTXO, "malformed utxo: %s", err)
|
||||
}
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return outpoint, entry, nil
|
||||
}
|
||||
|
||||
func protoUTXOSetToReadOnlyUTXOSetIterator(protoUTXOSet *utxoserialization.ProtoUTXOSet) model.ReadOnlyUTXOSetIterator {
|
||||
return &protoUTXOSetIterator{utxoSet: protoUTXOSet, index: -1}
|
||||
}
|
||||
@@ -52,10 +52,7 @@ func (csm *consensusStateManager) updateVirtual(newBlockHash *externalapi.Domain
|
||||
csm.multisetStore.Stage(model.VirtualBlockHash, virtualMultiset)
|
||||
|
||||
log.Debugf("Staging new UTXO diff for the virtual block")
|
||||
err = csm.consensusStateStore.StageVirtualUTXODiff(virtualUTXODiff)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
csm.consensusStateStore.StageVirtualUTXODiff(virtualUTXODiff)
|
||||
|
||||
log.Debugf("Updating the virtual diff parents after adding %s to the DAG", newBlockHash)
|
||||
err = csm.updateVirtualDiffParents(virtualUTXODiff)
|
||||
|
||||
@@ -3,9 +3,9 @@ package finalitymanager
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/database"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/infrastructure/db/database"
|
||||
)
|
||||
|
||||
type finalityManager struct {
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
package pruningmanager
|
||||
|
||||
import (
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/utxoserialization"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/multiset"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/utxo"
|
||||
"github.com/kaspanet/kaspad/infrastructure/db/database"
|
||||
"github.com/kaspanet/kaspad/infrastructure/logger"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// pruningManager resolves and manages the current pruning point
|
||||
type pruningManager struct {
|
||||
databaseContext model.DBReader
|
||||
databaseContext model.DBManager
|
||||
|
||||
dagTraversalManager model.DAGTraversalManager
|
||||
dagTopologyManager model.DAGTopologyManager
|
||||
@@ -36,7 +37,7 @@ type pruningManager struct {
|
||||
|
||||
// New instantiates a new PruningManager
|
||||
func New(
|
||||
databaseContext model.DBReader,
|
||||
databaseContext model.DBManager,
|
||||
|
||||
dagTraversalManager model.DAGTraversalManager,
|
||||
dagTopologyManager model.DAGTopologyManager,
|
||||
@@ -310,16 +311,15 @@ func (pm *pruningManager) savePruningPoint(pruningPointHash *externalapi.DomainH
|
||||
onEnd := logger.LogAndMeasureExecutionTime(log, "pruningManager.savePruningPoint")
|
||||
defer onEnd()
|
||||
|
||||
utxoIter, err := pm.consensusStateManager.RestorePastUTXOSetIterator(pruningPointHash)
|
||||
// TODO: This is an assert that takes ~30 seconds to run
|
||||
// It must be removed or optimized before launching testnet
|
||||
err := pm.validateUTXOSetFitsCommitment(pruningPointHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
serializedUtxo, err := pm.calculateAndValidateSerializedUTXOSet(utxoIter, pruningPointHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pm.pruningStore.StagePruningPoint(pruningPointHash, serializedUtxo)
|
||||
pm.pruningStore.StagePruningPoint(pruningPointHash)
|
||||
pm.pruningStore.StageStartUpdatingPruningPointUTXOSet()
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -410,32 +410,27 @@ func (pm *pruningManager) pruningPointCandidate() (*externalapi.DomainHash, erro
|
||||
return pm.pruningStore.PruningPointCandidate(pm.databaseContext)
|
||||
}
|
||||
|
||||
func (pm *pruningManager) calculateAndValidateSerializedUTXOSet(
|
||||
iter model.ReadOnlyUTXOSetIterator, pruningPointHash *externalapi.DomainHash) ([]byte, error) {
|
||||
|
||||
serializedUtxo, err := utxoserialization.ReadOnlyUTXOSetToProtoUTXOSet(iter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = pm.validateUTXOSetFitsCommitment(serializedUtxo, pruningPointHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return proto.Marshal(serializedUtxo)
|
||||
}
|
||||
|
||||
// validateUTXOSetFitsCommitment makes sure that the calculated UTXOSet of the new pruning point fits the commitment.
|
||||
// This is a sanity test, to make sure that kaspad doesn't store, and subsequently sends syncing peers the wrong UTXOSet.
|
||||
func (pm *pruningManager) validateUTXOSetFitsCommitment(
|
||||
serializedUtxo *utxoserialization.ProtoUTXOSet, pruningPointHash *externalapi.DomainHash) error {
|
||||
|
||||
utxoSetMultiSet, err := utxoserialization.CalculateMultisetFromProtoUTXOSet(serializedUtxo)
|
||||
func (pm *pruningManager) validateUTXOSetFitsCommitment(pruningPointHash *externalapi.DomainHash) error {
|
||||
utxoSetIterator, err := pm.consensusStateManager.RestorePastUTXOSetIterator(pruningPointHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
utxoSetHash := utxoSetMultiSet.Hash()
|
||||
|
||||
utxoSetMultiset := multiset.New()
|
||||
for ok := utxoSetIterator.First(); ok; ok = utxoSetIterator.Next() {
|
||||
outpoint, entry, err := utxoSetIterator.Get()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
serializedUTXO, err := utxo.SerializeUTXO(entry, outpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
utxoSetMultiset.Add(serializedUTXO)
|
||||
}
|
||||
utxoSetHash := utxoSetMultiset.Hash()
|
||||
|
||||
header, err := pm.blockHeaderStore.BlockHeader(pm.databaseContext, pruningPointHash)
|
||||
if err != nil {
|
||||
@@ -459,3 +454,95 @@ func (pm *pruningManager) validateUTXOSetFitsCommitment(
|
||||
func (pm *pruningManager) finalityScore(blueScore uint64) uint64 {
|
||||
return blueScore / pm.finalityInterval
|
||||
}
|
||||
|
||||
func (pm *pruningManager) ClearImportedPruningPointData() error {
|
||||
err := pm.pruningStore.ClearImportedPruningPointMultiset(pm.databaseContext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return pm.pruningStore.ClearImportedPruningPointUTXOs(pm.databaseContext)
|
||||
}
|
||||
|
||||
func (pm *pruningManager) AppendImportedPruningPointUTXOs(
|
||||
outpointAndUTXOEntryPairs []*externalapi.OutpointAndUTXOEntryPair) error {
|
||||
|
||||
dbTx, err := pm.databaseContext.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dbTx.RollbackUnlessClosed()
|
||||
|
||||
importedMultiset, err := pm.pruningStore.ImportedPruningPointMultiset(dbTx)
|
||||
if err != nil {
|
||||
if !database.IsNotFoundError(err) {
|
||||
return err
|
||||
}
|
||||
importedMultiset = multiset.New()
|
||||
}
|
||||
for _, outpointAndUTXOEntryPair := range outpointAndUTXOEntryPairs {
|
||||
serializedUTXO, err := utxo.SerializeUTXO(outpointAndUTXOEntryPair.UTXOEntry, outpointAndUTXOEntryPair.Outpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
importedMultiset.Add(serializedUTXO)
|
||||
}
|
||||
err = pm.pruningStore.UpdateImportedPruningPointMultiset(dbTx, importedMultiset)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = pm.pruningStore.AppendImportedPruningPointUTXOs(dbTx, outpointAndUTXOEntryPairs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return dbTx.Commit()
|
||||
}
|
||||
|
||||
func (pm *pruningManager) UpdatePruningPointUTXOSetIfRequired() error {
|
||||
hadStartedUpdatingPruningPointUTXOSet, err := pm.pruningStore.HadStartedUpdatingPruningPointUTXOSet(pm.databaseContext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !hadStartedUpdatingPruningPointUTXOSet {
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Debugf("Pruning point UTXO set update is required")
|
||||
err = pm.updatePruningPointUTXOSet()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debugf("Pruning point UTXO set updated")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pm *pruningManager) updatePruningPointUTXOSet() error {
|
||||
onEnd := logger.LogAndMeasureExecutionTime(log, "updatePruningPointUTXOSet")
|
||||
defer onEnd()
|
||||
|
||||
logger.LogMemoryStats(log, "updatePruningPointUTXOSet start")
|
||||
defer logger.LogMemoryStats(log, "updatePruningPointUTXOSet end")
|
||||
|
||||
log.Debugf("Getting the pruning point")
|
||||
pruningPoint, err := pm.pruningStore.PruningPoint(pm.databaseContext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("Restoring the pruning point UTXO set")
|
||||
utxoSetIterator, err := pm.consensusStateManager.RestorePastUTXOSetIterator(pruningPoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("Updating the pruning point UTXO set")
|
||||
err = pm.pruningStore.UpdatePruningPointUTXOSet(pm.databaseContext, utxoSetIterator)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("Finishing updating the pruning point UTXO set")
|
||||
return pm.pruningStore.FinishUpdatingPruningPointUTXOSet(pm.databaseContext)
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package reachabilitymanager
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus/database"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/reachabilitydata"
|
||||
"github.com/kaspanet/kaspad/infrastructure/db/database"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package syncmanager
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus/database"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/infrastructure/db/database"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
package dbkeys
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||
)
|
||||
|
||||
var bucketSeparator = []byte("/")
|
||||
|
||||
// Key is a helper type meant to combine prefix
|
||||
// and suffix into a single database key.
|
||||
type Key struct {
|
||||
bucket *Bucket
|
||||
suffix []byte
|
||||
}
|
||||
|
||||
// Bytes returns the full key bytes that are consisted
|
||||
// from the bucket path concatenated to the suffix.
|
||||
func (k *Key) Bytes() []byte {
|
||||
bucketPath := k.bucket.Path()
|
||||
keyBytes := make([]byte, len(bucketPath)+len(k.suffix))
|
||||
copy(keyBytes, bucketPath)
|
||||
copy(keyBytes[len(bucketPath):], k.suffix)
|
||||
return keyBytes
|
||||
}
|
||||
|
||||
func (k *Key) String() string {
|
||||
return hex.EncodeToString(k.Bytes())
|
||||
}
|
||||
|
||||
// Bucket returns the key bucket.
|
||||
func (k *Key) Bucket() model.DBBucket {
|
||||
return k.bucket
|
||||
}
|
||||
|
||||
// Suffix returns the key suffix.
|
||||
func (k *Key) Suffix() []byte {
|
||||
return k.suffix
|
||||
}
|
||||
|
||||
// newKey returns a new key composed
|
||||
// of the given bucket and suffix
|
||||
func newKey(bucket *Bucket, suffix []byte) model.DBKey {
|
||||
return &Key{bucket: bucket, suffix: suffix}
|
||||
}
|
||||
|
||||
// Bucket is a helper type meant to combine buckets
|
||||
// and sub-buckets that can be used to create database
|
||||
// keys and prefix-based cursors.
|
||||
type Bucket struct {
|
||||
path [][]byte
|
||||
}
|
||||
|
||||
// MakeBucket creates a new Bucket using the given path
|
||||
// of buckets.
|
||||
func MakeBucket(path ...[]byte) model.DBBucket {
|
||||
return &Bucket{path: path}
|
||||
}
|
||||
|
||||
// Bucket returns the sub-bucket of the current bucket
|
||||
// defined by bucketBytes.
|
||||
func (b *Bucket) Bucket(bucketBytes []byte) model.DBBucket {
|
||||
newPath := make([][]byte, len(b.path)+1)
|
||||
copy(newPath, b.path)
|
||||
copy(newPath[len(b.path):], [][]byte{bucketBytes})
|
||||
|
||||
return MakeBucket(newPath...)
|
||||
}
|
||||
|
||||
// Key returns a key in the current bucket with the
|
||||
// given suffix.
|
||||
func (b *Bucket) Key(suffix []byte) model.DBKey {
|
||||
return newKey(b, suffix)
|
||||
}
|
||||
|
||||
// Path returns the full path of the current bucket.
|
||||
func (b *Bucket) Path() []byte {
|
||||
bucketPath := bytes.Join(b.path, bucketSeparator)
|
||||
|
||||
bucketPathWithFinalSeparator := make([]byte, len(bucketPath)+len(bucketSeparator))
|
||||
copy(bucketPathWithFinalSeparator, bucketPath)
|
||||
copy(bucketPathWithFinalSeparator[len(bucketPath):], bucketSeparator)
|
||||
|
||||
return bucketPathWithFinalSeparator
|
||||
}
|
||||
@@ -29,6 +29,11 @@ func (uc utxoCollection) Iterator() model.ReadOnlyUTXOSetIterator {
|
||||
return &utxoCollectionIterator{index: -1, pairs: pairs}
|
||||
}
|
||||
|
||||
func (uci *utxoCollectionIterator) First() bool {
|
||||
uci.index = 0
|
||||
return len(uci.pairs) > 0
|
||||
}
|
||||
|
||||
func (uci *utxoCollectionIterator) Next() bool {
|
||||
uci.index++
|
||||
return uci.index < len(uci.pairs)
|
||||
|
||||
@@ -36,10 +36,31 @@ func IteratorWithDiff(iterator model.ReadOnlyUTXOSetIterator, diff model.UTXODif
|
||||
return &readOnlyUTXOIteratorWithDiff{
|
||||
baseIterator: iterator,
|
||||
diff: d,
|
||||
toAddIterator: d.mutableUTXODiff.toAdd.Iterator(),
|
||||
toAddIterator: d.ToAdd().Iterator(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *readOnlyUTXOIteratorWithDiff) First() bool {
|
||||
baseNotEmpty := r.baseIterator.First()
|
||||
baseEmpty := !baseNotEmpty
|
||||
|
||||
r.toAddIterator = r.diff.ToAdd().Iterator()
|
||||
toAddEmpty := r.diff.ToAdd().Len() == 0
|
||||
|
||||
if baseEmpty {
|
||||
if toAddEmpty {
|
||||
return false
|
||||
}
|
||||
return r.Next()
|
||||
}
|
||||
|
||||
r.currentOutpoint, r.currentUTXOEntry, r.currentErr = r.baseIterator.Get()
|
||||
if r.diff.mutableUTXODiff.toRemove.containsWithBlueScore(r.currentOutpoint, r.currentUTXOEntry.BlockBlueScore()) {
|
||||
return r.Next()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (r *readOnlyUTXOIteratorWithDiff) Next() bool {
|
||||
for r.baseIterator.Next() { // keep looping until we reach an outpoint/entry pair that is not in r.diff.toRemove
|
||||
r.currentOutpoint, r.currentUTXOEntry, r.currentErr = r.baseIterator.Get()
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
//go:generate protoc --go_out=. --go-grpc_out=. --go_opt=paths=source_relative --go-grpc_opt=paths=source_relative utxo.proto
|
||||
|
||||
package utxoserialization
|
||||
@@ -1,15 +0,0 @@
|
||||
package utxoserialization
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/multiset"
|
||||
)
|
||||
|
||||
// CalculateMultisetFromProtoUTXOSet calculates the Multiset corresponding to the given ProtuUTXOSet
|
||||
func CalculateMultisetFromProtoUTXOSet(protoUTXOSet *ProtoUTXOSet) (model.Multiset, error) {
|
||||
ms := multiset.New()
|
||||
for _, utxo := range protoUTXOSet.Utxos {
|
||||
ms.Add(utxo.EntryOutpointPair)
|
||||
}
|
||||
return ms, nil
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package utxoserialization
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/utxo"
|
||||
)
|
||||
|
||||
// ReadOnlyUTXOSetToProtoUTXOSet converts ReadOnlyUTXOSetIterator to ProtoUTXOSet
|
||||
func ReadOnlyUTXOSetToProtoUTXOSet(iter model.ReadOnlyUTXOSetIterator) (*ProtoUTXOSet, error) {
|
||||
protoUTXOSet := &ProtoUTXOSet{
|
||||
Utxos: []*ProtoUTXO{},
|
||||
}
|
||||
|
||||
for iter.Next() {
|
||||
outpoint, entry, err := iter.Get()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
serializedUTXOBytes, err := utxo.SerializeUTXO(entry, outpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
protoUTXOSet.Utxos = append(protoUTXOSet.Utxos, &ProtoUTXO{
|
||||
EntryOutpointPair: serializedUTXOBytes,
|
||||
})
|
||||
}
|
||||
return protoUTXOSet, nil
|
||||
}
|
||||
@@ -1,217 +0,0 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.25.0
|
||||
// protoc v3.12.3
|
||||
// source: utxo.proto
|
||||
|
||||
package utxoserialization
|
||||
|
||||
import (
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
// This is a compile-time assertion that a sufficiently up-to-date version
|
||||
// of the legacy proto package is being used.
|
||||
const _ = proto.ProtoPackageIsVersion4
|
||||
|
||||
type ProtoUTXO struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
EntryOutpointPair []byte `protobuf:"bytes,1,opt,name=entryOutpointPair,proto3" json:"entryOutpointPair,omitempty"`
|
||||
}
|
||||
|
||||
func (x *ProtoUTXO) Reset() {
|
||||
*x = ProtoUTXO{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_utxo_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *ProtoUTXO) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ProtoUTXO) ProtoMessage() {}
|
||||
|
||||
func (x *ProtoUTXO) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_utxo_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ProtoUTXO.ProtoReflect.Descriptor instead.
|
||||
func (*ProtoUTXO) Descriptor() ([]byte, []int) {
|
||||
return file_utxo_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *ProtoUTXO) GetEntryOutpointPair() []byte {
|
||||
if x != nil {
|
||||
return x.EntryOutpointPair
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type ProtoUTXOSet struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Utxos []*ProtoUTXO `protobuf:"bytes,1,rep,name=utxos,proto3" json:"utxos,omitempty"`
|
||||
}
|
||||
|
||||
func (x *ProtoUTXOSet) Reset() {
|
||||
*x = ProtoUTXOSet{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_utxo_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *ProtoUTXOSet) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ProtoUTXOSet) ProtoMessage() {}
|
||||
|
||||
func (x *ProtoUTXOSet) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_utxo_proto_msgTypes[1]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ProtoUTXOSet.ProtoReflect.Descriptor instead.
|
||||
func (*ProtoUTXOSet) Descriptor() ([]byte, []int) {
|
||||
return file_utxo_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *ProtoUTXOSet) GetUtxos() []*ProtoUTXO {
|
||||
if x != nil {
|
||||
return x.Utxos
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var File_utxo_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_utxo_proto_rawDesc = []byte{
|
||||
0x0a, 0x0a, 0x75, 0x74, 0x78, 0x6f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x11, 0x75, 0x74,
|
||||
0x78, 0x6f, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22,
|
||||
0x39, 0x0a, 0x09, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x55, 0x54, 0x58, 0x4f, 0x12, 0x2c, 0x0a, 0x11,
|
||||
0x65, 0x6e, 0x74, 0x72, 0x79, 0x4f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x50, 0x61, 0x69,
|
||||
0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x11, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x4f, 0x75,
|
||||
0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x50, 0x61, 0x69, 0x72, 0x22, 0x42, 0x0a, 0x0c, 0x50, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x55, 0x54, 0x58, 0x4f, 0x53, 0x65, 0x74, 0x12, 0x32, 0x0a, 0x05, 0x75, 0x74,
|
||||
0x78, 0x6f, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x75, 0x74, 0x78, 0x6f,
|
||||
0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x50, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x55, 0x54, 0x58, 0x4f, 0x52, 0x05, 0x75, 0x74, 0x78, 0x6f, 0x73, 0x42, 0x45,
|
||||
0x5a, 0x43, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6b, 0x61, 0x73,
|
||||
0x70, 0x61, 0x6e, 0x65, 0x74, 0x2f, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x64, 0x2f, 0x64, 0x6f, 0x6d,
|
||||
0x61, 0x69, 0x6e, 0x2f, 0x63, 0x6f, 0x6e, 0x73, 0x65, 0x6e, 0x73, 0x75, 0x73, 0x2f, 0x75, 0x74,
|
||||
0x69, 0x6c, 0x73, 0x2f, 0x75, 0x74, 0x78, 0x6f, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a,
|
||||
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_utxo_proto_rawDescOnce sync.Once
|
||||
file_utxo_proto_rawDescData = file_utxo_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_utxo_proto_rawDescGZIP() []byte {
|
||||
file_utxo_proto_rawDescOnce.Do(func() {
|
||||
file_utxo_proto_rawDescData = protoimpl.X.CompressGZIP(file_utxo_proto_rawDescData)
|
||||
})
|
||||
return file_utxo_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_utxo_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
||||
var file_utxo_proto_goTypes = []interface{}{
|
||||
(*ProtoUTXO)(nil), // 0: utxoserialization.ProtoUTXO
|
||||
(*ProtoUTXOSet)(nil), // 1: utxoserialization.ProtoUTXOSet
|
||||
}
|
||||
var file_utxo_proto_depIdxs = []int32{
|
||||
0, // 0: utxoserialization.ProtoUTXOSet.utxos:type_name -> utxoserialization.ProtoUTXO
|
||||
1, // [1:1] is the sub-list for method output_type
|
||||
1, // [1:1] is the sub-list for method input_type
|
||||
1, // [1:1] is the sub-list for extension type_name
|
||||
1, // [1:1] is the sub-list for extension extendee
|
||||
0, // [0:1] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_utxo_proto_init() }
|
||||
func file_utxo_proto_init() {
|
||||
if File_utxo_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_utxo_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*ProtoUTXO); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_utxo_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*ProtoUTXOSet); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_utxo_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 2,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
GoTypes: file_utxo_proto_goTypes,
|
||||
DependencyIndexes: file_utxo_proto_depIdxs,
|
||||
MessageInfos: file_utxo_proto_msgTypes,
|
||||
}.Build()
|
||||
File_utxo_proto = out.File
|
||||
file_utxo_proto_rawDesc = nil
|
||||
file_utxo_proto_goTypes = nil
|
||||
file_utxo_proto_depIdxs = nil
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
syntax = "proto3";
|
||||
package utxoserialization;
|
||||
|
||||
option go_package = "github.com/kaspanet/kaspad/domain/consensus/utils/utxoserialization";
|
||||
|
||||
message ProtoUTXO {
|
||||
bytes entryOutpointPair = 1;
|
||||
}
|
||||
|
||||
message ProtoUTXOSet {
|
||||
repeated ProtoUTXO utxos = 1;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user