diff --git a/peer/record.go b/peer/record.go index 3968fc2..95c8f6c 100644 --- a/peer/record.go +++ b/peer/record.go @@ -2,6 +2,7 @@ package peer import ( "fmt" + "sync/atomic" "time" pb "github.com/libp2p/go-libp2p-core/peer/pb" @@ -125,9 +126,21 @@ func PeerRecordFromProtobuf(msg *pb.PeerRecord) (*PeerRecord, error) { return record, nil } +var lastTimestamp uint64 + // TimestampSeq is a helper to generate a timestamp-based sequence number for a PeerRecord. func TimestampSeq() uint64 { - return uint64(time.Now().UnixNano()) + now := uint64(time.Now().UnixNano()) + previous := atomic.LoadUint64(&lastTimestamp) + // If the new time is not greater than the last tiemstamp, or if someone else beats us to + // updateing the timestamp, just use last+1. + // + // Technically, last+1 could be before "now". But it's still strictly increasing and close + // enough. + if now <= previous || !atomic.CompareAndSwapUint64(&lastTimestamp, previous, now) { + now = atomic.AddUint64(&lastTimestamp, 1) + } + return now } // Domain is used when signing and validating PeerRecords contained in Envelopes. diff --git a/peer/record_test.go b/peer/record_test.go index adbe03a..e5aa079 100644 --- a/peer/record_test.go +++ b/peer/record_test.go @@ -52,3 +52,16 @@ func TestSignedPeerRecordFromEnvelope(t *testing.T) { } }) } + +// This is pretty much guaranteed to pass on Linux no matter how we implement it, but Windows has +// low clock precision. This makes sure we never get a duplicate. +func TestTimestampSeq(t *testing.T) { + last := uint64(0) + for i := 0; i < 1000; i++ { + next := TimestampSeq() + if next <= last { + t.Errorf("non-increasing timestampfound: %d <= %d", next, last) + } + last = next + } +}