diff --git a/peer/record.go b/peer/record.go index 3968fc2..212cea7 100644 --- a/peer/record.go +++ b/peer/record.go @@ -2,6 +2,7 @@ package peer import ( "fmt" + "sync" "time" pb "github.com/libp2p/go-libp2p-core/peer/pb" @@ -125,9 +126,23 @@ func PeerRecordFromProtobuf(msg *pb.PeerRecord) (*PeerRecord, error) { return record, nil } +var ( + lastTimestampMu sync.Mutex + 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()) + lastTimestampMu.Lock() + defer lastTimestampMu.Unlock() + // Not all clocks are strictly increasing, but we need these sequence numbers to be strictly + // increasing. + if now <= lastTimestamp { + now = lastTimestamp + 1 + } + lastTimestamp = now + 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..4022d92 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) { + var last uint64 + for i := 0; i < 1000; i++ { + next := TimestampSeq() + if next <= last { + t.Errorf("non-increasing timestamp found: %d <= %d", next, last) + } + last = next + } +}