-
Notifications
You must be signed in to change notification settings - Fork 179
/
chunkVerifier.go
201 lines (163 loc) · 6.84 KB
/
chunkVerifier.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
package chunks
import (
"bytes"
"errors"
"fmt"
executionState "github.com/onflow/flow-go/engine/execution/state"
"github.com/onflow/flow-go/engine/execution/state/delta"
"github.com/onflow/flow-go/engine/verification"
"github.com/onflow/flow-go/fvm"
"github.com/onflow/flow-go/fvm/state"
"github.com/onflow/flow-go/ledger"
"github.com/onflow/flow-go/ledger/partial"
chmodels "github.com/onflow/flow-go/model/chunks"
"github.com/onflow/flow-go/model/flow"
)
type VirtualMachine interface {
Run(fvm.Context, fvm.Procedure, state.Ledger) error
}
// ChunkVerifier is a verifier based on the current definitions of the flow network
type ChunkVerifier struct {
vm VirtualMachine
vmCtx fvm.Context
}
// NewChunkVerifier creates a chunk verifier containing a flow virtual machine
func NewChunkVerifier(vm VirtualMachine, vmCtx fvm.Context) *ChunkVerifier {
return &ChunkVerifier{
vm: vm,
vmCtx: vmCtx,
}
}
// Verify verifies a given VerifiableChunk corresponding to a non-system chunk.
// by executing it and checking the final state commitment
// It returns a Spock Secret as a byte array, verification fault of the chunk, and an error.
// Note: Verify should only be executed on non-system chunks. It returns an error if it is invoked on
// system chunks.
func (fcv *ChunkVerifier) Verify(vc *verification.VerifiableChunkData) ([]byte, chmodels.ChunkFault, error) {
if vc.IsSystemChunk {
return nil, nil, fmt.Errorf("wrong method invoked for verifying system chunk")
}
transactions := make([]*fvm.TransactionProcedure, 0)
for _, txBody := range vc.Collection.Transactions {
tx := fvm.Transaction(txBody)
transactions = append(transactions, tx)
}
return fcv.verifyTransactions(vc.Chunk, vc.ChunkDataPack, vc.Result, vc.Header, transactions, vc.EndState)
}
// SystemChunkVerify verifies a given VerifiableChunk corresponding to a system chunk.
// by executing it and checking the final state commitment
// It returns a Spock Secret as a byte array, verification fault of the chunk, and an error.
// Note: SystemChunkVerify should only be executed on system chunks. It returns an error if it is invoked on
// non-system chunks.
func (fcv *ChunkVerifier) SystemChunkVerify(vc *verification.VerifiableChunkData) ([]byte, chmodels.ChunkFault, error) {
if !vc.IsSystemChunk {
return nil, nil, fmt.Errorf("wrong method invoked for verifying non-system chunk")
}
// transaction body of system chunk
txBody := fvm.SystemChunkTransaction(fcv.vmCtx.Chain.ServiceAddress())
tx := fvm.Transaction(txBody)
transactions := []*fvm.TransactionProcedure{tx}
return fcv.verifyTransactions(vc.Chunk, vc.ChunkDataPack, vc.Result, vc.Header, transactions, vc.EndState)
}
func (fcv *ChunkVerifier) verifyTransactions(chunk *flow.Chunk,
chunkDataPack *flow.ChunkDataPack,
result *flow.ExecutionResult,
header *flow.Header,
transactions []*fvm.TransactionProcedure,
endState flow.StateCommitment) ([]byte, chmodels.ChunkFault, error) {
// TODO check collection hash to match
// TODO check datapack hash to match
// TODO check the number of transactions and computation used
chIndex := chunk.Index
execResID := result.ID()
// build a block context
blockCtx := fvm.NewContextFromParent(fcv.vmCtx, fvm.WithBlockHeader(header))
if chunkDataPack == nil {
return nil, nil, fmt.Errorf("missing chunk data pack")
}
// constructing a partial trie given chunk data package
psmt, err := partial.NewLedger(chunkDataPack.Proof, chunkDataPack.StartState)
if err != nil {
// TODO provide more details based on the error type
return nil, chmodels.NewCFInvalidVerifiableChunk("error constructing partial trie: ", err, chIndex, execResID),
nil
}
// chunk view construction
// unknown register tracks access to parts of the partial trie which
// are not expanded and values are unknown.
unknownRegTouch := make(map[string]*ledger.Key)
getRegister := func(owner, controller, key string) (flow.RegisterValue, error) {
// check if register has been provided in the chunk data pack
registerID := flow.NewRegisterID(owner, controller, key)
registerKey := executionState.RegisterIDToKey(registerID)
query, err := ledger.NewQuery(chunkDataPack.StartState, []ledger.Key{registerKey})
if err != nil {
return nil, fmt.Errorf("cannot create query: %w", err)
}
values, err := psmt.Get(query)
if err != nil {
if errors.Is(err, ledger.ErrMissingKeys{}) {
unknownRegTouch[registerID.String()] = ®isterKey
return nil, fmt.Errorf("missing register")
}
// append to missing keys if error is ErrMissingKeys
return nil, fmt.Errorf("cannot query register: %w", err)
}
return values[0], nil
}
chunkView := delta.NewView(getRegister)
// executes all transactions in this chunk
for i, tx := range transactions {
txView := chunkView.NewChild()
// tx := fvm.Transaction(txBody)
err := fcv.vm.Run(blockCtx, tx, txView)
if err != nil {
// this covers unexpected and very rare cases (e.g. system memory issues...),
// so we shouldn't be here even if transaction naturally fails (e.g. permission, runtime ... )
return nil, nil, fmt.Errorf("failed to execute transaction: %d (%w)", i, err)
}
if tx.Err == nil {
// if tx is successful, we apply changes to the chunk view by merging the txView into chunk view
chunkView.MergeView(txView)
}
}
// check read access to unknown registers
if len(unknownRegTouch) > 0 {
var missingRegs []string
for _, key := range unknownRegTouch {
missingRegs = append(missingRegs, key.String())
}
return nil, chmodels.NewCFMissingRegisterTouch(missingRegs, chIndex, execResID), nil
}
// applying chunk delta (register updates at chunk level) to the partial trie
// this returns the expected end state commitment after updates and the list of
// register keys that was not provided by the chunk data package (err).
regs, values := chunkView.Delta().RegisterUpdates()
update, err := ledger.NewUpdate(
chunkDataPack.StartState,
executionState.RegisterIDSToKeys(regs),
executionState.RegisterValuesToValues(values),
)
if err != nil {
return nil, nil, fmt.Errorf("cannot create ledger update: %w", err)
}
expEndStateComm, err := psmt.Set(update)
if err != nil {
if errors.Is(err, ledger.ErrMissingKeys{}) {
keys := err.(*ledger.ErrMissingKeys).Keys
stringKeys := make([]string, len(keys))
for i, key := range keys {
stringKeys[i] = key.String()
}
return nil, chmodels.NewCFMissingRegisterTouch(stringKeys, chIndex, execResID), nil
}
return nil, chmodels.NewCFMissingRegisterTouch(nil, chIndex, execResID), nil
}
// TODO check if exec node provided register touches that was not used (no read and no update)
// check if the end state commitment mentioned in the chunk matches
// what the partial trie is providing.
if !bytes.Equal(expEndStateComm, endState) {
return nil, chmodels.NewCFNonMatchingFinalState(expEndStateComm, endState, chIndex, execResID), nil
}
return chunkView.SpockSecret(), nil, nil
}