@@ -3,6 +3,7 @@ package watchtower
33import (
44 "bytes"
55 "context"
6+ "encoding/binary"
67 "fmt"
78 "math/big"
89 "strings"
@@ -236,6 +237,46 @@ const (
236237 twapNumberOfSeconds uint32 = 60 * 60 * 12 // 12 hours
237238)
238239
240+ // getIndexToSubmit deterministically selects the ODAO member index whose turn
241+ // it is to submit during the turn that contains the given block number.
242+ // The shuffle is derived from a keccak256-seeded permutation for the same
243+ func getIndexToSubmit (blockNumber , count uint64 ) uint64 {
244+ if count <= 1 {
245+ return 0
246+ }
247+
248+ turn := blockNumber / BlocksPerTurn
249+ epoch := turn / count
250+ position := turn % count
251+
252+ // Seed the shuffle with keccak256(epoch || count) so that adding or
253+ // removing an ODAO member rerolls the order rather than producing a
254+ // permutation that happens to overlap with a previous one.
255+ var seed [16 ]byte
256+ binary .BigEndian .PutUint64 (seed [0 :8 ], epoch )
257+ binary .BigEndian .PutUint64 (seed [8 :16 ], count )
258+ digest := crypto .Keccak256 (seed [:])
259+
260+ perm := make ([]uint64 , count )
261+ for i := range perm {
262+ perm [i ] = uint64 (i )
263+ }
264+
265+ offset := 0
266+ for i := count - 1 ; i > 0 ; i -- {
267+ if offset + 8 > len (digest ) {
268+ digest = crypto .Keccak256 (digest )
269+ offset = 0
270+ }
271+ r := binary .BigEndian .Uint64 (digest [offset : offset + 8 ])
272+ offset += 8
273+ j := r % (i + 1 )
274+ perm [i ], perm [j ] = perm [j ], perm [i ]
275+ }
276+
277+ return perm [position ]
278+ }
279+
239280type poolObserveResponse struct {
240281 TickCumulatives []* big.Int `abi:"tickCumulatives"`
241282 SecondsPerLiquidityCumulativeX128s []* big.Int `abi:"secondsPerLiquidityCumulativeX128s"`
@@ -739,7 +780,7 @@ func (t *submitRplPrice) submitOptimismPrice() error {
739780 }
740781
741782 // Calculate whose turn it is to submit
742- indexToSubmit := (blockNumber / BlocksPerTurn ) % count
783+ indexToSubmit := getIndexToSubmit (blockNumber , count )
743784
744785 if index == indexToSubmit {
745786
@@ -860,7 +901,7 @@ func (t *submitRplPrice) submitPolygonPrice() error {
860901 }
861902
862903 // Calculate whose turn it is to submit
863- indexToSubmit := (blockNumber / BlocksPerTurn ) % count
904+ indexToSubmit := getIndexToSubmit (blockNumber , count )
864905
865906 if index == indexToSubmit {
866907
@@ -979,7 +1020,7 @@ func (t *submitRplPrice) submitArbitrumPrice(priceMessengerAddress string) error
9791020 }
9801021
9811022 // Calculate whose turn it is to submit
982- indexToSubmit := (blockNumber / BlocksPerTurn ) % count
1023+ indexToSubmit := getIndexToSubmit (blockNumber , count )
9831024
9841025 if index == indexToSubmit {
9851026
@@ -1125,7 +1166,7 @@ func (t *submitRplPrice) submitZkSyncEraPrice() error {
11251166 }
11261167
11271168 // Calculate whose turn it is to submit
1128- indexToSubmit := (blockNumber / BlocksPerTurn ) % count
1169+ indexToSubmit := getIndexToSubmit (blockNumber , count )
11291170
11301171 if index == indexToSubmit {
11311172
@@ -1264,7 +1305,7 @@ func (t *submitRplPrice) submitBasePrice() error {
12641305 }
12651306
12661307 // Calculate whose turn it is to submit
1267- indexToSubmit := (blockNumber / BlocksPerTurn ) % count
1308+ indexToSubmit := getIndexToSubmit (blockNumber , count )
12681309
12691310 if index == indexToSubmit {
12701311
@@ -1385,7 +1426,7 @@ func (t *submitRplPrice) submitScrollPrice() error {
13851426 }
13861427
13871428 // Calculate whose turn it is to submit
1388- indexToSubmit := (blockNumber / BlocksPerTurn ) % count
1429+ indexToSubmit := getIndexToSubmit (blockNumber , count )
13891430
13901431 if index == indexToSubmit {
13911432 l2GasEstimatorAddress := t .cfg .Smartnode .GetScrollFeeEstimatorAddress ()
0 commit comments