-
Notifications
You must be signed in to change notification settings - Fork 158
ResourceConstraintManager v2 (add support for setMultiGasPricingConstraints) #415
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
base: develop
Are you sure you want to change the base?
Changes from 14 commits
6863e43
4a56e58
40f7aed
e641bc2
7f8f60f
a256462
8a6f7db
4d3803b
dd9041d
de3c14c
cd1f823
aed24d5
cd266f1
80c0080
e266f6f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,12 +5,19 @@ | |
| pragma solidity ^0.8.0; | ||
|
|
||
| import "../precompiles/ArbOwner.sol"; | ||
| import "../precompiles/ArbGasInfo.sol"; | ||
| import "@openzeppelin/contracts/access/AccessControlEnumerable.sol"; | ||
|
|
||
| contract ResourceConstraintManager is AccessControlEnumerable { | ||
| ArbOwner internal constant ARB_OWNER = ArbOwner(address(0x70)); | ||
| ArbGasInfo internal constant ARB_GAS_INFO = ArbGasInfo(address(0x6c)); | ||
|
|
||
| // Constraint parameters boundaries | ||
| uint256 public constant MAX_SINGLE_GAS_CONSTRAINTS = 10; | ||
| uint256 public constant MAX_MULTI_GAS_CONSTRAINTS = 100; | ||
| uint64 public constant MIN_GAS_TARGET_PER_SEC = 7_000_000; | ||
| uint64 public constant MAX_GAS_TARGET_PER_SEC = 100_000_000; | ||
| uint32 public constant MIN_ADJUSTMENT_WINDOW_SECS = 5; | ||
| uint32 public constant MAX_ADJUSTMENT_WINDOW_SECS = 86400; | ||
| uint64 public constant MAX_PRICING_EXPONENT = 8000; // scaled by 1000 to allow for fractional exponents | ||
|
|
||
| bytes32 public constant MANAGER_ROLE = keccak256("MANAGER_ROLE"); | ||
| uint256 public expiryTimestamp; | ||
|
|
@@ -22,6 +29,7 @@ contract ResourceConstraintManager is AccessControlEnumerable { | |
| error InvalidTarget( | ||
| uint64 gasTargetPerSec, uint64 adjustmentWindowSecs, uint64 startingBacklogValue | ||
| ); | ||
| error InvalidResources(uint8 resourceKind); | ||
| error PricingExponentTooHigh(uint64 pricingExponent); | ||
| error NotExpired(); | ||
|
|
||
|
|
@@ -50,18 +58,23 @@ contract ResourceConstraintManager is AccessControlEnumerable { | |
| ) external onlyRole(MANAGER_ROLE) { | ||
| // If zero constraints are provided, the chain uses the single-constraint pricing model | ||
| uint256 nConstraints = constraints.length; | ||
| if (nConstraints > 10) { | ||
| if (nConstraints > MAX_SINGLE_GAS_CONSTRAINTS) { | ||
| revert TooManyConstraints(); | ||
| } | ||
| uint64 pricingExponent = 0; | ||
| for (uint256 i = 0; i < nConstraints; ++i) { | ||
| uint64 gasTargetPerSec = constraints[i][0]; | ||
| uint64 adjustmentWindowSecs = constraints[i][1]; | ||
| uint64 startingBacklogValue = constraints[i][2]; | ||
| if (gasTargetPerSec < 7_000_000 || gasTargetPerSec > 100_000_000) { | ||
| if ( | ||
| gasTargetPerSec < MIN_GAS_TARGET_PER_SEC || gasTargetPerSec > MAX_GAS_TARGET_PER_SEC | ||
| ) { | ||
| revert InvalidTarget(gasTargetPerSec, adjustmentWindowSecs, startingBacklogValue); | ||
| } | ||
| if (adjustmentWindowSecs < 5 || adjustmentWindowSecs > 86400) { | ||
| if ( | ||
| adjustmentWindowSecs < MIN_ADJUSTMENT_WINDOW_SECS | ||
| || adjustmentWindowSecs > MAX_ADJUSTMENT_WINDOW_SECS | ||
| ) { | ||
| revert InvalidPeriod(gasTargetPerSec, adjustmentWindowSecs, startingBacklogValue); | ||
| } | ||
| // we scale by 1000 to improve precision in calculating the exponent | ||
|
|
@@ -74,15 +87,109 @@ contract ResourceConstraintManager is AccessControlEnumerable { | |
| (startingBacklogValue * 1000) / (gasTargetPerSec * adjustmentWindowSecs); | ||
| } | ||
|
|
||
| // this calculated pricing exponent will by used by nitro to calculate the gas price | ||
| // this calculated pricing exponent will be used by nitro to calculate the gas price | ||
| // we check that the pricing exponent is below some reasonable number to avoid setting the gas price astronomically high | ||
| // as long as the gas price is not so high that no-one at all can send a transaction the chain will be able to function | ||
| // eg. these constraints can be changed again, or the sec council can send admin transactions | ||
| // with min base fee of 0.02, exponent of 8 (scaled by 1000) corresponds to a gas price of ~60 Gwei | ||
| if (pricingExponent > 8000) { | ||
| if (pricingExponent > MAX_PRICING_EXPONENT) { | ||
| revert PricingExponentTooHigh(pricingExponent); | ||
| } | ||
|
|
||
| ARB_OWNER.setGasPricingConstraints(constraints); | ||
| } | ||
|
|
||
| /// @notice Sets the list of multi-gas pricing constraints for the multi-dimensional multi-constraint pricing model. | ||
| /// See ArbOwner.setMultiGasPricingConstraints interface for more information. | ||
| /// @param constraints Array of ResourceConstraint structs, each containing: | ||
| /// - resources: list of (ResourceKind, weight) pairs. Must be sorted by ResourceKind and contain no duplicate ResourceKinds. (see ArbMultiGasConstraintsTypes for struct definitions) | ||
| /// - adjustmentWindowSecs: time window (seconds) over which the price will rise by a factor of e if demand is 2x the target (uint32, seconds) | ||
| /// - targetPerSec: target gas usage per second for this constraint (uint64, gas/sec) | ||
| /// - backlog: initial backlog value for this constraint (uint64, gas units) | ||
| function setMultiGasPricingConstraints( | ||
| ArbMultiGasConstraintsTypes.ResourceConstraint[] calldata constraints | ||
| ) external onlyRole(MANAGER_ROLE) { | ||
| // If zero constraints are provided, the chain uses the single-constraint pricing model | ||
| // Each constraint adds a small amount of overhead to the gas cost of each transaction and block, so we limit the number of constraints that can be set | ||
| uint256 nConstraints = constraints.length; | ||
| if (nConstraints > MAX_MULTI_GAS_CONSTRAINTS) { | ||
| revert TooManyConstraints(); | ||
| } | ||
|
|
||
| // We calculate the implied pricing exponent for each resource kind | ||
| uint8 numResourceKinds = uint8(type(ArbMultiGasConstraintsTypes.ResourceKind).max) + 1; | ||
| uint64[] memory pricingExponents = new uint64[](numResourceKinds); | ||
| for (uint256 i = 0; i < nConstraints; ++i) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe also check weight != 0
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it doesn't matter if a resource has a weight of 0, if I understand the nitro code correctly.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not necessary, but setting weight to 0 is probably a mistake (can be omitted instead) so good to check here |
||
| uint64 targetPerSec = constraints[i].targetPerSec; | ||
| uint32 adjustmentWindowSecs = constraints[i].adjustmentWindowSecs; | ||
| uint64 startingBacklogValue = constraints[i].backlog; | ||
| if (targetPerSec < MIN_GAS_TARGET_PER_SEC || targetPerSec > MAX_GAS_TARGET_PER_SEC) { | ||
| revert InvalidTarget(targetPerSec, adjustmentWindowSecs, startingBacklogValue); | ||
| } | ||
| if ( | ||
| adjustmentWindowSecs < MIN_ADJUSTMENT_WINDOW_SECS | ||
| || adjustmentWindowSecs > MAX_ADJUSTMENT_WINDOW_SECS | ||
|
TucksonDev marked this conversation as resolved.
|
||
| ) { | ||
| revert InvalidPeriod(targetPerSec, adjustmentWindowSecs, startingBacklogValue); | ||
| } | ||
| { | ||
| // Check for unsorted or duplicate resource kinds within this constraint | ||
| // The check is performed here instead of in the loop below (for calculating pricing exponents) | ||
| // to prevent bypassing the check when setting a starting backlog value of zero | ||
| // (in that case, nitro would only store the last of the duplicated entries) | ||
| uint8 lastResourceKind = 0; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. let's set this to
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've just removed the (j>0) guard so it also checks during the first iteration, which will revert if kind = 0 |
||
| uint256 nResources = constraints[i].resources.length; | ||
| for (uint256 j = 0; j < nResources; ++j) { | ||
| uint8 kind = uint8(constraints[i].resources[j].resource); | ||
| // check that resource kinds are sorted and contain no duplicates | ||
| if (j > 0 && kind <= lastResourceKind) { | ||
| revert InvalidResources(kind); | ||
| } | ||
| lastResourceKind = kind; | ||
| } | ||
| } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we can check final lastResourceKind is a valid ResourceKind too
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since the struct property is |
||
| if (startingBacklogValue > 0) { | ||
| // Find the maximum weight among all resources in this constraint | ||
| uint64 maxWeight = 0; | ||
| uint256 nResources = constraints[i].resources.length; | ||
| for (uint256 j = 0; j < nResources; ++j) { | ||
| uint64 weight = constraints[i].resources[j].weight; | ||
| if (weight > maxWeight) { | ||
| maxWeight = weight; | ||
| } | ||
| } | ||
|
|
||
| if (maxWeight > 0) { | ||
| // Neither of these values can be zero due to the earlier checks, so this division is safe | ||
| uint256 divisor = | ||
| uint256(adjustmentWindowSecs) * uint256(targetPerSec) * uint256(maxWeight); | ||
|
|
||
| // Calculate per-resource-kind exponent contribution | ||
| // we scale by 1000 to improve precision in calculating the exponent | ||
| // since this division will round down, it's always possible for the real exponent to be up to | ||
| // the number of constraints greater than the value we measure | ||
| // Operation is performed in uint256 to avoid overflow, but the result is guaranteed to fit in uint64 due to the earlier check | ||
| for (uint256 j = 0; j < nResources; ++j) { | ||
| uint8 kind = uint8(constraints[i].resources[j].resource); | ||
| uint64 weight = constraints[i].resources[j].weight; | ||
| pricingExponents[kind] += | ||
| uint64(uint256(startingBacklogValue) * uint256(weight) * 1000 / divisor); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // this calculated pricing exponent will be used by nitro to calculate the gas price | ||
| // we check that the pricing exponent is below some reasonable number to avoid setting the gas price astronomically high | ||
| // as long as the gas price is not so high that no-one at all can send a transaction the chain will be able to function | ||
| // eg. these constraints can be changed again, or the sec council can send admin transactions | ||
| // with min base fee of 0.02, exponent of 8 (scaled by 1000) corresponds to a gas price of ~60 Gwei | ||
| for (uint8 k = 0; k < numResourceKinds; ++k) { | ||
| if (pricingExponents[k] > MAX_PRICING_EXPONENT) { | ||
| revert PricingExponentTooHigh(pricingExponents[k]); | ||
| } | ||
| } | ||
|
|
||
| ARB_OWNER.setMultiGasPricingConstraints(constraints); | ||
| } | ||
| } | ||
| +67 −0 | .github/workflows/sbom-export.yaml | |
| +1 −1 | ArbAddressTable.sol | |
| +37 −0 | ArbFilteredTransactionsManager.sol | |
| +28 −0 | ArbGasInfo.sol | |
| +2 −1 | ArbInfo.sol | |
| +45 −0 | ArbMultiGasConstraintsTypes.sol | |
| +99 −0 | ArbOwner.sol | |
| +24 −0 | ArbOwnerPublic.sol | |
| +1 −1 | LICENSE.md |
Uh oh!
There was an error while loading. Please reload this page.