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

Manually pack initialValidators for subnetConversion ID hash-preimage #564

Merged
merged 6 commits into from
Sep 20, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion contracts/teleporter/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ Once sent on chain, Warp messages cannot be re-signed by a new validator set in

**Do not deploy the `TeleporterMessenger` contract using `forge create`**. The `TeleporterMessenger` contract must be deployed to the same contract address on every chain. To achieve this, the contract can be deployed using a static transaction that uses Nick's method as documented in [this guide](../..//utils/contract-deployment/README.md). Alternatively, if creating a new Subnet, the contract can be pre-allocated with the proper address and state in the new chain's [genesis file](https://docs.avax.network/build/subnet/upgrade/customize-a-subnet#setting-the-genesis-allocation).

As an example, to include `TeleporterMessenger` `v1.0.0` in the genesis file, include the following values in the `alloc` settings, as documented at the link above. The `storage` values included below correspond to the two contract values that are initialized as part of the default constructor of `TeleporterMessenger`. These are the `ReentrancyGuard` values set in this [abstract contract](../utilities/ReentrancyGuards.sol). Future versions of `TeleporterMessenger` may require different storage value intializations.
As an example, to include `TeleporterMessenger` `v1.0.0` in the genesis file, include the following values in the `alloc` settings, as documented at the link above. The `storage` values included below correspond to the two contract values that are initialized as part of the default constructor of `TeleporterMessenger`. These are the `ReentrancyGuard` values set in this [abstract contract](../utilities/ReentrancyGuards.sol). Future versions of `TeleporterMessenger` may require different storage value initializations.
```json
"alloc": {
"0x253b2784c75e510dD0fF1da844684a1aC0aa5fcf": {
Expand Down
18 changes: 2 additions & 16 deletions contracts/validator-manager/ValidatorManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -140,14 +140,6 @@ abstract contract ValidatorManager is Initializable, ContextUpgradeable, IValida

uint256 numInitialValidators = subnetConversionData.initialValidators.length;

// Verify that the sha256 hash of the Subnet conversion data matches with the Warp message's subnetConversionID.
bytes memory encodedConversion = abi.encodePacked(
subnetConversionData.convertSubnetTxID,
subnetConversionData.validatorManagerBlockchainID,
ADDRESS_LENGTH,
subnetConversionData.validatorManagerAddress,
uint32(numInitialValidators)
);
uint256 totalWeight;
for (uint32 i; i < numInitialValidators; ++i) {
InitialValidator memory initialValidator = subnetConversionData.initialValidators[i];
Expand All @@ -157,14 +149,6 @@ abstract contract ValidatorManager is Initializable, ContextUpgradeable, IValida
"ValidatorManager: node ID already active"
);

// Continue to encode the initial validators.
encodedConversion = abi.encodePacked(
encodedConversion,
initialValidator.nodeID,
initialValidator.weight,
initialValidator.blsPublicKey
);

// Validation ID of the initial validators is the sha256 hash of the
// convert Subnet tx ID and the index of the initial validator.
bytes32 validationID =
Expand Down Expand Up @@ -195,6 +179,8 @@ abstract contract ValidatorManager is Initializable, ContextUpgradeable, IValida
// Parse the Warp message into SubnetConversionMessage
bytes32 subnetConversionID =
ValidatorMessages.unpackSubnetConversionMessage(warpMessage.payload);
bytes memory encodedConversion =
ValidatorMessages.packSubnetConversionData(subnetConversionData);
require(
sha256(encodedConversion) == subnetConversionID,
"ValidatorManager: invalid subnet conversion ID"
Expand Down
229 changes: 102 additions & 127 deletions contracts/validator-manager/ValidatorMessages.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
// SPDX-License-Identifier: Ecosystem
pragma solidity 0.8.25;

import {SubnetConversionData} from "./interfaces/IValidatorManager.sol";

library ValidatorMessages {
// The information that uniquely identifies a subnet validation period.
// The validationID is the SHA-256 hash of the concatenation of the CODEC_ID,
Expand Down Expand Up @@ -65,21 +67,7 @@ library ValidatorMessages {
pure
returns (bytes memory)
{
bytes memory res = new bytes(38);
// Pack the codec ID
for (uint256 i; i < 2; ++i) {
res[i] = bytes1(uint8(CODEC_ID >> uint8((8 * (1 - i)))));
}
// Pack the type ID
for (uint256 i; i < 4; ++i) {
res[i + 2] = bytes1(uint8(SUBNET_CONVERSION_MESSAGE_TYPE_ID >> uint8((8 * (3 - i)))));
}
// Pack the subnetConversionID
for (uint256 i; i < 32; ++i) {
res[i + 6] = bytes1(uint8(uint256(subnetConversionID >> (8 * (31 - i)))));
}

return res;
return abi.encodePacked(CODEC_ID, SUBNET_CONVERSION_MESSAGE_TYPE_ID, subnetConversionID);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While we're here, would you mind removing the TODO from line 46 above?

}

/**
Expand Down Expand Up @@ -117,6 +105,85 @@ library ValidatorMessages {
return subnetConversionID;
}

/**
* @notice Packs SubnetConversionData into a byte array.
* This byte array is the SHA256 pre-image of the subnetConversionID hash
* The message format specification is:
*
* +-------------------+---------------+---------------------------------------------------------+
* | convertSubnetTxID : [32]byte | 32 bytes |
* +-------------------+-----------------+-------------------------------------------------------+
* | managerChainID : [32]byte | 32 bytes |
* +-------------------+-----------------+-------------------------------------------------------+
* | managerAddress : []byte | 4 + len(managerAddress) bytes |
* +-------------------+-----------------+-------------------------------------------------------+
* | validators : []ValidatorData | 4 + len(validators) * 88 bytes |
* +-------------------+-----------------+-------------------------------------------------------+
* | 72 + len(managerAddress) + len(validators) * 88 bytes |
* +-------------------------------------------------------+
* And ValidatorData:
* +--------------+----------+-----------+
* | nodeID : [32]byte | 32 bytes |
* +--------------+----------+-----------+
* | weight : uint64 | 8 bytes |
* +--------------+----------+-----------+
* | blsPublicKey : [48]byte | 48 bytes |
* +--------------+----------+-----------+
* | 88 bytes |
* +-----------+
*
* @param subnetConversionData The struct representing data to pack into the message.
* @return The packed message.
*/
function packSubnetConversionData(SubnetConversionData calldata subnetConversionData)
internal
pure
returns (bytes memory)
{
bytes memory res = new bytes(92 + subnetConversionData.initialValidators.length * 88);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the comment above, it wasn't immediately clear to me why the constant was 92 here rather than 72. Mind adding a comment explaining the address is known to be 20 bytes long?

// Pack the convertSubnetTx ID
for (uint256 i; i < 32; ++i) {
res[i] = subnetConversionData.convertSubnetTxID[i];
}
// Pack the validatorManagerBlockchainID
for (uint256 i; i < 32; ++i) {
res[i + 32] = subnetConversionData.validatorManagerBlockchainID[i];
}
// Pack the ADDRESS_LENGTH
for (uint256 i; i < 4; ++i) {
res[i + 64] = bytes1(uint8(20 >> (8 * (3 - i))));
}
// Pack the address
bytes20 addrBytes = bytes20(subnetConversionData.validatorManagerAddress);
for (uint256 i = 0; i < 20; i++) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Lets use pre-increment like each of the other loops here.

res[i + 68] = addrBytes[i];
}

// Pack the initial validators length
uint32 ivLength = uint32(subnetConversionData.initialValidators.length);
for (uint256 i; i < 4; ++i) {
res[i + 88] = bytes1(uint8(ivLength >> (8 * (3 - i))));
}

for (uint256 i = 0; i < subnetConversionData.initialValidators.length; i++) {
uint256 offset = 92 + i * 88;
// Pack the nodeID
for (uint256 j; j < 32; ++j) {
res[offset + j] = subnetConversionData.initialValidators[i].nodeID[j];
}
// Pack the weight
for (uint256 j; j < 8; ++j) {
res[offset + 32 + j] =
bytes1(uint8(subnetConversionData.initialValidators[i].weight >> (8 * (7 - j))));
}
// Pack the blsPublicKey
for (uint256 j; j < 48; ++j) {
res[offset + 40 + j] = subnetConversionData.initialValidators[i].blsPublicKey[j];
}
}
return res;
}

/**
* @notice Packs a RegisterSubnetValidatorMessage message into a byte array.
* The message format specification is:
Expand Down Expand Up @@ -149,38 +216,17 @@ library ValidatorMessages {
require(
validationPeriod.blsPublicKey.length == 48, "StakingMessages: invalid signature length"
);
bytes memory res = new bytes(134);
// Pack the codec ID
for (uint256 i; i < 2; ++i) {
res[i] = bytes1(uint8(CODEC_ID >> uint8((8 * (1 - i)))));
}
// Pack the type ID
for (uint256 i; i < 4; ++i) {
res[i + 2] =
bytes1(uint8(REGISTER_SUBNET_VALIDATOR_MESSAGE_TYPE_ID >> uint8((8 * (3 - i)))));
}

// Pack the subnetID
for (uint256 i; i < 32; ++i) {
res[i + 6] = validationPeriod.subnetID[i];
}
// Pack the nodeID
for (uint256 i; i < 32; ++i) {
res[i + 38] = validationPeriod.nodeID[i];
}
// Pack the weight
for (uint256 i; i < 8; ++i) {
res[i + 70] = bytes1(uint8(validationPeriod.weight >> uint8((8 * (7 - i)))));
}
// Pack the blsPublicKey
for (uint256 i; i < 48; ++i) {
res[i + 78] = validationPeriod.blsPublicKey[i];
}
// Pack the registration expiry
for (uint256 i; i < 8; ++i) {
res[i + 126] =
bytes1(uint8(validationPeriod.registrationExpiry >> uint64((8 * (7 - i)))));
}
// solhint-disable-next-line func-named-parameters
bytes memory res = abi.encodePacked(
CODEC_ID,
REGISTER_SUBNET_VALIDATOR_MESSAGE_TYPE_ID,
validationPeriod.subnetID,
validationPeriod.nodeID,
validationPeriod.weight,
validationPeriod.blsPublicKey,
validationPeriod.registrationExpiry
);
return (sha256(res), res);
}

Expand Down Expand Up @@ -278,23 +324,9 @@ library ValidatorMessages {
bytes32 validationID,
bool valid
) internal pure returns (bytes memory) {
bytes memory res = new bytes(39);
// Pack the codec ID.
for (uint256 i; i < 2; ++i) {
res[i] = bytes1(uint8(CODEC_ID >> (8 * (1 - i))));
}
// Pack the type ID.
for (uint256 i; i < 4; ++i) {
res[i + 2] =
bytes1(uint8(SUBNET_VALIDATOR_REGISTRATION_MESSAGE_TYPE_ID >> (8 * (3 - i))));
}
// Pack the validation ID.
for (uint256 i; i < 32; ++i) {
res[i + 6] = bytes1(uint8(uint256(validationID >> (8 * (31 - i)))));
}
// Pack the validity.
res[38] = bytes1(valid ? 1 : 0);
return res;
return abi.encodePacked(
CODEC_ID, SUBNET_VALIDATOR_REGISTRATION_MESSAGE_TYPE_ID, validationID, valid
);
}

/**
Expand Down Expand Up @@ -367,28 +399,9 @@ library ValidatorMessages {
uint64 nonce,
uint64 weight
) internal pure returns (bytes memory) {
bytes memory res = new bytes(54);
// Pack the codec ID.
for (uint256 i; i < 2; ++i) {
res[i] = bytes1(uint8(CODEC_ID >> (8 * (1 - i))));
}
// Pack the type ID.
for (uint256 i; i < 4; ++i) {
res[i + 2] = bytes1(uint8(SET_SUBNET_VALIDATOR_WEIGHT_MESSAGE_TYPE_ID >> (8 * (3 - i))));
}
// Pack the validation ID.
for (uint256 i; i < 32; ++i) {
res[i + 6] = bytes1(uint8(uint256(validationID >> (8 * (31 - i)))));
}
// Pack the nonce.
for (uint256 i; i < 8; ++i) {
res[i + 38] = bytes1(uint8(nonce >> (8 * (7 - i))));
}
// Pack the weight.
for (uint256 i; i < 8; ++i) {
res[i + 46] = bytes1(uint8(weight >> (8 * (7 - i))));
}
return res;
return abi.encodePacked(
CODEC_ID, SET_SUBNET_VALIDATOR_WEIGHT_MESSAGE_TYPE_ID, validationID, nonce, weight
);
}

/**
Expand Down Expand Up @@ -465,29 +478,9 @@ library ValidatorMessages {
uint64 nonce,
uint64 weight
) internal pure returns (bytes memory) {
bytes memory res = new bytes(54);
// Pack the codec ID.
for (uint256 i; i < 2; ++i) {
res[i] = bytes1(uint8(CODEC_ID >> (8 * (1 - i))));
}
// Pack the type ID.
for (uint256 i; i < 4; ++i) {
res[i + 2] =
bytes1(uint8(SUBNET_VALIDATOR_WEIGHT_UPDATE_MESSAGE_TYPE_ID >> (8 * (3 - i))));
}
// Pack the validation ID.
for (uint256 i; i < 32; ++i) {
res[i + 6] = bytes1(uint8(uint256(validationID >> (8 * (31 - i)))));
}
// Pack the nonce.
for (uint256 i; i < 8; ++i) {
res[i + 38] = bytes1(uint8(nonce >> (8 * (7 - i))));
}
// Pack the weight.
for (uint256 i; i < 8; ++i) {
res[i + 46] = bytes1(uint8(weight >> (8 * (7 - i))));
}
return res;
return abi.encodePacked(
CODEC_ID, SUBNET_VALIDATOR_WEIGHT_UPDATE_MESSAGE_TYPE_ID, validationID, nonce, weight
);
}

/**
Expand Down Expand Up @@ -565,25 +558,7 @@ library ValidatorMessages {
bytes32 validationID,
uint64 uptime
) internal pure returns (bytes memory) {
bytes memory res = new bytes(46);

// Pack the codec ID.
for (uint256 i; i < 2; ++i) {
res[i] = bytes1(uint8(CODEC_ID >> (8 * (1 - i))));
}
// Pack the type ID.
for (uint256 i; i < 4; ++i) {
res[i + 2] = bytes1(uint8(VALIDATION_UPTIME_MESSAGE_TYPE_ID >> (8 * (3 - i))));
}
// Pack the validation ID.
for (uint256 i; i < 32; ++i) {
res[i + 6] = bytes1(uint8(uint256(validationID >> (8 * (31 - i)))));
}
// Pack the uptime.
for (uint256 i; i < 8; ++i) {
res[i + 38] = bytes1(uint8(uptime >> (8 * (7 - i))));
}
return res;
return abi.encodePacked(CODEC_ID, VALIDATION_UPTIME_MESSAGE_TYPE_ID, validationID, uptime);
}

/**
Expand Down
4 changes: 2 additions & 2 deletions contracts/validator-manager/tests/ValidatorManagerTests.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ abstract contract ValidatorManagerTest is Test {
bytes32(hex"1234567812345678123456781234567812345678123456781234567812345678");
bytes32 public constant DEFAULT_NODE_ID =
bytes32(hex"1234567812345678123456781234567812345678123456781234567812345678");
bytes32 public constant DEFAULT_INTIIAL_VALIDATOR_NODE_ID =
bytes32 public constant DEFAULT_INITIAL_VALIDATOR_NODE_ID =
bytes32(hex"2345678123456781234567812345678123456781234567812345678123456781");
bytes public constant DEFAULT_BLS_PUBLIC_KEY = bytes(
hex"123456781234567812345678123456781234567812345678123456781234567812345678123456781234567812345678"
Expand Down Expand Up @@ -454,7 +454,7 @@ abstract contract ValidatorManagerTest is Test {
function _defaultSubnetConversionData() internal view returns (SubnetConversionData memory) {
InitialValidator[] memory initialValidators = new InitialValidator[](1);
initialValidators[0] = InitialValidator({
nodeID: DEFAULT_INTIIAL_VALIDATOR_NODE_ID,
nodeID: DEFAULT_INITIAL_VALIDATOR_NODE_ID,
weight: DEFAULT_INITIAL_VALIDATOR_WEIGHT,
blsPublicKey: DEFAULT_BLS_PUBLIC_KEY
});
Expand Down