Skip to content

Commit

Permalink
Added nonceHandlerV3.
Browse files Browse the repository at this point in the history
  • Loading branch information
cristure authored Mar 14, 2024
2 parents 23f6a82 + a717e00 commit b022055
Show file tree
Hide file tree
Showing 11 changed files with 1,020 additions and 11 deletions.
3 changes: 3 additions & 0 deletions interactors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,6 @@ var ErrTxWithSameNonceAndGasPriceAlreadySent = errors.New("transaction with the

// ErrGapNonce signals that a gap nonce between the lowest nonce of the transactions from the cache and the blockchain nonce has been detected
var ErrGapNonce = errors.New("gap nonce detected")

// ErrWorkerClosed signals that the worker is closed
var ErrWorkerClosed = errors.New("worker closed")
19 changes: 18 additions & 1 deletion interactors/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"

"github.com/multiversx/mx-chain-core-go/data/transaction"

"github.com/multiversx/mx-sdk-go/core"
"github.com/multiversx/mx-sdk-go/data"
)
Expand Down Expand Up @@ -40,6 +41,14 @@ type AddressNonceHandler interface {
IsInterfaceNil() bool
}

// AddressNonceHandlerV3 defines the component able to handler address nonces
type AddressNonceHandlerV3 interface {
ApplyNonceAndGasPrice(ctx context.Context, tx ...*transaction.FrontendTransaction) error
SendTransaction(ctx context.Context, tx *transaction.FrontendTransaction) (string, error)
IsInterfaceNil() bool
Close()
}

// TransactionNonceHandlerV1 defines the component able to manage transaction nonces
type TransactionNonceHandlerV1 interface {
GetNonce(ctx context.Context, address core.AddressHandler) (uint64, error)
Expand All @@ -49,10 +58,18 @@ type TransactionNonceHandlerV1 interface {
IsInterfaceNil() bool
}

// TransactionNonceHandlerV2 defines the component able to apply nonce for a given frontend transaction
// TransactionNonceHandlerV2 defines the component able to apply nonce for a given frontend transaction.
type TransactionNonceHandlerV2 interface {
ApplyNonceAndGasPrice(ctx context.Context, address core.AddressHandler, tx *transaction.FrontendTransaction) error
SendTransaction(ctx context.Context, tx *transaction.FrontendTransaction) (string, error)
Close() error
IsInterfaceNil() bool
}

// TransactionNonceHandlerV3 defines the component able to apply nonce for a given frontend transaction.
type TransactionNonceHandlerV3 interface {
ApplyNonceAndGasPrice(ctx context.Context, tx ...*transaction.FrontendTransaction) error
SendTransactions(ctx context.Context, txs ...*transaction.FrontendTransaction) ([]string, error)
Close()
IsInterfaceNil() bool
}
1 change: 1 addition & 0 deletions interactors/nonceHandlerV1/addressNonceHandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/multiversx/mx-chain-core-go/core"
"github.com/multiversx/mx-chain-core-go/data/transaction"

sdkCore "github.com/multiversx/mx-sdk-go/core"
"github.com/multiversx/mx-sdk-go/interactors"
)
Expand Down
9 changes: 4 additions & 5 deletions interactors/nonceHandlerV1/nonceTransactionsHandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/multiversx/mx-chain-core-go/core/check"
"github.com/multiversx/mx-chain-core-go/data/transaction"
logger "github.com/multiversx/mx-chain-logger-go"

"github.com/multiversx/mx-sdk-go/core"
"github.com/multiversx/mx-sdk-go/data"
"github.com/multiversx/mx-sdk-go/interactors"
Expand Down Expand Up @@ -111,14 +112,12 @@ func (nth *nonceTransactionsHandlerV1) SendTransaction(ctx context.Context, tx *
}

func (nth *nonceTransactionsHandlerV1) resendTransactionsLoop(ctx context.Context, intervalToResend time.Duration) {
timer := time.NewTimer(intervalToResend)
defer timer.Stop()
ticker := time.NewTicker(intervalToResend)
defer ticker.Stop()

for {
timer.Reset(intervalToResend)

select {
case <-timer.C:
case <-ticker.C:
nth.resendTransactions(ctx)
case <-ctx.Done():
log.Debug("finishing nonceTransactionsHandlerV1.resendTransactionsLoop...")
Expand Down
1 change: 1 addition & 0 deletions interactors/nonceHandlerV2/addressNonceHandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/multiversx/mx-chain-core-go/core"
"github.com/multiversx/mx-chain-core-go/core/check"
"github.com/multiversx/mx-chain-core-go/data/transaction"

sdkCore "github.com/multiversx/mx-sdk-go/core"
"github.com/multiversx/mx-sdk-go/interactors"
)
Expand Down
9 changes: 4 additions & 5 deletions interactors/nonceHandlerV2/nonceTransactionsHandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/multiversx/mx-chain-core-go/core/check"
"github.com/multiversx/mx-chain-core-go/data/transaction"
logger "github.com/multiversx/mx-chain-logger-go"

"github.com/multiversx/mx-sdk-go/core"
"github.com/multiversx/mx-sdk-go/data"
"github.com/multiversx/mx-sdk-go/interactors"
Expand Down Expand Up @@ -149,14 +150,12 @@ func (nth *nonceTransactionsHandlerV2) SendTransaction(ctx context.Context, tx *
}

func (nth *nonceTransactionsHandlerV2) resendTransactionsLoop(ctx context.Context) {
timer := time.NewTimer(nth.intervalToResend)
defer timer.Stop()
ticker := time.NewTicker(nth.intervalToResend)
defer ticker.Stop()

for {
timer.Reset(nth.intervalToResend)

select {
case <-timer.C:
case <-ticker.C:
nth.resendTransactions(ctx)
case <-ctx.Done():
log.Debug("finishing nonceTransactionsHandlerV2.resendTransactionsLoop...")
Expand Down
148 changes: 148 additions & 0 deletions interactors/nonceHandlerV3/addressNonceHandler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package nonceHandlerV3

import (
"context"
"fmt"
"sync"
"time"

"github.com/multiversx/mx-chain-core-go/core"
"github.com/multiversx/mx-chain-core-go/core/check"
"github.com/multiversx/mx-chain-core-go/data/transaction"

sdkCore "github.com/multiversx/mx-sdk-go/core"
"github.com/multiversx/mx-sdk-go/interactors"
"github.com/multiversx/mx-sdk-go/interactors/nonceHandlerV3/workers"
)

// addressNonceHandler is the handler used for one address. It is able to handle the current
// nonce as max(current_stored_nonce, account_nonce). After each call of the getNonce function
// the current_stored_nonce is incremented. This will prevent "nonce too low in transaction"
// errors on the node interceptor. To prevent the "nonce too high in transaction" error,
// a retrial mechanism is implemented. This struct is able to store all sent transactions,
// having a function that sweeps the map in order to resend a transaction or remove them
// because they were executed. This struct is concurrent safe.
type addressNonceHandler struct {
mut sync.RWMutex
address sdkCore.AddressHandler
proxy interactors.Proxy
nonce int64
gasPrice uint64
transactionWorker *workers.TransactionWorker
cancelFunc func()
}

// NewAddressNonceHandlerV3 returns a new instance of a addressNonceHandler
func NewAddressNonceHandlerV3(proxy interactors.Proxy, address sdkCore.AddressHandler, intervalToSend time.Duration) (*addressNonceHandler, error) {
if check.IfNil(proxy) {
return nil, interactors.ErrNilProxy
}
if check.IfNil(address) {
return nil, interactors.ErrNilAddress
}

ctx, cancelFunc := context.WithCancel(context.Background())

anh := &addressNonceHandler{
mut: sync.RWMutex{},
address: address,
nonce: -1,
proxy: proxy,
transactionWorker: workers.NewTransactionWorker(ctx, proxy, intervalToSend),
cancelFunc: cancelFunc,
}

return anh, nil
}

// ApplyNonceAndGasPrice will apply the computed nonce to the given FrontendTransaction
func (anh *addressNonceHandler) ApplyNonceAndGasPrice(ctx context.Context, txs ...*transaction.FrontendTransaction) error {
for _, tx := range txs {
nonce, err := anh.computeNonce(ctx)
if err != nil {
//anh.mut.Unlock()
return fmt.Errorf("failed to fetch nonce: %w", err)
}
tx.Nonce = uint64(nonce)

anh.applyGasPriceIfRequired(ctx, tx)
}

return nil
}

// SendTransaction will save and propagate a transaction to the network
func (anh *addressNonceHandler) SendTransaction(ctx context.Context, tx *transaction.FrontendTransaction) (string, error) {
ch := anh.transactionWorker.AddTransaction(tx)

select {
case response := <-ch:
anh.adaptNonceBasedOnResponse(response)

return response.TxHash, response.Error

case <-ctx.Done():
return "", ctx.Err()
}
}

func (anh *addressNonceHandler) adaptNonceBasedOnResponse(response *workers.TransactionResponse) {
anh.mut.Lock()
defer anh.mut.Unlock()

// if the response did not contain any errors, increase the cached nonce.
if response.Error == nil {
anh.nonce++
return
}
// we invalidate the cache if there was an error sending the transaction.
anh.nonce = -1
}

// IsInterfaceNil returns true if there is no value under the interface
func (anh *addressNonceHandler) IsInterfaceNil() bool {
return anh == nil
}

// Close will cancel all related processes..
func (anh *addressNonceHandler) Close() {
anh.cancelFunc()
}

func (anh *addressNonceHandler) applyGasPriceIfRequired(ctx context.Context, tx *transaction.FrontendTransaction) {
anh.mut.RLock()
gasPrice := anh.gasPrice
anh.mut.RUnlock()

if gasPrice == 0 {
networkConfig, err := anh.proxy.GetNetworkConfig(ctx)

if err != nil {
log.Error("%w: while fetching network config", err)
}

gasPrice = networkConfig.MinGasPrice
}
anh.mut.Lock()
defer anh.mut.Unlock()
anh.gasPrice = gasPrice
tx.GasPrice = core.MaxUint64(gasPrice, tx.GasPrice)
}

func (anh *addressNonceHandler) computeNonce(ctx context.Context) (int64, error) {
// if it is the first time applying nonces to this address, or if the cache was invalidated it will try to fetch
// the nonce from the chain.
anh.mut.Lock()
defer anh.mut.Unlock()

if anh.nonce == -1 {
account, err := anh.proxy.GetAccount(ctx, anh.address)
if err != nil {
return -1, fmt.Errorf("failed to fetch nonce: %w", err)
}
anh.nonce = int64(account.Nonce)
} else {
anh.nonce++
}
return anh.nonce, nil
}
Loading

0 comments on commit b022055

Please sign in to comment.