github.com/unicornultrafoundation/go-u2u@v1.0.0-rc1.0.20240205080301-e74a83d3fadc/vecmt/median_time.go (about)

     1  package vecmt
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  
     7  	"github.com/unicornultrafoundation/go-helios/hash"
     8  	"github.com/unicornultrafoundation/go-helios/native/idx"
     9  	"github.com/unicornultrafoundation/go-helios/native/pos"
    10  
    11  	"github.com/unicornultrafoundation/go-u2u/native"
    12  )
    13  
    14  // medianTimeIndex is a handy index for the MedianTime() func
    15  type medianTimeIndex struct {
    16  	weight       pos.Weight
    17  	creationTime native.Timestamp
    18  }
    19  
    20  // MedianTime calculates weighted median of claimed time within highest observed events.
    21  func (vi *Index) MedianTime(id hash.Event, defaultTime native.Timestamp) native.Timestamp {
    22  	vi.Engine.InitBranchesInfo()
    23  	// Get event by hash
    24  	_before := vi.Engine.GetMergedHighestBefore(id)
    25  	if _before == nil {
    26  		vi.crit(fmt.Errorf("event=%s not found", id.String()))
    27  	}
    28  	before := _before.(*HighestBefore)
    29  
    30  	honestTotalWeight := pos.Weight(0) // isn't equal to validators.TotalWeight(), because doesn't count cheaters
    31  	highests := make([]medianTimeIndex, 0, len(vi.validatorIdxs))
    32  	// convert []HighestBefore -> []medianTimeIndex
    33  	for creatorIdxI := range vi.validators.IDs() {
    34  		creatorIdx := idx.Validator(creatorIdxI)
    35  		highest := medianTimeIndex{}
    36  		highest.weight = vi.validators.GetWeightByIdx(creatorIdx)
    37  		highest.creationTime = before.VTime.Get(creatorIdx)
    38  		seq := before.VSeq.Get(creatorIdx)
    39  
    40  		// edge cases
    41  		if seq.IsForkDetected() {
    42  			// cheaters don't influence medianTime
    43  			highest.weight = 0
    44  		} else if seq.Seq == 0 {
    45  			// if no event was observed from this node, then use genesisTime
    46  			highest.creationTime = defaultTime
    47  		}
    48  
    49  		highests = append(highests, highest)
    50  		honestTotalWeight += highest.weight
    51  	}
    52  	// it's technically possible honestTotalWeight == 0 (all validators are cheaters)
    53  
    54  	// sort by claimed time (partial order is enough here, because we need only creationTime)
    55  	sort.Slice(highests, func(i, j int) bool {
    56  		a, b := highests[i], highests[j]
    57  		return a.creationTime < b.creationTime
    58  	})
    59  
    60  	// Calculate weighted median
    61  	halfWeight := honestTotalWeight / 2
    62  	var currWeight pos.Weight
    63  	var median native.Timestamp
    64  	for _, highest := range highests {
    65  		currWeight += highest.weight
    66  		if currWeight >= halfWeight {
    67  			median = highest.creationTime
    68  			break
    69  		}
    70  	}
    71  
    72  	// sanity check
    73  	if currWeight < halfWeight || currWeight > honestTotalWeight {
    74  		vi.crit(fmt.Errorf("median wasn't calculated correctly, median=%d, currWeight=%d, totalWeight=%d, len(highests)=%d, id=%s",
    75  			median,
    76  			currWeight,
    77  			honestTotalWeight,
    78  			len(highests),
    79  			id.String(),
    80  		))
    81  	}
    82  
    83  	return median
    84  }