-
Notifications
You must be signed in to change notification settings - Fork 8.8k
/
persistence.go
239 lines (200 loc) · 7.08 KB
/
persistence.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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
/*
Copyright IBM Corp. All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package persistence
import (
"encoding/hex"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/hyperledger/fabric/common/chaincode"
"github.com/hyperledger/fabric/common/flogging"
"github.com/hyperledger/fabric/common/util"
"github.com/pkg/errors"
)
var logger = flogging.MustGetLogger("chaincode.persistence")
// IOReadWriter defines the interface needed for reading, writing, removing, and
// checking for existence of a specified file
type IOReadWriter interface {
ReadDir(string) ([]os.FileInfo, error)
ReadFile(string) ([]byte, error)
Remove(name string) error
Stat(string) (os.FileInfo, error)
WriteFile(string, []byte, os.FileMode) error
}
// FilesystemIO is the production implementation of the IOWriter interface
type FilesystemIO struct {
}
// WriteFile writes a file to the filesystem
func (f *FilesystemIO) WriteFile(filename string, data []byte, perm os.FileMode) error {
return ioutil.WriteFile(filename, data, perm)
}
// Stat checks for existence of the file on the filesystem
func (f *FilesystemIO) Stat(name string) (os.FileInfo, error) {
return os.Stat(name)
}
// Remove removes a file from the filesystem - used for rolling back an in-flight
// Save operation upon a failure
func (f *FilesystemIO) Remove(name string) error {
return os.Remove(name)
}
// ReadFile reads a file from the filesystem
func (f *FilesystemIO) ReadFile(filename string) ([]byte, error) {
return ioutil.ReadFile(filename)
}
// ReadDir reads a directory from the filesystem
func (f *FilesystemIO) ReadDir(dirname string) ([]os.FileInfo, error) {
return ioutil.ReadDir(dirname)
}
// Store holds the information needed for persisting a chaincode install package
type Store struct {
Path string
ReadWriter IOReadWriter
}
// Save persists chaincode install package bytes with the given name
// and version
func (s *Store) Save(name, version string, ccInstallPkg []byte) ([]byte, error) {
metadataJSON, err := toJSON(name, version)
if err != nil {
return nil, err
}
hash := util.ComputeSHA256(ccInstallPkg)
hashString := hex.EncodeToString(hash)
metadataPath := filepath.Join(s.Path, hashString+".json")
if _, err := s.ReadWriter.Stat(metadataPath); err == nil {
return nil, errors.Errorf("chaincode metadata already exists at %s", metadataPath)
}
ccInstallPkgPath := filepath.Join(s.Path, hashString+".bin")
if _, err := s.ReadWriter.Stat(ccInstallPkgPath); err == nil {
return nil, errors.Errorf("ChaincodeInstallPackage already exists at %s", ccInstallPkgPath)
}
if err := s.ReadWriter.WriteFile(metadataPath, metadataJSON, 0600); err != nil {
return nil, errors.Wrapf(err, "error writing metadata file to %s", metadataPath)
}
if err := s.ReadWriter.WriteFile(ccInstallPkgPath, ccInstallPkg, 0600); err != nil {
err = errors.Wrapf(err, "error writing chaincode install package to %s", ccInstallPkgPath)
logger.Error(err.Error())
// need to roll back metadata write above on error
if err2 := s.ReadWriter.Remove(metadataPath); err2 != nil {
logger.Errorf("error removing metadata file at %s: %s", metadataPath, err2)
}
return nil, err
}
return hash, nil
}
// Load loads a persisted chaincode install package bytes with the given hash
// and also returns the name and version
func (s *Store) Load(hash []byte) (ccInstallPkg []byte, name, version string, err error) {
hashString := hex.EncodeToString(hash)
ccInstallPkgPath := filepath.Join(s.Path, hashString+".bin")
ccInstallPkg, err = s.ReadWriter.ReadFile(ccInstallPkgPath)
if err != nil {
err = errors.Wrapf(err, "error reading chaincode install package at %s", ccInstallPkgPath)
return nil, "", "", err
}
metadataPath := filepath.Join(s.Path, hashString+".json")
name, version, err = s.LoadMetadata(metadataPath)
if err != nil {
return nil, "", "", err
}
return ccInstallPkg, name, version, nil
}
// LoadMetadata loads the chaincode metadata stored at the specified path
func (s *Store) LoadMetadata(path string) (name, version string, err error) {
metadataBytes, err := s.ReadWriter.ReadFile(path)
if err != nil {
err = errors.Wrapf(err, "error reading metadata at %s", path)
return "", "", err
}
ccMetadata := &ChaincodeMetadata{}
err = json.Unmarshal(metadataBytes, ccMetadata)
if err != nil {
err = errors.Wrapf(err, "error unmarshaling metadata at %s", path)
return "", "", err
}
return ccMetadata.Name, ccMetadata.Version, nil
}
// CodePackageNotFoundErr is the error returned when a code package cannot
// be found in the persistence store
type CodePackageNotFoundErr struct {
Name string
Version string
}
func (e *CodePackageNotFoundErr) Error() string {
return fmt.Sprintf("chaincode install package not found with name '%s', version '%s'", e.Name, e.Version)
}
// RetrieveHash retrieves the hash of a chaincode install package given the
// name and version of the chaincode
func (s *Store) RetrieveHash(name string, version string) ([]byte, error) {
installedChaincodes, err := s.ListInstalledChaincodes()
if err != nil {
return nil, errors.WithMessage(err, "error getting installed chaincodes")
}
for _, installedChaincode := range installedChaincodes {
if installedChaincode.Name == name && installedChaincode.Version == version {
return installedChaincode.Id, nil
}
}
err = &CodePackageNotFoundErr{
Name: name,
Version: version,
}
return nil, err
}
// ListInstalledChaincodes returns an array with information about the
// chaincodes installed in the persistence store
func (s *Store) ListInstalledChaincodes() ([]chaincode.InstalledChaincode, error) {
files, err := s.ReadWriter.ReadDir(s.Path)
if err != nil {
return nil, errors.Wrapf(err, "error reading chaincode directory at %s", s.Path)
}
installedChaincodes := []chaincode.InstalledChaincode{}
for _, file := range files {
if strings.HasSuffix(file.Name(), ".json") {
metadataPath := filepath.Join(s.Path, file.Name())
ccName, ccVersion, err := s.LoadMetadata(metadataPath)
if err != nil {
logger.Warning(err.Error())
continue
}
// split the file name and get just the hash
hashString := strings.Split(file.Name(), ".")[0]
hash, err := hex.DecodeString(hashString)
if err != nil {
return nil, errors.Wrapf(err, "error decoding hash from hex string: %s", hashString)
}
installedChaincode := chaincode.InstalledChaincode{
Name: ccName,
Version: ccVersion,
Id: hash,
}
installedChaincodes = append(installedChaincodes, installedChaincode)
}
}
return installedChaincodes, nil
}
// GetChaincodeInstallPath returns the path where chaincodes
// are installed
func (s *Store) GetChaincodeInstallPath() string {
return s.Path
}
// ChaincodeMetadata holds the name and version of a chaincode
type ChaincodeMetadata struct {
Name string `json:"Name"`
Version string `json:"Version"`
}
func toJSON(name, version string) ([]byte, error) {
metadata := &ChaincodeMetadata{
Name: name,
Version: version,
}
metadataBytes, err := json.Marshal(metadata)
if err != nil {
return nil, errors.Wrap(err, "error marshaling name and version into JSON")
}
return metadataBytes, nil
}