forked from NebulousLabs/Sia
/
diffs.go
263 lines (235 loc) · 8.82 KB
/
diffs.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
package consensus
import (
"github.com/NebulousLabs/Sia/build"
"github.com/NebulousLabs/Sia/modules"
"github.com/NebulousLabs/Sia/types"
)
// diffs.go contains all of the functions related to diffs in the consensus
// set. Each block changes the consensus set in a deterministic way, these
// changes are recorded as diffs for easy rewinding and reapplying. The diffs
// are created, applied, reverted, and queried in this file.
// commitSiacoinOutputDiff applies or reverts a SiacoinOutputDiff.
func (s *State) commitSiacoinOutputDiff(scod modules.SiacoinOutputDiff, dir modules.DiffDirection) {
// Sanity check - should not be adding an output twice, or deleting an
// output that does not exist.
if build.DEBUG {
_, exists := s.siacoinOutputs[scod.ID]
if exists == (scod.Direction == dir) {
panic("rogue siacoin output in commitSiacoinOutputDiff")
}
}
if scod.Direction == dir {
s.siacoinOutputs[scod.ID] = scod.SiacoinOutput
} else {
delete(s.siacoinOutputs, scod.ID)
}
}
// commitFileContractDiff applies or reverts a FileContractDiff.
func (s *State) commitFileContractDiff(fcd modules.FileContractDiff, dir modules.DiffDirection) {
// Sanity check - should not be adding a contract twice, or deleting a
// contract that does not exist.
if build.DEBUG {
_, exists := s.fileContracts[fcd.ID]
if exists == (fcd.Direction == dir) {
panic("rogue file contract in commitFileContractDiff")
}
}
if fcd.Direction == dir {
s.fileContracts[fcd.ID] = fcd.FileContract
} else {
delete(s.fileContracts, fcd.ID)
}
}
// commitSiafundOutputDiff applies or reverts a SiafundOutputDiff.
func (s *State) commitSiafundOutputDiff(sfod modules.SiafundOutputDiff, dir modules.DiffDirection) {
// Sanity check - should not be adding an output twice, or deleting an
// output that does not exist.
if build.DEBUG {
_, exists := s.siafundOutputs[sfod.ID]
if exists == (sfod.Direction == dir) {
panic("rogue siafund output in commitSiafundOutputDiff")
}
}
if sfod.Direction == dir {
s.siafundOutputs[sfod.ID] = sfod.SiafundOutput
} else {
delete(s.siafundOutputs, sfod.ID)
}
}
// commitDelayedSiacoinOutputDiff applies or reverts a delayedSiacoinOutputDiff.
func (cs *State) commitDelayedSiacoinOutputDiff(dscod modules.DelayedSiacoinOutputDiff, dir modules.DiffDirection) {
// Sanity check - should not be adding an output twoice, or deleting an
// output that does not exist.
if build.DEBUG {
_, exists := cs.delayedSiacoinOutputs[dscod.MaturityHeight][dscod.ID]
if exists == (dscod.Direction == dir) {
panic("rogue delayed siacoin output in commitDelayedSiacoinOutputDiff")
}
}
if dscod.Direction == dir {
cs.delayedSiacoinOutputs[dscod.MaturityHeight][dscod.ID] = dscod.SiacoinOutput
} else {
delete(cs.delayedSiacoinOutputs[dscod.MaturityHeight], dscod.ID)
}
}
// commitSiafundPoolDiff applies or reverts a SiafundPoolDiff.
func (s *State) commitSiafundPoolDiff(sfpd modules.SiafundPoolDiff, dir modules.DiffDirection) {
if dir == modules.DiffApply {
s.siafundPool = sfpd.Adjusted
} else {
s.siafundPool = sfpd.Previous
}
}
// commitDiffSet applies or reverts the diffs in a blockNode.
func (s *State) commitDiffSet(bn *blockNode, dir modules.DiffDirection) {
// Sanity check
if build.DEBUG {
// Diffs should have already been generated for this node.
if !bn.diffsGenerated {
panic("misuse of applyDiffSet - diffs have not been generated!")
}
// Current node must be the input node's parent if applying, and
// current node must be the input node if reverting.
if dir == modules.DiffApply {
if bn.parent.block.ID() != s.currentBlockID() {
panic("applying a block node when it's not a valid successor")
}
} else {
if bn.block.ID() != s.currentBlockID() {
panic("applying a block node when it's not a valid successor")
}
}
}
// Create the filling delayed siacoin output map.
if dir == modules.DiffApply {
if build.DEBUG {
_, exists := s.delayedSiacoinOutputs[bn.height+types.MaturityDelay]
if exists {
panic("trying to create a map that already exists")
}
}
s.delayedSiacoinOutputs[bn.height+types.MaturityDelay] = make(map[types.SiacoinOutputID]types.SiacoinOutput)
} else {
// Skip creating maps for height's that can't have delayed outputs.
if bn.height > types.MaturityDelay {
if build.DEBUG {
_, exists := s.delayedSiacoinOutputs[bn.height]
if exists {
panic("trying to create a map that already exists")
}
}
s.delayedSiacoinOutputs[bn.height] = make(map[types.SiacoinOutputID]types.SiacoinOutput)
}
}
// Apply each of the diffs.
if dir == modules.DiffApply {
for _, scod := range bn.siacoinOutputDiffs {
s.commitSiacoinOutputDiff(scod, dir)
}
for _, fcd := range bn.fileContractDiffs {
s.commitFileContractDiff(fcd, dir)
}
for _, sfod := range bn.siafundOutputDiffs {
s.commitSiafundOutputDiff(sfod, dir)
}
for _, dscod := range bn.delayedSiacoinOutputDiffs {
s.commitDelayedSiacoinOutputDiff(dscod, dir)
}
} else {
for i := len(bn.siacoinOutputDiffs) - 1; i >= 0; i-- {
s.commitSiacoinOutputDiff(bn.siacoinOutputDiffs[i], dir)
}
for i := len(bn.fileContractDiffs) - 1; i >= 0; i-- {
s.commitFileContractDiff(bn.fileContractDiffs[i], dir)
}
for i := len(bn.siafundOutputDiffs) - 1; i >= 0; i-- {
s.commitSiafundOutputDiff(bn.siafundOutputDiffs[i], dir)
}
for i := len(bn.delayedSiacoinOutputDiffs) - 1; i >= 0; i-- {
s.commitDelayedSiacoinOutputDiff(bn.delayedSiacoinOutputDiffs[i], dir)
}
}
s.commitSiafundPoolDiff(bn.siafundPoolDiff, dir)
// Delete the emptied siacoin output map.
if dir == modules.DiffApply {
// There are no outputs that mature in the first MaturityDelay blocks.
if bn.height > types.MaturityDelay {
// Sanity check - the map being deleted should be empty.
if build.DEBUG {
if len(s.delayedSiacoinOutputs[bn.height]) != 0 {
panic("trying to delete a set of delayed outputs that is not empty.")
}
}
delete(s.delayedSiacoinOutputs, bn.height)
}
} else {
// Sanity check - the map being deleted should be empty
if build.DEBUG {
if len(s.delayedSiacoinOutputs[bn.height+types.MaturityDelay]) != 0 {
panic("trying to delete a set of delayed outputs that is not empty.")
}
}
delete(s.delayedSiacoinOutputs, bn.height+types.MaturityDelay)
}
// Update the current path.
if dir == modules.DiffApply {
s.currentPath = append(s.currentPath, bn.block.ID())
s.db.AddBlock(bn.block)
} else {
s.currentPath = s.currentPath[:len(s.currentPath)-1]
s.db.RemoveBlock()
}
}
// generateAndApplyDiff will verify the block and then integrate it into the
// consensus state. These two actions must happen at the same time because
// transactions are allowed to depend on each other. We can't be sure that a
// transaction is valid unless we have applied all of the previous transactions
// in the block, which means we need to apply while we verify.
func (s *State) generateAndApplyDiff(bn *blockNode) error {
// Sanity check
if build.DEBUG {
// Generate should only be called if the diffs have not yet been
// generated.
if bn.diffsGenerated {
panic("misuse of generateAndApplyDiff")
}
// Current node must be the input node's parent.
if bn.parent.block.ID() != s.currentBlockID() {
panic("applying a block node when it's not a valid successor")
}
}
// Update the state to point to the new block.
s.currentPath = append(s.currentPath, bn.block.ID())
s.db.AddBlock(bn.block)
s.delayedSiacoinOutputs[bn.height+types.MaturityDelay] = make(map[types.SiacoinOutputID]types.SiacoinOutput)
// diffsGenerated is set to true as soon as we start changing the set of
// diffs in the block node. If at any point the block is found to be
// invalid, the diffs can be safely reversed from whatever point.
bn.diffsGenerated = true
// The first diff to be applied is to mark what the starting siafundPool balance
// is.
bn.siafundPoolDiff.Previous = s.siafundPool
// Validate and apply each transaction in the block. They cannot be
// validated all at once because some transactions may not be valid until
// previous transactions have been applied.
for _, txn := range bn.block.Transactions {
err := s.validTransaction(txn)
if err != nil {
s.applyMaturedSiacoinOutputs(bn) // Awkward: need to apply the matured outputs otherwise the diff structure malforms.
s.commitDiffSet(bn, modules.DiffRevert)
s.dosBlocks[bn.block.ID()] = struct{}{}
s.deleteNode(bn)
return err
}
s.applyTransaction(bn, txn)
}
// After all of the transactions have been applied, 'maintenance' is
// applied on the block. This includes adding any outputs that have reached
// maturity, applying any contracts with missed storage proofs, and adding
// the miner payouts to the list of delayed outputs.
s.applyMaintenance(bn)
// The final thing is to update the siafundPoolDiff to indicate where the
// siafund pool ended up.
bn.siafundPoolDiff.Adjusted = s.siafundPool
return nil
}