[NOD-1496] Implement headers only verification (#987)

* [NOD-1496] Implement headers only verification

* [NOD-1496] Add checkParentsExist

* [NOD-1496] Stage block statuses in block processor

* [NOD-1496] Rename AddBlock->AddHeaderTip

* [NOD-1496] Return early from validateAndInsertBlock on header only and put ValidateProofOfWorkAndDifficulty inside validateBlock
This commit is contained in:
Ori Newman
2020-11-02 06:30:59 -08:00
committed by GitHub
parent c5707f64dc
commit 2abd4a274b
20 changed files with 425 additions and 73 deletions

View File

@@ -8,7 +8,7 @@ import (
// Consensus maintains the current core state of the node
type Consensus interface {
BuildBlock(coinbaseData *externalapi.DomainCoinbaseData, transactions []*externalapi.DomainTransaction) (*externalapi.DomainBlock, error)
ValidateAndInsertBlock(block *externalapi.DomainBlock) error
ValidateAndInsertBlock(block *externalapi.DomainBlock, headerOnly bool) error
ValidateTransactionAndPopulateWithConsensusData(transaction *externalapi.DomainTransaction) error
GetBlock(blockHash *externalapi.DomainHash) (*externalapi.DomainBlock, error)
@@ -50,8 +50,8 @@ func (s *consensus) BuildBlock(coinbaseData *externalapi.DomainCoinbaseData,
// ValidateAndInsertBlock validates the given block and, if valid, applies it
// to the current state
func (s *consensus) ValidateAndInsertBlock(block *externalapi.DomainBlock) error {
return s.blockProcessor.ValidateAndInsertBlock(block)
func (s *consensus) ValidateAndInsertBlock(block *externalapi.DomainBlock, headerOnly bool) error {
return s.blockProcessor.ValidateAndInsertBlock(block, headerOnly)
}
// ValidateTransactionAndPopulateWithConsensusData validates the given transaction

View File

@@ -0,0 +1,17 @@
package serialization
import (
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
)
// HeaderTipsToDBHeaderTips converts a slice of hashes to DbHeaderTips
func HeaderTipsToDBHeaderTips(tips []*externalapi.DomainHash) *DbHeaderTips {
return &DbHeaderTips{
Tips: DomainHashesToDbHashes(tips),
}
}
// DBHeaderTipsTOHeaderTips converts DbHeaderTips to a slice of hashes
func DBHeaderTipsTOHeaderTips(dbHeaderTips *DbHeaderTips) ([]*externalapi.DomainHash, error) {
return DbHashesToDomainHashes(dbHeaderTips.Tips)
}

View File

@@ -1441,7 +1441,7 @@ func (x *DbUtxoDiff) GetToRemove() []*DbUtxoCollectionItem {
return nil
}
type PruningPointUTXOSetBytes struct {
type DbPruningPointUTXOSetBytes struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
@@ -1449,8 +1449,8 @@ type PruningPointUTXOSetBytes struct {
Bytes []byte `protobuf:"bytes,1,opt,name=bytes,proto3" json:"bytes,omitempty"`
}
func (x *PruningPointUTXOSetBytes) Reset() {
*x = PruningPointUTXOSetBytes{}
func (x *DbPruningPointUTXOSetBytes) Reset() {
*x = DbPruningPointUTXOSetBytes{}
if protoimpl.UnsafeEnabled {
mi := &file_messages_proto_msgTypes[24]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
@@ -1458,13 +1458,13 @@ func (x *PruningPointUTXOSetBytes) Reset() {
}
}
func (x *PruningPointUTXOSetBytes) String() string {
func (x *DbPruningPointUTXOSetBytes) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*PruningPointUTXOSetBytes) ProtoMessage() {}
func (*DbPruningPointUTXOSetBytes) ProtoMessage() {}
func (x *PruningPointUTXOSetBytes) ProtoReflect() protoreflect.Message {
func (x *DbPruningPointUTXOSetBytes) ProtoReflect() protoreflect.Message {
mi := &file_messages_proto_msgTypes[24]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
@@ -1476,18 +1476,65 @@ func (x *PruningPointUTXOSetBytes) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
// Deprecated: Use PruningPointUTXOSetBytes.ProtoReflect.Descriptor instead.
func (*PruningPointUTXOSetBytes) Descriptor() ([]byte, []int) {
// Deprecated: Use DbPruningPointUTXOSetBytes.ProtoReflect.Descriptor instead.
func (*DbPruningPointUTXOSetBytes) Descriptor() ([]byte, []int) {
return file_messages_proto_rawDescGZIP(), []int{24}
}
func (x *PruningPointUTXOSetBytes) GetBytes() []byte {
func (x *DbPruningPointUTXOSetBytes) GetBytes() []byte {
if x != nil {
return x.Bytes
}
return nil
}
type DbHeaderTips struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Tips []*DbHash `protobuf:"bytes,1,rep,name=tips,proto3" json:"tips,omitempty"`
}
func (x *DbHeaderTips) Reset() {
*x = DbHeaderTips{}
if protoimpl.UnsafeEnabled {
mi := &file_messages_proto_msgTypes[25]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *DbHeaderTips) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*DbHeaderTips) ProtoMessage() {}
func (x *DbHeaderTips) ProtoReflect() protoreflect.Message {
mi := &file_messages_proto_msgTypes[25]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use DbHeaderTips.ProtoReflect.Descriptor instead.
func (*DbHeaderTips) Descriptor() ([]byte, []int) {
return file_messages_proto_rawDescGZIP(), []int{25}
}
func (x *DbHeaderTips) GetTips() []*DbHash {
if x != nil {
return x.Tips
}
return nil
}
var File_messages_proto protoreflect.FileDescriptor
var file_messages_proto_rawDesc = []byte{
@@ -1700,14 +1747,18 @@ var file_messages_proto_rawDesc = []byte{
0x08, 0x74, 0x6f, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32,
0x23, 0x2e, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e,
0x44, 0x62, 0x55, 0x74, 0x78, 0x6f, 0x43, 0x6f, 0x6c, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e,
0x49, 0x74, 0x65, 0x6d, 0x52, 0x08, 0x74, 0x6f, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x22, 0x30,
0x0a, 0x18, 0x50, 0x72, 0x75, 0x6e, 0x69, 0x6e, 0x67, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x55, 0x54,
0x58, 0x4f, 0x53, 0x65, 0x74, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x62, 0x79,
0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73,
0x42, 0x2a, 0x5a, 0x28, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6b,
0x61, 0x73, 0x70, 0x61, 0x6e, 0x65, 0x74, 0x2f, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x64, 0x2f, 0x73,
0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x62, 0x06, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x33,
0x49, 0x74, 0x65, 0x6d, 0x52, 0x08, 0x74, 0x6f, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x22, 0x32,
0x0a, 0x1a, 0x44, 0x62, 0x50, 0x72, 0x75, 0x6e, 0x69, 0x6e, 0x67, 0x50, 0x6f, 0x69, 0x6e, 0x74,
0x55, 0x54, 0x58, 0x4f, 0x53, 0x65, 0x74, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05,
0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x62, 0x79, 0x74,
0x65, 0x73, 0x22, 0x39, 0x0a, 0x0c, 0x44, 0x62, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x54, 0x69,
0x70, 0x73, 0x12, 0x29, 0x0a, 0x04, 0x74, 0x69, 0x70, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b,
0x32, 0x15, 0x2e, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e,
0x2e, 0x44, 0x62, 0x48, 0x61, 0x73, 0x68, 0x52, 0x04, 0x74, 0x69, 0x70, 0x73, 0x42, 0x2a, 0x5a,
0x28, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6b, 0x61, 0x73, 0x70,
0x61, 0x6e, 0x65, 0x74, 0x2f, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x64, 0x2f, 0x73, 0x65, 0x72, 0x69,
0x61, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x33,
}
var (
@@ -1722,7 +1773,7 @@ func file_messages_proto_rawDescGZIP() []byte {
return file_messages_proto_rawDescData
}
var file_messages_proto_msgTypes = make([]protoimpl.MessageInfo, 25)
var file_messages_proto_msgTypes = make([]protoimpl.MessageInfo, 26)
var file_messages_proto_goTypes = []interface{}{
(*DbBlock)(nil), // 0: serialization.DbBlock
(*DbBlockHeader)(nil), // 1: serialization.DbBlockHeader
@@ -1748,7 +1799,8 @@ var file_messages_proto_goTypes = []interface{}{
(*DbReachabilityTreeNode)(nil), // 21: serialization.DbReachabilityTreeNode
(*DbReachabilityInterval)(nil), // 22: serialization.DbReachabilityInterval
(*DbUtxoDiff)(nil), // 23: serialization.DbUtxoDiff
(*PruningPointUTXOSetBytes)(nil), // 24: serialization.PruningPointUTXOSetBytes
(*DbPruningPointUTXOSetBytes)(nil), // 24: serialization.DbPruningPointUTXOSetBytes
(*DbHeaderTips)(nil), // 25: serialization.DbHeaderTips
}
var file_messages_proto_depIdxs = []int32{
1, // 0: serialization.DbBlock.header:type_name -> serialization.DbBlockHeader
@@ -1783,11 +1835,12 @@ var file_messages_proto_depIdxs = []int32{
22, // 29: serialization.DbReachabilityTreeNode.interval:type_name -> serialization.DbReachabilityInterval
18, // 30: serialization.DbUtxoDiff.toAdd:type_name -> serialization.DbUtxoCollectionItem
18, // 31: serialization.DbUtxoDiff.toRemove:type_name -> serialization.DbUtxoCollectionItem
32, // [32:32] is the sub-list for method output_type
32, // [32:32] is the sub-list for method input_type
32, // [32:32] is the sub-list for extension type_name
32, // [32:32] is the sub-list for extension extendee
0, // [0:32] is the sub-list for field type_name
2, // 32: serialization.DbHeaderTips.tips:type_name -> serialization.DbHash
33, // [33:33] is the sub-list for method output_type
33, // [33:33] is the sub-list for method input_type
33, // [33:33] is the sub-list for extension type_name
33, // [33:33] is the sub-list for extension extendee
0, // [0:33] is the sub-list for field type_name
}
func init() { file_messages_proto_init() }
@@ -2085,7 +2138,19 @@ func file_messages_proto_init() {
}
}
file_messages_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*PruningPointUTXOSetBytes); i {
switch v := v.(*DbPruningPointUTXOSetBytes); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_messages_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*DbHeaderTips); i {
case 0:
return &v.state
case 1:
@@ -2103,7 +2168,7 @@ func file_messages_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_messages_proto_rawDesc,
NumEnums: 0,
NumMessages: 25,
NumMessages: 26,
NumExtensions: 0,
NumServices: 0,
},

View File

@@ -135,6 +135,10 @@ message DbUtxoDiff {
repeated DbUtxoCollectionItem toRemove = 2;
}
message PruningPointUTXOSetBytes {
message DbPruningPointUTXOSetBytes {
bytes bytes = 1;
}
message DbHeaderTips {
repeated DbHash tips = 1;
}

View File

@@ -0,0 +1,69 @@
package headertipsstore
import (
"github.com/golang/protobuf/proto"
"github.com/kaspanet/kaspad/domain/consensus/database/serialization"
"github.com/kaspanet/kaspad/domain/consensus/model"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/utils/dbkeys"
)
var headerTipsKey = dbkeys.MakeBucket().Key([]byte("header-tips"))
type headerTipsStore struct {
staging []*externalapi.DomainHash
}
func (h headerTipsStore) Discard() {
h.staging = nil
}
func (h headerTipsStore) Commit(dbTx model.DBTransaction) error {
tipsBytes, err := h.serializeTips(h.staging)
if err != nil {
return err
}
return dbTx.Put(headerTipsKey, tipsBytes)
}
func (h headerTipsStore) Stage(tips []*externalapi.DomainHash) {
h.staging = tips
}
func (h headerTipsStore) IsStaged() bool {
return h.staging != nil
}
func (h headerTipsStore) Tips(dbContext model.DBReader) ([]*externalapi.DomainHash, error) {
if h.staging != nil {
return h.staging, nil
}
tipsBytes, err := dbContext.Get(headerTipsKey)
if err != nil {
return nil, err
}
return h.deserializeTips(tipsBytes)
}
func (h headerTipsStore) serializeTips(tips []*externalapi.DomainHash) ([]byte, error) {
dbTips := serialization.HeaderTipsToDBHeaderTips(tips)
return proto.Marshal(dbTips)
}
func (h headerTipsStore) deserializeTips(tipsBytes []byte) ([]*externalapi.DomainHash, error) {
dbTips := &serialization.DbHeaderTips{}
err := proto.Unmarshal(tipsBytes, dbTips)
if err != nil {
return nil, err
}
return serialization.DBHeaderTipsTOHeaderTips(dbTips)
}
// New instantiates a new HeaderTipsStore
func New() model.HeaderTipsStore {
return &headerTipsStore{}
}

View File

@@ -106,11 +106,11 @@ func (ps *pruningStore) deserializePruningPoint(pruningPointBytes []byte) (*exte
}
func (ps *pruningStore) serializeUTXOSetBytes(pruningPointUTXOSetBytes []byte) ([]byte, error) {
return proto.Marshal(&serialization.PruningPointUTXOSetBytes{Bytes: pruningPointUTXOSetBytes})
return proto.Marshal(&serialization.DbPruningPointUTXOSetBytes{Bytes: pruningPointUTXOSetBytes})
}
func (ps *pruningStore) deserializeUTXOSetBytes(dbPruningPointUTXOSetBytes []byte) ([]byte, error) {
dbPruningPointUTXOSet := &serialization.PruningPointUTXOSetBytes{}
dbPruningPointUTXOSet := &serialization.DbPruningPointUTXOSetBytes{}
err := proto.Unmarshal(dbPruningPointUTXOSetBytes, dbPruningPointUTXOSet)
if err != nil {
return nil, err

View File

@@ -9,6 +9,7 @@ import (
"github.com/kaspanet/kaspad/domain/consensus/datastructures/blockstore"
"github.com/kaspanet/kaspad/domain/consensus/datastructures/consensusstatestore"
"github.com/kaspanet/kaspad/domain/consensus/datastructures/ghostdagdatastore"
"github.com/kaspanet/kaspad/domain/consensus/datastructures/headertipsstore"
"github.com/kaspanet/kaspad/domain/consensus/datastructures/multisetstore"
"github.com/kaspanet/kaspad/domain/consensus/datastructures/pruningstore"
"github.com/kaspanet/kaspad/domain/consensus/datastructures/reachabilitydatastore"
@@ -23,6 +24,7 @@ import (
"github.com/kaspanet/kaspad/domain/consensus/processes/dagtraversalmanager"
"github.com/kaspanet/kaspad/domain/consensus/processes/difficultymanager"
"github.com/kaspanet/kaspad/domain/consensus/processes/ghostdagmanager"
"github.com/kaspanet/kaspad/domain/consensus/processes/headertipsmanager"
"github.com/kaspanet/kaspad/domain/consensus/processes/mergedepthmanager"
"github.com/kaspanet/kaspad/domain/consensus/processes/pastmediantimemanager"
"github.com/kaspanet/kaspad/domain/consensus/processes/pruningmanager"
@@ -54,6 +56,7 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, db infrastructuredat
utxoDiffStore := utxodiffstore.New()
consensusStateStore := consensusstatestore.New()
ghostdagDataStore := ghostdagdatastore.New()
headerTipsStore := headertipsstore.New()
dbManager := consensusdatabase.New(db)
@@ -97,6 +100,7 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, db infrastructuredat
coinbaseManager := coinbasemanager.New(
ghostdagDataStore,
acceptanceDataStore)
headerTipsManager := headertipsmanager.New(dbManager, dagTopologyManager, headerTipsStore)
genesisHash := externalapi.DomainHash(*dagParams.GenesisHash)
mergeDepthManager := mergedepthmanager.New(
dagParams.FinalityDepth(),
@@ -124,6 +128,7 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, db infrastructuredat
blockStore,
ghostdagDataStore,
blockHeaderStore,
blockStatusStore,
)
consensusStateManager, err := consensusstatemanager.New(
dbManager,
@@ -162,6 +167,8 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, db infrastructuredat
pastMedianTimeManager,
ghostdagManager,
coinbaseManager,
headerTipsManager,
acceptanceDataStore,
blockStore,
blockStatusStore,
@@ -172,7 +179,8 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, db infrastructuredat
pruningStore,
reachabilityDataStore,
utxoDiffStore,
blockHeaderStore)
blockHeaderStore,
headerTipsStore)
syncManager := syncmanager.New(dagTraversalManager)

View File

@@ -4,8 +4,6 @@ package externalapi
type DomainBlock struct {
Header *DomainBlockHeader
Transactions []*DomainTransaction
Hash *DomainHash
}
// DomainBlockHeader represents the header part of a Kaspa block

View File

@@ -13,10 +13,6 @@ const (
// StatusValidateFailed indicates that the block has failed validation.
StatusValidateFailed
// StatusInvalidAncestor indicates that one of the block's ancestors has
// has failed validation, thus the block is also invalid.
StatusInvalidAncestor
// StatusUTXOPendingVerification indicates that the block is pending verification against its past UTXO-Set, either
// because it was not yet verified since the block was never in the selected parent chain, or if the
// block violates finality.
@@ -24,4 +20,7 @@ const (
// StatusDisqualifiedFromChain indicates that the block is not eligible to be a selected parent.
StatusDisqualifiedFromChain
// StatusHeaderOnly indicates that the block transactions are not held (pruned or wasn't added yet)
StatusHeaderOnly
)

View File

@@ -0,0 +1,11 @@
package model
import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
// HeaderTipsStore represents a store of the header tips
type HeaderTipsStore interface {
Store
Stage(tips []*externalapi.DomainHash)
IsStaged() bool
Tips(dbContext DBReader) ([]*externalapi.DomainHash, error)
}

View File

@@ -6,5 +6,5 @@ import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
// and creating blocks from the current state
type BlockProcessor interface {
BuildBlock(coinbaseData *externalapi.DomainCoinbaseData, transactions []*externalapi.DomainTransaction) (*externalapi.DomainBlock, error)
ValidateAndInsertBlock(block *externalapi.DomainBlock) error
ValidateAndInsertBlock(block *externalapi.DomainBlock, headerOnly bool) error
}

View File

@@ -0,0 +1,8 @@
package model
import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
// HeaderTipsManager manages the state of the header tips
type HeaderTipsManager interface {
AddHeaderTip(hash *externalapi.DomainHash) error
}

View File

@@ -22,6 +22,7 @@ type blockProcessor struct {
ghostdagManager model.GHOSTDAGManager
pastMedianTimeManager model.PastMedianTimeManager
coinbaseManager model.CoinbaseManager
headerTipsManager model.HeaderTipsManager
acceptanceDataStore model.AcceptanceDataStore
blockStore model.BlockStore
@@ -34,6 +35,7 @@ type blockProcessor struct {
reachabilityDataStore model.ReachabilityDataStore
utxoDiffStore model.UTXODiffStore
blockHeaderStore model.BlockHeaderStore
headerTipsStore model.HeaderTipsStore
stores []model.Store
}
@@ -51,6 +53,8 @@ func New(
pastMedianTimeManager model.PastMedianTimeManager,
ghostdagManager model.GHOSTDAGManager,
coinbaseManager model.CoinbaseManager,
headerTipsManager model.HeaderTipsManager,
acceptanceDataStore model.AcceptanceDataStore,
blockStore model.BlockStore,
blockStatusStore model.BlockStatusStore,
@@ -61,7 +65,8 @@ func New(
pruningStore model.PruningStore,
reachabilityDataStore model.ReachabilityDataStore,
utxoDiffStore model.UTXODiffStore,
blockHeaderStore model.BlockHeaderStore) model.BlockProcessor {
blockHeaderStore model.BlockHeaderStore,
headerTipsStore model.HeaderTipsStore) model.BlockProcessor {
return &blockProcessor{
dagParams: dagParams,
@@ -74,6 +79,7 @@ func New(
pastMedianTimeManager: pastMedianTimeManager,
ghostdagManager: ghostdagManager,
coinbaseManager: coinbaseManager,
headerTipsManager: headerTipsManager,
consensusStateManager: consensusStateManager,
acceptanceDataStore: acceptanceDataStore,
@@ -87,6 +93,7 @@ func New(
reachabilityDataStore: reachabilityDataStore,
utxoDiffStore: utxoDiffStore,
blockHeaderStore: blockHeaderStore,
headerTipsStore: headerTipsStore,
stores: []model.Store{
consensusStateStore,
@@ -101,6 +108,7 @@ func New(
reachabilityDataStore,
utxoDiffStore,
blockHeaderStore,
headerTipsStore,
},
}
}
@@ -118,9 +126,9 @@ func (bp *blockProcessor) BuildBlock(coinbaseData *externalapi.DomainCoinbaseDat
// ValidateAndInsertBlock validates the given block and, if valid, applies it
// to the current state
func (bp *blockProcessor) ValidateAndInsertBlock(block *externalapi.DomainBlock) error {
func (bp *blockProcessor) ValidateAndInsertBlock(block *externalapi.DomainBlock, headerOnly bool) error {
onEnd := logger.LogAndMeasureExecutionTime(log, "ValidateAndInsertBlock")
defer onEnd()
return bp.validateAndInsertBlock(block)
return bp.validateAndInsertBlock(block, headerOnly)
}

View File

@@ -26,12 +26,10 @@ func (bp *blockProcessor) buildBlock(coinbaseData *externalapi.DomainCoinbaseDat
if err != nil {
return nil, err
}
headerHash := hashserialization.HeaderHash(header)
return &externalapi.DomainBlock{
Header: header,
Transactions: transactionsWithCoinbase,
Hash: headerHash,
}, nil
}

View File

@@ -3,16 +3,50 @@ package blockprocessor
import (
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
"github.com/kaspanet/kaspad/domain/consensus/utils/hashserialization"
"github.com/pkg/errors"
)
func (bp *blockProcessor) validateAndInsertBlock(block *externalapi.DomainBlock) error {
err := bp.validateBlock(block)
func (bp *blockProcessor) validateAndInsertBlock(block *externalapi.DomainBlock, headerOnly bool) error {
hash := hashserialization.HeaderHash(block.Header)
if headerOnly && len(block.Transactions) != 0 {
return errors.Errorf("block %s contains transactions while validating in header only mode", hash)
}
err := bp.checkBlockStatus(hash, headerOnly)
if err != nil {
return err
}
err = bp.validateBlock(block, headerOnly)
if err != nil {
bp.discardAllChanges()
return err
}
hasHeader, err := bp.hasHeader(hash)
if err != nil {
return err
}
if !hasHeader {
err = bp.reachabilityManager.AddBlock(hash)
if err != nil {
return err
}
err = bp.headerTipsManager.AddHeaderTip(hash)
if err != nil {
return err
}
}
if headerOnly {
bp.blockStatusStore.Stage(hash, externalapi.StatusHeaderOnly)
} else {
bp.blockStatusStore.Stage(hash, externalapi.StatusUTXOPendingVerification)
}
// Block validations passed, save whatever DAG data was
// collected so far
err = bp.commitAllChanges()
@@ -20,8 +54,12 @@ func (bp *blockProcessor) validateAndInsertBlock(block *externalapi.DomainBlock)
return err
}
if headerOnly {
return nil
}
// Attempt to add the block to the virtual
err = bp.consensusStateManager.AddBlockToVirtual(block.Hash)
err = bp.consensusStateManager.AddBlockToVirtual(hash)
if err != nil {
return err
}
@@ -29,22 +67,55 @@ func (bp *blockProcessor) validateAndInsertBlock(block *externalapi.DomainBlock)
return bp.commitAllChanges()
}
func (bp *blockProcessor) validateBlock(block *externalapi.DomainBlock) error {
// If either in-isolation or proof-of-work validations fail, simply
func (bp *blockProcessor) checkBlockStatus(hash *externalapi.DomainHash, headerOnly bool) error {
exists, err := bp.blockStatusStore.Exists(bp.databaseContext, hash)
if err != nil {
return err
}
if !exists {
return nil
}
status, err := bp.blockStatusStore.Get(bp.databaseContext, hash)
if err != nil {
return err
}
if status == externalapi.StatusInvalid {
return errors.Wrapf(ruleerrors.ErrKnownInvalid, "block %s is a known invalid block", hash)
}
if headerOnly || status != externalapi.StatusHeaderOnly {
return errors.Wrapf(ruleerrors.ErrDuplicateBlock, "block %s already exists", hash)
}
return nil
}
func (bp *blockProcessor) validateBlock(block *externalapi.DomainBlock, headerOnly bool) error {
// If any validation until (included) proof-of-work fails, simply
// return an error without writing anything in the database.
// This is to prevent spamming attacks.
err := bp.validateBlockInIsolationAndProofOfWork(block)
err := bp.validatePreProofOfWork(block)
if err != nil {
return err
}
blockHash := hashserialization.HeaderHash(block.Header)
err = bp.blockValidator.ValidateProofOfWorkAndDifficulty(blockHash)
if err != nil {
return err
}
// If in-context validations fail, discard all changes and store the
// block with StatusInvalid.
err = bp.validateInContext(block)
err = bp.validatePostProofOfWork(block, headerOnly)
if err != nil {
if errors.As(err, &ruleerrors.RuleError{}) {
bp.discardAllChanges()
bp.blockStatusStore.Stage(block.Hash, externalapi.StatusInvalid)
hash := hashserialization.HeaderHash(block.Header)
bp.blockStatusStore.Stage(hash, externalapi.StatusInvalid)
commitErr := bp.commitAllChanges()
if commitErr != nil {
return commitErr
@@ -55,42 +126,75 @@ func (bp *blockProcessor) validateBlock(block *externalapi.DomainBlock) error {
return nil
}
func (bp *blockProcessor) validateBlockInIsolationAndProofOfWork(block *externalapi.DomainBlock) error {
err := bp.blockValidator.ValidateHeaderInIsolation(block.Hash)
func (bp *blockProcessor) validatePreProofOfWork(block *externalapi.DomainBlock) error {
blockHash := hashserialization.HeaderHash(block.Header)
hasHeader, err := bp.hasHeader(blockHash)
if err != nil {
return err
}
err = bp.blockValidator.ValidateBodyInIsolation(block.Hash)
if err != nil {
return err
if hasHeader {
return nil
}
err = bp.blockValidator.ValidateProofOfWorkAndDifficulty(block.Hash)
err = bp.blockValidator.ValidateHeaderInIsolation(blockHash)
if err != nil {
return err
}
return nil
}
func (bp *blockProcessor) validateInContext(block *externalapi.DomainBlock) error {
err := bp.dagTopologyManager.SetParents(block.Hash, block.Header.ParentHashes)
func (bp *blockProcessor) validatePostProofOfWork(block *externalapi.DomainBlock, headerOnly bool) error {
blockHash := hashserialization.HeaderHash(block.Header)
if !headerOnly {
bp.blockStore.Stage(blockHash, block)
err := bp.blockValidator.ValidateBodyInIsolation(blockHash)
if err != nil {
return err
}
}
hasHeader, err := bp.hasHeader(blockHash)
if err != nil {
return err
}
bp.blockStore.Stage(block.Hash, block)
bp.blockHeaderStore.Stage(block.Hash, block.Header)
if !hasHeader {
bp.blockHeaderStore.Stage(blockHash, block.Header)
err = bp.blockValidator.ValidateHeaderInContext(block.Hash)
if err != nil {
return err
}
err = bp.blockValidator.ValidateBodyInContext(block.Hash)
if err != nil {
return err
err := bp.dagTopologyManager.SetParents(blockHash, block.Header.ParentHashes)
if err != nil {
return err
}
err = bp.blockValidator.ValidateHeaderInContext(blockHash)
if err != nil {
return err
}
}
return nil
}
func (bp *blockProcessor) hasHeader(blockHash *externalapi.DomainHash) (bool, error) {
exists, err := bp.blockStatusStore.Exists(bp.databaseContext, blockHash)
if err != nil {
return false, err
}
if !exists {
return false, nil
}
status, err := bp.blockStatusStore.Get(bp.databaseContext, blockHash)
if err != nil {
return false, err
}
return status == externalapi.StatusHeaderOnly, nil
}
func (bp *blockProcessor) discardAllChanges() {
for _, store := range bp.stores {
store.Discard()

View File

@@ -26,11 +26,18 @@ func (v *blockValidator) ValidateHeaderInContext(blockHash *externalapi.DomainHa
return err
}
err = v.ghostdagManager.GHOSTDAG(blockHash)
status, err := v.blockStatusStore.Get(v.databaseContext, blockHash)
if err != nil {
return err
}
if status != externalapi.StatusHeaderOnly {
err = v.ghostdagManager.GHOSTDAG(blockHash)
if err != nil {
return err
}
}
err = v.checkMergeSizeLimit(blockHash)
if err != nil {
return err

View File

@@ -31,6 +31,7 @@ type blockValidator struct {
blockStore model.BlockStore
ghostdagDataStore model.GHOSTDAGDataStore
blockHeaderStore model.BlockHeaderStore
blockStatusStore model.BlockStatusStore
}
// New instantiates a new BlockValidator
@@ -52,7 +53,8 @@ func New(powMax *big.Int,
blockStore model.BlockStore,
ghostdagDataStore model.GHOSTDAGDataStore,
blockHeaderStore model.BlockHeaderStore) model.BlockValidator {
blockHeaderStore model.BlockHeaderStore,
blockStatusStore model.BlockStatusStore) model.BlockValidator {
return &blockValidator{
powMax: powMax,
@@ -73,5 +75,6 @@ func New(powMax *big.Int,
blockStore: blockStore,
ghostdagDataStore: ghostdagDataStore,
blockHeaderStore: blockHeaderStore,
blockStatusStore: blockStatusStore,
}
}

View File

@@ -20,6 +20,11 @@ func (v *blockValidator) ValidateProofOfWorkAndDifficulty(blockHash *externalapi
return err
}
err = v.checkParentsExist(header)
if err != nil {
return err
}
err = v.validateDifficulty(blockHash)
if err != nil {
return err
@@ -86,7 +91,7 @@ func (v *blockValidator) checkProofOfWork(header *externalapi.DomainBlockHeader)
func (v *blockValidator) checkParentsExist(header *externalapi.DomainBlockHeader) error {
for _, parent := range header.ParentHashes {
exists, err := v.blockStore.HasBlock(v.databaseContext, parent)
exists, err := v.blockHeaderStore.HasBlockHeader(v.databaseContext, parent)
if err != nil {
return err
}

View File

@@ -0,0 +1,46 @@
package headertipsmanager
import (
"github.com/kaspanet/kaspad/domain/consensus/model"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
)
type headerTipsManager struct {
databaseContext model.DBReader
dagTopologyManager model.DAGTopologyManager
headerTipsStore model.HeaderTipsStore
}
// New instantiates a new HeaderTipsManager
func New(databaseContext model.DBReader,
dagTopologyManager model.DAGTopologyManager,
headerTipsStore model.HeaderTipsStore) model.HeaderTipsManager {
return &headerTipsManager{
databaseContext: databaseContext,
dagTopologyManager: dagTopologyManager,
headerTipsStore: headerTipsStore,
}
}
func (h headerTipsManager) AddHeaderTip(hash *externalapi.DomainHash) error {
tips, err := h.headerTipsStore.Tips(h.databaseContext)
if err != nil {
return err
}
newTips := make([]*externalapi.DomainHash, 0, len(tips)+1)
for _, tip := range tips {
isAncestorOf, err := h.dagTopologyManager.IsAncestorOf(tip, hash)
if err != nil {
return err
}
if !isAncestorOf {
newTips = append(newTips, tip)
}
}
newTips = append(newTips, hash)
h.headerTipsStore.Stage(newTips)
return nil
}

View File

@@ -227,6 +227,8 @@ var (
// ErrMissingParent indicates one of the block parents is not found.
ErrMissingParent = newRuleError("ErrMissingParent")
ErrKnownInvalid = newRuleError("ErrKnownInvalid")
)
// RuleError identifies a rule violation. It is used to indicate that