diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/BlockchainBlock.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/BlockchainBlock.java index a242852e920a180971693a9c7127503f47e11d5e..a4104b3667a6de8b8dfa42eda4c50a7b06277eda 100644 --- a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/BlockchainBlock.java +++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/BlockchainBlock.java @@ -482,9 +482,17 @@ public class BlockchainBlock implements Serializable { public static class Revoked implements Serializable { + private String pubkey; private String signature; - private String userId; + + public String getPubkey() { + return pubkey; + } + + public void setPubkey(String pubkey) { + this.pubkey = pubkey; + } public String getSignature() { return signature; } @@ -492,20 +500,12 @@ public class BlockchainBlock implements Serializable { this.signature = signature; } - public String getUserId() { - return userId; - } - - public void setUserId(String userId) { - this.userId = userId; - } - @Override public String toString() { StringBuilder sb = new StringBuilder() - .append(signature) - .append(":").append(userId); + .append(pubkey) + .append(":").append(signature); return sb.toString(); } diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/BlockchainBlocks.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/BlockchainBlocks.java index 05040f22c3d3ebcf9fe70265c52de8ea2142c08c..1aace4642ac6e3eef446f713b43e7ad3f507e248 100644 --- a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/BlockchainBlocks.java +++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/BlockchainBlocks.java @@ -22,9 +22,7 @@ package org.duniter.core.client.model.bma; * #L% */ -import com.google.common.base.Preconditions; -import com.google.common.collect.Maps; -import com.google.common.collect.Sets; +import org.duniter.core.util.Preconditions; import java.math.BigInteger; import java.util.*; @@ -42,7 +40,10 @@ public final class BlockchainBlocks { public static final Pattern SIG_PUBKEY_PATTERN = Pattern.compile("SIG\\(([^)]+)\\)"); - public static final Pattern TX_UNLOCK_PATTERN = Pattern.compile("([0-9]+):SIG\\(([^)]+)\\)"); + public static final Pattern TX_INPUT_CONDITION_FUNCTION = Pattern.compile("(SIG|XHX)\\(([^)]+)\\)"); + public static final Pattern TX_INPUT_CONDITION = Pattern.compile(TX_INPUT_CONDITION_FUNCTION + "(:? " + TX_INPUT_CONDITION_FUNCTION + ")*"); + + public static final Pattern TX_UNLOCK_PATTERN = Pattern.compile("([0-9]+):(" + TX_INPUT_CONDITION+")"); public static final Pattern TX_OUTPUT_PATTERN = Pattern.compile("([0-9]+):([0-9]+):([^:]+)"); public static final Pattern TX_INPUT_PATTERN = Pattern.compile("([0-9]+):([0-9]+):([TD]):([^:]+):([^:]+)"); @@ -64,28 +65,25 @@ public final class BlockchainBlocks { public static long getTxAmount(final BlockchainBlock.Transaction tx, Predicate<String> issuerFilter) { - final Map<Integer, Integer> inputIndexByIssuerIndex = Maps.newHashMap(); - Arrays.stream(tx.getUnlocks()) - .map(TX_UNLOCK_PATTERN::matcher) - .filter(Matcher::matches) - .forEach(matcher -> inputIndexByIssuerIndex.put( - Integer.parseInt(matcher.group(1)), - Integer.parseInt(matcher.group(2))) - ); + final Map<Integer, List<String>> inputIssuers = getInputIssuers(tx); return IntStream.range(0, tx.getIssuers().length) - .mapToLong(i -> { - final String issuer = tx.getIssuers()[i]; + .mapToLong(issuerIndex -> { + final String issuer = tx.getIssuers()[issuerIndex]; // Skip if issuerFilter test failed if (issuerFilter != null && !issuerFilter.test(issuer)) return 0; long inputSum = IntStream.range(0, tx.getInputs().length) - .filter(j -> i == inputIndexByIssuerIndex.get(j)) - .mapToObj(j -> tx.getInputs()[j]) - .map(input -> input.split(":")) - .filter(inputParts -> inputParts.length > 2) - .mapToLong(inputParts -> powBase(Long.parseLong(inputParts[0]), Integer.parseInt(inputParts[1]))) + .filter(inputIssuers::containsKey) + .mapToLong(inputIndex -> { + String[] inputParts = tx.getInputs()[inputIndex].split(":"); + List<String> issuers = inputIssuers.get(inputIndex); + if (inputParts.length > 2 && issuers.contains(issuer)) { + return powBase(Long.parseLong(inputParts[0]), Integer.parseInt(inputParts[1]), issuers.size()); + } + return 0; + }) .sum(); long outputSum = Arrays.stream(tx.getOutputs()) @@ -105,15 +103,20 @@ public final class BlockchainBlocks { return amount * (long)Math.pow(10, unitbase); } + public static long powBase(long amount, int unitbase, int divisor) { + if (unitbase == 0) return amount; + return amount * (long)Math.pow(10, unitbase) / divisor; + } + public static List<TxInput> getTxInputs(final BlockchainBlock.Transaction tx) { Preconditions.checkNotNull(tx); - final Function<Integer, String> issuerByInputIndex = transformInputIndex2Issuer(tx); + final Map<Integer, List<String>> inputIssuers = getInputIssuers(tx); return IntStream.range(0, tx.getInputs().length) .mapToObj(i -> { TxInput txInput = parseInput(tx.getInputs()[i]); - txInput.issuer = issuerByInputIndex.apply(i); + txInput.issuers = inputIssuers.get(i); return txInput; }) .collect(Collectors.toList()); @@ -162,8 +165,8 @@ public final class BlockchainBlocks { Preconditions.checkNotNull(txInputs); return txInputs.stream() // only keep inputs from issuer - .filter(input -> Objects.equals(issuer, input.issuer)) - .mapToLong(input -> powBase(input.amount, input.unitbase)) + .filter(input -> input.issuers.contains(issuer)) + .mapToLong(input -> powBase(input.amount, input.unitbase, input.issuers.size())) .sum(); } @@ -180,7 +183,9 @@ public final class BlockchainBlocks { public static Set<String> getTxRecipients(Collection<TxOutput> txOutputs) { Preconditions.checkNotNull(txOutputs); - return txOutputs.stream().map(output -> output.recipient).distinct().collect(Collectors.toSet()); + return txOutputs.stream().map(output -> output.recipient) + .filter(Objects::nonNull) + .distinct().collect(Collectors.toSet()); } public static class TxInput { @@ -189,7 +194,7 @@ public final class BlockchainBlocks { public String type; public String txHashOrPubkey; public String indexOrBlockId; - public String issuer; + public List<String> issuers; public boolean isUD() { return "D".equals(type); @@ -206,18 +211,30 @@ public final class BlockchainBlocks { /* -- Internal methods -- */ - private static Function<Integer, String> transformInputIndex2Issuer(final BlockchainBlock.Transaction tx) { - final Map<Integer, Integer> inputIndexByIssuerIndex = Maps.newHashMap(); - Arrays.stream(tx.getUnlocks()) + private static Map<Integer, List<String>> getInputIssuers(final BlockchainBlock.Transaction tx) { + return Arrays.stream(tx.getUnlocks()) .map(TX_UNLOCK_PATTERN::matcher) .filter(Matcher::matches) - .forEach(matcher -> inputIndexByIssuerIndex.put( - Integer.parseInt(matcher.group(1)), - Integer.parseInt(matcher.group(2))) + .collect(Collectors.toMap( + matcher -> Integer.decode(matcher.group(1)), + matcher -> getUnlockConditionIssuers(tx.getIssuers(), matcher.group(2))) ); - - - return (inputIndex -> tx.getIssuers()[inputIndexByIssuerIndex.get(inputIndex)]); } + private static List<String> getUnlockConditionIssuers(String[] issuers, String condition) { + // parse condition + Matcher matcher = TX_INPUT_CONDITION_FUNCTION.matcher(condition); + int start = 0; + List<String> result = new ArrayList<>(1); + while (matcher.find(start)) { + String function = matcher.group(1); + if ("SIG".equals(function)) { + int issuerIndex = Integer.parseInt(matcher.group(2)); + String issuer = issuers[issuerIndex]; + result.add(issuer); + } + start = matcher.end(); + } + return result; + } } diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/jackson/RevokedDeserializer.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/jackson/RevokedDeserializer.java index 25d418a858b973319185dbdfb7570ef70199a1de..9d25954a3228d245ec086327911648c255d246ee 100644 --- a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/jackson/RevokedDeserializer.java +++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/jackson/RevokedDeserializer.java @@ -50,8 +50,8 @@ public class RevokedDeserializer extends JsonDeserializer<BlockchainBlock.Revoke BlockchainBlock.Revoked result = new BlockchainBlock.Revoked(); int i = 0; + result.setPubkey(parts[i++]); result.setSignature(parts[i++]); - result.setUserId(parts[i++]); return result; } diff --git a/duniter4j-core-shared/src/main/java/org/duniter/core/util/CollectionUtils.java b/duniter4j-core-shared/src/main/java/org/duniter/core/util/CollectionUtils.java index f686b38b7fd876bc5f923875d11e642ec99b1bd2..995e10b8210e033d3804efa6cc9b00ee1ad78e12 100644 --- a/duniter4j-core-shared/src/main/java/org/duniter/core/util/CollectionUtils.java +++ b/duniter4j-core-shared/src/main/java/org/duniter/core/util/CollectionUtils.java @@ -84,6 +84,10 @@ public class CollectionUtils { return collection == null ? 0 : collection.size(); } + public static int size(final Object[] array) { + return array == null ? 0 : array.length; + } + public static <E> E extractSingleton(Collection<E> collection) { if(collection != null && collection.size() == 1) { return collection.iterator().next(); diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/dao/impl/BlockStatDaoImpl.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/dao/impl/BlockStatDaoImpl.java index e4b8e7b3a4420b817be38bd57f5767d06e94993c..f79a6527795bfb483977b111d62509e70087562a 100644 --- a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/dao/impl/BlockStatDaoImpl.java +++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/dao/impl/BlockStatDaoImpl.java @@ -252,11 +252,16 @@ public class BlockStatDaoImpl extends AbstractDao implements BlockStatDao { .field("type", "long") .endObject() - // txChangeAmount + // txChangeCount .startObject(BlockchainBlockStat.PROPERTY_TX_CHANGE_COUNT) .field("type", "integer") .endObject() + // certCount + .startObject(BlockchainBlockStat.PROPERTY_CERT_COUNT) + .field("type", "integer") + .endObject() + .endObject() .endObject().endObject(); @@ -295,6 +300,9 @@ public class BlockStatDaoImpl extends AbstractDao implements BlockStatDao { result.setTxCount(0); } + // Cert count + result.setCertCount(CollectionUtils.size(block.getCertifications())); + return result; } diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/model/BlockchainBlockStat.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/model/BlockchainBlockStat.java index 94c6a922a07a0b65c5e2d42a102b8231f0a3e21e..add32d5ca052cd277827c7470721fa9c39385155 100644 --- a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/model/BlockchainBlockStat.java +++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/model/BlockchainBlockStat.java @@ -43,6 +43,7 @@ public class BlockchainBlockStat implements Serializable { public static final String PROPERTY_TX_COUNT = "txCount"; public static final String PROPERTY_TX_AMOUNT = "txAmount"; public static final String PROPERTY_TX_CHANGE_COUNT = "txChangeCount"; + public static final String PROPERTY_CERT_COUNT = "certCount"; // Property copied from Block private int version; @@ -60,6 +61,7 @@ public class BlockchainBlockStat implements Serializable { private Integer txCount; private BigInteger txAmount; private Integer txChangeCount; + private Integer certCount; public BlockchainBlockStat() { super(); @@ -169,4 +171,11 @@ public class BlockchainBlockStat implements Serializable { this.unitbase = unitbase; } + public Integer getCertCount() { + return certCount; + } + + public void setCertCount(Integer certCount) { + this.certCount = certCount; + } } diff --git a/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/model/UserEventCodes.java b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/model/UserEventCodes.java index db84a9a7cf1125f1047e0ff32e7542f47af8e7dc..f56e1474ff3219c7d20e9c7870bc77965aec4f0b 100644 --- a/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/model/UserEventCodes.java +++ b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/model/UserEventCodes.java @@ -35,6 +35,8 @@ public enum UserEventCodes { MEMBER_JOIN, MEMBER_LEAVE, MEMBER_ACTIVE, + MEMBER_REVOKE, + MEMBER_EXCLUDE, // TX TX_SENT, diff --git a/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/BlockchainUserEventService.java b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/BlockchainUserEventService.java index 738864c7e1b3578ca873bb53c13c132dc806d27a..626fbbbde0f9ee5865c68d8ac1e6e262ac726e4e 100644 --- a/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/BlockchainUserEventService.java +++ b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/BlockchainUserEventService.java @@ -95,6 +95,13 @@ public class BlockchainUserEventService extends AbstractBlockchainListenerServic } } + // Actives + if (CollectionUtils.isNotEmpty(block.getActives())) { + for (BlockchainBlock.Joiner active: block.getActives()) { + notifyUserEvent(block, active.getPublicKey(), UserEventCodes.MEMBER_ACTIVE, I18n.n("duniter.user.event.MEMBER_ACTIVE"), block.getCurrency()); + } + } + // Leavers if (CollectionUtils.isNotEmpty(block.getLeavers())) { for (BlockchainBlock.Joiner leaver: block.getJoiners()) { @@ -102,10 +109,17 @@ public class BlockchainUserEventService extends AbstractBlockchainListenerServic } } - // Actives - if (CollectionUtils.isNotEmpty(block.getActives())) { - for (BlockchainBlock.Joiner active: block.getActives()) { - notifyUserEvent(block, active.getPublicKey(), UserEventCodes.MEMBER_ACTIVE, I18n.n("duniter.user.event.MEMBER_ACTIVE"), block.getCurrency()); + // Revoked + if (CollectionUtils.isNotEmpty(block.getRevoked())) { + for (BlockchainBlock.Revoked revoked: block.getRevoked()) { + notifyUserEvent(block, revoked.getPubkey(), UserEventCodes.MEMBER_REVOKE, I18n.n("duniter.user.event.MEMBER_REVOKE"), block.getCurrency()); + } + } + + // Excluded + if (CollectionUtils.isNotEmpty(block.getExcluded())) { + for (String excluded: block.getExcluded()) { + notifyUserEvent(block, excluded, UserEventCodes.MEMBER_EXCLUDE, I18n.n("duniter.user.event.MEMBER_EXCLUDE"), block.getCurrency()); } } diff --git a/duniter4j-es-user/src/main/resources/i18n/duniter4j-es-user_en_GB.properties b/duniter4j-es-user/src/main/resources/i18n/duniter4j-es-user_en_GB.properties index 7110fb3bdd703cea3d7c23989bfb0313a29920b6..98d218d1ac2674b2a99cc8e0d673c1eb85d6576e 100644 --- a/duniter4j-es-user/src/main/resources/i18n/duniter4j-es-user_en_GB.properties +++ b/duniter4j-es-user/src/main/resources/i18n/duniter4j-es-user_en_GB.properties @@ -5,8 +5,10 @@ duniter.user.event.CERT_RECEIVED=You have received a certification from %2$s. duniter.user.event.CERT_SENT=Your certification to %2$s was executed. duniter.user.event.INVITATION_TO_CERTIFY=%2$s invites you to certify an identity. duniter.user.event.MEMBER_ACTIVE=Your membership to %1$s has been renewed successfully. +duniter.user.event.MEMBER_EXCLUDE= duniter.user.event.MEMBER_JOIN=You are now a member of currency %1$s\! duniter.user.event.MEMBER_LEAVE=You are not a member anymore of currency %1$s\! +duniter.user.event.MEMBER_REVOKE= duniter.user.event.MESSAGE_RECEIVED=You received a message from %2$s. duniter.user.event.NODE_BMA_DOWN=Duniter node [%1$s\:%2$s] is DOWN\: no access from ES node [%3$s]. Last connexion at %4$s. Blockchain indexation waiting. duniter.user.event.NODE_BMA_UP=Duniter node [%1$s\:%2$s] is UP again. diff --git a/duniter4j-es-user/src/main/resources/i18n/duniter4j-es-user_fr_FR.properties b/duniter4j-es-user/src/main/resources/i18n/duniter4j-es-user_fr_FR.properties index be2471dfe68a8b57fe958b79b6410933fbe396e2..e4e31362232b4af585877a5b90a3541bf51c8da1 100644 --- a/duniter4j-es-user/src/main/resources/i18n/duniter4j-es-user_fr_FR.properties +++ b/duniter4j-es-user/src/main/resources/i18n/duniter4j-es-user_fr_FR.properties @@ -5,8 +5,10 @@ duniter.user.event.CERT_RECEIVED=%2$s vous a certifié (certification prise en c duniter.user.event.CERT_SENT=Votre certification de %2$s a été pris en compte. duniter.user.event.INVITATION_TO_CERTIFY=%2$s vous invite à certifier une identité. duniter.user.event.MEMBER_ACTIVE=Votre adhésion comme membre a bien été renouvellée. +duniter.user.event.MEMBER_EXCLUDE=Votre adhésion comme membre n'est plus valide, faute de non renouvellement ou par manque de certifications. duniter.user.event.MEMBER_JOIN=Vous êtes maintenant membre de la monnaie %1$s \! -duniter.user.event.MEMBER_LEAVE=Votre adhésion comme membre à expirée. +duniter.user.event.MEMBER_LEAVE=Votre adhésion a pris fin, suite à votre demande. +duniter.user.event.MEMBER_REVOKE=Votre compte membre a été révoquée. Il ne pourra plus devenir membre. duniter.user.event.MESSAGE_RECEIVED=Vous avez reçu un message de %2$s. duniter.user.event.NODE_BMA_DOWN=Noeud Duniter [%1$s\:%2$s] non joignable, depuis le noeud ES API [%3$s]. Dernière connexion à %4$s. Indexation de blockchain en attente. duniter.user.event.NODE_BMA_UP=Noeud Duniter [%1$s\:%2$s] à nouveau accessible.