/
diffs.go
216 lines (191 loc) · 7.74 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
package consensus
import (
"errors"
"github.com/HyperspaceApp/Hyperspace/build"
"github.com/HyperspaceApp/Hyperspace/encoding"
"github.com/HyperspaceApp/Hyperspace/modules"
"github.com/HyperspaceApp/Hyperspace/types"
"github.com/coreos/bbolt"
)
var (
errDiffsNotGenerated = errors.New("applying diff set before generating errors")
errInvalidSuccessor = errors.New("generating diffs for a block that's an invalid successsor to the current block")
errNegativePoolAdjustment = errors.New("committing a siafund pool diff with a negative adjustment")
errWrongAppliedDiffSet = errors.New("applying a diff set that isn't the current block")
errWrongRevertDiffSet = errors.New("reverting a diff set that isn't the current block")
)
// commitDiffSetSanity performs a series of sanity checks before committing a
// diff set.
func commitDiffSetSanity(tx *bolt.Tx, pb *processedBlock, dir modules.DiffDirection) {
// This function is purely sanity checks.
if !build.DEBUG {
return
}
// Diffs should have already been generated for this node.
if !pb.DiffsGenerated {
panic(errDiffsNotGenerated)
}
// 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 {
parent, err := getBlockMap(tx, pb.Block.ParentID)
if build.DEBUG && err != nil {
panic(err)
}
if parent.Block.ID() != currentBlockID(tx) {
panic(errWrongAppliedDiffSet)
}
} else {
if pb.Block.ID() != currentBlockID(tx) {
panic(errWrongRevertDiffSet)
}
}
}
// commitSiacoinOutputDiff applies or reverts a SiacoinOutputDiff.
func commitSiacoinOutputDiff(tx *bolt.Tx, scod modules.SiacoinOutputDiff, dir modules.DiffDirection) {
if scod.Direction == dir {
addSiacoinOutput(tx, scod.ID, scod.SiacoinOutput)
} else {
removeSiacoinOutput(tx, scod.ID)
}
}
// commitFileContractDiff applies or reverts a FileContractDiff.
func commitFileContractDiff(tx *bolt.Tx, fcd modules.FileContractDiff, dir modules.DiffDirection) {
if fcd.Direction == dir {
addFileContract(tx, fcd.ID, fcd.FileContract)
} else {
removeFileContract(tx, fcd.ID)
}
}
// commitDelayedSiacoinOutputDiff applies or reverts a delayedSiacoinOutputDiff.
func commitDelayedSiacoinOutputDiff(tx *bolt.Tx, dscod modules.DelayedSiacoinOutputDiff, dir modules.DiffDirection) {
if dscod.Direction == dir {
addDSCO(tx, dscod.MaturityHeight, dscod.ID, dscod.SiacoinOutput)
} else {
removeDSCO(tx, dscod.MaturityHeight, dscod.ID)
}
}
// createUpcomingDelayeOutputdMaps creates the delayed siacoin output maps that
// will be used when applying delayed siacoin outputs in the diff set.
func createUpcomingDelayedOutputMaps(tx *bolt.Tx, height types.BlockHeight, dir modules.DiffDirection) {
if dir == modules.DiffApply {
// log.Printf("create dsco for %d", height+types.MaturityDelay)
createDSCOBucket(tx, height+types.MaturityDelay)
} else if height >= types.MaturityDelay {
createDSCOBucket(tx, height)
}
}
// commitNodeDiffs commits all of the diffs in a block node.
func commitNodeDiffs(tx *bolt.Tx, pb *processedBlock, dir modules.DiffDirection) {
if dir == modules.DiffApply {
for _, scod := range pb.SiacoinOutputDiffs {
commitSiacoinOutputDiff(tx, scod, dir)
}
for _, fcd := range pb.FileContractDiffs {
commitFileContractDiff(tx, fcd, dir)
}
for _, dscod := range pb.DelayedSiacoinOutputDiffs {
commitDelayedSiacoinOutputDiff(tx, dscod, dir)
}
} else {
for i := len(pb.SiacoinOutputDiffs) - 1; i >= 0; i-- {
commitSiacoinOutputDiff(tx, pb.SiacoinOutputDiffs[i], dir)
}
for i := len(pb.FileContractDiffs) - 1; i >= 0; i-- {
commitFileContractDiff(tx, pb.FileContractDiffs[i], dir)
}
for i := len(pb.DelayedSiacoinOutputDiffs) - 1; i >= 0; i-- {
commitDelayedSiacoinOutputDiff(tx, pb.DelayedSiacoinOutputDiffs[i], dir)
}
}
}
// deleteObsoleteDelayedOutputMaps deletes the delayed siacoin output maps that
// are no longer in use.
func deleteObsoleteDelayedOutputMaps(tx *bolt.Tx, height types.BlockHeight, dir modules.DiffDirection) {
// There are no outputs that mature in the first MaturityDelay blocks.
if dir == modules.DiffApply && height >= types.MaturityDelay {
// log.Printf("delete dsco for %d", height)
deleteDSCOBucket(tx, height)
} else if dir == modules.DiffRevert {
deleteDSCOBucket(tx, height+types.MaturityDelay)
}
}
// updateCurrentPath updates the current path after applying a diff set.
func updateCurrentPath(tx *bolt.Tx, id types.BlockID, dir modules.DiffDirection) {
// Update the current path.
if dir == modules.DiffApply {
pushPath(tx, id)
} else {
popPath(tx)
}
}
// commitDiffSet applies or reverts the diffs in a blockNode.
func commitDiffSet(tx *bolt.Tx, pb *processedBlock, dir modules.DiffDirection) {
// Sanity checks - there are a few so they were moved to another function.
if build.DEBUG {
commitDiffSetSanity(tx, pb, dir)
}
createUpcomingDelayedOutputMaps(tx, pb.Height, dir)
commitNodeDiffs(tx, pb, dir)
deleteObsoleteDelayedOutputMaps(tx, pb.Height, dir)
updateCurrentPath(tx, pb.Block.ID(), dir) // can form this with inconsistent blocks
}
// 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 generateAndApplyDiff(tx *bolt.Tx, pb *processedBlock, pbh *modules.ProcessedBlockHeader) error {
// Sanity check - the block being applied should have the current block as
// a parent.
if build.DEBUG && pb.Block.ParentID != currentBlockID(tx) {
panic(errInvalidSuccessor)
}
// Create the bucket to hold all of the delayed siacoin outputs created by
// transactions this block. Needs to happen before any transactions are
// applied.
createDSCOBucket(tx, pb.Height+types.MaturityDelay)
// 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 pb.Block.Transactions {
err := validTransaction(tx, txn)
if err != nil {
return err
}
applyTransaction(tx, pb, 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.
applyMaintenance(tx, pb, pbh)
// DiffsGenerated are only set to true after the block has been fully
// validated and integrated. This is required to prevent later blocks from
// being accepted on top of an invalid block - if the consensus set ever
// forks over an invalid block, 'DiffsGenerated' will be set to 'false',
// requiring validation to occur again. when 'DiffsGenerated' is set to
// true, validation is skipped, therefore the flag should only be set to
// true on fully validated blocks.
pb.DiffsGenerated = true
// Add the block to the current path and block map.
bid := pb.Block.ID()
blockMap := tx.Bucket(BlockMap)
updateCurrentPath(tx, pb.Block.ID(), modules.DiffApply)
// Sanity check preparation - set the consensus hash at this height so that
// during reverting a check can be performed to assure consistency when
// adding and removing blocks. Must happen after the block is added to the
// path.
if build.DEBUG {
pb.ConsensusChecksum = consensusChecksum(tx)
}
if pbh != nil {
blockHeaderMap := tx.Bucket(BlockHeaderMap)
err := blockHeaderMap.Put(bid[:], encoding.Marshal(*pbh))
if err != nil {
return err
}
//TODO: need to update the cs.processedBlockHeaders
}
return blockMap.Put(bid[:], encoding.Marshal(*pb))
}