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

feat!: introduce epochs #1660

Merged
merged 41 commits into from
Mar 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
01daa3c
cleanup ./changelog entries
mpoke Jan 11, 2024
1ab7c20
rebase
insumity Feb 23, 2024
efe8cb1
fix!: Validation of SlashAcks fails due to marshaling to Bech32 (bac…
mergify[bot] Jan 19, 2024
a5c1d4e
docs: update changelog for v4.0.0 (#1578)
mpoke Jan 22, 2024
c03587c
docs: prepare for v4.0.0 (#1581)
mpoke Jan 22, 2024
6c39a2f
added proto declaration
insumity Feb 21, 2024
ddae681
temp commit
insumity Feb 21, 2024
c9b1b3e
temp commit
insumity Feb 21, 2024
e0861e2
more changes
insumity Feb 21, 2024
d6cd207
first commit
insumity Feb 22, 2024
d144c39
add param and fix tests
insumity Feb 23, 2024
44d13cd
reduce epoch size for e2e
insumity Feb 23, 2024
076ec8b
clean up
insumity Feb 23, 2024
a56b6e0
mbt fix
insumity Feb 23, 2024
36afb40
fix diff bug
insumity Feb 26, 2024
481fd88
cleaning up
insumity Feb 26, 2024
35385dd
cleaning up
insumity Feb 26, 2024
27290f6
cleaning up
insumity Feb 26, 2024
c707c54
cleaning up
insumity Feb 26, 2024
cf8bdb8
cleaning up
insumity Feb 27, 2024
ead0873
cleaning up
insumity Feb 27, 2024
88081cd
added more tests
insumity Feb 27, 2024
f6397ad
more fixes
insumity Feb 27, 2024
bee8c10
nit fixes
insumity Feb 27, 2024
abb4abc
cleaning up
insumity Feb 27, 2024
30f2061
increase downtime by one block
insumity Feb 28, 2024
e986692
fix logs
insumity Feb 28, 2024
8aa1dc9
took into account Marius' comments
insumity Feb 28, 2024
16958bc
tiny fixes
insumity Feb 28, 2024
513a75d
Update x/ccv/provider/keeper/params.go
insumity Feb 29, 2024
2208cd4
use Bech32 addresses as keys for maps
insumity Feb 29, 2024
914840c
refactor nextBlocks(epoch) to nextEpoch
insumity Mar 1, 2024
791e582
fixed comment
insumity Mar 1, 2024
85a52b7
Remove new block creation during consumer chain setup
p-offtermatt Mar 4, 2024
f909e1a
Revert "Remove new block creation during consumer chain setup"
p-offtermatt Mar 4, 2024
d683b5a
added simple param test
insumity Mar 4, 2024
44b9eb1
added upper bound and addressed a comment
insumity Mar 4, 2024
538eee3
Add another edge case for diffing
p-offtermatt Mar 6, 2024
294aacb
used smarted solution (based on Philip's comment) for diffing validators
insumity Mar 6, 2024
31b7416
refactor!: remove key-assignment replacements (#1672)
insumity Mar 7, 2024
ba2ed34
add the epoch param in the docs
insumity Mar 7, 2024
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
116 changes: 17 additions & 99 deletions docs/docs/adrs/adr-001-key-assignment.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ title: Key Assignment

## Changelog
* 2022-12-01: Initial Draft
* 2024-03-01: Updated to take into account they key-assigment-replacement deprecation.

## Status

Expand All @@ -30,10 +31,6 @@ ConsumerValidatorsBytePrefix | len(chainID) | chainID | providerConsAddress -> c
```golang
ValidatorsByConsumerAddrBytePrefix | len(chainID) | chainID | consumerConsAddress -> providerConsAddress
```
- `KeyAssignmentReplacements` - Stores the key assignments that need to be replaced in the current block. Needed to apply the key assignments received in a block to the validator updates sent to the consumer chains.
```golang
KeyAssignmentReplacementsBytePrefix | len(chainID) | chainID | providerConsAddress -> abci.ValidatorUpdate{PubKey: oldConsumerKey, Power: currentPower},
```
- `ConsumerAddrsToPrune` - Stores the mapping from VSC ids to consumer validators addresses. Needed for pruning `ValidatorByConsumerAddr`.
```golang
ConsumerAddrsToPruneBytePrefix | len(chainID) | chainID | vscID -> []consumerConsAddresses
Expand Down Expand Up @@ -67,20 +64,6 @@ if _, consumerRegistered := GetConsumerClientId(chainID); consumerRegistered {
oldConsumerAddr := utils.TMCryptoPublicKeyToConsAddr(oldConsumerKey)
vscID := GetValidatorSetUpdateId()
AppendConsumerAddrsToPrune(chainID, vscID, oldConsumerAddr)
} else {
// the validator had no key assigned on this consumer chain
oldConsumerKey := validator.TmConsPublicKey()
}

// check whether the validator is valid, i.e., its power is positive
if currentPower := stakingKeeper.GetLastValidatorPower(providerAddr); currentPower > 0 {
// to enable multiple calls of AssignConsumerKey in the same block by the same validator
// the key assignment replacement should not be overwritten
if _, found := GetKeyAssignmentReplacement(chainID, providerConsAddr); !found {
// store old key and power for modifying the valset update in EndBlock
oldKeyAssignment := abci.ValidatorUpdate{PubKey: oldConsumerKey, Power: currentPower}
SetKeyAssignmentReplacement(chainID, providerConsAddr, oldKeyAssignment)
}
}
} else {
// if the consumer chain is not registered, then remove the previous reverse mapping
Expand Down Expand Up @@ -129,89 +112,24 @@ func (k Keeper) MakeConsumerGenesis(chainID string) (gen consumertypes.GenesisSt
}
```

On `EndBlock` while queueing `VSCPacket`s to send to registered consumer chains:
Note that key assignment works hand-in-hand with [epochs](https://github.com/cosmos/interchain-security/blob/main/docs/docs/adrs/adr-014-epochs.md).
For each consumer chain, we store the consumer validator set that is currently (i.e., in this epoch) validating the consumer chain.
Specifically, for each validator in the set we store among others, the public key that it is using on the consumer chain during the current (i.e., ongoing) epoch.
At the end of every epoch, if there were validator set changes on the provider, then for every consumer chain, we construct a `VSCPacket`
with all the validator updates and add it to the list of `PendingVSCPacket`s. We compute the validator updates needed by a consumer chain by
comparing the stored list of consumer validators with the current bonded validators on the provider, with something similar to this:
```golang
func QueueVSCPackets() {
valUpdateID := GetValidatorSetUpdateId()
// get the validator updates from the staking module
valUpdates := stakingKeeper.GetValidatorUpdates()

IterateConsumerChains(func(chainID, clientID string) (stop bool) {
// apply the key assignment to the validator updates
valUpdates := ApplyKeyAssignmentToValUpdates(chainID, valUpdates)
// ..
})
// ...
}

func ApplyKeyAssignmentToValUpdates(
chainID string,
valUpdates []abci.ValidatorUpdate,
) (newUpdates []abci.ValidatorUpdate) {
for _, valUpdate := range valUpdates {
providerAddr := utils.TMCryptoPublicKeyToConsAddr(valUpdate.PubKey)

// if a key assignment replacement is found, then
// remove the valupdate with the old consumer key
// and create two new valupdates
prevConsumerKey, _, found := GetKeyAssignmentReplacement(chainID, providerAddr)
if found {
// set the old consumer key's power to 0
newUpdates = append(newUpdates, abci.ValidatorUpdate{
PubKey: prevConsumerKey,
Power: 0,
})
// set the new consumer key's power to the power in the update
newConsumerKey := GetValidatorConsumerPubKey(chainID, providerAddr)
newUpdates = append(newUpdates, abci.ValidatorUpdate{
PubKey: newConsumerKey,
Power: valUpdate.Power,
})
// delete key assignment replacement
DeleteKeyAssignmentReplacement(chainID, providerAddr)
} else {
// there is no key assignment replacement;
// check if the validator's key is assigned
consumerKey, found := k.GetValidatorConsumerPubKey(ctx, chainID, providerAddr)
if found {
// replace the update containing the provider key
// with an update containing the consumer key
newUpdates = append(newUpdates, abci.ValidatorUpdate{
PubKey: consumerKey,
Power: valUpdate.Power,
})
} else {
// keep the same update
newUpdates = append(newUpdates, valUpdate)
}
}
}

// iterate over the remaining key assignment replacements
IterateKeyAssignmentReplacements(chainID, func(
pAddr sdk.ConsAddress,
prevCKey tmprotocrypto.PublicKey,
power int64,
) (stop bool) {
// set the old consumer key's power to 0
newUpdates = append(newUpdates, abci.ValidatorUpdate{
PubKey: prevCKey,
Power: 0,
})
// set the new consumer key's power to the power in key assignment replacement
newConsumerKey := GetValidatorConsumerPubKey(chainID, pAddr)
newUpdates = append(newUpdates, abci.ValidatorUpdate{
PubKey: newConsumerKey,
Power: power,
})
return false
})

// remove all the key assignment replacements

return newUpdates
}
// get the valset that has been validating the consumer chain during this epoch
currentValidators := GetConsumerValSet(consumerChain)
// generate the validator updates needed to be sent through a `VSCPacket` by comparing the current validators
// in the epoch with the latest bonded validators
valUpdates := DiffValidators(currentValidators, stakingmodule.GetBondedValidators())
// update the current validators set for the upcoming epoch to be the latest bonded validators instead
SetConsumerValSet(stakingmodule.GetBondedValidators())
```
where `DiffValidators` internally checks if the consumer public key for a validator has changed since the last
epoch and if so generates a validator update. This way, a validator can change its consumer public key for a consumer
chain an arbitrary amount of times and only the last set consumer public key would be taken into account.

On receiving a `SlashPacket` from a consumer chain with id `chainID` for a infraction of a validator `data.Validator`:
```golang
Expand Down
7 changes: 7 additions & 0 deletions docs/docs/introduction/params.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,3 +149,10 @@ This param would allow provider binaries to panic deterministically in the event

`RetryDelayPeriod` exists on the consumer for **ICS versions >= v3.2.0** (introduced by the implementation of [ADR-008](../adrs/adr-008-throttle-retries.md)) and is the period at which the consumer retries to send a `SlashPacket` that was rejected by the provider.


## Epoch Parameters

### BlocksPerEpoch
`BlocksPerEpoch` exists on the provider for **ICS versions >= 3.3.0** (introduced by the implementation of [ADR-014](../adrs/adr-014-epochs.md))
and corresponds to the number of blocks that constitute an epoch. This param is set to 600 by default and cannot exceed 1200.
Assuming we need 6 seconds per block, the default value corresponds to 1 hour and the maximum to 2 hours.
14 changes: 14 additions & 0 deletions proto/interchain_security/ccv/provider/v1/provider.proto
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,9 @@ message Params {
// The fee required to be paid to add a reward denom
cosmos.base.v1beta1.Coin consumer_reward_denom_registration_fee = 9
[ (gogoproto.nullable) = false ];

// The number of blocks that comprise an epoch.
int64 blocks_per_epoch = 10;
}

// SlashAcks contains cons addresses of consumer chain validators
Expand Down Expand Up @@ -295,3 +298,14 @@ message ConsumerAddrsToPrune {
uint64 vsc_id = 2;
AddressList consumer_addrs = 3;
}

// ConsumerValidator is used to facilitate epoch-based transitions. It contains relevant info for
// a validator that is expected to validate on a consumer chain during an epoch.
message ConsumerValidator {
// validator's consensus address on the provider chain
bytes provider_cons_addr = 1;
// voting power the validator has during this epoch
int64 power = 2;
// public key the validator uses on the consumer chain during this epoch
tendermint.crypto.PublicKey consumer_public_key = 3;
}
14 changes: 13 additions & 1 deletion tests/e2e/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -1307,6 +1307,12 @@ func (tr TestConfig) relayPacketsGorelayer(
target ExecutionTarget,
verbose bool,
) {
// Because `.app_state.provider.params.blocks_per_epoch` is set to 3 in the E2E tests, we wait 3 blocks
// before relaying the packets to guarantee that at least one epoch passes and hence any `VSCPacket`s get
// queued and are subsequently relayed.
tr.waitBlocks(action.ChainA, 3, 90*time.Second)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Later calls to waitBlocks for 1 block have a timeout of 30 seconds, so for 3 blocks I'm adding a 90 seconds time out here.

Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we have to wait for 3 blocks when the epoch param is set to 2 blocks?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch. Changed blocks_per_epoch to 3.

tr.waitBlocks(action.ChainB, 3, 90*time.Second)

pathName := tr.GetPathNameForGorelayer(action.ChainA, action.ChainB)

// rly transact relay-packets [path-name] --channel [channel-id]
Expand All @@ -1331,6 +1337,12 @@ func (tr TestConfig) relayPacketsHermes(
target ExecutionTarget,
verbose bool,
) {
// Because `.app_state.provider.params.blocks_per_epoch` is set to 3 in the E2E tests, we wait 3 blocks
// before relaying the packets to guarantee that at least one epoch passes and hence any `VSCPacket`s get
// queued and are subsequently relayed.
tr.waitBlocks(action.ChainA, 3, 90*time.Second)
tr.waitBlocks(action.ChainB, 3, 90*time.Second)

// hermes clear packets ibc0 transfer channel-13
cmd := target.ExecCommand("hermes", "clear", "packets",
"--chain", string(tr.chainConfigs[action.ChainA].ChainId),
Expand Down Expand Up @@ -1639,7 +1651,7 @@ func (tr TestConfig) invokeDowntimeSlash(action DowntimeSlashAction, target Exec
// Bring validator down
tr.setValidatorDowntime(action.Chain, action.Validator, true, target, verbose)
// Wait appropriate amount of blocks for validator to be slashed
tr.waitBlocks(action.Chain, 10, 3*time.Minute)
tr.waitBlocks(action.Chain, 11, 3*time.Minute)
// Bring validator back up
tr.setValidatorDowntime(action.Chain, action.Validator, false, target, verbose)
}
Expand Down
33 changes: 20 additions & 13 deletions tests/e2e/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,8 @@ func SlashThrottleTestConfig() TestConfig {
".app_state.slashing.params.downtime_jail_duration = \"60s\" | " +
".app_state.slashing.params.slash_fraction_downtime = \"0.010000000000000000\" | " +
".app_state.provider.params.slash_meter_replenish_fraction = \"0.10\" | " +
".app_state.provider.params.slash_meter_replenish_period = \"20s\"",
".app_state.provider.params.slash_meter_replenish_period = \"20s\" | " +
".app_state.provider.params.blocks_per_epoch = 3",
},
ChainID("consu"): {
ChainId: ChainID("consu"),
Expand All @@ -249,7 +250,7 @@ func SlashThrottleTestConfig() TestConfig {
IpPrefix: "7.7.8",
VotingWaitTime: 20,
GenesisChanges: ".app_state.gov.params.voting_period = \"20s\" | " +
".app_state.slashing.params.signed_blocks_window = \"15\" | " +
".app_state.slashing.params.signed_blocks_window = \"20\" | " +
".app_state.slashing.params.min_signed_per_window = \"0.500000000000000000\" | " +
".app_state.slashing.params.downtime_jail_duration = \"60s\" | " +
".app_state.slashing.params.slash_fraction_downtime = \"0.010000000000000000\" | " +
Expand Down Expand Up @@ -288,7 +289,8 @@ func DefaultTestConfig() TestConfig {
".app_state.slashing.params.downtime_jail_duration = \"60s\" | " +
".app_state.slashing.params.slash_fraction_downtime = \"0.010000000000000000\" | " +
".app_state.provider.params.slash_meter_replenish_fraction = \"1.0\" | " + // This disables slash packet throttling
".app_state.provider.params.slash_meter_replenish_period = \"3s\"",
".app_state.provider.params.slash_meter_replenish_period = \"3s\" | " +
".app_state.provider.params.blocks_per_epoch = 3",
},
ChainID("consu"): {
ChainId: ChainID("consu"),
Expand All @@ -297,7 +299,7 @@ func DefaultTestConfig() TestConfig {
IpPrefix: "7.7.8",
VotingWaitTime: 20,
GenesisChanges: ".app_state.gov.params.voting_period = \"20s\" | " +
".app_state.slashing.params.signed_blocks_window = \"15\" | " +
".app_state.slashing.params.signed_blocks_window = \"20\" | " +
".app_state.slashing.params.min_signed_per_window = \"0.500000000000000000\" | " +
".app_state.slashing.params.downtime_jail_duration = \"60s\" | " +
".app_state.slashing.params.slash_fraction_downtime = \"0.010000000000000000\"",
Expand All @@ -317,7 +319,8 @@ func DemocracyTestConfig(allowReward bool) TestConfig {
".app_state.slashing.params.min_signed_per_window = \"0.500000000000000000\" | " +
".app_state.slashing.params.downtime_jail_duration = \"60s\" | " +
".app_state.slashing.params.slash_fraction_downtime = \"0.010000000000000000\" | " +
".app_state.transfer.params.send_enabled = false"
".app_state.transfer.params.send_enabled = false | " +
".app_state.provider.params.blocks_per_epoch = 3"

if allowReward {
// This allows the consumer chain to send rewards in the stake denom
Expand Down Expand Up @@ -347,7 +350,8 @@ func DemocracyTestConfig(allowReward bool) TestConfig {
".app_state.slashing.params.min_signed_per_window = \"0.500000000000000000\" | " +
".app_state.slashing.params.downtime_jail_duration = \"60s\" | " +
".app_state.slashing.params.slash_fraction_downtime = \"0.010000000000000000\" | " +
".app_state.provider.params.slash_meter_replenish_fraction = \"1.0\"", // This disables slash packet throttling
".app_state.provider.params.slash_meter_replenish_fraction = \"1.0\" | " + // This disables slash packet throttling
".app_state.provider.params.blocks_per_epoch = 3",
},
ChainID("democ"): {
ChainId: ChainID("democ"),
Expand Down Expand Up @@ -389,7 +393,8 @@ func MultiConsumerTestConfig() TestConfig {
".app_state.slashing.params.min_signed_per_window = \"0.500000000000000000\" | " +
".app_state.slashing.params.downtime_jail_duration = \"60s\" | " +
".app_state.slashing.params.slash_fraction_downtime = \"0.010000000000000000\" | " +
".app_state.provider.params.slash_meter_replenish_fraction = \"1.0\"", // This disables slash packet throttling
".app_state.provider.params.slash_meter_replenish_fraction = \"1.0\" | " + // This disables slash packet throttling
".app_state.provider.params.blocks_per_epoch = 3",
},
ChainID("consu"): {
ChainId: ChainID("consu"),
Expand All @@ -398,7 +403,7 @@ func MultiConsumerTestConfig() TestConfig {
IpPrefix: "7.7.8",
VotingWaitTime: 20,
GenesisChanges: ".app_state.gov.params.voting_period = \"20s\" | " +
".app_state.slashing.params.signed_blocks_window = \"10\" | " +
".app_state.slashing.params.signed_blocks_window = \"20\" | " +
".app_state.slashing.params.min_signed_per_window = \"0.500000000000000000\" | " +
".app_state.slashing.params.downtime_jail_duration = \"60s\" | " +
".app_state.slashing.params.slash_fraction_downtime = \"0.010000000000000000\"",
Expand All @@ -410,7 +415,7 @@ func MultiConsumerTestConfig() TestConfig {
IpPrefix: "7.7.9",
VotingWaitTime: 20,
GenesisChanges: ".app_state.gov.params.voting_period = \"20s\" | " +
".app_state.slashing.params.signed_blocks_window = \"10\" | " +
".app_state.slashing.params.signed_blocks_window = \"20\" | " +
".app_state.slashing.params.min_signed_per_window = \"0.500000000000000000\" | " +
".app_state.slashing.params.downtime_jail_duration = \"60s\" | " +
".app_state.slashing.params.slash_fraction_downtime = \"0.010000000000000000\"",
Expand Down Expand Up @@ -448,7 +453,8 @@ func ChangeoverTestConfig() TestConfig {
".app_state.slashing.params.downtime_jail_duration = \"60s\" | " +
".app_state.slashing.params.slash_fraction_downtime = \"0.010000000000000000\" | " +
".app_state.provider.params.slash_meter_replenish_fraction = \"1.0\" | " + // This disables slash packet throttling
".app_state.provider.params.slash_meter_replenish_period = \"3s\"",
".app_state.provider.params.slash_meter_replenish_period = \"3s\" | " +
".app_state.provider.params.blocks_per_epoch = 3",
},
ChainID("sover"): {
ChainId: ChainID("sover"),
Expand All @@ -458,7 +464,7 @@ func ChangeoverTestConfig() TestConfig {
IpPrefix: "7.7.8",
VotingWaitTime: 20,
GenesisChanges: ".app_state.gov.params.voting_period = \"20s\" | " +
".app_state.slashing.params.signed_blocks_window = \"15\" | " +
".app_state.slashing.params.signed_blocks_window = \"20\" | " +
".app_state.slashing.params.min_signed_per_window = \"0.500000000000000000\" | " +
".app_state.slashing.params.downtime_jail_duration = \"60s\" | " +
".app_state.slashing.params.slash_fraction_downtime = \"0.010000000000000000\" | " +
Expand Down Expand Up @@ -548,7 +554,8 @@ func ConsumerMisbehaviourTestConfig() TestConfig {
".app_state.slashing.params.downtime_jail_duration = \"60s\" | " +
".app_state.slashing.params.slash_fraction_downtime = \"0.010000000000000000\" | " +
".app_state.provider.params.slash_meter_replenish_fraction = \"1.0\" | " + // This disables slash packet throttling
".app_state.provider.params.slash_meter_replenish_period = \"3s\"",
".app_state.provider.params.slash_meter_replenish_period = \"3s\" | " +
".app_state.provider.params.blocks_per_epoch = 3",
},
ChainID("consu"): {
ChainId: ChainID("consu"),
Expand All @@ -557,7 +564,7 @@ func ConsumerMisbehaviourTestConfig() TestConfig {
IpPrefix: "7.7.8",
VotingWaitTime: 20,
GenesisChanges: ".app_state.gov.params.voting_period = \"20s\" | " +
".app_state.slashing.params.signed_blocks_window = \"15\" | " +
".app_state.slashing.params.signed_blocks_window = \"20\" | " +
".app_state.slashing.params.min_signed_per_window = \"0.500000000000000000\" | " +
".app_state.slashing.params.downtime_jail_duration = \"60s\" | " +
".app_state.slashing.params.slash_fraction_downtime = \"0.010000000000000000\"",
Expand Down
11 changes: 10 additions & 1 deletion tests/integration/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ func delegateAndRedelegate(s *CCVTestSuite, delAddr sdk.AccAddress,
srcValTokensAfter := s.getVal(s.providerCtx(), srcValAddr).GetBondedTokens()
s.Require().Equal(srcValTokensAfter.Sub(srcValTokensBefore), amount)

s.providerChain.NextBlock()
s.nextEpoch()

dstValTokensBefore := s.getVal(s.providerCtx(), dstValAddr).GetBondedTokens()

Expand Down Expand Up @@ -625,3 +625,12 @@ func (s *CCVTestSuite) mustGetStakingValFromTmVal(tmVal tmtypes.Validator) (stak
s.Require().True(found)
return stakingVal
}

// nextEpoch moves `chain` forward by an epoch
func (s *CCVTestSuite) nextEpoch() {
blocksPerEpoch := s.providerApp.GetProviderKeeper().GetParams(s.providerCtx()).BlocksPerEpoch

for i := int64(0); i < blocksPerEpoch; i++ {
s.providerChain.NextBlock()
}
}
Loading
Loading