1 | package org.cardanofoundation.explorer.api.service.impl; | |
2 | ||
3 | import java.math.BigInteger; | |
4 | import java.time.Instant; | |
5 | import java.time.LocalDateTime; | |
6 | import java.time.ZoneOffset; | |
7 | import java.time.temporal.ChronoUnit; | |
8 | import java.util.*; | |
9 | ||
10 | import lombok.RequiredArgsConstructor; | |
11 | ||
12 | import org.springframework.beans.factory.annotation.Value; | |
13 | import org.springframework.data.domain.Page; | |
14 | import org.springframework.data.domain.Pageable; | |
15 | import org.springframework.data.redis.core.RedisTemplate; | |
16 | import org.springframework.stereotype.Service; | |
17 | import org.springframework.util.CollectionUtils; | |
18 | ||
19 | import org.cardanofoundation.explorer.api.common.enumeration.EpochStatus; | |
20 | import org.cardanofoundation.explorer.api.exception.BusinessCode; | |
21 | import org.cardanofoundation.explorer.api.exception.NoContentException; | |
22 | import org.cardanofoundation.explorer.api.mapper.EpochMapper; | |
23 | import org.cardanofoundation.explorer.api.model.response.BaseFilterResponse; | |
24 | import org.cardanofoundation.explorer.api.model.response.EpochResponse; | |
25 | import org.cardanofoundation.explorer.api.model.response.dashboard.EpochSummary; | |
26 | import org.cardanofoundation.explorer.api.repository.ledgersync.AdaPotsRepository; | |
27 | import org.cardanofoundation.explorer.api.repository.ledgersync.BlockRepository; | |
28 | import org.cardanofoundation.explorer.api.repository.ledgersync.EpochRepository; | |
29 | import org.cardanofoundation.explorer.api.service.EpochService; | |
30 | import org.cardanofoundation.explorer.api.service.FetchRewardDataService; | |
31 | import org.cardanofoundation.explorer.api.util.StreamUtil; | |
32 | import org.cardanofoundation.explorer.common.entity.ledgersync.Block; | |
33 | import org.cardanofoundation.explorer.common.entity.ledgersync.Epoch; | |
34 | import org.cardanofoundation.explorer.common.exception.BusinessException; | |
35 | ||
36 | @Service | |
37 | @RequiredArgsConstructor | |
38 | public class EpochServiceImpl implements EpochService { | |
39 | ||
40 | private final EpochRepository epochRepository; | |
41 | private final BlockRepository blockRepository; | |
42 | private final EpochMapper epochMapper; | |
43 | private final RedisTemplate<String, Object> redisTemplate; | |
44 | private final FetchRewardDataService fetchRewardDataService; | |
45 | private final AdaPotsRepository adaPotsRepository; | |
46 | private static final String UNIQUE_ACCOUNTS_KEY = "UNIQUE_ACCOUNTS"; | |
47 | private static final String UNDERSCORE = "_"; | |
48 | ||
49 | @Value("${application.network}") | |
50 | private String network; | |
51 | ||
52 | @Value("${application.epoch.days}") | |
53 | public int epochDays; | |
54 | ||
55 | @Value("${application.healthcheck.block-time-threshold}") | |
56 | private Long blockTimeThresholdInSecond; | |
57 | ||
58 | @Override | |
59 | public EpochResponse getEpochDetail(String no) { | |
60 | try { | |
61 | Integer epochNo = Integer.parseInt(no); | |
62 | Epoch epoch = | |
63 | epochRepository | |
64 | .findFirstByNo(epochNo) | |
65 |
1
1. lambda$getEpochDetail$0 : replaced return value with null for org/cardanofoundation/explorer/api/service/impl/EpochServiceImpl::lambda$getEpochDetail$0 → KILLED |
.orElseThrow(() -> new BusinessException(BusinessCode.EPOCH_NOT_FOUND)); |
66 | var currentEpoch = | |
67 | epochRepository | |
68 | .findCurrentEpochNo() | |
69 |
1
1. lambda$getEpochDetail$1 : replaced return value with null for org/cardanofoundation/explorer/api/service/impl/EpochServiceImpl::lambda$getEpochDetail$1 → KILLED |
.orElseThrow(() -> new BusinessException(BusinessCode.EPOCH_NOT_FOUND)); |
70 |
1
1. getEpochDetail : negated conditional → KILLED |
if (Boolean.FALSE.equals(fetchRewardDataService.checkEpochRewardDistributed(epoch)) |
71 |
3
1. getEpochDetail : Replaced integer subtraction with addition → SURVIVED 2. getEpochDetail : changed conditional boundary → SURVIVED 3. getEpochDetail : negated conditional → KILLED |
&& epoch.getNo() < currentEpoch - 1) { |
72 | List<Epoch> fetchEpochResponse = | |
73 | fetchRewardDataService.fetchEpochRewardDistributed(List.of(epochNo)); | |
74 |
1
1. getEpochDetail : negated conditional → SURVIVED |
if (!CollectionUtils.isEmpty(fetchEpochResponse)) { |
75 |
1
1. getEpochDetail : removed call to org/cardanofoundation/explorer/common/entity/ledgersync/Epoch::setRewardsDistributed → SURVIVED |
epoch.setRewardsDistributed(fetchEpochResponse.get(0).getRewardsDistributed()); |
76 | } | |
77 | } | |
78 | Epoch firstEpoch = | |
79 | epochRepository | |
80 | .findFirstByNo(BigInteger.ZERO.intValue()) | |
81 |
1
1. lambda$getEpochDetail$2 : replaced return value with null for org/cardanofoundation/explorer/api/service/impl/EpochServiceImpl::lambda$getEpochDetail$2 → NO_COVERAGE |
.orElseThrow(() -> new BusinessException(BusinessCode.EPOCH_NOT_FOUND)); |
82 | ||
83 | Block currentBlock = | |
84 | blockRepository | |
85 | .findLatestBlock() | |
86 |
1
1. lambda$getEpochDetail$3 : replaced return value with null for org/cardanofoundation/explorer/api/service/impl/EpochServiceImpl::lambda$getEpochDetail$3 → NO_COVERAGE |
.orElseThrow(() -> new BusinessException(BusinessCode.BLOCK_NOT_FOUND)); |
87 | ||
88 | LocalDateTime firstEpochStartTime = firstEpoch.getStartTime().toLocalDateTime(); | |
89 | EpochResponse response = epochMapper.epochToEpochResponse(epoch); | |
90 |
1
1. getEpochDetail : removed call to org/cardanofoundation/explorer/api/service/impl/EpochServiceImpl::checkEpochStatus → KILLED |
checkEpochStatus(response, currentBlock, currentEpoch); |
91 | LocalDateTime startTime = | |
92 | modifyStartTimeAndEndTimeOfEpoch(firstEpochStartTime, response.getStartTime()); | |
93 |
1
1. getEpochDetail : removed call to org/cardanofoundation/explorer/api/model/response/EpochResponse::setStartTime → SURVIVED |
response.setStartTime(startTime); |
94 |
1
1. getEpochDetail : removed call to org/cardanofoundation/explorer/api/model/response/EpochResponse::setEndTime → KILLED |
response.setEndTime(startTime.plusDays(epochDays)); |
95 | String uniqueAccountRedisKey = | |
96 | String.join(UNDERSCORE, getRedisKey(UNIQUE_ACCOUNTS_KEY), epoch.getNo().toString()); | |
97 | Integer account = redisTemplate.opsForHash().size(uniqueAccountRedisKey).intValue(); | |
98 |
1
1. getEpochDetail : removed call to org/cardanofoundation/explorer/api/model/response/EpochResponse::setAccount → KILLED |
response.setAccount(account); |
99 |
1
1. getEpochDetail : replaced return value with null for org/cardanofoundation/explorer/api/service/impl/EpochServiceImpl::getEpochDetail → KILLED |
return response; |
100 | } catch (NumberFormatException e) { | |
101 | throw new BusinessException(BusinessCode.EPOCH_NOT_FOUND); | |
102 | } | |
103 | } | |
104 | ||
105 | @Override | |
106 | public BaseFilterResponse<EpochResponse> getAllEpoch(Pageable pageable) { | |
107 | Block currentBlock = | |
108 | blockRepository | |
109 | .findLatestBlock() | |
110 |
1
1. lambda$getAllEpoch$4 : replaced return value with null for org/cardanofoundation/explorer/api/service/impl/EpochServiceImpl::lambda$getAllEpoch$4 → NO_COVERAGE |
.orElseThrow(() -> new BusinessException(BusinessCode.BLOCK_NOT_FOUND)); |
111 | ||
112 | Epoch firstEpoch = | |
113 | epochRepository | |
114 | .findFirstByNo(BigInteger.ZERO.intValue()) | |
115 |
1
1. lambda$getAllEpoch$5 : replaced return value with null for org/cardanofoundation/explorer/api/service/impl/EpochServiceImpl::lambda$getAllEpoch$5 → NO_COVERAGE |
.orElseThrow(() -> new NoContentException(BusinessCode.EPOCH_NOT_FOUND)); |
116 | LocalDateTime firstEpochStartTime = firstEpoch.getStartTime().toLocalDateTime(); | |
117 | ||
118 | var currentEpoch = | |
119 | epochRepository | |
120 | .findCurrentEpochNo() | |
121 |
1
1. lambda$getAllEpoch$6 : replaced return value with null for org/cardanofoundation/explorer/api/service/impl/EpochServiceImpl::lambda$getAllEpoch$6 → KILLED |
.orElseThrow(() -> new NoContentException(BusinessCode.EPOCH_NOT_FOUND)); |
122 | ||
123 | Page<Epoch> epochs = epochRepository.findAll(pageable); | |
124 | var epochNeedFetch = | |
125 | epochs.getContent().stream() | |
126 | .filter( | |
127 | epoch -> | |
128 |
2
1. lambda$getAllEpoch$7 : replaced boolean return with true for org/cardanofoundation/explorer/api/service/impl/EpochServiceImpl::lambda$getAllEpoch$7 → SURVIVED 2. lambda$getAllEpoch$7 : negated conditional → KILLED |
!fetchRewardDataService.checkEpochRewardDistributed(epoch) |
129 |
3
1. lambda$getAllEpoch$7 : changed conditional boundary → SURVIVED 2. lambda$getAllEpoch$7 : Replaced integer subtraction with addition → SURVIVED 3. lambda$getAllEpoch$7 : negated conditional → KILLED |
&& epoch.getNo() < currentEpoch - 1) |
130 | .map(Epoch::getNo) | |
131 | .toList(); | |
132 |
1
1. getAllEpoch : negated conditional → KILLED |
if (!CollectionUtils.isEmpty(epochNeedFetch)) { |
133 | List<Epoch> fetchEpochList = | |
134 | fetchRewardDataService.fetchEpochRewardDistributed(epochNeedFetch); | |
135 |
1
1. getAllEpoch : negated conditional → SURVIVED |
if (!CollectionUtils.isEmpty(fetchEpochList)) { |
136 | Map<Integer, BigInteger> epochRewardMap = | |
137 | StreamUtil.toMap(fetchEpochList, Epoch::getNo, Epoch::getRewardsDistributed); | |
138 |
1
1. getAllEpoch : removed call to org/springframework/data/domain/Page::forEach → SURVIVED |
epochs.forEach( |
139 | epoch -> { | |
140 |
1
1. lambda$getAllEpoch$8 : negated conditional → SURVIVED |
if (epochRewardMap.containsKey(epoch.getNo())) { |
141 |
1
1. lambda$getAllEpoch$8 : removed call to org/cardanofoundation/explorer/common/entity/ledgersync/Epoch::setRewardsDistributed → SURVIVED |
epoch.setRewardsDistributed(epochRewardMap.get(epoch.getNo())); |
142 | } | |
143 | }); | |
144 | } | |
145 | } | |
146 | ||
147 | Page<EpochResponse> pageResponse = epochs.map(epochMapper::epochToEpochResponse); | |
148 | pageResponse | |
149 | .getContent() | |
150 |
1
1. getAllEpoch : removed call to java/util/List::forEach → KILLED |
.forEach( |
151 | epoch -> { | |
152 |
1
1. lambda$getAllEpoch$9 : removed call to org/cardanofoundation/explorer/api/service/impl/EpochServiceImpl::checkEpochStatus → KILLED |
checkEpochStatus(epoch, currentBlock, currentEpoch); |
153 | LocalDateTime startTime = | |
154 | modifyStartTimeAndEndTimeOfEpoch(firstEpochStartTime, epoch.getStartTime()); | |
155 |
1
1. lambda$getAllEpoch$9 : removed call to org/cardanofoundation/explorer/api/model/response/EpochResponse::setStartTime → SURVIVED |
epoch.setStartTime(startTime); |
156 |
1
1. lambda$getAllEpoch$9 : removed call to org/cardanofoundation/explorer/api/model/response/EpochResponse::setEndTime → SURVIVED |
epoch.setEndTime(startTime.plusDays(epochDays)); |
157 | String uniqueAccountRedisKey = | |
158 | String.join( | |
159 | UNDERSCORE, getRedisKey(UNIQUE_ACCOUNTS_KEY), epoch.getNo().toString()); | |
160 |
1
1. lambda$getAllEpoch$9 : removed call to org/cardanofoundation/explorer/api/model/response/EpochResponse::setAccount → SURVIVED |
epoch.setAccount(redisTemplate.opsForHash().size(uniqueAccountRedisKey).intValue()); |
161 | }); | |
162 |
1
1. getAllEpoch : replaced return value with null for org/cardanofoundation/explorer/api/service/impl/EpochServiceImpl::getAllEpoch → KILLED |
return new BaseFilterResponse<>(pageResponse); |
163 | } | |
164 | ||
165 | /** | |
166 | * Set time of epoch belongs to start time of first epoch Set hour, minute, second of epoch | |
167 | * belongs to hour, minute, second of first epoch | |
168 | * | |
169 | * @param firstEpochStartTime start time of first epoch | |
170 | * @param epochTime start time or end time of epoch | |
171 | * @return epoch time after modify | |
172 | */ | |
173 | private LocalDateTime modifyStartTimeAndEndTimeOfEpoch( | |
174 | LocalDateTime firstEpochStartTime, LocalDateTime epochTime) { | |
175 |
1
1. modifyStartTimeAndEndTimeOfEpoch : replaced return value with null for org/cardanofoundation/explorer/api/service/impl/EpochServiceImpl::modifyStartTimeAndEndTimeOfEpoch → KILLED |
return LocalDateTime.of( |
176 | epochTime.getYear(), | |
177 | epochTime.getMonth(), | |
178 | epochTime.getDayOfMonth(), | |
179 | firstEpochStartTime.getHour(), | |
180 | firstEpochStartTime.getMinute(), | |
181 | firstEpochStartTime.getSecond()); | |
182 | } | |
183 | ||
184 | /** | |
185 | * Get epoch status from start time and end time | |
186 | * | |
187 | * <p>Start time < now < end time : in progress | |
188 | * | |
189 | * <p>End time > now - 10 day and not in progress: rewarding | |
190 | * | |
191 | * <p>Others: finished | |
192 | * | |
193 | * @param epoch epoch response | |
194 | */ | |
195 | private void checkEpochStatus(EpochResponse epoch, Block currentBlock, int currentEpochNo) { | |
196 |
1
1. checkEpochStatus : removed call to org/cardanofoundation/explorer/api/model/response/EpochResponse::setStatus → KILLED |
epoch.setStatus( |
197 | getEpochStatus(epoch.getStartTime(), epoch.getNo(), currentEpochNo, currentBlock)); | |
198 | ||
199 |
1
1. checkEpochStatus : negated conditional → KILLED |
if (epoch.getStatus().equals(EpochStatus.FINISHED)) { |
200 |
1
1. checkEpochStatus : removed call to org/cardanofoundation/explorer/api/model/response/EpochResponse::setSyncingProgress → SURVIVED |
epoch.setSyncingProgress(1D); |
201 | } else { | |
202 |
2
1. checkEpochStatus : removed call to org/cardanofoundation/explorer/api/model/response/EpochResponse::setSyncingProgress → KILLED 2. checkEpochStatus : Replaced double division with multiplication → KILLED |
epoch.setSyncingProgress((double) currentBlock.getEpochSlotNo() / epoch.getMaxSlot()); |
203 | } | |
204 | } | |
205 | ||
206 | private EpochStatus getEpochStatus( | |
207 | LocalDateTime startTime, Integer epochNo, Integer currentEpochNo, Block currentBlock) { | |
208 | EpochStatus status; | |
209 | LocalDateTime currentTime = LocalDateTime.now(ZoneOffset.UTC); | |
210 |
2
1. getEpochStatus : negated conditional → KILLED 2. getEpochStatus : negated conditional → KILLED |
if (startTime.plusDays(epochDays).isAfter(currentTime) && startTime.isBefore(currentTime)) { |
211 | status = EpochStatus.IN_PROGRESS; | |
212 | } else { | |
213 | status = EpochStatus.FINISHED; | |
214 | } | |
215 | ||
216 |
1
1. getEpochStatus : negated conditional → KILLED |
if (epochNo.equals(currentEpochNo) |
217 | && blockTimeThresholdInSecond | |
218 |
2
1. getEpochStatus : changed conditional boundary → SURVIVED 2. getEpochStatus : negated conditional → KILLED |
<= ChronoUnit.SECONDS.between(currentBlock.getTime().toLocalDateTime(), currentTime)) { |
219 | status = EpochStatus.SYNCING; | |
220 | } | |
221 |
1
1. getEpochStatus : replaced return value with null for org/cardanofoundation/explorer/api/service/impl/EpochServiceImpl::getEpochStatus → KILLED |
return status; |
222 | } | |
223 | ||
224 | @Override | |
225 | public EpochSummary getCurrentEpochSummary() { | |
226 | Block currentBlock = | |
227 | blockRepository | |
228 | .findLatestBlock() | |
229 |
1
1. lambda$getCurrentEpochSummary$10 : replaced return value with null for org/cardanofoundation/explorer/api/service/impl/EpochServiceImpl::lambda$getCurrentEpochSummary$10 → NO_COVERAGE |
.orElseThrow(() -> new BusinessException(BusinessCode.BLOCK_NOT_FOUND)); |
230 | ||
231 |
1
1. getCurrentEpochSummary : replaced return value with null for org/cardanofoundation/explorer/api/service/impl/EpochServiceImpl::getCurrentEpochSummary → KILLED |
return epochRepository |
232 | .findCurrentEpochSummary() | |
233 | .map( | |
234 | epochSummaryProjection -> { | |
235 | var currentLocalDateTime = LocalDateTime.ofInstant(Instant.now(), ZoneOffset.UTC); | |
236 | var epochStartTime = epochSummaryProjection.getStartTime().toLocalDateTime(); | |
237 | Epoch firstEpoch = | |
238 | epochRepository | |
239 | .findFirstByNo(BigInteger.ZERO.intValue()) | |
240 |
1
1. lambda$getCurrentEpochSummary$11 : replaced return value with null for org/cardanofoundation/explorer/api/service/impl/EpochServiceImpl::lambda$getCurrentEpochSummary$11 → NO_COVERAGE |
.orElseThrow(() -> new NoContentException(BusinessCode.EPOCH_NOT_FOUND)); |
241 | LocalDateTime firstEpochStartTime = firstEpoch.getStartTime().toLocalDateTime(); | |
242 | epochStartTime = | |
243 | modifyStartTimeAndEndTimeOfEpoch(firstEpochStartTime, epochStartTime); | |
244 | var slot = | |
245 | currentLocalDateTime.toEpochSecond(ZoneOffset.UTC) | |
246 |
1
1. lambda$getCurrentEpochSummary$12 : Replaced long subtraction with addition → SURVIVED |
- epochStartTime.toEpochSecond(ZoneOffset.UTC); |
247 | String uniqueAccountRedisKey = | |
248 | String.join( | |
249 | UNDERSCORE, | |
250 | getRedisKey(UNIQUE_ACCOUNTS_KEY), | |
251 | epochSummaryProjection.getNo().toString()); | |
252 | var account = redisTemplate.opsForHash().size(uniqueAccountRedisKey).intValue(); | |
253 |
1
1. lambda$getCurrentEpochSummary$12 : negated conditional → SURVIVED |
if (Boolean.FALSE.equals( |
254 | fetchRewardDataService.checkAdaPots(epochSummaryProjection.getNo()))) { | |
255 | fetchRewardDataService.fetchAdaPots(List.of(epochSummaryProjection.getNo())); | |
256 | } | |
257 |
1
1. lambda$getCurrentEpochSummary$12 : replaced return value with null for org/cardanofoundation/explorer/api/service/impl/EpochServiceImpl::lambda$getCurrentEpochSummary$12 → KILLED |
return EpochSummary.builder() |
258 | .no(epochSummaryProjection.getNo()) | |
259 | .slot((int) slot) | |
260 | .totalSlot(epochSummaryProjection.getMaxSlot()) | |
261 | .startTime(epochStartTime) | |
262 | .endTime(epochStartTime.plusDays(epochDays)) | |
263 | .account(account) | |
264 | .status( | |
265 | getEpochStatus( | |
266 | epochStartTime, | |
267 | epochSummaryProjection.getNo(), | |
268 | epochSummaryProjection.getNo(), | |
269 | currentBlock)) | |
270 | .syncingProgress( | |
271 |
1
1. lambda$getCurrentEpochSummary$12 : Replaced double division with multiplication → SURVIVED |
(double) currentBlock.getEpochSlotNo() / epochSummaryProjection.getMaxSlot()) |
272 | .blkCount(epochSummaryProjection.getBlkCount()) | |
273 | .build(); | |
274 | }) | |
275 | .orElse(EpochSummary.builder().slot(0).no(0).totalSlot(0).build()); | |
276 | } | |
277 | ||
278 | private String getRedisKey(String key) { | |
279 |
1
1. getRedisKey : replaced return value with "" for org/cardanofoundation/explorer/api/service/impl/EpochServiceImpl::getRedisKey → SURVIVED |
return String.join(UNDERSCORE, network.toUpperCase(), key); |
280 | } | |
281 | } | |
Mutations | ||
65 |
1.1 |
|
69 |
1.1 |
|
70 |
1.1 |
|
71 |
1.1 2.2 3.3 |
|
74 |
1.1 |
|
75 |
1.1 |
|
81 |
1.1 |
|
86 |
1.1 |
|
90 |
1.1 |
|
93 |
1.1 |
|
94 |
1.1 |
|
98 |
1.1 |
|
99 |
1.1 |
|
110 |
1.1 |
|
115 |
1.1 |
|
121 |
1.1 |
|
128 |
1.1 2.2 |
|
129 |
1.1 2.2 3.3 |
|
132 |
1.1 |
|
135 |
1.1 |
|
138 |
1.1 |
|
140 |
1.1 |
|
141 |
1.1 |
|
150 |
1.1 |
|
152 |
1.1 |
|
155 |
1.1 |
|
156 |
1.1 |
|
160 |
1.1 |
|
162 |
1.1 |
|
175 |
1.1 |
|
196 |
1.1 |
|
199 |
1.1 |
|
200 |
1.1 |
|
202 |
1.1 2.2 |
|
210 |
1.1 2.2 |
|
216 |
1.1 |
|
218 |
1.1 2.2 |
|
221 |
1.1 |
|
229 |
1.1 |
|
231 |
1.1 |
|
240 |
1.1 |
|
246 |
1.1 |
|
253 |
1.1 |
|
257 |
1.1 |
|
271 |
1.1 |
|
279 |
1.1 |