Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions api/errors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ var ErrInvalidGuardianAddress = errors.New("invalid guardian address")
// ErrTxGenerationFailed signals an error generating a transaction
var ErrTxGenerationFailed = errors.New("transaction generation failed")

// ErrDelegationOperationsUnavailable signals that delegation operations are temporarily unavailable
var ErrDelegationOperationsUnavailable = errors.New("delegation operations are temporarily unavailable")

// ErrInvalidSenderAddress signals a wrong format for sender address was provided
var ErrInvalidSenderAddress = errors.New("invalid sender address")

Expand Down
11 changes: 11 additions & 0 deletions api/groups/baseTransactionGroup.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package groups

import (
stdErrors "errors"
"fmt"
"net/http"
"strconv"
Expand Down Expand Up @@ -127,6 +128,16 @@ func (group *transactionGroup) sendMultipleTransactions(c *gin.Context) {

response, err := group.facade.SendMultipleTransactions(txs)
if err != nil {
if stdErrors.Is(err, errors.ErrDelegationOperationsUnavailable) {
shared.RespondWith(
c,
http.StatusServiceUnavailable,
nil,
err.Error(),
data.ReturnCodeInternalError,
)
return
}
shared.RespondWith(
c,
http.StatusInternalServerError,
Expand Down
25 changes: 25 additions & 0 deletions api/groups/baseTransactionGroup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,31 @@ func TestSendMultipleTransactions_ReturnsSuccessfully(t *testing.T) {
assert.Equal(t, uint64(10), response.Data.Num)
}

func TestSendMultipleTransactions_DelegationOperationsUnavailable(t *testing.T) {
t.Parallel()

facade := &mock.FacadeStub{
SendMultipleTransactionsHandler: func(_ []*data.Transaction) (data.MultipleTransactionsResponseData, error) {
return data.MultipleTransactionsResponseData{}, apiErrors.ErrDelegationOperationsUnavailable
},
}

transactionsGroup, err := groups.NewTransactionGroup(facade)
require.NoError(t, err)
ws := startProxyServer(transactionsGroup, transactionsPath)
req, err := http.NewRequest("POST", "/transaction/send-multiple", bytes.NewBufferString(`[{}]`))
require.NoError(t, err)

resp := httptest.NewRecorder()
ws.ServeHTTP(resp, req)

response := GeneralResponse{}
loadResponse(resp.Body, &response)

require.Equal(t, http.StatusServiceUnavailable, resp.Code)
require.Contains(t, response.Error, apiErrors.ErrDelegationOperationsUnavailable.Error())
}

func TestSendUserFunds_ErrorWhenFacadeSendUserFundsError(t *testing.T) {
t.Parallel()

Expand Down
85 changes: 85 additions & 0 deletions process/delegationOperationsGuard.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package process

import (
"bytes"

"github.com/multiversx/mx-chain-proxy-go/data"
)

const (
delegationSCAddressPrefixLength = 25
delegationSCAddressSuffixStart = 29
maxBlockedFunctionLength = len("mergeValidatorToDelegationWithWhitelist")
)

var delegationManagerSCAddress = [32]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 255, 255}
var firstDelegationSCAddress = [32]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 255, 255, 255}

var blockedDelegationFunctions = map[string]struct{}{
"claimRewards": {},
"delegate": {},
"reDelegateRewards": {},
"unDelegate": {},
"withdraw": {},
}

var blockedDelegationManagerFunctions = map[string]struct{}{
"claimMulti": {},
"mergeValidatorToDelegationSameOwner": {},
"mergeValidatorToDelegationWithWhitelist": {},
"reDelegateMulti": {},
}

func (tp *TransactionProcessor) shouldBlockDelegationOperation(tx *data.Transaction) bool {
blockedDelegation, blockedDelegationManager := getBlockedDelegationFunctionTypes(tx.Data)
if !blockedDelegation && !blockedDelegationManager {
return false
}

receiver, err := tp.pubKeyConverter.Decode(tx.Receiver)
if err != nil {
return false
}

if blockedDelegationManager && bytes.Equal(receiver, delegationManagerSCAddress[:]) {
return true
}
if blockedDelegation && isDelegationSCAddress(receiver) {
return true
}

return false
}

func getBlockedDelegationFunctionTypes(txData []byte) (bool, bool) {
selectorEnd := len(txData)
if selectorEnd > maxBlockedFunctionLength+1 {
selectorEnd = maxBlockedFunctionLength + 1
}

separatorIndex := bytes.IndexByte(txData[:selectorEnd], '@')
if separatorIndex >= 0 {
selectorEnd = separatorIndex
} else if len(txData) > maxBlockedFunctionLength {
return false, false
}

_, blockedDelegation := blockedDelegationFunctions[string(txData[:selectorEnd])]
_, blockedDelegationManager := blockedDelegationManagerFunctions[string(txData[:selectorEnd])]

return blockedDelegation, blockedDelegationManager
}

func isDelegationSCAddress(address []byte) bool {
if len(address) != len(firstDelegationSCAddress) {
return false
}
if !bytes.Equal(address[:delegationSCAddressPrefixLength], firstDelegationSCAddress[:delegationSCAddressPrefixLength]) {
return false
}
if !bytes.Equal(address[delegationSCAddressSuffixStart:], firstDelegationSCAddress[delegationSCAddressSuffixStart:]) {
return false
}

return bytes.Compare(address, firstDelegationSCAddress[:]) >= 0
}
67 changes: 67 additions & 0 deletions process/delegationOperationsGuard_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package process

import (
"bytes"
"testing"

"github.com/stretchr/testify/require"
)

func TestGetBlockedDelegationFunctionTypes(t *testing.T) {
t.Parallel()

testCases := []struct {
name string
data []byte
blockedDelegation bool
blockedDelegationManager bool
}{
{
name: "delegation function",
data: []byte("claimRewards"),
blockedDelegation: true,
},
{
name: "delegation function with arguments",
data: []byte("unDelegate@01"),
blockedDelegation: true,
},
{
name: "delegation manager function",
data: []byte("mergeValidatorToDelegationWithWhitelist@00"),
blockedDelegationManager: true,
},
{
name: "function prefix",
data: []byte("claimRewardsExtra"),
},
{
name: "long data",
data: bytes.Repeat([]byte{'a'}, 1<<20),
},
}

for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
t.Parallel()

blockedDelegation, blockedDelegationManager := getBlockedDelegationFunctionTypes(testCase.data)

require.Equal(t, testCase.blockedDelegation, blockedDelegation)
require.Equal(t, testCase.blockedDelegationManager, blockedDelegationManager)
})
}
}

func TestIsDelegationSCAddress(t *testing.T) {
t.Parallel()

addressAfterCounterCarry := firstDelegationSCAddress
addressAfterCounterCarry[27] = 1
addressAfterCounterCarry[28] = 0

require.True(t, isDelegationSCAddress(firstDelegationSCAddress[:]))
require.True(t, isDelegationSCAddress(addressAfterCounterCarry[:]))
require.False(t, isDelegationSCAddress(delegationManagerSCAddress[:]))
require.False(t, isDelegationSCAddress(make([]byte, len(firstDelegationSCAddress))))
}
6 changes: 6 additions & 0 deletions process/transactionProcessor.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,9 @@ func (tp *TransactionProcessor) SendTransaction(tx *data.Transaction) (int, stri
if err != nil {
return http.StatusBadRequest, "", err
}
if tp.shouldBlockDelegationOperation(tx) {
return http.StatusServiceUnavailable, "", errors.ErrDelegationOperationsUnavailable
}

senderBuff, err := tp.pubKeyConverter.Decode(tx.Sender)
if err != nil {
Expand Down Expand Up @@ -302,6 +305,9 @@ func (tp *TransactionProcessor) SendMultipleTransactions(txs []*data.Transaction
"error", err)
continue
}
if tp.shouldBlockDelegationOperation(currentTx) {
return data.MultipleTransactionsResponseData{}, errors.ErrDelegationOperationsUnavailable
}
txsToSend = append(txsToSend, currentTx)
}
if len(txsToSend) == 0 {
Expand Down
Loading
Loading