Skip to content

Commit

Permalink
Use TreeMap to prevent potential nondeterminism in BM share summation
Browse files Browse the repository at this point in the history
Replace HashMap in 'BurningManService.getBurningManCandidatesByName'
result construction with a TreeMap, to ensure that the map values are
ordered deterministically (alphabetically by candidate name) when
computing floating point sums. The map values are streamed over in a few
places in this method and elsewhere in 'DelayedPayoutTxReceiverService',
when performing double precision summation to compute the DPT. This
introduces potential nondeterminism due to the nonassociativity of FP
addition, making sums dependent on the term order.

(Note that 'DoubleStream::sum' uses compensated (Kahan) summation, which
makes it effectively quad precision internally, so the chance of term
reordering causing the result to differ by even a single ULP is probably
quite low here. So there might not be much problem in practice.)
  • Loading branch information
stejbac committed Nov 25, 2023
1 parent c234c23 commit 7dfd6aa
Showing 1 changed file with 3 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.TreeMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;

Expand Down Expand Up @@ -111,7 +112,7 @@ public BurningManService(DaoStateService daoStateService,
///////////////////////////////////////////////////////////////////////////////////////////

Map<String, BurningManCandidate> getBurningManCandidatesByName(int chainHeight) {
Map<String, BurningManCandidate> burningManCandidatesByName = new HashMap<>();
Map<String, BurningManCandidate> burningManCandidatesByName = new TreeMap<>();
Map<P2PDataStorage.ByteArray, Set<TxOutput>> proofOfBurnOpReturnTxOutputByHash = getProofOfBurnOpReturnTxOutputByHash(chainHeight);

// Add contributors who made a compensation request
Expand All @@ -120,8 +121,7 @@ Map<String, BurningManCandidate> getBurningManCandidatesByName(int chainHeight)
.forEach(issuance -> {
getCompensationProposalsForIssuance(issuance).forEach(compensationProposal -> {
String name = compensationProposal.getName();
burningManCandidatesByName.putIfAbsent(name, new BurningManCandidate());
BurningManCandidate candidate = burningManCandidatesByName.get(name);
BurningManCandidate candidate = burningManCandidatesByName.computeIfAbsent(name, n -> new BurningManCandidate());

// Issuance
Optional<String> customAddress = compensationProposal.getBurningManReceiverAddress();
Expand Down

0 comments on commit 7dfd6aa

Please sign in to comment.