Skip to content

Commit 0b7e030

Browse files
authored
Merge pull request #26 from stellar/patch/fix-ttl-bug
Add deduplication for ttl data
2 parents f40a8e2 + f4a682b commit 0b7e030

2 files changed

Lines changed: 138 additions & 0 deletions

File tree

internal/transform/ttl.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ func GetTTLDataDetails(changes []ingest.Change, lhe xdr.LedgerHeaderHistoryEntry
3030
ttlDataOutputs = append(ttlDataOutputs, TransformTTLData)
3131

3232
}
33+
34+
// It is possible to have multiple changes to the same ttl entry in a single ledger
35+
// example from testnet data: CDO7SMNK3H2ZTRWSLJOPMGFLHDN5SKNSWJI6CNB2TBXCXAKFC6DPTTZY, 83c830c6d200adbceda8e72d8204017f781b57e1ec8acf03674388a902732779, 901
36+
ttlDataOutputs = utils.RemoveDuplicatesByFields(ttlDataOutputs, []string{"KeyHash", "LedgerSequence"})
3337
return ttlDataOutputs, nil
3438
}
3539

internal/transform/ttl_test.go

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)