class TrimObserver {

    public int onBlockPushed(@Observes @BlockEvent(BlockEventType.BLOCK_PUSHED) Block block) {
        int scheduleTrimHeight = -1;
        if (block.getHeight() % trimFrequency == 0) {
            // we need to know that current config was just changed by 'APPLY_BLOCK' event on earlier processing stage
            // TODO: YL after separating 'shard' and 'trim' logic, we can remove 'isJustUpdated() usage
            boolean isConfigJustUpdated = blockchainConfig.isJustUpdated();
            HeightConfig currentConfig = blockchainConfig.getCurrentConfig();
            boolean shardingEnabled = currentConfig.isShardingEnabled();
            log.debug("Is sharding DISabled ? : '{}' || '{}' on height={}",
                !shardingEnabled, isShardingOff, block.getHeight());
            int randomTrimHeightIncrease = 0; // we will scheduled trim height by random value or by zero
            if (!shardingEnabled || isShardingOff) {
                // non sharded node, schedule next trim event processing randomized and added to current height all the time
                randomTrimHeightIncrease = generatePositiveIntBiggerThenZero(trimFrequency);
                log.trace("'Not sharded', trim height random increase = {}", randomTrimHeightIncrease);
            } else {
                // sharded node should 'predict' next shard height and DO NOT randomize in such case
                int trimHeight = Math.max(0, (block.getHeight() - maxRollback));
                int shardingFrequency;
                if (!isConfigJustUpdated) {
                    // config didn't change from previous trim scheduling
                    shardingFrequency = currentConfig.getShardingFrequency();
                } else {
                    // config has changed from previous trim scheduling, try to get previous 'shard frequency' value
                    shardingFrequency = blockchainConfig.getPreviousConfig().isPresent()
                        && blockchainConfig.getPreviousConfig().get().isShardingEnabled() ?
                        blockchainConfig.getPreviousConfig().get().getShardingFrequency() // previous config
                        : currentConfig.getShardingFrequency(); // fall back
                }
                // the boolean - if shard is possible by trim height
                boolean isShardingOnTrimHeight = (Math.max(trimHeight, 0)) % shardingFrequency == 0;

                // that boolean - if shard is possible by current blockchain height
                // we don't want support config params for 'maxRollback', 'shardFrequency' matched incorrectly/badly to each other
                // so we use that in order do not to 'miss' shard height additionally
                boolean isShardingOnBlockHeight = block.getHeight() % shardingFrequency == 0;
                // generate pseudo random for 'trim height divergence'
                randomTrimHeightIncrease = generatePositiveIntBiggerThenZero(trimFrequency);
                if (isShardingOnBlockHeight || isShardingOnTrimHeight) {
                    // prevent trim randomization on 'potentially dangerous heights'
                    randomTrimHeightIncrease = 0; // schedule that case in any conditions
                } else if (trimService.isTrimming()) {
                    // SAFE SKIP, DO NOT schedule next if we haven't finished previous trim
                    log.debug("Schedule next trim SKIPPED={}, trimHeight = {} / {}, shardFreq={} ({}}), isShardingOnTrimHeight={}, isShardingOnBlockHeight={}",
                        randomTrimHeightIncrease, trimHeight, block.getHeight(), shardingFrequency,
                        isConfigJustUpdated, isShardingOnTrimHeight, isShardingOnBlockHeight);
                    return scheduleTrimHeight;
                }
                log.debug("Schedule next trim for rndIncrease={}, height/trimHeight = {} / {}, shardFreq={} ({}}), isShardingOnTrimHeight={}, isShardingOnBlockHeight={}",
                    randomTrimHeightIncrease, trimHeight, block.getHeight(), shardingFrequency, isConfigJustUpdated, isShardingOnTrimHeight, isShardingOnBlockHeight);
            }
            synchronized (lock) {
                scheduleTrimHeight = block.getHeight() + randomTrimHeightIncrease; // increase next trim height with possible divergence
                log.debug("Schedule next trim for height={} at {}", scheduleTrimHeight, block.getHeight());
                trimHeights.add(scheduleTrimHeight);
            }
        } else {
            log.trace("Skip Trim schedule on block height='{}' NOT div % by trimFreq={}", block.getHeight(), trimFrequency);
        }
        return scheduleTrimHeight;
    }

    @Inject public  TrimObserver(TrimService trimService, BlockchainConfig blockchainConfig,
                        PropertiesHolder propertiesHolder, Random random,
                        Blockchain blockchain);

}