Skip to content

Commit 8584aba

Browse files
services: add new service for fetching blocks from NeoFS
Close #3496 Co-authored-by: Anna Shaleva <shaleva.ann@nspcc.ru> Signed-off-by: Ekaterina Pavlova <ekt@morphbits.io>
1 parent 705a775 commit 8584aba

File tree

10 files changed

+827
-47
lines changed

10 files changed

+827
-47
lines changed

config/protocol.testnet.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,3 +100,16 @@ ApplicationConfiguration:
100100
Enabled: false
101101
Addresses:
102102
- ":2113"
103+
NeoFSBlockFetcher:
104+
Enabled: false
105+
Addresses:
106+
- st1.storage.fs.neo.org:8080
107+
Timeout: 10m
108+
DownloaderWorkersCount: 500
109+
OIDBatchSize: 8000
110+
BQueueSize: 16000 # must be larger than OIDBatchSize; recommended to be 2*OIDBatchSize or 3*OIDBatchSize
111+
SkipIndexFilesSearch: false
112+
IndexFileSize: 128000
113+
ContainerID: "EPGuD26wYgQJbmDdVBoYoNZiMKHwFMJT3A5WqPjdUHxH"
114+
BlockAttribute: "block"
115+
IndexFileAttribute: "oid"

docs/neofs-blockstorage.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# NeoFS block storage
2+
3+
Using NeoFS to store chain's blocks and snapshots was proposed in
4+
[#3463](https://github.com/neo-project/neo/issues/3463). NeoGo contains several
5+
extensions utilizing NeoFS block storage aimed to improve node synchronization
6+
efficiency and reduce node storage size.
7+
8+
## Components and functionality
9+
10+
### Block storage schema
11+
12+
A single NeoFS container is used to store blocks and index files. Each block
13+
is stored in a binary form as a separate object with a unique OID and a set of
14+
attributes:
15+
- block object identifier with block index value (`block:1`)
16+
- primary node index (`primary:0`)
17+
- block hash in the LE form (`hash:5412a781caf278c0736556c0e544c7cfdbb6e3c62ae221ef53646be89364566b`)
18+
- previous block hash in the LE form (`prevHash:3654a054d82a8178c7dfacecc2c57282e23468a42ee407f14506368afe22d929`)
19+
- millisecond-precision block timestamp (`time:1627894840919`)
20+
21+
Each index file is an object containing a constant-sized batch of raw block object
22+
IDs in binary form ordered by block index. Each index file is marked with the
23+
following attributes:
24+
- index file identifier with consecutive file index value (`oid:0`)
25+
- the number of OIDs included into index file (`size:128000`)
26+
27+
### NeoFS BlockFetcher
28+
29+
NeoFS BlockFetcher service is designed as an alternative to P2P synchronisation
30+
protocol. It allows to download blocks from a trusted container in the NeoFS network
31+
and persist them to database using standard verification flow. NeoFS BlockFetcher
32+
service primarily used during the node's bootstrap, providing a fast alternative to
33+
P2P blocks synchronisation.
34+
35+
NeoFS BlockFetcher service has two modes of operation:
36+
- Index File Search: Search for index files, which contain batches of block object
37+
IDs and fetch blocks from NeoFS by retrieved OIDs.
38+
- Direct Block Search: Search and fetch blocks directly from NeoFS container via
39+
built-in NeoFS object search mechanism.
40+
41+
Operation mode of BlockFetcher can be configured via `SkipIndexFilesSearch`
42+
parameter.
43+
44+
#### Operation flow
45+
46+
1. **OID Fetching**:
47+
Depending on the mode, the service either:
48+
- Searches for index files by index file attribute and reads block OIDs from index
49+
file object-by-object.
50+
- Searches batches of blocks directly by block attribute (the batch size is
51+
configured via `OIDBatchSize` parameter).
52+
53+
Once the OIDs are retrieved, they are immediately redirected to the
54+
block downloading routines for further processing. The channel that
55+
is used to redirect block OIDs to downloading routines is buffered
56+
to provide smooth OIDs delivery without delays. The size of this channel
57+
can be configured via `OIDBatchSize` parameter and equals to `2*OIDBatchSize`.
58+
2. **Parallel Block Downloading**:
59+
The number of downloading routines can be configured via
60+
`DownloaderWorkersCount` parameter. It's up to the user to find the
61+
balance between the downloading speed and blocks persist speed for every
62+
node that uses NeoFS BlockFetcher. Downloaded blocks are placed into a
63+
buffered channel of size `IDBatchSize` with further redirection to the
64+
block queue.
65+
3. **Block Insertion**:
66+
Downloaded blocks are inserted into the blockchain using the same logic
67+
as in the P2P synchronisation protocol. The block queue is used to order
68+
downloaded blocks before they are inserted into the blockchain. The
69+
size of the queue can be configured via the `BQueueSize` parameter
70+
and should be larger than the `OIDBatchSize` parameter to avoid blocking
71+
the downloading routines.
72+
73+
Once all blocks available in the NeoFS container are processed, the service
74+
shuts down automatically.

docs/node-configuration.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ node-related settings described in the table below.
2121
| GarbageCollectionPeriod | `uint32` | 10000 | Controls MPT garbage collection interval (in blocks) for configurations with `RemoveUntraceableBlocks` enabled and `KeepOnlyLatestState` disabled. In this mode the node stores a number of MPT trees (corresponding to `MaxTraceableBlocks` and `StateSyncInterval`), but the DB needs to be clean from old entries from time to time. Doing it too often will cause too much processing overhead, doing it too rarely will leave more useless data in the DB. |
2222
| KeepOnlyLatestState | `bool` | `false` | Specifies if MPT should only store the latest state (or a set of latest states, see `P2PStateExchangeExtensions` section in the ProtocolConfiguration for details). If true, DB size will be smaller, but older roots won't be accessible. This value should remain the same for the same database. | |
2323
| LogPath | `string` | "", so only console logging | File path where to store node logs. |
24+
| NeoFSBlockFetcher | [NeoFS BlockFetcher Configuration](#NeoFS-BlockFetcher-Configuration) | | NeoFS BlockFetcher module configuration. See the [NeoFS BlockFetcher Configuration](#NeoFS-BlockFetcher-Configuration) section for details. |
2425
| Oracle | [Oracle Configuration](#Oracle-Configuration) | | Oracle module configuration. See the [Oracle Configuration](#Oracle-Configuration) section for details. |
2526
| P2P | [P2P Configuration](#P2P-Configuration) | | Configuration values for P2P network interaction. See the [P2P Configuration](#P2P-Configuration) section for details. |
2627
| P2PNotary | [P2P Notary Configuration](#P2P-Notary-Configuration) | | P2P Notary module configuration. See the [P2P Notary Configuration](#P2P-Notary-Configuration) section for details. |
@@ -153,6 +154,55 @@ where:
153154
Please, refer to the [Notary module documentation](./notary.md#Notary node module) for
154155
details on module features.
155156

157+
### NeoFS BlockFetcher Configuration
158+
159+
`NeoFSBlockFetcher` configuration section contains settings for NeoFS
160+
BlockFetcher module and has the following structure:
161+
```
162+
NeoFSBlockFetcher:
163+
Enabled: true
164+
UnlockWallet:
165+
Path: "./wallet.json"
166+
Password: "pass"
167+
Addresses:
168+
- st1.storage.fs.neo.org:8080
169+
Timeout: 10m
170+
DownloaderWorkersCount: 500
171+
OIDBatchSize: 8000
172+
BQueueSize: 16000
173+
SkipIndexFilesSearch: false
174+
ContainerID: "EPGuD26wYgQJbmDdVBoYoNZiMKHwFMJT3A5WqPjdUHxH"
175+
BlockAttribute: "block"
176+
IndexFileAttribute: "oid"
177+
IndexFileSize: 128000
178+
```
179+
where:
180+
- `Enabled` enables NeoFS BlockFetcher module.
181+
- `UnlockWallet` contains wallet settings to retrieve account to sign requests to
182+
NeoFS. Without this setting, the module will use randomly generated private key.
183+
For configuration details see [Unlock Wallet Configuration](#Unlock-Wallet-Configuration)
184+
- `Addresses` is a list of NeoFS storage nodes addresses.
185+
- `Timeout` is a timeout for a single request to NeoFS storage node.
186+
- `ContainerID` is a container ID to fetch blocks from.
187+
- `BlockAttribute` is an attribute name of NeoFS object that contains block
188+
data.
189+
- `IndexFileAttribute` is an attribute name of NeoFS index object that contains block
190+
object IDs.
191+
- `DownloaderWorkersCount` is a number of workers that download blocks from
192+
NeoFS in parallel.
193+
- `OIDBatchSize` is the number of blocks to search per a single request to NeoFS
194+
in case of disabled index files search. Also, for both modes of BlockFetcher
195+
operation this setting manages the buffer size of OIDs and blocks transferring
196+
channels.
197+
- `BQueueSize` is a size of the block queue used to manage consecutive blocks
198+
addition to the chain. It must be larger than `OIDBatchSize` and highly recommended
199+
to be `2*OIDBatchSize` or `3*OIDBatchSize`.
200+
- `SkipIndexFilesSearch` is a flag that allows to skip index files search and search
201+
for blocks directly. It is set to `false` by default.
202+
- `IndexFileSize` is the number of OID objects stored in the index files. This
203+
setting depends on the NeoFS block storage configuration and is applicable only if
204+
`SkipIndexFilesSearch` is set to `false`.
205+
156206
### Metrics Services Configuration
157207

158208
Metrics services configuration describes options for metrics services (pprof,

pkg/config/application_config.go

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,13 @@ type ApplicationConfiguration struct {
2323
Pprof BasicService `yaml:"Pprof"`
2424
Prometheus BasicService `yaml:"Prometheus"`
2525

26-
Relay bool `yaml:"Relay"`
27-
Consensus Consensus `yaml:"Consensus"`
28-
RPC RPC `yaml:"RPC"`
29-
Oracle OracleConfiguration `yaml:"Oracle"`
30-
P2PNotary P2PNotary `yaml:"P2PNotary"`
31-
StateRoot StateRoot `yaml:"StateRoot"`
26+
Relay bool `yaml:"Relay"`
27+
Consensus Consensus `yaml:"Consensus"`
28+
RPC RPC `yaml:"RPC"`
29+
Oracle OracleConfiguration `yaml:"Oracle"`
30+
P2PNotary P2PNotary `yaml:"P2PNotary"`
31+
StateRoot StateRoot `yaml:"StateRoot"`
32+
NeoFSBlockFetcher NeoFSBlockFetcher `yaml:"NeoFSBlockFetcher"`
3233
}
3334

3435
// EqualsButServices returns true when the o is the same as a except for services
@@ -141,3 +142,13 @@ func (a *ApplicationConfiguration) GetAddresses() ([]AnnounceableAddress, error)
141142
}
142143
return addrs, nil
143144
}
145+
146+
// Validate checks ApplicationConfiguration for internal consistency and returns
147+
// an error if any invalid settings are found. This ensures that the application
148+
// configuration is valid and safe to use for further operations.
149+
func (a *ApplicationConfiguration) Validate() error {
150+
if err := a.NeoFSBlockFetcher.Validate(); err != nil {
151+
return fmt.Errorf("invalid NeoFSBlockFetcher config: %w", err)
152+
}
153+
return nil
154+
}

pkg/config/blockfetcher_config.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package config
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"time"
7+
8+
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
9+
)
10+
11+
// NeoFSBlockFetcher represents the configuration for the NeoFS BlockFetcher service.
12+
type NeoFSBlockFetcher struct {
13+
InternalService `yaml:",inline"`
14+
Timeout time.Duration `yaml:"Timeout"`
15+
ContainerID string `yaml:"ContainerID"`
16+
Addresses []string `yaml:"Addresses"`
17+
OIDBatchSize int `yaml:"OIDBatchSize"`
18+
BlockAttribute string `yaml:"BlockAttribute"`
19+
IndexFileAttribute string `yaml:"IndexFileAttribute"`
20+
DownloaderWorkersCount int `yaml:"DownloaderWorkersCount"`
21+
BQueueSize int `yaml:"BQueueSize"`
22+
SkipIndexFilesSearch bool `yaml:"SkipIndexFilesSearch"`
23+
IndexFileSize uint32 `yaml:"IndexFileSize"`
24+
}
25+
26+
// Validate checks NeoFSBlockFetcher for internal consistency and ensures
27+
// that all required fields are properly set. It returns an error if the
28+
// configuration is invalid or if the ContainerID cannot be properly decoded.
29+
func (cfg *NeoFSBlockFetcher) Validate() error {
30+
if !cfg.Enabled {
31+
return nil
32+
}
33+
if cfg.ContainerID == "" {
34+
return errors.New("container ID is not set")
35+
}
36+
var containerID cid.ID
37+
err := containerID.DecodeString(cfg.ContainerID)
38+
if err != nil {
39+
return fmt.Errorf("invalid container ID: %w", err)
40+
}
41+
if cfg.BQueueSize < cfg.OIDBatchSize {
42+
return fmt.Errorf("BQueueSize (%d) is lower than OIDBatchSize (%d)", cfg.BQueueSize, cfg.OIDBatchSize)
43+
}
44+
if len(cfg.Addresses) == 0 {
45+
return errors.New("addresses are not set")
46+
}
47+
if cfg.IndexFileSize == 0 {
48+
return errors.New("IndexFileSize is not set")
49+
}
50+
return nil
51+
}

pkg/config/config.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,10 @@ func LoadFile(configPath string, relativePath ...string) (Config, error) {
116116
if err != nil {
117117
return Config{}, err
118118
}
119+
err = config.ApplicationConfiguration.Validate()
120+
if err != nil {
121+
return Config{}, err
122+
}
119123

120124
return config, nil
121125
}

0 commit comments

Comments
 (0)