-
Notifications
You must be signed in to change notification settings - Fork 459
/
block_validator.go
222 lines (207 loc) · 8.54 KB
/
block_validator.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
// Copyright 2015 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package core
import (
"fmt"
"time"
"github.com/dominant-strategies/go-quai/common"
"github.com/dominant-strategies/go-quai/consensus"
"github.com/dominant-strategies/go-quai/core/state"
"github.com/dominant-strategies/go-quai/core/types"
"github.com/dominant-strategies/go-quai/log"
"github.com/dominant-strategies/go-quai/params"
"github.com/dominant-strategies/go-quai/trie"
)
// BlockValidator is responsible for validating block headers, uncles and
// processed state.
//
// BlockValidator implements Validator.
type BlockValidator struct {
config *params.ChainConfig // Chain configuration options
hc *HeaderChain // HeaderChain
engine consensus.Engine // Consensus engine used for validating
}
// NewBlockValidator returns a new block validator which is safe for re-use
func NewBlockValidator(config *params.ChainConfig, headerChain *HeaderChain, engine consensus.Engine) *BlockValidator {
validator := &BlockValidator{
config: config,
engine: engine,
hc: headerChain,
}
return validator
}
// ValidateBody validates the given block's uncles and verifies the block
// header's transaction and uncle roots. The headers are assumed to be already
// validated at this point.
func (v *BlockValidator) ValidateBody(block *types.WorkObject) error {
nodeCtx := v.config.Location.Context()
// Check whether the block's known, and if not, that it's linkable
if nodeCtx == common.ZONE_CTX && v.hc.ProcessingState() {
if v.hc.bc.processor.HasBlockAndState(block.Hash(), block.NumberU64(nodeCtx)) {
return ErrKnownBlock
}
}
header := block.Header()
// Subordinate manifest must match ManifestHash in subordinate context, _iff_
// we have a subordinate (i.e. if we are not a zone)
if nodeCtx != common.ZONE_CTX {
// Region nodes should have body with zero length txs and etxs
if len(block.Transactions()) != 0 {
return fmt.Errorf("region body has non zero transactions")
}
if len(block.ExtTransactions()) != 0 {
return fmt.Errorf("region body has non zero etx transactions")
}
if len(block.Uncles()) != 0 {
return fmt.Errorf("region body has non zero uncles")
}
subManifestHash := types.DeriveSha(block.Manifest(), trie.NewStackTrie(nil))
if subManifestHash == types.EmptyRootHash || subManifestHash != header.ManifestHash(nodeCtx+1) {
// If we have a subordinate chain, it is impossible for the subordinate manifest to be empty
return ErrBadSubManifest
}
if nodeCtx == common.PRIME_CTX {
interlinkRootHash := types.DeriveSha(block.InterlinkHashes(), trie.NewStackTrie(nil))
if interlinkRootHash != header.InterlinkRootHash() {
return ErrBadInterlink
}
}
} else {
// Header validity is known at this point, check the uncles and transactions
if err := v.engine.VerifyUncles(v.hc, block); err != nil {
return err
}
if hash := types.CalcUncleHash(block.Uncles()); hash != header.UncleHash() {
return fmt.Errorf("uncle root hash mismatch: have %x, want %x", hash, header.UncleHash())
}
if v.hc.ProcessingState() {
if hash := types.DeriveSha(block.Transactions(), trie.NewStackTrie(nil)); hash != header.TxHash() {
return fmt.Errorf("transaction root hash mismatch: have %x, want %x", hash, header.TxHash())
}
if hash := types.DeriveSha(block.ExtTransactions(), trie.NewStackTrie(nil)); hash != header.EtxHash() {
return fmt.Errorf("external transaction root hash mismatch: have %x, want %x", hash, header.EtxHash())
}
}
}
return nil
}
// ValidateState validates the various changes that happen after a state
// transition, such as amount of used gas, the receipt roots and the state root
// itself. ValidateState returns a database batch if the validation was a success
// otherwise nil and an error is returned.
func (v *BlockValidator) ValidateState(block *types.WorkObject, statedb *state.StateDB, receipts types.Receipts, utxoEtxs []*types.Transaction, usedGas uint64) error {
start := time.Now()
header := types.CopyHeader(block.Header())
time1 := common.PrettyDuration(time.Since(start))
if block.GasUsed() != usedGas {
return fmt.Errorf("invalid gas used (remote: %d local: %d)", block.GasUsed(), usedGas)
}
time2 := common.PrettyDuration(time.Since(start))
time3 := common.PrettyDuration(time.Since(start))
// Tre receipt Trie's root (R = (Tr [[H1, R1], ... [Hn, Rn]]))
receiptSha := types.DeriveSha(receipts, trie.NewStackTrie(nil))
if receiptSha != header.ReceiptHash() {
return fmt.Errorf("invalid receipt root hash (remote: %x local: %x)", header.ReceiptHash(), receiptSha)
}
time4 := common.PrettyDuration(time.Since(start))
// Validate the state root against the received state root and throw
// an error if they don't match.
if root := statedb.IntermediateRoot(true); header.EVMRoot() != root {
return fmt.Errorf("invalid merkle root (remote: %x local: %x)", header.EVMRoot(), root)
}
if root := statedb.UTXORoot(); header.UTXORoot() != root {
return fmt.Errorf("invalid utxo root (remote: %x local: %x)", header.UTXORoot(), root)
}
if root := statedb.ETXRoot(); header.EtxSetRoot() != root {
return fmt.Errorf("invalid etx root (remote: %x local: %x)", header.EtxSetRoot(), root)
}
time5 := common.PrettyDuration(time.Since(start))
// Collect ETXs emitted from each successful transaction
var emittedEtxs types.Transactions
for _, receipt := range receipts {
if receipt.Status == types.ReceiptStatusSuccessful {
emittedEtxs = append(emittedEtxs, receipt.Etxs...)
}
}
emittedEtxs = append(emittedEtxs, utxoEtxs...)
time6 := common.PrettyDuration(time.Since(start))
// Confirm the ETXs emitted by the transactions in this block exactly match the
// ETXs given in the block body
if etxHash := types.DeriveSha(emittedEtxs, trie.NewStackTrie(nil)); etxHash != header.EtxHash() {
return fmt.Errorf("invalid etx hash (remote: %x local: %x)", header.EtxHash(), etxHash)
}
// Check that the UncledS in the header matches the S from the block
expectedUncledS := v.engine.UncledLogS(block)
if expectedUncledS.Cmp(header.UncledS()) != 0 {
return fmt.Errorf("invalid uncledS (remote: %x local: %x)", header.UncledS(), expectedUncledS)
}
v.hc.logger.WithFields(log.Fields{
"t1": time1,
"t2": time2,
"t3": time3,
"t4": time4,
"t5": time5,
"t6": time6,
}).Debug("times during validate state")
return nil
}
// CalcGasLimit computes the gas limit of the next block after parent. It aims
// to keep the baseline gas close to the provided target, and increase it towards
// the target if the baseline gas is lower.
func CalcGasLimit(parent *types.WorkObject, gasCeil uint64) uint64 {
// No Gas for TimeToStartTx days worth of zone blocks, this gives enough time to
// onboard new miners into the slice
if parent.NumberU64(common.ZONE_CTX) < params.TimeToStartTx {
return 0
}
// If parent gas is zero and we have passed the 5 day threshold, we can set the first block gas limit to min gas limit
if parent.GasLimit() == 0 {
return params.MinGasLimit
}
parentGasLimit := parent.GasLimit()
delta := parentGasLimit/params.GasLimitBoundDivisor - 1
limit := parentGasLimit
var desiredLimit uint64
percentGasUsed := parent.GasUsed() * 100 / parent.GasLimit()
if percentGasUsed > params.PercentGasUsedThreshold {
desiredLimit = CalcGasCeil(parent.NumberU64(common.ZONE_CTX), gasCeil)
if desiredLimit > gasCeil {
desiredLimit = gasCeil
}
if limit+delta > desiredLimit {
return desiredLimit
} else {
return limit + delta
}
} else {
desiredLimit = params.MinGasLimit
if limit-delta/2 < desiredLimit {
return desiredLimit
} else {
return limit - delta/2
}
}
}
func CalcGasCeil(blockNumber uint64, gasCeil uint64) uint64 {
if blockNumber < params.GasLimitStepOneBlockThreshold {
return gasCeil / 4
} else if blockNumber < params.GasLimitStepTwoBlockThreshold {
return gasCeil / 2
} else if blockNumber < params.GasLimitStepThreeBlockThreshold {
return gasCeil * 3 / 4
}
return gasCeil
}