Skip to content

Commit 6c18941

Browse files
authored
Adds approved list and node id hex restriction (#224)
* removing added field * remove address check * remove access node requirement * adds approved list and node id hex restriction * remove unnecessary lines from advance view
1 parent c574969 commit 6c18941

14 files changed

+270
-87
lines changed

contracts/FlowIDTableStaking.cdc

+69-2
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ pub contract FlowIDTableStaking {
160160
) {
161161
pre {
162162
id.length == 64: "Node ID length must be 32 bytes (64 hex characters)"
163+
FlowIDTableStaking.isValidNodeID(id): "The node ID must have only numbers and lowercase hex characters"
163164
FlowIDTableStaking.nodes[id] == nil: "The ID cannot already exist in the record"
164165
role >= UInt8(1) && role <= UInt8(5): "The role must be 1, 2, 3, 4, or 5"
165166
networkingAddress.length > 0 && networkingAddress.length <= 510: "The networkingAddress must be less than 255 bytes (510 hex characters)"
@@ -728,6 +729,15 @@ pub contract FlowIDTableStaking {
728729
return <-node
729730
}
730731

732+
/// Sets a list of approved node IDs for the next epoch
733+
/// Nodes not on this list will be unstaked at the end of the staking auction
734+
/// and not considered to be a proposed/staked node
735+
pub fun setApprovedList(_ nodeIDs: [String]) {
736+
let list = FlowIDTableStaking.account.load<[String]>(from: /storage/idTableApproveList)
737+
738+
FlowIDTableStaking.account.save<[String]>(nodeIDs, to: /storage/idTableApproveList)
739+
}
740+
731741
/// Starts the staking auction, the period when nodes and delegators
732742
/// are allowed to perform staking related operations
733743
pub fun startStakingAuction() {
@@ -737,7 +747,13 @@ pub contract FlowIDTableStaking {
737747

738748
/// Ends the staking Auction by removing any unapproved nodes
739749
/// and setting stakingEnabled to false
740-
pub fun endStakingAuction(approvedNodeIDs: {String: Bool}) {
750+
pub fun endStakingAuction() {
751+
let approvedList = FlowIDTableStaking.getApprovedList()
752+
let approvedNodeIDs: {String: Bool} = {}
753+
for id in approvedList {
754+
approvedNodeIDs[id] = true
755+
}
756+
741757
self.removeUnapprovedNodes(approvedNodeIDs: approvedNodeIDs)
742758

743759
FlowIDTableStaking.account.load<Bool>(from: /storage/stakingEnabled)
@@ -945,6 +961,9 @@ pub contract FlowIDTableStaking {
945961
// Start the new epoch's staking auction
946962
self.startStakingAuction()
947963

964+
// Set the current Epoch node list
965+
FlowIDTableStaking.setCurrentNodeList(FlowIDTableStaking.getApprovedList())
966+
948967
// Indicates that the tokens have moved and the epoch has ended
949968
// Tells what the new reward payout will be. The new payout is calculated and changed
950969
// before this method is executed and will not be changed for the rest of the epoch
@@ -1067,21 +1086,50 @@ pub contract FlowIDTableStaking {
10671086
self.account.save(claimedDictionary, to: path)
10681087
}
10691088

1089+
/// Sets a list of approved node IDs for the current epoch
1090+
access(contract) fun setCurrentNodeList(_ nodeIDs: [String]) {
1091+
let list = self.account.load<[String]>(from: /storage/idTableCurrentList)
1092+
1093+
self.account.save<[String]>(nodeIDs, to: /storage/idTableCurrentList)
1094+
}
1095+
1096+
/// Checks if the given string has all numbers or lowercase hex characters
1097+
/// Used to ensure that there are no duplicate node IDs
1098+
pub fun isValidNodeID(_ input: String): Bool {
1099+
let byteVersion = input.utf8
1100+
1101+
for character in byteVersion {
1102+
if ((character < 48) || (character > 57 && character < 97) || (character > 102)) {
1103+
return false
1104+
}
1105+
}
1106+
1107+
return true
1108+
}
1109+
10701110
/// Indicates if the staking auction is currently enabled
10711111
pub fun stakingEnabled(): Bool {
10721112
return self.account.copy<Bool>(from: /storage/stakingEnabled) ?? false
10731113
}
10741114

1075-
/// Gets an array of the node IDs that are proposed for the next epoch
1115+
/// Gets an array of the node IDs that are proposed and approved for the next epoch
10761116
pub fun getProposedNodeIDs(): [String] {
10771117
var proposedNodes: [String] = []
10781118

1119+
let approvedList = FlowIDTableStaking.getApprovedList()
1120+
let approvedNodeIDs: {String: Bool} = {}
1121+
for id in approvedList {
1122+
approvedNodeIDs[id] = true
1123+
}
1124+
10791125
for nodeID in FlowIDTableStaking.getNodeIDs() {
10801126
let nodeRecord = FlowIDTableStaking.borrowNodeRecord(nodeID)
1127+
let approved = approvedNodeIDs[nodeID] ?? false
10811128

10821129
// To be considered proposed, a node has to have tokens staked + committed equal or above the minimum
10831130
// Access nodes have a minimum of 0, so they need to be strictly greater than zero to be considered proposed
10841131
if self.isGreaterThanMinimumForRole(numTokens: self.NodeInfo(nodeID: nodeRecord.id).totalCommittedWithoutDelegators(), role: nodeRecord.role)
1132+
&& approved
10851133
{
10861134
proposedNodes.append(nodeID)
10871135
}
@@ -1096,12 +1144,21 @@ pub contract FlowIDTableStaking {
10961144
pub fun getStakedNodeIDs(): [String] {
10971145
var stakedNodes: [String] = []
10981146

1147+
let currentList = self.account.copy<[String]>(from: /storage/idTableCurrentList)
1148+
?? panic("Could not get current list")
1149+
let currentNodeIDs: {String: Bool} = {}
1150+
for id in currentList {
1151+
currentNodeIDs[id] = true
1152+
}
1153+
10991154
for nodeID in FlowIDTableStaking.getNodeIDs() {
11001155
let nodeRecord = FlowIDTableStaking.borrowNodeRecord(nodeID)
1156+
let current = currentNodeIDs[nodeID] ?? false
11011157

11021158
// To be considered staked, a node has to have tokens staked equal or above the minimum
11031159
// Access nodes have a minimum of 0, so they need to be strictly greater than zero to be considered staked
11041160
if self.isGreaterThanMinimumForRole(numTokens: nodeRecord.tokensStaked.balance, role: nodeRecord.role)
1161+
&& current
11051162
{
11061163
stakedNodes.append(nodeID)
11071164
}
@@ -1142,6 +1199,12 @@ pub contract FlowIDTableStaking {
11421199
return claimedDictionary[key] ?? false
11431200
}
11441201

1202+
/// Returns the list of approved node IDs that the admin has set
1203+
pub fun getApprovedList(): [String] {
1204+
return self.account.copy<[String]>(from: /storage/idTableApproveList)
1205+
?? panic("could not get approved list")
1206+
}
1207+
11451208
/// Gets the minimum stake requirements for all the node types
11461209
pub fun getMinimumStakeRequirements(): {UInt8: UFix64} {
11471210
return self.minimumStakeRequired
@@ -1203,6 +1266,10 @@ pub contract FlowIDTableStaking {
12031266
self.nodeDelegatingRewardCut = rewardCut
12041267
self.rewardRatios = {UInt8(1): 0.168, UInt8(2): 0.518, UInt8(3): 0.078, UInt8(4): 0.236, UInt8(5): 0.0}
12051268

1269+
let list: [String] = []
1270+
self.setCurrentNodeList(list)
1271+
self.account.save<[String]>(list, to: /storage/idTableApproveList)
1272+
12061273
self.account.save(<-create Admin(), to: self.StakingAdminStoragePath)
12071274
}
12081275
}

contracts/epochs/FlowEpoch.cdc

+5-15
Original file line numberDiff line numberDiff line change
@@ -349,17 +349,7 @@ pub contract FlowEpoch {
349349
switch FlowEpoch.currentEpochPhase {
350350
case EpochPhase.STAKINGAUCTION:
351351
if currentBlock.view >= currentEpochMetadata.stakingEndView {
352-
let ids = FlowIDTableStaking.getProposedNodeIDs()
353-
354-
let approvedIDs: {String: Bool} = {}
355-
for id in ids {
356-
// Here is where we would make sure that each node's
357-
// keys and addresses are correct, they haven't committed any violations,
358-
// and are operating properly
359-
// for now we just set approved to true for all
360-
approvedIDs[id] = true
361-
}
362-
self.endStakingAuction(approvedIDs: approvedIDs)
352+
self.endStakingAuction()
363353
}
364354
case EpochPhase.EPOCHSETUP:
365355
if FlowClusterQC.votingCompleted() && (FlowDKG.dkgCompleted() != nil) {
@@ -377,12 +367,12 @@ pub contract FlowEpoch {
377367

378368
/// Calls `FlowEpoch` functions to end the staking auction phase
379369
/// and start the Epoch Setup phase
380-
pub fun endStakingAuction(approvedIDs: {String: Bool}) {
370+
pub fun endStakingAuction() {
381371
pre {
382372
FlowEpoch.currentEpochPhase == EpochPhase.STAKINGAUCTION: "Can only end staking auction during the staking auction"
383373
}
384374

385-
FlowEpoch.endStakingAuction(approvedIDs: approvedIDs)
375+
FlowEpoch.endStakingAuction()
386376

387377
FlowEpoch.startEpochSetup(randomSource: unsafeRandom().toString())
388378
}
@@ -516,8 +506,8 @@ pub contract FlowEpoch {
516506
}
517507

518508
/// Ends the staking Auction with all the proposed nodes approved
519-
access(account) fun endStakingAuction(approvedIDs: {String: Bool}) {
520-
self.borrowStakingAdmin().endStakingAuction(approvedNodeIDs: approvedIDs)
509+
access(account) fun endStakingAuction() {
510+
self.borrowStakingAdmin().endStakingAuction()
521511
}
522512

523513
/// Starts the EpochSetup phase and emits the epoch setup event

lib/go/contracts/internal/assets/assets.go

+6-6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/go/templates/idtable_staking_templates.go

+7
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const (
1010
removeNodeFilename = "idTableStaking/admin/remove_node.cdc"
1111
endStakingFilename = "idTableStaking/admin/end_staking.cdc"
1212
removeUnapprovedNodesFilename = "idTableStaking/admin/remove_unapproved_nodes.cdc"
13+
setApprovedNodesFilename = "idTableStaking/admin/set_approved_nodes.cdc"
1314
payRewardsFilename = "idTableStaking/admin/pay_rewards.cdc"
1415
moveTokensFilename = "idTableStaking/admin/move_tokens.cdc"
1516
endEpochFilename = "idTableStaking/admin/end_epoch.cdc"
@@ -103,6 +104,12 @@ func GenerateRemoveUnapprovedNodesScript(env Environment) []byte {
103104
return []byte(replaceAddresses(code, env))
104105
}
105106

107+
func GenerateSetApprovedNodesScript(env Environment) []byte {
108+
code := assets.MustAssetString(setApprovedNodesFilename)
109+
110+
return []byte(replaceAddresses(code, env))
111+
}
112+
106113
// GeneratePayRewardsScript creates a script that pays rewards
107114
func GeneratePayRewardsScript(env Environment) []byte {
108115
code := assets.MustAssetString(payRewardsFilename)

0 commit comments

Comments
 (0)