Skip to content

Commit

Permalink
Merge branch 'develop' into generate/9a6c0845
Browse files Browse the repository at this point in the history
  • Loading branch information
gmalouf authored Jan 15, 2025
2 parents 6193384 + 35d8ab4 commit fe1959e
Show file tree
Hide file tree
Showing 11 changed files with 215 additions and 14 deletions.
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
- run: mvn test
integration-test:
machine:
image: "ubuntu-2204:2022.04.2"
image: "ubuntu-2204:2022.10.2"
resource_class: medium
steps:
- checkout
Expand Down
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@
<maven.surefire.version>3.0.0-M5</maven.surefire.version>

<!-- Test sources at 1.8 to allow using JUnit5 -->
<java.version>1.7</java.version>
<java.release.version>7</java.release.version>
<java.version>1.8</java.version>
<java.release.version>8</java.release.version>
<java.test.release.version>8</java.test.release.version>

<!-- github server corresponds to entry in ~/.m2/settings.xml -->
Expand Down
74 changes: 74 additions & 0 deletions src/main/java/com/algorand/algosdk/transaction/HeartbeatProof.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package com.algorand.algosdk.transaction;

import java.io.Serializable;
import java.util.Objects;

import com.algorand.algosdk.crypto.*;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;


@JsonPropertyOrder(alphabetic = true)
@JsonInclude(JsonInclude.Include.NON_DEFAULT)
public class HeartbeatProof implements Serializable {
@JsonProperty("s")
public Signature sig = new Signature();

@JsonProperty("p")
public Ed25519PublicKey pk = new Ed25519PublicKey();

@JsonProperty("p2")
public Ed25519PublicKey pk2 = new Ed25519PublicKey();

@JsonProperty("p1s")
public Signature pk1Sig = new Signature();

@JsonProperty("p2s")
public Signature pk2Sig = new Signature();

public HeartbeatProof() {}

public HeartbeatProof(
Signature sig,
Ed25519PublicKey pk,
Ed25519PublicKey pk2,
Signature pk1Sig,
Signature pk2Sig
) {
this.sig = Objects.requireNonNull(sig, "sig must not be null");
this.pk = Objects.requireNonNull(pk, "pk must not be null");
this.pk2 = Objects.requireNonNull(pk2, "pk2 must not be null");
this.pk1Sig = Objects.requireNonNull(pk1Sig, "pk1Sig must not be null");
this.pk2Sig = Objects.requireNonNull(pk2Sig, "pk2Sig must not be null");
}

@JsonCreator
public HeartbeatProof(
@JsonProperty("s") byte[] sig,
@JsonProperty("p") byte[] pk,
@JsonProperty("p2") byte[] pk2,
@JsonProperty("p1s") byte[] pk1Sig,
@JsonProperty("p2s") byte[] pk2Sig
) {
this.sig = new Signature(sig);
this.pk = new Ed25519PublicKey(pk);
this.pk2 = new Ed25519PublicKey(pk2);
this.pk1Sig = new Signature(pk1Sig);
this.pk2Sig = new Signature(pk2Sig);
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
HeartbeatProof that = (HeartbeatProof) o;
return Objects.equals(sig, that.sig) &&
Objects.equals(pk, that.pk) &&
Objects.equals(pk2, that.pk2) &&
Objects.equals(pk1Sig, that.pk1Sig) &&
Objects.equals(pk2Sig, that.pk2Sig);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package com.algorand.algosdk.transaction;

import java.io.Serializable;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.Objects;

import com.algorand.algosdk.crypto.*;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;

@JsonPropertyOrder(alphabetic = true)
@JsonInclude(JsonInclude.Include.NON_DEFAULT)
public class HeartbeatTxnFields implements Serializable {
@JsonProperty("a")
public Address hbAddress = new Address();

@JsonProperty("prf")
public HeartbeatProof hbProof = new HeartbeatProof();

@JsonProperty("sd")
public byte[] hbSeed = new byte[32]; // committee.Seed

@JsonProperty("vid")
public ParticipationPublicKey hbVoteID = new ParticipationPublicKey();

@JsonProperty("kd")
public BigInteger hbKeyDilution = BigInteger.valueOf(0);

public HeartbeatTxnFields() {}

public HeartbeatTxnFields(
Address hbAddress,
HeartbeatProof hbProof,
byte[] hbSeed,
ParticipationPublicKey hbVoteID,
BigInteger hbKeyDilution
) {
this.hbAddress = Objects.requireNonNull(hbAddress, "hbAddress must not be null");
this.hbProof = Objects.requireNonNull(hbProof, "hbProof must not be null");
this.hbVoteID = Objects.requireNonNull(hbVoteID, "hbVoteID must not be null");
this.hbKeyDilution = Objects.requireNonNull(hbKeyDilution, "hbKeyDilution must not be null");
if (hbSeed == null) {
throw new NullPointerException("hbSeed must not be null");
}
System.arraycopy(hbSeed, 0, this.hbSeed, 0, this.hbSeed.length);
}

@JsonCreator
public HeartbeatTxnFields(
@JsonProperty("a") byte[] hbAddress,
@JsonProperty("prf") HeartbeatProof hbProof,
@JsonProperty("sd") byte[] hbSeed,
@JsonProperty("vid") byte[] hbVoteID,
@JsonProperty("kd") BigInteger hbKeyDilution
) {
if (hbAddress != null) {
this.hbAddress = new Address(hbAddress);
}
if (hbProof != null) {
this.hbProof = hbProof;
}
if (hbSeed != null) {
System.arraycopy(hbSeed, 0, this.hbSeed, 0, this.hbSeed.length);
}
if (hbVoteID != null) {
this.hbVoteID = new ParticipationPublicKey(hbVoteID);
}
if (hbKeyDilution != null) {
this.hbKeyDilution = hbKeyDilution;
}
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
HeartbeatTxnFields that = (HeartbeatTxnFields) o;
return hbAddress.equals(that.hbAddress) &&
hbProof.equals(that.hbProof) &&
Arrays.equals(hbSeed, that.hbSeed) &&
hbVoteID.equals(that.hbVoteID) &&
hbKeyDilution.equals(that.hbKeyDilution);
}
}
20 changes: 15 additions & 5 deletions src/main/java/com/algorand/algosdk/transaction/Transaction.java
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,9 @@ public class Transaction implements Serializable {
@JsonProperty("spmsg")
public Map<String,Object> stateProofMessage = null;

@JsonProperty("hb")
public HeartbeatTxnFields heartbeatFields = new HeartbeatTxnFields();

/**
* Helper for Jackson conversion.
*/
Expand Down Expand Up @@ -238,7 +241,9 @@ private Transaction(@JsonProperty("type") Type type,
@JsonProperty("apid") Long applicationId,
@JsonProperty("apls") StateSchema localStateSchema,
@JsonProperty("apsu") byte[] clearStateProgram,
@JsonProperty("apep") Long extraPages
@JsonProperty("apep") Long extraPages,
// heartbeat fields
@JsonProperty("hb") HeartbeatTxnFields heartbeatFields
) throws IOException {
this(
type,
Expand Down Expand Up @@ -289,7 +294,8 @@ private Transaction(@JsonProperty("type") Type type,
applicationId,
localStateSchema,
clearStateProgram == null ? null : new TEALProgram(clearStateProgram),
extraPages
extraPages,
heartbeatFields
);
}

Expand Down Expand Up @@ -348,7 +354,8 @@ private Transaction(
Long applicationId,
StateSchema localStateSchema,
TEALProgram clearStateProgram,
Long extraPages
Long extraPages,
HeartbeatTxnFields heartbeatFields
) {
if (type != null) this.type = type;
if (sender != null) this.sender = sender;
Expand Down Expand Up @@ -393,6 +400,7 @@ private Transaction(
if (localStateSchema != null) this.localStateSchema = localStateSchema;
if (clearStateProgram != null) this.clearStateProgram = clearStateProgram;
if (extraPages != null) this.extraPages = extraPages;
if (heartbeatFields != null) this.heartbeatFields = heartbeatFields;
}

// Used by Jackson to determine "default" values.
Expand Down Expand Up @@ -433,7 +441,8 @@ public enum Type {
AssetTransfer("axfer"),
AssetFreeze("afrz"),
ApplicationCall("appl"),
StateProof("stpf");
StateProof("stpf"),
Heartbeat("hb");

private static Map<String, Type> namesMap = new HashMap<String, Type>(6);

Expand Down Expand Up @@ -604,7 +613,8 @@ public boolean equals(Object o) {
freezeState == that.freezeState &&
rekeyTo.equals(that.rekeyTo) &&
extraPages.equals(that.extraPages) &&
boxReferences.equals(that.boxReferences);
boxReferences.equals(that.boxReferences) &&
heartbeatFields.equals(that.heartbeatFields);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ public LookupAccountByID includeAll(Boolean includeAll) {
}

/**
* Include results for the specified round.
* Deprecated and disallowed. This parameter used to include results for a
* specified round. Requests with this parameter set are now rejected.
*/
public LookupAccountByID round(Long round) {
addQuery("round", String.valueOf(round));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,8 @@ public SearchForAccounts next(String next) {
}

/**
* Include results for the specified round. For performance reasons, this parameter
* may be disabled on some configurations. Using application-id or asset-id filters
* will return both creator and opt-in accounts. Filtering by include-all will
* return creator and opt-in accounts for deleted assets and accounts. Non-opt-in
* managers are not included in the results when asset-id is used.
* Deprecated and disallowed. This parameter used to include results for a
* specified round. Requests with this parameter set are now rejected.
*/
public SearchForAccounts round(Long round) {
addQuery("round", String.valueOf(round));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -917,6 +917,20 @@ public void EmptyByteArraysShouldBeRejected() throws Exception {
assertThat(tx.lease).isNull();
}

@Test
public void testDeserializationHeartbeats() throws Exception {

Address hbAddress = new Address("NRJ2UKUNLR3FHLTIYG5RP576RXX7MAU25F7DW6LCM5D45WF67H6EFQMWNM");
Address snd = new Address("GAU5WA6DT2EPFS6LKOA333BQP67NXIHZ7JPOOHMZWJDPZRL4XMHDDDUCKA");
String goldenString = "gqRsc2lngaFsxAYLMSAyAxKjdHhuhqJmdmqiZ2jEIP9SQzAGyec/v8omzEOW3/GIM+a7bvPaU5D/ohX7qjFtomhihaFhxCBsU6oqjVx2U65owbsX9/6N7/YCmul+O3liZ0fO2L75/KJrZGSjcHJmhaFwxCAM1TyIrIbgm+yPLT9so6VDI3rKl33t4c4RSGJv6G12eaNwMXPEQBETln14zJzQ1Mb/SNjmDNl0fyQ4DPBQZML8iTEbhqBj+YDAgpNSEduWj7OuVkCSQMq4N/Er/+2HfKUHu//spgOicDLEIB9c5n7WgG+5aOdjfBmuxH3z4TYiQzDVYKjBLhv4IkNfo3Ayc8RAeKpQ+o/GJyGCH0I4f9luN0i7BPXlMlaJAuXLX5Ng8DTN0vtZtztjqYfkwp1cVOYPu+Fce3aIdJHVoUDaJaMIDqFzxEBQN41y5zAZhYHQWf2wWF6CGboqQk6MxDcQ76zXHvVtzrAPUWXZDt4IB8Ha1z+54Hc6LmEoG090pk0IYs+jLN8HonNkxCCPVPjiD5O7V0c3P/SVsHmED7slwllta7c92WiKwnvgoqN2aWTEIHBy8sOi/V0YKXJw8VtW40MbqhtUyO9HC9m/haf84xiGomx2dKNzbmTEIDAp2wPDnojyy8tTgb3sMH++26D5+l7nHZmyRvzFfLsOpHR5cGWiaGI=";

SignedTransaction o = Encoder.decodeFromMsgPack(goldenString, SignedTransaction.class);
assertThat(o.tx.type).isEqualTo(Transaction.Type.Heartbeat);
assertThat(o.tx.heartbeatFields.hbKeyDilution).isEqualTo(100);
assertThat(o.tx.heartbeatFields.hbAddress).isEqualTo(hbAddress);
TestUtil.serializeDeserializeCheck(o);
}

@Nested
class TestFees {
@Test
Expand Down
5 changes: 5 additions & 0 deletions src/test/java/com/algorand/algosdk/unit/AlgodResponses.java
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,11 @@ public void the_parsed_Get_Block_response_should_have_rewards_pool(String string
verifyResponse(blockResponse, shared.bodyFile);
}

@Then("the parsed Get Block response should have heartbeat address {string}")
public void the_parsed_Get_Block_response_should_have_heartbeat_address(String string) throws IOException {
verifyResponse(blockResponse, shared.bodyFile);
}

@Then("the parsed Suggested Transaction Parameters response should have first round valid of {int}")
public void the_parsed_Suggested_Transaction_Parameters_response_should_have_first_round_valid_of(Integer int1) throws IOException {
verifyResponse(transactionParametersResponse, shared.bodyFile);
Expand Down
10 changes: 10 additions & 0 deletions src/test/java/com/algorand/algosdk/unit/IndexerResponses.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.algorand.algosdk.unit;

import static com.algorand.algosdk.unit.utils.TestingUtils.verifyResponse;
import static org.assertj.core.api.Assertions.assertThat;

import java.io.IOException;
import java.math.BigInteger;
Expand Down Expand Up @@ -188,4 +189,13 @@ public void the_parsed_SearchAccounts_response_should_be_valid_on_round_and_the_
public void the_parsed_SearchForTransactions_response_should_be_valid_on_round_and_the_array_should_be_of_len_and_the_element_at_index_should_have_rekey_to(Integer int1, Integer int2, Integer int3, String string) throws IOException {
verifyResponse(transactionsResponse, shared.bodyFile);
}

@When("the parsed SearchForTransactions response should be valid on round {int} and the array should be of len {int} and the element at index {int} should have hbaddress {string}")
public void the_parsed_SearchForTransactions_response_should_be_valid_on_round_and_the_array_should_be_of_len_and_the_element_at_index_should_have_hbaddress(Integer round, Integer length, Integer index, String hbaddress) throws IOException {
verifyResponse(transactionsResponse, shared.bodyFile);

assertThat(transactionsResponse.body().currentRound).isEqualTo(round.longValue());
assertThat(transactionsResponse.body().transactions.size()).isEqualTo(length.intValue());
assertThat(transactionsResponse.body().transactions.get(index).heartbeatTransaction.hbAddress).isEqualTo(hbaddress);
}
}
3 changes: 3 additions & 0 deletions src/test/unit.tags
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
@unit.abijson.byname
@unit.algod
@unit.algod.ledger_refactoring
@unit.algod.heartbeat
@unit.algod.heartbeat.msgp
@unit.applications
@unit.applications.boxes
@unit.atomic_transaction_composer
Expand All @@ -14,6 +16,7 @@
@unit.indexer.ledger_refactoring
@unit.indexer.logs
@unit.indexer.rekey
@unit.indexer.heartbeat
@unit.offline
@unit.program_sanity_check
@unit.ready
Expand Down

0 comments on commit fe1959e

Please sign in to comment.