db.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. // Copyright 2017 The go-ethereum Authors
  2. // This file is part of go-ethereum.
  3. //
  4. // go-ethereum is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // go-ethereum is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU General Public License
  15. // along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
  16. package main
  17. import (
  18. "archive/tar"
  19. "bytes"
  20. "encoding/binary"
  21. "encoding/hex"
  22. "fmt"
  23. "io"
  24. "os"
  25. "path/filepath"
  26. "github.com/ethereum/go-ethereum/cmd/utils"
  27. "github.com/ethereum/go-ethereum/common"
  28. "github.com/ethereum/go-ethereum/log"
  29. "github.com/ethereum/go-ethereum/rlp"
  30. "github.com/ethereum/go-ethereum/swarm/chunk"
  31. "github.com/ethereum/go-ethereum/swarm/storage/localstore"
  32. "github.com/syndtr/goleveldb/leveldb"
  33. "github.com/syndtr/goleveldb/leveldb/opt"
  34. "gopkg.in/urfave/cli.v1"
  35. )
  36. var legacyKeyIndex = byte(0)
  37. var keyData = byte(6)
  38. type dpaDBIndex struct {
  39. Idx uint64
  40. Access uint64
  41. }
  42. var dbCommand = cli.Command{
  43. Name: "db",
  44. CustomHelpTemplate: helpTemplate,
  45. Usage: "manage the local chunk database",
  46. ArgsUsage: "db COMMAND",
  47. Description: "Manage the local chunk database",
  48. Subcommands: []cli.Command{
  49. {
  50. Action: dbExport,
  51. CustomHelpTemplate: helpTemplate,
  52. Name: "export",
  53. Usage: "export a local chunk database as a tar archive (use - to send to stdout)",
  54. ArgsUsage: "<chunkdb> <file>",
  55. Description: `
  56. Export a local chunk database as a tar archive (use - to send to stdout).
  57. swarm db export ~/.ethereum/swarm/bzz-KEY/chunks chunks.tar
  58. The export may be quite large, consider piping the output through the Unix
  59. pv(1) tool to get a progress bar:
  60. swarm db export ~/.ethereum/swarm/bzz-KEY/chunks - | pv > chunks.tar
  61. `,
  62. },
  63. {
  64. Action: dbImport,
  65. CustomHelpTemplate: helpTemplate,
  66. Name: "import",
  67. Usage: "import chunks from a tar archive into a local chunk database (use - to read from stdin)",
  68. ArgsUsage: "<chunkdb> <file>",
  69. Description: `Import chunks from a tar archive into a local chunk database (use - to read from stdin).
  70. swarm db import ~/.ethereum/swarm/bzz-KEY/chunks chunks.tar
  71. The import may be quite large, consider piping the input through the Unix
  72. pv(1) tool to get a progress bar:
  73. pv chunks.tar | swarm db import ~/.ethereum/swarm/bzz-KEY/chunks -`,
  74. Flags: []cli.Flag{
  75. SwarmLegacyFlag,
  76. },
  77. },
  78. },
  79. }
  80. func dbExport(ctx *cli.Context) {
  81. args := ctx.Args()
  82. if len(args) != 3 {
  83. utils.Fatalf("invalid arguments, please specify both <chunkdb> (path to a local chunk database), <file> (path to write the tar archive to, - for stdout) and the base key")
  84. }
  85. var out io.Writer
  86. if args[1] == "-" {
  87. out = os.Stdout
  88. } else {
  89. f, err := os.Create(args[1])
  90. if err != nil {
  91. utils.Fatalf("error opening output file: %s", err)
  92. }
  93. defer f.Close()
  94. out = f
  95. }
  96. isLegacy := localstore.IsLegacyDatabase(args[0])
  97. if isLegacy {
  98. count, err := exportLegacy(args[0], common.Hex2Bytes(args[2]), out)
  99. if err != nil {
  100. utils.Fatalf("error exporting legacy local chunk database: %s", err)
  101. }
  102. log.Info(fmt.Sprintf("successfully exported %d chunks from legacy db", count))
  103. return
  104. }
  105. store, err := openLDBStore(args[0], common.Hex2Bytes(args[2]))
  106. if err != nil {
  107. utils.Fatalf("error opening local chunk database: %s", err)
  108. }
  109. defer store.Close()
  110. count, err := store.Export(out)
  111. if err != nil {
  112. utils.Fatalf("error exporting local chunk database: %s", err)
  113. }
  114. log.Info(fmt.Sprintf("successfully exported %d chunks", count))
  115. }
  116. func dbImport(ctx *cli.Context) {
  117. args := ctx.Args()
  118. if len(args) != 3 {
  119. utils.Fatalf("invalid arguments, please specify both <chunkdb> (path to a local chunk database), <file> (path to read the tar archive from, - for stdin) and the base key")
  120. }
  121. legacy := ctx.IsSet(SwarmLegacyFlag.Name)
  122. store, err := openLDBStore(args[0], common.Hex2Bytes(args[2]))
  123. if err != nil {
  124. utils.Fatalf("error opening local chunk database: %s", err)
  125. }
  126. defer store.Close()
  127. var in io.Reader
  128. if args[1] == "-" {
  129. in = os.Stdin
  130. } else {
  131. f, err := os.Open(args[1])
  132. if err != nil {
  133. utils.Fatalf("error opening input file: %s", err)
  134. }
  135. defer f.Close()
  136. in = f
  137. }
  138. count, err := store.Import(in, legacy)
  139. if err != nil {
  140. utils.Fatalf("error importing local chunk database: %s", err)
  141. }
  142. log.Info(fmt.Sprintf("successfully imported %d chunks", count))
  143. }
  144. func openLDBStore(path string, basekey []byte) (*localstore.DB, error) {
  145. if _, err := os.Stat(filepath.Join(path, "CURRENT")); err != nil {
  146. return nil, fmt.Errorf("invalid chunkdb path: %s", err)
  147. }
  148. return localstore.New(path, basekey, nil)
  149. }
  150. func decodeIndex(data []byte, index *dpaDBIndex) error {
  151. dec := rlp.NewStream(bytes.NewReader(data), 0)
  152. return dec.Decode(index)
  153. }
  154. func getDataKey(idx uint64, po uint8) []byte {
  155. key := make([]byte, 10)
  156. key[0] = keyData
  157. key[1] = po
  158. binary.BigEndian.PutUint64(key[2:], idx)
  159. return key
  160. }
  161. func exportLegacy(path string, basekey []byte, out io.Writer) (int64, error) {
  162. tw := tar.NewWriter(out)
  163. defer tw.Close()
  164. db, err := leveldb.OpenFile(path, &opt.Options{OpenFilesCacheCapacity: 128})
  165. if err != nil {
  166. return 0, err
  167. }
  168. defer db.Close()
  169. it := db.NewIterator(nil, nil)
  170. defer it.Release()
  171. var count int64
  172. for ok := it.Seek([]byte{legacyKeyIndex}); ok; ok = it.Next() {
  173. key := it.Key()
  174. if (key == nil) || (key[0] != legacyKeyIndex) {
  175. break
  176. }
  177. var index dpaDBIndex
  178. hash := key[1:]
  179. decodeIndex(it.Value(), &index)
  180. po := uint8(chunk.Proximity(basekey, hash))
  181. datakey := getDataKey(index.Idx, po)
  182. data, err := db.Get(datakey, nil)
  183. if err != nil {
  184. log.Crit(fmt.Sprintf("Chunk %x found but could not be accessed: %v, %x", key, err, datakey))
  185. continue
  186. }
  187. hdr := &tar.Header{
  188. Name: hex.EncodeToString(hash),
  189. Mode: 0644,
  190. Size: int64(len(data)),
  191. }
  192. if err := tw.WriteHeader(hdr); err != nil {
  193. return count, err
  194. }
  195. if _, err := tw.Write(data); err != nil {
  196. return count, err
  197. }
  198. count++
  199. }
  200. return count, nil
  201. }