github.com/InjectiveLabs/sdk-go@v1.53.0/chain/peggy/types/types.go (about) 1 package types 2 3 import ( 4 "encoding/binary" 5 "fmt" 6 "math" 7 "math/big" 8 "sort" 9 "strconv" 10 "strings" 11 12 "cosmossdk.io/errors" 13 sdkmath "cosmossdk.io/math" 14 sdk "github.com/cosmos/cosmos-sdk/types" 15 "github.com/ethereum/go-ethereum/accounts/abi" 16 gethcommon "github.com/ethereum/go-ethereum/common" 17 "github.com/ethereum/go-ethereum/crypto" 18 ) 19 20 // UInt64FromBytes create uint from binary big endian representation 21 func UInt64FromBytes(s []byte) uint64 { 22 return binary.BigEndian.Uint64(s) 23 } 24 25 // UInt64Bytes uses the SDK byte marshaling to encode a uint64 26 func UInt64Bytes(n uint64) []byte { 27 return sdk.Uint64ToBigEndian(n) 28 } 29 30 // UInt64FromString to parse out a uint64 for a nonce 31 func UInt64FromString(s string) (uint64, error) { 32 return strconv.ParseUint(s, 10, 64) 33 } 34 35 ////////////////////////////////////// 36 // BRIDGE VALIDATOR(S) // 37 ////////////////////////////////////// 38 39 // ValidateBasic performs stateless checks on validity 40 func (b *BridgeValidator) ValidateBasic() error { 41 if b.Power == 0 { 42 return errors.Wrap(ErrEmpty, "power") 43 } 44 if err := ValidateEthAddress(b.EthereumAddress); err != nil { 45 return errors.Wrap(err, "ethereum address") 46 } 47 if b.EthereumAddress == "" { 48 return errors.Wrap(ErrEmpty, "address") 49 } 50 return nil 51 } 52 53 // BridgeValidators is the sorted set of validator data for Ethereum bridge MultiSig set 54 type BridgeValidators []*BridgeValidator 55 56 // Sort sorts the validators by power 57 func (b BridgeValidators) Sort() { 58 sort.SliceStable(b, func(i, j int) bool { 59 if b[i].Power == b[j].Power { 60 // Secondary sort on eth address in case powers are equal 61 return EthAddrLessThan(b[i].EthereumAddress, b[j].EthereumAddress) 62 } 63 return b[i].Power > b[j].Power 64 }) 65 } 66 67 // PowerDiff returns the difference in power between two bridge validator sets 68 // note this is Gravity bridge power *not* Cosmos voting power. Cosmos voting 69 // power is based on the absolute number of tokens in the staking pool at any given 70 // time Gravity bridge power is normalized using the equation. 71 // 72 // validators cosmos voting power / total cosmos voting power in this block = gravity bridge power / u32_max 73 // 74 // As an example if someone has 52% of the Cosmos voting power when a validator set is created their Gravity 75 // bridge voting power is u32_max * .52 76 // 77 // Normalized voting power dramatically reduces how often we have to produce new validator set updates. For example 78 // if the total on chain voting power increases by 1% due to inflation, we shouldn't have to generate a new validator 79 // set, after all the validators retained their relative percentages during inflation and normalized Gravity bridge power 80 // shows no difference. 81 func (b BridgeValidators) PowerDiff(c BridgeValidators) float64 { 82 powers := map[string]int64{} 83 // loop over b and initialize the map with their powers 84 for _, bv := range b { 85 powers[bv.EthereumAddress] = int64(bv.Power) 86 } 87 88 // subtract c powers from powers in the map, initializing 89 // uninitialized keys with negative numbers 90 for _, bv := range c { 91 if val, ok := powers[bv.EthereumAddress]; ok { 92 powers[bv.EthereumAddress] = val - int64(bv.Power) 93 } else { 94 powers[bv.EthereumAddress] = -int64(bv.Power) 95 } 96 } 97 98 var delta float64 99 for _, v := range powers { 100 // NOTE: we care about the absolute value of the changes 101 delta += math.Abs(float64(v)) 102 } 103 104 return math.Abs(delta / float64(math.MaxUint32)) 105 } 106 107 // TotalPower returns the total power in the bridge validator set 108 func (b BridgeValidators) TotalPower() (out uint64) { 109 for _, v := range b { 110 out += v.Power 111 } 112 return 113 } 114 115 // HasDuplicates returns true if there are duplicates in the set 116 func (b BridgeValidators) HasDuplicates() bool { 117 m := make(map[string]struct{}, len(b)) 118 for i := range b { 119 m[b[i].EthereumAddress] = struct{}{} 120 } 121 return len(m) != len(b) 122 } 123 124 // GetPowers returns only the power values for all members 125 func (b BridgeValidators) GetPowers() []uint64 { 126 r := make([]uint64, len(b)) 127 for i := range b { 128 r[i] = b[i].Power 129 } 130 return r 131 } 132 133 // ValidateBasic performs stateless checks 134 func (b BridgeValidators) ValidateBasic() error { 135 if len(b) == 0 { 136 return ErrEmpty 137 } 138 for i := range b { 139 if err := b[i].ValidateBasic(); err != nil { 140 return errors.Wrapf(err, "member %d", i) 141 } 142 } 143 if b.HasDuplicates() { 144 return errors.Wrap(ErrDuplicate, "addresses") 145 } 146 return nil 147 } 148 149 // NewValset returns a new valset 150 func NewValset(nonce, height uint64, members BridgeValidators, rewardAmount sdkmath.Int, rewardToken gethcommon.Address) *Valset { 151 members.Sort() 152 mem := make([]*BridgeValidator, 0) 153 for _, val := range members { 154 mem = append(mem, val) 155 } 156 return &Valset{Nonce: nonce, Members: mem, Height: height, RewardAmount: rewardAmount, RewardToken: rewardToken.Hex()} 157 } 158 159 // GetCheckpoint returns the checkpoint hash 160 func (v Valset) GetCheckpoint(peggyIDstring string) gethcommon.Hash { 161 // error case here should not occur outside of testing since the above is a constant 162 contractAbi, abiErr := abi.JSON(strings.NewReader(ValsetCheckpointABIJSON)) 163 if abiErr != nil { 164 panic("Bad ABI constant!") 165 } 166 167 // the contract argument is not a arbitrary length array but a fixed length 32 byte 168 // array, therefore we have to utf8 encode the string (the default in this case) and 169 // then copy the variable length encoded data into a fixed length array. This function 170 // will panic if peggyId is too long to fit in 32 bytes 171 peggyID, err := strToFixByteArray(peggyIDstring) 172 if err != nil { 173 panic(err) 174 } 175 // this should never happen, unless an invalid paramater value has been set by the chain 176 err = ValidateEthAddress(v.RewardToken) 177 if err != nil { 178 panic(err) 179 } 180 rewardToken := gethcommon.HexToAddress(v.RewardToken) 181 182 if v.RewardAmount.BigInt() == nil { 183 // this must be programmer error 184 panic("Invalid reward amount passed in valset GetCheckpoint!") 185 } 186 rewardAmount := v.RewardAmount.BigInt() 187 188 checkpointBytes := []uint8("checkpoint") 189 var checkpoint [32]uint8 190 copy(checkpoint[:], checkpointBytes) 191 192 memberAddresses := make([]gethcommon.Address, len(v.Members)) 193 convertedPowers := make([]*big.Int, len(v.Members)) 194 for i, m := range v.Members { 195 memberAddresses[i] = gethcommon.HexToAddress(m.EthereumAddress) 196 convertedPowers[i] = big.NewInt(int64(m.Power)) 197 } 198 // the word 'checkpoint' needs to be the same as the 'name' above in the checkpointAbiJson 199 // but other than that it's a constant that has no impact on the output. This is because 200 // it gets encoded as a function name which we must then discard. 201 bytes, packErr := contractAbi.Pack("checkpoint", peggyID, checkpoint, big.NewInt(int64(v.Nonce)), memberAddresses, convertedPowers, rewardAmount, rewardToken) 202 // this should never happen outside of test since any case that could crash on encoding 203 // should be filtered above. 204 if packErr != nil { 205 panic(fmt.Sprintf("Error packing checkpoint! %s/n", packErr)) 206 } 207 208 // we hash the resulting encoded bytes discarding the first 4 bytes these 4 bytes are the constant 209 // method name 'checkpoint'. If you where to replace the checkpoint constant in this code you would 210 // then need to adjust how many bytes you truncate off the front to get the output of abi.encode() 211 hash := crypto.Keccak256Hash(bytes[4:]) 212 return hash 213 } 214 215 // This interface is implemented by all the types that are used 216 // to create transactions on Ethereum that are signed by validators. 217 // The naming here could be improved. 218 type EthereumSigned interface { 219 GetCheckpoint(peggyIDstring string) gethcommon.Hash 220 } 221 222 // WithoutEmptyMembers returns a new Valset without member that have 0 power or an empty Ethereum address. 223 func (v *Valset) WithoutEmptyMembers() *Valset { 224 if v == nil { 225 return nil 226 } 227 r := Valset{Nonce: v.Nonce, Members: make([]*BridgeValidator, 0, len(v.Members))} 228 for i := range v.Members { 229 if err := v.Members[i].ValidateBasic(); err == nil { 230 r.Members = append(r.Members, v.Members[i]) 231 } 232 } 233 return &r 234 } 235 236 // Valsets is a collection of valset 237 type Valsets []*Valset 238 239 func (v Valsets) Len() int { 240 return len(v) 241 } 242 243 func (v Valsets) Less(i, j int) bool { 244 return v[i].Nonce > v[j].Nonce 245 } 246 247 func (v Valsets) Swap(i, j int) { 248 v[i], v[j] = v[j], v[i] 249 } 250 251 // GetFees returns the total fees contained within a given batch 252 func (b OutgoingTxBatch) GetFees() sdkmath.Int { 253 sum := sdkmath.ZeroInt() 254 for _, t := range b.Transactions { 255 sum.Add(t.Erc20Fee.Amount) 256 } 257 return sum 258 }