mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-12-23 03:41:34 +00:00
Merge pull request #440 from nspcc-dev/db-dump-restore
DB dump and restore, fixes #436.
This commit is contained in:
commit
f1750d117b
7 changed files with 238 additions and 57 deletions
10
cli/main.go
10
cli/main.go
|
@ -17,12 +17,10 @@ func main() {
|
|||
ctl.Version = config.Version
|
||||
ctl.Usage = "Official Go client for Neo"
|
||||
|
||||
ctl.Commands = []cli.Command{
|
||||
server.NewCommand(),
|
||||
smartcontract.NewCommand(),
|
||||
wallet.NewCommand(),
|
||||
vm.NewCommand(),
|
||||
}
|
||||
ctl.Commands = append(ctl.Commands, server.NewCommands()...)
|
||||
ctl.Commands = append(ctl.Commands, smartcontract.NewCommands()...)
|
||||
ctl.Commands = append(ctl.Commands, wallet.NewCommands()...)
|
||||
ctl.Commands = append(ctl.Commands, vm.NewCommands()...)
|
||||
|
||||
if err := ctl.Run(os.Args); err != nil {
|
||||
panic(err)
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/CityOfZion/neo-go/config"
|
||||
"github.com/CityOfZion/neo-go/pkg/core"
|
||||
"github.com/CityOfZion/neo-go/pkg/core/storage"
|
||||
"github.com/CityOfZion/neo-go/pkg/io"
|
||||
"github.com/CityOfZion/neo-go/pkg/network"
|
||||
"github.com/CityOfZion/neo-go/pkg/rpc"
|
||||
"github.com/pkg/errors"
|
||||
|
@ -16,18 +17,63 @@ import (
|
|||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
// NewCommand creates a new Node command.
|
||||
func NewCommand() cli.Command {
|
||||
return cli.Command{
|
||||
Name: "node",
|
||||
Usage: "start a NEO node",
|
||||
Action: startServer,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{Name: "config-path"},
|
||||
cli.BoolFlag{Name: "privnet, p"},
|
||||
cli.BoolFlag{Name: "mainnet, m"},
|
||||
cli.BoolFlag{Name: "testnet, t"},
|
||||
cli.BoolFlag{Name: "debug, d"},
|
||||
// NewCommands returns 'node' command.
|
||||
func NewCommands() []cli.Command {
|
||||
var cfgFlags = []cli.Flag{
|
||||
cli.StringFlag{Name: "config-path"},
|
||||
cli.BoolFlag{Name: "privnet, p"},
|
||||
cli.BoolFlag{Name: "mainnet, m"},
|
||||
cli.BoolFlag{Name: "testnet, t"},
|
||||
cli.BoolFlag{Name: "debug, d"},
|
||||
}
|
||||
var cfgWithCountFlags = make([]cli.Flag, len(cfgFlags))
|
||||
copy(cfgWithCountFlags, cfgFlags)
|
||||
cfgWithCountFlags = append(cfgWithCountFlags,
|
||||
cli.UintFlag{
|
||||
Name: "count, c",
|
||||
Usage: "number of blocks to be processed (default or 0: all chain)",
|
||||
},
|
||||
cli.UintFlag{
|
||||
Name: "skip, s",
|
||||
Usage: "number of blocks to skip (default: 0)",
|
||||
},
|
||||
)
|
||||
var cfgCountOutFlags = make([]cli.Flag, len(cfgWithCountFlags))
|
||||
copy(cfgCountOutFlags, cfgWithCountFlags)
|
||||
cfgCountOutFlags = append(cfgCountOutFlags, cli.StringFlag{
|
||||
Name: "out, o",
|
||||
Usage: "Output file (stdout if not given)",
|
||||
})
|
||||
var cfgCountInFlags = make([]cli.Flag, len(cfgWithCountFlags))
|
||||
copy(cfgCountInFlags, cfgWithCountFlags)
|
||||
cfgCountInFlags = append(cfgCountInFlags, cli.StringFlag{
|
||||
Name: "in, i",
|
||||
Usage: "Input file (stdin if not given)",
|
||||
})
|
||||
return []cli.Command{
|
||||
{
|
||||
Name: "node",
|
||||
Usage: "start a NEO node",
|
||||
Action: startServer,
|
||||
Flags: cfgFlags,
|
||||
},
|
||||
{
|
||||
Name: "db",
|
||||
Usage: "database manipulations",
|
||||
Subcommands: []cli.Command{
|
||||
{
|
||||
Name: "dump",
|
||||
Usage: "dump blocks (starting with block #1) to the file",
|
||||
Action: dumpDB,
|
||||
Flags: cfgCountOutFlags,
|
||||
},
|
||||
{
|
||||
Name: "restore",
|
||||
Usage: "restore blocks from the file",
|
||||
Action: restoreDB,
|
||||
Flags: cfgCountInFlags,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -43,26 +89,154 @@ func newGraceContext() context.Context {
|
|||
return ctx
|
||||
}
|
||||
|
||||
func startServer(ctx *cli.Context) error {
|
||||
net := config.ModePrivNet
|
||||
// getConfigFromContext looks at path and mode flags in the given config and
|
||||
// returns appropriate config.
|
||||
func getConfigFromContext(ctx *cli.Context) (config.Config, error) {
|
||||
var net = config.ModePrivNet
|
||||
if ctx.Bool("testnet") {
|
||||
net = config.ModeTestNet
|
||||
}
|
||||
if ctx.Bool("mainnet") {
|
||||
net = config.ModeMainNet
|
||||
}
|
||||
|
||||
grace, cancel := context.WithCancel(newGraceContext())
|
||||
defer cancel()
|
||||
|
||||
configPath := "./config"
|
||||
if argCp := ctx.String("config-path"); argCp != "" {
|
||||
configPath = argCp
|
||||
}
|
||||
cfg, err := config.Load(configPath, net)
|
||||
return config.Load(configPath, net)
|
||||
}
|
||||
|
||||
// handleLoggingParams enables debugging output is that's requested by the user.
|
||||
func handleLoggingParams(ctx *cli.Context) {
|
||||
if ctx.Bool("debug") {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
}
|
||||
}
|
||||
|
||||
func getCountAndSkipFromContext(ctx *cli.Context) (uint32, uint32) {
|
||||
count := uint32(ctx.Uint("count"))
|
||||
skip := uint32(ctx.Uint("skip"))
|
||||
return count, skip
|
||||
}
|
||||
|
||||
func dumpDB(ctx *cli.Context) error {
|
||||
cfg, err := getConfigFromContext(ctx)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
handleLoggingParams(ctx)
|
||||
count, skip := getCountAndSkipFromContext(ctx)
|
||||
|
||||
var outStream = os.Stdout
|
||||
if out := ctx.String("out"); out != "" {
|
||||
outStream, err = os.Create(out)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
}
|
||||
defer outStream.Close()
|
||||
writer := io.NewBinWriterFromIO(outStream)
|
||||
|
||||
grace, cancel := context.WithCancel(newGraceContext())
|
||||
defer cancel()
|
||||
|
||||
chain, err := initBlockChain(cfg)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
go chain.Run(grace)
|
||||
|
||||
chainHeight := chain.BlockHeight()
|
||||
if skip+count > chainHeight {
|
||||
return cli.NewExitError(fmt.Errorf("chain is not that high (%d) to dump %d blocks starting from %d", chainHeight, count, skip), 1)
|
||||
}
|
||||
if count == 0 {
|
||||
count = chainHeight - skip
|
||||
}
|
||||
writer.WriteLE(count)
|
||||
for i := skip + 1; i <= count; i++ {
|
||||
bh := chain.GetHeaderHash(int(i))
|
||||
b, err := chain.GetBlock(bh)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("failed to get block %d: %s", i, err), 1)
|
||||
}
|
||||
b.EncodeBinary(writer)
|
||||
if writer.Err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func restoreDB(ctx *cli.Context) error {
|
||||
cfg, err := getConfigFromContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
handleLoggingParams(ctx)
|
||||
count, skip := getCountAndSkipFromContext(ctx)
|
||||
|
||||
var inStream = os.Stdin
|
||||
if in := ctx.String("in"); in != "" {
|
||||
inStream, err = os.Open(in)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
}
|
||||
defer inStream.Close()
|
||||
reader := io.NewBinReaderFromIO(inStream)
|
||||
|
||||
grace, cancel := context.WithCancel(newGraceContext())
|
||||
defer cancel()
|
||||
|
||||
chain, err := initBlockChain(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
go chain.Run(grace)
|
||||
|
||||
var allBlocks uint32
|
||||
reader.ReadLE(&allBlocks)
|
||||
if reader.Err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
if skip+count > allBlocks {
|
||||
return cli.NewExitError(fmt.Errorf("input file has only %d blocks, can't read %d starting from %d", allBlocks, count, skip), 1)
|
||||
}
|
||||
if count == 0 {
|
||||
count = allBlocks
|
||||
}
|
||||
i := uint32(0)
|
||||
for ; i < skip; i++ {
|
||||
b := &core.Block{}
|
||||
b.DecodeBinary(reader)
|
||||
if reader.Err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
}
|
||||
for ; i < count; i++ {
|
||||
b := &core.Block{}
|
||||
b.DecodeBinary(reader)
|
||||
if reader.Err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
err := chain.AddBlock(b)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("failed to add block %d: %s", i, err), 1)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func startServer(ctx *cli.Context) error {
|
||||
cfg, err := getConfigFromContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
handleLoggingParams(ctx)
|
||||
|
||||
grace, cancel := context.WithCancel(newGraceContext())
|
||||
defer cancel()
|
||||
|
||||
serverConfig := network.NewServerConfig(cfg)
|
||||
|
||||
|
@ -71,10 +245,6 @@ func startServer(ctx *cli.Context) error {
|
|||
return err
|
||||
}
|
||||
|
||||
if ctx.Bool("debug") {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
}
|
||||
|
||||
server := network.NewServer(serverConfig, chain)
|
||||
rpcServer := rpc.NewServer(chain, cfg.ApplicationConfiguration.RPCPort, server)
|
||||
errChan := make(chan error)
|
||||
|
|
|
@ -35,9 +35,9 @@ func Main(op string, args []interface{}) {
|
|||
}`
|
||||
)
|
||||
|
||||
// NewCommand returns a new contract command.
|
||||
func NewCommand() cli.Command {
|
||||
return cli.Command{
|
||||
// NewCommands returns 'contract' command.
|
||||
func NewCommands() []cli.Command {
|
||||
return []cli.Command{{
|
||||
Name: "contract",
|
||||
Usage: "compile - debug - deploy smart contracts",
|
||||
Subcommands: []cli.Command{
|
||||
|
@ -98,7 +98,7 @@ func NewCommand() cli.Command {
|
|||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}}
|
||||
}
|
||||
|
||||
// initSmartContract initializes a given directory with some boiler plate code.
|
||||
|
|
|
@ -9,9 +9,9 @@ import (
|
|||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
// NewCommand creates a new VM command.
|
||||
func NewCommand() cli.Command {
|
||||
return cli.Command{
|
||||
// NewCommands returns 'vm' command.
|
||||
func NewCommands() []cli.Command {
|
||||
return []cli.Command{{
|
||||
Name: "vm",
|
||||
Usage: "start the virtual machine",
|
||||
Action: startVMPrompt,
|
||||
|
@ -31,7 +31,7 @@ func NewCommand() cli.Command {
|
|||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}}
|
||||
}
|
||||
|
||||
func startVMPrompt(ctx *cli.Context) error {
|
||||
|
|
|
@ -16,9 +16,9 @@ var (
|
|||
errPhraseMismatch = errors.New("the entered pass-phrases do not match. Maybe you have misspelled them")
|
||||
)
|
||||
|
||||
// NewCommand creates a new Wallet command.
|
||||
func NewCommand() cli.Command {
|
||||
return cli.Command{
|
||||
// NewCommands returns 'wallet' command.
|
||||
func NewCommands() []cli.Command {
|
||||
return []cli.Command{{
|
||||
Name: "wallet",
|
||||
Usage: "create, open and manage a NEO wallet",
|
||||
Subcommands: []cli.Command{
|
||||
|
@ -49,7 +49,7 @@ func NewCommand() cli.Command {
|
|||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}}
|
||||
}
|
||||
|
||||
func openWallet(ctx *cli.Context) error {
|
||||
|
|
|
@ -137,7 +137,16 @@ func (bc *Blockchain) init() error {
|
|||
// that with stored blocks.
|
||||
if currHeaderHeight > bc.storedHeaderCount {
|
||||
hash := currHeaderHash
|
||||
targetHash := bc.headerList.Get(bc.headerList.Len() - 1)
|
||||
var targetHash util.Uint256
|
||||
if bc.headerList.Len() > 0 {
|
||||
targetHash = bc.headerList.Get(bc.headerList.Len() - 1)
|
||||
} else {
|
||||
genesisBlock, err := createGenesisBlock(bc.config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
targetHash = genesisBlock.Hash()
|
||||
}
|
||||
headers := make([]*Header, 0)
|
||||
|
||||
for hash != targetHash {
|
||||
|
@ -166,7 +175,7 @@ func (bc *Blockchain) Run(ctx context.Context) {
|
|||
persistTimer := time.NewTimer(persistInterval)
|
||||
defer func() {
|
||||
persistTimer.Stop()
|
||||
if err := bc.persist(ctx); err != nil {
|
||||
if err := bc.persist(); err != nil {
|
||||
log.Warnf("failed to persist: %s", err)
|
||||
}
|
||||
if err := bc.store.Close(); err != nil {
|
||||
|
@ -182,7 +191,7 @@ func (bc *Blockchain) Run(ctx context.Context) {
|
|||
bc.headersOpDone <- struct{}{}
|
||||
case <-persistTimer.C:
|
||||
go func() {
|
||||
err := bc.persist(ctx)
|
||||
err := bc.persist()
|
||||
if err != nil {
|
||||
log.Warnf("failed to persist blockchain: %s", err)
|
||||
}
|
||||
|
@ -461,7 +470,7 @@ func (bc *Blockchain) storeBlock(block *Block) error {
|
|||
}
|
||||
|
||||
// persist flushes current in-memory store contents to the persistent storage.
|
||||
func (bc *Blockchain) persist(ctx context.Context) error {
|
||||
func (bc *Blockchain) persist() error {
|
||||
var (
|
||||
start = time.Now()
|
||||
persisted int
|
||||
|
@ -479,13 +488,16 @@ func (bc *Blockchain) persist(ctx context.Context) error {
|
|||
oldHeight := atomic.SwapUint32(&bc.persistedHeight, bHeight)
|
||||
diff := bHeight - oldHeight
|
||||
|
||||
storedHeaderHeight, _, err := storage.CurrentHeaderHeight(bc.store)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if persisted > 0 {
|
||||
log.WithFields(log.Fields{
|
||||
"persistedBlocks": diff,
|
||||
"persistedKeys": persisted,
|
||||
"headerHeight": bc.HeaderHeight(),
|
||||
"blockHeight": bc.BlockHeight(),
|
||||
"persistedHeight": atomic.LoadUint32(&bc.persistedHeight),
|
||||
"headerHeight": storedHeaderHeight,
|
||||
"blockHeight": bHeight,
|
||||
"took": time.Since(start),
|
||||
}).Info("blockchain persist completed")
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/core/storage"
|
||||
|
@ -53,7 +52,7 @@ func TestAddBlock(t *testing.T) {
|
|||
assert.Equal(t, lastBlock.Hash(), bc.CurrentHeaderHash())
|
||||
|
||||
// This one tests persisting blocks, so it does need to persist()
|
||||
require.NoError(t, bc.persist(context.Background()))
|
||||
require.NoError(t, bc.persist())
|
||||
|
||||
for _, block := range blocks {
|
||||
key := storage.AppendPrefix(storage.DataBlock, block.Hash().BytesReverse())
|
||||
|
@ -82,7 +81,7 @@ func TestGetHeader(t *testing.T) {
|
|||
b2 := newBlock(2)
|
||||
_, err = bc.GetHeader(b2.Hash())
|
||||
assert.Error(t, err)
|
||||
assert.NoError(t, bc.persist(context.Background()))
|
||||
assert.NoError(t, bc.persist())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -106,7 +105,7 @@ func TestGetBlock(t *testing.T) {
|
|||
assert.Equal(t, blocks[i].Index, block.Index)
|
||||
assert.Equal(t, blocks[i].Hash(), block.Hash())
|
||||
}
|
||||
assert.NoError(t, bc.persist(context.Background()))
|
||||
assert.NoError(t, bc.persist())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -127,7 +126,7 @@ func TestHasBlock(t *testing.T) {
|
|||
}
|
||||
newBlock := newBlock(51)
|
||||
assert.False(t, bc.HasBlock(newBlock.Hash()))
|
||||
assert.NoError(t, bc.persist(context.Background()))
|
||||
assert.NoError(t, bc.persist())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -135,10 +134,12 @@ func TestGetTransaction(t *testing.T) {
|
|||
b1 := getDecodedBlock(t, 1)
|
||||
block := getDecodedBlock(t, 2)
|
||||
bc := newTestChain(t)
|
||||
// Turn verification off, because these blocks are really from some other chain
|
||||
// and can't be verified, but we don't care about that in this test.
|
||||
bc.config.VerifyBlocks = false
|
||||
|
||||
// These are from some kind of different chain, so can't be added via AddBlock().
|
||||
assert.Nil(t, bc.storeBlock(b1))
|
||||
assert.Nil(t, bc.storeBlock(block))
|
||||
assert.Nil(t, bc.AddBlock(b1))
|
||||
assert.Nil(t, bc.AddBlock(block))
|
||||
|
||||
// Test unpersisted and persisted access
|
||||
for j := 0; j < 2; j++ {
|
||||
|
@ -151,6 +152,6 @@ func TestGetTransaction(t *testing.T) {
|
|||
assert.Equal(t, 1, io.GetVarSize(tx.Inputs))
|
||||
assert.Equal(t, 1, io.GetVarSize(tx.Outputs))
|
||||
assert.Equal(t, 1, io.GetVarSize(tx.Scripts))
|
||||
assert.NoError(t, bc.persist(context.Background()))
|
||||
assert.NoError(t, bc.persist())
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue