mirror of
https://gitlab.com/pulsechaincom/prysm-pulse.git
synced 2024-12-27 05:38:55 +00:00
193 lines
6.0 KiB
Go
193 lines
6.0 KiB
Go
// Package proposer defines all relevant functionality for a Proposer actor
|
|
// within Ethereum 2.0.
|
|
package proposer
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"sync"
|
|
|
|
"github.com/golang/protobuf/proto"
|
|
"github.com/golang/protobuf/ptypes"
|
|
pbp2p "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1"
|
|
pb "github.com/prysmaticlabs/prysm/proto/beacon/rpc/v1"
|
|
"github.com/prysmaticlabs/prysm/shared/event"
|
|
"github.com/sirupsen/logrus"
|
|
blake2b "golang.org/x/crypto/blake2b"
|
|
)
|
|
|
|
var log = logrus.WithField("prefix", "proposer")
|
|
|
|
type rpcClientService interface {
|
|
ProposerServiceClient() pb.ProposerServiceClient
|
|
}
|
|
|
|
type beaconClientService interface {
|
|
ProposerAssignmentFeed() *event.Feed
|
|
}
|
|
|
|
type rpcAttestationService interface {
|
|
ProcessedAttestationFeed() *event.Feed
|
|
}
|
|
|
|
// Proposer holds functionality required to run a block proposer
|
|
// in Ethereum 2.0. Must satisfy the Service interface defined in
|
|
// sharding/service.go.
|
|
type Proposer struct {
|
|
ctx context.Context
|
|
cancel context.CancelFunc
|
|
beaconService beaconClientService
|
|
rpcClientService rpcClientService
|
|
assignmentChan chan *pbp2p.BeaconBlock
|
|
attestationService rpcAttestationService
|
|
attestationChan chan *pbp2p.AggregatedAttestation
|
|
pendingAttestation []*pbp2p.AggregatedAttestation
|
|
lock sync.Mutex
|
|
}
|
|
|
|
// Config options for proposer service.
|
|
type Config struct {
|
|
AssignmentBuf int
|
|
AttestationBufferSize int
|
|
Assigner beaconClientService
|
|
AttesterFeed rpcAttestationService
|
|
Client rpcClientService
|
|
}
|
|
|
|
// NewProposer creates a new attester instance.
|
|
func NewProposer(ctx context.Context, cfg *Config) *Proposer {
|
|
ctx, cancel := context.WithCancel(ctx)
|
|
return &Proposer{
|
|
ctx: ctx,
|
|
cancel: cancel,
|
|
beaconService: cfg.Assigner,
|
|
rpcClientService: cfg.Client,
|
|
attestationService: cfg.AttesterFeed,
|
|
assignmentChan: make(chan *pbp2p.BeaconBlock, cfg.AssignmentBuf),
|
|
attestationChan: make(chan *pbp2p.AggregatedAttestation, cfg.AttestationBufferSize),
|
|
pendingAttestation: make([]*pbp2p.AggregatedAttestation, 0),
|
|
lock: sync.Mutex{},
|
|
}
|
|
}
|
|
|
|
// Start the main routine for a proposer.
|
|
func (p *Proposer) Start() {
|
|
log.Info("Starting service")
|
|
client := p.rpcClientService.ProposerServiceClient()
|
|
|
|
go p.run(p.ctx.Done(), client)
|
|
go p.processAttestation(p.ctx.Done())
|
|
|
|
}
|
|
|
|
// Stop the main loop.
|
|
func (p *Proposer) Stop() error {
|
|
defer p.cancel()
|
|
log.Info("Stopping service")
|
|
return nil
|
|
}
|
|
|
|
// DoesAttestationExist checks if an attester has already attested to a block.
|
|
func (p *Proposer) DoesAttestationExist(attestation *pbp2p.AggregatedAttestation) bool {
|
|
exists := false
|
|
for _, record := range p.pendingAttestation {
|
|
if bytes.Equal(record.GetAttesterBitfield(), attestation.GetAttesterBitfield()) {
|
|
exists = true
|
|
break
|
|
}
|
|
}
|
|
return exists
|
|
}
|
|
|
|
// AddPendingAttestation adds a pending attestation to the memory so that it can be included
|
|
// in the next proposed block.
|
|
func (p *Proposer) AddPendingAttestation(attestation *pbp2p.AggregatedAttestation) {
|
|
p.pendingAttestation = append(p.pendingAttestation, attestation)
|
|
}
|
|
|
|
// AggregateAllSignatures aggregates all the signatures of the attesters. This is currently a
|
|
// stub for now till BLS/other signature schemes are implemented.
|
|
func (p *Proposer) AggregateAllSignatures(attestations []*pbp2p.AggregatedAttestation) []uint32 {
|
|
// TODO(#258): Implement Signature Aggregation.
|
|
return []uint32{}
|
|
}
|
|
|
|
// GenerateBitmask creates the attestation bitmask from all the attester bitfields in the
|
|
// attestation records.
|
|
func (p *Proposer) GenerateBitmask(attestations []*pbp2p.AggregatedAttestation) []byte {
|
|
// TODO(#258): Implement bitmask where all attesters bitfields are aggregated.
|
|
return []byte{}
|
|
}
|
|
|
|
// processAttestation processes incoming broadcasted attestations from the beacon node.
|
|
func (p *Proposer) processAttestation(done <-chan struct{}) {
|
|
attestationSub := p.attestationService.ProcessedAttestationFeed().Subscribe(p.attestationChan)
|
|
defer attestationSub.Unsubscribe()
|
|
|
|
for {
|
|
select {
|
|
case <-done:
|
|
log.Debug("Proposer context closed, exiting goroutine")
|
|
return
|
|
case attestationRecord := <-p.attestationChan:
|
|
attestationExists := p.DoesAttestationExist(attestationRecord)
|
|
if !attestationExists {
|
|
p.AddPendingAttestation(attestationRecord)
|
|
log.Info("Attestation stored in memory")
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
// run the main event loop that listens for a proposer assignment.
|
|
func (p *Proposer) run(done <-chan struct{}, client pb.ProposerServiceClient) {
|
|
sub := p.beaconService.ProposerAssignmentFeed().Subscribe(p.assignmentChan)
|
|
defer sub.Unsubscribe()
|
|
|
|
for {
|
|
select {
|
|
case <-done:
|
|
log.Debug("Proposer context closed, exiting goroutine")
|
|
return
|
|
// When we receive an assignment on a slot, we leverage the fields
|
|
// from the latest canonical beacon block to perform a proposal responsibility.
|
|
case latestBeaconBlock := <-p.assignmentChan:
|
|
log.Info("Performing proposer responsibility")
|
|
|
|
// Extract the hash of the latest beacon block to use as parent hash in
|
|
// the proposal.
|
|
data, err := proto.Marshal(latestBeaconBlock)
|
|
if err != nil {
|
|
log.Errorf("Could not marshal latest beacon block: %v", err)
|
|
continue
|
|
}
|
|
latestBlockHash := blake2b.Sum512(data)
|
|
|
|
// To prevent any unaccounted attestations from being added.
|
|
p.lock.Lock()
|
|
|
|
bitmask := p.GenerateBitmask(p.pendingAttestation)
|
|
|
|
// TODO(#552): Implement real proposals with randao reveals and attestation fields.
|
|
req := &pb.ProposeRequest{
|
|
ParentHash: latestBlockHash[:],
|
|
// TODO(#511): Fix to be the actual, timebased slot number instead.
|
|
SlotNumber: latestBeaconBlock.GetSlot() + 1,
|
|
RandaoReveal: []byte{},
|
|
AttestationBitmask: bitmask,
|
|
Timestamp: ptypes.TimestampNow(),
|
|
}
|
|
res, err := client.ProposeBlock(p.ctx, req)
|
|
if err != nil {
|
|
log.Errorf("Could not propose block: %v", err)
|
|
continue
|
|
}
|
|
|
|
log.Infof("Block proposed successfully with hash 0x%x", res.BlockHash)
|
|
p.pendingAttestation = nil
|
|
p.lock.Unlock()
|
|
}
|
|
}
|
|
}
|