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  }