-
Notifications
You must be signed in to change notification settings - Fork 3
/
backend.go
140 lines (126 loc) · 5.95 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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
package zikade
import (
"context"
"fmt"
lru "github.com/hashicorp/golang-lru/v2"
"github.com/ipfs/boxo/ipns"
ds "github.com/ipfs/go-datastore"
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. key won't contain the
// namespace prefix.
Fetch(ctx context.Context, key string) (any, error)
// Validate validates the given values and returns the index of the "best"
// value or an error and -1 if all values are invalid. If the method is used
// with a single value, it will return 0 and no error if it is valid or an
// error and -1 if it is invalid. For multiple values, it will select the
// "best" value based on user-defined logic and return its index in the
// original values list. If we receive a request for /ipns/$binary_id, the
// key parameter will be set to $binary_id. Decisions about which value is
// the "best" from the given list must be stable. So if there are multiple
// equally good values, the implementation must always return the same
// index - for example, always the first good or last good value.
Validate(ctx context.Context, key string, values ...any) (int, 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) (be *RecordBackend, err error) {
if cfg == nil {
if cfg, err = DefaultRecordBackendConfig(); err != nil {
return nil, fmt.Errorf("default ipns backend config: %w", err)
}
}
return &RecordBackend{
cfg: cfg,
log: cfg.Logger,
namespace: namespaceIPNS,
datastore: ds,
validator: ipns.Validator{KeyBook: kb},
}, nil
}
// 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) (be *RecordBackend, err error) {
if cfg == nil {
if cfg, err = DefaultRecordBackendConfig(); err != nil {
return nil, fmt.Errorf("default public key backend config: %w", err)
}
}
return &RecordBackend{
cfg: cfg,
log: cfg.Logger,
namespace: namespacePublicKey,
datastore: ds,
validator: record.PublicKeyValidator{},
}, nil
}
// 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.Datastore, cfg *ProvidersBackendConfig) (be *ProvidersBackend, err error) {
if cfg == nil {
if cfg, err = DefaultProviderBackendConfig(); err != nil {
return nil, fmt.Errorf("default provider backend config: %w", err)
}
}
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,
addrBook: pstore,
datastore: dstore,
}
return p, nil
}