From 1ae601787db999a928baa5da2df894c3082289d8 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Thu, 14 Jul 2022 14:13:38 +0300 Subject: [PATCH] network: allow to handle GetMPTData with KeepOnlyLatestState on And adjust documentation along the way. --- docs/node-configuration.md | 7 +- pkg/config/protocol_config.go | 4 +- pkg/core/statesync/neotest_test.go | 367 +++++++++++++++-------------- pkg/network/server.go | 5 +- pkg/network/server_test.go | 32 ++- 5 files changed, 212 insertions(+), 203 deletions(-) diff --git a/docs/node-configuration.md b/docs/node-configuration.md index 1ff7fc01f..bd7d53b75 100644 --- a/docs/node-configuration.md +++ b/docs/node-configuration.md @@ -241,8 +241,7 @@ protocol-related settings described in the table below. | CommitteeHistory | map[uint32]int | none | Number of committee members after the given height, for example `{0: 1, 20: 4}` sets up a chain with one committee member since the genesis and then changes the setting to 4 committee members at the height of 20. `StandbyCommittee` committee setting must have the number of keys equal or exceeding the highest value in this option. Blocks numbers where the change happens must be divisible by the old and by the new values simultaneously. If not set, committee size is derived from the `StandbyCommittee` setting and never changes. | | GarbageCollectionPeriod | `uint32` | 10000 | Controls MPT garbage collection interval (in blocks) for configurations with `RemoveUntraceableBlocks` enabled and `KeepOnlyLatestState` disabled. In this mode the node stores a number of MPT trees (corresponding to `MaxTraceableBlocks` and `StateSyncInterval`), but the DB needs to be clean from old entries from time to time. Doing it too often will cause too much processing overhead, doing it too rarely will leave more useless data in the DB. | | Hardforks | `map[string]uint32` | [] | The set of incompatible changes that affect node behaviour starting from the specified height. The default value is an empty set which should be interpreted as "each known hard-fork is applied from the zero blockchain height". The list of valid hard-fork names:
• `Aspidochelone` represents hard-fork introduced in [#2469](https://github.com/nspcc-dev/neo-go/pull/2469) (ported from the [reference](https://github.com/neo-project/neo/pull/2712)). It adjusts the prices of `System.Contract.CreateStandardAccount` and `System.Contract.CreateMultisigAccount` interops so that the resulting prices are in accordance with `sha256` method of native `CryptoLib` contract. `Aspidochelone` is also includes [#2519](https://github.com/nspcc-dev/neo-go/pull/2519) (ported from the [reference](https://github.com/neo-project/neo/pull/2749)). It adjusts the price of `System.Runtime.GetRandom` interop and fixes its vulnerability. | -| KeepOnlyLatestState | `bool` | `false` | Specifies if MPT should only store latest state. If true, DB size will be smaller, but older roots won't be accessible. This value should remain th -e same for the same database. | Conflicts with `P2PStateExchangeExtensions`. | +| KeepOnlyLatestState | `bool` | `false` | Specifies if MPT should only store latest state (or a set of latest states, see `P2PStateExcangeExtensions` section for details). If true, DB size will be smaller, but older roots won't be accessible. This value should remain the same for the same database. | | | Magic | `uint32` | `0` | Magic number which uniquely identifies NEO network. | | MaxBlockSize | `uint32` | `262144` | Maximum block size in bytes. | | MaxBlockSystemFee | `int64` | `900000000000` | Maximum overall transactions system fee per block. | @@ -252,8 +251,8 @@ e same for the same database. | Conflicts with `P2PStateExchangeExtensions`. | | NativeActivations | `map[string][]uint32` | ContractManagement: [0]
StdLib: [0]
CryptoLib: [0]
LedgerContract: [0]
NeoToken: [0]
GasToken: [0]
PolicyContract: [0]
RoleManagement: [0]
OracleContract: [0] | The list of histories of native contracts updates. Each list item shod be presented as a known native contract name with the corresponding list of chain's heights. The contract is not active until chain reaches the first height value specified in the list. | `Notary` is supported. | | P2PNotaryRequestPayloadPoolSize | `int` | `1000` | Size of the node's P2P Notary request payloads memory pool where P2P Notary requests are stored before main or fallback transaction is completed and added to the chain.
This option is valid only if `P2PSigExtensions` are enabled. | Not supported by the C# node, thus may affect heterogeneous networks functionality. | | P2PSigExtensions | `bool` | `false` | Enables following additional Notary service related logic:
• Transaction attributes `NotValidBefore`, `Conflicts` and `NotaryAssisted`
• Network payload of the `P2PNotaryRequest` type
• Native `Notary` contract
• Notary node module | Not supported by the C# node, thus may affect heterogeneous networks functionality. | -| P2PStateExchangeExtensions | `bool` | `false` | Enables following P2P MPT state data exchange logic:
• `StateSyncInterval` protocol setting
• P2P commands `GetMPTDataCMD` and `MPTDataCMD` | Not supported by the C# node, thus may affect heterogeneous networks functionality. Conflicts with `KeepOnlyLatestState`. | -| RemoveUntraceableBlocks | `bool`| `false` | Denotes whether old blocks should be removed from cache and database. If enabled, then only last `MaxTraceableBlocks` are stored and accessible to smart contracts. Old MPT data is also deleted in accordance with `GarbageCollectionPeriod` setting. | +| P2PStateExchangeExtensions | `bool` | `false` | Enables following P2P MPT state data exchange logic:
• `StateSyncInterval` protocol setting
• P2P commands `GetMPTDataCMD` and `MPTDataCMD` | Not supported by the C# node, thus may affect heterogeneous networks functionality. Can be supported either on MPT-complete node (`KeepOnlyLatestState`=`false`) or on light GC-enabled node (`KeepOnlyLatestState`=`true`, `RemoveUntraceableBlocks`=`true`) | +| RemoveUntraceableBlocks | `bool`| `false` | Denotes whether old blocks should be removed from cache and database. If enabled, then only last `MaxTraceableBlocks` are stored and accessible to smart contracts. Old MPT data is also deleted in accordance with `GarbageCollectionPeriod` setting. If enabled along with `P2PStateExchangeExtensions`, then old blocks and MPT states should be removed up to the second latest state synchronisation point. | | ReservedAttributes | `bool` | `false` | Allows to have reserved attributes range for experimental or private purposes. | | SaveStorageBatch | `bool` | `false` | Enables storage batch saving before every persist. It is similar to StorageDump plugin for C# node. | | SecondsPerBlock | `int` | `15` | Minimal time that should pass before next block is accepted. | diff --git a/pkg/config/protocol_config.go b/pkg/config/protocol_config.go index 2685f18b2..b7129a92e 100644 --- a/pkg/config/protocol_config.go +++ b/pkg/config/protocol_config.go @@ -89,8 +89,8 @@ type heightNumber struct { func (p *ProtocolConfiguration) Validate() error { var err error - if p.KeepOnlyLatestState && p.P2PStateExchangeExtensions { - return errors.New("can't have both KeepOnlyLatestState and P2PStateExchangeExtensions") + if p.P2PStateExchangeExtensions && p.KeepOnlyLatestState && !p.RemoveUntraceableBlocks { + return fmt.Errorf("P2PStateExchangeExtensions can be supprted either on MPT-complete node (KeepOnlyLatestState=false) or on light GC-enabled node (KeepOnlyLatestState=true, RemoveUntraceableBlocks=true)") } for name := range p.NativeUpdateHistories { if !nativenames.IsValid(name) { diff --git a/pkg/core/statesync/neotest_test.go b/pkg/core/statesync/neotest_test.go index 2a95e882d..af6bd5d90 100644 --- a/pkg/core/statesync/neotest_test.go +++ b/pkg/core/statesync/neotest_test.go @@ -282,186 +282,201 @@ func TestStateSyncModule_Init(t *testing.T) { } func TestStateSyncModule_RestoreBasicChain(t *testing.T) { - var ( - stateSyncInterval = 4 - maxTraceable uint32 = 6 - stateSyncPoint = 24 - ) - spoutCfg := func(c *config.ProtocolConfiguration) { - c.StateRootInHeader = true - c.P2PStateExchangeExtensions = true - c.StateSyncInterval = stateSyncInterval - c.MaxTraceableBlocks = maxTraceable - c.P2PSigExtensions = true // `basicchain.Init` assumes Notary is enabled. - } - bcSpoutStore := storage.NewMemoryStore() - bcSpout, validators, committee := chain.NewMultiWithCustomConfigAndStore(t, spoutCfg, bcSpoutStore, false) - go bcSpout.Run() // Will close it manually at the end. - e := neotest.NewExecutor(t, bcSpout, validators, committee) - basicchain.Init(t, "../../../", e) - - // make spout chain higher than latest state sync point (add several blocks up to stateSyncPoint+2) - e.AddNewBlock(t) - e.AddNewBlock(t) - e.AddNewBlock(t) - e.AddNewBlock(t) - require.Equal(t, stateSyncPoint+2, int(bcSpout.BlockHeight())) - - boltCfg := func(c *config.ProtocolConfiguration) { - spoutCfg(c) - c.KeepOnlyLatestState = true - c.RemoveUntraceableBlocks = true - } - bcBoltStore := storage.NewMemoryStore() - bcBolt, _, _ := chain.NewMultiWithCustomConfigAndStore(t, boltCfg, bcBoltStore, false) - go bcBolt.Run() // Will close it manually at the end. - module := bcBolt.GetStateSyncModule() - - t.Run("error: add headers before initialisation", func(t *testing.T) { - h, err := bcSpout.GetHeader(bcSpout.GetHeaderHash(1)) - require.NoError(t, err) - require.Error(t, module.AddHeaders(h)) - }) - t.Run("no error: add blocks before initialisation", func(t *testing.T) { - b, err := bcSpout.GetBlock(bcSpout.GetHeaderHash(1)) - require.NoError(t, err) - require.NoError(t, module.AddBlock(b)) - }) - t.Run("error: add MPT nodes without initialisation", func(t *testing.T) { - require.Error(t, module.AddMPTNodes([][]byte{})) - }) - - require.NoError(t, module.Init(bcSpout.BlockHeight())) - require.True(t, module.IsActive()) - require.True(t, module.IsInitialized()) - require.True(t, module.NeedHeaders()) - require.False(t, module.NeedMPTNodes()) - - // add headers to module - headers := make([]*block.Header, 0, bcSpout.HeaderHeight()) - for i := uint32(1); i <= bcSpout.HeaderHeight(); i++ { - h, err := bcSpout.GetHeader(bcSpout.GetHeaderHash(int(i))) - require.NoError(t, err) - headers = append(headers, h) - } - require.NoError(t, module.AddHeaders(headers...)) - require.True(t, module.IsActive()) - require.True(t, module.IsInitialized()) - require.False(t, module.NeedHeaders()) - require.True(t, module.NeedMPTNodes()) - require.Equal(t, bcSpout.HeaderHeight(), bcBolt.HeaderHeight()) - - // add blocks - t.Run("error: unexpected block index", func(t *testing.T) { - b, err := bcSpout.GetBlock(bcSpout.GetHeaderHash(stateSyncPoint - int(maxTraceable))) - require.NoError(t, err) - require.Error(t, module.AddBlock(b)) - }) - t.Run("error: missing state root in block header", func(t *testing.T) { - b := &block.Block{ - Header: block.Header{ - Index: uint32(stateSyncPoint) - maxTraceable + 1, - StateRootEnabled: false, - }, + check := func(t *testing.T, spoutEnableGC bool) { + var ( + stateSyncInterval = 4 + maxTraceable uint32 = 6 + stateSyncPoint = 24 + ) + spoutCfg := func(c *config.ProtocolConfiguration) { + c.KeepOnlyLatestState = spoutEnableGC + c.RemoveUntraceableBlocks = spoutEnableGC + c.StateRootInHeader = true + c.P2PStateExchangeExtensions = true + c.StateSyncInterval = stateSyncInterval + c.MaxTraceableBlocks = maxTraceable + c.P2PSigExtensions = true // `basicchain.Init` assumes Notary is enabled. } - require.Error(t, module.AddBlock(b)) - }) - t.Run("error: invalid block merkle root", func(t *testing.T) { - b := &block.Block{ - Header: block.Header{ - Index: uint32(stateSyncPoint) - maxTraceable + 1, - StateRootEnabled: true, - MerkleRoot: util.Uint256{1, 2, 3}, - }, + bcSpoutStore := storage.NewMemoryStore() + bcSpout, validators, committee := chain.NewMultiWithCustomConfigAndStore(t, spoutCfg, bcSpoutStore, false) + go bcSpout.Run() // Will close it manually at the end. + e := neotest.NewExecutor(t, bcSpout, validators, committee) + basicchain.Init(t, "../../../", e) + + // make spout chain higher than latest state sync point (add several blocks up to stateSyncPoint+2) + e.AddNewBlock(t) + e.AddNewBlock(t) // This block is stateSyncPoint-th block. + e.AddNewBlock(t) + e.AddNewBlock(t) + require.Equal(t, stateSyncPoint+2, int(bcSpout.BlockHeight())) + + boltCfg := func(c *config.ProtocolConfiguration) { + spoutCfg(c) + c.KeepOnlyLatestState = true + c.RemoveUntraceableBlocks = true } - require.Error(t, module.AddBlock(b)) - }) + bcBoltStore := storage.NewMemoryStore() + bcBolt, _, _ := chain.NewMultiWithCustomConfigAndStore(t, boltCfg, bcBoltStore, false) + go bcBolt.Run() // Will close it manually at the end. + module := bcBolt.GetStateSyncModule() - for i := stateSyncPoint - int(maxTraceable) + 1; i <= stateSyncPoint; i++ { - b, err := bcSpout.GetBlock(bcSpout.GetHeaderHash(i)) - require.NoError(t, err) - require.NoError(t, module.AddBlock(b)) - } - require.True(t, module.IsActive()) - require.True(t, module.IsInitialized()) - require.False(t, module.NeedHeaders()) - require.True(t, module.NeedMPTNodes()) - require.Equal(t, uint32(stateSyncPoint), module.BlockHeight()) - - // add MPT nodes in batches - h, err := bcSpout.GetHeader(bcSpout.GetHeaderHash(stateSyncPoint + 1)) - require.NoError(t, err) - unknownHashes := module.GetUnknownMPTNodesBatch(100) - require.Equal(t, 1, len(unknownHashes)) - require.Equal(t, h.PrevStateRoot, unknownHashes[0]) - nodesMap := make(map[util.Uint256][]byte) - err = bcSpout.GetStateSyncModule().Traverse(h.PrevStateRoot, func(n mpt.Node, nodeBytes []byte) bool { - nodesMap[n.Hash()] = nodeBytes - return false - }) - require.NoError(t, err) - for { - need := module.GetUnknownMPTNodesBatch(10) - if len(need) == 0 { - break - } - add := make([][]byte, len(need)) - for i, h := range need { - nodeBytes, ok := nodesMap[h] - if !ok { - t.Fatal("unknown or restored node requested") - } - add[i] = nodeBytes - delete(nodesMap, h) - } - require.NoError(t, module.AddMPTNodes(add)) - } - require.False(t, module.IsActive()) - require.False(t, module.NeedHeaders()) - require.False(t, module.NeedMPTNodes()) - unknownNodes := module.GetUnknownMPTNodesBatch(1) - require.True(t, len(unknownNodes) == 0) - require.Equal(t, uint32(stateSyncPoint), module.BlockHeight()) - require.Equal(t, uint32(stateSyncPoint), bcBolt.BlockHeight()) - - // add missing blocks to bcBolt: should be ok, because state is synced - for i := stateSyncPoint + 1; i <= int(bcSpout.BlockHeight()); i++ { - b, err := bcSpout.GetBlock(bcSpout.GetHeaderHash(i)) - require.NoError(t, err) - require.NoError(t, bcBolt.AddBlock(b)) - } - require.Equal(t, bcSpout.BlockHeight(), bcBolt.BlockHeight()) - - // compare storage states - fetchStorage := func(ps storage.Store, storagePrefix byte) []storage.KeyValue { - var kv []storage.KeyValue - ps.Seek(storage.SeekRange{Prefix: []byte{storagePrefix}}, func(k, v []byte) bool { - key := slice.Copy(k) - value := slice.Copy(v) - if key[0] == byte(storage.STTempStorage) { - key[0] = byte(storage.STStorage) - } - kv = append(kv, storage.KeyValue{ - Key: key, - Value: value, - }) - return true + t.Run("error: add headers before initialisation", func(t *testing.T) { + h, err := bcSpout.GetHeader(bcSpout.GetHeaderHash(1)) + require.NoError(t, err) + require.Error(t, module.AddHeaders(h)) + }) + t.Run("no error: add blocks before initialisation", func(t *testing.T) { + b, err := bcSpout.GetBlock(bcSpout.GetHeaderHash(int(bcSpout.BlockHeight()))) + require.NoError(t, err) + require.NoError(t, module.AddBlock(b)) + }) + t.Run("error: add MPT nodes without initialisation", func(t *testing.T) { + require.Error(t, module.AddMPTNodes([][]byte{})) }) - return kv - } - // Both blockchains are running, so we need to wait until recent changes will be persisted - // to the underlying backend store. Close blockchains to ensure persist was completed. - bcSpout.Close() - bcBolt.Close() - expected := fetchStorage(bcSpoutStore, byte(storage.STStorage)) - actual := fetchStorage(bcBoltStore, byte(storage.STTempStorage)) - require.ElementsMatch(t, expected, actual) - // no temp items should be left - var haveItems bool - bcBoltStore.Seek(storage.SeekRange{Prefix: []byte{byte(storage.STStorage)}}, func(_, _ []byte) bool { - haveItems = true - return false + require.NoError(t, module.Init(bcSpout.BlockHeight())) + require.True(t, module.IsActive()) + require.True(t, module.IsInitialized()) + require.True(t, module.NeedHeaders()) + require.False(t, module.NeedMPTNodes()) + + // add headers to module + headers := make([]*block.Header, 0, bcSpout.HeaderHeight()) + for i := uint32(1); i <= bcSpout.HeaderHeight(); i++ { + h, err := bcSpout.GetHeader(bcSpout.GetHeaderHash(int(i))) + require.NoError(t, err) + headers = append(headers, h) + } + require.NoError(t, module.AddHeaders(headers...)) + require.True(t, module.IsActive()) + require.True(t, module.IsInitialized()) + require.False(t, module.NeedHeaders()) + require.True(t, module.NeedMPTNodes()) + require.Equal(t, bcSpout.HeaderHeight(), bcBolt.HeaderHeight()) + + // add blocks + t.Run("error: unexpected block index", func(t *testing.T) { + b, err := bcSpout.GetBlock(bcSpout.GetHeaderHash(stateSyncPoint - int(maxTraceable))) + require.NoError(t, err) + require.Error(t, module.AddBlock(b)) + }) + t.Run("error: missing state root in block header", func(t *testing.T) { + b := &block.Block{ + Header: block.Header{ + Index: uint32(stateSyncPoint) - maxTraceable + 1, + StateRootEnabled: false, + }, + } + require.Error(t, module.AddBlock(b)) + }) + t.Run("error: invalid block merkle root", func(t *testing.T) { + b := &block.Block{ + Header: block.Header{ + Index: uint32(stateSyncPoint) - maxTraceable + 1, + StateRootEnabled: true, + MerkleRoot: util.Uint256{1, 2, 3}, + }, + } + require.Error(t, module.AddBlock(b)) + }) + + for i := stateSyncPoint - int(maxTraceable) + 1; i <= stateSyncPoint; i++ { + b, err := bcSpout.GetBlock(bcSpout.GetHeaderHash(i)) + require.NoError(t, err) + require.NoError(t, module.AddBlock(b)) + } + require.True(t, module.IsActive()) + require.True(t, module.IsInitialized()) + require.False(t, module.NeedHeaders()) + require.True(t, module.NeedMPTNodes()) + require.Equal(t, uint32(stateSyncPoint), module.BlockHeight()) + + // add MPT nodes in batches + h, err := bcSpout.GetHeader(bcSpout.GetHeaderHash(stateSyncPoint + 1)) + require.NoError(t, err) + unknownHashes := module.GetUnknownMPTNodesBatch(100) + require.Equal(t, 1, len(unknownHashes)) + require.Equal(t, h.PrevStateRoot, unknownHashes[0]) + nodesMap := make(map[util.Uint256][]byte) + + sm := bcSpout.GetStateModule() + sroo, err := sm.GetStateRoot(uint32(stateSyncPoint)) + require.NoError(t, err) + require.Equal(t, sroo.Root, h.PrevStateRoot) + err = bcSpout.GetStateSyncModule().Traverse(h.PrevStateRoot, func(n mpt.Node, nodeBytes []byte) bool { + nodesMap[n.Hash()] = nodeBytes + return false + }) + require.NoError(t, err) + for { + need := module.GetUnknownMPTNodesBatch(10) + if len(need) == 0 { + break + } + add := make([][]byte, len(need)) + for i, h := range need { + nodeBytes, ok := nodesMap[h] + if !ok { + t.Fatal("unknown or restored node requested") + } + add[i] = nodeBytes + delete(nodesMap, h) + } + require.NoError(t, module.AddMPTNodes(add)) + } + require.False(t, module.IsActive()) + require.False(t, module.NeedHeaders()) + require.False(t, module.NeedMPTNodes()) + unknownNodes := module.GetUnknownMPTNodesBatch(1) + require.True(t, len(unknownNodes) == 0) + require.Equal(t, uint32(stateSyncPoint), module.BlockHeight()) + require.Equal(t, uint32(stateSyncPoint), bcBolt.BlockHeight()) + + // add missing blocks to bcBolt: should be ok, because state is synced + for i := stateSyncPoint + 1; i <= int(bcSpout.BlockHeight()); i++ { + b, err := bcSpout.GetBlock(bcSpout.GetHeaderHash(i)) + require.NoError(t, err) + require.NoError(t, bcBolt.AddBlock(b)) + } + require.Equal(t, bcSpout.BlockHeight(), bcBolt.BlockHeight()) + + // compare storage states + fetchStorage := func(ps storage.Store, storagePrefix byte) []storage.KeyValue { + var kv []storage.KeyValue + ps.Seek(storage.SeekRange{Prefix: []byte{storagePrefix}}, func(k, v []byte) bool { + key := slice.Copy(k) + value := slice.Copy(v) + if key[0] == byte(storage.STTempStorage) { + key[0] = byte(storage.STStorage) + } + kv = append(kv, storage.KeyValue{ + Key: key, + Value: value, + }) + return true + }) + return kv + } + // Both blockchains are running, so we need to wait until recent changes will be persisted + // to the underlying backend store. Close blockchains to ensure persist was completed. + bcSpout.Close() + bcBolt.Close() + expected := fetchStorage(bcSpoutStore, byte(storage.STStorage)) + actual := fetchStorage(bcBoltStore, byte(storage.STTempStorage)) + require.ElementsMatch(t, expected, actual) + + // no temp items should be left + var haveItems bool + bcBoltStore.Seek(storage.SeekRange{Prefix: []byte{byte(storage.STStorage)}}, func(_, _ []byte) bool { + haveItems = true + return false + }) + require.False(t, haveItems) + } + t.Run("source node is archive", func(t *testing.T) { + check(t, false) + }) + t.Run("source node is light with GC", func(t *testing.T) { + check(t, true) }) - require.False(t, haveItems) } diff --git a/pkg/network/server.go b/pkg/network/server.go index c71e8c617..929271e99 100644 --- a/pkg/network/server.go +++ b/pkg/network/server.go @@ -780,10 +780,7 @@ func (s *Server) handleGetMPTDataCmd(p Peer, inv *payload.MPTInventory) error { if !s.config.P2PStateExchangeExtensions { return errors.New("GetMPTDataCMD was received, but P2PStateExchangeExtensions are disabled") } - if s.config.KeepOnlyLatestState { - // TODO: implement keeping MPT states for P1 and P2 height (#2095, #2152 related) - return errors.New("GetMPTDataCMD was received, but only latest MPT state is supported") - } + // Even if s.config.KeepOnlyLatestState enabled, we'll keep latest P1 and P2 MPT states. resp := payload.MPTData{} capLeft := payload.MaxSize - 8 // max(io.GetVarSize(len(resp.Nodes))) added := make(map[util.Uint256]struct{}) diff --git a/pkg/network/server_test.go b/pkg/network/server_test.go index 6c1af736f..514851ab9 100644 --- a/pkg/network/server_test.go +++ b/pkg/network/server_test.go @@ -752,23 +752,7 @@ func TestHandleGetMPTData(t *testing.T) { require.Error(t, s.handleMessage(p, msg)) }) - t.Run("KeepOnlyLatestState on", func(t *testing.T) { - s := startTestServer(t, func(c *config.ProtocolConfiguration) { - c.P2PStateExchangeExtensions = true - c.KeepOnlyLatestState = true - }) - p := newLocalPeer(t, s) - p.handshaked = true - msg := NewMessage(CMDGetMPTData, &payload.MPTInventory{ - Hashes: []util.Uint256{{1, 2, 3}}, - }) - require.Error(t, s.handleMessage(p, msg)) - }) - - t.Run("good", func(t *testing.T) { - s := startTestServer(t, func(c *config.ProtocolConfiguration) { - c.P2PStateExchangeExtensions = true - }) + check := func(t *testing.T, s *Server) { var recvResponse atomic.Bool r1 := random.Uint256() r2 := random.Uint256() @@ -797,6 +781,20 @@ func TestHandleGetMPTData(t *testing.T) { s.testHandleMessage(t, p, CMDGetMPTData, payload.NewMPTInventory(hs)) require.Eventually(t, recvResponse.Load, time.Second, time.Millisecond) + } + t.Run("KeepOnlyLatestState on", func(t *testing.T) { + s := startTestServer(t, func(c *config.ProtocolConfiguration) { + c.P2PStateExchangeExtensions = true + c.KeepOnlyLatestState = true + }) + check(t, s) + }) + + t.Run("good", func(t *testing.T) { + s := startTestServer(t, func(c *config.ProtocolConfiguration) { + c.P2PStateExchangeExtensions = true + }) + check(t, s) }) }