Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Apply code improvments suggested during audit #831

Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -290,11 +290,15 @@ public void _timeoutPacket(Packet packet, byte[] proofHeight, byte[] proof, BigI
ConnectionEnd connection = ConnectionEnd.decode(connectionPb);

// check that timeout height or timeout timestamp has passed on the other end
ILightClient client = getClient(connection.getClientId());
Height height = Height.decode(proofHeight);
BigInteger timestamp = client.getTimestampAtHeight(connection.getClientId(), proofHeight);
boolean heightTimeout = packet.getTimeoutHeight().getRevisionHeight().compareTo(BigInteger.ZERO) > 0
&& height.getRevisionHeight()
.compareTo(packet.getTimeoutHeight().getRevisionHeight()) >= 0;
Context.require(heightTimeout, "Packet has not yet timed out");
boolean timeTimeout = packet.getTimeoutTimestamp().compareTo(BigInteger.ZERO) > 0
&& timestamp.compareTo(packet.getTimeoutTimestamp()) >= 0;
Context.require(heightTimeout || timeTimeout, "Packet has not yet timed out");

// verify we actually sent this packet, check the store
byte[] packetCommitmentKey = IBCCommitment.packetCommitmentKey(packet.getSourcePort(),
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
package ibc.ics08.tendermint;

import icon.ibc.interfaces.ILightClient;
import ibc.icon.score.util.ByteUtil;
import ibc.icon.score.util.NullChecker;
import ibc.icon.score.util.StringUtil;
import ibc.ics23.commitment.types.Merkle;
import ibc.ics24.host.IBCCommitment;
import cosmos.ics23.v1.*;
import google.protobuf.*;
import tendermint.types.*;
import ibc.core.commitment.v1.*;
@@ -17,7 +15,6 @@
import score.Context;
import score.DictDB;
import score.annotation.External;
import score.annotation.Optional;

import java.math.BigInteger;
import java.util.Arrays;
@@ -54,7 +51,7 @@ private void onlyHandler() {

/**
* @dev getTimestampAtHeight returns the timestamp of the consensus state at the
* given height.
* given height.
*/
@External(readonly = true)
public BigInteger getTimestampAtHeight(
@@ -118,79 +115,29 @@ public Map<String, byte[]> createClient(String clientId, byte[] clientStateBytes
@External
public Map<String, byte[]> updateClient(String clientId, byte[] clientMessageBytes) {
onlyHandler();
ibc.lightclients.tendermint.v1.Header tmHeader = ibc.lightclients.tendermint.v1.Header.decode(clientMessageBytes);
boolean conflictingHeader = false;
// Check if the Client store already has a consensus state for the header's
// height
// If the consensus state exists, and it matches the header then we return early
// since header has already been submitted in a previous UpdateClient.
byte[] prevConsState = consensusStates.at(clientId)
.get(tmHeader.getSignedHeader().getHeader().getHeight());
if (prevConsState != null) {
// This header has already been submitted and the necessary state is already
// stored
Context.require(!Arrays.equals(prevConsState, toConsensusState(tmHeader).encode()),
"LC: This header has already been submitted");

// A consensus state already exists for this height, but it does not match the
// provided header.
// Thus, we must check that this header is valid, and if so we will freeze the
// client.
conflictingHeader = true;
}
ibc.lightclients.tendermint.v1.Header tmHeader = ibc.lightclients.tendermint.v1.Header
.decode(clientMessageBytes);
boolean conflictingHeader = checkForDuplicateHeader(clientId, tmHeader);

byte[] encodedClientState = clientStates.get(clientId);
require(encodedClientState != null, "LC: client state is invalid");
ClientState clientState = ClientState.decode(encodedClientState);
byte[] encodedTrustedonsensusState = consensusStates.at(clientId).get(tmHeader.getTrustedHeight().getRevisionHeight());
require(encodedTrustedonsensusState != null, "LC: consensusState not found at trusted height");
ConsensusState trustedConsensusState = ConsensusState.decode(encodedTrustedonsensusState);

Timestamp currentTime = getCurrentTime();
checkValidity(clientState, trustedConsensusState, tmHeader, currentTime);
byte[] encodedTrustedConsensusState = consensusStates.at(clientId)
.get(tmHeader.getTrustedHeight().getRevisionHeight());
require(encodedTrustedConsensusState != null, "LC: consensusState not found at trusted height");
ConsensusState trustedConsensusState = ConsensusState.decode(encodedTrustedConsensusState);

checkValidity(clientState, trustedConsensusState, tmHeader);

// Header is different from existing consensus state and also valid, so freeze
// the client and return
if (conflictingHeader) {
BigInteger revision = getRevisionNumber(tmHeader.getSignedHeader().getHeader().getChainId());
clientState.setFrozenHeight(newHeight(tmHeader.getSignedHeader().getHeader().getHeight(), revision));
encodedClientState = clientState.encode();
clientStates.set(clientId, encodedClientState);

byte[] encodedConsensusState = toConsensusState(tmHeader).encode();
consensusStates.at(clientId).set(clientState.getLatestHeight().getRevisionHeight(), encodedConsensusState);
processedHeights.at(clientId).set(tmHeader.getSignedHeader().getHeader().getHeight(),
BigInteger.valueOf(Context.getBlockHeight()));
processedTimes.at(clientId).set(tmHeader.getSignedHeader().getHeader().getHeight(),
BigInteger.valueOf(Context.getBlockTimestamp()));

return Map.of(
"clientStateCommitment", IBCCommitment.keccak256(encodedClientState),
"consensusStateCommitment", IBCCommitment.keccak256(encodedConsensusState),
"height",
newHeight(tmHeader.getSignedHeader().getHeader().getHeight(), revision).encode());
return handleConflict(clientId, tmHeader, clientState);
}

// update the consensus state from a new header and set processed time metadata
if (tmHeader.getSignedHeader().getHeader().getHeight().compareTo(clientState.getLatestHeight().getRevisionHeight()) > 0) {
BigInteger revision = getRevisionNumber(tmHeader.getSignedHeader().getHeader().getChainId());
clientState.setLatestHeight(newHeight(tmHeader.getSignedHeader().getHeader().getHeight(), revision));
encodedClientState = clientState.encode();
clientStates.set(clientId, encodedClientState);
}
return updateHeader(clientId, tmHeader, clientState, encodedClientState);

byte[] encodedConsensusState = toConsensusState(tmHeader).encode();
consensusStates.at(clientId).set(tmHeader.getSignedHeader().getHeader().getHeight(),
encodedConsensusState);
processedHeights.at(clientId).set(tmHeader.getSignedHeader().getHeader().getHeight(),
BigInteger.valueOf(Context.getBlockHeight()));
processedTimes.at(clientId).set(tmHeader.getSignedHeader().getHeader().getHeight(),
BigInteger.valueOf(Context.getBlockTimestamp()));

return Map.of(
"clientStateCommitment", IBCCommitment.keccak256(encodedClientState),
"consensusStateCommitment", IBCCommitment.keccak256(encodedConsensusState),
"height", clientState.getLatestHeight().encode());
}

@External(readonly = true)
@@ -242,12 +189,79 @@ public void verifyNonMembership(
Merkle.verifyNonMembership(merkleProof, Merkle.SDK_SPEC, root, merklePath);
}

private Map<String, byte[]> updateHeader(String clientId, ibc.lightclients.tendermint.v1.Header tmHeader,
ClientState clientState, byte[] encodedClientState) {
if (tmHeader.getSignedHeader().getHeader().getHeight()
.compareTo(clientState.getLatestHeight().getRevisionHeight()) > 0) {
BigInteger revision = getRevisionNumber(tmHeader.getSignedHeader().getHeader().getChainId());
clientState.setLatestHeight(newHeight(tmHeader.getSignedHeader().getHeader().getHeight(), revision));
encodedClientState = clientState.encode();
clientStates.set(clientId, encodedClientState);
}

byte[] encodedConsensusState = toConsensusState(tmHeader).encode();
consensusStates.at(clientId).set(tmHeader.getSignedHeader().getHeader().getHeight(),
encodedConsensusState);
processedHeights.at(clientId).set(tmHeader.getSignedHeader().getHeader().getHeight(),
BigInteger.valueOf(Context.getBlockHeight()));
processedTimes.at(clientId).set(tmHeader.getSignedHeader().getHeader().getHeight(),
BigInteger.valueOf(Context.getBlockTimestamp()));

return Map.of(
"clientStateCommitment", IBCCommitment.keccak256(encodedClientState),
"consensusStateCommitment", IBCCommitment.keccak256(encodedConsensusState),
"height", clientState.getLatestHeight().encode());
}

private Map<String, byte[]> handleConflict(String clientId, ibc.lightclients.tendermint.v1.Header tmHeader,
ClientState clientState) {
BigInteger revision = getRevisionNumber(tmHeader.getSignedHeader().getHeader().getChainId());
clientState.setFrozenHeight(newHeight(tmHeader.getSignedHeader().getHeader().getHeight(), revision));
byte[] encodedClientState = clientState.encode();
clientStates.set(clientId, encodedClientState);

byte[] encodedConsensusState = toConsensusState(tmHeader).encode();
consensusStates.at(clientId).set(clientState.getLatestHeight().getRevisionHeight(), encodedConsensusState);
processedHeights.at(clientId).set(tmHeader.getSignedHeader().getHeader().getHeight(),
BigInteger.valueOf(Context.getBlockHeight()));
processedTimes.at(clientId).set(tmHeader.getSignedHeader().getHeader().getHeight(),
BigInteger.valueOf(Context.getBlockTimestamp()));

return Map.of(
"clientStateCommitment", IBCCommitment.keccak256(encodedClientState),
"consensusStateCommitment", IBCCommitment.keccak256(encodedConsensusState),
"height",
newHeight(tmHeader.getSignedHeader().getHeader().getHeight(), revision).encode());
}

private boolean checkForDuplicateHeader(String clientId, ibc.lightclients.tendermint.v1.Header tmHeader) {
// Check if the Client store already has a consensus state for the header's
// height
// If the consensus state exists, and it matches the header then we return early
// since header has already been submitted in a previous UpdateClient.
byte[] prevConsState = consensusStates.at(clientId)
.get(tmHeader.getSignedHeader().getHeader().getHeight());
if (prevConsState == null) {
return false;
}

// This header has already been submitted and the necessary state is already
// stored
Context.require(!Arrays.equals(prevConsState, toConsensusState(tmHeader).encode()),
"LC: This header has already been submitted");

// A consensus state already exists for this height, but it does not match the
// provided header.
// Thus, we must check that this header is valid, and if so we will freeze the
// client.
return true;
};

// checkValidity checks if the Tendermint header is valid.
public void checkValidity(
ClientState clientState,
ConsensusState trustedConsensusState,
ibc.lightclients.tendermint.v1.Header tmHeader,
Timestamp currentTime) {
ibc.lightclients.tendermint.v1.Header tmHeader) {
// assert header height is newer than consensus state
require(
tmHeader.getSignedHeader().getHeader().getHeight()
@@ -267,11 +281,11 @@ public void checkValidity(
SignedHeader untrustedHeader = tmHeader.getSignedHeader();
ValidatorSet untrustedVals = tmHeader.getValidatorSet();

Timestamp currentTime = getCurrentTime();
Context.require(!isExpired(trustedHeader, clientState.getTrustingPeriod(), currentTime),
"header can't be expired");

boolean ok = verify(
clientState.getTrustingPeriod(),
clientState.getMaxClockDrift(),
clientState.getTrustLevel(),
trustedHeader,
@@ -287,15 +301,15 @@ private void validateArgs(ClientState cs, BigInteger height, byte[] prefix, byte
Context.require(cs.getLatestHeight().getRevisionHeight().compareTo(height) >= 0,
"Latest height must be greater or equal to proof height");
Context.require(cs.getFrozenHeight().getRevisionHeight().equals(BigInteger.ZERO) ||
cs.getFrozenHeight().getRevisionHeight().compareTo(height) >= 0,
cs.getFrozenHeight().getRevisionHeight().compareTo(height) >= 0,
"Client is Frozen");
Context.require(prefix.length > 0, "Prefix cant be empty");
Context.require(proof.length > 0, "Proof cant be empty");
}

private void validateDelayPeriod(String clientId, Height height,
BigInteger delayPeriodTime,
BigInteger delayPeriodBlocks) {
BigInteger delayPeriodTime,
BigInteger delayPeriodBlocks) {
BigInteger currentTime = BigInteger.valueOf(Context.getBlockTimestamp());
BigInteger validTime = mustGetProcessedTime(clientId,
height.getRevisionHeight()).add(delayPeriodTime);
Original file line number Diff line number Diff line change
@@ -13,7 +13,6 @@

public abstract class Tendermint {
protected boolean verify(
Duration trustingPeriod,
Duration maxClockDrift,
Fraction trustLevel,
SignedHeader trustedHeader,
@@ -26,29 +25,22 @@ protected boolean verify(
boolean isAdjacent = untrustedHeader.getHeader().getHeight()
.equals(trustedHeader.getHeader().getHeight().add(BigInteger.ONE));
if (isAdjacent) {
return verifyAdjacent(trustedHeader, untrustedHeader, untrustedVals, trustingPeriod, currentTime,
maxClockDrift);
return verifyAdjacent(trustedHeader, untrustedHeader, untrustedVals);
}

return verifyNonAdjacent(
trustedHeader,
trustedVals,
untrustedHeader,
untrustedVals,
trustingPeriod,
currentTime,
maxClockDrift,
trustLevel);

}

protected boolean verifyAdjacent(
SignedHeader trustedHeader,
SignedHeader untrustedHeader,
ValidatorSet untrustedVals,
Duration trustingPeriod,
Timestamp currentTime,
Duration maxClockDrift) {
ValidatorSet untrustedVals) {

// Check the validator hashes are the same
Context.require(
@@ -71,9 +63,6 @@ protected boolean verifyNonAdjacent(
ValidatorSet trustedVals,
SignedHeader untrustedHeader,
ValidatorSet untrustedVals,
Duration trustingPeriod,
Timestamp currentTime,
Duration maxClockDrift,
Fraction trustLevel) {

// assert that trustedVals is NextValidators of last trusted header
Original file line number Diff line number Diff line change
@@ -160,8 +160,7 @@ void updateConflictingHeader() throws Exception {
doNothing().when(clientSpy).checkValidity(
any(ibc.lightclients.tendermint.v1.ClientState.class),
any(ibc.lightclients.tendermint.v1.ConsensusState.class),
any(ibc.lightclients.tendermint.v1.Header.class),
any());
any(ibc.lightclients.tendermint.v1.Header.class));

// Act
updateClient(3, 1);
Original file line number Diff line number Diff line change
@@ -11,7 +11,6 @@

public abstract class Tendermint {
protected boolean verify(
Duration trustingPeriod,
Duration maxClockDrift,
Fraction trustLevel,
SignedHeader trustedHeader,
@@ -24,29 +23,22 @@ protected boolean verify(
boolean isAdjacent = untrustedHeader.getHeader().getHeight()
.equals(trustedHeader.getHeader().getHeight().add(BigInteger.ONE));
if (isAdjacent) {
return verifyAdjacent(trustedHeader, untrustedHeader, untrustedVals, trustingPeriod, currentTime,
maxClockDrift);
return verifyAdjacent(trustedHeader, untrustedHeader, untrustedVals);
}

return verifyNonAdjacent(
trustedHeader,
trustedVals,
untrustedHeader,
untrustedVals,
trustingPeriod,
currentTime,
maxClockDrift,
trustLevel);

}

protected boolean verifyAdjacent(
SignedHeader trustedHeader,
SignedHeader untrustedHeader,
ValidatorSet untrustedVals,
Duration trustingPeriod,
Timestamp currentTime,
Duration maxClockDrift) {
ValidatorSet untrustedVals) {

// Check the validator hashes are the same
Context.require(
@@ -69,9 +61,6 @@ protected boolean verifyNonAdjacent(
ValidatorSet trustedVals,
SignedHeader untrustedHeader,
ValidatorSet untrustedVals,
Duration trustingPeriod,
Timestamp currentTime,
Duration maxClockDrift,
Fraction trustLevel) {

// assert that trustedVals is NextValidators of last trusted header
Loading