-
Notifications
You must be signed in to change notification settings - Fork 216
/
backend.go
121 lines (108 loc) · 4.85 KB
/
backend.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
package dht
import (
"context"
lru "github.com/hashicorp/golang-lru/v2"
"github.com/ipfs/boxo/ipns"
ds "github.com/ipfs/go-datastore"
"github.com/ipfs/go-datastore/autobatch"
record "github.com/libp2p/go-libp2p-record"
"github.com/libp2p/go-libp2p/core/peerstore"
)
// Default namespaces
const (
namespaceIPNS = "ipns"
namespacePublicKey = "pk"
namespaceProviders = "providers"
)
// A Backend implementation handles requests for certain record types from other
// peers. A Backend always belongs to a certain namespace. In this case a
// namespace is equivalent to a type of record that this DHT supports. In the
// case of IPFS, the DHT supports the "ipns", "pk", and "providers" namespaces
// and therefore uses three different backends. Depending on the request's key
// the DHT invokes the corresponding backend Store and Fetch methods. A key
// has the structure "/$namespace/$path". The DHT parses uses the $namespace
// part to decide which Backend to use. The $path part is then passed to the
// Backend's Store and Fetch methods as the "key" parameter. Backends for
// different namespace may or may not operate on the same underlying datastore.
//
// To support additional record types, users would implement this Backend
// interface and register it for a custom namespace with the [DHT] [Config] by
// adding it to the [Config.Backend] map. Any PUT_VALUE/GET_VALUE requests would
// start to support the new record type. The requirement is though that all
// "any" types must be [*recpb.Record] types. The below interface cannot enforce
// that type because provider records are handled slightly differently. For
// example, with provider records, the return values are not assigned to the
// [pb.Message.Record] field but to the [pb.Message.ProviderPeers] field.
//
// This repository defines default Backends for the "ipns", "pk", and
// "providers" namespaces. They can be instantiated with [NewBackendIPNS],
// [NewBackendPublicKey], and [NewBackendProvider] respectively.
type Backend interface {
// Store stores the given value such that it can be retrieved via Fetch
// with the same key parameter. It returns the written record. The key
// that will be handed into the Store won't contain the namespace prefix. For
// example, if we receive a request for /ipns/$binary_id, key will be set to
// $binary_id. The backend implementation is free to decide how to store the
// data in the datastore. However, it makes sense to prefix the record with
// the namespace that this Backend operates in.
Store(ctx context.Context, key string, value any) (any, error)
// Fetch returns the record for the given path or a [ds.ErrNotFound] if it
// wasn't found or another error if any occurred.
Fetch(ctx context.Context, key string) (any, error)
}
// NewBackendIPNS initializes a new backend for the "ipns" namespace that can
// store and fetch IPNS records from the given datastore. The stored and
// returned records must be of type [*recpb.Record]. The cfg parameter can be
// nil, in which case the [DefaultRecordBackendConfig] will be used.
func NewBackendIPNS(ds ds.TxnDatastore, kb peerstore.KeyBook, cfg *RecordBackendConfig) *RecordBackend {
if cfg == nil {
cfg = DefaultRecordBackendConfig()
}
return &RecordBackend{
cfg: cfg,
log: cfg.Logger,
namespace: namespaceIPNS,
datastore: ds,
validator: ipns.Validator{KeyBook: kb},
}
}
// NewBackendPublicKey initializes a new backend for the "pk" namespace that can
// store and fetch public key records from the given datastore. The stored and
// returned records must be of type [*recpb.Record]. The cfg parameter can be
// nil, in which case the [DefaultRecordBackendConfig] will be used.
func NewBackendPublicKey(ds ds.TxnDatastore, cfg *RecordBackendConfig) *RecordBackend {
if cfg == nil {
cfg = DefaultRecordBackendConfig()
}
return &RecordBackend{
cfg: cfg,
log: cfg.Logger,
namespace: namespacePublicKey,
datastore: ds,
validator: record.PublicKeyValidator{},
}
}
// NewBackendProvider initializes a new backend for the "providers" namespace
// that can store and fetch provider records from the given datastore. The
// values passed into [ProvidersBackend.Store] must be of type [peer.AddrInfo].
// The values returned from [ProvidersBackend.Fetch] will be of type
// [*providerSet] (unexported). The cfg parameter can be nil, in which case the
// [DefaultProviderBackendConfig] will be used.
func NewBackendProvider(pstore peerstore.Peerstore, dstore ds.Batching, cfg *ProvidersBackendConfig) (*ProvidersBackend, error) {
if cfg == nil {
cfg = DefaultProviderBackendConfig()
}
cache, err := lru.New[string, providerSet](cfg.CacheSize)
if err != nil {
return nil, err
}
p := &ProvidersBackend{
cfg: cfg,
log: cfg.Logger,
cache: cache,
namespace: namespaceProviders,
peerstore: pstore,
datastore: autobatch.NewAutoBatching(dstore, cfg.BatchSize),
}
return p, nil
}