github.com/smartcontractkit/chainlink-testing-framework/libs@v0.0.0-20240227141906-ec710b4eb1a3/blockchain/transaction_confirmers.go (about) 1 package blockchain 2 3 import ( 4 "context" 5 "encoding/hex" 6 "encoding/json" 7 "fmt" 8 "math/big" 9 "strings" 10 "sync" 11 "time" 12 13 "github.com/ethereum/go-ethereum" 14 "github.com/ethereum/go-ethereum/accounts/abi" 15 "github.com/ethereum/go-ethereum/common" 16 "github.com/ethereum/go-ethereum/core" 17 "github.com/ethereum/go-ethereum/core/types" 18 "github.com/rs/zerolog" 19 "golang.org/x/sync/errgroup" 20 ) 21 22 // How many successful connection polls to consider the connection restored after a disconnection 23 const targetSuccessCount = 3 24 25 // TransactionConfirmer is an implementation of HeaderEventSubscription that checks whether tx are confirmed 26 type TransactionConfirmer struct { 27 minConfirmations int 28 confirmations int 29 client EVMClient 30 tx *types.Transaction 31 doneChan chan struct{} 32 revertChan chan struct{} 33 context context.Context 34 cancel context.CancelFunc 35 networkConfig *EVMNetwork 36 lastReceivedHeaderNum uint64 37 complete bool 38 completeMu sync.Mutex 39 l zerolog.Logger 40 } 41 42 // NewTransactionConfirmer returns a new instance of the transaction confirmer that waits for on-chain minimum 43 // confirmations 44 func NewTransactionConfirmer(client EVMClient, tx *types.Transaction, minConfirmations int, logger zerolog.Logger) *TransactionConfirmer { 45 ctx, ctxCancel := context.WithTimeout(context.Background(), client.GetNetworkConfig().Timeout.Duration) 46 tc := &TransactionConfirmer{ 47 minConfirmations: minConfirmations, 48 confirmations: 0, 49 client: client, 50 tx: tx, 51 doneChan: make(chan struct{}, 1), 52 revertChan: make(chan struct{}, 1), 53 context: ctx, 54 cancel: ctxCancel, 55 networkConfig: client.GetNetworkConfig(), 56 complete: false, 57 l: logger, 58 } 59 return tc 60 } 61 62 // ReceiveHeader the implementation of the HeaderEventSubscription that receives each header and checks 63 // tx confirmation 64 func (t *TransactionConfirmer) ReceiveHeader(header NodeHeader) error { 65 if header.Number.Uint64() <= t.lastReceivedHeaderNum { 66 return nil // Header with same number mined, disregard for confirming 67 } 68 t.lastReceivedHeaderNum = header.Number.Uint64() 69 confirmationLog := t.l.Debug(). 70 Str("Network Name", t.networkConfig.Name). 71 Str("Header Hash", header.Hash.Hex()). 72 Str("Header Number", header.Number.String()). 73 Str("Tx Hash", t.tx.Hash().String()). 74 Uint64("Nonce", t.tx.Nonce()). 75 Int("Minimum Confirmations", t.minConfirmations) 76 isConfirmed, err := t.client.IsTxConfirmed(t.tx.Hash()) 77 if err != nil { 78 if err.Error() == "not found" { 79 confirmationLog.Msg("Transaction not found on chain yet. Waiting to confirm.") 80 return nil 81 } 82 if strings.Contains(err.Error(), "transaction failed and was reverted") { 83 t.revertChan <- struct{}{} 84 } 85 return err 86 } 87 if isConfirmed { 88 t.confirmations++ 89 } 90 if t.confirmations >= t.minConfirmations { 91 confirmationLog.Int("Current Confirmations", t.confirmations).Msg("Transaction confirmations met") 92 t.complete = true 93 t.doneChan <- struct{}{} 94 } else { 95 confirmationLog.Int("Current Confirmations", t.confirmations).Msg("Waiting on minimum confirmations") 96 } 97 return nil 98 } 99 100 // Wait is a blocking function that waits until the transaction is complete 101 func (t *TransactionConfirmer) Wait() error { 102 defer func() { 103 t.completeMu.Lock() 104 t.complete = true 105 t.completeMu.Unlock() 106 }() 107 108 if t.Complete() { 109 t.cancel() 110 return nil 111 } 112 113 for { 114 select { 115 case <-t.doneChan: 116 t.cancel() 117 return nil 118 case <-t.revertChan: 119 return fmt.Errorf("transaction reverted: %s network %s", t.tx.Hash(), t.client.GetNetworkName()) 120 case <-t.context.Done(): 121 return fmt.Errorf("timeout waiting for transaction to confirm: %s network %s", t.tx.Hash(), t.client.GetNetworkName()) 122 } 123 } 124 } 125 126 // Complete returns if the confirmer has completed or not 127 func (t *TransactionConfirmer) Complete() bool { 128 t.completeMu.Lock() 129 defer t.completeMu.Unlock() 130 return t.complete 131 } 132 133 // InstantConfirmer is a near-instant confirmation method, primarily for optimistic L2s that have near-instant finalization 134 type InstantConfirmer struct { 135 client EVMClient 136 txHash common.Hash 137 complete bool // tracks if the subscription is completed or not 138 context context.Context 139 cancel context.CancelFunc 140 newHeaderChan chan struct{} 141 // For events 142 confirmed bool // tracks the confirmation status of the subscription 143 confirmedChan chan bool 144 log zerolog.Logger 145 } 146 147 func NewInstantConfirmer( 148 client EVMClient, 149 txHash common.Hash, 150 confirmedChan chan bool, 151 _ chan error, 152 logger zerolog.Logger, 153 ) *InstantConfirmer { 154 ctx, ctxCancel := context.WithTimeout(context.Background(), client.GetNetworkConfig().Timeout.Duration) 155 return &InstantConfirmer{ 156 client: client, 157 txHash: txHash, 158 context: ctx, 159 cancel: ctxCancel, 160 // For events 161 confirmedChan: confirmedChan, 162 log: logger, 163 } 164 } 165 166 // ReceiveHeader does a quick check on if the tx is confirmed already 167 func (l *InstantConfirmer) ReceiveHeader(_ NodeHeader) error { 168 return nil 169 } 170 171 // Wait checks every header if the tx has been included on chain or not 172 func (l *InstantConfirmer) Wait() error { 173 defer func() { 174 l.complete = true 175 l.cancel() 176 }() 177 confirmed, err := l.checkConfirmed() 178 if err != nil { 179 return err 180 } 181 if confirmed { 182 return nil 183 } 184 185 poll := time.NewTicker(time.Millisecond * 250) 186 for { 187 select { 188 case <-poll.C: 189 confirmed, err := l.checkConfirmed() 190 if err != nil { 191 return err 192 } 193 if confirmed { 194 return nil 195 } 196 case <-l.newHeaderChan: 197 confirmed, err := l.checkConfirmed() 198 if err != nil { 199 return err 200 } 201 if confirmed { 202 return nil 203 } 204 case <-l.context.Done(): 205 return fmt.Errorf("timeout waiting for instant transaction to confirm after %s: %s", 206 l.client.GetNetworkConfig().Timeout.String(), l.txHash.Hex()) 207 } 208 } 209 } 210 211 func (l *InstantConfirmer) checkConfirmed() (bool, error) { 212 confirmed, err := l.client.IsTxConfirmed(l.txHash) 213 if err != nil { 214 return false, err 215 } 216 l.confirmed = confirmed 217 if confirmed { 218 go func() { 219 if l.confirmedChan != nil { 220 l.confirmedChan <- true 221 } 222 }() 223 } 224 l.log.Trace().Bool("Confirmed", confirmed).Str("Hash", l.txHash.Hex()).Msg("Checked if transaction confirmed") 225 return confirmed, nil 226 } 227 228 // Complete returns if the transaction is complete or not 229 func (l *InstantConfirmer) Complete() bool { 230 return l.complete 231 } 232 233 // EventConfirmer confirms that an event is confirmed by a certain amount of headers 234 type EventConfirmer struct { 235 eventName string 236 minConfirmations int 237 confirmations int 238 client EVMClient 239 event *types.Log 240 waitChan chan struct{} 241 errorChan chan error 242 confirmedChan chan bool 243 context context.Context 244 cancel context.CancelFunc 245 lastReceivedHeaderNum uint64 246 complete bool 247 } 248 249 // NewEventConfirmer returns a new instance of the event confirmer that waits for on-chain minimum 250 // confirmations 251 func NewEventConfirmer( 252 eventName string, 253 client EVMClient, 254 event *types.Log, 255 minConfirmations int, 256 confirmedChan chan bool, 257 errorChan chan error, 258 ) *EventConfirmer { 259 ctx, ctxCancel := context.WithTimeout(context.Background(), client.GetNetworkConfig().Timeout.Duration) 260 tc := &EventConfirmer{ 261 eventName: eventName, 262 minConfirmations: minConfirmations, 263 confirmations: 0, 264 client: client, 265 event: event, 266 waitChan: make(chan struct{}, 1), 267 errorChan: errorChan, 268 confirmedChan: confirmedChan, 269 context: ctx, 270 cancel: ctxCancel, 271 complete: false, 272 } 273 return tc 274 } 275 276 // ReceiveHeader will attempt to confirm an event for the chain's configured minimum confirmed headers. Errors encountered 277 // are sent along the eventErrorChan, and the result of confirming the event is sent to eventConfirmedChan. 278 func (e *EventConfirmer) ReceiveHeader(header NodeHeader) error { 279 if header.Number.Uint64() <= e.lastReceivedHeaderNum { 280 return nil 281 } 282 e.lastReceivedHeaderNum = header.Number.Uint64() 283 confirmed, removed, err := e.client.IsEventConfirmed(e.event) 284 if err != nil { 285 e.errorChan <- err 286 return err 287 } 288 if removed { 289 e.confirmedChan <- false 290 e.complete = true 291 return nil 292 } 293 if confirmed { 294 e.confirmations++ 295 } 296 if e.confirmations >= e.minConfirmations { 297 e.confirmedChan <- true 298 e.complete = true 299 } 300 return nil 301 } 302 303 // Wait until the event fully presents as complete 304 func (e *EventConfirmer) Wait() error { 305 defer func() { e.complete = true }() 306 for { 307 select { 308 case <-e.waitChan: 309 e.cancel() 310 return nil 311 case <-e.context.Done(): 312 return fmt.Errorf("timeout waiting for event to confirm after %s: %s", 313 e.client.GetNetworkConfig().Timeout.String(), e.event.TxHash.Hex()) 314 } 315 } 316 } 317 318 // Complete returns if the confirmer is done, whether confirmation was successful or not 319 func (e *EventConfirmer) Complete() bool { 320 return e.complete 321 } 322 323 // GetHeaderSubscriptions returns a duplicate map of the queued transactions 324 func (e *EthereumClient) GetHeaderSubscriptions() map[string]HeaderEventSubscription { 325 e.subscriptionMutex.Lock() 326 defer e.subscriptionMutex.Unlock() 327 328 newMap := map[string]HeaderEventSubscription{} 329 for k, v := range e.headerSubscriptions { 330 newMap[k] = v 331 } 332 return newMap 333 } 334 335 // subscribeToNewHeaders kicks off the primary header subscription for the test loop 336 func (e *EthereumClient) subscribeToNewHeaders() error { 337 headerChannel := make(chan *SafeEVMHeader) 338 subscription, err := e.SubscribeNewHeaders(context.Background(), headerChannel) 339 if err != nil { 340 return err 341 } 342 e.l.Info().Str("Network", e.NetworkConfig.Name).Msg("Subscribed to new block headers") 343 344 e.subscriptionWg.Add(1) 345 go e.headerSubscriptionLoop(subscription, headerChannel) 346 return nil 347 } 348 349 // headerSubscriptionLoop receives new headers, and handles subscription errors when they pop up 350 func (e *EthereumClient) headerSubscriptionLoop(subscription ethereum.Subscription, headerChannel chan *SafeEVMHeader) { 351 defer e.subscriptionWg.Done() 352 lastHeaderNumber := uint64(0) 353 for { 354 select { 355 case err := <-subscription.Err(): // Most subscription errors are temporary RPC downtime, so let's poll to resubscribe 356 e.l.Error(). 357 Str("URL Suffix", e.NetworkConfig.URL[len(e.NetworkConfig.URL)-6:]). 358 Str("Network", e.NetworkConfig.Name). 359 Err(err). 360 Msg("Error while subscribed to new headers, likely RPC downtime. Attempting to resubscribe") 361 e.connectionIssueCh <- time.Now() 362 subscription = e.resubscribeLoop(headerChannel, lastHeaderNumber) 363 e.connectionRestoredCh <- time.Now() 364 case header := <-headerChannel: 365 err := e.receiveHeader(header) 366 if err != nil { 367 e.l.Error().Str("Network", e.NetworkConfig.Name).Err(err).Msg("Error receiving header, possible RPC issues") 368 } else { 369 lastHeaderNumber = header.Number.Uint64() 370 } 371 case <-e.doneChan: 372 e.l.Debug().Str("Network", e.NetworkConfig.Name).Msg("Subscription cancelled") 373 e.Client.Close() 374 return 375 } 376 } 377 } 378 379 // resubscribeLoop polls the RPC connection until it comes back up, and then resubscribes to new headers 380 func (e *EthereumClient) resubscribeLoop(headerChannel chan *SafeEVMHeader, lastHeaderNumber uint64) ethereum.Subscription { 381 rpcDegradedTime := time.Now() 382 pollInterval := time.Millisecond * 500 383 checkTicker, informTicker := time.NewTicker(pollInterval), time.NewTicker(time.Second*10) 384 reconnectAttempts, consecutiveSuccessCount := 0, 0 385 e.l.Debug().Str("Network", e.NetworkConfig.Name).Str("Poll Interval", pollInterval.String()).Msg("Attempting to resubscribe to new headers") 386 387 for { 388 select { 389 case <-checkTicker.C: 390 reconnectAttempts++ 391 _, err := e.LatestBlockNumber(context.Background()) 392 if err != nil { 393 e.l.Trace().Err(err).Msg("Error trying to resubscribe to new headers, likely RPC down") 394 consecutiveSuccessCount = 0 395 continue 396 } 397 consecutiveSuccessCount++ 398 e.l.Debug(). 399 Str("Network", e.NetworkConfig.Name). 400 Str("URL Suffix", e.NetworkConfig.URL[len(e.NetworkConfig.URL)-6:]). 401 Int("Target Success Count", targetSuccessCount). 402 Int("Consecutive Success Count", consecutiveSuccessCount). 403 Msg("RPC connection seems to be healthy, still checking") 404 405 if consecutiveSuccessCount >= targetSuccessCount { // Make sure node is actually back up and not just a blip 406 subscription, err := e.SubscribeNewHeaders(context.Background(), headerChannel) 407 if err != nil { 408 e.l.Error().Err(err).Msg("Error resubscribing to new headers, RPC connection is still coming up") 409 consecutiveSuccessCount = 0 410 continue 411 } 412 // No error on resubscription, RPC connection restored, back to regularly scheduled programming 413 checkTicker.Stop() 414 informTicker.Stop() 415 e.l.Info(). 416 Int("Reconnect Attempts", reconnectAttempts). 417 Str("URL Suffix", e.NetworkConfig.URL[len(e.NetworkConfig.URL)-6:]). 418 Str("Time waiting", time.Since(rpcDegradedTime).String()). 419 Msg("RPC connection and subscription restored") 420 go e.backfillMissedBlocks(lastHeaderNumber, headerChannel) 421 return subscription 422 } 423 case <-informTicker.C: 424 e.l.Warn(). 425 Str("Network", e.NetworkConfig.Name). 426 Str("URL Suffix", e.NetworkConfig.URL[len(e.NetworkConfig.URL)-6:]). 427 Int("Reconnect Attempts", reconnectAttempts). 428 Str("Time waiting", time.Since(rpcDegradedTime).String()). 429 Str("RPC URL", e.NetworkConfig.URL). 430 Msg("RPC connection still down, waiting for it to come back up") 431 } 432 } 433 } 434 435 // backfillMissedBlocks checks if there are any missed blocks since a bad connection was detected, and if so, backfills them 436 // to our header channel 437 func (e *EthereumClient) backfillMissedBlocks(lastBlockSeen uint64, headerChannel chan *SafeEVMHeader) { 438 start := time.Now() 439 latestBlockNumber, err := e.LatestBlockNumber(context.Background()) 440 if err != nil { 441 e.l.Error().Str("Network", e.NetworkConfig.Name).Err(err).Msg("Error getting latest block number. Unable to backfill missed blocks. Subscription likely degraded") 442 return 443 } 444 if latestBlockNumber <= lastBlockSeen { 445 e.l.Info().Msg("No missed blocks to backfill") 446 return 447 } 448 e.l.Info(). 449 Str("Network", e.NetworkConfig.Name). 450 Uint64("Last Block Seen", lastBlockSeen). 451 Uint64("Blocks Behind", latestBlockNumber-lastBlockSeen). 452 Uint64("Latest Block", latestBlockNumber). 453 Msg("Backfilling missed blocks since RPC connection issues") 454 for i := lastBlockSeen + 1; i <= latestBlockNumber; i++ { 455 header, err := e.HeaderByNumber(context.Background(), big.NewInt(int64(i))) 456 if err != nil { 457 e.l.Err(err).Uint64("Number", i).Msg("Error getting header, unable to backfill and process it") 458 return 459 } 460 e.l.Trace().Str("Network", e.NetworkConfig.Name).Str("Hash", header.Hash.Hex()).Uint64("Number", i).Msg("Backfilling header") 461 headerChannel <- header 462 } 463 e.l.Info(). 464 Str("Network", e.NetworkConfig.Name). 465 Uint64("Backfilled blocks", latestBlockNumber-lastBlockSeen). 466 Str("Time", time.Since(start).String()). 467 Msg("Finished backfilling missed blocks") 468 } 469 470 // receiveHeader takes in a new header from the chain, and sends the header to all active header subscriptions 471 func (e *EthereumClient) receiveHeader(header *SafeEVMHeader) error { 472 if header == nil { 473 e.l.Debug().Msg("Received Nil Header") 474 return nil 475 } 476 headerValue := *header 477 478 e.l.Debug(). 479 Str("NetworkName", e.NetworkConfig.Name). 480 Int("Node", e.ID). 481 Str("Hash", headerValue.Hash.String()). 482 Str("Number", headerValue.Number.String()). 483 Msg("Received Header") 484 485 safeHeader := NodeHeader{NodeID: e.ID, SafeEVMHeader: headerValue} 486 subs := e.GetHeaderSubscriptions() 487 e.l.Trace(). 488 Str("NetworkName", e.NetworkConfig.Name). 489 Int("Node", e.ID). 490 Interface("Map", subs).Msg("Active Header Subscriptions") 491 492 g := errgroup.Group{} 493 for _, sub := range subs { 494 sub := sub 495 g.Go(func() error { 496 return sub.ReceiveHeader(safeHeader) 497 }) 498 } 499 500 if err := g.Wait(); err != nil { 501 return fmt.Errorf("error on sending block header to receivers: '%w'", err) 502 } 503 504 if len(subs) > 0 { 505 var subsRemoved uint 506 for key, sub := range subs { // Cleanup subscriptions that might not have Wait called on them 507 if sub.Complete() { 508 subsRemoved++ 509 e.DeleteHeaderEventSubscription(key) 510 } 511 } 512 if subsRemoved > 0 { 513 e.l.Trace(). 514 Str("NetworkName", e.NetworkConfig.Name). 515 Int("Node", e.ID). 516 Uint("Recently Removed", subsRemoved). 517 Int("Active", len(e.GetHeaderSubscriptions())). 518 Msg("Updated Header Subscriptions") 519 } 520 } 521 return nil 522 } 523 524 // errorReason decodes tx revert reason 525 func (e *EthereumClient) errorReason( 526 b ethereum.ContractCaller, 527 tx *types.Transaction, 528 receipt *types.Receipt, 529 ) (string, error) { 530 chID, err := e.Client.NetworkID(context.Background()) 531 if err != nil { 532 return "", err 533 } 534 var msg *core.Message 535 if e.NetworkConfig.SupportsEIP1559 { 536 msg, err = core.TransactionToMessage(tx, types.LatestSignerForChainID(chID), nil) 537 } else { 538 msg, err = core.TransactionToMessage(tx, types.NewEIP155Signer(chID), nil) 539 } 540 if err != nil { 541 return "", err 542 } 543 544 callMsg := ethereum.CallMsg{ 545 From: msg.From, 546 To: tx.To(), 547 Gas: tx.Gas(), 548 GasPrice: tx.GasPrice(), 549 Value: tx.Value(), 550 Data: tx.Data(), 551 } 552 _, txError := b.CallContract(context.Background(), callMsg, receipt.BlockNumber) 553 if txError == nil { 554 return "", fmt.Errorf("no error in CallContract: %w", err) 555 } 556 return RPCErrorFromError(txError) 557 } 558 559 func RPCErrorFromError(txError error) (string, error) { 560 errBytes, err := json.Marshal(txError) 561 if err != nil { 562 return "", err 563 } 564 var callErr struct { 565 Code int 566 Data string `json:"data"` 567 Message string `json:"message"` 568 } 569 err = json.Unmarshal(errBytes, &callErr) 570 if err != nil { 571 return "", err 572 } 573 // If the error data is blank 574 if len(callErr.Data) == 0 { 575 return callErr.Data, nil 576 } 577 // Some nodes prepend "Reverted " and we also remove the 0x 578 trimmed := strings.TrimPrefix(callErr.Data, "Reverted ")[2:] 579 data, err := hex.DecodeString(trimmed) 580 if err != nil { 581 return "", err 582 } 583 revert, err := abi.UnpackRevert(data) 584 // If we can't decode the revert reason, return the raw data 585 if err != nil { 586 return callErr.Data, nil 587 } 588 return revert, nil 589 }