/
settle_pending_claims.go
163 lines (137 loc) · 6.67 KB
/
settle_pending_claims.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
package keeper
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/pokt-network/smt"
"github.com/pokt-network/poktroll/telemetry"
prooftypes "github.com/pokt-network/poktroll/x/proof/types"
sessionkeeper "github.com/pokt-network/poktroll/x/session/keeper"
"github.com/pokt-network/poktroll/x/tokenomics/types"
)
const (
// TODO_BLOCKER/TODO_UPNEXT(@Olshansk): Implement this properly. Using a constant
// for "probabilistic proofs" is just a simple placeholder mechanism to get
// #359 over the finish line.
ProofRequiredComputeUnits = 100
)
// SettlePendingClaims settles all pending (i.e. expiring) claims.
// If a claim is expired and requires a proof and a proof IS available -> it's settled.
// If a claim is expired and requires a proof and a proof IS NOT available -> it's deleted.
// If a claim is expired and does NOT require a proof -> it's settled.
// Events are emitted for each claim that is settled or removed.
// On-chain Claims & Proofs are deleted after they're settled or expired to free up space.
func (k Keeper) SettlePendingClaims(ctx sdk.Context) (numClaimsSettled, numClaimsExpired uint64, err error) {
logger := k.Logger().With("method", "SettlePendingClaims")
isSuccessful := false
defer telemetry.EventSuccessCounter(
"claims_settled",
func() float32 { return float32(numClaimsSettled) },
func() bool { return isSuccessful },
)
defer telemetry.EventSuccessCounter(
"claims_expired",
func() float32 { return float32(numClaimsExpired) },
func() bool { return isSuccessful },
)
// TODO_BLOCKER(@Olshansk): Optimize this by indexing expiringClaims appropriately
// and only retrieving the expiringClaims that need to be settled rather than all
// of them and iterating through them one by one.
expiringClaims := k.getExpiringClaims(ctx)
blockHeight := ctx.BlockHeight()
logger.Info(fmt.Sprintf("found %d expiring claims at block height %d", len(expiringClaims), blockHeight))
for _, claim := range expiringClaims {
// Retrieve the number of compute units in the claim for the events emitted
root := (smt.MerkleRoot)(claim.GetRootHash())
claimComputeUnits := root.Sum()
sessionId := claim.SessionHeader.SessionId
// Using the probabilistic proofs approach, determine if this expiring
// claim required an on-chain proof
isProofRequiredForClaim := k.isProofRequiredForClaim(ctx, &claim)
if isProofRequiredForClaim {
_, isProofFound := k.proofKeeper.GetProof(ctx, sessionId, claim.SupplierAddress)
// If a proof is not found, the claim will expire and never be settled.
if !isProofFound {
// Emit an event that a claim has expired and being removed without being settled.
claimExpiredEvent := types.EventClaimExpired{
Claim: &claim,
ComputeUnits: claimComputeUnits,
}
if err := ctx.EventManager().EmitTypedEvent(&claimExpiredEvent); err != nil {
return 0, 0, err
}
// The claim & proof are no longer necessary, so there's no need for them
// to take up on-chain space.
k.proofKeeper.RemoveClaim(ctx, sessionId, claim.SupplierAddress)
numClaimsExpired++
continue
}
// NB: If a proof is found, it is valid because verification is done
// at the time of submission.
}
// Manage the mint & burn accounting for the claim.
if err := k.SettleSessionAccounting(ctx, &claim); err != nil {
logger.Error(fmt.Sprintf("error settling session accounting for claim %q: %v", claim.SessionHeader.SessionId, err))
return 0, 0, err
}
claimSettledEvent := types.EventClaimSettled{
Claim: &claim,
ComputeUnits: claimComputeUnits,
ProofRequired: isProofRequiredForClaim,
}
if err := ctx.EventManager().EmitTypedEvent(&claimSettledEvent); err != nil {
return 0, 0, err
}
// The claim & proof are no longer necessary, so there's no need for them
// to take up on-chain space.
k.proofKeeper.RemoveClaim(ctx, sessionId, claim.SupplierAddress)
// NB: We are calling `RemoveProof` of whether or not the proof was required
// to delete it from the state. It is okay for it to fail here if it doesn't exist.
k.proofKeeper.RemoveProof(ctx, sessionId, claim.SupplierAddress)
numClaimsSettled++
logger.Info(fmt.Sprintf("Successfully settled claim for session ID %q at block height %d", claim.SessionHeader.SessionId, blockHeight))
}
logger.Info(fmt.Sprintf("settled %d and expired %d claims at block height %d", numClaimsSettled, numClaimsExpired, blockHeight))
isSuccessful = true
return numClaimsSettled, numClaimsExpired, nil
}
// getExpiringClaims returns all claims that are expiring at the current block height.
// This is the height at which the proof window closes.
// If the proof window closes and a proof IS NOT required -> settle the claim.
// If the proof window closes and a proof IS required -> only settle it if a proof is available.
func (k Keeper) getExpiringClaims(ctx sdk.Context) (expiringClaims []prooftypes.Claim) {
blockHeight := ctx.BlockHeight()
// TODO_BLOCKER: query the on-chain governance parameter once available.
// `* 3` is just a random factor Olshansky added for now to make sure expiration
// doesn't happen immediately after a session's grace period is complete.
submitProofWindowEndHeight := sessionkeeper.GetSessionGracePeriodBlockCount() * 3
// TODO_BLOCKER(@Olshansk): Optimize this by indexing claims appropriately
// and only retrieving the claims that need to be settled rather than all
// of them and iterating through them one by one.
claims := k.proofKeeper.GetAllClaims(ctx)
// Loop over all claims we need to check for expiration
for _, claim := range claims {
expirationHeight := claim.SessionHeader.SessionEndBlockHeight + submitProofWindowEndHeight
if blockHeight >= expirationHeight {
expiringClaims = append(expiringClaims, claim)
}
}
// Return the actually expiring claims
return expiringClaims
}
// isProofRequiredForClaim checks if a proof is required for a claim.
// If it is not, the claim will be settled without a proof.
// If it is, the claim will only be settled if a valid proof is available.
// TODO_TECHDEBT(#419): Document safety assumptions of the probabilistic proofs mechanism.
func (k Keeper) isProofRequiredForClaim(_ sdk.Context, claim *prooftypes.Claim) bool {
// NB: Assumption that claim is non-nil and has a valid root sum because it
// is retrieved from the store and validated, on-chain, at time of creation.
root := (smt.MerkleRoot)(claim.GetRootHash())
claimComputeUnits := root.Sum()
// TODO_BLOCKER(#419): This is just VERY BASIC placeholder logic to have something
// in place while we implement proper probabilistic proofs. If you're reading it,
// do not overthink it and look at the documents linked in #419.
if claimComputeUnits < ProofRequiredComputeUnits {
return false
}
return true
}