Add sanity check that makes sure that the utxoset that we save fits the pruningPoints's commitment. (#1366)

Co-authored-by: Elichai Turkel <elichai.turkel@gmail.com>
This commit is contained in:
Svarog 2021-01-06 13:27:02 +02:00 committed by GitHub
parent a04a5462ae
commit 9ea4c0fa38
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 61 additions and 15 deletions

View File

@ -234,6 +234,7 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, db infrastructuredat
multisetStore,
acceptanceDataStore,
blockStore,
blockHeaderStore,
utxoDiffStore,
genesisHash,
dagParams.FinalityDepth(),

View File

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

View File

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

View File

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

View File

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