Skip to content

Commit 21b3f29

Browse files
authored
Merge branch 'develop' into c/lower-integration-tests-memory
2 parents 1443adb + 63190cb commit 21b3f29

8 files changed

+172
-8
lines changed

gossip/common_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ func newTestEnv(firstEpoch idx.Epoch, validatorsNum idx.Validator, tb testing.TB
140140
rules := opera.FakeNetRules()
141141
rules.Epochs.MaxEpochDuration = inter.Timestamp(maxEpochDuration)
142142
rules.Blocks.MaxEmptyBlockSkipPeriod = 0
143+
rules.Emitter.Interval = 0
143144

144145
genStore := makefakegenesis.FakeGenesisStoreWithRulesAndStart(validatorsNum, utils.ToFtm(genesisBalance), utils.ToFtm(genesisStake), rules, firstEpoch, 2)
145146
genesis := genStore.Genesis()

gossip/emitter/control.go

+45
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/Fantom-foundation/lachesis-base/utils/piecefunc"
1010

1111
"github.com/Fantom-foundation/go-opera/inter"
12+
"github.com/Fantom-foundation/go-opera/opera"
1213
)
1314

1415
func scalarUpdMetric(diff idx.Event, weight pos.Weight, totalWeight pos.Weight) ancestor.Metric {
@@ -46,6 +47,12 @@ func (em *Emitter) isAllowedToEmit(e inter.EventI, eTxs bool, metric ancestor.Me
4647
if passedTime < 0 {
4748
passedTime = 0
4849
}
50+
51+
// If a emitter interval is defined, all other heuristics are ignored.
52+
if interval, enabled := em.getEmitterIntervalLimit(); enabled {
53+
return passedTime >= interval
54+
}
55+
4956
passedTimeIdle := e.CreationTime().Time().Sub(em.prevIdleTime)
5057
if passedTimeIdle < 0 {
5158
passedTimeIdle = 0
@@ -143,3 +150,41 @@ func (em *Emitter) recheckIdleTime() {
143150
em.prevIdleTime = time.Now()
144151
}
145152
}
153+
154+
func (em *Emitter) getEmitterIntervalLimit() (interval time.Duration, enabled bool) {
155+
rules := em.world.GetRules().Emitter
156+
157+
var lastConfirmationTime time.Time
158+
if last := em.lastTimeAnEventWasConfirmed.Load(); last != nil {
159+
lastConfirmationTime = *last
160+
} else {
161+
// If we have not seen any event confirmed so far, we take the current time
162+
// as the last confirmation time. Thus, during start-up we would not unnecessarily
163+
// slow down the event emission for the very first event. The switch into the stall
164+
// mode is delayed by the stall-threshold.
165+
now := time.Now()
166+
em.lastTimeAnEventWasConfirmed.Store(&now)
167+
lastConfirmationTime = now
168+
}
169+
170+
return getEmitterIntervalLimit(rules, time.Since(lastConfirmationTime))
171+
}
172+
173+
func getEmitterIntervalLimit(
174+
rules opera.EmitterRules,
175+
delayOfLastConfirmedEvent time.Duration,
176+
) (interval time.Duration, enabled bool) {
177+
// Check whether the fixed-interval emitter should be enabled.
178+
if rules.Interval == 0 {
179+
return 0, false
180+
}
181+
182+
// Check for a network-stall situation in which events emitting should be slowed down.
183+
stallThreshold := time.Duration(rules.StallThreshold)
184+
if delayOfLastConfirmedEvent > stallThreshold {
185+
return time.Duration(rules.StalledInterval), true
186+
}
187+
188+
// Use the regular emitter interval.
189+
return time.Duration(rules.Interval), true
190+
}

gossip/emitter/control_test.go

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package emitter
2+
3+
import (
4+
"testing"
5+
"time"
6+
7+
"github.com/Fantom-foundation/go-opera/inter"
8+
"github.com/Fantom-foundation/go-opera/opera"
9+
)
10+
11+
func TestGetEmitterIntervalLimit_IsOffWhenIntervalIsZero(t *testing.T) {
12+
ms := time.Microsecond
13+
rules := opera.EmitterRules{
14+
Interval: 0,
15+
StallThreshold: inter.Timestamp(200 * ms),
16+
}
17+
for _, delay := range []time.Duration{0, 100 * ms, 199 * ms, 200 * ms, 201 * ms} {
18+
_, enabled := getEmitterIntervalLimit(rules, delay)
19+
if enabled {
20+
t.Fatal("should be disabled")
21+
}
22+
}
23+
}
24+
25+
func TestGetEmitterIntervalLimit_SwitchesToStallIfDelayed(t *testing.T) {
26+
ms := time.Millisecond
27+
regular := 100 * ms
28+
stallThreshold := 200 * ms
29+
stalled := 300 * ms
30+
31+
rules := opera.EmitterRules{
32+
Interval: inter.Timestamp(regular),
33+
StallThreshold: inter.Timestamp(stallThreshold),
34+
StalledInterval: inter.Timestamp(stalled),
35+
}
36+
37+
for _, delay := range []time.Duration{0, 100 * ms, 199 * ms, 200 * ms, 201 * ms, 60 * time.Minute} {
38+
got, enabled := getEmitterIntervalLimit(rules, delay)
39+
if !enabled {
40+
t.Fatalf("should be enabled for delay %v", delay)
41+
}
42+
want := regular
43+
if delay > stallThreshold {
44+
want = stalled
45+
}
46+
if want != got {
47+
t.Errorf("for delay %v, want %v, got %v", delay, want, got)
48+
}
49+
}
50+
}

gossip/emitter/emitter.go

+3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"os"
99
"strings"
1010
"sync"
11+
"sync/atomic"
1112
"time"
1213

1314
"github.com/Fantom-foundation/go-opera/utils"
@@ -107,6 +108,8 @@ type Emitter struct {
107108
logger.Periodic
108109

109110
baseFeeSource BaseFeeSource
111+
112+
lastTimeAnEventWasConfirmed atomic.Pointer[time.Time]
110113
}
111114

112115
type BaseFeeSource interface {

gossip/emitter/hooks.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
package emitter
22

33
import (
4-
"github.com/Fantom-foundation/go-opera/utils/txtime"
54
"time"
65

6+
"github.com/Fantom-foundation/go-opera/utils/txtime"
7+
78
"github.com/Fantom-foundation/lachesis-base/emitter/ancestor"
89
"github.com/Fantom-foundation/lachesis-base/inter/idx"
910
"github.com/Fantom-foundation/lachesis-base/inter/pos"
@@ -117,6 +118,8 @@ func (em *Emitter) OnEventConfirmed(he inter.EventI) {
117118
if !em.isValidator() {
118119
return
119120
}
121+
now := time.Now()
122+
em.lastTimeAnEventWasConfirmed.Store(&now)
120123
if em.pendingGas > he.GasPowerUsed() {
121124
em.pendingGas -= he.GasPowerUsed()
122125
} else {

opera/rules.go

+51
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ type RulesRLP struct {
6565
// Graph options
6666
Dag DagRules
6767

68+
// Emitter options
69+
Emitter EmitterRules
70+
6871
// Epochs options
6972
Epochs EpochsRules
7073

@@ -115,6 +118,44 @@ type DagRules struct {
115118
MaxExtraData uint32
116119
}
117120

121+
// EmitterRules contains options for the emitter of Lachesis events.
122+
type EmitterRules struct {
123+
// Interval defines the length of the period
124+
// between events produced by the emitter in milliseconds.
125+
// If set to zero, a heuristic is used producing irregular
126+
// intervals.
127+
//
128+
// The Interval is used to control the rate of event
129+
// production by the emitter. It thus indirectly controls
130+
// the rate of blocks production on the network, by providing
131+
// a lower bound. The actual block production rate is also
132+
// influenced by the number of validators, their weighting,
133+
// and the inter-connection of events. However, the Interval
134+
// should provide an effective mean to control the block
135+
// production rate.
136+
Interval inter.Timestamp
137+
138+
// StallThreshold defines a maximum time the confirmation of
139+
// new events may be delayed before the emitter considers the
140+
// network stalled.
141+
//
142+
// The emitter has two modes: normal and stalled. In normal
143+
// mode, the emitter produces events at a regular interval, as
144+
// defined by the Interval option. In stalled mode, the emitter
145+
// produces events at a much lower rate, to avoid building up
146+
// a backlog of events. The StallThreshold defines the upper
147+
// limit of delay seen for new confirmed events before the emitter
148+
// switches to stalled mode.
149+
//
150+
// This option is disabled if Interval is set to 0.
151+
StallThreshold inter.Timestamp
152+
153+
// StallInterval defines the length of the period between
154+
// events produced by the emitter in milliseconds when the
155+
// network is stalled.
156+
StalledInterval inter.Timestamp
157+
}
158+
118159
// BlocksMissed is information about missed blocks from a staker
119160
type BlocksMissed struct {
120161
BlocksNum idx.Block
@@ -250,6 +291,7 @@ func MainNetRules() Rules {
250291
Name: "main",
251292
NetworkID: MainNetworkID,
252293
Dag: DefaultDagRules(),
294+
Emitter: DefaultEmitterRules(),
253295
Epochs: DefaultEpochsRules(),
254296
Economy: DefaultEconomyRules(),
255297
Blocks: BlocksRules{
@@ -264,6 +306,7 @@ func FakeNetRules() Rules {
264306
Name: "fake",
265307
NetworkID: FakeNetworkID,
266308
Dag: DefaultDagRules(),
309+
Emitter: DefaultEmitterRules(),
267310
Epochs: FakeNetEpochsRules(),
268311
Economy: FakeEconomyRules(),
269312
Blocks: BlocksRules{
@@ -308,6 +351,14 @@ func DefaultDagRules() DagRules {
308351
}
309352
}
310353

354+
func DefaultEmitterRules() EmitterRules {
355+
return EmitterRules{
356+
Interval: inter.Timestamp(600 * time.Millisecond),
357+
StallThreshold: inter.Timestamp(30 * time.Second),
358+
StalledInterval: inter.Timestamp(60 * time.Second),
359+
}
360+
}
361+
311362
func DefaultEpochsRules() EpochsRules {
312363
return EpochsRules{
313364
MaxEpochGas: 1500000000,

tests/block_header_test.go

+14-3
Original file line numberDiff line numberDiff line change
@@ -279,10 +279,21 @@ func testHeaders_MixDigestDiffersForAllBlocks(t *testing.T, headers []*types.Hea
279279
seen := map[common.Hash]struct{}{}
280280

281281
for i := 1; i < len(headers); i++ {
282-
_, ok := seen[headers[i].MixDigest]
283-
require.False(ok, "mix digest is not unique")
284-
seen[headers[i].MixDigest] = struct{}{}
282+
// We skip empty blocks, since in those cases the MixDigest value is not
283+
// consumed by any transaction. For those cases, values may be reused.
284+
// Since the prev-randao value filling this field is computed based on
285+
// the hash of non-empty lachesis events, the value used for empty blocks
286+
// is always the same.
287+
header := headers[i]
288+
if header.GasUsed == 0 {
289+
continue
290+
}
291+
digest := header.MixDigest
292+
_, found := seen[digest]
293+
require.False(found, "mix digest is not unique, block %d, value %x", i, digest)
294+
seen[digest] = struct{}{}
285295
}
296+
require.NotZero(len(seen), "no non-empty blocks in the chain")
286297
}
287298

288299
func testHeaders_LastBlockOfEpochContainsSealingTransaction(t *testing.T, headers []*types.Header, client *ethclient.Client) {

tests/gas_price_suggestion_test.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,10 @@ func TestGasPrice_SuggestedGasPricesApproximateActualBaseFees(t *testing.T) {
3737
}
3838

3939
// Suggestions should over-estimate the actual prices by ~10%
40-
for i := range suggestions {
41-
ratio := float64(suggestions[i]) / float64(fees[i])
42-
require.Less(1.09, ratio)
43-
require.Less(ratio, 1.11)
40+
for i := 1; i < int(len(suggestions)); i++ {
41+
ratio := float64(suggestions[i]) / float64(fees[i-1])
42+
require.Less(1.09, ratio, "step %d, suggestion %d, fees %d", i, suggestions[i], fees[i-1])
43+
require.Less(ratio, 1.11, "step %d, suggestion %d, fees %d", i, suggestions[i], fees[i-1])
4444
}
4545
}
4646

0 commit comments

Comments
 (0)