Skip to content

Commit bdbcb43

Browse files
authored
Capture finality certificates with F3 Observer (#1027)
Expand the ability of observer to capture and store all finality certificates, and make them available for query over SQL. Fixes #745
1 parent f9c95ae commit bdbcb43

File tree

5 files changed

+377
-41
lines changed

5 files changed

+377
-41
lines changed

cmd/f3/observer.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010

1111
"github.com/filecoin-project/go-f3/gpbft"
1212
"github.com/filecoin-project/go-f3/observer"
13+
"github.com/ipfs/go-cid"
1314
"github.com/libp2p/go-libp2p"
1415
"github.com/libp2p/go-libp2p/core/crypto"
1516
"github.com/libp2p/go-libp2p/core/peer"
@@ -115,6 +116,15 @@ var observerCmd = cli.Command{
115116
Usage: "The maximum time to wait before a batch is flushed to the database.",
116117
Value: time.Minute,
117118
},
119+
&cli.StringFlag{
120+
Name: "initialPowerTableCID",
121+
Usage: "The CID of the initial power table. If not set, no finality certificates will be captured.",
122+
},
123+
&cli.PathFlag{
124+
Name: "certStorePath",
125+
Usage: "The path to the directory used for intermediary finality certificate certstore. If not set, in-memory backing store will be used.",
126+
DefaultText: "in-memory",
127+
},
118128
},
119129

120130
Action: func(cctx *cli.Context) error {
@@ -150,6 +160,17 @@ var observerCmd = cli.Command{
150160
if cctx.IsSet("networkName") {
151161
opts = append(opts, observer.WithNetworkName(gpbft.NetworkName(cctx.String("networkName"))))
152162
}
163+
if cctx.IsSet("initialPowerTableCID") {
164+
initialPowerTableCID, err := cid.Decode(cctx.String("initialPowerTableCID"))
165+
if err != nil {
166+
return fmt.Errorf("failed to decode initial power table CID: %w", err)
167+
}
168+
opts = append(opts, observer.WithInitialPowerTableCID(initialPowerTableCID))
169+
}
170+
if cctx.IsSet("certStorePath") {
171+
certStorePath := cctx.Path("certStorePath")
172+
opts = append(opts, observer.WithFinalityCertsStorePath(certStorePath))
173+
}
153174
bCThreshold := cctx.Int("bootstrapperConnectivityThreshold")
154175
if cctx.IsSet("bootstrapAddr") {
155176
opts = append(opts, observer.WithBootstrapPeersFromString(bCThreshold, cctx.StringSlice("bootstrapAddr")...))

observer/model.go

Lines changed: 83 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,13 @@ import (
77

88
"github.com/filecoin-project/go-bitfield"
99
rlepluslazy "github.com/filecoin-project/go-bitfield/rle"
10+
"github.com/filecoin-project/go-f3/certs"
1011
"github.com/filecoin-project/go-f3/gpbft"
1112
"github.com/ipfs/go-cid"
1213
)
1314

15+
const maxSingers = 1 << 16
16+
1417
var emptyCommitments [32]byte
1518

1619
type Message struct {
@@ -50,6 +53,23 @@ type TipSet struct {
5053
PowerTable string `json:"PowerTable"`
5154
}
5255

56+
type FinalityCertificate struct {
57+
Timestamp time.Time `json:"Timestamp"`
58+
NetworkName string `json:"NetworkName"`
59+
Instance uint64 `json:"Instance"`
60+
ECChain []TipSet `json:"Value"`
61+
SupplementalData SupplementalData `json:"SupplementalData"`
62+
Signers []uint64 `json:"Signers"`
63+
Signature []byte `json:"Signature"`
64+
PowerTableDelta []PowerTableDelta `json:"PowerTableDelta"`
65+
}
66+
67+
type PowerTableDelta struct {
68+
ParticipantID uint64 `json:"ParticipantID"`
69+
PowerDelta int64 `json:"PowerDelta"`
70+
SigningKey []byte `json:"SigningKey"`
71+
}
72+
5373
func newMessage(timestamp time.Time, nn string, msg gpbft.PartialGMessage) (*Message, error) {
5474
j, err := newJustification(msg.Justification)
5575
if err != nil {
@@ -71,7 +91,6 @@ func newJustification(gj *gpbft.Justification) (*Justification, error) {
7191
if gj == nil {
7292
return nil, nil
7393
}
74-
const maxSingers = 1 << 16
7594
signers, err := gj.Signers.All(maxSingers)
7695
if err != nil {
7796
return nil, err
@@ -84,15 +103,18 @@ func newJustification(gj *gpbft.Justification) (*Justification, error) {
84103
}
85104

86105
func newPayload(gp gpbft.Payload) Payload {
87-
var commitments []byte
88-
if gp.SupplementalData.Commitments != emptyCommitments {
89-
// Currently, all Commitments are always empty. For completeness and reducing
90-
// future schema changes include them anyway when they are non-empty.
91-
commitments = gp.SupplementalData.Commitments[:]
106+
return Payload{
107+
Instance: gp.Instance,
108+
Round: gp.Round,
109+
Phase: gp.Phase.String(),
110+
SupplementalData: supplementalDataFromGpbft(gp.SupplementalData),
111+
Value: tipsetsFromGpbft(gp.Value),
92112
}
113+
}
93114

94-
value := make([]TipSet, gp.Value.Len())
95-
for i, v := range gp.Value.TipSets {
115+
func tipsetsFromGpbft(chain *gpbft.ECChain) []TipSet {
116+
value := make([]TipSet, chain.Len())
117+
for i, v := range chain.TipSets {
96118
value[i] = TipSet{
97119
Epoch: v.Epoch,
98120
Key: v.Key,
@@ -102,15 +124,19 @@ func newPayload(gp gpbft.Payload) Payload {
102124
value[i].Commitments = v.Commitments[:]
103125
}
104126
}
105-
return Payload{
106-
Instance: gp.Instance,
107-
Round: gp.Round,
108-
Phase: gp.Phase.String(),
109-
SupplementalData: SupplementalData{
110-
Commitments: commitments,
111-
PowerTable: gp.SupplementalData.PowerTable.String(),
112-
},
113-
Value: value,
127+
return value
128+
}
129+
130+
func supplementalDataFromGpbft(sd gpbft.SupplementalData) SupplementalData {
131+
var commitments []byte
132+
if sd.Commitments != emptyCommitments {
133+
// Currently, all Commitments are always empty. For completeness and reducing
134+
// future schema changes include them anyway when they are non-empty.
135+
commitments = sd.Commitments[:]
136+
}
137+
return SupplementalData{
138+
Commitments: commitments,
139+
PowerTable: sd.PowerTable.String(),
114140
}
115141
}
116142

@@ -249,3 +275,43 @@ func phaseFromString(phase string) (gpbft.Phase, error) {
249275
return 0, fmt.Errorf("unknown phase: %s", phase)
250276
}
251277
}
278+
279+
func newFinalityCertificate(timestamp time.Time, nn gpbft.NetworkName, cert *certs.FinalityCertificate) (*FinalityCertificate, error) {
280+
if cert == nil {
281+
return nil, fmt.Errorf("cannot create FinalityCertificate with nil cert")
282+
}
283+
284+
signers, err := cert.Signers.All(maxSingers)
285+
if err != nil {
286+
return nil, fmt.Errorf("failed to list finality certificate signers: %w", err)
287+
}
288+
289+
deltas, err := powerTableDeltaFromGpbft(cert.PowerTableDelta)
290+
if err != nil {
291+
return nil, fmt.Errorf("failed to convert finality certificate power table delta: %w", err)
292+
}
293+
294+
return &FinalityCertificate{
295+
Timestamp: timestamp,
296+
NetworkName: string(nn),
297+
Instance: cert.GPBFTInstance,
298+
ECChain: tipsetsFromGpbft(cert.ECChain),
299+
SupplementalData: supplementalDataFromGpbft(cert.SupplementalData),
300+
Signers: signers,
301+
Signature: cert.Signature,
302+
PowerTableDelta: deltas,
303+
}, nil
304+
}
305+
306+
func powerTableDeltaFromGpbft(ptd certs.PowerTableDiff) ([]PowerTableDelta, error) {
307+
308+
deltas := make([]PowerTableDelta, len(ptd))
309+
for i, delta := range ptd {
310+
deltas[i] = PowerTableDelta{
311+
ParticipantID: uint64(delta.ParticipantID),
312+
PowerDelta: delta.PowerDelta.Int64(),
313+
SigningKey: delta.SigningKey,
314+
}
315+
}
316+
return deltas, nil
317+
}

0 commit comments

Comments
 (0)