github.com/iotexproject/iotex-core@v1.14.1-rc1/actpool/actpool.go (about) 1 // Copyright (c) 2019 IoTeX Foundation 2 // This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability 3 // or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed. 4 // This source code is governed by Apache License 2.0 that can be found in the LICENSE file. 5 6 package actpool 7 8 import ( 9 "context" 10 "encoding/hex" 11 "sort" 12 "sync" 13 "sync/atomic" 14 15 "github.com/pkg/errors" 16 "github.com/prometheus/client_golang/prometheus" 17 "go.uber.org/zap" 18 19 "github.com/iotexproject/go-pkgs/cache/ttl" 20 "github.com/iotexproject/go-pkgs/hash" 21 "github.com/iotexproject/iotex-address/address" 22 "github.com/iotexproject/iotex-proto/golang/iotextypes" 23 24 "github.com/iotexproject/iotex-core/action" 25 "github.com/iotexproject/iotex-core/action/protocol" 26 accountutil "github.com/iotexproject/iotex-core/action/protocol/account/util" 27 "github.com/iotexproject/iotex-core/blockchain/block" 28 "github.com/iotexproject/iotex-core/blockchain/genesis" 29 "github.com/iotexproject/iotex-core/pkg/log" 30 "github.com/iotexproject/iotex-core/pkg/prometheustimer" 31 "github.com/iotexproject/iotex-core/pkg/tracer" 32 ) 33 34 const ( 35 // TODO: move to config 36 _numWorker = 16 37 ) 38 39 var ( 40 _actpoolMtc = prometheus.NewCounterVec(prometheus.CounterOpts{ 41 Name: "iotex_actpool_rejection_metrics", 42 Help: "actpool metrics.", 43 }, []string{"type"}) 44 // ErrGasTooHigh error when the intrinsic gas of an action is too high 45 ErrGasTooHigh = errors.New("action gas is too high") 46 ) 47 48 func init() { 49 prometheus.MustRegister(_actpoolMtc) 50 } 51 52 // ActPool is the interface of actpool 53 type ActPool interface { 54 action.SealedEnvelopeValidator 55 // Reset resets actpool state 56 Reset() 57 // PendingActionMap returns an action map with all accepted actions 58 PendingActionMap() map[string][]*action.SealedEnvelope 59 // Add adds an action into the pool after passing validation 60 Add(ctx context.Context, act *action.SealedEnvelope) error 61 // GetPendingNonce returns pending nonce in pool given an account address 62 GetPendingNonce(addr string) (uint64, error) 63 // GetUnconfirmedActs returns unconfirmed actions in pool given an account address 64 GetUnconfirmedActs(addr string) []*action.SealedEnvelope 65 // GetActionByHash returns the pending action in pool given action's hash 66 GetActionByHash(hash hash.Hash256) (*action.SealedEnvelope, error) 67 // GetSize returns the act pool size 68 GetSize() uint64 69 // GetCapacity returns the act pool capacity 70 GetCapacity() uint64 71 // GetGasSize returns the act pool gas size 72 GetGasSize() uint64 73 // GetGasCapacity returns the act pool gas capacity 74 GetGasCapacity() uint64 75 // DeleteAction deletes an invalid action from pool 76 DeleteAction(address.Address) 77 // ReceiveBlock will be called when a new block is committed 78 ReceiveBlock(*block.Block) error 79 80 AddActionEnvelopeValidators(...action.SealedEnvelopeValidator) 81 } 82 83 // SortedActions is a slice of actions that implements sort.Interface to sort by Value. 84 type SortedActions []*action.SealedEnvelope 85 86 func (p SortedActions) Swap(i, j int) { p[i], p[j] = p[j], p[i] } 87 func (p SortedActions) Len() int { return len(p) } 88 func (p SortedActions) Less(i, j int) bool { return p[i].Nonce() < p[j].Nonce() } 89 90 // Option sets action pool construction parameter 91 type Option func(pool *actPool) error 92 93 // actPool implements ActPool interface 94 type actPool struct { 95 cfg Config 96 g genesis.Genesis 97 sf protocol.StateReader 98 accountDesActs *destinationMap 99 allActions *ttl.Cache 100 gasInPool uint64 101 actionEnvelopeValidators []action.SealedEnvelopeValidator 102 timerFactory *prometheustimer.TimerFactory 103 senderBlackList map[string]bool 104 jobQueue []chan workerJob 105 worker []*queueWorker 106 } 107 108 // NewActPool constructs a new actpool 109 func NewActPool(g genesis.Genesis, sf protocol.StateReader, cfg Config, opts ...Option) (ActPool, error) { 110 if sf == nil { 111 return nil, errors.New("Try to attach a nil state reader") 112 } 113 114 senderBlackList := make(map[string]bool) 115 for _, bannedSender := range cfg.BlackList { 116 senderBlackList[bannedSender] = true 117 } 118 119 actsMap, _ := ttl.NewCache() 120 ap := &actPool{ 121 cfg: cfg, 122 g: g, 123 sf: sf, 124 senderBlackList: senderBlackList, 125 accountDesActs: &destinationMap{acts: make(map[string]map[hash.Hash256]*action.SealedEnvelope)}, 126 allActions: actsMap, 127 jobQueue: make([]chan workerJob, _numWorker), 128 worker: make([]*queueWorker, _numWorker), 129 } 130 for _, opt := range opts { 131 if err := opt(ap); err != nil { 132 return nil, err 133 } 134 } 135 timerFactory, err := prometheustimer.New( 136 "iotex_action_pool_perf", 137 "Performance of action pool", 138 []string{"type"}, 139 []string{"default"}, 140 ) 141 if err != nil { 142 return nil, err 143 } 144 ap.timerFactory = timerFactory 145 146 for i := 0; i < _numWorker; i++ { 147 ap.jobQueue[i] = make(chan workerJob, ap.cfg.WorkerBufferSize) 148 ap.worker[i] = newQueueWorker(ap, ap.jobQueue[i]) 149 if err := ap.worker[i].Start(); err != nil { 150 return nil, err 151 } 152 } 153 return ap, nil 154 } 155 156 func (ap *actPool) AddActionEnvelopeValidators(fs ...action.SealedEnvelopeValidator) { 157 ap.actionEnvelopeValidators = append(ap.actionEnvelopeValidators, fs...) 158 } 159 160 // Reset resets actpool state 161 // Step I: remove all the actions in actpool that have already been committed to block 162 // Step II: update pending balance of each account if it still exists in pool 163 // Step III: update queue's status in each account and remove invalid actions following queue's update 164 // Specifically, first reset the pending nonce based on confirmed nonce in order to prevent omitting reevaluation of 165 // unconfirmed but pending actions in pool after update of pending balance 166 // Then starting from the current confirmed nonce, iteratively update pending nonce if nonces are consecutive and pending 167 // balance is sufficient, and remove all the subsequent actions once the pending balance becomes insufficient 168 func (ap *actPool) Reset() { 169 ap.reset() 170 } 171 172 func (ap *actPool) reset() { 173 var ( 174 wg sync.WaitGroup 175 ctx = ap.context(context.Background()) 176 ) 177 for i := range ap.worker { 178 wg.Add(1) 179 go func(worker *queueWorker) { 180 defer wg.Done() 181 worker.Reset(ctx) 182 }(ap.worker[i]) 183 } 184 wg.Wait() 185 } 186 187 func (ap *actPool) ReceiveBlock(*block.Block) error { 188 ap.reset() 189 return nil 190 } 191 192 // PendingActionMap returns an action interator with all accepted actions 193 func (ap *actPool) PendingActionMap() map[string][]*action.SealedEnvelope { 194 var ( 195 wg sync.WaitGroup 196 actsFromWorker = make([][]*pendingActions, _numWorker) 197 ctx = ap.context(context.Background()) 198 totalAccounts = uint64(0) 199 ) 200 for i := range ap.worker { 201 wg.Add(1) 202 go func(i int) { 203 defer wg.Done() 204 actsFromWorker[i] = ap.worker[i].PendingActions(ctx) 205 atomic.AddUint64(&totalAccounts, uint64(len(actsFromWorker[i]))) 206 }(i) 207 } 208 wg.Wait() 209 210 ret := make(map[string][]*action.SealedEnvelope, totalAccounts) 211 for _, v := range actsFromWorker { 212 for _, w := range v { 213 ret[w.sender] = w.acts 214 } 215 } 216 return ret 217 } 218 219 func (ap *actPool) Add(ctx context.Context, act *action.SealedEnvelope) error { 220 ctx, span := tracer.NewSpan(ap.context(ctx), "actPool.Add") 221 defer span.End() 222 ctx = ap.context(ctx) 223 224 // system action is only added by proposer when creating a block 225 if action.IsSystemAction(act) { 226 return action.ErrInvalidAct 227 } 228 229 if err := checkSelpData(act); err != nil { 230 return err 231 } 232 233 if err := ap.checkSelpWithoutState(ctx, act); err != nil { 234 return err 235 } 236 237 intrinsicGas, err := act.IntrinsicGas() 238 if err != nil { 239 return err 240 } 241 if intrinsicGas > ap.cfg.MaxGasLimitPerPool { 242 _actpoolMtc.WithLabelValues("overMaxGasLimitPerPool").Inc() 243 return ErrGasTooHigh 244 } 245 246 return ap.enqueue( 247 ctx, 248 act, 249 atomic.LoadUint64(&ap.gasInPool) > ap.cfg.MaxGasLimitPerPool-intrinsicGas || 250 uint64(ap.allActions.Count()) >= ap.cfg.MaxNumActsPerPool, 251 ) 252 } 253 254 func checkSelpData(act *action.SealedEnvelope) error { 255 _, err := act.IntrinsicGas() 256 if err != nil { 257 return err 258 } 259 _, err = act.Hash() 260 if err != nil { 261 return err 262 } 263 _, err = act.Cost() 264 if err != nil { 265 return err 266 } 267 if act.SrcPubkey() == nil { 268 return action.ErrAddress 269 } 270 return nil 271 } 272 273 func (ap *actPool) checkSelpWithoutState(ctx context.Context, selp *action.SealedEnvelope) error { 274 span := tracer.SpanFromContext(ctx) 275 span.AddEvent("actPool.checkSelpWithoutState") 276 defer span.End() 277 278 hash, _ := selp.Hash() 279 // Reject action if it already exists in pool 280 if _, exist := ap.allActions.Get(hash); exist { 281 _actpoolMtc.WithLabelValues("existedAction").Inc() 282 return action.ErrExistedInPool 283 } 284 285 // Reject action if the gas price is lower than the threshold 286 if selp.Encoding() != uint32(iotextypes.Encoding_ETHEREUM_UNPROTECTED) && selp.GasPrice().Cmp(ap.cfg.MinGasPrice()) < 0 { 287 _actpoolMtc.WithLabelValues("gasPriceLower").Inc() 288 actHash, _ := selp.Hash() 289 log.L().Debug("action rejected due to low gas price", 290 zap.String("actionHash", hex.EncodeToString(actHash[:])), 291 zap.String("gasPrice", selp.GasPrice().String())) 292 return action.ErrUnderpriced 293 } 294 295 if _, ok := ap.senderBlackList[selp.SenderAddress().String()]; ok { 296 _actpoolMtc.WithLabelValues("blacklisted").Inc() 297 return errors.Wrap(action.ErrAddress, "action source address is blacklisted") 298 } 299 300 for _, ev := range ap.actionEnvelopeValidators { 301 span.AddEvent("ev.Validate") 302 if err := ev.Validate(ctx, selp); err != nil { 303 return err 304 } 305 } 306 return nil 307 } 308 309 // GetPendingNonce returns pending nonce in pool or confirmed nonce given an account address 310 func (ap *actPool) GetPendingNonce(addrStr string) (uint64, error) { 311 addr, err := address.FromString(addrStr) 312 if err != nil { 313 return 0, err 314 } 315 if pendingNonce, ok := ap.worker[ap.allocatedWorker(addr)].PendingNonce(addr); ok { 316 return pendingNonce, nil 317 } 318 ctx := ap.context(context.Background()) 319 confirmedState, err := accountutil.AccountState(ctx, ap.sf, addr) 320 if err != nil { 321 return 0, err 322 } 323 if protocol.MustGetFeatureCtx(ctx).UseZeroNonceForFreshAccount { 324 return confirmedState.PendingNonceConsideringFreshAccount(), nil 325 } 326 return confirmedState.PendingNonce(), nil 327 } 328 329 // GetUnconfirmedActs returns unconfirmed actions in pool given an account address 330 func (ap *actPool) GetUnconfirmedActs(addrStr string) []*action.SealedEnvelope { 331 addr, err := address.FromString(addrStr) 332 if err != nil { 333 return []*action.SealedEnvelope{} 334 } 335 336 var ret []*action.SealedEnvelope 337 if actions, ok := ap.worker[ap.allocatedWorker(addr)].AllActions(addr); ok { 338 ret = append(ret, actions...) 339 } 340 ret = append(ret, ap.accountDesActs.actionsByDestination(addrStr)...) 341 return ret 342 } 343 344 // GetActionByHash returns the pending action in pool given action's hash 345 func (ap *actPool) GetActionByHash(hash hash.Hash256) (*action.SealedEnvelope, error) { 346 act, ok := ap.allActions.Get(hash) 347 if !ok { 348 return nil, errors.Wrapf(action.ErrNotFound, "action hash %x does not exist in pool", hash) 349 } 350 return act.(*action.SealedEnvelope), nil 351 } 352 353 // GetSize returns the act pool size 354 func (ap *actPool) GetSize() uint64 { 355 return uint64(ap.allActions.Count()) 356 } 357 358 // GetCapacity returns the act pool capacity 359 func (ap *actPool) GetCapacity() uint64 { 360 return ap.cfg.MaxNumActsPerPool 361 } 362 363 // GetGasSize returns the act pool gas size 364 func (ap *actPool) GetGasSize() uint64 { 365 return atomic.LoadUint64(&ap.gasInPool) 366 } 367 368 // GetGasCapacity returns the act pool gas capacity 369 func (ap *actPool) GetGasCapacity() uint64 { 370 return ap.cfg.MaxGasLimitPerPool 371 } 372 373 func (ap *actPool) Validate(ctx context.Context, selp *action.SealedEnvelope) error { 374 return ap.validate(ctx, selp) 375 } 376 377 func (ap *actPool) DeleteAction(caller address.Address) { 378 worker := ap.worker[ap.allocatedWorker(caller)] 379 if pendingActs := worker.ResetAccount(caller); len(pendingActs) != 0 { 380 ap.removeInvalidActs(pendingActs) 381 } 382 } 383 384 func (ap *actPool) validate(ctx context.Context, selp *action.SealedEnvelope) error { 385 span := tracer.SpanFromContext(ctx) 386 span.AddEvent("actPool.validate") 387 defer span.End() 388 389 caller := selp.SenderAddress() 390 if caller == nil { 391 return errors.New("failed to get address") 392 } 393 if _, ok := ap.senderBlackList[caller.String()]; ok { 394 _actpoolMtc.WithLabelValues("blacklisted").Inc() 395 return errors.Wrap(action.ErrAddress, "action source address is blacklisted") 396 } 397 // if already validated 398 selpHash, err := selp.Hash() 399 if err != nil { 400 return err 401 } 402 if _, ok := ap.allActions.Get(selpHash); ok { 403 return nil 404 } 405 for _, ev := range ap.actionEnvelopeValidators { 406 span.AddEvent("ev.Validate") 407 if err := ev.Validate(ctx, selp); err != nil { 408 return err 409 } 410 } 411 412 return nil 413 } 414 415 func (ap *actPool) removeInvalidActs(acts []*action.SealedEnvelope) { 416 for _, act := range acts { 417 hash, err := act.Hash() 418 if err != nil { 419 log.L().Debug("Skipping action due to hash error", zap.Error(err)) 420 continue 421 } 422 log.L().Debug("Removed invalidated action.", log.Hex("hash", hash[:])) 423 ap.allActions.Delete(hash) 424 intrinsicGas, _ := act.IntrinsicGas() 425 atomic.AddUint64(&ap.gasInPool, ^uint64(intrinsicGas-1)) 426 ap.accountDesActs.delete(act) 427 } 428 } 429 430 func (ap *actPool) context(ctx context.Context) context.Context { 431 height, _ := ap.sf.Height() 432 return protocol.WithFeatureCtx(protocol.WithBlockCtx( 433 genesis.WithGenesisContext(ctx, ap.g), protocol.BlockCtx{ 434 BlockHeight: height + 1, 435 })) 436 } 437 438 func (ap *actPool) enqueue(ctx context.Context, act *action.SealedEnvelope, replace bool) error { 439 var errChan = make(chan error, 1) // unused errChan will be garbage-collected 440 ap.jobQueue[ap.allocatedWorker(act.SenderAddress())] <- workerJob{ 441 ctx, 442 act, 443 replace, 444 errChan, 445 } 446 447 for { 448 select { 449 case <-ctx.Done(): 450 log.L().Error("enqueue actpool fails", zap.Error(ctx.Err())) 451 return ctx.Err() 452 case ret := <-errChan: 453 return ret 454 } 455 } 456 } 457 458 func (ap *actPool) allocatedWorker(senderAddr address.Address) int { 459 senderBytes := senderAddr.Bytes() 460 var lastByte uint8 = senderBytes[len(senderBytes)-1] 461 return int(lastByte) % _numWorker 462 } 463 464 type destinationMap struct { 465 mu sync.Mutex 466 acts map[string]map[hash.Hash256]*action.SealedEnvelope 467 } 468 469 func (des *destinationMap) addAction(act *action.SealedEnvelope) error { 470 des.mu.Lock() 471 defer des.mu.Unlock() 472 destn, ok := act.Destination() 473 if !ok { 474 return errors.New("the recipient is empty") 475 } 476 actHash, err := act.Hash() 477 if err != nil { 478 return err 479 } 480 if _, exist := des.acts[destn]; !exist { 481 des.acts[destn] = make(map[hash.Hash256]*action.SealedEnvelope) 482 } 483 des.acts[destn][actHash] = act 484 return nil 485 } 486 487 func (des *destinationMap) actionsByDestination(addr string) []*action.SealedEnvelope { 488 des.mu.Lock() 489 defer des.mu.Unlock() 490 desMap, ok := des.acts[addr] 491 if !ok { 492 return []*action.SealedEnvelope{} 493 } 494 sortActions := make(SortedActions, 0) 495 for _, v := range desMap { 496 sortActions = append(sortActions, v) 497 } 498 sort.Stable(sortActions) 499 return sortActions 500 } 501 502 func (des *destinationMap) delete(act *action.SealedEnvelope) { 503 des.mu.Lock() 504 defer des.mu.Unlock() 505 desAddress, ok := act.Destination() 506 if !ok { 507 return 508 } 509 hash, err := act.Hash() 510 if err != nil { 511 log.L().Debug("Skipping action due to hash error", zap.Error(err)) 512 return 513 } 514 dst, exist := des.acts[desAddress] 515 if !exist { 516 return 517 } 518 delete(dst, hash) 519 if len(dst) == 0 { 520 delete(des.acts, desAddress) 521 } 522 }