Skip to content

Commit a50cc4f

Browse files
committed
Publish eon keys via publisher contract
1 parent 14961a1 commit a50cc4f

File tree

8 files changed

+211
-11
lines changed

8 files changed

+211
-11
lines changed

rolling-shutter/chainobserver/db/keyper/extend.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,17 @@ import (
77
"github.com/shutter-network/rolling-shutter/rolling-shutter/shdb"
88
)
99

10+
// GetIndex returns the index of the given address in the KeyperSet.
11+
func (s *KeyperSet) GetIndex(address common.Address) (uint64, error) {
12+
encodedAddress := shdb.EncodeAddress(address)
13+
for i, m := range s.Keypers {
14+
if m == encodedAddress {
15+
return uint64(i), nil
16+
}
17+
}
18+
return 0, errors.Errorf("keyper %s not found", address.String())
19+
}
20+
1021
// Contains checks if the given address is present in the KeyperSet.
1122
// It returns true if the address is found, otherwise false.
1223
func (s *KeyperSet) Contains(address common.Address) bool {

rolling-shutter/chainobserver/db/keyper/extend_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,20 @@ func makeTestKeyperSet() KeyperSet {
2222
}
2323
}
2424

25+
func TestKeyperSetGetIndex(t *testing.T) {
26+
keyperSet := makeTestKeyperSet()
27+
addresses, err := shdb.DecodeAddresses(keyperSet.Keypers)
28+
assert.NilError(t, err)
29+
30+
for i, address := range addresses {
31+
index, err := keyperSet.GetIndex(address)
32+
assert.NilError(t, err)
33+
assert.Equal(t, uint64(i), index)
34+
}
35+
_, err = keyperSet.GetIndex(common.HexToAddress("0xffffffffffffffffffffffffffffffffffffffff"))
36+
assert.ErrorContains(t, err, "keyper 0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF not found")
37+
}
38+
2539
func TestKeyperSetContains(t *testing.T) {
2640
keyperSet := makeTestKeyperSet()
2741
addresses, err := shdb.DecodeAddresses(keyperSet.Keypers)
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
package eonkeypublisher
2+
3+
import (
4+
"context"
5+
"crypto/ecdsa"
6+
"time"
7+
8+
"github.com/ethereum/go-ethereum/accounts/abi/bind"
9+
"github.com/ethereum/go-ethereum/common"
10+
"github.com/ethereum/go-ethereum/core/types"
11+
ethcrypto "github.com/ethereum/go-ethereum/crypto"
12+
"github.com/ethereum/go-ethereum/ethclient"
13+
"github.com/jackc/pgx/v4/pgxpool"
14+
"github.com/pkg/errors"
15+
"github.com/rs/zerolog/log"
16+
"github.com/shutter-network/shop-contracts/bindings"
17+
18+
obskeyperdb "github.com/shutter-network/rolling-shutter/rolling-shutter/chainobserver/db/keyper"
19+
"github.com/shutter-network/rolling-shutter/rolling-shutter/keyper"
20+
"github.com/shutter-network/rolling-shutter/rolling-shutter/medley/retry"
21+
"github.com/shutter-network/rolling-shutter/rolling-shutter/medley/service"
22+
)
23+
24+
const (
25+
eonKeyChannelSize = 32
26+
retryInterval = time.Second * 12
27+
)
28+
29+
// EonKeyPublisher is a service that publishes eon keys via a eon key publisher contract.
30+
type EonKeyPublisher struct {
31+
dbpool *pgxpool.Pool
32+
client *ethclient.Client
33+
contract *bindings.EonKeyPublish
34+
privateKey *ecdsa.PrivateKey
35+
36+
keys chan keyper.EonPublicKey
37+
}
38+
39+
func NewEonKeyPublisher(
40+
dbpool *pgxpool.Pool,
41+
client *ethclient.Client,
42+
eonKeyPublishAddress common.Address,
43+
privateKey *ecdsa.PrivateKey,
44+
) (*EonKeyPublisher, error) {
45+
contract, err := bindings.NewEonKeyPublish(eonKeyPublishAddress, client)
46+
if err != nil {
47+
return nil, errors.Wrap(err, "failed to instantiate eon key publisher contract")
48+
}
49+
return &EonKeyPublisher{
50+
dbpool: dbpool,
51+
client: client,
52+
contract: contract,
53+
privateKey: privateKey,
54+
55+
keys: make(chan keyper.EonPublicKey, eonKeyChannelSize),
56+
}, nil
57+
}
58+
59+
func (p *EonKeyPublisher) Start(ctx context.Context, runner service.Runner) error { //nolint: unparam
60+
runner.Go(func() error {
61+
for {
62+
select {
63+
case key := <-p.keys:
64+
p.publish(ctx, key)
65+
case <-ctx.Done():
66+
return ctx.Err()
67+
}
68+
}
69+
})
70+
return nil
71+
}
72+
73+
// Publish schedules a eon key to be published.
74+
func (p *EonKeyPublisher) Publish(key keyper.EonPublicKey) {
75+
p.keys <- key
76+
}
77+
78+
func (p *EonKeyPublisher) publish(ctx context.Context, key keyper.EonPublicKey) {
79+
_, err := retry.FunctionCall[struct{}](ctx, func(ctx context.Context) (struct{}, error) {
80+
return struct{}{}, p.tryPublish(ctx, key)
81+
}, retry.Interval(retryInterval))
82+
if err != nil {
83+
log.Error().
84+
Err(err).
85+
Uint64("keyper-set-index", key.KeyperConfigIndex).
86+
Hex("key", key.PublicKey).
87+
Msg("failed to publish eon key")
88+
}
89+
}
90+
91+
func (p *EonKeyPublisher) tryPublish(ctx context.Context, key keyper.EonPublicKey) error {
92+
db := obskeyperdb.New(p.dbpool)
93+
keyperSet, err := db.GetKeyperSetByKeyperConfigIndex(ctx, int64(key.Eon))
94+
if err != nil {
95+
return errors.Wrapf(err, "failed to query keyper set %d by index from db", key.KeyperConfigIndex)
96+
}
97+
keyperAddress := ethcrypto.PubkeyToAddress(p.privateKey.PublicKey)
98+
keyperIndex, err := keyperSet.GetIndex(keyperAddress)
99+
if err != nil {
100+
log.Info().
101+
Uint64("keyper-set-index", key.KeyperConfigIndex).
102+
Str("keyper-address", keyperAddress.Hex()).
103+
Msg("not publishing eon key as keyper is not part of corresponding keyper set")
104+
return nil
105+
}
106+
107+
hasAlreadyVoted, err := p.contract.HasKeyperVoted(&bind.CallOpts{}, keyperAddress)
108+
if err != nil {
109+
return errors.Wrap(err, "failed to query eon key publisher contract if keyper has already voted")
110+
}
111+
if hasAlreadyVoted {
112+
log.Info().
113+
Uint64("keyper-set-index", key.KeyperConfigIndex).
114+
Str("keyper-address", keyperAddress.Hex()).
115+
Msg("not publishing eon key as keyper has already voted")
116+
return nil
117+
}
118+
isAlreadyConfirmed, err := p.contract.EonKeyConfirmed(&bind.CallOpts{}, key.PublicKey)
119+
if err != nil {
120+
return errors.Wrap(err, "failed to query eon key publisher contract if eon key is confirmed")
121+
}
122+
if isAlreadyConfirmed {
123+
log.Info().
124+
Uint64("keyper-set-index", key.KeyperConfigIndex).
125+
Hex("key", key.PublicKey).
126+
Msg("not publishing eon key as it is already confirmed")
127+
return nil
128+
}
129+
130+
chainID, err := p.client.ChainID(ctx)
131+
if err != nil {
132+
return errors.Wrap(err, "failed to get chain ID")
133+
}
134+
opts, err := bind.NewKeyedTransactorWithChainID(p.privateKey, chainID)
135+
if err != nil {
136+
return errors.Wrap(err, "failed to construct tx opts")
137+
}
138+
tx, err := p.contract.PublishEonKey(opts, key.PublicKey, keyperIndex)
139+
if err != nil {
140+
return errors.Wrap(err, "failed to send publish eon key tx")
141+
}
142+
log.Info().
143+
Uint64("keyper-set-index", key.KeyperConfigIndex).
144+
Hex("key", key.PublicKey).
145+
Hex("tx-hash", tx.Hash().Bytes()).
146+
Msg("eon key publish tx sent")
147+
receipt, err := bind.WaitMined(ctx, p.client, tx)
148+
if err != nil {
149+
log.Error().Err(err).Msg("error waiting for eon key publish tx to be mined")
150+
return err
151+
}
152+
if receipt.Status != types.ReceiptStatusSuccessful {
153+
log.Error().
154+
Hex("tx-hash", tx.Hash().Bytes()).
155+
Interface("receipt", receipt).
156+
Msg("eon key publish tx failed")
157+
return errors.New("eon key publish tx failed")
158+
}
159+
log.Info().Msg("successfully published eon key")
160+
return nil
161+
}

rolling-shutter/go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ require (
3030
github.com/prometheus/client_golang v1.17.0
3131
github.com/rs/zerolog v1.28.0
3232
github.com/shutter-network/gnosh-contracts v0.2.0
33-
github.com/shutter-network/shop-contracts v0.0.0-20231220085304-80b8977d0bca
33+
github.com/shutter-network/shop-contracts v0.0.0-20240407151512-08ef5d8355b6
3434
github.com/shutter-network/shutter/shlib v0.1.13
3535
github.com/shutter-network/txtypes v0.1.0
3636
github.com/spf13/afero v1.8.2

rolling-shutter/go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -833,8 +833,8 @@ github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYED
833833
github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw=
834834
github.com/shutter-network/gnosh-contracts v0.2.0 h1:qH3gAhlh5VZzvJcbi044lxFWQ+MAR9GevKKUirWxSlU=
835835
github.com/shutter-network/gnosh-contracts v0.2.0/go.mod h1:QB0d64ybbVFKMrLjrc1tldri87KNjTmKQjhk9jaso2E=
836-
github.com/shutter-network/shop-contracts v0.0.0-20231220085304-80b8977d0bca h1:05Ghqw3FqH/UFuYIzc7z6GJyHk3HxAqY3iuY4L3x4Ow=
837-
github.com/shutter-network/shop-contracts v0.0.0-20231220085304-80b8977d0bca/go.mod h1:LEWXLRruvxq9fe2oKtJI3xfzbauhfWTjOczHN61RU+4=
836+
github.com/shutter-network/shop-contracts v0.0.0-20240407151512-08ef5d8355b6 h1:m6Ti1/IH+GBTtGqyAX3xbh+ruUKvC+m+/uzYDUa+JDQ=
837+
github.com/shutter-network/shop-contracts v0.0.0-20240407151512-08ef5d8355b6/go.mod h1:LEWXLRruvxq9fe2oKtJI3xfzbauhfWTjOczHN61RU+4=
838838
github.com/shutter-network/shutter/shlib v0.1.13 h1:9YloDJBdhFAKm2GMg4gBNeaJ+Mw9Qzeh5Kz9A2ayp1E=
839839
github.com/shutter-network/shutter/shlib v0.1.13/go.mod h1:RlYNZjx+pfKAi0arH+jfdlxG4kQ75UFzDfVjgCVYaUw=
840840
github.com/shutter-network/txtypes v0.1.0 h1:QqdiiiB9AiBCSJ/ke6z1ZoDGfu2+1Lgpz5vHzVN4FKc=

rolling-shutter/keyperimpl/gnosis/config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,13 +149,15 @@ func (c *GnosisConfig) TOMLWriteHeader(_ io.Writer) (int, error) {
149149
type GnosisContractsConfig struct {
150150
KeyperSetManager common.Address `shconfig:",required"`
151151
KeyBroadcastContract common.Address `shconfig:",required"`
152+
EonKeyPublish common.Address `shconfig:",required"`
152153
Sequencer common.Address `shconfig:",required"`
153154
}
154155

155156
func NewGnosisContractsConfig() *GnosisContractsConfig {
156157
return &GnosisContractsConfig{
157158
KeyperSetManager: common.Address{},
158159
KeyBroadcastContract: common.Address{},
160+
EonKeyPublish: common.Address{},
159161
Sequencer: common.Address{},
160162
}
161163
}

rolling-shutter/keyperimpl/gnosis/keyper.go

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"golang.org/x/exp/slog"
1515

1616
obskeyper "github.com/shutter-network/rolling-shutter/rolling-shutter/chainobserver/db/keyper"
17+
"github.com/shutter-network/rolling-shutter/rolling-shutter/eonkeypublisher"
1718
"github.com/shutter-network/rolling-shutter/rolling-shutter/keyper"
1819
"github.com/shutter-network/rolling-shutter/rolling-shutter/keyper/epochkghandler"
1920
"github.com/shutter-network/rolling-shutter/rolling-shutter/keyper/kprconfig"
@@ -33,8 +34,10 @@ type Keyper struct {
3334
core *keyper.KeyperCore
3435
config *Config
3536
dbpool *pgxpool.Pool
37+
client *ethclient.Client
3638
chainSyncClient *chainsync.Client
3739
sequencerSyncer *SequencerSyncer
40+
eonKeyPublisher *eonkeypublisher.EonKeyPublisher
3841

3942
// input events
4043
newBlocks chan *syncevent.LatestBlock
@@ -117,13 +120,27 @@ func (kpr *Keyper) Start(ctx context.Context, runner service.Runner) error {
117120
return err
118121
}
119122

123+
eonKeyPublisherClient, err := ethclient.DialContext(ctx, kpr.config.Gnosis.Node.EthereumURL)
124+
if err != nil {
125+
return errors.Wrapf(err, "failed to dial ethereum node at %s", kpr.config.Gnosis.Node.EthereumURL)
126+
}
127+
kpr.eonKeyPublisher, err = eonkeypublisher.NewEonKeyPublisher(
128+
kpr.dbpool,
129+
eonKeyPublisherClient,
130+
kpr.config.Gnosis.Contracts.EonKeyPublish,
131+
kpr.config.Gnosis.Node.PrivateKey.Key,
132+
)
133+
if err != nil {
134+
return errors.Wrap(err, "failed to initialize eon key publisher")
135+
}
136+
120137
err = kpr.initSequencerSyncer(ctx)
121138
if err != nil {
122139
return err
123140
}
124141

125142
runner.Go(func() error { return kpr.processInputs(ctx) })
126-
return runner.StartService(kpr.core, kpr.chainSyncClient, kpr.slotTicker)
143+
return runner.StartService(kpr.core, kpr.chainSyncClient, kpr.slotTicker, kpr.eonKeyPublisher)
127144
}
128145

129146
// initSequencerSycer initializes the sequencer syncer if the keyper is known to be a member of a

rolling-shutter/keyperimpl/gnosis/neweonpublickey.go

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,10 @@ package gnosis
33
import (
44
"context"
55

6-
"github.com/rs/zerolog/log"
7-
86
"github.com/shutter-network/rolling-shutter/rolling-shutter/keyper"
97
)
108

11-
func (kpr *Keyper) processNewEonPublicKey(_ context.Context, key keyper.EonPublicKey) error { //nolint:unparam
12-
log.Info().
13-
Uint64("eon", key.Eon).
14-
Uint64("activation-block", key.ActivationBlock).
15-
Msg("new eon pk")
9+
func (kpr *Keyper) processNewEonPublicKey(ctx context.Context, key keyper.EonPublicKey) error {
10+
kpr.eonKeyPublisher.Publish(key)
1611
return nil
1712
}

0 commit comments

Comments
 (0)