Skip to content

Commit cdc37af

Browse files
authored
[TT-1934] event hash collision (#1544)
1 parent d9665c9 commit cdc37af

File tree

4 files changed

+137
-28
lines changed

4 files changed

+137
-28
lines changed

seth/.changeset/v1.50.11.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
- improvement: allow to retry gas estimation by providing `gas_price_estimation_attempt_count` setting on a Network level (defaults to 1 -> no retries)
2+
- improvement: better handling of event signature (hash) collisions (although still not 100% bullet-proof due to complexlity)

seth/client.go

Lines changed: 130 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"math/big"
88
"net/http"
99
"path/filepath"
10+
"reflect"
1011
"strings"
1112
"time"
1213

@@ -1167,38 +1168,144 @@ func (t TransactionLog) GetData() []byte {
11671168
}
11681169

11691170
func (m *Client) decodeContractLogs(l zerolog.Logger, logs []types.Log, allABIs []*abi.ABI) ([]DecodedTransactionLog, error) {
1170-
l.Trace().Msg("Decoding ALL events")
1171+
l.Trace().
1172+
Msg("Decoding events")
1173+
sigMap := buildEventSignatureMap(allABIs)
1174+
11711175
var eventsParsed []DecodedTransactionLog
11721176
for _, lo := range logs {
1173-
ABI_LOOP:
1174-
for _, a := range allABIs {
1175-
for _, evSpec := range a.Events {
1176-
if evSpec.ID.Hex() == lo.Topics[0].Hex() {
1177-
d := TransactionLog{lo.Topics, lo.Data}
1178-
l.Trace().Str("Name", evSpec.RawName).Str("Signature", evSpec.Sig).Msg("Unpacking event")
1179-
eventsMap, topicsMap, err := decodeEventFromLog(l, *a, evSpec, d)
1180-
if err != nil {
1181-
return nil, errors.Wrap(err, ErrDecodeLog)
1182-
}
1183-
parsedEvent := decodedLogFromMaps(&DecodedTransactionLog{}, eventsMap, topicsMap)
1184-
decodedTransactionLog, ok := parsedEvent.(*DecodedTransactionLog)
1185-
if ok {
1186-
decodedTransactionLog.Signature = evSpec.Sig
1187-
m.mergeLogMeta(decodedTransactionLog, lo)
1188-
eventsParsed = append(eventsParsed, *decodedTransactionLog)
1189-
l.Trace().Interface("Log", parsedEvent).Msg("Transaction log")
1190-
break ABI_LOOP
1191-
}
1192-
l.Trace().
1193-
Str("Actual type", fmt.Sprintf("%T", decodedTransactionLog)).
1194-
Msg("Failed to cast decoded event to DecodedCommonLog")
1177+
if len(lo.Topics) == 0 {
1178+
l.Debug().
1179+
Msg("Log has no topics; skipping")
1180+
continue
1181+
}
1182+
1183+
eventSig := lo.Topics[0].Hex()
1184+
possibleEvents, exists := sigMap[eventSig]
1185+
if !exists {
1186+
l.Trace().
1187+
Str("Event signature", eventSig).
1188+
Msg("No matching events found for signature")
1189+
continue
1190+
}
1191+
1192+
// Check if we know what contract is this log from and if we do, get its ABI to skip unnecessary iterations
1193+
var knownContractABI *abi.ABI
1194+
if contractName := m.ContractAddressToNameMap.GetContractName(lo.Address.Hex()); contractName != "" {
1195+
maybeABI, ok := m.ContractStore.GetABI(contractName)
1196+
if !ok {
1197+
l.Trace().
1198+
Str("Event signature", eventSig).
1199+
Str("Contract name", contractName).
1200+
Str("Contract address", lo.Address.Hex()).
1201+
Msg("No ABI found for known contract; this is unexpected. Continuing with step-by-step ABI search")
1202+
} else {
1203+
knownContractABI = maybeABI
1204+
}
1205+
}
1206+
1207+
// Iterate over possible events with the same signature
1208+
matched := false
1209+
for _, evWithABI := range possibleEvents {
1210+
evSpec := evWithABI.EventSpec
1211+
contractABI := evWithABI.ContractABI
1212+
1213+
// Check if known contract ABI matches candidate ABI and if not, skip this ABI and try the next one
1214+
if knownContractABI != nil && !reflect.DeepEqual(knownContractABI, contractABI) {
1215+
l.Trace().
1216+
Str("Event signature", eventSig).
1217+
Str("Contract address", lo.Address.Hex()).
1218+
Msg("ABI doesn't match known ABI for this address; trying next ABI")
1219+
continue
1220+
}
1221+
1222+
// Validate indexed parameters count
1223+
// Non-indexed parameters are stored in the Data field,
1224+
// and much harder to validate due to dynamic types,
1225+
// so we skip them for now
1226+
var indexedParams abi.Arguments
1227+
for _, input := range evSpec.Inputs {
1228+
if input.Indexed {
1229+
indexedParams = append(indexedParams, input)
11951230
}
11961231
}
1232+
1233+
expectedIndexed := len(indexedParams)
1234+
actualIndexed := len(lo.Topics) - 1 // First topic is the event signature
1235+
1236+
if expectedIndexed != actualIndexed {
1237+
l.Trace().
1238+
Str("Event", evSpec.Name).
1239+
Int("Expected indexed param count", expectedIndexed).
1240+
Int("Actual indexed param count", actualIndexed).
1241+
Msg("Mismatch in indexed parameters; skipping event")
1242+
continue
1243+
}
1244+
1245+
// Proceed to decode the event
1246+
d := TransactionLog{lo.Topics, lo.Data}
1247+
l.Trace().
1248+
Str("Name", evSpec.RawName).
1249+
Str("Signature", evSpec.Sig).
1250+
Msg("Unpacking event")
1251+
1252+
eventsMap, topicsMap, err := decodeEventFromLog(l, *contractABI, *evSpec, d)
1253+
if err != nil {
1254+
l.Error().
1255+
Err(err).
1256+
Str("Event", evSpec.Name).
1257+
Msg("Failed to decode event; skipping")
1258+
continue // Skip this event instead of returning an error
1259+
}
1260+
1261+
parsedEvent := decodedLogFromMaps(&DecodedTransactionLog{}, eventsMap, topicsMap)
1262+
decodedTransactionLog, ok := parsedEvent.(*DecodedTransactionLog)
1263+
if ok {
1264+
decodedTransactionLog.Signature = evSpec.Sig
1265+
m.mergeLogMeta(decodedTransactionLog, lo)
1266+
eventsParsed = append(eventsParsed, *decodedTransactionLog)
1267+
l.Trace().
1268+
Interface("Log", parsedEvent).
1269+
Msg("Transaction log decoded successfully")
1270+
matched = true
1271+
break // Move to the next log after successful decoding
1272+
}
1273+
1274+
l.Trace().
1275+
Str("Actual type", fmt.Sprintf("%T", decodedTransactionLog)).
1276+
Msg("Failed to cast decoded event to DecodedTransactionLog")
1277+
}
1278+
1279+
if !matched {
1280+
l.Warn().
1281+
Str("Signature", eventSig).
1282+
Msg("No matching event with valid indexed parameter count found for log")
11971283
}
11981284
}
11991285
return eventsParsed, nil
12001286
}
12011287

1288+
type eventWithABI struct {
1289+
ContractABI *abi.ABI
1290+
EventSpec *abi.Event
1291+
}
1292+
1293+
// buildEventSignatureMap precomputes a mapping from event signature to events with their ABIs
1294+
func buildEventSignatureMap(allABIs []*abi.ABI) map[string][]*eventWithABI {
1295+
sigMap := make(map[string][]*eventWithABI)
1296+
for _, a := range allABIs {
1297+
for _, ev := range a.Events {
1298+
event := ev //nolint:copyloopvar // Explicitly keeping the copy for clarity
1299+
sigMap[ev.ID.Hex()] = append(sigMap[ev.ID.Hex()], &eventWithABI{
1300+
ContractABI: a,
1301+
EventSpec: &event,
1302+
})
1303+
}
1304+
}
1305+
1306+
return sigMap
1307+
}
1308+
12021309
// WaitUntilNoPendingTxForRootKey waits until there's no pending transaction for root key. If after timeout there are still pending transactions, it returns error.
12031310
func (m *Client) WaitUntilNoPendingTxForRootKey(timeout time.Duration) error {
12041311
return m.WaitUntilNoPendingTx(m.MustGetRootKeyAddress(), timeout)

seth/contract_map.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,15 +45,15 @@ func (c ContractMap) GetContractName(addr string) string {
4545
return c.addressMap[strings.ToLower(addr)]
4646
}
4747

48-
func (c ContractMap) GetContractAddress(addr string) string {
49-
if addr == UNKNOWN {
48+
func (c ContractMap) GetContractAddress(name string) string {
49+
if name == UNKNOWN {
5050
return UNKNOWN
5151
}
5252

5353
c.mu.Lock()
5454
defer c.mu.Unlock()
5555
for k, v := range c.addressMap {
56-
if v == addr {
56+
if v == name {
5757
return k
5858
}
5959
}

tools/breakingchanges/cmd/main.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,8 @@ func getRetractedTags(goModPath string) ([]*semver.Constraints, error) {
8585

8686
func getLatestTag(pathPrefix string, retractedTags []*semver.Constraints) (string, error) {
8787
// use regex to find exact matches, as otherwise might include pre-release versions
88-
// or versions that partially match the path prefix, e.g. when seraching for 'lib'
89-
// we won't make sure we won't include tags like `lib/grafana/v1.0.0`
88+
// or versions that partially match the path prefix, e.g. when searching for 'lib'
89+
// we want to make sure we won't include tags like `lib/grafana/v1.0.0`
9090
grepRegex := fmt.Sprintf("^%s/v[0-9]+\\.[0-9]+\\.[0-9]+$", pathPrefix)
9191

9292
//nolint

0 commit comments

Comments
 (0)