Compare commits

..

22 Commits

Author SHA1 Message Date
stasatdaglabs
3916534a7e In kaspactl, prettify responses before printing them (#1453)
* In kaspactl, prettify responses before printing them.

* Indent with four spaces instead of a tab.

* Unwrap the response.

* Simplify unwrapping the response.

* Don't unwrap responses.

* Use protojson.MarshalOptions for prettification.
2021-01-27 09:13:48 +02:00
Elichai Turkel
0561347ff1 Remove default dns/grpc seeders (#1454) 2021-01-26 17:40:54 +02:00
Svarog
fb11981da1 Make kaspactl usable by human beings (#1452)
* Add request_types and their help

* Added command parser

* Updated main to use command if it's specified

* Some progress in making everything work

* Fix command parser for pointers to structs

* Cleanup code

* Enhance usage text

* Fixed typo

* Some minor style fixing, and remove temporary code

* Correctly fallthrough in stringToValue unsupported types

Co-authored-by: stasatdaglabs <39559713+stasatdaglabs@users.noreply.github.com>
2021-01-26 14:22:35 +02:00
Ori Newman
1742e76af7 Add TestHandleRelayInvsErrors (#1450) 2021-01-26 13:31:28 +02:00
Svarog
ddfe376388 Don't ban peers that sent a requested duplicate block (#1440)
* Ignore ErrDuplicateBlock for any blocks that were requested

* Don't check for DuplicateBlockErr in processHeaders + improve logs

* Return the check for ErrDuplicateBlock in processHeader

* Fix log message
2021-01-26 08:42:04 +02:00
Elichai Turkel
52da65077a codecov: Ignore protobuf autogenerated files (#1449) 2021-01-25 16:58:11 +02:00
Elichai Turkel
ed85f09742 Add P2P/RPC name to the gRPC logs (#1448)
* Add P2P/RPC name to the gRPC logs

* Add p2p/rpc name in more logs
2021-01-25 16:41:43 +02:00
stasatdaglabs
819ec9f2a7 Pass fromOutpoint into GetPruningPointUTXOs instead of offset, so it could Seek over the cursor (#1447)
* Implement TestGetPruningPointUTXOs.

* Fix a bad error message.

* Fix TestGetPruningPointUTXOs for testnet.

* Make sure all the UTXOs are returned in TestGetPruningPointUTXOs.

* Implement BenchmarkGetPruningPointUTXOs.

* Pass fromOutpoint into GetPruningPointUTXOs instead of offset, so it could Seek over the cursor.

* Fix weird benchmark timer calls.

* Remove unnecessary collection of outpointAndUTXOEntryPairs from BenchmarkGetPruningPointUTXOs.

* Fix a comment.
2021-01-25 13:38:59 +02:00
Ori Newman
7ea8a72a9e Add TestReceiveAddressesErrors (#1446)
* Add TestReceiveAddressesErrors

* Change errors to be more descriptive

* Fix checkFlowError
2021-01-24 17:35:20 +02:00
stasatdaglabs
ca04c049ab When the pruning point moves, update its UTXO set outside of a database transaction (#1444)
* Remove pruningPointUTXOSetStaging and implement UpdatePruningPointUTXOSet.

* Implement StageStartSavingNewPruningPointUTXOSet, HadStartedSavingNewPruningPointUTXOSet, and FinishSavingNewPruningPointUTXOSet.

* Fix a bad return.

* Implement UpdatePruningPointUTXOSetIfRequired.

* Call UpdatePruningPointUTXOSetIfRequired on consensus creation.

* Rename savingNewPruningPointUTXOSetKey to updatingPruningPointUTXOSet.

* Add a log.

* Add calls to runtime.GC() at its start and end.

* Rename a variable.

* Replace calls to runtime.GC to calls to LogMemoryStats.

* Wrap the contents of LogMemoryStats in a log closure.
2021-01-24 14:48:11 +02:00
Ori Newman
9a17198e7d Remove redundant type check (#1445) 2021-01-24 14:14:03 +02:00
stasatdaglabs
756f40c59a Sync pruning point UTXO sets incrementally instead of all at once (#1431)
* Replaced the content of MsgIBDRootUTXOSetChunk with pairs of outpoint-utxo entry pairs.

* Rename utxoIter to utxoIterator.

* Add a big stinky TODO on an assert.

* Replace pruningStore staging with a UTXO set iterator.

* Reimplement receiveAndInsertIBDRootUTXOSet.

* Extract OutpointAndUTXOEntryPairsToDomainOutpointAndUTXOEntryPairs into domainconverters.go.

* Pass the outpoint and utxy entry pairs to the pruning store.

* Implement InsertCandidatePruningPointUTXOs.

* Implement ClearCandidatePruningPointUTXOs.

* Implement UpdateCandidatePruningPointMultiset.

* Use the candidate pruning point multiset in updatePruningPoint.

* Implement CandidatePruningPointUTXOIterator.

* Use the pruning point utxo set iterator for StageVirtualUTXOSet.

* Defer ClearCandidatePruningPointUTXOs.

* Implement OverwriteVirtualUTXOSet.

* Implement CommitCandidatePruningPointUTXOSet.

* Implement BeginOverwritingVirtualUTXOSet and FinishOverwritingVirtualUTXOSet.

* Implement overwriteVirtualUTXOSetAndCommitPruningPointUTXOSet.

* Rename ClearCandidatePruningPointUTXOs to ClearCandidatePruningPointData.

* Add missing methods to dbManager.

* Implement PruningPointUTXOs.

* Implement RecoverUTXOIfRequired.

* Delete the utxoserialization package.

* Fix compilation errors in TestValidateAndInsertPruningPoint.

* Switch order of operations in the if statements in PruningPointUTXOs so that Next() wouldn't be unnecessarily called.

* Fix missing pruning point utxo set staging and bad slice length.

* Fix no default multiset in InsertCandidatePruningPointUTXOs.

* Make go vet happy.

* Rename candidateXXX to importedXXX.

* Do some more renaming.

* Rename some more.

* Fix bad MsgIBDRootNotFound logic.

* Fix an error message.

* Simplify receiveIBDRootBlock.

* Fix error message in receiveAndInsertIBDRootUTXOSet.

* Do some more renaming.

* Fix merge errors.

* Fix a bug caused by calling iterator.First() unnecessarily.

* Remove databaseContext from stores and don't use a transaction in ClearXXX functions.

* Simplify receiveAndInsertIBDRootUTXOSet.

* Fix offset count in PruningPointUTXOs().

* Fix readOnlyUTXOIteratorWithDiff.First().

* Split handleRequestIBDRootUTXOSetAndBlockFlow into smaller methods.

* Rename IbdRootNotFound to UnexpectedPruningPoint.

* Rename requestIBDRootHash to requestPruningPointHash.

* Rename IBDRootHash to PruningPointHash.

* Rename RequestIBDRootUTXOSetAndBlock to RequestPruningPointUTXOSetAndBlock.

* Rename IBDRootUTXOSetChunk to PruningPointUTXOSetChunk.

* Rename RequestNextIBDRootUTXOSetChunk to RequestNextPruningPointUTXOSetChunk.

* Rename DoneIBDRootUTXOSetChunks to DonePruningPointUTXOSetChunks.

* Rename remaining references to IBD root.

* Fix an error message.

* Add a check for HadStartedImportingPruningPointUTXOSet in commitVirtualUTXODiff.

* Add a check for HadStartedImportingPruningPointUTXOSet in ImportPruningPointUTXOSetIntoVirtualUTXOSet.

* Move FinishImportingPruningPointUTXOSet closer to HadStartedImportingPruningPointUTXOSet.

* Remove reference to pruningStore in utxoSetIterator.

* Pointerify utxoSetIterator receivers.

* Fix bad insert in CommitImportedPruningPointUTXOSet.

* Rename commitImportedPruningPointUTXOSetAll to applyImportedPruningPointUTXOSet.

* Simplify PruningPointUTXOs.

* Add populateTransactionWithUTXOEntriesFromUTXOSet.

* Fix a TODO comment.

* Rename InsertImportedPruningPointUTXOs to AppendImportedPruningPointUTXOs.

* Extract handleRequestPruningPointUTXOSetAndBlockMessage to a separate method.

* Rename stuff in readOnlyUTXOIteratorWithDiff.First().

* Address toAddIterator in readOnlyUTXOIteratorWithDiff.First().

* Call First() before any full iteration on ReadOnlyUTXOSetIterator.

* Call First() before any full iteration on a database Cursor.

* Put StartImportingPruningPointUTXOSet inside the pruning point transaction.

* Make serializeOutpoint and serializeUTXOEntry free functions in pruningStore.

* Fix readOnlyUTXOIteratorWithDiff.First().

* Fix bad validations in importPruningPoint.

* Remove superfluous call to validateBlockTransactionsAgainstPastUTXO.
2021-01-21 17:24:52 +02:00
Elichai Turkel
6a03d31f98 Remove accidental pointer indirection in dbKey (#1441) 2021-01-21 12:14:52 +02:00
Ori Newman
319ab6cfcd Always request orphan roots, even when you get an inv of a known orphan (#1436)
Co-authored-by: Svarog <feanorr@gmail.com>
2021-01-20 10:58:45 +02:00
Ori Newman
abef96e3de Add TestIBDWithPruning (#1425)
* Add TestIBDWithPruning

* Test block count

* Fix a typo

Co-authored-by: Elichai Turkel <elichai.turkel@gmail.com>
Co-authored-by: Mike Zak <feanorr@gmail.com>
2021-01-20 10:07:32 +02:00
stasatdaglabs
2e0bc0f8c4 Increase P2P connections' dial timeouts to 5 seconds (#1437)
Co-authored-by: Elichai Turkel <elichai.turkel@gmail.com>
2021-01-20 09:48:48 +02:00
Ori Newman
acf5423c63 Remove docker test parallelism (#1434)
* Remove docker parallelism

* Remove redundant dash
2021-01-19 17:30:24 +02:00
Ori Newman
effb545d20 Fix wrong condition and add logs (#1435) 2021-01-19 17:20:25 +02:00
Svarog
ad9c213a06 Restructure database to prevent double-slashes in keys, causing bugs in cursors (#1432)
* Add TestValidateAndInsertPruningPointWithSideBlocks

* Optimize infrastracture bucket paths

* Update infrastracture tests

* Refactor the consensus/database layer

* Remove utils/dbkeys

* Use consensus/database in consensus instead of infrastructure

* Fix a bug in dbBucketToDatabaseBucket and MakeBucket combination

Co-authored-by: Elichai Turkel <elichai.turkel@gmail.com>
Co-authored-by: stasatdaglabs <39559713+stasatdaglabs@users.noreply.github.com>
2021-01-19 14:19:08 +02:00
Svarog
a4adbabf96 TestBuildBlockErrorCases and remove redundant check of coinbase script length (#1427)
* Write structure of TestBlockBuilderErrorCases

* Remove double verification of script length in serializeCoinbasePayload

* Remove redundant code in TestBuildBlockErrorCases

* Rename povTransactionHash -> povBlockHash

* Convert coinbasePayloadScriptPublicKeyMaxLength to uint8

* Re-use consensus in TestBuildBlockErrorCases
2021-01-19 10:37:51 +02:00
Ori Newman
799eb7515c Test validateAndInsertPruningPoint (#1420)
* Add TestValidateAndInsertPruningPoint

* Check fake UTXO set and validate that the pruning point changed
2021-01-18 18:17:13 +02:00
Mike Zak
0769705b37 Update to version 0.8.6 2021-01-18 15:17:15 +02:00
143 changed files with 5584 additions and 2665 deletions

View File

@@ -5,3 +5,5 @@ coverage:
project:
default:
informational: true
ignore:
- "**/*.pb.go" # Ignore all auto generated protobuf structures.

View File

@@ -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
}

View File

@@ -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

View File

@@ -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{}
}

View File

@@ -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{}
}

View 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{}
}

View File

@@ -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,
}
}

View File

@@ -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,
}
}

View 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,
}
}

View 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
}

View File

@@ -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,
}
}

View File

@@ -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{}
}

View 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{}
}

View 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,
}
}

View File

@@ -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{}
}

View 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{}
}

View 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{}
}

View File

@@ -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,
}
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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))

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -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
}

View File

@@ -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()
}
}

View File

@@ -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())
}
}
}
}

View File

@@ -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)

View File

@@ -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

View 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)
}
}

View 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)
}
}
})
}

View 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)
}
})
}

View File

@@ -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)
},
),

View File

@@ -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

View 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 := &parameterDescription{
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
View 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, &parameterDescription{
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()
}

View File

@@ -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
}

View File

@@ -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)

View 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
}

View File

@@ -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

View File

@@ -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) {

View File

@@ -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}
}

View File

@@ -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 {

View 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)
}

View File

@@ -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}
}

View File

@@ -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

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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

View File

@@ -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 {

View File

@@ -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
}

View File

@@ -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 {

View File

@@ -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
}

View File

@@ -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

View File

@@ -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 {

View File

@@ -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

View File

@@ -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

View File

@@ -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 {

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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)
}
}
})
}

View File

@@ -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)
}

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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(&params, 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
}
}
}
}

View File

@@ -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 {

View File

@@ -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)
}

View File

@@ -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)

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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}
}

View File

@@ -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)

View File

@@ -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 {

View File

@@ -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)
}

View File

@@ -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"
)

View File

@@ -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"
)

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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()

View File

@@ -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

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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