forked from hyperledger-archives/fabric
/
hashfromcode.go
267 lines (229 loc) · 6.69 KB
/
hashfromcode.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
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
package container
import (
"archive/tar"
"bytes"
"encoding/hex"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"strings"
"time"
"errors"
"github.com/spf13/viper"
"github.com/openblockchain/obc-peer/openchain/util"
pb "github.com/openblockchain/obc-peer/protos"
)
func addFile(tw *tar.Writer, path string, info os.FileInfo, fbytes []byte) error {
h, err := tar.FileInfoHeader(info, path)
if err != nil {
return fmt.Errorf("Error getting FileInfoHeader: %s", err)
}
//Let's take the variance out of the tar, make headers identical by using zero time
var zeroTime time.Time
h.AccessTime = zeroTime
h.ModTime = zeroTime
h.ChangeTime = zeroTime
h.Name = path
if err = tw.WriteHeader(h); err != nil {
return fmt.Errorf("Error writing header: %s", err)
}
rdr := bytes.NewReader(fbytes)
if _, err := io.Copy(tw, rdr); err != nil {
return fmt.Errorf("Error copying file : %s", err)
}
return nil
}
//hashFilesInDir computes h=hash(h,file bytes) for each file in a directory
//Directory entries are traversed recursively. In the end a single
//hash value is returned for the entire directory structure
func hashFilesInDir(rootDir string, dir string, hash []byte, tw *tar.Writer) ([]byte, error) {
//ReadDir returns sorted list of files in dir
fis, err := ioutil.ReadDir(rootDir + "/" + dir)
if err != nil {
return hash, fmt.Errorf("ReadDir failed %s\n", err)
}
for _, fi := range fis {
name := fmt.Sprintf("%s/%s", dir, fi.Name())
if fi.IsDir() {
var err error
hash, err = hashFilesInDir(rootDir, name, hash, tw)
if err != nil {
return hash, err
}
continue
}
buf, err := ioutil.ReadFile(rootDir + "/" + name)
if err != nil {
fmt.Printf("Error reading %s\n", err)
return hash, err
}
newSlice := make([]byte, len(hash)+len(buf))
copy(newSlice[len(buf):], hash[:])
//hash = md5.Sum(newSlice)
hash = util.ComputeCryptoHash(newSlice)
if tw != nil {
if err = addFile(tw, "src/"+name, fi, buf); err != nil {
return hash, fmt.Errorf("Error adding file to tar %s", err)
}
}
}
return hash, nil
}
func isCodeExist(tmppath string) error {
file, err := os.Open(tmppath)
if err != nil {
return fmt.Errorf("Download failer %s", err)
}
fi, err := file.Stat()
if err != nil {
return fmt.Errorf("could not stat file %s", err)
}
if !fi.IsDir() {
return fmt.Errorf("file %s is not dir\n", file.Name())
}
return nil
}
func getCodeFromHTTP(path string) (codegopath string, err error) {
codegopath = ""
err = nil
env := os.Environ()
var newgopath string
var origgopath string
var gopathenvIndex int
for i, v := range env {
if strings.Index(v, "GOPATH=") == 0 {
p := strings.SplitAfter(v, "GOPATH=")
origgopath = p[1]
newgopath = origgopath + "/_usercode_"
gopathenvIndex = i
break
}
}
if newgopath == "" {
err = fmt.Errorf("GOPATH not defined")
return
}
//ignore errors.. _usercode_ might exist. TempDir will catch any other errors
os.Mkdir(newgopath, 0755)
if codegopath, err = ioutil.TempDir(newgopath, ""); err != nil {
err = fmt.Errorf("could not create tmp dir under %s(%s)", newgopath, err)
return
}
env[gopathenvIndex] = "GOPATH=" + codegopath
// Use a 'go get' command to pull the chaincode from the given repo
cmd := exec.Command("go", "get", path)
cmd.Env = env
var out bytes.Buffer
cmd.Stdout = &out
err = cmd.Start()
// Create a go routine that will wait for the command to finish
done := make(chan error, 1)
go func() {
done <- cmd.Wait()
}()
select {
case <-time.After(time.Duration(viper.GetInt("chaincode.deploytimeout")) * time.Millisecond):
// If pulling repos takes too long, we should give up
// (This can happen if a repo is private and the git clone asks for credentials)
if err = cmd.Process.Kill(); err != nil {
err = fmt.Errorf("failed to kill: %s", err)
} else {
err = errors.New("Getting chaincode took too long")
}
case err = <-done:
// If we're here, the 'go get' command must have finished
if err != nil {
err = fmt.Errorf("process done with error = %v", err)
}
}
return
}
func getCodeFromFS(path string) (codegopath string, err error) {
env := os.Environ()
var gopath string
for _, v := range env {
if strings.Index(v, "GOPATH=") == 0 {
p := strings.SplitAfter(v, "GOPATH=")
gopath = p[1]
break
}
}
if gopath == "" {
return
}
codegopath = gopath
return
}
//name could be ChaincodeID.Name or ChaincodeID.Path
func generateHashFromSignature(path string, ctor string, args []string) []byte {
fargs := ctor
if args != nil {
for _, str := range args {
fargs = fargs + str
}
}
cbytes := []byte(path + fargs)
b := make([]byte, len(cbytes))
copy(b, cbytes)
hash := util.ComputeCryptoHash(b)
return hash
}
//generateHashcode gets hashcode of the code under path. If path is a HTTP(s) url
//it downloads the code first to compute the hash.
//NOTE: for dev mode, user builds and runs chaincode manually. The name provided
//by the user is equivalent to the path. This method will treat the name
//as codebytes and compute the hash from it. ie, user cannot run the chaincode
//with the same (name, ctor, args)
func generateHashcode(spec *pb.ChaincodeSpec, tw *tar.Writer) (string, error) {
if spec == nil {
return "", fmt.Errorf("Cannot generate hashcode from nil spec")
}
chaincodeID := spec.ChaincodeID
if chaincodeID == nil || chaincodeID.Path == "" {
return "", fmt.Errorf("Cannot generate hashcode from empty chaincode path")
}
ctor := spec.CtorMsg
if ctor == nil || ctor.Function == "" {
return "", fmt.Errorf("Cannot generate hashcode from empty ctor")
}
//code root will point to the directory where the code exists
//in the case of http it will be a temporary dir that
//will have to be deleted
var codegopath string
var ishttp bool
defer func() {
if ishttp && codegopath != "" {
os.RemoveAll(codegopath)
}
}()
path := chaincodeID.Path
var err error
var actualcodepath string
if strings.HasPrefix(path, "http://") {
ishttp = true
actualcodepath = path[7:]
codegopath, err = getCodeFromHTTP(actualcodepath)
} else if strings.HasPrefix(path, "https://") {
ishttp = true
actualcodepath = path[8:]
codegopath, err = getCodeFromHTTP(actualcodepath)
} else {
actualcodepath = path
codegopath, err = getCodeFromFS(path)
}
if err != nil {
return "", fmt.Errorf("Error getting code %s", err)
}
tmppath := codegopath + "/src/" + actualcodepath
if err = isCodeExist(tmppath); err != nil {
return "", fmt.Errorf("code does not exist %s", err)
}
hash := generateHashFromSignature(actualcodepath, ctor.Function, ctor.Args)
hash, err = hashFilesInDir(codegopath+"/src/", actualcodepath, hash, tw)
if err != nil {
return "", fmt.Errorf("Could not get hashcode for %s - %s\n", path, err)
}
return hex.EncodeToString(hash[:]), nil
}