Skip to content

Commit

Permalink
Feature 69 spo gov votes notifications (#71)
Browse files Browse the repository at this point in the history
* work in progress

Signed-off-by: Alessio <[email protected]>

* initial implementation of pool votes notification system. Nedes to be tested

Signed-off-by: Alessio <[email protected]>

* tested at runtime (no votes yet)

Signed-off-by: Alessio <[email protected]>

* Added integration tests

Signed-off-by: Alessio <[email protected]>

* Completed tests

Signed-off-by: Alessio <[email protected]>

---------

Signed-off-by: Alessio <[email protected]>
Co-authored-by: Alessio <[email protected]>
  • Loading branch information
DevStakePool and slux83 authored Feb 22, 2025
1 parent 582b77b commit 719ded9
Show file tree
Hide file tree
Showing 15 changed files with 7,871 additions and 48 deletions.
20 changes: 20 additions & 0 deletions sql/alter-1.9.1-TO-1.9.2.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
-- Create new Pools Votes table
CREATE TABLE public.pool_votes (
id bigint NOT NULL,
gov_id character varying(128) NOT NULL,
pool_id character varying(128) NOT NULL,
block_time bigint NOT NULL
);

CREATE SEQUENCE public.pool_votes_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;

ALTER SEQUENCE public.pool_votes_id_seq OWNED BY public.pool_votes.id;
ALTER TABLE ONLY public.pool_votes ALTER COLUMN id SET DEFAULT nextval('public.pool_votes_id_seq'::regclass);

ALTER TABLE ONLY public.pool_votes
ADD CONSTRAINT pool_votes_pkey PRIMARY KEY (id);
2 changes: 1 addition & 1 deletion src/main/java/com/devpool/thothBot/dao/AssetsDao.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public Optional<Asset> getAssetInformation(String policyId, String assetName) {

if (assets.isEmpty()) return Optional.empty();

return Optional.ofNullable(assets.get(0));
return Optional.ofNullable(assets.getFirst());
}

@Transactional(isolation = Isolation.REPEATABLE_READ)
Expand Down
73 changes: 73 additions & 0 deletions src/main/java/com/devpool/thothBot/dao/PoolVotesDao.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package com.devpool.thothBot.dao;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.stereotype.Repository;

import javax.annotation.PostConstruct;
import java.util.List;
import java.util.Map;

@Repository
public class PoolVotesDao {
private static final Logger LOG = LoggerFactory.getLogger(PoolVotesDao.class);
private static final String FIELD_GOV_ID = "gov_id";
private static final String FIELD_BLOCK_TIME = "block_time";
private static final String FIELD_POOL_ID = "pool_id";

private final NamedParameterJdbcTemplate namedParameterJdbcTemplate;

public PoolVotesDao(NamedParameterJdbcTemplate namedParameterJdbcTemplate) {
this.namedParameterJdbcTemplate = namedParameterJdbcTemplate;
}

@PostConstruct
public void post() {
LOG.info("Pool Votes DAO initialised");
}

/**
* Get the votes for a given gov action of a pool, lower than the given block time
*
* @param govId gov action
* @param poolId the Pool ID
* @param blockTime latest block time of the vote
* @return the list of block times of all the pool votes for a given gov action
*/
public List<Long> getVotesForGovAction(String govId, String poolId, long blockTime) {
return this.namedParameterJdbcTemplate.query(
"""
select block_time
from pool_votes
where gov_id = :gov_id and
pool_id = :pool_id and
block_time <= :block_time
""",
Map.of(FIELD_GOV_ID, govId,
FIELD_POOL_ID, poolId,
FIELD_BLOCK_TIME, blockTime),
(rs, numRow) -> rs.getLong(FIELD_BLOCK_TIME));
}

public void addPoolVote(String govId, String poolId, Long blockTime) {
GeneratedKeyHolder keyHolder = new GeneratedKeyHolder();
namedParameterJdbcTemplate.update(
"""
insert into pool_votes (gov_id, pool_id, block_time)
values (:gov_id, :pool_id, :block_time)
""",
new MapSqlParameterSource(Map.of(
FIELD_GOV_ID, govId,
FIELD_POOL_ID, poolId,
FIELD_BLOCK_TIME, blockTime)),
keyHolder, new String[]{"id"});

LOG.trace("Inserted new pool vote with key {} for gov action {} and pool {} ",
keyHolder.getKeyAs(Long.class), govId, poolId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.devpool.thothBot.koios.KoiosFacade;
import com.devpool.thothBot.model.model.DrepMetadata;
import com.devpool.thothBot.oracle.CoinPaprikaOracle;
import com.devpool.thothBot.util.CollectionsUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
Expand All @@ -14,11 +15,15 @@
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
import rest.koios.client.backend.api.account.model.AccountAsset;
import rest.koios.client.backend.api.account.model.AccountInfo;
import rest.koios.client.backend.api.address.model.AddressAsset;
import rest.koios.client.backend.api.base.Result;
import rest.koios.client.backend.api.base.common.Asset;
import rest.koios.client.backend.api.base.exception.ApiException;
import rest.koios.client.backend.api.pool.model.PoolInfo;
import rest.koios.client.backend.factory.options.Limit;
import rest.koios.client.backend.factory.options.Offset;
import rest.koios.client.backend.factory.options.Options;

import java.net.URI;
import java.nio.charset.StandardCharsets;
Expand Down Expand Up @@ -256,4 +261,37 @@ protected String shortenDrepHash(String drepHash) {

return drepHash;
}

protected String constructInFilter(Collection<String> items) {
return "(" + String.join(",", items) + ")";
}

// returns Staking Address -> Pool Address
protected Map<String, String> collectPoolAddressesAssociatedToStakingAddresses(List<String> allStakingAddresses) throws ApiException {
Map<String, String> allPoolIdsStakingAddresses = new HashMap<>();
Options options = Options.builder()
.option(Limit.of(DEFAULT_PAGINATION_SIZE))
.option(Offset.of(0))
.build();

var iter = CollectionsUtil.batchesList(allStakingAddresses, usersBatchSize).iterator();
while (iter.hasNext()) {
var batch = iter.next();
LOG.debug("Grabbing staking account info for batch of size {}", batch.size());
var resp = koiosFacade.getKoiosService().getAccountService().getCachedAccountInformation(batch, options);
if (!resp.isSuccessful()) {
throw new ApiException(String.format("Invalid API response while getting account infos: %d - %s",
resp.getCode(), resp.getResponse()));
}

for (AccountInfo accountInfo : resp.getValue()) {
var stakingAddr = accountInfo.getStakeAddress();
var poolAddr = accountInfo.getDelegatedPool();
if (poolAddr != null)
allPoolIdsStakingAddresses.put(stakingAddr, poolAddr);
}
}

return allPoolIdsStakingAddresses;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,25 +24,25 @@
import java.util.stream.Collectors;

@Component
public class GovernanceVotesCheckerTask extends AbstractCheckerTask implements Runnable {
private static final Logger LOG = LoggerFactory.getLogger(GovernanceVotesCheckerTask.class);
public class GovernanceDrepVotesCheckerTask extends AbstractCheckerTask implements Runnable {
private static final Logger LOG = LoggerFactory.getLogger(GovernanceDrepVotesCheckerTask.class);
private static final String FIELD_BLOCK_TIME = "block_time";
private final TelegramFacade telegramFacade;

public GovernanceVotesCheckerTask(TelegramFacade telegramFacade) {
public GovernanceDrepVotesCheckerTask(TelegramFacade telegramFacade) {
this.telegramFacade = telegramFacade;
}

@Override
public void run() {
LOG.info("Checking for new governance votes");
LOG.info("Checking for new DRep governance votes");

try {

LOG.info("Checking governance votes for {} wallets", this.userDao.getUsers().size());
// Filter out non-staking users
Iterator<List<User>> batchIterator = CollectionsUtil.batchesList(
userDao.getUsers().stream().filter(User::isStakeAddress).collect(Collectors.toList()),
userDao.getUsers().stream().filter(User::isStakeAddress).toList(),
this.usersBatchSize).iterator();

while (batchIterator.hasNext()) {
Expand Down Expand Up @@ -73,7 +73,7 @@ private void processUserBatch(List<User> usersBatch) {
Map<String, String> batchDreps;
try {
var response = koiosFacade.getKoiosService().getAccountService().getCachedAccountInformation(
usersBatch.stream().map(User::getAddress).collect(Collectors.toList()), defaultOpts);
usersBatch.stream().map(User::getAddress).toList(), defaultOpts);

if (!response.isSuccessful())
throw new ApiException("Response was not successful.");
Expand All @@ -93,7 +93,7 @@ private void processUserBatch(List<User> usersBatch) {
return; // We'll try again later
}

var drepNames = super.getDrepNames(batchDreps.values().stream().distinct().collect(Collectors.toList()));
var drepNames = super.getDrepNames(batchDreps.values().stream().distinct().toList());
var handles = super.getAdaHandleForAccount(batchDreps.keySet().toArray(new String[0]));
for (var user : batchDreps.entrySet()) {
var drepName = drepNames.get(user.getValue());
Expand Down Expand Up @@ -162,7 +162,7 @@ private void renderUserNotification(User user, String drepId, String drepName, L

this.telegramFacade.sendMessageTo(user.getChatId(), sb.toString());
if (LOG.isTraceEnabled()) {
LOG.trace("Sending telegram message for governance votes: {}", sb);
LOG.trace("Sending telegram message for DREP governance votes: {}", sb);
}
}
}
Loading

0 comments on commit 719ded9

Please sign in to comment.