AddTimeSample adds a time sample that is used when determining the median time of the added samples.

This function is safe for concurrent access and is part of the MedianTimeSource interface implementation.

AddTimeSample is referenced in 0 repositories


func (m *medianTime) AddTimeSample(sourceID string, timeVal time.Time) {
	defer m.mtx.Unlock()

	// Don't add time data from the same source.
	if _, exists := m.knownIDs[sourceID]; exists {
	m.knownIDs[sourceID] = struct{}{}

	// Truncate the provided offset to seconds and append it to the slice
	// of offsets while respecting the maximum number of allowed entries by
	// replacing the oldest entry with the new entry once the maximum number
	// of entries is reached.
	now := time.Unix(time.Now().Unix(), 0)
	offsetSecs := int64(timeVal.Sub(now).Seconds())
	numOffsets := len(m.offsets)
	if numOffsets == maxMedianTimeEntries && maxMedianTimeEntries > 0 {
		m.offsets = m.offsets[1:]
	m.offsets = append(m.offsets, offsetSecs)

	// Sort the offsets so the median can be obtained as needed later.
	sortedOffsets := make([]int64, numOffsets)
	copy(sortedOffsets, m.offsets)

	offsetDuration := time.Duration(offsetSecs) * time.Second
	log.Debugf("Added time sample of %v (total: %v)", offsetDuration,

	// NOTE: The following code intentionally has a bug to mirror the
	// buggy behavior in Bitcoin Core since the median time is used in the
	// consensus rules.
	// In particular, the offset is only updated when the number of entries
	// is odd, but the max number of entries is 200, an even number.  Thus,
	// the offset will never be updated again once the max number of entries
	// is reached.

	// The median offset is only updated when there are enough offsets and
	// the number of offsets is odd so the middle value is the true median.
	// Thus, there is nothing to do when those conditions are not met.
	if numOffsets < 5 || numOffsets&0x01 != 1 {

	// At this point the number of offsets in the list is odd, so the
	// middle value of the sorted offsets is the median.
	median := sortedOffsets[numOffsets/2]

	// Set the new offset when the median offset is within the allowed
	// offset range.
	if math.Abs(float64(median)) < maxAllowedOffsetSecs {
		m.offsetSecs = median
	} else {
		// The median offset of all added time data is larger than the
		// maximum allowed offset, so don't use an offset.  This
		// effectively limits how far the local clock can be skewed.
		m.offsetSecs = 0

		if !m.invalidTimeChecked {
			m.invalidTimeChecked = true

			// Find if any time samples have a time that is close
			// to the local time.
			var remoteHasCloseTime bool
			for _, offset := range sortedOffsets {
				if math.Abs(float64(offset)) < similarTimeSecs {
					remoteHasCloseTime = true

			// Warn if none of the time samples are close.
			if !remoteHasCloseTime {
				log.Warnf("Please check your date and time " +
					"are correct!  btcd will not work " +
					"properly with an invalid time")

	medianDuration := time.Duration(m.offsetSecs) * time.Second
	log.Debugf("New time offset: %v", medianDuration)