/
compute_fields.go
215 lines (177 loc) · 5.86 KB
/
compute_fields.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
package documents
import (
"bytes"
"context"
"time"
coredocumentpb "github.com/centrifuge/centrifuge-protobufs/gen/go/coredocument"
"github.com/centrifuge/go-centrifuge/errors"
"github.com/centrifuge/go-substrate-rpc-client/scale"
logging "github.com/ipfs/go-log"
"github.com/perlin-network/life/exec"
)
var computeLog = logging.Logger("compute_fields")
const (
// ErrComputeFieldsInvalidWASM is a sentinel error for invalid WASM blob
ErrComputeFieldsInvalidWASM = errors.Error("Invalid WASM blob")
// ErrComputeFieldsAllocateNotFound is a sentinel error when WASM doesn't expose 'allocate' function
ErrComputeFieldsAllocateNotFound = errors.Error("'allocate' function not exported")
// ErrComputeFieldsComputeNotFound is a sentinel error when WASM doesn't expose 'compute' function
ErrComputeFieldsComputeNotFound = errors.Error("'compute' function not exported")
// computeFieldsTimeout is the max time we let the WASM computation to be run.
computeFieldsTimeout = time.Second * 20
)
// fetchComputeFunctions checks WASM if the required exported fields are present
// `allocate`: allocate function to allocate the required bytes on WASM
// `compute`: compute function to compute the 32byte value from the passed attributes
// and returns both functions along with the VM instance
func fetchComputeFunctions(wasm []byte) (i *exec.VirtualMachine, allocate, compute int, err error) {
i, err = exec.NewVirtualMachine(wasm, exec.VMConfig{}, &exec.NopResolver{}, nil)
if err != nil {
return i, allocate, compute, errors.AppendError(nil, ErrComputeFieldsInvalidWASM)
}
allocate, ok := i.GetFunctionExport("allocate")
if !ok {
err = errors.AppendError(err, ErrComputeFieldsAllocateNotFound)
}
compute, ok = i.GetFunctionExport("compute")
if !ok {
err = errors.AppendError(err, ErrComputeFieldsComputeNotFound)
}
return i, allocate, compute, err
}
// executeWASM encodes the passed attributes and executes WASM.
// returns a 32byte value. If the WASM exits with an error, returns a zero 32byte value
// execution is allowed to run for upto timeout. Once the timeout is reached, VM is stopped and returned a zero value.
func executeWASM(wasm []byte, attributes []Attribute, timeout time.Duration) (result [32]byte) {
i, allocate, compute, err := fetchComputeFunctions(wasm)
if err != nil {
computeLog.Error(err)
return result
}
cattrs, err := toComputeFieldsAttributes(attributes)
if err != nil {
computeLog.Error(err)
return result
}
var buf bytes.Buffer
enc := scale.NewEncoder(&buf)
err = enc.Encode(cattrs)
if err != nil {
computeLog.Error(err)
return result
}
ctx, cancelFunc := context.WithTimeout(context.Background(), timeout)
defer cancelFunc()
// allocate memory
res, err := i.Run(ctx, allocate, int64(buf.Len()))
if err != nil {
computeLog.Errorf("failed to execute 'allocate': %v", err)
return
}
// copy encoded attributes to memory
mem := i.Memory[res:]
copy(mem, buf.Bytes())
// execute compute
res, err = i.Run(ctx, compute, res, int64(buf.Len()))
if err != nil {
computeLog.Errorf("failed to execute 'compute': %v", err)
return
}
// copy result from the wasm
d := i.Memory[res : res+32]
copy(result[:], d)
return result
}
type computeSigned struct {
Identity, DocumentVersion, Value []byte
Type string
Signature, PublicKey []byte
}
type computeAttribute struct {
Key string
Type string
Value []byte
Signed computeSigned
}
func toComputeFieldsAttributes(attrs []Attribute) (cattrs []computeAttribute, err error) {
for _, attr := range attrs {
cattr, err := toComputeFieldsAttribute(attr)
if err != nil {
return nil, err
}
cattrs = append(cattrs, cattr)
}
return cattrs, nil
}
// toComputeFieldsAttribute convert attribute of type `string`, `bytes`, `integer`, `signed` to compute field attribute
func toComputeFieldsAttribute(attr Attribute) (cattr computeAttribute, err error) {
cattr = computeAttribute{
Key: attr.KeyLabel,
Type: attr.Value.Type.String()}
switch attr.Value.Type {
case AttrSigned:
s := attr.Value.Signed
cattr.Signed = computeSigned{
Identity: s.Identity.ToAddress().Bytes(),
DocumentVersion: s.DocumentVersion,
Value: s.Value,
Type: s.Type.String(),
Signature: s.Signature,
PublicKey: s.PublicKey,
}
case AttrBytes, AttrInt256, AttrString:
cattr.Value, err = attr.Value.ToBytes()
default:
err = errors.New("'%s' attribute type not supported by compute fields", attr.Value.Type)
}
return cattr, err
}
// ExecuteComputeFields executes all the compute fields and updates the document with target attributes
// each WASM is executed at a max of timeout duration.
func (cd *CoreDocument) ExecuteComputeFields(timeout time.Duration) error {
computeFieldsRules := cd.GetComputeFieldsRules()
ncd := cd
for _, computeField := range computeFieldsRules {
targetAttr, err := executeComputeField(computeField, ncd.Attributes, timeout)
if err != nil {
return err
}
ncd, err = ncd.AddAttributes(CollaboratorsAccess{}, false, nil, targetAttr)
if err != nil {
return err
}
}
*cd = *ncd
return nil
}
func executeComputeField(rule *coredocumentpb.TransitionRule, attributes map[AttrKey]Attribute, timeout time.Duration) (result Attribute, err error) {
var attrs []Attribute
// filter attributes
for _, attr := range rule.ComputeFields {
key, err := AttrKeyFromBytes(attr)
if err != nil {
return result, err
}
fa, ok := attributes[key]
if !ok {
continue
}
attrs = append(attrs, fa)
}
// execute WASM
r := executeWASM(rule.ComputeCode, attrs, timeout)
// set result into the target attribute
targetKey, err := AttrKeyFromLabel(string(rule.ComputeTargetField))
if err != nil {
return result, err
}
result = Attribute{
KeyLabel: string(rule.ComputeTargetField),
Key: targetKey,
Value: AttrVal{
Type: AttrBytes,
Bytes: r[:],
},
}
return result, nil
}