@@ -11,11 +11,13 @@ import (
11
11
"github.com/ipfs/go-datastore"
12
12
logging "github.com/ipfs/go-log/v2"
13
13
"github.com/ipld/go-ipld-prime"
14
+ "github.com/ipld/go-ipld-prime/codec/dagjson"
14
15
cidlink "github.com/ipld/go-ipld-prime/linking/cid"
15
16
"github.com/ipld/go-ipld-prime/node/basicnode"
16
17
"github.com/ipld/go-ipld-prime/node/bindnode"
18
+ "github.com/ipni/go-libipni/maurl"
19
+ "github.com/ipni/go-libipni/mautil"
17
20
"github.com/libp2p/go-libp2p/core/crypto"
18
- "github.com/libp2p/go-libp2p/core/peer"
19
21
)
20
22
21
23
var (
@@ -26,15 +28,14 @@ var (
26
28
type (
27
29
Federation struct {
28
30
* options
29
- members []peer.ID
30
- vc vectorClock
31
+ vc vectorClock
31
32
32
- mu sync.Mutex
33
- snapshotTicker * time. Ticker
34
- reconciliationTicker * time. Ticker
35
- headNodeCache ipld. Node
36
- ctx context. Context
37
- cancel context. CancelFunc
33
+ mu sync.Mutex
34
+ headNodeCache ipld. Node
35
+ http http. Server
36
+ shutdown chan struct {}
37
+
38
+ lastSeenSnapshotByMember map [ string ]ipld. Link
38
39
}
39
40
)
40
41
@@ -43,30 +44,33 @@ func New(o ...Option) (*Federation, error) {
43
44
if err != nil {
44
45
return nil , err
45
46
}
46
- ctx , cancel := context .WithCancel (context .Background ())
47
47
return & Federation {
48
- options : opts ,
49
- snapshotTicker : time .NewTicker (opts .snapshotInterval ),
50
- reconciliationTicker : time .NewTicker (opts .reconciliationInterval ),
51
- vc : newVectorClock (),
52
- ctx : ctx ,
53
- cancel : cancel ,
48
+ options : opts ,
49
+ vc : newVectorClock (),
50
+ shutdown : make (chan struct {}),
51
+ lastSeenSnapshotByMember : make (map [string ]ipld.Link ),
54
52
}, nil
55
53
}
56
54
57
55
func (f * Federation ) Start (_ context.Context ) error {
58
56
go func () {
57
+ snapshotTicker := time .NewTicker (f .snapshotInterval )
58
+ reconciliationTicker := time .NewTicker (f .reconciliationInterval )
59
+ defer func () {
60
+ snapshotTicker .Stop ()
61
+ reconciliationTicker .Stop ()
62
+ }()
59
63
for {
60
64
select {
61
- case <- f .ctx . Done () :
62
- logger .Warnw ("Stopping federation event loop" , "err" , f . ctx . Err () )
65
+ case <- f .shutdown :
66
+ logger .Warnw ("Stopping federation event loop" )
63
67
return
64
- case t := <- f . snapshotTicker .C :
65
- if err := f .snapshot (f . ctx , t ); err != nil {
68
+ case t := <- snapshotTicker .C :
69
+ if err := f .snapshot (context . TODO () , t ); err != nil {
66
70
logger .Errorw ("Failed to take snapshot" , "err" , err )
67
71
}
68
- case t := <- f . reconciliationTicker .C :
69
- if err := f .reconcile (f . ctx , t ); err != nil {
72
+ case t := <- reconciliationTicker .C :
73
+ if err := f .reconcile (context . TODO () , t ); err != nil {
70
74
logger .Errorw ("Failed to reconcile" , "err" , err )
71
75
}
72
76
}
@@ -117,11 +121,10 @@ func (f *Federation) snapshot(ctx context.Context, t time.Time) error {
117
121
f .vc .untick (f .host .ID ())
118
122
return err
119
123
}
120
- err = f .setHeadLink (ctx , headSnapshotLink )
121
- if err != nil {
124
+ if err := f .setHeadLink (ctx , headSnapshotLink ); err != nil {
122
125
f .vc .untick (f .host .ID ())
123
126
}
124
- return err
127
+ return nil
125
128
}
126
129
127
130
func (f * Federation ) getHeadNode (ctx context.Context ) (ipld.Node , error ) {
@@ -133,63 +136,113 @@ func (f *Federation) getHeadNode(ctx context.Context) (ipld.Node, error) {
133
136
}
134
137
135
138
headLink , err := f .getHeadLink (ctx )
136
- switch {
137
- case errors .Is (err , datastore .ErrNotFound ):
138
- // Set headNodeCache to an empty node to avoid hitting datastore every time head is fetched.
139
- f .headNodeCache = basicnode .Prototype .Any .NewBuilder ().Build ()
140
- return f .headNodeCache , nil
141
- case err != nil :
142
- return nil , err
143
- default :
144
- key , err := f .host .ID ().ExtractPublicKey ()
145
- if err != nil {
146
- logger .Errorw ("Failed to get public key" , "err" , err )
147
- return nil , err
148
- }
149
- marshalledPubKey , err := crypto .MarshalPublicKey (key )
150
- if err != nil {
151
- logger .Errorw ("Failed to marshal public key" , "err" , err )
152
- return nil , err
153
- }
154
- head := Head {
155
- Head : headLink ,
156
- PublicKey : marshalledPubKey ,
157
- }
158
- if err := head .Sign (f .host .Peerstore ().PrivKey (f .host .ID ())); err != nil {
159
- logger .Errorw ("Failed to sign head" , "err" , err )
160
- return nil , err
139
+ if err != nil {
140
+ if errors .Is (err , datastore .ErrNotFound ) {
141
+ // Set headNodeCache to an empty node to avoid hitting datastore every time head is fetched.
142
+ f .headNodeCache = basicnode .Prototype .Any .NewBuilder ().Build ()
143
+ return f .headNodeCache , nil
161
144
}
162
- f .headNodeCache = bindnode .Wrap (head , Prototypes .Head .Type ())
163
- return f .headNodeCache , nil
145
+ return nil , err
146
+ }
147
+ key , err := f .host .ID ().ExtractPublicKey ()
148
+ if err != nil {
149
+ logger .Errorw ("Failed to get public key" , "err" , err )
150
+ return nil , err
151
+ }
152
+ marshalledPubKey , err := crypto .MarshalPublicKey (key )
153
+ if err != nil {
154
+ logger .Errorw ("Failed to marshal public key" , "err" , err )
155
+ return nil , err
156
+ }
157
+ head := Head {
158
+ Head : headLink ,
159
+ PublicKey : marshalledPubKey ,
160
+ }
161
+ if err := head .Sign (f .host .Peerstore ().PrivKey (f .host .ID ())); err != nil {
162
+ logger .Errorw ("Failed to sign head" , "err" , err )
163
+ return nil , err
164
164
}
165
+ f .headNodeCache = bindnode .Wrap (head , Prototypes .Head .Type ())
166
+ return f .headNodeCache , nil
165
167
}
166
168
167
169
func (f * Federation ) getHeadLink (ctx context.Context ) (ipld.Link , error ) {
168
- switch v , err := f .datastore .Get (ctx , headSnapshotKey ); {
169
- case errors .Is (err , datastore .ErrNotFound ):
170
- return nil , nil
171
- case err != nil :
172
- return nil , err
173
- default :
174
- _ , c , err := cid .CidFromBytes (v )
175
- if err != nil {
176
- return nil , err
170
+ v , err := f .datastore .Get (ctx , headSnapshotKey )
171
+ if err != nil {
172
+ if errors .Is (err , datastore .ErrNotFound ) {
173
+ return nil , nil
177
174
}
178
- return cidlink. Link { Cid : c }, nil
175
+ return nil , err
179
176
}
177
+ _ , c , err := cid .CidFromBytes (v )
178
+ if err != nil {
179
+ return nil , err
180
+ }
181
+ return cidlink.Link {Cid : c }, nil
180
182
}
181
183
182
184
func (f * Federation ) setHeadLink (ctx context.Context , head ipld.Link ) error {
183
185
return f .datastore .Put (ctx , headSnapshotKey , head .(cidlink.Link ).Cid .Bytes ())
184
186
}
185
187
186
188
func (f * Federation ) Shutdown (_ context.Context ) error {
187
- f .snapshotTicker .Stop ()
188
- f .cancel ()
189
+ close (f .shutdown )
189
190
return nil
190
191
}
191
192
192
193
func (f * Federation ) reconcile (ctx context.Context , t time.Time ) error {
194
+ for _ , member := range f .members {
195
+ logger := logger .With ("id" , member .ID )
196
+ memberPubKey , err := member .ID .ExtractPublicKey ()
197
+ if err != nil {
198
+ logger .Errorw ("Failed to extract public key fom federation member peer ID" , "err" , err )
199
+ continue
200
+ }
201
+ httpAddrs := mautil .FindHTTPAddrs (member .Addrs )
202
+ switch len (httpAddrs ) {
203
+ case 0 :
204
+ logger .Errorw ("No HTTP(S) address found for federation member" , "addrs" , member .Addrs )
205
+ continue
206
+ case 1 :
207
+ // Exactly one HTTP(S) multiaddr was found. As you were; proceed.
208
+ default :
209
+ logger .Warnw ("Multiple HTTP(S) multiaddrs found for federation member. Picking the first one." , "httpAddrs" , httpAddrs )
210
+ }
211
+ endpoint , err := maurl .ToURL (httpAddrs [0 ])
212
+ if err != nil {
213
+ logger .Errorw ("Failed to extract URL from HTTP(S) multiaddr" , "httpAddr" , httpAddrs [0 ], "err" , err )
214
+ continue
215
+ }
216
+ req , err := http .NewRequest (http .MethodGet , endpoint .JoinPath ("ipni" , "v1" , "fed" , "head" ).String (), nil )
217
+ if err != nil {
218
+ logger .Errorw ("Failed instantiate GET request" , "err" , err )
219
+ continue
220
+ }
221
+ // TODO: accept other kinds too, like dagcbor.
222
+ req .Header .Set ("Accept" , "application/json" )
223
+ resp , err := f .reconciliationHttpClient .Do (req )
224
+ if err != nil {
225
+ logger .Errorw ("Failed to GET federation state head from member" , "err" , err )
226
+ continue
227
+ }
228
+ if resp .StatusCode != http .StatusOK {
229
+ logger .Errorw ("GET federation state head response status code is not OK" , "status" , resp .Status , "err" , err )
230
+ continue
231
+ }
232
+ defer func () { _ = resp .Body .Close () }()
233
+ // TODO: add checks for Content-Type.
234
+ var head Head
235
+ builder := Prototypes .Head .NewBuilder ()
236
+ if err := dagjson .Decode (builder , resp .Body ); err != nil {
237
+ logger .Errorw ("Failed to decode " , "status" , resp .Status , "err" , err )
238
+ continue
239
+ }
240
+ if err := head .Verify (memberPubKey ); err != nil {
241
+ logger .Errorw ("Invalid federation state head" , "err" , err )
242
+ continue
243
+ }
244
+ }
245
+
193
246
// TODO: For each f.peers, get head, get snapshot, f.vc.reconcile, aggregate providers.
194
247
return nil
195
248
}
0 commit comments