88 "net/http"
99 "strconv"
1010 "sync"
11+ "time"
1112
1213 "github.com/ethereum/go-ethereum/common"
1314 "github.com/ethereum/go-ethereum/common/hexutil"
@@ -19,82 +20,138 @@ type testBeaconClient struct {
1920 slot uint64
2021}
2122
23+ func (b * testBeaconClient ) Stop () {
24+ return
25+ }
26+
2227func (b * testBeaconClient ) isValidator (pubkey PubkeyHex ) bool {
2328 return true
2429}
2530func (b * testBeaconClient ) getProposerForNextSlot (requestedSlot uint64 ) (PubkeyHex , error ) {
2631 return PubkeyHex (hexutil .Encode (b .validator .Pk )), nil
2732}
28- func (b * testBeaconClient ) onForkchoiceUpdate () ( uint64 , error ) {
29- return b . slot , nil
33+ func (b * testBeaconClient ) Start () error {
34+ return nil
3035}
3136
3237type BeaconClient struct {
33- endpoint string
38+ endpoint string
39+ slotsInEpoch uint64
40+ secondsInSlot uint64
41+
42+ mu sync.Mutex
43+ slotProposerMap map [uint64 ]PubkeyHex
3444
35- mu sync.Mutex
36- currentEpoch uint64
37- currentSlot uint64
38- nextSlotProposer PubkeyHex
39- slotProposerMap map [uint64 ]PubkeyHex
45+ closeCh chan struct {}
4046}
4147
42- func NewBeaconClient (endpoint string ) * BeaconClient {
48+ func NewBeaconClient (endpoint string , slotsInEpoch uint64 , secondsInSlot uint64 ) * BeaconClient {
4349 return & BeaconClient {
4450 endpoint : endpoint ,
51+ slotsInEpoch : slotsInEpoch ,
52+ secondsInSlot : secondsInSlot ,
4553 slotProposerMap : make (map [uint64 ]PubkeyHex ),
54+ closeCh : make (chan struct {}),
4655 }
4756}
4857
58+ func (b * BeaconClient ) Stop () {
59+ close (b .closeCh )
60+ }
61+
4962func (b * BeaconClient ) isValidator (pubkey PubkeyHex ) bool {
5063 return true
5164}
5265
5366func (b * BeaconClient ) getProposerForNextSlot (requestedSlot uint64 ) (PubkeyHex , error ) {
54- /* Only returns proposer if requestedSlot is currentSlot + 1, would be a race otherwise */
5567 b .mu .Lock ()
5668 defer b .mu .Unlock ()
5769
58- if b .currentSlot + 1 != requestedSlot {
59- return PubkeyHex ("" ), errors .New ("slot out of sync" )
70+ nextSlotProposer , found := b .slotProposerMap [requestedSlot ]
71+ if ! found {
72+ log .Error ("inconsistent proposer mapping" , "requestSlot" , requestedSlot , "slotProposerMap" , b .slotProposerMap )
73+ return PubkeyHex ("" ), errors .New ("inconsistent proposer mapping" )
6074 }
61- return b . nextSlotProposer , nil
75+ return nextSlotProposer , nil
6276}
6377
64- /* Returns next slot's proposer pubkey */
65- // TODO: what happens if no block for previous slot - should still get next slot
66- func (b * BeaconClient ) onForkchoiceUpdate () (uint64 , error ) {
67- b .mu .Lock ()
68- defer b .mu .Unlock ()
78+ func (b * BeaconClient ) Start () error {
79+ go b .UpdateValidatorMapForever ()
80+ return nil
81+ }
6982
83+ func (b * BeaconClient ) UpdateValidatorMapForever () {
84+ durationPerSlot := time .Duration (b .secondsInSlot ) * time .Second
85+
86+ prevFetchSlot := uint64 (0 )
87+
88+ // fetch current epoch if beacon is online
7089 currentSlot , err := fetchCurrentSlot (b .endpoint )
7190 if err != nil {
72- return 0 , err
91+ log .Error ("could not get current slot" , "err" , err )
92+ } else {
93+ currentEpoch := currentSlot / b .slotsInEpoch
94+ slotProposerMap , err := fetchEpochProposersMap (b .endpoint , currentEpoch )
95+ if err != nil {
96+ log .Error ("could not fetch validators map" , "epoch" , currentEpoch , "err" , err )
97+ } else {
98+ b .mu .Lock ()
99+ b .slotProposerMap = slotProposerMap
100+ b .mu .Unlock ()
101+ }
73102 }
74103
75- nextSlot := currentSlot + 1
104+ retryDelay := time . Second
76105
77- b .currentSlot = currentSlot
78- nextSlotEpoch := nextSlot / 32
106+ // Every half epoch request validators map, polling for the slot
107+ // more frequently to avoid missing updates on errors
108+ timer := time .NewTimer (retryDelay )
109+ defer timer .Stop ()
110+ for true {
111+ select {
112+ case <- b .closeCh :
113+ return
114+ case <- timer .C :
115+ }
79116
80- if nextSlotEpoch != b .currentEpoch {
81- // TODO: this should be prepared in advance, possibly just fetch for next epoch in advance
82- slotProposerMap , err := fetchEpochProposersMap (b .endpoint , nextSlotEpoch )
117+ currentSlot , err := fetchCurrentSlot (b .endpoint )
83118 if err != nil {
84- return 0 , err
119+ log .Error ("could not get current slot" , "err" , err )
120+ timer .Reset (retryDelay )
121+ continue
85122 }
86123
87- b .currentEpoch = nextSlotEpoch
88- b .slotProposerMap = slotProposerMap
89- }
124+ // TODO: should poll after consistent slot within the epoch (slot % slotsInEpoch/2 == 0)
125+ nextFetchSlot := prevFetchSlot + b .slotsInEpoch / 2
126+ if currentSlot < nextFetchSlot {
127+ timer .Reset (time .Duration (nextFetchSlot - currentSlot ) * durationPerSlot )
128+ continue
129+ }
90130
91- nextSlotProposer , found := b .slotProposerMap [nextSlot ]
92- if ! found {
93- log .Error ("inconsistent proposer mapping" , "currentSlot" , currentSlot , "slotProposerMap" , b .slotProposerMap )
94- return 0 , errors .New ("inconsistent proposer mapping" )
131+ currentEpoch := currentSlot / b .slotsInEpoch
132+ slotProposerMap , err := fetchEpochProposersMap (b .endpoint , currentEpoch + 1 )
133+ if err != nil {
134+ log .Error ("could not fetch validators map" , "epoch" , currentEpoch + 1 , "err" , err )
135+ timer .Reset (retryDelay )
136+ continue
137+ }
138+
139+ prevFetchSlot = currentSlot
140+ b .mu .Lock ()
141+ // remove previous epoch slots
142+ for k := range b .slotProposerMap {
143+ if k < currentEpoch * b .slotsInEpoch {
144+ delete (b .slotProposerMap , k )
145+ }
146+ }
147+ // update the slot proposer map for next epoch
148+ for k , v := range slotProposerMap {
149+ b .slotProposerMap [k ] = v
150+ }
151+ b .mu .Unlock ()
152+
153+ timer .Reset (time .Duration (nextFetchSlot - currentSlot ) * durationPerSlot )
95154 }
96- b .nextSlotProposer = nextSlotProposer
97- return nextSlot , nil
98155}
99156
100157func fetchCurrentSlot (endpoint string ) (uint64 , error ) {
0 commit comments