• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lightningnetwork / lnd / 15117244231

19 May 2025 03:37PM UTC coverage: 57.988% (-11.0%) from 68.99%
15117244231

Pull #9825

github

web-flow
Merge e714b31b8 into 3707b1fb7
Pull Request #9825: Refactor Payment PR 1

326 of 469 new or added lines in 4 files covered. (69.51%)

29180 existing lines in 455 files now uncovered.

96428 of 166290 relevant lines covered (57.99%)

1.22 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

71.36
/accessman.go
1
package lnd
2

3
import (
4
        "context"
5
        "fmt"
6
        "maps"
7
        "sync"
8

9
        "github.com/btcsuite/btcd/btcec/v2"
10
        "github.com/btcsuite/btclog/v2"
11
        "github.com/lightningnetwork/lnd/channeldb"
12
        "github.com/lightningnetwork/lnd/lnutils"
13
)
14

15
// accessMan is responsible for managing the server's access permissions.
16
type accessMan struct {
17
        cfg *accessManConfig
18

19
        // banScoreMtx is used for the server's ban tracking. If the server
20
        // mutex is also going to be locked, ensure that this is locked after
21
        // the server mutex.
22
        banScoreMtx sync.RWMutex
23

24
        // peerCounts is a mapping from remote public key to {bool, uint64}
25
        // where the bool indicates that we have an open/closed channel with
26
        // the peer and where the uint64 indicates the number of pending-open
27
        // channels we currently have with them. This mapping will be used to
28
        // determine access permissions for the peer. The map key is the
29
        // string-version of the serialized public key.
30
        //
31
        // NOTE: This MUST be accessed with the banScoreMtx held.
32
        peerCounts map[string]channeldb.ChanCount
33

34
        // peerScores stores each connected peer's access status. The map key
35
        // is the string-version of the serialized public key.
36
        //
37
        // NOTE: This MUST be accessed with the banScoreMtx held.
38
        peerScores map[string]peerSlotStatus
39

40
        // numRestricted tracks the number of peers with restricted access in
41
        // peerScores. This MUST be accessed with the banScoreMtx held.
42
        numRestricted int64
43
}
44

45
type accessManConfig struct {
46
        // initAccessPerms checks the channeldb for initial access permissions
47
        // and then populates the peerCounts and peerScores maps.
48
        initAccessPerms func() (map[string]channeldb.ChanCount, error)
49

50
        // shouldDisconnect determines whether we should disconnect a peer or
51
        // not.
52
        shouldDisconnect func(*btcec.PublicKey) (bool, error)
53

54
        // maxRestrictedSlots is the number of restricted slots we'll allocate.
55
        maxRestrictedSlots int64
56
}
57

58
func newAccessMan(cfg *accessManConfig) (*accessMan, error) {
2✔
59
        a := &accessMan{
2✔
60
                cfg:        cfg,
2✔
61
                peerCounts: make(map[string]channeldb.ChanCount),
2✔
62
                peerScores: make(map[string]peerSlotStatus),
2✔
63
        }
2✔
64

2✔
65
        counts, err := a.cfg.initAccessPerms()
2✔
66
        if err != nil {
2✔
67
                return nil, err
×
68
        }
×
69

70
        // We'll populate the server's peerCounts map with the counts fetched
71
        // via initAccessPerms. Also note that we haven't yet connected to the
72
        // peers.
73
        maps.Copy(a.peerCounts, counts)
2✔
74

2✔
75
        acsmLog.Info("Access Manager initialized")
2✔
76

2✔
77
        return a, nil
2✔
78
}
79

80
// assignPeerPerms assigns a new peer its permissions. This does not track the
81
// access in the maps. This is intentional.
82
func (a *accessMan) assignPeerPerms(remotePub *btcec.PublicKey) (
83
        peerAccessStatus, error) {
2✔
84

2✔
85
        ctx := btclog.WithCtx(
2✔
86
                context.TODO(), lnutils.LogPubKey("peer", remotePub),
2✔
87
        )
2✔
88

2✔
89
        peerMapKey := string(remotePub.SerializeCompressed())
2✔
90

2✔
91
        acsmLog.DebugS(ctx, "Assigning permissions")
2✔
92

2✔
93
        // Default is restricted unless the below filters say otherwise.
2✔
94
        access := peerStatusRestricted
2✔
95

2✔
96
        shouldDisconnect, err := a.cfg.shouldDisconnect(remotePub)
2✔
97
        if err != nil {
2✔
98
                acsmLog.ErrorS(ctx, "Error checking disconnect status", err)
×
99

×
100
                // Access is restricted here.
×
101
                return access, err
×
102
        }
×
103

104
        if shouldDisconnect {
2✔
105
                acsmLog.WarnS(ctx, "Peer is banned, assigning restricted access",
×
106
                        ErrGossiperBan)
×
107

×
108
                // Access is restricted here.
×
109
                return access, ErrGossiperBan
×
110
        }
×
111

112
        // Lock banScoreMtx for reading so that we can update the banning maps
113
        // below.
114
        a.banScoreMtx.RLock()
2✔
115
        defer a.banScoreMtx.RUnlock()
2✔
116

2✔
117
        if count, found := a.peerCounts[peerMapKey]; found {
4✔
118
                if count.HasOpenOrClosedChan {
4✔
119
                        acsmLog.DebugS(ctx, "Peer has open/closed channel, "+
2✔
120
                                "assigning protected access")
2✔
121

2✔
122
                        access = peerStatusProtected
2✔
123
                } else if count.PendingOpenCount != 0 {
6✔
124
                        acsmLog.DebugS(ctx, "Peer has pending channel(s), "+
2✔
125
                                "assigning temporary access")
2✔
126

2✔
127
                        access = peerStatusTemporary
2✔
128
                }
2✔
129
        }
130

131
        // If we've reached this point and access hasn't changed from
132
        // restricted, then we need to check if we even have a slot for this
133
        // peer.
134
        if access == peerStatusRestricted {
4✔
135
                acsmLog.DebugS(ctx, "Peer has no channels, assigning "+
2✔
136
                        "restricted access")
2✔
137

2✔
138
                if a.numRestricted >= a.cfg.maxRestrictedSlots {
2✔
139
                        acsmLog.WarnS(ctx, "No more restricted slots "+
×
140
                                "available, denying peer",
×
141
                                ErrNoMoreRestrictedAccessSlots,
×
142
                                "num_restricted", a.numRestricted,
×
143
                                "max_restricted", a.cfg.maxRestrictedSlots)
×
144

×
145
                        return access, ErrNoMoreRestrictedAccessSlots
×
146
                }
×
147
        }
148

149
        return access, nil
2✔
150
}
151

152
// newPendingOpenChan is called after the pending-open channel has been
153
// committed to the database. This may transition a restricted-access peer to a
154
// temporary-access peer.
155
func (a *accessMan) newPendingOpenChan(remotePub *btcec.PublicKey) error {
2✔
156
        a.banScoreMtx.Lock()
2✔
157
        defer a.banScoreMtx.Unlock()
2✔
158

2✔
159
        ctx := btclog.WithCtx(
2✔
160
                context.TODO(), lnutils.LogPubKey("peer", remotePub),
2✔
161
        )
2✔
162

2✔
163
        acsmLog.DebugS(ctx, "Processing new pending open channel")
2✔
164

2✔
165
        peerMapKey := string(remotePub.SerializeCompressed())
2✔
166

2✔
167
        // Fetch the peer's access status from peerScores.
2✔
168
        status, found := a.peerScores[peerMapKey]
2✔
169
        if !found {
2✔
170
                acsmLog.ErrorS(ctx, "Peer score not found", ErrNoPeerScore)
×
171

×
172
                // If we didn't find the peer, we'll return an error.
×
173
                return ErrNoPeerScore
×
174
        }
×
175

176
        switch status.state {
2✔
177
        case peerStatusProtected:
2✔
178
                acsmLog.DebugS(ctx, "Peer already protected, no change")
2✔
179

2✔
180
                // If this peer's access status is protected, we don't need to
2✔
181
                // do anything.
2✔
182
                return nil
2✔
183

184
        case peerStatusTemporary:
2✔
185
                // If this peer's access status is temporary, we'll need to
2✔
186
                // update the peerCounts map. The peer's access status will stay
2✔
187
                // temporary.
2✔
188
                peerCount, found := a.peerCounts[peerMapKey]
2✔
189
                if !found {
2✔
190
                        // Error if we did not find any info in peerCounts.
×
191
                        acsmLog.ErrorS(ctx, "Pending peer info not found",
×
192
                                ErrNoPendingPeerInfo)
×
193

×
194
                        return ErrNoPendingPeerInfo
×
195
                }
×
196

197
                // Increment the pending channel amount.
198
                peerCount.PendingOpenCount += 1
2✔
199
                a.peerCounts[peerMapKey] = peerCount
2✔
200

2✔
201
                acsmLog.DebugS(ctx, "Peer is temporary, incremented "+
2✔
202
                        "pending count",
2✔
203
                        "pending_count", peerCount.PendingOpenCount)
2✔
204

205
        case peerStatusRestricted:
2✔
206
                // If the peer's access status is restricted, then we can
2✔
207
                // transition it to a temporary-access peer. We'll need to
2✔
208
                // update numRestricted and also peerScores. We'll also need to
2✔
209
                // update peerCounts.
2✔
210
                peerCount := channeldb.ChanCount{
2✔
211
                        HasOpenOrClosedChan: false,
2✔
212
                        PendingOpenCount:    1,
2✔
213
                }
2✔
214

2✔
215
                a.peerCounts[peerMapKey] = peerCount
2✔
216

2✔
217
                // A restricted-access slot has opened up.
2✔
218
                oldRestricted := a.numRestricted
2✔
219
                a.numRestricted -= 1
2✔
220

2✔
221
                a.peerScores[peerMapKey] = peerSlotStatus{
2✔
222
                        state: peerStatusTemporary,
2✔
223
                }
2✔
224

2✔
225
                acsmLog.InfoS(ctx, "Peer transitioned restricted -> "+
2✔
226
                        "temporary (pending open)",
2✔
227
                        "old_restricted", oldRestricted,
2✔
228
                        "new_restricted", a.numRestricted)
2✔
229

230
        default:
×
231
                // This should not be possible.
×
232
                err := fmt.Errorf("invalid peer access status %v for %x",
×
233
                        status.state, peerMapKey)
×
234
                acsmLog.ErrorS(ctx, "Invalid peer access status", err)
×
235

×
236
                return err
×
237
        }
238

239
        return nil
2✔
240
}
241

242
// newPendingCloseChan is called when a pending-open channel prematurely closes
243
// before the funding transaction has confirmed. This potentially demotes a
244
// temporary-access peer to a restricted-access peer. If no restricted-access
245
// slots are available, the peer will be disconnected.
246
func (a *accessMan) newPendingCloseChan(remotePub *btcec.PublicKey) error {
2✔
247
        a.banScoreMtx.Lock()
2✔
248
        defer a.banScoreMtx.Unlock()
2✔
249

2✔
250
        ctx := btclog.WithCtx(
2✔
251
                context.TODO(), lnutils.LogPubKey("peer", remotePub),
2✔
252
        )
2✔
253

2✔
254
        acsmLog.DebugS(ctx, "Processing pending channel close")
2✔
255

2✔
256
        peerMapKey := string(remotePub.SerializeCompressed())
2✔
257

2✔
258
        // Fetch the peer's access status from peerScores.
2✔
259
        status, found := a.peerScores[peerMapKey]
2✔
260
        if !found {
2✔
261
                acsmLog.ErrorS(ctx, "Peer score not found", ErrNoPeerScore)
×
262

×
263
                return ErrNoPeerScore
×
264
        }
×
265

266
        switch status.state {
2✔
267
        case peerStatusProtected:
×
268
                // If this peer is protected, we don't do anything.
×
269
                acsmLog.DebugS(ctx, "Peer is protected, no change")
×
270

×
271
                return nil
×
272

273
        case peerStatusTemporary:
2✔
274
                // If this peer is temporary, we need to check if it will
2✔
275
                // revert to a restricted-access peer.
2✔
276
                peerCount, found := a.peerCounts[peerMapKey]
2✔
277
                if !found {
2✔
278
                        acsmLog.ErrorS(ctx, "Pending peer info not found",
×
279
                                ErrNoPendingPeerInfo)
×
280

×
281
                        // Error if we did not find any info in peerCounts.
×
282
                        return ErrNoPendingPeerInfo
×
283
                }
×
284

285
                currentNumPending := peerCount.PendingOpenCount - 1
2✔
286

2✔
287
                acsmLog.DebugS(ctx, "Peer is temporary, decrementing "+
2✔
288
                        "pending count",
2✔
289
                        "pending_count", currentNumPending)
2✔
290

2✔
291
                if currentNumPending == 0 {
4✔
292
                        // Remove the entry from peerCounts.
2✔
293
                        delete(a.peerCounts, peerMapKey)
2✔
294

2✔
295
                        // If this is the only pending-open channel for this
2✔
296
                        // peer and it's getting removed, attempt to demote
2✔
297
                        // this peer to a restricted peer.
2✔
298
                        if a.numRestricted == a.cfg.maxRestrictedSlots {
2✔
UNCOV
299
                                // There are no available restricted slots, so
×
UNCOV
300
                                // we need to disconnect this peer. We leave
×
UNCOV
301
                                // this up to the caller.
×
UNCOV
302
                                acsmLog.WarnS(ctx, "Peer last pending "+
×
UNCOV
303
                                        "channel closed: ",
×
UNCOV
304
                                        ErrNoMoreRestrictedAccessSlots,
×
UNCOV
305
                                        "num_restricted", a.numRestricted,
×
UNCOV
306
                                        "max_restricted", a.cfg.maxRestrictedSlots)
×
UNCOV
307

×
UNCOV
308
                                return ErrNoMoreRestrictedAccessSlots
×
UNCOV
309
                        }
×
310

311
                        // Otherwise, there is an available restricted-access
312
                        // slot, so we can demote this peer.
313
                        a.peerScores[peerMapKey] = peerSlotStatus{
2✔
314
                                state: peerStatusRestricted,
2✔
315
                        }
2✔
316

2✔
317
                        // Update numRestricted.
2✔
318
                        oldRestricted := a.numRestricted
2✔
319
                        a.numRestricted++
2✔
320

2✔
321
                        acsmLog.InfoS(ctx, "Peer transitioned "+
2✔
322
                                "temporary -> restricted "+
2✔
323
                                "(last pending closed)",
2✔
324
                                "old_restricted", oldRestricted,
2✔
325
                                "new_restricted", a.numRestricted)
2✔
326

2✔
327
                        return nil
2✔
328
                }
329

330
                // Else, we don't need to demote this peer since it has other
331
                // pending-open channels with us.
332
                peerCount.PendingOpenCount = currentNumPending
×
333
                a.peerCounts[peerMapKey] = peerCount
×
334

×
335
                acsmLog.DebugS(ctx, "Peer still has other pending channels",
×
336
                        "pending_count", currentNumPending)
×
337

×
338
                return nil
×
339

340
        case peerStatusRestricted:
×
341
                // This should not be possible. This indicates an error.
×
342
                err := fmt.Errorf("invalid peer access state transition: "+
×
343
                        "pending close for restricted peer %x", peerMapKey)
×
344
                acsmLog.ErrorS(ctx, "Invalid peer access state transition", err)
×
345

×
346
                return err
×
347

348
        default:
×
349
                // This should not be possible.
×
350
                err := fmt.Errorf("invalid peer access status %v for %x",
×
351
                        status.state, peerMapKey)
×
352
                acsmLog.ErrorS(ctx, "Invalid peer access status", err)
×
353

×
354
                return err
×
355
        }
356
}
357

358
// newOpenChan is called when a pending-open channel becomes an open channel
359
// (i.e. the funding transaction has confirmed). If the remote peer is a
360
// temporary-access peer, it will be promoted to a protected-access peer.
361
func (a *accessMan) newOpenChan(remotePub *btcec.PublicKey) error {
2✔
362
        a.banScoreMtx.Lock()
2✔
363
        defer a.banScoreMtx.Unlock()
2✔
364

2✔
365
        ctx := btclog.WithCtx(
2✔
366
                context.TODO(), lnutils.LogPubKey("peer", remotePub),
2✔
367
        )
2✔
368

2✔
369
        acsmLog.DebugS(ctx, "Processing new open channel")
2✔
370

2✔
371
        peerMapKey := string(remotePub.SerializeCompressed())
2✔
372

2✔
373
        // Fetch the peer's access status from peerScores.
2✔
374
        status, found := a.peerScores[peerMapKey]
2✔
375
        if !found {
4✔
376
                // If we didn't find the peer, we'll return an error.
2✔
377
                acsmLog.ErrorS(ctx, "Peer score not found", ErrNoPeerScore)
2✔
378

2✔
379
                return ErrNoPeerScore
2✔
380
        }
2✔
381

382
        switch status.state {
2✔
383
        case peerStatusProtected:
2✔
384
                acsmLog.DebugS(ctx, "Peer already protected, no change")
2✔
385

2✔
386
                // If the peer's state is already protected, we don't need to do
2✔
387
                // anything more.
2✔
388
                return nil
2✔
389

390
        case peerStatusTemporary:
2✔
391
                // If the peer's state is temporary, we'll upgrade the peer to
2✔
392
                // a protected peer.
2✔
393
                peerCount, found := a.peerCounts[peerMapKey]
2✔
394
                if !found {
2✔
395
                        // Error if we did not find any info in peerCounts.
×
396
                        acsmLog.ErrorS(ctx, "Pending peer info not found",
×
397
                                ErrNoPendingPeerInfo)
×
398

×
399
                        return ErrNoPendingPeerInfo
×
400
                }
×
401

402
                peerCount.HasOpenOrClosedChan = true
2✔
403
                a.peerCounts[peerMapKey] = peerCount
2✔
404

2✔
405
                newStatus := peerSlotStatus{
2✔
406
                        state: peerStatusProtected,
2✔
407
                }
2✔
408
                a.peerScores[peerMapKey] = newStatus
2✔
409

2✔
410
                acsmLog.InfoS(ctx, "Peer transitioned temporary -> "+
2✔
411
                        "protected (channel opened)")
2✔
412

2✔
413
                return nil
2✔
414

415
        case peerStatusRestricted:
×
416
                // This should not be possible. For the server to receive a
×
417
                // state-transition event via NewOpenChan, the server must have
×
418
                // previously granted this peer "temporary" access. This
×
419
                // temporary access would not have been revoked or downgraded
×
420
                // without `CloseChannel` being called with the pending
×
421
                // argument set to true. This means that an open-channel state
×
422
                // transition would be impossible. Therefore, we can return an
×
423
                // error.
×
424
                err := fmt.Errorf("invalid peer access status: new open "+
×
425
                        "channel for restricted peer %x", peerMapKey)
×
426

×
427
                acsmLog.ErrorS(ctx, "Invalid peer access status", err)
×
428

×
429
                return err
×
430

431
        default:
×
432
                // This should not be possible.
×
433
                err := fmt.Errorf("invalid peer access status %v for %x",
×
434
                        status.state, peerMapKey)
×
435

×
436
                acsmLog.ErrorS(ctx, "Invalid peer access status", err)
×
437

×
438
                return err
×
439
        }
440
}
441

442
// checkIncomingConnBanScore checks whether, given the remote's public hex-
443
// encoded key, we should not accept this incoming connection or immediately
444
// disconnect. This does not assign to the server's peerScores maps. This is
445
// just an inbound filter that the brontide listeners use.
446
func (a *accessMan) checkIncomingConnBanScore(remotePub *btcec.PublicKey) (
447
        bool, error) {
2✔
448

2✔
449
        ctx := btclog.WithCtx(
2✔
450
                context.TODO(), lnutils.LogPubKey("peer", remotePub),
2✔
451
        )
2✔
452

2✔
453
        peerMapKey := string(remotePub.SerializeCompressed())
2✔
454

2✔
455
        acsmLog.TraceS(ctx, "Checking incoming connection ban score")
2✔
456

2✔
457
        a.banScoreMtx.RLock()
2✔
458
        defer a.banScoreMtx.RUnlock()
2✔
459

2✔
460
        if _, found := a.peerCounts[peerMapKey]; !found {
4✔
461
                acsmLog.DebugS(ctx, "Peer not found in counts, "+
2✔
462
                        "checking restricted slots")
2✔
463

2✔
464
                // Check numRestricted to see if there is an available slot. In
2✔
465
                // the future, it's possible to add better heuristics.
2✔
466
                if a.numRestricted < a.cfg.maxRestrictedSlots {
4✔
467
                        // There is an available slot.
2✔
468
                        acsmLog.DebugS(ctx, "Restricted slot available, "+
2✔
469
                                "accepting",
2✔
470
                                "num_restricted", a.numRestricted,
2✔
471
                                "max_restricted", a.cfg.maxRestrictedSlots)
2✔
472

2✔
473
                        return true, nil
2✔
474
                }
2✔
475

476
                // If there are no slots left, then we reject this connection.
477
                acsmLog.WarnS(ctx, "No restricted slots available, "+
2✔
478
                        "rejecting",
2✔
479
                        ErrNoMoreRestrictedAccessSlots,
2✔
480
                        "num_restricted", a.numRestricted,
2✔
481
                        "max_restricted", a.cfg.maxRestrictedSlots)
2✔
482

2✔
483
                return false, ErrNoMoreRestrictedAccessSlots
2✔
484
        }
485

486
        // Else, the peer is either protected or temporary.
487
        acsmLog.DebugS(ctx, "Peer found (protected/temporary), accepting")
2✔
488

2✔
489
        return true, nil
2✔
490
}
491

492
// addPeerAccess tracks a peer's access in the maps. This should be called when
493
// the peer has fully connected.
494
func (a *accessMan) addPeerAccess(remotePub *btcec.PublicKey,
495
        access peerAccessStatus) {
2✔
496

2✔
497
        ctx := btclog.WithCtx(
2✔
498
                context.TODO(), lnutils.LogPubKey("peer", remotePub),
2✔
499
        )
2✔
500

2✔
501
        acsmLog.DebugS(ctx, "Adding peer access", "access", access)
2✔
502

2✔
503
        // Add the remote public key to peerScores.
2✔
504
        a.banScoreMtx.Lock()
2✔
505
        defer a.banScoreMtx.Unlock()
2✔
506

2✔
507
        peerMapKey := string(remotePub.SerializeCompressed())
2✔
508

2✔
509
        a.peerScores[peerMapKey] = peerSlotStatus{state: access}
2✔
510

2✔
511
        // Increment numRestricted.
2✔
512
        if access == peerStatusRestricted {
4✔
513
                oldRestricted := a.numRestricted
2✔
514
                a.numRestricted++
2✔
515

2✔
516
                acsmLog.DebugS(ctx, "Incremented restricted slots",
2✔
517
                        "old_restricted", oldRestricted,
2✔
518
                        "new_restricted", a.numRestricted)
2✔
519
        }
2✔
520
}
521

522
// removePeerAccess removes the peer's access from the maps. This should be
523
// called when the peer has been disconnected.
524
func (a *accessMan) removePeerAccess(remotePub *btcec.PublicKey) {
2✔
525
        a.banScoreMtx.Lock()
2✔
526
        defer a.banScoreMtx.Unlock()
2✔
527

2✔
528
        ctx := btclog.WithCtx(
2✔
529
                context.TODO(), lnutils.LogPubKey("peer", remotePub),
2✔
530
        )
2✔
531

2✔
532
        acsmLog.DebugS(ctx, "Removing peer access")
2✔
533

2✔
534
        peerMapKey := string(remotePub.SerializeCompressed())
2✔
535

2✔
536
        status, found := a.peerScores[peerMapKey]
2✔
537
        if !found {
2✔
538
                acsmLog.InfoS(ctx, "Peer score not found during removal")
×
539
                return
×
540
        }
×
541

542
        if status.state == peerStatusRestricted {
4✔
543
                // If the status is restricted, then we decrement from
2✔
544
                // numRestrictedSlots.
2✔
545
                oldRestricted := a.numRestricted
2✔
546
                a.numRestricted--
2✔
547

2✔
548
                acsmLog.DebugS(ctx, "Decremented restricted slots",
2✔
549
                        "old_restricted", oldRestricted,
2✔
550
                        "new_restricted", a.numRestricted)
2✔
551
        }
2✔
552

553
        acsmLog.TraceS(ctx, "Deleting peer from peerScores")
2✔
554

2✔
555
        delete(a.peerScores, peerMapKey)
2✔
556
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc