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 }