core: introduce RemoveUntraceableHeaders

With blocks available from NeoFS we can drop them from the local DB.

Signed-off-by: Roman Khimov <roman@nspcc.ru>
This commit is contained in:
Roman Khimov 2024-12-12 18:27:07 +03:00
parent 727262a95a
commit c7f5f173ae
6 changed files with 49 additions and 20 deletions

View file

@ -30,6 +30,7 @@ node-related settings described in the table below.
| Relay | `bool` | `true` | Determines whether the server is forwarding its inventory. | | Relay | `bool` | `true` | Determines whether the server is forwarding its inventory. |
| Consensus | [Consensus Configuration](#Consensus-Configuration) | | Describes consensus (dBFT) configuration. See the [Consensus Configuration](#Consensus-Configuration) for details. | | Consensus | [Consensus Configuration](#Consensus-Configuration) | | Describes consensus (dBFT) configuration. See the [Consensus Configuration](#Consensus-Configuration) for details. |
| RemoveUntraceableBlocks | `bool`| `false` | Denotes whether old blocks should be removed from cache and database. If enabled, then only the 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` protocol extension, then old blocks and MPT states will be removed up to the second latest state synchronisation point (see `StateSyncInterval`). | | RemoveUntraceableBlocks | `bool`| `false` | Denotes whether old blocks should be removed from cache and database. If enabled, then only the 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` protocol extension, then old blocks and MPT states will be removed up to the second latest state synchronisation point (see `StateSyncInterval`). |
| RemoveUntraceableHeaders | `bool`| `false` | Used only with RemoveUntraceableBlocks and makes node delete untraceable block headers as well. Notice that this is an experimental option, not recommended for production use. |
| RPC | [RPC Configuration](#RPC-Configuration) | | Describes [RPC subsystem](rpc.md) configuration. See the [RPC Configuration](#RPC-Configuration) for details. | | RPC | [RPC Configuration](#RPC-Configuration) | | Describes [RPC subsystem](rpc.md) configuration. See the [RPC Configuration](#RPC-Configuration) for details. |
| SaveStorageBatch | `bool` | `false` | Enables storage batch saving before every persist. It is similar to StorageDump plugin for C# node. | | SaveStorageBatch | `bool` | `false` | Enables storage batch saving before every persist. It is similar to StorageDump plugin for C# node. |
| SkipBlockVerification | `bool` | `false` | Allows to disable verification of received/processed blocks (including cryptographic checks). | | SkipBlockVerification | `bool` | `false` | Allows to disable verification of received/processed blocks (including cryptographic checks). |

View file

@ -14,6 +14,9 @@ type Ledger struct {
KeepOnlyLatestState bool `yaml:"KeepOnlyLatestState"` KeepOnlyLatestState bool `yaml:"KeepOnlyLatestState"`
// RemoveUntraceableBlocks specifies if old data should be removed. // RemoveUntraceableBlocks specifies if old data should be removed.
RemoveUntraceableBlocks bool `yaml:"RemoveUntraceableBlocks"` RemoveUntraceableBlocks bool `yaml:"RemoveUntraceableBlocks"`
// RemoveUntraceableHeaders is used in addition to RemoveUntraceableBlocks
// when headers need to be removed as well.
RemoveUntraceableHeaders bool `yaml:"RemoveUntraceableHeaders"`
// SaveStorageBatch enables storage batch saving before every persist. // SaveStorageBatch enables storage batch saving before every persist.
SaveStorageBatch bool `yaml:"SaveStorageBatch"` SaveStorageBatch bool `yaml:"SaveStorageBatch"`
// SkipBlockVerification allows to disable verification of received // SkipBlockVerification allows to disable verification of received

View file

@ -283,6 +283,9 @@ func NewBlockchain(s storage.Store, cfg config.Blockchain, log *zap.Logger) (*Bl
zap.Int("StateSyncInterval", cfg.StateSyncInterval)) zap.Int("StateSyncInterval", cfg.StateSyncInterval))
} }
} }
if cfg.RemoveUntraceableHeaders && !cfg.RemoveUntraceableBlocks {
return nil, errors.New("RemoveUntraceableHeaders is enabled, but RemoveUntraceableBlocks is not")
}
if cfg.Hardforks == nil { if cfg.Hardforks == nil {
cfg.Hardforks = map[string]uint32{} cfg.Hardforks = map[string]uint32{}
for _, hf := range config.StableHardforks { for _, hf := range config.StableHardforks {
@ -603,7 +606,7 @@ func (bc *Blockchain) jumpToStateInternal(p uint32, stage stateChangeStage) erro
// After current state is updated, we need to remove outdated state-related data if so. // After current state is updated, we need to remove outdated state-related data if so.
// The only outdated data we might have is genesis-related data, so check it. // The only outdated data we might have is genesis-related data, so check it.
if p-bc.config.MaxTraceableBlocks > 0 { if p-bc.config.MaxTraceableBlocks > 0 {
err := cache.DeleteBlock(bc.GetHeaderHash(0)) err := cache.DeleteBlock(bc.GetHeaderHash(0), false)
if err != nil { if err != nil {
return fmt.Errorf("failed to remove outdated state data for the genesis block: %w", err) return fmt.Errorf("failed to remove outdated state data for the genesis block: %w", err)
} }
@ -797,7 +800,7 @@ func (bc *Blockchain) resetStateInternal(height uint32, stage stateChangeStage)
keysCnt = new(int) keysCnt = new(int)
) )
for i := height + 1; i <= currHeight; i++ { for i := height + 1; i <= currHeight; i++ {
err := upperCache.DeleteBlock(bc.GetHeaderHash(i)) err := upperCache.DeleteBlock(bc.GetHeaderHash(i), false)
if err != nil { if err != nil {
return fmt.Errorf("error while removing block %d: %w", i, err) return fmt.Errorf("error while removing block %d: %w", i, err)
} }
@ -1622,7 +1625,7 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error
stop = start + 1 stop = start + 1
} }
for index := start; index < stop; index++ { for index := start; index < stop; index++ {
err := kvcache.DeleteBlock(bc.GetHeaderHash(index)) err := kvcache.DeleteBlock(bc.GetHeaderHash(index), bc.config.Ledger.RemoveUntraceableHeaders)
if err != nil { if err != nil {
bc.log.Warn("error while removing old block", bc.log.Warn("error while removing old block",
zap.Uint32("index", index), zap.Uint32("index", index),

View file

@ -139,6 +139,28 @@ func TestRemoveOldTransfers(t *testing.T) {
} }
} }
func checkNewBlockchainErr(t *testing.T, cfg func(c *config.Config), store storage.Store, errText string) {
unitTestNetCfg, err := config.Load("../../config", testchain.Network())
require.NoError(t, err)
cfg(&unitTestNetCfg)
log := zaptest.NewLogger(t)
_, err = NewBlockchain(store, unitTestNetCfg.Blockchain(), log)
if len(errText) != 0 {
require.Error(t, err)
require.True(t, strings.Contains(err.Error(), errText))
} else {
require.NoError(t, err)
}
}
func TestNewBlockchainIncosistencies(t *testing.T) {
t.Run("untraceable blocks/headers", func(t *testing.T) {
checkNewBlockchainErr(t, func(c *config.Config) {
c.ApplicationConfiguration.RemoveUntraceableHeaders = true
}, storage.NewMemoryStore(), "RemoveUntraceableHeaders is enabled, but RemoveUntraceableBlocks is not")
})
}
func TestBlockchain_InitWithIncompleteStateJump(t *testing.T) { func TestBlockchain_InitWithIncompleteStateJump(t *testing.T) {
var ( var (
stateSyncInterval = 4 stateSyncInterval = 4
@ -186,19 +208,6 @@ func TestBlockchain_InitWithIncompleteStateJump(t *testing.T) {
_, err := batch.Persist() _, err := batch.Persist()
require.NoError(t, err) require.NoError(t, err)
checkNewBlockchainErr := func(t *testing.T, cfg func(c *config.Config), store storage.Store, errText string) {
unitTestNetCfg, err := config.Load("../../config", testchain.Network())
require.NoError(t, err)
cfg(&unitTestNetCfg)
log := zaptest.NewLogger(t)
_, err = NewBlockchain(store, unitTestNetCfg.Blockchain(), log)
if len(errText) != 0 {
require.Error(t, err)
require.True(t, strings.Contains(err.Error(), errText))
} else {
require.NoError(t, err)
}
}
boltCfg := func(c *config.Config) { boltCfg := func(c *config.Config) {
spountCfg(c) spountCfg(c)
c.ApplicationConfiguration.KeepOnlyLatestState = true c.ApplicationConfiguration.KeepOnlyLatestState = true

View file

@ -766,16 +766,20 @@ func (dao *Simple) StoreAsBlock(block *block.Block, aer1 *state.AppExecResult, a
// DeleteBlock removes the block from dao. It's not atomic, so make sure you're // DeleteBlock removes the block from dao. It's not atomic, so make sure you're
// using private MemCached instance here. // using private MemCached instance here.
func (dao *Simple) DeleteBlock(h util.Uint256) error { func (dao *Simple) DeleteBlock(h util.Uint256, dropHeader bool) error {
key := dao.makeExecutableKey(h) key := dao.makeExecutableKey(h)
b, err := dao.getBlock(key) b, err := dao.getBlock(key)
if err != nil { if err != nil {
return err return err
} }
err = dao.storeHeader(key, &b.Header) if !dropHeader {
if err != nil { err = dao.storeHeader(key, &b.Header)
return err if err != nil {
return err
}
} else {
dao.Store.Delete(key)
} }
for _, tx := range b.Transactions { for _, tx := range b.Transactions {

View file

@ -107,6 +107,15 @@ func TestPutGetBlock(t *testing.T) {
require.Equal(t, 2, len(gotAppExecResult)) require.Equal(t, 2, len(gotAppExecResult))
require.Equal(t, *appExecResult1, gotAppExecResult[0]) require.Equal(t, *appExecResult1, gotAppExecResult[0])
require.Equal(t, *appExecResult2, gotAppExecResult[1]) require.Equal(t, *appExecResult2, gotAppExecResult[1])
require.NoError(t, dao.DeleteBlock(hash, false))
gotBlock, err = dao.GetBlock(hash) // It's just a header, but it's still there.
require.NoError(t, err)
require.NotNil(t, gotBlock)
require.NoError(t, dao.DeleteBlock(hash, true))
_, err = dao.GetBlock(hash)
require.Error(t, err)
} }
func TestGetVersion_NoVersion(t *testing.T) { func TestGetVersion_NoVersion(t *testing.T) {