prysm-pulse/validator/client/validator_attest.go
Ivan Martinez 23072983ff Create and Verify Signatures for Attestations (#1908)
* Verify signatures of attestations

* Implement BLS Signing for attestations

* Remove custody bit 0 from the attestation for now

* Fixes tests for attestations

* Fix tests to ensure they use proper attester indice

* Run gazelle

* Goimports

* Test attestation sigs in block operations

* Change formatting and make sure signatures are actually verified

* Fix duplicate import

* Fix duplicate import from merge

* Organize attestation sig to be conssitent with codebase

* Update to comments

* Change signatures to use aggregation

* Run gazelle

* Change function to return err instead of bool
Also gofmt

* Fix for comments

* Move createAggregationSignature to a function in attestations.go
2019-03-08 15:30:01 +08:00

156 lines
6.5 KiB
Go

package client
import (
"context"
"fmt"
"time"
ptypes "github.com/gogo/protobuf/types"
pbp2p "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1"
pb "github.com/prysmaticlabs/prysm/proto/beacon/rpc/v1"
"github.com/prysmaticlabs/prysm/shared/forkutils"
"github.com/prysmaticlabs/prysm/shared/hashutil"
"github.com/prysmaticlabs/prysm/shared/params"
"go.opencensus.io/trace"
)
var delay = params.BeaconConfig().SecondsPerSlot / 2
// AttestToBlockHead completes the validator client's attester responsibility at a given slot.
// It fetches the latest beacon block head along with the latest canonical beacon state
// information in order to sign the block and include information about the validator's
// participation in voting on the block.
func (v *validator) AttestToBlockHead(ctx context.Context, slot uint64) {
ctx, span := trace.StartSpan(ctx, "validator.AttestToBlockHead")
defer span.End()
log.Info("Attesting...")
// First the validator should construct attestation_data, an AttestationData
// object based upon the state at the assigned slot.
attData := &pbp2p.AttestationData{
Slot: slot,
CrosslinkDataRootHash32: params.BeaconConfig().ZeroHash[:], // Stub for Phase 0.
}
// We fetch the validator index as it is necessary to generate the aggregation
// bitfield of the attestation itself.
pubKey := v.key.PublicKey.Marshal()
idxReq := &pb.ValidatorIndexRequest{
PublicKey: pubKey,
}
validatorIndexRes, err := v.validatorClient.ValidatorIndex(ctx, idxReq)
if err != nil {
log.Errorf("Could not fetch validator index: %v", err)
return
}
req := &pb.ValidatorEpochAssignmentsRequest{
EpochStart: slot,
PublicKey: pubKey,
}
resp, err := v.validatorClient.CommitteeAssignment(ctx, req)
if err != nil {
log.Errorf("Could not fetch crosslink committees at slot %d: %v",
slot-params.BeaconConfig().GenesisSlot, err)
return
}
// Set the attestation data's shard as the shard associated with the validator's
// committee as retrieved by CrosslinkCommitteesAtSlot.
attData.Shard = resp.Shard
// Fetch other necessary information from the beacon node in order to attest
// including the justified epoch, epoch boundary information, and more.
infoReq := &pb.AttestationDataRequest{
Slot: slot,
Shard: resp.Shard,
}
infoRes, err := v.attesterClient.AttestationDataAtSlot(ctx, infoReq)
if err != nil {
log.Errorf("Could not fetch necessary info to produce attestation at slot %d: %v",
slot-params.BeaconConfig().GenesisSlot, err)
return
}
log.Infof("Attestation info response: %v", infoRes)
// Set the attestation data's beacon block root = hash_tree_root(head) where head
// is the validator's view of the head block of the beacon chain during the slot.
attData.BeaconBlockRootHash32 = infoRes.BeaconBlockRootHash32
// Set the attestation data's epoch boundary root = hash_tree_root(epoch_boundary)
// where epoch_boundary is the block at the most recent epoch boundary in the
// chain defined by head -- i.e. the BeaconBlock where block.slot == get_epoch_start_slot(slot_to_epoch(head.slot)).
attData.EpochBoundaryRootHash32 = infoRes.EpochBoundaryRootHash32
// Set the attestation data's latest crosslink root = state.latest_crosslinks[shard].shard_block_root
// where state is the beacon state at head and shard is the validator's assigned shard.
attData.LatestCrosslink = infoRes.LatestCrosslink
// Set the attestation data's justified epoch = state.justified_epoch where state
// is the beacon state at the head.
attData.JustifiedEpoch = infoRes.JustifiedEpoch
// Set the attestation data's justified block root = hash_tree_root(justified_block) where
// justified_block is the block at state.justified_epoch in the chain defined by head.
// On the server side, this is fetched by calling get_block_root(state, justified_epoch).
attData.JustifiedBlockRootHash32 = infoRes.JustifiedBlockRootHash32
// The validator now creates an Attestation object using the AttestationData as
// set in the code above after all properties have been set.
attestation := &pbp2p.Attestation{
Data: attData,
}
// We set the custody bitfield to an slice of zero values as a stub for phase 0
// of length len(committee)+7 // 8.
attestation.CustodyBitfield = make([]byte, (len(resp.Committee)+7)/8)
// We set the attestation's aggregation bitfield by determining the index in the committee
// corresponding to the validator and modifying the bitfield itself.
aggregationBitfield := make([]byte, (len(resp.Committee)+7)/8)
var indexIntoCommittee uint
for i, validator := range resp.Committee {
if validator == validatorIndexRes.Index {
indexIntoCommittee = uint(i)
break
}
}
if len(aggregationBitfield) == 0 {
log.Error("Aggregation bitfield is empty so unable to attest to block head")
return
}
aggregationBitfield[indexIntoCommittee/8] |= 1 << (indexIntoCommittee % 8)
// Note: calling get_attestation_participants(state, attestation.data, attestation.aggregation_bitfield)
// should return a list of length equal to 1, containing validator_index.
attestation.AggregationBitfield = aggregationBitfield
// Retrieve the current fork data from the beacon node.
fork, err := v.beaconClient.ForkData(ctx, &ptypes.Empty{})
if err != nil {
log.Errorf("Failed to get fork data from beacon node's state: %v", err)
return
}
epoch := slot / params.BeaconConfig().SlotsPerEpoch
attDataHash, err := hashutil.HashProto(&pbp2p.AttestationDataAndCustodyBit{
Data: attestation.Data,
CustodyBit: true,
})
if err != nil {
log.Errorf("Could not hash attestation data: %v", err)
return
}
log.Infof("Signing attestation for slot: %d", slot)
domain := forkutils.DomainVersion(fork, epoch, params.BeaconConfig().DomainAttestation)
aggregateSig := v.key.SecretKey.Sign(attDataHash[:], domain)
attestation.AggregateSignature = aggregateSig.Marshal()
log.Infof("Attestation signature: %#x", attestation.AggregateSignature)
duration := time.Duration(slot*params.BeaconConfig().SecondsPerSlot+delay) * time.Second
timeToBroadcast := time.Unix(int64(v.genesisTime), 0).Add(duration)
_, sleepSpan := trace.StartSpan(ctx, "validator.AttestToBlockHead_sleepUntilTimeToBroadcast")
time.Sleep(time.Until(timeToBroadcast))
sleepSpan.End()
log.Infof("Produced attestation: %v", attestation)
attestRes, err := v.attesterClient.AttestHead(ctx, attestation)
if err != nil {
log.Errorf("Could not submit attestation to beacon node: %v", err)
return
}
log.WithField(
"hash", fmt.Sprintf("%#x", attestRes.AttestationHash),
).Infof("Submitted attestation successfully with hash %#x", attestRes.AttestationHash)
}