code.vegaprotocol.io/vega@v0.79.0/wallet/api/spam/pow.go (about)

     1  // Copyright (C) 2023 Gobalsky Labs Limited
     2  //
     3  // This program is free software: you can redistribute it and/or modify
     4  // it under the terms of the GNU Affero General Public License as
     5  // published by the Free Software Foundation, either version 3 of the
     6  // License, or (at your option) any later version.
     7  //
     8  // This program is distributed in the hope that it will be useful,
     9  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    10  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11  // GNU Affero General Public License for more details.
    12  //
    13  // You should have received a copy of the GNU Affero General Public License
    14  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    15  
    16  package spam
    17  
    18  import (
    19  	vgcrypto "code.vegaprotocol.io/vega/libs/crypto"
    20  	commandspb "code.vegaprotocol.io/vega/protos/vega/commands/v1"
    21  	"code.vegaprotocol.io/vega/wallet/api"
    22  	"code.vegaprotocol.io/vega/wallet/api/node/types"
    23  	nodetypes "code.vegaprotocol.io/vega/wallet/api/node/types"
    24  )
    25  
    26  type txCounter struct {
    27  	// slice of maps from pubKey->nTxnSent where size is the numbers of past
    28  	// blocks a pow is valid for the transaction count for party p sent in
    29  	// block b is store[b%size][p].
    30  	store []map[string]uint32
    31  	size  int64
    32  
    33  	lastBlock int64 // the highest last block we've counted against.
    34  }
    35  
    36  // add increments the counter for the number of times the public key has sent in
    37  // a transaction with pow against a particular height.
    38  func (t *txCounter) add(pubKey string, state types.PoWBlockState) (uint32, error) {
    39  	height := int64(state.BlockHeight)
    40  	if height <= t.lastBlock-t.size {
    41  		return 0, api.ErrBlockHeightTooHistoric
    42  	}
    43  
    44  	// our new height might be more than 1 bigger than the lastBlock we sent a transaction for,
    45  	// so we need to scrub all those heights in between because we sent 0 transactions in those.
    46  	for i := t.lastBlock + 1; i <= height; i++ {
    47  		t.store[i%t.size] = nil
    48  	}
    49  
    50  	i := height % t.size
    51  	if t.store[i] == nil {
    52  		t.store[i] = map[string]uint32{}
    53  	}
    54  
    55  	// If our stored height is less than the current block state either we've
    56  	// restarted the wallet, and we can now pick up the current amount, or some
    57  	// external transaction were sent outside our view, so we take the biggest
    58  	// value
    59  	if t.store[i][pubKey] < uint32(state.TransactionsSeen) {
    60  		t.store[i][pubKey] = uint32(state.TransactionsSeen)
    61  	}
    62  	t.store[i][pubKey]++
    63  
    64  	if height > t.lastBlock {
    65  		t.lastBlock = height
    66  	}
    67  	return t.store[i][pubKey], nil
    68  }
    69  
    70  func (t *txCounter) resize(n int64) {
    71  	if n == t.size {
    72  		return
    73  	}
    74  
    75  	if t.size == 0 {
    76  		t.store = make([]map[string]uint32, n)
    77  		t.size = n
    78  		return
    79  	}
    80  
    81  	// make a new slice
    82  	newStore := make([]map[string]uint32, n)
    83  
    84  	// transfer maps from old slice to new
    85  	nTransfer := n
    86  	if t.size < nTransfer || t.lastBlock < nTransfer {
    87  		nTransfer = t.size
    88  	}
    89  
    90  	for i := int64(0); i < nTransfer; i++ {
    91  		offset := t.lastBlock - i
    92  		newStore[offset%n] = t.store[offset%t.size]
    93  	}
    94  	t.size = n
    95  	t.store = newStore
    96  }
    97  
    98  func (s *Handler) getCounterForChain(chainID string) *txCounter {
    99  	if _, ok := s.counters[chainID]; !ok {
   100  		s.counters[chainID] = &txCounter{}
   101  	}
   102  	return s.counters[chainID]
   103  }
   104  
   105  // GenerateProofOfWork Generate returns a proof-of-work with difficult that
   106  // respects the history of transactions sent in against a particular block.
   107  func (s *Handler) GenerateProofOfWork(pubKey string, st *nodetypes.SpamStatistics) (*commandspb.ProofOfWork, error) {
   108  	s.mu.Lock()
   109  	counter := s.getCounterForChain(st.ChainID)
   110  	blockState := st.PoW.PowBlockStates[0]
   111  
   112  	// If the network parameter for past blocks has changed we need to tell the
   113  	// counter, so it can tell us if we're using a now historic block.
   114  	counter.resize(int64(st.PoW.PastBlocks))
   115  	nSent, err := counter.add(pubKey, blockState)
   116  	s.mu.Unlock()
   117  	if err != nil {
   118  		return nil, err
   119  	}
   120  
   121  	nPerBlock := blockState.TxPerBlock
   122  
   123  	// now work out the pow difficulty
   124  	difficulty := blockState.Difficulty
   125  	if uint64(nSent) > nPerBlock {
   126  		if !blockState.IncreasingDifficulty {
   127  			return nil, api.ErrTransactionsPerBlockLimitReached
   128  		}
   129  		// how many times have we hit the limit
   130  		difficulty += uint64(nSent) / nPerBlock
   131  	}
   132  
   133  	tid := vgcrypto.RandomHash()
   134  	powNonce, _, err := vgcrypto.PoW(blockState.BlockHash, tid, uint(difficulty), vgcrypto.Sha3)
   135  	if err != nil {
   136  		return nil, err
   137  	}
   138  
   139  	return &commandspb.ProofOfWork{
   140  		Tid:   tid,
   141  		Nonce: powNonce,
   142  	}, nil
   143  }