-
Notifications
You must be signed in to change notification settings - Fork 71
/
disk.go
135 lines (110 loc) · 3.49 KB
/
disk.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
// Package disk implements the ability to read and write blocks to disk
// writing each block to a separate block numbered file.
package disk
import (
"encoding/json"
"errors"
"fmt"
"io/fs"
"os"
"path"
"strconv"
"github.com/ardanlabs/blockchain/foundation/blockchain/database"
)
// Disk represents the serialization implementation for reading and storing
// blocks in their own separate files on disk. This implements the database.Storage
// interface.
type Disk struct {
dbPath string
}
// New constructs an Disk value for use.
func New(dbPath string) (*Disk, error) {
if err := os.MkdirAll(dbPath, 0755); err != nil {
return nil, err
}
return &Disk{dbPath: dbPath}, nil
}
// Close in this implementation has nothing to do since a new file is
// written to disk for each now block and then immediately closed.
func (d *Disk) Close() error {
return nil
}
// Write takes the specified database blocks and stores it on disk in a
// file labeled with the block number.
func (d *Disk) Write(blockData database.BlockData) error {
// Marshal the block for writing to disk in a more human readable format.
data, err := json.MarshalIndent(blockData, "", " ")
if err != nil {
return err
}
// Create a new file for this block and name it based on the block number.
f, err := os.OpenFile(d.getPath(blockData.Header.Number), os.O_CREATE|os.O_RDWR, 0600)
if err != nil {
return err
}
defer f.Close()
// Write the new block to disk.
if _, err := f.Write(data); err != nil {
return err
}
return nil
}
// GetBlock searches the blockchain on disk to locate and return the
// contents of the specified block by number.
func (d *Disk) GetBlock(num uint64) (database.BlockData, error) {
// Open the block file for the specified number.
f, err := os.OpenFile(d.getPath(num), os.O_RDONLY, 0600)
if err != nil {
return database.BlockData{}, err
}
defer f.Close()
// Decode the contents of the block.
var blockData database.BlockData
if err := json.NewDecoder(f).Decode(&blockData); err != nil {
return database.BlockData{}, err
}
// Return the block as a database block.
return blockData, nil
}
// ForEach returns an iterator to walk through all the blocks
// starting with block number 1.
func (d *Disk) ForEach() database.Iterator {
return &diskIterator{storage: d}
}
// Reset will clear out the blockchain on disk.
func (d *Disk) Reset() error {
if err := os.RemoveAll(d.dbPath); err != nil {
return err
}
return os.MkdirAll(d.dbPath, 0755)
}
// getPath forms the path to the specified block.
func (d *Disk) getPath(blockNum uint64) string {
name := strconv.FormatUint(blockNum, 10)
return path.Join(d.dbPath, fmt.Sprintf("%s.json", name))
}
// =============================================================================
// diskIterator represents the iteration implementation for walking
// through and reading blocks on disk. This implements the database
// Iterator interface.
type diskIterator struct {
storage *Disk // Access to the storage API.
current uint64 // Currenet block number being iterated over.
eoc bool // Represents the iterator is at the end of the chain.
}
// Next retrieves the next block from disk.
func (di *diskIterator) Next() (database.BlockData, error) {
if di.eoc {
return database.BlockData{}, errors.New("end of chain")
}
di.current++
blockData, err := di.storage.GetBlock(di.current)
if errors.Is(err, fs.ErrNotExist) {
di.eoc = true
}
return blockData, err
}
// Done returns the end of chain value.
func (di *diskIterator) Done() bool {
return di.eoc
}