Skip to content
Open
Show file tree
Hide file tree
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
Expand Up @@ -10,10 +10,11 @@ import {PodAuctionConsumer} from "../contracts/PodAuctionConsumer.sol";
contract PodAuctionConsumerDeployer is BaseDeployer {
function run() public {
address[] memory initialValidators = getValidatorAddresses();
(string[] memory initialHosts, uint16[] memory initialPorts) = getValidatorHostsAndPorts();

vm.startBroadcast();

PodRegistry podRegistry = new PodRegistry(initialValidators);
PodRegistry podRegistry = new PodRegistry(initialValidators, initialHosts, initialPorts);
console.log("PodRegistry deployed:");
console.logAddress(address(podRegistry));

Expand Down
6 changes: 5 additions & 1 deletion examples/optimistic-auction/test/PodAuctionConsumer.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,20 @@ contract PodAuctionConsumerTest is Test {

function setUp() public {
address[] memory initialValidators = new address[](NUMBER_OF_VALIDATORS);
string[] memory initialHosts = new string[](NUMBER_OF_VALIDATORS);
uint16[] memory initialPorts = new uint16[](NUMBER_OF_VALIDATORS);

validatorPrivateKeys = new uint256[](NUMBER_OF_VALIDATORS);

for (uint256 i = 0; i < NUMBER_OF_VALIDATORS; i++) {
validatorPrivateKeys[i] = uint256(i + 1);
initialValidators[i] = vm.addr(validatorPrivateKeys[i]);
initialHosts[i] = string.concat("validator-", vm.toString(i), ".example.org");
initialPorts[i] = uint16(30000 + i);
}

vm.prank(OWNER);
podRegistry = new PodRegistry(initialValidators);
podRegistry = new PodRegistry(initialValidators, initialHosts, initialPorts);

consumer = new PodAuctionConsumer(address(podRegistry), 1 ether);
vm.deal(SMALLER_BIDDER, 2 ether);
Expand Down
3 changes: 2 additions & 1 deletion examples/solidity/script/Deploy.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,9 @@ contract Deployer is BaseDeployer {

if (testContracts) {
address[] memory initialValidators = getValidatorAddresses();
(string[] memory initialHosts, uint16[] memory initialPorts) = getValidatorHostsAndPorts();

PodRegistry podRegistry = new PodRegistry(initialValidators);
PodRegistry podRegistry = new PodRegistry(initialValidators, initialHosts, initialPorts);
console.log("PodRegistry deployed at:", address(podRegistry));

uint256 bondAmount = 1 ether;
Expand Down
27 changes: 27 additions & 0 deletions protocol/script/BaseDeployer.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,31 @@ contract BaseDeployer is Script {

return initialValidators;
}

function getValidatorHostsAndPorts() internal view returns (string[] memory hosts, uint16[] memory ports) {
// Read validator endpoints from environment variable
string memory committeeHosts = vm.envString("POD_COMMITTEE_HOSTS");

// Split comma-separated host:port pairs
string[] memory entries = vm.split(committeeHosts, ",");
Copy link
Contributor

Choose a reason for hiding this comment

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

If the env var is empty string "" or has a trailing comma (e.g., "a:1,b:2,"), vm.split yields an empty entry ("") that will fail with invalid host:port entry, which is confusing vs. having an early check for bytes(committeeHosts).length > 0 and having instead an error handling message like "no hosts provided"

Copy link
Contributor

Choose a reason for hiding this comment

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

another case, accidental white space, vm.split doesn’t trim; vm.parseUint(" 8080") will likely revert if we let spaces out, say if someone sets "host1:8080, host2:9090" (note the space), parts.length == 2 passes, but vm.parseUint(parts[1]) will revert.

maybe a prior removal of possible spaces beforevm.split could help avoid that

Copy link
Contributor

Choose a reason for hiding this comment

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

IPv6 not supported by this split

[2001:db8::1]:8080 would break vm.split(entry, ":") below


uint256 len = entries.length;
hosts = new string[](len);
ports = new uint16[](len);

for (uint256 i = 0; i < len; i++) {
// Split each entry on ":" (host:port)
string[] memory parts = vm.split(entries[i], ":");
require(parts.length == 2, "invalid host:port entry");

hosts[i] = parts[0];

// Parse port string into uint16
uint256 portVal = vm.parseUint(parts[1]);
require(portVal > 0 && portVal <= type(uint16).max, "invalid port");
ports[i] = uint16(portVal);
}

require(len > 0, "No validator hosts provided");
}
}
5 changes: 3 additions & 2 deletions protocol/script/PodRegistryDeployer.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ import {PodRegistry} from "../src/PodRegistry.sol";
contract PodRegistryDeployer is BaseDeployer {
function run() public {
address[] memory initialValidators = getValidatorAddresses();
(string[] memory initialHosts, uint16[] memory initialPorts) = getValidatorHostsAndPorts();

vm.startBroadcast();
PodRegistry podRegistry = new PodRegistry(initialValidators);
PodRegistry registry = new PodRegistry(initialValidators, initialHosts, initialPorts);
vm.stopBroadcast();

console.log("PodRegistry deployed:");
console.logAddress(address(podRegistry));
console.logAddress(address(registry));
}
}
103 changes: 88 additions & 15 deletions protocol/src/PodRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ contract PodRegistry is IPodRegistry, Ownable {
*/
address[] public validators;

/**
* @notice Array of endpoints in the registry. We also use `validators.length + 1` to track the 1-based
* index of the next validator to add.
*/
Endpoint[] private endpoints;

/**
* @notice Bitmap of the currently active validators
*/
Expand All @@ -65,11 +71,19 @@ contract PodRegistry is IPodRegistry, Ownable {
* @notice Initialize the registry with a set of initial validators. Only creates one snapshot
* after adding all the initial validators.
* @param initialValidators Array of validator addresses to initialize with
* @param initialHosts The hostnames or IPs for each validator (same length as validators)
* @param initialPorts The TCP ports for each validator (same length as validators)
* @dev The contract owner will be set to msg.sender
*/
constructor(address[] memory initialValidators) Ownable(msg.sender) {
for (uint8 i = 0; i < initialValidators.length; i++) {
_addValidator(initialValidators[i]);
constructor(address[] memory initialValidators, string[] memory initialHosts, uint16[] memory initialPorts)
Ownable(msg.sender)
{
uint256 len = initialValidators.length;
require(len == initialHosts.length && len == initialPorts.length, "length mismatch");
require(len <= 255, "too many validators");

for (uint8 i = 0; i < len; i++) {
_addValidator(initialValidators[i], initialHosts[i], initialPorts[i]);
activeValidatorBitmap |= (1 << i);
}

Expand All @@ -79,9 +93,11 @@ contract PodRegistry is IPodRegistry, Ownable {
/**
* @notice Add a validator to the registry
* @param validator The address of the validator to add
* @param host The hostname or IP address of the validator
* @param port The TCP port used by the validator
* @dev Internal function called by addValidator
*/
function _addValidator(address validator) internal {
function _addValidator(address validator, string memory host, uint16 port) internal {
if (validator == address(0)) {
revert ValidatorIsZeroAddress();
}
Expand All @@ -91,16 +107,26 @@ contract PodRegistry is IPodRegistry, Ownable {
if (validators.length >= MAX_VALIDATOR_COUNT) {
revert MaxValidatorCountReached();
}
if (bytes(host).length == 0) {
revert ValidatorInvalidHost();
}
if (bytes(host).length > 255) {
revert ValidatorInvalidHost();
}
if (port == 0) {
revert ValidatorInvalidPort();
}

validators.push(validator);
validatorIndex[validator] = uint8(validators.length);
endpoints.push(Endpoint({host: host, port: port}));
}

/**
* @inheritdoc IPodRegistry
*/
function addValidator(address validator) external onlyOwner {
_addValidator(validator);
function addValidator(address validator, string calldata host, uint16 port) external onlyOwner {
_addValidator(validator, host, port);

uint8 index = uint8(validators.length);
_activateValidator(index);
Expand Down Expand Up @@ -143,6 +169,29 @@ contract PodRegistry is IPodRegistry, Ownable {
emit ValidatorUnbanned(validator);
}

/**
* @inheritdoc IPodRegistry
*/
function setValidatorEndpoint(address validator, string calldata host, uint16 port) external onlyOwner {
uint8 index = validatorIndex[validator];
if (index == 0) {
revert ValidatorDoesNotExist();
}
if (bytes(host).length == 0) {
revert ValidatorInvalidHost();
}
if (bytes(host).length > 255) {
revert ValidatorInvalidHost();
}
if (port == 0) {
revert ValidatorInvalidPort();
}

endpoints[index - 1] = Endpoint({host: host, port: port});

emit ValidatorNetworkUpdated(validator, host, port);
}

/**
* @inheritdoc IPodRegistry
*/
Expand Down Expand Up @@ -283,25 +332,45 @@ contract PodRegistry is IPodRegistry, Ownable {
/**
* @inheritdoc IPodRegistry
*/
function getValidatorsAtIndex(uint256 snapshotIndex) public view returns (address[] memory) {
function getValidatorsAtIndex(uint256 snapshotIndex)
public
view
returns (address[] memory addrs, string[] memory hosts, uint16[] memory ports)
{
uint256 bitmap = history[snapshotIndex].bitmap;
uint8 count = _popCount(bitmap);
address[] memory subset = new address[](count);

addrs = new address[](count);
hosts = new string[](count);
ports = new uint16[](count);

address[] storage all = validators;
uint8 j = 0;
for (uint8 i = 0; i < validators.length; i++) {

for (uint8 i = 0; i < all.length; i++) {
if (_isBitSet(bitmap, i)) {
subset[j++] = validators[i];
address v = all[i];
addrs[j] = v;

Endpoint storage ep = endpoints[i];
hosts[j] = ep.host;
ports[j] = ep.port;

j++;
}
}
return subset;
}

/**
* @inheritdoc IPodRegistry
*/
function getActiveValidators() external view returns (address[] memory) {
function getActiveValidators()
external
view
returns (address[] memory addrs, string[] memory hosts, uint16[] memory ports)
{
if (history.length == 0) {
return new address[](0);
return (new address[](0), new string[](0), new uint16[](0));
}

return getValidatorsAtIndex(history.length - 1);
Expand All @@ -310,9 +379,13 @@ contract PodRegistry is IPodRegistry, Ownable {
/**
* @inheritdoc IPodRegistry
*/
function getActiveValidatorsAtTimestamp(uint256 timestamp) external view returns (address[] memory) {
function getActiveValidatorsAtTimestamp(uint256 timestamp)
external
view
returns (address[] memory addrs, string[] memory hosts, uint16[] memory ports)
{
if (history.length == 0) {
return new address[](0);
return (new address[](0), new string[](0), new uint16[](0));
}

uint256 snapshotIndex = findSnapshotIndex(timestamp);
Expand Down
58 changes: 51 additions & 7 deletions protocol/src/interfaces/IPodRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,16 @@ interface IPodRegistry {
uint256 bitmap;
}

/**
* @notice Network endpoint information for a validator
* @param host The hostname or IP address of the validator
* @param port The TCP port used by the validator
*/
struct Endpoint {
string host;
uint16 port;
}

/// @notice Error thrown when a validator is a zero address
error ValidatorIsZeroAddress();

Expand All @@ -36,6 +46,12 @@ interface IPodRegistry {
/// @notice Error thrown when a validator is not banned
error ValidatorNotBanned();

/// @notice Error thrown when a validator host is invalid
error ValidatorInvalidHost();

/// @notice Error thrown when a validator port is invalid
error ValidatorInvalidPort();

/// @notice Error thrown when the caller is not a validator
error CallerNotValidator();

Expand Down Expand Up @@ -75,6 +91,9 @@ interface IPodRegistry {
/// @notice Event emitted when a validator is reactivated
event ValidatorReactivated(address indexed validator);

/// @notice Event emitted when a validator network information is updated
event ValidatorNetworkUpdated(address indexed validator, string host, uint16 port);

/// @notice Event emitted when a snapshot is created
event SnapshotCreated(uint256 indexed activeAsOfTimestamp, uint256 bitmap);

Expand Down Expand Up @@ -108,9 +127,11 @@ interface IPodRegistry {
* @notice Add a new validator to the registry and activate them. Modifies the current validator set
* therefore creates a new snapshot.
* @param validator The address of the validator to add
* @param host The hostname or IP address of the validator
* @param port The TCP port used by the validator
* @dev Only callable by the contract owner
*/
function addValidator(address validator) external;
function addValidator(address validator, string calldata host, uint16 port) external;

/**
* @notice Ban a validator from the registry.
Expand All @@ -129,6 +150,14 @@ interface IPodRegistry {
*/
function unbanValidator(address validator) external;

/**
* @notice Update the network endpoint of an existing validator
* @param validator The address of the validator
* @param host The new hostname or IP address of the validator
* @param port The new TCP port used by the validator
*/
function setValidatorEndpoint(address validator, string calldata host, uint16 port) external;

/**
* @notice Deactivate the caller's validator status. Modifies the current validator set
* therefore creates a new snapshot.
Expand Down Expand Up @@ -192,23 +221,38 @@ interface IPodRegistry {

/**
* @notice Get all currently active validators
* @return Array of addresses of currently active validators
* @return validators The list of addresses of currently active validators
* @return hosts The list of hostnames or IP addresses corresponding to each validator
* @return ports The list of TCP ports corresponding to each validator
*/
function getActiveValidators() external view returns (address[] memory);
function getActiveValidators()
external
view
returns (address[] memory validators, string[] memory hosts, uint16[] memory ports);

/**
* @notice Get all validators that were active at a specific timestamp
* @param timestamp The timestamp to query
* @return Array of addresses of validators active at the specified timestamp
* @return validators The list of addresses of currently active validators
* @return hosts The list of hostnames or IP addresses corresponding to each validator
* @return ports The list of TCP ports corresponding to each validator
*/
function getActiveValidatorsAtTimestamp(uint256 timestamp) external view returns (address[] memory);
function getActiveValidatorsAtTimestamp(uint256 timestamp)
external
view
returns (address[] memory validators, string[] memory hosts, uint16[] memory ports);

/**
* @notice Get all validators at a specific snapshot
* @param snapshotIndex The snapshot index to query
* @return Array of addresses of validators at the specified snapshot
* @return validators The list of addresses of currently active validators
* @return hosts The list of hostnames or IP addresses corresponding to each validator
* @return ports The list of TCP ports corresponding to each validator
*/
function getValidatorsAtIndex(uint256 snapshotIndex) external view returns (address[] memory);
function getValidatorsAtIndex(uint256 snapshotIndex)
external
view
returns (address[] memory validators, string[] memory hosts, uint16[] memory ports);

/**
* @notice Get snapshot details at a specific index
Expand Down
Loading