Skip to content

Latest commit

 

History

History
96 lines (66 loc) · 2.87 KB

010.md

File metadata and controls

96 lines (66 loc) · 2.87 KB

Petite Fleece Cheetah

High

Non-determinism will result in a chain halt when using a mapping

Summary

The iteration over a map will result in a chain halt.

Root Cause

The choice to iterate over a map will result in non-determinism and therefore a chain halt. According to the CosmosSDK documentation (https://docs.cosmos.network/v0.52/build/building-apps/security-part-1#go-map-internals):

When building on the Cosmos SDK, you should never iterate over a Go map. Doing so results in non-determinism. Instead, if map usage is inevitable, it is necessary to convert it to a slice and sort it. See an example here

The function findLastBlockHash() iterates over a blockHashes mapping causing a consequent chain halt.

Internal Pre-conditions

No response

External Pre-conditions

No response

Attack Path

No response

Impact

The chain will halt (which falls under high categorization according to the contest docs). There are some existing examples of the chain halt as a result of non-determinism. Here's an example of such issue (due to the map) in the thorchain:

https://gitlab.com/thorchain/thornode/-/issues/1169

PoC

Let's take a look at the findLastBlockHash() functionality:

https://github.com/sherlock-audit/2024-12-babylon/blob/main/babylon/x/checkpointing/proposal.go#L244-249

// Find the block hash that has the maximum voting power committed to it
	for blockHash, power := range blockHashes {
		if power > maxPower {
			resBlockHash = blockHash
			maxPower = power
		}
	}
	if len(resBlockHash) == 0 {
		return nil, fmt.Errorf("could not find the block hash")
	}

It can be seen here that the function iterates over a mapping of blockHashes:

https://github.com/sherlock-audit/2024-12-babylon/blob/main/babylon/x/checkpointing/proposal.go#L214-215

// Mapping between block hashes and voting power committed to them
	blockHashes := make(map[string]int64, 0)

This will result in non-determinism as on different nodes the parameter resBlockHash will be a different value depending on when iterations start that is an array with the same power will return different resBlockHash.

Here's an example of the possible outcome:

blockHashes := map[string]int64{
    "a3f1b2c4d5e6f7890123456789abcdef12345678": 500,
    "b7c8d9e0f123456789abcdef0123456789abcdef": 400,
    "c1d2e3f4a567890123456789abcdefabcdef1234": 200,
    "d4e5f6a7b890123456789abcdef0123456789abc": 300,
    "e5f6a7b8c90123456789abcdef0123456789abcd": 500,
}

Mitigation

Slice the mapping first. An example from the Allora protocol code:

// Generic function that sorts the keys of a map
// Used for deterministic ranging of maps
func GetSortedKeys[K cmp.Ordered, V any](m map[K]V) []K {
	keys := make([]K, 0, len(m))
	for k := range m {
		keys = append(keys, k)
	}
	sort.Slice(keys, func(i, j int) bool { return keys[i] < keys[j] })
	return keys
}