From 2abd4a274bf9cc989596cc3df73a3d93beffb359 Mon Sep 17 00:00:00 2001 From: Ori Newman Date: Mon, 2 Nov 2020 06:30:59 -0800 Subject: [PATCH] [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 --- domain/consensus/consensus.go | 6 +- .../database/serialization/header_tips.go | 17 ++ .../database/serialization/messages.pb.go | 117 ++++++++++--- .../database/serialization/messages.proto | 6 +- .../headertipsstore/headertipsstore.go | 69 ++++++++ .../pruningstore/pruningstore.go | 4 +- domain/consensus/factory.go | 10 +- domain/consensus/model/externalapi/block.go | 2 - .../model/externalapi/blockstatus.go | 7 +- ...nterface_datastructures_headertipsstore.go | 11 ++ .../interface_processes_blockprocessor.go | 2 +- .../interface_processes_headertipsmanager.go | 8 + .../blockprocessor/blockprocessor.go | 14 +- .../processes/blockprocessor/buildblock.go | 2 - .../blockprocessor/validateandinsertblock.go | 154 +++++++++++++++--- .../blockvalidator/block_header_in_context.go | 9 +- .../blockvalidator/blockvalidator.go | 5 +- .../processes/blockvalidator/proof_of_work.go | 7 +- .../headertipsmanager/headertipsmanager.go | 46 ++++++ domain/consensus/ruleerrors/rule_error.go | 2 + 20 files changed, 425 insertions(+), 73 deletions(-) create mode 100644 domain/consensus/database/serialization/header_tips.go create mode 100644 domain/consensus/datastructures/headertipsstore/headertipsstore.go create mode 100644 domain/consensus/model/interface_datastructures_headertipsstore.go create mode 100644 domain/consensus/model/interface_processes_headertipsmanager.go create mode 100644 domain/consensus/processes/headertipsmanager/headertipsmanager.go diff --git a/domain/consensus/consensus.go b/domain/consensus/consensus.go index fc8f4563f..27b4aa681 100644 --- a/domain/consensus/consensus.go +++ b/domain/consensus/consensus.go @@ -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 diff --git a/domain/consensus/database/serialization/header_tips.go b/domain/consensus/database/serialization/header_tips.go new file mode 100644 index 000000000..320f340ec --- /dev/null +++ b/domain/consensus/database/serialization/header_tips.go @@ -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) +} diff --git a/domain/consensus/database/serialization/messages.pb.go b/domain/consensus/database/serialization/messages.pb.go index 8bb562113..d267f8d44 100644 --- a/domain/consensus/database/serialization/messages.pb.go +++ b/domain/consensus/database/serialization/messages.pb.go @@ -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, }, diff --git a/domain/consensus/database/serialization/messages.proto b/domain/consensus/database/serialization/messages.proto index 7b0112fdb..8e6aa3c14 100644 --- a/domain/consensus/database/serialization/messages.proto +++ b/domain/consensus/database/serialization/messages.proto @@ -135,6 +135,10 @@ message DbUtxoDiff { repeated DbUtxoCollectionItem toRemove = 2; } -message PruningPointUTXOSetBytes { +message DbPruningPointUTXOSetBytes { bytes bytes = 1; +} + +message DbHeaderTips { + repeated DbHash tips = 1; } \ No newline at end of file diff --git a/domain/consensus/datastructures/headertipsstore/headertipsstore.go b/domain/consensus/datastructures/headertipsstore/headertipsstore.go new file mode 100644 index 000000000..341d214ea --- /dev/null +++ b/domain/consensus/datastructures/headertipsstore/headertipsstore.go @@ -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{} +} diff --git a/domain/consensus/datastructures/pruningstore/pruningstore.go b/domain/consensus/datastructures/pruningstore/pruningstore.go index 9b20b2dc3..057f7bccd 100644 --- a/domain/consensus/datastructures/pruningstore/pruningstore.go +++ b/domain/consensus/datastructures/pruningstore/pruningstore.go @@ -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 diff --git a/domain/consensus/factory.go b/domain/consensus/factory.go index ec1f3e210..64cd4e2f3 100644 --- a/domain/consensus/factory.go +++ b/domain/consensus/factory.go @@ -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) diff --git a/domain/consensus/model/externalapi/block.go b/domain/consensus/model/externalapi/block.go index e63488fd2..522465c47 100644 --- a/domain/consensus/model/externalapi/block.go +++ b/domain/consensus/model/externalapi/block.go @@ -4,8 +4,6 @@ package externalapi type DomainBlock struct { Header *DomainBlockHeader Transactions []*DomainTransaction - - Hash *DomainHash } // DomainBlockHeader represents the header part of a Kaspa block diff --git a/domain/consensus/model/externalapi/blockstatus.go b/domain/consensus/model/externalapi/blockstatus.go index 8fb0db67f..3346e7e7e 100644 --- a/domain/consensus/model/externalapi/blockstatus.go +++ b/domain/consensus/model/externalapi/blockstatus.go @@ -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 ) diff --git a/domain/consensus/model/interface_datastructures_headertipsstore.go b/domain/consensus/model/interface_datastructures_headertipsstore.go new file mode 100644 index 000000000..01e501a61 --- /dev/null +++ b/domain/consensus/model/interface_datastructures_headertipsstore.go @@ -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) +} diff --git a/domain/consensus/model/interface_processes_blockprocessor.go b/domain/consensus/model/interface_processes_blockprocessor.go index ee2c1dffb..dd5b5a682 100644 --- a/domain/consensus/model/interface_processes_blockprocessor.go +++ b/domain/consensus/model/interface_processes_blockprocessor.go @@ -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 } diff --git a/domain/consensus/model/interface_processes_headertipsmanager.go b/domain/consensus/model/interface_processes_headertipsmanager.go new file mode 100644 index 000000000..3cdb73307 --- /dev/null +++ b/domain/consensus/model/interface_processes_headertipsmanager.go @@ -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 +} diff --git a/domain/consensus/processes/blockprocessor/blockprocessor.go b/domain/consensus/processes/blockprocessor/blockprocessor.go index ea3a53197..d847f36c6 100644 --- a/domain/consensus/processes/blockprocessor/blockprocessor.go +++ b/domain/consensus/processes/blockprocessor/blockprocessor.go @@ -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) } diff --git a/domain/consensus/processes/blockprocessor/buildblock.go b/domain/consensus/processes/blockprocessor/buildblock.go index d57520cb3..a43a87737 100644 --- a/domain/consensus/processes/blockprocessor/buildblock.go +++ b/domain/consensus/processes/blockprocessor/buildblock.go @@ -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 } diff --git a/domain/consensus/processes/blockprocessor/validateandinsertblock.go b/domain/consensus/processes/blockprocessor/validateandinsertblock.go index 2e4805212..2d9e0d396 100644 --- a/domain/consensus/processes/blockprocessor/validateandinsertblock.go +++ b/domain/consensus/processes/blockprocessor/validateandinsertblock.go @@ -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() diff --git a/domain/consensus/processes/blockvalidator/block_header_in_context.go b/domain/consensus/processes/blockvalidator/block_header_in_context.go index e1dc91587..a30276a4d 100644 --- a/domain/consensus/processes/blockvalidator/block_header_in_context.go +++ b/domain/consensus/processes/blockvalidator/block_header_in_context.go @@ -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 diff --git a/domain/consensus/processes/blockvalidator/blockvalidator.go b/domain/consensus/processes/blockvalidator/blockvalidator.go index 101722ca3..b57ca51ac 100644 --- a/domain/consensus/processes/blockvalidator/blockvalidator.go +++ b/domain/consensus/processes/blockvalidator/blockvalidator.go @@ -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, } } diff --git a/domain/consensus/processes/blockvalidator/proof_of_work.go b/domain/consensus/processes/blockvalidator/proof_of_work.go index c5dd6264c..26a692090 100644 --- a/domain/consensus/processes/blockvalidator/proof_of_work.go +++ b/domain/consensus/processes/blockvalidator/proof_of_work.go @@ -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 } diff --git a/domain/consensus/processes/headertipsmanager/headertipsmanager.go b/domain/consensus/processes/headertipsmanager/headertipsmanager.go new file mode 100644 index 000000000..79c419139 --- /dev/null +++ b/domain/consensus/processes/headertipsmanager/headertipsmanager.go @@ -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 +} diff --git a/domain/consensus/ruleerrors/rule_error.go b/domain/consensus/ruleerrors/rule_error.go index 6a39303c4..3f7266a34 100644 --- a/domain/consensus/ruleerrors/rule_error.go +++ b/domain/consensus/ruleerrors/rule_error.go @@ -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