Skip to content

Commit

Permalink
Set maximum number of hashes in contract + keeper
Browse files Browse the repository at this point in the history
  • Loading branch information
m30m committed Aug 9, 2024
1 parent d25c74a commit 5145a0c
Show file tree
Hide file tree
Showing 11 changed files with 279 additions and 7 deletions.
28 changes: 28 additions & 0 deletions apps/fortuna/src/command/setup_provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,14 @@ async fn setup_chain_provider(
.in_current_span()
.await?;

sync_max_num_hashes(
&contract,
&provider_info,
chain_config.max_num_hashes.unwrap_or(0),
)
.in_current_span()
.await?;

Ok(())
}

Expand Down Expand Up @@ -248,3 +256,23 @@ async fn sync_fee_manager(
}
Ok(())
}


async fn sync_max_num_hashes(
contract: &Arc<SignablePythContract>,
provider_info: &ProviderInfo,
max_num_hashes: u32,
) -> Result<()> {
if provider_info.max_num_hashes != max_num_hashes {
tracing::info!("Updating provider max num hashes to {:?}", max_num_hashes);
if let Some(receipt) = contract
.set_max_num_hashes(max_num_hashes)
.send()
.await?
.await?
{
tracing::info!("Updated provider max num hashes to : {:?}", receipt);
}
}
Ok(())
}
4 changes: 4 additions & 0 deletions apps/fortuna/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,10 @@ pub struct EthereumConfig {

/// Historical commitments made by the provider.
pub commitments: Option<Vec<Commitment>>,

/// Maximum number of hashes to record in a request.
/// This should be set according to the maximum gas limit the provider supports for callbacks.
pub max_num_hashes: Option<u32>,
}


Expand Down
79 changes: 79 additions & 0 deletions apps/fortuna/src/keeper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ const TRACK_INTERVAL: Duration = Duration::from_secs(10);
const WITHDRAW_INTERVAL: Duration = Duration::from_secs(300);
/// Check whether we need to adjust the fee at this interval.
const ADJUST_FEE_INTERVAL: Duration = Duration::from_secs(30);
/// Check whether we need to manually update the commitments to reduce numHashes for future
/// requests
const UPDATE_COMMITMENTS_INTERVAL: Duration = Duration::from_secs(30);
/// Rety last N blocks
const RETRY_PREVIOUS_BLOCKS: u64 = 100;

Expand Down Expand Up @@ -314,6 +317,9 @@ pub async fn run_keeper_threads(
.in_current_span(),
);

// Spawn a thread that periodically adjusts the provider fee.
spawn(update_commitments_wrapper(contract.clone(), chain_state.clone()).in_current_span());


// Spawn a thread to track the provider info and the balance of the keeper
spawn(
Expand Down Expand Up @@ -1020,6 +1026,79 @@ pub async fn adjust_fee_wrapper(
}
}

#[tracing::instrument(name = "update_commitments", skip_all)]
pub async fn update_commitments_wrapper(
contract: Arc<InstrumentedSignablePythContract>,
chain_state: BlockchainState,
) {
loop {
if let Err(e) = update_commitments_if_necessary(contract.clone(), &chain_state)
.in_current_span()
.await
{
tracing::error!("Update commitments. error: {:?}", e);
}
time::sleep(UPDATE_COMMITMENTS_INTERVAL).await;
}
}


pub async fn update_commitments_if_necessary(
contract: Arc<InstrumentedSignablePythContract>,
chain_state: &BlockchainState,
) -> Result<()> {
let latest_safe_block = get_latest_safe_block(&chain_state).in_current_span().await;
let provider_address = chain_state.provider_address;
let provider_info = contract
.get_provider_info(provider_address)
.block(latest_safe_block)
.call()
.await
.map_err(|e| anyhow!("Error while getting provider info. error: {:?}", e))?;
if provider_info.max_num_hashes == 0 {
return Ok(());
}
let threshold: u64 = (provider_info.max_num_hashes * 95 / 100).into();
if provider_info.sequence_number - provider_info.current_commitment_sequence_number > threshold
{
let seq_number = provider_info.sequence_number - 1;
let provider_revelation = chain_state
.state
.reveal(seq_number)
.map_err(|e| anyhow!("Error revealing: {:?}", e))?;
let contract_call =
contract.update_provider_commitment(provider_address, seq_number, provider_revelation);
// TODO: refactor
let pending_tx = contract_call.send().await.map_err(|e| {
anyhow!(
"Error submitting the update commitment transaction: {:?}",
e
)
})?;

let tx_result = pending_tx
.await
.map_err(|e| {
anyhow!(
"Error waiting for update commitment transaction receipt: {:?}",
e
)
})?
.ok_or_else(|| {
anyhow!(
"Can't verify the update commitment transaction, probably dropped from mempool"
)
})?;

tracing::info!(
transaction_hash = &tx_result.transaction_hash.to_string(),
"Updated commitment to avoid falling back. Receipt: {:?}",
tx_result,
);
}
Ok(())
}

/// Adjust the fee charged by the provider to ensure that it is profitable at the prevailing gas price.
/// This method targets a fee as a function of the maximum cost of the callback,
/// c = (gas_limit) * (current gas price), with min_fee_wei as a lower bound on the fee.
Expand Down
27 changes: 24 additions & 3 deletions target_chains/ethereum/contracts/contracts/entropy/Entropy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -234,8 +234,10 @@ abstract contract Entropy is IEntropy, EntropyState {
assignedSequenceNumber -
providerInfo.currentCommitmentSequenceNumber
);
if (req.numHashes > 500) {
//TODO: make this a constant
if (
providerInfo.maxNumHashes != 0 &&
req.numHashes > providerInfo.maxNumHashes
) {
revert EntropyErrors.LastRevealedTooOld();
}
req.commitment = keccak256(
Expand Down Expand Up @@ -359,7 +361,7 @@ abstract contract Entropy is IEntropy, EntropyState {
// This is used to reduce the `numHashes` required for future requests which leads to reduced gas usage.
function updateProviderCommitment(
address provider,
uint32 updatedSequenceNumber,
uint64 updatedSequenceNumber,
bytes32 providerRevelation
) public override {
EntropyStructs.ProviderInfo storage providerInfo = _state.providers[
Expand Down Expand Up @@ -602,6 +604,25 @@ abstract contract Entropy is IEntropy, EntropyState {
emit ProviderFeeManagerUpdated(msg.sender, oldFeeManager, manager);
}

// Set the maximum number of hashes to record in a request. This should be set according to the maximum gas limit
// the provider supports for callbacks.
function setMaxNumHashes(uint32 maxNumHashes) external override {
EntropyStructs.ProviderInfo storage provider = _state.providers[
msg.sender
];
if (provider.sequenceNumber == 0) {
revert EntropyErrors.NoSuchProvider();
}

uint64 oldMaxNumHashes = provider.maxNumHashes;
provider.maxNumHashes = maxNumHashes;
emit ProviderMaxNumHashesUpdated(
msg.sender,
oldMaxNumHashes,
maxNumHashes
);
}

function constructUserCommitment(
bytes32 userRandomness
) public pure override returns (bytes32 userCommitment) {
Expand Down
23 changes: 20 additions & 3 deletions target_chains/ethereum/contracts/forge-test/Entropy.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ contract EntropyTest is Test, EntropyTestUtils, EntropyEvents {
bytes32[] provider1Proofs;
uint128 provider1FeeInWei = 8;
uint64 provider1ChainLength = 1000;
uint32 provider1MaxNumHashes = 500;
bytes provider1Uri = bytes("https://foo.com");
bytes provider1CommitmentMetadata = hex"0100";

Expand Down Expand Up @@ -65,6 +66,8 @@ contract EntropyTest is Test, EntropyTestUtils, EntropyEvents {
provider1ChainLength,
provider1Uri
);
vm.prank(provider1);
random.setMaxNumHashes(provider1MaxNumHashes);

bytes32[] memory hashChain2 = generateHashChain(provider2, 0, 100);
provider2Proofs = hashChain2;
Expand Down Expand Up @@ -967,7 +970,7 @@ contract EntropyTest is Test, EntropyTestUtils, EntropyEvents {
provider1Proofs[sequenceNumber],
ALL_ZEROS
);
for (uint256 i = 0; i < 500; i++) {
for (uint256 i = 0; i < provider1MaxNumHashes; i++) {
request(user1, provider1, 42, false);
}
assertRequestReverts(
Expand All @@ -983,7 +986,7 @@ contract EntropyTest is Test, EntropyTestUtils, EntropyEvents {
uint32 requestCount,
uint32 updateSeqNumber
) public {
vm.assume(requestCount < 500);
vm.assume(requestCount < provider1MaxNumHashes);
vm.assume(requestCount < provider1ChainLength);
vm.assume(updateSeqNumber < requestCount);
vm.assume(0 < updateSeqNumber);
Expand Down Expand Up @@ -1013,7 +1016,7 @@ contract EntropyTest is Test, EntropyTestUtils, EntropyEvents {
uint32 requestCount,
uint32 updateSeqNumber
) public {
vm.assume(requestCount < 500);
vm.assume(requestCount < provider1MaxNumHashes);
vm.assume(requestCount < provider1ChainLength);
vm.assume(updateSeqNumber < requestCount);
vm.assume(0 < updateSeqNumber);
Expand Down Expand Up @@ -1081,6 +1084,20 @@ contract EntropyTest is Test, EntropyTestUtils, EntropyEvents {
);
}

function testSetMaxNumHashes(uint32 maxNumHashes) public {
vm.prank(provider1);
random.setMaxNumHashes(maxNumHashes);
EntropyStructs.ProviderInfo memory info1 = random.getProviderInfo(
provider1
);
assertEq(info1.maxNumHashes, maxNumHashes);
}

function testSetMaxNumHashesRevertIfNotFromProvider() public {
vm.expectRevert(EntropyErrors.NoSuchProvider.selector);
random.setMaxNumHashes(100);
}

function testFeeManager() public {
address manager = address(12);

Expand Down
5 changes: 5 additions & 0 deletions target_chains/ethereum/entropy_sdk/solidity/EntropyEvents.sol
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ interface EntropyEvents {
address oldFeeManager,
address newFeeManager
);
event ProviderMaxNumHashesUpdated(
address provider,
uint64 oldMaxNumHashes,
uint64 newMaxNumHashes
);

event Withdrawal(
address provider,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ contract EntropyStructs {
uint64 currentCommitmentSequenceNumber;
// An address that is authorized to set / withdraw fees on behalf of this provider.
address feeManager;
// Maximum number of hashes to record in a request. This should be set according to the maximum gas limit
// the provider supports for callbacks.
uint32 maxNumHashes;
}

struct Request {
Expand Down
6 changes: 5 additions & 1 deletion target_chains/ethereum/entropy_sdk/solidity/IEntropy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,15 @@ interface IEntropy is EntropyEvents {
// will override the previous value. Call this function with the all-zero address to disable the fee manager role.
function setFeeManager(address manager) external;

// Set the maximum number of hashes to record in a request. This should be set according to the maximum gas limit
// the provider supports for callbacks.
function setMaxNumHashes(uint32 maxNumHashes) external;

// Update the provider commitment and increase the sequence number.
// This is used to reduce the `numHashes` required for future requests which leads to reduced gas usage.
function updateProviderCommitment(
address provider,
uint32 updatedSequenceNumber,
uint64 updatedSequenceNumber,
bytes32 providerRevelation
) external;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@
"name": "InvalidUpgradeMagic",
"type": "error"
},
{
"inputs": [],
"name": "LastRevealedTooOld",
"type": "error"
},
{
"inputs": [],
"name": "NoSuchProvider",
Expand All @@ -53,5 +58,10 @@
"inputs": [],
"name": "Unauthorized",
"type": "error"
},
{
"inputs": [],
"name": "UpdateTooOld",
"type": "error"
}
]
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,31 @@
"name": "ProviderFeeUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "provider",
"type": "address"
},
{
"indexed": false,
"internalType": "uint64",
"name": "oldMaxNumHashes",
"type": "uint64"
},
{
"indexed": false,
"internalType": "uint64",
"name": "newMaxNumHashes",
"type": "uint64"
}
],
"name": "ProviderMaxNumHashesUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
Expand Down Expand Up @@ -133,6 +158,11 @@
"internalType": "address",
"name": "feeManager",
"type": "address"
},
{
"internalType": "uint32",
"name": "maxNumHashes",
"type": "uint32"
}
],
"indexed": false,
Expand Down
Loading

0 comments on commit 5145a0c

Please sign in to comment.