github.com/electroneum/electroneum-sc@v0.0.0-20230105223411-3bc1d078281e/consensus/istanbul/backend/snapshot.go (about) 1 // Copyright 2017 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package backend 18 19 import ( 20 "bytes" 21 "encoding/json" 22 23 "github.com/electroneum/electroneum-sc/common" 24 "github.com/electroneum/electroneum-sc/consensus/istanbul" 25 "github.com/electroneum/electroneum-sc/consensus/istanbul/validator" 26 "github.com/electroneum/electroneum-sc/ethdb" 27 ) 28 29 const ( 30 dbKeySnapshotPrefix = "istanbul-snapshot" 31 ) 32 33 // Vote represents a single vote that an authorized validator made to modify the 34 // list of authorizations. 35 type Vote struct { 36 Validator common.Address `json:"validator"` // Authorized validator that cast this vote 37 Block uint64 `json:"block"` // Block number the vote was cast in (expire old votes) 38 Address common.Address `json:"address"` // Account being voted on to change its authorization 39 Authorize bool `json:"authorize"` // Whether to authorize or deauthorize the voted account 40 } 41 42 // Tally is a simple vote tally to keep the current score of votes. Votes that 43 // go against the proposal aren't counted since it's equivalent to not voting. 44 type Tally struct { 45 Authorize bool `json:"authorize"` // Whether the vote it about authorizing or kicking someone 46 Votes int `json:"votes"` // Number of votes until now wanting to pass the proposal 47 } 48 49 // Snapshot is the state of the authorization voting at a given point in time. 50 type Snapshot struct { 51 Epoch uint64 // The number of blocks after which to checkpoint and reset the pending votes 52 53 Number uint64 // Block number where the snapshot was created 54 Hash common.Hash // Block hash where the snapshot was created 55 Votes []*Vote // List of votes cast in chronological order 56 Tally map[common.Address]Tally // Current vote tally to avoid recalculating 57 ValSet istanbul.ValidatorSet // Set of authorized validators at this moment 58 } 59 60 // newSnapshot create a new snapshot with the specified startup parameters. This 61 // method does not initialize the set of recent validators, so only ever use if for 62 // the genesis block. 63 func newSnapshot(epoch uint64, number uint64, hash common.Hash, valSet istanbul.ValidatorSet) *Snapshot { 64 snap := &Snapshot{ 65 Epoch: epoch, 66 Number: number, 67 Hash: hash, 68 ValSet: valSet, 69 Tally: make(map[common.Address]Tally), 70 } 71 return snap 72 } 73 74 // loadSnapshot loads an existing snapshot from the database. 75 func loadSnapshot(epoch uint64, db ethdb.Database, hash common.Hash) (*Snapshot, error) { 76 blob, err := db.Get(append([]byte(dbKeySnapshotPrefix), hash[:]...)) 77 if err != nil { 78 return nil, err 79 } 80 snap := new(Snapshot) 81 if err := json.Unmarshal(blob, snap); err != nil { 82 return nil, err 83 } 84 snap.Epoch = epoch 85 86 return snap, nil 87 } 88 89 // store inserts the snapshot into the database. 90 func (s *Snapshot) store(db ethdb.Database) error { 91 blob, err := json.Marshal(s) 92 if err != nil { 93 return err 94 } 95 return db.Put(append([]byte(dbKeySnapshotPrefix), s.Hash[:]...), blob) 96 } 97 98 // copy creates a deep copy of the snapshot, though not the individual votes. 99 func (s *Snapshot) copy() *Snapshot { 100 cpy := &Snapshot{ 101 Epoch: s.Epoch, 102 Number: s.Number, 103 Hash: s.Hash, 104 ValSet: s.ValSet.Copy(), 105 Votes: make([]*Vote, len(s.Votes)), 106 Tally: make(map[common.Address]Tally), 107 } 108 109 for address, tally := range s.Tally { 110 cpy.Tally[address] = tally 111 } 112 copy(cpy.Votes, s.Votes) 113 114 return cpy 115 } 116 117 // checkVote return whether it's a valid vote 118 func (s *Snapshot) checkVote(address common.Address, authorize bool) bool { 119 _, validator := s.ValSet.GetByAddress(address) 120 return (validator != nil && !authorize) || (validator == nil && authorize) 121 } 122 123 // cast adds a new vote into the tally. 124 func (s *Snapshot) cast(address common.Address, authorize bool) bool { 125 // Ensure the vote is meaningful 126 if !s.checkVote(address, authorize) { 127 return false 128 } 129 // Cast the vote into an existing or new tally 130 if old, ok := s.Tally[address]; ok { 131 old.Votes++ 132 s.Tally[address] = old 133 } else { 134 s.Tally[address] = Tally{Authorize: authorize, Votes: 1} 135 } 136 return true 137 } 138 139 // uncast removes a previously cast vote from the tally. 140 func (s *Snapshot) uncast(address common.Address, authorize bool) bool { 141 // If there's no tally, it's a dangling vote, just drop 142 tally, ok := s.Tally[address] 143 if !ok { 144 return false 145 } 146 // Ensure we only revert counted votes 147 if tally.Authorize != authorize { 148 return false 149 } 150 // Otherwise revert the vote 151 if tally.Votes > 1 { 152 tally.Votes-- 153 s.Tally[address] = tally 154 } else { 155 delete(s.Tally, address) 156 } 157 return true 158 } 159 160 // validators retrieves the list of authorized validators in ascending order. 161 func (s *Snapshot) validators() []common.Address { 162 validators := make([]common.Address, 0, s.ValSet.Size()) 163 for _, validator := range s.ValSet.List() { 164 validators = append(validators, validator.Address()) 165 } 166 for i := 0; i < len(validators); i++ { 167 for j := i + 1; j < len(validators); j++ { 168 if bytes.Compare(validators[i][:], validators[j][:]) > 0 { 169 validators[i], validators[j] = validators[j], validators[i] 170 } 171 } 172 } 173 return validators 174 } 175 176 type snapshotJSON struct { 177 Epoch uint64 `json:"epoch"` 178 Number uint64 `json:"number"` 179 Hash common.Hash `json:"hash"` 180 Votes []*Vote `json:"votes"` 181 Tally map[common.Address]Tally `json:"tally"` 182 183 // for validator set 184 Validators []common.Address `json:"validators"` 185 Policy istanbul.ProposerPolicyId `json:"policy"` 186 } 187 188 func (s *Snapshot) toJSONStruct() *snapshotJSON { 189 return &snapshotJSON{ 190 Epoch: s.Epoch, 191 Number: s.Number, 192 Hash: s.Hash, 193 Votes: s.Votes, 194 Tally: s.Tally, 195 Validators: s.validators(), 196 Policy: s.ValSet.Policy().Id, 197 } 198 } 199 200 // Unmarshal from a json byte array 201 func (s *Snapshot) UnmarshalJSON(b []byte) error { 202 var j snapshotJSON 203 if err := json.Unmarshal(b, &j); err != nil { 204 return err 205 } 206 207 s.Epoch = j.Epoch 208 s.Number = j.Number 209 s.Hash = j.Hash 210 s.Votes = j.Votes 211 s.Tally = j.Tally 212 213 // Setting the By function to ValidatorSortByStringFunc should be fine, as the validator do not change only the order changes 214 pp := istanbul.NewProposerPolicyByIdAndSortFunc(j.Policy, istanbul.ValidatorSortByString()) 215 s.ValSet = validator.NewSet(j.Validators, pp) 216 return nil 217 } 218 219 // Marshal to a json byte array 220 func (s *Snapshot) MarshalJSON() ([]byte, error) { 221 j := s.toJSONStruct() 222 return json.Marshal(j) 223 }