-
Notifications
You must be signed in to change notification settings - Fork 79
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3515 from nspcc-dev/block-fetcher
services: add new block fetching from NeoFS service
- Loading branch information
Showing
18 changed files
with
1,314 additions
and
96 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
package server | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"path/filepath" | ||
|
||
"github.com/nspcc-dev/neo-go/cli/cmdargs" | ||
"github.com/nspcc-dev/neo-go/cli/options" | ||
"github.com/nspcc-dev/neo-go/pkg/core/block" | ||
"github.com/nspcc-dev/neo-go/pkg/io" | ||
"github.com/urfave/cli/v2" | ||
) | ||
|
||
func dumpBin(ctx *cli.Context) error { | ||
if err := cmdargs.EnsureNone(ctx); err != nil { | ||
return err | ||
} | ||
cfg, err := options.GetConfigFromContext(ctx) | ||
if err != nil { | ||
return cli.Exit(err, 1) | ||
} | ||
log, _, logCloser, err := options.HandleLoggingParams(ctx.Bool("debug"), cfg.ApplicationConfiguration) | ||
if err != nil { | ||
return cli.Exit(err, 1) | ||
} | ||
if logCloser != nil { | ||
defer func() { _ = logCloser() }() | ||
} | ||
count := uint32(ctx.Uint("count")) | ||
start := uint32(ctx.Uint("start")) | ||
|
||
chain, prometheus, pprof, err := initBCWithMetrics(cfg, log) | ||
if err != nil { | ||
return err | ||
} | ||
defer func() { | ||
pprof.ShutDown() | ||
prometheus.ShutDown() | ||
chain.Close() | ||
}() | ||
|
||
blocksCount := chain.BlockHeight() + 1 | ||
if start+count > blocksCount { | ||
return cli.Exit(fmt.Errorf("chain is not that high (%d) to dump %d blocks starting from %d", blocksCount-1, count, start), 1) | ||
} | ||
if count == 0 { | ||
count = blocksCount - start | ||
} | ||
|
||
out := ctx.String("out") | ||
if out == "" { | ||
return cli.Exit("output directory is not specified", 1) | ||
} | ||
if _, err = os.Stat(out); os.IsNotExist(err) { | ||
if err = os.MkdirAll(out, os.ModePerm); err != nil { | ||
return cli.Exit(fmt.Sprintf("failed to create directory %s: %s", out, err), 1) | ||
} | ||
} | ||
if err != nil { | ||
return cli.Exit(fmt.Sprintf("failed to check directory %s: %s", out, err), 1) | ||
} | ||
|
||
for i := start; i < start+count; i++ { | ||
blk, err := chain.GetBlock(chain.GetHeaderHash(i)) | ||
if err != nil { | ||
return cli.Exit(fmt.Sprintf("failed to get block %d: %s", i, err), 1) | ||
} | ||
filePath := filepath.Join(out, fmt.Sprintf("block-%d.bin", i)) | ||
if err = saveBlockToFile(blk, filePath); err != nil { | ||
return cli.Exit(fmt.Sprintf("failed to save block %d to file %s: %s", i, filePath, err), 1) | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
func saveBlockToFile(blk *block.Block, filePath string) error { | ||
file, err := os.Create(filePath) | ||
if err != nil { | ||
return err | ||
} | ||
defer file.Close() | ||
|
||
writer := io.NewBinWriterFromIO(file) | ||
blk.EncodeBinary(writer) | ||
return writer.Err | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
package server_test | ||
|
||
import ( | ||
"os" | ||
"path/filepath" | ||
"strconv" | ||
"testing" | ||
|
||
"github.com/nspcc-dev/neo-go/internal/testcli" | ||
"github.com/nspcc-dev/neo-go/pkg/config" | ||
"github.com/nspcc-dev/neo-go/pkg/core/storage/dbconfig" | ||
"github.com/stretchr/testify/require" | ||
"gopkg.in/yaml.v3" | ||
) | ||
|
||
func TestDumpBin(t *testing.T) { | ||
tmpDir := t.TempDir() | ||
|
||
loadConfig := func(t *testing.T) config.Config { | ||
chainPath := filepath.Join(tmpDir, "neogotestchain") | ||
cfg, err := config.LoadFile(filepath.Join("..", "..", "config", "protocol.unit_testnet.yml")) | ||
require.NoError(t, err, "could not load config") | ||
cfg.ApplicationConfiguration.DBConfiguration.Type = dbconfig.LevelDB | ||
cfg.ApplicationConfiguration.DBConfiguration.LevelDBOptions.DataDirectoryPath = chainPath | ||
return cfg | ||
} | ||
|
||
cfg := loadConfig(t) | ||
out, err := yaml.Marshal(cfg) | ||
require.NoError(t, err) | ||
|
||
cfgPath := filepath.Join(tmpDir, "protocol.unit_testnet.yml") | ||
require.NoError(t, os.WriteFile(cfgPath, out, os.ModePerm)) | ||
|
||
e := testcli.NewExecutor(t, false) | ||
|
||
restoreArgs := []string{"neo-go", "db", "restore", | ||
"--config-file", cfgPath, "--in", inDump} | ||
e.Run(t, restoreArgs...) | ||
|
||
t.Run("missing output directory", func(t *testing.T) { | ||
args := []string{"neo-go", "db", "dump-bin", | ||
"--config-file", cfgPath, "--out", ""} | ||
e.RunWithErrorCheck(t, "output directory is not specified", args...) | ||
}) | ||
|
||
t.Run("successful dump", func(t *testing.T) { | ||
outDir := filepath.Join(tmpDir, "blocks") | ||
args := []string{"neo-go", "db", "dump-bin", | ||
"--config-file", cfgPath, "--out", outDir, "--count", "5", "--start", "0"} | ||
|
||
e.Run(t, args...) | ||
|
||
require.DirExists(t, outDir) | ||
|
||
for i := range 5 { | ||
blockFile := filepath.Join(outDir, "block-"+strconv.Itoa(i)+".bin") | ||
require.FileExists(t, blockFile) | ||
} | ||
}) | ||
|
||
t.Run("invalid block range", func(t *testing.T) { | ||
outDir := filepath.Join(tmpDir, "invalid-blocks") | ||
args := []string{"neo-go", "db", "dump-bin", | ||
"--config-file", cfgPath, "--out", outDir, "--count", "1000", "--start", "0"} | ||
|
||
e.RunWithError(t, args...) | ||
}) | ||
|
||
t.Run("zero blocks (full chain dump)", func(t *testing.T) { | ||
outDir := filepath.Join(tmpDir, "full-dump") | ||
args := []string{"neo-go", "db", "dump-bin", | ||
"--config-file", cfgPath, "--out", outDir} | ||
|
||
e.Run(t, args...) | ||
|
||
require.DirExists(t, outDir) | ||
for i := range 5 { | ||
blockFile := filepath.Join(outDir, "block-"+strconv.Itoa(i)+".bin") | ||
require.FileExists(t, blockFile) | ||
} | ||
}) | ||
|
||
t.Run("invalid config file", func(t *testing.T) { | ||
outDir := filepath.Join(tmpDir, "blocks") | ||
args := []string{"neo-go", "db", "dump-bin", | ||
"--config-file", "invalid-config-path", "--out", outDir} | ||
|
||
e.RunWithError(t, args...) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
# NeoFS block storage | ||
|
||
Using NeoFS to store chain's blocks and snapshots was proposed in | ||
[#3463](https://github.com/neo-project/neo/issues/3463). NeoGo contains several | ||
extensions utilizing NeoFS block storage aimed to improve node synchronization | ||
efficiency and reduce node storage size. | ||
|
||
## Components and functionality | ||
|
||
### Block storage schema | ||
|
||
A single NeoFS container is used to store blocks and index files. Each block | ||
is stored in a binary form as a separate object with a unique OID and a set of | ||
attributes: | ||
- block object identifier with block index value (`block:1`) | ||
- primary node index (`primary:0`) | ||
- block hash in the LE form (`hash:5412a781caf278c0736556c0e544c7cfdbb6e3c62ae221ef53646be89364566b`) | ||
- previous block hash in the LE form (`prevHash:3654a054d82a8178c7dfacecc2c57282e23468a42ee407f14506368afe22d929`) | ||
- millisecond-precision block timestamp (`time:1627894840919`) | ||
|
||
Each index file is an object containing a constant-sized batch of raw block object | ||
IDs in binary form ordered by block index. Each index file is marked with the | ||
following attributes: | ||
- index file identifier with consecutive file index value (`oid:0`) | ||
- the number of OIDs included into index file (`size:128000`) | ||
|
||
### NeoFS BlockFetcher | ||
|
||
NeoFS BlockFetcher service is designed as an alternative to P2P synchronisation | ||
protocol. It allows to download blocks from a trusted container in the NeoFS network | ||
and persist them to database using standard verification flow. NeoFS BlockFetcher | ||
service primarily used during the node's bootstrap, providing a fast alternative to | ||
P2P blocks synchronisation. | ||
|
||
NeoFS BlockFetcher service has two modes of operation: | ||
- Index File Search: Search for index files, which contain batches of block object | ||
IDs and fetch blocks from NeoFS by retrieved OIDs. | ||
- Direct Block Search: Search and fetch blocks directly from NeoFS container via | ||
built-in NeoFS object search mechanism. | ||
|
||
Operation mode of BlockFetcher can be configured via `SkipIndexFilesSearch` | ||
parameter. | ||
|
||
#### Operation flow | ||
|
||
1. **OID Fetching**: | ||
Depending on the mode, the service either: | ||
- Searches for index files by index file attribute and reads block OIDs from index | ||
file object-by-object. | ||
- Searches batches of blocks directly by block attribute (the batch size is | ||
configured via `OIDBatchSize` parameter). | ||
|
||
Once the OIDs are retrieved, they are immediately redirected to the | ||
block downloading routines for further processing. The channel that | ||
is used to redirect block OIDs to downloading routines is buffered | ||
to provide smooth OIDs delivery without delays. The size of this channel | ||
can be configured via `OIDBatchSize` parameter and equals to `2*OIDBatchSize`. | ||
2. **Parallel Block Downloading**: | ||
The number of downloading routines can be configured via | ||
`DownloaderWorkersCount` parameter. It's up to the user to find the | ||
balance between the downloading speed and blocks persist speed for every | ||
node that uses NeoFS BlockFetcher. Downloaded blocks are placed into a | ||
buffered channel of size `IDBatchSize` with further redirection to the | ||
block queue. | ||
3. **Block Insertion**: | ||
Downloaded blocks are inserted into the blockchain using the same logic | ||
as in the P2P synchronisation protocol. The block queue is used to order | ||
downloaded blocks before they are inserted into the blockchain. The | ||
size of the queue can be configured via the `BQueueSize` parameter | ||
and should be larger than the `OIDBatchSize` parameter to avoid blocking | ||
the downloading routines. | ||
|
||
Once all blocks available in the NeoFS container are processed, the service | ||
shuts down automatically. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.