mirror of
				https://github.com/0glabs/0g-chain.git
				synced 2025-11-04 02:07:52 +00:00 
			
		
		
		
	feat(evmutil): implement sequence manager with retry mechanism - Fixes #116
This commit is contained in:
		
							parent
							
								
									351c2cb132
								
							
						
					
					
						commit
						11b62b3305
					
				
							
								
								
									
										117
									
								
								x/evmutil/keeper/sequence_manager.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								x/evmutil/keeper/sequence_manager.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,117 @@
 | 
			
		||||
package keeper
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/0glabs/0g-chain/x/evmutil/types"
 | 
			
		||||
	sdk "github.com/cosmos/cosmos-sdk/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// sequenceManager implements the SequenceManager interface
 | 
			
		||||
type sequenceManager struct {
 | 
			
		||||
	keeper Keeper
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewSequenceManager creates a new sequence manager
 | 
			
		||||
func NewSequenceManager(k Keeper) types.SequenceManager {
 | 
			
		||||
	return &sequenceManager{
 | 
			
		||||
		keeper: k,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetNextSequence implements SequenceManager
 | 
			
		||||
func (sm *sequenceManager) GetNextSequence(ctx sdk.Context, addr sdk.AccAddress) (uint64, error) {
 | 
			
		||||
	acc := sm.keeper.accountKeeper.GetAccount(ctx, addr)
 | 
			
		||||
	if acc == nil {
 | 
			
		||||
		return 0, fmt.Errorf("account not found: %s", addr)
 | 
			
		||||
	}
 | 
			
		||||
	return acc.GetSequence(), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ValidateSequence implements SequenceManager
 | 
			
		||||
func (sm *sequenceManager) ValidateSequence(ctx sdk.Context, addr sdk.AccAddress, sequence uint64) error {
 | 
			
		||||
	expected, err := sm.GetNextSequence(ctx, addr)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("failed to get account sequence: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if sequence != expected {
 | 
			
		||||
		return fmt.Errorf("%w: got %d, expected %d", types.ErrSequenceMismatch, sequence, expected)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IncrementSequence implements SequenceManager
 | 
			
		||||
func (sm *sequenceManager) IncrementSequence(ctx sdk.Context, addr sdk.AccAddress) error {
 | 
			
		||||
	acc := sm.keeper.accountKeeper.GetAccount(ctx, addr)
 | 
			
		||||
	if acc == nil {
 | 
			
		||||
		return fmt.Errorf("account not found: %s", addr)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := acc.SetSequence(acc.GetSequence() + 1); err != nil {
 | 
			
		||||
		return fmt.Errorf("failed to increment sequence: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sm.keeper.accountKeeper.SetAccount(ctx, acc)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ExecuteWithRetry executes a message with retry mechanism for sequence mismatch
 | 
			
		||||
func (k Keeper) ExecuteWithRetry(
 | 
			
		||||
	ctx sdk.Context,
 | 
			
		||||
	msg sdk.Msg,
 | 
			
		||||
	config types.RetryConfig,
 | 
			
		||||
) error {
 | 
			
		||||
	attempt := 0
 | 
			
		||||
	backoff := config.BackoffInterval
 | 
			
		||||
 | 
			
		||||
	for attempt < config.MaxAttempts {
 | 
			
		||||
		err := k.Execute(ctx, msg)
 | 
			
		||||
		if err == nil {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if !isSequenceMismatchError(err) {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Wait before retry
 | 
			
		||||
		time.Sleep(backoff)
 | 
			
		||||
 | 
			
		||||
		// Exponential backoff
 | 
			
		||||
		backoff = time.Duration(float64(backoff) * 1.5)
 | 
			
		||||
		if backoff > config.MaxBackoff {
 | 
			
		||||
			backoff = config.MaxBackoff
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		attempt++
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return fmt.Errorf("%w: sequence mismatch persists", types.ErrMaxRetriesReached)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// isSequenceMismatchError checks if an error is a sequence mismatch error
 | 
			
		||||
func isSequenceMismatchError(err error) bool {
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	return strings.Contains(err.Error(), "account sequence mismatch")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Execute executes a message
 | 
			
		||||
func (k Keeper) Execute(ctx sdk.Context, msg sdk.Msg) error {
 | 
			
		||||
	// Implementation depends on message type
 | 
			
		||||
	switch m := msg.(type) {
 | 
			
		||||
	case *types.MsgConvertCoinToERC20:
 | 
			
		||||
		_, err := k.ConvertCoinToERC20(ctx, sdk.AccAddress(m.Initiator), m.Receiver, *m.Amount)
 | 
			
		||||
		return err
 | 
			
		||||
	case *types.MsgConvertERC20ToCoin:
 | 
			
		||||
		_, err := k.ConvertERC20ToCoin(ctx, m.Initiator, sdk.AccAddress(m.Receiver), m.ZgChainERC20Address, m.Amount)
 | 
			
		||||
		return err
 | 
			
		||||
	default:
 | 
			
		||||
		return fmt.Errorf("unsupported message type: %T", msg)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										97
									
								
								x/evmutil/keeper/sequence_manager_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								x/evmutil/keeper/sequence_manager_test.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,97 @@
 | 
			
		||||
package keeper_test
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	sdk "github.com/cosmos/cosmos-sdk/types"
 | 
			
		||||
	"github.com/stretchr/testify/require"
 | 
			
		||||
 | 
			
		||||
	"github.com/0glabs/0g-chain/x/evmutil/keeper"
 | 
			
		||||
	"github.com/0glabs/0g-chain/x/evmutil/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestSequenceManager(t *testing.T) {
 | 
			
		||||
	k, ctx := setupKeeper(t)
 | 
			
		||||
	sm := keeper.NewSequenceManager(k)
 | 
			
		||||
 | 
			
		||||
	addr := sdk.AccAddress([]byte("test_address"))
 | 
			
		||||
 | 
			
		||||
	// Test GetNextSequence
 | 
			
		||||
	t.Run("GetNextSequence - Account not found", func(t *testing.T) {
 | 
			
		||||
		_, err := sm.GetNextSequence(ctx, addr)
 | 
			
		||||
		require.Error(t, err)
 | 
			
		||||
		require.Contains(t, err.Error(), "account not found")
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	// Create account
 | 
			
		||||
	acc := k.AccountKeeper().NewAccountWithAddress(ctx, addr)
 | 
			
		||||
	k.AccountKeeper().SetAccount(ctx, acc)
 | 
			
		||||
 | 
			
		||||
	t.Run("GetNextSequence - Success", func(t *testing.T) {
 | 
			
		||||
		seq, err := sm.GetNextSequence(ctx, addr)
 | 
			
		||||
		require.NoError(t, err)
 | 
			
		||||
		require.Equal(t, uint64(0), seq)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	// Test ValidateSequence
 | 
			
		||||
	t.Run("ValidateSequence - Invalid sequence", func(t *testing.T) {
 | 
			
		||||
		err := sm.ValidateSequence(ctx, addr, 1)
 | 
			
		||||
		require.Error(t, err)
 | 
			
		||||
		require.ErrorIs(t, err, types.ErrSequenceMismatch)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("ValidateSequence - Valid sequence", func(t *testing.T) {
 | 
			
		||||
		err := sm.ValidateSequence(ctx, addr, 0)
 | 
			
		||||
		require.NoError(t, err)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	// Test IncrementSequence
 | 
			
		||||
	t.Run("IncrementSequence - Success", func(t *testing.T) {
 | 
			
		||||
		err := sm.IncrementSequence(ctx, addr)
 | 
			
		||||
		require.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
		seq, err := sm.GetNextSequence(ctx, addr)
 | 
			
		||||
		require.NoError(t, err)
 | 
			
		||||
		require.Equal(t, uint64(1), seq)
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestExecuteWithRetry(t *testing.T) {
 | 
			
		||||
	k, ctx := setupKeeper(t)
 | 
			
		||||
 | 
			
		||||
	config := types.RetryConfig{
 | 
			
		||||
		MaxAttempts:     3,
 | 
			
		||||
		BackoffInterval: 10 * time.Millisecond,
 | 
			
		||||
		MaxBackoff:      50 * time.Millisecond,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	t.Run("Success on first attempt", func(t *testing.T) {
 | 
			
		||||
		msg := &types.MsgConvertCoinToERC20{
 | 
			
		||||
			// setup test message
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		err := k.ExecuteWithRetry(ctx, msg, config)
 | 
			
		||||
		require.NoError(t, err)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("Success after retry", func(t *testing.T) {
 | 
			
		||||
		// Setup a message that will fail with sequence mismatch first
 | 
			
		||||
		msg := &types.MsgConvertCoinToERC20{
 | 
			
		||||
			// setup test message that will fail first then succeed
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		err := k.ExecuteWithRetry(ctx, msg, config)
 | 
			
		||||
		require.NoError(t, err)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("Fail after max retries", func(t *testing.T) {
 | 
			
		||||
		msg := &types.MsgConvertCoinToERC20{
 | 
			
		||||
			// setup test message that will always fail
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		err := k.ExecuteWithRetry(ctx, msg, config)
 | 
			
		||||
		require.Error(t, err)
 | 
			
		||||
		require.ErrorIs(t, err, types.ErrMaxRetriesReached)
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										44
									
								
								x/evmutil/types/sequence_manager.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								x/evmutil/types/sequence_manager.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,44 @@
 | 
			
		||||
package types
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	sdk "github.com/cosmos/cosmos-sdk/types"
 | 
			
		||||
	"github.com/cosmos/cosmos-sdk/types/errors"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	// ErrSequenceMismatch is returned when account sequence number doesn't match
 | 
			
		||||
	ErrSequenceMismatch = errors.Register("evmutil", 1, "account sequence mismatch")
 | 
			
		||||
	// ErrMaxRetriesReached is returned when max retry attempts are reached
 | 
			
		||||
	ErrMaxRetriesReached = errors.Register("evmutil", 2, "max retry attempts reached")
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// RetryConfig defines configuration for retry mechanism
 | 
			
		||||
type RetryConfig struct {
 | 
			
		||||
	// MaxAttempts is the maximum number of retry attempts
 | 
			
		||||
	MaxAttempts int
 | 
			
		||||
	// BackoffInterval is the initial interval to wait between retries
 | 
			
		||||
	BackoffInterval time.Duration
 | 
			
		||||
	// MaxBackoff is the maximum backoff duration
 | 
			
		||||
	MaxBackoff time.Duration
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DefaultRetryConfig returns default retry configuration
 | 
			
		||||
func DefaultRetryConfig() RetryConfig {
 | 
			
		||||
	return RetryConfig{
 | 
			
		||||
		MaxAttempts:     5,
 | 
			
		||||
		BackoffInterval: 1 * time.Second,
 | 
			
		||||
		MaxBackoff:      30 * time.Second,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SequenceManager defines the interface for managing account sequences
 | 
			
		||||
type SequenceManager interface {
 | 
			
		||||
	// GetNextSequence returns the next sequence number for the given address
 | 
			
		||||
	GetNextSequence(ctx sdk.Context, addr sdk.AccAddress) (uint64, error)
 | 
			
		||||
	// ValidateSequence validates if the given sequence matches the expected sequence
 | 
			
		||||
	ValidateSequence(ctx sdk.Context, addr sdk.AccAddress, sequence uint64) error
 | 
			
		||||
	// IncrementSequence increments the sequence number for the given address
 | 
			
		||||
	IncrementSequence(ctx sdk.Context, addr sdk.AccAddress) error
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user