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 }