This repository has been archived by the owner on Jan 5, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2
/
prophecy.go
150 lines (130 loc) · 5.76 KB
/
prophecy.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
package types
import (
"encoding/json"
"github.com/cosmos/cosmos-sdk/x/staking"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// DefaultConsensusNeeded defines the default consensus value required for a
// prophecy to be finalized
const DefaultConsensusNeeded float64 = 0.7
// Prophecy is a struct that contains all the metadata of an oracle ritual.
// Claims are indexed by the claim's validator bech32 address and by the claim's json value to allow
// for constant lookup times for any validation/verifiation checks of duplicate claims
// Each transaction, pending potential results are also calculated, stored and indexed by their byte result
// to allow discovery of consensus on any the result in constant time without having to sort or run
// through the list of claims to find the one with highest consensus
type Prophecy struct {
ID string `json:"id"`
Status Status `json:"status"`
//WARNING: Mappings are nondeterministic in Amino, an so iterating over them could result in consensus failure. New code should not iterate over the below 2 mappings.
ClaimValidators map[string][]sdk.ValAddress `json:"claim_validators"` //This is a mapping from a claim to the list of validators that made that claim.
ValidatorClaims map[string]string `json:"validator_claims"` //This is a mapping from a validator bech32 address to their claim
}
// DBProphecy is what the prophecy becomes when being saved to the database. Tendermint/Amino does not support maps so we must serialize those variables into bytes.
type DBProphecy struct {
ID string `json:"id"`
Status Status `json:"status"`
ClaimValidators []byte `json:"claim_validators"` //This is a mapping from a claim to the list of validators that made that claim
ValidatorClaims []byte `json:"validator_claims"` //This is a mapping from a validator bech32 address to their claim
}
// SerializeForDB serializes a prophecy into a DBProphecy
// TODO: Using gob here may mean that different tendermint clients in different languages may serialize/store
// prophecies in their db in different ways - check with @codereviewer if this is ok or if it introduces a risk of creating forks.
// Or maybe using a slower json serializer or Amino:JSON would be ok
func (prophecy Prophecy) SerializeForDB() (DBProphecy, error) {
claimValidators, err := json.Marshal(prophecy.ClaimValidators)
if err != nil {
return DBProphecy{}, err
}
validatorClaims, err := json.Marshal(prophecy.ValidatorClaims)
if err != nil {
return DBProphecy{}, err
}
return DBProphecy{
ID: prophecy.ID,
Status: prophecy.Status,
ClaimValidators: claimValidators,
ValidatorClaims: validatorClaims,
}, nil
}
// DeserializeFromDB deserializes a DBProphecy into a prophecy
func (dbProphecy DBProphecy) DeserializeFromDB() (Prophecy, error) {
var claimValidators map[string][]sdk.ValAddress
err := json.Unmarshal(dbProphecy.ClaimValidators, &claimValidators)
if err != nil {
return Prophecy{}, err
}
var validatorClaims map[string]string
err = json.Unmarshal(dbProphecy.ValidatorClaims, &validatorClaims)
if err != nil {
return Prophecy{}, err
}
return Prophecy{
ID: dbProphecy.ID,
Status: dbProphecy.Status,
ClaimValidators: claimValidators,
ValidatorClaims: validatorClaims,
}, nil
}
// AddClaim adds a given claim to this prophecy
func (prophecy Prophecy) AddClaim(validator sdk.ValAddress, claim string) {
claimValidators := prophecy.ClaimValidators[claim]
prophecy.ClaimValidators[claim] = append(claimValidators, validator)
validatorBech32 := validator.String()
prophecy.ValidatorClaims[validatorBech32] = claim
}
// FindHighestClaim looks through all the existing claims on a given prophecy. It adds up the total power across
// all claims and returns the highest claim, power for that claim, and total power claimed on the prophecy overall.
func (prophecy Prophecy) FindHighestClaim(ctx sdk.Context, stakeKeeper staking.Keeper) (string, int64, int64) {
validators := stakeKeeper.GetBondedValidatorsByPower(ctx)
//Index the validators by address for looking when scanning through claims
validatorsByAddress := make(map[string]staking.Validator)
for _, validator := range validators {
validatorsByAddress[validator.OperatorAddress.String()] = validator
}
totalClaimsPower := int64(0)
highestClaimPower := int64(-1)
highestClaim := ""
for claim, validatorAddrs := range prophecy.ClaimValidators {
claimPower := int64(0)
for _, validatorAddr := range validatorAddrs {
validator, found := validatorsByAddress[validatorAddr.String()]
if found {
// Note: If claim validator is not found in the current validator set, we assume it is no longer
// an active validator and so can silently ignore it's claim and no longer count it towards total power.
claimPower += validator.GetConsensusPower()
}
}
totalClaimsPower += claimPower
if claimPower > highestClaimPower {
highestClaimPower = claimPower
highestClaim = claim
}
}
return highestClaim, highestClaimPower, totalClaimsPower
}
// NewProphecy returns a new Prophecy, initialized in pending status with an initial claim
func NewProphecy(id string) Prophecy {
return Prophecy{
ID: id,
Status: NewStatus(PendingStatusText, ""),
ClaimValidators: make(map[string][]sdk.ValAddress),
ValidatorClaims: make(map[string]string),
}
}
// NewEmptyProphecy returns a blank prophecy, used with errors
func NewEmptyProphecy() Prophecy {
return NewProphecy("")
}
// Status is a struct that contains the status of a given prophecy
type Status struct {
Text StatusText `json:"text"`
FinalClaim string `json:"final_claim"`
}
// NewStatus returns a new Status with the given data contained
func NewStatus(text StatusText, finalClaim string) Status {
return Status{
Text: text,
FinalClaim: finalClaim,
}
}