gitlab.com/SiaPrime/SiaPrime@v1.4.1/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 "gitlab.com/SiaPrime/SiaPrime/build" 13 "gitlab.com/SiaPrime/SiaPrime/crypto" 14 "gitlab.com/SiaPrime/SiaPrime/encoding" 15 "gitlab.com/SiaPrime/SiaPrime/modules" 16 "gitlab.com/SiaPrime/SiaPrime/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 var setFees types.Currency 175 for _, txn := range superset { 176 for _, fee := range txn.MinerFees { 177 setFees = setFees.Add(fee) 178 } 179 } 180 if requiredFees.Cmp(setFees) > 0 { 181 // TODO: check if there is an existing set with lower fees that we can 182 // kick out. 183 return errLowMinerFees 184 } 185 186 // Check that the transaction set is valid. 187 cc, err := txnFn(superset) 188 if err != nil { 189 return modules.NewConsensusConflict("provided transaction set has prereqs, but is still invalid: " + err.Error()) 190 } 191 192 // Remove the conflicts from the transaction pool. 193 for conflict := range supersetMap { 194 conflictSet := tp.transactionSets[conflict] 195 tp.transactionListSize -= len(encoding.Marshal(conflictSet)) 196 delete(tp.transactionSets, conflict) 197 delete(tp.transactionSetDiffs, conflict) 198 } 199 200 // Add the transaction set to the pool. 201 setID := TransactionSetID(crypto.HashObject(superset)) 202 tp.transactionSets[setID] = superset 203 for _, diff := range cc.SiacoinOutputDiffs { 204 tp.knownObjects[ObjectID(diff.ID)] = setID 205 } 206 for _, diff := range cc.FileContractDiffs { 207 tp.knownObjects[ObjectID(diff.ID)] = setID 208 } 209 for _, diff := range cc.SiafundOutputDiffs { 210 tp.knownObjects[ObjectID(diff.ID)] = setID 211 } 212 tp.transactionSetDiffs[setID] = &cc 213 tsetSize := len(encoding.Marshal(superset)) 214 tp.transactionListSize += tsetSize 215 216 // debug logging 217 if build.DEBUG { 218 txLogs := "" 219 for i, t := range superset { 220 txLogs += fmt.Sprintf("superset transaction %v size: %vB\n", i, len(encoding.Marshal(t))) 221 } 222 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) 223 } 224 225 return nil 226 } 227 228 // acceptTransactionSet verifies that a transaction set is allowed to be in the 229 // transaction pool, and then adds it to the transaction pool. 230 func (tp *TransactionPool) acceptTransactionSet(ts []types.Transaction, txnFn func([]types.Transaction) (modules.ConsensusChange, error)) error { 231 if len(ts) == 0 { 232 return errEmptySet 233 } 234 235 // Remove all transactions that have been confirmed in the transaction set. 236 oldTS := ts 237 ts = []types.Transaction{} 238 for _, txn := range oldTS { 239 if !tp.transactionConfirmed(tp.dbTx, txn.ID()) { 240 ts = append(ts, txn) 241 } 242 } 243 // If no transactions remain, return a dublicate error. 244 if len(ts) == 0 { 245 return modules.ErrDuplicateTransactionSet 246 } 247 248 // Check the composition of the transaction set. 249 setSize, err := tp.checkTransactionSetComposition(ts) 250 if err != nil { 251 return err 252 } 253 254 // Check that the transaction set has enough fees to justify adding it to 255 // the transaction list. 256 requiredFees := tp.requiredFeesToExtendTpool().Mul64(setSize) 257 var setFees types.Currency 258 for _, txn := range ts { 259 for _, fee := range txn.MinerFees { 260 setFees = setFees.Add(fee) 261 } 262 } 263 if requiredFees.Cmp(setFees) > 0 { 264 // TODO: check if there is an existing set with lower fees that we can 265 // kick out. 266 return errLowMinerFees 267 } 268 269 // Check for conflicts with other transactions, which would indicate a 270 // double-spend. Legal children of a transaction set will also trigger the 271 // conflict-detector. 272 oids := relatedObjectIDs(ts) 273 var conflicts []TransactionSetID 274 for _, oid := range oids { 275 conflict, exists := tp.knownObjects[oid] 276 if exists { 277 conflicts = append(conflicts, conflict) 278 } 279 } 280 if len(conflicts) > 0 { 281 return tp.handleConflicts(ts, conflicts, txnFn) 282 } 283 cc, err := txnFn(ts) 284 if err != nil { 285 return modules.NewConsensusConflict("provided transaction set is standalone and invalid: " + err.Error()) 286 } 287 288 // Add the transaction set to the pool. 289 setID := TransactionSetID(crypto.HashObject(ts)) 290 tp.transactionSets[setID] = ts 291 for _, oid := range oids { 292 tp.knownObjects[oid] = setID 293 } 294 tp.transactionSetDiffs[setID] = &cc 295 tsetSize := len(encoding.Marshal(ts)) 296 tp.transactionListSize += tsetSize 297 for _, txn := range ts { 298 if _, exists := tp.transactionHeights[txn.ID()]; !exists { 299 tp.transactionHeights[txn.ID()] = tp.blockHeight 300 } 301 } 302 303 // debug logging 304 if build.DEBUG { 305 txLogs := "" 306 for i, t := range ts { 307 txLogs += fmt.Sprintf("transaction %v size: %vB\n", i, len(encoding.Marshal(t))) 308 } 309 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) 310 } 311 return nil 312 } 313 314 // AcceptTransactionSet adds a transaction to the unconfirmed set of 315 // transactions. If the transaction is accepted, it will be relayed to 316 // connected peers. 317 // 318 // TODO: Break into component sets when the set gets accepted. 319 func (tp *TransactionPool) AcceptTransactionSet(ts []types.Transaction) error { 320 // assert on consensus set to get special method 321 cs, ok := tp.consensusSet.(interface { 322 LockedTryTransactionSet(fn func(func(txns []types.Transaction) (modules.ConsensusChange, error)) error) error 323 }) 324 if !ok { 325 return errors.New("consensus set does not support LockedTryTransactionSet method") 326 } 327 328 return cs.LockedTryTransactionSet(func(txnFn func(txns []types.Transaction) (modules.ConsensusChange, error)) error { 329 tp.log.Debugln("Beginning broadcast of transaction set") 330 tp.mu.Lock() 331 defer tp.mu.Unlock() 332 err := tp.acceptTransactionSet(ts, txnFn) 333 if err != nil { 334 tp.log.Debugln("Transaction set broadcast has failed:", err) 335 return err 336 } 337 go tp.gateway.Broadcast("RelayTransactionSet", ts, tp.gateway.Peers()) 338 // Notify subscribers of an accepted transaction set 339 tp.updateSubscribersTransactions() 340 tp.log.Debugln("Transaction set broadcast appears to have succeeded") 341 return nil 342 }) 343 } 344 345 // relayTransactionSet is an RPC that accepts a transaction set from a peer. If 346 // the accept is successful, the transaction will be relayed to the gateway's 347 // other peers. 348 func (tp *TransactionPool) relayTransactionSet(conn modules.PeerConn) error { 349 if err := tp.tg.Add(); err != nil { 350 return err 351 } 352 defer tp.tg.Done() 353 err := conn.SetDeadline(time.Now().Add(relayTransactionSetTimeout)) 354 if err != nil { 355 return err 356 } 357 // Automatically close the channel when tg.Stop() is called. 358 finishedChan := make(chan struct{}) 359 defer close(finishedChan) 360 go func() { 361 select { 362 case <-tp.tg.StopChan(): 363 case <-finishedChan: 364 } 365 conn.Close() 366 }() 367 368 var ts []types.Transaction 369 err = encoding.ReadObject(conn, &ts, types.BlockSizeLimit) 370 if err != nil { 371 return err 372 } 373 374 return tp.AcceptTransactionSet(ts) 375 }