diff --git a/channels/adminListener.go b/channels/adminListener.go index d9e203f1c0c4bab8be5fa14220130c773fd74ee5..86f80142ef5345a3aad346907d96940097a281a6 100644 --- a/channels/adminListener.go +++ b/channels/adminListener.go @@ -15,6 +15,7 @@ import ( "gitlab.com/elixxir/crypto/channel" "gitlab.com/elixxir/primitives/states" "gitlab.com/xx_network/primitives/id" + "time" ) // adminListener adheres to the [broadcast.ListenerFunc] interface and is used @@ -54,8 +55,10 @@ func (al *adminListener) Listen(payload []byte, return } - // Modify the timestamp to reduce the chance message order will be ambiguous - ts := mutateTimestamp(round.Timestamps[states.QUEUED], msgID) + // Replace the timestamp on the message if it is outside of the + // allowable range + ts := vetTimestamp(time.Unix(0, cm.LocalTimestamp), + round.Timestamps[states.QUEUED], msgID) // Submit the message to the event model for listening if uuid, err := al.trigger(al.chID, cm, ts, msgID, receptionID, diff --git a/channels/mutateTimestamp.go b/channels/mutateTimestamp.go index 47c2bf1f02da14eaf829f37cb5b77970aaebf394..8b8bf683e971635458124783b76754fa9e3638b5 100644 --- a/channels/mutateTimestamp.go +++ b/channels/mutateTimestamp.go @@ -18,10 +18,28 @@ const ( // arise due to cofactors with the message ID when doing the modulo tenMsInNs = 10000019 halfTenMsInNs = tenMsInNs / 2 + beforeGrace = 5 * time.Second + afterGrace = 2 * time.Second ) var tenMsInNsLargeInt = large.NewInt(tenMsInNs) +// vetTimestamp determines which timestamp to use for a message. It will +// use the local timestamp provided in the message as long as it is within 5 +// seconds before the round and 2 second after the round. Otherwise, it will +// use the round timestamp via mutateTimestamp +func vetTimestamp(localTS, ts time.Time, msgID channel.MessageID) time.Time { + + before := ts.Add(-beforeGrace) + after := ts.Add(afterGrace) + + if localTS.Before(before) || localTS.After(after) { + return mutateTimestamp(ts, msgID) + } + + return localTS +} + // mutateTimestamp is used to modify the the timestamps on all messages in a // deterministic manner. This is because message ordering is done by timestamp // and the timestamps come from the rounds, which means multiple messages can diff --git a/channels/mutateTimestamp_test.go b/channels/mutateTimestamp_test.go index 120fd44156c2c1616a876d60db27725caebd6eaf..93561868b10ac781ef2c86cc0b483a0fed1815f8 100644 --- a/channels/mutateTimestamp_test.go +++ b/channels/mutateTimestamp_test.go @@ -53,3 +53,77 @@ func TestMutateTimestampDeltaAverage(t *testing.T) { t.Fatal() } } + +const generationRange = beforeGrace + afterGrace + +// TestVetTimestamp_Happy tests that when the localTS is within +// the allowed range, it is unmodified +func TestVetTimestamp_Happy(t *testing.T) { + samples := 10000 + + rng := rand.New(rand.NewSource(netTime.Now().UnixNano())) + + for i := 0; i < samples; i++ { + + now := time.Now() + + tested := now.Add(-beforeGrace).Add(time.Duration(rng.Int63()) % generationRange) + + var msgID channel.MessageID + rng.Read(msgID[:]) + + result := vetTimestamp(tested, now, msgID) + + if !tested.Equal(result) { + t.Errorf("Timestamp was molested unexpectedly") + } + } +} + +// TestVetTimestamp_Happy tests that when the localTS is less than +// the allowed time period it is replaced +func TestVetTimestamp_BeforePeriod(t *testing.T) { + samples := 10000 + + rng := rand.New(rand.NewSource(netTime.Now().UnixNano())) + + for i := 0; i < samples; i++ { + + now := time.Now() + + tested := now.Add(-beforeGrace).Add(-time.Duration(rng.Int63()) % (100000 * time.Hour)) + + var msgID channel.MessageID + rng.Read(msgID[:]) + + result := vetTimestamp(tested, now, msgID) + + if tested.Equal(result) { + t.Errorf("Timestamp was unmolested unexpectedly") + } + } +} + +// TestVetTimestamp_Happy tests that when the localTS is greater than +// the allowed time period it is replaced +func TestVetTimestamp_AfterPeriod(t *testing.T) { + samples := 10000 + + rng := rand.New(rand.NewSource(netTime.Now().UnixNano())) + + for i := 0; i < samples; i++ { + + now := time.Now() + + tested := now.Add(afterGrace).Add(-time.Duration(rng.Int63()) % (100000 * time.Hour)) + + var msgID channel.MessageID + rng.Read(msgID[:]) + + result := vetTimestamp(tested, now, msgID) + + if tested.Equal(result) { + t.Errorf("Timestamp was unmolested unexpectedly") + } + } +} diff --git a/channels/send.go b/channels/send.go index 7b46152bb07b801fcd23fcd649829065d4e654db..0f8cfd7d5e4bab5a355732bb80f6e908c79e8219 100644 --- a/channels/send.go +++ b/channels/send.go @@ -17,6 +17,7 @@ import ( "gitlab.com/elixxir/crypto/rsa" "gitlab.com/xx_network/primitives/id" "gitlab.com/xx_network/primitives/id/ephemeral" + "gitlab.com/xx_network/primitives/netTime" "google.golang.org/protobuf/proto" "time" ) @@ -50,11 +51,12 @@ func (m *manager) SendGeneric(channelID *id.ID, messageType MessageType, var msgId cryptoChannel.MessageID chMsg := &ChannelMessage{ - Lease: validUntil.Nanoseconds(), - PayloadType: uint32(messageType), - Payload: msg, - Nickname: nickname, - Nonce: make([]byte, messageNonceSize), + Lease: validUntil.Nanoseconds(), + PayloadType: uint32(messageType), + Payload: msg, + Nickname: nickname, + Nonce: make([]byte, messageNonceSize), + LocalTimestamp: netTime.Now().UnixNano(), } // Generate random nonce to be used for message ID generation. This makes it @@ -75,7 +77,7 @@ func (m *manager) SendGeneric(channelID *id.ID, messageType MessageType, ECCPublicKey: m.me.PubKey, } - //Note: we are not checking check if message is too long before trying to + //Note: we are not checking if message is too long before trying to //find a round //Build the function pointer that will build the message @@ -145,11 +147,12 @@ func (m *manager) SendAdminGeneric(privKey rsa.PrivateKey, channelID *id.ID, var msgId cryptoChannel.MessageID chMsg := &ChannelMessage{ - Lease: validUntil.Nanoseconds(), - PayloadType: uint32(messageType), - Payload: msg, - Nickname: AdminUsername, - Nonce: make([]byte, messageNonceSize), + Lease: validUntil.Nanoseconds(), + PayloadType: uint32(messageType), + Payload: msg, + Nickname: AdminUsername, + Nonce: make([]byte, messageNonceSize), + LocalTimestamp: netTime.Now().UnixNano(), } // Generate random nonce to be used for message ID generation. This makes it diff --git a/channels/userListener.go b/channels/userListener.go index dad5075fd08daf0a46f2a221108df2b90dc45a91..202e5fa7d93b4ba15485cc2549806e0b8b014dea 100644 --- a/channels/userListener.go +++ b/channels/userListener.go @@ -14,6 +14,7 @@ import ( "gitlab.com/elixxir/client/cmix/rounds" "gitlab.com/elixxir/primitives/states" "gitlab.com/xx_network/primitives/id" + "time" ) // the userListener adheres to the [broadcast.ListenerFunc] interface and is @@ -64,8 +65,9 @@ func (ul *userListener) Listen(payload []byte, return } - // Modify the timestamp to reduce the chance message order will be ambiguous - ts := mutateTimestamp(round.Timestamps[states.QUEUED], msgID) + // Replace the timestamp on the message if it is outside of the + // allowable range + ts := vetTimestamp(time.Unix(0, cm.LocalTimestamp), round.Timestamps[states.QUEUED], msgID) //TODO: Processing of the message relative to admin commands will be here