From d9a7648d36760da820d04994336d51cc4e9b318f Mon Sep 17 00:00:00 2001 From: kennycud Date: Sun, 5 Jan 2025 15:59:09 -0800 Subject: [PATCH] access to decoded online accounts by block --- .../qortal/api/resource/BlocksResource.java | 52 +++++++++- .../data/block/DecodedOnlineAccountData.java | 85 ++++++++++++++++ src/main/java/org/qortal/utils/Blocks.java | 99 +++++++++++++++++++ 3 files changed, 235 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/qortal/data/block/DecodedOnlineAccountData.java create mode 100644 src/main/java/org/qortal/utils/Blocks.java diff --git a/src/main/java/org/qortal/api/resource/BlocksResource.java b/src/main/java/org/qortal/api/resource/BlocksResource.java index ff0bb979..0203bafc 100644 --- a/src/main/java/org/qortal/api/resource/BlocksResource.java +++ b/src/main/java/org/qortal/api/resource/BlocksResource.java @@ -19,6 +19,8 @@ import org.qortal.crypto.Crypto; import org.qortal.data.account.AccountData; import org.qortal.data.block.BlockData; import org.qortal.data.block.BlockSummaryData; +import org.qortal.data.block.DecodedOnlineAccountData; +import org.qortal.data.network.OnlineAccountData; import org.qortal.data.transaction.TransactionData; import org.qortal.repository.BlockArchiveReader; import org.qortal.repository.DataException; @@ -27,6 +29,7 @@ import org.qortal.repository.RepositoryManager; import org.qortal.transform.TransformationException; import org.qortal.transform.block.BlockTransformer; import org.qortal.utils.Base58; +import org.qortal.utils.Blocks; import org.qortal.utils.Triple; import javax.servlet.http.HttpServletRequest; @@ -45,6 +48,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.List; +import java.util.Set; @Path("/blocks") @Tag(name = "Blocks") @@ -889,4 +893,50 @@ public class BlocksResource { throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e); } } -} + + @GET + @Path("/onlineaccounts/{height}") + @Operation( + summary = "Get online accounts for block", + description = "Returns the online accounts who submitted signatures for this block", + responses = { + @ApiResponse( + description = "online accounts", + content = @Content( + array = @ArraySchema( + schema = @Schema( + implementation = DecodedOnlineAccountData.class + ) + ) + ) + ) + } + ) + @ApiErrors({ + ApiError.BLOCK_UNKNOWN, ApiError.REPOSITORY_ISSUE + }) + public Set getOnlineAccounts(@PathParam("height") int height) { + + try (final Repository repository = RepositoryManager.getRepository()) { + + // get block from database + BlockData blockData = repository.getBlockRepository().fromHeight(height); + + // if block data is not in the database, then try the archive + if (blockData == null) { + blockData = repository.getBlockArchiveRepository().fromHeight(height); + + // if the block is not in the database or the archive, then the block is unknown + if( blockData == null ) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.BLOCK_UNKNOWN); + } + } + + Set onlineAccounts = Blocks.getDecodedOnlineAccountsForBlock(repository, blockData); + + return onlineAccounts; + } catch (DataException e) { + throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE); + } + } +} \ No newline at end of file diff --git a/src/main/java/org/qortal/data/block/DecodedOnlineAccountData.java b/src/main/java/org/qortal/data/block/DecodedOnlineAccountData.java new file mode 100644 index 00000000..a2ecc1ca --- /dev/null +++ b/src/main/java/org/qortal/data/block/DecodedOnlineAccountData.java @@ -0,0 +1,85 @@ +package org.qortal.data.block; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import java.util.Objects; + +// All properties to be converted to JSON via JAX-RS +@XmlAccessorType(XmlAccessType.FIELD) +public class DecodedOnlineAccountData { + + private long onlineTimestamp; + private String minter; + private String recipient; + private int sharePercent; + private boolean minterGroupMember; + private String name; + private int level; + + public DecodedOnlineAccountData() { + } + + public DecodedOnlineAccountData(long onlineTimestamp, String minter, String recipient, int sharePercent, boolean minterGroupMember, String name, int level) { + this.onlineTimestamp = onlineTimestamp; + this.minter = minter; + this.recipient = recipient; + this.sharePercent = sharePercent; + this.minterGroupMember = minterGroupMember; + this.name = name; + this.level = level; + } + + public long getOnlineTimestamp() { + return onlineTimestamp; + } + + public String getMinter() { + return minter; + } + + public String getRecipient() { + return recipient; + } + + public int getSharePercent() { + return sharePercent; + } + + public boolean isMinterGroupMember() { + return minterGroupMember; + } + + public String getName() { + return name; + } + + public int getLevel() { + return level; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + DecodedOnlineAccountData that = (DecodedOnlineAccountData) o; + return onlineTimestamp == that.onlineTimestamp && sharePercent == that.sharePercent && minterGroupMember == that.minterGroupMember && level == that.level && Objects.equals(minter, that.minter) && Objects.equals(recipient, that.recipient) && Objects.equals(name, that.name); + } + + @Override + public int hashCode() { + return Objects.hash(onlineTimestamp, minter, recipient, sharePercent, minterGroupMember, name, level); + } + + @Override + public String toString() { + return "DecodedOnlineAccountData{" + + "onlineTimestamp=" + onlineTimestamp + + ", minter='" + minter + '\'' + + ", recipient='" + recipient + '\'' + + ", sharePercent=" + sharePercent + + ", minterGroupMember=" + minterGroupMember + + ", name='" + name + '\'' + + ", level=" + level + + '}'; + } +} diff --git a/src/main/java/org/qortal/utils/Blocks.java b/src/main/java/org/qortal/utils/Blocks.java new file mode 100644 index 00000000..54ad86da --- /dev/null +++ b/src/main/java/org/qortal/utils/Blocks.java @@ -0,0 +1,99 @@ +package org.qortal.utils; + +import io.druid.extendedset.intset.ConciseSet; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.qortal.block.BlockChain; +import org.qortal.data.account.AddressLevelPairing; +import org.qortal.data.account.RewardShareData; +import org.qortal.data.block.BlockData; +import org.qortal.data.block.DecodedOnlineAccountData; +import org.qortal.data.group.GroupMemberData; +import org.qortal.data.naming.NameData; +import org.qortal.repository.DataException; +import org.qortal.repository.Repository; +import org.qortal.transform.block.BlockTransformer; + +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Class Blocks + * + * Methods for block related logic. + */ +public class Blocks { + + private static final Logger LOGGER = LogManager.getLogger(Blocks.class); + + /** + * Get Decode Online Accounts For Block + * + * @param repository the data repository + * @param blockData the block data + * + * @return the online accounts set to the block + * + * @throws DataException + */ + public static Set getDecodedOnlineAccountsForBlock(Repository repository, BlockData blockData) throws DataException { + try { + // get all online account indices from block + ConciseSet onlineAccountIndices = BlockTransformer.decodeOnlineAccounts(blockData.getEncodedOnlineAccounts()); + + // get online reward shares from the online accounts on the block + List onlineRewardShares = repository.getAccountRepository().getRewardSharesByIndexes(onlineAccountIndices.toArray()); + + // online timestamp for block + long onlineTimestamp = blockData.getOnlineAccountsTimestamp(); + Set onlineAccounts = new HashSet<>(); + + // all minting group member addresses + List mintingGroupAddresses + = repository.getGroupRepository() + .getGroupMembers(BlockChain.getInstance().getMintingGroupId()).stream() + .map(GroupMemberData::getMember) + .collect(Collectors.toList()); + + // all names, indexed by address + Map nameByAddress + = repository.getNameRepository() + .getAllNames().stream() + .collect(Collectors.toMap(NameData::getOwner, NameData::getName)); + + // all accounts at level 1 or higher, indexed by address + Map levelByAddress + = repository.getAccountRepository().getAddressLevelPairings(1).stream() + .collect(Collectors.toMap(AddressLevelPairing::getAddress, AddressLevelPairing::getLevel)); + + // for each reward share where the minter is online, + // construct the data object and add it to the return list + for (RewardShareData onlineRewardShare : onlineRewardShares) { + String minter = onlineRewardShare.getMinter(); + DecodedOnlineAccountData onlineAccountData + = new DecodedOnlineAccountData( + onlineTimestamp, + minter, + onlineRewardShare.getRecipient(), + onlineRewardShare.getSharePercent(), + mintingGroupAddresses.contains(minter), + nameByAddress.get(minter), + levelByAddress.get(minter) + ); + + onlineAccounts.add(onlineAccountData); + } + + return onlineAccounts; + } catch (DataException e) { + throw e; + } catch (Exception e ) { + LOGGER.error(e.getMessage(), e); + + return new HashSet<>(0); + } + } +} \ No newline at end of file