diff --git a/go.mod b/go.mod index 7ae8b60..a40460d 100644 --- a/go.mod +++ b/go.mod @@ -6,10 +6,10 @@ require ( github.com/coinbase/rosetta-sdk-go v0.8.3 github.com/coinbase/rosetta-sdk-go/types v1.0.0 github.com/gin-gonic/gin v1.10.0 - github.com/multiversx/mx-chain-core-go v1.4.0 - github.com/multiversx/mx-chain-go v1.10.1 + github.com/multiversx/mx-chain-core-go v1.4.2-0.20260219122727-014ae9f9311f + github.com/multiversx/mx-chain-go v1.11.1 github.com/multiversx/mx-chain-logger-go v1.1.0 - github.com/multiversx/mx-chain-proxy-go v1.3.0 + github.com/multiversx/mx-chain-proxy-go v1.4.0 github.com/multiversx/mx-chain-storage-go v1.1.0 github.com/stretchr/testify v1.10.0 github.com/urfave/cli v1.22.16 @@ -44,7 +44,7 @@ require ( github.com/mr-tron/base58 v1.2.0 // indirect github.com/multiversx/mx-chain-communication-go v1.3.0 // indirect github.com/multiversx/mx-chain-crypto-go v1.3.0 // indirect - github.com/multiversx/mx-chain-es-indexer-go v1.9.1 // indirect + github.com/multiversx/mx-chain-es-indexer-go v1.9.3-0.20260112102658-97d6a0ceb5f6 // indirect github.com/multiversx/mx-chain-vm-common-go v1.6.0 // indirect github.com/pelletier/go-toml v1.9.3 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect diff --git a/go.sum b/go.sum index 904969b..118c005 100644 --- a/go.sum +++ b/go.sum @@ -47,8 +47,8 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/gabriel-vasile/mimetype v1.4.6 h1:3+PzJTKLkvgjeTbts6msPJt4DixhT4YtFNf1gtGe3zc= github.com/gabriel-vasile/mimetype v1.4.6/go.mod h1:JX1qVKqZd40hUPpAfiNTe0Sne7hdfKSbOqqmkq8GCXc= -github.com/gin-contrib/cors v1.4.0 h1:oJ6gwtUl3lqV0WEIwM/LxPF1QZ5qe2lGWdY2+bz7y0g= -github.com/gin-contrib/cors v1.4.0/go.mod h1:bs9pNM0x/UsmHPBWT2xZz9ROh8xYjYkiURUfmBoMlcs= +github.com/gin-contrib/cors v1.6.0 h1:0Z7D/bVhE6ja07lI8CTjTonp6SB07o8bNuFyRbsBUQg= +github.com/gin-contrib/cors v1.6.0/go.mod h1:cI+h6iOAyxKRtUtC6iF/Si1KSFvGm/gK+kshxlCi8ro= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= @@ -121,18 +121,18 @@ github.com/multiversx/concurrent-map v0.1.4 h1:hdnbM8VE4b0KYJaGY5yJS2aNIW9TFFsUY github.com/multiversx/concurrent-map v0.1.4/go.mod h1:8cWFRJDOrWHOTNSqgYCUvwT7c7eFQ4U2vKMOp4A/9+o= github.com/multiversx/mx-chain-communication-go v1.3.0 h1:ziNM1dRuiR/7al2L/jGEA/a/hjurtJ/HEqgazHNt9P8= github.com/multiversx/mx-chain-communication-go v1.3.0/go.mod h1:gDVWn6zUW6aCN1YOm/FbbT5MUmhgn/L1Rmpl8EoH3Yg= -github.com/multiversx/mx-chain-core-go v1.4.0 h1:p6FbfCzvMXF54kpS0B5mrjNWYpq4SEQqo0UvrMF7YVY= -github.com/multiversx/mx-chain-core-go v1.4.0/go.mod h1:IO+vspNan+gT0WOHnJ95uvWygiziHZvfXpff6KnxV7g= +github.com/multiversx/mx-chain-core-go v1.4.2-0.20260219122727-014ae9f9311f h1:kngckbX3TbZpU0LQpetUM8xNvUu/FtmZQtM66yoGcz0= +github.com/multiversx/mx-chain-core-go v1.4.2-0.20260219122727-014ae9f9311f/go.mod h1:IO+vspNan+gT0WOHnJ95uvWygiziHZvfXpff6KnxV7g= github.com/multiversx/mx-chain-crypto-go v1.3.0 h1:0eK2bkDOMi8VbSPrB1/vGJSYT81IBtfL4zw+C4sWe/k= github.com/multiversx/mx-chain-crypto-go v1.3.0/go.mod h1:nPIkxxzyTP8IquWKds+22Q2OJ9W7LtusC7cAosz7ojM= -github.com/multiversx/mx-chain-es-indexer-go v1.9.1 h1:Jg/4CLzIiwyrjuy+ZccEJ4TcvlHXnBUr5o3pclVitGo= -github.com/multiversx/mx-chain-es-indexer-go v1.9.1/go.mod h1:t1rkD2vHXSI4EClig0h7+kRCSUCRrMF+emr4DHxFtfA= -github.com/multiversx/mx-chain-go v1.10.1 h1:sRx7Ronn9SNB2oBMXJSl8kaJqm5iuluuGKNOec1h+XM= -github.com/multiversx/mx-chain-go v1.10.1/go.mod h1:DavxpLJtHRLLLKU1xfrU1bAhgZD5paptiGS5KfX4Lco= +github.com/multiversx/mx-chain-es-indexer-go v1.9.3-0.20260112102658-97d6a0ceb5f6 h1:ywYAthnCkytgSGfMBTBvojlBJh9o5zUmottZwaVYTd8= +github.com/multiversx/mx-chain-es-indexer-go v1.9.3-0.20260112102658-97d6a0ceb5f6/go.mod h1:F/BpaYVPuHN7POJN6gwvJfZ22diYtvz2576a+PWiPvw= +github.com/multiversx/mx-chain-go v1.11.1 h1:PC2cP10LEmfL18yMyfsMu0usTxa/ttXNQyi3BIVsuwM= +github.com/multiversx/mx-chain-go v1.11.1/go.mod h1:7c9Qvi3lvE0wuHP/xTxbjs1GvTO/7W4NGmzT9IDsJCM= github.com/multiversx/mx-chain-logger-go v1.1.0 h1:97x84A6L4RfCa6YOx1HpAFxZp1cf/WI0Qh112whgZNM= github.com/multiversx/mx-chain-logger-go v1.1.0/go.mod h1:K9XgiohLwOsNACETMNL0LItJMREuEvTH6NsoXWXWg7g= -github.com/multiversx/mx-chain-proxy-go v1.3.0 h1:sEfgO8qEKnUotCnX5EPTb+XC+VNiJcbzvZaY9sLrSXg= -github.com/multiversx/mx-chain-proxy-go v1.3.0/go.mod h1:hdCT241dL+3d4t5+g/OUeAfZ8H/Y72SbY7SNF7vp0us= +github.com/multiversx/mx-chain-proxy-go v1.4.0 h1:fwGWr3q2j3oMqYSfupVuAIPyuOgGSDP/aAA2Q8MU4T0= +github.com/multiversx/mx-chain-proxy-go v1.4.0/go.mod h1:kizqVThJggPV0cD29qeOCFhLwnrbuYQ7saGg+GzEOog= github.com/multiversx/mx-chain-storage-go v1.1.0 h1:M1Y9DqMrJ62s7Zw31+cyuqsnPIvlG4jLBJl5WzeZLe8= github.com/multiversx/mx-chain-storage-go v1.1.0/go.mod h1:o6Jm7cjfPmcc6XpyihYWrd6sx3sgqwurrunw3ZrfyxI= github.com/multiversx/mx-chain-vm-common-go v1.6.0 h1:M2zmf/ptEINciWxYCPLIkwOMTvvzWjELYYB+0MMQ5Gw= diff --git a/server/factory/provider.go b/server/factory/provider.go index 3ba434b..9388dda 100644 --- a/server/factory/provider.go +++ b/server/factory/provider.go @@ -1,6 +1,8 @@ package factory import ( + "time" + "github.com/multiversx/mx-chain-core-go/core/pubkeyConverter" hasherFactory "github.com/multiversx/mx-chain-core-go/hashing/factory" marshalFactory "github.com/multiversx/mx-chain-core-go/marshal/factory" @@ -12,6 +14,7 @@ import ( "github.com/multiversx/mx-chain-rosetta/server/factory/components" "github.com/multiversx/mx-chain-rosetta/server/provider" "github.com/multiversx/mx-chain-rosetta/server/resources" + "github.com/multiversx/mx-chain-storage-go/timecache" ) const ( @@ -119,7 +122,16 @@ func CreateNetworkProvider(args ArgsCreateNetworkProvider) (NetworkProvider, err return nil, err } - blockProcessor, err := process.NewBlockProcessor(baseProcessor) + cacheDuration := time.Duration(30) * time.Second + timedCache, err := timecache.NewTimeCacher(timecache.ArgTimeCacher{ + DefaultSpan: cacheDuration, + CacheExpiry: cacheDuration, + }) + if err != nil { + return nil, err + } + + blockProcessor, err := process.NewBlockProcessor(baseProcessor, timedCache) if err != nil { return nil, err } diff --git a/server/provider/accounts.go b/server/provider/accounts.go index 5e4e8be..d70a702 100644 --- a/server/provider/accounts.go +++ b/server/provider/accounts.go @@ -28,7 +28,7 @@ func (provider *networkProvider) GetAccount(address string) (*resources.AccountO return data, nil } -// GetAccountNativeBalance gets the native balance by address +// GetAccountBalance gets the native balance by address func (provider *networkProvider) GetAccountBalance(address string, tokenIdentifier string, options resources.AccountQueryOptions) (*resources.AccountBalanceOnBlock, error) { isNativeBalance := tokenIdentifier == provider.nativeCurrency.Symbol if isNativeBalance { diff --git a/server/provider/networkProvider.go b/server/provider/networkProvider.go index b3c79b8..3615bf7 100644 --- a/server/provider/networkProvider.go +++ b/server/provider/networkProvider.go @@ -195,7 +195,8 @@ func (provider *networkProvider) getBlockSummaryByNonce(nonce uint64) (resources Nonce: blockResponse.Data.Block.Nonce, Hash: blockResponse.Data.Block.Hash, PreviousBlockHash: blockResponse.Data.Block.PrevBlockHash, - Timestamp: int64(blockResponse.Data.Block.Timestamp), + Timestamp: blockResponse.Data.Block.Timestamp, + TimestampMs: blockResponse.Data.Block.TimestampMs, }, nil } @@ -285,6 +286,7 @@ func createBlockCopy(block *api.Block) *api.Block { StateRootHash: block.StateRootHash, Status: block.Status, Timestamp: block.Timestamp, + TimestampMs: block.TimestampMs, MiniBlocks: miniblocksCopy, } } diff --git a/server/provider/nodeStatus.go b/server/provider/nodeStatus.go index 3f2738d..bf02b32 100644 --- a/server/provider/nodeStatus.go +++ b/server/provider/nodeStatus.go @@ -18,7 +18,7 @@ func (provider *networkProvider) GetNodeStatus() (*resources.AggregatedNodeStatu return nil, err } - latestNonce, err := getLatestNonceGivenHighestFinalNonce(plainNodeStatus.HighestFinalNonce) + latestNonce, err := getLatestNonceGivenHighestFinalNonceAndLastExecutedNonce(plainNodeStatus.HighestFinalNonce, plainNodeStatus.LastExecutedNonce) if err != nil { return nil, err } @@ -71,10 +71,10 @@ func (provider *networkProvider) getLatestBlockNonce() (uint64, error) { } // In the context of scheduled transactions, make sure the N+1 block is final, as well. - return getLatestNonceGivenHighestFinalNonce(nodeStatus.HighestFinalNonce) + return getLatestNonceGivenHighestFinalNonceAndLastExecutedNonce(nodeStatus.HighestFinalNonce, nodeStatus.LastExecutedNonce) } -func getLatestNonceGivenHighestFinalNonce(highestFinalNonce uint64) (uint64, error) { +func getLatestNonceGivenHighestFinalNonceAndLastExecutedNonce(highestFinalNonce uint64, lastExecutedNonce uint64) (uint64, error) { // Account for rollback-related edge cases while node is syncing (in conjunction with scheduled miniblocks). const nonceDelta = 2 @@ -82,7 +82,12 @@ func getLatestNonceGivenHighestFinalNonce(highestFinalNonce uint64) (uint64, err return 0, errCannotGetLatestBlockNonce } - return highestFinalNonce - nonceDelta, nil + nonceToReturn := highestFinalNonce - nonceDelta + if lastExecutedNonce > 0 && nonceToReturn > lastExecutedNonce { + return lastExecutedNonce, nil + } + + return nonceToReturn, nil } func (provider *networkProvider) getOldestNonceWithHistoricalStateGivenNodeStatus(status *resources.NodeStatus) (uint64, error) { diff --git a/server/provider/nodeStatus_test.go b/server/provider/nodeStatus_test.go index 366437c..a9d7d8e 100644 --- a/server/provider/nodeStatus_test.go +++ b/server/provider/nodeStatus_test.go @@ -66,6 +66,7 @@ func TestNetworkProvider_GetNodeStatusWithSuccess(t *testing.T) { Hash: "00000998", PrevBlockHash: "00000997", Timestamp: 998, + TimestampMs: 998200, }, }, }, nil @@ -80,6 +81,7 @@ func TestNetworkProvider_GetNodeStatusWithSuccess(t *testing.T) { Hash: "00000300", PrevBlockHash: "00000299", Timestamp: 300, + TimestampMs: 3002000, }, }, }, nil @@ -102,6 +104,7 @@ func TestNetworkProvider_GetNodeStatusWithSuccess(t *testing.T) { Hash: "00000998", PreviousBlockHash: "00000997", Timestamp: 998, + TimestampMs: 998200, } expectedSummaryOfOldest := resources.BlockSummary{ @@ -109,6 +112,7 @@ func TestNetworkProvider_GetNodeStatusWithSuccess(t *testing.T) { Hash: "00000300", PreviousBlockHash: "00000299", Timestamp: 300, + TimestampMs: 3002000, } nodeStatus, err := provider.GetNodeStatus() @@ -164,8 +168,8 @@ func TestNetworkProvider_GetLatestBlockNonce(t *testing.T) { args.FirstHistoricalEpoch = 2 args.NumHistoricalEpochs = 8 - provider, err := NewNetworkProvider(args) - require.Nil(t, err) + provider, errC := NewNetworkProvider(args) + require.Nil(t, errC) require.NotNil(t, provider) t.Run("when HighestFinalNonce <= 2 (node didn't start syncing)", func(t *testing.T) { @@ -207,6 +211,74 @@ func TestNetworkProvider_GetLatestBlockNonce(t *testing.T) { return 0, errors.New("unexpected request") } + nonce, err := provider.getLatestBlockNonce() + require.Nil(t, err) + require.Equal(t, uint64(40), nonce) + }) + t.Run("when HighestFinalNonce is greater than LastExecutedNonce", func(t *testing.T) { + t.Parallel() + + observerFacade.CallGetRestEndPointCalled = func(baseUrl, path string, value interface{}) (int, error) { + if path == "/node/status" { + value.(*resources.NodeStatusApiResponse).Data = resources.NodeStatusApiResponsePayload{ + Status: resources.NodeStatus{ + HighestFinalNonce: 42, + LastExecutedNonce: 39, + }, + } + + return 0, nil + } + + return 0, errors.New("unexpected request") + } + + nonce, err := provider.getLatestBlockNonce() + require.Nil(t, err) + require.Equal(t, uint64(39), nonce) + }) + + t.Run("when HighestFinalNonce is greater than LastExecutedNonce, but LastExecutedNonce is zero", func(t *testing.T) { + t.Parallel() + + observerFacade.CallGetRestEndPointCalled = func(baseUrl, path string, value interface{}) (int, error) { + if path == "/node/status" { + value.(*resources.NodeStatusApiResponse).Data = resources.NodeStatusApiResponsePayload{ + Status: resources.NodeStatus{ + HighestFinalNonce: 42, + LastExecutedNonce: 0, + }, + } + + return 0, nil + } + + return 0, errors.New("unexpected request") + } + + nonce, err := provider.getLatestBlockNonce() + require.Nil(t, err) + require.Equal(t, uint64(40), nonce) + }) + + t.Run("when LastExecutedNonce is greater than or equal to HighestFinalNonce minus two", func(t *testing.T) { + t.Parallel() + + observerFacade.CallGetRestEndPointCalled = func(baseUrl, path string, value interface{}) (int, error) { + if path == "/node/status" { + value.(*resources.NodeStatusApiResponse).Data = resources.NodeStatusApiResponsePayload{ + Status: resources.NodeStatus{ + HighestFinalNonce: 42, + LastExecutedNonce: 40, + }, + } + + return 0, nil + } + + return 0, errors.New("unexpected request") + } + nonce, err := provider.getLatestBlockNonce() require.Nil(t, err) require.Equal(t, uint64(40), nonce) diff --git a/server/resources/network.go b/server/resources/network.go index 1406370..5c84d5d 100644 --- a/server/resources/network.go +++ b/server/resources/network.go @@ -34,6 +34,7 @@ type NodeStatus struct { CurrentEpoch uint32 `json:"erd_epoch_number"` HighestNonce uint64 `json:"erd_nonce"` HighestFinalNonce uint64 `json:"erd_highest_final_nonce"` + LastExecutedNonce uint64 `json:"erd_last_executed_nonce"` } // EpochStartApiResponse is an API resource diff --git a/server/resources/resources.go b/server/resources/resources.go index e2572a0..6c524dc 100644 --- a/server/resources/resources.go +++ b/server/resources/resources.go @@ -18,6 +18,7 @@ type BlockSummary struct { Hash string PreviousBlockHash string Timestamp int64 + TimestampMs int64 } // Currency is an internal resource diff --git a/server/services/blockService.go b/server/services/blockService.go index 6f84c57..c14cb44 100644 --- a/server/services/blockService.go +++ b/server/services/blockService.go @@ -220,7 +220,7 @@ func (service *blockService) convertToRosettaBlock(block *api.Block) (*types.Blo Block: &types.Block{ BlockIdentifier: blockToIdentifier(block), ParentBlockIdentifier: parentBlockIdentifier, - Timestamp: timestampInMilliseconds(int64(block.Timestamp)), + Timestamp: getTimestampInMS(block.Timestamp, block.TimestampMs), Transactions: transactions, Metadata: objectsMap{ "shard": block.Shard, diff --git a/server/services/converters.go b/server/services/converters.go index 338f200..d0e994f 100644 --- a/server/services/converters.go +++ b/server/services/converters.go @@ -72,6 +72,13 @@ func timestampInMilliseconds(timestamp int64) int64 { return timestamp * 1000 } +func getTimestampInMS(timestampSec int64, timestampMS int64) int64 { + if timestampMS > 0 { + return timestampMS + } + return timestampInMilliseconds(timestampSec) +} + func stringToHex(value string) string { encoded := hex.EncodeToString([]byte(value)) encoded = ensureEvenLengthOfHexString(encoded) diff --git a/server/services/converters_test.go b/server/services/converters_test.go index 1ddc6e9..f17d97a 100644 --- a/server/services/converters_test.go +++ b/server/services/converters_test.go @@ -64,3 +64,10 @@ func TestHexToAmount(t *testing.T) { require.Nil(t, err) require.Equal(t, "100", amount) } + +func TestGetTimestamp(t *testing.T) { + + require.Equal(t, int64(0), getTimestampInMS(0, 0)) + require.Equal(t, int64(500000), getTimestampInMS(500, 0)) + require.Equal(t, int64(500200), getTimestampInMS(500, 500200)) +} diff --git a/server/services/networkService.go b/server/services/networkService.go index a2f545e..3155c9d 100644 --- a/server/services/networkService.go +++ b/server/services/networkService.go @@ -56,7 +56,7 @@ func (service *networkService) NetworkStatus( networkStatusResponse := &types.NetworkStatusResponse{ CurrentBlockIdentifier: blockSummaryToIdentifier(&nodeStatus.LatestBlock), - CurrentBlockTimestamp: timestampInMilliseconds(nodeStatus.LatestBlock.Timestamp), + CurrentBlockTimestamp: getTimestampInMS(nodeStatus.LatestBlock.Timestamp, nodeStatus.LatestBlock.TimestampMs), GenesisBlockIdentifier: service.extension.getGenesisBlockIdentifier(), OldestBlockIdentifier: blockSummaryToIdentifier(&nodeStatus.OldestBlockWithHistoricalState), SyncStatus: &types.SyncStatus{