/
disputeChecker.go
181 lines (158 loc) · 5.18 KB
/
disputeChecker.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
package tracker
import (
"context"
"fmt"
"io/ioutil"
"math"
"math/big"
"strings"
"time"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
zapCommon "github.com/zapproject/pythia/common"
"github.com/zapproject/pythia/config"
"github.com/zapproject/pythia/contracts1"
"github.com/zapproject/pythia/rpc"
"github.com/zapproject/pythia/util"
)
var disputeLogger = util.NewLogger("tracker", "disputeChecker")
type disputeChecker struct {
lastCheckedBlock uint64
}
func (c *disputeChecker) String() string {
return "DisputeChecker"
}
// ValueCheckResult holds the details regarding the disputed value
type ValueCheckResult struct {
High, Low float64
WithinRange bool
Datapoints []float64
Times []time.Time
}
// CheckValueAtTime queries for the details regarding the disputed value
func CheckValueAtTime(reqID uint64, val *big.Int, at time.Time) *ValueCheckResult {
cfg := config.GetConfig()
//
BuildIndexTrackers()
//check the value in 5 places, spread over cfg.DisputeTimeDelta.Duration
var datapoints []float64
var times []time.Time
for i := 0; i < 5; i++ {
t := at.Add((time.Duration(i) - 2) * cfg.DisputeTimeDelta.Duration / 5)
fval, confidence := PSRValueForTime(int(reqID), t)
// fmt.Println("FVAL: ", fval, " - Confidence: ", confidence)
if confidence > 0.8 {
datapoints = append(datapoints, fval)
times = append(times, t)
}
}
if len(datapoints) == 0 {
return nil
}
min := math.MaxFloat64
max := 0.0
for _, dp := range datapoints {
if dp > max {
max = dp
}
if dp < min {
min = dp
}
}
min *= 1 - cfg.DisputeThreshold
max *= 1 + cfg.DisputeThreshold
bigF := new(big.Float)
bigF.SetInt(val)
floatVal, _ := bigF.Float64()
withinRange := (floatVal > min) && (floatVal < max)
return &ValueCheckResult{
Low: min,
High: max,
WithinRange: withinRange,
Datapoints: datapoints,
Times: times,
}
}
func (c *disputeChecker) Exec(ctx context.Context) error {
client := ctx.Value(zapCommon.ClientContextKey).(rpc.ETHClient)
header, err := client.HeaderByNumber(ctx, nil)
if err != nil {
return fmt.Errorf("failed to get latest eth block header: %v", err)
}
if c.lastCheckedBlock == 0 {
c.lastCheckedBlock = header.Number.Uint64()
}
toCheck := header.Number.Uint64()
const blockDelay = 100
if toCheck-c.lastCheckedBlock < blockDelay {
return nil
}
tokenAbi, err := abi.JSON(strings.NewReader(contracts1.ZapLibraryABI))
if err != nil {
return fmt.Errorf("failed to parse abi: %v", err)
}
contractAddress := ctx.Value(zapCommon.ContractAddress).(common.Address)
//just use nil for most of the variables, only using this object to call UnpackLog which only uses the abi
bar := bind.NewBoundContract(contractAddress, tokenAbi, nil, nil, nil)
checkUntil := toCheck - blockDelay
nonceSubmitID := tokenAbi.Events["NonceSubmitted"].ID
query := ethereum.FilterQuery{
FromBlock: big.NewInt(int64(c.lastCheckedBlock)),
ToBlock: big.NewInt(int64(checkUntil)),
Addresses: []common.Address{contractAddress},
Topics: [][]common.Hash{{nonceSubmitID}},
}
logs, err := client.FilterLogs(ctx, query)
if err != nil {
return fmt.Errorf("failed to filter eth logs: %v", err)
}
blockTimes := make(map[uint64]time.Time)
for _, l := range logs {
nonceSubmit := contracts1.ZapLibraryNonceSubmitted{}
err := bar.UnpackLog(&nonceSubmit, "NonceSubmitted", l)
if err != nil {
return fmt.Errorf("failed to unpack into object: %v", err)
}
blockTime, ok := blockTimes[l.BlockNumber]
if !ok {
header, err := client.HeaderByNumber(ctx, big.NewInt(int64(l.BlockNumber)))
if err != nil {
return fmt.Errorf("failed to get nonce block header: %v", err)
}
blockTime = time.Unix(int64(header.Time), 0)
blockTimes[l.BlockNumber] = blockTime
}
reqID := nonceSubmit.RequestId.Uint64()
result := CheckValueAtTime(reqID, nonceSubmit.Value, blockTime)
if result == nil {
disputeLogger.Warn("no value data for reqID %d at %s", reqID, blockTime)
continue
}
if !result.WithinRange {
s := fmt.Sprintf("suspected incorrect value for requestID %d at %s:\n", reqID, blockTime)
s += fmt.Sprintf("nearest values:\n")
for i, pt := range result.Datapoints {
s += fmt.Sprintf("\t%.0f, ", pt)
delta := blockTime.Sub(result.Times[i])
if delta > 0 {
s += fmt.Sprintf("%s before\n", delta.String())
} else {
s += fmt.Sprintf("%s after\n", (-delta).String())
}
}
s += fmt.Sprintf("value submitted by miner with address %s", nonceSubmit.Miner)
disputeLogger.Error(s)
filename := fmt.Sprintf("possible-dispute-%s.txt", blockTime)
err := ioutil.WriteFile(filename, []byte(s), 0655)
if err != nil {
disputeLogger.Error("failed to save dispute data to %s: %v", filename, err)
}
} else {
disputeLogger.Info("value of %s for requestid %d at %s appears to be within expected range", nonceSubmit.Value, reqID, blockTime.String())
}
}
c.lastCheckedBlock = checkUntil
return nil
}