Skip to content

Commit ab9f501

Browse files
committed
keyper-core: add synchandler for new chainsyncer
1 parent 9059382 commit ab9f501

File tree

1 file changed

+233
-0
lines changed

1 file changed

+233
-0
lines changed
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
package synchandler
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/jackc/pgx/v4"
8+
"github.com/jackc/pgx/v4/pgxpool"
9+
"github.com/pkg/errors"
10+
"github.com/rs/zerolog/log"
11+
12+
"github.com/ethereum/go-ethereum/accounts/abi"
13+
"github.com/ethereum/go-ethereum/accounts/abi/bind"
14+
"github.com/ethereum/go-ethereum/common"
15+
"github.com/ethereum/go-ethereum/core/types"
16+
obskeyper "github.com/shutter-network/rolling-shutter/rolling-shutter/chainobserver/db/keyper"
17+
"github.com/shutter-network/rolling-shutter/rolling-shutter/medley"
18+
"github.com/shutter-network/rolling-shutter/rolling-shutter/medley/chainsync/client"
19+
"github.com/shutter-network/rolling-shutter/rolling-shutter/medley/chainsync/syncer"
20+
"github.com/shutter-network/rolling-shutter/rolling-shutter/medley/encodeable/number"
21+
"github.com/shutter-network/rolling-shutter/rolling-shutter/shdb"
22+
"github.com/shutter-network/shop-contracts/bindings"
23+
)
24+
25+
var ErrParseKeyperSet = errors.New("can't parse KeyperSet")
26+
27+
func makeCallError(attrName string, err error) error {
28+
return fmt.Errorf("could not retrieve `%s` from contract: %w", attrName, err)
29+
}
30+
31+
func init() {
32+
var err error
33+
KeyperSetManagerContractABI, err = bindings.KeyperSetManagerMetaData.GetAbi()
34+
if err != nil {
35+
panic(err)
36+
}
37+
}
38+
39+
var KeyperSetManagerContractABI *abi.ABI
40+
41+
func NewKeyperSetAdded(
42+
db *pgxpool.Pool,
43+
ethClient client.Client,
44+
contractAddress,
45+
ethereumAddress common.Address,
46+
) (syncer.ContractEventHandler, error) {
47+
ksm, err := bindings.NewKeyperSetManager(contractAddress, ethClient)
48+
if err != nil {
49+
return nil, err
50+
}
51+
return syncer.WrapHandler(&KeyperSetAdded{
52+
evABI: KeyperSetManagerContractABI,
53+
keyperSetManagerAddress: contractAddress,
54+
ethereumAddress: ethereumAddress,
55+
dbpool: db,
56+
ethClient: ethClient,
57+
keyperSetManager: ksm,
58+
})
59+
}
60+
61+
type KeyperSetAdded struct {
62+
evABI *abi.ABI
63+
keyperSetManagerAddress common.Address
64+
// our address to check wether we are part of the keyper-set
65+
ethereumAddress common.Address
66+
dbpool *pgxpool.Pool
67+
68+
// we only need this because we have to poll
69+
// additional data from the contract
70+
ethClient client.Client
71+
keyperSetManager *bindings.KeyperSetManager
72+
}
73+
74+
func (handler *KeyperSetAdded) Address() common.Address {
75+
return handler.keyperSetManagerAddress
76+
}
77+
78+
func (*KeyperSetAdded) Event() string {
79+
return "KeyperSetAdded"
80+
}
81+
82+
func (handler *KeyperSetAdded) ABI() abi.ABI {
83+
return *handler.evABI
84+
}
85+
86+
func (handler *KeyperSetAdded) Accept(
87+
_ context.Context,
88+
_ types.Header,
89+
_ bindings.KeyperSetManagerKeyperSetAdded,
90+
) (bool, error) {
91+
return true, nil
92+
}
93+
94+
func (handler *KeyperSetAdded) Handle(
95+
ctx context.Context,
96+
update syncer.ChainUpdateContext,
97+
events []bindings.KeyperSetManagerKeyperSetAdded,
98+
) error {
99+
// TODO: we don't handle reorgs here.
100+
// This is because we don't have a good way to deal with
101+
// them:
102+
// When we originally insert the event, we would have to save
103+
// the insert block-hash in the db and upon a reorg delete the keypersets
104+
// by insert block-hash. We can't do this in production, since we don't
105+
// have a good database migration strategy and framework yet.
106+
107+
for _, ev := range events {
108+
ks, err := QueryFullKeyperSetFromKeyperSetAddedEvent(ctx, handler.ethClient, ev, handler.keyperSetManager)
109+
if err != nil {
110+
log.Error().
111+
Err(err).
112+
Msg("KeyperSetAdded event, error querying keyperset-data")
113+
}
114+
err = handler.processNewKeyperSet(ctx, ks)
115+
if err != nil {
116+
log.Error().Err(err).Msg("KeyperSetAdded event, error writing to database")
117+
}
118+
}
119+
return nil
120+
}
121+
122+
func (handler *KeyperSetAdded) processNewKeyperSet(ctx context.Context, ev *KeyperSet) error {
123+
isMember := false
124+
for _, m := range ev.Members {
125+
if m.Cmp(handler.ethereumAddress) == 0 {
126+
isMember = true
127+
break
128+
}
129+
}
130+
log.Info().
131+
Uint64("activation-block", ev.ActivationBlock).
132+
Uint64("eon", ev.Eon).
133+
Int("num-members", len(ev.Members)).
134+
Uint64("threshold", ev.Threshold).
135+
Bool("is-member", isMember).
136+
Msg("new keyper set added")
137+
138+
// TODO: before, we were notifying the SequencerTransactionSubmitted
139+
// handler when we were part of the newly inserted KeyperSet.
140+
// This was an optimisation measure to not let the node
141+
// insert SequencerTransactionSubmitted events into it's DB for
142+
// Eons that are before the point where it is part of the KeyperSet.
143+
// This optimisation is for now omitted, since it unnecessarily coupled
144+
// both handler. It was mainly done like this to avoid adding an
145+
// additional field 'weAreMember' to the KeyperSet in the db.
146+
// XXX: do we have to insert keypersets we are not part of?
147+
// Maybe it would be easiest to just ignore those keypersets?
148+
149+
return handler.dbpool.BeginFunc(ctx, func(tx pgx.Tx) error {
150+
obskeyperdb := obskeyper.New(tx)
151+
152+
keyperConfigIndex, err := medley.Uint64ToInt64Safe(ev.Eon)
153+
if err != nil {
154+
return errors.Wrap(err, ErrParseKeyperSet.Error())
155+
}
156+
activationBlockNumber, err := medley.Uint64ToInt64Safe(ev.ActivationBlock)
157+
if err != nil {
158+
return errors.Wrap(err, ErrParseKeyperSet.Error())
159+
}
160+
threshold, err := medley.Uint64ToInt64Safe(ev.Threshold)
161+
if err != nil {
162+
return errors.Wrap(err, ErrParseKeyperSet.Error())
163+
}
164+
165+
// we insert the keyperset into the db, even though we are not member of it.
166+
// Since there is no field to mark our keypersets, we would always
167+
// have to iterate over all keypersets...
168+
return obskeyperdb.InsertKeyperSet(ctx, obskeyper.InsertKeyperSetParams{
169+
KeyperConfigIndex: keyperConfigIndex,
170+
ActivationBlockNumber: activationBlockNumber,
171+
Keypers: shdb.EncodeAddresses(ev.Members),
172+
Threshold: int32(threshold),
173+
})
174+
})
175+
}
176+
177+
type KeyperSet struct {
178+
ActivationBlock uint64
179+
Members []common.Address
180+
Threshold uint64
181+
Eon uint64
182+
183+
AtBlockNumber *number.BlockNumber
184+
}
185+
186+
// QueryFullKeyperSetFromKeyperSetAddedEvent polls some additional
187+
// data from the contracts in order to construct the full set of
188+
// information for a keyper-set.
189+
// This has to be done because not all information relevant to
190+
// the keyperset is included in the KeyperSetAdded event.
191+
func QueryFullKeyperSetFromKeyperSetAddedEvent(
192+
ctx context.Context,
193+
ethClient client.Client,
194+
event bindings.KeyperSetManagerKeyperSetAdded,
195+
keyperSetManager *bindings.KeyperSetManager,
196+
) (*KeyperSet, error) {
197+
keyperSet, err := bindings.NewKeyperSet(event.KeyperSetContract, ethClient)
198+
if err != nil {
199+
return nil, fmt.Errorf("can't bind KeyperSet contract: %w", err)
200+
}
201+
opts := &bind.CallOpts{
202+
BlockHash: event.Raw.BlockHash,
203+
Context: ctx,
204+
}
205+
// the manager only accepts final keyper sets,
206+
// so we expect this to be final now.
207+
final, err := keyperSet.IsFinalized(opts)
208+
if err != nil {
209+
return nil, makeCallError("IsFinalized", err)
210+
}
211+
if !final {
212+
return nil, errors.New("contract did accept unfinalized keyper-sets")
213+
}
214+
members, err := keyperSet.GetMembers(opts)
215+
if err != nil {
216+
return nil, makeCallError("Members", err)
217+
}
218+
threshold, err := keyperSet.GetThreshold(opts)
219+
if err != nil {
220+
return nil, makeCallError("Threshold", err)
221+
}
222+
eon, err := keyperSetManager.GetKeyperSetIndexByBlock(opts, event.ActivationBlock)
223+
if err != nil {
224+
return nil, makeCallError("KeyperSetIndexByBlock", err)
225+
}
226+
return &KeyperSet{
227+
ActivationBlock: event.ActivationBlock,
228+
Members: members,
229+
Threshold: threshold,
230+
Eon: eon,
231+
AtBlockNumber: number.BigToBlockNumber(opts.BlockNumber),
232+
}, nil
233+
}

0 commit comments

Comments
 (0)