2020-09-27 23:46:00 -07:00
// Copyright © 2020 Attestant Limited.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package standard
import (
2020-11-14 23:29:19 -08:00
"bytes"
2020-09-27 23:46:00 -07:00
"context"
"fmt"
"time"
eth2client "github.com/attestantio/go-eth2-client"
spec "github.com/attestantio/go-eth2-client/spec/phase0"
"github.com/attestantio/vouch/services/accountmanager"
"github.com/attestantio/vouch/services/attester"
"github.com/attestantio/vouch/services/metrics"
2020-11-24 16:02:13 -08:00
"github.com/attestantio/vouch/services/signer"
2020-09-27 23:46:00 -07:00
"github.com/attestantio/vouch/services/submitter"
"github.com/pkg/errors"
"github.com/prysmaticlabs/go-bitfield"
"github.com/rs/zerolog"
zerologger "github.com/rs/zerolog/log"
2020-11-24 16:02:13 -08:00
e2wtypes "github.com/wealdtech/go-eth2-wallet-types/v2"
2020-09-27 23:46:00 -07:00
)
// Service is a beacon block attester.
type Service struct {
monitor metrics . AttestationMonitor
processConcurrency int64
slotsPerEpoch uint64
validatingAccountsProvider accountmanager . ValidatingAccountsProvider
attestationDataProvider eth2client . AttestationDataProvider
2020-11-26 12:32:04 -08:00
attestationsSubmitter submitter . AttestationsSubmitter
2020-11-24 16:02:13 -08:00
beaconAttestationsSigner signer . BeaconAttestationsSigner
2020-09-27 23:46:00 -07:00
}
// module-wide log.
var log zerolog . Logger
// New creates a new beacon block attester.
func New ( ctx context . Context , params ... Parameter ) ( * Service , error ) {
parameters , err := parseAndCheckParameters ( params ... )
if err != nil {
return nil , errors . Wrap ( err , "problem with parameters" )
}
// Set logging.
log = zerologger . With ( ) . Str ( "service" , "attester" ) . Str ( "impl" , "standard" ) . Logger ( )
if parameters . logLevel != log . GetLevel ( ) {
log = log . Level ( parameters . logLevel )
}
slotsPerEpoch , err := parameters . slotsPerEpochProvider . SlotsPerEpoch ( ctx )
if err != nil {
return nil , errors . Wrap ( err , "failed to obtain slots per epoch" )
}
s := & Service {
monitor : parameters . monitor ,
processConcurrency : parameters . processConcurrency ,
slotsPerEpoch : slotsPerEpoch ,
validatingAccountsProvider : parameters . validatingAccountsProvider ,
attestationDataProvider : parameters . attestationDataProvider ,
2020-11-26 12:32:04 -08:00
attestationsSubmitter : parameters . attestationsSubmitter ,
2020-11-24 16:02:13 -08:00
beaconAttestationsSigner : parameters . beaconAttestationsSigner ,
2020-09-27 23:46:00 -07:00
}
return s , nil
}
// Attest carries out attestations for a slot.
// It returns a map of attestations made, keyed on the validator index.
func ( s * Service ) Attest ( ctx context . Context , data interface { } ) ( [ ] * spec . Attestation , error ) {
started := time . Now ( )
duty , ok := data . ( * attester . Duty )
if ! ok {
2020-12-16 06:05:11 -08:00
s . monitor . AttestationsCompleted ( started , len ( duty . ValidatorIndices ( ) ) , "failed" )
2020-09-27 23:46:00 -07:00
return nil , errors . New ( "passed invalid data structure" )
}
2020-11-14 23:29:19 -08:00
uints := make ( [ ] uint64 , len ( duty . ValidatorIndices ( ) ) )
for i := range duty . ValidatorIndices ( ) {
uints [ i ] = uint64 ( duty . ValidatorIndices ( ) [ i ] )
}
log := log . With ( ) . Uint64 ( "slot" , uint64 ( duty . Slot ( ) ) ) . Uints64 ( "validator_indices" , uints ) . Logger ( )
2020-09-27 23:46:00 -07:00
// Fetch the attestation data.
attestationData , err := s . attestationDataProvider . AttestationData ( ctx , duty . Slot ( ) , duty . CommitteeIndices ( ) [ 0 ] )
if err != nil {
2020-12-16 06:05:11 -08:00
s . monitor . AttestationsCompleted ( started , len ( duty . ValidatorIndices ( ) ) , "failed" )
2020-09-27 23:46:00 -07:00
return nil , errors . Wrap ( err , "failed to obtain attestation data" )
}
log . Trace ( ) . Dur ( "elapsed" , time . Since ( started ) ) . Msg ( "Obtained attestation data" )
if attestationData . Slot != duty . Slot ( ) {
2020-12-16 06:05:11 -08:00
s . monitor . AttestationsCompleted ( started , len ( duty . ValidatorIndices ( ) ) , "failed" )
2020-09-27 23:46:00 -07:00
return nil , fmt . Errorf ( "attestation request for slot %d returned data for slot %d" , duty . Slot ( ) , attestationData . Slot )
}
2020-10-26 07:31:24 -07:00
if attestationData . Source . Epoch > attestationData . Target . Epoch {
2020-12-16 06:05:11 -08:00
s . monitor . AttestationsCompleted ( started , len ( duty . ValidatorIndices ( ) ) , "failed" )
2020-10-26 07:31:24 -07:00
return nil , fmt . Errorf ( "attestation request for slot %d returned source epoch %d greater than target epoch %d" , duty . Slot ( ) , attestationData . Source . Epoch , attestationData . Target . Epoch )
}
2020-11-14 23:29:19 -08:00
if attestationData . Target . Epoch > spec . Epoch ( uint64 ( duty . Slot ( ) ) / s . slotsPerEpoch ) {
2020-12-16 06:05:11 -08:00
s . monitor . AttestationsCompleted ( started , len ( duty . ValidatorIndices ( ) ) , "failed" )
2020-11-14 23:29:19 -08:00
return nil , fmt . Errorf ( "attestation request for slot %d returned target epoch %d greater than current epoch %d" , duty . Slot ( ) , attestationData . Target . Epoch , spec . Epoch ( uint64 ( duty . Slot ( ) ) / s . slotsPerEpoch ) )
2020-10-26 07:31:24 -07:00
}
2020-09-27 23:46:00 -07:00
// Fetch the validating accounts.
2020-11-24 16:02:13 -08:00
validatingAccounts , err := s . validatingAccountsProvider . ValidatingAccountsForEpochByIndex ( ctx , spec . Epoch ( uint64 ( duty . Slot ( ) ) / s . slotsPerEpoch ) , duty . ValidatorIndices ( ) )
2020-09-27 23:46:00 -07:00
if err != nil {
2020-12-16 06:05:11 -08:00
s . monitor . AttestationsCompleted ( started , len ( duty . ValidatorIndices ( ) ) , "failed" )
2020-09-27 23:46:00 -07:00
return nil , errors . New ( "failed to obtain attesting validator accounts" )
}
2021-01-31 05:49:15 -08:00
log . Trace ( ) . Dur ( "elapsed" , time . Since ( started ) ) . Int ( "validating_accounts" , len ( validatingAccounts ) ) . Msg ( "Obtained validating accounts" )
2020-09-27 23:46:00 -07:00
2020-11-24 16:02:13 -08:00
// Break the map in to two arrays.
accountValidatorIndices := make ( [ ] spec . ValidatorIndex , 0 , len ( validatingAccounts ) )
accountsArray := make ( [ ] e2wtypes . Account , 0 , len ( validatingAccounts ) )
for index , account := range validatingAccounts {
accountValidatorIndices = append ( accountValidatorIndices , index )
accountsArray = append ( accountsArray , account )
}
2020-10-26 07:31:24 -07:00
// Set the per-validator information.
2020-11-14 00:36:49 -08:00
validatorIndexToArrayIndexMap := make ( map [ spec . ValidatorIndex ] int )
2020-09-27 23:46:00 -07:00
for i := range duty . ValidatorIndices ( ) {
validatorIndexToArrayIndexMap [ duty . ValidatorIndices ( ) [ i ] ] = i
}
2020-11-24 16:02:13 -08:00
committeeIndices := make ( [ ] spec . CommitteeIndex , len ( validatingAccounts ) )
validatorCommitteeIndices := make ( [ ] spec . ValidatorIndex , len ( validatingAccounts ) )
committeeSizes := make ( [ ] uint64 , len ( validatingAccounts ) )
for i := range accountsArray {
committeeIndices [ i ] = duty . CommitteeIndices ( ) [ validatorIndexToArrayIndexMap [ accountValidatorIndices [ i ] ] ]
validatorCommitteeIndices [ i ] = spec . ValidatorIndex ( duty . ValidatorCommitteeIndices ( ) [ validatorIndexToArrayIndexMap [ accountValidatorIndices [ i ] ] ] )
2020-10-26 07:31:24 -07:00
committeeSizes [ i ] = duty . CommitteeSize ( committeeIndices [ i ] )
}
2020-09-27 23:46:00 -07:00
2020-10-26 07:31:24 -07:00
attestations , err := s . attest ( ctx ,
2020-11-14 23:29:19 -08:00
duty . Slot ( ) ,
2020-10-26 07:31:24 -07:00
duty ,
2020-11-24 16:02:13 -08:00
accountsArray ,
accountValidatorIndices ,
2020-10-26 07:31:24 -07:00
committeeIndices ,
validatorCommitteeIndices ,
committeeSizes ,
attestationData ,
started ,
)
if err != nil {
log . Error ( ) . Err ( err ) . Msg ( "Failed to attest" )
2020-12-16 06:05:11 -08:00
s . monitor . AttestationsCompleted ( started , len ( duty . ValidatorIndices ( ) ) , "failed" )
2020-09-27 23:46:00 -07:00
}
return attestations , nil
}
func ( s * Service ) attest (
ctx context . Context ,
2020-11-14 00:36:49 -08:00
slot spec . Slot ,
2020-10-26 07:31:24 -07:00
duty * attester . Duty ,
2020-11-24 16:02:13 -08:00
accounts [ ] e2wtypes . Account ,
validatorIndices [ ] spec . ValidatorIndex ,
2020-11-14 23:29:19 -08:00
committeeIndices [ ] spec . CommitteeIndex ,
validatorCommitteeIndices [ ] spec . ValidatorIndex ,
2020-10-26 07:31:24 -07:00
committeeSizes [ ] uint64 ,
data * spec . AttestationData ,
started time . Time ,
) ( [ ] * spec . Attestation , error ) {
2020-11-24 16:02:13 -08:00
// Sign the attestation for all validating accounts.
2020-11-14 23:29:19 -08:00
uintCommitteeIndices := make ( [ ] uint64 , len ( committeeIndices ) )
for i := range committeeIndices {
uintCommitteeIndices [ i ] = uint64 ( committeeIndices [ i ] )
}
2020-11-24 16:02:13 -08:00
accountsArray := make ( [ ] e2wtypes . Account , 0 , len ( accounts ) )
accountsArray = append ( accountsArray , accounts ... )
sigs , err := s . beaconAttestationsSigner . SignBeaconAttestations ( ctx ,
accountsArray ,
2020-10-26 07:31:24 -07:00
duty . Slot ( ) ,
committeeIndices ,
data . BeaconBlockRoot ,
data . Source . Epoch ,
data . Source . Root ,
data . Target . Epoch ,
2020-11-14 23:29:19 -08:00
data . Target . Root ,
)
2020-09-27 23:46:00 -07:00
if err != nil {
2021-01-31 05:49:15 -08:00
s . monitor . AttestationsCompleted ( started , len ( duty . ValidatorIndices ( ) ) , "failed" )
2020-10-26 07:31:24 -07:00
return nil , errors . Wrap ( err , "failed to sign beacon attestations" )
2020-09-27 23:46:00 -07:00
}
2020-11-14 23:29:19 -08:00
log . Trace ( ) . Dur ( "elapsed" , time . Since ( started ) ) . Msg ( "Signed" )
2020-09-27 23:46:00 -07:00
2020-11-26 12:32:04 -08:00
// Create the attestations.
2020-11-14 23:29:19 -08:00
zeroSig := spec . BLSSignature { }
2020-11-26 12:32:04 -08:00
attestations := make ( [ ] * spec . Attestation , 0 , len ( sigs ) )
for i := range sigs {
if bytes . Equal ( sigs [ i ] [ : ] , zeroSig [ : ] ) {
log . Warn ( ) . Msg ( "No signature for validator; not creating attestation" )
continue
}
aggregationBits := bitfield . NewBitlist ( committeeSizes [ i ] )
aggregationBits . SetBitAt ( uint64 ( validatorCommitteeIndices [ i ] ) , true )
attestation := & spec . Attestation {
AggregationBits : aggregationBits ,
Data : & spec . AttestationData {
Slot : duty . Slot ( ) ,
Index : committeeIndices [ i ] ,
BeaconBlockRoot : data . BeaconBlockRoot ,
Source : & spec . Checkpoint {
Epoch : data . Source . Epoch ,
Root : data . Source . Root ,
2020-10-26 07:31:24 -07:00
} ,
2020-11-26 12:32:04 -08:00
Target : & spec . Checkpoint {
Epoch : data . Target . Epoch ,
Root : data . Target . Root ,
} ,
} ,
2020-10-26 07:31:24 -07:00
}
2020-11-26 12:32:04 -08:00
copy ( attestation . Signature [ : ] , sigs [ i ] [ : ] )
attestations = append ( attestations , attestation )
}
2021-01-31 05:49:15 -08:00
if len ( attestations ) == 0 {
log . Info ( ) . Msg ( "No signed attestations; not submitting" )
return attestations , nil
}
2020-11-26 12:32:04 -08:00
// Submit the attestations.
if err := s . attestationsSubmitter . SubmitAttestations ( ctx , attestations ) ; err != nil {
2021-01-31 05:49:15 -08:00
s . monitor . AttestationsCompleted ( started , len ( attestations ) , "failed" )
return nil , errors . Wrap ( err , "failed to submit attestations" )
2020-10-26 07:31:24 -07:00
}
2020-11-26 12:32:04 -08:00
log . Trace ( ) . Dur ( "elapsed" , time . Since ( started ) ) . Msg ( "Submitted attestations" )
2020-12-16 06:05:11 -08:00
s . monitor . AttestationsCompleted ( started , len ( duty . ValidatorIndices ( ) ) , "succeeded" )
2020-10-26 07:31:24 -07:00
return attestations , nil
2020-09-27 23:46:00 -07:00
}