mirror of
https://github.com/kaspanet/kaspad.git
synced 2026-03-22 16:13:45 +00:00
Compare commits
15 Commits
fix-balanc
...
v0.11.10
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
598392d0cf | ||
|
|
6d27637055 | ||
|
|
4855d845b3 | ||
|
|
b1b179c105 | ||
|
|
dadacdc0f4 | ||
|
|
d2379608ad | ||
|
|
14b2bcbd81 | ||
|
|
71b284f4d5 | ||
|
|
0e1d247915 | ||
|
|
504ec36612 | ||
|
|
c80b113319 | ||
|
|
0bdd19136f | ||
|
|
7c1cddff11 | ||
|
|
064b0454e8 | ||
|
|
8282fb486e |
@@ -87,6 +87,7 @@ func (app *kaspadApp) main(startedChan chan<- struct{}) error {
|
||||
if app.cfg.Profile != "" {
|
||||
profiling.Start(app.cfg.Profile, log)
|
||||
}
|
||||
profiling.TrackHeap(app.cfg.AppDir, log)
|
||||
|
||||
// Return now if an interrupt signal was triggered.
|
||||
if signal.InterruptRequested(interrupt) {
|
||||
|
||||
@@ -436,10 +436,10 @@ func RPCBlockToDomainBlock(block *RPCBlock) (*externalapi.DomainBlock, error) {
|
||||
|
||||
// BlockWithTrustedDataToDomainBlockWithTrustedData converts *MsgBlockWithTrustedData to *externalapi.BlockWithTrustedData
|
||||
func BlockWithTrustedDataToDomainBlockWithTrustedData(block *MsgBlockWithTrustedData) *externalapi.BlockWithTrustedData {
|
||||
daaWindow := make([]*externalapi.TrustedDataDataDAABlock, len(block.DAAWindow))
|
||||
daaWindow := make([]*externalapi.TrustedDataDataDAAHeader, len(block.DAAWindow))
|
||||
for i, daaBlock := range block.DAAWindow {
|
||||
daaWindow[i] = &externalapi.TrustedDataDataDAABlock{
|
||||
Block: MsgBlockToDomainBlock(daaBlock.Block),
|
||||
daaWindow[i] = &externalapi.TrustedDataDataDAAHeader{
|
||||
Header: BlockHeaderToDomainBlockHeader(&daaBlock.Block.Header),
|
||||
GHOSTDAGData: ghostdagDataToDomainGHOSTDAGData(daaBlock.GHOSTDAGData),
|
||||
}
|
||||
}
|
||||
@@ -454,12 +454,27 @@ func BlockWithTrustedDataToDomainBlockWithTrustedData(block *MsgBlockWithTrusted
|
||||
|
||||
return &externalapi.BlockWithTrustedData{
|
||||
Block: MsgBlockToDomainBlock(block.Block),
|
||||
DAAScore: block.DAAScore,
|
||||
DAAWindow: daaWindow,
|
||||
GHOSTDAGData: ghostdagData,
|
||||
}
|
||||
}
|
||||
|
||||
// TrustedDataDataDAABlockV4ToTrustedDataDataDAAHeader converts *TrustedDataDAAHeader to *externalapi.TrustedDataDataDAAHeader
|
||||
func TrustedDataDataDAABlockV4ToTrustedDataDataDAAHeader(daaBlock *TrustedDataDAAHeader) *externalapi.TrustedDataDataDAAHeader {
|
||||
return &externalapi.TrustedDataDataDAAHeader{
|
||||
Header: BlockHeaderToDomainBlockHeader(daaBlock.Header),
|
||||
GHOSTDAGData: ghostdagDataToDomainGHOSTDAGData(daaBlock.GHOSTDAGData),
|
||||
}
|
||||
}
|
||||
|
||||
// GHOSTDAGHashPairToDomainGHOSTDAGHashPair converts *BlockGHOSTDAGDataHashPair to *externalapi.BlockGHOSTDAGDataHashPair
|
||||
func GHOSTDAGHashPairToDomainGHOSTDAGHashPair(datum *BlockGHOSTDAGDataHashPair) *externalapi.BlockGHOSTDAGDataHashPair {
|
||||
return &externalapi.BlockGHOSTDAGDataHashPair{
|
||||
Hash: datum.Hash,
|
||||
GHOSTDAGData: ghostdagDataToDomainGHOSTDAGData(datum.GHOSTDAGData),
|
||||
}
|
||||
}
|
||||
|
||||
func ghostdagDataToDomainGHOSTDAGData(data *BlockGHOSTDAGData) *externalapi.BlockGHOSTDAGData {
|
||||
bluesAnticoneSizes := make(map[externalapi.DomainHash]externalapi.KType, len(data.BluesAnticoneSizes))
|
||||
for _, pair := range data.BluesAnticoneSizes {
|
||||
@@ -500,7 +515,9 @@ func DomainBlockWithTrustedDataToBlockWithTrustedData(block *externalapi.BlockWi
|
||||
daaWindow := make([]*TrustedDataDataDAABlock, len(block.DAAWindow))
|
||||
for i, daaBlock := range block.DAAWindow {
|
||||
daaWindow[i] = &TrustedDataDataDAABlock{
|
||||
Block: DomainBlockToMsgBlock(daaBlock.Block),
|
||||
Block: &MsgBlock{
|
||||
Header: *DomainBlockHeaderToBlockHeader(daaBlock.Header),
|
||||
},
|
||||
GHOSTDAGData: domainGHOSTDAGDataGHOSTDAGData(daaBlock.GHOSTDAGData),
|
||||
}
|
||||
}
|
||||
@@ -515,7 +532,41 @@ func DomainBlockWithTrustedDataToBlockWithTrustedData(block *externalapi.BlockWi
|
||||
|
||||
return &MsgBlockWithTrustedData{
|
||||
Block: DomainBlockToMsgBlock(block.Block),
|
||||
DAAScore: block.DAAScore,
|
||||
DAAScore: block.Block.Header.DAAScore(),
|
||||
DAAWindow: daaWindow,
|
||||
GHOSTDAGData: ghostdagData,
|
||||
}
|
||||
}
|
||||
|
||||
// DomainBlockWithTrustedDataToBlockWithTrustedDataV4 converts a set of *externalapi.DomainBlock, daa window indices and ghostdag data indices
|
||||
// to *MsgBlockWithTrustedDataV4
|
||||
func DomainBlockWithTrustedDataToBlockWithTrustedDataV4(block *externalapi.DomainBlock, daaWindowIndices, ghostdagDataIndices []uint64) *MsgBlockWithTrustedDataV4 {
|
||||
return &MsgBlockWithTrustedDataV4{
|
||||
Block: DomainBlockToMsgBlock(block),
|
||||
DAAWindowIndices: daaWindowIndices,
|
||||
GHOSTDAGDataIndices: ghostdagDataIndices,
|
||||
}
|
||||
}
|
||||
|
||||
// DomainTrustedDataToTrustedData converts *externalapi.BlockWithTrustedData to *MsgBlockWithTrustedData
|
||||
func DomainTrustedDataToTrustedData(domainDAAWindow []*externalapi.TrustedDataDataDAAHeader, domainGHOSTDAGData []*externalapi.BlockGHOSTDAGDataHashPair) *MsgTrustedData {
|
||||
daaWindow := make([]*TrustedDataDAAHeader, len(domainDAAWindow))
|
||||
for i, daaBlock := range domainDAAWindow {
|
||||
daaWindow[i] = &TrustedDataDAAHeader{
|
||||
Header: DomainBlockHeaderToBlockHeader(daaBlock.Header),
|
||||
GHOSTDAGData: domainGHOSTDAGDataGHOSTDAGData(daaBlock.GHOSTDAGData),
|
||||
}
|
||||
}
|
||||
|
||||
ghostdagData := make([]*BlockGHOSTDAGDataHashPair, len(domainGHOSTDAGData))
|
||||
for i, datum := range domainGHOSTDAGData {
|
||||
ghostdagData[i] = &BlockGHOSTDAGDataHashPair{
|
||||
Hash: datum.Hash,
|
||||
GHOSTDAGData: domainGHOSTDAGDataGHOSTDAGData(datum.GHOSTDAGData),
|
||||
}
|
||||
}
|
||||
|
||||
return &MsgTrustedData{
|
||||
DAAWindow: daaWindow,
|
||||
GHOSTDAGData: ghostdagData,
|
||||
}
|
||||
|
||||
@@ -66,6 +66,9 @@ const (
|
||||
CmdPruningPoints
|
||||
CmdRequestPruningPointProof
|
||||
CmdPruningPointProof
|
||||
CmdReady
|
||||
CmdTrustedData
|
||||
CmdBlockWithTrustedDataV4
|
||||
|
||||
// rpc
|
||||
CmdGetCurrentNetworkRequestMessage
|
||||
@@ -187,6 +190,9 @@ var ProtocolMessageCommandToString = map[MessageCommand]string{
|
||||
CmdPruningPoints: "PruningPoints",
|
||||
CmdRequestPruningPointProof: "RequestPruningPointProof",
|
||||
CmdPruningPointProof: "PruningPointProof",
|
||||
CmdReady: "Ready",
|
||||
CmdTrustedData: "TrustedData",
|
||||
CmdBlockWithTrustedDataV4: "BlockWithTrustedDataV4",
|
||||
}
|
||||
|
||||
// RPCMessageCommandToString maps all MessageCommands to their string representation
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
|
||||
// TestBlock tests the MsgBlock API.
|
||||
func TestBlock(t *testing.T) {
|
||||
pver := ProtocolVersion
|
||||
pver := uint32(4)
|
||||
|
||||
// Block 1 header.
|
||||
parents := blockOne.Header.Parents
|
||||
|
||||
20
app/appmessage/p2p_msgblockwithtrusteddatav4.go
Normal file
20
app/appmessage/p2p_msgblockwithtrusteddatav4.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package appmessage
|
||||
|
||||
// MsgBlockWithTrustedDataV4 represents a kaspa BlockWithTrustedDataV4 message
|
||||
type MsgBlockWithTrustedDataV4 struct {
|
||||
baseMessage
|
||||
|
||||
Block *MsgBlock
|
||||
DAAWindowIndices []uint64
|
||||
GHOSTDAGDataIndices []uint64
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message
|
||||
func (msg *MsgBlockWithTrustedDataV4) Command() MessageCommand {
|
||||
return CmdBlockWithTrustedDataV4
|
||||
}
|
||||
|
||||
// NewMsgBlockWithTrustedDataV4 returns a new MsgBlockWithTrustedDataV4.
|
||||
func NewMsgBlockWithTrustedDataV4() *MsgBlockWithTrustedDataV4 {
|
||||
return &MsgBlockWithTrustedDataV4{}
|
||||
}
|
||||
25
app/appmessage/p2p_msgtrusteddata.go
Normal file
25
app/appmessage/p2p_msgtrusteddata.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package appmessage
|
||||
|
||||
// MsgTrustedData represents a kaspa TrustedData message
|
||||
type MsgTrustedData struct {
|
||||
baseMessage
|
||||
|
||||
DAAWindow []*TrustedDataDAAHeader
|
||||
GHOSTDAGData []*BlockGHOSTDAGDataHashPair
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message
|
||||
func (msg *MsgTrustedData) Command() MessageCommand {
|
||||
return CmdTrustedData
|
||||
}
|
||||
|
||||
// NewMsgTrustedData returns a new MsgTrustedData.
|
||||
func NewMsgTrustedData() *MsgTrustedData {
|
||||
return &MsgTrustedData{}
|
||||
}
|
||||
|
||||
// TrustedDataDAAHeader is an appmessage representation of externalapi.TrustedDataDataDAAHeader
|
||||
type TrustedDataDAAHeader struct {
|
||||
Header *MsgBlockHeader
|
||||
GHOSTDAGData *BlockGHOSTDAGData
|
||||
}
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
|
||||
// TestTx tests the MsgTx API.
|
||||
func TestTx(t *testing.T) {
|
||||
pver := ProtocolVersion
|
||||
pver := uint32(4)
|
||||
|
||||
txIDStr := "000000000003ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506"
|
||||
txID, err := transactionid.FromString(txIDStr)
|
||||
|
||||
@@ -82,12 +82,12 @@ func (msg *MsgVersion) Command() MessageCommand {
|
||||
// Message interface using the passed parameters and defaults for the remaining
|
||||
// fields.
|
||||
func NewMsgVersion(addr *NetAddress, id *id.ID, network string,
|
||||
subnetworkID *externalapi.DomainSubnetworkID) *MsgVersion {
|
||||
subnetworkID *externalapi.DomainSubnetworkID, protocolVersion uint32) *MsgVersion {
|
||||
|
||||
// Limit the timestamp to one millisecond precision since the protocol
|
||||
// doesn't support better.
|
||||
return &MsgVersion{
|
||||
ProtocolVersion: ProtocolVersion,
|
||||
ProtocolVersion: protocolVersion,
|
||||
Network: network,
|
||||
Services: 0,
|
||||
Timestamp: mstime.Now(),
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
|
||||
// TestVersion tests the MsgVersion API.
|
||||
func TestVersion(t *testing.T) {
|
||||
pver := ProtocolVersion
|
||||
pver := uint32(4)
|
||||
|
||||
// Create version message data.
|
||||
tcpAddrMe := &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 16111}
|
||||
@@ -26,7 +26,7 @@ func TestVersion(t *testing.T) {
|
||||
}
|
||||
|
||||
// Ensure we get the correct data back out.
|
||||
msg := NewMsgVersion(me, generatedID, "mainnet", nil)
|
||||
msg := NewMsgVersion(me, generatedID, "mainnet", nil, 4)
|
||||
if msg.ProtocolVersion != pver {
|
||||
t.Errorf("NewMsgVersion: wrong protocol version - got %v, want %v",
|
||||
msg.ProtocolVersion, pver)
|
||||
|
||||
@@ -5,8 +5,9 @@
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/util/mstime"
|
||||
"net"
|
||||
|
||||
"github.com/kaspanet/kaspad/util/mstime"
|
||||
)
|
||||
|
||||
// NetAddress defines information about a peer on the network including the time
|
||||
@@ -57,3 +58,7 @@ func NewNetAddressTimestamp(
|
||||
func NewNetAddress(addr *net.TCPAddr) *NetAddress {
|
||||
return NewNetAddressIPPort(addr.IP, uint16(addr.Port))
|
||||
}
|
||||
|
||||
func (na NetAddress) String() string {
|
||||
return na.TCPAddress().String()
|
||||
}
|
||||
|
||||
22
app/appmessage/p2p_ready.go
Normal file
22
app/appmessage/p2p_ready.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package appmessage
|
||||
|
||||
// MsgReady implements the Message interface and represents a kaspa
|
||||
// Ready message. It is used to notify that the peer is ready to receive
|
||||
// messages.
|
||||
//
|
||||
// This message has no payload.
|
||||
type MsgReady struct {
|
||||
baseMessage
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
// of the Message interface implementation.
|
||||
func (msg *MsgReady) Command() MessageCommand {
|
||||
return CmdReady
|
||||
}
|
||||
|
||||
// NewMsgReady returns a new kaspa Ready message that conforms to the
|
||||
// Message interface.
|
||||
func NewMsgReady() *MsgReady {
|
||||
return &MsgReady{}
|
||||
}
|
||||
@@ -11,9 +11,6 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
// ProtocolVersion is the latest protocol version this package supports.
|
||||
ProtocolVersion uint32 = 3
|
||||
|
||||
// DefaultServices describes the default services that are supported by
|
||||
// the server.
|
||||
DefaultServices = SFNodeNetwork | SFNodeBloom | SFNodeCF
|
||||
|
||||
@@ -4,7 +4,8 @@ package appmessage
|
||||
// its respective RPC message
|
||||
type SubmitBlockRequestMessage struct {
|
||||
baseMessage
|
||||
Block *RPCBlock
|
||||
Block *RPCBlock
|
||||
AllowNonDAABlocks bool
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message
|
||||
@@ -13,9 +14,10 @@ func (msg *SubmitBlockRequestMessage) Command() MessageCommand {
|
||||
}
|
||||
|
||||
// NewSubmitBlockRequestMessage returns a instance of the message
|
||||
func NewSubmitBlockRequestMessage(block *RPCBlock) *SubmitBlockRequestMessage {
|
||||
func NewSubmitBlockRequestMessage(block *RPCBlock, allowNonDAABlocks bool) *SubmitBlockRequestMessage {
|
||||
return &SubmitBlockRequestMessage{
|
||||
Block: block,
|
||||
Block: block,
|
||||
AllowNonDAABlocks: allowNonDAABlocks,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
peerpkg "github.com/kaspanet/kaspad/app/protocol/peer"
|
||||
routerpkg "github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
@@ -12,3 +14,14 @@ const DefaultTimeout = 120 * time.Second
|
||||
|
||||
// ErrPeerWithSameIDExists signifies that a peer with the same ID already exist.
|
||||
var ErrPeerWithSameIDExists = errors.New("ready peer with the same ID already exists")
|
||||
|
||||
type flowExecuteFunc func(peer *peerpkg.Peer)
|
||||
|
||||
// Flow is a a data structure that is used in order to associate a p2p flow to some route in a router.
|
||||
type Flow struct {
|
||||
Name string
|
||||
ExecuteFunc flowExecuteFunc
|
||||
}
|
||||
|
||||
// FlowInitializeFunc is a function that is used in order to initialize a flow
|
||||
type FlowInitializeFunc func(route *routerpkg.Route, peer *peerpkg.Peer) error
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/app/protocol/flows/blockrelay"
|
||||
)
|
||||
|
||||
// OnNewBlock updates the mempool after a new block arrival, and
|
||||
@@ -109,7 +108,7 @@ func (f *FlowContext) broadcastTransactionsAfterBlockAdded(
|
||||
|
||||
// SharedRequestedBlocks returns a *blockrelay.SharedRequestedBlocks for sharing
|
||||
// data about requested blocks between different peers.
|
||||
func (f *FlowContext) SharedRequestedBlocks() *blockrelay.SharedRequestedBlocks {
|
||||
func (f *FlowContext) SharedRequestedBlocks() *SharedRequestedBlocks {
|
||||
return f.sharedRequestedBlocks
|
||||
}
|
||||
|
||||
|
||||
@@ -10,8 +10,6 @@ import (
|
||||
|
||||
"github.com/kaspanet/kaspad/domain"
|
||||
|
||||
"github.com/kaspanet/kaspad/app/protocol/flows/blockrelay"
|
||||
"github.com/kaspanet/kaspad/app/protocol/flows/transactionrelay"
|
||||
peerpkg "github.com/kaspanet/kaspad/app/protocol/peer"
|
||||
"github.com/kaspanet/kaspad/infrastructure/config"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/addressmanager"
|
||||
@@ -52,9 +50,9 @@ type FlowContext struct {
|
||||
onTransactionAddedToMempoolHandler OnTransactionAddedToMempoolHandler
|
||||
|
||||
lastRebroadcastTime time.Time
|
||||
sharedRequestedTransactions *transactionrelay.SharedRequestedTransactions
|
||||
sharedRequestedTransactions *SharedRequestedTransactions
|
||||
|
||||
sharedRequestedBlocks *blockrelay.SharedRequestedBlocks
|
||||
sharedRequestedBlocks *SharedRequestedBlocks
|
||||
|
||||
ibdPeer *peerpkg.Peer
|
||||
ibdPeerMutex sync.RWMutex
|
||||
@@ -82,8 +80,8 @@ func New(cfg *config.Config, domain domain.Domain, addressManager *addressmanage
|
||||
domain: domain,
|
||||
addressManager: addressManager,
|
||||
connectionManager: connectionManager,
|
||||
sharedRequestedTransactions: transactionrelay.NewSharedRequestedTransactions(),
|
||||
sharedRequestedBlocks: blockrelay.NewSharedRequestedBlocks(),
|
||||
sharedRequestedTransactions: NewSharedRequestedTransactions(),
|
||||
sharedRequestedBlocks: NewSharedRequestedBlocks(),
|
||||
peers: make(map[id.ID]*peerpkg.Peer),
|
||||
orphans: make(map[externalapi.DomainHash]*externalapi.DomainBlock),
|
||||
timeStarted: mstime.Now().UnixMilliseconds(),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package blockrelay
|
||||
package flowcontext
|
||||
|
||||
import (
|
||||
"sync"
|
||||
@@ -13,13 +13,15 @@ type SharedRequestedBlocks struct {
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
func (s *SharedRequestedBlocks) remove(hash *externalapi.DomainHash) {
|
||||
// Remove removes a block from the set.
|
||||
func (s *SharedRequestedBlocks) Remove(hash *externalapi.DomainHash) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
delete(s.blocks, *hash)
|
||||
}
|
||||
|
||||
func (s *SharedRequestedBlocks) removeSet(blockHashes map[externalapi.DomainHash]struct{}) {
|
||||
// RemoveSet removes a set of blocks from the set.
|
||||
func (s *SharedRequestedBlocks) RemoveSet(blockHashes map[externalapi.DomainHash]struct{}) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
for hash := range blockHashes {
|
||||
@@ -27,7 +29,8 @@ func (s *SharedRequestedBlocks) removeSet(blockHashes map[externalapi.DomainHash
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SharedRequestedBlocks) addIfNotExists(hash *externalapi.DomainHash) (exists bool) {
|
||||
// AddIfNotExists adds a block to the set if it doesn't exist yet.
|
||||
func (s *SharedRequestedBlocks) AddIfNotExists(hash *externalapi.DomainHash) (exists bool) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
_, ok := s.blocks[*hash]
|
||||
@@ -1,4 +1,4 @@
|
||||
package transactionrelay
|
||||
package flowcontext
|
||||
|
||||
import (
|
||||
"sync"
|
||||
@@ -13,13 +13,15 @@ type SharedRequestedTransactions struct {
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
func (s *SharedRequestedTransactions) remove(txID *externalapi.DomainTransactionID) {
|
||||
// Remove removes a transaction from the set.
|
||||
func (s *SharedRequestedTransactions) Remove(txID *externalapi.DomainTransactionID) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
delete(s.transactions, *txID)
|
||||
}
|
||||
|
||||
func (s *SharedRequestedTransactions) removeMany(txIDs []*externalapi.DomainTransactionID) {
|
||||
// RemoveMany removes a set of transactions from the set.
|
||||
func (s *SharedRequestedTransactions) RemoveMany(txIDs []*externalapi.DomainTransactionID) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
for _, txID := range txIDs {
|
||||
@@ -27,7 +29,8 @@ func (s *SharedRequestedTransactions) removeMany(txIDs []*externalapi.DomainTran
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SharedRequestedTransactions) addIfNotExists(txID *externalapi.DomainTransactionID) (exists bool) {
|
||||
// AddIfNotExists adds a transaction to the set if it doesn't exist yet.
|
||||
func (s *SharedRequestedTransactions) AddIfNotExists(txID *externalapi.DomainTransactionID) (exists bool) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
_, ok := s.transactions[*txID]
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/app/protocol/flows/transactionrelay"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
|
||||
)
|
||||
@@ -30,7 +29,7 @@ func (f *FlowContext) shouldRebroadcastTransactions() bool {
|
||||
|
||||
// SharedRequestedTransactions returns a *transactionrelay.SharedRequestedTransactions for sharing
|
||||
// data about requested transactions between different peers.
|
||||
func (f *FlowContext) SharedRequestedTransactions() *transactionrelay.SharedRequestedTransactions {
|
||||
func (f *FlowContext) SharedRequestedTransactions() *SharedRequestedTransactions {
|
||||
return f.sharedRequestedTransactions
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ type HandleHandshakeContext interface {
|
||||
HandleError(err error, flowName string, isStopping *uint32, errChan chan<- error)
|
||||
}
|
||||
|
||||
// HandleHandshake sets up the handshake protocol - It sends a version message and waits for an incoming
|
||||
// HandleHandshake sets up the new_handshake protocol - It sends a version message and waits for an incoming
|
||||
// version message, as well as a verack for the sent version
|
||||
func HandleHandshake(context HandleHandshakeContext, netConnection *netadapter.NetConnection,
|
||||
receiveVersionRoute *routerpkg.Route, sendVersionRoute *routerpkg.Route, outgoingRoute *routerpkg.Route,
|
||||
@@ -98,7 +98,7 @@ func HandleHandshake(context HandleHandshakeContext, netConnection *netadapter.N
|
||||
}
|
||||
|
||||
// Handshake is different from other flows, since in it should forward router.ErrRouteClosed to errChan
|
||||
// Therefore we implement a separate handleError for handshake
|
||||
// Therefore we implement a separate handleError for new_handshake
|
||||
func handleError(err error, flowName string, isStopping *uint32, errChan chan error) {
|
||||
if errors.Is(err, routerpkg.ErrRouteClosed) {
|
||||
if atomic.AddUint32(isStopping, 1) == 1 {
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/kaspanet/kaspad/app/protocol/protocolerrors"
|
||||
"github.com/kaspanet/kaspad/infrastructure/logger"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -17,7 +18,9 @@ var (
|
||||
|
||||
// minAcceptableProtocolVersion is the lowest protocol version that a
|
||||
// connected peer may support.
|
||||
minAcceptableProtocolVersion = appmessage.ProtocolVersion
|
||||
minAcceptableProtocolVersion = uint32(3)
|
||||
|
||||
maxAcceptableProtocolVersion = uint32(4)
|
||||
)
|
||||
|
||||
type receiveVersionFlow struct {
|
||||
@@ -97,7 +100,12 @@ func (flow *receiveVersionFlow) start() (*appmessage.NetAddress, error) {
|
||||
return nil, protocolerrors.New(false, "incompatible subnetworks")
|
||||
}
|
||||
|
||||
flow.peer.UpdateFieldsFromMsgVersion(msgVersion)
|
||||
if flow.Config().ProtocolVersion > maxAcceptableProtocolVersion {
|
||||
return nil, errors.Errorf("%d is a non existing protocol version", flow.Config().ProtocolVersion)
|
||||
}
|
||||
|
||||
maxProtocolVersion := flow.Config().ProtocolVersion
|
||||
flow.peer.UpdateFieldsFromMsgVersion(msgVersion, maxProtocolVersion)
|
||||
err = flow.outgoingRoute.Enqueue(appmessage.NewMsgVerAck())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/kaspanet/kaspad/infrastructure/logger"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
||||
"github.com/kaspanet/kaspad/version"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -56,15 +57,18 @@ func (flow *sendVersionFlow) start() error {
|
||||
// Version message.
|
||||
localAddress := flow.AddressManager().BestLocalAddress(flow.peer.Connection().NetAddress())
|
||||
subnetworkID := flow.Config().SubnetworkID
|
||||
if flow.Config().ProtocolVersion < minAcceptableProtocolVersion {
|
||||
return errors.Errorf("configured protocol version %d is obsolete", flow.Config().ProtocolVersion)
|
||||
}
|
||||
msg := appmessage.NewMsgVersion(localAddress, flow.NetAdapter().ID(),
|
||||
flow.Config().ActiveNetParams.Name, subnetworkID)
|
||||
flow.Config().ActiveNetParams.Name, subnetworkID, flow.Config().ProtocolVersion)
|
||||
msg.AddUserAgent(userAgentName, userAgentVersion, flow.Config().UserAgentComments...)
|
||||
|
||||
// Advertise the services flag
|
||||
msg.Services = defaultServices
|
||||
|
||||
// Advertise our max supported protocol version.
|
||||
msg.ProtocolVersion = appmessage.ProtocolVersion
|
||||
msg.ProtocolVersion = flow.Config().ProtocolVersion
|
||||
|
||||
// Advertise if inv messages for transactions are desired.
|
||||
msg.DisableRelayTx = flow.Config().BlocksOnly
|
||||
|
||||
9
app/protocol/flows/ready/log.go
Normal file
9
app/protocol/flows/ready/log.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package ready
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/infrastructure/logger"
|
||||
"github.com/kaspanet/kaspad/util/panics"
|
||||
)
|
||||
|
||||
var log = logger.RegisterSubSystem("PROT")
|
||||
var spawn = panics.GoroutineWrapperFunc(log)
|
||||
56
app/protocol/flows/ready/ready.go
Normal file
56
app/protocol/flows/ready/ready.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package ready
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/app/protocol/common"
|
||||
"sync/atomic"
|
||||
|
||||
peerpkg "github.com/kaspanet/kaspad/app/protocol/peer"
|
||||
"github.com/kaspanet/kaspad/app/protocol/protocolerrors"
|
||||
routerpkg "github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// HandleReady notify the other peer that peer is ready for messages, and wait for the other peer
|
||||
// to send a ready message before start running the flows.
|
||||
func HandleReady(incomingRoute *routerpkg.Route, outgoingRoute *routerpkg.Route,
|
||||
peer *peerpkg.Peer,
|
||||
) error {
|
||||
|
||||
log.Debugf("Sending ready message to %s", peer)
|
||||
|
||||
isStopping := uint32(0)
|
||||
err := outgoingRoute.Enqueue(appmessage.NewMsgReady())
|
||||
if err != nil {
|
||||
return handleError(err, "HandleReady", &isStopping)
|
||||
}
|
||||
|
||||
_, err = incomingRoute.DequeueWithTimeout(common.DefaultTimeout)
|
||||
if err != nil {
|
||||
return handleError(err, "HandleReady", &isStopping)
|
||||
}
|
||||
|
||||
log.Debugf("Got ready message from %s", peer)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Ready is different from other flows, since in it should forward router.ErrRouteClosed to errChan
|
||||
// Therefore we implement a separate handleError for 'ready'
|
||||
func handleError(err error, flowName string, isStopping *uint32) error {
|
||||
if errors.Is(err, routerpkg.ErrRouteClosed) {
|
||||
if atomic.AddUint32(isStopping, 1) == 1 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if protocolErr := (protocolerrors.ProtocolError{}); errors.As(err, &protocolErr) {
|
||||
log.Errorf("Ready protocol error from %s: %s", flowName, err)
|
||||
if atomic.AddUint32(isStopping, 1) == 1 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
panic(err)
|
||||
}
|
||||
@@ -13,15 +13,21 @@ func (flow *handleRelayInvsFlow) sendGetBlockLocator(highHash *externalapi.Domai
|
||||
}
|
||||
|
||||
func (flow *handleRelayInvsFlow) receiveBlockLocator() (blockLocatorHashes []*externalapi.DomainHash, err error) {
|
||||
message, err := flow.dequeueIncomingMessageAndSkipInvs(common.DefaultTimeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
for {
|
||||
message, err := flow.incomingRoute.DequeueWithTimeout(common.DefaultTimeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch message := message.(type) {
|
||||
case *appmessage.MsgInvRelayBlock:
|
||||
flow.invsQueue = append(flow.invsQueue, message)
|
||||
case *appmessage.MsgBlockLocator:
|
||||
return message.BlockLocatorHashes, nil
|
||||
default:
|
||||
return nil,
|
||||
protocolerrors.Errorf(true, "received unexpected message type. "+
|
||||
"expected: %s, got: %s", appmessage.CmdBlockLocator, message.Command())
|
||||
}
|
||||
}
|
||||
msgBlockLocator, ok := message.(*appmessage.MsgBlockLocator)
|
||||
if !ok {
|
||||
return nil,
|
||||
protocolerrors.Errorf(true, "received unexpected message type. "+
|
||||
"expected: %s, got: %s", appmessage.CmdBlockLocator, message.Command())
|
||||
}
|
||||
return msgBlockLocator.BlockLocatorHashes, nil
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package blockrelay
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/app/protocol/common"
|
||||
"github.com/kaspanet/kaspad/app/protocol/flowcontext"
|
||||
peerpkg "github.com/kaspanet/kaspad/app/protocol/peer"
|
||||
"github.com/kaspanet/kaspad/app/protocol/protocolerrors"
|
||||
"github.com/kaspanet/kaspad/domain"
|
||||
@@ -26,14 +27,12 @@ type RelayInvsContext interface {
|
||||
OnNewBlock(block *externalapi.DomainBlock, virtualChangeSet *externalapi.VirtualChangeSet) error
|
||||
OnVirtualChange(virtualChangeSet *externalapi.VirtualChangeSet) error
|
||||
OnPruningPointUTXOSetOverride() error
|
||||
SharedRequestedBlocks() *SharedRequestedBlocks
|
||||
SharedRequestedBlocks() *flowcontext.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
|
||||
UnsetIBDRunning()
|
||||
IsRecoverableError(err error) bool
|
||||
}
|
||||
|
||||
@@ -56,7 +55,10 @@ func HandleRelayInvs(context RelayInvsContext, incomingRoute *router.Route, outg
|
||||
peer: peer,
|
||||
invsQueue: make([]*appmessage.MsgInvRelayBlock, 0),
|
||||
}
|
||||
return flow.start()
|
||||
err := flow.start()
|
||||
// Currently, HandleRelayInvs flow is the only place where IBD is triggered, so the channel can be closed now
|
||||
close(peer.IBDRequestChannel())
|
||||
return err
|
||||
}
|
||||
|
||||
func (flow *handleRelayInvsFlow) start() error {
|
||||
@@ -194,14 +196,14 @@ func (flow *handleRelayInvsFlow) readInv() (*appmessage.MsgInvRelayBlock, error)
|
||||
}
|
||||
|
||||
func (flow *handleRelayInvsFlow) requestBlock(requestHash *externalapi.DomainHash) (*externalapi.DomainBlock, bool, error) {
|
||||
exists := flow.SharedRequestedBlocks().addIfNotExists(requestHash)
|
||||
exists := flow.SharedRequestedBlocks().AddIfNotExists(requestHash)
|
||||
if exists {
|
||||
return nil, true, nil
|
||||
}
|
||||
|
||||
// In case the function returns earlier than expected, we want to make sure flow.SharedRequestedBlocks() is
|
||||
// clean from any pending blocks.
|
||||
defer flow.SharedRequestedBlocks().remove(requestHash)
|
||||
defer flow.SharedRequestedBlocks().Remove(requestHash)
|
||||
|
||||
getRelayBlocksMsg := appmessage.NewMsgRequestRelayBlocks([]*externalapi.DomainHash{requestHash})
|
||||
err := flow.outgoingRoute.Enqueue(getRelayBlocksMsg)
|
||||
@@ -305,7 +307,14 @@ func (flow *handleRelayInvsFlow) processOrphan(block *externalapi.DomainBlock) e
|
||||
// Start IBD unless we already are in IBD
|
||||
log.Debugf("Block %s is out of orphan resolution range. "+
|
||||
"Attempting to start IBD against it.", blockHash)
|
||||
return flow.runIBDIfNotRunning(block)
|
||||
|
||||
// Send the block to IBD flow via the IBDRequestChannel.
|
||||
// Note that this is a non-blocking send, since if IBD is already running, there is no need to trigger it
|
||||
select {
|
||||
case flow.peer.IBDRequestChannel() <- block:
|
||||
default:
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (flow *handleRelayInvsFlow) isGenesisVirtualSelectedParent() (bool, error) {
|
||||
@@ -1,22 +1,69 @@
|
||||
package blockrelay
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/kaspanet/kaspad/infrastructure/logger"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"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/domain"
|
||||
"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/infrastructure/config"
|
||||
"github.com/kaspanet/kaspad/infrastructure/logger"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
||||
"github.com/pkg/errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (flow *handleRelayInvsFlow) runIBDIfNotRunning(block *externalapi.DomainBlock) error {
|
||||
// IBDContext is the interface for the context needed for the HandleIBD flow.
|
||||
type IBDContext interface {
|
||||
Domain() domain.Domain
|
||||
Config() *config.Config
|
||||
OnNewBlock(block *externalapi.DomainBlock, virtualChangeSet *externalapi.VirtualChangeSet) error
|
||||
OnVirtualChange(virtualChangeSet *externalapi.VirtualChangeSet) error
|
||||
OnPruningPointUTXOSetOverride() error
|
||||
IsIBDRunning() bool
|
||||
TrySetIBDRunning(ibdPeer *peerpkg.Peer) bool
|
||||
UnsetIBDRunning()
|
||||
IsRecoverableError(err error) bool
|
||||
}
|
||||
|
||||
type handleIBDFlow struct {
|
||||
IBDContext
|
||||
incomingRoute, outgoingRoute *router.Route
|
||||
peer *peerpkg.Peer
|
||||
}
|
||||
|
||||
// HandleIBD handles IBD
|
||||
func HandleIBD(context IBDContext, incomingRoute *router.Route, outgoingRoute *router.Route,
|
||||
peer *peerpkg.Peer) error {
|
||||
|
||||
flow := &handleIBDFlow{
|
||||
IBDContext: context,
|
||||
incomingRoute: incomingRoute,
|
||||
outgoingRoute: outgoingRoute,
|
||||
peer: peer,
|
||||
}
|
||||
return flow.start()
|
||||
}
|
||||
|
||||
func (flow *handleIBDFlow) start() error {
|
||||
for {
|
||||
// Wait for IBD requests triggered by other flows
|
||||
block, ok := <-flow.peer.IBDRequestChannel()
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
err := flow.runIBDIfNotRunning(block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (flow *handleIBDFlow) runIBDIfNotRunning(block *externalapi.DomainBlock) error {
|
||||
wasIBDNotRunning := flow.TrySetIBDRunning(flow.peer)
|
||||
if !wasIBDNotRunning {
|
||||
log.Debugf("IBD is already running")
|
||||
@@ -84,7 +131,16 @@ func (flow *handleRelayInvsFlow) runIBDIfNotRunning(block *externalapi.DomainBlo
|
||||
return nil
|
||||
}
|
||||
|
||||
func (flow *handleRelayInvsFlow) logIBDFinished(isFinishedSuccessfully bool) {
|
||||
func (flow *handleIBDFlow) isGenesisVirtualSelectedParent() (bool, error) {
|
||||
virtualSelectedParent, err := flow.Domain().Consensus().GetVirtualSelectedParent()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return virtualSelectedParent.Equal(flow.Config().NetParams().GenesisHash), nil
|
||||
}
|
||||
|
||||
func (flow *handleIBDFlow) logIBDFinished(isFinishedSuccessfully bool) {
|
||||
successString := "successfully"
|
||||
if !isFinishedSuccessfully {
|
||||
successString = "(interrupted)"
|
||||
@@ -95,7 +151,7 @@ func (flow *handleRelayInvsFlow) logIBDFinished(isFinishedSuccessfully bool) {
|
||||
// findHighestSharedBlock attempts to find the highest shared block between the peer
|
||||
// and this node. This method may fail because the peer and us have conflicting pruning
|
||||
// points. In that case we return (nil, false, nil) so that we may stop IBD gracefully.
|
||||
func (flow *handleRelayInvsFlow) findHighestSharedBlockHash(
|
||||
func (flow *handleIBDFlow) findHighestSharedBlockHash(
|
||||
targetHash *externalapi.DomainHash) (*externalapi.DomainHash, bool, error) {
|
||||
|
||||
log.Debugf("Sending a blockLocator to %s between pruning point and headers selected tip", flow.peer)
|
||||
@@ -138,7 +194,7 @@ func (flow *handleRelayInvsFlow) findHighestSharedBlockHash(
|
||||
}
|
||||
}
|
||||
|
||||
func (flow *handleRelayInvsFlow) nextBlockLocator(lowHash, highHash *externalapi.DomainHash) (externalapi.BlockLocator, error) {
|
||||
func (flow *handleIBDFlow) nextBlockLocator(lowHash, highHash *externalapi.DomainHash) (externalapi.BlockLocator, error) {
|
||||
log.Debugf("Sending a blockLocator to %s between %s and %s", flow.peer, lowHash, highHash)
|
||||
blockLocator, err := flow.Domain().Consensus().CreateHeadersSelectedChainBlockLocator(lowHash, highHash)
|
||||
if err != nil {
|
||||
@@ -156,7 +212,7 @@ func (flow *handleRelayInvsFlow) nextBlockLocator(lowHash, highHash *externalapi
|
||||
return blockLocator, nil
|
||||
}
|
||||
|
||||
func (flow *handleRelayInvsFlow) findHighestHashIndex(
|
||||
func (flow *handleIBDFlow) findHighestHashIndex(
|
||||
highestHash *externalapi.DomainHash, blockLocator externalapi.BlockLocator) (int, error) {
|
||||
|
||||
highestHashIndex := 0
|
||||
@@ -181,7 +237,7 @@ func (flow *handleRelayInvsFlow) findHighestHashIndex(
|
||||
// fetchHighestHash attempts to fetch the highest hash the peer knows amongst the given
|
||||
// blockLocator. This method may fail because the peer and us have conflicting pruning
|
||||
// points. In that case we return (nil, false, nil) so that we may stop IBD gracefully.
|
||||
func (flow *handleRelayInvsFlow) fetchHighestHash(
|
||||
func (flow *handleIBDFlow) fetchHighestHash(
|
||||
targetHash *externalapi.DomainHash, blockLocator externalapi.BlockLocator) (*externalapi.DomainHash, bool, error) {
|
||||
|
||||
ibdBlockLocatorMessage := appmessage.NewMsgIBDBlockLocator(targetHash, blockLocator)
|
||||
@@ -189,7 +245,7 @@ func (flow *handleRelayInvsFlow) fetchHighestHash(
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
message, err := flow.dequeueIncomingMessageAndSkipInvs(common.DefaultTimeout)
|
||||
message, err := flow.incomingRoute.DequeueWithTimeout(common.DefaultTimeout)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
@@ -209,7 +265,7 @@ func (flow *handleRelayInvsFlow) fetchHighestHash(
|
||||
}
|
||||
}
|
||||
|
||||
func (flow *handleRelayInvsFlow) syncPruningPointFutureHeaders(consensus externalapi.Consensus, highestSharedBlockHash *externalapi.DomainHash,
|
||||
func (flow *handleIBDFlow) syncPruningPointFutureHeaders(consensus externalapi.Consensus, highestSharedBlockHash *externalapi.DomainHash,
|
||||
highHash *externalapi.DomainHash) error {
|
||||
|
||||
log.Infof("Downloading headers from %s", flow.peer)
|
||||
@@ -273,15 +329,15 @@ func (flow *handleRelayInvsFlow) syncPruningPointFutureHeaders(consensus externa
|
||||
}
|
||||
}
|
||||
|
||||
func (flow *handleRelayInvsFlow) sendRequestHeaders(highestSharedBlockHash *externalapi.DomainHash,
|
||||
func (flow *handleIBDFlow) sendRequestHeaders(highestSharedBlockHash *externalapi.DomainHash,
|
||||
peerSelectedTipHash *externalapi.DomainHash) error {
|
||||
|
||||
msgGetBlockInvs := appmessage.NewMsgRequstHeaders(highestSharedBlockHash, peerSelectedTipHash)
|
||||
return flow.outgoingRoute.Enqueue(msgGetBlockInvs)
|
||||
}
|
||||
|
||||
func (flow *handleRelayInvsFlow) receiveHeaders() (msgIBDBlock *appmessage.BlockHeadersMessage, doneHeaders bool, err error) {
|
||||
message, err := flow.dequeueIncomingMessageAndSkipInvs(common.DefaultTimeout)
|
||||
func (flow *handleIBDFlow) receiveHeaders() (msgIBDBlock *appmessage.BlockHeadersMessage, doneHeaders bool, err error) {
|
||||
message, err := flow.incomingRoute.DequeueWithTimeout(common.DefaultTimeout)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
@@ -300,7 +356,7 @@ func (flow *handleRelayInvsFlow) receiveHeaders() (msgIBDBlock *appmessage.Block
|
||||
}
|
||||
}
|
||||
|
||||
func (flow *handleRelayInvsFlow) processHeader(consensus externalapi.Consensus, msgBlockHeader *appmessage.MsgBlockHeader) error {
|
||||
func (flow *handleIBDFlow) processHeader(consensus externalapi.Consensus, msgBlockHeader *appmessage.MsgBlockHeader) error {
|
||||
header := appmessage.BlockHeaderToDomainBlockHeader(msgBlockHeader)
|
||||
block := &externalapi.DomainBlock{
|
||||
Header: header,
|
||||
@@ -333,7 +389,7 @@ func (flow *handleRelayInvsFlow) processHeader(consensus externalapi.Consensus,
|
||||
return nil
|
||||
}
|
||||
|
||||
func (flow *handleRelayInvsFlow) validatePruningPointFutureHeaderTimestamps() error {
|
||||
func (flow *handleIBDFlow) validatePruningPointFutureHeaderTimestamps() error {
|
||||
headerSelectedTipHash, err := flow.Domain().StagingConsensus().GetHeadersSelectedTip()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -367,7 +423,7 @@ func (flow *handleRelayInvsFlow) validatePruningPointFutureHeaderTimestamps() er
|
||||
return nil
|
||||
}
|
||||
|
||||
func (flow *handleRelayInvsFlow) receiveAndInsertPruningPointUTXOSet(
|
||||
func (flow *handleIBDFlow) receiveAndInsertPruningPointUTXOSet(
|
||||
consensus externalapi.Consensus, pruningPointHash *externalapi.DomainHash) (bool, error) {
|
||||
|
||||
onEnd := logger.LogAndMeasureExecutionTime(log, "receiveAndInsertPruningPointUTXOSet")
|
||||
@@ -376,7 +432,7 @@ func (flow *handleRelayInvsFlow) receiveAndInsertPruningPointUTXOSet(
|
||||
receivedChunkCount := 0
|
||||
receivedUTXOCount := 0
|
||||
for {
|
||||
message, err := flow.dequeueIncomingMessageAndSkipInvs(common.DefaultTimeout)
|
||||
message, err := flow.incomingRoute.DequeueWithTimeout(common.DefaultTimeout)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@@ -422,7 +478,7 @@ func (flow *handleRelayInvsFlow) receiveAndInsertPruningPointUTXOSet(
|
||||
}
|
||||
}
|
||||
|
||||
func (flow *handleRelayInvsFlow) syncMissingBlockBodies(highHash *externalapi.DomainHash) error {
|
||||
func (flow *handleIBDFlow) syncMissingBlockBodies(highHash *externalapi.DomainHash) error {
|
||||
hashes, err := flow.Domain().Consensus().GetMissingBlockBodyHashes(highHash)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -449,7 +505,7 @@ func (flow *handleRelayInvsFlow) syncMissingBlockBodies(highHash *externalapi.Do
|
||||
}
|
||||
|
||||
for _, expectedHash := range hashesToRequest {
|
||||
message, err := flow.dequeueIncomingMessageAndSkipInvs(common.DefaultTimeout)
|
||||
message, err := flow.incomingRoute.DequeueWithTimeout(common.DefaultTimeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -489,7 +545,16 @@ func (flow *handleRelayInvsFlow) syncMissingBlockBodies(highHash *externalapi.Do
|
||||
return flow.resolveVirtual()
|
||||
}
|
||||
|
||||
func (flow *handleRelayInvsFlow) resolveVirtual() error {
|
||||
func (flow *handleIBDFlow) banIfBlockIsHeaderOnly(block *externalapi.DomainBlock) error {
|
||||
if len(block.Transactions) == 0 {
|
||||
return protocolerrors.Errorf(true, "sent header of %s block where expected block with body",
|
||||
consensushashing.BlockHash(block))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (flow *handleIBDFlow) resolveVirtual() error {
|
||||
for i := 0; ; i++ {
|
||||
if i%10 == 0 {
|
||||
log.Infof("Resolving virtual. This may take some time...")
|
||||
@@ -510,18 +575,3 @@ func (flow *handleRelayInvsFlow) resolveVirtual() error {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// dequeueIncomingMessageAndSkipInvs is a convenience method to be used during
|
||||
// IBD. Inv messages are expected to arrive at any given moment, but should be
|
||||
// ignored while we're in IBD
|
||||
func (flow *handleRelayInvsFlow) dequeueIncomingMessageAndSkipInvs(timeout time.Duration) (appmessage.Message, error) {
|
||||
for {
|
||||
message, err := flow.incomingRoute.DequeueWithTimeout(timeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, ok := message.(*appmessage.MsgInvRelayBlock); !ok {
|
||||
return message, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (flow *handleRelayInvsFlow) ibdWithHeadersProof(highHash *externalapi.DomainHash) error {
|
||||
func (flow *handleIBDFlow) ibdWithHeadersProof(highHash *externalapi.DomainHash) error {
|
||||
err := flow.Domain().InitStagingConsensus()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -44,7 +44,7 @@ func (flow *handleRelayInvsFlow) ibdWithHeadersProof(highHash *externalapi.Domai
|
||||
return nil
|
||||
}
|
||||
|
||||
func (flow *handleRelayInvsFlow) shouldSyncAndShouldDownloadHeadersProof(highBlock *externalapi.DomainBlock,
|
||||
func (flow *handleIBDFlow) shouldSyncAndShouldDownloadHeadersProof(highBlock *externalapi.DomainBlock,
|
||||
highestSharedBlockFound bool) (shouldDownload, shouldSync bool, err error) {
|
||||
|
||||
if !highestSharedBlockFound {
|
||||
@@ -63,7 +63,7 @@ func (flow *handleRelayInvsFlow) shouldSyncAndShouldDownloadHeadersProof(highBlo
|
||||
return false, true, nil
|
||||
}
|
||||
|
||||
func (flow *handleRelayInvsFlow) checkIfHighHashHasMoreBlueWorkThanSelectedTipAndPruningDepthMoreBlueScore(highBlock *externalapi.DomainBlock) (bool, error) {
|
||||
func (flow *handleIBDFlow) checkIfHighHashHasMoreBlueWorkThanSelectedTipAndPruningDepthMoreBlueScore(highBlock *externalapi.DomainBlock) (bool, error) {
|
||||
headersSelectedTip, err := flow.Domain().Consensus().GetHeadersSelectedTip()
|
||||
if err != nil {
|
||||
return false, err
|
||||
@@ -81,13 +81,13 @@ func (flow *handleRelayInvsFlow) checkIfHighHashHasMoreBlueWorkThanSelectedTipAn
|
||||
return highBlock.Header.BlueWork().Cmp(headersSelectedTipInfo.BlueWork) > 0, nil
|
||||
}
|
||||
|
||||
func (flow *handleRelayInvsFlow) syncAndValidatePruningPointProof() (*externalapi.DomainHash, error) {
|
||||
func (flow *handleIBDFlow) syncAndValidatePruningPointProof() (*externalapi.DomainHash, error) {
|
||||
log.Infof("Downloading the pruning point proof from %s", flow.peer)
|
||||
err := flow.outgoingRoute.Enqueue(appmessage.NewMsgRequestPruningPointProof())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
message, err := flow.dequeueIncomingMessageAndSkipInvs(common.DefaultTimeout)
|
||||
message, err := flow.incomingRoute.DequeueWithTimeout(common.DefaultTimeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -113,7 +113,7 @@ func (flow *handleRelayInvsFlow) syncAndValidatePruningPointProof() (*externalap
|
||||
return consensushashing.HeaderHash(pruningPointProof.Headers[0][len(pruningPointProof.Headers[0])-1]), nil
|
||||
}
|
||||
|
||||
func (flow *handleRelayInvsFlow) downloadHeadersAndPruningUTXOSet(highHash *externalapi.DomainHash) error {
|
||||
func (flow *handleIBDFlow) downloadHeadersAndPruningUTXOSet(highHash *externalapi.DomainHash) error {
|
||||
proofPruningPoint, err := flow.syncAndValidatePruningPointProof()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -164,7 +164,7 @@ func (flow *handleRelayInvsFlow) downloadHeadersAndPruningUTXOSet(highHash *exte
|
||||
return nil
|
||||
}
|
||||
|
||||
func (flow *handleRelayInvsFlow) syncPruningPointsAndPruningPointAnticone(proofPruningPoint *externalapi.DomainHash) error {
|
||||
func (flow *handleIBDFlow) syncPruningPointsAndPruningPointAnticone(proofPruningPoint *externalapi.DomainHash) error {
|
||||
log.Infof("Downloading the past pruning points and the pruning point anticone from %s", flow.peer)
|
||||
err := flow.outgoingRoute.Enqueue(appmessage.NewMsgRequestPruningPointAndItsAnticone())
|
||||
if err != nil {
|
||||
@@ -214,15 +214,15 @@ func (flow *handleRelayInvsFlow) syncPruningPointsAndPruningPointAnticone(proofP
|
||||
return nil
|
||||
}
|
||||
|
||||
func (flow *handleRelayInvsFlow) processBlockWithTrustedData(
|
||||
func (flow *handleIBDFlow) processBlockWithTrustedData(
|
||||
consensus externalapi.Consensus, block *appmessage.MsgBlockWithTrustedData) error {
|
||||
|
||||
_, err := consensus.ValidateAndInsertBlockWithTrustedData(appmessage.BlockWithTrustedDataToDomainBlockWithTrustedData(block), false)
|
||||
return err
|
||||
}
|
||||
|
||||
func (flow *handleRelayInvsFlow) receiveBlockWithTrustedData() (*appmessage.MsgBlockWithTrustedData, bool, error) {
|
||||
message, err := flow.dequeueIncomingMessageAndSkipInvs(common.DefaultTimeout)
|
||||
func (flow *handleIBDFlow) receiveBlockWithTrustedData() (*appmessage.MsgBlockWithTrustedData, bool, error) {
|
||||
message, err := flow.incomingRoute.DequeueWithTimeout(common.DefaultTimeout)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
@@ -242,8 +242,8 @@ func (flow *handleRelayInvsFlow) receiveBlockWithTrustedData() (*appmessage.MsgB
|
||||
}
|
||||
}
|
||||
|
||||
func (flow *handleRelayInvsFlow) receivePruningPoints() (*appmessage.MsgPruningPoints, error) {
|
||||
message, err := flow.dequeueIncomingMessageAndSkipInvs(common.DefaultTimeout)
|
||||
func (flow *handleIBDFlow) receivePruningPoints() (*appmessage.MsgPruningPoints, error) {
|
||||
message, err := flow.incomingRoute.DequeueWithTimeout(common.DefaultTimeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -258,7 +258,7 @@ func (flow *handleRelayInvsFlow) receivePruningPoints() (*appmessage.MsgPruningP
|
||||
return msgPruningPoints, nil
|
||||
}
|
||||
|
||||
func (flow *handleRelayInvsFlow) validateAndInsertPruningPoints(proofPruningPoint *externalapi.DomainHash) error {
|
||||
func (flow *handleIBDFlow) validateAndInsertPruningPoints(proofPruningPoint *externalapi.DomainHash) error {
|
||||
currentPruningPoint, err := flow.Domain().Consensus().PruningPoint()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -302,7 +302,7 @@ func (flow *handleRelayInvsFlow) validateAndInsertPruningPoints(proofPruningPoin
|
||||
return nil
|
||||
}
|
||||
|
||||
func (flow *handleRelayInvsFlow) syncPruningPointUTXOSet(consensus externalapi.Consensus,
|
||||
func (flow *handleIBDFlow) syncPruningPointUTXOSet(consensus externalapi.Consensus,
|
||||
pruningPoint *externalapi.DomainHash) (bool, error) {
|
||||
|
||||
log.Infof("Checking if the suggested pruning point %s is compatible to the node DAG", pruningPoint)
|
||||
@@ -330,7 +330,7 @@ func (flow *handleRelayInvsFlow) syncPruningPointUTXOSet(consensus externalapi.C
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (flow *handleRelayInvsFlow) fetchMissingUTXOSet(consensus externalapi.Consensus, pruningPointHash *externalapi.DomainHash) (succeed bool, err error) {
|
||||
func (flow *handleIBDFlow) fetchMissingUTXOSet(consensus externalapi.Consensus, pruningPointHash *externalapi.DomainHash) (succeed bool, err error) {
|
||||
defer func() {
|
||||
err := flow.Domain().StagingConsensus().ClearImportedPruningPointData()
|
||||
if err != nil {
|
||||
187
app/protocol/flows/v3/register.go
Normal file
187
app/protocol/flows/v3/register.go
Normal file
@@ -0,0 +1,187 @@
|
||||
package v3
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/app/protocol/common"
|
||||
"github.com/kaspanet/kaspad/app/protocol/flowcontext"
|
||||
"github.com/kaspanet/kaspad/app/protocol/flows/v3/addressexchange"
|
||||
"github.com/kaspanet/kaspad/app/protocol/flows/v3/blockrelay"
|
||||
"github.com/kaspanet/kaspad/app/protocol/flows/v3/ping"
|
||||
"github.com/kaspanet/kaspad/app/protocol/flows/v3/rejects"
|
||||
"github.com/kaspanet/kaspad/app/protocol/flows/v3/transactionrelay"
|
||||
peerpkg "github.com/kaspanet/kaspad/app/protocol/peer"
|
||||
routerpkg "github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
||||
)
|
||||
|
||||
type protocolManager interface {
|
||||
RegisterFlow(name string, router *routerpkg.Router, messageTypes []appmessage.MessageCommand, isStopping *uint32,
|
||||
errChan chan error, initializeFunc common.FlowInitializeFunc) *common.Flow
|
||||
RegisterOneTimeFlow(name string, router *routerpkg.Router, messageTypes []appmessage.MessageCommand,
|
||||
isStopping *uint32, stopChan chan error, initializeFunc common.FlowInitializeFunc) *common.Flow
|
||||
RegisterFlowWithCapacity(name string, capacity int, router *routerpkg.Router,
|
||||
messageTypes []appmessage.MessageCommand, isStopping *uint32,
|
||||
errChan chan error, initializeFunc common.FlowInitializeFunc) *common.Flow
|
||||
Context() *flowcontext.FlowContext
|
||||
}
|
||||
|
||||
// Register is used in order to register all the protocol flows to the given router.
|
||||
func Register(m protocolManager, router *routerpkg.Router, errChan chan error, isStopping *uint32) (flows []*common.Flow) {
|
||||
flows = registerAddressFlows(m, router, isStopping, errChan)
|
||||
flows = append(flows, registerBlockRelayFlows(m, router, isStopping, errChan)...)
|
||||
flows = append(flows, registerPingFlows(m, router, isStopping, errChan)...)
|
||||
flows = append(flows, registerTransactionRelayFlow(m, router, isStopping, errChan)...)
|
||||
flows = append(flows, registerRejectsFlow(m, router, isStopping, errChan)...)
|
||||
|
||||
return flows
|
||||
}
|
||||
|
||||
func registerAddressFlows(m protocolManager, router *routerpkg.Router, isStopping *uint32, errChan chan error) []*common.Flow {
|
||||
outgoingRoute := router.OutgoingRoute()
|
||||
|
||||
return []*common.Flow{
|
||||
m.RegisterOneTimeFlow("ReceiveAddresses", router, []appmessage.MessageCommand{appmessage.CmdAddresses}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return addressexchange.ReceiveAddresses(m.Context(), incomingRoute, outgoingRoute, peer)
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func registerBlockRelayFlows(m protocolManager, router *routerpkg.Router, isStopping *uint32, errChan chan error) []*common.Flow {
|
||||
outgoingRoute := router.OutgoingRoute()
|
||||
|
||||
return []*common.Flow{
|
||||
m.RegisterOneTimeFlow("SendVirtualSelectedParentInv", router, []appmessage.MessageCommand{},
|
||||
isStopping, errChan, func(route *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return blockrelay.SendVirtualSelectedParentInv(m.Context(), outgoingRoute, peer)
|
||||
}),
|
||||
|
||||
m.RegisterFlow("HandleRelayInvs", router, []appmessage.MessageCommand{
|
||||
appmessage.CmdInvRelayBlock, appmessage.CmdBlock, appmessage.CmdBlockLocator,
|
||||
},
|
||||
isStopping, errChan, func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return blockrelay.HandleRelayInvs(m.Context(), incomingRoute,
|
||||
outgoingRoute, peer)
|
||||
},
|
||||
),
|
||||
|
||||
m.RegisterFlow("HandleIBD", router, []appmessage.MessageCommand{
|
||||
appmessage.CmdDoneHeaders, appmessage.CmdUnexpectedPruningPoint, appmessage.CmdPruningPointUTXOSetChunk,
|
||||
appmessage.CmdBlockHeaders, appmessage.CmdIBDBlockLocatorHighestHash, appmessage.CmdBlockWithTrustedData,
|
||||
appmessage.CmdDoneBlocksWithTrustedData, appmessage.CmdIBDBlockLocatorHighestHashNotFound,
|
||||
appmessage.CmdDonePruningPointUTXOSetChunks, appmessage.CmdIBDBlock, appmessage.CmdPruningPoints,
|
||||
appmessage.CmdPruningPointProof,
|
||||
},
|
||||
isStopping, errChan, func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return blockrelay.HandleIBD(m.Context(), incomingRoute,
|
||||
outgoingRoute, peer)
|
||||
},
|
||||
),
|
||||
|
||||
m.RegisterFlow("HandleRelayBlockRequests", router, []appmessage.MessageCommand{appmessage.CmdRequestRelayBlocks}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return blockrelay.HandleRelayBlockRequests(m.Context(), incomingRoute, outgoingRoute, peer)
|
||||
},
|
||||
),
|
||||
|
||||
m.RegisterFlow("HandleRequestBlockLocator", router,
|
||||
[]appmessage.MessageCommand{appmessage.CmdRequestBlockLocator}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return blockrelay.HandleRequestBlockLocator(m.Context(), incomingRoute, outgoingRoute)
|
||||
},
|
||||
),
|
||||
|
||||
m.RegisterFlow("HandleRequestHeaders", router,
|
||||
[]appmessage.MessageCommand{appmessage.CmdRequestHeaders, appmessage.CmdRequestNextHeaders}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return blockrelay.HandleRequestHeaders(m.Context(), incomingRoute, outgoingRoute, peer)
|
||||
},
|
||||
),
|
||||
|
||||
m.RegisterFlow("HandleIBDBlockRequests", router,
|
||||
[]appmessage.MessageCommand{appmessage.CmdRequestIBDBlocks}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return blockrelay.HandleIBDBlockRequests(m.Context(), incomingRoute, outgoingRoute)
|
||||
},
|
||||
),
|
||||
|
||||
m.RegisterFlow("HandleRequestPruningPointUTXOSet", router,
|
||||
[]appmessage.MessageCommand{appmessage.CmdRequestPruningPointUTXOSet,
|
||||
appmessage.CmdRequestNextPruningPointUTXOSetChunk}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return blockrelay.HandleRequestPruningPointUTXOSet(m.Context(), incomingRoute, outgoingRoute)
|
||||
},
|
||||
),
|
||||
|
||||
m.RegisterFlow("HandlePruningPointAndItsAnticoneRequests", router,
|
||||
[]appmessage.MessageCommand{appmessage.CmdRequestPruningPointAndItsAnticone}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return blockrelay.HandlePruningPointAndItsAnticoneRequests(m.Context(), incomingRoute, outgoingRoute, peer)
|
||||
},
|
||||
),
|
||||
|
||||
m.RegisterFlow("HandleIBDBlockLocator", router,
|
||||
[]appmessage.MessageCommand{appmessage.CmdIBDBlockLocator}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return blockrelay.HandleIBDBlockLocator(m.Context(), incomingRoute, outgoingRoute, peer)
|
||||
},
|
||||
),
|
||||
|
||||
m.RegisterFlow("HandlePruningPointProofRequests", router,
|
||||
[]appmessage.MessageCommand{appmessage.CmdRequestPruningPointProof}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return blockrelay.HandlePruningPointProofRequests(m.Context(), incomingRoute, outgoingRoute, peer)
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func registerPingFlows(m protocolManager, router *routerpkg.Router, isStopping *uint32, errChan chan error) []*common.Flow {
|
||||
outgoingRoute := router.OutgoingRoute()
|
||||
|
||||
return []*common.Flow{
|
||||
m.RegisterFlow("ReceivePings", router, []appmessage.MessageCommand{appmessage.CmdPing}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return ping.ReceivePings(m.Context(), incomingRoute, outgoingRoute)
|
||||
},
|
||||
),
|
||||
|
||||
m.RegisterFlow("SendPings", router, []appmessage.MessageCommand{appmessage.CmdPong}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return ping.SendPings(m.Context(), incomingRoute, outgoingRoute, peer)
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func registerTransactionRelayFlow(m protocolManager, router *routerpkg.Router, isStopping *uint32, errChan chan error) []*common.Flow {
|
||||
outgoingRoute := router.OutgoingRoute()
|
||||
|
||||
return []*common.Flow{
|
||||
m.RegisterFlowWithCapacity("HandleRelayedTransactions", 10_000, router,
|
||||
[]appmessage.MessageCommand{appmessage.CmdInvTransaction, appmessage.CmdTx, appmessage.CmdTransactionNotFound}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return transactionrelay.HandleRelayedTransactions(m.Context(), incomingRoute, outgoingRoute)
|
||||
},
|
||||
),
|
||||
m.RegisterFlow("HandleRequestTransactions", router,
|
||||
[]appmessage.MessageCommand{appmessage.CmdRequestTransactions}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return transactionrelay.HandleRequestedTransactions(m.Context(), incomingRoute, outgoingRoute)
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func registerRejectsFlow(m protocolManager, router *routerpkg.Router, isStopping *uint32, errChan chan error) []*common.Flow {
|
||||
outgoingRoute := router.OutgoingRoute()
|
||||
|
||||
return []*common.Flow{
|
||||
m.RegisterFlow("HandleRejects", router,
|
||||
[]appmessage.MessageCommand{appmessage.CmdReject}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return rejects.HandleRejects(m.Context(), incomingRoute, outgoingRoute)
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/app/protocol/flows/addressexchange"
|
||||
"github.com/kaspanet/kaspad/app/protocol/flows/v3/addressexchange"
|
||||
peerpkg "github.com/kaspanet/kaspad/app/protocol/peer"
|
||||
"github.com/kaspanet/kaspad/domain/consensus"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/testutils"
|
||||
@@ -3,6 +3,7 @@ package transactionrelay
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/app/protocol/common"
|
||||
"github.com/kaspanet/kaspad/app/protocol/flowcontext"
|
||||
"github.com/kaspanet/kaspad/app/protocol/protocolerrors"
|
||||
"github.com/kaspanet/kaspad/domain"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
@@ -18,7 +19,7 @@ import (
|
||||
type TransactionsRelayContext interface {
|
||||
NetAdapter() *netadapter.NetAdapter
|
||||
Domain() domain.Domain
|
||||
SharedRequestedTransactions() *SharedRequestedTransactions
|
||||
SharedRequestedTransactions() *flowcontext.SharedRequestedTransactions
|
||||
OnTransactionAddedToMempool()
|
||||
EnqueueTransactionIDsForPropagation(transactionIDs []*externalapi.DomainTransactionID) error
|
||||
}
|
||||
@@ -68,7 +69,7 @@ func (flow *handleRelayedTransactionsFlow) requestInvTransactions(
|
||||
if flow.isKnownTransaction(txID) {
|
||||
continue
|
||||
}
|
||||
exists := flow.SharedRequestedTransactions().addIfNotExists(txID)
|
||||
exists := flow.SharedRequestedTransactions().AddIfNotExists(txID)
|
||||
if exists {
|
||||
continue
|
||||
}
|
||||
@@ -82,7 +83,7 @@ func (flow *handleRelayedTransactionsFlow) requestInvTransactions(
|
||||
msgGetTransactions := appmessage.NewMsgRequestTransactions(idsToRequest)
|
||||
err = flow.outgoingRoute.Enqueue(msgGetTransactions)
|
||||
if err != nil {
|
||||
flow.SharedRequestedTransactions().removeMany(idsToRequest)
|
||||
flow.SharedRequestedTransactions().RemoveMany(idsToRequest)
|
||||
return nil, err
|
||||
}
|
||||
return idsToRequest, nil
|
||||
@@ -151,7 +152,7 @@ func (flow *handleRelayedTransactionsFlow) readMsgTxOrNotFound() (
|
||||
func (flow *handleRelayedTransactionsFlow) receiveTransactions(requestedTransactions []*externalapi.DomainTransactionID) error {
|
||||
// In case the function returns earlier than expected, we want to make sure sharedRequestedTransactions is
|
||||
// clean from any pending transactions.
|
||||
defer flow.SharedRequestedTransactions().removeMany(requestedTransactions)
|
||||
defer flow.SharedRequestedTransactions().RemoveMany(requestedTransactions)
|
||||
for _, expectedID := range requestedTransactions {
|
||||
msgTx, msgTxNotFound, err := flow.readMsgTxOrNotFound()
|
||||
if err != nil {
|
||||
@@ -2,10 +2,11 @@ package transactionrelay_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/kaspanet/kaspad/app/protocol/flowcontext"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/kaspanet/kaspad/app/protocol/flows/transactionrelay"
|
||||
"github.com/kaspanet/kaspad/app/protocol/flows/v3/transactionrelay"
|
||||
"github.com/kaspanet/kaspad/app/protocol/protocolerrors"
|
||||
"github.com/kaspanet/kaspad/domain"
|
||||
"github.com/kaspanet/kaspad/domain/consensus"
|
||||
@@ -24,7 +25,7 @@ import (
|
||||
type mocTransactionsRelayContext struct {
|
||||
netAdapter *netadapter.NetAdapter
|
||||
domain domain.Domain
|
||||
sharedRequestedTransactions *transactionrelay.SharedRequestedTransactions
|
||||
sharedRequestedTransactions *flowcontext.SharedRequestedTransactions
|
||||
}
|
||||
|
||||
func (m *mocTransactionsRelayContext) NetAdapter() *netadapter.NetAdapter {
|
||||
@@ -35,7 +36,7 @@ func (m *mocTransactionsRelayContext) Domain() domain.Domain {
|
||||
return m.domain
|
||||
}
|
||||
|
||||
func (m *mocTransactionsRelayContext) SharedRequestedTransactions() *transactionrelay.SharedRequestedTransactions {
|
||||
func (m *mocTransactionsRelayContext) SharedRequestedTransactions() *flowcontext.SharedRequestedTransactions {
|
||||
return m.sharedRequestedTransactions
|
||||
}
|
||||
|
||||
@@ -60,7 +61,7 @@ func TestHandleRelayedTransactionsNotFound(t *testing.T) {
|
||||
}
|
||||
defer teardown(false)
|
||||
|
||||
sharedRequestedTransactions := transactionrelay.NewSharedRequestedTransactions()
|
||||
sharedRequestedTransactions := flowcontext.NewSharedRequestedTransactions()
|
||||
adapter, err := netadapter.NewNetAdapter(config.DefaultConfig())
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create a NetAdapter: %v", err)
|
||||
@@ -153,7 +154,7 @@ func TestOnClosedIncomingRoute(t *testing.T) {
|
||||
}
|
||||
defer teardown(false)
|
||||
|
||||
sharedRequestedTransactions := transactionrelay.NewSharedRequestedTransactions()
|
||||
sharedRequestedTransactions := flowcontext.NewSharedRequestedTransactions()
|
||||
adapter, err := netadapter.NewNetAdapter(config.DefaultConfig())
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to creat a NetAdapter : %v", err)
|
||||
@@ -1,10 +1,11 @@
|
||||
package transactionrelay_test
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/app/protocol/flowcontext"
|
||||
"testing"
|
||||
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/app/protocol/flows/transactionrelay"
|
||||
"github.com/kaspanet/kaspad/app/protocol/flows/v3/transactionrelay"
|
||||
"github.com/kaspanet/kaspad/domain"
|
||||
"github.com/kaspanet/kaspad/domain/consensus"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
@@ -31,7 +32,7 @@ func TestHandleRequestedTransactionsNotFound(t *testing.T) {
|
||||
}
|
||||
defer teardown(false)
|
||||
|
||||
sharedRequestedTransactions := transactionrelay.NewSharedRequestedTransactions()
|
||||
sharedRequestedTransactions := flowcontext.NewSharedRequestedTransactions()
|
||||
adapter, err := netadapter.NewNetAdapter(config.DefaultConfig())
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create a NetAdapter: %v", err)
|
||||
33
app/protocol/flows/v4/blockrelay/block_locator.go
Normal file
33
app/protocol/flows/v4/blockrelay/block_locator.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package blockrelay
|
||||
|
||||
import (
|
||||
"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/consensus/model/externalapi"
|
||||
)
|
||||
|
||||
func (flow *handleRelayInvsFlow) sendGetBlockLocator(highHash *externalapi.DomainHash, limit uint32) error {
|
||||
msgGetBlockLocator := appmessage.NewMsgRequestBlockLocator(highHash, limit)
|
||||
return flow.outgoingRoute.Enqueue(msgGetBlockLocator)
|
||||
}
|
||||
|
||||
func (flow *handleRelayInvsFlow) receiveBlockLocator() (blockLocatorHashes []*externalapi.DomainHash, err error) {
|
||||
for {
|
||||
message, err := flow.incomingRoute.DequeueWithTimeout(common.DefaultTimeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch message := message.(type) {
|
||||
case *appmessage.MsgInvRelayBlock:
|
||||
flow.invsQueue = append(flow.invsQueue, message)
|
||||
case *appmessage.MsgBlockLocator:
|
||||
return message.BlockLocatorHashes, nil
|
||||
default:
|
||||
return nil,
|
||||
protocolerrors.Errorf(true, "received unexpected message type. "+
|
||||
"expected: %s, got: %s", appmessage.CmdBlockLocator, message.Command())
|
||||
}
|
||||
}
|
||||
}
|
||||
86
app/protocol/flows/v4/blockrelay/handle_ibd_block_locator.go
Normal file
86
app/protocol/flows/v4/blockrelay/handle_ibd_block_locator.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package blockrelay
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/app/protocol/peer"
|
||||
"github.com/kaspanet/kaspad/app/protocol/protocolerrors"
|
||||
"github.com/kaspanet/kaspad/domain"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
||||
)
|
||||
|
||||
// HandleIBDBlockLocatorContext is the interface for the context needed for the HandleIBDBlockLocator flow.
|
||||
type HandleIBDBlockLocatorContext interface {
|
||||
Domain() domain.Domain
|
||||
}
|
||||
|
||||
// HandleIBDBlockLocator listens to appmessage.MsgIBDBlockLocator messages and sends
|
||||
// the highest known block that's in the selected parent chain of `targetHash` to the
|
||||
// requesting peer.
|
||||
func HandleIBDBlockLocator(context HandleIBDBlockLocatorContext, incomingRoute *router.Route,
|
||||
outgoingRoute *router.Route, peer *peer.Peer) error {
|
||||
|
||||
for {
|
||||
message, err := incomingRoute.Dequeue()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ibdBlockLocatorMessage := message.(*appmessage.MsgIBDBlockLocator)
|
||||
|
||||
targetHash := ibdBlockLocatorMessage.TargetHash
|
||||
log.Debugf("Received IBDBlockLocator from %s with targetHash %s", peer, targetHash)
|
||||
|
||||
blockInfo, err := context.Domain().Consensus().GetBlockInfo(targetHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !blockInfo.Exists {
|
||||
return protocolerrors.Errorf(true, "received IBDBlockLocator "+
|
||||
"with an unknown targetHash %s", targetHash)
|
||||
}
|
||||
|
||||
foundHighestHashInTheSelectedParentChainOfTargetHash := false
|
||||
for _, blockLocatorHash := range ibdBlockLocatorMessage.BlockLocatorHashes {
|
||||
blockInfo, err := context.Domain().Consensus().GetBlockInfo(blockLocatorHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// The IBD block locator is checking only existing blocks with bodies.
|
||||
if !blockInfo.Exists || blockInfo.BlockStatus == externalapi.StatusHeaderOnly {
|
||||
continue
|
||||
}
|
||||
|
||||
isBlockLocatorHashInSelectedParentChainOfHighHash, err :=
|
||||
context.Domain().Consensus().IsInSelectedParentChainOf(blockLocatorHash, targetHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !isBlockLocatorHashInSelectedParentChainOfHighHash {
|
||||
continue
|
||||
}
|
||||
|
||||
foundHighestHashInTheSelectedParentChainOfTargetHash = true
|
||||
log.Debugf("Found a known hash %s amongst peer %s's "+
|
||||
"blockLocator that's in the selected parent chain of targetHash %s", blockLocatorHash, peer, targetHash)
|
||||
|
||||
ibdBlockLocatorHighestHashMessage := appmessage.NewMsgIBDBlockLocatorHighestHash(blockLocatorHash)
|
||||
err = outgoingRoute.Enqueue(ibdBlockLocatorHighestHashMessage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if !foundHighestHashInTheSelectedParentChainOfTargetHash {
|
||||
log.Warnf("no hash was found in the blockLocator "+
|
||||
"that was in the selected parent chain of targetHash %s", targetHash)
|
||||
|
||||
ibdBlockLocatorHighestHashNotFoundMessage := appmessage.NewMsgIBDBlockLocatorHighestHashNotFound()
|
||||
err = outgoingRoute.Enqueue(ibdBlockLocatorHighestHashNotFoundMessage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package blockrelay
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/app/protocol/protocolerrors"
|
||||
"github.com/kaspanet/kaspad/domain"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// HandleIBDBlockRequestsContext is the interface for the context needed for the HandleIBDBlockRequests flow.
|
||||
type HandleIBDBlockRequestsContext interface {
|
||||
Domain() domain.Domain
|
||||
}
|
||||
|
||||
// HandleIBDBlockRequests listens to appmessage.MsgRequestRelayBlocks messages and sends
|
||||
// their corresponding blocks to the requesting peer.
|
||||
func HandleIBDBlockRequests(context HandleIBDBlockRequestsContext, incomingRoute *router.Route,
|
||||
outgoingRoute *router.Route) error {
|
||||
|
||||
for {
|
||||
message, err := incomingRoute.Dequeue()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
msgRequestIBDBlocks := message.(*appmessage.MsgRequestIBDBlocks)
|
||||
log.Debugf("Got request for %d ibd blocks", len(msgRequestIBDBlocks.Hashes))
|
||||
for i, hash := range msgRequestIBDBlocks.Hashes {
|
||||
// Fetch the block from the database.
|
||||
blockInfo, err := context.Domain().Consensus().GetBlockInfo(hash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !blockInfo.Exists || blockInfo.BlockStatus == externalapi.StatusHeaderOnly {
|
||||
return protocolerrors.Errorf(true, "block %s not found", hash)
|
||||
}
|
||||
block, err := context.Domain().Consensus().GetBlock(hash)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "unable to fetch requested block hash %s", hash)
|
||||
}
|
||||
|
||||
// TODO (Partial nodes): Convert block to partial block if needed
|
||||
|
||||
blockMessage := appmessage.DomainBlockToMsgBlock(block)
|
||||
ibdBlockMessage := appmessage.NewMsgIBDBlock(blockMessage)
|
||||
err = outgoingRoute.Enqueue(ibdBlockMessage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debugf("sent %d out of %d", i+1, len(msgRequestIBDBlocks.Hashes))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
package blockrelay
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
peerpkg "github.com/kaspanet/kaspad/app/protocol/peer"
|
||||
"github.com/kaspanet/kaspad/app/protocol/protocolerrors"
|
||||
"github.com/kaspanet/kaspad/domain"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/infrastructure/config"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// PruningPointAndItsAnticoneRequestsContext is the interface for the context needed for the HandlePruningPointAndItsAnticoneRequests flow.
|
||||
type PruningPointAndItsAnticoneRequestsContext interface {
|
||||
Domain() domain.Domain
|
||||
Config() *config.Config
|
||||
}
|
||||
|
||||
var isBusy uint32
|
||||
|
||||
// HandlePruningPointAndItsAnticoneRequests listens to appmessage.MsgRequestPruningPointAndItsAnticone messages and sends
|
||||
// the pruning point and its anticone to the requesting peer.
|
||||
func HandlePruningPointAndItsAnticoneRequests(context PruningPointAndItsAnticoneRequestsContext, incomingRoute *router.Route,
|
||||
outgoingRoute *router.Route, peer *peerpkg.Peer) error {
|
||||
|
||||
for {
|
||||
err := func() error {
|
||||
_, err := incomingRoute.Dequeue()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !atomic.CompareAndSwapUint32(&isBusy, 0, 1) {
|
||||
return protocolerrors.Errorf(false, "node is busy with other pruning point anticone requests")
|
||||
}
|
||||
defer atomic.StoreUint32(&isBusy, 0)
|
||||
|
||||
log.Debugf("Got request for pruning point and its anticone from %s", peer)
|
||||
|
||||
pruningPointHeaders, err := context.Domain().Consensus().PruningPointHeaders()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
msgPruningPointHeaders := make([]*appmessage.MsgBlockHeader, len(pruningPointHeaders))
|
||||
for i, header := range pruningPointHeaders {
|
||||
msgPruningPointHeaders[i] = appmessage.DomainBlockHeaderToBlockHeader(header)
|
||||
}
|
||||
|
||||
err = outgoingRoute.Enqueue(appmessage.NewMsgPruningPoints(msgPruningPointHeaders))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pointAndItsAnticone, err := context.Domain().Consensus().PruningPointAndItsAnticone()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
windowSize := context.Config().NetParams().DifficultyAdjustmentWindowSize
|
||||
daaWindowBlocks := make([]*externalapi.TrustedDataDataDAAHeader, 0, windowSize)
|
||||
daaWindowHashesToIndex := make(map[externalapi.DomainHash]int, windowSize)
|
||||
trustedDataDAABlockIndexes := make(map[externalapi.DomainHash][]uint64)
|
||||
|
||||
ghostdagData := make([]*externalapi.BlockGHOSTDAGDataHashPair, 0)
|
||||
ghostdagDataHashToIndex := make(map[externalapi.DomainHash]int)
|
||||
trustedDataGHOSTDAGDataIndexes := make(map[externalapi.DomainHash][]uint64)
|
||||
for _, blockHash := range pointAndItsAnticone {
|
||||
blockDAAWindowHashes, err := context.Domain().Consensus().BlockDAAWindowHashes(blockHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
trustedDataDAABlockIndexes[*blockHash] = make([]uint64, 0, windowSize)
|
||||
for i, daaBlockHash := range blockDAAWindowHashes {
|
||||
index, exists := daaWindowHashesToIndex[*daaBlockHash]
|
||||
if !exists {
|
||||
trustedDataDataDAAHeader, err := context.Domain().Consensus().TrustedDataDataDAAHeader(blockHash, daaBlockHash, uint64(i))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
daaWindowBlocks = append(daaWindowBlocks, trustedDataDataDAAHeader)
|
||||
index = len(daaWindowBlocks) - 1
|
||||
daaWindowHashesToIndex[*daaBlockHash] = index
|
||||
}
|
||||
|
||||
trustedDataDAABlockIndexes[*blockHash] = append(trustedDataDAABlockIndexes[*blockHash], uint64(index))
|
||||
}
|
||||
|
||||
ghostdagDataBlockHashes, err := context.Domain().Consensus().TrustedBlockAssociatedGHOSTDAGDataBlockHashes(blockHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
trustedDataGHOSTDAGDataIndexes[*blockHash] = make([]uint64, 0, context.Config().NetParams().K)
|
||||
for _, ghostdagDataBlockHash := range ghostdagDataBlockHashes {
|
||||
index, exists := ghostdagDataHashToIndex[*ghostdagDataBlockHash]
|
||||
if !exists {
|
||||
data, err := context.Domain().Consensus().TrustedGHOSTDAGData(ghostdagDataBlockHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ghostdagData = append(ghostdagData, &externalapi.BlockGHOSTDAGDataHashPair{
|
||||
Hash: ghostdagDataBlockHash,
|
||||
GHOSTDAGData: data,
|
||||
})
|
||||
index = len(ghostdagData) - 1
|
||||
ghostdagDataHashToIndex[*ghostdagDataBlockHash] = index
|
||||
}
|
||||
|
||||
trustedDataGHOSTDAGDataIndexes[*blockHash] = append(trustedDataGHOSTDAGDataIndexes[*blockHash], uint64(index))
|
||||
}
|
||||
}
|
||||
|
||||
err = outgoingRoute.Enqueue(appmessage.DomainTrustedDataToTrustedData(daaWindowBlocks, ghostdagData))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, blockHash := range pointAndItsAnticone {
|
||||
block, err := context.Domain().Consensus().GetBlock(blockHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = outgoingRoute.Enqueue(appmessage.DomainBlockWithTrustedDataToBlockWithTrustedDataV4(block, trustedDataDAABlockIndexes[*blockHash], trustedDataGHOSTDAGDataIndexes[*blockHash]))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = outgoingRoute.Enqueue(appmessage.NewMsgDoneBlocksWithTrustedData())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("Sent pruning point and its anticone to %s", peer)
|
||||
return nil
|
||||
}()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package blockrelay
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
peerpkg "github.com/kaspanet/kaspad/app/protocol/peer"
|
||||
"github.com/kaspanet/kaspad/domain"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
||||
)
|
||||
|
||||
// PruningPointProofRequestsContext is the interface for the context needed for the HandlePruningPointProofRequests flow.
|
||||
type PruningPointProofRequestsContext interface {
|
||||
Domain() domain.Domain
|
||||
}
|
||||
|
||||
// HandlePruningPointProofRequests listens to appmessage.MsgRequestPruningPointProof messages and sends
|
||||
// the pruning point proof to the requesting peer.
|
||||
func HandlePruningPointProofRequests(context PruningPointProofRequestsContext, incomingRoute *router.Route,
|
||||
outgoingRoute *router.Route, peer *peerpkg.Peer) error {
|
||||
|
||||
for {
|
||||
_, err := incomingRoute.Dequeue()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("Got request for pruning point proof from %s", peer)
|
||||
|
||||
pruningPointProof, err := context.Domain().Consensus().BuildPruningPointProof()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pruningPointProofMessage := appmessage.DomainPruningPointProofToMsgPruningPointProof(pruningPointProof)
|
||||
err = outgoingRoute.Enqueue(pruningPointProofMessage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("Sent pruning point proof to %s", peer)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package blockrelay
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
peerpkg "github.com/kaspanet/kaspad/app/protocol/peer"
|
||||
"github.com/kaspanet/kaspad/app/protocol/protocolerrors"
|
||||
"github.com/kaspanet/kaspad/domain"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// RelayBlockRequestsContext is the interface for the context needed for the HandleRelayBlockRequests flow.
|
||||
type RelayBlockRequestsContext interface {
|
||||
Domain() domain.Domain
|
||||
}
|
||||
|
||||
// HandleRelayBlockRequests listens to appmessage.MsgRequestRelayBlocks messages and sends
|
||||
// their corresponding blocks to the requesting peer.
|
||||
func HandleRelayBlockRequests(context RelayBlockRequestsContext, incomingRoute *router.Route,
|
||||
outgoingRoute *router.Route, peer *peerpkg.Peer) error {
|
||||
|
||||
for {
|
||||
message, err := incomingRoute.Dequeue()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
getRelayBlocksMessage := message.(*appmessage.MsgRequestRelayBlocks)
|
||||
log.Debugf("Got request for relay blocks with hashes %s", getRelayBlocksMessage.Hashes)
|
||||
for _, hash := range getRelayBlocksMessage.Hashes {
|
||||
// Fetch the block from the database.
|
||||
blockInfo, err := context.Domain().Consensus().GetBlockInfo(hash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !blockInfo.Exists || blockInfo.BlockStatus == externalapi.StatusHeaderOnly {
|
||||
return protocolerrors.Errorf(true, "block %s not found", hash)
|
||||
}
|
||||
block, err := context.Domain().Consensus().GetBlock(hash)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "unable to fetch requested block hash %s", hash)
|
||||
}
|
||||
|
||||
// TODO (Partial nodes): Convert block to partial block if needed
|
||||
|
||||
err = outgoingRoute.Enqueue(appmessage.DomainBlockToMsgBlock(block))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debugf("Relayed block with hash %s", hash)
|
||||
}
|
||||
}
|
||||
}
|
||||
382
app/protocol/flows/v4/blockrelay/handle_relay_invs.go
Normal file
382
app/protocol/flows/v4/blockrelay/handle_relay_invs.go
Normal file
@@ -0,0 +1,382 @@
|
||||
package blockrelay
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/app/protocol/common"
|
||||
"github.com/kaspanet/kaspad/app/protocol/flowcontext"
|
||||
peerpkg "github.com/kaspanet/kaspad/app/protocol/peer"
|
||||
"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/domain/consensus/utils/consensushashing"
|
||||
"github.com/kaspanet/kaspad/infrastructure/config"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// orphanResolutionRange is the maximum amount of blockLocator hashes
|
||||
// to search for known blocks. See isBlockInOrphanResolutionRange for
|
||||
// further details
|
||||
var orphanResolutionRange uint32 = 5
|
||||
|
||||
// RelayInvsContext is the interface for the context needed for the HandleRelayInvs flow.
|
||||
type RelayInvsContext interface {
|
||||
Domain() domain.Domain
|
||||
Config() *config.Config
|
||||
OnNewBlock(block *externalapi.DomainBlock, virtualChangeSet *externalapi.VirtualChangeSet) error
|
||||
OnVirtualChange(virtualChangeSet *externalapi.VirtualChangeSet) error
|
||||
OnPruningPointUTXOSetOverride() error
|
||||
SharedRequestedBlocks() *flowcontext.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
|
||||
IsRecoverableError(err error) bool
|
||||
}
|
||||
|
||||
type handleRelayInvsFlow struct {
|
||||
RelayInvsContext
|
||||
incomingRoute, outgoingRoute *router.Route
|
||||
peer *peerpkg.Peer
|
||||
invsQueue []*appmessage.MsgInvRelayBlock
|
||||
}
|
||||
|
||||
// HandleRelayInvs listens to appmessage.MsgInvRelayBlock messages, requests their corresponding blocks if they
|
||||
// are missing, adds them to the DAG and propagates them to the rest of the network.
|
||||
func HandleRelayInvs(context RelayInvsContext, incomingRoute *router.Route, outgoingRoute *router.Route,
|
||||
peer *peerpkg.Peer) error {
|
||||
|
||||
flow := &handleRelayInvsFlow{
|
||||
RelayInvsContext: context,
|
||||
incomingRoute: incomingRoute,
|
||||
outgoingRoute: outgoingRoute,
|
||||
peer: peer,
|
||||
invsQueue: make([]*appmessage.MsgInvRelayBlock, 0),
|
||||
}
|
||||
err := flow.start()
|
||||
// Currently, HandleRelayInvs flow is the only place where IBD is triggered, so the channel can be closed now
|
||||
close(peer.IBDRequestChannel())
|
||||
return err
|
||||
}
|
||||
|
||||
func (flow *handleRelayInvsFlow) start() error {
|
||||
for {
|
||||
log.Debugf("Waiting for inv")
|
||||
inv, err := flow.readInv()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("Got relay inv for block %s", inv.Hash)
|
||||
|
||||
blockInfo, err := flow.Domain().Consensus().GetBlockInfo(inv.Hash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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)
|
||||
}
|
||||
log.Debugf("Block %s already exists. continuing...", inv.Hash)
|
||||
continue
|
||||
}
|
||||
|
||||
isGenesisVirtualSelectedParent, err := flow.isGenesisVirtualSelectedParent()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if flow.IsOrphan(inv.Hash) {
|
||||
if flow.Config().NetParams().DisallowDirectBlocksOnTopOfGenesis && !flow.Config().AllowSubmitBlockWhenNotSynced && isGenesisVirtualSelectedParent {
|
||||
log.Infof("Cannot process orphan %s for a node with only the genesis block. The node needs to IBD "+
|
||||
"to the recent pruning point before normal operation can resume.", inv.Hash)
|
||||
continue
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// Block relay is disabled during IBD
|
||||
if flow.IsIBDRunning() {
|
||||
log.Debugf("Got block %s while in IBD. continuing...", inv.Hash)
|
||||
continue
|
||||
}
|
||||
|
||||
log.Debugf("Requesting block %s", inv.Hash)
|
||||
block, exists, err := flow.requestBlock(inv.Hash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exists {
|
||||
log.Debugf("Aborting requesting block %s because it already exists", inv.Hash)
|
||||
continue
|
||||
}
|
||||
|
||||
err = flow.banIfBlockIsHeaderOnly(block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if flow.Config().NetParams().DisallowDirectBlocksOnTopOfGenesis && !flow.Config().AllowSubmitBlockWhenNotSynced && !flow.Config().Devnet && flow.isChildOfGenesis(block) {
|
||||
log.Infof("Cannot process %s because it's a direct child of genesis.", consensushashing.BlockHash(block))
|
||||
continue
|
||||
}
|
||||
|
||||
log.Debugf("Processing block %s", inv.Hash)
|
||||
missingParents, virtualChangeSet, err := flow.processBlock(block)
|
||||
if err != nil {
|
||||
if errors.Is(err, ruleerrors.ErrPrunedBlock) {
|
||||
log.Infof("Ignoring pruned block %s", inv.Hash)
|
||||
continue
|
||||
}
|
||||
|
||||
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 is orphan and has missing parents: %s", inv.Hash, missingParents)
|
||||
err := flow.processOrphan(block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
log.Debugf("Relaying block %s", inv.Hash)
|
||||
err = flow.relayBlock(block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infof("Accepted block %s via relay", inv.Hash)
|
||||
err = flow.OnNewBlock(block, virtualChangeSet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (flow *handleRelayInvsFlow) banIfBlockIsHeaderOnly(block *externalapi.DomainBlock) error {
|
||||
if len(block.Transactions) == 0 {
|
||||
return protocolerrors.Errorf(true, "sent header of %s block where expected block with body",
|
||||
consensushashing.BlockHash(block))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (flow *handleRelayInvsFlow) readInv() (*appmessage.MsgInvRelayBlock, error) {
|
||||
if len(flow.invsQueue) > 0 {
|
||||
var inv *appmessage.MsgInvRelayBlock
|
||||
inv, flow.invsQueue = flow.invsQueue[0], flow.invsQueue[1:]
|
||||
return inv, nil
|
||||
}
|
||||
|
||||
msg, err := flow.incomingRoute.Dequeue()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
inv, ok := msg.(*appmessage.MsgInvRelayBlock)
|
||||
if !ok {
|
||||
return nil, protocolerrors.Errorf(true, "unexpected %s message in the block relay handleRelayInvsFlow while "+
|
||||
"expecting an inv message", msg.Command())
|
||||
}
|
||||
return inv, nil
|
||||
}
|
||||
|
||||
func (flow *handleRelayInvsFlow) requestBlock(requestHash *externalapi.DomainHash) (*externalapi.DomainBlock, bool, error) {
|
||||
exists := flow.SharedRequestedBlocks().AddIfNotExists(requestHash)
|
||||
if exists {
|
||||
return nil, true, nil
|
||||
}
|
||||
|
||||
// In case the function returns earlier than expected, we want to make sure flow.SharedRequestedBlocks() is
|
||||
// clean from any pending blocks.
|
||||
defer flow.SharedRequestedBlocks().Remove(requestHash)
|
||||
|
||||
getRelayBlocksMsg := appmessage.NewMsgRequestRelayBlocks([]*externalapi.DomainHash{requestHash})
|
||||
err := flow.outgoingRoute.Enqueue(getRelayBlocksMsg)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
msgBlock, err := flow.readMsgBlock()
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
block := appmessage.MsgBlockToDomainBlock(msgBlock)
|
||||
blockHash := consensushashing.BlockHash(block)
|
||||
if !blockHash.Equal(requestHash) {
|
||||
return nil, false, protocolerrors.Errorf(true, "got unrequested block %s", blockHash)
|
||||
}
|
||||
|
||||
return block, false, nil
|
||||
}
|
||||
|
||||
// readMsgBlock returns the next msgBlock in msgChan, and populates invsQueue with any inv messages that meanwhile arrive.
|
||||
//
|
||||
// Note: this function assumes msgChan can contain only appmessage.MsgInvRelayBlock and appmessage.MsgBlock messages.
|
||||
func (flow *handleRelayInvsFlow) readMsgBlock() (msgBlock *appmessage.MsgBlock, err error) {
|
||||
for {
|
||||
message, err := flow.incomingRoute.DequeueWithTimeout(common.DefaultTimeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch message := message.(type) {
|
||||
case *appmessage.MsgInvRelayBlock:
|
||||
flow.invsQueue = append(flow.invsQueue, message)
|
||||
case *appmessage.MsgBlock:
|
||||
return message, nil
|
||||
default:
|
||||
return nil, errors.Errorf("unexpected message %s", message.Command())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (flow *handleRelayInvsFlow) processBlock(block *externalapi.DomainBlock) ([]*externalapi.DomainHash, *externalapi.VirtualChangeSet, error) {
|
||||
blockHash := consensushashing.BlockHash(block)
|
||||
virtualChangeSet, err := flow.Domain().Consensus().ValidateAndInsertBlock(block, true)
|
||||
if err != nil {
|
||||
if !errors.As(err, &ruleerrors.RuleError{}) {
|
||||
return nil, nil, errors.Wrapf(err, "failed to process block %s", blockHash)
|
||||
}
|
||||
|
||||
missingParentsError := &ruleerrors.ErrMissingParents{}
|
||||
if errors.As(err, missingParentsError) {
|
||||
return missingParentsError.MissingParentHashes, nil, nil
|
||||
}
|
||||
log.Warnf("Rejected block %s from %s: %s", blockHash, flow.peer, err)
|
||||
return nil, nil, protocolerrors.Wrapf(true, err, "got invalid block %s from relay", blockHash)
|
||||
}
|
||||
return nil, virtualChangeSet, nil
|
||||
}
|
||||
|
||||
func (flow *handleRelayInvsFlow) relayBlock(block *externalapi.DomainBlock) error {
|
||||
blockHash := consensushashing.BlockHash(block)
|
||||
return flow.Broadcast(appmessage.NewMsgInvBlock(blockHash))
|
||||
}
|
||||
|
||||
func (flow *handleRelayInvsFlow) processOrphan(block *externalapi.DomainBlock) error {
|
||||
blockHash := consensushashing.BlockHash(block)
|
||||
|
||||
// Return if the block has been orphaned from elsewhere already
|
||||
if flow.IsOrphan(blockHash) {
|
||||
log.Debugf("Skipping orphan processing for block %s because it is already an orphan", blockHash)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Add the block to the orphan set if it's within orphan resolution range
|
||||
isBlockInOrphanResolutionRange, err := flow.isBlockInOrphanResolutionRange(blockHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if isBlockInOrphanResolutionRange {
|
||||
if flow.Config().NetParams().DisallowDirectBlocksOnTopOfGenesis && !flow.Config().AllowSubmitBlockWhenNotSynced {
|
||||
isGenesisVirtualSelectedParent, err := flow.isGenesisVirtualSelectedParent()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if isGenesisVirtualSelectedParent {
|
||||
log.Infof("Cannot process orphan %s for a node with only the genesis block. The node needs to IBD "+
|
||||
"to the recent pruning point before normal operation can resume.", blockHash)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf("Block %s is within orphan resolution range. "+
|
||||
"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
|
||||
log.Debugf("Block %s is out of orphan resolution range. "+
|
||||
"Attempting to start IBD against it.", blockHash)
|
||||
|
||||
// Send the block to IBD flow via the IBDRequestChannel.
|
||||
// Note that this is a non-blocking send, since if IBD is already running, there is no need to trigger it
|
||||
select {
|
||||
case flow.peer.IBDRequestChannel() <- block:
|
||||
default:
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (flow *handleRelayInvsFlow) isGenesisVirtualSelectedParent() (bool, error) {
|
||||
virtualSelectedParent, err := flow.Domain().Consensus().GetVirtualSelectedParent()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return virtualSelectedParent.Equal(flow.Config().NetParams().GenesisHash), nil
|
||||
}
|
||||
|
||||
func (flow *handleRelayInvsFlow) isChildOfGenesis(block *externalapi.DomainBlock) bool {
|
||||
parents := block.Header.DirectParents()
|
||||
return len(parents) == 1 && parents[0].Equal(flow.Config().NetParams().GenesisHash)
|
||||
}
|
||||
|
||||
// isBlockInOrphanResolutionRange finds out whether the given blockHash should be
|
||||
// retrieved via the unorphaning mechanism or via IBD. This method sends a
|
||||
// getBlockLocator request to the peer with a limit of orphanResolutionRange.
|
||||
// In the response, if we know none of the hashes, we should retrieve the given
|
||||
// blockHash via IBD. Otherwise, via unorphaning.
|
||||
func (flow *handleRelayInvsFlow) isBlockInOrphanResolutionRange(blockHash *externalapi.DomainHash) (bool, error) {
|
||||
err := flow.sendGetBlockLocator(blockHash, orphanResolutionRange)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
blockLocatorHashes, err := flow.receiveBlockLocator()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
for _, blockLocatorHash := range blockLocatorHashes {
|
||||
blockInfo, err := flow.Domain().Consensus().GetBlockInfo(blockLocatorHash)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if blockInfo.Exists && blockInfo.BlockStatus != externalapi.StatusHeaderOnly {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package blockrelay
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/app/protocol/protocolerrors"
|
||||
"github.com/kaspanet/kaspad/domain"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
||||
)
|
||||
|
||||
// RequestBlockLocatorContext is the interface for the context needed for the HandleRequestBlockLocator flow.
|
||||
type RequestBlockLocatorContext interface {
|
||||
Domain() domain.Domain
|
||||
}
|
||||
|
||||
type handleRequestBlockLocatorFlow struct {
|
||||
RequestBlockLocatorContext
|
||||
incomingRoute, outgoingRoute *router.Route
|
||||
}
|
||||
|
||||
// HandleRequestBlockLocator handles getBlockLocator messages
|
||||
func HandleRequestBlockLocator(context RequestBlockLocatorContext, incomingRoute *router.Route,
|
||||
outgoingRoute *router.Route) error {
|
||||
|
||||
flow := &handleRequestBlockLocatorFlow{
|
||||
RequestBlockLocatorContext: context,
|
||||
incomingRoute: incomingRoute,
|
||||
outgoingRoute: outgoingRoute,
|
||||
}
|
||||
return flow.start()
|
||||
}
|
||||
|
||||
func (flow *handleRequestBlockLocatorFlow) start() error {
|
||||
for {
|
||||
highHash, limit, err := flow.receiveGetBlockLocator()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debugf("Received getBlockLocator with highHash: %s, limit: %d", highHash, limit)
|
||||
|
||||
locator, err := flow.Domain().Consensus().CreateBlockLocatorFromPruningPoint(highHash, limit)
|
||||
if err != nil || len(locator) == 0 {
|
||||
if err != nil {
|
||||
log.Debugf("Received error from CreateBlockLocatorFromPruningPoint: %s", err)
|
||||
}
|
||||
return protocolerrors.Errorf(true, "couldn't build a block "+
|
||||
"locator between the pruning point and %s", highHash)
|
||||
}
|
||||
|
||||
err = flow.sendBlockLocator(locator)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (flow *handleRequestBlockLocatorFlow) receiveGetBlockLocator() (highHash *externalapi.DomainHash, limit uint32, err error) {
|
||||
|
||||
message, err := flow.incomingRoute.Dequeue()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
msgGetBlockLocator := message.(*appmessage.MsgRequestBlockLocator)
|
||||
|
||||
return msgGetBlockLocator.HighHash, msgGetBlockLocator.Limit, nil
|
||||
}
|
||||
|
||||
func (flow *handleRequestBlockLocatorFlow) sendBlockLocator(locator externalapi.BlockLocator) error {
|
||||
msgBlockLocator := appmessage.NewMsgBlockLocator(locator)
|
||||
err := flow.outgoingRoute.Enqueue(msgBlockLocator)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
105
app/protocol/flows/v4/blockrelay/handle_request_headers.go
Normal file
105
app/protocol/flows/v4/blockrelay/handle_request_headers.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package blockrelay
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/app/protocol/peer"
|
||||
"github.com/kaspanet/kaspad/app/protocol/protocolerrors"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/domain"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
||||
)
|
||||
|
||||
const ibdBatchSize = router.DefaultMaxMessages
|
||||
|
||||
// RequestHeadersContext is the interface for the context needed for the HandleRequestHeaders flow.
|
||||
type RequestHeadersContext interface {
|
||||
Domain() domain.Domain
|
||||
}
|
||||
|
||||
type handleRequestHeadersFlow struct {
|
||||
RequestHeadersContext
|
||||
incomingRoute, outgoingRoute *router.Route
|
||||
peer *peer.Peer
|
||||
}
|
||||
|
||||
// HandleRequestHeaders handles RequestHeaders messages
|
||||
func HandleRequestHeaders(context RequestHeadersContext, incomingRoute *router.Route,
|
||||
outgoingRoute *router.Route, peer *peer.Peer) error {
|
||||
|
||||
flow := &handleRequestHeadersFlow{
|
||||
RequestHeadersContext: context,
|
||||
incomingRoute: incomingRoute,
|
||||
outgoingRoute: outgoingRoute,
|
||||
peer: peer,
|
||||
}
|
||||
return flow.start()
|
||||
}
|
||||
|
||||
func (flow *handleRequestHeadersFlow) start() error {
|
||||
for {
|
||||
lowHash, highHash, err := receiveRequestHeaders(flow.incomingRoute)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debugf("Recieved requestHeaders with lowHash: %s, highHash: %s", lowHash, highHash)
|
||||
|
||||
for !lowHash.Equal(highHash) {
|
||||
log.Debugf("Getting block headers between %s and %s to %s", lowHash, highHash, flow.peer)
|
||||
|
||||
// GetHashesBetween is a relatively heavy operation so we limit it
|
||||
// in order to avoid locking the consensus for too long
|
||||
// maxBlocks MUST be >= MergeSetSizeLimit + 1
|
||||
const maxBlocks = 1 << 10
|
||||
blockHashes, _, err := flow.Domain().Consensus().GetHashesBetween(lowHash, highHash, maxBlocks)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debugf("Got %d header hashes above lowHash %s", len(blockHashes), lowHash)
|
||||
|
||||
blockHeaders := make([]*appmessage.MsgBlockHeader, len(blockHashes))
|
||||
for i, blockHash := range blockHashes {
|
||||
blockHeader, err := flow.Domain().Consensus().GetBlockHeader(blockHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
blockHeaders[i] = appmessage.DomainBlockHeaderToBlockHeader(blockHeader)
|
||||
}
|
||||
|
||||
blockHeadersMessage := appmessage.NewBlockHeadersMessage(blockHeaders)
|
||||
err = flow.outgoingRoute.Enqueue(blockHeadersMessage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
message, err := flow.incomingRoute.Dequeue()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, ok := message.(*appmessage.MsgRequestNextHeaders); !ok {
|
||||
return protocolerrors.Errorf(true, "received unexpected message type. "+
|
||||
"expected: %s, got: %s", appmessage.CmdRequestNextHeaders, message.Command())
|
||||
}
|
||||
|
||||
// The next lowHash is the last element in blockHashes
|
||||
lowHash = blockHashes[len(blockHashes)-1]
|
||||
}
|
||||
|
||||
err = flow.outgoingRoute.Enqueue(appmessage.NewMsgDoneHeaders())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func receiveRequestHeaders(incomingRoute *router.Route) (lowHash *externalapi.DomainHash,
|
||||
highHash *externalapi.DomainHash, err error) {
|
||||
|
||||
message, err := incomingRoute.Dequeue()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
msgRequestIBDBlocks := message.(*appmessage.MsgRequestHeaders)
|
||||
|
||||
return msgRequestIBDBlocks.LowHash, msgRequestIBDBlocks.HighHash, nil
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
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"
|
||||
)
|
||||
|
||||
// HandleRequestPruningPointUTXOSetContext is the interface for the context needed for the HandleRequestPruningPointUTXOSet flow.
|
||||
type HandleRequestPruningPointUTXOSetContext interface {
|
||||
Domain() domain.Domain
|
||||
}
|
||||
|
||||
type handleRequestPruningPointUTXOSetFlow struct {
|
||||
HandleRequestPruningPointUTXOSetContext
|
||||
incomingRoute, outgoingRoute *router.Route
|
||||
}
|
||||
|
||||
// HandleRequestPruningPointUTXOSet listens to appmessage.MsgRequestPruningPointUTXOSet messages and sends
|
||||
// the pruning point UTXO set and block body.
|
||||
func HandleRequestPruningPointUTXOSet(context HandleRequestPruningPointUTXOSetContext, incomingRoute,
|
||||
outgoingRoute *router.Route) error {
|
||||
|
||||
flow := &handleRequestPruningPointUTXOSetFlow{
|
||||
HandleRequestPruningPointUTXOSetContext: context,
|
||||
incomingRoute: incomingRoute,
|
||||
outgoingRoute: outgoingRoute,
|
||||
}
|
||||
|
||||
return flow.start()
|
||||
}
|
||||
|
||||
func (flow *handleRequestPruningPointUTXOSetFlow) start() error {
|
||||
for {
|
||||
msgRequestPruningPointUTXOSet, err := flow.waitForRequestPruningPointUTXOSetMessages()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = flow.handleRequestPruningPointUTXOSetMessage(msgRequestPruningPointUTXOSet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (flow *handleRequestPruningPointUTXOSetFlow) handleRequestPruningPointUTXOSetMessage(
|
||||
msgRequestPruningPointUTXOSet *appmessage.MsgRequestPruningPointUTXOSet) error {
|
||||
|
||||
onEnd := logger.LogAndMeasureExecutionTime(log, "handleRequestPruningPointUTXOSetFlow")
|
||||
defer onEnd()
|
||||
|
||||
log.Debugf("Got request for pruning point UTXO set")
|
||||
|
||||
return flow.sendPruningPointUTXOSet(msgRequestPruningPointUTXOSet)
|
||||
}
|
||||
|
||||
func (flow *handleRequestPruningPointUTXOSetFlow) waitForRequestPruningPointUTXOSetMessages() (
|
||||
*appmessage.MsgRequestPruningPointUTXOSet, error) {
|
||||
|
||||
message, err := flow.incomingRoute.Dequeue()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
msgRequestPruningPointUTXOSet, ok := message.(*appmessage.MsgRequestPruningPointUTXOSet)
|
||||
if !ok {
|
||||
// TODO: Change to shouldBan: true once we fix the bug of getting redundant messages
|
||||
return nil, protocolerrors.Errorf(false, "received unexpected message type. "+
|
||||
"expected: %s, got: %s", appmessage.CmdRequestPruningPointUTXOSet, message.Command())
|
||||
}
|
||||
return msgRequestPruningPointUTXOSet, nil
|
||||
}
|
||||
|
||||
func (flow *handleRequestPruningPointUTXOSetFlow) sendPruningPointUTXOSet(
|
||||
msgRequestPruningPointUTXOSet *appmessage.MsgRequestPruningPointUTXOSet) 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(
|
||||
msgRequestPruningPointUTXOSet.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), msgRequestPruningPointUTXOSet.PruningPointHash)
|
||||
|
||||
outpointAndUTXOEntryPairs :=
|
||||
appmessage.DomainOutpointAndUTXOEntryPairsToOutpointAndUTXOEntryPairs(pruningPointUTXOs)
|
||||
err = flow.outgoingRoute.Enqueue(appmessage.NewMsgPruningPointUTXOSetChunk(outpointAndUTXOEntryPairs))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
finished := len(pruningPointUTXOs) < step
|
||||
if finished && chunksSent%ibdBatchSize != 0 {
|
||||
log.Debugf("Finished sending UTXOs for pruning block %s",
|
||||
msgRequestPruningPointUTXOSet.PruningPointHash)
|
||||
|
||||
return flow.outgoingRoute.Enqueue(appmessage.NewMsgDonePruningPointUTXOSetChunks())
|
||||
}
|
||||
|
||||
if len(pruningPointUTXOs) > 0 {
|
||||
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 {
|
||||
// TODO: Change to shouldBan: true once we fix the bug of getting redundant messages
|
||||
return protocolerrors.Errorf(false, "received unexpected message type. "+
|
||||
"expected: %s, got: %s", appmessage.CmdRequestNextPruningPointUTXOSetChunk, message.Command())
|
||||
}
|
||||
|
||||
if finished {
|
||||
log.Debugf("Finished sending UTXOs for pruning block %s",
|
||||
msgRequestPruningPointUTXOSet.PruningPointHash)
|
||||
|
||||
return flow.outgoingRoute.Enqueue(appmessage.NewMsgDonePruningPointUTXOSetChunks())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
577
app/protocol/flows/v4/blockrelay/ibd.go
Normal file
577
app/protocol/flows/v4/blockrelay/ibd.go
Normal file
@@ -0,0 +1,577 @@
|
||||
package blockrelay
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"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/domain"
|
||||
"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/infrastructure/config"
|
||||
"github.com/kaspanet/kaspad/infrastructure/logger"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
||||
"github.com/pkg/errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
// IBDContext is the interface for the context needed for the HandleIBD flow.
|
||||
type IBDContext interface {
|
||||
Domain() domain.Domain
|
||||
Config() *config.Config
|
||||
OnNewBlock(block *externalapi.DomainBlock, virtualChangeSet *externalapi.VirtualChangeSet) error
|
||||
OnVirtualChange(virtualChangeSet *externalapi.VirtualChangeSet) error
|
||||
OnPruningPointUTXOSetOverride() error
|
||||
IsIBDRunning() bool
|
||||
TrySetIBDRunning(ibdPeer *peerpkg.Peer) bool
|
||||
UnsetIBDRunning()
|
||||
IsRecoverableError(err error) bool
|
||||
}
|
||||
|
||||
type handleIBDFlow struct {
|
||||
IBDContext
|
||||
incomingRoute, outgoingRoute *router.Route
|
||||
peer *peerpkg.Peer
|
||||
}
|
||||
|
||||
// HandleIBD handles IBD
|
||||
func HandleIBD(context IBDContext, incomingRoute *router.Route, outgoingRoute *router.Route,
|
||||
peer *peerpkg.Peer) error {
|
||||
|
||||
flow := &handleIBDFlow{
|
||||
IBDContext: context,
|
||||
incomingRoute: incomingRoute,
|
||||
outgoingRoute: outgoingRoute,
|
||||
peer: peer,
|
||||
}
|
||||
return flow.start()
|
||||
}
|
||||
|
||||
func (flow *handleIBDFlow) start() error {
|
||||
for {
|
||||
// Wait for IBD requests triggered by other flows
|
||||
block, ok := <-flow.peer.IBDRequestChannel()
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
err := flow.runIBDIfNotRunning(block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (flow *handleIBDFlow) runIBDIfNotRunning(block *externalapi.DomainBlock) error {
|
||||
wasIBDNotRunning := flow.TrySetIBDRunning(flow.peer)
|
||||
if !wasIBDNotRunning {
|
||||
log.Debugf("IBD is already running")
|
||||
return nil
|
||||
}
|
||||
|
||||
isFinishedSuccessfully := false
|
||||
defer func() {
|
||||
flow.UnsetIBDRunning()
|
||||
flow.logIBDFinished(isFinishedSuccessfully)
|
||||
}()
|
||||
|
||||
highHash := consensushashing.BlockHash(block)
|
||||
log.Debugf("IBD started with peer %s and highHash %s", flow.peer, highHash)
|
||||
log.Debugf("Syncing blocks up to %s", highHash)
|
||||
log.Debugf("Trying to find highest shared chain block with peer %s with high hash %s", flow.peer, highHash)
|
||||
highestSharedBlockHash, highestSharedBlockFound, err := flow.findHighestSharedBlockHash(highHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debugf("Found highest shared chain block %s with peer %s", highestSharedBlockHash, flow.peer)
|
||||
|
||||
shouldDownloadHeadersProof, shouldSync, err := flow.shouldSyncAndShouldDownloadHeadersProof(block, highestSharedBlockFound)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !shouldSync {
|
||||
return nil
|
||||
}
|
||||
|
||||
if shouldDownloadHeadersProof {
|
||||
log.Infof("Starting IBD with headers proof")
|
||||
err := flow.ibdWithHeadersProof(highHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if flow.Config().NetParams().DisallowDirectBlocksOnTopOfGenesis && !flow.Config().AllowSubmitBlockWhenNotSynced {
|
||||
isGenesisVirtualSelectedParent, err := flow.isGenesisVirtualSelectedParent()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if isGenesisVirtualSelectedParent {
|
||||
log.Infof("Cannot IBD to %s because it won't change the pruning point. The node needs to IBD "+
|
||||
"to the recent pruning point before normal operation can resume.", highHash)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
err = flow.syncPruningPointFutureHeaders(flow.Domain().Consensus(), highestSharedBlockHash, highHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = flow.syncMissingBlockBodies(highHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("Finished syncing blocks up to %s", highHash)
|
||||
isFinishedSuccessfully = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (flow *handleIBDFlow) isGenesisVirtualSelectedParent() (bool, error) {
|
||||
virtualSelectedParent, err := flow.Domain().Consensus().GetVirtualSelectedParent()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return virtualSelectedParent.Equal(flow.Config().NetParams().GenesisHash), nil
|
||||
}
|
||||
|
||||
func (flow *handleIBDFlow) logIBDFinished(isFinishedSuccessfully bool) {
|
||||
successString := "successfully"
|
||||
if !isFinishedSuccessfully {
|
||||
successString = "(interrupted)"
|
||||
}
|
||||
log.Infof("IBD finished %s", successString)
|
||||
}
|
||||
|
||||
// findHighestSharedBlock attempts to find the highest shared block between the peer
|
||||
// and this node. This method may fail because the peer and us have conflicting pruning
|
||||
// points. In that case we return (nil, false, nil) so that we may stop IBD gracefully.
|
||||
func (flow *handleIBDFlow) findHighestSharedBlockHash(
|
||||
targetHash *externalapi.DomainHash) (*externalapi.DomainHash, bool, error) {
|
||||
|
||||
log.Debugf("Sending a blockLocator to %s between pruning point and headers selected tip", flow.peer)
|
||||
blockLocator, err := flow.Domain().Consensus().CreateFullHeadersSelectedChainBlockLocator()
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
for {
|
||||
highestHash, highestHashFound, err := flow.fetchHighestHash(targetHash, blockLocator)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
if !highestHashFound {
|
||||
return nil, false, nil
|
||||
}
|
||||
highestHashIndex, err := flow.findHighestHashIndex(highestHash, blockLocator)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
if highestHashIndex == 0 ||
|
||||
// If the block locator contains only two adjacent chain blocks, the
|
||||
// syncer will always find the same highest chain block, so to avoid
|
||||
// an endless loop, we explicitly stop the loop in such situation.
|
||||
(len(blockLocator) == 2 && highestHashIndex == 1) {
|
||||
|
||||
return highestHash, true, nil
|
||||
}
|
||||
|
||||
locatorHashAboveHighestHash := highestHash
|
||||
if highestHashIndex > 0 {
|
||||
locatorHashAboveHighestHash = blockLocator[highestHashIndex-1]
|
||||
}
|
||||
|
||||
blockLocator, err = flow.nextBlockLocator(highestHash, locatorHashAboveHighestHash)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (flow *handleIBDFlow) nextBlockLocator(lowHash, highHash *externalapi.DomainHash) (externalapi.BlockLocator, error) {
|
||||
log.Debugf("Sending a blockLocator to %s between %s and %s", flow.peer, lowHash, highHash)
|
||||
blockLocator, err := flow.Domain().Consensus().CreateHeadersSelectedChainBlockLocator(lowHash, highHash)
|
||||
if err != nil {
|
||||
if errors.Is(model.ErrBlockNotInSelectedParentChain, err) {
|
||||
return nil, err
|
||||
}
|
||||
log.Debugf("Headers selected parent chain moved since findHighestSharedBlockHash - " +
|
||||
"restarting with full block locator")
|
||||
blockLocator, err = flow.Domain().Consensus().CreateFullHeadersSelectedChainBlockLocator()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return blockLocator, nil
|
||||
}
|
||||
|
||||
func (flow *handleIBDFlow) findHighestHashIndex(
|
||||
highestHash *externalapi.DomainHash, blockLocator externalapi.BlockLocator) (int, error) {
|
||||
|
||||
highestHashIndex := 0
|
||||
highestHashIndexFound := false
|
||||
for i, blockLocatorHash := range blockLocator {
|
||||
if highestHash.Equal(blockLocatorHash) {
|
||||
highestHashIndex = i
|
||||
highestHashIndexFound = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !highestHashIndexFound {
|
||||
return 0, protocolerrors.Errorf(true, "highest hash %s "+
|
||||
"returned from peer %s is not in the original blockLocator", highestHash, flow.peer)
|
||||
}
|
||||
log.Debugf("The index of the highest hash in the original "+
|
||||
"blockLocator sent to %s is %d", flow.peer, highestHashIndex)
|
||||
|
||||
return highestHashIndex, nil
|
||||
}
|
||||
|
||||
// fetchHighestHash attempts to fetch the highest hash the peer knows amongst the given
|
||||
// blockLocator. This method may fail because the peer and us have conflicting pruning
|
||||
// points. In that case we return (nil, false, nil) so that we may stop IBD gracefully.
|
||||
func (flow *handleIBDFlow) fetchHighestHash(
|
||||
targetHash *externalapi.DomainHash, blockLocator externalapi.BlockLocator) (*externalapi.DomainHash, bool, error) {
|
||||
|
||||
ibdBlockLocatorMessage := appmessage.NewMsgIBDBlockLocator(targetHash, blockLocator)
|
||||
err := flow.outgoingRoute.Enqueue(ibdBlockLocatorMessage)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
message, err := flow.incomingRoute.DequeueWithTimeout(common.DefaultTimeout)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
switch message := message.(type) {
|
||||
case *appmessage.MsgIBDBlockLocatorHighestHash:
|
||||
highestHash := message.HighestHash
|
||||
log.Debugf("The highest hash the peer %s knows is %s", flow.peer, highestHash)
|
||||
|
||||
return highestHash, true, nil
|
||||
case *appmessage.MsgIBDBlockLocatorHighestHashNotFound:
|
||||
log.Debugf("Peer %s does not know any block within our blockLocator. "+
|
||||
"This should only happen if there's a DAG split deeper than the pruning point.", flow.peer)
|
||||
return nil, false, nil
|
||||
default:
|
||||
return nil, false, protocolerrors.Errorf(true, "received unexpected message type. "+
|
||||
"expected: %s, got: %s", appmessage.CmdIBDBlockLocatorHighestHash, message.Command())
|
||||
}
|
||||
}
|
||||
|
||||
func (flow *handleIBDFlow) syncPruningPointFutureHeaders(consensus externalapi.Consensus, highestSharedBlockHash *externalapi.DomainHash,
|
||||
highHash *externalapi.DomainHash) error {
|
||||
|
||||
log.Infof("Downloading headers from %s", flow.peer)
|
||||
|
||||
err := flow.sendRequestHeaders(highestSharedBlockHash, highHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Keep a short queue of BlockHeadersMessages so that there's
|
||||
// never a moment when the node is not validating and inserting
|
||||
// headers
|
||||
blockHeadersMessageChan := make(chan *appmessage.BlockHeadersMessage, 2)
|
||||
errChan := make(chan error)
|
||||
spawn("handleRelayInvsFlow-syncPruningPointFutureHeaders", func() {
|
||||
for {
|
||||
blockHeadersMessage, doneIBD, err := flow.receiveHeaders()
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
if doneIBD {
|
||||
close(blockHeadersMessageChan)
|
||||
return
|
||||
}
|
||||
|
||||
blockHeadersMessageChan <- blockHeadersMessage
|
||||
|
||||
err = flow.outgoingRoute.Enqueue(appmessage.NewMsgRequestNextHeaders())
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
for {
|
||||
select {
|
||||
case ibdBlocksMessage, ok := <-blockHeadersMessageChan:
|
||||
if !ok {
|
||||
// If the highHash has not been received, the peer is misbehaving
|
||||
highHashBlockInfo, err := consensus.GetBlockInfo(highHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !highHashBlockInfo.Exists {
|
||||
return protocolerrors.Errorf(true, "did not receive "+
|
||||
"highHash block %s from peer %s during block download", highHash, flow.peer)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
for _, header := range ibdBlocksMessage.BlockHeaders {
|
||||
err = flow.processHeader(consensus, header)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case err := <-errChan:
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (flow *handleIBDFlow) sendRequestHeaders(highestSharedBlockHash *externalapi.DomainHash,
|
||||
peerSelectedTipHash *externalapi.DomainHash) error {
|
||||
|
||||
msgGetBlockInvs := appmessage.NewMsgRequstHeaders(highestSharedBlockHash, peerSelectedTipHash)
|
||||
return flow.outgoingRoute.Enqueue(msgGetBlockInvs)
|
||||
}
|
||||
|
||||
func (flow *handleIBDFlow) receiveHeaders() (msgIBDBlock *appmessage.BlockHeadersMessage, doneHeaders bool, err error) {
|
||||
message, err := flow.incomingRoute.DequeueWithTimeout(common.DefaultTimeout)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
switch message := message.(type) {
|
||||
case *appmessage.BlockHeadersMessage:
|
||||
return message, false, nil
|
||||
case *appmessage.MsgDoneHeaders:
|
||||
return nil, true, nil
|
||||
default:
|
||||
return nil, false,
|
||||
protocolerrors.Errorf(true, "received unexpected message type. "+
|
||||
"expected: %s or %s, got: %s",
|
||||
appmessage.CmdBlockHeaders,
|
||||
appmessage.CmdDoneHeaders,
|
||||
message.Command())
|
||||
}
|
||||
}
|
||||
|
||||
func (flow *handleIBDFlow) processHeader(consensus externalapi.Consensus, msgBlockHeader *appmessage.MsgBlockHeader) error {
|
||||
header := appmessage.BlockHeaderToDomainBlockHeader(msgBlockHeader)
|
||||
block := &externalapi.DomainBlock{
|
||||
Header: header,
|
||||
Transactions: nil,
|
||||
}
|
||||
|
||||
blockHash := consensushashing.BlockHash(block)
|
||||
blockInfo, err := consensus.GetBlockInfo(blockHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if blockInfo.Exists {
|
||||
log.Debugf("Block header %s is already in the DAG. Skipping...", blockHash)
|
||||
return nil
|
||||
}
|
||||
_, err = consensus.ValidateAndInsertBlock(block, false)
|
||||
if err != nil {
|
||||
if !errors.As(err, &ruleerrors.RuleError{}) {
|
||||
return errors.Wrapf(err, "failed to process header %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
|
||||
}
|
||||
|
||||
func (flow *handleIBDFlow) validatePruningPointFutureHeaderTimestamps() error {
|
||||
headerSelectedTipHash, err := flow.Domain().StagingConsensus().GetHeadersSelectedTip()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
headerSelectedTipHeader, err := flow.Domain().StagingConsensus().GetBlockHeader(headerSelectedTipHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
headerSelectedTipTimestamp := headerSelectedTipHeader.TimeInMilliseconds()
|
||||
|
||||
currentSelectedTipHash, err := flow.Domain().Consensus().GetHeadersSelectedTip()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
currentSelectedTipHeader, err := flow.Domain().Consensus().GetBlockHeader(currentSelectedTipHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
currentSelectedTipTimestamp := currentSelectedTipHeader.TimeInMilliseconds()
|
||||
|
||||
if headerSelectedTipTimestamp < currentSelectedTipTimestamp {
|
||||
return protocolerrors.Errorf(false, "the timestamp of the candidate selected "+
|
||||
"tip is smaller than the current selected tip")
|
||||
}
|
||||
|
||||
minTimestampDifferenceInMilliseconds := (10 * time.Minute).Milliseconds()
|
||||
if headerSelectedTipTimestamp-currentSelectedTipTimestamp < minTimestampDifferenceInMilliseconds {
|
||||
return protocolerrors.Errorf(false, "difference between the timestamps of "+
|
||||
"the current pruning point and the candidate pruning point is too small. Aborting IBD...")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (flow *handleIBDFlow) receiveAndInsertPruningPointUTXOSet(
|
||||
consensus externalapi.Consensus, pruningPointHash *externalapi.DomainHash) (bool, error) {
|
||||
|
||||
onEnd := logger.LogAndMeasureExecutionTime(log, "receiveAndInsertPruningPointUTXOSet")
|
||||
defer onEnd()
|
||||
|
||||
receivedChunkCount := 0
|
||||
receivedUTXOCount := 0
|
||||
for {
|
||||
message, err := flow.incomingRoute.DequeueWithTimeout(common.DefaultTimeout)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
switch message := message.(type) {
|
||||
case *appmessage.MsgPruningPointUTXOSetChunk:
|
||||
receivedUTXOCount += len(message.OutpointAndUTXOEntryPairs)
|
||||
domainOutpointAndUTXOEntryPairs :=
|
||||
appmessage.OutpointAndUTXOEntryPairsToDomainOutpointAndUTXOEntryPairs(message.OutpointAndUTXOEntryPairs)
|
||||
|
||||
err := consensus.AppendImportedPruningPointUTXOs(domainOutpointAndUTXOEntryPairs)
|
||||
if err != nil {
|
||||
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.Infof("Finished receiving the UTXO set. Total UTXOs: %d", receivedUTXOCount)
|
||||
return true, nil
|
||||
|
||||
case *appmessage.MsgUnexpectedPruningPoint:
|
||||
log.Infof("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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (flow *handleIBDFlow) syncMissingBlockBodies(highHash *externalapi.DomainHash) error {
|
||||
hashes, err := flow.Domain().Consensus().GetMissingBlockBodyHashes(highHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(hashes) == 0 {
|
||||
// Blocks can be inserted inside the DAG during IBD if those were requested before IBD started.
|
||||
// In rare cases, all the IBD blocks might be already inserted by the time we reach this point.
|
||||
// In these cases - GetMissingBlockBodyHashes would return an empty array.
|
||||
log.Debugf("No missing block body hashes found.")
|
||||
return nil
|
||||
}
|
||||
|
||||
for offset := 0; offset < len(hashes); offset += ibdBatchSize {
|
||||
var hashesToRequest []*externalapi.DomainHash
|
||||
if offset+ibdBatchSize < len(hashes) {
|
||||
hashesToRequest = hashes[offset : offset+ibdBatchSize]
|
||||
} else {
|
||||
hashesToRequest = hashes[offset:]
|
||||
}
|
||||
|
||||
err := flow.outgoingRoute.Enqueue(appmessage.NewMsgRequestIBDBlocks(hashesToRequest))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, expectedHash := range hashesToRequest {
|
||||
message, err := flow.incomingRoute.DequeueWithTimeout(common.DefaultTimeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
msgIBDBlock, ok := message.(*appmessage.MsgIBDBlock)
|
||||
if !ok {
|
||||
return protocolerrors.Errorf(true, "received unexpected message type. "+
|
||||
"expected: %s, got: %s", appmessage.CmdIBDBlock, message.Command())
|
||||
}
|
||||
|
||||
block := appmessage.MsgBlockToDomainBlock(msgIBDBlock.MsgBlock)
|
||||
blockHash := consensushashing.BlockHash(block)
|
||||
if !expectedHash.Equal(blockHash) {
|
||||
return protocolerrors.Errorf(true, "expected block %s but got %s", expectedHash, blockHash)
|
||||
}
|
||||
|
||||
err = flow.banIfBlockIsHeaderOnly(block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
virtualChangeSet, err := flow.Domain().Consensus().ValidateAndInsertBlock(block, false)
|
||||
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, virtualChangeSet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return flow.resolveVirtual()
|
||||
}
|
||||
|
||||
func (flow *handleIBDFlow) banIfBlockIsHeaderOnly(block *externalapi.DomainBlock) error {
|
||||
if len(block.Transactions) == 0 {
|
||||
return protocolerrors.Errorf(true, "sent header of %s block where expected block with body",
|
||||
consensushashing.BlockHash(block))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (flow *handleIBDFlow) resolveVirtual() error {
|
||||
for i := 0; ; i++ {
|
||||
if i%10 == 0 {
|
||||
log.Infof("Resolving virtual. This may take some time...")
|
||||
}
|
||||
virtualChangeSet, isCompletelyResolved, err := flow.Domain().Consensus().ResolveVirtual()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = flow.OnVirtualChange(virtualChangeSet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if isCompletelyResolved {
|
||||
log.Infof("Resolved virtual")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
389
app/protocol/flows/v4/blockrelay/ibd_with_headers_proof.go
Normal file
389
app/protocol/flows/v4/blockrelay/ibd_with_headers_proof.go
Normal file
@@ -0,0 +1,389 @@
|
||||
package blockrelay
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"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/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (flow *handleIBDFlow) ibdWithHeadersProof(highHash *externalapi.DomainHash) error {
|
||||
err := flow.Domain().InitStagingConsensus()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = flow.downloadHeadersAndPruningUTXOSet(highHash)
|
||||
if err != nil {
|
||||
if !flow.IsRecoverableError(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
deleteStagingConsensusErr := flow.Domain().DeleteStagingConsensus()
|
||||
if deleteStagingConsensusErr != nil {
|
||||
return deleteStagingConsensusErr
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
err = flow.Domain().CommitStagingConsensus()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = flow.OnPruningPointUTXOSetOverride()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (flow *handleIBDFlow) shouldSyncAndShouldDownloadHeadersProof(highBlock *externalapi.DomainBlock,
|
||||
highestSharedBlockFound bool) (shouldDownload, shouldSync bool, err error) {
|
||||
|
||||
if !highestSharedBlockFound {
|
||||
hasMoreBlueWorkThanSelectedTipAndPruningDepthMoreBlueScore, err := flow.checkIfHighHashHasMoreBlueWorkThanSelectedTipAndPruningDepthMoreBlueScore(highBlock)
|
||||
if err != nil {
|
||||
return false, false, err
|
||||
}
|
||||
|
||||
if hasMoreBlueWorkThanSelectedTipAndPruningDepthMoreBlueScore {
|
||||
return true, true, nil
|
||||
}
|
||||
|
||||
return false, false, nil
|
||||
}
|
||||
|
||||
return false, true, nil
|
||||
}
|
||||
|
||||
func (flow *handleIBDFlow) checkIfHighHashHasMoreBlueWorkThanSelectedTipAndPruningDepthMoreBlueScore(highBlock *externalapi.DomainBlock) (bool, error) {
|
||||
headersSelectedTip, err := flow.Domain().Consensus().GetHeadersSelectedTip()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
headersSelectedTipInfo, err := flow.Domain().Consensus().GetBlockInfo(headersSelectedTip)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if highBlock.Header.BlueScore() < headersSelectedTipInfo.BlueScore+flow.Config().NetParams().PruningDepth() {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return highBlock.Header.BlueWork().Cmp(headersSelectedTipInfo.BlueWork) > 0, nil
|
||||
}
|
||||
|
||||
func (flow *handleIBDFlow) syncAndValidatePruningPointProof() (*externalapi.DomainHash, error) {
|
||||
log.Infof("Downloading the pruning point proof from %s", flow.peer)
|
||||
err := flow.outgoingRoute.Enqueue(appmessage.NewMsgRequestPruningPointProof())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
message, err := flow.incomingRoute.DequeueWithTimeout(common.DefaultTimeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pruningPointProofMessage, ok := message.(*appmessage.MsgPruningPointProof)
|
||||
if !ok {
|
||||
return nil, protocolerrors.Errorf(true, "received unexpected message type. "+
|
||||
"expected: %s, got: %s", appmessage.CmdPruningPointProof, message.Command())
|
||||
}
|
||||
pruningPointProof := appmessage.MsgPruningPointProofToDomainPruningPointProof(pruningPointProofMessage)
|
||||
err = flow.Domain().Consensus().ValidatePruningPointProof(pruningPointProof)
|
||||
if err != nil {
|
||||
if errors.As(err, &ruleerrors.RuleError{}) {
|
||||
return nil, protocolerrors.Wrapf(true, err, "pruning point proof validation failed")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = flow.Domain().StagingConsensus().ApplyPruningPointProof(pruningPointProof)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return consensushashing.HeaderHash(pruningPointProof.Headers[0][len(pruningPointProof.Headers[0])-1]), nil
|
||||
}
|
||||
|
||||
func (flow *handleIBDFlow) downloadHeadersAndPruningUTXOSet(highHash *externalapi.DomainHash) error {
|
||||
proofPruningPoint, err := flow.syncAndValidatePruningPointProof()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = flow.syncPruningPointsAndPruningPointAnticone(proofPruningPoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: Remove this condition once there's more proper way to check finality violation
|
||||
// in the headers proof.
|
||||
if proofPruningPoint.Equal(flow.Config().NetParams().GenesisHash) {
|
||||
return protocolerrors.Errorf(true, "the genesis pruning point violates finality")
|
||||
}
|
||||
|
||||
err = flow.syncPruningPointFutureHeaders(flow.Domain().StagingConsensus(), proofPruningPoint, highHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Infof("Headers downloaded from peer %s", flow.peer)
|
||||
|
||||
highHashInfo, err := flow.Domain().StagingConsensus().GetBlockInfo(highHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !highHashInfo.Exists {
|
||||
return protocolerrors.Errorf(true, "the triggering IBD block was not sent")
|
||||
}
|
||||
|
||||
err = flow.validatePruningPointFutureHeaderTimestamps()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("Syncing the current pruning point UTXO set")
|
||||
syncedPruningPointUTXOSetSuccessfully, err := flow.syncPruningPointUTXOSet(flow.Domain().StagingConsensus(), proofPruningPoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !syncedPruningPointUTXOSetSuccessfully {
|
||||
log.Debugf("Aborting IBD because the pruning point UTXO set failed to sync")
|
||||
return nil
|
||||
}
|
||||
log.Debugf("Finished syncing the current pruning point UTXO set")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (flow *handleIBDFlow) syncPruningPointsAndPruningPointAnticone(proofPruningPoint *externalapi.DomainHash) error {
|
||||
log.Infof("Downloading the past pruning points and the pruning point anticone from %s", flow.peer)
|
||||
err := flow.outgoingRoute.Enqueue(appmessage.NewMsgRequestPruningPointAndItsAnticone())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = flow.validateAndInsertPruningPoints(proofPruningPoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
message, err := flow.incomingRoute.DequeueWithTimeout(common.DefaultTimeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
msgTrustedData, ok := message.(*appmessage.MsgTrustedData)
|
||||
if !ok {
|
||||
return protocolerrors.Errorf(true, "received unexpected message type. "+
|
||||
"expected: %s, got: %s", appmessage.CmdTrustedData, message.Command())
|
||||
}
|
||||
|
||||
pruningPointWithMetaData, done, err := flow.receiveBlockWithTrustedData()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if done {
|
||||
return protocolerrors.Errorf(true, "got `done` message before receiving the pruning point")
|
||||
}
|
||||
|
||||
if !pruningPointWithMetaData.Block.Header.BlockHash().Equal(proofPruningPoint) {
|
||||
return protocolerrors.Errorf(true, "first block with trusted data is not the pruning point")
|
||||
}
|
||||
|
||||
err = flow.processBlockWithTrustedData(flow.Domain().StagingConsensus(), pruningPointWithMetaData, msgTrustedData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for {
|
||||
blockWithTrustedData, done, err := flow.receiveBlockWithTrustedData()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if done {
|
||||
break
|
||||
}
|
||||
|
||||
err = flow.processBlockWithTrustedData(flow.Domain().StagingConsensus(), blockWithTrustedData, msgTrustedData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
log.Infof("Finished downloading pruning point and its anticone from %s", flow.peer)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (flow *handleIBDFlow) processBlockWithTrustedData(
|
||||
consensus externalapi.Consensus, block *appmessage.MsgBlockWithTrustedDataV4, data *appmessage.MsgTrustedData) error {
|
||||
|
||||
blockWithTrustedData := &externalapi.BlockWithTrustedData{
|
||||
Block: appmessage.MsgBlockToDomainBlock(block.Block),
|
||||
DAAWindow: make([]*externalapi.TrustedDataDataDAAHeader, 0, len(block.DAAWindowIndices)),
|
||||
GHOSTDAGData: make([]*externalapi.BlockGHOSTDAGDataHashPair, 0, len(block.GHOSTDAGDataIndices)),
|
||||
}
|
||||
|
||||
for _, index := range block.DAAWindowIndices {
|
||||
blockWithTrustedData.DAAWindow = append(blockWithTrustedData.DAAWindow, appmessage.TrustedDataDataDAABlockV4ToTrustedDataDataDAAHeader(data.DAAWindow[index]))
|
||||
}
|
||||
|
||||
for _, index := range block.GHOSTDAGDataIndices {
|
||||
blockWithTrustedData.GHOSTDAGData = append(blockWithTrustedData.GHOSTDAGData, appmessage.GHOSTDAGHashPairToDomainGHOSTDAGHashPair(data.GHOSTDAGData[index]))
|
||||
}
|
||||
|
||||
_, err := consensus.ValidateAndInsertBlockWithTrustedData(blockWithTrustedData, false)
|
||||
return err
|
||||
}
|
||||
|
||||
func (flow *handleIBDFlow) receiveBlockWithTrustedData() (*appmessage.MsgBlockWithTrustedDataV4, bool, error) {
|
||||
message, err := flow.incomingRoute.DequeueWithTimeout(common.DefaultTimeout)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
switch downCastedMessage := message.(type) {
|
||||
case *appmessage.MsgBlockWithTrustedDataV4:
|
||||
return downCastedMessage, false, nil
|
||||
case *appmessage.MsgDoneBlocksWithTrustedData:
|
||||
return nil, true, nil
|
||||
default:
|
||||
return nil, false,
|
||||
protocolerrors.Errorf(true, "received unexpected message type. "+
|
||||
"expected: %s or %s, got: %s",
|
||||
(&appmessage.MsgBlockWithTrustedData{}).Command(),
|
||||
(&appmessage.MsgDoneBlocksWithTrustedData{}).Command(),
|
||||
downCastedMessage.Command())
|
||||
}
|
||||
}
|
||||
|
||||
func (flow *handleIBDFlow) receivePruningPoints() (*appmessage.MsgPruningPoints, error) {
|
||||
message, err := flow.incomingRoute.DequeueWithTimeout(common.DefaultTimeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
msgPruningPoints, ok := message.(*appmessage.MsgPruningPoints)
|
||||
if !ok {
|
||||
return nil,
|
||||
protocolerrors.Errorf(true, "received unexpected message type. "+
|
||||
"expected: %s, got: %s", appmessage.CmdPruningPoints, message.Command())
|
||||
}
|
||||
|
||||
return msgPruningPoints, nil
|
||||
}
|
||||
|
||||
func (flow *handleIBDFlow) validateAndInsertPruningPoints(proofPruningPoint *externalapi.DomainHash) error {
|
||||
currentPruningPoint, err := flow.Domain().Consensus().PruningPoint()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if currentPruningPoint.Equal(proofPruningPoint) {
|
||||
return protocolerrors.Errorf(true, "the proposed pruning point is the same as the current pruning point")
|
||||
}
|
||||
|
||||
pruningPoints, err := flow.receivePruningPoints()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
headers := make([]externalapi.BlockHeader, len(pruningPoints.Headers))
|
||||
for i, header := range pruningPoints.Headers {
|
||||
headers[i] = appmessage.BlockHeaderToDomainBlockHeader(header)
|
||||
}
|
||||
|
||||
arePruningPointsViolatingFinality, err := flow.Domain().Consensus().ArePruningPointsViolatingFinality(headers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if arePruningPointsViolatingFinality {
|
||||
// TODO: Find a better way to deal with finality conflicts.
|
||||
return protocolerrors.Errorf(false, "pruning points are violating finality")
|
||||
}
|
||||
|
||||
lastPruningPoint := consensushashing.HeaderHash(headers[len(headers)-1])
|
||||
if !lastPruningPoint.Equal(proofPruningPoint) {
|
||||
return protocolerrors.Errorf(true, "the proof pruning point is not equal to the last pruning "+
|
||||
"point in the list")
|
||||
}
|
||||
|
||||
err = flow.Domain().StagingConsensus().ImportPruningPoints(headers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (flow *handleIBDFlow) syncPruningPointUTXOSet(consensus externalapi.Consensus,
|
||||
pruningPoint *externalapi.DomainHash) (bool, error) {
|
||||
|
||||
log.Infof("Checking if the suggested pruning point %s is compatible to the node DAG", pruningPoint)
|
||||
isValid, err := flow.Domain().StagingConsensus().IsValidPruningPoint(pruningPoint)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if !isValid {
|
||||
return false, protocolerrors.Errorf(true, "invalid pruning point %s", pruningPoint)
|
||||
}
|
||||
|
||||
log.Info("Fetching the pruning point UTXO set")
|
||||
isSuccessful, err := flow.fetchMissingUTXOSet(consensus, pruningPoint)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if !isSuccessful {
|
||||
log.Infof("Couldn't successfully fetch the pruning point UTXO set. Stopping IBD.")
|
||||
return false, nil
|
||||
}
|
||||
|
||||
log.Info("Fetched the new pruning point UTXO set")
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (flow *handleIBDFlow) fetchMissingUTXOSet(consensus externalapi.Consensus, pruningPointHash *externalapi.DomainHash) (succeed bool, err error) {
|
||||
defer func() {
|
||||
err := flow.Domain().StagingConsensus().ClearImportedPruningPointData()
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("failed to clear imported pruning point data: %s", err))
|
||||
}
|
||||
}()
|
||||
|
||||
err = flow.outgoingRoute.Enqueue(appmessage.NewMsgRequestPruningPointUTXOSet(pruningPointHash))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
receivedAll, err := flow.receiveAndInsertPruningPointUTXOSet(consensus, pruningPointHash)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !receivedAll {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
err = flow.Domain().StagingConsensus().ValidateAndInsertImportedPruningPoint(pruningPointHash)
|
||||
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 pruning point UTXO set")
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
9
app/protocol/flows/v4/blockrelay/log.go
Normal file
9
app/protocol/flows/v4/blockrelay/log.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package blockrelay
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/infrastructure/logger"
|
||||
"github.com/kaspanet/kaspad/util/panics"
|
||||
)
|
||||
|
||||
var log = logger.RegisterSubSystem("PROT")
|
||||
var spawn = panics.GoroutineWrapperFunc(log)
|
||||
@@ -0,0 +1,35 @@
|
||||
package blockrelay
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
peerpkg "github.com/kaspanet/kaspad/app/protocol/peer"
|
||||
"github.com/kaspanet/kaspad/domain"
|
||||
"github.com/kaspanet/kaspad/infrastructure/config"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
||||
)
|
||||
|
||||
// SendVirtualSelectedParentInvContext is the interface for the context needed for the SendVirtualSelectedParentInv flow.
|
||||
type SendVirtualSelectedParentInvContext interface {
|
||||
Domain() domain.Domain
|
||||
Config() *config.Config
|
||||
}
|
||||
|
||||
// SendVirtualSelectedParentInv sends a peer the selected parent hash of the virtual
|
||||
func SendVirtualSelectedParentInv(context SendVirtualSelectedParentInvContext,
|
||||
outgoingRoute *router.Route, peer *peerpkg.Peer) error {
|
||||
|
||||
virtualSelectedParent, err := context.Domain().Consensus().GetVirtualSelectedParent()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if virtualSelectedParent.Equal(context.Config().NetParams().GenesisHash) {
|
||||
log.Debugf("Skipping sending the virtual selected parent hash to peer %s because it's the genesis", peer)
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Debugf("Sending virtual selected parent hash %s to peer %s", virtualSelectedParent, peer)
|
||||
|
||||
virtualSelectedParentInv := appmessage.NewMsgInvBlock(virtualSelectedParent)
|
||||
return outgoingRoute.Enqueue(virtualSelectedParentInv)
|
||||
}
|
||||
196
app/protocol/flows/v4/register.go
Normal file
196
app/protocol/flows/v4/register.go
Normal file
@@ -0,0 +1,196 @@
|
||||
package v4
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/app/protocol/common"
|
||||
"github.com/kaspanet/kaspad/app/protocol/flowcontext"
|
||||
"github.com/kaspanet/kaspad/app/protocol/flows/v3/addressexchange"
|
||||
"github.com/kaspanet/kaspad/app/protocol/flows/v3/ping"
|
||||
"github.com/kaspanet/kaspad/app/protocol/flows/v3/rejects"
|
||||
"github.com/kaspanet/kaspad/app/protocol/flows/v3/transactionrelay"
|
||||
"github.com/kaspanet/kaspad/app/protocol/flows/v4/blockrelay"
|
||||
peerpkg "github.com/kaspanet/kaspad/app/protocol/peer"
|
||||
routerpkg "github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
||||
)
|
||||
|
||||
type protocolManager interface {
|
||||
RegisterFlow(name string, router *routerpkg.Router, messageTypes []appmessage.MessageCommand, isStopping *uint32,
|
||||
errChan chan error, initializeFunc common.FlowInitializeFunc) *common.Flow
|
||||
RegisterOneTimeFlow(name string, router *routerpkg.Router, messageTypes []appmessage.MessageCommand,
|
||||
isStopping *uint32, stopChan chan error, initializeFunc common.FlowInitializeFunc) *common.Flow
|
||||
RegisterFlowWithCapacity(name string, capacity int, router *routerpkg.Router,
|
||||
messageTypes []appmessage.MessageCommand, isStopping *uint32,
|
||||
errChan chan error, initializeFunc common.FlowInitializeFunc) *common.Flow
|
||||
Context() *flowcontext.FlowContext
|
||||
}
|
||||
|
||||
// Register is used in order to register all the protocol flows to the given router.
|
||||
func Register(m protocolManager, router *routerpkg.Router, errChan chan error, isStopping *uint32) (flows []*common.Flow) {
|
||||
flows = registerAddressFlows(m, router, isStopping, errChan)
|
||||
flows = append(flows, registerBlockRelayFlows(m, router, isStopping, errChan)...)
|
||||
flows = append(flows, registerPingFlows(m, router, isStopping, errChan)...)
|
||||
flows = append(flows, registerTransactionRelayFlow(m, router, isStopping, errChan)...)
|
||||
flows = append(flows, registerRejectsFlow(m, router, isStopping, errChan)...)
|
||||
|
||||
return flows
|
||||
}
|
||||
|
||||
func registerAddressFlows(m protocolManager, router *routerpkg.Router, isStopping *uint32, errChan chan error) []*common.Flow {
|
||||
outgoingRoute := router.OutgoingRoute()
|
||||
|
||||
return []*common.Flow{
|
||||
// TODO: This code was moved to the upper level to prevent a race condition when connecting to v3 peers. This should be uncommented
|
||||
// and removed from the upper level once v3 is obsolete.
|
||||
//m.RegisterFlow("SendAddresses", router, []appmessage.MessageCommand{appmessage.CmdRequestAddresses}, isStopping, errChan,
|
||||
// func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
// return addressexchange.SendAddresses(m.Context(), incomingRoute, outgoingRoute)
|
||||
// },
|
||||
//),
|
||||
|
||||
m.RegisterOneTimeFlow("ReceiveAddresses", router, []appmessage.MessageCommand{appmessage.CmdAddresses}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return addressexchange.ReceiveAddresses(m.Context(), incomingRoute, outgoingRoute, peer)
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func registerBlockRelayFlows(m protocolManager, router *routerpkg.Router, isStopping *uint32, errChan chan error) []*common.Flow {
|
||||
outgoingRoute := router.OutgoingRoute()
|
||||
|
||||
return []*common.Flow{
|
||||
m.RegisterOneTimeFlow("SendVirtualSelectedParentInv", router, []appmessage.MessageCommand{},
|
||||
isStopping, errChan, func(route *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return blockrelay.SendVirtualSelectedParentInv(m.Context(), outgoingRoute, peer)
|
||||
}),
|
||||
|
||||
m.RegisterFlow("HandleRelayInvs", router, []appmessage.MessageCommand{
|
||||
appmessage.CmdInvRelayBlock, appmessage.CmdBlock, appmessage.CmdBlockLocator,
|
||||
},
|
||||
isStopping, errChan, func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return blockrelay.HandleRelayInvs(m.Context(), incomingRoute,
|
||||
outgoingRoute, peer)
|
||||
},
|
||||
),
|
||||
|
||||
m.RegisterFlow("HandleIBD", router, []appmessage.MessageCommand{
|
||||
appmessage.CmdDoneHeaders, appmessage.CmdUnexpectedPruningPoint, appmessage.CmdPruningPointUTXOSetChunk,
|
||||
appmessage.CmdBlockHeaders, appmessage.CmdIBDBlockLocatorHighestHash, appmessage.CmdBlockWithTrustedDataV4,
|
||||
appmessage.CmdDoneBlocksWithTrustedData, appmessage.CmdIBDBlockLocatorHighestHashNotFound,
|
||||
appmessage.CmdDonePruningPointUTXOSetChunks, appmessage.CmdIBDBlock, appmessage.CmdPruningPoints,
|
||||
appmessage.CmdPruningPointProof,
|
||||
appmessage.CmdTrustedData,
|
||||
},
|
||||
isStopping, errChan, func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return blockrelay.HandleIBD(m.Context(), incomingRoute,
|
||||
outgoingRoute, peer)
|
||||
},
|
||||
),
|
||||
|
||||
m.RegisterFlow("HandleRelayBlockRequests", router, []appmessage.MessageCommand{appmessage.CmdRequestRelayBlocks}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return blockrelay.HandleRelayBlockRequests(m.Context(), incomingRoute, outgoingRoute, peer)
|
||||
},
|
||||
),
|
||||
|
||||
m.RegisterFlow("HandleRequestBlockLocator", router,
|
||||
[]appmessage.MessageCommand{appmessage.CmdRequestBlockLocator}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return blockrelay.HandleRequestBlockLocator(m.Context(), incomingRoute, outgoingRoute)
|
||||
},
|
||||
),
|
||||
|
||||
m.RegisterFlow("HandleRequestHeaders", router,
|
||||
[]appmessage.MessageCommand{appmessage.CmdRequestHeaders, appmessage.CmdRequestNextHeaders}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return blockrelay.HandleRequestHeaders(m.Context(), incomingRoute, outgoingRoute, peer)
|
||||
},
|
||||
),
|
||||
|
||||
m.RegisterFlow("HandleIBDBlockRequests", router,
|
||||
[]appmessage.MessageCommand{appmessage.CmdRequestIBDBlocks}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return blockrelay.HandleIBDBlockRequests(m.Context(), incomingRoute, outgoingRoute)
|
||||
},
|
||||
),
|
||||
|
||||
m.RegisterFlow("HandleRequestPruningPointUTXOSet", router,
|
||||
[]appmessage.MessageCommand{appmessage.CmdRequestPruningPointUTXOSet,
|
||||
appmessage.CmdRequestNextPruningPointUTXOSetChunk}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return blockrelay.HandleRequestPruningPointUTXOSet(m.Context(), incomingRoute, outgoingRoute)
|
||||
},
|
||||
),
|
||||
|
||||
m.RegisterFlow("HandlePruningPointAndItsAnticoneRequests", router,
|
||||
[]appmessage.MessageCommand{appmessage.CmdRequestPruningPointAndItsAnticone}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return blockrelay.HandlePruningPointAndItsAnticoneRequests(m.Context(), incomingRoute, outgoingRoute, peer)
|
||||
},
|
||||
),
|
||||
|
||||
m.RegisterFlow("HandleIBDBlockLocator", router,
|
||||
[]appmessage.MessageCommand{appmessage.CmdIBDBlockLocator}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return blockrelay.HandleIBDBlockLocator(m.Context(), incomingRoute, outgoingRoute, peer)
|
||||
},
|
||||
),
|
||||
|
||||
m.RegisterFlow("HandlePruningPointProofRequests", router,
|
||||
[]appmessage.MessageCommand{appmessage.CmdRequestPruningPointProof}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return blockrelay.HandlePruningPointProofRequests(m.Context(), incomingRoute, outgoingRoute, peer)
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func registerPingFlows(m protocolManager, router *routerpkg.Router, isStopping *uint32, errChan chan error) []*common.Flow {
|
||||
outgoingRoute := router.OutgoingRoute()
|
||||
|
||||
return []*common.Flow{
|
||||
m.RegisterFlow("ReceivePings", router, []appmessage.MessageCommand{appmessage.CmdPing}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return ping.ReceivePings(m.Context(), incomingRoute, outgoingRoute)
|
||||
},
|
||||
),
|
||||
|
||||
m.RegisterFlow("SendPings", router, []appmessage.MessageCommand{appmessage.CmdPong}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return ping.SendPings(m.Context(), incomingRoute, outgoingRoute, peer)
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func registerTransactionRelayFlow(m protocolManager, router *routerpkg.Router, isStopping *uint32, errChan chan error) []*common.Flow {
|
||||
outgoingRoute := router.OutgoingRoute()
|
||||
|
||||
return []*common.Flow{
|
||||
m.RegisterFlowWithCapacity("HandleRelayedTransactions", 10_000, router,
|
||||
[]appmessage.MessageCommand{appmessage.CmdInvTransaction, appmessage.CmdTx, appmessage.CmdTransactionNotFound}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return transactionrelay.HandleRelayedTransactions(m.Context(), incomingRoute, outgoingRoute)
|
||||
},
|
||||
),
|
||||
m.RegisterFlow("HandleRequestTransactions", router,
|
||||
[]appmessage.MessageCommand{appmessage.CmdRequestTransactions}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return transactionrelay.HandleRequestedTransactions(m.Context(), incomingRoute, outgoingRoute)
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func registerRejectsFlow(m protocolManager, router *routerpkg.Router, isStopping *uint32, errChan chan error) []*common.Flow {
|
||||
outgoingRoute := router.OutgoingRoute()
|
||||
|
||||
return []*common.Flow{
|
||||
m.RegisterFlow("HandleRejects", router,
|
||||
[]appmessage.MessageCommand{appmessage.CmdReject}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return rejects.HandleRejects(m.Context(), incomingRoute, outgoingRoute)
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package protocol
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/app/protocol/common"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
@@ -71,11 +72,16 @@ func (m *Manager) AddBlock(block *externalapi.DomainBlock) error {
|
||||
return m.context.AddBlock(block)
|
||||
}
|
||||
|
||||
func (m *Manager) runFlows(flows []*flow, peer *peerpkg.Peer, errChan <-chan error, flowsWaitGroup *sync.WaitGroup) error {
|
||||
// Context returns the manager's flow context
|
||||
func (m *Manager) Context() *flowcontext.FlowContext {
|
||||
return m.context
|
||||
}
|
||||
|
||||
func (m *Manager) runFlows(flows []*common.Flow, peer *peerpkg.Peer, errChan <-chan error, flowsWaitGroup *sync.WaitGroup) error {
|
||||
flowsWaitGroup.Add(len(flows))
|
||||
for _, flow := range flows {
|
||||
executeFunc := flow.executeFunc // extract to new variable so that it's not overwritten
|
||||
spawn(fmt.Sprintf("flow-%s", flow.name), func() {
|
||||
executeFunc := flow.ExecuteFunc // extract to new variable so that it's not overwritten
|
||||
spawn(fmt.Sprintf("flow-%s", flow.Name), func() {
|
||||
executeFunc(peer)
|
||||
flowsWaitGroup.Done()
|
||||
})
|
||||
|
||||
@@ -13,10 +13,6 @@ import (
|
||||
"github.com/kaspanet/kaspad/util/mstime"
|
||||
)
|
||||
|
||||
// maxProtocolVersion version is the maximum supported protocol
|
||||
// version this kaspad node supports
|
||||
const maxProtocolVersion = 3
|
||||
|
||||
// Peer holds data about a peer.
|
||||
type Peer struct {
|
||||
connection *netadapter.NetConnection
|
||||
@@ -35,6 +31,8 @@ type Peer struct {
|
||||
lastPingNonce uint64 // The nonce of the last ping we sent
|
||||
lastPingTime time.Time // Time we sent last ping
|
||||
lastPingDuration time.Duration // Time for last ping to return
|
||||
|
||||
ibdRequestChannel chan *externalapi.DomainBlock // A channel used to communicate IBD requests between flows
|
||||
}
|
||||
|
||||
// New returns a new Peer
|
||||
@@ -42,6 +40,7 @@ func New(connection *netadapter.NetConnection) *Peer {
|
||||
return &Peer{
|
||||
connection: connection,
|
||||
connectionStarted: time.Now(),
|
||||
ibdRequestChannel: make(chan *externalapi.DomainBlock),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,6 +75,11 @@ func (p *Peer) AdvertisedProtocolVersion() uint32 {
|
||||
return p.advertisedProtocolVerion
|
||||
}
|
||||
|
||||
// ProtocolVersion returns the protocol version which is used when communicating with the peer.
|
||||
func (p *Peer) ProtocolVersion() uint32 {
|
||||
return p.protocolVersion
|
||||
}
|
||||
|
||||
// TimeConnected returns the time since the connection to this been has been started.
|
||||
func (p *Peer) TimeConnected() time.Duration {
|
||||
return time.Since(p.connectionStarted)
|
||||
@@ -87,7 +91,7 @@ func (p *Peer) IsOutbound() bool {
|
||||
}
|
||||
|
||||
// UpdateFieldsFromMsgVersion updates the peer with the data from the version message.
|
||||
func (p *Peer) UpdateFieldsFromMsgVersion(msg *appmessage.MsgVersion) {
|
||||
func (p *Peer) UpdateFieldsFromMsgVersion(msg *appmessage.MsgVersion, maxProtocolVersion uint32) {
|
||||
// Negotiate the protocol version.
|
||||
p.advertisedProtocolVerion = msg.ProtocolVersion
|
||||
p.protocolVersion = mathUtil.MinUint32(maxProtocolVersion, p.advertisedProtocolVerion)
|
||||
@@ -142,3 +146,8 @@ func (p *Peer) LastPingDuration() time.Duration {
|
||||
|
||||
return p.lastPingDuration
|
||||
}
|
||||
|
||||
// IBDRequestChannel returns the channel used in order to communicate an IBD request between peer flows
|
||||
func (p *Peer) IBDRequestChannel() chan *externalapi.DomainBlock {
|
||||
return p.ibdRequestChannel
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
package protocol
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/app/protocol/common"
|
||||
"github.com/kaspanet/kaspad/app/protocol/flows/ready"
|
||||
v3 "github.com/kaspanet/kaspad/app/protocol/flows/v3"
|
||||
"github.com/kaspanet/kaspad/app/protocol/flows/v3/addressexchange"
|
||||
v4 "github.com/kaspanet/kaspad/app/protocol/flows/v4"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/app/protocol/flows/addressexchange"
|
||||
"github.com/kaspanet/kaspad/app/protocol/flows/blockrelay"
|
||||
"github.com/kaspanet/kaspad/app/protocol/flows/handshake"
|
||||
"github.com/kaspanet/kaspad/app/protocol/flows/ping"
|
||||
"github.com/kaspanet/kaspad/app/protocol/flows/rejects"
|
||||
"github.com/kaspanet/kaspad/app/protocol/flows/transactionrelay"
|
||||
peerpkg "github.com/kaspanet/kaspad/app/protocol/peer"
|
||||
"github.com/kaspanet/kaspad/app/protocol/protocolerrors"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/addressmanager"
|
||||
@@ -20,14 +20,6 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type flowInitializeFunc func(route *routerpkg.Route, peer *peerpkg.Peer) error
|
||||
type flowExecuteFunc func(peer *peerpkg.Peer)
|
||||
|
||||
type flow struct {
|
||||
name string
|
||||
executeFunc flowExecuteFunc
|
||||
}
|
||||
|
||||
func (m *Manager) routerInitializer(router *routerpkg.Router, netConnection *netadapter.NetConnection) {
|
||||
// isStopping flag is raised the moment that the connection associated with this router is disconnected
|
||||
// errChan is used by the flow goroutines to return to runFlows when an error occurs.
|
||||
@@ -35,8 +27,7 @@ func (m *Manager) routerInitializer(router *routerpkg.Router, netConnection *net
|
||||
isStopping := uint32(0)
|
||||
errChan := make(chan error)
|
||||
|
||||
flows := m.registerFlows(router, errChan, &isStopping)
|
||||
receiveVersionRoute, sendVersionRoute := registerHandshakeRoutes(router)
|
||||
receiveVersionRoute, sendVersionRoute, receiveReadyRoute := registerHandshakeRoutes(router)
|
||||
|
||||
// After flows were registered - spawn a new thread that will wait for connection to finish initializing
|
||||
// and start receiving messages
|
||||
@@ -64,6 +55,14 @@ func (m *Manager) routerInitializer(router *routerpkg.Router, netConnection *net
|
||||
}
|
||||
})
|
||||
|
||||
// TODO: This code was moved here to prevent a race condition when connecting to v3 peers. This should be moved to v4.registerAddressFlows
|
||||
// once v3 is obsolete.
|
||||
sendAddressesFlow := m.RegisterFlow("SendAddresses", router, []appmessage.MessageCommand{appmessage.CmdRequestAddresses}, &isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return addressexchange.SendAddresses(m.Context(), incomingRoute, router.OutgoingRoute())
|
||||
},
|
||||
)
|
||||
|
||||
peer, err := handshake.HandleHandshake(m.context, netConnection, receiveVersionRoute,
|
||||
sendVersionRoute, router.OutgoingRoute())
|
||||
|
||||
@@ -84,6 +83,26 @@ func (m *Manager) routerInitializer(router *routerpkg.Router, netConnection *net
|
||||
}
|
||||
defer m.context.RemoveFromPeers(peer)
|
||||
|
||||
var flows []*common.Flow
|
||||
log.Infof("Registering p2p flows for peer %s for protocol version %d", peer, peer.ProtocolVersion())
|
||||
switch peer.ProtocolVersion() {
|
||||
case 3:
|
||||
flows = v3.Register(m, router, errChan, &isStopping)
|
||||
case 4:
|
||||
flows = v4.Register(m, router, errChan, &isStopping)
|
||||
default:
|
||||
panic(errors.Errorf("no way to handle protocol version %d", peer.ProtocolVersion()))
|
||||
}
|
||||
flows = append(flows, sendAddressesFlow)
|
||||
|
||||
if peer.ProtocolVersion() > 3 {
|
||||
err = ready.HandleReady(receiveReadyRoute, router.OutgoingRoute(), peer)
|
||||
if err != nil {
|
||||
m.handleError(err, netConnection, router.OutgoingRoute())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
removeHandshakeRoutes(router)
|
||||
|
||||
flowsWaitGroup := &sync.WaitGroup{}
|
||||
@@ -130,167 +149,9 @@ func (m *Manager) handleError(err error, netConnection *netadapter.NetConnection
|
||||
panic(err)
|
||||
}
|
||||
|
||||
func (m *Manager) registerFlows(router *routerpkg.Router, errChan chan error, isStopping *uint32) (flows []*flow) {
|
||||
flows = m.registerAddressFlows(router, isStopping, errChan)
|
||||
flows = append(flows, m.registerBlockRelayFlows(router, isStopping, errChan)...)
|
||||
flows = append(flows, m.registerPingFlows(router, isStopping, errChan)...)
|
||||
flows = append(flows, m.registerTransactionRelayFlow(router, isStopping, errChan)...)
|
||||
flows = append(flows, m.registerRejectsFlow(router, isStopping, errChan)...)
|
||||
|
||||
return flows
|
||||
}
|
||||
|
||||
func (m *Manager) registerAddressFlows(router *routerpkg.Router, isStopping *uint32, errChan chan error) []*flow {
|
||||
outgoingRoute := router.OutgoingRoute()
|
||||
|
||||
return []*flow{
|
||||
m.registerFlow("SendAddresses", router, []appmessage.MessageCommand{appmessage.CmdRequestAddresses}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return addressexchange.SendAddresses(m.context, incomingRoute, outgoingRoute)
|
||||
},
|
||||
),
|
||||
|
||||
m.registerOneTimeFlow("ReceiveAddresses", router, []appmessage.MessageCommand{appmessage.CmdAddresses}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return addressexchange.ReceiveAddresses(m.context, incomingRoute, outgoingRoute, peer)
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) registerBlockRelayFlows(router *routerpkg.Router, isStopping *uint32, errChan chan error) []*flow {
|
||||
outgoingRoute := router.OutgoingRoute()
|
||||
|
||||
return []*flow{
|
||||
m.registerOneTimeFlow("SendVirtualSelectedParentInv", router, []appmessage.MessageCommand{},
|
||||
isStopping, errChan, func(route *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return blockrelay.SendVirtualSelectedParentInv(m.context, outgoingRoute, peer)
|
||||
}),
|
||||
|
||||
m.registerFlow("HandleRelayInvs", router, []appmessage.MessageCommand{
|
||||
appmessage.CmdInvRelayBlock, appmessage.CmdBlock, appmessage.CmdBlockLocator,
|
||||
appmessage.CmdDoneHeaders, appmessage.CmdUnexpectedPruningPoint, appmessage.CmdPruningPointUTXOSetChunk,
|
||||
appmessage.CmdBlockHeaders, appmessage.CmdIBDBlockLocatorHighestHash, appmessage.CmdBlockWithTrustedData,
|
||||
appmessage.CmdDoneBlocksWithTrustedData, appmessage.CmdIBDBlockLocatorHighestHashNotFound,
|
||||
appmessage.CmdDonePruningPointUTXOSetChunks, appmessage.CmdIBDBlock, appmessage.CmdPruningPoints,
|
||||
appmessage.CmdPruningPointProof,
|
||||
},
|
||||
isStopping, errChan, func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return blockrelay.HandleRelayInvs(m.context, incomingRoute,
|
||||
outgoingRoute, peer)
|
||||
},
|
||||
),
|
||||
|
||||
m.registerFlow("HandleRelayBlockRequests", router, []appmessage.MessageCommand{appmessage.CmdRequestRelayBlocks}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return blockrelay.HandleRelayBlockRequests(m.context, incomingRoute, outgoingRoute, peer)
|
||||
},
|
||||
),
|
||||
|
||||
m.registerFlow("HandleRequestBlockLocator", router,
|
||||
[]appmessage.MessageCommand{appmessage.CmdRequestBlockLocator}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return blockrelay.HandleRequestBlockLocator(m.context, incomingRoute, outgoingRoute)
|
||||
},
|
||||
),
|
||||
|
||||
m.registerFlow("HandleRequestHeaders", router,
|
||||
[]appmessage.MessageCommand{appmessage.CmdRequestHeaders, appmessage.CmdRequestNextHeaders}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return blockrelay.HandleRequestHeaders(m.context, incomingRoute, outgoingRoute, peer)
|
||||
},
|
||||
),
|
||||
|
||||
m.registerFlow("HandleIBDBlockRequests", router,
|
||||
[]appmessage.MessageCommand{appmessage.CmdRequestIBDBlocks}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return blockrelay.HandleIBDBlockRequests(m.context, incomingRoute, outgoingRoute)
|
||||
},
|
||||
),
|
||||
|
||||
m.registerFlow("HandleRequestPruningPointUTXOSet", router,
|
||||
[]appmessage.MessageCommand{appmessage.CmdRequestPruningPointUTXOSet,
|
||||
appmessage.CmdRequestNextPruningPointUTXOSetChunk}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return blockrelay.HandleRequestPruningPointUTXOSet(m.context, incomingRoute, outgoingRoute)
|
||||
},
|
||||
),
|
||||
|
||||
m.registerFlow("HandlePruningPointAndItsAnticoneRequests", router,
|
||||
[]appmessage.MessageCommand{appmessage.CmdRequestPruningPointAndItsAnticone}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return blockrelay.HandlePruningPointAndItsAnticoneRequests(m.context, incomingRoute, outgoingRoute, peer)
|
||||
},
|
||||
),
|
||||
|
||||
m.registerFlow("HandleIBDBlockLocator", router,
|
||||
[]appmessage.MessageCommand{appmessage.CmdIBDBlockLocator}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return blockrelay.HandleIBDBlockLocator(m.context, incomingRoute, outgoingRoute, peer)
|
||||
},
|
||||
),
|
||||
|
||||
m.registerFlow("HandlePruningPointProofRequests", router,
|
||||
[]appmessage.MessageCommand{appmessage.CmdRequestPruningPointProof}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return blockrelay.HandlePruningPointProofRequests(m.context, incomingRoute, outgoingRoute, peer)
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) registerPingFlows(router *routerpkg.Router, isStopping *uint32, errChan chan error) []*flow {
|
||||
outgoingRoute := router.OutgoingRoute()
|
||||
|
||||
return []*flow{
|
||||
m.registerFlow("ReceivePings", router, []appmessage.MessageCommand{appmessage.CmdPing}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return ping.ReceivePings(m.context, incomingRoute, outgoingRoute)
|
||||
},
|
||||
),
|
||||
|
||||
m.registerFlow("SendPings", router, []appmessage.MessageCommand{appmessage.CmdPong}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return ping.SendPings(m.context, incomingRoute, outgoingRoute, peer)
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) registerTransactionRelayFlow(router *routerpkg.Router, isStopping *uint32, errChan chan error) []*flow {
|
||||
outgoingRoute := router.OutgoingRoute()
|
||||
|
||||
return []*flow{
|
||||
m.registerFlowWithCapacity("HandleRelayedTransactions", 10_000, router,
|
||||
[]appmessage.MessageCommand{appmessage.CmdInvTransaction, appmessage.CmdTx, appmessage.CmdTransactionNotFound}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return transactionrelay.HandleRelayedTransactions(m.context, incomingRoute, outgoingRoute)
|
||||
},
|
||||
),
|
||||
m.registerFlow("HandleRequestTransactions", router,
|
||||
[]appmessage.MessageCommand{appmessage.CmdRequestTransactions}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return transactionrelay.HandleRequestedTransactions(m.context, incomingRoute, outgoingRoute)
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) registerRejectsFlow(router *routerpkg.Router, isStopping *uint32, errChan chan error) []*flow {
|
||||
outgoingRoute := router.OutgoingRoute()
|
||||
|
||||
return []*flow{
|
||||
m.registerFlow("HandleRejects", router,
|
||||
[]appmessage.MessageCommand{appmessage.CmdReject}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return rejects.HandleRejects(m.context, incomingRoute, outgoingRoute)
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) registerFlow(name string, router *routerpkg.Router, messageTypes []appmessage.MessageCommand, isStopping *uint32,
|
||||
errChan chan error, initializeFunc flowInitializeFunc) *flow {
|
||||
// RegisterFlow registers a flow to the given router.
|
||||
func (m *Manager) RegisterFlow(name string, router *routerpkg.Router, messageTypes []appmessage.MessageCommand, isStopping *uint32,
|
||||
errChan chan error, initializeFunc common.FlowInitializeFunc) *common.Flow {
|
||||
|
||||
route, err := router.AddIncomingRoute(name, messageTypes)
|
||||
if err != nil {
|
||||
@@ -300,9 +161,10 @@ func (m *Manager) registerFlow(name string, router *routerpkg.Router, messageTyp
|
||||
return m.registerFlowForRoute(route, name, isStopping, errChan, initializeFunc)
|
||||
}
|
||||
|
||||
func (m *Manager) registerFlowWithCapacity(name string, capacity int, router *routerpkg.Router,
|
||||
// RegisterFlowWithCapacity registers a flow to the given router with a custom capacity.
|
||||
func (m *Manager) RegisterFlowWithCapacity(name string, capacity int, router *routerpkg.Router,
|
||||
messageTypes []appmessage.MessageCommand, isStopping *uint32,
|
||||
errChan chan error, initializeFunc flowInitializeFunc) *flow {
|
||||
errChan chan error, initializeFunc common.FlowInitializeFunc) *common.Flow {
|
||||
|
||||
route, err := router.AddIncomingRouteWithCapacity(name, capacity, messageTypes)
|
||||
if err != nil {
|
||||
@@ -313,11 +175,11 @@ func (m *Manager) registerFlowWithCapacity(name string, capacity int, router *ro
|
||||
}
|
||||
|
||||
func (m *Manager) registerFlowForRoute(route *routerpkg.Route, name string, isStopping *uint32,
|
||||
errChan chan error, initializeFunc flowInitializeFunc) *flow {
|
||||
errChan chan error, initializeFunc common.FlowInitializeFunc) *common.Flow {
|
||||
|
||||
return &flow{
|
||||
name: name,
|
||||
executeFunc: func(peer *peerpkg.Peer) {
|
||||
return &common.Flow{
|
||||
Name: name,
|
||||
ExecuteFunc: func(peer *peerpkg.Peer) {
|
||||
err := initializeFunc(route, peer)
|
||||
if err != nil {
|
||||
m.context.HandleError(err, name, isStopping, errChan)
|
||||
@@ -327,17 +189,18 @@ func (m *Manager) registerFlowForRoute(route *routerpkg.Route, name string, isSt
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) registerOneTimeFlow(name string, router *routerpkg.Router, messageTypes []appmessage.MessageCommand,
|
||||
isStopping *uint32, stopChan chan error, initializeFunc flowInitializeFunc) *flow {
|
||||
// RegisterOneTimeFlow registers a one-time flow (that exits once some operations are done) to the given router.
|
||||
func (m *Manager) RegisterOneTimeFlow(name string, router *routerpkg.Router, messageTypes []appmessage.MessageCommand,
|
||||
isStopping *uint32, stopChan chan error, initializeFunc common.FlowInitializeFunc) *common.Flow {
|
||||
|
||||
route, err := router.AddIncomingRoute(name, messageTypes)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return &flow{
|
||||
name: name,
|
||||
executeFunc: func(peer *peerpkg.Peer) {
|
||||
return &common.Flow{
|
||||
Name: name,
|
||||
ExecuteFunc: func(peer *peerpkg.Peer) {
|
||||
defer func() {
|
||||
err := router.RemoveRoute(messageTypes)
|
||||
if err != nil {
|
||||
@@ -355,7 +218,7 @@ func (m *Manager) registerOneTimeFlow(name string, router *routerpkg.Router, mes
|
||||
}
|
||||
|
||||
func registerHandshakeRoutes(router *routerpkg.Router) (
|
||||
receiveVersionRoute *routerpkg.Route, sendVersionRoute *routerpkg.Route) {
|
||||
receiveVersionRoute, sendVersionRoute, receiveReadyRoute *routerpkg.Route) {
|
||||
receiveVersionRoute, err := router.AddIncomingRoute("recieveVersion - incoming", []appmessage.MessageCommand{appmessage.CmdVersion})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -366,11 +229,16 @@ func registerHandshakeRoutes(router *routerpkg.Router) (
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return receiveVersionRoute, sendVersionRoute
|
||||
receiveReadyRoute, err = router.AddIncomingRoute("recieveReady - incoming", []appmessage.MessageCommand{appmessage.CmdReady})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return receiveVersionRoute, sendVersionRoute, receiveReadyRoute
|
||||
}
|
||||
|
||||
func removeHandshakeRoutes(router *routerpkg.Router) {
|
||||
err := router.RemoveRoute([]appmessage.MessageCommand{appmessage.CmdVersion, appmessage.CmdVerAck})
|
||||
err := router.RemoveRoute([]appmessage.MessageCommand{appmessage.CmdVersion, appmessage.CmdVerAck, appmessage.CmdReady})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
@@ -34,6 +34,23 @@ func HandleSubmitBlock(context *rpccontext.Context, _ *router.Router, request ap
|
||||
}, nil
|
||||
}
|
||||
|
||||
if !submitBlockRequest.AllowNonDAABlocks {
|
||||
virtualDAAScore, err := context.Domain.Consensus().GetVirtualDAAScore()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// A simple heuristic check which signals that the mined block is out of date
|
||||
// and should not be accepted unless user explicitly requests
|
||||
daaWindowSize := uint64(context.Config.NetParams().DifficultyAdjustmentWindowSize)
|
||||
if virtualDAAScore > daaWindowSize && domainBlock.Header.DAAScore() < virtualDAAScore-daaWindowSize {
|
||||
return &appmessage.SubmitBlockResponseMessage{
|
||||
Error: appmessage.RPCErrorf("Block rejected. Reason: block DAA score %d is too far "+
|
||||
"behind virtual's DAA score %d", domainBlock.Header.DAAScore(), virtualDAAScore),
|
||||
RejectReason: appmessage.RejectReasonBlockInvalid,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
err = context.ProtocolManager.AddBlock(domainBlock)
|
||||
if err != nil {
|
||||
isProtocolOrRuleError := errors.As(err, &ruleerrors.RuleError{}) || errors.As(err, &protocolerrors.ProtocolError{})
|
||||
|
||||
@@ -1,3 +1,28 @@
|
||||
Kaspad v0.11.10 - 2022-01-27
|
||||
===========================
|
||||
* Add monitoring of heap and save heap profile if size is over some limit (#1932)
|
||||
* Extract IBD management from invs relay flow to a new separated flow (#1930)
|
||||
* Add --transaction-file options to the `sign` and `broadcast` wallet subcommands (#1927)
|
||||
* Filter redundant blocks from daa window on IBD (#1925)
|
||||
* Implement a P2P upgrade mechanism (#1921)
|
||||
|
||||
Kaspad v0.11.9 - 2021-12-30
|
||||
===========================
|
||||
Breaking changes:
|
||||
* Implement the new monetary policy. Breaking change effective only in ~4 months (#1892)
|
||||
|
||||
Bug fixes:
|
||||
* Fix two pruning proof IBD crash bugs (#1913)
|
||||
* Fix UTXO index bug showing wrong wallet balance (#1891)
|
||||
|
||||
Non-breaking changes:
|
||||
* Address search: cleanup repetitively-offline addresses and use randomization weighted by connection failures (#1899, #1916)
|
||||
* New DNS seeders and removal of offline one (#1901, #1910, #1918)
|
||||
* Add request balance by address to kaspactl (#1885)
|
||||
* Wallet: show balance by addresses (#1904)
|
||||
* Reject outdated non-DAA blocks submitted via RPC (#1914)
|
||||
* Add a profile option to kaspawallet daemon (#1854)
|
||||
|
||||
Kaspad v0.11.8 - 2021-12-13
|
||||
===========================
|
||||
Bug fixes:
|
||||
|
||||
@@ -34,7 +34,7 @@ var commandTypes = []reflect.Type{
|
||||
reflect.TypeOf(protowire.KaspadMessage_SubmitTransactionRequest{}),
|
||||
|
||||
reflect.TypeOf(protowire.KaspadMessage_GetUtxosByAddressesRequest{}),
|
||||
reflect.TypeOf(protowire.KaspadMessage_GetBalanceByAddressRequestMessage{}),
|
||||
reflect.TypeOf(protowire.KaspadMessage_GetBalanceByAddressRequest{}),
|
||||
|
||||
reflect.TypeOf(protowire.KaspadMessage_BanRequest{}),
|
||||
reflect.TypeOf(protowire.KaspadMessage_UnbanRequest{}),
|
||||
|
||||
@@ -6,6 +6,9 @@ import (
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/client"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
|
||||
"github.com/pkg/errors"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func broadcast(conf *broadcastConfig) error {
|
||||
@@ -18,7 +21,23 @@ func broadcast(conf *broadcastConfig) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), daemonTimeout)
|
||||
defer cancel()
|
||||
|
||||
transaction, err := hex.DecodeString(conf.Transaction)
|
||||
if conf.Transaction == "" && conf.TransactionFile == "" {
|
||||
return errors.Errorf("Either --transaction or --transaction-file is required")
|
||||
}
|
||||
if conf.Transaction != "" && conf.TransactionFile != "" {
|
||||
return errors.Errorf("Both --transaction and --transaction-file cannot be passed at the same time")
|
||||
}
|
||||
|
||||
transactionHex := conf.Transaction
|
||||
if conf.TransactionFile != "" {
|
||||
transactionHexBytes, err := ioutil.ReadFile(conf.TransactionFile)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Could not read hex from %s", conf.TransactionFile)
|
||||
}
|
||||
transactionHex = strings.TrimSpace(string(transactionHexBytes))
|
||||
}
|
||||
|
||||
transaction, err := hex.DecodeString(transactionHex)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -65,15 +65,17 @@ type createUnsignedTransactionConfig struct {
|
||||
}
|
||||
|
||||
type signConfig struct {
|
||||
KeysFile string `long:"keys-file" short:"f" description:"Keys file location (default: ~/.kaspawallet/keys.json (*nix), %USERPROFILE%\\AppData\\Local\\Kaspawallet\\key.json (Windows))"`
|
||||
Password string `long:"password" short:"p" description:"Wallet password"`
|
||||
Transaction string `long:"transaction" short:"t" description:"The unsigned transaction to sign on (encoded in hex)" required:"true"`
|
||||
KeysFile string `long:"keys-file" short:"f" description:"Keys file location (default: ~/.kaspawallet/keys.json (*nix), %USERPROFILE%\\AppData\\Local\\Kaspawallet\\key.json (Windows))"`
|
||||
Password string `long:"password" short:"p" description:"Wallet password"`
|
||||
Transaction string `long:"transaction" short:"t" description:"The unsigned transaction to sign on (encoded in hex)"`
|
||||
TransactionFile string `long:"transaction-file" short:"F" description:"The file containing the unsigned transaction to sign on (encoded in hex)"`
|
||||
config.NetworkFlags
|
||||
}
|
||||
|
||||
type broadcastConfig struct {
|
||||
DaemonAddress string `long:"daemonaddress" short:"d" description:"Wallet daemon server to connect to (default: localhost:8082)"`
|
||||
Transaction string `long:"transaction" short:"t" description:"The signed transaction to broadcast (encoded in hex)" required:"true"`
|
||||
DaemonAddress string `long:"daemonaddress" short:"d" description:"Wallet daemon server to connect to (default: localhost:8082)"`
|
||||
Transaction string `long:"transaction" short:"t" description:"The signed transaction to broadcast (encoded in hex)"`
|
||||
TransactionFile string `long:"transaction-file" short:"F" description:"The file containing the unsigned transaction to sign on (encoded in hex)"`
|
||||
config.NetworkFlags
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,9 @@ import (
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/keys"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
|
||||
"github.com/pkg/errors"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func sign(conf *signConfig) error {
|
||||
@@ -13,7 +16,23 @@ func sign(conf *signConfig) error {
|
||||
return err
|
||||
}
|
||||
|
||||
partiallySignedTransaction, err := hex.DecodeString(conf.Transaction)
|
||||
if conf.Transaction == "" && conf.TransactionFile == "" {
|
||||
return errors.Errorf("Either --transaction or --transaction-file is required")
|
||||
}
|
||||
if conf.Transaction != "" && conf.TransactionFile != "" {
|
||||
return errors.Errorf("Both --transaction and --transaction-file cannot be passed at the same time")
|
||||
}
|
||||
|
||||
transactionHex := conf.Transaction
|
||||
if conf.TransactionFile != "" {
|
||||
transactionHexBytes, err := ioutil.ReadFile(conf.TransactionFile)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "Could not read hex from %s", conf.TransactionFile)
|
||||
}
|
||||
transactionHex = strings.TrimSpace(string(transactionHexBytes))
|
||||
}
|
||||
|
||||
partiallySignedTransaction, err := hex.DecodeString(transactionHex)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -39,21 +39,22 @@ type consensus struct {
|
||||
finalityManager model.FinalityManager
|
||||
pruningProofManager model.PruningProofManager
|
||||
|
||||
acceptanceDataStore model.AcceptanceDataStore
|
||||
blockStore model.BlockStore
|
||||
blockHeaderStore model.BlockHeaderStore
|
||||
pruningStore model.PruningStore
|
||||
ghostdagDataStores []model.GHOSTDAGDataStore
|
||||
blockRelationStores []model.BlockRelationStore
|
||||
blockStatusStore model.BlockStatusStore
|
||||
consensusStateStore model.ConsensusStateStore
|
||||
headersSelectedTipStore model.HeaderSelectedTipStore
|
||||
multisetStore model.MultisetStore
|
||||
reachabilityDataStores []model.ReachabilityDataStore
|
||||
utxoDiffStore model.UTXODiffStore
|
||||
finalityStore model.FinalityStore
|
||||
headersSelectedChainStore model.HeadersSelectedChainStore
|
||||
daaBlocksStore model.DAABlocksStore
|
||||
acceptanceDataStore model.AcceptanceDataStore
|
||||
blockStore model.BlockStore
|
||||
blockHeaderStore model.BlockHeaderStore
|
||||
pruningStore model.PruningStore
|
||||
ghostdagDataStores []model.GHOSTDAGDataStore
|
||||
blockRelationStores []model.BlockRelationStore
|
||||
blockStatusStore model.BlockStatusStore
|
||||
consensusStateStore model.ConsensusStateStore
|
||||
headersSelectedTipStore model.HeaderSelectedTipStore
|
||||
multisetStore model.MultisetStore
|
||||
reachabilityDataStores []model.ReachabilityDataStore
|
||||
utxoDiffStore model.UTXODiffStore
|
||||
finalityStore model.FinalityStore
|
||||
headersSelectedChainStore model.HeadersSelectedChainStore
|
||||
daaBlocksStore model.DAABlocksStore
|
||||
blocksWithTrustedDataDAAWindowStore model.BlocksWithTrustedDataDAAWindowStore
|
||||
}
|
||||
|
||||
func (s *consensus) ValidateAndInsertBlockWithTrustedData(block *externalapi.BlockWithTrustedData, validateUTXO bool) (*externalapi.VirtualChangeSet, error) {
|
||||
@@ -119,7 +120,6 @@ func (s *consensus) Init(skipAddingGenesis bool) error {
|
||||
if !skipAddingGenesis && s.blockStore.Count(stagingArea) == 0 {
|
||||
genesisWithTrustedData := &externalapi.BlockWithTrustedData{
|
||||
Block: s.genesisBlock,
|
||||
DAAScore: 0,
|
||||
DAAWindow: nil,
|
||||
GHOSTDAGData: []*externalapi.BlockGHOSTDAGDataHashPair{
|
||||
{
|
||||
@@ -144,6 +144,7 @@ func (s *consensus) PruningPointAndItsAnticone() ([]*externalapi.DomainHash, err
|
||||
return s.pruningManager.PruningPointAndItsAnticone()
|
||||
}
|
||||
|
||||
// TODO: Remove this method once v3 is obsolete
|
||||
func (s *consensus) BlockWithTrustedData(blockHash *externalapi.DomainHash) (*externalapi.BlockWithTrustedData, error) {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
@@ -744,6 +745,9 @@ func (s *consensus) ValidatePruningPointProof(pruningPointProof *externalapi.Pru
|
||||
}
|
||||
|
||||
func (s *consensus) ApplyPruningPointProof(pruningPointProof *externalapi.PruningPointProof) error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
stagingArea := model.NewStagingArea()
|
||||
err := s.pruningProofManager.ApplyPruningPointProof(stagingArea, pruningPointProof)
|
||||
if err != nil {
|
||||
@@ -757,3 +761,66 @@ func (s *consensus) ApplyPruningPointProof(pruningPointProof *externalapi.Prunin
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *consensus) BlockDAAWindowHashes(blockHash *externalapi.DomainHash) ([]*externalapi.DomainHash, error) {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
stagingArea := model.NewStagingArea()
|
||||
return s.dagTraversalManager.DAABlockWindow(stagingArea, blockHash)
|
||||
}
|
||||
|
||||
func (s *consensus) TrustedDataDataDAAHeader(trustedBlockHash, daaBlockHash *externalapi.DomainHash, daaBlockWindowIndex uint64) (*externalapi.TrustedDataDataDAAHeader, error) {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
stagingArea := model.NewStagingArea()
|
||||
header, err := s.blockHeaderStore.BlockHeader(s.databaseContext, stagingArea, daaBlockHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ghostdagData, err := s.ghostdagDataStores[0].Get(s.databaseContext, stagingArea, daaBlockHash, false)
|
||||
isNotFoundError := database.IsNotFoundError(err)
|
||||
if !isNotFoundError && err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !isNotFoundError {
|
||||
return &externalapi.TrustedDataDataDAAHeader{
|
||||
Header: header,
|
||||
GHOSTDAGData: ghostdagData,
|
||||
}, nil
|
||||
}
|
||||
|
||||
ghostdagDataHashPair, err := s.blocksWithTrustedDataDAAWindowStore.DAAWindowBlock(s.databaseContext, stagingArea, trustedBlockHash, daaBlockWindowIndex)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &externalapi.TrustedDataDataDAAHeader{
|
||||
Header: header,
|
||||
GHOSTDAGData: ghostdagDataHashPair.GHOSTDAGData,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *consensus) TrustedBlockAssociatedGHOSTDAGDataBlockHashes(blockHash *externalapi.DomainHash) ([]*externalapi.DomainHash, error) {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
return s.pruningManager.TrustedBlockAssociatedGHOSTDAGDataBlockHashes(model.NewStagingArea(), blockHash)
|
||||
}
|
||||
|
||||
func (s *consensus) TrustedGHOSTDAGData(blockHash *externalapi.DomainHash) (*externalapi.BlockGHOSTDAGData, error) {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
stagingArea := model.NewStagingArea()
|
||||
ghostdagData, err := s.ghostdagDataStores[0].Get(s.databaseContext, stagingArea, blockHash, false)
|
||||
isNotFoundError := database.IsNotFoundError(err)
|
||||
if isNotFoundError || ghostdagData.SelectedParent().Equal(model.VirtualGenesisBlockHash) {
|
||||
return s.ghostdagDataStores[0].Get(s.databaseContext, stagingArea, blockHash, true)
|
||||
}
|
||||
|
||||
return ghostdagData, nil
|
||||
}
|
||||
|
||||
@@ -203,14 +203,11 @@ func (f *factory) NewConsensus(config *Config, db infrastructuredatabase.Databas
|
||||
coinbaseManager := coinbasemanager.New(
|
||||
dbManager,
|
||||
config.SubsidyGenesisReward,
|
||||
config.MinSubsidy,
|
||||
config.MaxSubsidy,
|
||||
config.SubsidyPastRewardMultiplier,
|
||||
config.SubsidyMergeSetRewardMultiplier,
|
||||
config.PreDeflationaryPhaseBaseSubsidy,
|
||||
config.CoinbasePayloadScriptPublicKeyMaxLength,
|
||||
config.GenesisHash,
|
||||
config.FixedSubsidySwitchPruningPointInterval,
|
||||
config.FixedSubsidySwitchHashRateThreshold,
|
||||
config.DeflationaryPhaseDaaScore,
|
||||
config.DeflationaryPhaseBaseSubsidy,
|
||||
dagTraversalManager,
|
||||
ghostdagDataStore,
|
||||
acceptanceDataStore,
|
||||
@@ -448,21 +445,22 @@ func (f *factory) NewConsensus(config *Config, db infrastructuredatabase.Databas
|
||||
finalityManager: finalityManager,
|
||||
pruningProofManager: pruningProofManager,
|
||||
|
||||
acceptanceDataStore: acceptanceDataStore,
|
||||
blockStore: blockStore,
|
||||
blockHeaderStore: blockHeaderStore,
|
||||
pruningStore: pruningStore,
|
||||
ghostdagDataStores: ghostdagDataStores,
|
||||
blockStatusStore: blockStatusStore,
|
||||
blockRelationStores: blockRelationStores,
|
||||
consensusStateStore: consensusStateStore,
|
||||
headersSelectedTipStore: headersSelectedTipStore,
|
||||
multisetStore: multisetStore,
|
||||
reachabilityDataStores: reachabilityDataStores,
|
||||
utxoDiffStore: utxoDiffStore,
|
||||
finalityStore: finalityStore,
|
||||
headersSelectedChainStore: headersSelectedChainStore,
|
||||
daaBlocksStore: daaBlocksStore,
|
||||
acceptanceDataStore: acceptanceDataStore,
|
||||
blockStore: blockStore,
|
||||
blockHeaderStore: blockHeaderStore,
|
||||
pruningStore: pruningStore,
|
||||
ghostdagDataStores: ghostdagDataStores,
|
||||
blockStatusStore: blockStatusStore,
|
||||
blockRelationStores: blockRelationStores,
|
||||
consensusStateStore: consensusStateStore,
|
||||
headersSelectedTipStore: headersSelectedTipStore,
|
||||
multisetStore: multisetStore,
|
||||
reachabilityDataStores: reachabilityDataStores,
|
||||
utxoDiffStore: utxoDiffStore,
|
||||
finalityStore: finalityStore,
|
||||
headersSelectedChainStore: headersSelectedChainStore,
|
||||
daaBlocksStore: daaBlocksStore,
|
||||
blocksWithTrustedDataDAAWindowStore: daaWindowStore,
|
||||
}
|
||||
|
||||
err = c.Init(config.SkipAddingGenesis)
|
||||
@@ -640,7 +638,8 @@ func (f *factory) dagProcesses(config *Config,
|
||||
reachabilityDataStores[i],
|
||||
ghostdagManagers[i],
|
||||
daaWindowStore,
|
||||
config.GenesisHash)
|
||||
config.GenesisHash,
|
||||
config.DifficultyAdjustmentWindowSize)
|
||||
}
|
||||
|
||||
return reachabilityManagers, dagTopologyManagers, ghostdagManagers, dagTraversalManagers
|
||||
|
||||
@@ -6,17 +6,13 @@ package externalapi
|
||||
// anticone on a pruned-headers node.
|
||||
type BlockWithTrustedData struct {
|
||||
Block *DomainBlock
|
||||
DAAScore uint64
|
||||
DAAWindow []*TrustedDataDataDAABlock
|
||||
DAAWindow []*TrustedDataDataDAAHeader
|
||||
GHOSTDAGData []*BlockGHOSTDAGDataHashPair
|
||||
}
|
||||
|
||||
// TrustedDataDataDAABlock is a block that belongs to BlockWithTrustedData.DAAWindow
|
||||
// TODO: Currently each trusted data block contains the entire set of blocks in its
|
||||
// DAA window. There's a lot of duplications between DAA windows of trusted blocks.
|
||||
// This duplication should be optimized out.
|
||||
type TrustedDataDataDAABlock struct {
|
||||
Block *DomainBlock
|
||||
// TrustedDataDataDAAHeader is a block that belongs to BlockWithTrustedData.DAAWindow
|
||||
type TrustedDataDataDAAHeader struct {
|
||||
Header BlockHeader
|
||||
GHOSTDAGData *BlockGHOSTDAGData
|
||||
}
|
||||
|
||||
|
||||
@@ -47,4 +47,8 @@ type Consensus interface {
|
||||
EstimateNetworkHashesPerSecond(startHash *DomainHash, windowSize int) (uint64, error)
|
||||
PopulateMass(transaction *DomainTransaction)
|
||||
ResolveVirtual() (*VirtualChangeSet, bool, error)
|
||||
BlockDAAWindowHashes(blockHash *DomainHash) ([]*DomainHash, error)
|
||||
TrustedDataDataDAAHeader(trustedBlockHash, daaBlockHash *DomainHash, daaBlockWindowIndex uint64) (*TrustedDataDataDAAHeader, error)
|
||||
TrustedBlockAssociatedGHOSTDAGDataBlockHashes(blockHash *DomainHash) ([]*DomainHash, error)
|
||||
TrustedGHOSTDAGData(blockHash *DomainHash) (*BlockGHOSTDAGData, error)
|
||||
}
|
||||
|
||||
@@ -7,6 +7,6 @@ import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
type CoinbaseManager interface {
|
||||
ExpectedCoinbaseTransaction(stagingArea *StagingArea, blockHash *externalapi.DomainHash,
|
||||
coinbaseData *externalapi.DomainCoinbaseData) (*externalapi.DomainTransaction, error)
|
||||
CalcBlockSubsidy(blockHash *externalapi.DomainHash) (uint64, error)
|
||||
CalcBlockSubsidy(stagingArea *StagingArea, blockHash *externalapi.DomainHash) (uint64, error)
|
||||
ExtractCoinbaseDataBlueScoreAndSubsidy(coinbaseTx *externalapi.DomainTransaction) (blueScore uint64, coinbaseData *externalapi.DomainCoinbaseData, subsidy uint64, err error)
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ type DAGTraversalManager interface {
|
||||
AnticoneFromVirtualPOV(stagingArea *StagingArea, blockHash *externalapi.DomainHash) ([]*externalapi.DomainHash, error)
|
||||
BlockWindow(stagingArea *StagingArea, highHash *externalapi.DomainHash, windowSize int) ([]*externalapi.DomainHash, error)
|
||||
BlockWindowWithGHOSTDAGData(stagingArea *StagingArea, highHash *externalapi.DomainHash, windowSize int) ([]*externalapi.BlockGHOSTDAGDataHashPair, error)
|
||||
DAABlockWindow(stagingArea *StagingArea, highHash *externalapi.DomainHash) ([]*externalapi.DomainHash, error)
|
||||
NewDownHeap(stagingArea *StagingArea) BlockHeap
|
||||
NewUpHeap(stagingArea *StagingArea) BlockHeap
|
||||
CalculateChainPath(stagingArea *StagingArea, fromBlockHash, toBlockHash *externalapi.DomainHash) (
|
||||
|
||||
@@ -14,5 +14,6 @@ type PruningManager interface {
|
||||
PruneAllBlocksBelow(stagingArea *StagingArea, pruningPointHash *externalapi.DomainHash) error
|
||||
PruningPointAndItsAnticone() ([]*externalapi.DomainHash, error)
|
||||
ExpectedHeaderPruningPoint(stagingArea *StagingArea, blockHash *externalapi.DomainHash) (*externalapi.DomainHash, error)
|
||||
TrustedBlockAssociatedGHOSTDAGDataBlockHashes(stagingArea *StagingArea, blockHash *externalapi.DomainHash) ([]*externalapi.DomainHash, error)
|
||||
BlockWithTrustedData(stagingArea *StagingArea, blockHash *externalapi.DomainHash) (*externalapi.BlockWithTrustedData, error)
|
||||
}
|
||||
|
||||
@@ -12,13 +12,12 @@ func (bp *blockProcessor) validateAndInsertBlockWithTrustedData(stagingArea *mod
|
||||
|
||||
blockHash := consensushashing.BlockHash(block.Block)
|
||||
for i, daaBlock := range block.DAAWindow {
|
||||
hash := consensushashing.BlockHash(daaBlock.Block)
|
||||
hash := consensushashing.HeaderHash(daaBlock.Header)
|
||||
bp.blocksWithTrustedDataDAAWindowStore.Stage(stagingArea, blockHash, uint64(i), &externalapi.BlockGHOSTDAGDataHashPair{
|
||||
Hash: hash,
|
||||
GHOSTDAGData: daaBlock.GHOSTDAGData,
|
||||
})
|
||||
bp.blockStore.Stage(stagingArea, hash, daaBlock.Block)
|
||||
bp.blockHeaderStore.Stage(stagingArea, hash, daaBlock.Block.Header)
|
||||
bp.blockHeaderStore.Stage(stagingArea, hash, daaBlock.Header)
|
||||
}
|
||||
|
||||
blockReplacedGHOSTDAGData, err := bp.ghostdagDataWithoutPrunedBlocks(stagingArea, block.GHOSTDAGData[0].GHOSTDAGData)
|
||||
@@ -31,7 +30,7 @@ func (bp *blockProcessor) validateAndInsertBlockWithTrustedData(stagingArea *mod
|
||||
bp.ghostdagDataStore.Stage(stagingArea, pair.Hash, pair.GHOSTDAGData, true)
|
||||
}
|
||||
|
||||
bp.daaBlocksStore.StageDAAScore(stagingArea, blockHash, block.DAAScore)
|
||||
bp.daaBlocksStore.StageDAAScore(stagingArea, blockHash, block.Block.Header.DAAScore())
|
||||
return bp.validateAndInsertBlock(stagingArea, block.Block, false, validateUTXO, true)
|
||||
}
|
||||
|
||||
|
||||
@@ -178,7 +178,7 @@ func (v *blockValidator) checkCoinbaseSubsidy(
|
||||
return err
|
||||
}
|
||||
|
||||
expectedSubsidy, err := v.coinbaseManager.CalcBlockSubsidy(blockHash)
|
||||
expectedSubsidy, err := v.coinbaseManager.CalcBlockSubsidy(stagingArea, blockHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
package coinbasemanager
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
func (c *coinbaseManager) isBlockRewardFixed(stagingArea *model.StagingArea, blockPruningPoint *externalapi.DomainHash) (bool, error) {
|
||||
blockPruningPointIndex, found, err := c.findPruningPointIndex(stagingArea, blockPruningPoint)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// The given `pruningPointBlock` may only not be found under one circumstance:
|
||||
// we're currently in the process of building the next pruning point. As such,
|
||||
// we must manually set highIndex to currentIndex + 1 because the next pruning
|
||||
// point is not yet stored in the database
|
||||
highPruningPointIndex := blockPruningPointIndex
|
||||
highPruningPointHash := blockPruningPoint
|
||||
if !found {
|
||||
currentPruningPointIndex, err := c.pruningStore.CurrentPruningPointIndex(c.databaseContext, stagingArea)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
highPruningPointIndex = currentPruningPointIndex + 1
|
||||
}
|
||||
|
||||
for {
|
||||
if highPruningPointIndex <= c.fixedSubsidySwitchPruningPointInterval {
|
||||
break
|
||||
}
|
||||
|
||||
lowPruningPointIndex := highPruningPointIndex - c.fixedSubsidySwitchPruningPointInterval
|
||||
lowPruningPointHash, err := c.pruningStore.PruningPointByIndex(c.databaseContext, stagingArea, lowPruningPointIndex)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
highPruningPointHeader, err := c.blockHeaderStore.BlockHeader(c.databaseContext, stagingArea, highPruningPointHash)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
lowPruningPointHeader, err := c.blockHeaderStore.BlockHeader(c.databaseContext, stagingArea, lowPruningPointHash)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
blueWorkDifference := new(big.Int).Sub(highPruningPointHeader.BlueWork(), lowPruningPointHeader.BlueWork())
|
||||
blueScoreDifference := new(big.Int).SetUint64(highPruningPointHeader.BlueScore() - lowPruningPointHeader.BlueScore())
|
||||
estimatedAverageHashRate := new(big.Int).Div(blueWorkDifference, blueScoreDifference)
|
||||
if estimatedAverageHashRate.Cmp(c.fixedSubsidySwitchHashRateThreshold) >= 0 {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
highPruningPointIndex--
|
||||
highPruningPointHash, err = c.pruningStore.PruningPointByIndex(c.databaseContext, stagingArea, highPruningPointIndex)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (c *coinbaseManager) findPruningPointIndex(stagingArea *model.StagingArea, pruningPointHash *externalapi.DomainHash) (uint64, bool, error) {
|
||||
currentPruningPointHash, err := c.pruningStore.PruningPoint(c.databaseContext, stagingArea)
|
||||
if err != nil {
|
||||
return 0, false, err
|
||||
}
|
||||
currentPruningPointIndex, err := c.pruningStore.CurrentPruningPointIndex(c.databaseContext, stagingArea)
|
||||
if err != nil {
|
||||
return 0, false, err
|
||||
}
|
||||
for !currentPruningPointHash.Equal(pruningPointHash) && currentPruningPointIndex > 0 {
|
||||
currentPruningPointIndex--
|
||||
currentPruningPointHash, err = c.pruningStore.PruningPointByIndex(c.databaseContext, stagingArea, currentPruningPointIndex)
|
||||
if err != nil {
|
||||
return 0, false, err
|
||||
}
|
||||
}
|
||||
if currentPruningPointIndex == 0 && !currentPruningPointHash.Equal(pruningPointHash) {
|
||||
return 0, false, nil
|
||||
}
|
||||
return currentPruningPointIndex, true, nil
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
package coinbasemanager_test
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/testutils"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/transactionhelper"
|
||||
"github.com/kaspanet/kaspad/util/difficulty"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestBlockRewardSwitch(t *testing.T) {
|
||||
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
|
||||
// Set the pruning depth to 10
|
||||
consensusConfig.MergeSetSizeLimit = 1
|
||||
consensusConfig.K = 1
|
||||
consensusConfig.FinalityDuration = 1 * time.Second
|
||||
consensusConfig.TargetTimePerBlock = 1 * time.Second
|
||||
|
||||
// Disable difficulty adjustment so that we could reason about blue work
|
||||
consensusConfig.DisableDifficultyAdjustment = true
|
||||
|
||||
// Disable pruning so that we could have access to all the blocks
|
||||
consensusConfig.IsArchival = true
|
||||
|
||||
// Set the interval to 10
|
||||
consensusConfig.FixedSubsidySwitchPruningPointInterval = 10
|
||||
|
||||
// Set the hash rate difference such that the switch would trigger exactly
|
||||
// on the `FixedSubsidySwitchPruningPointInterval + 1`th pruning point
|
||||
workToAcceptGenesis := difficulty.CalcWork(consensusConfig.GenesisBlock.Header.Bits())
|
||||
consensusConfig.FixedSubsidySwitchHashRateThreshold = workToAcceptGenesis
|
||||
|
||||
// Set the min, max, and post-switch subsidies to values that would make it
|
||||
// easy to tell whether the switch happened
|
||||
consensusConfig.MinSubsidy = 2 * constants.SompiPerKaspa
|
||||
consensusConfig.MaxSubsidy = 2 * constants.SompiPerKaspa
|
||||
consensusConfig.SubsidyGenesisReward = 1 * constants.SompiPerKaspa
|
||||
|
||||
factory := consensus.NewFactory()
|
||||
tc, teardown, err := factory.NewTestConsensus(consensusConfig, "TestBlockRewardSwitch")
|
||||
if err != nil {
|
||||
t.Fatalf("Error setting up consensus: %+v", err)
|
||||
}
|
||||
defer teardown(false)
|
||||
|
||||
// Make the pruning point move FixedSubsidySwitchPruningPointInterval times
|
||||
tipHash := consensusConfig.GenesisHash
|
||||
for i := uint64(0); i < consensusConfig.PruningDepth()+consensusConfig.FixedSubsidySwitchPruningPointInterval; i++ {
|
||||
addedBlockHash, _, err := tc.AddBlock([]*externalapi.DomainHash{tipHash}, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("AddBlock: %+v", err)
|
||||
}
|
||||
tipHash = addedBlockHash
|
||||
}
|
||||
|
||||
// We expect to see `FixedSubsidySwitchPruningPointInterval` pruning points + the genesis
|
||||
pruningPointHeaders, err := tc.PruningPointHeaders()
|
||||
if err != nil {
|
||||
t.Fatalf("PruningPointHeaders: %+v", pruningPointHeaders)
|
||||
}
|
||||
expectedPruningPointHeaderAmount := consensusConfig.FixedSubsidySwitchPruningPointInterval + 1
|
||||
if uint64(len(pruningPointHeaders)) != expectedPruningPointHeaderAmount {
|
||||
t.Fatalf("Unexpected amount of pruning point headers. "+
|
||||
"Want: %d, got: %d", expectedPruningPointHeaderAmount, len(pruningPointHeaders))
|
||||
}
|
||||
|
||||
// Make sure that all the headers thus far had a non-fixed subsidies
|
||||
// Note that we skip the genesis, since that always has the post-switch
|
||||
// value
|
||||
for _, pruningPointHeader := range pruningPointHeaders[1:] {
|
||||
pruningPointHash := consensushashing.HeaderHash(pruningPointHeader)
|
||||
pruningPoint, err := tc.GetBlock(pruningPointHash)
|
||||
if err != nil {
|
||||
t.Fatalf("GetBlock: %+v", err)
|
||||
}
|
||||
pruningPointCoinbase := pruningPoint.Transactions[transactionhelper.CoinbaseTransactionIndex]
|
||||
_, _, subsidy, err := tc.CoinbaseManager().ExtractCoinbaseDataBlueScoreAndSubsidy(pruningPointCoinbase)
|
||||
if err != nil {
|
||||
t.Fatalf("ExtractCoinbaseDataBlueScoreAndSubsidy: %+v", err)
|
||||
}
|
||||
if subsidy != consensusConfig.MinSubsidy {
|
||||
t.Fatalf("Subsidy has unexpected value. Want: %d, got: %d", consensusConfig.MinSubsidy, subsidy)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,9 +1,6 @@
|
||||
package coinbasemanager
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"math/big"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
||||
@@ -12,18 +9,16 @@ import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/transactionhelper"
|
||||
"github.com/kaspanet/kaspad/infrastructure/db/database"
|
||||
"github.com/pkg/errors"
|
||||
"math"
|
||||
)
|
||||
|
||||
type coinbaseManager struct {
|
||||
subsidyGenesisReward uint64
|
||||
minSubsidy uint64
|
||||
maxSubsidy uint64
|
||||
subsidyPastRewardMultiplier *big.Rat
|
||||
subsidyMergeSetRewardMultiplier *big.Rat
|
||||
preDeflationaryPhaseBaseSubsidy uint64
|
||||
coinbasePayloadScriptPublicKeyMaxLength uint8
|
||||
genesisHash *externalapi.DomainHash
|
||||
fixedSubsidySwitchPruningPointInterval uint64
|
||||
fixedSubsidySwitchHashRateThreshold *big.Int
|
||||
deflationaryPhaseDaaScore uint64
|
||||
deflationaryPhaseBaseSubsidy uint64
|
||||
|
||||
databaseContext model.DBReader
|
||||
dagTraversalManager model.DAGTraversalManager
|
||||
@@ -84,7 +79,7 @@ func (c *coinbaseManager) ExpectedCoinbaseTransaction(stagingArea *model.Staging
|
||||
txOuts = append(txOuts, txOut)
|
||||
}
|
||||
|
||||
subsidy, err := c.CalcBlockSubsidy(blockHash)
|
||||
subsidy, err := c.CalcBlockSubsidy(stagingArea, blockHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -182,97 +177,68 @@ func acceptanceDataFromArrayToMap(acceptanceData externalapi.AcceptanceData) map
|
||||
// should have. This is mainly used for determining how much the coinbase for
|
||||
// newly generated blocks awards as well as validating the coinbase for blocks
|
||||
// has the expected value.
|
||||
//
|
||||
// Further details: https://hashdag.medium.com/kaspa-launch-plan-9a63f4d754a6
|
||||
func (c *coinbaseManager) CalcBlockSubsidy(blockHash *externalapi.DomainHash) (uint64, error) {
|
||||
func (c *coinbaseManager) CalcBlockSubsidy(stagingArea *model.StagingArea, blockHash *externalapi.DomainHash) (uint64, error) {
|
||||
if blockHash.Equal(c.genesisHash) {
|
||||
return c.subsidyGenesisReward, nil
|
||||
}
|
||||
|
||||
return c.maxSubsidy, nil
|
||||
}
|
||||
|
||||
func (c *coinbaseManager) calculateAveragePastSubsidy(stagingArea *model.StagingArea, blockHash *externalapi.DomainHash) (*big.Rat, error) {
|
||||
const subsidyPastWindowSize = 100
|
||||
blockWindow, err := c.dagTraversalManager.BlockWindow(stagingArea, blockHash, subsidyPastWindowSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(blockWindow) == 0 {
|
||||
return new(big.Rat).SetFrac64(int64(c.subsidyGenesisReward), 1), nil
|
||||
}
|
||||
|
||||
pastBlocks, err := c.blockStore.Blocks(c.databaseContext, stagingArea, blockWindow)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pastBlockSubsidySum := int64(0)
|
||||
for _, pastBlock := range pastBlocks {
|
||||
coinbaseTransaction := pastBlock.Transactions[transactionhelper.CoinbaseTransactionIndex]
|
||||
_, _, pastBlockSubsidy, err := c.ExtractCoinbaseDataBlueScoreAndSubsidy(coinbaseTransaction)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pastBlockSubsidySum += int64(pastBlockSubsidy)
|
||||
}
|
||||
return big.NewRat(pastBlockSubsidySum, int64(len(blockWindow))), nil
|
||||
}
|
||||
|
||||
func (c *coinbaseManager) calculateMergeSetSubsidySum(stagingArea *model.StagingArea, blockHash *externalapi.DomainHash) (*big.Rat, error) {
|
||||
ghostdagData, err := c.ghostdagDataStore.Get(c.databaseContext, stagingArea, blockHash, true)
|
||||
if !database.IsNotFoundError(err) && err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If there's ghostdag data with trusted data we prefer it because we need the original merge set non-pruned merge set.
|
||||
if database.IsNotFoundError(err) {
|
||||
ghostdagData, err = c.ghostdagDataStore.Get(c.databaseContext, stagingArea, blockHash, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
mergeSet := append(ghostdagData.MergeSetBlues(), ghostdagData.MergeSetReds()...)
|
||||
mergeSetBlocks, err := c.blockStore.Blocks(c.databaseContext, stagingArea, mergeSet)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mergeSetSubsidySum := int64(0)
|
||||
for _, mergeSetBlock := range mergeSetBlocks {
|
||||
coinbaseTransaction := mergeSetBlock.Transactions[transactionhelper.CoinbaseTransactionIndex]
|
||||
_, _, mergeSetBlockSubsidy, err := c.ExtractCoinbaseDataBlueScoreAndSubsidy(coinbaseTransaction)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mergeSetSubsidySum += int64(mergeSetBlockSubsidy)
|
||||
}
|
||||
return big.NewRat(mergeSetSubsidySum, 1), nil
|
||||
}
|
||||
|
||||
func (c *coinbaseManager) calculateSubsidyRandomVariable(stagingArea *model.StagingArea, blockHash *externalapi.DomainHash) (int64, error) {
|
||||
ghostdagData, err := c.ghostdagDataStore.Get(c.databaseContext, stagingArea, blockHash, false)
|
||||
blockDaaScore, err := c.daaBlocksStore.DAAScore(c.databaseContext, stagingArea, blockHash)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
selectedParentHash := ghostdagData.SelectedParent()
|
||||
if selectedParentHash == nil {
|
||||
return 0, nil
|
||||
if blockDaaScore < c.deflationaryPhaseDaaScore {
|
||||
return c.preDeflationaryPhaseBaseSubsidy, nil
|
||||
}
|
||||
|
||||
const binomialSteps = 10
|
||||
binomialSum := int64(0)
|
||||
blockSubsidy := c.calcDeflationaryPeriodBlockSubsidy(blockDaaScore)
|
||||
return blockSubsidy, nil
|
||||
}
|
||||
|
||||
// The first two bytes of a hash are a good deterministic source
|
||||
// of randomness, so we use that instead of any rand implementation
|
||||
firstTwoBytes := binary.LittleEndian.Uint16(selectedParentHash.ByteSlice()[:2])
|
||||
for i := 0; i < binomialSteps; i++ {
|
||||
step := firstTwoBytes & 1
|
||||
firstTwoBytes >>= 1
|
||||
binomialSum += int64(step)
|
||||
func (c *coinbaseManager) calcDeflationaryPeriodBlockSubsidy(blockDaaScore uint64) uint64 {
|
||||
// We define a year as 365.25 days and a month as 365.25 / 12 = 30.4375
|
||||
// secondsPerMonth = 30.4375 * 24 * 60 * 60
|
||||
const secondsPerMonth = 2629800
|
||||
// Note that this calculation implicitly assumes that block per second = 1 (by assuming daa score diff is in second units).
|
||||
monthsSinceDeflationaryPhaseStarted := (blockDaaScore - c.deflationaryPhaseDaaScore) / secondsPerMonth
|
||||
// Return the pre-calculated value from subsidy-per-month table
|
||||
return c.getDeflationaryPeriodBlockSubsidyFromTable(monthsSinceDeflationaryPhaseStarted)
|
||||
}
|
||||
|
||||
/*
|
||||
This table was pre-calculated by calling `calcDeflationaryPeriodBlockSubsidyFloatCalc` for all months until reaching 0 subsidy.
|
||||
To regenerate this table, run `TestBuildSubsidyTable` in coinbasemanager_test.go (note the `deflationaryPhaseBaseSubsidy` therein)
|
||||
*/
|
||||
var subsidyByDeflationaryMonthTable = []uint64{
|
||||
44000000000, 41530469757, 39199543598, 36999442271, 34922823143, 32962755691, 31112698372, 29366476791, 27718263097, 26162556530, 24694165062, 23308188075, 22000000000, 20765234878, 19599771799, 18499721135, 17461411571, 16481377845, 15556349186, 14683238395, 13859131548, 13081278265, 12347082531, 11654094037, 11000000000,
|
||||
10382617439, 9799885899, 9249860567, 8730705785, 8240688922, 7778174593, 7341619197, 6929565774, 6540639132, 6173541265, 5827047018, 5500000000, 5191308719, 4899942949, 4624930283, 4365352892, 4120344461, 3889087296, 3670809598, 3464782887, 3270319566, 3086770632, 2913523509, 2750000000, 2595654359,
|
||||
2449971474, 2312465141, 2182676446, 2060172230, 1944543648, 1835404799, 1732391443, 1635159783, 1543385316, 1456761754, 1375000000, 1297827179, 1224985737, 1156232570, 1091338223, 1030086115, 972271824, 917702399, 866195721, 817579891, 771692658, 728380877, 687500000, 648913589, 612492868,
|
||||
578116285, 545669111, 515043057, 486135912, 458851199, 433097860, 408789945, 385846329, 364190438, 343750000, 324456794, 306246434, 289058142, 272834555, 257521528, 243067956, 229425599, 216548930, 204394972, 192923164, 182095219, 171875000, 162228397, 153123217, 144529071,
|
||||
136417277, 128760764, 121533978, 114712799, 108274465, 102197486, 96461582, 91047609, 85937500, 81114198, 76561608, 72264535, 68208638, 64380382, 60766989, 57356399, 54137232, 51098743, 48230791, 45523804, 42968750, 40557099, 38280804, 36132267, 34104319,
|
||||
32190191, 30383494, 28678199, 27068616, 25549371, 24115395, 22761902, 21484375, 20278549, 19140402, 18066133, 17052159, 16095095, 15191747, 14339099, 13534308, 12774685, 12057697, 11380951, 10742187, 10139274, 9570201, 9033066, 8526079, 8047547,
|
||||
7595873, 7169549, 6767154, 6387342, 6028848, 5690475, 5371093, 5069637, 4785100, 4516533, 4263039, 4023773, 3797936, 3584774, 3383577, 3193671, 3014424, 2845237, 2685546, 2534818, 2392550, 2258266, 2131519, 2011886, 1898968,
|
||||
1792387, 1691788, 1596835, 1507212, 1422618, 1342773, 1267409, 1196275, 1129133, 1065759, 1005943, 949484, 896193, 845894, 798417, 753606, 711309, 671386, 633704, 598137, 564566, 532879, 502971, 474742, 448096,
|
||||
422947, 399208, 376803, 355654, 335693, 316852, 299068, 282283, 266439, 251485, 237371, 224048, 211473, 199604, 188401, 177827, 167846, 158426, 149534, 141141, 133219, 125742, 118685, 112024, 105736,
|
||||
99802, 94200, 88913, 83923, 79213, 74767, 70570, 66609, 62871, 59342, 56012, 52868, 49901, 47100, 44456, 41961, 39606, 37383, 35285, 33304, 31435, 29671, 28006, 26434, 24950,
|
||||
23550, 22228, 20980, 19803, 18691, 17642, 16652, 15717, 14835, 14003, 13217, 12475, 11775, 11114, 10490, 9901, 9345, 8821, 8326, 7858, 7417, 7001, 6608, 6237, 5887,
|
||||
5557, 5245, 4950, 4672, 4410, 4163, 3929, 3708, 3500, 3304, 3118, 2943, 2778, 2622, 2475, 2336, 2205, 2081, 1964, 1854, 1750, 1652, 1559, 1471, 1389,
|
||||
1311, 1237, 1168, 1102, 1040, 982, 927, 875, 826, 779, 735, 694, 655, 618, 584, 551, 520, 491, 463, 437, 413, 389, 367, 347, 327,
|
||||
309, 292, 275, 260, 245, 231, 218, 206, 194, 183, 173, 163, 154, 146, 137, 130, 122, 115, 109, 103, 97, 91, 86, 81, 77,
|
||||
73, 68, 65, 61, 57, 54, 51, 48, 45, 43, 40, 38, 36, 34, 32, 30, 28, 27, 25, 24, 22, 21, 20, 19, 18,
|
||||
17, 16, 15, 14, 13, 12, 12, 11, 10, 10, 9, 9, 8, 8, 7, 7, 6, 6, 6, 5, 5, 5, 4, 4, 4,
|
||||
4, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
0,
|
||||
}
|
||||
|
||||
func (c *coinbaseManager) getDeflationaryPeriodBlockSubsidyFromTable(month uint64) uint64 {
|
||||
if month >= uint64(len(subsidyByDeflationaryMonthTable)) {
|
||||
month = uint64(len(subsidyByDeflationaryMonthTable) - 1)
|
||||
}
|
||||
return binomialSum - (binomialSteps / 2), nil
|
||||
return subsidyByDeflationaryMonthTable[month]
|
||||
}
|
||||
|
||||
func (c *coinbaseManager) calcDeflationaryPeriodBlockSubsidyFloatCalc(month uint64) uint64 {
|
||||
baseSubsidy := c.deflationaryPhaseBaseSubsidy
|
||||
subsidy := float64(baseSubsidy) / math.Pow(2, float64(month)/12)
|
||||
return uint64(subsidy)
|
||||
}
|
||||
|
||||
func (c *coinbaseManager) calcMergedBlockReward(stagingArea *model.StagingArea, blockHash *externalapi.DomainHash,
|
||||
@@ -312,14 +278,11 @@ func New(
|
||||
databaseContext model.DBReader,
|
||||
|
||||
subsidyGenesisReward uint64,
|
||||
minSubsidy uint64,
|
||||
maxSubsidy uint64,
|
||||
subsidyPastRewardMultiplier *big.Rat,
|
||||
subsidyMergeSetRewardMultiplier *big.Rat,
|
||||
preDeflationaryPhaseBaseSubsidy uint64,
|
||||
coinbasePayloadScriptPublicKeyMaxLength uint8,
|
||||
genesisHash *externalapi.DomainHash,
|
||||
fixedSubsidySwitchPruningPointInterval uint64,
|
||||
fixedSubsidySwitchHashRateThreshold *big.Int,
|
||||
deflationaryPhaseDaaScore uint64,
|
||||
deflationaryPhaseBaseSubsidy uint64,
|
||||
|
||||
dagTraversalManager model.DAGTraversalManager,
|
||||
ghostdagDataStore model.GHOSTDAGDataStore,
|
||||
@@ -333,14 +296,11 @@ func New(
|
||||
databaseContext: databaseContext,
|
||||
|
||||
subsidyGenesisReward: subsidyGenesisReward,
|
||||
minSubsidy: minSubsidy,
|
||||
maxSubsidy: maxSubsidy,
|
||||
subsidyPastRewardMultiplier: subsidyPastRewardMultiplier,
|
||||
subsidyMergeSetRewardMultiplier: subsidyMergeSetRewardMultiplier,
|
||||
preDeflationaryPhaseBaseSubsidy: preDeflationaryPhaseBaseSubsidy,
|
||||
coinbasePayloadScriptPublicKeyMaxLength: coinbasePayloadScriptPublicKeyMaxLength,
|
||||
genesisHash: genesisHash,
|
||||
fixedSubsidySwitchPruningPointInterval: fixedSubsidySwitchPruningPointInterval,
|
||||
fixedSubsidySwitchHashRateThreshold: fixedSubsidySwitchHashRateThreshold,
|
||||
deflationaryPhaseDaaScore: deflationaryPhaseDaaScore,
|
||||
deflationaryPhaseBaseSubsidy: deflationaryPhaseBaseSubsidy,
|
||||
|
||||
dagTraversalManager: dagTraversalManager,
|
||||
ghostdagDataStore: ghostdagDataStore,
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
package coinbasemanager
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCalcDeflationaryPeriodBlockSubsidy(t *testing.T) {
|
||||
const secondsPerMonth = 2629800
|
||||
const secondsPerHalving = secondsPerMonth * 12
|
||||
const deflationaryPhaseDaaScore = secondsPerMonth * 6
|
||||
const deflationaryPhaseBaseSubsidy = 440 * constants.SompiPerKaspa
|
||||
coinbaseManagerInterface := New(
|
||||
nil,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
&externalapi.DomainHash{},
|
||||
deflationaryPhaseDaaScore,
|
||||
deflationaryPhaseBaseSubsidy,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil)
|
||||
coinbaseManagerInstance := coinbaseManagerInterface.(*coinbaseManager)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
blockDaaScore uint64
|
||||
expectedBlockSubsidy uint64
|
||||
}{
|
||||
{
|
||||
name: "start of deflationary phase",
|
||||
blockDaaScore: deflationaryPhaseDaaScore,
|
||||
expectedBlockSubsidy: deflationaryPhaseBaseSubsidy,
|
||||
},
|
||||
{
|
||||
name: "after one halving",
|
||||
blockDaaScore: deflationaryPhaseDaaScore + secondsPerHalving,
|
||||
expectedBlockSubsidy: deflationaryPhaseBaseSubsidy / 2,
|
||||
},
|
||||
{
|
||||
name: "after two halvings",
|
||||
blockDaaScore: deflationaryPhaseDaaScore + secondsPerHalving*2,
|
||||
expectedBlockSubsidy: deflationaryPhaseBaseSubsidy / 4,
|
||||
},
|
||||
{
|
||||
name: "after five halvings",
|
||||
blockDaaScore: deflationaryPhaseDaaScore + secondsPerHalving*5,
|
||||
expectedBlockSubsidy: deflationaryPhaseBaseSubsidy / 32,
|
||||
},
|
||||
{
|
||||
name: "after 32 halvings",
|
||||
blockDaaScore: deflationaryPhaseDaaScore + secondsPerHalving*32,
|
||||
expectedBlockSubsidy: deflationaryPhaseBaseSubsidy / 4294967296,
|
||||
},
|
||||
{
|
||||
name: "just before subsidy depleted",
|
||||
blockDaaScore: deflationaryPhaseDaaScore + secondsPerHalving*35,
|
||||
expectedBlockSubsidy: 1,
|
||||
},
|
||||
{
|
||||
name: "after subsidy depleted",
|
||||
blockDaaScore: deflationaryPhaseDaaScore + secondsPerHalving*36,
|
||||
expectedBlockSubsidy: 0,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
blockSubsidy := coinbaseManagerInstance.calcDeflationaryPeriodBlockSubsidy(test.blockDaaScore)
|
||||
if blockSubsidy != test.expectedBlockSubsidy {
|
||||
t.Errorf("TestCalcDeflationaryPeriodBlockSubsidy: test '%s' failed. Want: %d, got: %d",
|
||||
test.name, test.expectedBlockSubsidy, blockSubsidy)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildSubsidyTable(t *testing.T) {
|
||||
deflationaryPhaseBaseSubsidy := dagconfig.MainnetParams.DeflationaryPhaseBaseSubsidy
|
||||
if deflationaryPhaseBaseSubsidy != 440*constants.SompiPerKaspa {
|
||||
t.Errorf("TestBuildSubsidyTable: table generation function was not updated to reflect "+
|
||||
"the new base subsidy %d. Please fix the constant above and replace subsidyByDeflationaryMonthTable "+
|
||||
"in coinbasemanager.go with the printed table", deflationaryPhaseBaseSubsidy)
|
||||
}
|
||||
coinbaseManagerInterface := New(
|
||||
nil,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
&externalapi.DomainHash{},
|
||||
0,
|
||||
deflationaryPhaseBaseSubsidy,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil)
|
||||
coinbaseManagerInstance := coinbaseManagerInterface.(*coinbaseManager)
|
||||
|
||||
var subsidyTable []uint64
|
||||
for M := uint64(0); ; M++ {
|
||||
subsidy := coinbaseManagerInstance.calcDeflationaryPeriodBlockSubsidyFloatCalc(M)
|
||||
subsidyTable = append(subsidyTable, subsidy)
|
||||
if subsidy == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
tableStr := "\n{\t"
|
||||
for i := 0; i < len(subsidyTable); i++ {
|
||||
tableStr += strconv.FormatUint(subsidyTable[i], 10) + ", "
|
||||
if (i+1)%25 == 0 {
|
||||
tableStr += "\n\t"
|
||||
}
|
||||
}
|
||||
tableStr += "\n}"
|
||||
t.Logf(tableStr)
|
||||
}
|
||||
@@ -11,12 +11,13 @@ import (
|
||||
type dagTraversalManager struct {
|
||||
databaseContext model.DBReader
|
||||
|
||||
dagTopologyManager model.DAGTopologyManager
|
||||
ghostdagManager model.GHOSTDAGManager
|
||||
ghostdagDataStore model.GHOSTDAGDataStore
|
||||
reachabilityDataStore model.ReachabilityDataStore
|
||||
daaWindowStore model.BlocksWithTrustedDataDAAWindowStore
|
||||
genesisHash *externalapi.DomainHash
|
||||
dagTopologyManager model.DAGTopologyManager
|
||||
ghostdagManager model.GHOSTDAGManager
|
||||
ghostdagDataStore model.GHOSTDAGDataStore
|
||||
reachabilityDataStore model.ReachabilityDataStore
|
||||
daaWindowStore model.BlocksWithTrustedDataDAAWindowStore
|
||||
genesisHash *externalapi.DomainHash
|
||||
difficultyAdjustmentWindowSize int
|
||||
}
|
||||
|
||||
// New instantiates a new DAGTraversalManager
|
||||
@@ -27,7 +28,8 @@ func New(
|
||||
reachabilityDataStore model.ReachabilityDataStore,
|
||||
ghostdagManager model.GHOSTDAGManager,
|
||||
daaWindowStore model.BlocksWithTrustedDataDAAWindowStore,
|
||||
genesisHash *externalapi.DomainHash) model.DAGTraversalManager {
|
||||
genesisHash *externalapi.DomainHash,
|
||||
difficultyAdjustmentWindowSize int) model.DAGTraversalManager {
|
||||
return &dagTraversalManager{
|
||||
databaseContext: databaseContext,
|
||||
dagTopologyManager: dagTopologyManager,
|
||||
@@ -36,7 +38,8 @@ func New(
|
||||
ghostdagManager: ghostdagManager,
|
||||
daaWindowStore: daaWindowStore,
|
||||
|
||||
genesisHash: genesisHash,
|
||||
genesisHash: genesisHash,
|
||||
difficultyAdjustmentWindowSize: difficultyAdjustmentWindowSize,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,10 @@ import (
|
||||
"github.com/kaspanet/kaspad/infrastructure/db/database"
|
||||
)
|
||||
|
||||
func (dtm *dagTraversalManager) DAABlockWindow(stagingArea *model.StagingArea, highHash *externalapi.DomainHash) ([]*externalapi.DomainHash, error) {
|
||||
return dtm.BlockWindow(stagingArea, highHash, dtm.difficultyAdjustmentWindowSize)
|
||||
}
|
||||
|
||||
// BlockWindow returns a blockWindow of the given size that contains the
|
||||
// blocks in the past of highHash, the sorting is unspecified.
|
||||
// If the number of blocks in the past of startingNode is less then windowSize,
|
||||
|
||||
@@ -513,8 +513,11 @@ func (pm *pruningManager) ArePruningPointsViolatingFinality(stagingArea *model.S
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, header := range pruningPoints {
|
||||
blockHash := consensushashing.HeaderHash(header)
|
||||
// We need to check if virtualFinalityPointFinalityPoint is in the selected chain of
|
||||
// the most recent known pruning point, so we iterate the pruning points from the most
|
||||
// recent one until we find a known pruning point.
|
||||
for i := len(pruningPoints) - 1; i >= 0; i-- {
|
||||
blockHash := consensushashing.HeaderHash(pruningPoints[i])
|
||||
exists, err := pm.blockStatusStore.Exists(pm.databaseContext, stagingArea, blockHash)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@@ -965,26 +968,21 @@ func (pm *pruningManager) BlockWithTrustedData(stagingArea *model.StagingArea, b
|
||||
return nil, err
|
||||
}
|
||||
|
||||
daaScore, err := pm.daaBlocksStore.DAAScore(pm.databaseContext, stagingArea, blockHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
windowSize := pm.difficultyAdjustmentWindowSize
|
||||
window, err := pm.dagTraversalManager.BlockWindowWithGHOSTDAGData(stagingArea, blockHash, windowSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
windowPairs := make([]*externalapi.TrustedDataDataDAABlock, len(window))
|
||||
windowPairs := make([]*externalapi.TrustedDataDataDAAHeader, len(window))
|
||||
for i, daaBlock := range window {
|
||||
daaDomainBlock, err := pm.blocksStore.Block(pm.databaseContext, stagingArea, daaBlock.Hash)
|
||||
daaDomainBlock, err := pm.blockHeaderStore.BlockHeader(pm.databaseContext, stagingArea, daaBlock.Hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
windowPairs[i] = &externalapi.TrustedDataDataDAABlock{
|
||||
Block: daaDomainBlock,
|
||||
windowPairs[i] = &externalapi.TrustedDataDataDAAHeader{
|
||||
Header: daaDomainBlock,
|
||||
GHOSTDAGData: daaBlock.GHOSTDAGData,
|
||||
}
|
||||
}
|
||||
@@ -1024,7 +1022,6 @@ func (pm *pruningManager) BlockWithTrustedData(stagingArea *model.StagingArea, b
|
||||
|
||||
return &externalapi.BlockWithTrustedData{
|
||||
Block: block,
|
||||
DAAScore: daaScore,
|
||||
DAAWindow: windowPairs,
|
||||
GHOSTDAGData: ghostdagDataHashPairs,
|
||||
}, nil
|
||||
@@ -1130,3 +1127,37 @@ func (pm *pruningManager) isPruningPointInPruningDepth(stagingArea *model.Stagin
|
||||
|
||||
return blockGHOSTDAGData.BlueScore() >= pruningPointHeader.BlueScore()+pm.pruningDepth, nil
|
||||
}
|
||||
|
||||
func (pm *pruningManager) TrustedBlockAssociatedGHOSTDAGDataBlockHashes(stagingArea *model.StagingArea, blockHash *externalapi.DomainHash) ([]*externalapi.DomainHash, error) {
|
||||
blockHashes := make([]*externalapi.DomainHash, 0, pm.k)
|
||||
current := blockHash
|
||||
isTrustedData := false
|
||||
for i := externalapi.KType(0); i <= pm.k; i++ {
|
||||
ghostdagData, err := pm.ghostdagDataStore.Get(pm.databaseContext, stagingArea, current, isTrustedData)
|
||||
isNotFoundError := database.IsNotFoundError(err)
|
||||
if !isNotFoundError && err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if isNotFoundError || ghostdagData.SelectedParent().Equal(model.VirtualGenesisBlockHash) {
|
||||
isTrustedData = true
|
||||
ghostdagData, err = pm.ghostdagDataStore.Get(pm.databaseContext, stagingArea, current, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
blockHashes = append(blockHashes, current)
|
||||
|
||||
if ghostdagData.SelectedParent().Equal(pm.genesisHash) {
|
||||
break
|
||||
}
|
||||
|
||||
if current.Equal(pm.genesisHash) {
|
||||
break
|
||||
}
|
||||
|
||||
current = ghostdagData.SelectedParent()
|
||||
}
|
||||
|
||||
return blockHashes, nil
|
||||
}
|
||||
|
||||
@@ -441,6 +441,11 @@ func (ppm *pruningProofManager) ValidatePruningPointProof(pruningPointProof *ext
|
||||
return err
|
||||
}
|
||||
|
||||
currentDAGPruningPointHeader, err := ppm.blockHeaderStore.BlockHeader(ppm.databaseContext, model.NewStagingArea(), currentDAGPruningPoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for blockLevel, selectedTip := range selectedTipByLevel {
|
||||
if blockLevel <= pruningPointBlockLevel {
|
||||
if !selectedTip.Equal(consensushashing.HeaderHash(pruningPointHeader)) {
|
||||
@@ -493,10 +498,7 @@ func (ppm *pruningProofManager) ValidatePruningPointProof(pruningPointProof *ext
|
||||
|
||||
if commonAncestor != nil {
|
||||
selectedTipBlueWorkDiff := big.NewInt(0).Sub(selectedTipGHOSTDAGData.BlueWork(), commonAncestorGHOSTDAGData.BlueWork())
|
||||
currentDAGPruningPointParents, err := ppm.dagTopologyManagers[blockLevel].Parents(model.NewStagingArea(), currentDAGPruningPoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
currentDAGPruningPointParents := ppm.parentsManager.ParentsAtLevel(currentDAGPruningPointHeader, blockLevel)
|
||||
|
||||
foundBetterParent := false
|
||||
for _, parent := range currentDAGPruningPointParents {
|
||||
|
||||
@@ -46,13 +46,11 @@ const (
|
||||
// Should be at least an order of magnitude smaller than defaultFinalityDuration/defaultTargetTimePerBlock.
|
||||
// (Higher values make pruning attacks easier by a constant, lower values make merging after a split or a spike
|
||||
// in block take longer)
|
||||
defaultMergeSetSizeLimit = defaultGHOSTDAGK * 10
|
||||
defaultSubsidyGenesisReward = 1 * constants.SompiPerKaspa
|
||||
defaultMinSubsidy = 1 * constants.SompiPerKaspa
|
||||
defaultMaxSubsidy = 500 * constants.SompiPerKaspa
|
||||
defaultBaseSubsidy = 50 * constants.SompiPerKaspa
|
||||
defaultFixedSubsidySwitchPruningPointInterval uint64 = 7
|
||||
defaultCoinbasePayloadScriptPublicKeyMaxLength = 150
|
||||
defaultMergeSetSizeLimit = defaultGHOSTDAGK * 10
|
||||
defaultSubsidyGenesisReward = 1 * constants.SompiPerKaspa
|
||||
defaultPreDeflationaryPhaseBaseSubsidy = 500 * constants.SompiPerKaspa
|
||||
defaultDeflationaryPhaseBaseSubsidy = 440 * constants.SompiPerKaspa
|
||||
defaultCoinbasePayloadScriptPublicKeyMaxLength = 150
|
||||
// defaultDifficultyAdjustmentWindowSize is the number of blocks in a block's past used to calculate its difficulty
|
||||
// target.
|
||||
// The DAA should take the median of 2640 blocks, so in order to do that we need 2641 window size.
|
||||
@@ -76,4 +74,12 @@ const (
|
||||
defaultTargetTimePerBlock = 1 * time.Second
|
||||
|
||||
defaultPruningProofM = 1000
|
||||
|
||||
// defaultDeflationaryPhaseDaaScore is the DAA score after which the pre-deflationary period
|
||||
// switches to the deflationary period. This number is calculated as follows:
|
||||
// We define a year as 365.25 days
|
||||
// Half a year in seconds = 365.25 / 2 * 24 * 60 * 60 = 15778800
|
||||
// The network was down for three days shortly after launch
|
||||
// Three days in seconds = 3 * 24 * 60 * 60 = 259200
|
||||
defaultDeflationaryPhaseDaaScore = 15778800 - 259200
|
||||
)
|
||||
|
||||
@@ -92,10 +92,8 @@ type Params struct {
|
||||
// SubsidyPastRewardMultiplier are part of the block subsidy equation.
|
||||
// Further details: https://hashdag.medium.com/kaspa-launch-plan-9a63f4d754a6
|
||||
SubsidyGenesisReward uint64
|
||||
MinSubsidy uint64
|
||||
MaxSubsidy uint64
|
||||
SubsidyPastRewardMultiplier *big.Rat
|
||||
SubsidyMergeSetRewardMultiplier *big.Rat
|
||||
PreDeflationaryPhaseBaseSubsidy uint64
|
||||
DeflationaryPhaseBaseSubsidy uint64
|
||||
|
||||
// TargetTimePerBlock is the desired amount of time to generate each
|
||||
// block.
|
||||
@@ -180,12 +178,9 @@ type Params struct {
|
||||
// PruningProofM is the 'm' constant in the pruning proof. For more details see: https://github.com/kaspanet/research/issues/3
|
||||
PruningProofM uint64
|
||||
|
||||
// BaseSubsidy is the starting subsidy amount for mined blocks.
|
||||
BaseSubsidy uint64
|
||||
|
||||
FixedSubsidySwitchPruningPointInterval uint64
|
||||
|
||||
FixedSubsidySwitchHashRateThreshold *big.Int
|
||||
// DeflationaryPhaseDaaScore is the DAA score after which the monetary policy switches
|
||||
// to its deflationary phase
|
||||
DeflationaryPhaseDaaScore uint64
|
||||
|
||||
DisallowDirectBlocksOnTopOfGenesis bool
|
||||
|
||||
@@ -221,8 +216,6 @@ var MainnetParams = Params{
|
||||
"mainnet-dnsseed-1.kaspanet.org",
|
||||
// This DNS seeder is run by Denis Mashkevich
|
||||
"mainnet-dnsseed-2.kaspanet.org",
|
||||
// This DNS seeder is run by Elichai Turkel
|
||||
"kaspa.turkel.in",
|
||||
// This DNS seeder is run by Constantine Bytensky
|
||||
"dnsseed.cbytensky.org",
|
||||
// This DNS seeder is run by Georges Künzli
|
||||
@@ -243,10 +236,8 @@ var MainnetParams = Params{
|
||||
PowMax: mainPowMax,
|
||||
BlockCoinbaseMaturity: 100,
|
||||
SubsidyGenesisReward: defaultSubsidyGenesisReward,
|
||||
MinSubsidy: defaultMinSubsidy,
|
||||
MaxSubsidy: defaultMaxSubsidy,
|
||||
SubsidyPastRewardMultiplier: big.NewRat(9, 10),
|
||||
SubsidyMergeSetRewardMultiplier: big.NewRat(1, 10),
|
||||
PreDeflationaryPhaseBaseSubsidy: defaultPreDeflationaryPhaseBaseSubsidy,
|
||||
DeflationaryPhaseBaseSubsidy: defaultDeflationaryPhaseBaseSubsidy,
|
||||
TargetTimePerBlock: defaultTargetTimePerBlock,
|
||||
FinalityDuration: defaultFinalityDuration,
|
||||
DifficultyAdjustmentWindowSize: defaultDifficultyAdjustmentWindowSize,
|
||||
@@ -286,19 +277,18 @@ var MainnetParams = Params{
|
||||
MergeSetSizeLimit: defaultMergeSetSizeLimit,
|
||||
CoinbasePayloadScriptPublicKeyMaxLength: defaultCoinbasePayloadScriptPublicKeyMaxLength,
|
||||
PruningProofM: defaultPruningProofM,
|
||||
FixedSubsidySwitchPruningPointInterval: defaultFixedSubsidySwitchPruningPointInterval,
|
||||
FixedSubsidySwitchHashRateThreshold: big.NewInt(150_000_000_000),
|
||||
DeflationaryPhaseDaaScore: defaultDeflationaryPhaseDaaScore,
|
||||
DisallowDirectBlocksOnTopOfGenesis: true,
|
||||
}
|
||||
|
||||
// TestnetParams defines the network parameters for the test Kaspa network.
|
||||
var TestnetParams = Params{
|
||||
K: defaultGHOSTDAGK,
|
||||
Name: "kaspa-testnet-7",
|
||||
Name: "kaspa-testnet-8",
|
||||
Net: appmessage.Testnet,
|
||||
RPCPort: "16210",
|
||||
DefaultPort: "16211",
|
||||
DNSSeeds: []string{"testnet-7-dnsseed.daglabs-dev.com"},
|
||||
DNSSeeds: []string{"testnet-8-dnsseed.daglabs-dev.com"},
|
||||
|
||||
// DAG parameters
|
||||
GenesisBlock: &testnetGenesisBlock,
|
||||
@@ -306,10 +296,8 @@ var TestnetParams = Params{
|
||||
PowMax: testnetPowMax,
|
||||
BlockCoinbaseMaturity: 100,
|
||||
SubsidyGenesisReward: defaultSubsidyGenesisReward,
|
||||
MinSubsidy: defaultMinSubsidy,
|
||||
MaxSubsidy: defaultMaxSubsidy,
|
||||
SubsidyPastRewardMultiplier: big.NewRat(9, 10),
|
||||
SubsidyMergeSetRewardMultiplier: big.NewRat(1, 10),
|
||||
PreDeflationaryPhaseBaseSubsidy: defaultPreDeflationaryPhaseBaseSubsidy,
|
||||
DeflationaryPhaseBaseSubsidy: defaultDeflationaryPhaseBaseSubsidy,
|
||||
TargetTimePerBlock: defaultTargetTimePerBlock,
|
||||
FinalityDuration: defaultFinalityDuration,
|
||||
DifficultyAdjustmentWindowSize: defaultDifficultyAdjustmentWindowSize,
|
||||
@@ -349,8 +337,7 @@ var TestnetParams = Params{
|
||||
MergeSetSizeLimit: defaultMergeSetSizeLimit,
|
||||
CoinbasePayloadScriptPublicKeyMaxLength: defaultCoinbasePayloadScriptPublicKeyMaxLength,
|
||||
PruningProofM: defaultPruningProofM,
|
||||
FixedSubsidySwitchPruningPointInterval: defaultFixedSubsidySwitchPruningPointInterval,
|
||||
FixedSubsidySwitchHashRateThreshold: big.NewInt(150_000_000_000),
|
||||
DeflationaryPhaseDaaScore: defaultDeflationaryPhaseDaaScore,
|
||||
IgnoreHeaderMass: true,
|
||||
}
|
||||
|
||||
@@ -375,10 +362,8 @@ var SimnetParams = Params{
|
||||
PowMax: simnetPowMax,
|
||||
BlockCoinbaseMaturity: 100,
|
||||
SubsidyGenesisReward: defaultSubsidyGenesisReward,
|
||||
MinSubsidy: defaultMinSubsidy,
|
||||
MaxSubsidy: defaultMaxSubsidy,
|
||||
SubsidyPastRewardMultiplier: big.NewRat(9, 10),
|
||||
SubsidyMergeSetRewardMultiplier: big.NewRat(1, 10),
|
||||
PreDeflationaryPhaseBaseSubsidy: defaultPreDeflationaryPhaseBaseSubsidy,
|
||||
DeflationaryPhaseBaseSubsidy: defaultDeflationaryPhaseBaseSubsidy,
|
||||
TargetTimePerBlock: time.Millisecond,
|
||||
FinalityDuration: time.Minute,
|
||||
DifficultyAdjustmentWindowSize: defaultDifficultyAdjustmentWindowSize,
|
||||
@@ -416,8 +401,7 @@ var SimnetParams = Params{
|
||||
MergeSetSizeLimit: defaultMergeSetSizeLimit,
|
||||
CoinbasePayloadScriptPublicKeyMaxLength: defaultCoinbasePayloadScriptPublicKeyMaxLength,
|
||||
PruningProofM: defaultPruningProofM,
|
||||
FixedSubsidySwitchPruningPointInterval: defaultFixedSubsidySwitchPruningPointInterval,
|
||||
FixedSubsidySwitchHashRateThreshold: big.NewInt(150_000_000_000),
|
||||
DeflationaryPhaseDaaScore: defaultDeflationaryPhaseDaaScore,
|
||||
}
|
||||
|
||||
// DevnetParams defines the network parameters for the development Kaspa network.
|
||||
@@ -435,10 +419,8 @@ var DevnetParams = Params{
|
||||
PowMax: devnetPowMax,
|
||||
BlockCoinbaseMaturity: 100,
|
||||
SubsidyGenesisReward: defaultSubsidyGenesisReward,
|
||||
MinSubsidy: defaultMinSubsidy,
|
||||
MaxSubsidy: defaultMaxSubsidy,
|
||||
SubsidyPastRewardMultiplier: big.NewRat(9, 10),
|
||||
SubsidyMergeSetRewardMultiplier: big.NewRat(1, 10),
|
||||
PreDeflationaryPhaseBaseSubsidy: defaultPreDeflationaryPhaseBaseSubsidy,
|
||||
DeflationaryPhaseBaseSubsidy: defaultDeflationaryPhaseBaseSubsidy,
|
||||
TargetTimePerBlock: defaultTargetTimePerBlock,
|
||||
FinalityDuration: defaultFinalityDuration,
|
||||
DifficultyAdjustmentWindowSize: defaultDifficultyAdjustmentWindowSize,
|
||||
@@ -478,8 +460,7 @@ var DevnetParams = Params{
|
||||
MergeSetSizeLimit: defaultMergeSetSizeLimit,
|
||||
CoinbasePayloadScriptPublicKeyMaxLength: defaultCoinbasePayloadScriptPublicKeyMaxLength,
|
||||
PruningProofM: defaultPruningProofM,
|
||||
FixedSubsidySwitchPruningPointInterval: defaultFixedSubsidySwitchPruningPointInterval,
|
||||
FixedSubsidySwitchHashRateThreshold: big.NewInt(150_000_000_000),
|
||||
DeflationaryPhaseDaaScore: defaultDeflationaryPhaseDaaScore,
|
||||
IgnoreHeaderMass: true,
|
||||
}
|
||||
|
||||
|
||||
@@ -49,7 +49,6 @@ func TestCreateStagingConsensus(t *testing.T) {
|
||||
addGenesisToStagingConsensus := func() {
|
||||
genesisWithTrustedData := &externalapi.BlockWithTrustedData{
|
||||
Block: consensusConfig.GenesisBlock,
|
||||
DAAScore: 0,
|
||||
DAAWindow: nil,
|
||||
GHOSTDAGData: []*externalapi.BlockGHOSTDAGDataHashPair{
|
||||
{
|
||||
|
||||
@@ -52,6 +52,7 @@ const (
|
||||
defaultSigCacheMaxSize = 100000
|
||||
sampleConfigFilename = "sample-kaspad.conf"
|
||||
defaultMaxUTXOCacheSize = 5000000000
|
||||
defaultProtocolVersion = 4
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -122,6 +123,7 @@ type Flags struct {
|
||||
IsArchivalNode bool `long:"archival" description:"Run as an archival node: don't delete old block data when moving the pruning point (Warning: heavy disk usage)'"`
|
||||
AllowSubmitBlockWhenNotSynced bool `long:"allow-submit-block-when-not-synced" hidden:"true" description:"Allow the node to accept blocks from RPC while not synced (this flag is mainly used for testing)"`
|
||||
EnableSanityCheckPruningUTXOSet bool `long:"enable-sanity-check-pruning-utxo" hidden:"true" description:"When moving the pruning point - check that the utxo set matches the utxo commitment"`
|
||||
ProtocolVersion uint32 `long:"protocol-version" description:"Use non default p2p protocol version"`
|
||||
NetworkFlags
|
||||
ServiceOptions *ServiceOptions
|
||||
}
|
||||
@@ -188,6 +190,7 @@ func defaultFlags() *Flags {
|
||||
MinRelayTxFee: defaultMinRelayTxFee,
|
||||
MaxUTXOCacheSize: defaultMaxUTXOCacheSize,
|
||||
ServiceOptions: &ServiceOptions{},
|
||||
ProtocolVersion: defaultProtocolVersion,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,12 +15,14 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const maxAddresses = 4096
|
||||
const (
|
||||
maxAddresses = 4096
|
||||
connectionFailedCountForRemove = 4
|
||||
)
|
||||
|
||||
// addressRandomizer is the interface for the randomizer needed for the AddressManager.
|
||||
type addressRandomizer interface {
|
||||
RandomAddress(addresses []*appmessage.NetAddress) *appmessage.NetAddress
|
||||
RandomAddresses(addresses []*appmessage.NetAddress, count int) []*appmessage.NetAddress
|
||||
RandomAddresses(addresses []*address, count int) []*appmessage.NetAddress
|
||||
}
|
||||
|
||||
// addressKey represents a pair of IP and port, the IP is always in V6 representation
|
||||
@@ -76,7 +78,7 @@ func New(cfg *Config, database database.Database) (*AddressManager, error) {
|
||||
return &AddressManager{
|
||||
store: addressStore,
|
||||
localAddresses: localAddresses,
|
||||
random: NewAddressRandomize(),
|
||||
random: NewAddressRandomize(connectionFailedCountForRemove),
|
||||
cfg: cfg,
|
||||
}, nil
|
||||
}
|
||||
@@ -87,7 +89,8 @@ func (am *AddressManager) addAddressNoLock(netAddress *appmessage.NetAddress) er
|
||||
}
|
||||
|
||||
key := netAddressKey(netAddress)
|
||||
address := &address{netAddress: netAddress, connectionFailedCount: 0}
|
||||
// We mark `connectionFailedCount` as 0 only after first success
|
||||
address := &address{netAddress: netAddress, connectionFailedCount: 1}
|
||||
err := am.store.add(key, address)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -105,8 +108,7 @@ func (am *AddressManager) addAddressNoLock(netAddress *appmessage.NetAddress) er
|
||||
}
|
||||
}
|
||||
|
||||
toRemoveKey := netAddressKey(toRemove.netAddress)
|
||||
err := am.store.remove(toRemoveKey)
|
||||
err := am.removeAddressNoLock(toRemove.netAddress)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -161,6 +163,12 @@ func (am *AddressManager) MarkConnectionFailure(address *appmessage.NetAddress)
|
||||
return errors.Errorf("address %s is not registered with the address manager", address.TCPAddress())
|
||||
}
|
||||
entry.connectionFailedCount = entry.connectionFailedCount + 1
|
||||
|
||||
if entry.connectionFailedCount >= connectionFailedCountForRemove {
|
||||
log.Debugf("Address %s has failed %d connection attempts - removing from address manager",
|
||||
address, entry.connectionFailedCount)
|
||||
return am.store.remove(key)
|
||||
}
|
||||
return am.store.updateNotBanned(key, entry)
|
||||
}
|
||||
|
||||
@@ -196,19 +204,13 @@ func (am *AddressManager) BannedAddresses() []*appmessage.NetAddress {
|
||||
}
|
||||
|
||||
// notBannedAddressesWithException returns all not banned addresses with excpetion
|
||||
func (am *AddressManager) notBannedAddressesWithException(exceptions []*appmessage.NetAddress) []*appmessage.NetAddress {
|
||||
func (am *AddressManager) notBannedAddressesWithException(exceptions []*appmessage.NetAddress) []*address {
|
||||
am.mutex.Lock()
|
||||
defer am.mutex.Unlock()
|
||||
|
||||
return am.store.getAllNotBannedNetAddressesWithout(exceptions)
|
||||
}
|
||||
|
||||
// RandomAddress returns a random address that isn't banned and isn't in exceptions
|
||||
func (am *AddressManager) RandomAddress(exceptions []*appmessage.NetAddress) *appmessage.NetAddress {
|
||||
validAddresses := am.notBannedAddressesWithException(exceptions)
|
||||
return am.random.RandomAddress(validAddresses)
|
||||
}
|
||||
|
||||
// RandomAddresses returns count addresses at random that aren't banned and aren't in exceptions
|
||||
func (am *AddressManager) RandomAddresses(count int, exceptions []*appmessage.NetAddress) []*appmessage.NetAddress {
|
||||
validAddresses := am.notBannedAddressesWithException(exceptions)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package addressmanager
|
||||
|
||||
import (
|
||||
"math"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
@@ -9,38 +10,54 @@ import (
|
||||
|
||||
// AddressRandomize implement addressRandomizer interface
|
||||
type AddressRandomize struct {
|
||||
random *rand.Rand
|
||||
random *rand.Rand
|
||||
maxFailedCount uint64
|
||||
}
|
||||
|
||||
// NewAddressRandomize returns a new RandomizeAddress.
|
||||
func NewAddressRandomize() *AddressRandomize {
|
||||
func NewAddressRandomize(maxFailedCount uint64) *AddressRandomize {
|
||||
return &AddressRandomize{
|
||||
random: rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||
random: rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||
maxFailedCount: maxFailedCount,
|
||||
}
|
||||
}
|
||||
|
||||
// RandomAddress returns a random address from input list
|
||||
func (amc *AddressRandomize) RandomAddress(addresses []*appmessage.NetAddress) *appmessage.NetAddress {
|
||||
if len(addresses) > 0 {
|
||||
randomIndex := rand.Intn(len(addresses))
|
||||
return addresses[randomIndex]
|
||||
// weightedRand is a help function which returns a random index in the
|
||||
// range [0, len(weights)-1] with probability weighted by `weights`
|
||||
func weightedRand(weights []float32) int {
|
||||
sum := float32(0)
|
||||
for _, weight := range weights {
|
||||
sum += weight
|
||||
}
|
||||
|
||||
return nil
|
||||
randPoint := rand.Float32()
|
||||
scanPoint := float32(0)
|
||||
for i, weight := range weights {
|
||||
normalizedWeight := weight / sum
|
||||
scanPoint += normalizedWeight
|
||||
if randPoint <= scanPoint {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return len(weights) - 1
|
||||
}
|
||||
|
||||
// RandomAddresses returns count addresses at random from input list
|
||||
func (amc *AddressRandomize) RandomAddresses(addresses []*appmessage.NetAddress, count int) []*appmessage.NetAddress {
|
||||
func (amc *AddressRandomize) RandomAddresses(addresses []*address, count int) []*appmessage.NetAddress {
|
||||
if len(addresses) < count {
|
||||
count = len(addresses)
|
||||
}
|
||||
|
||||
result := make([]*appmessage.NetAddress, 0, count)
|
||||
|
||||
randomIndexes := rand.Perm(len(addresses))
|
||||
for i := 0; i < count; i++ {
|
||||
result = append(result, addresses[randomIndexes[i]])
|
||||
weights := make([]float32, 0, len(addresses))
|
||||
for _, addr := range addresses {
|
||||
weights = append(weights, float32(math.Pow(64, float64(amc.maxFailedCount-addr.connectionFailedCount))))
|
||||
}
|
||||
result := make([]*appmessage.NetAddress, 0, count)
|
||||
for count > 0 {
|
||||
i := weightedRand(weights)
|
||||
result = append(result, addresses[i].netAddress)
|
||||
// Zero entry i to avoid re-selection
|
||||
weights[i] = 0
|
||||
// Update count
|
||||
count--
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -144,13 +144,13 @@ func (as *addressStore) getAllNotBannedNetAddresses() []*appmessage.NetAddress {
|
||||
return addresses
|
||||
}
|
||||
|
||||
func (as *addressStore) getAllNotBannedNetAddressesWithout(ignoredAddresses []*appmessage.NetAddress) []*appmessage.NetAddress {
|
||||
func (as *addressStore) getAllNotBannedNetAddressesWithout(ignoredAddresses []*appmessage.NetAddress) []*address {
|
||||
ignoredKeys := netAddressesKeys(ignoredAddresses)
|
||||
|
||||
addresses := make([]*appmessage.NetAddress, 0, len(as.notBannedAddresses))
|
||||
addresses := make([]*address, 0, len(as.notBannedAddresses))
|
||||
for key, address := range as.notBannedAddresses {
|
||||
if !ignoredKeys[key] {
|
||||
addresses = append(addresses, address.netAddress)
|
||||
addresses = append(addresses, address)
|
||||
}
|
||||
}
|
||||
return addresses
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
package connmanager
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/dnsseed"
|
||||
"github.com/pkg/errors"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/dnsseed"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/addressmanager"
|
||||
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter"
|
||||
@@ -123,8 +124,6 @@ func (c *ConnectionManager) connectionsLoop() {
|
||||
|
||||
c.checkIncomingConnections(connSet)
|
||||
|
||||
c.seedFromDNS()
|
||||
|
||||
c.waitTillNextIteration()
|
||||
}
|
||||
}
|
||||
@@ -257,8 +256,6 @@ func (c *ConnectionManager) extractAddressIPs(address string) ([]net.IP, error)
|
||||
func (c *ConnectionManager) seedFromDNS() {
|
||||
cfg := c.cfg
|
||||
if len(c.activeOutgoing) == 0 && !cfg.DisableDNSSeed {
|
||||
log.Infof("No ongoing connections, trying to get new addresses from seed...")
|
||||
|
||||
dnsseed.SeedFromDNS(cfg.NetParams(), cfg.DNSSeed, false, nil,
|
||||
cfg.Lookup, func(addresses []*appmessage.NetAddress) {
|
||||
// Kaspad uses a lookup of the dns seeder here. Since seeder returns
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user