neo-go/pkg/rpc/response/result/version.go
Anna Shaleva 9862b40f2c rpc: support InitialGasDistribution response from old Neo-Go nodes
https://github.com/nspcc-dev/neo-go/pull/2435 breaks compatibility
between newer RPC clients and older RPC servers with the following
error:
```
failed to get network magic: json: cannot unmarshal string into Go struct field Protocol.protocol.initialgasdistribution of type int64
```

This behaviour is expected, but we can't allow this radical change.
Thus, the following solution is implemented:
1. RPC server responds with proper non-stringified
   InitialGasDistribution value. The value represents an integral
   of fixed8 multiplied by the decimals.
2. RPC client is able to distinguish older and newer responses. For
   older one the stringified value without decimals part is
   expected. For newer responses the int64 value with decimal part
   is expected.

The cludge will be present in the code for a while until nodes of
version <=0.98.3 become completely absolete.
2022-04-27 19:00:46 +03:00

182 lines
7.2 KiB
Go

package result
import (
"encoding/json"
"fmt"
"strings"
"github.com/coreos/go-semver/semver"
"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"
)
type (
// Version model used for reporting server version
// info.
Version struct {
// Magic contains network magic.
// Deprecated: use Protocol.StateRootInHeader instead
Magic netmode.Magic
TCPPort uint16
WSPort uint16
Nonce uint32
UserAgent string
Protocol Protocol
// StateRootInHeader is true if state root is contained in block header.
// Deprecated: use Protocol.StateRootInHeader instead
StateRootInHeader bool
}
// Protocol represents network-dependent parameters.
Protocol struct {
AddressVersion byte
Network netmode.Magic
MillisecondsPerBlock int
MaxTraceableBlocks uint32
MaxValidUntilBlockIncrement uint32
MaxTransactionsPerBlock uint16
MemoryPoolMaxTransactions int
ValidatorsCount byte
InitialGasDistribution fixedn.Fixed8
// StateRootInHeader is true if state root is contained in block header.
StateRootInHeader bool
}
)
type (
// versionMarshallerAux is an auxiliary struct used for Version JSON marshalling.
versionMarshallerAux struct {
Magic netmode.Magic `json:"network"`
TCPPort uint16 `json:"tcpport"`
WSPort uint16 `json:"wsport,omitempty"`
Nonce uint32 `json:"nonce"`
UserAgent string `json:"useragent"`
Protocol protocolMarshallerAux `json:"protocol"`
StateRootInHeader bool `json:"staterootinheader,omitempty"`
}
// protocolMarshallerAux is an auxiliary struct used for Protocol JSON marshalling.
protocolMarshallerAux struct {
AddressVersion byte `json:"addressversion"`
Network netmode.Magic `json:"network"`
MillisecondsPerBlock int `json:"msperblock"`
MaxTraceableBlocks uint32 `json:"maxtraceableblocks"`
MaxValidUntilBlockIncrement uint32 `json:"maxvaliduntilblockincrement"`
MaxTransactionsPerBlock uint16 `json:"maxtransactionsperblock"`
MemoryPoolMaxTransactions int `json:"memorypoolmaxtransactions"`
ValidatorsCount byte `json:"validatorscount"`
InitialGasDistribution int64 `json:"initialgasdistribution"`
StateRootInHeader bool `json:"staterootinheader,omitempty"`
}
// versionUnmarshallerAux is an auxiliary struct used for Version JSON unmarshalling.
versionUnmarshallerAux struct {
Magic netmode.Magic `json:"network"`
TCPPort uint16 `json:"tcpport"`
WSPort uint16 `json:"wsport,omitempty"`
Nonce uint32 `json:"nonce"`
UserAgent string `json:"useragent"`
Protocol protocolUnmarshallerAux `json:"protocol"`
StateRootInHeader bool `json:"staterootinheader,omitempty"`
}
// protocolUnmarshallerAux is an auxiliary struct used for Protocol JSON unmarshalling.
protocolUnmarshallerAux struct {
AddressVersion byte `json:"addressversion"`
Network netmode.Magic `json:"network"`
MillisecondsPerBlock int `json:"msperblock"`
MaxTraceableBlocks uint32 `json:"maxtraceableblocks"`
MaxValidUntilBlockIncrement uint32 `json:"maxvaliduntilblockincrement"`
MaxTransactionsPerBlock uint16 `json:"maxtransactionsperblock"`
MemoryPoolMaxTransactions int `json:"memorypoolmaxtransactions"`
ValidatorsCount byte `json:"validatorscount"`
InitialGasDistribution json.RawMessage `json:"initialgasdistribution"`
StateRootInHeader bool `json:"staterootinheader,omitempty"`
}
)
// latestNonBreakingVersion is a latest NeoGo revision that keeps older RPC
// clients compatibility with newer RPC servers (https://github.com/nspcc-dev/neo-go/pull/2435).
var latestNonBreakingVersion = *semver.New("0.98.2")
// MarshalJSON implements the json marshaller interface.
func (v *Version) MarshalJSON() ([]byte, error) {
aux := versionMarshallerAux{
Magic: v.Magic,
TCPPort: v.TCPPort,
WSPort: v.WSPort,
Nonce: v.Nonce,
UserAgent: v.UserAgent,
Protocol: protocolMarshallerAux{
AddressVersion: v.Protocol.AddressVersion,
Network: v.Protocol.Network,
MillisecondsPerBlock: v.Protocol.MillisecondsPerBlock,
MaxTraceableBlocks: v.Protocol.MaxTraceableBlocks,
MaxValidUntilBlockIncrement: v.Protocol.MaxValidUntilBlockIncrement,
MaxTransactionsPerBlock: v.Protocol.MaxTransactionsPerBlock,
MemoryPoolMaxTransactions: v.Protocol.MemoryPoolMaxTransactions,
ValidatorsCount: v.Protocol.ValidatorsCount,
InitialGasDistribution: int64(v.Protocol.InitialGasDistribution),
StateRootInHeader: v.Protocol.StateRootInHeader,
},
StateRootInHeader: v.StateRootInHeader,
}
return json.Marshal(aux)
}
// UnmarshalJSON implements the json unmarshaller interface.
func (v *Version) UnmarshalJSON(data []byte) error {
var aux versionUnmarshallerAux
err := json.Unmarshal(data, &aux)
if err != nil {
return err
}
v.Magic = aux.Magic
v.TCPPort = aux.TCPPort
v.WSPort = aux.WSPort
v.Nonce = aux.Nonce
v.UserAgent = aux.UserAgent
v.Protocol.AddressVersion = aux.Protocol.AddressVersion
v.Protocol.Network = aux.Protocol.Network
v.Protocol.MillisecondsPerBlock = aux.Protocol.MillisecondsPerBlock
v.Protocol.MaxTraceableBlocks = aux.Protocol.MaxTraceableBlocks
v.Protocol.MaxValidUntilBlockIncrement = aux.Protocol.MaxValidUntilBlockIncrement
v.Protocol.MaxTransactionsPerBlock = aux.Protocol.MaxTransactionsPerBlock
v.Protocol.MemoryPoolMaxTransactions = aux.Protocol.MemoryPoolMaxTransactions
v.Protocol.ValidatorsCount = aux.Protocol.ValidatorsCount
v.Protocol.StateRootInHeader = aux.Protocol.StateRootInHeader
v.StateRootInHeader = aux.StateRootInHeader
if len(aux.Protocol.InitialGasDistribution) == 0 {
return nil
}
if strings.HasPrefix(v.UserAgent, config.UserAgentWrapper+config.UserAgentPrefix) {
ver, err := userAgentToVersion(v.UserAgent)
if err == nil && ver.Compare(latestNonBreakingVersion) <= 0 {
err := json.Unmarshal(aux.Protocol.InitialGasDistribution, &v.Protocol.InitialGasDistribution)
if err != nil {
return fmt.Errorf("failed to unmarshal InitialGASDistribution into fixed8: %w", err)
}
return nil
}
}
var val int64
err = json.Unmarshal(aux.Protocol.InitialGasDistribution, &val)
if err != nil {
return fmt.Errorf("failed to unmarshal InitialGASDistribution into int64: %w", err)
}
v.Protocol.InitialGasDistribution = fixedn.Fixed8(val)
return nil
}
func userAgentToVersion(userAgent string) (*semver.Version, error) {
verStr := strings.Trim(userAgent, config.UserAgentWrapper)
verStr = strings.TrimPrefix(verStr, config.UserAgentPrefix)
ver, err := semver.NewVersion(verStr)
if err != nil {
return nil, fmt.Errorf("can't retrieve neo-go version from UserAgent: %w", err)
}
return ver, nil
}