Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple scrs with refund #319

Merged
Merged
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
1 change: 1 addition & 0 deletions data/scresult.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,5 @@ type ScResult struct {
SenderAddressBytes []byte `json:"-"`
InitialTxGasUsed uint64 `json:"-"`
InitialTxFee string `json:"-"`
GasRefunded uint64 `json:"-"`
}
11 changes: 6 additions & 5 deletions data/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,11 @@ type Transaction struct {
CompletedEvent bool `json:"completedEvent,omitempty"`
RelayedAddr string `json:"relayer,omitempty"`
RelayedSignature string `json:"relayerSignature,omitempty"`
HadRefund bool `json:"hadRefund,omitempty"`
ExecutionOrder int `json:"-"`
SmartContractResults []*ScResult `json:"-"`
Hash string `json:"-"`
BlockHash string `json:"-"`
HadRefund bool `json:"-"`
}

// Receipt is a structure containing all the fields that need to be safe for a Receipt
Expand Down Expand Up @@ -89,8 +89,9 @@ type ResponseTransactionDB struct {

// FeeData is the structure that contains data about transaction fee and gas used
type FeeData struct {
FeeNum float64
Fee string
GasUsed uint64
Receiver string
FeeNum float64
Fee string
GasUsed uint64
Receiver string
GasRefunded uint64
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ require (
github.com/gin-contrib/cors v1.4.0
github.com/gin-gonic/gin v1.9.1
github.com/multiversx/mx-chain-communication-go v1.1.0
github.com/multiversx/mx-chain-core-go v1.2.24-0.20250109151319-81a62c045af8
github.com/multiversx/mx-chain-core-go v1.2.24-0.20250116081327-adb8c08089b4
github.com/multiversx/mx-chain-logger-go v1.0.15
github.com/multiversx/mx-chain-vm-common-go v1.5.13
github.com/prometheus/client_model v0.4.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -249,8 +249,8 @@ github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
github.com/multiversx/mx-chain-communication-go v1.1.0 h1:J7bX6HoN3HiHY7cUeEjG8AJWgQDDPcY+OPDOsSUOkRE=
github.com/multiversx/mx-chain-communication-go v1.1.0/go.mod h1:WK6bP4pGEHGDDna/AYRIMtl6G9OA0NByI1Lw8PmOnRM=
github.com/multiversx/mx-chain-core-go v1.2.24-0.20250109151319-81a62c045af8 h1:0ivlwcl+dKK7BTVngm1uNM2aDneaXK2rhS0HVeBkvYg=
github.com/multiversx/mx-chain-core-go v1.2.24-0.20250109151319-81a62c045af8/go.mod h1:B5zU4MFyJezmEzCsAHE9YNULmGCm2zbPHvl9hazNxmE=
github.com/multiversx/mx-chain-core-go v1.2.24-0.20250116081327-adb8c08089b4 h1:rWYFL38q5cbo5MtdW2DvAp4+WMaVp8e7gBjmrLQ9SCY=
github.com/multiversx/mx-chain-core-go v1.2.24-0.20250116081327-adb8c08089b4/go.mod h1:B5zU4MFyJezmEzCsAHE9YNULmGCm2zbPHvl9hazNxmE=
github.com/multiversx/mx-chain-crypto-go v1.2.12 h1:zWip7rpUS4CGthJxfKn5MZfMfYPjVjIiCID6uX5BSOk=
github.com/multiversx/mx-chain-logger-go v1.0.15 h1:HlNdK8etyJyL9NQ+6mIXyKPEBo+wRqOwi3n+m2QIHXc=
github.com/multiversx/mx-chain-logger-go v1.0.15/go.mod h1:t3PRKaWB1M+i6gUfD27KXgzLJJC+mAQiN+FLlL1yoGQ=
Expand Down
139 changes: 139 additions & 0 deletions integrationtests/relayedV3_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
//go:build integrationtests

package integrationtests

import (
"context"
"encoding/hex"
"math/big"
"testing"

dataBlock "github.com/multiversx/mx-chain-core-go/data/block"
"github.com/multiversx/mx-chain-core-go/data/outport"
"github.com/multiversx/mx-chain-core-go/data/smartContractResult"
"github.com/multiversx/mx-chain-core-go/data/transaction"
indexerdata "github.com/multiversx/mx-chain-es-indexer-go/process/dataindexer"
"github.com/stretchr/testify/require"
)

func TestRelayedV3TransactionWithMultipleRefunds(t *testing.T) {
setLogLevelDebug()

esClient, err := createESClient(esURL)
require.Nil(t, err)

esProc, err := CreateElasticProcessor(esClient)
require.Nil(t, err)

txHash := []byte("relayedTxV3WithMultipleRefunds")
header := &dataBlock.Header{
Round: 50,
TimeStamp: 5040,
}

body := &dataBlock.Body{
MiniBlocks: dataBlock.MiniBlockSlice{
{
Type: dataBlock.TxBlock,
SenderShardID: 0,
ReceiverShardID: 0,
TxHashes: [][]byte{txHash},
},
},
}

initialTx := &transaction.Transaction{
Nonce: 1000,
SndAddr: decodeAddress("erd1ykqd64fxxpp4wsz0v7sjqem038wfpzlljhx4mhwx8w9lcxmdzcfszrp64a"),
RcvAddr: decodeAddress("erd1qqqqqqqqqqqqqpgqak8zt22wl2ph4tswtyc39namqx6ysa2sd8ss4xmlj3"),
RelayerAddr: decodeAddress("erd10ksryjr065ad5475jcg82pnjfg9j9qtszjsrp24anl6ym7cmeddshwnru8"),
Signature: []byte("d"),
RelayerSignature: []byte("a"),
GasLimit: 500_000_000,
GasPrice: 1000000000,
Value: big.NewInt(0),
Data: []byte("doSomething"),
}

txInfo := &outport.TxInfo{
Transaction: initialTx,
FeeInfo: &outport.FeeInfo{
GasUsed: 180_150_000,
Fee: big.NewInt(2864760000000000),
InitialPaidFee: big.NewInt(2864760000000000),
},
ExecutionOrder: 0,
}

pool := &outport.TransactionPool{
Transactions: map[string]*outport.TxInfo{
hex.EncodeToString(txHash): txInfo,
},
}
err = esProc.SaveTransactions(createOutportBlockWithHeader(body, header, pool, nil, testNumOfShards))
require.Nil(t, err)

ids := []string{hex.EncodeToString(txHash)}
genericResponse := &GenericResponse{}
err = esClient.DoMultiGet(context.Background(), ids, indexerdata.TransactionsIndex, true, genericResponse)
require.Nil(t, err)

require.JSONEq(t,
readExpectedResult("./testdata/relayedTxV3/relayed-v3-no-refund.json"),
string(genericResponse.Docs[0].Source),
)

// execute first SCR with refund
pool = &outport.TransactionPool{
SmartContractResults: map[string]*outport.SCRInfo{
"scrHash": {
SmartContractResult: &smartContractResult.SmartContractResult{
OriginalTxHash: txHash,
},
FeeInfo: &outport.FeeInfo{
GasRefunded: 9_692_000,
Fee: big.NewInt(96920000000000),
},
},
},
}
err = esProc.SaveTransactions(createOutportBlockWithHeader(body, header, pool, nil, testNumOfShards))
require.Nil(t, err)

ids = []string{hex.EncodeToString(txHash)}
genericResponse = &GenericResponse{}
err = esClient.DoMultiGet(context.Background(), ids, indexerdata.TransactionsIndex, true, genericResponse)
require.Nil(t, err)

require.JSONEq(t,
readExpectedResult("./testdata/relayedTxV3/relayed-v3-with-one-refund.json"),
string(genericResponse.Docs[0].Source),
)

// execute second SCR with refund
pool = &outport.TransactionPool{
SmartContractResults: map[string]*outport.SCRInfo{
"scrHash": {
SmartContractResult: &smartContractResult.SmartContractResult{
OriginalTxHash: txHash,
},
FeeInfo: &outport.FeeInfo{
GasRefunded: 9_692_000,
Fee: big.NewInt(96920000000000),
},
},
},
}
err = esProc.SaveTransactions(createOutportBlockWithHeader(body, header, pool, nil, testNumOfShards))
require.Nil(t, err)

ids = []string{hex.EncodeToString(txHash)}
genericResponse = &GenericResponse{}
err = esClient.DoMultiGet(context.Background(), ids, indexerdata.TransactionsIndex, true, genericResponse)
require.Nil(t, err)

require.JSONEq(t,
readExpectedResult("./testdata/relayedTxV3/relayed-v3-with-two-refunds.json"),
string(genericResponse.Docs[0].Source),
)
}
28 changes: 28 additions & 0 deletions integrationtests/testdata/relayedTxV3/relayed-v3-no-refund.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"miniBlockHash": "785a251c08b314939528e553ac879dbee0627fbe2b76c0bca601c3f1e7162640",
"nonce": 1000,
"round": 50,
"value": "0",
"valueNum": 0,
"receiver": "erd1qqqqqqqqqqqqqpgqak8zt22wl2ph4tswtyc39namqx6ysa2sd8ss4xmlj3",
"sender": "erd1ykqd64fxxpp4wsz0v7sjqem038wfpzlljhx4mhwx8w9lcxmdzcfszrp64a",
"receiverShard": 0,
"senderShard": 0,
"gasPrice": 1000000000,
"gasLimit": 500000000,
"gasUsed": 180150000,
"fee": "2864760000000000",
"feeNum": 0.00286476,
"initialPaidFee": "2864760000000000",
"data": "ZG9Tb21ldGhpbmc=",
"signature": "64",
"timestamp": 5040,
"status": "success",
"searchOrder": 0,
"isScCall": true,
"operation": "transfer",
"function": "doSomething",
"isRelayed": true,
"relayer": "erd10ksryjr065ad5475jcg82pnjfg9j9qtszjsrp24anl6ym7cmeddshwnru8",
"relayerSignature": "61"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"miniBlockHash": "785a251c08b314939528e553ac879dbee0627fbe2b76c0bca601c3f1e7162640",
"nonce": 1000,
"round": 50,
"value": "0",
"valueNum": 0,
"receiver": "erd1qqqqqqqqqqqqqpgqak8zt22wl2ph4tswtyc39namqx6ysa2sd8ss4xmlj3",
"sender": "erd1ykqd64fxxpp4wsz0v7sjqem038wfpzlljhx4mhwx8w9lcxmdzcfszrp64a",
"receiverShard": 0,
"senderShard": 0,
"gasPrice": 1000000000,
"gasLimit": 500000000,
"gasUsed": 170458000,
"fee": "2767840000000000",
"feeNum": 0.00276784,
"initialPaidFee": "2864760000000000",
"data": "ZG9Tb21ldGhpbmc=",
"signature": "64",
"timestamp": 5040,
"status": "success",
"searchOrder": 0,
"isScCall": true,
"operation": "transfer",
"function": "doSomething",
"isRelayed": true,
"relayer": "erd10ksryjr065ad5475jcg82pnjfg9j9qtszjsrp24anl6ym7cmeddshwnru8",
"relayerSignature": "61",
"hadRefund": true
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"miniBlockHash": "785a251c08b314939528e553ac879dbee0627fbe2b76c0bca601c3f1e7162640",
"nonce": 1000,
"round": 50,
"value": "0",
"valueNum": 0,
"receiver": "erd1qqqqqqqqqqqqqpgqak8zt22wl2ph4tswtyc39namqx6ysa2sd8ss4xmlj3",
"sender": "erd1ykqd64fxxpp4wsz0v7sjqem038wfpzlljhx4mhwx8w9lcxmdzcfszrp64a",
"receiverShard": 0,
"senderShard": 0,
"gasPrice": 1000000000,
"gasLimit": 500000000,
"gasUsed": 160766000,
"fee": "2670920000000000",
"feeNum": 0.0026709200000000002,
"initialPaidFee": "2864760000000000",
"data": "ZG9Tb21ldGhpbmc=",
"signature": "64",
"timestamp": 5040,
"status": "success",
"searchOrder": 0,
"isScCall": true,
"operation": "transfer",
"function": "doSomething",
"isRelayed": true,
"relayer": "erd10ksryjr065ad5475jcg82pnjfg9j9qtszjsrp24anl6ym7cmeddshwnru8",
"relayerSignature": "61",
"hadRefund": true
}
11 changes: 6 additions & 5 deletions process/elasticproc/transactions/scrsDataToTransactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func (st *scrsDataToTransactions) processTransactionsAfterSCRsWereAttached(trans
func (st *scrsDataToTransactions) processSCRsWithoutTx(scrs []*data.ScResult) map[string]*data.FeeData {
txHashRefund := make(map[string]*data.FeeData)
for _, scr := range scrs {
if scr.InitialTxGasUsed == 0 {
if scr.InitialTxGasUsed == 0 && scr.GasRefunded == 0 {
continue
}

Expand All @@ -70,10 +70,11 @@ func (st *scrsDataToTransactions) processSCRsWithoutTx(scrs []*data.ScResult) ma
}

txHashRefund[scr.OriginalTxHash] = &data.FeeData{
FeeNum: feeNum,
Fee: scr.InitialTxFee,
GasUsed: scr.InitialTxGasUsed,
Receiver: scr.Receiver,
FeeNum: feeNum,
Fee: scr.InitialTxFee,
GasUsed: scr.InitialTxGasUsed,
Receiver: scr.Receiver,
GasRefunded: scr.GasRefunded,
}
}

Expand Down
35 changes: 32 additions & 3 deletions process/elasticproc/transactions/serialize.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,35 @@ func (tdp *txsDatabaseProcessor) SerializeReceipts(receipts []*data.Receipt, buf
func (tdp *txsDatabaseProcessor) SerializeTransactionsFeeData(txHashRefund map[string]*data.FeeData, buffSlice *data.BufferSlice, index string) error {
for txHash, feeData := range txHashRefund {
meta := []byte(fmt.Sprintf(`{"update":{ "_index":"%s","_id":"%s"}}%s`, index, converters.JsonEscape(txHash), "\n"))
codeToExecute := `

var codeToExecute string
if feeData.GasRefunded != 0 {
codeToExecute = `
if ('create' == ctx.op) {
ctx.op = 'noop'
} else {
BigInteger feeFromSource;
if ((ctx._source.containsKey('hadRefund')) && (ctx._source.hadRefund)) {
feeFromSource = new BigInteger(ctx._source.fee);
} else {
feeFromSource = new BigInteger(ctx._source.initialPaidFee);
ctx._source.hadRefund = true;
}

BigInteger fee = new BigInteger(params.fee);
if (feeFromSource.compareTo(fee) > 0) {
ctx._source.fee = feeFromSource.subtract(fee).toString();
}
if (ctx._source.feeNum > params.feeNum) {
ctx._source.feeNum -= params.feeNum;
}
if (ctx._source.gasUsed > params.gasRefunded) {
ctx._source.gasUsed -= params.gasRefunded;
}
}
`
} else {
codeToExecute = `
if ('create' == ctx.op) {
ctx.op = 'noop'
} else {
Expand All @@ -60,13 +88,14 @@ func (tdp *txsDatabaseProcessor) SerializeTransactionsFeeData(txHashRefund map[s
ctx._source.gasUsed = params.gasUsed;
}
`
}

serializedDataStr := fmt.Sprintf(`{"scripted_upsert": true, "script": {`+
`"source": "%s",`+
`"lang": "painless",`+
`"params": {"fee": "%s", "gasUsed": %d, "feeNum": %g}},`+
`"params": {"fee": "%s", "gasUsed": %d, "feeNum": %g, "gasRefunded": %d}},`+
`"upsert": {}}`,
converters.FormatPainlessSource(codeToExecute), feeData.Fee, feeData.GasUsed, feeData.FeeNum,
converters.FormatPainlessSource(codeToExecute), feeData.Fee, feeData.GasUsed, feeData.FeeNum, feeData.GasRefunded,
)

err := buffSlice.PutData(meta, []byte(serializedDataStr))
Expand Down
2 changes: 1 addition & 1 deletion process/elasticproc/transactions/serialize_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ func TestTxsDatabaseProcessor_SerializeTransactionWithRefund(t *testing.T) {
require.Nil(t, err)

expectedBuff := `{"update":{ "_index":"transactions","_id":"txHash"}}
{"scripted_upsert": true, "script": {"source": "if ('create' == ctx.op) {ctx.op = 'noop'} else {ctx._source.fee = params.fee;ctx._source.feeNum = params.feeNum;ctx._source.gasUsed = params.gasUsed;}","lang": "painless","params": {"fee": "100000", "gasUsed": 5000, "feeNum": 5e-15}},"upsert": {}}
{"scripted_upsert": true, "script": {"source": "if ('create' == ctx.op) {ctx.op = 'noop'} else {ctx._source.fee = params.fee;ctx._source.feeNum = params.feeNum;ctx._source.gasUsed = params.gasUsed;}","lang": "painless","params": {"fee": "100000", "gasUsed": 5000, "feeNum": 5e-15, "gasRefunded": 0}},"upsert": {}}
`
require.Equal(t, expectedBuff, buffSlice.Buffers()[0].String())
}
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ func (proc *smartContractResultsProcessor) prepareSmartContractResult(
OriginalSender: originalSenderAddr,
InitialTxFee: feeInfo.Fee.String(),
InitialTxGasUsed: feeInfo.GasUsed,
GasRefunded: feeInfo.GasRefunded,
ExecutionOrder: int(scrInfo.ExecutionOrder),
}
}
Expand Down
7 changes: 6 additions & 1 deletion process/elasticproc/transactions/transactionDBBuilder.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,12 +125,17 @@ func (dtb *dbTransactionBuilder) prepareTransaction(
Operation: res.Operation,
RelayedSignature: hex.EncodeToString(tx.RelayerSignature),
RelayedAddr: relayedAddress,
HadRefund: feeInfo.HadRefund,
}

hasValidRelayer := len(eTx.RelayedAddr) == len(eTx.Sender) && len(eTx.RelayedAddr) > 0
hasValidRelayerSignature := len(eTx.RelayedSignature) == len(eTx.Signature) && len(eTx.RelayedSignature) > 0
isRelayedV3 := hasValidRelayer && hasValidRelayerSignature

eTx.Function = converters.TruncateFieldIfExceedsMaxLength(res.Function)
eTx.Tokens = converters.TruncateSliceElementsIfExceedsMaxLength(res.Tokens)
eTx.ReceiversShardIDs = res.ReceiversShardID
eTx.IsRelayed = res.IsRelayed
eTx.IsRelayed = res.IsRelayed || isRelayedV3
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure this is needed, IsRelayed should already be true

Copy link
Contributor Author

@miiu96 miiu96 Jan 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is needed because is not set for relayed v3


return eTx
}
Expand Down