Skip to content

Commit

Permalink
Merge pull request #12 from bloxbean/rewards
Browse files Browse the repository at this point in the history
feat: Add DelegationAndRewards query for stake addresses
  • Loading branch information
satran004 authored Jun 12, 2023
2 parents d13720e + 16dea12 commit 3f7d295
Show file tree
Hide file tree
Showing 3 changed files with 214 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package com.bloxbean.cardano.yaci.core.protocol.localstate.queries;

import co.nstant.in.cbor.model.Array;
import co.nstant.in.cbor.model.ByteString;
import co.nstant.in.cbor.model.DataItem;
import co.nstant.in.cbor.model.UnsignedInteger;
import com.bloxbean.cardano.client.address.Address;
import com.bloxbean.cardano.client.address.Credential;
import com.bloxbean.cardano.client.address.CredentialType;
import com.bloxbean.cardano.yaci.core.protocol.handshake.messages.AcceptVersion;
import com.bloxbean.cardano.yaci.core.protocol.handshake.util.N2CVersionTableConstant;
import com.bloxbean.cardano.yaci.core.protocol.localstate.api.Era;
import com.bloxbean.cardano.yaci.core.protocol.localstate.api.EraQuery;
import com.bloxbean.cardano.yaci.core.util.HexUtil;
import lombok.Getter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;

import java.math.BigInteger;
import java.util.*;

/**
* Get delegations and reward balances for list of stake addresses
*/
@Getter
@ToString
@Slf4j
public class DelegationsAndRewardAccountsQuery implements EraQuery<DelegationsAndRewardAccountsResult> {
private Era era;
private Set<Address> stakeAddresses;
private Map<Credential, Address> credentialAddressMap = new HashMap<>();

/**
* @param era current era
* @param stakeAddresses list of stake addresses
*/
public DelegationsAndRewardAccountsQuery(Era era, Set<Address> stakeAddresses) {
this.era = era;
this.stakeAddresses = stakeAddresses;

if (stakeAddresses == null || stakeAddresses.size() == 0)
throw new IllegalArgumentException("Stake addresses cannot be null or empty");

stakeAddresses.stream()
.forEach(stakeAddress -> {
Credential credential = stakeAddress.getDelegationCredential()
.map(credBytes -> {
Credential cred;
if (stakeAddress.isStakeKeyHashInDelegationPart())
cred = Credential.fromKey(credBytes);
else
cred = Credential.fromScript(credBytes);
return cred;
}).orElseThrow(() -> new IllegalArgumentException("Delegation credential not found for address: " + stakeAddress));
credentialAddressMap.put(credential, stakeAddress);
});
}

/**
* List of stake addresses
*
* @param stakeAddresses
*/
public DelegationsAndRewardAccountsQuery(Set<Address> stakeAddresses) {
this(Era.Babbage, stakeAddresses);
}

/**
* Format:
* query [10 #6.258([ *rwdr ])]
* rwdr [flag bytestring] bytestring is the keyhash of the staking vkey
* flag 0/1 0=keyhash 1=scripthash
* result [[ delegation rewards] ]
* delegation { * rwdr - poolid } poolid is a bytestring
* Reference: https://arsmagna.xyz/docs/network-lsq/
**/
@Override
public DataItem serialize(AcceptVersion protocolVersion) {
Array array = new Array();
array.add(new UnsignedInteger(10)); //tag

//Sort credentials by Script first, then bytes based on network order integers
List<Credential> inputCredentials = new ArrayList(credentialAddressMap.keySet());
inputCredentials.sort(Comparator.comparing(Credential::getType, Comparator.reverseOrder())
.thenComparing(c -> HexUtil.encodeHexString(c.getBytes())));

//Build input cbor array
Array credentials = new Array();
inputCredentials.stream()
.forEach(cred -> {
int flag = cred.getType() == CredentialType.Key ? 0 : 1;
var credentialArr = new Array();
credentialArr.add(new UnsignedInteger(flag));
credentialArr.add(new ByteString(cred.getBytes()));
credentials.add(credentialArr);
});

if (protocolVersion.getVersionNumber() <= N2CVersionTableConstant.PROTOCOL_V13) {
credentials.setTag(258);
}

array.add(credentials);
return wrapWithOuterArray(array);
}

@Override
public DelegationsAndRewardAccountsResult deserializeResult(AcceptVersion protocolVersion, DataItem[] di) {
List<DataItem> delegationRewardsDIList = extractResultArray(di[0]);
Array delegationRewardsArray = (Array) delegationRewardsDIList.get(0);

Map<Address, String> delegations = new LinkedHashMap<>();
Map<Address, BigInteger> rewards = new LinkedHashMap<>();

var delegationMap = (co.nstant.in.cbor.model.Map) delegationRewardsArray.getDataItems().get(0);
var rewardsMap = (co.nstant.in.cbor.model.Map) delegationRewardsArray.getDataItems().get(1);

delegationMap.getKeys().stream()
.forEach(key -> {
var rwdrFlag = ((UnsignedInteger) ((Array) key).getDataItems().get(0)).getValue().intValue(); //[flag bytestring]
var rwdrStakingKeyHash = ((ByteString) ((Array) key).getDataItems().get(1)).getBytes();

Credential credential = rwdrFlag == 0 ? Credential.fromKey(rwdrStakingKeyHash) : Credential.fromScript(rwdrStakingKeyHash);
Address stakeAddress = credentialAddressMap.get(credential);
if (stakeAddress == null)
throw new IllegalStateException("Stake address not found for credential: " + credential);

var poolBytes = ((ByteString) delegationMap.get(key)).getBytes();
delegations.put(stakeAddress, HexUtil.encodeHexString(poolBytes));
});

rewardsMap.getKeys().stream()
.forEach(key -> {
var rwdrFlag = ((UnsignedInteger) ((Array) key).getDataItems().get(0)).getValue().intValue(); //[flag bytestring]
var rwdrStakingKeyHash = ((ByteString) ((Array) key).getDataItems().get(1)).getBytes();

Credential credential = rwdrFlag == 0 ? Credential.fromKey(rwdrStakingKeyHash) : Credential.fromScript(rwdrStakingKeyHash);
Address stakeAddress = credentialAddressMap.get(credential);
if (stakeAddress == null)
throw new IllegalStateException("Stake address not found for credential: " + credential);

BigInteger amount = ((UnsignedInteger) rewardsMap.get(key)).getValue();
rewards.put(stakeAddress, amount);
});

return new DelegationsAndRewardAccountsResult(delegations, rewards);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.bloxbean.cardano.yaci.core.protocol.localstate.queries;

import com.bloxbean.cardano.client.address.Address;
import com.bloxbean.cardano.yaci.core.protocol.localstate.api.QueryResult;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;

import java.math.BigInteger;
import java.util.Map;

/**
* Result of DelegationsAndRewardAccountsQuery. Contains delegations and rewards for stake addresses
* Delegations are represented as Map of Stake address - Delegated pool id in hex
* Rewards are represented as Map of Stake address - Rewards
*/
@Getter
@AllArgsConstructor
@ToString
public class DelegationsAndRewardAccountsResult implements QueryResult {
//Stake address -> Delegated pool id
private Map<Address, String> delegations;
//Stake address -> Rewards
private Map<Address, BigInteger> rewards;

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.List;
import java.util.Optional;
import java.util.*;

import static org.assertj.core.api.Assertions.assertThat;

Expand Down Expand Up @@ -170,9 +169,9 @@ void acquireReacquireAndQuery() {
epochNoQueryResult = queryResultMono.block(Duration.ofSeconds(20));
}

// @Test
// @Test
void nestedCalls() throws InterruptedException {
Mono<CurrentProtocolParamQueryResult> mono= localQueryProvider.getTxMonitorClient()
Mono<CurrentProtocolParamQueryResult> mono = localQueryProvider.getTxMonitorClient()
.acquireAndGetMempoolSizeAndCapacity()
.filter(mempoolStatus -> mempoolStatus.getNumberOfTxs() == 0)
.flatMap(mempoolStatus -> localStateQueryClient.acquire())
Expand Down Expand Up @@ -227,7 +226,7 @@ void poolDistrQuery() {
System.out.println(result);
}

// @Test
// @Test
void epochStateQuery() {
Mono<EpochStateQueryResult> mono = localStateQueryClient.executeQuery(new EpochStateQuery());
EpochStateQueryResult result = mono.block();
Expand All @@ -253,4 +252,41 @@ void genesisConfigQuery() {
assertThat(result.getMaxLovelaceSupply()).isEqualTo(45000000000000000L);

}

@Test
void delegationRewardsQuery() {
//Mix of regular staking key and script staking key to test if the sorting during input works, otherwise the request will fail
Set<Address> stakeAddresses = new LinkedHashSet<>(List.of(
new Address("stake_test1upfjudlteayz2ayenwkyqy77zxf92k69f28g0vur0rqmg3sjr95v7"),
new Address("stake_test1up97ct2wt8jqlly2cnkhuwc7tvevmjpp7h6ts3rucpksy8c8cnspn"),
new Address("stake_test1upkmrqs6qjmhwc89l3fzfvzf7vd0xl8zd5h76pah9kv4npc2ql0vf"),
new Address("stake_test1upzr8x6my8u5qfx74vgjsfexf2xf3qrfnmd6uahvu866xugj26s8g"),
new Address("stake_test1uret0j5crrwvvm6e94p20yfgyswwzvrr3hd8cz7n6h4qensv6502k"),
new Address("stake_test17qag3rt979nep9g2wtdwu8mr4gz6m4kjdpp5zp705km8wys6r5wgh"),//script 1,
new Address("stake_test17rgr608tyvgawu5ja328xy523rrj75xx5x492gltauc649czsnx8t"), //script 2
new Address("stake_test17p6ffq4jle9vw9daatwx0kclgfsqfqltkxgnl2q2yeq35cc9du8ml"), //script 3
new Address("stake_test17rqvxk8m24yfl3qveuj0r32tktk3eymjvlf2cujtdavqfvc4xpaer") //script 4
));

stakeAddresses.forEach(address -> System.out.println("Input Address >> " + address.toBech32()));

Mono<DelegationsAndRewardAccountsResult> mono = localStateQueryClient.executeQuery(new DelegationsAndRewardAccountsQuery(stakeAddresses));
DelegationsAndRewardAccountsResult result = mono.block();

Map<Address, String> delegations = result.getDelegations();
Map<Address, BigInteger> rewards = result.getRewards();

System.out.println("######### Delegations and Rewards ########");
delegations.forEach((address, poolId) -> {
System.out.println("Delegation >> " + address.toBech32() + " : " + poolId);
});

rewards.forEach((address, reward) -> {
System.out.println("Reward >> " + address.toBech32() + " : " + reward);
});

assertThat(delegations).hasSizeGreaterThanOrEqualTo(5); //only 5 regular staking keys has delegations
assertThat(rewards).hasSizeGreaterThanOrEqualTo(5); //only 5 regular staking keys has rewards
}

}

0 comments on commit 3f7d295

Please sign in to comment.