Skip to content

Commit cfaf4c0

Browse files
authored
feat(API): return witness list with latest vote count in desc order (#6373)
* add wallet draft logic * update logic * remove unused logic * revert merge import folders * fix merge import * fix review * fix format * sort witness using the same logic as in maintence * add rpc, http test * update comments * delete unused code * fix review * fix style * fix style * fix check style * add check maintenance period and throw error * add test case for throw error * fix review * update comments * fix build * fix checkstyle * fix style
1 parent f7a6748 commit cfaf4c0

File tree

15 files changed

+407
-6
lines changed

15 files changed

+407
-6
lines changed

chainbase/src/main/java/org/tron/core/store/WitnessStore.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public List<WitnessCapsule> getWitnessStandby(boolean isSortOpt) {
5555
return ret;
5656
}
5757

58-
public void sortWitnesses(List<WitnessCapsule> witnesses, boolean isSortOpt) {
58+
public static void sortWitnesses(List<WitnessCapsule> witnesses, boolean isSortOpt) {
5959
witnesses.sort(Comparator.comparingLong(WitnessCapsule::getVoteCount).reversed()
6060
.thenComparing(isSortOpt
6161
? Comparator.comparing(WitnessCapsule::createReadableString).reversed()

common/src/main/java/org/tron/core/config/Parameter.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ public class DatabaseConstants {
108108
public static final int PROPOSAL_COUNT_LIMIT_MAX = 1000;
109109
public static final int EXCHANGE_COUNT_LIMIT_MAX = 1000;
110110
public static final int MARKET_COUNT_LIMIT_MAX = 1000;
111+
public static final int WITNESS_COUNT_LIMIT_MAX = 1000;
111112
}
112113

113114
public class AdaptiveResourceLimitConstants {
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package org.tron.core.exception;
2+
3+
/**
4+
* Maintenance unavailable exception - thrown when service is in maintenance state
5+
* Please try again later
6+
*/
7+
public class MaintenanceUnavailableException extends TronException {
8+
9+
public MaintenanceUnavailableException() {
10+
super();
11+
}
12+
13+
public MaintenanceUnavailableException(String message) {
14+
super(message);
15+
}
16+
17+
public MaintenanceUnavailableException(String message, Throwable cause) {
18+
super(message, cause);
19+
}
20+
}

framework/src/main/java/org/tron/core/Wallet.java

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import static org.tron.core.config.Parameter.DatabaseConstants.EXCHANGE_COUNT_LIMIT_MAX;
3131
import static org.tron.core.config.Parameter.DatabaseConstants.MARKET_COUNT_LIMIT_MAX;
3232
import static org.tron.core.config.Parameter.DatabaseConstants.PROPOSAL_COUNT_LIMIT_MAX;
33+
import static org.tron.core.config.Parameter.DatabaseConstants.WITNESS_COUNT_LIMIT_MAX;
3334
import static org.tron.core.services.jsonrpc.JsonRpcApiUtil.parseEnergyFee;
3435
import static org.tron.core.services.jsonrpc.TronJsonRpcImpl.EARLIEST_STR;
3536
import static org.tron.core.services.jsonrpc.TronJsonRpcImpl.FINALIZED_STR;
@@ -44,6 +45,7 @@
4445
import com.google.common.collect.DiscreteDomain;
4546
import com.google.common.collect.ImmutableList;
4647
import com.google.common.collect.Lists;
48+
import com.google.common.collect.Maps;
4749
import com.google.common.collect.Range;
4850
import com.google.protobuf.ByteString;
4951
import com.google.protobuf.InvalidProtocolBufferException;
@@ -59,6 +61,7 @@
5961
import java.util.Map.Entry;
6062
import java.util.Objects;
6163
import java.util.Optional;
64+
import java.util.stream.Collectors;
6265
import lombok.Getter;
6366
import lombok.extern.slf4j.Slf4j;
6467
import org.apache.commons.lang3.ArrayUtils;
@@ -161,6 +164,7 @@
161164
import org.tron.core.capsule.TransactionInfoCapsule;
162165
import org.tron.core.capsule.TransactionResultCapsule;
163166
import org.tron.core.capsule.TransactionRetCapsule;
167+
import org.tron.core.capsule.VotesCapsule;
164168
import org.tron.core.capsule.WitnessCapsule;
165169
import org.tron.core.capsule.utils.MarketUtils;
166170
import org.tron.core.config.args.Args;
@@ -177,6 +181,7 @@
177181
import org.tron.core.exception.DupTransactionException;
178182
import org.tron.core.exception.HeaderNotFound;
179183
import org.tron.core.exception.ItemNotFoundException;
184+
import org.tron.core.exception.MaintenanceUnavailableException;
180185
import org.tron.core.exception.NonUniqueObjectException;
181186
import org.tron.core.exception.PermissionException;
182187
import org.tron.core.exception.SignatureFormatException;
@@ -201,6 +206,8 @@
201206
import org.tron.core.store.MarketPairPriceToOrderStore;
202207
import org.tron.core.store.MarketPairToPriceStore;
203208
import org.tron.core.store.StoreFactory;
209+
import org.tron.core.store.VotesStore;
210+
import org.tron.core.store.WitnessStore;
204211
import org.tron.core.utils.TransactionUtil;
205212
import org.tron.core.vm.program.Program;
206213
import org.tron.core.zen.ShieldedTRC20ParametersBuilder;
@@ -764,6 +771,107 @@ public WitnessList getWitnessList() {
764771
return builder.build();
765772
}
766773

774+
public WitnessList getPaginatedNowWitnessList(long offset, long limit) throws
775+
MaintenanceUnavailableException {
776+
if (limit <= 0 || offset < 0) {
777+
return null;
778+
}
779+
if (limit > WITNESS_COUNT_LIMIT_MAX) {
780+
limit = WITNESS_COUNT_LIMIT_MAX;
781+
}
782+
783+
/*
784+
In the maintenance period, the VoteStores will be cleared.
785+
To avoid the race condition of VoteStores deleted but Witness vote counts not updated,
786+
return retry error.
787+
*/
788+
if (chainBaseManager.getDynamicPropertiesStore().getStateFlag() == 1) {
789+
String message =
790+
"Service temporarily unavailable during maintenance period. Please try again later.";
791+
throw new MaintenanceUnavailableException(message);
792+
}
793+
// It contains the final vote count at the end of the last epoch.
794+
List<WitnessCapsule> witnessCapsuleList = chainBaseManager.getWitnessStore().getAllWitnesses();
795+
if (offset >= witnessCapsuleList.size()) {
796+
return null;
797+
}
798+
799+
VotesStore votesStore = chainBaseManager.getVotesStore();
800+
// Count the vote changes for each witness in the current epoch, it is maybe negative.
801+
Map<ByteString, Long> countWitness = countVote(votesStore);
802+
803+
// Iterate through the witness list to apply vote changes and calculate the real-time vote count
804+
witnessCapsuleList.forEach(witnessCapsule -> {
805+
long voteCount = countWitness.getOrDefault(witnessCapsule.getAddress(), 0L);
806+
witnessCapsule.setVoteCount(witnessCapsule.getVoteCount() + voteCount);
807+
});
808+
809+
// Use the same sorting logic as in the Maintenance period
810+
WitnessStore.sortWitnesses(witnessCapsuleList,
811+
chainBaseManager.getDynamicPropertiesStore().allowWitnessSortOptimization());
812+
813+
List<WitnessCapsule> sortedWitnessList = witnessCapsuleList.stream()
814+
.skip(offset)
815+
.limit(limit)
816+
.collect(Collectors.toList());
817+
818+
WitnessList.Builder builder = WitnessList.newBuilder();
819+
sortedWitnessList.forEach(witnessCapsule ->
820+
builder.addWitnesses(witnessCapsule.getInstance()));
821+
822+
return builder.build();
823+
}
824+
825+
/**
826+
* Counts vote changes for witnesses in the current epoch.
827+
*
828+
* Vote count changes are tracked as follows:
829+
* - Negative values for votes removed from previous witness in the last epoch
830+
* - Positive values for votes added to new witness in the current epoch
831+
*
832+
* Example:
833+
* an Account X had 100 votes for witness W1 in the previous epoch.
834+
* In the current epoch, X changes votes to:
835+
* - W2: 60 votes
836+
* - W3: 80 votes
837+
*
838+
* Resulting vote changes:
839+
* - W1: -100 (votes removed)
840+
* - W2: +60 (new votes)
841+
* - W3: +80 (new votes)
842+
*/
843+
private Map<ByteString, Long> countVote(VotesStore votesStore) {
844+
// Initialize a result map to store vote changes for each witness
845+
Map<ByteString, Long> countWitness = Maps.newHashMap();
846+
847+
// VotesStore is a key-value store, where the key is the address of the voter
848+
Iterator<Entry<byte[], VotesCapsule>> dbIterator = votesStore.iterator();
849+
850+
while (dbIterator.hasNext()) {
851+
Entry<byte[], VotesCapsule> next = dbIterator.next();
852+
VotesCapsule votes = next.getValue();
853+
854+
/**
855+
* VotesCapsule contains two lists:
856+
* - Old votes: Last votes from the previous epoch, updated in maintenance period
857+
* - New votes: Latest votes in current epoch, updated after each vote transaction
858+
*/
859+
votes.getOldVotes().forEach(vote -> {
860+
ByteString voteAddress = vote.getVoteAddress();
861+
long voteCount = vote.getVoteCount();
862+
countWitness.put(voteAddress,
863+
countWitness.getOrDefault(voteAddress, 0L) - voteCount);
864+
});
865+
votes.getNewVotes().forEach(vote -> {
866+
ByteString voteAddress = vote.getVoteAddress();
867+
long voteCount = vote.getVoteCount();
868+
countWitness.put(voteAddress,
869+
countWitness.getOrDefault(voteAddress, 0L) + voteCount);
870+
});
871+
}
872+
return countWitness;
873+
}
874+
767875
public ProposalList getProposalList() {
768876
ProposalList.Builder builder = ProposalList.newBuilder();
769877
List<ProposalCapsule> proposalCapsuleList =

framework/src/main/java/org/tron/core/services/RpcApiService.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@
8989
import org.tron.core.exception.ContractExeException;
9090
import org.tron.core.exception.ContractValidateException;
9191
import org.tron.core.exception.ItemNotFoundException;
92+
import org.tron.core.exception.MaintenanceUnavailableException;
9293
import org.tron.core.exception.NonUniqueObjectException;
9394
import org.tron.core.exception.StoreException;
9495
import org.tron.core.exception.VMIllegalException;
@@ -382,6 +383,18 @@ public void listWitnesses(EmptyMessage request, StreamObserver<WitnessList> resp
382383
responseObserver.onCompleted();
383384
}
384385

386+
@Override
387+
public void getPaginatedNowWitnessList(PaginatedMessage request,
388+
StreamObserver<WitnessList> responseObserver) {
389+
try {
390+
responseObserver.onNext(
391+
wallet.getPaginatedNowWitnessList(request.getOffset(), request.getLimit()));
392+
} catch (MaintenanceUnavailableException e) {
393+
responseObserver.onError(getRunTimeException(e));
394+
}
395+
responseObserver.onCompleted();
396+
}
397+
385398
@Override
386399
public void getAssetIssueList(EmptyMessage request,
387400
StreamObserver<AssetIssueList> responseObserver) {
@@ -1845,6 +1858,18 @@ public void listWitnesses(EmptyMessage request,
18451858
responseObserver.onCompleted();
18461859
}
18471860

1861+
@Override
1862+
public void getPaginatedNowWitnessList(PaginatedMessage request,
1863+
StreamObserver<WitnessList> responseObserver) {
1864+
try {
1865+
responseObserver.onNext(
1866+
wallet.getPaginatedNowWitnessList(request.getOffset(), request.getLimit()));
1867+
} catch (MaintenanceUnavailableException e) {
1868+
responseObserver.onError(getRunTimeException(e));
1869+
}
1870+
responseObserver.onCompleted();
1871+
}
1872+
18481873
@Override
18491874
public void listProposals(EmptyMessage request,
18501875
StreamObserver<ProposalList> responseObserver) {

framework/src/main/java/org/tron/core/services/http/FullNodeHttpApiService.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ public class FullNodeHttpApiService extends HttpService {
8686
@Autowired
8787
private ListWitnessesServlet listWitnessesServlet;
8888
@Autowired
89+
private GetPaginatedNowWitnessListServlet getPaginatedNowWitnessListServlet;
90+
@Autowired
8991
private GetAssetIssueListServlet getAssetIssueListServlet;
9092
@Autowired
9193
private GetPaginatedAssetIssueListServlet getPaginatedAssetIssueListServlet;
@@ -342,7 +344,11 @@ protected void addServlet(ServletContextHandler context) {
342344
context.addServlet(
343345
new ServletHolder(getTransactionCountByBlockNumServlet),
344346
"/wallet/gettransactioncountbyblocknum");
347+
// Get the list of witnesses info with contains vote counts for last epoch/maintenance
345348
context.addServlet(new ServletHolder(listWitnessesServlet), "/wallet/listwitnesses");
349+
// Get the paged list of witnesses info with realtime vote counts
350+
context.addServlet(new ServletHolder(getPaginatedNowWitnessListServlet),
351+
"/wallet/getpaginatednowwitnesslist");
346352
context.addServlet(new ServletHolder(getAssetIssueListServlet), "/wallet/getassetissuelist");
347353
context.addServlet(
348354
new ServletHolder(getPaginatedAssetIssueListServlet),
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package org.tron.core.services.http;
2+
3+
import java.io.IOException;
4+
import javax.servlet.http.HttpServletRequest;
5+
import javax.servlet.http.HttpServletResponse;
6+
import lombok.extern.slf4j.Slf4j;
7+
import org.springframework.beans.factory.annotation.Autowired;
8+
import org.springframework.stereotype.Component;
9+
import org.tron.api.GrpcAPI;
10+
import org.tron.core.Wallet;
11+
import org.tron.core.exception.MaintenanceUnavailableException;
12+
13+
// Get the paged list of witnesses info with realtime vote counts
14+
@Component
15+
@Slf4j(topic = "API")
16+
public class GetPaginatedNowWitnessListServlet extends RateLimiterServlet {
17+
18+
@Autowired
19+
private Wallet wallet;
20+
21+
protected void doGet(HttpServletRequest request, HttpServletResponse response) {
22+
try {
23+
boolean visible = Util.getVisible(request);
24+
long offset = Long.parseLong(request.getParameter("offset"));
25+
long limit = Long.parseLong(request.getParameter("limit"));
26+
fillResponse(offset, limit, visible, response);
27+
} catch (Exception e) {
28+
Util.processError(e, response);
29+
}
30+
}
31+
32+
protected void doPost(HttpServletRequest request, HttpServletResponse response) {
33+
try {
34+
PostParams params = PostParams.getPostParams(request);
35+
GrpcAPI.PaginatedMessage.Builder build = GrpcAPI.PaginatedMessage.newBuilder();
36+
JsonFormat.merge(params.getParams(), build, params.isVisible());
37+
fillResponse(build.getOffset(), build.getLimit(), params.isVisible(), response);
38+
} catch (Exception e) {
39+
Util.processError(e, response);
40+
}
41+
}
42+
43+
private void fillResponse(long offset, long limit, boolean visible, HttpServletResponse response)
44+
throws IOException, MaintenanceUnavailableException {
45+
GrpcAPI.WitnessList reply = wallet.getPaginatedNowWitnessList(offset, limit);
46+
if (reply != null) {
47+
response.getWriter().println(JsonFormat.printToString(reply, visible));
48+
} else {
49+
response.getWriter().println("{}");
50+
}
51+
}
52+
}

framework/src/main/java/org/tron/core/services/interfaceOnSolidity/RpcApiServiceOnSolidity.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,13 @@ public void listWitnesses(EmptyMessage request, StreamObserver<WitnessList> resp
162162
() -> rpcApiService.getWalletSolidityApi().listWitnesses(request, responseObserver));
163163
}
164164

165+
public void getPaginatedNowWitnessList(PaginatedMessage request,
166+
StreamObserver<WitnessList> responseObserver) {
167+
walletOnSolidity.futureGet(
168+
() -> rpcApiService.getWalletSolidityApi()
169+
.getPaginatedNowWitnessList(request, responseObserver));
170+
}
171+
165172
@Override
166173
public void getAssetIssueById(BytesMessage request,
167174
StreamObserver<AssetIssueContract> responseObserver) {
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package org.tron.core.services.interfaceOnSolidity.http;
2+
3+
import javax.servlet.http.HttpServletRequest;
4+
import javax.servlet.http.HttpServletResponse;
5+
import lombok.extern.slf4j.Slf4j;
6+
import org.springframework.beans.factory.annotation.Autowired;
7+
import org.springframework.stereotype.Component;
8+
import org.tron.core.services.http.GetPaginatedNowWitnessListServlet;
9+
import org.tron.core.services.interfaceOnSolidity.WalletOnSolidity;
10+
11+
@Component
12+
@Slf4j(topic = "API")
13+
public class GetPaginatedNowWitnessListOnSolidityServlet extends GetPaginatedNowWitnessListServlet {
14+
@Autowired
15+
private WalletOnSolidity walletOnSolidity;
16+
17+
protected void doGet(HttpServletRequest request, HttpServletResponse response) {
18+
walletOnSolidity.futureGet(() -> super.doGet(request, response));
19+
}
20+
21+
protected void doPost(HttpServletRequest request, HttpServletResponse response) {
22+
walletOnSolidity.futureGet(() -> super.doPost(request, response));
23+
}
24+
}

framework/src/main/java/org/tron/core/services/interfaceOnSolidity/http/solidity/HttpApiOnSolidityService.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
import java.util.EnumSet;
44
import javax.servlet.DispatcherType;
55
import lombok.extern.slf4j.Slf4j;
6-
import org.eclipse.jetty.server.ConnectionLimit;
7-
import org.eclipse.jetty.server.Server;
86
import org.eclipse.jetty.servlet.FilterHolder;
97
import org.eclipse.jetty.servlet.ServletContextHandler;
108
import org.eclipse.jetty.servlet.ServletHolder;
@@ -46,6 +44,7 @@
4644
import org.tron.core.services.interfaceOnSolidity.http.GetNodeInfoOnSolidityServlet;
4745
import org.tron.core.services.interfaceOnSolidity.http.GetNowBlockOnSolidityServlet;
4846
import org.tron.core.services.interfaceOnSolidity.http.GetPaginatedAssetIssueListOnSolidityServlet;
47+
import org.tron.core.services.interfaceOnSolidity.http.GetPaginatedNowWitnessListOnSolidityServlet;
4948
import org.tron.core.services.interfaceOnSolidity.http.GetRewardOnSolidityServlet;
5049
import org.tron.core.services.interfaceOnSolidity.http.GetTransactionCountByBlockNumOnSolidityServlet;
5150
import org.tron.core.services.interfaceOnSolidity.http.GetTransactionInfoByBlockNumOnSolidityServlet;
@@ -60,7 +59,6 @@
6059
import org.tron.core.services.interfaceOnSolidity.http.ScanShieldedTRC20NotesByOvkOnSolidityServlet;
6160
import org.tron.core.services.interfaceOnSolidity.http.TriggerConstantContractOnSolidityServlet;
6261

63-
6462
@Slf4j(topic = "API")
6563
public class HttpApiOnSolidityService extends HttpService {
6664

@@ -74,6 +72,8 @@ public class HttpApiOnSolidityService extends HttpService {
7472
@Autowired
7573
private ListWitnessesOnSolidityServlet listWitnessesOnSolidityServlet;
7674
@Autowired
75+
private GetPaginatedNowWitnessListOnSolidityServlet getPaginatedNowWitnessListOnSolidityServlet;
76+
@Autowired
7777
private GetAssetIssueListOnSolidityServlet getAssetIssueListOnSolidityServlet;
7878
@Autowired
7979
private GetPaginatedAssetIssueListOnSolidityServlet getPaginatedAssetIssueListOnSolidityServlet;
@@ -189,6 +189,8 @@ protected void addServlet(ServletContextHandler context) {
189189
context.addServlet(new ServletHolder(accountOnSolidityServlet), "/walletsolidity/getaccount");
190190
context.addServlet(new ServletHolder(listWitnessesOnSolidityServlet),
191191
"/walletsolidity/listwitnesses");
192+
context.addServlet(new ServletHolder(getPaginatedNowWitnessListOnSolidityServlet),
193+
"/walletsolidity/getpaginatednowwitnesslist");
192194
context.addServlet(new ServletHolder(getAssetIssueListOnSolidityServlet),
193195
"/walletsolidity/getassetissuelist");
194196
context.addServlet(new ServletHolder(getPaginatedAssetIssueListOnSolidityServlet),

0 commit comments

Comments
 (0)