github.com/ZuluSpl0it/Sia@v1.3.7/modules/transactionpool/accept.go (about) 1 package transactionpool 2 3 // TODO: It seems like the transaction pool is not properly detecting conflicts 4 // between a file contract revision and a file contract. 5 6 import ( 7 "errors" 8 "fmt" 9 "math" 10 "time" 11 12 "github.com/NebulousLabs/Sia/build" 13 "github.com/NebulousLabs/Sia/crypto" 14 "github.com/NebulousLabs/Sia/encoding" 15 "github.com/NebulousLabs/Sia/modules" 16 "github.com/NebulousLabs/Sia/types" 17 ) 18 19 var ( 20 errEmptySet = errors.New("transaction set is empty") 21 errFullTransactionPool = errors.New("transaction pool cannot accept more transactions") 22 errLowMinerFees = errors.New("transaction set needs more miner fees to be accepted") 23 errObjectConflict = errors.New("transaction set conflicts with an existing transaction set") 24 ) 25 26 // relatedObjectIDs determines all of the object ids related to a transaction. 27 func relatedObjectIDs(ts []types.Transaction) []ObjectID { 28 oidMap := make(map[ObjectID]struct{}) 29 for _, t := range ts { 30 for _, sci := range t.SiacoinInputs { 31 oidMap[ObjectID(sci.ParentID)] = struct{}{} 32 } 33 for i := range t.SiacoinOutputs { 34 oidMap[ObjectID(t.SiacoinOutputID(uint64(i)))] = struct{}{} 35 } 36 for i := range t.FileContracts { 37 oidMap[ObjectID(t.FileContractID(uint64(i)))] = struct{}{} 38 } 39 for _, fcr := range t.FileContractRevisions { 40 oidMap[ObjectID(fcr.ParentID)] = struct{}{} 41 } 42 for _, sp := range t.StorageProofs { 43 oidMap[ObjectID(sp.ParentID)] = struct{}{} 44 } 45 for _, sfi := range t.SiafundInputs { 46 oidMap[ObjectID(sfi.ParentID)] = struct{}{} 47 } 48 for i := range t.SiafundOutputs { 49 oidMap[ObjectID(t.SiafundOutputID(uint64(i)))] = struct{}{} 50 } 51 } 52 53 var oids []ObjectID 54 for oid := range oidMap { 55 oids = append(oids, oid) 56 } 57 return oids 58 } 59 60 // requiredFeesToExtendTpool returns the amount of fees required to extend the 61 // transaction pool to fit another transaction set. The amount returned has the 62 // unit 'currency per byte'. 63 func (tp *TransactionPool) requiredFeesToExtendTpool() types.Currency { 64 // If the transaction pool is nearly empty, it can be extended even if there 65 // are no fees. 66 if tp.transactionListSize < TransactionPoolSizeForFee { 67 return types.ZeroCurrency 68 } 69 70 // Calculate the fee required to bump out the size of the transaction pool. 71 ratioToTarget := float64(tp.transactionListSize) / TransactionPoolSizeTarget 72 feeFactor := math.Pow(ratioToTarget, TransactionPoolExponentiation) 73 return types.SiacoinPrecision.MulFloat(feeFactor).Div64(1000) // Divide by 1000 to get SC / kb 74 } 75 76 // checkTransactionSetComposition checks if the transaction set is valid given 77 // the state of the pool. It does not check that each individual transaction 78 // would be legal in the next block, but does check things like miner fees and 79 // IsStandard. 80 func (tp *TransactionPool) checkTransactionSetComposition(ts []types.Transaction) (uint64, error) { 81 // Check that the transaction set is not already known. 82 setID := TransactionSetID(crypto.HashObject(ts)) 83 _, exists := tp.transactionSets[setID] 84 if exists { 85 return 0, modules.ErrDuplicateTransactionSet 86 } 87 88 // All checks after this are expensive. 89 // 90 // TODO: There is no DoS prevention mechanism in place to prevent repeated 91 // expensive verifications of invalid transactions that are created on the 92 // fly. 93 94 // Check that all transactions follow 'Standard.md' guidelines. 95 setSize, err := isStandardTransactionSet(ts) 96 if err != nil { 97 return 0, err 98 } 99 100 return setSize, nil 101 } 102 103 // handleConflicts detects whether the conflicts in the transaction pool are 104 // legal children of the new transaction pool set or not. 105 func (tp *TransactionPool) handleConflicts(ts []types.Transaction, conflicts []TransactionSetID, txnFn func([]types.Transaction) (modules.ConsensusChange, error)) error { 106 // Create a list of all the transaction ids that compose the set of 107 // conflicts. 108 conflictMap := make(map[types.TransactionID]TransactionSetID) 109 for _, conflict := range conflicts { 110 conflictSet := tp.transactionSets[conflict] 111 for _, conflictTxn := range conflictSet { 112 conflictMap[conflictTxn.ID()] = conflict 113 } 114 } 115 116 // Discard all duplicate transactions from the input transaction set. 117 var dedupSet []types.Transaction 118 for _, t := range ts { 119 _, exists := conflictMap[t.ID()] 120 if exists { 121 continue 122 } 123 dedupSet = append(dedupSet, t) 124 } 125 if len(dedupSet) == 0 { 126 return modules.ErrDuplicateTransactionSet 127 } 128 // If transactions were pruned, it's possible that the set of 129 // dependencies/conflicts has also reduced. To minimize computational load 130 // on the consensus set, we want to prune out all of the conflicts that are 131 // no longer relevant. As an example, consider the transaction set {A}, the 132 // set {B}, and the new set {A, C}, where C is dependent on B. {A} and {B} 133 // are both conflicts, but after deduplication {A} is no longer a conflict. 134 // This is recursive, but it is guaranteed to run only once as the first 135 // deduplication is guaranteed to be complete. 136 if len(dedupSet) < len(ts) { 137 oids := relatedObjectIDs(dedupSet) 138 var conflicts []TransactionSetID 139 for _, oid := range oids { 140 conflict, exists := tp.knownObjects[oid] 141 if exists { 142 conflicts = append(conflicts, conflict) 143 } 144 } 145 return tp.handleConflicts(dedupSet, conflicts, txnFn) 146 } 147 148 // Merge all of the conflict sets with the input set (input set goes last 149 // to preserve dependency ordering), and see if the set as a whole is both 150 // small enough to be legal and valid as a set. If no, return an error. If 151 // yes, add the new set to the pool, and eliminate the old set. The output 152 // diff objects can be repeated, (no need to remove those). Just need to 153 // remove the conflicts from tp.transactionSets. 154 var superset []types.Transaction 155 supersetMap := make(map[TransactionSetID]struct{}) 156 for _, conflict := range conflictMap { 157 supersetMap[conflict] = struct{}{} 158 } 159 for conflict := range supersetMap { 160 superset = append(superset, tp.transactionSets[conflict]...) 161 } 162 superset = append(superset, dedupSet...) 163 164 // Check the composition of the transaction set, including fees and 165 // IsStandard rules (this is a new set, the rules must be rechecked). 166 setSize, err := tp.checkTransactionSetComposition(superset) 167 if err != nil { 168 return err 169 } 170 171 // Check that the transaction set has enough fees to justify adding it to 172 // the transaction list. 173 requiredFees := tp.requiredFeesToExtendTpool().Mul64(setSize) 174 if err != nil { 175 return err 176 } 177 var setFees types.Currency 178 for _, txn := range superset { 179 for _, fee := range txn.MinerFees { 180 setFees = setFees.Add(fee) 181 } 182 } 183 if requiredFees.Cmp(setFees) > 0 { 184 // TODO: check if there is an existing set with lower fees that we can 185 // kick out. 186 return errLowMinerFees 187 } 188 189 // Check that the transaction set is valid. 190 cc, err := txnFn(superset) 191 if err != nil { 192 return modules.NewConsensusConflict("provided transaction set has prereqs, but is still invalid: " + err.Error()) 193 } 194 195 // Remove the conflicts from the transaction pool. 196 for conflict := range supersetMap { 197 conflictSet := tp.transactionSets[conflict] 198 tp.transactionListSize -= len(encoding.Marshal(conflictSet)) 199 delete(tp.transactionSets, conflict) 200 delete(tp.transactionSetDiffs, conflict) 201 } 202 203 // Add the transaction set to the pool. 204 setID := TransactionSetID(crypto.HashObject(superset)) 205 tp.transactionSets[setID] = superset 206 for _, diff := range cc.SiacoinOutputDiffs { 207 tp.knownObjects[ObjectID(diff.ID)] = setID 208 } 209 for _, diff := range cc.FileContractDiffs { 210 tp.knownObjects[ObjectID(diff.ID)] = setID 211 } 212 for _, diff := range cc.SiafundOutputDiffs { 213 tp.knownObjects[ObjectID(diff.ID)] = setID 214 } 215 tp.transactionSetDiffs[setID] = &cc 216 tsetSize := len(encoding.Marshal(superset)) 217 tp.transactionListSize += tsetSize 218 219 // debug logging 220 if build.DEBUG { 221 txLogs := "" 222 for i, t := range superset { 223 txLogs += fmt.Sprintf("superset transaction %v size: %vB\n", i, len(encoding.Marshal(t))) 224 } 225 tp.log.Debugf("accepted transaction superset %v, size: %vB\ntpool size is %vB after accpeting transaction superset\ntransactions: \n%v\n", setID, tsetSize, tp.transactionListSize, txLogs) 226 } 227 228 return nil 229 } 230 231 // acceptTransactionSet verifies that a transaction set is allowed to be in the 232 // transaction pool, and then adds it to the transaction pool. 233 func (tp *TransactionPool) acceptTransactionSet(ts []types.Transaction, txnFn func([]types.Transaction) (modules.ConsensusChange, error)) error { 234 if len(ts) == 0 { 235 return errEmptySet 236 } 237 238 // Remove all transactions that have been confirmed in the transaction set. 239 oldTS := ts 240 ts = []types.Transaction{} 241 for _, txn := range oldTS { 242 if !tp.transactionConfirmed(tp.dbTx, txn.ID()) { 243 ts = append(ts, txn) 244 } 245 } 246 // If no transactions remain, return a dublicate error. 247 if len(ts) == 0 { 248 return modules.ErrDuplicateTransactionSet 249 } 250 251 // Check the composition of the transaction set. 252 setSize, err := tp.checkTransactionSetComposition(ts) 253 if err != nil { 254 return err 255 } 256 257 // Check that the transaction set has enough fees to justify adding it to 258 // the transaction list. 259 requiredFees := tp.requiredFeesToExtendTpool().Mul64(setSize) 260 if err != nil { 261 return err 262 } 263 var setFees types.Currency 264 for _, txn := range ts { 265 for _, fee := range txn.MinerFees { 266 setFees = setFees.Add(fee) 267 } 268 } 269 if requiredFees.Cmp(setFees) > 0 { 270 // TODO: check if there is an existing set with lower fees that we can 271 // kick out. 272 return errLowMinerFees 273 } 274 275 // Check for conflicts with other transactions, which would indicate a 276 // double-spend. Legal children of a transaction set will also trigger the 277 // conflict-detector. 278 oids := relatedObjectIDs(ts) 279 var conflicts []TransactionSetID 280 for _, oid := range oids { 281 conflict, exists := tp.knownObjects[oid] 282 if exists { 283 conflicts = append(conflicts, conflict) 284 } 285 } 286 if len(conflicts) > 0 { 287 return tp.handleConflicts(ts, conflicts, txnFn) 288 } 289 cc, err := txnFn(ts) 290 if err != nil { 291 return modules.NewConsensusConflict("provided transaction set is standalone and invalid: " + err.Error()) 292 } 293 294 // Add the transaction set to the pool. 295 setID := TransactionSetID(crypto.HashObject(ts)) 296 tp.transactionSets[setID] = ts 297 for _, oid := range oids { 298 tp.knownObjects[oid] = setID 299 } 300 tp.transactionSetDiffs[setID] = &cc 301 tsetSize := len(encoding.Marshal(ts)) 302 tp.transactionListSize += tsetSize 303 for _, txn := range ts { 304 if _, exists := tp.transactionHeights[txn.ID()]; !exists { 305 tp.transactionHeights[txn.ID()] = tp.blockHeight 306 } 307 } 308 309 // debug logging 310 if build.DEBUG { 311 txLogs := "" 312 for i, t := range ts { 313 txLogs += fmt.Sprintf("transaction %v size: %vB\n", i, len(encoding.Marshal(t))) 314 } 315 tp.log.Debugf("accepted transaction set %v, size: %vB\ntpool size is %vB after accpeting transaction set\ntransactions: \n%v\n", setID, tsetSize, tp.transactionListSize, txLogs) 316 } 317 return nil 318 } 319 320 // AcceptTransactionSet adds a transaction to the unconfirmed set of 321 // transactions. If the transaction is accepted, it will be relayed to 322 // connected peers. 323 // 324 // TODO: Break into component sets when the set gets accepted. 325 func (tp *TransactionPool) AcceptTransactionSet(ts []types.Transaction) error { 326 // assert on consensus set to get special method 327 cs, ok := tp.consensusSet.(interface { 328 LockedTryTransactionSet(fn func(func(txns []types.Transaction) (modules.ConsensusChange, error)) error) error 329 }) 330 if !ok { 331 return errors.New("consensus set does not support LockedTryTransactionSet method") 332 } 333 334 return cs.LockedTryTransactionSet(func(txnFn func(txns []types.Transaction) (modules.ConsensusChange, error)) error { 335 tp.log.Debugln("Beginning broadcast of transaction set") 336 tp.mu.Lock() 337 defer tp.mu.Unlock() 338 err := tp.acceptTransactionSet(ts, txnFn) 339 if err != nil { 340 tp.log.Debugln("Transaction set broadcast has failed:", err) 341 return err 342 } 343 go tp.gateway.Broadcast("RelayTransactionSet", ts, tp.gateway.Peers()) 344 // Notify subscribers of an accepted transaction set 345 tp.updateSubscribersTransactions() 346 tp.log.Debugln("Transaction set broadcast appears to have succeeded") 347 return nil 348 }) 349 } 350 351 // relayTransactionSet is an RPC that accepts a transaction set from a peer. If 352 // the accept is successful, the transaction will be relayed to the gateway's 353 // other peers. 354 func (tp *TransactionPool) relayTransactionSet(conn modules.PeerConn) error { 355 if err := tp.tg.Add(); err != nil { 356 return err 357 } 358 defer tp.tg.Done() 359 err := conn.SetDeadline(time.Now().Add(relayTransactionSetTimeout)) 360 if err != nil { 361 return err 362 } 363 // Automatically close the channel when tg.Stop() is called. 364 finishedChan := make(chan struct{}) 365 defer close(finishedChan) 366 go func() { 367 select { 368 case <-tp.tg.StopChan(): 369 case <-finishedChan: 370 } 371 conn.Close() 372 }() 373 374 var ts []types.Transaction 375 err = encoding.ReadObject(conn, &ts, types.BlockSizeLimit) 376 if err != nil { 377 return err 378 } 379 380 return tp.AcceptTransactionSet(ts) 381 }