diff --git a/domain/consensus/factory.go b/domain/consensus/factory.go index dec61335c..807455522 100644 --- a/domain/consensus/factory.go +++ b/domain/consensus/factory.go @@ -234,6 +234,7 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, db infrastructuredat multisetStore, acceptanceDataStore, blockStore, + blockHeaderStore, utxoDiffStore, genesisHash, dagParams.FinalityDepth(), diff --git a/domain/consensus/processes/consensusstatemanager/multisets.go b/domain/consensus/processes/consensusstatemanager/multisets.go index 8099ed49b..5960dcc14 100644 --- a/domain/consensus/processes/consensusstatemanager/multisets.go +++ b/domain/consensus/processes/consensusstatemanager/multisets.go @@ -6,7 +6,6 @@ import ( "github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing" "github.com/kaspanet/kaspad/domain/consensus/utils/multiset" "github.com/kaspanet/kaspad/domain/consensus/utils/utxo" - "github.com/kaspanet/kaspad/domain/consensus/utils/utxoserialization" ) func (csm *consensusStateManager) calculateMultiset( @@ -107,11 +106,3 @@ func removeUTXOFromMultiset(multiset model.Multiset, entry externalapi.UTXOEntry return nil } - -func calcMultisetFromProtoUTXOSet(protoUTXOSet *utxoserialization.ProtoUTXOSet) (model.Multiset, error) { - ms := multiset.New() - for _, utxo := range protoUTXOSet.Utxos { - ms.Add(utxo.EntryOutpointPair) - } - return ms, nil -} diff --git a/domain/consensus/processes/consensusstatemanager/update_pruning_utxo_set.go b/domain/consensus/processes/consensusstatemanager/update_pruning_utxo_set.go index f20d9a215..5ecdb16d3 100644 --- a/domain/consensus/processes/consensusstatemanager/update_pruning_utxo_set.go +++ b/domain/consensus/processes/consensusstatemanager/update_pruning_utxo_set.go @@ -51,7 +51,7 @@ func (csm *consensusStateManager) updatePruningPoint(newPruningPoint *externalap return err } - utxoSetMultiSet, err := calcMultisetFromProtoUTXOSet(protoUTXOSet) + utxoSetMultiSet, err := utxoserialization.CalculateMultisetFromProtoUTXOSet(protoUTXOSet) if err != nil { return err } diff --git a/domain/consensus/processes/pruningmanager/pruningmanager.go b/domain/consensus/processes/pruningmanager/pruningmanager.go index 883d4453a..7fb43d826 100644 --- a/domain/consensus/processes/pruningmanager/pruningmanager.go +++ b/domain/consensus/processes/pruningmanager/pruningmanager.go @@ -6,6 +6,7 @@ import ( "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" "github.com/kaspanet/kaspad/domain/consensus/utils/utxoserialization" "github.com/kaspanet/kaspad/infrastructure/logger" + "github.com/pkg/errors" ) // pruningManager resolves and manages the current pruning point @@ -24,6 +25,7 @@ type pruningManager struct { multiSetStore model.MultisetStore acceptanceDataStore model.AcceptanceDataStore blocksStore model.BlockStore + blockHeaderStore model.BlockHeaderStore utxoDiffStore model.UTXODiffStore genesisHash *externalapi.DomainHash @@ -47,6 +49,7 @@ func New( multiSetStore model.MultisetStore, acceptanceDataStore model.AcceptanceDataStore, blocksStore model.BlockStore, + blockHeaderStore model.BlockHeaderStore, utxoDiffStore model.UTXODiffStore, genesisHash *externalapi.DomainHash, @@ -66,6 +69,7 @@ func New( multiSetStore: multiSetStore, acceptanceDataStore: acceptanceDataStore, blocksStore: blocksStore, + blockHeaderStore: blockHeaderStore, utxoDiffStore: utxoDiffStore, headerSelectedTipStore: headerSelectedTipStore, genesisHash: genesisHash, @@ -297,19 +301,20 @@ func (pm *pruningManager) deletePastBlocks(pruningPoint *externalapi.DomainHash) return nil } -func (pm *pruningManager) savePruningPoint(blockHash *externalapi.DomainHash) error { +func (pm *pruningManager) savePruningPoint(pruningPointHash *externalapi.DomainHash) error { onEnd := logger.LogAndMeasureExecutionTime(log, "pruningManager.savePruningPoint") defer onEnd() - utxoIter, err := pm.consensusStateManager.RestorePastUTXOSetIterator(blockHash) + utxoIter, err := pm.consensusStateManager.RestorePastUTXOSetIterator(pruningPointHash) if err != nil { return err } - serializedUtxo, err := serializeUTXOSetIterator(utxoIter) + + serializedUtxo, err := pm.calculateAndValidateSerializedUTXOSet(utxoIter, pruningPointHash) if err != nil { return err } - pm.pruningStore.StagePruningPoint(blockHash, serializedUtxo) + pm.pruningStore.StagePruningPoint(pruningPointHash, serializedUtxo) return nil } @@ -396,14 +401,48 @@ func (pm *pruningManager) pruningPointCandidate() (*externalapi.DomainHash, erro return pm.pruningStore.PruningPointCandidate(pm.databaseContext) } -func serializeUTXOSetIterator(iter model.ReadOnlyUTXOSetIterator) ([]byte, error) { +func (pm *pruningManager) calculateAndValidateSerializedUTXOSet( + iter model.ReadOnlyUTXOSetIterator, pruningPointHash *externalapi.DomainHash) ([]byte, error) { + serializedUtxo, err := utxoserialization.ReadOnlyUTXOSetToProtoUTXOSet(iter) if err != nil { return nil, err } + + err = pm.validateUTXOSetFitsCommitment(serializedUtxo, pruningPointHash) + if err != nil { + return nil, err + } + return proto.Marshal(serializedUtxo) } +// validateUTXOSetFitsCommitment makes sure that the calculated UTXOSet of the new pruning point fits the commitment. +// This is a sanity test, to make sure that kaspad doesn't store, and subsequently sends syncing peers the wrong UTXOSet. +func (pm *pruningManager) validateUTXOSetFitsCommitment( + serializedUtxo *utxoserialization.ProtoUTXOSet, pruningPointHash *externalapi.DomainHash) error { + + utxoSetMultiSet, err := utxoserialization.CalculateMultisetFromProtoUTXOSet(serializedUtxo) + if err != nil { + return err + } + utxoSetHash := utxoSetMultiSet.Hash() + + header, err := pm.blockHeaderStore.BlockHeader(pm.databaseContext, pruningPointHash) + if err != nil { + return err + } + expectedUTXOCommitment := header.UTXOCommitment() + + if !expectedUTXOCommitment.Equal(utxoSetHash) { + return errors.Errorf("Calculated UTXOSet for next pruning point %s doesn't match it's UTXO commitment\n"+ + "Calculated UTXOSet hash: %s. Commitment: %s", + pruningPointHash, utxoSetHash, expectedUTXOCommitment) + } + + return nil +} + // finalityScore is the number of finality intervals passed since // the given block. func (pm *pruningManager) finalityScore(blueScore uint64) uint64 { diff --git a/domain/consensus/utils/utxoserialization/multiset.go b/domain/consensus/utils/utxoserialization/multiset.go new file mode 100644 index 000000000..6e62cd0f7 --- /dev/null +++ b/domain/consensus/utils/utxoserialization/multiset.go @@ -0,0 +1,15 @@ +package utxoserialization + +import ( + "github.com/kaspanet/kaspad/domain/consensus/model" + "github.com/kaspanet/kaspad/domain/consensus/utils/multiset" +) + +// CalculateMultisetFromProtoUTXOSet calculates the Multiset corresponding to the given ProtuUTXOSet +func CalculateMultisetFromProtoUTXOSet(protoUTXOSet *ProtoUTXOSet) (model.Multiset, error) { + ms := multiset.New() + for _, utxo := range protoUTXOSet.Utxos { + ms.Add(utxo.EntryOutpointPair) + } + return ms, nil +}