diff --git a/domain/consensus/factory.go b/domain/consensus/factory.go index 221580091..8ffd9c509 100644 --- a/domain/consensus/factory.go +++ b/domain/consensus/factory.go @@ -67,8 +67,9 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, databaseContext *dba ghostdagDataStore, model.KType(dagParams.K)) dagTraversalManager := dagtraversalmanager.New( + domainDBContext, dagTopologyManager, - ghostdagManager) + ghostdagDataStore) pruningManager := pruningmanager.New( dagTraversalManager, dagTopologyManager, @@ -92,7 +93,10 @@ func (f *factory) NewConsensus(dagParams *dagconfig.Params, databaseContext *dba difficultyManager := difficultymanager.New( ghostdagManager) pastMedianTimeManager := pastmediantimemanager.New( - ghostdagManager) + dagParams.TimestampDeviationTolerance, + domainDBContext, + dagTraversalManager, + blockStore) transactionValidator := transactionvalidator.New(dagParams.BlockCoinbaseMaturity, domainDBContext, pastMedianTimeManager, diff --git a/domain/consensus/model/interface_processes_dagtraversalmanager.go b/domain/consensus/model/interface_processes_dagtraversalmanager.go index 85a6df8cb..ce52a225f 100644 --- a/domain/consensus/model/interface_processes_dagtraversalmanager.go +++ b/domain/consensus/model/interface_processes_dagtraversalmanager.go @@ -7,4 +7,5 @@ import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" type DAGTraversalManager interface { HighestChainBlockBelowBlueScore(highHash *externalapi.DomainHash, blueScore uint64) (*externalapi.DomainHash, error) SelectedParentIterator(highHash *externalapi.DomainHash) (SelectedParentIterator, error) + BlueWindow(highHash *externalapi.DomainHash, windowSize uint64) ([]*externalapi.DomainHash, error) } diff --git a/domain/consensus/processes/dagtraversalmanager/dagtraversalmanager.go b/domain/consensus/processes/dagtraversalmanager/dagtraversalmanager.go index 8752d4fbe..ef215f967 100644 --- a/domain/consensus/processes/dagtraversalmanager/dagtraversalmanager.go +++ b/domain/consensus/processes/dagtraversalmanager/dagtraversalmanager.go @@ -8,17 +8,21 @@ import ( // dagTraversalManager exposes methods for travering blocks // in the DAG type dagTraversalManager struct { + databaseContext model.DBContextProxy + dagTopologyManager model.DAGTopologyManager - ghostdagManager model.GHOSTDAGManager + ghostdagDataStore model.GHOSTDAGDataStore } // New instantiates a new DAGTraversalManager func New( + databaseContext model.DBContextProxy, dagTopologyManager model.DAGTopologyManager, - ghostdagManager model.GHOSTDAGManager) model.DAGTraversalManager { + ghostdagDataStore model.GHOSTDAGDataStore) model.DAGTraversalManager { return &dagTraversalManager{ + databaseContext: databaseContext, dagTopologyManager: dagTopologyManager, - ghostdagManager: ghostdagManager, + ghostdagDataStore: ghostdagDataStore, } } diff --git a/domain/consensus/processes/dagtraversalmanager/window.go b/domain/consensus/processes/dagtraversalmanager/window.go new file mode 100644 index 000000000..8f4805984 --- /dev/null +++ b/domain/consensus/processes/dagtraversalmanager/window.go @@ -0,0 +1,41 @@ +package dagtraversalmanager + +import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" + +// blueBlockWindow returns a blockWindow of the given size that contains the +// blues in the past of startindNode, sorted by GHOSTDAG order. +// If the number of blues in the past of startingNode is less then windowSize, +// the window will be padded by genesis blocks to achieve a size of windowSize. +func (dtm *dagTraversalManager) BlueWindow(startingBlock *externalapi.DomainHash, windowSize uint64) ([]*externalapi.DomainHash, error) { + window := make([]*externalapi.DomainHash, 0, windowSize) + + currentHash := startingBlock + currentGHOSTDAGData, err := dtm.ghostdagDataStore.Get(dtm.databaseContext, currentHash) + if err != nil { + return nil, err + } + + for uint64(len(window)) < windowSize && currentGHOSTDAGData.SelectedParent != nil { + for _, blue := range currentGHOSTDAGData.MergeSetBlues { + window = append(window, blue) + if uint64(len(window)) == windowSize { + break + } + } + + currentHash = currentGHOSTDAGData.SelectedParent + currentGHOSTDAGData, err = dtm.ghostdagDataStore.Get(dtm.databaseContext, currentHash) + if err != nil { + return nil, err + } + } + + if uint64(len(window)) < windowSize { + genesis := currentHash + for uint64(len(window)) < windowSize { + window = append(window, genesis) + } + } + + return window, nil +} diff --git a/domain/consensus/processes/pastmediantimemanager/pastmediantimemanager.go b/domain/consensus/processes/pastmediantimemanager/pastmediantimemanager.go index 5188664f3..766edf9e1 100644 --- a/domain/consensus/processes/pastmediantimemanager/pastmediantimemanager.go +++ b/domain/consensus/processes/pastmediantimemanager/pastmediantimemanager.go @@ -3,22 +3,64 @@ package pastmediantimemanager import ( "github.com/kaspanet/kaspad/domain/consensus/model" "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" + "github.com/pkg/errors" + "sort" ) // pastMedianTimeManager provides a method to resolve the // past median time of a block type pastMedianTimeManager struct { - ghostdagManager model.GHOSTDAGManager + timestampDeviationTolerance uint64 + + databaseContext model.DBContextProxy + + dagTraversalManager model.DAGTraversalManager + + blockStore model.BlockStore } // New instantiates a new PastMedianTimeManager -func New(ghostdagManager model.GHOSTDAGManager) model.PastMedianTimeManager { +func New(timestampDeviationTolerance uint64, + databaseContext model.DBContextProxy, + dagTraversalManager model.DAGTraversalManager, + blockStore model.BlockStore) model.PastMedianTimeManager { return &pastMedianTimeManager{ - ghostdagManager: ghostdagManager, + timestampDeviationTolerance: timestampDeviationTolerance, + databaseContext: databaseContext, + dagTraversalManager: dagTraversalManager, + blockStore: blockStore, } } // PastMedianTime returns the past median time for some block func (pmtm *pastMedianTimeManager) PastMedianTime(blockHash *externalapi.DomainHash) (int64, error) { - return 0, nil + window, err := pmtm.dagTraversalManager.BlueWindow(blockHash, 2*pmtm.timestampDeviationTolerance-1) + if err != nil { + return 0, err + } + + return pmtm.windowMedianTimestamp(window) +} + +func (pmtm *pastMedianTimeManager) windowMedianTimestamp(window []*externalapi.DomainHash) (int64, error) { + if len(window) == 0 { + return 0, errors.New("Cannot calculate median timestamp for an empty block window") + } + + timestamps := make([]int64, len(window)) + for i, blockHash := range window { + block, err := pmtm.blockStore.Block(pmtm.databaseContext, blockHash) + if err != nil { + return 0, err + } + + // TODO: Use headers store + timestamps[i] = block.Header.TimeInMilliseconds + } + + sort.Slice(timestamps, func(i, j int) bool { + return timestamps[i] < timestamps[j] + }) + + return timestamps[len(timestamps)/2], nil }