mirror of
https://github.com/bisq-network/bisq.git
synced 2024-11-19 01:41:11 +01:00
Support for persisted bsqState as resource file
- Move DecryptedBallotsWithMerits list and EvaluatedProposal list to BsqState - Use StoreService for handling persistence of snapshots
This commit is contained in:
parent
8e46b1760f
commit
a308469ac6
@ -936,8 +936,7 @@ message PersistableEnvelope {
|
||||
MeritList merit_list = 23;
|
||||
BondedRoleList bonded_role_list = 24;
|
||||
RemovedAssetList removed_asset_list = 25;
|
||||
EvaluatedProposalList evaluated_proposal_list = 26;
|
||||
DecryptedBallotsWithMeritsList decrypted_ballots_with_merits_list = 27;
|
||||
DaoStateStore dao_state_store = 28;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1460,6 +1459,8 @@ message BsqState {
|
||||
map<string, BaseTxOutput> confiscated_tx_output_map = 7;
|
||||
map<string, SpentInfo> spent_info_map = 8;
|
||||
repeated ParamChange param_change_list = 9;
|
||||
repeated EvaluatedProposal evaluated_proposal_list = 10;
|
||||
repeated DecryptedBallotsWithMerits decrypted_ballots_with_merits_list = 11;
|
||||
}
|
||||
|
||||
message Issuance {
|
||||
@ -1647,21 +1648,17 @@ message EvaluatedProposal {
|
||||
int64 required_threshold = 4;
|
||||
}
|
||||
|
||||
message EvaluatedProposalList {
|
||||
repeated EvaluatedProposal evaluated_proposal = 1;
|
||||
}
|
||||
|
||||
message DecryptedBallotsWithMerits {
|
||||
bytes hash_of_blind_vote_list = 1;
|
||||
string vote_reveal_tx_id = 2;
|
||||
string blind_vote_tx_id = 3;
|
||||
string blind_vote_tx_id = 2;
|
||||
string vote_reveal_tx_id = 3;
|
||||
int64 stake = 4;
|
||||
BallotList ballot_list = 5;
|
||||
MeritList merit_list = 6;
|
||||
}
|
||||
|
||||
message DecryptedBallotsWithMeritsList {
|
||||
repeated DecryptedBallotsWithMerits decrypted_ballots_with_merits = 1;
|
||||
message DaoStateStore {
|
||||
BsqState bsq_state = 1;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -23,7 +23,6 @@ import bisq.core.dao.governance.ballot.BallotListService;
|
||||
import bisq.core.dao.governance.blindvote.MyBlindVoteListService;
|
||||
import bisq.core.dao.governance.myvote.MyVoteListService;
|
||||
import bisq.core.dao.governance.role.BondedRolesService;
|
||||
import bisq.core.dao.governance.voteresult.VoteResultService;
|
||||
import bisq.core.filter.FilterManager;
|
||||
import bisq.core.payment.AccountAgeWitnessService;
|
||||
import bisq.core.trade.statistics.TradeStatisticsManager;
|
||||
@ -53,8 +52,7 @@ public class AppSetupWithP2PAndDAO extends AppSetupWithP2P {
|
||||
BallotListService ballotListService,
|
||||
MyBlindVoteListService myBlindVoteListService,
|
||||
BondedRolesService bondedRolesService,
|
||||
AssetService assetService,
|
||||
VoteResultService voteResultService) {
|
||||
AssetService assetService) {
|
||||
super(encryptionService,
|
||||
keyRing,
|
||||
p2PService,
|
||||
@ -69,7 +67,6 @@ public class AppSetupWithP2PAndDAO extends AppSetupWithP2P {
|
||||
persistedDataHosts.add(myBlindVoteListService);
|
||||
persistedDataHosts.add(bondedRolesService);
|
||||
persistedDataHosts.add(assetService);
|
||||
persistedDataHosts.add(voteResultService);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -65,8 +65,9 @@ import bisq.core.dao.node.parser.BlockParser;
|
||||
import bisq.core.dao.node.parser.TxParser;
|
||||
import bisq.core.dao.state.BsqState;
|
||||
import bisq.core.dao.state.BsqStateService;
|
||||
import bisq.core.dao.state.DaoStateSnapshotService;
|
||||
import bisq.core.dao.state.DaoStateStorageService;
|
||||
import bisq.core.dao.state.GenesisTxInfo;
|
||||
import bisq.core.dao.state.SnapshotManager;
|
||||
import bisq.core.dao.state.period.CycleService;
|
||||
import bisq.core.dao.state.period.PeriodService;
|
||||
|
||||
@ -103,7 +104,9 @@ public class DaoModule extends AppModule {
|
||||
bind(GenesisTxInfo.class).in(Singleton.class);
|
||||
bind(BsqState.class).in(Singleton.class);
|
||||
bind(BsqStateService.class).in(Singleton.class);
|
||||
bind(SnapshotManager.class).in(Singleton.class);
|
||||
bind(DaoStateSnapshotService.class).in(Singleton.class);
|
||||
bind(DaoStateStorageService.class).in(Singleton.class);
|
||||
|
||||
bind(ExportJsonFilesService.class).in(Singleton.class);
|
||||
|
||||
// Period
|
||||
|
@ -43,17 +43,17 @@ import lombok.extern.slf4j.Slf4j;
|
||||
@Value
|
||||
public class DecryptedBallotsWithMerits implements PersistablePayload {
|
||||
private final byte[] hashOfBlindVoteList;
|
||||
private final String voteRevealTxId; // not used yet but keep it for now
|
||||
private final String blindVoteTxId; // not used yet but keep it for now
|
||||
private final String blindVoteTxId;
|
||||
private final String voteRevealTxId;
|
||||
private final long stake;
|
||||
private final BallotList ballotList;
|
||||
private final MeritList meritList;
|
||||
|
||||
DecryptedBallotsWithMerits(byte[] hashOfBlindVoteList, String voteRevealTxId, String blindVoteTxId, long stake,
|
||||
BallotList ballotList, MeritList meritList) {
|
||||
public DecryptedBallotsWithMerits(byte[] hashOfBlindVoteList, String blindVoteTxId, String voteRevealTxId, long stake,
|
||||
BallotList ballotList, MeritList meritList) {
|
||||
this.hashOfBlindVoteList = hashOfBlindVoteList;
|
||||
this.voteRevealTxId = voteRevealTxId;
|
||||
this.blindVoteTxId = blindVoteTxId;
|
||||
this.voteRevealTxId = voteRevealTxId;
|
||||
this.stake = stake;
|
||||
this.ballotList = ballotList;
|
||||
this.meritList = meritList;
|
||||
@ -66,20 +66,23 @@ public class DecryptedBallotsWithMerits implements PersistablePayload {
|
||||
|
||||
@Override
|
||||
public PB.DecryptedBallotsWithMerits toProtoMessage() {
|
||||
PB.DecryptedBallotsWithMerits.Builder builder = PB.DecryptedBallotsWithMerits.newBuilder()
|
||||
return getBuilder().build();
|
||||
}
|
||||
|
||||
public PB.DecryptedBallotsWithMerits.Builder getBuilder() {
|
||||
return PB.DecryptedBallotsWithMerits.newBuilder()
|
||||
.setHashOfBlindVoteList(ByteString.copyFrom(hashOfBlindVoteList))
|
||||
.setVoteRevealTxId(voteRevealTxId)
|
||||
.setBlindVoteTxId(blindVoteTxId)
|
||||
.setVoteRevealTxId(voteRevealTxId)
|
||||
.setStake(stake)
|
||||
.setBallotList(ballotList.getBuilder())
|
||||
.setMeritList(meritList.getBuilder());
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
public static DecryptedBallotsWithMerits fromProto(PB.DecryptedBallotsWithMerits proto) {
|
||||
return new DecryptedBallotsWithMerits(proto.getHashOfBlindVoteList().toByteArray(),
|
||||
proto.getVoteRevealTxId(),
|
||||
proto.getBlindVoteTxId(),
|
||||
proto.getVoteRevealTxId(),
|
||||
proto.getStake(),
|
||||
BallotList.fromProto(proto.getBallotList()),
|
||||
MeritList.fromProto(proto.getMeritList()));
|
||||
@ -105,8 +108,8 @@ public class DecryptedBallotsWithMerits implements PersistablePayload {
|
||||
public String toString() {
|
||||
return "DecryptedBallotsWithMerits{" +
|
||||
"\n hashOfBlindVoteList=" + Utilities.bytesAsHexString(hashOfBlindVoteList) +
|
||||
",\n voteRevealTxId='" + voteRevealTxId + '\'' +
|
||||
",\n blindVoteTxId='" + blindVoteTxId + '\'' +
|
||||
",\n voteRevealTxId='" + voteRevealTxId + '\'' +
|
||||
",\n stake=" + stake +
|
||||
",\n ballotList=" + ballotList +
|
||||
",\n meritList=" + meritList +
|
||||
|
@ -1,76 +0,0 @@
|
||||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.core.dao.governance.voteresult;
|
||||
|
||||
import bisq.core.dao.governance.ConsensusCritical;
|
||||
|
||||
import bisq.common.proto.persistable.PersistableList;
|
||||
|
||||
import io.bisq.generated.protobuffer.PB;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* PersistableEnvelope wrapper for list of decryptedBallotsWithMerits.
|
||||
*/
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class DecryptedBallotsWithMeritsList extends PersistableList<DecryptedBallotsWithMerits> implements ConsensusCritical {
|
||||
|
||||
public DecryptedBallotsWithMeritsList(List<DecryptedBallotsWithMerits> list) {
|
||||
super(list);
|
||||
}
|
||||
|
||||
public DecryptedBallotsWithMeritsList() {
|
||||
super();
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// PROTO BUFFER
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public PB.PersistableEnvelope toProtoMessage() {
|
||||
return PB.PersistableEnvelope.newBuilder().setDecryptedBallotsWithMeritsList(getBuilder()).build();
|
||||
}
|
||||
|
||||
private PB.DecryptedBallotsWithMeritsList.Builder getBuilder() {
|
||||
return PB.DecryptedBallotsWithMeritsList.newBuilder()
|
||||
.addAllDecryptedBallotsWithMerits(getList().stream()
|
||||
.map(DecryptedBallotsWithMerits::toProtoMessage)
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
public static DecryptedBallotsWithMeritsList fromProto(PB.DecryptedBallotsWithMeritsList proto) {
|
||||
return new DecryptedBallotsWithMeritsList(new ArrayList<>(proto.getDecryptedBallotsWithMeritsList().stream()
|
||||
.map(DecryptedBallotsWithMerits::fromProto)
|
||||
.collect(Collectors.toList())));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "List of blindVoteTxId's in DecryptedBallotsWithMeritsList: " + getList().stream()
|
||||
.map(DecryptedBallotsWithMerits::getBlindVoteTxId)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
@ -29,6 +29,8 @@ import lombok.Value;
|
||||
public class EvaluatedProposal implements PersistablePayload {
|
||||
private final boolean isAccepted;
|
||||
private final ProposalVoteResult proposalVoteResult;
|
||||
|
||||
//TODO remove - can be derived from params
|
||||
private final long requiredQuorum;
|
||||
private final long requiredThreshold;
|
||||
|
||||
|
@ -1,76 +0,0 @@
|
||||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.core.dao.governance.voteresult;
|
||||
|
||||
import bisq.core.dao.governance.ConsensusCritical;
|
||||
|
||||
import bisq.common.proto.persistable.PersistableList;
|
||||
|
||||
import io.bisq.generated.protobuffer.PB;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* PersistableEnvelope wrapper for list of evaluatedProposals.
|
||||
*/
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class EvaluatedProposalList extends PersistableList<EvaluatedProposal> implements ConsensusCritical {
|
||||
|
||||
public EvaluatedProposalList(List<EvaluatedProposal> list) {
|
||||
super(list);
|
||||
}
|
||||
|
||||
public EvaluatedProposalList() {
|
||||
super();
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// PROTO BUFFER
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public PB.PersistableEnvelope toProtoMessage() {
|
||||
return PB.PersistableEnvelope.newBuilder().setEvaluatedProposalList(getBuilder()).build();
|
||||
}
|
||||
|
||||
public PB.EvaluatedProposalList.Builder getBuilder() {
|
||||
return PB.EvaluatedProposalList.newBuilder()
|
||||
.addAllEvaluatedProposal(getList().stream()
|
||||
.map(EvaluatedProposal::toProtoMessage)
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
public static EvaluatedProposalList fromProto(PB.EvaluatedProposalList proto) {
|
||||
return new EvaluatedProposalList(new ArrayList<>(proto.getEvaluatedProposalList().stream()
|
||||
.map(EvaluatedProposal::fromProto)
|
||||
.collect(Collectors.toList())));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "List of proposalTxId's in EvaluatedProposalList: " + getList().stream()
|
||||
.map(EvaluatedProposal::getProposalTxId)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,6 @@
|
||||
|
||||
package bisq.core.dao.governance.voteresult;
|
||||
|
||||
import bisq.core.app.BisqEnvironment;
|
||||
import bisq.core.dao.DaoSetupService;
|
||||
import bisq.core.dao.governance.asset.AssetService;
|
||||
import bisq.core.dao.governance.ballot.Ballot;
|
||||
@ -56,8 +55,6 @@ import bisq.core.locale.CurrencyUtil;
|
||||
|
||||
import bisq.network.p2p.storage.P2PDataStorage;
|
||||
|
||||
import bisq.common.proto.persistable.PersistedDataHost;
|
||||
import bisq.common.storage.Storage;
|
||||
import bisq.common.util.Utilities;
|
||||
|
||||
import javax.inject.Inject;
|
||||
@ -95,7 +92,7 @@ import static com.google.common.base.Preconditions.checkArgument;
|
||||
* the missing blindVotes from the network.
|
||||
*/
|
||||
@Slf4j
|
||||
public class VoteResultService implements BsqStateListener, DaoSetupService, PersistedDataHost {
|
||||
public class VoteResultService implements BsqStateListener, DaoSetupService {
|
||||
private final VoteRevealService voteRevealService;
|
||||
private final ProposalListPresentation proposalListPresentation;
|
||||
private final BsqStateService bsqStateService;
|
||||
@ -109,13 +106,6 @@ public class VoteResultService implements BsqStateListener, DaoSetupService, Per
|
||||
@Getter
|
||||
private final ObservableList<VoteResultException> voteResultExceptions = FXCollections.observableArrayList();
|
||||
|
||||
private final Storage<EvaluatedProposalList> evaluatedProposalStorage;
|
||||
private Storage<DecryptedBallotsWithMeritsList> decryptedBallotsWithMeritsStorage;
|
||||
@Getter
|
||||
private final EvaluatedProposalList evaluatedProposalList = new EvaluatedProposalList();
|
||||
@Getter
|
||||
private final DecryptedBallotsWithMeritsList decryptedBallotsWithMeritsList = new DecryptedBallotsWithMeritsList();
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor
|
||||
@ -131,9 +121,7 @@ public class VoteResultService implements BsqStateListener, DaoSetupService, Per
|
||||
BondedRolesService bondedRolesService,
|
||||
IssuanceService issuanceService,
|
||||
AssetService assetService,
|
||||
MissingDataRequestService missingDataRequestService,
|
||||
Storage<EvaluatedProposalList> evaluatedProposalStorage,
|
||||
Storage<DecryptedBallotsWithMeritsList> decryptedBallotsWithMeritsStorage) {
|
||||
MissingDataRequestService missingDataRequestService) {
|
||||
this.voteRevealService = voteRevealService;
|
||||
this.proposalListPresentation = proposalListPresentation;
|
||||
this.bsqStateService = bsqStateService;
|
||||
@ -144,30 +132,6 @@ public class VoteResultService implements BsqStateListener, DaoSetupService, Per
|
||||
this.issuanceService = issuanceService;
|
||||
this.assetService = assetService;
|
||||
this.missingDataRequestService = missingDataRequestService;
|
||||
this.evaluatedProposalStorage = evaluatedProposalStorage;
|
||||
this.decryptedBallotsWithMeritsStorage = decryptedBallotsWithMeritsStorage;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// PersistedDataHost
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public void readPersisted() {
|
||||
if (BisqEnvironment.isDAOActivatedAndBaseCurrencySupportingBsq()) {
|
||||
EvaluatedProposalList persisted1 = evaluatedProposalStorage.initAndGetPersisted(evaluatedProposalList, 100);
|
||||
if (persisted1 != null) {
|
||||
evaluatedProposalList.clear();
|
||||
evaluatedProposalList.addAll(persisted1.getList());
|
||||
}
|
||||
|
||||
DecryptedBallotsWithMeritsList persisted2 = decryptedBallotsWithMeritsStorage.initAndGetPersisted(decryptedBallotsWithMeritsList, 100);
|
||||
if (persisted2 != null) {
|
||||
decryptedBallotsWithMeritsList.clear();
|
||||
decryptedBallotsWithMeritsList.addAll(persisted2.getList());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -213,9 +177,8 @@ public class VoteResultService implements BsqStateListener, DaoSetupService, Per
|
||||
if (isInVoteResultPhase(chainHeight)) {
|
||||
Set<DecryptedBallotsWithMerits> decryptedBallotsWithMeritsSet = getDecryptedBallotsWithMeritsSet(chainHeight);
|
||||
decryptedBallotsWithMeritsSet.stream()
|
||||
.filter(e -> !decryptedBallotsWithMeritsList.getList().contains(e))
|
||||
.forEach(decryptedBallotsWithMeritsList::add);
|
||||
persistDecryptedBallotsWithMerits();
|
||||
.filter(e -> !bsqStateService.getDecryptedBallotsWithMeritsList().contains(e))
|
||||
.forEach(bsqStateService.getDecryptedBallotsWithMeritsList()::add);
|
||||
|
||||
if (!decryptedBallotsWithMeritsSet.isEmpty()) {
|
||||
// From the decryptedBallotsWithMerits we create a map with the hash of the blind vote list as key and the
|
||||
@ -244,9 +207,8 @@ public class VoteResultService implements BsqStateListener, DaoSetupService, Per
|
||||
applyAcceptedProposals(acceptedEvaluatedProposals, chainHeight);
|
||||
|
||||
evaluatedProposals.stream()
|
||||
.filter(e -> !evaluatedProposalList.getList().contains(e))
|
||||
.forEach(evaluatedProposalList::add);
|
||||
persistEvaluatedProposals();
|
||||
.filter(e -> !bsqStateService.getEvaluatedProposalList().contains(e))
|
||||
.forEach(bsqStateService.getEvaluatedProposalList()::add);
|
||||
log.info("processAllVoteResults completed");
|
||||
} else {
|
||||
log.warn("Our list of received blind votes do not match the list from the majority of voters.");
|
||||
@ -303,8 +265,6 @@ public class VoteResultService implements BsqStateListener, DaoSetupService, Per
|
||||
Tx blindVoteTx = VoteResultConsensus.getBlindVoteTx(blindVoteStakeOutput, bsqStateService, periodService, chainHeight);
|
||||
String blindVoteTxId = blindVoteTx.getId();
|
||||
|
||||
// Here we deal with eventual consistency of the p2p network data!
|
||||
// TODO make more clear we are in p2p domain now
|
||||
List<BlindVote> blindVoteList = BlindVoteConsensus.getSortedBlindVoteListOfCycle(blindVoteListService);
|
||||
Optional<BlindVote> optionalBlindVote = blindVoteList.stream()
|
||||
.filter(blindVote -> blindVote.getTxId().equals(blindVoteTxId))
|
||||
@ -318,9 +278,8 @@ public class VoteResultService implements BsqStateListener, DaoSetupService, Per
|
||||
// voteWithProposalTxIdList and create a ballot list with the proposal and the vote from
|
||||
// the voteWithProposalTxIdList
|
||||
BallotList ballotList = createBallotList(voteWithProposalTxIdList);
|
||||
return new DecryptedBallotsWithMerits(hashOfBlindVoteList, voteRevealTxId, blindVoteTxId, blindVoteStake, ballotList, meritList);
|
||||
return new DecryptedBallotsWithMerits(hashOfBlindVoteList, blindVoteTxId, voteRevealTxId, blindVoteStake, ballotList, meritList);
|
||||
} catch (VoteResultException.MissingBallotException missingBallotException) {
|
||||
//TODO handle case that we are missing proposals
|
||||
log.warn("We are missing proposals to create the vote result: " + missingBallotException.toString());
|
||||
missingDataRequestService.addVoteResultException(missingBallotException);
|
||||
voteResultExceptions.add(missingBallotException);
|
||||
@ -331,8 +290,7 @@ public class VoteResultService implements BsqStateListener, DaoSetupService, Per
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
//TODO handle recovering
|
||||
log.warn("We have a blindVoteTx but we do not have the corresponding blindVote in our local list.\n" +
|
||||
log.warn("We have a blindVoteTx but we do not have the corresponding blindVote payload in our local database.\n" +
|
||||
"That can happen if the blindVote item was not properly broadcast. We will go on " +
|
||||
"and see if that blindVote was part of the majority data view. If so we should " +
|
||||
"recover the missing blind vote by a request to our peers. blindVoteTxId={}", blindVoteTxId);
|
||||
@ -738,14 +696,6 @@ public class VoteResultService implements BsqStateListener, DaoSetupService, Per
|
||||
return periodService.getFirstBlockOfPhase(chainHeight, DaoPhase.Phase.RESULT) == chainHeight;
|
||||
}
|
||||
|
||||
private void persistEvaluatedProposals() {
|
||||
evaluatedProposalStorage.queueUpForSave(20);
|
||||
}
|
||||
|
||||
private void persistDecryptedBallotsWithMerits() {
|
||||
decryptedBallotsWithMeritsStorage.queueUpForSave(20);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Inner classes
|
||||
|
@ -20,7 +20,7 @@ package bisq.core.dao.node;
|
||||
import bisq.core.dao.DaoSetupService;
|
||||
import bisq.core.dao.node.parser.BlockParser;
|
||||
import bisq.core.dao.state.BsqStateService;
|
||||
import bisq.core.dao.state.SnapshotManager;
|
||||
import bisq.core.dao.state.DaoStateSnapshotService;
|
||||
import bisq.core.dao.state.blockchain.RawBlock;
|
||||
|
||||
import bisq.network.p2p.P2PService;
|
||||
@ -45,7 +45,7 @@ public abstract class BsqNode implements DaoSetupService {
|
||||
protected final BsqStateService bsqStateService;
|
||||
private final String genesisTxId;
|
||||
private final int genesisBlockHeight;
|
||||
private final SnapshotManager snapshotManager;
|
||||
private final DaoStateSnapshotService daoStateSnapshotService;
|
||||
private final P2PServiceListener p2PServiceListener;
|
||||
protected boolean parseBlockchainComplete;
|
||||
protected boolean p2pNetworkReady;
|
||||
@ -60,11 +60,11 @@ public abstract class BsqNode implements DaoSetupService {
|
||||
@Inject
|
||||
public BsqNode(BlockParser blockParser,
|
||||
BsqStateService bsqStateService,
|
||||
SnapshotManager snapshotManager,
|
||||
DaoStateSnapshotService daoStateSnapshotService,
|
||||
P2PService p2PService) {
|
||||
this.blockParser = blockParser;
|
||||
this.bsqStateService = bsqStateService;
|
||||
this.snapshotManager = snapshotManager;
|
||||
this.daoStateSnapshotService = daoStateSnapshotService;
|
||||
this.p2PService = p2PService;
|
||||
|
||||
genesisTxId = bsqStateService.getGenesisTxId();
|
||||
@ -201,6 +201,6 @@ public abstract class BsqNode implements DaoSetupService {
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private void applySnapshot() {
|
||||
snapshotManager.applySnapshot();
|
||||
daoStateSnapshotService.applySnapshot();
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ import bisq.core.dao.node.json.ExportJsonFilesService;
|
||||
import bisq.core.dao.node.parser.BlockParser;
|
||||
import bisq.core.dao.node.parser.exceptions.BlockNotConnectingException;
|
||||
import bisq.core.dao.state.BsqStateService;
|
||||
import bisq.core.dao.state.SnapshotManager;
|
||||
import bisq.core.dao.state.DaoStateSnapshotService;
|
||||
import bisq.core.dao.state.blockchain.Block;
|
||||
|
||||
import bisq.network.p2p.P2PService;
|
||||
@ -61,12 +61,12 @@ public class FullNode extends BsqNode {
|
||||
@Inject
|
||||
public FullNode(BlockParser blockParser,
|
||||
BsqStateService bsqStateService,
|
||||
SnapshotManager snapshotManager,
|
||||
DaoStateSnapshotService daoStateSnapshotService,
|
||||
P2PService p2PService,
|
||||
RpcService rpcService,
|
||||
ExportJsonFilesService exportJsonFilesService,
|
||||
FullNodeNetworkService fullNodeNetworkService) {
|
||||
super(blockParser, bsqStateService, snapshotManager, p2PService);
|
||||
super(blockParser, bsqStateService, daoStateSnapshotService, p2PService);
|
||||
this.rpcService = rpcService;
|
||||
|
||||
this.exportJsonFilesService = exportJsonFilesService;
|
||||
|
@ -24,7 +24,7 @@ import bisq.core.dao.node.messages.NewBlockBroadcastMessage;
|
||||
import bisq.core.dao.node.parser.BlockParser;
|
||||
import bisq.core.dao.node.parser.exceptions.BlockNotConnectingException;
|
||||
import bisq.core.dao.state.BsqStateService;
|
||||
import bisq.core.dao.state.SnapshotManager;
|
||||
import bisq.core.dao.state.DaoStateSnapshotService;
|
||||
import bisq.core.dao.state.blockchain.RawBlock;
|
||||
|
||||
import bisq.network.p2p.P2PService;
|
||||
@ -56,10 +56,10 @@ public class LiteNode extends BsqNode {
|
||||
@Inject
|
||||
public LiteNode(BlockParser blockParser,
|
||||
BsqStateService bsqStateService,
|
||||
SnapshotManager snapshotManager,
|
||||
DaoStateSnapshotService daoStateSnapshotService,
|
||||
P2PService p2PService,
|
||||
LiteNodeNetworkService liteNodeNetworkService) {
|
||||
super(blockParser, bsqStateService, snapshotManager, p2PService);
|
||||
super(blockParser, bsqStateService, daoStateSnapshotService, p2PService);
|
||||
|
||||
this.liteNodeNetworkService = liteNodeNetworkService;
|
||||
}
|
||||
|
@ -17,6 +17,8 @@
|
||||
|
||||
package bisq.core.dao.state;
|
||||
|
||||
import bisq.core.dao.governance.voteresult.DecryptedBallotsWithMerits;
|
||||
import bisq.core.dao.governance.voteresult.EvaluatedProposal;
|
||||
import bisq.core.dao.state.blockchain.Block;
|
||||
import bisq.core.dao.state.blockchain.SpentInfo;
|
||||
import bisq.core.dao.state.blockchain.TxOutput;
|
||||
@ -25,7 +27,7 @@ import bisq.core.dao.state.governance.Issuance;
|
||||
import bisq.core.dao.state.governance.ParamChange;
|
||||
import bisq.core.dao.state.period.Cycle;
|
||||
|
||||
import bisq.common.proto.persistable.PersistableEnvelope;
|
||||
import bisq.common.proto.persistable.PersistablePayload;
|
||||
|
||||
import io.bisq.generated.protobuffer.PB;
|
||||
|
||||
@ -47,9 +49,11 @@ import lombok.extern.slf4j.Slf4j;
|
||||
/**
|
||||
* Root class for mutable state of the DAO.
|
||||
* Holds both blockchain data as well as data derived from the governance process (voting).
|
||||
* <p>
|
||||
* One BSQ block with empty txs adds 152 bytes which results in about 8 MB/year
|
||||
*/
|
||||
@Slf4j
|
||||
public class BsqState implements PersistableEnvelope {
|
||||
public class BsqState implements PersistablePayload {
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Fields
|
||||
@ -78,6 +82,14 @@ public class BsqState implements PersistableEnvelope {
|
||||
@Getter
|
||||
private final List<ParamChange> paramChangeList;
|
||||
|
||||
// Vote result data
|
||||
// All evaluated proposals which get added at the result phase
|
||||
@Getter
|
||||
private final List<EvaluatedProposal> evaluatedProposalList;
|
||||
// All voting data which get added at the result phase
|
||||
@Getter
|
||||
private final List<DecryptedBallotsWithMerits> decryptedBallotsWithMeritsList;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor
|
||||
@ -93,6 +105,8 @@ public class BsqState implements PersistableEnvelope {
|
||||
new HashMap<>(),
|
||||
new HashMap<>(),
|
||||
new HashMap<>(),
|
||||
new ArrayList<>(),
|
||||
new ArrayList<>(),
|
||||
new ArrayList<>()
|
||||
);
|
||||
}
|
||||
@ -110,7 +124,9 @@ public class BsqState implements PersistableEnvelope {
|
||||
Map<TxOutputKey, SpentInfo> spentInfoMap,
|
||||
Map<TxOutputKey, TxOutput> confiscatedTxOutputMap,
|
||||
Map<String, Issuance> issuanceMap,
|
||||
List<ParamChange> paramChangeList) {
|
||||
List<ParamChange> paramChangeList,
|
||||
List<EvaluatedProposal> evaluatedProposalList,
|
||||
List<DecryptedBallotsWithMerits> decryptedBallotsWithMeritsList) {
|
||||
this.chainHeight = chainHeight;
|
||||
this.blocks = blocks;
|
||||
this.cycles = cycles;
|
||||
@ -122,14 +138,16 @@ public class BsqState implements PersistableEnvelope {
|
||||
this.confiscatedTxOutputMap = confiscatedTxOutputMap;
|
||||
this.issuanceMap = issuanceMap;
|
||||
this.paramChangeList = paramChangeList;
|
||||
this.evaluatedProposalList = evaluatedProposalList;
|
||||
this.decryptedBallotsWithMeritsList = decryptedBallotsWithMeritsList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message toProtoMessage() {
|
||||
return PB.PersistableEnvelope.newBuilder().setBsqState(getBsqStateBuilder()).build();
|
||||
return getBsqStateBuilder().build();
|
||||
}
|
||||
|
||||
private PB.BsqState.Builder getBsqStateBuilder() {
|
||||
public PB.BsqState.Builder getBsqStateBuilder() {
|
||||
final PB.BsqState.Builder builder = PB.BsqState.newBuilder();
|
||||
builder.setChainHeight(chainHeight)
|
||||
.addAllBlocks(blocks.stream().map(Block::toProtoMessage).collect(Collectors.toList()))
|
||||
@ -144,15 +162,17 @@ public class BsqState implements PersistableEnvelope {
|
||||
.collect(Collectors.toMap(e -> e.getKey().toString(), e -> e.getValue().toProtoMessage())))
|
||||
.putAllIssuanceMap(issuanceMap.entrySet().stream()
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().toProtoMessage())))
|
||||
.addAllParamChangeList(paramChangeList.stream().map(ParamChange::toProtoMessage).collect(Collectors.toList()));
|
||||
.addAllParamChangeList(paramChangeList.stream().map(ParamChange::toProtoMessage).collect(Collectors.toList()))
|
||||
.addAllEvaluatedProposalList(evaluatedProposalList.stream().map(EvaluatedProposal::toProtoMessage).collect(Collectors.toList()))
|
||||
.addAllDecryptedBallotsWithMeritsList(decryptedBallotsWithMeritsList.stream().map(DecryptedBallotsWithMerits::toProtoMessage).collect(Collectors.toList()));
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static PersistableEnvelope fromProto(PB.BsqState proto) {
|
||||
public static BsqState fromProto(PB.BsqState proto) {
|
||||
LinkedList<Block> blocks = proto.getBlocksList().stream()
|
||||
.map(Block::fromProto)
|
||||
.collect(Collectors.toCollection(LinkedList::new));
|
||||
final LinkedList<Cycle> cycles = proto.getCyclesList().stream()
|
||||
LinkedList<Cycle> cycles = proto.getCyclesList().stream()
|
||||
.map(Cycle::fromProto).collect(Collectors.toCollection(LinkedList::new));
|
||||
Map<TxOutputKey, TxOutput> unspentTxOutputMap = proto.getUnspentTxOutputMapMap().entrySet().stream()
|
||||
.collect(Collectors.toMap(e -> TxOutputKey.getKeyFromString(e.getKey()), e -> TxOutput.fromProto(e.getValue())));
|
||||
@ -164,8 +184,12 @@ public class BsqState implements PersistableEnvelope {
|
||||
.collect(Collectors.toMap(e -> TxOutputKey.getKeyFromString(e.getKey()), e -> TxOutput.fromProto(e.getValue())));
|
||||
Map<String, Issuance> issuanceMap = proto.getIssuanceMapMap().entrySet().stream()
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, e -> Issuance.fromProto(e.getValue())));
|
||||
final List<ParamChange> paramChangeList = proto.getParamChangeListList().stream()
|
||||
List<ParamChange> paramChangeList = proto.getParamChangeListList().stream()
|
||||
.map(ParamChange::fromProto).collect(Collectors.toCollection(ArrayList::new));
|
||||
List<EvaluatedProposal> evaluatedProposalList = proto.getEvaluatedProposalListList().stream()
|
||||
.map(EvaluatedProposal::fromProto).collect(Collectors.toCollection(ArrayList::new));
|
||||
List<DecryptedBallotsWithMerits> decryptedBallotsWithMeritsList = proto.getDecryptedBallotsWithMeritsListList().stream()
|
||||
.map(DecryptedBallotsWithMerits::fromProto).collect(Collectors.toCollection(ArrayList::new));
|
||||
return new BsqState(proto.getChainHeight(),
|
||||
blocks,
|
||||
cycles,
|
||||
@ -174,7 +198,9 @@ public class BsqState implements PersistableEnvelope {
|
||||
spentInfoMap,
|
||||
confiscatedTxOutputMap,
|
||||
issuanceMap,
|
||||
paramChangeList);
|
||||
paramChangeList,
|
||||
evaluatedProposalList,
|
||||
decryptedBallotsWithMeritsList);
|
||||
}
|
||||
|
||||
|
||||
@ -187,10 +213,10 @@ public class BsqState implements PersistableEnvelope {
|
||||
}
|
||||
|
||||
BsqState getClone() {
|
||||
return (BsqState) BsqState.fromProto(getBsqStateBuilder().build());
|
||||
return BsqState.fromProto(getBsqStateBuilder().build());
|
||||
}
|
||||
|
||||
BsqState getClone(BsqState snapshotCandidate) {
|
||||
return (BsqState) BsqState.fromProto(snapshotCandidate.getBsqStateBuilder().build());
|
||||
return BsqState.fromProto(snapshotCandidate.getBsqStateBuilder().build());
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,8 @@ package bisq.core.dao.state;
|
||||
import bisq.core.dao.DaoSetupService;
|
||||
import bisq.core.dao.bonding.BondingConsensus;
|
||||
import bisq.core.dao.governance.role.BondedRole;
|
||||
import bisq.core.dao.governance.voteresult.DecryptedBallotsWithMerits;
|
||||
import bisq.core.dao.governance.voteresult.EvaluatedProposal;
|
||||
import bisq.core.dao.state.blockchain.Block;
|
||||
import bisq.core.dao.state.blockchain.SpentInfo;
|
||||
import bisq.core.dao.state.blockchain.Tx;
|
||||
@ -55,6 +57,9 @@ import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
/**
|
||||
* Provides access methods to BsqState data.
|
||||
*/
|
||||
@Slf4j
|
||||
public class BsqStateService implements DaoSetupService {
|
||||
private final BsqState bsqState;
|
||||
@ -114,12 +119,22 @@ public class BsqStateService implements DaoSetupService {
|
||||
|
||||
bsqState.getParamChangeList().clear();
|
||||
bsqState.getParamChangeList().addAll(snapshot.getParamChangeList());
|
||||
|
||||
bsqState.getEvaluatedProposalList().clear();
|
||||
bsqState.getEvaluatedProposalList().addAll(snapshot.getEvaluatedProposalList());
|
||||
|
||||
bsqState.getDecryptedBallotsWithMeritsList().clear();
|
||||
bsqState.getDecryptedBallotsWithMeritsList().addAll(snapshot.getDecryptedBallotsWithMeritsList());
|
||||
}
|
||||
|
||||
public BsqState getClone() {
|
||||
return bsqState.getClone();
|
||||
}
|
||||
|
||||
BsqState getClone(BsqState snapshotCandidate) {
|
||||
return bsqState.getClone(snapshotCandidate);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// ChainHeight
|
||||
@ -196,8 +211,8 @@ public class BsqStateService implements DaoSetupService {
|
||||
* Whether specified block hash belongs to a block we already know about.
|
||||
*
|
||||
* @param blockHash The hash of a {@link Block}.
|
||||
* @return True if the hash belongs to a {@link Block} we know about, otherwise
|
||||
* {@code false}.
|
||||
* @return True if the hash belongs to a {@link Block} we know about, otherwise
|
||||
* {@code false}.
|
||||
*/
|
||||
public boolean isBlockHashKnown(String blockHash) {
|
||||
// TODO(chirhonul): If performance of O(n) time in number of blocks becomes an issue,
|
||||
@ -772,6 +787,19 @@ public class BsqStateService implements DaoSetupService {
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Vote result data
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public List<EvaluatedProposal> getEvaluatedProposalList() {
|
||||
return bsqState.getEvaluatedProposalList();
|
||||
}
|
||||
|
||||
public List<DecryptedBallotsWithMerits> getDecryptedBallotsWithMeritsList() {
|
||||
return bsqState.getDecryptedBallotsWithMeritsList();
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Listeners
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -19,48 +19,42 @@ package bisq.core.dao.state;
|
||||
|
||||
import bisq.core.dao.state.blockchain.Block;
|
||||
|
||||
import bisq.common.proto.persistable.PersistenceProtoResolver;
|
||||
import bisq.common.storage.Storage;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import java.util.LinkedList;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
/**
|
||||
* Manages snapshots of BsqState.
|
||||
*
|
||||
* One BSQ block with empty txs adds 152 bytes which results in about 8 MB/year
|
||||
* Manages periodical snapshots of the BsqState.
|
||||
* At startup we apply a snapshot if available.
|
||||
* At each trigger height we persist the latest snapshot candidate and set the current bsqState as new candidate.
|
||||
* The trigger height is determined by the SNAPSHOT_GRID. The latest persisted snapshot is min. the height of
|
||||
* SNAPSHOT_GRID old not less than 2 times the SNAPSHOT_GRID old.
|
||||
*/
|
||||
@Slf4j
|
||||
public class SnapshotManager implements BsqStateListener {
|
||||
private static final int SNAPSHOT_GRID = 100;
|
||||
public class DaoStateSnapshotService implements BsqStateListener {
|
||||
private static final int SNAPSHOT_GRID = 10;
|
||||
|
||||
private final BsqState bsqState;
|
||||
private final BsqStateService bsqStateService;
|
||||
private final GenesisTxInfo genesisTxInfo;
|
||||
private final Storage<BsqState> storage;
|
||||
private final DaoStateStorageService daoStateStorageService;
|
||||
|
||||
private BsqState snapshotCandidate;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Inject
|
||||
public SnapshotManager(BsqState bsqState,
|
||||
BsqStateService bsqStateService,
|
||||
PersistenceProtoResolver persistenceProtoResolver,
|
||||
GenesisTxInfo genesisTxInfo,
|
||||
@Named(Storage.STORAGE_DIR) File storageDir) {
|
||||
this.bsqState = bsqState;
|
||||
public DaoStateSnapshotService(BsqStateService bsqStateService,
|
||||
GenesisTxInfo genesisTxInfo,
|
||||
DaoStateStorageService daoStateStorageService) {
|
||||
this.bsqStateService = bsqStateService;
|
||||
this.genesisTxInfo = genesisTxInfo;
|
||||
storage = new Storage<>(storageDir, persistenceProtoResolver);
|
||||
this.daoStateStorageService = daoStateStorageService;
|
||||
|
||||
this.bsqStateService.addBsqStateListener(this);
|
||||
}
|
||||
@ -77,24 +71,33 @@ public class SnapshotManager implements BsqStateListener {
|
||||
@Override
|
||||
public void onParseTxsComplete(Block block) {
|
||||
int chainHeight = block.getHeight();
|
||||
|
||||
// Either we don't have a snapshot candidate yet, or if we have one the height at that snapshot candidate must be
|
||||
// different to our current height.
|
||||
boolean noSnapshotCandidateOrDifferentHeight = snapshotCandidate == null || snapshotCandidate.getChainHeight() != chainHeight;
|
||||
if (isSnapshotHeight(chainHeight) &&
|
||||
(snapshotCandidate == null ||
|
||||
snapshotCandidate.getChainHeight() != chainHeight)) {
|
||||
isValidHeight(bsqStateService.getBlocks().getLast().getHeight()) &&
|
||||
noSnapshotCandidateOrDifferentHeight) {
|
||||
// At trigger event we store the latest snapshotCandidate to disc
|
||||
if (snapshotCandidate != null) {
|
||||
// We clone because storage is in a threaded context
|
||||
final BsqState cloned = bsqState.getClone(snapshotCandidate);
|
||||
if (cloned.getBlocks().getLast().getHeight() >= genesisTxInfo.getGenesisBlockHeight())
|
||||
storage.queueUpForSave(cloned);
|
||||
log.info("Saved snapshotCandidate to Disc at height " + chainHeight);
|
||||
// We clone because storage is in a threaded context and we set the snapshotCandidate to our current
|
||||
// state in the next step
|
||||
BsqState cloned = bsqStateService.getClone(snapshotCandidate);
|
||||
daoStateStorageService.persist(cloned);
|
||||
log.info("Saved snapshotCandidate with height {} to Disc at height {} ",
|
||||
snapshotCandidate.getChainHeight(), chainHeight);
|
||||
}
|
||||
// Now we clone and keep it in memory for the next trigger
|
||||
snapshotCandidate = bsqState.getClone();
|
||||
// don't access cloned anymore with methods as locks are transient!
|
||||
|
||||
// Now we clone and keep it in memory for the next trigger event
|
||||
snapshotCandidate = bsqStateService.getClone();
|
||||
log.info("Cloned new snapshotCandidate at height " + chainHeight);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isValidHeight(int heightOfLastBlock) {
|
||||
return heightOfLastBlock >= genesisTxInfo.getGenesisBlockHeight();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onParseBlockChainComplete() {
|
||||
}
|
||||
@ -105,14 +108,17 @@ public class SnapshotManager implements BsqStateListener {
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public void applySnapshot() {
|
||||
checkNotNull(storage, "storage must not be null");
|
||||
BsqState persisted = storage.initAndGetPersisted(bsqState, 100);
|
||||
BsqState persisted = daoStateStorageService.getPersistedBsqState();
|
||||
if (persisted != null) {
|
||||
log.info("applySnapshot persisted.chainHeight=" + new LinkedList<>(persisted.getBlocks()).getLast().getHeight());
|
||||
if (persisted.getBlocks().getLast().getHeight() >= genesisTxInfo.getGenesisBlockHeight())
|
||||
bsqStateService.applySnapshot(persisted);
|
||||
LinkedList<Block> blocks = persisted.getBlocks();
|
||||
if (!blocks.isEmpty()) {
|
||||
int heightOfLastBlock = blocks.getLast().getHeight();
|
||||
log.info("applySnapshot from persisted bsqState with height of last block {}", heightOfLastBlock);
|
||||
if (isValidHeight(heightOfLastBlock))
|
||||
bsqStateService.applySnapshot(persisted);
|
||||
}
|
||||
} else {
|
||||
log.info("Try to apply snapshot but no stored snapshot available");
|
||||
log.info("Try to apply snapshot but no stored snapshot available. That is expected at first blocks.");
|
||||
}
|
||||
}
|
||||
|
||||
@ -132,6 +138,6 @@ public class SnapshotManager implements BsqStateListener {
|
||||
}
|
||||
|
||||
private boolean isSnapshotHeight(int height) {
|
||||
return isSnapshotHeight(bsqStateService.getGenesisBlockHeight(), height, SNAPSHOT_GRID);
|
||||
return isSnapshotHeight(genesisTxInfo.getGenesisBlockHeight(), height, SNAPSHOT_GRID);
|
||||
}
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.core.dao.state;
|
||||
|
||||
import bisq.network.p2p.storage.persistence.ResourceDataStoreService;
|
||||
import bisq.network.p2p.storage.persistence.StoreService;
|
||||
|
||||
import bisq.common.storage.Storage;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* Manages persistence of the bsqState.
|
||||
*/
|
||||
@Slf4j
|
||||
public class DaoStateStorageService extends StoreService<DaoStateStore> {
|
||||
private static final String FILE_NAME = "DaoStateStore";
|
||||
|
||||
private BsqState bsqState;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constructor
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Inject
|
||||
public DaoStateStorageService(ResourceDataStoreService resourceDataStoreService,
|
||||
BsqState bsqState,
|
||||
@Named(Storage.STORAGE_DIR) File storageDir,
|
||||
Storage<DaoStateStore> daoSnapshotStorage) {
|
||||
super(storageDir, daoSnapshotStorage);
|
||||
this.bsqState = bsqState;
|
||||
|
||||
resourceDataStoreService.addService(this);
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// API
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public String getFileName() {
|
||||
return FILE_NAME;
|
||||
}
|
||||
|
||||
public void persist(BsqState bsqState) {
|
||||
store.setBsqState(bsqState);
|
||||
storage.queueUpForSave(store);
|
||||
}
|
||||
|
||||
public BsqState getPersistedBsqState() {
|
||||
return store.getBsqState();
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Protected
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
protected DaoStateStore createStore() {
|
||||
return new DaoStateStore(bsqState.getClone());
|
||||
}
|
||||
}
|
65
core/src/main/java/bisq/core/dao/state/DaoStateStore.java
Normal file
65
core/src/main/java/bisq/core/dao/state/DaoStateStore.java
Normal file
@ -0,0 +1,65 @@
|
||||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.core.dao.state;
|
||||
|
||||
import bisq.common.proto.persistable.PersistableEnvelope;
|
||||
|
||||
import io.bisq.generated.protobuffer.PB;
|
||||
|
||||
import com.google.protobuf.Message;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
|
||||
@Slf4j
|
||||
public class DaoStateStore implements PersistableEnvelope {
|
||||
// BsqState is always a clone and must not be used for read access beside initial read from disc when we apply
|
||||
// the snapshot!
|
||||
@Nullable
|
||||
@Getter
|
||||
@Setter
|
||||
BsqState bsqState;
|
||||
|
||||
DaoStateStore(BsqState bsqState) {
|
||||
this.bsqState = bsqState;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// PROTO BUFFER
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
public Message toProtoMessage() {
|
||||
checkNotNull(bsqState, "bsqState must not be null when toProtoMessage is invoked");
|
||||
PB.DaoStateStore.Builder builder = PB.DaoStateStore.newBuilder()
|
||||
.setBsqState(bsqState.getBsqStateBuilder());
|
||||
return PB.PersistableEnvelope.newBuilder()
|
||||
.setDaoStateStore(builder)
|
||||
.build();
|
||||
}
|
||||
|
||||
public static PersistableEnvelope fromProto(PB.DaoStateStore proto) {
|
||||
return new DaoStateStore(BsqState.fromProto(proto.getBsqState()));
|
||||
}
|
||||
}
|
@ -30,9 +30,7 @@ import bisq.core.dao.governance.proposal.MyProposalList;
|
||||
import bisq.core.dao.governance.proposal.storage.appendonly.ProposalStore;
|
||||
import bisq.core.dao.governance.proposal.storage.temp.TempProposalStore;
|
||||
import bisq.core.dao.governance.role.BondedRoleList;
|
||||
import bisq.core.dao.governance.voteresult.DecryptedBallotsWithMeritsList;
|
||||
import bisq.core.dao.governance.voteresult.EvaluatedProposalList;
|
||||
import bisq.core.dao.state.BsqState;
|
||||
import bisq.core.dao.state.DaoStateStore;
|
||||
import bisq.core.payment.AccountAgeWitnessStore;
|
||||
import bisq.core.payment.PaymentAccountList;
|
||||
import bisq.core.proto.CoreProtoResolver;
|
||||
@ -121,8 +119,6 @@ public class CorePersistenceProtoResolver extends CoreProtoResolver implements P
|
||||
return ProposalStore.fromProto(proto.getProposalStore());
|
||||
case TEMP_PROPOSAL_STORE:
|
||||
return TempProposalStore.fromProto(proto.getTempProposalStore(), networkProtoResolver);
|
||||
case BSQ_STATE:
|
||||
return BsqState.fromProto(proto.getBsqState());
|
||||
case MY_PROPOSAL_LIST:
|
||||
return MyProposalList.fromProto(proto.getMyProposalList());
|
||||
case BALLOT_LIST:
|
||||
@ -137,10 +133,8 @@ public class CorePersistenceProtoResolver extends CoreProtoResolver implements P
|
||||
return BondedRoleList.fromProto(proto.getBondedRoleList());
|
||||
case REMOVED_ASSET_LIST:
|
||||
return RemovedAssetsList.fromProto(proto.getRemovedAssetList());
|
||||
case EVALUATED_PROPOSAL_LIST:
|
||||
return EvaluatedProposalList.fromProto(proto.getEvaluatedProposalList());
|
||||
case DECRYPTED_BALLOTS_WITH_MERITS_LIST:
|
||||
return DecryptedBallotsWithMeritsList.fromProto(proto.getDecryptedBallotsWithMeritsList());
|
||||
case DAO_STATE_STORE:
|
||||
return DaoStateStore.fromProto(proto.getDaoStateStore());
|
||||
default:
|
||||
throw new ProtobufferRuntimeException("Unknown proto message case(PB.PersistableEnvelope). " +
|
||||
"messageCase=" + proto.getMessageCase() + "; proto raw data=" + proto.toString());
|
||||
|
@ -26,7 +26,6 @@ import bisq.core.dao.governance.blindvote.MyBlindVoteListService;
|
||||
import bisq.core.dao.governance.myvote.MyVoteListService;
|
||||
import bisq.core.dao.governance.proposal.MyProposalListService;
|
||||
import bisq.core.dao.governance.role.BondedRolesService;
|
||||
import bisq.core.dao.governance.voteresult.VoteResultService;
|
||||
import bisq.core.offer.OpenOfferManager;
|
||||
import bisq.core.trade.TradeManager;
|
||||
import bisq.core.trade.closed.ClosedTradableManager;
|
||||
@ -70,7 +69,6 @@ public class CorePersistedDataHost {
|
||||
persistedDataHosts.add(injector.getInstance(MyProposalListService.class));
|
||||
persistedDataHosts.add(injector.getInstance(BondedRolesService.class));
|
||||
persistedDataHosts.add(injector.getInstance(AssetService.class));
|
||||
persistedDataHosts.add(injector.getInstance(VoteResultService.class));
|
||||
}
|
||||
return persistedDataHosts;
|
||||
}
|
||||
|
@ -0,0 +1,76 @@
|
||||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.core.dao.state;
|
||||
|
||||
import org.powermock.core.classloader.annotations.PowerMockIgnore;
|
||||
import org.powermock.core.classloader.annotations.PrepareForTest;
|
||||
import org.powermock.modules.junit4.PowerMockRunner;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.powermock.api.mockito.PowerMockito.mock;
|
||||
|
||||
@RunWith(PowerMockRunner.class)
|
||||
@PrepareForTest({BsqStateService.class, GenesisTxInfo.class, DaoStateStorageService.class})
|
||||
@PowerMockIgnore({"com.sun.org.apache.xerces.*", "javax.xml.*", "org.xml.*"})
|
||||
public class DaoStateSnapshotServiceTest {
|
||||
|
||||
private DaoStateSnapshotService daoStateSnapshotService;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
daoStateSnapshotService = new DaoStateSnapshotService(mock(BsqStateService.class),
|
||||
mock(GenesisTxInfo.class),
|
||||
mock(DaoStateStorageService.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSnapshotHeight() {
|
||||
assertEquals(120, daoStateSnapshotService.getSnapshotHeight(102, 0, 10));
|
||||
assertEquals(120, daoStateSnapshotService.getSnapshotHeight(102, 100, 10));
|
||||
assertEquals(120, daoStateSnapshotService.getSnapshotHeight(102, 102, 10));
|
||||
assertEquals(120, daoStateSnapshotService.getSnapshotHeight(102, 119, 10));
|
||||
assertEquals(120, daoStateSnapshotService.getSnapshotHeight(102, 120, 10));
|
||||
assertEquals(120, daoStateSnapshotService.getSnapshotHeight(102, 121, 10));
|
||||
assertEquals(120, daoStateSnapshotService.getSnapshotHeight(102, 130, 10));
|
||||
assertEquals(120, daoStateSnapshotService.getSnapshotHeight(102, 139, 10));
|
||||
assertEquals(130, daoStateSnapshotService.getSnapshotHeight(102, 140, 10));
|
||||
assertEquals(130, daoStateSnapshotService.getSnapshotHeight(102, 141, 10));
|
||||
assertEquals(990, daoStateSnapshotService.getSnapshotHeight(102, 1000, 10));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSnapshotHeight() {
|
||||
assertFalse(daoStateSnapshotService.isSnapshotHeight(102, 0, 10));
|
||||
assertFalse(daoStateSnapshotService.isSnapshotHeight(102, 80, 10));
|
||||
assertFalse(daoStateSnapshotService.isSnapshotHeight(102, 90, 10));
|
||||
assertFalse(daoStateSnapshotService.isSnapshotHeight(102, 100, 10));
|
||||
assertFalse(daoStateSnapshotService.isSnapshotHeight(102, 119, 10));
|
||||
assertTrue(daoStateSnapshotService.isSnapshotHeight(102, 120, 10));
|
||||
assertTrue(daoStateSnapshotService.isSnapshotHeight(102, 130, 10));
|
||||
assertTrue(daoStateSnapshotService.isSnapshotHeight(102, 140, 10));
|
||||
assertTrue(daoStateSnapshotService.isSnapshotHeight(102, 200, 10));
|
||||
assertFalse(daoStateSnapshotService.isSnapshotHeight(102, 201, 10));
|
||||
assertFalse(daoStateSnapshotService.isSnapshotHeight(102, 199, 10));
|
||||
}
|
||||
}
|
@ -1,82 +0,0 @@
|
||||
/*
|
||||
* This file is part of Bisq.
|
||||
*
|
||||
* Bisq is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Bisq is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.core.dao.state;
|
||||
|
||||
import bisq.common.proto.persistable.PersistenceProtoResolver;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.powermock.core.classloader.annotations.PowerMockIgnore;
|
||||
import org.powermock.core.classloader.annotations.PrepareForTest;
|
||||
import org.powermock.modules.junit4.PowerMockRunner;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.powermock.api.mockito.PowerMockito.mock;
|
||||
|
||||
@RunWith(PowerMockRunner.class)
|
||||
@PrepareForTest({BsqState.class, BsqStateService.class, PersistenceProtoResolver.class, File.class})
|
||||
@PowerMockIgnore({"com.sun.org.apache.xerces.*", "javax.xml.*", "org.xml.*"})
|
||||
public class SnapshotManagerTest {
|
||||
|
||||
private SnapshotManager snapshotManager;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
snapshotManager = new SnapshotManager(mock(BsqState.class),
|
||||
mock(BsqStateService.class),
|
||||
mock(PersistenceProtoResolver.class),
|
||||
mock(GenesisTxInfo.class),
|
||||
mock(File.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSnapshotHeight() {
|
||||
assertEquals(120, snapshotManager.getSnapshotHeight(102, 0, 10));
|
||||
assertEquals(120, snapshotManager.getSnapshotHeight(102, 100, 10));
|
||||
assertEquals(120, snapshotManager.getSnapshotHeight(102, 102, 10));
|
||||
assertEquals(120, snapshotManager.getSnapshotHeight(102, 119, 10));
|
||||
assertEquals(120, snapshotManager.getSnapshotHeight(102, 120, 10));
|
||||
assertEquals(120, snapshotManager.getSnapshotHeight(102, 121, 10));
|
||||
assertEquals(120, snapshotManager.getSnapshotHeight(102, 130, 10));
|
||||
assertEquals(120, snapshotManager.getSnapshotHeight(102, 139, 10));
|
||||
assertEquals(130, snapshotManager.getSnapshotHeight(102, 140, 10));
|
||||
assertEquals(130, snapshotManager.getSnapshotHeight(102, 141, 10));
|
||||
assertEquals(990, snapshotManager.getSnapshotHeight(102, 1000, 10));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSnapshotHeight() {
|
||||
assertFalse(snapshotManager.isSnapshotHeight(102, 0, 10));
|
||||
assertFalse(snapshotManager.isSnapshotHeight(102, 80, 10));
|
||||
assertFalse(snapshotManager.isSnapshotHeight(102, 90, 10));
|
||||
assertFalse(snapshotManager.isSnapshotHeight(102, 100, 10));
|
||||
assertFalse(snapshotManager.isSnapshotHeight(102, 119, 10));
|
||||
assertTrue(snapshotManager.isSnapshotHeight(102, 120, 10));
|
||||
assertTrue(snapshotManager.isSnapshotHeight(102, 130, 10));
|
||||
assertTrue(snapshotManager.isSnapshotHeight(102, 140, 10));
|
||||
assertTrue(snapshotManager.isSnapshotHeight(102, 200, 10));
|
||||
assertFalse(snapshotManager.isSnapshotHeight(102, 201, 10));
|
||||
assertFalse(snapshotManager.isSnapshotHeight(102, 199, 10));
|
||||
}
|
||||
}
|
@ -44,8 +44,8 @@ import bisq.core.dao.governance.ballot.vote.Vote;
|
||||
import bisq.core.dao.governance.myvote.MyVote;
|
||||
import bisq.core.dao.governance.proposal.Proposal;
|
||||
import bisq.core.dao.governance.voteresult.EvaluatedProposal;
|
||||
import bisq.core.dao.governance.voteresult.VoteResultService;
|
||||
import bisq.core.dao.state.BsqStateListener;
|
||||
import bisq.core.dao.state.BsqStateService;
|
||||
import bisq.core.dao.state.blockchain.Block;
|
||||
import bisq.core.dao.state.period.DaoPhase;
|
||||
import bisq.core.locale.Res;
|
||||
@ -103,7 +103,7 @@ public class ProposalsView extends ActivatableView<GridPane, Void> implements Bs
|
||||
private final DaoFacade daoFacade;
|
||||
private final BsqWalletService bsqWalletService;
|
||||
private final PhasesView phasesView;
|
||||
private final VoteResultService voteResultService;
|
||||
private final BsqStateService bsqStateService;
|
||||
private final BsqFormatter bsqFormatter;
|
||||
private final BSFormatter btcFormatter;
|
||||
|
||||
@ -142,13 +142,13 @@ public class ProposalsView extends ActivatableView<GridPane, Void> implements Bs
|
||||
private ProposalsView(DaoFacade daoFacade,
|
||||
BsqWalletService bsqWalletService,
|
||||
PhasesView phasesView,
|
||||
VoteResultService voteResultService,
|
||||
BsqStateService bsqStateService,
|
||||
BsqFormatter bsqFormatter,
|
||||
BSFormatter btcFormatter) {
|
||||
this.daoFacade = daoFacade;
|
||||
this.bsqWalletService = bsqWalletService;
|
||||
this.phasesView = phasesView;
|
||||
this.voteResultService = voteResultService;
|
||||
this.bsqStateService = bsqStateService;
|
||||
this.bsqFormatter = bsqFormatter;
|
||||
this.btcFormatter = btcFormatter;
|
||||
}
|
||||
@ -418,7 +418,7 @@ public class ProposalsView extends ActivatableView<GridPane, Void> implements Bs
|
||||
private void onSelectProposal(ProposalsListItem item) {
|
||||
selectedItem = item;
|
||||
if (selectedItem != null) {
|
||||
EvaluatedProposal evaluatedProposal = voteResultService.getEvaluatedProposalList().stream()
|
||||
EvaluatedProposal evaluatedProposal = bsqStateService.getEvaluatedProposalList().stream()
|
||||
.filter(e -> daoFacade.isTxInCorrectCycle(e.getProposal().getTxId(),
|
||||
daoFacade.getChainHeight()))
|
||||
.filter(e -> e.getProposalTxId().equals(selectedItem.getProposal().getTxId()))
|
||||
|
@ -245,11 +245,11 @@ public class VoteResultView extends ActivatableView<GridPane, Void> implements B
|
||||
.filter(proposal -> cycleService.isTxInCycle(cycle, proposal.getTxId()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
List<EvaluatedProposal> evaluatedProposalsForCycle = voteResultService.getEvaluatedProposalList().stream()
|
||||
List<EvaluatedProposal> evaluatedProposalsForCycle = bsqStateService.getEvaluatedProposalList().stream()
|
||||
.filter(evaluatedProposal -> cycleService.isTxInCycle(cycle, evaluatedProposal.getProposal().getTxId()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
List<DecryptedBallotsWithMerits> decryptedVotesForCycle = voteResultService.getDecryptedBallotsWithMeritsList().stream()
|
||||
List<DecryptedBallotsWithMerits> decryptedVotesForCycle = bsqStateService.getDecryptedBallotsWithMeritsList().stream()
|
||||
.filter(decryptedBallotsWithMerits -> cycleService.isTxInCycle(cycle, decryptedBallotsWithMerits.getBlindVoteTxId()))
|
||||
.filter(decryptedBallotsWithMerits -> cycleService.isTxInCycle(cycle, decryptedBallotsWithMerits.getVoteRevealTxId()))
|
||||
.collect(Collectors.toList());
|
||||
|
@ -44,8 +44,8 @@ import static com.google.common.base.Preconditions.checkArgument;
|
||||
*/
|
||||
@Deprecated
|
||||
@Slf4j
|
||||
public final class PersistableNetworkPayloadListService extends StoreService<PersistableNetworkPayloadList, PersistableNetworkPayload> {
|
||||
public static final String FILE_NAME = "PersistableNetworkPayloadMap";
|
||||
public final class PersistableNetworkPayloadListService extends MapStoreService<PersistableNetworkPayloadList, PersistableNetworkPayload> {
|
||||
private static final String FILE_NAME = "PersistableNetworkPayloadMap";
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
Loading…
Reference in New Issue
Block a user