Skip to content

Commit 8087723

Browse files
committed
chore(op): improve sync client
1 parent 152a30d commit 8087723

File tree

8 files changed

+262
-67
lines changed

8 files changed

+262
-67
lines changed

rolling-shutter/keyperimpl/optimism/bootstrap/config.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ func NewConfig() *Config {
1919

2020
func (c *Config) Init() {
2121
c.SigningKey = &keys.ECDSAPrivate{}
22-
c.ByActivationBlockNumber = number.NewBlockNumber()
22+
c.ByActivationBlockNumber = number.NewBlockNumber(nil)
2323
}
2424

2525
type Config struct {

rolling-shutter/keyperimpl/optimism/keyper.go

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package optimism
33
import (
44
"context"
55

6+
"github.com/ethereum/go-ethereum/accounts/abi/bind"
67
"github.com/jackc/pgx/v4"
78
"github.com/jackc/pgx/v4/pgxpool"
89
"github.com/pkg/errors"
@@ -28,9 +29,10 @@ import (
2829
var ErrParseKeyperSet = errors.New("can't parse KeyperSet")
2930

3031
type Keyper struct {
31-
core *keyper.KeyperCore
32-
dbpool *pgxpool.Pool
33-
config *config.Config
32+
core *keyper.KeyperCore
33+
l2Client *shopclient.ShutterL2Client
34+
dbpool *pgxpool.Pool
35+
config *config.Config
3436

3537
trigger chan<- *broker.Event[*epochkghandler.DecryptionTrigger]
3638
}
@@ -50,7 +52,6 @@ func (kpr *Keyper) Start(ctx context.Context, runner service.Runner) error {
5052
}
5153
kpr.dbpool = dbpool
5254

53-
// TODO: the new latest block handler function will put values into this channel
5455
trigger := make(chan *broker.Event[*epochkghandler.DecryptionTrigger])
5556
kpr.trigger = trigger
5657

@@ -77,17 +78,17 @@ func (kpr *Keyper) Start(ctx context.Context, runner service.Runner) error {
7778
return errors.Wrap(err, "can't instantiate keyper core")
7879
}
7980
// TODO: wrap the logger and pass in
80-
l2Client, err := shopclient.NewShutterL2Client(
81+
kpr.l2Client, err = shopclient.NewShutterL2Client(
8182
ctx,
8283
shopclient.WithClientURL(kpr.config.Optimism.JSONRPCURL),
8384
shopclient.WithSyncNewBlock(kpr.newBlock),
8485
shopclient.WithSyncNewKeyperSet(kpr.newKeyperSet),
86+
shopclient.WithPrivateKey(kpr.config.Optimism.PrivateKey.Key),
8587
)
86-
// TODO: how to deal with polling past state? (sounds like a big addition to the l2Client)
8788
if err != nil {
8889
return err
8990
}
90-
return runner.StartService(kpr.core, l2Client)
91+
return runner.StartService(kpr.core, kpr.l2Client)
9192
}
9293

9394
func (kpr *Keyper) newBlock(_ context.Context, ev *shopevent.LatestBlock) error {
@@ -139,11 +140,34 @@ func (kpr *Keyper) newKeyperSet(ctx context.Context, ev *shopevent.KeyperSet) er
139140
})
140141
}
141142

142-
func (kpr *Keyper) newEonPublicKey(ctx context.Context, pk keyper.EonPublicKey) error {
143+
func (kpr *Keyper) newEonPublicKey(ctx context.Context, pubKey keyper.EonPublicKey) error {
143144
log.Info().
144-
Uint64("eon", pk.Eon).
145-
Uint64("activation-block", pk.ActivationBlock).
145+
Uint64("eon", pubKey.Eon).
146+
Uint64("activation-block", pubKey.ActivationBlock).
146147
Msg("new eon pk")
147-
// TODO: post the public key to the contract
148+
// Currently all keypers call this and race to call this function first.
149+
// For now this is fine, but a keyper should only send a transaction if
150+
// the key is not set yet.
151+
// Best would be a coordinatated leader election who will broadcast the key.
152+
tx, err := kpr.l2Client.BroadcastEonKey(ctx, pubKey.Eon, pubKey.PublicKey)
153+
if err != nil {
154+
log.Error().Err(err).Msg("error broadcasting eon public key")
155+
return errors.Wrap(err, "error broadcasting eon public-key")
156+
}
157+
log.Info().
158+
Str("hash", tx.Hash().Hex()).
159+
Msg("sent eon pubkey transaction")
160+
161+
receipt, err := bind.WaitMined(ctx, kpr.l2Client, tx)
162+
if err != nil {
163+
log.Error().Err(err).Msg("error waiting for pubkey tx mined")
164+
return err
165+
}
166+
// NOCHECKIN: log the JSON receipt or only specific fields
167+
log.Info().
168+
Interface("receipt", receipt).
169+
Msg("eon pubkey transaction mined")
170+
// TODO:
171+
// wait / confirm of tx, otherwise resend
148172
return nil
149173
}

rolling-shutter/keyperimpl/optimism/sync/client.go

Lines changed: 39 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@ package sync
22

33
import (
44
"context"
5+
"crypto/ecdsa"
56
"io"
7+
"math/big"
68

79
"github.com/ethereum/go-ethereum/accounts/abi/bind"
10+
"github.com/ethereum/go-ethereum/core/types"
811
"github.com/ethereum/go-ethereum/log"
912
"github.com/pkg/errors"
1013
"github.com/shutter-network/shop-contracts/bindings"
@@ -32,6 +35,8 @@ type ShutterL2Client struct {
3235
log log.Logger
3336

3437
options *options
38+
chainID *big.Int
39+
privKey *ecdsa.PrivateKey
3540

3641
KeyperSetManager *bindings.KeyperSetManager
3742
KeyBroadcast *bindings.KeyBroadcastContract
@@ -74,26 +79,6 @@ func (s *ShutterL2Client) getServices() []service.Service {
7479
return s.services
7580
}
7681

77-
// func syncInitial() {
78-
// if s.ForceEmitActiveKeyperSet {
79-
// var b *number.BlockNumber
80-
// if s.StartBlock == nil {
81-
// b = number.LatestBlock
82-
// } else {
83-
// b = number.NewBlockNumber()
84-
// b.SetInt64(int64(*s.StartBlock))
85-
// }
86-
// activeKSAddred, err := s.getKeyperSetForBlock(ctx, b)
87-
// if err != nil {
88-
// return err
89-
// }
90-
// err = s.handler(activeKSAddred)
91-
// if err != nil {
92-
// return errors.Wrap(err, "handling of forced emission of active keyper set failed")
93-
// }
94-
// }
95-
// }
96-
9782
func (s *ShutterL2Client) GetShutterState(ctx context.Context) (*event.ShutterState, error) {
9883
if s.sssync == nil {
9984
return nil, errors.Wrap(ErrServiceNotInstantiated, "ShutterStateSyncer service not instantiated")
@@ -126,14 +111,46 @@ func (s *ShutterL2Client) GetKeyperSetForBlock(ctx context.Context, b *number.Bl
126111

127112
func (s *ShutterL2Client) GetEonPubKeyForEon(ctx context.Context, eon uint64) (*event.EonPublicKey, error) {
128113
if s.sssync == nil {
129-
return nil, errors.Wrap(ErrServiceNotInstantiated, "EonPubKeySyncer service not instantiated")
114+
return nil, errors.Wrap(ErrServiceNotInstantiated, "ShutterStateSyncer service not instantiated")
130115
}
131116
opts := &bind.CallOpts{
132117
Context: ctx,
133118
}
134119
return s.epksync.GetEonPubKeyForEon(ctx, opts, eon)
135120
}
136121

137-
func (s *ShutterL2Client) Start(ctx context.Context, runner service.Runner) error {
122+
func (s *ShutterL2Client) BroadcastEonKey(ctx context.Context, eon uint64, eonPubKey []byte) (*types.Transaction, error) {
123+
// TODO: first do a getEonKey. If we already have something (ideally the same)
124+
// don't do a transaction
125+
// s.KeyBroadcast.GetEonKey(eon)
126+
if s.privKey == nil {
127+
return nil, errors.New("can't broadcast eon public-key, client does not have a signer set")
128+
}
129+
chainID, err := s.ChainID(ctx)
130+
if err != nil {
131+
return nil, errors.Wrap(err, "retrieve chain id")
132+
}
133+
opts, err := bind.NewKeyedTransactorWithChainID(s.privKey, chainID)
134+
if err != nil {
135+
return nil, errors.Wrap(err, "construct signer transaction opts")
136+
}
137+
opts.Context = ctx
138+
return s.KeyBroadcast.BroadcastEonKey(opts, eon, eonPubKey)
139+
}
140+
141+
// ChainID returns the chainid of the underlying L2 chain.
142+
// This value is cached, since it is not expected to change.
143+
func (s *ShutterL2Client) ChainID(ctx context.Context) (*big.Int, error) {
144+
if s.chainID == nil {
145+
cid, err := s.Client.ChainID(ctx)
146+
if err != nil {
147+
return nil, err
148+
}
149+
s.chainID = cid
150+
}
151+
return s.chainID, nil
152+
}
153+
154+
func (s *ShutterL2Client) Start(_ context.Context, runner service.Runner) error {
138155
return runner.StartService(s.getServices()...)
139156
}

rolling-shutter/keyperimpl/optimism/sync/options.go

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,19 @@ package sync
22

33
import (
44
"context"
5-
"errors"
5+
"crypto/ecdsa"
66

77
"github.com/ethereum/go-ethereum/common"
88
"github.com/ethereum/go-ethereum/ethclient"
99
"github.com/ethereum/go-ethereum/log"
10+
"github.com/pkg/errors"
1011
"github.com/shutter-network/shop-contracts/bindings"
1112
"github.com/shutter-network/shop-contracts/predeploy"
1213

1314
syncclient "github.com/shutter-network/rolling-shutter/rolling-shutter/keyperimpl/optimism/sync/client"
1415
"github.com/shutter-network/rolling-shutter/rolling-shutter/keyperimpl/optimism/sync/event"
1516
"github.com/shutter-network/rolling-shutter/rolling-shutter/keyperimpl/optimism/sync/syncer"
17+
"github.com/shutter-network/rolling-shutter/rolling-shutter/medley/encodeable/number"
1618
"github.com/shutter-network/rolling-shutter/rolling-shutter/medley/service"
1719
)
1820

@@ -25,7 +27,8 @@ type options struct {
2527
client syncclient.Client
2628
logger log.Logger
2729
runner service.Runner
28-
syncStart *uint64
30+
syncStart *number.BlockNumber
31+
privKey *ecdsa.PrivateKey
2932

3033
handlerShutterState event.ShutterStateHandler
3134
handlerKeyperSet event.KeyperSetHandler
@@ -67,6 +70,16 @@ func (o *options) apply(ctx context.Context, c *ShutterL2Client) error {
6770

6871
c.Client = client
6972

73+
// the nil passthrough will use "latest" for each call,
74+
// but we want to harmonize and fix the sync start to a specific block.
75+
if o.syncStart.IsLatest() {
76+
latestBlock, err := c.Client.BlockNumber(ctx)
77+
if err != nil {
78+
return errors.Wrap(err, "polling latest block")
79+
}
80+
o.syncStart = number.NewBlockNumber(&latestBlock)
81+
}
82+
7083
c.KeyperSetManager, err = bindings.NewKeyperSetManager(*o.keyperSetManagerAddress, client)
7184
if err != nil {
7285
return err
@@ -118,6 +131,7 @@ func (o *options) apply(ctx context.Context, c *ShutterL2Client) error {
118131
if o.handlerBlock != nil {
119132
c.services = append(c.services, c.uhsync)
120133
}
134+
c.privKey = o.privKey
121135
return nil
122136
}
123137

@@ -129,14 +143,16 @@ func defaultOptions() *options {
129143
client: nil,
130144
logger: noopLogger,
131145
runner: nil,
132-
syncStart: nil,
146+
syncStart: number.NewBlockNumber(nil),
133147
}
134148
}
135149

136-
func WithSyncStartBlock(blockNumber uint64) Option {
150+
func WithSyncStartBlock(blockNumber *number.BlockNumber) Option {
151+
if blockNumber == nil {
152+
blockNumber = number.NewBlockNumber(nil)
153+
}
137154
return func(o *options) error {
138-
bn := blockNumber
139-
o.syncStart = &bn
155+
o.syncStart = blockNumber
140156
return nil
141157
}
142158
}
@@ -183,6 +199,13 @@ func WithClient(client syncclient.Client) Option {
183199
}
184200
}
185201

202+
func WithPrivateKey(key *ecdsa.PrivateKey) Option {
203+
return func(o *options) error {
204+
o.privKey = key
205+
return nil
206+
}
207+
}
208+
186209
func WithSyncNewKeyperSet(handler event.KeyperSetHandler) Option {
187210
return func(o *options) error {
188211
o.handlerKeyperSet = handler

rolling-shutter/keyperimpl/optimism/sync/syncer/eonpubkey.go

Lines changed: 62 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,43 +11,99 @@ import (
1111

1212
"github.com/shutter-network/rolling-shutter/rolling-shutter/keyperimpl/optimism/sync/client"
1313
"github.com/shutter-network/rolling-shutter/rolling-shutter/keyperimpl/optimism/sync/event"
14+
"github.com/shutter-network/rolling-shutter/rolling-shutter/medley/encodeable/number"
1415
"github.com/shutter-network/rolling-shutter/rolling-shutter/medley/service"
1516
)
1617

1718
type EonPubKeySyncer struct {
1819
Client client.Client
1920
Log log.Logger
2021
Contract *bindings.KeyBroadcastContract
21-
StartBlock *uint64
22+
StartBlock *number.BlockNumber
2223
Handler event.EonPublicKeyHandler
2324

2425
keyBroadcastCh chan *bindings.KeyBroadcastContractEonKeyBroadcast
26+
ksManager *bindings.KeyperSetManager
2527
}
2628

2729
func (s *EonPubKeySyncer) Start(ctx context.Context, runner service.Runner) error {
2830
if s.Handler == nil {
2931
return errors.New("no handler registered")
3032
}
33+
// the latest block still has to be fixed.
34+
// otherwise we could skip some block events
35+
// between the initial poll and the subscription.
36+
if s.StartBlock.IsLatest() {
37+
latest, err := s.Client.BlockNumber(ctx)
38+
if err != nil {
39+
return err
40+
}
41+
s.StartBlock.SetUint64(latest)
42+
}
43+
pubKs, err := s.getInitialPubKeys(ctx)
44+
if err != nil {
45+
return err
46+
}
47+
for _, k := range pubKs {
48+
err := s.Handler(ctx, k)
49+
if err != nil {
50+
return err
51+
}
52+
}
53+
3154
watchOpts := &bind.WatchOpts{
32-
Start: s.StartBlock, // nil means latest
55+
Start: s.StartBlock.ToUInt64Ptr(),
3356
Context: ctx,
3457
}
35-
s.keyBroadcastCh = make(chan *bindings.KeyBroadcastContractEonKeyBroadcast, 10)
58+
s.keyBroadcastCh = make(chan *bindings.KeyBroadcastContractEonKeyBroadcast, channelSize)
59+
runner.Defer(func() {
60+
close(s.keyBroadcastCh)
61+
})
3662
subs, err := s.Contract.WatchEonKeyBroadcast(watchOpts, s.keyBroadcastCh)
3763
// FIXME: what to do on subs.Error()
3864
if err != nil {
3965
return err
4066
}
4167
runner.Defer(subs.Unsubscribe)
42-
runner.Defer(func() {
43-
close(s.keyBroadcastCh)
44-
})
4568
runner.Go(func() error {
4669
return s.watchNewEonPubkey(ctx)
4770
})
4871
return nil
4972
}
5073

74+
func (s *EonPubKeySyncer) getInitialPubKeys(ctx context.Context) ([]*event.EonPublicKey, error) {
75+
// This blocknumber specifies AT what state
76+
// the contract is called
77+
// XXX: does the call-opts blocknumber -1 also means latest?
78+
opts := &bind.CallOpts{
79+
Context: ctx,
80+
BlockNumber: s.StartBlock.Int,
81+
}
82+
numKS, err := s.ksManager.GetNumKeyperSets(opts)
83+
if err != nil {
84+
return nil, err
85+
}
86+
// this blocknumber specifies the argument to the contract
87+
// getter
88+
activeEon, err := s.ksManager.GetKeyperSetIndexByBlock(opts, s.StartBlock.Uint64())
89+
if err != nil {
90+
return nil, err
91+
}
92+
93+
initialPubKeys := []*event.EonPublicKey{}
94+
for i := activeEon; i < numKS; i++ {
95+
e, err := s.GetEonPubKeyForEon(ctx, opts, i)
96+
// FIXME: translate the error that there is no key
97+
// to a continue of the loop
98+
// (key not in mapping error, how can we catch that?)
99+
if err != nil {
100+
return nil, err
101+
}
102+
initialPubKeys = append(initialPubKeys, e)
103+
}
104+
return initialPubKeys, nil
105+
}
106+
51107
func (s *EonPubKeySyncer) logCallError(attrName string, err error) {
52108
s.Log.Error(
53109
fmt.Sprintf("could not retrieve `%s` from contract", attrName),

0 commit comments

Comments
 (0)