OSDN Git Service

Dev (#148)
[bytom/bytom.git] / protocol / bc / entry.go
1 package bc
2
3 import (
4         "fmt"
5         "io"
6         "reflect"
7
8         "github.com/golang/protobuf/proto"
9
10         "github.com/bytom/crypto/sha3pool"
11         "github.com/bytom/encoding/blockchain"
12         "github.com/bytom/errors"
13 )
14
15 // Entry is the interface implemented by each addressable unit in a
16 // blockchain: transaction components such as spends, issuances,
17 // outputs, and retirements (among others), plus blockheaders.
18 type Entry interface {
19         proto.Message
20
21         // type produces a short human-readable string uniquely identifying
22         // the type of this entry.
23         typ() string
24
25         // writeForHash writes the entry's body for hashing.
26         writeForHash(w io.Writer)
27 }
28
29 var errInvalidValue = errors.New("invalid value")
30
31 // EntryID computes the identifier of an entry, as the hash of its
32 // body plus some metadata.
33 func EntryID(e Entry) (hash Hash) {
34         if e == nil {
35                 return hash
36         }
37
38         // Nil pointer; not the same as nil interface above. (See
39         // https://golang.org/doc/faq#nil_error.)
40         if v := reflect.ValueOf(e); v.Kind() == reflect.Ptr && v.IsNil() {
41                 return hash
42         }
43
44         hasher := sha3pool.Get256()
45         defer sha3pool.Put256(hasher)
46
47         hasher.Write([]byte("entryid:"))
48         hasher.Write([]byte(e.typ()))
49         hasher.Write([]byte{':'})
50
51         bh := sha3pool.Get256()
52         defer sha3pool.Put256(bh)
53
54         e.writeForHash(bh)
55
56         var innerHash [32]byte
57         bh.Read(innerHash[:])
58
59         hasher.Write(innerHash[:])
60
61         hash.ReadFrom(hasher)
62         return hash
63 }
64
65 var byte32zero [32]byte
66
67 // mustWriteForHash serializes the object c to the writer w, from which
68 // presumably a hash can be extracted.
69 //
70 // This function may panic with an error from the underlying writer,
71 // and may produce errors of its own if passed objects whose
72 // hash-serialization formats are not specified. It MUST NOT produce
73 // errors in other cases.
74 func mustWriteForHash(w io.Writer, c interface{}) {
75         if err := writeForHash(w, c); err != nil {
76                 panic(err)
77         }
78 }
79
80 func writeForHash(w io.Writer, c interface{}) error {
81         switch v := c.(type) {
82         case byte:
83                 _, err := w.Write([]byte{v})
84                 return errors.Wrap(err, "writing byte for hash")
85         case uint64:
86                 _, err := blockchain.WriteVarint63(w, v)
87                 return errors.Wrapf(err, "writing uint64 (%d) for hash", v)
88         case []byte:
89                 _, err := blockchain.WriteVarstr31(w, v)
90                 return errors.Wrapf(err, "writing []byte (len %d) for hash", len(v))
91         case [][]byte:
92                 _, err := blockchain.WriteVarstrList(w, v)
93                 return errors.Wrapf(err, "writing [][]byte (len %d) for hash", len(v))
94         case string:
95                 _, err := blockchain.WriteVarstr31(w, []byte(v))
96                 return errors.Wrapf(err, "writing string (len %d) for hash", len(v))
97         case *Hash:
98                 if v == nil {
99                         _, err := w.Write(byte32zero[:])
100                         return errors.Wrap(err, "writing nil *Hash for hash")
101                 }
102                 _, err := w.Write(v.Bytes())
103                 return errors.Wrap(err, "writing *Hash for hash")
104         case *AssetID:
105                 if v == nil {
106                         _, err := w.Write(byte32zero[:])
107                         return errors.Wrap(err, "writing nil *AssetID for hash")
108                 }
109                 _, err := w.Write(v.Bytes())
110                 return errors.Wrap(err, "writing *AssetID for hash")
111         case Hash:
112                 _, err := v.WriteTo(w)
113                 return errors.Wrap(err, "writing Hash for hash")
114         case AssetID:
115                 _, err := v.WriteTo(w)
116                 return errors.Wrap(err, "writing AssetID for hash")
117         }
118
119         // The two container types in the spec (List and Struct)
120         // correspond to slices and structs in Go. They can't be
121         // handled with type assertions, so we must use reflect.
122         switch v := reflect.ValueOf(c); v.Kind() {
123         case reflect.Ptr:
124                 if v.IsNil() {
125                         return nil
126                 }
127                 elem := v.Elem()
128                 return writeForHash(w, elem.Interface())
129         case reflect.Slice:
130                 l := v.Len()
131                 if _, err := blockchain.WriteVarint31(w, uint64(l)); err != nil {
132                         return errors.Wrapf(err, "writing slice (len %d) for hash", l)
133                 }
134                 for i := 0; i < l; i++ {
135                         c := v.Index(i)
136                         if !c.CanInterface() {
137                                 return errInvalidValue
138                         }
139                         if err := writeForHash(w, c.Interface()); err != nil {
140                                 return errors.Wrapf(err, "writing slice element %d for hash", i)
141                         }
142                 }
143                 return nil
144
145         case reflect.Struct:
146                 typ := v.Type()
147                 for i := 0; i < typ.NumField(); i++ {
148                         c := v.Field(i)
149                         if !c.CanInterface() {
150                                 return errInvalidValue
151                         }
152                         if err := writeForHash(w, c.Interface()); err != nil {
153                                 t := v.Type()
154                                 f := t.Field(i)
155                                 return errors.Wrapf(err, "writing struct field %d (%s.%s) for hash", i, t.Name(), f.Name)
156                         }
157                 }
158                 return nil
159         }
160
161         return errors.Wrap(fmt.Errorf("bad type %T", c))
162 }