github.com/ZuluSpl0it/Sia@v1.3.7/modules/transactionpool/transactionpool.go (about) 1 package transactionpool 2 3 import ( 4 "errors" 5 6 "github.com/NebulousLabs/demotemutex" 7 "github.com/coreos/bbolt" 8 9 "github.com/NebulousLabs/Sia/crypto" 10 "github.com/NebulousLabs/Sia/modules" 11 "github.com/NebulousLabs/Sia/persist" 12 "github.com/NebulousLabs/Sia/sync" 13 "github.com/NebulousLabs/Sia/types" 14 ) 15 16 var ( 17 errNilCS = errors.New("transaction pool cannot initialize with a nil consensus set") 18 errNilGateway = errors.New("transaction pool cannot initialize with a nil gateway") 19 ) 20 21 type ( 22 // ObjectID is the ID of an object such as siacoin output and file 23 // contracts, and is used to see if there is are conflicts or overlaps within 24 // the transaction pool. 25 ObjectID crypto.Hash 26 // TransactionSetID is the hash of a transaction set. 27 TransactionSetID crypto.Hash 28 29 // The TransactionPool tracks incoming transactions, accepting them or 30 // rejecting them based on internal criteria such as fees and unconfirmed 31 // double spends. 32 TransactionPool struct { 33 // Dependencies of the transaction pool. 34 consensusSet modules.ConsensusSet 35 gateway modules.Gateway 36 37 // To prevent double spends in the unconfirmed transaction set, the 38 // transaction pool keeps a list of all objects that have either been 39 // created or consumed by the current unconfirmed transaction pool. All 40 // transactions with overlaps are rejected. This model is 41 // over-aggressive - one transaction set may create an object that 42 // another transaction set spends. This is done to minimize the 43 // computation and memory load on the transaction pool. Dependent 44 // transactions should be lumped into a single transaction set. 45 // 46 // transactionSetDiffs map form a transaction set id to the set of 47 // diffs that resulted from the transaction set. 48 knownObjects map[ObjectID]TransactionSetID 49 subscriberSets map[TransactionSetID]*modules.UnconfirmedTransactionSet 50 transactionHeights map[types.TransactionID]types.BlockHeight 51 transactionSets map[TransactionSetID][]types.Transaction 52 transactionSetDiffs map[TransactionSetID]*modules.ConsensusChange 53 transactionListSize int 54 55 // Variables related to the blockchain. 56 blockHeight types.BlockHeight 57 recentMedians []types.Currency 58 recentMedianFee types.Currency // SC per byte 59 60 // The consensus change index tracks how many consensus changes have 61 // been sent to the transaction pool. When a new subscriber joins the 62 // transaction pool, all prior consensus changes are sent to the new 63 // subscriber. 64 subscribers []modules.TransactionPoolSubscriber 65 66 // Utilities. 67 db *persist.BoltDatabase 68 dbTx *bolt.Tx 69 log *persist.Logger 70 mu demotemutex.DemoteMutex 71 tg sync.ThreadGroup 72 persistDir string 73 } 74 ) 75 76 // New creates a transaction pool that is ready to receive transactions. 77 func New(cs modules.ConsensusSet, g modules.Gateway, persistDir string) (*TransactionPool, error) { 78 // Check that the input modules are non-nil. 79 if cs == nil { 80 return nil, errNilCS 81 } 82 if g == nil { 83 return nil, errNilGateway 84 } 85 86 // Initialize a transaction pool. 87 tp := &TransactionPool{ 88 consensusSet: cs, 89 gateway: g, 90 91 knownObjects: make(map[ObjectID]TransactionSetID), 92 subscriberSets: make(map[TransactionSetID]*modules.UnconfirmedTransactionSet), 93 transactionHeights: make(map[types.TransactionID]types.BlockHeight), 94 transactionSets: make(map[TransactionSetID][]types.Transaction), 95 transactionSetDiffs: make(map[TransactionSetID]*modules.ConsensusChange), 96 97 persistDir: persistDir, 98 } 99 100 // Open the tpool database. 101 err := tp.initPersist() 102 if err != nil { 103 return nil, err 104 } 105 106 // Register RPCs 107 g.RegisterRPC("RelayTransactionSet", tp.relayTransactionSet) 108 tp.tg.OnStop(func() { 109 tp.gateway.UnregisterRPC("RelayTransactionSet") 110 }) 111 return tp, nil 112 } 113 114 // Close releases any resources held by the transaction pool, stopping all of 115 // its worker threads. 116 func (tp *TransactionPool) Close() error { 117 return tp.tg.Stop() 118 } 119 120 // FeeEstimation returns an estimation for what fee should be applied to 121 // transactions. It returns a minimum and maximum estimated fee per transaction 122 // byte. 123 func (tp *TransactionPool) FeeEstimation() (min, max types.Currency) { 124 err := tp.tg.Add() 125 if err != nil { 126 return 127 } 128 defer tp.tg.Done() 129 tp.mu.Lock() 130 defer tp.mu.Unlock() 131 132 // Use three methods to determine an acceptable fee, and then take the 133 // largest result of the two methods. The first method checks the historic 134 // blocks, to make sure that we don't under-estimate the number of fees 135 // needed in the event that we just purged the tpool. 136 // 137 // The second method looks at the existing tpool. Sudden congestion won't be 138 // represented on the blockchain right away, but should be immediately 139 // influencing how you set fees. Using the current tpool fullness will help 140 // pick appropriate fees in the event of sudden congestion. 141 // 142 // The third method just has hardcoded minimums as a sanity check. In the 143 // event of empty blocks, there should still be some fees being added to the 144 // chain. 145 146 // Set the minimum fee to the numbers recommended by the blockchain. 147 min = tp.recentMedianFee 148 max = tp.recentMedianFee.Mul64(maxMultiplier) 149 150 // Method two: use 'requiredFeesToExtendPool'. 151 required := tp.requiredFeesToExtendTpool() 152 requiredMin := required.MulFloat(minExtendMultiplier) // Clear the local requirement by a little bit. 153 requiredMax := requiredMin.MulFloat(maxMultiplier) // Clear the local requirement by a lot. 154 if min.Cmp(requiredMin) < 0 { 155 min = requiredMin 156 } 157 if max.Cmp(requiredMax) < 0 { 158 max = requiredMax 159 } 160 161 // Method three: sane mimimums. 162 if min.Cmp(minEstimation) < 0 { 163 min = minEstimation 164 } 165 if max.Cmp(minEstimation.Mul64(maxMultiplier)) < 0 { 166 max = minEstimation.Mul64(maxMultiplier) 167 } 168 169 return 170 } 171 172 // TransactionList returns a list of all transactions in the transaction pool. 173 // The transactions are provided in an order that can acceptably be put into a 174 // block. 175 func (tp *TransactionPool) TransactionList() []types.Transaction { 176 tp.mu.Lock() 177 defer tp.mu.Unlock() 178 179 var txns []types.Transaction 180 for _, tSet := range tp.transactionSets { 181 txns = append(txns, tSet...) 182 } 183 return txns 184 } 185 186 // Transaction returns the transaction with the provided txid, its parents, and 187 // a bool indicating if it exists in the transaction pool. 188 func (tp *TransactionPool) Transaction(id types.TransactionID) (types.Transaction, []types.Transaction, bool) { 189 tp.mu.Lock() 190 defer tp.mu.Unlock() 191 192 // find the transaction 193 exists := false 194 var txn types.Transaction 195 var allParents []types.Transaction 196 for _, tSet := range tp.transactionSets { 197 for i, t := range tSet { 198 if t.ID() == id { 199 txn = t 200 allParents = tSet[:i] 201 exists = true 202 break 203 } 204 } 205 } 206 207 // prune unneeded parents 208 parentIDs := make(map[types.OutputID]struct{}) 209 addOutputIDs := func(txn types.Transaction) { 210 for _, input := range txn.SiacoinInputs { 211 parentIDs[types.OutputID(input.ParentID)] = struct{}{} 212 } 213 for _, fcr := range txn.FileContractRevisions { 214 parentIDs[types.OutputID(fcr.ParentID)] = struct{}{} 215 } 216 for _, input := range txn.SiafundInputs { 217 parentIDs[types.OutputID(input.ParentID)] = struct{}{} 218 } 219 for _, proof := range txn.StorageProofs { 220 parentIDs[types.OutputID(proof.ParentID)] = struct{}{} 221 } 222 for _, sig := range txn.TransactionSignatures { 223 parentIDs[types.OutputID(sig.ParentID)] = struct{}{} 224 } 225 } 226 isParent := func(t types.Transaction) bool { 227 for i := range t.SiacoinOutputs { 228 if _, exists := parentIDs[types.OutputID(t.SiacoinOutputID(uint64(i)))]; exists { 229 return true 230 } 231 } 232 for i := range t.FileContracts { 233 if _, exists := parentIDs[types.OutputID(t.SiacoinOutputID(uint64(i)))]; exists { 234 return true 235 } 236 } 237 for i := range t.SiafundOutputs { 238 if _, exists := parentIDs[types.OutputID(t.SiacoinOutputID(uint64(i)))]; exists { 239 return true 240 } 241 } 242 return false 243 } 244 245 addOutputIDs(txn) 246 var necessaryParents []types.Transaction 247 for i := len(allParents) - 1; i >= 0; i-- { 248 parent := allParents[i] 249 250 if isParent(parent) { 251 necessaryParents = append([]types.Transaction{parent}, necessaryParents...) 252 addOutputIDs(parent) 253 } 254 } 255 256 return txn, necessaryParents, exists 257 } 258 259 // TransactionSet returns the transaction set the provided object 260 // appears in. 261 func (tp *TransactionPool) TransactionSet(oid crypto.Hash) []types.Transaction { 262 tp.mu.RLock() 263 defer tp.mu.RUnlock() 264 var parents []types.Transaction 265 tSetID, exists := tp.knownObjects[ObjectID(oid)] 266 if !exists { 267 return nil 268 } 269 tSet, exists := tp.transactionSets[tSetID] 270 if !exists { 271 return nil 272 } 273 parents = append(parents, tSet...) 274 return parents 275 } 276 277 // Broadcast broadcasts a transaction set to all of the transaction pool's 278 // peers. 279 func (tp *TransactionPool) Broadcast(ts []types.Transaction) { 280 go tp.gateway.Broadcast("RelayTransactionSet", ts, tp.gateway.Peers()) 281 }