···14 return nil, stacktrace.Propagate(err)
15 }
16 if result.Code == 0 {
17- if action == TransactionActionAuthoritativeImport {
18 // this type of transaction is meant to be included only by validator nodes
19 return &abcitypes.ResponseCheckTx{
20 Code: 4002,
21- Log: "AuthoritativeImport transactions can only be introduced by validator nodes",
22 }, nil
23 }
24
···14 return nil, stacktrace.Propagate(err)
15 }
16 if result.Code == 0 {
17+ if !action.SubmittableViaMempool() {
18 // this type of transaction is meant to be included only by validator nodes
19 return &abcitypes.ResponseCheckTx{
20 Code: 4002,
21+ Log: "This action can only be introduced by validator nodes",
22 }, nil
23 }
24
···1819const CommitToChallengeMaxAgeInBlocks = 3
20const CommitToChallengeMinRange = 1000
21-const CommitToChallengeMaxRange = 10000
22-const CommitToChallengeTargetInterval = 5000
2324-// TODO adjust these depending on how fast we want inactive validators to lose reputation
25-// TODO reputation loss (and gain?) should probably be based on a % of the current reputation
26-// or loss should stack (multiplicatively?) if a validator remains inactive for too long
27-// so that if a very reputable validator goes offline, it doesn't continue to have a lot of voting power for too long
28-// perhaps simpler idea: in addition to entropy, apply additional (separate) penalty based on age of last proven height
29-// (store.Consensus.ValidatorRangeChallengeCompletion) when age crosses some threshold
30const ReputationGainPerProvenBlock = 100
31const ReputationEntropyLossPerBlock = 90
32···77 }, nil
78 }
7980- validatorAddress := validatorPubKey.Address()
81-82 if tx.Arguments.ToHeight < tx.Arguments.FromHeight || tx.Arguments.ToHeight >= deps.workingHeight {
83 return &processResult{
84 Code: 4201,
···107 }, nil
108 }
109110- toHeightBlockMeta := deps.blockStore.LoadBlockMeta(tx.Arguments.ToHeight)
111- if toHeightBlockMeta == nil {
112 return &processResult{
113 Code: 4205,
114 Log: "unknown block in challenge range",
115 }, nil
116 }
117118- if deps.readTx.Timestamp().Sub(toHeightBlockMeta.Header.Time) > CommitToChallengeMaxAge {
119 return &processResult{
120 Code: 4206,
121 Log: "outdated challenge range",
122 }, nil
123 }
124125- currentCompletion, err := store.Consensus.ValidatorRangeChallengeCompletion(deps.readTx, validatorAddress)
126 if err == nil {
127 if tx.Arguments.FromHeight <= int64(currentCompletion) {
128 return &processResult{
···154 proofHeight := int64(binary.BigEndian.Uint64(existenceProof.Key))
155156 expectedProofHeight := computeHeightToProveInRange(
157- toHeightBlockMeta.Header.LastCommitHash.Bytes(),
158- validatorAddress.Bytes(),
159 tx.Arguments.FromHeight,
160 tx.Arguments.ToHeight,
161 mo.None[int64]())
···167 }, nil
168 }
169170- blockProofValid, err := deps.blockChallengeCoordinator.verifyBlockChallengeProof(int64(proofHeight), validatorAddress, existenceProof.Value)
171 if err != nil {
172 return nil, stacktrace.Propagate(err)
173 }
···188 if writeTx, ok := deps.writeTx.Get(); ok {
189 err = store.Consensus.SetValidatorRangeChallengeCommitment(
190 writeTx,
191- validatorAddress,
192 uint64(tx.Arguments.FromHeight),
193 uint64(tx.Arguments.ToHeight),
194 uint64(proofHeight),
···209type CompleteChallengeArguments struct {
210 // This transaction is not signed. It is a no-op if it isn't valid and we don't really care if an entity is able to complete a challenge on behalf of another validator
211 // (that would be quite an achievement, on the level of a validator being able to find a collision for the committed root in order to fake not doing all the work)
212- Validator []byte `json:"validator" refmt:"validator"`
213214 // this shall be a membership proof on the same tree the validator previously committed to,
215 // for the key deterministically-randomly determined by the last_commit_hash of the block _after_ the one
···236 }, nil
237 }
238239- fromHeight, toHeight, provenHeight, includedOnHeight, committedTreeRoot, err := store.Consensus.ValidatorRangeChallengeCommitment(deps.readTx, tx.Arguments.Validator)
240 if err != nil {
241 if errors.Is(err, store.ErrNoActiveChallengeCommitment) {
242 return &processResult{
···255 }, nil
256 }
257258- blockAfterMeta := deps.blockStore.LoadBlockMeta(int64(includedOnHeight + 1))
259- if blockAfterMeta == nil {
260 // this shouldn't happen unless the prover is submitting the completion on the same block as the commitment
261 return &processResult{
262 Code: 4302,
···264 }, nil
265 }
266267- if deps.readTx.Timestamp().Sub(blockAfterMeta.Header.Time) > CompleteChallengeMaxAge {
268 // validator must commit to a new challenge
269 return &processResult{
270 Code: 4303,
···284285 proofHeight := int64(binary.BigEndian.Uint64(existenceProof.Key))
28600000000287 expectedProofHeight := computeHeightToProveInRange(
288- blockAfterMeta.Header.LastCommitHash.Bytes(),
289- tx.Arguments.Validator,
290 int64(fromHeight),
291 int64(toHeight),
292 mo.Some(int64(provenHeight)))
293294 if proofHeight != expectedProofHeight {
295 return &processResult{
296- Code: 4305,
297 Log: "incorrect key proven",
298 }, nil
299 }
300301- blockProofValid, err := deps.blockChallengeCoordinator.verifyBlockChallengeProof(int64(proofHeight), tx.Arguments.Validator, existenceProof.Value)
302 if err != nil {
303 return nil, stacktrace.Propagate(err)
304 }
305 if !blockProofValid {
306 return &processResult{
307- Code: 4306,
308 Log: "invalid proof",
309 }, nil
310 }
311312 if !ics23.VerifyMembership(ics23.IavlSpec, committedTreeRoot, proof, existenceProof.Key, existenceProof.Value) {
313 return &processResult{
314- Code: 4307,
315 Log: "invalid proof",
316 }, nil
317 }
318319 if writeTx, ok := deps.writeTx.Get(); ok {
320- err = store.Consensus.ClearValidatorRangeChallengeCommitment(writeTx, tx.Arguments.Validator)
321 if err != nil {
322 return nil, stacktrace.Propagate(err)
323 }
324325- err = store.Consensus.SetValidatorRangeChallengeCompletion(writeTx, tx.Arguments.Validator, toHeight)
326 if err != nil {
327 return nil, stacktrace.Propagate(err)
328 }
···330 numProvenBlocks := toHeight - fromHeight + 1
331 repGain := numProvenBlocks * ReputationGainPerProvenBlock
332333- err = store.Consensus.ChangeValidatorReputation(writeTx, tx.Arguments.Validator, int64(repGain))
334 if err != nil {
335 return nil, stacktrace.Propagate(err)
336 }
···1819const CommitToChallengeMaxAgeInBlocks = 3
20const CommitToChallengeMinRange = 1000
21+const CommitToChallengeMaxRange = 5000
22+const CommitToChallengeTargetInterval = 2000
2300000024const ReputationGainPerProvenBlock = 100
25const ReputationEntropyLossPerBlock = 90
26···71 }, nil
72 }
730074 if tx.Arguments.ToHeight < tx.Arguments.FromHeight || tx.Arguments.ToHeight >= deps.workingHeight {
75 return &processResult{
76 Code: 4201,
···99 }, nil
100 }
101102+ toHeightBlockHeader, err := deps.blockHeaderGetter(tx.Arguments.ToHeight)
103+ if err != nil {
104 return &processResult{
105 Code: 4205,
106 Log: "unknown block in challenge range",
107 }, nil
108 }
109110+ if deps.readTx.Timestamp().Sub(toHeightBlockHeader.Time) > CommitToChallengeMaxAge {
111 return &processResult{
112 Code: 4206,
113 Log: "outdated challenge range",
114 }, nil
115 }
116117+ currentCompletion, err := store.Consensus.ValidatorRangeChallengeCompletion(deps.readTx, validatorPubKey.Bytes())
118 if err == nil {
119 if tx.Arguments.FromHeight <= int64(currentCompletion) {
120 return &processResult{
···146 proofHeight := int64(binary.BigEndian.Uint64(existenceProof.Key))
147148 expectedProofHeight := computeHeightToProveInRange(
149+ toHeightBlockHeader.LastCommitHash.Bytes(),
150+ validatorPubKey,
151 tx.Arguments.FromHeight,
152 tx.Arguments.ToHeight,
153 mo.None[int64]())
···159 }, nil
160 }
161162+ blockProofValid, err := deps.blockChallengeCoordinator.verifyBlockChallengeProof(int64(proofHeight), validatorPubKey.Address(), existenceProof.Value)
163 if err != nil {
164 return nil, stacktrace.Propagate(err)
165 }
···180 if writeTx, ok := deps.writeTx.Get(); ok {
181 err = store.Consensus.SetValidatorRangeChallengeCommitment(
182 writeTx,
183+ validatorPubKey.Bytes(),
184 uint64(tx.Arguments.FromHeight),
185 uint64(tx.Arguments.ToHeight),
186 uint64(proofHeight),
···201type CompleteChallengeArguments struct {
202 // This transaction is not signed. It is a no-op if it isn't valid and we don't really care if an entity is able to complete a challenge on behalf of another validator
203 // (that would be quite an achievement, on the level of a validator being able to find a collision for the committed root in order to fake not doing all the work)
204+ ValidatorPubKey PubKeyInArguments `json:"validator" refmt:"validator"`
205206 // this shall be a membership proof on the same tree the validator previously committed to,
207 // for the key deterministically-randomly determined by the last_commit_hash of the block _after_ the one
···228 }, nil
229 }
230231+ fromHeight, toHeight, provenHeight, includedOnHeight, committedTreeRoot, err := store.Consensus.ValidatorRangeChallengeCommitment(deps.readTx, tx.Arguments.ValidatorPubKey.Key)
232 if err != nil {
233 if errors.Is(err, store.ErrNoActiveChallengeCommitment) {
234 return &processResult{
···247 }, nil
248 }
249250+ blockAfterHeader, err := deps.blockHeaderGetter(int64(includedOnHeight + 1))
251+ if err != nil {
252 // this shouldn't happen unless the prover is submitting the completion on the same block as the commitment
253 return &processResult{
254 Code: 4302,
···256 }, nil
257 }
258259+ if deps.readTx.Timestamp().Sub(blockAfterHeader.Time) > CompleteChallengeMaxAge {
260 // validator must commit to a new challenge
261 return &processResult{
262 Code: 4303,
···276277 proofHeight := int64(binary.BigEndian.Uint64(existenceProof.Key))
278279+ pubKey, err := tx.Arguments.ValidatorPubKey.ToPubKey()
280+ if err != nil || proof.GetExist() == nil {
281+ return &processResult{
282+ Code: 4305,
283+ Log: "invalid public key",
284+ }, nil
285+ }
286+287 expectedProofHeight := computeHeightToProveInRange(
288+ blockAfterHeader.LastCommitHash.Bytes(),
289+ pubKey,
290 int64(fromHeight),
291 int64(toHeight),
292 mo.Some(int64(provenHeight)))
293294 if proofHeight != expectedProofHeight {
295 return &processResult{
296+ Code: 4306,
297 Log: "incorrect key proven",
298 }, nil
299 }
300301+ blockProofValid, err := deps.blockChallengeCoordinator.verifyBlockChallengeProof(int64(proofHeight), pubKey.Address(), existenceProof.Value)
302 if err != nil {
303 return nil, stacktrace.Propagate(err)
304 }
305 if !blockProofValid {
306 return &processResult{
307+ Code: 4307,
308 Log: "invalid proof",
309 }, nil
310 }
311312 if !ics23.VerifyMembership(ics23.IavlSpec, committedTreeRoot, proof, existenceProof.Key, existenceProof.Value) {
313 return &processResult{
314+ Code: 4308,
315 Log: "invalid proof",
316 }, nil
317 }
318319 if writeTx, ok := deps.writeTx.Get(); ok {
320+ err = store.Consensus.ClearValidatorRangeChallengeCommitment(writeTx, pubKey.Bytes())
321 if err != nil {
322 return nil, stacktrace.Propagate(err)
323 }
324325+ err = store.Consensus.SetValidatorRangeChallengeCompletion(writeTx, pubKey.Bytes(), toHeight)
326 if err != nil {
327 return nil, stacktrace.Propagate(err)
328 }
···330 numProvenBlocks := toHeight - fromHeight + 1
331 repGain := numProvenBlocks * ReputationGainPerProvenBlock
332333+ err = store.Consensus.ChangeValidatorReputation(writeTx, pubKey.Bytes(), int64(repGain))
334 if err != nil {
335 return nil, stacktrace.Propagate(err)
336 }