github.com/avahowell/sia@v0.5.1-beta.0.20160524050156-83dcc3d37c94/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 9 "github.com/NebulousLabs/Sia/crypto" 10 "github.com/NebulousLabs/Sia/encoding" 11 "github.com/NebulousLabs/Sia/modules" 12 "github.com/NebulousLabs/Sia/types" 13 14 "github.com/NebulousLabs/bolt" 15 ) 16 17 const ( 18 // The TransactionPoolSizeLimit is first checked, and then a transaction 19 // set is added. The current transaction pool does not do any priority 20 // ordering, so the size limit is such that the transaction pool will never 21 // exceed the size of a block. 22 // 23 // TODO: Add a priority structure that will allow the transaction pool to 24 // fill up beyond the size of a single block, without being subject to 25 // manipulation. 26 // 27 // The first ~1/4 of the transaction pool can be filled for free. This is 28 // mostly to preserve compatibility with clients that do not add fees. 29 TransactionPoolSizeLimit = 2e6 - 5e3 - modules.TransactionSetSizeLimit 30 TransactionPoolSizeForFee = 500e3 31 ) 32 33 var ( 34 errObjectConflict = errors.New("transaction set conflicts with an existing transaction set") 35 errFullTransactionPool = errors.New("transaction pool cannot accept more transactions") 36 errLowMinerFees = errors.New("transaction set needs more miner fees to be accepted") 37 errEmptySet = errors.New("transaction set is empty") 38 39 TransactionMinFee = types.SiacoinPrecision.Mul64(2) 40 ) 41 42 // relatedObjectIDs determines all of the object ids related to a transaction. 43 func relatedObjectIDs(ts []types.Transaction) []ObjectID { 44 oidMap := make(map[ObjectID]struct{}) 45 for _, t := range ts { 46 for _, sci := range t.SiacoinInputs { 47 oidMap[ObjectID(sci.ParentID)] = struct{}{} 48 } 49 for i := range t.SiacoinOutputs { 50 oidMap[ObjectID(t.SiacoinOutputID(uint64(i)))] = struct{}{} 51 } 52 for i := range t.FileContracts { 53 oidMap[ObjectID(t.FileContractID(uint64(i)))] = struct{}{} 54 } 55 for _, fcr := range t.FileContractRevisions { 56 oidMap[ObjectID(fcr.ParentID)] = struct{}{} 57 } 58 for _, sp := range t.StorageProofs { 59 oidMap[ObjectID(sp.ParentID)] = struct{}{} 60 } 61 for _, sfi := range t.SiafundInputs { 62 oidMap[ObjectID(sfi.ParentID)] = struct{}{} 63 } 64 for i := range t.SiafundOutputs { 65 oidMap[ObjectID(t.SiafundOutputID(uint64(i)))] = struct{}{} 66 } 67 } 68 69 var oids []ObjectID 70 for oid := range oidMap { 71 oids = append(oids, oid) 72 } 73 return oids 74 } 75 76 // checkMinerFees checks that the total amount of transaction fees in the 77 // transaction set is sufficient to earn a spot in the transaction pool. 78 func (tp *TransactionPool) checkMinerFees(ts []types.Transaction) error { 79 // Transactions cannot be added after the TransactionPoolSizeLimit has been 80 // hit. 81 if tp.transactionListSize > TransactionPoolSizeLimit { 82 return errFullTransactionPool 83 } 84 85 // The first TransactionPoolSizeForFee transactions do not need fees. 86 if tp.transactionListSize > TransactionPoolSizeForFee { 87 // Currently required fees are set on a per-transaction basis. 2 coins 88 // are required per transaction if the free-fee limit has been reached, 89 // adding a larger fee is not useful. 90 var feeSum types.Currency 91 for i := range ts { 92 for _, fee := range ts[i].MinerFees { 93 feeSum = feeSum.Add(fee) 94 } 95 } 96 feeRequired := TransactionMinFee.Mul64(uint64(len(ts))) 97 if feeSum.Cmp(feeRequired) < 0 { 98 return errLowMinerFees 99 } 100 } 101 return nil 102 } 103 104 // checkTransactionSetComposition checks if the transaction set is valid given 105 // the state of the pool. It does not check that each individual transaction 106 // would be legal in the next block, but does check things like miner fees and 107 // IsStandard. 108 func (tp *TransactionPool) checkTransactionSetComposition(ts []types.Transaction) error { 109 // Check that the transaction set is not already known. 110 setID := TransactionSetID(crypto.HashObject(ts)) 111 _, exists := tp.transactionSets[setID] 112 if exists { 113 return modules.ErrDuplicateTransactionSet 114 } 115 116 // Check that the transaction set has enough fees to justify adding it to 117 // the transaction list. 118 err := tp.checkMinerFees(ts) 119 if err != nil { 120 return err 121 } 122 123 // All checks after this are expensive. 124 // 125 // TODO: There is no DoS prevention mechanism in place to prevent repeated 126 // expensive verifications of invalid transactions that are created on the 127 // fly. 128 129 // Check that all transactions follow 'Standard.md' guidelines. 130 err = tp.IsStandardTransactionSet(ts) 131 if err != nil { 132 return err 133 } 134 return nil 135 } 136 137 // handleConflicts detects whether the conflicts in the transaction pool are 138 // legal children of the new transaction pool set or not. 139 func (tp *TransactionPool) handleConflicts(ts []types.Transaction, conflicts []TransactionSetID) error { 140 // Create a list of all the transaction ids that compose the set of 141 // conflicts. 142 conflictMap := make(map[types.TransactionID]TransactionSetID) 143 for _, conflict := range conflicts { 144 conflictSet := tp.transactionSets[conflict] 145 for _, conflictTxn := range conflictSet { 146 conflictMap[conflictTxn.ID()] = conflict 147 } 148 } 149 150 // Discard all duplicate transactions from the input transaction set. 151 var dedupSet []types.Transaction 152 for _, t := range ts { 153 _, exists := conflictMap[t.ID()] 154 if exists { 155 continue 156 } 157 dedupSet = append(dedupSet, t) 158 } 159 if len(dedupSet) == 0 { 160 return modules.ErrDuplicateTransactionSet 161 } 162 // If transactions were pruned, it's possible that the set of 163 // dependencies/conflicts has also reduced. To minimize computational load 164 // on the consensus set, we want to prune out all of the conflicts that are 165 // no longer relevant. As an example, consider the transaction set {A}, the 166 // set {B}, and the new set {A, C}, where C is dependent on B. {A} and {B} 167 // are both conflicts, but after deduplication {A} is no longer a conflict. 168 // This is recursive, but it is guaranteed to run only once as the first 169 // deduplication is guaranteed to be complete. 170 if len(dedupSet) < len(ts) { 171 oids := relatedObjectIDs(dedupSet) 172 var conflicts []TransactionSetID 173 for _, oid := range oids { 174 conflict, exists := tp.knownObjects[oid] 175 if exists { 176 conflicts = append(conflicts, conflict) 177 } 178 } 179 return tp.handleConflicts(dedupSet, conflicts) 180 } 181 182 // Merge all of the conflict sets with the input set (input set goes last 183 // to preserve dependency ordering), and see if the set as a whole is both 184 // small enough to be legal and valid as a set. If no, return an error. If 185 // yes, add the new set to the pool, and eliminate the old set. The output 186 // diff objects can be repeated, (no need to remove those). Just need to 187 // remove the conflicts from tp.transactionSets. 188 var superset []types.Transaction 189 supersetMap := make(map[TransactionSetID]struct{}) 190 for _, conflict := range conflictMap { 191 supersetMap[conflict] = struct{}{} 192 } 193 for conflict := range supersetMap { 194 superset = append(superset, tp.transactionSets[conflict]...) 195 } 196 superset = append(superset, dedupSet...) 197 198 // Check the composition of the transaction set, including fees and 199 // IsStandard rules (this is a new set, the rules must be rechecked). 200 err := tp.checkTransactionSetComposition(superset) 201 if err != nil { 202 return err 203 } 204 205 // Check that the transaction set is valid. 206 cc, err := tp.consensusSet.TryTransactionSet(superset) 207 if err != nil { 208 return modules.NewConsensusConflict(err.Error()) 209 } 210 211 // Remove the conflicts from the transaction pool. The diffs do not need to 212 // be removed, they will be overwritten later in the function. 213 for _, conflict := range conflictMap { 214 conflictSet := tp.transactionSets[conflict] 215 tp.transactionListSize -= len(encoding.Marshal(conflictSet)) 216 delete(tp.transactionSets, conflict) 217 delete(tp.transactionSetDiffs, conflict) 218 } 219 220 // Add the transaction set to the pool. 221 setID := TransactionSetID(crypto.HashObject(superset)) 222 tp.transactionSets[setID] = superset 223 for _, diff := range cc.SiacoinOutputDiffs { 224 tp.knownObjects[ObjectID(diff.ID)] = setID 225 } 226 for _, diff := range cc.FileContractDiffs { 227 tp.knownObjects[ObjectID(diff.ID)] = setID 228 } 229 for _, diff := range cc.SiafundOutputDiffs { 230 tp.knownObjects[ObjectID(diff.ID)] = setID 231 } 232 tp.transactionSetDiffs[setID] = cc 233 tp.transactionListSize += len(encoding.Marshal(superset)) 234 return nil 235 } 236 237 // acceptTransactionSet verifies that a transaction set is allowed to be in the 238 // transaction pool, and then adds it to the transaction pool. 239 func (tp *TransactionPool) acceptTransactionSet(ts []types.Transaction) error { 240 if len(ts) == 0 { 241 return errEmptySet 242 } 243 244 // Remove all transactions that have been confirmed in the transaction set. 245 err := tp.db.Update(func(tx *bolt.Tx) error { 246 oldTS := ts 247 ts = []types.Transaction{} 248 for _, txn := range oldTS { 249 if !tp.transactionConfirmed(tx, txn.ID()) { 250 ts = append(ts, txn) 251 } 252 } 253 return nil 254 }) 255 if err != nil { 256 return err 257 } 258 // If no transactions remain, return a dublicate error. 259 if len(ts) == 0 { 260 return modules.ErrDuplicateTransactionSet 261 } 262 263 // Check the composition of the transaction set, including fees and 264 // IsStandard rules. 265 err = tp.checkTransactionSetComposition(ts) 266 if err != nil { 267 return err 268 } 269 270 // Check for conflicts with other transactions, which would indicate a 271 // double-spend. Legal children of a transaction set will also trigger the 272 // conflict-detector. 273 oids := relatedObjectIDs(ts) 274 var conflicts []TransactionSetID 275 for _, oid := range oids { 276 conflict, exists := tp.knownObjects[oid] 277 if exists { 278 conflicts = append(conflicts, conflict) 279 } 280 } 281 if len(conflicts) > 0 { 282 return tp.handleConflicts(ts, conflicts) 283 } 284 cc, err := tp.consensusSet.TryTransactionSet(ts) 285 if err != nil { 286 return modules.NewConsensusConflict(err.Error()) 287 } 288 289 // Add the transaction set to the pool. 290 setID := TransactionSetID(crypto.HashObject(ts)) 291 tp.transactionSets[setID] = ts 292 for _, oid := range oids { 293 tp.knownObjects[oid] = setID 294 } 295 tp.transactionSetDiffs[setID] = cc 296 tp.transactionListSize += len(encoding.Marshal(ts)) 297 return nil 298 } 299 300 // AcceptTransaction adds a transaction to the unconfirmed set of 301 // transactions. If the transaction is accepted, it will be relayed to 302 // connected peers. 303 func (tp *TransactionPool) AcceptTransactionSet(ts []types.Transaction) error { 304 tp.mu.Lock() 305 defer tp.mu.Unlock() 306 307 err := tp.acceptTransactionSet(ts) 308 if err != nil { 309 return err 310 } 311 312 // Notify subscribers and broadcast the transaction set. 313 go tp.gateway.Broadcast("RelayTransactionSet", ts, tp.gateway.Peers()) 314 tp.updateSubscribersTransactions() 315 return nil 316 } 317 318 // relayTransactionSet is an RPC that accepts a transaction set from a peer. If 319 // the accept is successful, the transaction will be relayed to the gateway's 320 // other peers. 321 func (tp *TransactionPool) relayTransactionSet(conn modules.PeerConn) error { 322 var ts []types.Transaction 323 err := encoding.ReadObject(conn, &ts, types.BlockSizeLimit) 324 if err != nil { 325 return err 326 } 327 return tp.AcceptTransactionSet(ts) 328 }