diff --git a/.github/workflows/deploy-docker.yml b/.github/workflows/deploy-docker.yml new file mode 100644 index 00000000..df006e3f --- /dev/null +++ b/.github/workflows/deploy-docker.yml @@ -0,0 +1,54 @@ +name: Publish Docker image + +on: + release: + types: [published] + +jobs: + push_to_registry: + name: Push Docker image to Docker Hub + runs-on: ubuntu-latest + permissions: + packages: write + contents: read + attestations: write + steps: + - name: Check out the repo + uses: actions/checkout@v4 + + - name: Set up Go 1.x + uses: actions/setup-go@v2 + with: + go-version: 1.20.7 + id: go + + - name: Get dependencies + run: | + go mod download + if [ -f Gopkg.toml ]; then + curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh + dep ensure + fi + + - name: Log in to Docker Hub + uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7 + with: + images: multiversx/elastic-indexer + + - name: Build and push Docker image + id: push + uses: docker/build-push-action@3b5e8027fcad23fda98b2e3ac259d8d67585f671 + with: + context: . + file: ./Dockerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + diff --git a/Dockerfile b/Dockerfile index d4436ca2..33ba0102 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.20.5 as builder +FROM golang:1.20.7 as builder RUN apt-get update && apt-get install -y diff --git a/client/elasticClient.go b/client/elasticClient.go index 64f7e30f..f388c049 100644 --- a/client/elasticClient.go +++ b/client/elasticClient.go @@ -3,6 +3,7 @@ package client import ( "bytes" "context" + "errors" "fmt" "io" "net/http" @@ -84,6 +85,23 @@ func (ec *elasticClient) CheckAndCreateIndex(indexName string) error { return ec.createIndex(indexName) } +// PutMappings will put the provided mappings to a given index +func (ec *elasticClient) PutMappings(indexName string, mappings *bytes.Buffer) error { + res, err := ec.client.Indices.PutMapping( + mappings, + ec.client.Indices.PutMapping.WithIndex(indexName), + ) + if err != nil { + return err + } + + if res.IsError() { + return errors.New(res.String()) + } + + return nil +} + // CheckAndCreateAlias creates a new alias if it does not already exist func (ec *elasticClient) CheckAndCreateAlias(alias string, indexName string) error { if ec.aliasExists(alias) { diff --git a/data/tokens.go b/data/tokens.go index 4018c6ea..e878facf 100644 --- a/data/tokens.go +++ b/data/tokens.go @@ -2,6 +2,8 @@ package data import ( "time" + + "github.com/multiversx/mx-chain-core-go/core" ) const metaESDT = "MetaESDT" @@ -9,13 +11,17 @@ const metaESDT = "MetaESDT" // NFTDataUpdate will contain the update information for an NFT or SFT type NFTDataUpdate struct { Identifier string + Address string + NewCreator string URIsToAdd [][]byte NewAttributes []byte - Address string Freeze bool UnFreeze bool Pause bool UnPause bool + SetURIs bool + NewRoyalties core.OptionalUint32 + NewMetaData *TokenMetaData } // ResponseTokens is the structure for the tokens response @@ -51,6 +57,7 @@ type TokenInfo struct { Data *TokenMetaData `json:"data,omitempty"` OwnersHistory []*OwnerData `json:"ownersHistory,omitempty"` TransferOwnership bool `json:"-"` + ChangeToDynamic bool `json:"-"` Properties *TokenProperties `json:"properties,omitempty"` } diff --git a/data/transaction.go b/data/transaction.go index d5cefd56..2a85ce52 100644 --- a/data/transaction.go +++ b/data/transaction.go @@ -2,57 +2,61 @@ package data import ( "time" + + "github.com/multiversx/mx-chain-core-go/data/transaction" ) // Transaction is a structure containing all the fields that need // to be saved for a transaction. It has all the default fields // plus some extra information for ease of search and filter type Transaction struct { - MBHash string `json:"miniBlockHash"` - Nonce uint64 `json:"nonce"` - Round uint64 `json:"round"` - Value string `json:"value"` - ValueNum float64 `json:"valueNum"` - Receiver string `json:"receiver"` - Sender string `json:"sender"` - ReceiverShard uint32 `json:"receiverShard"` - SenderShard uint32 `json:"senderShard"` - GasPrice uint64 `json:"gasPrice"` - GasLimit uint64 `json:"gasLimit"` - GasUsed uint64 `json:"gasUsed"` - Fee string `json:"fee"` - FeeNum float64 `json:"feeNum"` - InitialPaidFee string `json:"initialPaidFee,omitempty"` - Data []byte `json:"data"` - Signature string `json:"signature"` - Timestamp time.Duration `json:"timestamp"` - Status string `json:"status"` - SearchOrder uint32 `json:"searchOrder"` - SenderUserName []byte `json:"senderUserName,omitempty"` - ReceiverUserName []byte `json:"receiverUserName,omitempty"` - HasSCR bool `json:"hasScResults,omitempty"` - IsScCall bool `json:"isScCall,omitempty"` - HasOperations bool `json:"hasOperations,omitempty"` - HasLogs bool `json:"hasLogs,omitempty"` - Tokens []string `json:"tokens,omitempty"` - ESDTValues []string `json:"esdtValues,omitempty"` - ESDTValuesNum []float64 `json:"esdtValuesNum,omitempty"` - Receivers []string `json:"receivers,omitempty"` - ReceiversShardIDs []uint32 `json:"receiversShardIDs,omitempty"` - Type string `json:"type,omitempty"` - Operation string `json:"operation,omitempty"` - Function string `json:"function,omitempty"` - IsRelayed bool `json:"isRelayed,omitempty"` - Version uint32 `json:"version,omitempty"` - GuardianAddress string `json:"guardian,omitempty"` - GuardianSignature string `json:"guardianSignature,omitempty"` - ErrorEvent bool `json:"errorEvent,omitempty"` - CompletedEvent bool `json:"completedEvent,omitempty"` - ExecutionOrder int `json:"-"` - SmartContractResults []*ScResult `json:"-"` - Hash string `json:"-"` - BlockHash string `json:"-"` - HadRefund bool `json:"-"` + MBHash string `json:"miniBlockHash"` + Nonce uint64 `json:"nonce"` + Round uint64 `json:"round"` + Value string `json:"value"` + ValueNum float64 `json:"valueNum"` + Receiver string `json:"receiver"` + Sender string `json:"sender"` + ReceiverShard uint32 `json:"receiverShard"` + SenderShard uint32 `json:"senderShard"` + GasPrice uint64 `json:"gasPrice"` + GasLimit uint64 `json:"gasLimit"` + GasUsed uint64 `json:"gasUsed"` + Fee string `json:"fee"` + FeeNum float64 `json:"feeNum"` + InitialPaidFee string `json:"initialPaidFee,omitempty"` + Data []byte `json:"data"` + Signature string `json:"signature"` + Timestamp time.Duration `json:"timestamp"` + Status string `json:"status"` + SearchOrder uint32 `json:"searchOrder"` + SenderUserName []byte `json:"senderUserName,omitempty"` + ReceiverUserName []byte `json:"receiverUserName,omitempty"` + HasSCR bool `json:"hasScResults,omitempty"` + IsScCall bool `json:"isScCall,omitempty"` + HasOperations bool `json:"hasOperations,omitempty"` + HasLogs bool `json:"hasLogs,omitempty"` + Tokens []string `json:"tokens,omitempty"` + ESDTValues []string `json:"esdtValues,omitempty"` + ESDTValuesNum []float64 `json:"esdtValuesNum,omitempty"` + Receivers []string `json:"receivers,omitempty"` + ReceiversShardIDs []uint32 `json:"receiversShardIDs,omitempty"` + Type string `json:"type,omitempty"` + Operation string `json:"operation,omitempty"` + Function string `json:"function,omitempty"` + IsRelayed bool `json:"isRelayed,omitempty"` + Version uint32 `json:"version,omitempty"` + GuardianAddress string `json:"guardian,omitempty"` + GuardianSignature string `json:"guardianSignature,omitempty"` + ErrorEvent bool `json:"errorEvent,omitempty"` + CompletedEvent bool `json:"completedEvent,omitempty"` + RelayedAddr string `json:"relayed,omitempty"` + InnerTransactions []*transaction.FrontendTransaction `json:"innerTransactions,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 diff --git a/go.mod b/go.mod index 8daf717f..33369820 100644 --- a/go.mod +++ b/go.mod @@ -6,10 +6,10 @@ require ( github.com/elastic/go-elasticsearch/v7 v7.12.0 github.com/gin-contrib/cors v1.4.0 github.com/gin-gonic/gin v1.9.1 - github.com/multiversx/mx-chain-communication-go v1.0.15-0.20240508074652-e128a1c05c8e - github.com/multiversx/mx-chain-core-go v1.2.21-0.20240514102932-72b1cac83784 - github.com/multiversx/mx-chain-logger-go v1.0.15-0.20240508072523-3f00a726af57 - github.com/multiversx/mx-chain-vm-common-go v1.5.13-0.20240514104734-6dc768a4c5aa + github.com/multiversx/mx-chain-communication-go v1.1.0 + github.com/multiversx/mx-chain-core-go v1.2.23-0.20241007113300-50ac1ae23824 + github.com/multiversx/mx-chain-logger-go v1.0.15 + github.com/multiversx/mx-chain-vm-common-go v1.5.17-0.20241008143259-334f3a5bafe0 github.com/prometheus/client_model v0.4.0 github.com/prometheus/common v0.37.0 github.com/stretchr/testify v1.8.4 diff --git a/go.sum b/go.sum index 74fbbfee..9100464b 100644 --- a/go.sum +++ b/go.sum @@ -247,15 +247,15 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 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.0.15-0.20240508074652-e128a1c05c8e h1:Tsmwhu+UleE+l3buPuqXSKTqfu5FbPmzQ4MjMoUvCWA= -github.com/multiversx/mx-chain-communication-go v1.0.15-0.20240508074652-e128a1c05c8e/go.mod h1:2yXl18wUbuV3cRZr7VHxM1xo73kTaC1WUcu2kx8R034= -github.com/multiversx/mx-chain-core-go v1.2.21-0.20240514102932-72b1cac83784 h1:Dxm8MIIbEQW9hUIjfiVFm7mR+UbOSkb7xhMtXHP7dmk= -github.com/multiversx/mx-chain-core-go v1.2.21-0.20240514102932-72b1cac83784/go.mod h1:P/YBoFnt25XUaCQ7Q/SD15vhnc9yV5JDhHxyFO9P8Z0= -github.com/multiversx/mx-chain-crypto-go v1.2.12-0.20240508074452-cc21c1b505df h1:clihfi78bMEOWk/qw6WA4uQbCM2e2NGliqswLAvw19k= -github.com/multiversx/mx-chain-logger-go v1.0.15-0.20240508072523-3f00a726af57 h1:g9t410dqjcb7UUptbVd/H6Ua12sEzWU4v7VplyNvRZ0= -github.com/multiversx/mx-chain-logger-go v1.0.15-0.20240508072523-3f00a726af57/go.mod h1:cY6CIXpndW5g5PTPn4WzPwka/UBEf+mgw+PSY5pHGAU= -github.com/multiversx/mx-chain-vm-common-go v1.5.13-0.20240514104734-6dc768a4c5aa h1:CJyBXV0PHDE3/bjJU3quoNBjOmGjrn/EBqU7wBdcqhk= -github.com/multiversx/mx-chain-vm-common-go v1.5.13-0.20240514104734-6dc768a4c5aa/go.mod h1:/scqfBjA912V3Z2sPlkRVxWAE3puVNS2adQegm4LjCc= +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.23-0.20241007113300-50ac1ae23824 h1:OHYcWOeTNwSaTMRAfusu6/1zoTWGEtHKPBig4dbRAwM= +github.com/multiversx/mx-chain-core-go v1.2.23-0.20241007113300-50ac1ae23824/go.mod h1:P/YBoFnt25XUaCQ7Q/SD15vhnc9yV5JDhHxyFO9P8Z0= +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= +github.com/multiversx/mx-chain-vm-common-go v1.5.17-0.20241008143259-334f3a5bafe0 h1:WpfFe6ueS+nRIq2RzVaeF1/TJStyYH/YLYPCmN8kdDM= +github.com/multiversx/mx-chain-vm-common-go v1.5.17-0.20241008143259-334f3a5bafe0/go.mod h1:IOE+gYAtTEKZJwUr9ZhOnjdf4vFKrZdZ9RVEyuFsEP4= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= diff --git a/integrationtests/esdtTransfer_test.go b/integrationtests/esdtTransfer_test.go index 285802d8..584a0604 100644 --- a/integrationtests/esdtTransfer_test.go +++ b/integrationtests/esdtTransfer_test.go @@ -113,3 +113,203 @@ func TestESDTTransferTooMuchGasProvided(t *testing.T) { require.JSONEq(t, readExpectedResult("./testdata/esdtTransfer/esdt-transfer.json"), string(genericResponse.Docs[0].Source)) } + +func TestESDTTransferCrossShardWithRefundOnSourceShard(t *testing.T) { + esClient, err := createESClient(esURL) + require.Nil(t, err) + + esProc, err := CreateElasticProcessor(esClient) + require.Nil(t, err) + + txHash := []byte("esdtTransferCross") + header := &dataBlock.Header{ + Round: 50, + TimeStamp: 10101, + ShardID: 2, + } + + scrHash1 := []byte("scrHash1ESDTTransferOnSourceShard") + body := &dataBlock.Body{ + MiniBlocks: dataBlock.MiniBlockSlice{ + { + Type: dataBlock.TxBlock, + SenderShardID: 2, + ReceiverShardID: 1, + TxHashes: [][]byte{txHash}, + }, + { + Type: dataBlock.SmartContractResultBlock, + SenderShardID: 2, + ReceiverShardID: 2, + TxHashes: [][]byte{scrHash1}, + }, + }, + } + + address1 := "erd1ef6470tjdtlgpa9f6g3ae4nsedmjg0gv6w73v32xtvhkfff993hq750xl9" + address2 := "erd13u7zyekzvdvzek8768r5gau9p6677ufppsjuklu9e6t7yx7rhg4s68e2ze" + + txESDT := outport.TxInfo{ + Transaction: &transaction.Transaction{ + Nonce: 1, + SndAddr: decodeAddress(address1), + RcvAddr: decodeAddress(address2), + GasLimit: 500_000, + GasPrice: 1000000000, + Data: []byte("ESDTTransfer@54474e2d383862383366@0a"), + Value: big.NewInt(0), + }, + FeeInfo: &outport.FeeInfo{ + GasUsed: 334_000, + Fee: big.NewInt(136000000000000), + InitialPaidFee: big.NewInt(137660000000000), + }, + } + + refundValueBig, _ := big.NewInt(0).SetString("1660000000000", 10) + scrRefund := &smartContractResult.SmartContractResult{ + Nonce: 20, + SndAddr: decodeAddress(address2), + RcvAddr: decodeAddress(address1), + PrevTxHash: txHash, + OriginalTxHash: txHash, + Value: refundValueBig, + Data: []byte("@6f6b"), + } + + pool := &outport.TransactionPool{ + Transactions: map[string]*outport.TxInfo{ + hex.EncodeToString(txHash): &txESDT, + }, + SmartContractResults: map[string]*outport.SCRInfo{ + hex.EncodeToString(scrHash1): {SmartContractResult: scrRefund, FeeInfo: &outport.FeeInfo{}}, + }, + } + 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.OperationsIndex, true, genericResponse) + require.Nil(t, err) + + require.JSONEq(t, readExpectedResult("./testdata/esdtTransfer/esdt-transfer-cross-shard-on-source.json"), string(genericResponse.Docs[0].Source)) + + header = &dataBlock.Header{ + Round: 55, + TimeStamp: 10102, + ShardID: 1, + } + txESDT.FeeInfo = &outport.FeeInfo{ + GasUsed: 500_000, + Fee: big.NewInt(137660000000000), + InitialPaidFee: big.NewInt(137660000000000), + } + + err = esProc.SaveTransactions(createOutportBlockWithHeader(body, header, pool, nil, testNumOfShards)) + require.Nil(t, err) + + err = esClient.DoMultiGet(context.Background(), ids, indexerdata.OperationsIndex, true, genericResponse) + require.Nil(t, err) + + require.JSONEq(t, readExpectedResult("./testdata/esdtTransfer/esdt-transfer-cross-shard-on-destination.json"), string(genericResponse.Docs[0].Source)) +} + +func TestESDTTransferCrossShardIndexFirstOnDestinationAndAfterSource(t *testing.T) { + esClient, err := createESClient(esURL) + require.Nil(t, err) + + esProc, err := CreateElasticProcessor(esClient) + require.Nil(t, err) + + txHash := []byte("esdtTransferCross2") + header := &dataBlock.Header{ + Round: 55, + TimeStamp: 10102, + ShardID: 1, + } + + scrHash1 := []byte("scrHash1ESDTTransferOnSourceShard2") + body := &dataBlock.Body{ + MiniBlocks: dataBlock.MiniBlockSlice{ + { + Type: dataBlock.TxBlock, + SenderShardID: 2, + ReceiverShardID: 1, + TxHashes: [][]byte{txHash}, + }, + }, + } + + address1 := "erd1ef6470tjdtlgpa9f6g3ae4nsedmjg0gv6w73v32xtvhkfff993hq750xl9" + address2 := "erd13u7zyekzvdvzek8768r5gau9p6677ufppsjuklu9e6t7yx7rhg4s68e2ze" + + txESDT := outport.TxInfo{ + Transaction: &transaction.Transaction{ + Nonce: 1, + SndAddr: decodeAddress(address1), + RcvAddr: decodeAddress(address2), + GasLimit: 500_000, + GasPrice: 1000000000, + Data: []byte("ESDTTransfer@54474e2d383862383366@0a"), + Value: big.NewInt(0), + }, + FeeInfo: &outport.FeeInfo{ + GasUsed: 500_000, + Fee: big.NewInt(137660000000000), + InitialPaidFee: big.NewInt(137660000000000), + }, + } + + refundValueBig, _ := big.NewInt(0).SetString("1660000000000", 10) + scrRefund := &smartContractResult.SmartContractResult{ + Nonce: 20, + SndAddr: decodeAddress(address2), + RcvAddr: decodeAddress(address1), + PrevTxHash: txHash, + OriginalTxHash: txHash, + Value: refundValueBig, + Data: []byte("@6f6b"), + } + + pool := &outport.TransactionPool{ + Transactions: map[string]*outport.TxInfo{ + hex.EncodeToString(txHash): &txESDT, + }, + SmartContractResults: map[string]*outport.SCRInfo{ + hex.EncodeToString(scrHash1): {SmartContractResult: scrRefund, FeeInfo: &outport.FeeInfo{}}, + }, + } + 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.OperationsIndex, true, genericResponse) + require.Nil(t, err) + require.JSONEq(t, readExpectedResult("./testdata/esdtTransfer/esdt-transfer-first-on-destination.json"), string(genericResponse.Docs[0].Source)) + + txESDT.FeeInfo = &outport.FeeInfo{ + GasUsed: 334_000, + Fee: big.NewInt(136000000000000), + InitialPaidFee: big.NewInt(137660000000000), + } + header = &dataBlock.Header{ + Round: 50, + TimeStamp: 10101, + ShardID: 2, + } + body.MiniBlocks = append(body.MiniBlocks, &dataBlock.MiniBlock{ + Type: dataBlock.SmartContractResultBlock, + SenderShardID: 2, + ReceiverShardID: 2, + TxHashes: [][]byte{scrHash1}, + }) + + err = esProc.SaveTransactions(createOutportBlockWithHeader(body, header, pool, nil, testNumOfShards)) + require.Nil(t, err) + + err = esClient.DoMultiGet(context.Background(), ids, indexerdata.OperationsIndex, true, genericResponse) + require.Nil(t, err) + require.JSONEq(t, readExpectedResult("./testdata/esdtTransfer/esdt-transfer-second-on-source.json"), string(genericResponse.Docs[0].Source)) +} diff --git a/integrationtests/issueTokenAndChangeType_test.go b/integrationtests/issueTokenAndChangeType_test.go new file mode 100644 index 00000000..a368634b --- /dev/null +++ b/integrationtests/issueTokenAndChangeType_test.go @@ -0,0 +1,96 @@ +//go:build integrationtests + +package integrationtests + +import ( + "context" + "encoding/hex" + "math/big" + "testing" + + "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-core-go/data/alteredAccount" + 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/transaction" + indexerdata "github.com/multiversx/mx-chain-es-indexer-go/process/dataindexer" + "github.com/stretchr/testify/require" +) + +func TestIssueTokenAndChangeToDynamic(t *testing.T) { + setLogLevelDebug() + + esClient, err := createESClient(esURL) + require.Nil(t, err) + + esProc, err := CreateElasticProcessor(esClient) + require.Nil(t, err) + + body := &dataBlock.Body{} + header := &dataBlock.Header{ + Round: 50, + TimeStamp: 5040, + ShardID: core.MetachainShardId, + } + + address1 := "erd1k04pxr6c0gvlcx4rd5fje0a4uy33axqxwz0fpcrgtfdy3nrqauqqgvxprv" + pool := &outport.TransactionPool{ + Logs: []*outport.LogData{ + { + TxHash: hex.EncodeToString([]byte("h1")), + Log: &transaction.Log{ + Address: decodeAddress(address1), + Events: []*transaction.Event{ + { + Address: decodeAddress(address1), + Identifier: []byte("issueSemiFungible"), + Topics: [][]byte{[]byte("CON-abcd"), []byte("semi-token"), []byte("SEMI"), []byte(core.SemiFungibleESDT)}, + }, + { + Address: decodeAddress(address1), + Identifier: []byte("upgradeProperties"), + Topics: [][]byte{[]byte("CON-abcd"), big.NewInt(0).Bytes(), []byte("canUpgrade"), []byte("true")}, + }, + nil, + }, + }, + }, + }, + } + + err = esProc.SaveTransactions(createOutportBlockWithHeader(body, header, pool, map[string]*alteredAccount.AlteredAccount{}, testNumOfShards)) + require.Nil(t, err) + + ids := []string{"CON-abcd"} + genericResponse := &GenericResponse{} + err = esClient.DoMultiGet(context.Background(), ids, indexerdata.TokensIndex, true, genericResponse) + require.Nil(t, err) + require.JSONEq(t, readExpectedResult("./testdata/issueTokenAndChangeType/token.json"), string(genericResponse.Docs[0].Source)) + + header.TimeStamp = 10_000 + pool = &outport.TransactionPool{ + Logs: []*outport.LogData{ + { + TxHash: hex.EncodeToString([]byte("h1")), + Log: &transaction.Log{ + Address: decodeAddress(address1), + Events: []*transaction.Event{ + { + Address: decodeAddress(address1), + Identifier: []byte("changeToDynamic"), + Topics: [][]byte{[]byte("CON-abcd"), []byte("semi-token"), []byte("SEMI"), []byte(core.DynamicSFTESDT)}, + }, + nil, + }, + }, + }, + }, + } + + err = esProc.SaveTransactions(createOutportBlockWithHeader(body, header, pool, map[string]*alteredAccount.AlteredAccount{}, testNumOfShards)) + require.Nil(t, err) + + err = esClient.DoMultiGet(context.Background(), ids, indexerdata.TokensIndex, true, genericResponse) + require.Nil(t, err) + require.JSONEq(t, readExpectedResult("./testdata/issueTokenAndChangeType/token-with-new-type.json"), string(genericResponse.Docs[0].Source)) +} diff --git a/integrationtests/relayedTxV3_test.go b/integrationtests/relayedTxV3_test.go new file mode 100644 index 00000000..e440c215 --- /dev/null +++ b/integrationtests/relayedTxV3_test.go @@ -0,0 +1,369 @@ +//go:build integrationtests + +package integrationtests + +import ( + "context" + "encoding/hex" + "math/big" + "testing" + + "github.com/multiversx/mx-chain-core-go/core" + 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 TestRelayedTxV3(t *testing.T) { + setLogLevelDebug() + + esClient, err := createESClient(esURL) + require.Nil(t, err) + + esProc, err := CreateElasticProcessor(esClient) + require.Nil(t, err) + + txHash := []byte("relayedTxV3") + header := &dataBlock.Header{ + Round: 50, + TimeStamp: 5040, + } + + body := &dataBlock.Body{ + MiniBlocks: dataBlock.MiniBlockSlice{ + { + Type: dataBlock.TxBlock, + SenderShardID: 0, + ReceiverShardID: 0, + TxHashes: [][]byte{txHash}, + }, + }, + } + + address1 := "erd1k7j6ewjsla4zsgv8v6f6fe3dvrkgv3d0d9jerczw45hzedhyed8sh2u34u" + address2 := "erd14eyayfrvlrhzfrwg5zwleua25mkzgncggn35nvc6xhv5yxwml2es0f3dht" + initialTx := &transaction.Transaction{ + Nonce: 1000, + SndAddr: decodeAddress(address1), + RcvAddr: decodeAddress(address2), + GasLimit: 15406000, + GasPrice: 1000000000, + Value: big.NewInt(0), + InnerTransactions: []*transaction.Transaction{ + { + Nonce: 10, + SndAddr: decodeAddress(address1), + RcvAddr: decodeAddress(address2), + GasLimit: 15406000, + GasPrice: 1000000000, + Value: big.NewInt(0), + }, + { + Nonce: 20, + SndAddr: decodeAddress(address1), + RcvAddr: decodeAddress(address2), + GasLimit: 15406000, + GasPrice: 1000000000, + Value: big.NewInt(1000), + }, + }, + } + + txInfo := &outport.TxInfo{ + Transaction: initialTx, + FeeInfo: &outport.FeeInfo{ + GasUsed: 10556000, + Fee: big.NewInt(2257820000000000), + InitialPaidFee: big.NewInt(2306320000000000), + }, + 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-tx-v3.json"), + string(genericResponse.Docs[0].Source), + ) +} + +func TestRelayedTxV3WithSignalErrorAndCompletedEvent(t *testing.T) { + setLogLevelDebug() + + esClient, err := createESClient(esURL) + require.Nil(t, err) + + esProc, err := CreateElasticProcessor(esClient) + require.Nil(t, err) + + txHash := []byte("relayedTxV3WithSignalErrorAndCompletedEvent") + 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("erd1ykqd64fxxpp4wsz0v7sjqem038wfpzlljhx4mhwx8w9lcxmdzcfszrp64a"), + GasLimit: 300_000, + GasPrice: 1000000000, + Value: big.NewInt(0), + InnerTransactions: []*transaction.Transaction{ + { + Nonce: 5, + SndAddr: decodeAddress("erd10ksryjr065ad5475jcg82pnjfg9j9qtszjsrp24anl6ym7cmeddshwnru8"), + RcvAddr: decodeAddress("erd1aduqqezzw0u3j7tywlq3mrl0yn4z6f6vytdju8gg0neq38fauyzsa5yy6r"), + GasLimit: 50_000, + GasPrice: 1000000000, + Value: big.NewInt(10000000000000000), + }, + { + Nonce: 3, + SndAddr: decodeAddress("erd10ksryjr065ad5475jcg82pnjfg9j9qtszjsrp24anl6ym7cmeddshwnru8"), + RcvAddr: decodeAddress("erd1aduqqezzw0u3j7tywlq3mrl0yn4z6f6vytdju8gg0neq38fauyzsa5yy6r"), + GasLimit: 50_000, + GasPrice: 1000000000, + Value: big.NewInt(10000000000000000), + }, + { + Nonce: 4, + SndAddr: decodeAddress("erd10ksryjr065ad5475jcg82pnjfg9j9qtszjsrp24anl6ym7cmeddshwnru8"), + RcvAddr: decodeAddress("erd1aduqqezzw0u3j7tywlq3mrl0yn4z6f6vytdju8gg0neq38fauyzsa5yy6r"), + GasLimit: 50_000, + GasPrice: 1000000000, + Value: big.NewInt(10000000000000000), + }, + }, + } + + txInfo := &outport.TxInfo{ + Transaction: initialTx, + FeeInfo: &outport.FeeInfo{ + GasUsed: 10556000, + Fee: big.NewInt(2257820000000000), + InitialPaidFee: big.NewInt(2306320000000000), + }, + ExecutionOrder: 0, + } + + pool := &outport.TransactionPool{ + Transactions: map[string]*outport.TxInfo{ + hex.EncodeToString(txHash): txInfo, + }, + Logs: []*outport.LogData{ + { + TxHash: hex.EncodeToString(txHash), + Log: &transaction.Log{ + Address: decodeAddress("erd1ykqd64fxxpp4wsz0v7sjqem038wfpzlljhx4mhwx8w9lcxmdzcfszrp64a"), + Events: []*transaction.Event{ + { + Address: decodeAddress("erd1ykqd64fxxpp4wsz0v7sjqem038wfpzlljhx4mhwx8w9lcxmdzcfszrp64a"), + Identifier: []byte(core.CompletedTxEventIdentifier), + Topics: [][]byte{[]byte("t1"), []byte("t2")}, + }, + nil, + }, + }, + }, + { + TxHash: hex.EncodeToString(txHash), + Log: &transaction.Log{ + Address: decodeAddress("erd1ykqd64fxxpp4wsz0v7sjqem038wfpzlljhx4mhwx8w9lcxmdzcfszrp64a"), + Events: []*transaction.Event{ + { + Address: decodeAddress("erd1ykqd64fxxpp4wsz0v7sjqem038wfpzlljhx4mhwx8w9lcxmdzcfszrp64a"), + Identifier: []byte(core.SignalErrorOperation), + Topics: [][]byte{[]byte("t1"), []byte("t2")}, + }, + nil, + }, + }, + }, + }, + } + 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-tx-v3-with-events.json"), + string(genericResponse.Docs[0].Source), + ) +} + +func TestRelayedV3WithSCRCross(t *testing.T) { + setLogLevelDebug() + + esClient, err := createESClient(esURL) + require.Nil(t, err) + + esProc, err := CreateElasticProcessor(esClient) + require.Nil(t, err) + + txHash := []byte("relayedTxV3WithScrCross") + 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("erd1ykqd64fxxpp4wsz0v7sjqem038wfpzlljhx4mhwx8w9lcxmdzcfszrp64a"), + GasLimit: 300_000, + GasPrice: 1000000000, + Value: big.NewInt(0), + InnerTransactions: []*transaction.Transaction{ + { + Nonce: 5, + SndAddr: decodeAddress("erd10ksryjr065ad5475jcg82pnjfg9j9qtszjsrp24anl6ym7cmeddshwnru8"), + RcvAddr: decodeAddress("erd1aduqqezzw0u3j7tywlq3mrl0yn4z6f6vytdju8gg0neq38fauyzsa5yy6r"), + GasLimit: 50_000, + GasPrice: 1000000000, + Value: big.NewInt(10000000000000000), + }, + { + Nonce: 3, + SndAddr: decodeAddress("erd10ksryjr065ad5475jcg82pnjfg9j9qtszjsrp24anl6ym7cmeddshwnru8"), + RcvAddr: decodeAddress("erd1aduqqezzw0u3j7tywlq3mrl0yn4z6f6vytdju8gg0neq38fauyzsa5yy6r"), + GasLimit: 50_000, + GasPrice: 1000000000, + Value: big.NewInt(10000000000000000), + }, + }, + } + + txInfo := &outport.TxInfo{ + Transaction: initialTx, + FeeInfo: &outport.FeeInfo{ + GasUsed: 10556000, + Fee: big.NewInt(2257820000000000), + InitialPaidFee: big.NewInt(2306320000000000), + }, + ExecutionOrder: 0, + } + + pool := &outport.TransactionPool{ + Transactions: map[string]*outport.TxInfo{ + hex.EncodeToString(txHash): txInfo, + }, + Logs: []*outport.LogData{ + { + TxHash: hex.EncodeToString(txHash), + Log: &transaction.Log{ + Address: decodeAddress("erd1ykqd64fxxpp4wsz0v7sjqem038wfpzlljhx4mhwx8w9lcxmdzcfszrp64a"), + Events: []*transaction.Event{ + { + Address: decodeAddress("erd1ykqd64fxxpp4wsz0v7sjqem038wfpzlljhx4mhwx8w9lcxmdzcfszrp64a"), + Identifier: []byte(core.CompletedTxEventIdentifier), + Topics: [][]byte{[]byte("t1"), []byte("t2")}, + }, + nil, + }, + }, + }, + }, + } + 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-execution-source.json"), + string(genericResponse.Docs[0].Source), + ) + + // execute scr on destination + header = &dataBlock.Header{ + Round: 60, + TimeStamp: 6040, + } + + scrInfo := &outport.SCRInfo{ + SmartContractResult: &smartContractResult.SmartContractResult{ + OriginalTxHash: txHash, + }, + FeeInfo: &outport.FeeInfo{ + Fee: big.NewInt(0), + InitialPaidFee: big.NewInt(0), + }, + ExecutionOrder: 0, + } + + pool = &outport.TransactionPool{ + SmartContractResults: map[string]*outport.SCRInfo{ + hex.EncodeToString([]byte("scr")): scrInfo, + }, + Logs: []*outport.LogData{ + { + TxHash: hex.EncodeToString([]byte("scr")), + Log: &transaction.Log{ + Address: decodeAddress("erd1ykqd64fxxpp4wsz0v7sjqem038wfpzlljhx4mhwx8w9lcxmdzcfszrp64a"), + Events: []*transaction.Event{ + { + Address: decodeAddress("erd1ykqd64fxxpp4wsz0v7sjqem038wfpzlljhx4mhwx8w9lcxmdzcfszrp64a"), + Identifier: []byte(core.SignalErrorOperation), + Topics: [][]byte{[]byte("t1"), []byte("t2")}, + }, + nil, + }, + }, + }, + }, + } + err = esProc.SaveTransactions(createOutportBlockWithHeader(body, header, pool, nil, testNumOfShards)) + require.Nil(t, err) + + err = esClient.DoMultiGet(context.Background(), ids, indexerdata.TransactionsIndex, true, genericResponse) + require.Nil(t, err) + + require.JSONEq(t, + readExpectedResult("./testdata/relayedTxV3/relayed-v3-execution-scr-on-dest.json"), + string(genericResponse.Docs[0].Source), + ) +} diff --git a/integrationtests/testdata/esdtTransfer/esdt-transfer-cross-shard-on-destination.json b/integrationtests/testdata/esdtTransfer/esdt-transfer-cross-shard-on-destination.json new file mode 100644 index 00000000..2e1bc23b --- /dev/null +++ b/integrationtests/testdata/esdtTransfer/esdt-transfer-cross-shard-on-destination.json @@ -0,0 +1,34 @@ +{ + "miniBlockHash": "579fe2f60e8fa9c396254b2173282c6ecb6e736b42e015c3b744c86001be3981", + "nonce": 1, + "round": 55, + "value": "0", + "valueNum": 0, + "receiver": "erd13u7zyekzvdvzek8768r5gau9p6677ufppsjuklu9e6t7yx7rhg4s68e2ze", + "sender": "erd1ef6470tjdtlgpa9f6g3ae4nsedmjg0gv6w73v32xtvhkfff993hq750xl9", + "receiverShard": 1, + "senderShard": 2, + "gasPrice": 1000000000, + "gasLimit": 500000, + "gasUsed": 334000, + "fee": "136000000000000", + "feeNum": 0.000136, + "initialPaidFee": "137660000000000", + "data": "RVNEVFRyYW5zZmVyQDU0NDc0ZTJkMzgzODYyMzgzMzY2QDBh", + "signature": "", + "timestamp": 10102, + "status": "success", + "searchOrder": 0, + "hasScResults": true, + "tokens": [ + "TGN-88b83f" + ], + "esdtValues": [ + "10" + ], + "esdtValuesNum": [ + 1e-17 + ], + "type": "normal", + "operation": "ESDTTransfer" +} diff --git a/integrationtests/testdata/esdtTransfer/esdt-transfer-cross-shard-on-source.json b/integrationtests/testdata/esdtTransfer/esdt-transfer-cross-shard-on-source.json new file mode 100644 index 00000000..df53964e --- /dev/null +++ b/integrationtests/testdata/esdtTransfer/esdt-transfer-cross-shard-on-source.json @@ -0,0 +1,34 @@ +{ + "miniBlockHash": "579fe2f60e8fa9c396254b2173282c6ecb6e736b42e015c3b744c86001be3981", + "nonce": 1, + "round": 50, + "value": "0", + "valueNum": 0, + "receiver": "erd13u7zyekzvdvzek8768r5gau9p6677ufppsjuklu9e6t7yx7rhg4s68e2ze", + "sender": "erd1ef6470tjdtlgpa9f6g3ae4nsedmjg0gv6w73v32xtvhkfff993hq750xl9", + "receiverShard": 1, + "senderShard": 2, + "gasPrice": 1000000000, + "gasLimit": 500000, + "gasUsed": 334000, + "fee": "136000000000000", + "feeNum": 0.000136, + "initialPaidFee": "137660000000000", + "data": "RVNEVFRyYW5zZmVyQDU0NDc0ZTJkMzgzODYyMzgzMzY2QDBh", + "signature": "", + "timestamp": 10101, + "status": "pending", + "searchOrder": 0, + "hasScResults": true, + "tokens": [ + "TGN-88b83f" + ], + "esdtValues": [ + "10" + ], + "esdtValuesNum": [ + 1e-17 + ], + "type": "normal", + "operation": "ESDTTransfer" +} diff --git a/integrationtests/testdata/esdtTransfer/esdt-transfer-first-on-destination.json b/integrationtests/testdata/esdtTransfer/esdt-transfer-first-on-destination.json new file mode 100644 index 00000000..1b0f46b7 --- /dev/null +++ b/integrationtests/testdata/esdtTransfer/esdt-transfer-first-on-destination.json @@ -0,0 +1,34 @@ +{ + "data": "RVNEVFRyYW5zZmVyQDU0NDc0ZTJkMzgzODYyMzgzMzY2QDBh", + "signature": "", + "fee": "137660000000000", + "esdtValues": [ + "10" + ], + "initialPaidFee": "137660000000000", + "type": "normal", + "gasLimit": 500000, + "esdtValuesNum": [ + 1e-17 + ], + "gasUsed": 500000, + "miniBlockHash": "d0f273198e773465908be139872605d93e86115b6320f9b34edb6a5e38668989", + "senderShard": 2, + "tokens": [ + "TGN-88b83f" + ], + "value": "0", + "gasPrice": 1000000000, + "timestamp": 10102, + "receiver": "erd13u7zyekzvdvzek8768r5gau9p6677ufppsjuklu9e6t7yx7rhg4s68e2ze", + "valueNum": 0, + "feeNum": 0.00013766, + "nonce": 1, + "round": 55, + "hasScResults": true, + "sender": "erd1ef6470tjdtlgpa9f6g3ae4nsedmjg0gv6w73v32xtvhkfff993hq750xl9", + "receiverShard": 1, + "operation": "ESDTTransfer", + "status": "success", + "searchOrder": 0 +} diff --git a/integrationtests/testdata/esdtTransfer/esdt-transfer-second-on-source.json b/integrationtests/testdata/esdtTransfer/esdt-transfer-second-on-source.json new file mode 100644 index 00000000..eccaa6ba --- /dev/null +++ b/integrationtests/testdata/esdtTransfer/esdt-transfer-second-on-source.json @@ -0,0 +1,34 @@ +{ + "data": "RVNEVFRyYW5zZmVyQDU0NDc0ZTJkMzgzODYyMzgzMzY2QDBh", + "signature": "", + "fee": "136000000000000", + "esdtValues": [ + "10" + ], + "initialPaidFee": "137660000000000", + "type": "normal", + "gasLimit": 500000, + "esdtValuesNum": [ + 1e-17 + ], + "gasUsed": 334000, + "miniBlockHash": "d0f273198e773465908be139872605d93e86115b6320f9b34edb6a5e38668989", + "senderShard": 2, + "tokens": [ + "TGN-88b83f" + ], + "value": "0", + "gasPrice": 1000000000, + "timestamp": 10102, + "receiver": "erd13u7zyekzvdvzek8768r5gau9p6677ufppsjuklu9e6t7yx7rhg4s68e2ze", + "valueNum": 0, + "feeNum": 0.000136, + "nonce": 1, + "round": 55, + "hasScResults": true, + "sender": "erd1ef6470tjdtlgpa9f6g3ae4nsedmjg0gv6w73v32xtvhkfff993hq750xl9", + "receiverShard": 1, + "operation": "ESDTTransfer", + "status": "success", + "searchOrder": 0 +} diff --git a/integrationtests/testdata/issueTokenAndChangeType/token-with-new-type.json b/integrationtests/testdata/issueTokenAndChangeType/token-with-new-type.json new file mode 100644 index 00000000..8f19fc4b --- /dev/null +++ b/integrationtests/testdata/issueTokenAndChangeType/token-with-new-type.json @@ -0,0 +1,29 @@ +{ + "name": "semi-token", + "ticker": "SEMI", + "token": "CON-abcd", + "issuer": "erd1k04pxr6c0gvlcx4rd5fje0a4uy33axqxwz0fpcrgtfdy3nrqauqqgvxprv", + "currentOwner": "erd1k04pxr6c0gvlcx4rd5fje0a4uy33axqxwz0fpcrgtfdy3nrqauqqgvxprv", + "numDecimals": 0, + "type": "DynamicSemiFungibleESDT", + "timestamp": 5040, + "ownersHistory": [ + { + "address": "erd1k04pxr6c0gvlcx4rd5fje0a4uy33axqxwz0fpcrgtfdy3nrqauqqgvxprv", + "timestamp": 5040 + } + ], + "properties": { + "canMint": false, + "canBurn": false, + "canUpgrade": true, + "canTransferNFTCreateRole": false, + "canAddSpecialRoles": false, + "canPause": false, + "canFreeze": false, + "canWipe": false, + "canChangeOwner": false, + "canCreateMultiShard": false + }, + "changedToDynamicTimestamp": 10000 +} diff --git a/integrationtests/testdata/issueTokenAndChangeType/token.json b/integrationtests/testdata/issueTokenAndChangeType/token.json new file mode 100644 index 00000000..8b33340d --- /dev/null +++ b/integrationtests/testdata/issueTokenAndChangeType/token.json @@ -0,0 +1,28 @@ +{ + "name": "semi-token", + "ticker": "SEMI", + "token": "CON-abcd", + "issuer": "erd1k04pxr6c0gvlcx4rd5fje0a4uy33axqxwz0fpcrgtfdy3nrqauqqgvxprv", + "currentOwner": "erd1k04pxr6c0gvlcx4rd5fje0a4uy33axqxwz0fpcrgtfdy3nrqauqqgvxprv", + "numDecimals": 0, + "type": "SemiFungibleESDT", + "timestamp": 5040, + "ownersHistory": [ + { + "address": "erd1k04pxr6c0gvlcx4rd5fje0a4uy33axqxwz0fpcrgtfdy3nrqauqqgvxprv", + "timestamp": 5040 + } + ], + "properties": { + "canMint": false, + "canBurn": false, + "canUpgrade": true, + "canTransferNFTCreateRole": false, + "canAddSpecialRoles": false, + "canPause": false, + "canFreeze": false, + "canWipe": false, + "canChangeOwner": false, + "canCreateMultiShard": false + } +} diff --git a/integrationtests/testdata/mappings/esdts.json b/integrationtests/testdata/mappings/esdts.json index bcdaec16..da8bc0bd 100644 --- a/integrationtests/testdata/mappings/esdts.json +++ b/integrationtests/testdata/mappings/esdts.json @@ -110,6 +110,10 @@ "type": "date", "format": "epoch_second" }, + "changedToDynamicTimestamp": { + "type": "date", + "format": "epoch_second" + }, "token": { "type": "text" }, diff --git a/integrationtests/testdata/relayedTxV3/relayed-tx-v3-with-events.json b/integrationtests/testdata/relayedTxV3/relayed-tx-v3-with-events.json new file mode 100644 index 00000000..f60cafb2 --- /dev/null +++ b/integrationtests/testdata/relayedTxV3/relayed-tx-v3-with-events.json @@ -0,0 +1,70 @@ +{ + "miniBlockHash": "2b8a2e2b21d1cf1c5e3d782552810a07818e6b179dd196e901466bb588fc387a", + "nonce": 1000, + "round": 50, + "value": "0", + "valueNum": 0, + "receiver": "erd1ykqd64fxxpp4wsz0v7sjqem038wfpzlljhx4mhwx8w9lcxmdzcfszrp64a", + "sender": "erd1ykqd64fxxpp4wsz0v7sjqem038wfpzlljhx4mhwx8w9lcxmdzcfszrp64a", + "receiverShard": 0, + "senderShard": 0, + "gasPrice": 1000000000, + "gasLimit": 300000, + "gasUsed": 10556000, + "fee": "2257820000000000", + "feeNum": 0.00225782, + "initialPaidFee": "2306320000000000", + "data": null, + "signature": "", + "timestamp": 5040, + "status": "success", + "searchOrder": 0, + "hasOperations": true, + "hasLogs": true, + "receivers": [ + "erd1aduqqezzw0u3j7tywlq3mrl0yn4z6f6vytdju8gg0neq38fauyzsa5yy6r", + "erd1aduqqezzw0u3j7tywlq3mrl0yn4z6f6vytdju8gg0neq38fauyzsa5yy6r", + "erd1aduqqezzw0u3j7tywlq3mrl0yn4z6f6vytdju8gg0neq38fauyzsa5yy6r" + ], + "receiversShardIDs": [ + 1, + 1, + 1 + ], + "operation": "transfer", + "isRelayed": true, + "errorEvent": true, + "completedEvent": true, + "innerTransactions": [ + { + "nonce": 5, + "value": "10000000000000000", + "receiver": "erd1aduqqezzw0u3j7tywlq3mrl0yn4z6f6vytdju8gg0neq38fauyzsa5yy6r", + "sender": "erd10ksryjr065ad5475jcg82pnjfg9j9qtszjsrp24anl6ym7cmeddshwnru8", + "gasPrice": 1000000000, + "gasLimit": 50000, + "chainID": "", + "version": 0 + }, + { + "nonce": 3, + "value": "10000000000000000", + "receiver": "erd1aduqqezzw0u3j7tywlq3mrl0yn4z6f6vytdju8gg0neq38fauyzsa5yy6r", + "sender": "erd10ksryjr065ad5475jcg82pnjfg9j9qtszjsrp24anl6ym7cmeddshwnru8", + "gasPrice": 1000000000, + "gasLimit": 50000, + "chainID": "", + "version": 0 + }, + { + "nonce": 4, + "value": "10000000000000000", + "receiver": "erd1aduqqezzw0u3j7tywlq3mrl0yn4z6f6vytdju8gg0neq38fauyzsa5yy6r", + "sender": "erd10ksryjr065ad5475jcg82pnjfg9j9qtszjsrp24anl6ym7cmeddshwnru8", + "gasPrice": 1000000000, + "gasLimit": 50000, + "chainID": "", + "version": 0 + } + ] +} diff --git a/integrationtests/testdata/relayedTxV3/relayed-tx-v3.json b/integrationtests/testdata/relayedTxV3/relayed-tx-v3.json new file mode 100644 index 00000000..210df682 --- /dev/null +++ b/integrationtests/testdata/relayedTxV3/relayed-tx-v3.json @@ -0,0 +1,54 @@ +{ + "miniBlockHash": "b3222be4506429fa237393c56b732a283eea5ccab465deb66b6348083793b6c9", + "nonce": 1000, + "round": 50, + "value": "0", + "valueNum": 0, + "receiver": "erd14eyayfrvlrhzfrwg5zwleua25mkzgncggn35nvc6xhv5yxwml2es0f3dht", + "sender": "erd1k7j6ewjsla4zsgv8v6f6fe3dvrkgv3d0d9jerczw45hzedhyed8sh2u34u", + "receiverShard": 0, + "senderShard": 0, + "gasPrice": 1000000000, + "gasLimit": 15406000, + "gasUsed": 10556000, + "fee": "2257820000000000", + "feeNum": 0.00225782, + "initialPaidFee": "2306320000000000", + "data": null, + "signature": "", + "timestamp": 5040, + "status": "success", + "searchOrder": 0, + "receivers": [ + "erd14eyayfrvlrhzfrwg5zwleua25mkzgncggn35nvc6xhv5yxwml2es0f3dht", + "erd14eyayfrvlrhzfrwg5zwleua25mkzgncggn35nvc6xhv5yxwml2es0f3dht" + ], + "receiversShardIDs": [ + 1, + 1 + ], + "operation": "transfer", + "isRelayed": true, + "innerTransactions": [ + { + "nonce": 10, + "value": "0", + "receiver": "erd14eyayfrvlrhzfrwg5zwleua25mkzgncggn35nvc6xhv5yxwml2es0f3dht", + "sender": "erd1k7j6ewjsla4zsgv8v6f6fe3dvrkgv3d0d9jerczw45hzedhyed8sh2u34u", + "gasPrice": 1000000000, + "gasLimit": 15406000, + "chainID": "", + "version": 0 + }, + { + "nonce": 20, + "value": "1000", + "receiver": "erd14eyayfrvlrhzfrwg5zwleua25mkzgncggn35nvc6xhv5yxwml2es0f3dht", + "sender": "erd1k7j6ewjsla4zsgv8v6f6fe3dvrkgv3d0d9jerczw45hzedhyed8sh2u34u", + "gasPrice": 1000000000, + "gasLimit": 15406000, + "chainID": "", + "version": 0 + } + ] +} diff --git a/integrationtests/testdata/relayedTxV3/relayed-v3-execution-scr-on-dest.json b/integrationtests/testdata/relayedTxV3/relayed-v3-execution-scr-on-dest.json new file mode 100644 index 00000000..e525f876 --- /dev/null +++ b/integrationtests/testdata/relayedTxV3/relayed-v3-execution-scr-on-dest.json @@ -0,0 +1,58 @@ +{ + "miniBlockHash": "5fba08d48700058cf2789651a50ebaa35ee559ce51ee43a709943c6a4f763d25", + "nonce": 1000, + "round": 50, + "value": "0", + "valueNum": 0, + "receiver": "erd1ykqd64fxxpp4wsz0v7sjqem038wfpzlljhx4mhwx8w9lcxmdzcfszrp64a", + "sender": "erd1ykqd64fxxpp4wsz0v7sjqem038wfpzlljhx4mhwx8w9lcxmdzcfszrp64a", + "receiverShard": 0, + "senderShard": 0, + "gasPrice": 1000000000, + "gasLimit": 300000, + "gasUsed": 10556000, + "fee": "2257820000000000", + "feeNum": 0.00225782, + "initialPaidFee": "2306320000000000", + "data": null, + "signature": "", + "timestamp": 5040, + "status": "success", + "searchOrder": 0, + "hasOperations": true, + "hasLogs": true, + "receivers": [ + "erd1aduqqezzw0u3j7tywlq3mrl0yn4z6f6vytdju8gg0neq38fauyzsa5yy6r", + "erd1aduqqezzw0u3j7tywlq3mrl0yn4z6f6vytdju8gg0neq38fauyzsa5yy6r" + ], + "receiversShardIDs": [ + 1, + 1 + ], + "operation": "transfer", + "isRelayed": true, + "completedEvent": true, + "errorEvent": true, + "innerTransactions": [ + { + "nonce": 5, + "value": "10000000000000000", + "receiver": "erd1aduqqezzw0u3j7tywlq3mrl0yn4z6f6vytdju8gg0neq38fauyzsa5yy6r", + "sender": "erd10ksryjr065ad5475jcg82pnjfg9j9qtszjsrp24anl6ym7cmeddshwnru8", + "gasPrice": 1000000000, + "gasLimit": 50000, + "chainID": "", + "version": 0 + }, + { + "nonce": 3, + "value": "10000000000000000", + "receiver": "erd1aduqqezzw0u3j7tywlq3mrl0yn4z6f6vytdju8gg0neq38fauyzsa5yy6r", + "sender": "erd10ksryjr065ad5475jcg82pnjfg9j9qtszjsrp24anl6ym7cmeddshwnru8", + "gasPrice": 1000000000, + "gasLimit": 50000, + "chainID": "", + "version": 0 + } + ] +} diff --git a/integrationtests/testdata/relayedTxV3/relayed-v3-execution-source.json b/integrationtests/testdata/relayedTxV3/relayed-v3-execution-source.json new file mode 100644 index 00000000..e9d692b7 --- /dev/null +++ b/integrationtests/testdata/relayedTxV3/relayed-v3-execution-source.json @@ -0,0 +1,57 @@ +{ + "miniBlockHash": "5fba08d48700058cf2789651a50ebaa35ee559ce51ee43a709943c6a4f763d25", + "nonce": 1000, + "round": 50, + "value": "0", + "valueNum": 0, + "receiver": "erd1ykqd64fxxpp4wsz0v7sjqem038wfpzlljhx4mhwx8w9lcxmdzcfszrp64a", + "sender": "erd1ykqd64fxxpp4wsz0v7sjqem038wfpzlljhx4mhwx8w9lcxmdzcfszrp64a", + "receiverShard": 0, + "senderShard": 0, + "gasPrice": 1000000000, + "gasLimit": 300000, + "gasUsed": 10556000, + "fee": "2257820000000000", + "feeNum": 0.00225782, + "initialPaidFee": "2306320000000000", + "data": null, + "signature": "", + "timestamp": 5040, + "status": "success", + "searchOrder": 0, + "hasOperations": true, + "hasLogs": true, + "receivers": [ + "erd1aduqqezzw0u3j7tywlq3mrl0yn4z6f6vytdju8gg0neq38fauyzsa5yy6r", + "erd1aduqqezzw0u3j7tywlq3mrl0yn4z6f6vytdju8gg0neq38fauyzsa5yy6r" + ], + "receiversShardIDs": [ + 1, + 1 + ], + "operation": "transfer", + "isRelayed": true, + "completedEvent": true, + "innerTransactions": [ + { + "nonce": 5, + "value": "10000000000000000", + "receiver": "erd1aduqqezzw0u3j7tywlq3mrl0yn4z6f6vytdju8gg0neq38fauyzsa5yy6r", + "sender": "erd10ksryjr065ad5475jcg82pnjfg9j9qtszjsrp24anl6ym7cmeddshwnru8", + "gasPrice": 1000000000, + "gasLimit": 50000, + "chainID": "", + "version": 0 + }, + { + "nonce": 3, + "value": "10000000000000000", + "receiver": "erd1aduqqezzw0u3j7tywlq3mrl0yn4z6f6vytdju8gg0neq38fauyzsa5yy6r", + "sender": "erd10ksryjr065ad5475jcg82pnjfg9j9qtszjsrp24anl6ym7cmeddshwnru8", + "gasPrice": 1000000000, + "gasLimit": 50000, + "chainID": "", + "version": 0 + } + ] +} diff --git a/integrationtests/testdata/updateNFT/token-after-new-creator.json b/integrationtests/testdata/updateNFT/token-after-new-creator.json new file mode 100644 index 00000000..ac967297 --- /dev/null +++ b/integrationtests/testdata/updateNFT/token-after-new-creator.json @@ -0,0 +1,21 @@ +{ + "identifier": "NFT-abcd-0e", + "token": "NFT-abcd", + "numDecimals": 0, + "nonce": 14, + "timestamp": 5040, + "data": { + "uris": [ + "dXJp", + "dXJp", + "dXJp", + "dXJp", + "dXJp" + ], + "nonEmptyURIs": true, + "whiteListedStorage": false, + "attributes": "c29tZXRoaW5n", + "creator": "erd12m3x8jp6dl027pj5f2nw6ght2cyhhjfrs86cdwsa8xn83r375qfqrwpdx0" + }, + "frozen": false +} diff --git a/integrationtests/testdata/updateNFT/token-after-new-royalties.json b/integrationtests/testdata/updateNFT/token-after-new-royalties.json new file mode 100644 index 00000000..b8eb8dbc --- /dev/null +++ b/integrationtests/testdata/updateNFT/token-after-new-royalties.json @@ -0,0 +1,22 @@ +{ + "identifier": "NFT-abcd-0e", + "token": "NFT-abcd", + "numDecimals": 0, + "nonce": 14, + "timestamp": 5040, + "data": { + "uris": [ + "dXJp", + "dXJp", + "dXJp", + "dXJp", + "dXJp" + ], + "nonEmptyURIs": true, + "whiteListedStorage": false, + "attributes": "c29tZXRoaW5n", + "creator": "erd12m3x8jp6dl027pj5f2nw6ght2cyhhjfrs86cdwsa8xn83r375qfqrwpdx0", + "royalties": 100 + }, + "frozen": false +} diff --git a/integrationtests/testdata/updateNFT/token-after-recreate.json b/integrationtests/testdata/updateNFT/token-after-recreate.json new file mode 100644 index 00000000..3b2385f6 --- /dev/null +++ b/integrationtests/testdata/updateNFT/token-after-recreate.json @@ -0,0 +1,16 @@ +{ + "identifier": "NEW-abcd-64", + "token": "NEW-abcd", + "numDecimals": 0, + "nonce": 100, + "timestamp": 5040, + "data": { + "uris": [ + "dXJp" + ], + "whiteListedStorage": false, + "name": "token", + "nonEmptyURIs": true, + "hash": "aGFzaA==" + } +} diff --git a/integrationtests/testdata/updateNFT/token-after-set-new-uris.json b/integrationtests/testdata/updateNFT/token-after-set-new-uris.json new file mode 100644 index 00000000..c3b3ad8a --- /dev/null +++ b/integrationtests/testdata/updateNFT/token-after-set-new-uris.json @@ -0,0 +1,20 @@ +{ + "identifier": "NFT-abcd-0e", + "token": "NFT-abcd", + "numDecimals": 0, + "nonce": 14, + "timestamp": 5040, + "data": { + "uris": [ + "dXJp", + "dXJp", + "dXJp", + "dXJp", + "dXJp" + ], + "nonEmptyURIs": true, + "whiteListedStorage": false, + "attributes": "c29tZXRoaW5n" + }, + "frozen": false +} diff --git a/integrationtests/testdata/updateNFT/token-after-update.json b/integrationtests/testdata/updateNFT/token-after-update.json new file mode 100644 index 00000000..ebb9c835 --- /dev/null +++ b/integrationtests/testdata/updateNFT/token-after-update.json @@ -0,0 +1,16 @@ +{ + "identifier": "NEW-abcd-64", + "token": "NEW-abcd", + "numDecimals": 0, + "nonce": 100, + "timestamp": 5040, + "data": { + "uris": [ + "dXJp" + ], + "whiteListedStorage": false, + "name": "token-second", + "nonEmptyURIs": true, + "hash": "aGFzaA==" + } +} diff --git a/integrationtests/testdata/updateNFT/token-before-recreate.json b/integrationtests/testdata/updateNFT/token-before-recreate.json new file mode 100644 index 00000000..8abec9d2 --- /dev/null +++ b/integrationtests/testdata/updateNFT/token-before-recreate.json @@ -0,0 +1,16 @@ +{ + "identifier": "NEW-abcd-64", + "token": "NEW-abcd", + "numDecimals": 0, + "nonce": 100, + "timestamp": 5040, + "data": { + "name": "token-token-token", + "uris": [ + "dXJp", + "dXJp" + ], + "nonEmptyURIs": true, + "whiteListedStorage": false + } +} diff --git a/integrationtests/testdata/updateNFT/token-color-1.json b/integrationtests/testdata/updateNFT/token-color-1.json new file mode 100644 index 00000000..4c4f1a45 --- /dev/null +++ b/integrationtests/testdata/updateNFT/token-color-1.json @@ -0,0 +1,16 @@ +{ + "identifier": "COLORS-df0e82-01", + "token": "COLORS-df0e82", + "numDecimals": 0, + "nonce": 1, + "timestamp": 5040, + "data": { + "uris": [ + "dXJp" + ], + "whiteListedStorage": false, + "name": "GREEN", + "nonEmptyURIs": true, + "hash": "aGFzaA==" + } +} diff --git a/integrationtests/testdata/updateNFT/token-color-2.json b/integrationtests/testdata/updateNFT/token-color-2.json new file mode 100644 index 00000000..87eccc69 --- /dev/null +++ b/integrationtests/testdata/updateNFT/token-color-2.json @@ -0,0 +1,16 @@ +{ + "identifier": "COLORS-df0e82-02", + "token": "COLORS-df0e82", + "numDecimals": 0, + "nonce": 2, + "timestamp": 5040, + "data": { + "uris": [ + "dXJp" + ], + "whiteListedStorage": false, + "name": "GREEN", + "nonEmptyURIs": true, + "hash": "aGFzaA==" + } +} diff --git a/integrationtests/updateNFT_test.go b/integrationtests/updateNFT_test.go index 723fddfc..5a482431 100644 --- a/integrationtests/updateNFT_test.go +++ b/integrationtests/updateNFT_test.go @@ -225,4 +225,316 @@ func TestNFTUpdateMetadata(t *testing.T) { err = esClient.DoMultiGet(context.Background(), ids, indexerdata.TokensIndex, true, genericResponse) require.Nil(t, err) require.JSONEq(t, readExpectedResult("./testdata/updateNFT/token-after-un-freeze.json"), string(genericResponse.Docs[0].Source)) + + // Set new uris + pool = &outport.TransactionPool{ + Logs: []*outport.LogData{ + { + TxHash: hex.EncodeToString([]byte("h1")), + Log: &transaction.Log{ + Events: []*transaction.Event{ + { + Address: decodeAddress(address), + Identifier: []byte(core.ESDTSetNewURIs), + Topics: [][]byte{[]byte("NFT-abcd"), big.NewInt(14).Bytes(), big.NewInt(0).Bytes(), []byte("uri"), []byte("uri"), []byte("uri"), []byte("uri"), []byte("uri")}, + }, + nil, + }, + }, + }, + }, + } + err = esProc.SaveTransactions(createOutportBlockWithHeader(body, header, pool, nil, testNumOfShards)) + require.Nil(t, err) + ids = []string{"NFT-abcd-0e"} + genericResponse = &GenericResponse{} + err = esClient.DoMultiGet(context.Background(), ids, indexerdata.TokensIndex, true, genericResponse) + require.Nil(t, err) + require.JSONEq(t, readExpectedResult("./testdata/updateNFT/token-after-set-new-uris.json"), string(genericResponse.Docs[0].Source)) + + // new creator + newCreator := "erd12m3x8jp6dl027pj5f2nw6ght2cyhhjfrs86cdwsa8xn83r375qfqrwpdx0" + pool = &outport.TransactionPool{ + Logs: []*outport.LogData{ + { + TxHash: hex.EncodeToString([]byte("h1")), + Log: &transaction.Log{ + Events: []*transaction.Event{ + { + Address: decodeAddress(newCreator), + Identifier: []byte(core.ESDTModifyCreator), + Topics: [][]byte{[]byte("NFT-abcd"), big.NewInt(14).Bytes(), big.NewInt(0).Bytes()}, + }, + nil, + }, + }, + }, + }, + } + err = esProc.SaveTransactions(createOutportBlockWithHeader(body, header, pool, nil, testNumOfShards)) + require.Nil(t, err) + ids = []string{"NFT-abcd-0e"} + genericResponse = &GenericResponse{} + err = esClient.DoMultiGet(context.Background(), ids, indexerdata.TokensIndex, true, genericResponse) + require.Nil(t, err) + require.JSONEq(t, readExpectedResult("./testdata/updateNFT/token-after-new-creator.json"), string(genericResponse.Docs[0].Source)) + + // new royalties + pool = &outport.TransactionPool{ + Logs: []*outport.LogData{ + { + TxHash: hex.EncodeToString([]byte("h1")), + Log: &transaction.Log{ + Events: []*transaction.Event{ + { + Address: decodeAddress(address), + Identifier: []byte(core.ESDTModifyRoyalties), + Topics: [][]byte{[]byte("NFT-abcd"), big.NewInt(14).Bytes(), big.NewInt(0).Bytes(), big.NewInt(100).Bytes()}, + }, + nil, + }, + }, + }, + }, + } + err = esProc.SaveTransactions(createOutportBlockWithHeader(body, header, pool, nil, testNumOfShards)) + require.Nil(t, err) + ids = []string{"NFT-abcd-0e"} + genericResponse = &GenericResponse{} + err = esClient.DoMultiGet(context.Background(), ids, indexerdata.TokensIndex, true, genericResponse) + require.Nil(t, err) + require.JSONEq(t, readExpectedResult("./testdata/updateNFT/token-after-new-royalties.json"), string(genericResponse.Docs[0].Source)) +} + +func TestCreateNFTAndMetaDataRecreate(t *testing.T) { + setLogLevelDebug() + + esClient, err := createESClient(esURL) + require.Nil(t, err) + + esdtCreateData := &esdt.ESDigitalToken{ + TokenMetaData: &esdt.MetaData{ + Name: []byte("token-token-token"), + URIs: [][]byte{[]byte("uri"), []byte("uri")}, + }, + } + marshalizedCreate, _ := json.Marshal(esdtCreateData) + + esProc, err := CreateElasticProcessor(esClient) + require.Nil(t, err) + + header := &dataBlock.Header{ + Round: 50, + TimeStamp: 5040, + ShardID: 1, + } + body := &dataBlock.Body{} + + // CREATE NFT data + address := "erd1w7jyzuj6cv4ngw8luhlkakatjpmjh3ql95lmxphd3vssc4vpymks6k5th7" + pool := &outport.TransactionPool{ + Logs: []*outport.LogData{ + { + TxHash: hex.EncodeToString([]byte("h1")), + Log: &transaction.Log{ + Events: []*transaction.Event{ + { + Address: decodeAddress(address), + Identifier: []byte(core.BuiltInFunctionESDTNFTCreate), + Topics: [][]byte{[]byte("NEW-abcd"), big.NewInt(100).Bytes(), big.NewInt(1).Bytes(), marshalizedCreate}, + }, + nil, + }, + }, + }, + }, + } + err = esProc.SaveTransactions(createOutportBlockWithHeader(body, header, pool, nil, testNumOfShards)) + require.Nil(t, err) + + ids := []string{"NEW-abcd-64"} + genericResponse := &GenericResponse{} + err = esClient.DoMultiGet(context.Background(), ids, indexerdata.TokensIndex, true, genericResponse) + require.Nil(t, err) + require.JSONEq(t, readExpectedResult("./testdata/updateNFT/token-before-recreate.json"), string(genericResponse.Docs[0].Source)) + + // RECREATE + reCreate := &esdt.ESDigitalToken{ + TokenMetaData: &esdt.MetaData{ + Name: []byte("token"), + URIs: [][]byte{[]byte("uri")}, + Hash: []byte("hash"), + }, + } + marshalizedReCreate, _ := json.Marshal(reCreate) + + pool = &outport.TransactionPool{ + Logs: []*outport.LogData{ + { + TxHash: hex.EncodeToString([]byte("h1")), + Log: &transaction.Log{ + Events: []*transaction.Event{ + { + Address: decodeAddress(address), + Identifier: []byte(core.ESDTMetaDataRecreate), + Topics: [][]byte{[]byte("NEW-abcd"), big.NewInt(100).Bytes(), big.NewInt(0).Bytes(), marshalizedReCreate}, + }, + nil, + }, + }, + }, + }, + } + err = esProc.SaveTransactions(createOutportBlockWithHeader(body, header, pool, nil, testNumOfShards)) + require.Nil(t, err) + + genericResponse = &GenericResponse{} + err = esClient.DoMultiGet(context.Background(), ids, indexerdata.TokensIndex, true, genericResponse) + require.Nil(t, err) + require.JSONEq(t, readExpectedResult("./testdata/updateNFT/token-after-recreate.json"), string(genericResponse.Docs[0].Source)) + + // UPDATE + update := &esdt.ESDigitalToken{ + TokenMetaData: &esdt.MetaData{ + Name: []byte("token-second"), + URIs: [][]byte{[]byte("uri")}, + Hash: []byte("hash"), + }, + } + marshalizedUpdate, _ := json.Marshal(update) + + pool = &outport.TransactionPool{ + Logs: []*outport.LogData{ + { + TxHash: hex.EncodeToString([]byte("h1")), + Log: &transaction.Log{ + Events: []*transaction.Event{ + { + Address: decodeAddress(address), + Identifier: []byte(core.ESDTMetaDataUpdate), + Topics: [][]byte{[]byte("NEW-abcd"), big.NewInt(100).Bytes(), big.NewInt(0).Bytes(), marshalizedUpdate}, + }, + nil, + }, + }, + }, + }, + } + err = esProc.SaveTransactions(createOutportBlockWithHeader(body, header, pool, nil, testNumOfShards)) + require.Nil(t, err) + + genericResponse = &GenericResponse{} + err = esClient.DoMultiGet(context.Background(), ids, indexerdata.TokensIndex, true, genericResponse) + require.Nil(t, err) + require.JSONEq(t, readExpectedResult("./testdata/updateNFT/token-after-update.json"), string(genericResponse.Docs[0].Source)) +} + +func TestMultipleESDTMetadataRecreate(t *testing.T) { + setLogLevelDebug() + + esClient, err := createESClient(esURL) + require.Nil(t, err) + + esdtCreateData := &esdt.ESDigitalToken{ + TokenMetaData: &esdt.MetaData{ + Name: []byte("YELLOW"), + URIs: [][]byte{[]byte("uri"), []byte("uri")}, + }, + } + marshalizedCreate, _ := json.Marshal(esdtCreateData) + + esProc, err := CreateElasticProcessor(esClient) + require.Nil(t, err) + + header := &dataBlock.Header{ + Round: 50, + TimeStamp: 5040, + ShardID: 1, + } + body := &dataBlock.Body{} + + // CREATE NFT data + address := "erd1w7jyzuj6cv4ngw8luhlkakatjpmjh3ql95lmxphd3vssc4vpymks6k5th7" + pool := &outport.TransactionPool{ + Logs: []*outport.LogData{ + { + TxHash: hex.EncodeToString([]byte("h1")), + Log: &transaction.Log{ + Events: []*transaction.Event{ + { + Address: decodeAddress(address), + Identifier: []byte(core.BuiltInFunctionESDTNFTCreate), + Topics: [][]byte{[]byte("COLORS-df0e82"), big.NewInt(1).Bytes(), big.NewInt(1).Bytes(), marshalizedCreate}, + }, + nil, + }, + }, + }, + { + TxHash: hex.EncodeToString([]byte("h2")), + Log: &transaction.Log{ + Events: []*transaction.Event{ + { + Address: decodeAddress(address), + Identifier: []byte(core.BuiltInFunctionESDTNFTCreate), + Topics: [][]byte{[]byte("COLORS-df0e82"), big.NewInt(2).Bytes(), big.NewInt(1).Bytes(), marshalizedCreate}, + }, + nil, + }, + }, + }, + }, + } + err = esProc.SaveTransactions(createOutportBlockWithHeader(body, header, pool, nil, testNumOfShards)) + require.Nil(t, err) + + // RECREATE + reCreate := &esdt.ESDigitalToken{ + TokenMetaData: &esdt.MetaData{ + Name: []byte("GREEN"), + URIs: [][]byte{[]byte("uri")}, + Hash: []byte("hash"), + }, + } + marshalizedReCreate, _ := json.Marshal(reCreate) + + pool = &outport.TransactionPool{ + Logs: []*outport.LogData{ + { + TxHash: hex.EncodeToString([]byte("h1")), + Log: &transaction.Log{ + Events: []*transaction.Event{ + { + Address: decodeAddress(address), + Identifier: []byte(core.ESDTMetaDataRecreate), + Topics: [][]byte{[]byte("COLORS-df0e82"), big.NewInt(1).Bytes(), big.NewInt(0).Bytes(), marshalizedReCreate}, + }, + nil, + }, + }, + }, + { + TxHash: hex.EncodeToString([]byte("h2")), + Log: &transaction.Log{ + Events: []*transaction.Event{ + { + Address: decodeAddress(address), + Identifier: []byte(core.ESDTMetaDataRecreate), + Topics: [][]byte{[]byte("COLORS-df0e82"), big.NewInt(2).Bytes(), big.NewInt(0).Bytes(), marshalizedReCreate}, + }, + nil, + }, + }, + }, + }, + } + err = esProc.SaveTransactions(createOutportBlockWithHeader(body, header, pool, nil, testNumOfShards)) + require.Nil(t, err) + + ids := []string{"COLORS-df0e82-01", "COLORS-df0e82-02"} + genericResponse := &GenericResponse{} + err = esClient.DoMultiGet(context.Background(), ids, indexerdata.TokensIndex, true, genericResponse) + require.Nil(t, err) + require.JSONEq(t, readExpectedResult("./testdata/updateNFT/token-color-1.json"), string(genericResponse.Docs[0].Source)) + require.JSONEq(t, readExpectedResult("./testdata/updateNFT/token-color-2.json"), string(genericResponse.Docs[1].Source)) } diff --git a/integrationtests/utils.go b/integrationtests/utils.go index 27f07c23..5fe2aaf8 100644 --- a/integrationtests/utils.go +++ b/integrationtests/utils.go @@ -3,6 +3,7 @@ package integrationtests import ( "encoding/json" "fmt" + "io" "io/ioutil" "net/http" "net/url" @@ -68,7 +69,7 @@ func CreateElasticProcessor( // nolint func readExpectedResult(path string) string { jsonFile, _ := os.Open(path) - byteValue, _ := ioutil.ReadAll(jsonFile) + byteValue, _ := io.ReadAll(jsonFile) return string(byteValue) } diff --git a/mock/databaseWriterStub.go b/mock/databaseWriterStub.go index 9a41e0ff..1df608a8 100644 --- a/mock/databaseWriterStub.go +++ b/mock/databaseWriterStub.go @@ -14,6 +14,11 @@ type DatabaseWriterStub struct { DoScrollRequestCalled func(index string, body []byte, withSource bool, handlerFunc func(responseBytes []byte) error) error } +// PutMappings - +func (dwm *DatabaseWriterStub) PutMappings(_ string, _ *bytes.Buffer) error { + return nil +} + // UpdateByQuery - func (dwm *DatabaseWriterStub) UpdateByQuery(_ context.Context, _ string, _ *bytes.Buffer) error { return nil diff --git a/process/elasticproc/converters/tokenMetaData.go b/process/elasticproc/converters/tokenMetaData.go index 09f5f845..8cff539b 100644 --- a/process/elasticproc/converters/tokenMetaData.go +++ b/process/elasticproc/converters/tokenMetaData.go @@ -83,11 +83,49 @@ func PrepareNFTUpdateData(buffSlice *data.BufferSlice, updateNFTData []*data.NFT metaData := []byte(fmt.Sprintf(`{"update":{ "_index":"%s","_id":"%s"}}%s`, index, id, "\n")) freezeOrUnfreezeTokenIndex := (nftUpdate.Freeze || nftUpdate.UnFreeze) && !isAccountsESDTIndex if freezeOrUnfreezeTokenIndex { - return buffSlice.PutData(metaData, prepareSerializeDataForFreezeAndUnFreeze(nftUpdate)) + err := buffSlice.PutData(metaData, prepareSerializeDataForFreezeAndUnFreeze(nftUpdate)) + if err != nil { + return err + } + continue } pauseOrUnPauseTokenIndex := (nftUpdate.Pause || nftUpdate.UnPause) && !isAccountsESDTIndex if pauseOrUnPauseTokenIndex { - return buffSlice.PutData(metaData, prepareSerializedDataForPauseAndUnPause(nftUpdate)) + err := buffSlice.PutData(metaData, prepareSerializedDataForPauseAndUnPause(nftUpdate)) + if err != nil { + return err + } + + continue + } + if nftUpdate.NewMetaData != nil { + serializedData, err := prepareSerializedDataForMetaDataRecreate(nftUpdate) + if err != nil { + return err + } + err = buffSlice.PutData(metaData, serializedData) + if err != nil { + return err + } + + continue + } + + if nftUpdate.NewCreator != "" { + err := buffSlice.PutData(metaData, prepareSerializeDataForNewCreator(nftUpdate)) + if err != nil { + return err + } + + continue + } + if nftUpdate.NewRoyalties.HasValue { + err := buffSlice.PutData(metaData, prepareSerializeDataForNewRoyalties(nftUpdate)) + if err != nil { + return err + } + + continue } truncatedAttributes := TruncateFieldIfExceedsMaxLengthBase64(string(nftUpdate.NewAttributes)) @@ -134,7 +172,7 @@ func PrepareNFTUpdateData(buffSlice *data.BufferSlice, updateNFTData []*data.NFT codeToExecute = ` if (ctx._source.containsKey('data')) { - if (!ctx._source.data.containsKey('uris')) { + if ((!ctx._source.data.containsKey('uris')) || (params.set)) { ctx._source.data.uris = params.uris; } else { int i; @@ -155,7 +193,7 @@ func PrepareNFTUpdateData(buffSlice *data.BufferSlice, updateNFTData []*data.NFT ctx._source.data.nonEmptyURIs = true; } ` - serializedData = []byte(fmt.Sprintf(`{"script": {"source": "%s","lang": "painless","params": {"uris": %s}},"upsert": {}}`, FormatPainlessSource(codeToExecute), marshalizedURIS)) + serializedData = []byte(fmt.Sprintf(`{"script": {"source": "%s","lang": "painless","params": {"uris": %s, "set":%t}},"upsert": {}}`, FormatPainlessSource(codeToExecute), marshalizedURIS, nftUpdate.SetURIs)) } err := buffSlice.PutData(metaData, serializedData) @@ -190,3 +228,45 @@ func prepareSerializedDataForPauseAndUnPause(nftUpdateData *data.NFTDataUpdate) return serializedData } + +func prepareSerializedDataForMetaDataRecreate(nftUpdateData *data.NFTDataUpdate) ([]byte, error) { + tokenMetaDataBytes, err := json.Marshal(nftUpdateData.NewMetaData) + if err != nil { + return nil, err + } + + codeToExecute := ` + ctx._source.data = params.metaData; +` + serializedData := []byte(fmt.Sprintf(`{"script": {"source": "%s","lang": "painless","params": {"metaData": %s}}, "upsert": {}}`, + FormatPainlessSource(codeToExecute), tokenMetaDataBytes), + ) + + return serializedData, nil +} + +func prepareSerializeDataForNewRoyalties(nftUpdateData *data.NFTDataUpdate) []byte { + codeToExecute := ` + if (ctx._source.containsKey('data')) { + ctx._source.data.royalties = params.royalties; + } +` + serializedData := []byte(fmt.Sprintf(`{"script": {"source": "%s","lang": "painless","params": {"royalties": %d}}, "upsert": {}}`, + FormatPainlessSource(codeToExecute), nftUpdateData.NewRoyalties.Value), + ) + + return serializedData +} + +func prepareSerializeDataForNewCreator(nftUpdateData *data.NFTDataUpdate) []byte { + codeToExecute := ` + if (ctx._source.containsKey('data')) { + ctx._source.data.creator = params.creator; + } +` + serializedData := []byte(fmt.Sprintf(`{"script": {"source": "%s","lang": "painless","params": {"creator": "%s"}}, "upsert": {}}`, + FormatPainlessSource(codeToExecute), nftUpdateData.NewCreator), + ) + + return serializedData +} diff --git a/process/elasticproc/converters/tokenMetaData_test.go b/process/elasticproc/converters/tokenMetaData_test.go index b68c6551..a6bacfab 100644 --- a/process/elasticproc/converters/tokenMetaData_test.go +++ b/process/elasticproc/converters/tokenMetaData_test.go @@ -59,7 +59,7 @@ func TestPrepareNFTUpdateData(t *testing.T) { require.Equal(t, `{"update":{ "_index":"tokens","_id":"MYTKN-abcd-01"}} {"script": {"source": "if (ctx._source.containsKey('data')) {ctx._source.data.attributes = params.attributes;if (!params.metadata.isEmpty() ) {ctx._source.data.metadata = params.metadata} else {if (ctx._source.data.containsKey('metadata')) {ctx._source.data.remove('metadata')}}if (params.tags != null) {ctx._source.data.tags = params.tags} else {if (ctx._source.data.containsKey('tags')) {ctx._source.data.remove('tags')}}}","lang": "painless","params": {"attributes": "YWFhYQ==", "metadata": "", "tags": null}}, "upsert": {}} {"update":{ "_index":"tokens","_id":"TOKEN-1234-1a"}} -{"script": {"source": "if (ctx._source.containsKey('data')) {if (!ctx._source.data.containsKey('uris')) {ctx._source.data.uris = params.uris;} else {int i;for ( i = 0; i < params.uris.length; i++) {boolean found = false;int j;for ( j = 0; j < ctx._source.data.uris.length; j++) {if ( params.uris.get(i) == ctx._source.data.uris.get(j) ) {found = true;break}}if ( !found ) {ctx._source.data.uris.add(params.uris.get(i))}}}ctx._source.data.nonEmptyURIs = true;}","lang": "painless","params": {"uris": ["dXJpMQ==","dXJpMg=="]}},"upsert": {}} +{"script": {"source": "if (ctx._source.containsKey('data')) {if ((!ctx._source.data.containsKey('uris')) || (params.set)) {ctx._source.data.uris = params.uris;} else {int i;for ( i = 0; i < params.uris.length; i++) {boolean found = false;int j;for ( j = 0; j < ctx._source.data.uris.length; j++) {if ( params.uris.get(i) == ctx._source.data.uris.get(j) ) {found = true;break}}if ( !found ) {ctx._source.data.uris.add(params.uris.get(i))}}}ctx._source.data.nonEmptyURIs = true;}","lang": "painless","params": {"uris": ["dXJpMQ==","dXJpMg=="], "set":false}},"upsert": {}} `, buffSlice.Buffers()[0].String()) } diff --git a/process/elasticproc/elasticProcessor.go b/process/elasticproc/elasticProcessor.go index 640e5f09..89634a95 100644 --- a/process/elasticproc/elasticProcessor.go +++ b/process/elasticproc/elasticProcessor.go @@ -20,6 +20,7 @@ import ( "github.com/multiversx/mx-chain-es-indexer-go/process/elasticproc/converters" "github.com/multiversx/mx-chain-es-indexer-go/process/elasticproc/tags" "github.com/multiversx/mx-chain-es-indexer-go/process/elasticproc/tokeninfo" + "github.com/multiversx/mx-chain-es-indexer-go/templates" logger "github.com/multiversx/mx-chain-logger-go" ) @@ -44,6 +45,7 @@ type ArgElasticProcessor struct { ImportDB bool IndexTemplates map[string]*bytes.Buffer IndexPolicies map[string]*bytes.Buffer + ExtraMappings []templates.ExtraMapping EnabledIndexes map[string]struct{} TransactionsProc DBTransactionsHandler AccountsProc DBAccountHandler @@ -94,7 +96,7 @@ func NewElasticProcessor(arguments *ArgElasticProcessor) (*elasticProcessor, err bulkRequestMaxSize: arguments.BulkRequestMaxSize, } - err = ei.init(arguments.UseKibana, arguments.IndexTemplates, arguments.IndexPolicies) + err = ei.init(arguments.UseKibana, arguments.IndexTemplates, arguments.IndexPolicies, arguments.ExtraMappings) if err != nil { return nil, err } @@ -105,7 +107,7 @@ func NewElasticProcessor(arguments *ArgElasticProcessor) (*elasticProcessor, err } // TODO move all the index create part in a new component -func (ei *elasticProcessor) init(useKibana bool, indexTemplates, _ map[string]*bytes.Buffer) error { +func (ei *elasticProcessor) init(useKibana bool, indexTemplates, _ map[string]*bytes.Buffer, extraMappings []templates.ExtraMapping) error { err := ei.createOpenDistroTemplates(indexTemplates) if err != nil { return err @@ -135,6 +137,17 @@ func (ei *elasticProcessor) init(useKibana bool, indexTemplates, _ map[string]*b return err } + return ei.addExtraMappings(extraMappings) +} + +func (ei *elasticProcessor) addExtraMappings(extraMappings []templates.ExtraMapping) error { + for _, mappingsTuple := range extraMappings { + err := ei.elasticClient.PutMappings(mappingsTuple.Index, mappingsTuple.Mappings) + if err != nil { + return err + } + } + return nil } diff --git a/process/elasticproc/factory/elasticProcessorFactory.go b/process/elasticproc/factory/elasticProcessorFactory.go index eb10110c..ad865953 100644 --- a/process/elasticproc/factory/elasticProcessorFactory.go +++ b/process/elasticproc/factory/elasticProcessorFactory.go @@ -40,6 +40,10 @@ func CreateElasticProcessor(arguments ArgElasticProcessorFactory) (dataindexer.E if err != nil { return nil, err } + extraMappings, err := templatesAndPoliciesReader.GetExtraMappings() + if err != nil { + return nil, err + } enabledIndexesMap := make(map[string]struct{}) for _, index := range arguments.EnabledIndexes { @@ -119,6 +123,7 @@ func CreateElasticProcessor(arguments ArgElasticProcessorFactory) (dataindexer.E UseKibana: arguments.UseKibana, IndexTemplates: indexTemplates, IndexPolicies: indexPolicies, + ExtraMappings: extraMappings, OperationsProc: operationsProc, ImportDB: arguments.ImportDB, Version: arguments.Version, diff --git a/process/elasticproc/interface.go b/process/elasticproc/interface.go index 997697b2..fe4b565f 100644 --- a/process/elasticproc/interface.go +++ b/process/elasticproc/interface.go @@ -21,6 +21,7 @@ type DatabaseClientHandler interface { DoCountRequest(ctx context.Context, index string, body []byte) (uint64, error) UpdateByQuery(ctx context.Context, index string, buff *bytes.Buffer) error + PutMappings(indexName string, mappings *bytes.Buffer) error CheckAndCreateIndex(index string) error CheckAndCreateAlias(alias string, index string) error CheckAndCreateTemplate(templateName string, template *bytes.Buffer) error diff --git a/process/elasticproc/logsevents/delegatorsProcessor.go b/process/elasticproc/logsevents/delegatorsProcessor.go index 0cec5816..f85264d0 100644 --- a/process/elasticproc/logsevents/delegatorsProcessor.go +++ b/process/elasticproc/logsevents/delegatorsProcessor.go @@ -18,6 +18,9 @@ const ( withdrawFunc = "withdraw" reDelegateRewardsFunc = "reDelegateRewards" claimRewardsFunc = "claimRewards" + + minNumTopicsClaimRewards = 2 + numTopicsClaimRewardsWithContractAddress = 3 ) type delegatorsProc struct { @@ -133,18 +136,23 @@ func (dp *delegatorsProc) getDelegatorFromClaimRewardsEvent(args *argsProcessEve // topics slice contains: // topics[0] -- claimed rewards // topics[1] -- true = if delegator was deleted + // topics[2] -- if is present will contain the contract address - if len(topics) < 2 { + if len(topics) < minNumTopicsClaimRewards { return nil } - shouldDelete := bytesToBool(topics[1]) + shouldDelete := bytesToBool(topics[minNumTopicsClaimRewards-1]) if !shouldDelete { return nil } encodedAddr := dp.pubkeyConverter.SilentEncode(args.event.GetAddress(), log) + encodedContractAddr := dp.pubkeyConverter.SilentEncode(args.logAddress, log) + if len(topics) == numTopicsClaimRewardsWithContractAddress { + encodedContractAddr = dp.pubkeyConverter.SilentEncode(topics[numTopicsClaimRewardsWithContractAddress-1], log) + } return &data.Delegator{ Address: encodedAddr, diff --git a/process/elasticproc/logsevents/delegatorsProcessor_test.go b/process/elasticproc/logsevents/delegatorsProcessor_test.go index e808fb4e..8d8a5471 100644 --- a/process/elasticproc/logsevents/delegatorsProcessor_test.go +++ b/process/elasticproc/logsevents/delegatorsProcessor_test.go @@ -1,6 +1,7 @@ package logsevents import ( + "encoding/hex" "math/big" "strconv" "testing" @@ -100,6 +101,34 @@ func TestDelegatorProcessor_ClaimRewardsWithDelete(t *testing.T) { }, res.delegator) } +func TestDelegatorProcessor_ClaimRewardsContractAddressInTopics(t *testing.T) { + t.Parallel() + + contractAddress := []byte("contract2") + event := &transaction.Event{ + Address: []byte("addr"), + Identifier: []byte(claimRewardsFunc), + Topics: [][]byte{big.NewInt(1000).Bytes(), []byte(strconv.FormatBool(true)), contractAddress}, + } + args := &argsProcessEvent{ + timestamp: 1234, + event: event, + logAddress: []byte("contract1"), + selfShardID: core.MetachainShardId, + } + + balanceConverter, _ := converters.NewBalanceConverter(10) + delegatorsProcessor := newDelegatorsProcessor(&mock.PubkeyConverterMock{}, balanceConverter) + + res := delegatorsProcessor.processEvent(args) + require.True(t, res.processed) + require.Equal(t, &data.Delegator{ + Address: "61646472", + Contract: hex.EncodeToString(contractAddress), + ShouldDelete: true, + }, res.delegator) +} + func TestDelegatorProcessor_ClaimRewardsNoDelete(t *testing.T) { t.Parallel() diff --git a/process/elasticproc/logsevents/esdtIssueProcessor.go b/process/elasticproc/logsevents/esdtIssueProcessor.go index 7b649cc1..04bcfc23 100644 --- a/process/elasticproc/logsevents/esdtIssueProcessor.go +++ b/process/elasticproc/logsevents/esdtIssueProcessor.go @@ -11,13 +11,16 @@ import ( const ( numIssueLogTopics = 4 - issueFungibleESDTFunc = "issue" - issueSemiFungibleESDTFunc = "issueSemiFungible" - issueNonFungibleESDTFunc = "issueNonFungible" - registerMetaESDTFunc = "registerMetaESDT" - changeSFTToMetaESDTFunc = "changeSFTToMetaESDT" - transferOwnershipFunc = "transferOwnership" - registerAndSetRolesFunc = "registerAndSetAllRoles" + issueFungibleESDTFunc = "issue" + issueSemiFungibleESDTFunc = "issueSemiFungible" + issueNonFungibleESDTFunc = "issueNonFungible" + registerMetaESDTFunc = "registerMetaESDT" + changeSFTToMetaESDTFunc = "changeSFTToMetaESDT" + changeToDynamicESDTFunc = "changeToDynamic" + transferOwnershipFunc = "transferOwnership" + registerAndSetRolesFunc = "registerAndSetAllRoles" + registerDynamicFunc = "registerDynamic" + registerAndSetRolesDynamicFunc = "registerAndSetAllRolesDynamic" ) type esdtIssueProcessor struct { @@ -29,13 +32,16 @@ func newESDTIssueProcessor(pubkeyConverter core.PubkeyConverter) *esdtIssueProce return &esdtIssueProcessor{ pubkeyConverter: pubkeyConverter, issueOperationsIdentifiers: map[string]struct{}{ - issueFungibleESDTFunc: {}, - issueSemiFungibleESDTFunc: {}, - issueNonFungibleESDTFunc: {}, - registerMetaESDTFunc: {}, - changeSFTToMetaESDTFunc: {}, - transferOwnershipFunc: {}, - registerAndSetRolesFunc: {}, + issueFungibleESDTFunc: {}, + issueSemiFungibleESDTFunc: {}, + issueNonFungibleESDTFunc: {}, + registerMetaESDTFunc: {}, + changeSFTToMetaESDTFunc: {}, + transferOwnershipFunc: {}, + registerAndSetRolesFunc: {}, + registerDynamicFunc: {}, + registerAndSetRolesDynamicFunc: {}, + changeToDynamicESDTFunc: {}, }, } } @@ -91,6 +97,10 @@ func (eip *esdtIssueProcessor) processEvent(args *argsProcessEvent) argOutputPro Properties: &data.TokenProperties{}, } + if identifierStr == changeToDynamicESDTFunc { + tokenInfo.ChangeToDynamic = true + } + if identifierStr == transferOwnershipFunc && len(topics) >= numIssueLogTopics+1 { newOwner := eip.pubkeyConverter.SilentEncode(topics[4], log) tokenInfo.TransferOwnership = true diff --git a/process/elasticproc/logsevents/informativeLogsProcessor.go b/process/elasticproc/logsevents/informativeLogsProcessor.go index ad3709de..a7ca0666 100644 --- a/process/elasticproc/logsevents/informativeLogsProcessor.go +++ b/process/elasticproc/logsevents/informativeLogsProcessor.go @@ -49,6 +49,11 @@ func (ilp *informativeLogsProcessor) processEvent(args *argsProcessEvent) argOut } } + setSuccess := tx.CompletedEvent && tx.ErrorEvent + if setSuccess { + tx.Status = transaction.TxStatusSuccess.String() + } + return argOutputProcessEvent{ processed: true, } diff --git a/process/elasticproc/logsevents/logsAndEventsProcessor.go b/process/elasticproc/logsevents/logsAndEventsProcessor.go index d3076699..b80e6714 100644 --- a/process/elasticproc/logsevents/logsAndEventsProcessor.go +++ b/process/elasticproc/logsevents/logsAndEventsProcessor.go @@ -69,7 +69,7 @@ func createEventsProcessors(args ArgsLogsAndEventsProcessor) []eventsProcessor { nftsProc := newNFTsProcessor(args.PubKeyConverter, args.Marshalizer) scDeploysProc := newSCDeploysProcessor(args.PubKeyConverter) informativeProc := newInformativeLogsProcessor() - updateNFTProc := newNFTsPropertiesProcessor(args.PubKeyConverter) + updateNFTProc := newNFTsPropertiesProcessor(args.PubKeyConverter, args.Marshalizer) esdtPropProc := newEsdtPropertiesProcessor(args.PubKeyConverter) esdtIssueProc := newESDTIssueProcessor(args.PubKeyConverter) delegatorsProcessor := newDelegatorsProcessor(args.PubKeyConverter, args.BalanceConverter) diff --git a/process/elasticproc/logsevents/nftsProcessor.go b/process/elasticproc/logsevents/nftsProcessor.go index 95716f15..4189f964 100644 --- a/process/elasticproc/logsevents/nftsProcessor.go +++ b/process/elasticproc/logsevents/nftsProcessor.go @@ -144,7 +144,7 @@ func (np *nftsProcessor) processNFTEventOnSender( return } - tokenMetaData := converters.PrepareTokenMetaData(np.convertMetaData(esdtToken.TokenMetaData)) + tokenMetaData := converters.PrepareTokenMetaData(convertMetaData(np.pubKeyConverter, esdtToken.TokenMetaData)) tokensCreateInfo.Add(&data.TokenInfo{ Token: token, Identifier: converters.ComputeTokenIdentifier(token, nonceBig.Uint64()), @@ -154,11 +154,11 @@ func (np *nftsProcessor) processNFTEventOnSender( }) } -func (np *nftsProcessor) convertMetaData(metaData *esdt.MetaData) *alteredAccount.TokenMetaData { +func convertMetaData(pubKeyConverter core.PubkeyConverter, metaData *esdt.MetaData) *alteredAccount.TokenMetaData { if metaData == nil { return nil } - encodedCreatorAddr, err := np.pubKeyConverter.Encode(metaData.Creator) + encodedCreatorAddr, err := pubKeyConverter.Encode(metaData.Creator) if err != nil { log.Warn("nftsProcessor.convertMetaData", "cannot encode creator address", "error", err, "address", metaData.Creator) } diff --git a/process/elasticproc/logsevents/nftsPropertiesProcessor.go b/process/elasticproc/logsevents/nftsPropertiesProcessor.go index 990e844e..8b40f891 100644 --- a/process/elasticproc/logsevents/nftsPropertiesProcessor.go +++ b/process/elasticproc/logsevents/nftsPropertiesProcessor.go @@ -4,6 +4,8 @@ import ( "math/big" "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-core-go/data/esdt" + "github.com/multiversx/mx-chain-core-go/marshal" "github.com/multiversx/mx-chain-es-indexer-go/data" "github.com/multiversx/mx-chain-es-indexer-go/process/elasticproc/converters" ) @@ -11,12 +13,14 @@ import ( const minTopicsUpdate = 4 type nftsPropertiesProc struct { + marshaller marshal.Marshalizer pubKeyConverter core.PubkeyConverter propertiesChangeOperations map[string]struct{} } -func newNFTsPropertiesProcessor(pubKeyConverter core.PubkeyConverter) *nftsPropertiesProc { +func newNFTsPropertiesProcessor(pubKeyConverter core.PubkeyConverter, marshaller marshal.Marshalizer) *nftsPropertiesProc { return &nftsPropertiesProc{ + marshaller: marshaller, pubKeyConverter: pubKeyConverter, propertiesChangeOperations: map[string]struct{}{ core.BuiltInFunctionESDTNFTAddURI: {}, @@ -25,6 +29,11 @@ func newNFTsPropertiesProcessor(pubKeyConverter core.PubkeyConverter) *nftsPrope core.BuiltInFunctionESDTUnFreeze: {}, core.BuiltInFunctionESDTPause: {}, core.BuiltInFunctionESDTUnPause: {}, + core.ESDTMetaDataRecreate: {}, + core.ESDTMetaDataUpdate: {}, + core.ESDTSetNewURIs: {}, + core.ESDTModifyCreator: {}, + core.ESDTModifyRoyalties: {}, }, } } @@ -54,7 +63,10 @@ func (npp *nftsPropertiesProc) processEvent(args *argsProcessEvent) argOutputPro // [1] --> nonce of the NFT (bytes) // [2] --> value // [3:] --> modified data - if len(topics) < minTopicsUpdate { + // [3] --> ESDT token data in case of ESDTMetaDataRecreate + + isModifyCreator := len(topics) == minTopicsUpdate-1 && eventIdentifier == core.ESDTModifyCreator + if len(topics) < minTopicsUpdate && !isModifyCreator { return argOutputProcessEvent{ processed: true, } @@ -86,10 +98,23 @@ func (npp *nftsPropertiesProc) processEvent(args *argsProcessEvent) argOutputPro updateNFT.NewAttributes = topics[3] case core.BuiltInFunctionESDTNFTAddURI: updateNFT.URIsToAdd = topics[3:] + case core.ESDTSetNewURIs: + updateNFT.SetURIs = true + updateNFT.URIsToAdd = topics[3:] case core.BuiltInFunctionESDTFreeze: updateNFT.Freeze = true case core.BuiltInFunctionESDTUnFreeze: updateNFT.UnFreeze = true + case core.ESDTMetaDataRecreate, core.ESDTMetaDataUpdate: + npp.processMetaDataUpdate(updateNFT, topics[3]) + case core.ESDTModifyCreator: + updateNFT.NewCreator = callerAddress + case core.ESDTModifyRoyalties: + newRoyalties := uint32(big.NewInt(0).SetBytes(topics[3]).Uint64()) + updateNFT.NewRoyalties = core.OptionalUint32{ + Value: newRoyalties, + HasValue: true, + } } return argOutputProcessEvent{ @@ -98,6 +123,18 @@ func (npp *nftsPropertiesProc) processEvent(args *argsProcessEvent) argOutputPro } } +func (npp *nftsPropertiesProc) processMetaDataUpdate(updateNFT *data.NFTDataUpdate, esdtTokenBytes []byte) { + esdtToken := &esdt.ESDigitalToken{} + err := npp.marshaller.Unmarshal(esdtToken, esdtTokenBytes) + if err != nil { + log.Warn("nftsPropertiesProc.processMetaDataRecreate() cannot urmarshal", "error", err.Error()) + return + } + + tokenMetaData := converters.PrepareTokenMetaData(convertMetaData(npp.pubKeyConverter, esdtToken.TokenMetaData)) + updateNFT.NewMetaData = tokenMetaData +} + func (npp *nftsPropertiesProc) processPauseAndUnPauseEvent(eventIdentifier string, token string) argOutputProcessEvent { var updateNFT *data.NFTDataUpdate diff --git a/process/elasticproc/logsevents/nftsPropertiesProcessor_test.go b/process/elasticproc/logsevents/nftsPropertiesProcessor_test.go index 32f039c6..7ee39dda 100644 --- a/process/elasticproc/logsevents/nftsPropertiesProcessor_test.go +++ b/process/elasticproc/logsevents/nftsPropertiesProcessor_test.go @@ -1,10 +1,13 @@ package logsevents import ( + "encoding/hex" + "encoding/json" "math/big" "testing" "github.com/multiversx/mx-chain-core-go/core" + "github.com/multiversx/mx-chain-core-go/data/esdt" "github.com/multiversx/mx-chain-core-go/data/transaction" "github.com/multiversx/mx-chain-es-indexer-go/data" "github.com/multiversx/mx-chain-es-indexer-go/mock" @@ -24,7 +27,7 @@ func TestProcessNFTProperties_Update(t *testing.T) { event: event, } - nftsPropertiesP := newNFTsPropertiesProcessor(&mock.PubkeyConverterMock{}) + nftsPropertiesP := newNFTsPropertiesProcessor(&mock.PubkeyConverterMock{}, &mock.MarshalizerMock{}) res := nftsPropertiesP.processEvent(args) require.True(t, res.processed) @@ -48,7 +51,7 @@ func TestProcessNFTProperties_AddUris(t *testing.T) { event: event, } - nftsPropertiesP := newNFTsPropertiesProcessor(&mock.PubkeyConverterMock{}) + nftsPropertiesP := newNFTsPropertiesProcessor(&mock.PubkeyConverterMock{}, &mock.MarshalizerMock{}) res := nftsPropertiesP.processEvent(args) require.True(t, res.processed) @@ -59,6 +62,33 @@ func TestProcessNFTProperties_AddUris(t *testing.T) { }, res.updatePropNFT) } +func TestProcessNFTMetaDataRecreate(t *testing.T) { + nftsPropertiesP := newNFTsPropertiesProcessor(&mock.PubkeyConverterMock{}, &mock.MarshalizerMock{}) + + esdtData := &esdt.ESDigitalToken{ + TokenMetaData: &esdt.MetaData{ + Creator: []byte("creator"), + }, + } + esdtDataBytes, _ := json.Marshal(esdtData) + + nonce := uint64(19) + event := &transaction.Event{ + Address: []byte("addr"), + Identifier: []byte(core.ESDTMetaDataRecreate), + Topics: [][]byte{[]byte("my-token"), big.NewInt(0).SetUint64(nonce).Bytes(), big.NewInt(1).Bytes(), esdtDataBytes}, + } + args := &argsProcessEvent{ + timestamp: 1234, + event: event, + } + + res := nftsPropertiesP.processEvent(args) + require.True(t, res.processed) + require.NotNil(t, res.updatePropNFT.NewMetaData) + require.Equal(t, hex.EncodeToString([]byte("creator")), res.updatePropNFT.NewMetaData.Creator) +} + func TestProcessNFTProperties_FreezeAndUnFreeze(t *testing.T) { t.Parallel() @@ -73,7 +103,7 @@ func TestProcessNFTProperties_FreezeAndUnFreeze(t *testing.T) { event: event, } - nftsPropertiesP := newNFTsPropertiesProcessor(&mock.PubkeyConverterMock{}) + nftsPropertiesP := newNFTsPropertiesProcessor(&mock.PubkeyConverterMock{}, &mock.MarshalizerMock{}) res := nftsPropertiesP.processEvent(args) require.True(t, res.processed) diff --git a/process/elasticproc/logsevents/serialize.go b/process/elasticproc/logsevents/serialize.go index a96a125a..22508c3c 100644 --- a/process/elasticproc/logsevents/serialize.go +++ b/process/elasticproc/logsevents/serialize.go @@ -202,6 +202,9 @@ func serializeToken(tokenData *data.TokenInfo, index string) ([]byte, []byte, er if tokenData.TransferOwnership { return serializeTokenTransferOwnership(tokenData, index) } + if tokenData.ChangeToDynamic { + return serializeTokenChangeType(tokenData, index) + } meta := []byte(fmt.Sprintf(`{ "update" : { "_index":"%s", "_id" : "%s" } }%s`, index, converters.JsonEscape(tokenData.Token), "\n")) serializedTokenData, err := json.Marshal(tokenData) @@ -226,6 +229,23 @@ func serializeToken(tokenData *data.TokenInfo, index string) ([]byte, []byte, er return meta, []byte(serializedDataStr), nil } +func serializeTokenChangeType(tokenData *data.TokenInfo, index string) ([]byte, []byte, error) { + meta := []byte(fmt.Sprintf(`{ "update" : { "_index":"%s", "_id" : "%s" } }%s`, index, converters.JsonEscape(tokenData.Token), "\n")) + + codeToExecute := ` + ctx._source.type = params.type; + ctx._source.changedToDynamicTimestamp = params.timestamp; +` + serializedDataStr := fmt.Sprintf(`{"script": {`+ + `"source": "%s",`+ + `"lang": "painless",`+ + `"params": {"type": "%s", "timestamp": %d }},`+ + `"upsert": {}}`, + converters.FormatPainlessSource(codeToExecute), tokenData.Type, tokenData.Timestamp) + + return meta, []byte(serializedDataStr), nil +} + func serializeTokenTransferOwnership(tokenData *data.TokenInfo, index string) ([]byte, []byte, error) { meta := []byte(fmt.Sprintf(`{ "update" : { "_index":"%s", "_id" : "%s" } }%s`, index, converters.JsonEscape(tokenData.Token), "\n")) tokenDataSerialized, err := json.Marshal(tokenData) diff --git a/process/elasticproc/templatesAndPolicies/interface.go b/process/elasticproc/templatesAndPolicies/interface.go index f4b740c9..2215c781 100644 --- a/process/elasticproc/templatesAndPolicies/interface.go +++ b/process/elasticproc/templatesAndPolicies/interface.go @@ -1,8 +1,13 @@ package templatesAndPolicies -import "bytes" +import ( + "bytes" + + "github.com/multiversx/mx-chain-es-indexer-go/templates" +) // TemplatesAndPoliciesHandler defines the actions that a templates and policies handler should do type TemplatesAndPoliciesHandler interface { GetElasticTemplatesAndPolicies() (map[string]*bytes.Buffer, map[string]*bytes.Buffer, error) + GetExtraMappings() ([]templates.ExtraMapping, error) } diff --git a/process/elasticproc/templatesAndPolicies/noKibana.go b/process/elasticproc/templatesAndPolicies/noKibana.go index a6928858..d87acaba 100644 --- a/process/elasticproc/templatesAndPolicies/noKibana.go +++ b/process/elasticproc/templatesAndPolicies/noKibana.go @@ -4,6 +4,7 @@ import ( "bytes" indexer "github.com/multiversx/mx-chain-es-indexer-go/process/dataindexer" + "github.com/multiversx/mx-chain-es-indexer-go/templates" "github.com/multiversx/mx-chain-es-indexer-go/templates/noKibana" ) @@ -45,3 +46,20 @@ func (tr *templatesAndPolicyReaderNoKibana) GetElasticTemplatesAndPolicies() (ma return indexTemplates, indexPolicies, nil } + +// GetExtraMappings will return an array of indices extra mappings +func (tr *templatesAndPolicyReaderNoKibana) GetExtraMappings() ([]templates.ExtraMapping, error) { + transactionsExtraMappings := templates.ExtraMapping{ + Index: indexer.TransactionsIndex, + Mappings: noKibana.InnerTxs.ToBuffer(), + } + operationsExtraMappings := templates.ExtraMapping{ + Index: indexer.OperationsIndex, + Mappings: noKibana.InnerTxs.ToBuffer(), + } + + return []templates.ExtraMapping{ + transactionsExtraMappings, + operationsExtraMappings, + }, nil +} diff --git a/process/elasticproc/templatesAndPolicies/withKibana.go b/process/elasticproc/templatesAndPolicies/withKibana.go index 2fe1cd38..7ffe6b2f 100644 --- a/process/elasticproc/templatesAndPolicies/withKibana.go +++ b/process/elasticproc/templatesAndPolicies/withKibana.go @@ -4,6 +4,7 @@ import ( "bytes" indexer "github.com/multiversx/mx-chain-es-indexer-go/process/dataindexer" + "github.com/multiversx/mx-chain-es-indexer-go/templates" "github.com/multiversx/mx-chain-es-indexer-go/templates/withKibana" ) @@ -22,6 +23,11 @@ func (tr *templatesAndPolicyReaderWithKibana) GetElasticTemplatesAndPolicies() ( return indexTemplates, indexPolicies, nil } +// GetExtraMappings will return an array of indices extra mappings +func (tr *templatesAndPolicyReaderWithKibana) GetExtraMappings() ([]templates.ExtraMapping, error) { + return []templates.ExtraMapping{}, nil +} + func getTemplatesKibana() map[string]*bytes.Buffer { indexTemplates := make(map[string]*bytes.Buffer) diff --git a/process/elasticproc/transactions/serialize.go b/process/elasticproc/transactions/serialize.go index bb5db537..939742a0 100644 --- a/process/elasticproc/transactions/serialize.go +++ b/process/elasticproc/transactions/serialize.go @@ -136,6 +136,10 @@ func serializeTxHashStatus(buffSlice *data.BufferSlice, txHashStatusInfo map[str if (params.statusInfo.errorEvent) { ctx._source.errorEvent = params.statusInfo.errorEvent; } + + if ((ctx._source.completedEvent != null && ctx._source.completedEvent) && (ctx._source.errorEvent != null && ctx._source.errorEvent)) { + ctx._source.status = 'success'; + } ` serializedData := []byte(fmt.Sprintf(`{"script": {"source": "%s","lang": "painless","params": {"statusInfo": %s}}, "upsert": %s }`, converters.FormatPainlessSource(codeToExecute), string(marshaledStatusInfo), string(marshaledTx))) err = buffSlice.PutData(metaData, serializedData) @@ -159,10 +163,23 @@ func prepareSerializedDataForATransaction( } if isCrossShardOnSourceShard(tx, selfShardID) { - // if transaction is cross-shard and current shard ID is source, use upsert without updating anything - serializedData := - []byte(fmt.Sprintf(`{"script":{"source":"return"},"upsert":%s}`, - string(marshaledTx))) + if isSimpleESDTTransfer(tx) { + codeToExecute := ` + if ('create' == ctx.op) { + ctx._source = params.tx; + } else { + ctx._source.gasUsed = params.tx.gasUsed; + ctx._source.fee = params.tx.fee; + ctx._source.feeNum = params.tx.feeNum; + } + ` + serializedData := []byte(fmt.Sprintf(`{"scripted_upsert": true, "script":{"source":"%s","lang": "painless","params":{"tx": %s}},"upsert":{}}`, + converters.FormatPainlessSource(codeToExecute), string(marshaledTx))) + + return metaData, serializedData, nil + } + + serializedData := []byte(fmt.Sprintf(`{"script":{"source":"return"},"upsert":%s}`, string(marshaledTx))) return metaData, serializedData, nil } @@ -176,12 +193,34 @@ func prepareSerializedDataForATransaction( return metaData, serializedData, nil } + if isSimpleESDTTransferCrossShardOnDestination(tx, selfShardID) { + return metaData, prepareSerializedDataForESDTTransferOnDestination(marshaledTx), nil + } + // transaction is intra-shard, invalid or cross-shard destination me meta := []byte(fmt.Sprintf(`{ "index" : { "_index":"%s", "_id" : "%s" } }%s`, index, converters.JsonEscape(tx.Hash), "\n")) return meta, marshaledTx, nil } +func prepareSerializedDataForESDTTransferOnDestination(marshaledTx []byte) []byte { + codeToExecute := ` + if ('create' == ctx.op) { + ctx._source = params.tx; + } else { + def gasUsed = ctx._source.gasUsed; + def fee = ctx._source.fee; + def feeNum = ctx._source.feeNum; + ctx._source = params.tx; + ctx._source.gasUsed = gasUsed; + ctx._source.fee = fee; + ctx._source.feeNum = feeNum; + } +` + return []byte(fmt.Sprintf(`{"scripted_upsert": true, "script":{"source":"%s","lang": "painless","params":{"tx": %s}},"upsert":{}}`, + converters.FormatPainlessSource(codeToExecute), string(marshaledTx))) +} + func prepareNFTESDTTransferOrMultiESDTTransfer(marshaledTx []byte) ([]byte, error) { codeToExecute := ` if ('create' == ctx.op) { @@ -221,3 +260,13 @@ func isNFTTransferOrMultiTransfer(tx *data.Transaction) bool { return splitData[0] == core.BuiltInFunctionESDTNFTTransfer || splitData[0] == core.BuiltInFunctionMultiESDTNFTTransfer } + +func isSimpleESDTTransferCrossShardOnDestination(tx *data.Transaction, selfShard uint32) bool { + isCrossOnDestination := tx.SenderShard != tx.ReceiverShard && tx.ReceiverShard == selfShard + + return isSimpleESDTTransfer(tx) && isCrossOnDestination +} + +func isSimpleESDTTransfer(tx *data.Transaction) bool { + return tx.Operation == core.BuiltInFunctionESDTTransfer && tx.Function == "" +} diff --git a/process/elasticproc/transactions/transactionDBBuilder.go b/process/elasticproc/transactions/transactionDBBuilder.go index 0b497ef7..f22e1a31 100644 --- a/process/elasticproc/transactions/transactionDBBuilder.go +++ b/process/elasticproc/transactions/transactionDBBuilder.go @@ -11,6 +11,7 @@ import ( "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/receipt" + "github.com/multiversx/mx-chain-core-go/data/transaction" "github.com/multiversx/mx-chain-es-indexer-go/data" "github.com/multiversx/mx-chain-es-indexer-go/process/dataindexer" "github.com/multiversx/mx-chain-es-indexer-go/process/elasticproc/converters" @@ -86,7 +87,8 @@ func (dtb *dbTransactionBuilder) prepareTransaction( senderUserName := converters.TruncateFieldIfExceedsMaxLengthBase64(string(tx.SndUserName)) receiverUserName := converters.TruncateFieldIfExceedsMaxLengthBase64(string(tx.RcvUserName)) - return &data.Transaction{ + + eTx := &data.Transaction{ Hash: hex.EncodeToString(txHash), MBHash: hex.EncodeToString(mbHash), Nonce: tx.Nonce, @@ -110,19 +112,75 @@ func (dtb *dbTransactionBuilder) prepareTransaction( ReceiverUserName: []byte(receiverUserName), SenderUserName: []byte(senderUserName), IsScCall: isScCall, - Operation: res.Operation, - Function: converters.TruncateFieldIfExceedsMaxLength(res.Function), ESDTValues: esdtValues, ESDTValuesNum: esdtValuesNum, - Tokens: converters.TruncateSliceElementsIfExceedsMaxLength(res.Tokens), Receivers: receiversAddr, - ReceiversShardIDs: res.ReceiversShardID, - IsRelayed: res.IsRelayed, Version: tx.Version, GuardianAddress: guardianAddress, GuardianSignature: hex.EncodeToString(tx.GuardianSignature), ExecutionOrder: int(txInfo.ExecutionOrder), + Operation: res.Operation, } + + isRelayedV3 := len(tx.InnerTransactions) > 0 + if isRelayedV3 { + dtb.addRelayedV3InfoInIndexerTx(tx, eTx, numOfShards) + + return eTx + } + + eTx.Function = converters.TruncateFieldIfExceedsMaxLength(res.Function) + eTx.Tokens = converters.TruncateSliceElementsIfExceedsMaxLength(res.Tokens) + eTx.ReceiversShardIDs = res.ReceiversShardID + eTx.IsRelayed = res.IsRelayed + + return eTx +} + +func (dtb *dbTransactionBuilder) addRelayedV3InfoInIndexerTx(tx *transaction.Transaction, indexerTx *data.Transaction, numOfShards uint32) { + if len(tx.InnerTransactions) == 0 { + return + } + + innerTxs := make([]*transaction.FrontendTransaction, 0, len(tx.InnerTransactions)) + receivers := make([]string, 0, len(tx.InnerTransactions)) + receiversShardIDs := make([]uint32, 0, len(tx.InnerTransactions)) + for _, innerTx := range tx.InnerTransactions { + frontEndTx := &transaction.FrontendTransaction{ + Nonce: innerTx.Nonce, + Value: innerTx.Value.String(), + Receiver: dtb.addressPubkeyConverter.SilentEncode(innerTx.RcvAddr, log), + Sender: dtb.addressPubkeyConverter.SilentEncode(innerTx.SndAddr, log), + SenderUsername: innerTx.SndUserName, + ReceiverUsername: innerTx.RcvUserName, + GasPrice: innerTx.GasPrice, + GasLimit: innerTx.GasLimit, + Data: innerTx.Data, + Signature: hex.EncodeToString(innerTx.Signature), + ChainID: string(innerTx.ChainID), + Version: innerTx.Version, + Options: innerTx.Options, + } + + if len(innerTx.GuardianAddr) > 0 { + frontEndTx.GuardianAddr = dtb.addressPubkeyConverter.SilentEncode(innerTx.GuardianAddr, log) + frontEndTx.GuardianSignature = hex.EncodeToString(innerTx.GuardianSignature) + } + + if len(innerTx.RelayerAddr) > 0 { + frontEndTx.Relayer = dtb.addressPubkeyConverter.SilentEncode(innerTx.RelayerAddr, log) + } + + receivers = append(receivers, frontEndTx.Receiver) + receiversShardIDs = append(receiversShardIDs, sharding.ComputeShardID(innerTx.RcvAddr, numOfShards)) + + innerTxs = append(innerTxs, frontEndTx) + } + + indexerTx.InnerTransactions = innerTxs + indexerTx.IsRelayed = true + indexerTx.Receivers = receivers + indexerTx.ReceiversShardIDs = receiversShardIDs } func (dtb *dbTransactionBuilder) prepareRewardTransaction( diff --git a/process/elasticproc/transactions/transactionDBBuilder_test.go b/process/elasticproc/transactions/transactionDBBuilder_test.go index ceeca3de..b892166e 100644 --- a/process/elasticproc/transactions/transactionDBBuilder_test.go +++ b/process/elasticproc/transactions/transactionDBBuilder_test.go @@ -204,3 +204,130 @@ func TestGetMoveBalanceTransactionInvalid(t *testing.T) { dbTx := cp.prepareTransaction(txInfo, txHash, mbHash, mb, header, status, 3) require.Equal(t, expectedTx, dbTx) } + +func TestTxsDatabaseProcessor_PrepareTransactionRelayedV3(t *testing.T) { + t.Parallel() + + txHash := []byte("txHash") + mbHash := []byte("mbHash") + mb := &block.MiniBlock{TxHashes: [][]byte{txHash}} + header := &block.Header{Nonce: 2} + status := "Success" + gasPrice := uint64(1000) + gasLimit := uint64(1000) + cp := createCommonProcessor() + + tx := &transaction.Transaction{ + Nonce: 1, + Value: big.NewInt(1000), + RcvAddr: []byte("receiver"), + SndAddr: []byte("sender"), + GasPrice: gasPrice, + GasLimit: gasLimit, + ChainID: []byte("1"), + Version: 1, + Signature: []byte("signature"), + RcvUserName: []byte("rcv"), + SndUserName: []byte("snd"), + InnerTransactions: []*transaction.Transaction{ + { + Nonce: 2, + Value: big.NewInt(1000), + SndAddr: []byte("inner1"), + RcvAddr: []byte("rcv1"), + Data: []byte("data1"), + Signature: []byte("signature1"), + GasPrice: gasPrice, + GasLimit: gasLimit, + ChainID: []byte("1"), + Version: 1, + }, + { + Nonce: 3, + Value: big.NewInt(1), + SndAddr: []byte("inner2"), + RcvAddr: []byte("rcv2"), + Data: []byte("data2"), + Signature: []byte("signature2"), + GasPrice: gasPrice, + GasLimit: gasLimit, + ChainID: []byte("1"), + Version: 1, + }, + }, + } + + txInfo := &outport.TxInfo{ + Transaction: tx, + FeeInfo: &outport.FeeInfo{ + GasUsed: 500, + Fee: big.NewInt(100), + InitialPaidFee: big.NewInt(100), + }, + ExecutionOrder: 0, + } + + senderAddr, err := cp.addressPubkeyConverter.Encode(tx.RcvAddr) + require.Nil(t, err) + receiverAddr, err := cp.addressPubkeyConverter.Encode(tx.SndAddr) + require.Nil(t, err) + + expectedTx := &data.Transaction{ + Hash: hex.EncodeToString(txHash), + MBHash: hex.EncodeToString(mbHash), + Nonce: tx.Nonce, + Round: header.Round, + Value: tx.Value.String(), + ValueNum: 1e-15, + Receiver: senderAddr, + Sender: receiverAddr, + ReceiverShard: mb.ReceiverShardID, + SenderShard: mb.SenderShardID, + GasPrice: gasPrice, + GasLimit: gasLimit, + GasUsed: uint64(500), + InitialPaidFee: "100", + Signature: hex.EncodeToString(tx.Signature), + Timestamp: time.Duration(header.GetTimeStamp()), + Status: status, + Fee: "100", + FeeNum: 1e-16, + ReceiverUserName: []byte("rcv"), + SenderUserName: []byte("snd"), + ESDTValuesNum: []float64{}, + Operation: "transfer", + Version: 1, + Receivers: []string{"72637631", "72637632"}, + ReceiversShardIDs: []uint32{1, 2}, + InnerTransactions: []*transaction.FrontendTransaction{ + { + Nonce: 2, + Value: "1000", + Sender: "696e6e657231", + Receiver: "72637631", + GasPrice: gasPrice, + GasLimit: gasLimit, + Signature: "7369676e617475726531", + Data: []byte("data1"), + Version: 1, + ChainID: "1", + }, + { + Nonce: 3, + Value: "1", + Sender: "696e6e657232", + Receiver: "72637632", + GasPrice: gasPrice, + GasLimit: gasLimit, + Signature: "7369676e617475726532", + Data: []byte("data2"), + Version: 1, + ChainID: "1", + }, + }, + IsRelayed: true, + } + + dbTx := cp.prepareTransaction(txInfo, txHash, mbHash, mb, header, status, 3) + require.Equal(t, expectedTx, dbTx) +} diff --git a/templates/noKibana/esdts.go b/templates/noKibana/esdts.go index 072d215a..75d0bfa5 100644 --- a/templates/noKibana/esdts.go +++ b/templates/noKibana/esdts.go @@ -36,6 +36,10 @@ var ESDTs = Object{ "type": "date", "format": "epoch_second", }, + "changedToDynamicTimestamp": Object{ + "type": "date", + "format": "epoch_second", + }, "ownersHistory": Object{ "type": "nested", "properties": Object{ diff --git a/templates/noKibana/innerTx.go b/templates/noKibana/innerTx.go new file mode 100644 index 00000000..c97f5cbe --- /dev/null +++ b/templates/noKibana/innerTx.go @@ -0,0 +1,63 @@ +package noKibana + +// InnerTxs will hold the configuration for the inner transaction structure +var InnerTxs = Object{ + "properties": Object{ + "innerTransactions": Object{ + "properties": Object{ + "nonce": Object{ + "type": "double", + }, + "value": Object{ + "type": "keyword", + }, + "sender": Object{ + "type": "keyword", + }, + "receiver": Object{ + "type": "keyword", + }, + "senderUserName": Object{ + "type": "keyword", + }, + "receiverUsername": Object{ + "type": "keyword", + }, + "gasLimit": Object{ + "index": "false", + "type": "double", + }, + "gasPrice": Object{ + "index": "false", + "type": "double", + }, + "data": Object{ + "type": "text", + }, + "signature": Object{ + "index": "false", + "type": "keyword", + }, + "chainID": Object{ + "type": "keyword", + }, + "version": Object{ + "type": "long", + }, + "options": Object{ + "type": "long", + }, + "guardian": Object{ + "type": "keyword", + }, + "guardianSignature": Object{ + "index": "false", + "type": "keyword", + }, + "relayer": Object{ + "type": "keyword", + }, + }, + }, + }, +} diff --git a/templates/noKibana/operations.go b/templates/noKibana/operations.go index 8c734d9d..9834f19b 100644 --- a/templates/noKibana/operations.go +++ b/templates/noKibana/operations.go @@ -169,6 +169,62 @@ var Operations = Object{ "index": "false", "type": "keyword", }, + "innerTransactions": Object{ + "properties": Object{ + "nonce": Object{ + "type": "double", + }, + "value": Object{ + "type": "keyword", + }, + "sender": Object{ + "type": "keyword", + }, + "receiver": Object{ + "type": "keyword", + }, + "senderUserName": Object{ + "type": "keyword", + }, + "receiverUsername": Object{ + "type": "keyword", + }, + "gasLimit": Object{ + "index": "false", + "type": "double", + }, + "gasPrice": Object{ + "index": "false", + "type": "double", + }, + "data": Object{ + "type": "text", + }, + "signature": Object{ + "index": "false", + "type": "keyword", + }, + "chainID": Object{ + "type": "keyword", + }, + "version": Object{ + "type": "long", + }, + "options": Object{ + "type": "long", + }, + "guardian": Object{ + "type": "keyword", + }, + "guardianSignature": Object{ + "index": "false", + "type": "keyword", + }, + "relayer": Object{ + "type": "keyword", + }, + }, + }, }, }, } diff --git a/templates/noKibana/tokens.go b/templates/noKibana/tokens.go index 6d4e8240..cc8e6d07 100644 --- a/templates/noKibana/tokens.go +++ b/templates/noKibana/tokens.go @@ -161,6 +161,10 @@ var Tokens = Object{ "type": "date", "format": "epoch_second", }, + "changedToDynamicTimestamp": Object{ + "type": "date", + "format": "epoch_second", + }, "token": Object{ "type": "keyword", }, diff --git a/templates/noKibana/transactions.go b/templates/noKibana/transactions.go index 91ebea75..16ddef08 100644 --- a/templates/noKibana/transactions.go +++ b/templates/noKibana/transactions.go @@ -135,6 +135,62 @@ var Transactions = Object{ "index": "false", "type": "keyword", }, + "innerTransactions": Object{ + "properties": Object{ + "nonce": Object{ + "type": "double", + }, + "value": Object{ + "type": "keyword", + }, + "sender": Object{ + "type": "keyword", + }, + "receiver": Object{ + "type": "keyword", + }, + "senderUserName": Object{ + "type": "keyword", + }, + "receiverUsername": Object{ + "type": "keyword", + }, + "gasLimit": Object{ + "index": "false", + "type": "double", + }, + "gasPrice": Object{ + "index": "false", + "type": "double", + }, + "data": Object{ + "type": "text", + }, + "signature": Object{ + "index": "false", + "type": "keyword", + }, + "chainID": Object{ + "type": "keyword", + }, + "version": Object{ + "type": "long", + }, + "options": Object{ + "type": "long", + }, + "guardian": Object{ + "type": "keyword", + }, + "guardianSignature": Object{ + "index": "false", + "type": "keyword", + }, + "relayer": Object{ + "type": "keyword", + }, + }, + }, }, }, } diff --git a/templates/types.go b/templates/types.go index 74371fb5..3d776138 100644 --- a/templates/types.go +++ b/templates/types.go @@ -5,6 +5,12 @@ import ( "encoding/json" ) +// ExtraMapping holds the tuple for the index and extra mappings +type ExtraMapping struct { + Index string + Mappings *bytes.Buffer +} + // Array type will rename type []interface{} type Array []interface{}