diff --git a/pkg/neorpc/result/version.go b/pkg/neorpc/result/version.go index deb4f33d4..4107e7f86 100644 --- a/pkg/neorpc/result/version.go +++ b/pkg/neorpc/result/version.go @@ -2,7 +2,10 @@ package result import ( "encoding/json" + "fmt" + "strings" + "github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/config/netmode" "github.com/nspcc-dev/neo-go/pkg/encoding/fixedn" ) @@ -29,6 +32,8 @@ type ( MemoryPoolMaxTransactions int ValidatorsCount byte InitialGasDistribution fixedn.Fixed8 + // Hardforks is the map of network hardforks with the enabling height. + Hardforks map[config.Hardfork]uint32 // Below are NeoGo-specific extensions to the protocol that are // returned by the server in case they're enabled. @@ -54,16 +59,36 @@ type ( MemoryPoolMaxTransactions int `json:"memorypoolmaxtransactions"` ValidatorsCount byte `json:"validatorscount"` InitialGasDistribution int64 `json:"initialgasdistribution"` + Hardforks []hardforkAux `json:"hardforks"` CommitteeHistory map[uint32]uint32 `json:"committeehistory,omitempty"` P2PSigExtensions bool `json:"p2psigextensions,omitempty"` StateRootInHeader bool `json:"staterootinheader,omitempty"` ValidatorsHistory map[uint32]uint32 `json:"validatorshistory,omitempty"` } + + // hardforkAux is an auxiliary struct used for Hardfork JSON marshalling. + hardforkAux struct { + Name string `json:"name"` + Height uint32 `json:"blockheight"` + } ) +// prefixHardfork is a prefix used for hardfork names in C# node. +const prefixHardfork = "HF_" + // MarshalJSON implements the JSON marshaler interface. func (p Protocol) MarshalJSON() ([]byte, error) { + // Keep hardforks sorted by name in the result. + hfs := make([]hardforkAux, 0, len(p.Hardforks)) + for _, hf := range config.Hardforks { + if h, ok := p.Hardforks[hf]; ok { + hfs = append(hfs, hardforkAux{ + Name: hf.String(), + Height: h, + }) + } + } aux := protocolMarshallerAux{ AddressVersion: p.AddressVersion, Network: p.Network, @@ -74,6 +99,7 @@ func (p Protocol) MarshalJSON() ([]byte, error) { MemoryPoolMaxTransactions: p.MemoryPoolMaxTransactions, ValidatorsCount: p.ValidatorsCount, InitialGasDistribution: int64(p.InitialGasDistribution), + Hardforks: hfs, CommitteeHistory: p.CommitteeHistory, P2PSigExtensions: p.P2PSigExtensions, @@ -104,5 +130,21 @@ func (p *Protocol) UnmarshalJSON(data []byte) error { p.ValidatorsHistory = aux.ValidatorsHistory p.InitialGasDistribution = fixedn.Fixed8(aux.InitialGasDistribution) + // Filter out unknown hardforks. + for i := range aux.Hardforks { + aux.Hardforks[i].Name = strings.TrimPrefix(aux.Hardforks[i].Name, prefixHardfork) + if !config.IsHardforkValid(aux.Hardforks[i].Name) { + return fmt.Errorf("unexpected hardfork: %s", aux.Hardforks[i].Name) + } + } + p.Hardforks = make(map[config.Hardfork]uint32, len(aux.Hardforks)) + for _, cfgHf := range config.Hardforks { + for _, auxHf := range aux.Hardforks { + if auxHf.Name == cfgHf.String() { + p.Hardforks[cfgHf] = auxHf.Height + } + } + } + return nil } diff --git a/pkg/neorpc/result/version_test.go b/pkg/neorpc/result/version_test.go index d0388cca0..919f27972 100644 --- a/pkg/neorpc/result/version_test.go +++ b/pkg/neorpc/result/version_test.go @@ -4,6 +4,7 @@ import ( "encoding/json" "testing" + "github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/encoding/fixedn" "github.com/stretchr/testify/require" ) @@ -38,7 +39,8 @@ func TestVersion_MarshalUnmarshalJSON(t *testing.T) { "memorypoolmaxtransactions": 50000, "msperblock": 15000, "network": 860833102, - "validatorscount": 7 + "validatorscount": 7, + "hardforks": [{"name": "Aspidochelone", "blockheight": 123}, {"name": "Basilisk", "blockheight": 1234}] }, "tcpport": 10333, "useragent": "/NEO-GO:0.98.6/", @@ -55,7 +57,8 @@ func TestVersion_MarshalUnmarshalJSON(t *testing.T) { "memorypoolmaxtransactions": 50000, "msperblock": 15000, "network": 860833102, - "validatorscount": 7 + "validatorscount": 7, + "hardforks": [{"name": "HF_Aspidochelone", "blockheight": 123}, {"name": "HF_Basilisk", "blockheight": 1234}] }, "tcpport": 10333, "useragent": "/Neo:3.1.0/", @@ -78,6 +81,7 @@ func TestVersion_MarshalUnmarshalJSON(t *testing.T) { // Unmarshalled InitialGasDistribution should always be a valid Fixed8 for both old and new clients. InitialGasDistribution: fixedn.Fixed8FromInt64(52000000), StateRootInHeader: false, + Hardforks: map[config.Hardfork]uint32{config.HFAspidochelone: 123, config.HFBasilisk: 1234}, }, } t.Run("MarshalJSON", func(t *testing.T) { diff --git a/pkg/rpcclient/rpc_test.go b/pkg/rpcclient/rpc_test.go index da9139d09..d36d6804c 100644 --- a/pkg/rpcclient/rpc_test.go +++ b/pkg/rpcclient/rpc_test.go @@ -15,6 +15,7 @@ import ( "github.com/gorilla/websocket" "github.com/nspcc-dev/neo-go/internal/testserdes" + "github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/config/netmode" "github.com/nspcc-dev/neo-go/pkg/core/block" "github.com/nspcc-dev/neo-go/pkg/core/state" @@ -1031,7 +1032,8 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{ Nonce: 2153672787, UserAgent: "/NEO-GO:0.73.1-pre-273-ge381358/", Protocol: result.Protocol{ - Network: netmode.UnitTestNet, + Network: netmode.UnitTestNet, + Hardforks: map[config.Hardfork]uint32{}, }, } }, diff --git a/pkg/services/rpcsrv/client_test.go b/pkg/services/rpcsrv/client_test.go index d65274a2a..31aaa3f0b 100644 --- a/pkg/services/rpcsrv/client_test.go +++ b/pkg/services/rpcsrv/client_test.go @@ -2356,3 +2356,20 @@ func TestClient_GetStorageHistoric(t *testing.T) { _, err = c.GetStorageByHashHistoric(earlyRoot.Root, h, key) require.ErrorIs(t, neorpc.ErrUnknownStorageItem, err) } + +func TestClient_GetVersion_Hardforks(t *testing.T) { + chain, rpcSrv, httpSrv := initServerWithInMemoryChain(t) + defer chain.Close() + defer rpcSrv.Shutdown() + + c, err := rpcclient.New(context.Background(), httpSrv.URL, rpcclient.Options{}) + require.NoError(t, err) + require.NoError(t, c.Init()) + + v, err := c.GetVersion() + require.NoError(t, err) + expected := map[config.Hardfork]uint32{ + config.HFAspidochelone: 25, + } + require.InDeltaMapValues(t, expected, v.Protocol.Hardforks, 0) +} diff --git a/pkg/services/rpcsrv/server.go b/pkg/services/rpcsrv/server.go index 3037d99c2..f83a68854 100644 --- a/pkg/services/rpcsrv/server.go +++ b/pkg/services/rpcsrv/server.go @@ -839,6 +839,14 @@ func (s *Server) getVersion(_ params.Params) (any, *neorpc.Error) { } cfg := s.chain.GetConfig() + hfs := make(map[config.Hardfork]uint32, len(cfg.Hardforks)) + for _, cfgHf := range config.Hardforks { + height, ok := cfg.Hardforks[cfgHf.String()] + if !ok { + continue + } + hfs[cfgHf] = height + } return &result.Version{ TCPPort: port, Nonce: s.coreServer.ID(), @@ -853,6 +861,7 @@ func (s *Server) getVersion(_ params.Params) (any, *neorpc.Error) { MemoryPoolMaxTransactions: cfg.MemPoolSize, ValidatorsCount: byte(cfg.GetNumOfCNs(s.chain.BlockHeight())), InitialGasDistribution: cfg.InitialGASSupply, + Hardforks: hfs, CommitteeHistory: cfg.CommitteeHistory, P2PSigExtensions: cfg.P2PSigExtensions,