Skip to content

Commit

Permalink
Merge pull request #420 from onflow/khalil/5639-epoch-recovery-transa…
Browse files Browse the repository at this point in the history
…ction

EpochRecover service event and transaction
  • Loading branch information
kc1116 authored Jul 29, 2024
2 parents 768787d + 1f63515 commit 3118243
Show file tree
Hide file tree
Showing 9 changed files with 770 additions and 23 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,4 @@ jobs:
- name: Update PATH
run: echo "/root/.local/bin" >> $GITHUB_PATH
- name: Run tests
run: export GOPATH=$HOME/go && make ci
run: export GOPATH=$HOME/go && make ci
15 changes: 15 additions & 0 deletions contracts/epochs/FlowClusterQC.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,21 @@ access(all) contract FlowClusterQC {
}
}

/// Represents the quorum certificate vote data for a signer
/// of the certificate.
access(all) struct ClusterQCVoteData {
/// The aggregated signature, hex-encoded, encompasses all individual vote signatures contributed by nodes across the cluster
access(all) let aggregatedSignature: String

/// The node IDs that contributed their vote to the aggregated signature
access(all) let voterIDs: [String]

init(aggregatedSignature: String, voterIDs: [String]) {
self.aggregatedSignature = aggregatedSignature
self.voterIDs = voterIDs
}
}

/// The Voter resource is generated for each collection node after they register.
/// Each resource instance is good for all future potential epochs, but will
/// only be valid if the node operator has been confirmed as a collector node for the next epoch.
Expand Down
316 changes: 303 additions & 13 deletions contracts/epochs/FlowEpoch.cdc

Large diffs are not rendered by default.

12 changes: 6 additions & 6 deletions lib/go/contracts/internal/assets/assets.go

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions lib/go/templates/epoch_templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const (
updateRewardPercentageFilename = "epoch/admin/update_reward.cdc"
advanceViewFilename = "epoch/admin/advance_view.cdc"
resetEpochFilename = "epoch/admin/reset_epoch.cdc"
recoverEpochFilename = "epoch/admin/recover_epoch.cdc"
epochCalculateSetRewardsFilename = "epoch/admin/calculate_rewards.cdc"
epochPayRewardsFilename = "epoch/admin/pay_rewards.cdc"
epochSetAutoRewardsFilename = "epoch/admin/set_automatic_rewards.cdc"
Expand Down Expand Up @@ -114,6 +115,12 @@ func GenerateResetEpochScript(env Environment) []byte {
return []byte(ReplaceAddresses(code, env))
}

func GenerateRecoverEpochScript(env Environment) []byte {
code := assets.MustAssetString(recoverEpochFilename)

return []byte(ReplaceAddresses(code, env))
}

func GenerateEpochCalculateSetRewardsScript(env Environment) []byte {
code := assets.MustAssetString(epochCalculateSetRewardsFilename)

Expand Down
23 changes: 23 additions & 0 deletions lib/go/templates/internal/assets/assets.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

187 changes: 184 additions & 3 deletions lib/go/test/epoch_test_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (

"github.com/onflow/cadence"
jsoncdc "github.com/onflow/cadence/encoding/json"
"github.com/onflow/cadence/runtime/common"
cdcCommon "github.com/onflow/cadence/runtime/common"
"github.com/onflow/cadence/runtime/interpreter"
"github.com/onflow/flow-core-contracts/lib/go/contracts"
"github.com/onflow/flow-core-contracts/lib/go/templates"
Expand Down Expand Up @@ -130,6 +132,82 @@ type EpochCommit struct {
dkgPubKeys []string
}

// EpochRecover used to verify EpochRecover event fields in tests.
type EpochRecover struct {
counter uint64
nodeInfoLength int
firstView uint64
finalView uint64
collectorClusters []cadence.Value
randomSource string
dkgPhase1FinalView uint64
dkgPhase2FinalView uint64
dkgPhase3FinalView uint64
targetDuration uint64
targetEndTime uint64
clusterQCVoteDataLength int
dkgPubKeys []string
}

type EpochRecoverEvent flow.Event

// Counter returns counter field in EpochRecover event.
func (evt EpochRecoverEvent) Counter() cadence.UInt64 {
return cadence.SearchFieldByName(evt.Value, "counter").(cadence.UInt64)
}

// NodeInfo returns nodeInfo field in EpochRecover event.
func (evt EpochRecoverEvent) NodeInfo() cadence.Array {
return cadence.SearchFieldByName(evt.Value, "nodeInfo").(cadence.Array)
}

// FirstView returns firstView field in EpochRecover event.
func (evt EpochRecoverEvent) FirstView() cadence.UInt64 {
return cadence.SearchFieldByName(evt.Value, "firstView").(cadence.UInt64)
}

// FinalView returns finalView field in EpochRecover event.
func (evt EpochRecoverEvent) FinalView() cadence.UInt64 {
return cadence.SearchFieldByName(evt.Value, "finalView").(cadence.UInt64)
}

// CollectorClusters returns clusterAssignments field in EpochRecover event.
func (evt EpochRecoverEvent) CollectorClusters() cadence.Array {
return cadence.SearchFieldByName(evt.Value, "clusterAssignments").(cadence.Array)
}

// RandomSource returns randomSource field in EpochRecover event.
func (evt EpochRecoverEvent) RandomSource() cadence.String {
return cadence.SearchFieldByName(evt.Value, "randomSource").(cadence.String)
}

// DKGFinalViews returns dkgFinalViews field in EpochRecover event.
func (evt EpochRecoverEvent) DKGFinalViews() (cadence.UInt64, cadence.UInt64, cadence.UInt64) {
return cadence.SearchFieldByName(evt.Value, "DKGPhase1FinalView").(cadence.UInt64),
cadence.SearchFieldByName(evt.Value, "DKGPhase2FinalView").(cadence.UInt64),
cadence.SearchFieldByName(evt.Value, "DKGPhase3FinalView").(cadence.UInt64)
}

// TargetDuration returns targetDuration field in EpochRecover event.
func (evt EpochRecoverEvent) TargetDuration() cadence.UInt64 {
return cadence.SearchFieldByName(evt.Value, "targetDuration").(cadence.UInt64)
}

// TargetEndTime returns targetEndTime field in EpochRecover event.
func (evt EpochRecoverEvent) TargetEndTime() cadence.UInt64 {
return cadence.SearchFieldByName(evt.Value, "targetEndTime").(cadence.UInt64)
}

// ClusterQCVoteData returns clusterQCVoteData field in EpochRecover event.
func (evt EpochRecoverEvent) ClusterQCVoteData() cadence.Array {
return cadence.SearchFieldByName(evt.Value, "clusterQCVoteData").(cadence.Array)
}

// DKGPubKeys returns dkgPubKeys field in EpochRecover event.
func (evt EpochRecoverEvent) DKGPubKeys() cadence.Array {
return cadence.SearchFieldByName(evt.Value, "dkgPubKeys").(cadence.Array)
}

// Go event definitions for the epoch events
// Can be used with the SDK to retreive and parse epoch events

Expand Down Expand Up @@ -495,10 +573,9 @@ func verifyEpochMetadata(
env templates.Environment,
expectedMetadata EpochMetadata) {

result := executeScriptAndCheck(t, b, templates.GenerateGetEpochMetadataScript(env), [][]byte{jsoncdc.MustEncode(cadence.UInt64(expectedMetadata.counter))})
metadataFields := cadence.FieldsMappedByName(result.(cadence.Struct))

metadataFields := getEpochMetadata(t, b, env, cadence.UInt64(expectedMetadata.counter))
counter := metadataFields["counter"]

assertEqual(t, cadence.NewUInt64(expectedMetadata.counter), counter)

if len(expectedMetadata.seed) != 0 {
Expand Down Expand Up @@ -714,6 +791,15 @@ func verifyEpochCommit(

}

// verifyCollectorClusters verifies both collector clusters are equal.
func verifyCollectorClusters(t *testing.T, expected, got []cadence.Value) {
for i, cluster := range got {
for j, node := range cluster.(cadence.Array).Values {
assertEqual(t, expected[i].(cadence.Array).Values[j], node)
}
}
}

// expectedTargetEndTime returns the expected `targetEndTime` for the given target epoch,
// as a second-precision Unix time.
func expectedTargetEndTime(timingConfig cadence.Value, targetEpoch uint64) uint64 {
Expand All @@ -724,3 +810,98 @@ func expectedTargetEndTime(timingConfig cadence.Value, targetEpoch uint64) uint6

return refTimestamp + duration*(targetEpoch-refCounter)
}

// verifyEpochRecover verifies that an emitted EpochRecover event is equal to the provided `expectedRecover`.
// Assumptions:
// - only one `EpochRecover` is emitted (otherwise, only the first is verified)
// - the `EpochRecover` is emitted within the first 1000 blocks
func verifyEpochRecover(
t *testing.T,
adapter *adapters.SDKAdapter,
epochAddress flow.Address,
expectedRecover EpochRecover,
) {
var emittedEvent EpochRecoverEvent
addrLocation := common.NewAddressLocation(nil, common.Address(epochAddress), "FlowEpoch")
evtTypeID := string(addrLocation.TypeID(nil, "FlowEpoch.EpochRecover"))
for i := uint64(0); i < 1000; i++ {
results, _ := adapter.GetEventsForHeightRange(context.Background(), evtTypeID, i, i)

for _, result := range results {
for _, event := range result.Events {

if event.Type == evtTypeID {
emittedEvent = EpochRecoverEvent(event)
}
}
}
}

assertEqual(t, cadence.NewUInt64(expectedRecover.counter), emittedEvent.Counter())
assertEqual(t, expectedRecover.nodeInfoLength, len(emittedEvent.NodeInfo().Values))
assertEqual(t, cadence.NewUInt64(expectedRecover.firstView), emittedEvent.FirstView())
assertEqual(t, cadence.NewUInt64(expectedRecover.finalView), emittedEvent.FinalView())
verifyCollectorClusters(t, expectedRecover.collectorClusters, emittedEvent.CollectorClusters().Values)
assertEqual(t, cadence.String(expectedRecover.randomSource), emittedEvent.RandomSource())
dkgPhase1FinalView, dkgPhase2FinalView, dkgPhase3FinalView := emittedEvent.DKGFinalViews()
assertEqual(t, cadence.NewUInt64(expectedRecover.dkgPhase1FinalView), dkgPhase1FinalView)
assertEqual(t, cadence.NewUInt64(expectedRecover.dkgPhase2FinalView), dkgPhase2FinalView)
assertEqual(t, cadence.NewUInt64(expectedRecover.dkgPhase3FinalView), dkgPhase3FinalView)
assertEqual(t, cadence.NewUInt64(expectedRecover.targetDuration), emittedEvent.TargetDuration())
assertEqual(t, cadence.NewUInt64(expectedRecover.targetEndTime), emittedEvent.TargetEndTime())
assertEqual(t, expectedRecover.clusterQCVoteDataLength, len(emittedEvent.ClusterQCVoteData().Values))
assertEqual(t, len(expectedRecover.dkgPubKeys), len(emittedEvent.DKGPubKeys().Values))
}

func getEpochMetadata(t *testing.T, b emulator.Emulator, env templates.Environment, counter cadence.Value) map[string]cadence.Value {
result := executeScriptAndCheck(t, b, templates.GenerateGetEpochMetadataScript(env), [][]byte{jsoncdc.MustEncode(counter)})
return cadence.FieldsMappedByName(result.(cadence.Struct))
}

func getCurrentEpochCounter(t *testing.T, b emulator.Emulator, env templates.Environment) cadence.UInt64 {
result := executeScriptAndCheck(t, b, templates.GenerateGetCurrentEpochCounterScript(env), [][]byte{})
return result.(cadence.UInt64)
}

// newClusterQCVoteDataCdcType returns the FlowClusterQC cadence struct type.
func newClusterQCVoteDataCdcType(clusterQcAddress string) *cadence.StructType {

// FlowClusterQC.ClusterQCVoteData
address, _ := cdcCommon.HexToAddress(clusterQcAddress)
location := cdcCommon.NewAddressLocation(nil, address, "FlowClusterQC")

return &cadence.StructType{
Location: location,
QualifiedIdentifier: "FlowClusterQC.ClusterQCVoteData",
Fields: []cadence.Field{
{
Identifier: "aggregatedSignature",
Type: cadence.StringType,
},
{
Identifier: "voterIDs",
Type: cadence.NewVariableSizedArrayType(cadence.StringType),
},
},
}
}

func convertClusterQcsCdc(env templates.Environment, clusters []cadence.Value) []cadence.Value {
voteDataType := newClusterQCVoteDataCdcType(env.QuorumCertificateAddress)
qcVoteData := make([]cadence.Value, len(clusters))
for i, cluster := range clusters {
clusterCdc := cluster.(cadence.Array)
cdcVoterIds := make([]cadence.Value, len(clusterCdc.Values))
for i, id := range clusterCdc.Values {
cdcVoterIds[i] = cadence.String(id.String())
}
qcVoteData[i] = cadence.NewStruct([]cadence.Value{
// aggregatedSignature
cadence.String(fmt.Sprintf("signature_%d", i)),
// Node IDs of signers
cadence.NewArray(cdcVoterIds).WithType(cadence.NewVariableSizedArrayType(cadence.StringType)),
}).WithType(voteDataType)
}

return qcVoteData
}
Loading

0 comments on commit 3118243

Please sign in to comment.