@@ -107,3 +107,137 @@ func makeTtlTestOutput() []contract.TtlOutput {
107107 },
108108 }
109109}
110+
111+ // TestGetTTLDetailsWithDuplicates tests the deduplication behavior when multiple changes
112+ // to the same TTL entry occur within a single ledger, preventing regression of issue #25
113+ func TestGetTTLDetailsWithDuplicates (t * testing.T ) {
114+ var hash xdr.Hash
115+ expectedKeyHash := "0000000000000000000000000000000000000000000000000000000000000000"
116+ ledgerSeq := xdr .Uint32 (10 )
117+
118+ // Simulate multiple changes to the same TTL entry within a single ledger
119+ // Each change updates the LiveUntilLedgerSeq value
120+ // In practice, this can happen when the same entry is bumped multiple times
121+ // within the same ledger by different operations
122+
123+ // First change: initial update to LiveUntilLedgerSeq = 100
124+ preTtlLedgerEntry1 := xdr.LedgerEntry {
125+ LastModifiedLedgerSeq : ledgerSeq - 1 ,
126+ Data : xdr.LedgerEntryData {
127+ Type : xdr .LedgerEntryTypeTtl ,
128+ Ttl : & xdr.TtlEntry {
129+ KeyHash : hash ,
130+ LiveUntilLedgerSeq : 0 ,
131+ },
132+ },
133+ }
134+
135+ postTtlLedgerEntry1 := xdr.LedgerEntry {
136+ LastModifiedLedgerSeq : ledgerSeq ,
137+ Data : xdr.LedgerEntryData {
138+ Type : xdr .LedgerEntryTypeTtl ,
139+ Ttl : & xdr.TtlEntry {
140+ KeyHash : hash ,
141+ LiveUntilLedgerSeq : 100 ,
142+ },
143+ },
144+ }
145+
146+ // Second change: another update to the same KeyHash, LiveUntilLedgerSeq = 200
147+ preTtlLedgerEntry2 := xdr.LedgerEntry {
148+ LastModifiedLedgerSeq : ledgerSeq ,
149+ Data : xdr.LedgerEntryData {
150+ Type : xdr .LedgerEntryTypeTtl ,
151+ Ttl : & xdr.TtlEntry {
152+ KeyHash : hash ,
153+ LiveUntilLedgerSeq : 100 ,
154+ },
155+ },
156+ }
157+
158+ postTtlLedgerEntry2 := xdr.LedgerEntry {
159+ LastModifiedLedgerSeq : ledgerSeq ,
160+ Data : xdr.LedgerEntryData {
161+ Type : xdr .LedgerEntryTypeTtl ,
162+ Ttl : & xdr.TtlEntry {
163+ KeyHash : hash ,
164+ LiveUntilLedgerSeq : 200 ,
165+ },
166+ },
167+ }
168+
169+ // Third change: final update to the same KeyHash, LiveUntilLedgerSeq = 300
170+ preTtlLedgerEntry3 := xdr.LedgerEntry {
171+ LastModifiedLedgerSeq : ledgerSeq ,
172+ Data : xdr.LedgerEntryData {
173+ Type : xdr .LedgerEntryTypeTtl ,
174+ Ttl : & xdr.TtlEntry {
175+ KeyHash : hash ,
176+ LiveUntilLedgerSeq : 200 ,
177+ },
178+ },
179+ }
180+
181+ postTtlLedgerEntry3 := xdr.LedgerEntry {
182+ LastModifiedLedgerSeq : ledgerSeq ,
183+ Data : xdr.LedgerEntryData {
184+ Type : xdr .LedgerEntryTypeTtl ,
185+ Ttl : & xdr.TtlEntry {
186+ KeyHash : hash ,
187+ LiveUntilLedgerSeq : 300 ,
188+ },
189+ },
190+ }
191+
192+ // Create multiple changes to the same TTL entry within a single ledger
193+ changes := []ingest.Change {
194+ {
195+ ChangeType : xdr .LedgerEntryChangeTypeLedgerEntryUpdated ,
196+ Type : xdr .LedgerEntryTypeTtl ,
197+ Pre : & preTtlLedgerEntry1 ,
198+ Post : & postTtlLedgerEntry1 ,
199+ },
200+ {
201+ ChangeType : xdr .LedgerEntryChangeTypeLedgerEntryUpdated ,
202+ Type : xdr .LedgerEntryTypeTtl ,
203+ Pre : & preTtlLedgerEntry2 ,
204+ Post : & postTtlLedgerEntry2 ,
205+ },
206+ {
207+ ChangeType : xdr .LedgerEntryChangeTypeLedgerEntryUpdated ,
208+ Type : xdr .LedgerEntryTypeTtl ,
209+ Pre : & preTtlLedgerEntry3 ,
210+ Post : & postTtlLedgerEntry3 ,
211+ },
212+ }
213+
214+ header := xdr.LedgerHeaderHistoryEntry {
215+ Header : xdr.LedgerHeader {
216+ ScpValue : xdr.StellarValue {
217+ CloseTime : 1000 ,
218+ },
219+ LedgerSeq : ledgerSeq ,
220+ },
221+ }
222+
223+ actualOutput , actualError := GetTTLDataDetails (changes , header )
224+
225+ // Should not error
226+ assert .NoError (t , actualError )
227+
228+ // Should only have 1 entry after deduplication (the latest one)
229+ assert .Equal (t , 1 , len (actualOutput ))
230+
231+ // The retained entry should be the latest one with LiveUntilLedgerSeq = 300
232+ expectedOutput := contract.TtlOutput {
233+ KeyHash : expectedKeyHash ,
234+ LiveUntilLedgerSeq : 300 ,
235+ LastModifiedLedger : uint32 (ledgerSeq ),
236+ LedgerEntryChange : 1 , // Updated entry
237+ Deleted : false ,
238+ LedgerSequence : uint32 (ledgerSeq ),
239+ ClosedAt : time .Date (1970 , time .January , 1 , 0 , 16 , 40 , 0 , time .UTC ),
240+ }
241+
242+ assert .Equal (t , expectedOutput , actualOutput [0 ])
243+ }
0 commit comments