Skip to content

Commit ad464c7

Browse files
committed
Trigger decryption based on blocks and slots
Before this commit, decryption for slot n was triggered at the start of slot n. As validators are supposed to propose at the start of their slot, this gives them no time to receive the decryption keys. This commit changes the condition to trigger decryption for slot n either when the block for slot n-1 has been observed or when 1/3 of slot n-1 has passed, whatever is first.
1 parent 4e536e5 commit ad464c7

File tree

6 files changed

+114
-33
lines changed

6 files changed

+114
-33
lines changed

rolling-shutter/keyperimpl/gnosis/keyper.go

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,23 @@ import (
3030

3131
var ErrParseKeyperSet = errors.New("cannot parse KeyperSet")
3232

33+
// The relative proposal timeout specifies for how long we wait for a block proposal to appear in
34+
// a block. If we don't receive one in this time, we assume the slot is empty. The timeout is
35+
// given as a fraction of the slot duration.
36+
const (
37+
relativeProposalTimeoutNumerator = 1
38+
relativeProposalTimeoutDenominator = 3
39+
)
40+
3341
type Keyper struct {
34-
core *keyper.KeyperCore
35-
config *Config
36-
dbpool *pgxpool.Pool
37-
client *ethclient.Client
38-
chainSyncClient *chainsync.Client
39-
sequencerSyncer *SequencerSyncer
40-
eonKeyPublisher *eonkeypublisher.EonKeyPublisher
42+
core *keyper.KeyperCore
43+
config *Config
44+
dbpool *pgxpool.Pool
45+
client *ethclient.Client
46+
chainSyncClient *chainsync.Client
47+
sequencerSyncer *SequencerSyncer
48+
eonKeyPublisher *eonkeypublisher.EonKeyPublisher
49+
latestTriggeredSlot *uint64
4150

4251
// input events
4352
newBlocks chan *syncevent.LatestBlock
@@ -67,9 +76,15 @@ func (kpr *Keyper) Start(ctx context.Context, runner service.Runner) error {
6776
runner.Defer(func() { close(kpr.newEonPublicKeys) })
6877
runner.Defer(func() { close(kpr.decryptionTriggerChannel) })
6978

79+
kpr.latestTriggeredSlot = nil
80+
81+
offset := -(time.Duration(kpr.config.Gnosis.SecondsPerSlot) * time.Second) *
82+
(relativeProposalTimeoutDenominator - relativeProposalTimeoutNumerator) /
83+
relativeProposalTimeoutDenominator
7084
kpr.slotTicker = slotticker.NewSlotTicker(
7185
time.Duration(kpr.config.Gnosis.SecondsPerSlot*uint64(time.Second)),
7286
time.Unix(int64(kpr.config.Gnosis.GenesisSlotTimestamp), 0),
87+
offset,
7388
)
7489

7590
kpr.dbpool, err = db.Connect(ctx, runner, kpr.config.DatabaseURL, database.Definition.Name())

rolling-shutter/keyperimpl/gnosis/newblock.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package gnosis
33
import (
44
"context"
55

6+
"github.com/shutter-network/rolling-shutter/rolling-shutter/medley"
67
syncevent "github.com/shutter-network/rolling-shutter/rolling-shutter/medley/chainsync/event"
78
)
89

@@ -12,5 +13,10 @@ func (kpr *Keyper) processNewBlock(ctx context.Context, ev *syncevent.LatestBloc
1213
return err
1314
}
1415
}
15-
return nil
16+
slot := medley.BlockTimestampToSlot(
17+
ev.Header.Time,
18+
kpr.config.Gnosis.GenesisSlotTimestamp,
19+
kpr.config.Gnosis.SecondsPerSlot,
20+
)
21+
return kpr.maybeTriggerDecryption(ctx, slot+1)
1622
}

rolling-shutter/keyperimpl/gnosis/newslot.go

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,21 @@ const maxTxPointerAge = 2
2828
var errZeroTxPointerAge = errors.New("tx pointer has age 0")
2929

3030
func (kpr *Keyper) processNewSlot(ctx context.Context, slot slotticker.Slot) error {
31+
return kpr.maybeTriggerDecryption(ctx, slot.Number)
32+
}
33+
34+
// maybeTriggerDecryption triggers decryption for the given slot if
35+
// - it hasn't been triggered for this slot before and
36+
// - the keyper is part of the corresponding keyper set.
37+
func (kpr *Keyper) maybeTriggerDecryption(ctx context.Context, slot uint64) error {
38+
if kpr.latestTriggeredSlot != nil && slot <= *kpr.latestTriggeredSlot {
39+
return nil
40+
}
41+
kpr.latestTriggeredSlot = &slot
42+
3143
fmt.Println("")
3244
fmt.Println("")
33-
fmt.Println(slot.Number)
45+
fmt.Println(slot)
3446
fmt.Println("")
3547
fmt.Println("")
3648

@@ -39,20 +51,20 @@ func (kpr *Keyper) processNewSlot(ctx context.Context, slot slotticker.Slot) err
3951
if err != nil {
4052
return errors.Wrap(err, "failed to query synced until from db")
4153
}
42-
if syncedUntil.Slot >= int64(slot.Number) {
54+
if syncedUntil.Slot >= int64(slot) {
4355
// If we already synced the block for slot n before this slot has started on our clock,
4456
// either the previous block proposer proposed early (ie is malicious) or our clocks are
4557
// out of sync. In any case, it does not make sense to produce keys as the block has
4658
// already been built, so we return an error.
47-
return errors.Errorf("processing slot %d for which a block has already been processed", slot.Number)
59+
return errors.Errorf("processing slot %d for which a block has already been processed", slot)
4860
}
4961
nextBlock := syncedUntil.BlockNumber + 1
5062

5163
queries := obskeyper.New(kpr.dbpool)
5264
keyperSet, err := queries.GetKeyperSet(ctx, nextBlock)
5365
if err == pgx.ErrNoRows {
5466
log.Debug().
55-
Uint64("slot", slot.Number).
67+
Uint64("slot", slot).
5668
Int64("block-number", nextBlock).
5769
Msg("skipping slot as no keyper set has been found for it")
5870
return nil
@@ -64,7 +76,7 @@ func (kpr *Keyper) processNewSlot(ctx context.Context, slot slotticker.Slot) err
6476
return kpr.triggerDecryption(ctx, slot, nextBlock, &keyperSet)
6577
}
6678
log.Debug().
67-
Uint64("slot", slot.Number).
79+
Uint64("slot", slot).
6880
Int64("block-number", nextBlock).
6981
Int64("keyper-set-index", keyperSet.KeyperConfigIndex).
7082
Str("address", kpr.config.GetAddress().Hex()).
@@ -134,7 +146,7 @@ func (kpr *Keyper) getTxPointer(ctx context.Context, eon int64, slot int64, keyp
134146

135147
func (kpr *Keyper) triggerDecryption(
136148
ctx context.Context,
137-
slot slotticker.Slot,
149+
slot uint64,
138150
nextBlock int64,
139151
keyperSet *obskeyper.KeyperSet,
140152
) error {
@@ -147,10 +159,10 @@ func (kpr *Keyper) triggerDecryption(
147159
}
148160
eon := eonStruct.Eon
149161

150-
txPointer, err := kpr.getTxPointer(ctx, eon, int64(slot.Number), keyperSet.KeyperConfigIndex)
162+
txPointer, err := kpr.getTxPointer(ctx, eon, int64(slot), keyperSet.KeyperConfigIndex)
151163
if err == errZeroTxPointerAge {
152164
log.Warn().
153-
Uint64("slot", slot.Number).
165+
Uint64("slot", slot).
154166
Int64("block-number", nextBlock).
155167
Int64("eon", eon).
156168
Int64("tx-pointer", txPointer).
@@ -166,7 +178,7 @@ func (kpr *Keyper) triggerDecryption(
166178
}
167179
err = gnosisKeyperDB.SetCurrentDecryptionTrigger(ctx, gnosisdatabase.SetCurrentDecryptionTriggerParams{
168180
Eon: eon,
169-
Slot: int64(slot.Number),
181+
Slot: int64(slot),
170182
TxPointer: txPointer,
171183
IdentitiesHash: computeIdentitiesHash(identityPreimages),
172184
})
@@ -179,7 +191,7 @@ func (kpr *Keyper) triggerDecryption(
179191
}
180192
event := broker.NewEvent(&trigger)
181193
log.Debug().
182-
Uint64("slot", slot.Number).
194+
Uint64("slot", slot).
183195
Uint64("block-number", uint64(nextBlock)).
184196
Int("num-identities", len(trigger.IdentityPreimages)).
185197
Int64("tx-pointer", txPointer).
@@ -190,7 +202,7 @@ func (kpr *Keyper) triggerDecryption(
190202
}
191203

192204
func (kpr *Keyper) getDecryptionIdentityPreimages(
193-
ctx context.Context, slot slotticker.Slot, eon int64, txPointer int64,
205+
ctx context.Context, slot uint64, eon int64, txPointer int64,
194206
) ([]identitypreimage.IdentityPreimage, error) {
195207
identityPreimages := []identitypreimage.IdentityPreimage{}
196208

@@ -243,13 +255,13 @@ func transactionSubmittedEventToIdentityPreimage(
243255
return identitypreimage.IdentityPreimage(buf.Bytes()), nil
244256
}
245257

246-
func makeSlotIdentityPreimage(slot slotticker.Slot) identitypreimage.IdentityPreimage {
258+
func makeSlotIdentityPreimage(slot uint64) identitypreimage.IdentityPreimage {
247259
// 32 bytes of zeros plus the block number as big endian (ie starting with lots of zeros as well)
248260
// this ensures the block identity preimage is always alphanumerically before any transaction
249261
// identity preimages.
250262
var buf bytes.Buffer
251263
buf.Write(common.BigToHash(common.Big0).Bytes())
252-
buf.Write(common.BigToHash(new(big.Int).SetUint64(slot.Number)).Bytes())
264+
buf.Write(common.BigToHash(new(big.Int).SetUint64(slot)).Bytes())
253265

254266
return identitypreimage.IdentityPreimage(buf.Bytes())
255267
}

rolling-shutter/medley/slots.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
package medley
22

3-
func BlockTimestampToSlot(blockTimestamp uint64, secondsPerSlot uint64, genesisSlotTimestamp uint64) uint64 {
3+
func BlockTimestampToSlot(blockTimestamp uint64, genesisSlotTimestamp uint64, secondsPerSlot uint64) uint64 {
44
return (blockTimestamp - genesisSlotTimestamp) / secondsPerSlot
55
}

rolling-shutter/medley/slotticker/slotticker.go

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,16 @@ type SlotTicker struct {
2424
C chan Slot
2525
slotDuration time.Duration
2626
genesisSlotTime time.Time
27+
offset time.Duration
2728
}
2829

29-
func NewSlotTicker(slotDuration time.Duration, genesisSlotTime time.Time) *SlotTicker {
30+
func NewSlotTicker(slotDuration time.Duration, genesisSlotTime time.Time, offset time.Duration) *SlotTicker {
3031
c := make(chan Slot, 1)
3132
return &SlotTicker{
3233
C: c,
3334
slotDuration: slotDuration,
3435
genesisSlotTime: genesisSlotTime,
36+
offset: offset,
3537
}
3638
}
3739

@@ -64,14 +66,7 @@ func (t *SlotTicker) run(ctx context.Context) error {
6466

6567
for {
6668
now := time.Now()
67-
timeSinceGenesis := now.Sub(t.genesisSlotTime)
68-
69-
var nextSlotNumber uint64
70-
if timeSinceGenesis < 0 {
71-
nextSlotNumber = 0
72-
} else {
73-
nextSlotNumber = uint64(timeSinceGenesis/t.slotDuration) + 1
74-
}
69+
nextSlotNumber, nextTickTime := calcNextTick(now, t.genesisSlotTime, t.slotDuration, t.offset)
7570

7671
if prevSlotNumber != nil {
7772
expectedNextSlotNumber := *prevSlotNumber + 1
@@ -95,8 +90,7 @@ func (t *SlotTicker) run(ctx context.Context) error {
9590
}
9691
}
9792

98-
nextSlotTime := t.genesisSlotTime.Add(t.slotDuration * time.Duration(nextSlotNumber))
99-
timeToNextSlot := nextSlotTime.Sub(now)
93+
timeToNextSlot := nextTickTime.Sub(now)
10094
timer.Reset(timeToNextSlot)
10195
<-timer.C
10296

@@ -107,3 +101,13 @@ func (t *SlotTicker) run(ctx context.Context) error {
107101
prevSlotNumber = &nextSlotNumber
108102
}
109103
}
104+
105+
func calcNextTick(now time.Time, genesisSlotTime time.Time, slotDuration time.Duration, offset time.Duration) (uint64, time.Time) {
106+
firstTick := genesisSlotTime.Add(offset)
107+
if now.Before(firstTick) {
108+
return 0, firstTick
109+
}
110+
slot := uint64((now.Sub(genesisSlotTime) - offset + slotDuration - 1) / slotDuration)
111+
tick := genesisSlotTime.Add(slotDuration * time.Duration(slot)).Add(offset)
112+
return slot, tick
113+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package slotticker
2+
3+
import (
4+
"testing"
5+
"time"
6+
7+
"gotest.tools/assert"
8+
)
9+
10+
func TestCalcNextSlot(t *testing.T) {
11+
duration := time.Second * 5
12+
genesisTime := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)
13+
14+
epsilon := time.Millisecond
15+
16+
for _, testCase := range []struct {
17+
timeSinceGenesis time.Duration
18+
offset time.Duration
19+
slot uint64
20+
}{
21+
{0, 0, 0},
22+
{-time.Second * 100, 0, 0},
23+
{epsilon, 0, 1},
24+
{duration / 2, 0, 1},
25+
{duration, 0, 1},
26+
{2 * duration, 0, 2},
27+
{100*duration - epsilon, 0, 100},
28+
29+
{-time.Second, -time.Second, 0},
30+
{0, -time.Second, 1},
31+
{4 * time.Second, -time.Second, 1},
32+
{4*time.Second + epsilon, -time.Second, 2},
33+
{100*duration - time.Second, -time.Second, 100},
34+
{100*duration - time.Second + epsilon, -time.Second, 101},
35+
} {
36+
t.Run("", func(t *testing.T) {
37+
now := genesisTime.Add(testCase.timeSinceGenesis)
38+
slot, tick := calcNextTick(now, genesisTime, duration, testCase.offset)
39+
assert.Equal(t, testCase.slot, slot)
40+
expectedTick := genesisTime.Add(duration * time.Duration(testCase.slot)).Add(testCase.offset)
41+
assert.Equal(t, tick, expectedTick)
42+
})
43+
}
44+
}

0 commit comments

Comments
 (0)