Compare commits

...

4 Commits

Author SHA1 Message Date
Raul Jordan
2cb814648a Improve Slashing Protection for V1, More Tests and Observability (#7934)
* tests tests and more tests

* tests all passsss

* log for double vote
2020-11-23 19:03:04 -06:00
Raul Jordan
dc897a2007 Optionally Save Wallet Password on Web Onboarding (#7930)
* persist wallet password to wallet dir if onboarded via web

* add flag

* gaz

* add test

* fmt

Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
2020-11-23 22:11:42 +00:00
terence tsao
a051e684ae Update log levels (#7931) 2020-11-23 13:16:08 -08:00
Radosław Kapka
64be627a6d Make grpc-headers flag work (#7932) 2020-11-23 20:38:32 +00:00
21 changed files with 760 additions and 152 deletions

View File

@@ -265,7 +265,7 @@ func (bs *Server) StreamIndexedAttestations(
}
if data.Attestation == nil || data.Attestation.Aggregate == nil {
// One nil attestation shouldn't stop the stream.
log.Info("Indexed attestations stream got nil attestation or nil attestation aggregate")
log.Debug("Indexed attestations stream got nil attestation or nil attestation aggregate")
continue
}
bs.ReceivedAttestationsBuffer <- data.Attestation.Aggregate
@@ -340,7 +340,7 @@ func (bs *Server) collectReceivedAttestations(ctx context.Context) {
// We aggregate the received attestations, we know they all have the same data root.
aggAtts, err := attaggregation.Aggregate(atts)
if err != nil {
log.WithError(err).Error("Could not aggregate collected attestations")
log.WithError(err).Error("Could not aggregate attestations")
continue
}
if len(aggAtts) == 0 {
@@ -356,7 +356,7 @@ func (bs *Server) collectReceivedAttestations(ctx context.Context) {
case att := <-bs.ReceivedAttestationsBuffer:
attDataRoot, err := att.Data.HashTreeRoot()
if err != nil {
log.Errorf("Could not hash tree root data: %v", err)
log.Errorf("Could not hash tree root attestation data: %v", err)
continue
}
attsByRoot[attDataRoot] = append(attsByRoot[attDataRoot], att)

View File

@@ -38,7 +38,7 @@ func (bs *Server) ListBlocks(
case *ethpb.ListBlocksRequest_Epoch:
blks, _, err := bs.BeaconDB.Blocks(ctx, filters.NewFilter().SetStartEpoch(q.Epoch).SetEndEpoch(q.Epoch))
if err != nil {
return nil, status.Errorf(codes.Internal, "Failed to get blocks: %v", err)
return nil, status.Errorf(codes.Internal, "Could not get blocks: %v", err)
}
numBlks := len(blks)
@@ -194,12 +194,12 @@ func (bs *Server) StreamBlocks(_ *ptypes.Empty, stream ethpb.BeaconChain_StreamB
}
headState, err := bs.HeadFetcher.HeadState(bs.Ctx)
if err != nil {
log.WithError(err).WithField("blockSlot", data.SignedBlock.Block.Slot).Warn("Could not get head state to verify block signature")
log.WithError(err).WithField("blockSlot", data.SignedBlock.Block.Slot).Error("Could not get head state")
continue
}
if err := blocks.VerifyBlockSignature(headState, data.SignedBlock); err != nil {
log.WithError(err).WithField("blockSlot", data.SignedBlock.Block.Slot).Warn("Could not verify block signature")
log.WithError(err).WithField("blockSlot", data.SignedBlock.Block.Slot).Error("Could not verify block signature")
continue
}
if err := stream.Send(data.SignedBlock); err != nil {

View File

@@ -358,7 +358,7 @@ func (is *infostream) generatePendingValidatorInfo(info *ethpb.ValidatorInfo) (*
if deposit.block != nil {
info.Status = ethpb.ValidatorStatus_DEPOSITED
if queueTimestamp, err := is.depositQueueTimestamp(deposit.block); err != nil {
log.WithError(err).Error("Failed to obtain queue activation timestamp")
log.WithError(err).Error("Could not obtain queue activation timestamp")
} else {
info.TransitionTimestamp = queueTimestamp
}
@@ -415,7 +415,7 @@ func (is *infostream) calculateActivationTimeForPendingValidators(res []*ethpb.V
for curEpoch := epoch + 1; len(sortedIndices) > 0 && len(pendingValidators) > 0; curEpoch++ {
toProcess, err := helpers.ValidatorChurnLimit(numAttestingValidators)
if err != nil {
log.WithError(err).Error("Failed to determine validator churn limit")
log.WithError(err).Error("Could not determine validator churn limit")
}
if toProcess > uint64(len(sortedIndices)) {
toProcess = uint64(len(sortedIndices))
@@ -456,7 +456,7 @@ func (is *infostream) handleBlockProcessed() {
is.currentEpoch = blockEpoch
if err := is.sendValidatorsInfo(is.pubKeys); err != nil {
// Client probably disconnected.
log.WithError(err).Debug("Failed to send infostream response")
log.WithError(err).Debug("Could not send infostream response")
}
}

View File

@@ -64,7 +64,7 @@ func (vs *Server) GetAttestationData(ctx context.Context, req *ethpb.Attestation
}
defer func() {
if err := vs.AttestationCache.MarkNotInProgress(req); err != nil {
log.WithError(err).Error("Failed to mark cache not in progress")
log.WithError(err).Error("Could not mark cache not in progress")
}
}()
@@ -89,7 +89,7 @@ func (vs *Server) GetAttestationData(ctx context.Context, req *ethpb.Attestation
}
}
if headState == nil {
return nil, status.Error(codes.Internal, "Failed to lookup parent state from head.")
return nil, status.Error(codes.Internal, "Could not lookup parent state from head.")
}
if helpers.CurrentEpoch(headState) < helpers.SlotToEpoch(req.Slot) {

View File

@@ -192,12 +192,12 @@ func (vs *Server) eth1Data(ctx context.Context, slot uint64) (*ethpb.Eth1Data, e
// Look up most recent block up to timestamp
blockNumber, err := vs.Eth1BlockFetcher.BlockNumberByTimestamp(ctx, eth1VotingPeriodStartTime)
if err != nil {
log.WithError(err).Error("Failed to get block number from timestamp")
log.WithError(err).Error("Could not get block number from timestamp")
return vs.randomETH1DataVote(ctx)
}
eth1Data, err := vs.defaultEth1DataResponse(ctx, blockNumber)
if err != nil {
log.WithError(err).Error("Failed to get eth1 data from block number")
log.WithError(err).Error("Could not get eth1 data from block number")
return vs.randomETH1DataVote(ctx)
}
@@ -237,12 +237,12 @@ func (vs *Server) eth1DataMajorityVote(ctx context.Context, beaconState *stateTr
lastBlockByEarliestValidTime, err := vs.Eth1BlockFetcher.BlockNumberByTimestamp(ctx, earliestValidTime)
if err != nil {
log.WithError(err).Error("Failed to get last block by earliest valid time")
log.WithError(err).Error("Could not get last block by earliest valid time")
return vs.randomETH1DataVote(ctx)
}
timeOfLastBlockByEarliestValidTime, err := vs.Eth1BlockFetcher.BlockTimeByHeight(ctx, lastBlockByEarliestValidTime)
if err != nil {
log.WithError(err).Error("Failed to get time of last block by earliest valid time")
log.WithError(err).Error("Could not get time of last block by earliest valid time")
return vs.randomETH1DataVote(ctx)
}
// Increment the earliest block if the original block's time is before valid time.
@@ -253,12 +253,12 @@ func (vs *Server) eth1DataMajorityVote(ctx context.Context, beaconState *stateTr
lastBlockByLatestValidTime, err := vs.Eth1BlockFetcher.BlockNumberByTimestamp(ctx, latestValidTime)
if err != nil {
log.WithError(err).Error("Failed to get last block by latest valid time")
log.WithError(err).Error("Could not get last block by latest valid time")
return vs.randomETH1DataVote(ctx)
}
timeOfLastBlockByLatestValidTime, err := vs.Eth1BlockFetcher.BlockTimeByHeight(ctx, lastBlockByLatestValidTime)
if err != nil {
log.WithError(err).Error("Failed to get time of last block by latest valid time")
log.WithError(err).Error("Could not get time of last block by latest valid time")
return vs.randomETH1DataVote(ctx)
}
if timeOfLastBlockByLatestValidTime < earliestValidTime {
@@ -278,7 +278,7 @@ func (vs *Server) eth1DataMajorityVote(ctx context.Context, beaconState *stateTr
if lastBlockDepositCount >= vs.HeadFetcher.HeadETH1Data().DepositCount {
hash, err := vs.Eth1BlockFetcher.BlockHashByHeight(ctx, lastBlockByLatestValidTime)
if err != nil {
log.WithError(err).Error("Failed to get hash of last block by latest valid time")
log.WithError(err).Error("Could not get hash of last block by latest valid time")
return vs.randomETH1DataVote(ctx)
}
return &ethpb.Eth1Data{
@@ -507,7 +507,7 @@ func (vs *Server) canonicalEth1Data(
// Add in current vote, to get accurate vote tally
if err := beaconState.AppendEth1DataVotes(currentVote); err != nil {
return nil, nil, errors.Wrap(err, "failed to append eth1 data votes to state")
return nil, nil, errors.Wrap(err, "could not append eth1 data votes to state")
}
hasSupport, err := blocks.Eth1DataHasEnoughSupport(beaconState, currentVote)
if err != nil {

View File

@@ -36,18 +36,19 @@ type Flags struct {
PyrmontTestnet bool // PyrmontTestnet defines the flag through which we can enable the node to run on the Pyrmont testnet.
// Feature related flags.
WriteSSZStateTransitions bool // WriteSSZStateTransitions to tmp directory.
SkipBLSVerify bool // Skips BLS verification across the runtime.
EnableBlst bool // Enables new BLS library from supranational.
PruneEpochBoundaryStates bool // PruneEpochBoundaryStates prunes the epoch boundary state before last finalized check point.
EnableSnappyDBCompression bool // EnableSnappyDBCompression in the database.
SlasherProtection bool // SlasherProtection protects validator fron sending over a slashable offense over the network using external slasher.
EnableNoise bool // EnableNoise enables the beacon node to use NOISE instead of SECIO when performing a handshake with another peer.
EnableEth1DataMajorityVote bool // EnableEth1DataMajorityVote uses the Voting With The Majority algorithm to vote for eth1data.
EnablePeerScorer bool // EnablePeerScorer enables experimental peer scoring in p2p.
EnablePruningDepositProofs bool // EnablePruningDepositProofs enables pruning deposit proofs which significantly reduces the size of a deposit
EnableSyncBacktracking bool // EnableSyncBacktracking enables backtracking algorithm when searching for alternative forks during initial sync.
EnableLargerGossipHistory bool // EnableLargerGossipHistory increases the gossip history we store in our caches.
WriteSSZStateTransitions bool // WriteSSZStateTransitions to tmp directory.
SkipBLSVerify bool // Skips BLS verification across the runtime.
EnableBlst bool // Enables new BLS library from supranational.
PruneEpochBoundaryStates bool // PruneEpochBoundaryStates prunes the epoch boundary state before last finalized check point.
EnableSnappyDBCompression bool // EnableSnappyDBCompression in the database.
SlasherProtection bool // SlasherProtection protects validator fron sending over a slashable offense over the network using external slasher.
EnableNoise bool // EnableNoise enables the beacon node to use NOISE instead of SECIO when performing a handshake with another peer.
EnableEth1DataMajorityVote bool // EnableEth1DataMajorityVote uses the Voting With The Majority algorithm to vote for eth1data.
EnablePeerScorer bool // EnablePeerScorer enables experimental peer scoring in p2p.
EnablePruningDepositProofs bool // EnablePruningDepositProofs enables pruning deposit proofs which significantly reduces the size of a deposit
EnableSyncBacktracking bool // EnableSyncBacktracking enables backtracking algorithm when searching for alternative forks during initial sync.
EnableLargerGossipHistory bool // EnableLargerGossipHistory increases the gossip history we store in our caches.
WriteWalletPasswordOnWebOnboarding bool // WriteWalletPasswordOnWebOnboarding writes the password to disk after Prysm web signup.
// Logging related toggles.
DisableGRPCConnectionLogs bool // Disables logging when a new grpc client has connected.
@@ -206,6 +207,11 @@ func ConfigureValidator(ctx *cli.Context) {
log.Warn("Enabled validator attestation and block slashing protection using an external slasher.")
cfg.SlasherProtection = true
}
if ctx.Bool(writeWalletPasswordOnWebOnboarding.Name) {
log.Warn("Enabled full web mode, wallet password will be written to disk at the wallet directory " +
"upon completing web onboarding.")
cfg.WriteWalletPasswordOnWebOnboarding = true
}
Init(cfg)
}

View File

@@ -84,6 +84,11 @@ var (
Name: "enable-larger-gossip-history",
Usage: "Enables the node to store a larger amount of gossip messages in its cache.",
}
writeWalletPasswordOnWebOnboarding = &cli.BoolFlag{
Name: "write-wallet-password-on-web-onboarding",
Usage: "(Danger): Writes the wallet password to the wallet directory on completing Prysm web onboarding. " +
"We recommend against this flag unless you are an advanced user.",
}
)
// devModeFlags holds list of flags that are set when development mode is on.
@@ -94,6 +99,7 @@ var devModeFlags = []cli.Flag{
// ValidatorFlags contains a list of all the feature flags that apply to the validator client.
var ValidatorFlags = append(deprecatedFlags, []cli.Flag{
writeWalletPasswordOnWebOnboarding,
enableExternalSlasherProtectionFlag,
ToledoTestnet,
PyrmontTestnet,

View File

@@ -173,7 +173,6 @@ func prepareClients(cliCtx *cli.Context) (*ethpb.BeaconNodeValidatorClient, *eth
dialOpts := client.ConstructDialOptions(
cliCtx.Int(cmd.GrpcMaxCallRecvMsgSizeFlag.Name),
cliCtx.String(flags.CertFlag.Name),
strings.Split(cliCtx.String(flags.GrpcHeadersFlag.Name), ","),
cliCtx.Uint(flags.GrpcRetriesFlag.Name),
cliCtx.Duration(flags.GrpcRetryDelayFlag.Name),
)

View File

@@ -33,7 +33,9 @@ const (
WalletPasswordPromptText = "Wallet password"
// ConfirmPasswordPromptText for confirming a wallet password.
ConfirmPasswordPromptText = "Confirm password"
hashCost = 8
// DefaultWalletPasswordFile used to store a wallet password with appropriate permissions
// if a user signs up via the Prysm web UI via RPC.
DefaultWalletPasswordFile = "walletpassword.txt"
// CheckExistsErrMsg for when there is an error while checking for a wallet
CheckExistsErrMsg = "could not check if wallet exists"
// CheckValidityErrMsg for when there is an error while checking wallet validity

View File

@@ -64,10 +64,10 @@ func (v *validator) SubmitAttestation(ctx context.Context, slot uint64, pubKey [
Data: data,
}
if err := v.preAttSignValidations(ctx, indexedAtt, pubKey); err != nil {
log.WithFields(logrus.Fields{
"sourceEpoch": indexedAtt.Data.Source.Epoch,
"targetEpoch": indexedAtt.Data.Target.Epoch,
}).WithError(err).Error("Failed attestation safety check")
log.WithError(err).Error("Failed attestation slashing protection check")
log.WithFields(
attestationLogFields(pubKey, indexedAtt),
).Debug("Attempted slashable attestation details")
return
}
@@ -107,10 +107,10 @@ func (v *validator) SubmitAttestation(ctx context.Context, slot uint64, pubKey [
indexedAtt.Signature = sig
if err := v.postAttSignUpdate(ctx, indexedAtt, pubKey, signingRoot); err != nil {
log.WithFields(logrus.Fields{
"sourceEpoch": indexedAtt.Data.Source.Epoch,
"targetEpoch": indexedAtt.Data.Target.Epoch,
}).WithError(err).Error("Failed post attestation signing updates")
log.WithError(err).Error("Failed attestation slashing protection check")
log.WithFields(
attestationLogFields(pubKey, indexedAtt),
).Debug("Attempted slashable attestation details")
return
}
@@ -227,3 +227,17 @@ func (v *validator) waitToSlotOneThird(ctx context.Context, slot uint64) {
finalTime := startTime.Add(delay)
time.Sleep(timeutils.Until(finalTime))
}
func attestationLogFields(pubKey [48]byte, indexedAtt *ethpb.IndexedAttestation) logrus.Fields {
return logrus.Fields{
"attesterPublicKey": fmt.Sprintf("%#x", pubKey),
"attestationSlot": indexedAtt.Data.Slot,
"committeeIndex": indexedAtt.Data.CommitteeIndex,
"beaconBlockRoot": fmt.Sprintf("%#x", indexedAtt.Data.BeaconBlockRoot),
"sourceEpoch": indexedAtt.Data.Source.Epoch,
"sourceRoot": fmt.Sprintf("%#x", indexedAtt.Data.Source.Root),
"targetEpoch": indexedAtt.Data.Target.Epoch,
"targetRoot": fmt.Sprintf("%#x", indexedAtt.Data.Target.Root),
"signature": fmt.Sprintf("%#x", indexedAtt.Signature),
}
}

View File

@@ -10,6 +10,7 @@ import (
"github.com/prysmaticlabs/prysm/shared/featureconfig"
"github.com/prysmaticlabs/prysm/shared/params"
"github.com/prysmaticlabs/prysm/validator/db/kv"
"github.com/sirupsen/logrus"
)
var failedAttLocalProtectionErr = "attempted to make slashable attestation, rejected by local slashing protection"
@@ -27,12 +28,24 @@ func (v *validator) preAttSignValidations(ctx context.Context, indexedAtt *ethpb
log.WithError(err).Error("Could not get domain and signing root from attestation")
return err
}
if ok && isNewAttSlashable(ctx, attesterHistory, indexedAtt.Data.Source.Epoch, indexedAtt.Data.Target.Epoch, sr) {
if v.emitAccountMetrics {
ValidatorAttestFailVec.WithLabelValues(fmtKey).Inc()
if ok {
slashable, err := isNewAttSlashable(
ctx,
attesterHistory,
indexedAtt.Data.Source.Epoch,
indexedAtt.Data.Target.Epoch,
sr,
)
if err != nil {
return errors.Wrap(err, "could not check if attestation is slashable")
}
return errors.New(failedAttLocalProtectionErr)
} else if !ok {
if slashable {
if v.emitAccountMetrics {
ValidatorAttestFailVec.WithLabelValues(fmtKey).Inc()
}
return errors.New(failedAttLocalProtectionErr)
}
} else {
log.WithField("publicKey", fmtKey).Debug("Could not get local slashing protection data for validator in pre validation")
}
@@ -53,14 +66,35 @@ func (v *validator) postAttSignUpdate(ctx context.Context, indexedAtt *ethpb.Ind
defer v.attesterHistoryByPubKeyLock.Unlock()
attesterHistory, ok := v.attesterHistoryByPubKey[pubKey]
if ok {
if isNewAttSlashable(ctx, attesterHistory, indexedAtt.Data.Source.Epoch, indexedAtt.Data.Target.Epoch, signingRoot) {
slashable, err := isNewAttSlashable(
ctx,
attesterHistory,
indexedAtt.Data.Source.Epoch,
indexedAtt.Data.Target.Epoch,
signingRoot,
)
if err != nil {
return errors.Wrap(err, "could not check if attestation is slashable")
}
if slashable {
if v.emitAccountMetrics {
ValidatorAttestFailVec.WithLabelValues(fmtKey).Inc()
}
return errors.New(failedAttLocalProtectionErr)
}
attesterHistory = markAttestationForTargetEpoch(ctx, attesterHistory, indexedAtt.Data.Source.Epoch, indexedAtt.Data.Target.Epoch, signingRoot)
v.attesterHistoryByPubKey[pubKey] = attesterHistory
newHistory, err := kv.MarkAllAsAttestedSinceLatestWrittenEpoch(
ctx,
attesterHistory,
indexedAtt.Data.Target.Epoch,
&kv.HistoryData{
Source: indexedAtt.Data.Source.Epoch,
SigningRoot: signingRoot[:],
},
)
if err != nil {
return errors.Wrapf(err, "could not mark epoch %d as attested", indexedAtt.Data.Target.Epoch)
}
v.attesterHistoryByPubKey[pubKey] = newHistory
} else {
log.WithField("publicKey", fmtKey).Debug("Could not get local slashing protection data for validator in post validation")
}
@@ -78,114 +112,139 @@ func (v *validator) postAttSignUpdate(ctx context.Context, indexedAtt *ethpb.Ind
// isNewAttSlashable uses the attestation history to determine if an attestation of sourceEpoch
// and targetEpoch would be slashable. It can detect double, surrounding, and surrounded votes.
func isNewAttSlashable(ctx context.Context, history kv.EncHistoryData, sourceEpoch, targetEpoch uint64, signingRoot [32]byte) bool {
func isNewAttSlashable(
ctx context.Context,
history kv.EncHistoryData,
sourceEpoch,
targetEpoch uint64,
signingRoot [32]byte,
) (bool, error) {
if history == nil {
return false
return false, nil
}
wsPeriod := params.BeaconConfig().WeakSubjectivityPeriod
// Previously pruned, we should return false.
latestEpochWritten, err := history.GetLatestEpochWritten(ctx)
if err != nil {
log.WithError(err).Error("Could not get latest epoch written from encapsulated data")
return false
return false, err
}
if latestEpochWritten >= wsPeriod && targetEpoch <= latestEpochWritten-wsPeriod { //Underflow protected older then weak subjectivity check.
return false
return false, nil
}
// Check if there has already been a vote for this target epoch.
hd, err := history.GetTargetData(ctx, targetEpoch)
if err != nil {
log.WithError(err).Errorf("Could not get target data for target epoch: %d", targetEpoch)
return false
return false, errors.Wrapf(err, "could not get target data for epoch: %d", targetEpoch)
}
if !hd.IsEmpty() && !bytes.Equal(signingRoot[:], hd.SigningRoot) {
return true
log.WithFields(logrus.Fields{
"signingRoot": fmt.Sprintf("%#x", signingRoot),
"targetEpoch": targetEpoch,
"previouslyAttestedSigningRoot": fmt.Sprintf("%#x", hd.SigningRoot),
}).Warn("Attempted to submit a double vote, but blocked by slashing protection")
return true, nil
}
// Check if the new attestation would be surrounding another attestation.
isSurround, err := isSurroundVote(ctx, history, latestEpochWritten, sourceEpoch, targetEpoch)
if err != nil {
return false, errors.Wrap(err, "could not check if attestation is surround vote")
}
return isSurround, nil
}
func isSurroundVote(
ctx context.Context,
history kv.EncHistoryData,
latestEpochWritten,
sourceEpoch,
targetEpoch uint64,
) (bool, error) {
for i := sourceEpoch; i <= targetEpoch; i++ {
// Unattested for epochs are marked as (*kv.HistoryData)(nil).
historyBoundary := safeTargetToSource(ctx, history, i)
if historyBoundary.IsEmpty() {
historicalAtt, err := checkHistoryAtTargetEpoch(ctx, history, latestEpochWritten, i)
if err != nil {
return false, errors.Wrapf(err, "could not check historical attestation at target epoch: %d", i)
}
if historicalAtt.IsEmpty() {
continue
}
if historyBoundary.Source > sourceEpoch {
return true
prevTarget := i
prevSource := historicalAtt.Source
if surroundingPrevAttestation(prevSource, prevTarget, sourceEpoch, targetEpoch) {
// Surrounding attestation caught.
log.WithFields(logrus.Fields{
"targetEpoch": targetEpoch,
"sourceEpoch": sourceEpoch,
"previouslyAttestedTargetEpoch": prevTarget,
"previouslyAttestedSourceEpoch": prevSource,
}).Warn("Attempted to submit a surrounding attestation, but blocked by slashing protection")
return true, nil
}
}
// Check if the new attestation is being surrounded.
for i := targetEpoch; i <= latestEpochWritten; i++ {
h := safeTargetToSource(ctx, history, i)
if h.IsEmpty() {
historicalAtt, err := checkHistoryAtTargetEpoch(ctx, history, latestEpochWritten, i)
if err != nil {
return false, errors.Wrapf(err, "could not check historical attestation at target epoch: %d", i)
}
if historicalAtt.IsEmpty() {
continue
}
if h.Source < sourceEpoch {
return true
prevTarget := i
prevSource := historicalAtt.Source
if surroundedByPrevAttestation(prevSource, prevTarget, sourceEpoch, targetEpoch) {
// Surrounded attestation caught.
log.WithFields(logrus.Fields{
"targetEpoch": targetEpoch,
"sourceEpoch": sourceEpoch,
"previouslyAttestedTargetEpoch": prevTarget,
"previouslyAttestedSourceEpoch": prevSource,
}).Warn("Attempted to submit a surrounded attestation, but blocked by slashing protection")
return true, nil
}
}
return false
return false, nil
}
// markAttestationForTargetEpoch returns the modified attestation history with the passed-in epochs marked
// as attested for. This is done to prevent the validator client from signing any slashable attestations.
func markAttestationForTargetEpoch(ctx context.Context, history kv.EncHistoryData, sourceEpoch, targetEpoch uint64, signingRoot [32]byte) kv.EncHistoryData {
if history == nil {
return nil
}
func surroundedByPrevAttestation(prevSource, prevTarget, newSource, newTarget uint64) bool {
return prevSource < newSource && newTarget < prevTarget
}
func surroundingPrevAttestation(prevSource, prevTarget, newSource, newTarget uint64) bool {
return newSource < prevSource && prevTarget < newTarget
}
// Checks that the difference between the latest epoch written and
// target epoch is greater than or equal to the weak subjectivity period.
func differenceOutsideWeakSubjectivityBounds(latestEpochWritten, targetEpoch uint64) bool {
wsPeriod := params.BeaconConfig().WeakSubjectivityPeriod
latestEpochWritten, err := history.GetLatestEpochWritten(ctx)
if err != nil {
log.WithError(err).Error("Could not get latest epoch written from encapsulated data")
return nil
}
if targetEpoch > latestEpochWritten {
// If the target epoch to mark is ahead of latest written epoch, override the old targets and mark the requested epoch.
// Limit the overwriting to one weak subjectivity period as further is not needed.
maxToWrite := latestEpochWritten + wsPeriod
for i := latestEpochWritten + 1; i < targetEpoch && i <= maxToWrite; i++ {
history, err = history.SetTargetData(ctx, i%wsPeriod, &kv.HistoryData{Source: params.BeaconConfig().FarFutureEpoch})
if err != nil {
log.WithError(err).Error("Could not set target to the encapsulated data")
return nil
}
}
history, err = history.SetLatestEpochWritten(ctx, targetEpoch)
if err != nil {
log.WithError(err).Error("Could not set latest epoch written to the encapsulated data")
return nil
}
}
history, err = history.SetTargetData(ctx, targetEpoch%wsPeriod, &kv.HistoryData{Source: sourceEpoch, SigningRoot: signingRoot[:]})
if err != nil {
log.WithError(err).Error("Could not set target to the encapsulated data")
return nil
}
return history
return latestEpochWritten >= wsPeriod && targetEpoch <= latestEpochWritten-wsPeriod
}
// safeTargetToSource makes sure the epoch accessed is within bounds, and if it's not it at
// returns the "default" nil value.
func safeTargetToSource(ctx context.Context, history kv.EncHistoryData, targetEpoch uint64) *kv.HistoryData {
// Returns the actual attesting history at a specified target epoch.
// The response is nil if there was no attesting history at that epoch.
func checkHistoryAtTargetEpoch(
ctx context.Context,
history kv.EncHistoryData,
latestEpochWritten,
targetEpoch uint64,
) (*kv.HistoryData, error) {
wsPeriod := params.BeaconConfig().WeakSubjectivityPeriod
latestEpochWritten, err := history.GetLatestEpochWritten(ctx)
if err != nil {
log.WithError(err).Error("Could not get latest epoch written from encapsulated data")
return nil
if differenceOutsideWeakSubjectivityBounds(latestEpochWritten, targetEpoch) {
return nil, nil
}
// Ignore target epoch is > latest written.
if targetEpoch > latestEpochWritten {
return nil
return nil, nil
}
if latestEpochWritten >= wsPeriod && targetEpoch < latestEpochWritten-wsPeriod { //Underflow protected older then weak subjectivity check.
return nil
}
hd, err := history.GetTargetData(ctx, targetEpoch%wsPeriod)
historicalAtt, err := history.GetTargetData(ctx, targetEpoch%wsPeriod)
if err != nil {
log.WithError(err).Errorf("Could not get target data for target epoch: %d", targetEpoch)
return nil
return nil, errors.Wrapf(err, "could not get target data for target epoch: %d", targetEpoch)
}
return hd
return historicalAtt, nil
}

View File

@@ -2,6 +2,7 @@ package client
import (
"context"
"reflect"
"strings"
"sync"
"testing"
@@ -168,7 +169,12 @@ func TestAttestationHistory_BlocksDoubleAttestation(t *testing.T) {
newAttSource := uint64(0)
newAttTarget := uint64(3)
sr1 := [32]byte{1}
history = markAttestationForTargetEpoch(ctx, history, newAttSource, newAttTarget, sr1)
newHist, err := kv.MarkAllAsAttestedSinceLatestWrittenEpoch(ctx, history, newAttTarget, &kv.HistoryData{
Source: newAttSource,
SigningRoot: sr1[:],
})
require.NoError(t, err)
history = newHist
lew, err := history.GetLatestEpochWritten(ctx)
require.NoError(t, err)
require.Equal(t, newAttTarget, lew, "Unexpected latest epoch written")
@@ -177,7 +183,9 @@ func TestAttestationHistory_BlocksDoubleAttestation(t *testing.T) {
sr2 := [32]byte{2}
newAttSource = uint64(1)
newAttTarget = uint64(3)
if !isNewAttSlashable(ctx, history, newAttSource, newAttTarget, sr2) {
slashable, err := isNewAttSlashable(ctx, history, newAttSource, newAttTarget, sr2)
require.NoError(t, err)
if !slashable {
t.Fatalf("Expected attestation of source %d and target %d to be considered slashable", newAttSource, newAttTarget)
}
}
@@ -294,15 +302,27 @@ func TestAttestationHistory_Prunes(t *testing.T) {
history := kv.NewAttestationHistoryArray(0)
// Try an attestation on totally unmarked history, should not be slashable.
require.Equal(t, false, isNewAttSlashable(ctx, history, 0, wsPeriod+5, signingRoot), "Should not be slashable")
slashable, err := isNewAttSlashable(ctx, history, 0, wsPeriod+5, signingRoot)
require.NoError(t, err)
require.Equal(t, false, slashable, "Should not be slashable")
// Mark attestations spanning epochs 0 to 3 and 6 to 9.
prunedNewAttSource := uint64(0)
prunedNewAttTarget := uint64(3)
history = markAttestationForTargetEpoch(ctx, history, prunedNewAttSource, prunedNewAttTarget, signingRoot)
newHist, err := kv.MarkAllAsAttestedSinceLatestWrittenEpoch(ctx, history, prunedNewAttTarget, &kv.HistoryData{
Source: prunedNewAttSource,
SigningRoot: signingRoot[:],
})
require.NoError(t, err)
history = newHist
newAttSource := prunedNewAttSource + 6
newAttTarget := prunedNewAttTarget + 6
history = markAttestationForTargetEpoch(ctx, history, newAttSource, newAttTarget, signingRoot2)
newHist, err = kv.MarkAllAsAttestedSinceLatestWrittenEpoch(ctx, history, newAttTarget, &kv.HistoryData{
Source: newAttSource,
SigningRoot: signingRoot2[:],
})
require.NoError(t, err)
history = newHist
lte, err := history.GetLatestEpochWritten(ctx)
require.NoError(t, err)
require.Equal(t, newAttTarget, lte, "Unexpected latest epoch")
@@ -310,24 +330,39 @@ func TestAttestationHistory_Prunes(t *testing.T) {
// Mark an attestation spanning epochs 54000 to 54003.
farNewAttSource := newAttSource + wsPeriod
farNewAttTarget := newAttTarget + wsPeriod
history = markAttestationForTargetEpoch(ctx, history, farNewAttSource, farNewAttTarget, signingRoot3)
newHist, err = kv.MarkAllAsAttestedSinceLatestWrittenEpoch(ctx, history, farNewAttTarget, &kv.HistoryData{
Source: farNewAttSource,
SigningRoot: signingRoot3[:],
})
require.NoError(t, err)
history = newHist
lte, err = history.GetLatestEpochWritten(ctx)
require.NoError(t, err)
require.Equal(t, farNewAttTarget, lte, "Unexpected latest epoch")
require.Equal(t, (*kv.HistoryData)(nil), safeTargetToSource(ctx, history, prunedNewAttTarget), "Unexpectedly marked attestation")
require.Equal(t, farNewAttSource, safeTargetToSource(ctx, history, farNewAttTarget).Source, "Unexpectedly marked attestation")
histAtt, err := checkHistoryAtTargetEpoch(ctx, history, lte, prunedNewAttTarget)
require.NoError(t, err)
require.Equal(t, (*kv.HistoryData)(nil), histAtt, "Unexpectedly marked attestation")
histAtt, err = checkHistoryAtTargetEpoch(ctx, history, lte, farNewAttTarget)
require.NoError(t, err)
require.Equal(t, farNewAttSource, histAtt.Source, "Unexpectedly marked attestation")
// Try an attestation from existing source to outside prune, should slash.
if !isNewAttSlashable(ctx, history, newAttSource, farNewAttTarget, signingRoot4) {
slashable, err = isNewAttSlashable(ctx, history, newAttSource, farNewAttTarget, signingRoot4)
require.NoError(t, err)
if !slashable {
t.Fatalf("Expected attestation of source %d, target %d to be considered slashable", newAttSource, farNewAttTarget)
}
// Try an attestation from before existing target to outside prune, should slash.
if !isNewAttSlashable(ctx, history, newAttTarget-1, farNewAttTarget, signingRoot4) {
slashable, err = isNewAttSlashable(ctx, history, newAttTarget-1, farNewAttTarget, signingRoot4)
require.NoError(t, err)
if !slashable {
t.Fatalf("Expected attestation of source %d, target %d to be considered slashable", newAttTarget-1, farNewAttTarget)
}
// Try an attestation larger than pruning amount, should slash.
if !isNewAttSlashable(ctx, history, 0, farNewAttTarget+5, signingRoot4) {
slashable, err = isNewAttSlashable(ctx, history, 0, farNewAttTarget+5, signingRoot4)
require.NoError(t, err)
if !slashable {
t.Fatalf("Expected attestation of source 0, target %d to be considered slashable", farNewAttTarget+5)
}
}
@@ -340,7 +375,12 @@ func TestAttestationHistory_BlocksSurroundedAttestation(t *testing.T) {
signingRoot := [32]byte{1}
newAttSource := uint64(0)
newAttTarget := uint64(3)
history = markAttestationForTargetEpoch(ctx, history, newAttSource, newAttTarget, signingRoot)
newHist, err := kv.MarkAllAsAttestedSinceLatestWrittenEpoch(ctx, history, newAttTarget, &kv.HistoryData{
Source: newAttSource,
SigningRoot: signingRoot[:],
})
require.NoError(t, err)
history = newHist
lte, err := history.GetLatestEpochWritten(ctx)
require.NoError(t, err)
require.Equal(t, newAttTarget, lte)
@@ -348,7 +388,9 @@ func TestAttestationHistory_BlocksSurroundedAttestation(t *testing.T) {
// Try an attestation that should be slashable (being surrounded) spanning epochs 1 to 2.
newAttSource = uint64(1)
newAttTarget = uint64(2)
require.Equal(t, true, isNewAttSlashable(ctx, history, newAttSource, newAttTarget, signingRoot), "Expected slashable attestation")
slashable, err := isNewAttSlashable(ctx, history, newAttSource, newAttTarget, signingRoot)
require.NoError(t, err)
require.Equal(t, true, slashable, "Expected slashable attestation")
}
func TestAttestationHistory_BlocksSurroundingAttestation(t *testing.T) {
@@ -359,7 +401,12 @@ func TestAttestationHistory_BlocksSurroundingAttestation(t *testing.T) {
// Mark an attestation spanning epochs 1 to 2.
newAttSource := uint64(1)
newAttTarget := uint64(2)
history = markAttestationForTargetEpoch(ctx, history, newAttSource, newAttTarget, signingRoot)
newHist, err := kv.MarkAllAsAttestedSinceLatestWrittenEpoch(ctx, history, newAttTarget, &kv.HistoryData{
Source: newAttSource,
SigningRoot: signingRoot[:],
})
require.NoError(t, err)
history = newHist
lte, err := history.GetLatestEpochWritten(ctx)
require.NoError(t, err)
require.Equal(t, newAttTarget, lte)
@@ -370,5 +417,346 @@ func TestAttestationHistory_BlocksSurroundingAttestation(t *testing.T) {
// Try an attestation that should be slashable (surrounding) spanning epochs 0 to 3.
newAttSource = uint64(0)
newAttTarget = uint64(3)
require.Equal(t, true, isNewAttSlashable(ctx, history, newAttSource, newAttTarget, signingRoot))
slashable, err := isNewAttSlashable(ctx, history, newAttSource, newAttTarget, signingRoot)
require.NoError(t, err)
require.Equal(t, true, slashable)
}
func Test_isSurroundVote(t *testing.T) {
ctx := context.Background()
source := uint64(1)
target := uint64(4)
history := kv.NewAttestationHistoryArray(0)
signingRoot1 := bytesutil.PadTo([]byte{1}, 32)
hist, err := history.SetTargetData(ctx, target, &kv.HistoryData{
Source: source,
SigningRoot: signingRoot1,
})
require.NoError(t, err)
history = hist
tests := []struct {
name string
history kv.EncHistoryData
latestEpochWritten uint64
sourceEpoch uint64
targetEpoch uint64
want bool
wantErr bool
}{
{
name: "ignores attestations outside of weak subjectivity bounds",
history: kv.NewAttestationHistoryArray(0),
latestEpochWritten: 2 * params.BeaconConfig().WeakSubjectivityPeriod,
targetEpoch: params.BeaconConfig().WeakSubjectivityPeriod,
sourceEpoch: params.BeaconConfig().WeakSubjectivityPeriod,
want: false,
},
{
name: "detects surrounding attestations",
history: history,
latestEpochWritten: target,
targetEpoch: target + 1,
sourceEpoch: source - 1,
want: true,
},
{
name: "detects surrounded attestations",
history: history,
latestEpochWritten: target,
targetEpoch: target - 1,
sourceEpoch: source + 1,
want: true,
},
{
name: "new attestation source == old source, but new target < old target",
history: history,
latestEpochWritten: target,
targetEpoch: target - 1,
sourceEpoch: source,
want: false,
},
{
name: "new attestation source > old source, but new target == old target",
history: history,
latestEpochWritten: target,
targetEpoch: target,
sourceEpoch: source + 1,
want: false,
},
{
name: "new attestation source and targets equal to old one",
history: history,
latestEpochWritten: target,
targetEpoch: target,
sourceEpoch: source,
want: false,
},
{
name: "new attestation source == old source, but new target > old target",
history: history,
latestEpochWritten: target,
targetEpoch: target + 1,
sourceEpoch: source,
want: false,
},
{
name: "new attestation source < old source, but new target == old target",
history: history,
latestEpochWritten: target,
targetEpoch: target,
sourceEpoch: source - 1,
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := isSurroundVote(ctx, tt.history, tt.latestEpochWritten, tt.sourceEpoch, tt.targetEpoch)
if (err != nil) != tt.wantErr {
t.Errorf("isSurroundVote() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("isSurroundVote() got = %v, want %v", got, tt.want)
}
})
}
}
func Test_surroundedByPrevAttestation(t *testing.T) {
type args struct {
oldSource uint64
oldTarget uint64
newSource uint64
newTarget uint64
}
tests := []struct {
name string
args args
want bool
}{
{
name: "0 values returns false",
args: args{
oldSource: 0,
oldTarget: 0,
newSource: 0,
newTarget: 0,
},
want: false,
},
{
name: "new attestation is surrounded by an old one",
args: args{
oldSource: 2,
oldTarget: 6,
newSource: 3,
newTarget: 5,
},
want: true,
},
{
name: "new attestation source and targets equal to old one",
args: args{
oldSource: 3,
oldTarget: 5,
newSource: 3,
newTarget: 5,
},
want: false,
},
{
name: "new attestation source == old source, but new target < old target",
args: args{
oldSource: 3,
oldTarget: 5,
newSource: 3,
newTarget: 4,
},
want: false,
},
{
name: "new attestation source > old source, but new target == old target",
args: args{
oldSource: 3,
oldTarget: 5,
newSource: 4,
newTarget: 5,
},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := surroundedByPrevAttestation(tt.args.oldSource, tt.args.oldTarget, tt.args.newSource, tt.args.newTarget); got != tt.want {
t.Errorf("surroundedByPrevAttestation() = %v, want %v", got, tt.want)
}
})
}
}
func Test_surroundingPrevAttestation(t *testing.T) {
type args struct {
oldSource uint64
oldTarget uint64
newSource uint64
newTarget uint64
}
tests := []struct {
name string
args args
want bool
}{
{
name: "0 values returns false",
args: args{
oldSource: 0,
oldTarget: 0,
newSource: 0,
newTarget: 0,
},
want: false,
},
{
name: "new attestation is surrounding an old one",
args: args{
oldSource: 3,
oldTarget: 5,
newSource: 2,
newTarget: 6,
},
want: true,
},
{
name: "new attestation source and targets equal to old one",
args: args{
oldSource: 3,
oldTarget: 5,
newSource: 3,
newTarget: 5,
},
want: false,
},
{
name: "new attestation source == old source, but new target > old target",
args: args{
oldSource: 3,
oldTarget: 5,
newSource: 3,
newTarget: 6,
},
want: false,
},
{
name: "new attestation source < old source, but new target == old target",
args: args{
oldSource: 3,
oldTarget: 5,
newSource: 2,
newTarget: 5,
},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := surroundingPrevAttestation(tt.args.oldSource, tt.args.oldTarget, tt.args.newSource, tt.args.newTarget); got != tt.want {
t.Errorf("surroundingPrevAttestation() = %v, want %v", got, tt.want)
}
})
}
}
func Test_checkHistoryAtTargetEpoch(t *testing.T) {
ctx := context.Background()
history := kv.NewAttestationHistoryArray(0)
signingRoot1 := bytesutil.PadTo([]byte{1}, 32)
hist, err := history.SetTargetData(ctx, 1, &kv.HistoryData{
Source: 0,
SigningRoot: signingRoot1,
})
require.NoError(t, err)
history = hist
tests := []struct {
name string
history kv.EncHistoryData
latestEpochWritten uint64
targetEpoch uint64
want *kv.HistoryData
wantErr bool
}{
{
name: "ignores difference in epochs outside of weak subjectivity bounds",
history: kv.NewAttestationHistoryArray(0),
latestEpochWritten: 2 * params.BeaconConfig().WeakSubjectivityPeriod,
targetEpoch: params.BeaconConfig().WeakSubjectivityPeriod,
want: nil,
wantErr: false,
},
{
name: "ignores target epoch > latest written epoch",
history: kv.NewAttestationHistoryArray(0),
latestEpochWritten: params.BeaconConfig().WeakSubjectivityPeriod,
targetEpoch: params.BeaconConfig().WeakSubjectivityPeriod + 1,
want: nil,
wantErr: false,
},
{
name: "target epoch == latest written epoch should return correct results",
history: history,
latestEpochWritten: 1,
targetEpoch: 1,
want: &kv.HistoryData{
Source: 0,
SigningRoot: signingRoot1,
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := checkHistoryAtTargetEpoch(ctx, tt.history, tt.latestEpochWritten, tt.targetEpoch)
if (err != nil) != tt.wantErr {
t.Errorf("checkHistoryAtTargetEpoch() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("checkHistoryAtTargetEpoch() got = %v, want %v", got, tt.want)
}
})
}
}
func Test_differenceOutsideWeakSubjectivityBounds(t *testing.T) {
tests := []struct {
name string
want bool
latestEpochWritten uint64
targetEpoch uint64
}{
{
name: "difference of weak subjectivity period - 1 returns false",
latestEpochWritten: (2 * params.BeaconConfig().WeakSubjectivityPeriod) - 1,
targetEpoch: params.BeaconConfig().WeakSubjectivityPeriod,
want: false,
},
{
name: "difference of weak subjectivity period returns true",
latestEpochWritten: 2 * params.BeaconConfig().WeakSubjectivityPeriod,
targetEpoch: params.BeaconConfig().WeakSubjectivityPeriod,
want: true,
},
{
name: "difference > weak subjectivity period returns true",
latestEpochWritten: (2 * params.BeaconConfig().WeakSubjectivityPeriod) + 1,
targetEpoch: params.BeaconConfig().WeakSubjectivityPeriod,
want: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := differenceOutsideWeakSubjectivityBounds(tt.latestEpochWritten, tt.targetEpoch); got != tt.want {
t.Errorf("differenceOutsideWeakSubjectivityBounds() = %v, want %v", got, tt.want)
}
})
}
}

View File

@@ -67,7 +67,9 @@ func (v *validator) ProposeBlock(ctx context.Context, slot uint64, pubKey [48]by
}
if err := v.preBlockSignValidations(ctx, pubKey, b); err != nil {
log.WithField("slot", b.Slot).WithError(err).Error("Failed block safety check")
log.WithFields(
blockLogFields(pubKey, b, nil),
).WithError(err).Error("Failed block slashing protection check")
return
}
@@ -86,7 +88,9 @@ func (v *validator) ProposeBlock(ctx context.Context, slot uint64, pubKey [48]by
}
if err := v.postBlockSignUpdate(ctx, pubKey, blk, domain); err != nil {
log.WithField("slot", blk.Block.Slot).WithError(err).Error("Failed post block signing validations")
log.WithFields(
blockLogFields(pubKey, b, sig),
).WithError(err).Error("Failed block slashing protection check")
return
}

View File

@@ -11,6 +11,7 @@ import (
"github.com/prysmaticlabs/prysm/shared/blockutil"
"github.com/prysmaticlabs/prysm/shared/featureconfig"
"github.com/prysmaticlabs/prysm/shared/params"
"github.com/sirupsen/logrus"
)
var failedPreBlockSignLocalErr = "attempted to sign a double proposal, block rejected by local protection"
@@ -83,3 +84,15 @@ func (v *validator) postBlockSignUpdate(ctx context.Context, pubKey [48]byte, bl
}
return nil
}
func blockLogFields(pubKey [48]byte, blk *ethpb.BeaconBlock, sig []byte) logrus.Fields {
fields := logrus.Fields{
"proposerPublicKey": fmt.Sprintf("%#x", pubKey),
"proposerIndex": blk.ProposerIndex,
"blockSlot": blk.Slot,
}
if sig != nil {
fields["signature"] = fmt.Sprintf("%#x", sig)
}
return fields
}

View File

@@ -126,7 +126,6 @@ func (v *ValidatorService) Start() {
dialOpts := ConstructDialOptions(
v.maxCallRecvMsgSize,
v.withCert,
v.grpcHeaders,
v.grpcRetries,
v.grpcRetryDelay,
streamInterceptor,
@@ -134,6 +133,18 @@ func (v *ValidatorService) Start() {
if dialOpts == nil {
return
}
for _, hdr := range v.grpcHeaders {
if hdr != "" {
ss := strings.Split(hdr, "=")
if len(ss) != 2 {
log.Warnf("Incorrect gRPC header flag format. Skipping %v", hdr)
continue
}
v.ctx = metadata.AppendToOutgoingContext(v.ctx, ss[0], ss[1])
}
}
conn, err := grpc.DialContext(v.ctx, v.endpoint, dialOpts...)
if err != nil {
log.Errorf("Could not dial endpoint: %s, %v", v.endpoint, err)
@@ -236,7 +247,6 @@ func (v *ValidatorService) recheckKeys(ctx context.Context) {
func ConstructDialOptions(
maxCallRecvMsgSize int,
withCert string,
grpcHeaders []string,
grpcRetries uint,
grpcRetryDelay time.Duration,
extraOpts ...grpc.DialOption,
@@ -260,25 +270,12 @@ func ConstructDialOptions(
maxCallRecvMsgSize = 10 * 5 << 20 // Default 50Mb
}
md := make(metadata.MD)
for _, hdr := range grpcHeaders {
if hdr != "" {
ss := strings.Split(hdr, "=")
if len(ss) != 2 {
log.Warnf("Incorrect gRPC header flag format. Skipping %v", hdr)
continue
}
md.Set(ss[0], ss[1])
}
}
dialOpts := []grpc.DialOption{
transportSecurity,
grpc.WithDefaultCallOptions(
grpc.MaxCallRecvMsgSize(maxCallRecvMsgSize),
grpc_retry.WithMax(grpcRetries),
grpc_retry.WithBackoff(grpc_retry.BackoffLinear(grpcRetryDelay)),
grpc.Header(&md),
),
grpc.WithStatsHandler(&ocgrpc.ClientHandler{}),
grpc.WithUnaryInterceptor(middleware.ChainUnaryClient(

View File

@@ -801,10 +801,21 @@ func TestSaveProtections_OK(t *testing.T) {
attesterHistoryByPubKey: cleanHistories,
}
sr := [32]byte{1}
history1 := cleanHistories[pubKey1]
history1 = markAttestationForTargetEpoch(ctx, history1, 0, 1, [32]byte{1})
newHist, err := kv.MarkAllAsAttestedSinceLatestWrittenEpoch(ctx, history1, 1, &kv.HistoryData{
Source: 0,
SigningRoot: sr[:],
})
require.NoError(t, err)
history1 = newHist
history2 := markAttestationForTargetEpoch(ctx, history1, 2, 3, [32]byte{2})
sr2 := [32]byte{2}
history2, err := kv.MarkAllAsAttestedSinceLatestWrittenEpoch(ctx, history1, 3, &kv.HistoryData{
Source: 2,
SigningRoot: sr2[:],
})
require.NoError(t, err)
cleanHistories[pubKey1] = history1
cleanHistories[pubKey2] = history2
@@ -835,7 +846,13 @@ func TestSaveProtection_OK(t *testing.T) {
}
history1 := cleanHistories[pubKey1]
history1 = markAttestationForTargetEpoch(ctx, history1, 0, 1, [32]byte{1})
sr := [32]byte{1}
newHist, err := kv.MarkAllAsAttestedSinceLatestWrittenEpoch(ctx, history1, 1, &kv.HistoryData{
Source: 0,
SigningRoot: sr[:],
})
require.NoError(t, err)
history1 = newHist
cleanHistories[pubKey1] = history1

View File

@@ -127,6 +127,49 @@ func (hd EncHistoryData) SetTargetData(ctx context.Context, target uint64, histo
return hd, nil
}
// MarkAllAsAttestedSinceLatestWrittenEpoch returns an attesting history with specified target+epoch pairs
// since the latest written epoch up to the incoming attestation's target epoch as attested for.
func MarkAllAsAttestedSinceLatestWrittenEpoch(
ctx context.Context,
hist EncHistoryData,
incomingTarget uint64,
incomingAtt *HistoryData,
) (EncHistoryData, error) {
wsPeriod := params.BeaconConfig().WeakSubjectivityPeriod
latestEpochWritten, err := hist.GetLatestEpochWritten(ctx)
if err != nil {
return EncHistoryData{}, errors.Wrap(err, "could not get latest epoch written from history")
}
currentHD := hist
if incomingTarget > latestEpochWritten {
// If the target epoch to mark is ahead of latest written epoch, override the old targets and mark the requested epoch.
// Limit the overwriting to one weak subjectivity period as further is not needed.
maxToWrite := latestEpochWritten + wsPeriod
for i := latestEpochWritten + 1; i < incomingTarget && i <= maxToWrite; i++ {
newHD, err := hist.SetTargetData(ctx, i%wsPeriod, &HistoryData{
Source: params.BeaconConfig().FarFutureEpoch,
})
if err != nil {
return EncHistoryData{}, errors.Wrap(err, "could not set target data")
}
currentHD = newHD
}
newHD, err := currentHD.SetLatestEpochWritten(ctx, incomingTarget)
if err != nil {
return EncHistoryData{}, errors.Wrap(err, "could not set latest epoch written")
}
currentHD = newHD
}
newHD, err := currentHD.SetTargetData(ctx, incomingTarget%wsPeriod, &HistoryData{
Source: incomingAtt.Source,
SigningRoot: incomingAtt.SigningRoot,
})
if err != nil {
return EncHistoryData{}, errors.Wrap(err, "could not set target data")
}
return newHD, nil
}
// AttestationHistoryForPubKeysV2 accepts an array of validator public keys and returns a mapping of corresponding attestation history.
func (store *Store) AttestationHistoryForPubKeysV2(ctx context.Context, publicKeys [][48]byte) (map[[48]byte]EncHistoryData, error) {
ctx, span := trace.StartSpan(ctx, "Validator.AttestationHistoryForPubKeysV2")

View File

@@ -247,6 +247,13 @@ func (s *ValidatorClient) initializeFromCLI(cliCtx *cli.Context) error {
func (s *ValidatorClient) initializeForWeb(cliCtx *cli.Context) error {
var keyManager keymanager.IKeymanager
var err error
walletDir := cliCtx.String(flags.WalletDirFlag.Name)
defaultWalletPasswordFilePath := filepath.Join(walletDir, wallet.DefaultWalletPasswordFile)
if fileutil.FileExists(defaultWalletPasswordFilePath) {
if err := cliCtx.Set(flags.WalletPasswordFileFlag.Name, defaultWalletPasswordFilePath); err != nil {
return errors.Wrap(err, "could not set default wallet password file path")
}
}
// Read the wallet from the specified path.
w, err := wallet.OpenWalletOrElseCli(cliCtx, func(cliCtx *cli.Context) (*wallet.Wallet, error) {
return nil, nil

View File

@@ -17,6 +17,7 @@ go_library(
"//proto/validator/accounts/v2:go_default_library",
"//shared/cmd:go_default_library",
"//shared/event:go_default_library",
"//shared/featureconfig:go_default_library",
"//shared/fileutil:go_default_library",
"//shared/pagination:go_default_library",
"//shared/petnames:go_default_library",
@@ -66,6 +67,7 @@ go_test(
"//proto/validator/accounts/v2:go_default_library",
"//shared/bls:go_default_library",
"//shared/event:go_default_library",
"//shared/featureconfig:go_default_library",
"//shared/fileutil:go_default_library",
"//shared/testutil/assert:go_default_library",
"//shared/testutil/require:go_default_library",

View File

@@ -4,10 +4,14 @@ import (
"context"
"encoding/hex"
"encoding/json"
"fmt"
"path/filepath"
ptypes "github.com/gogo/protobuf/types"
"github.com/pkg/errors"
pb "github.com/prysmaticlabs/prysm/proto/validator/accounts/v2"
"github.com/prysmaticlabs/prysm/shared/featureconfig"
"github.com/prysmaticlabs/prysm/shared/fileutil"
"github.com/prysmaticlabs/prysm/shared/rand"
"github.com/prysmaticlabs/prysm/validator/accounts"
"github.com/prysmaticlabs/prysm/validator/accounts/wallet"
@@ -73,6 +77,9 @@ func (s *Server) CreateWallet(ctx context.Context, req *pb.CreateWalletRequest)
}); err != nil {
return nil, err
}
if err := writeWalletPasswordToDisk(walletDir, req.WalletPassword); err != nil {
return nil, status.Error(codes.Internal, "Could not write wallet password to disk")
}
return &pb.CreateWalletResponse{
Wallet: &pb.WalletResponse{
WalletPath: walletDir,
@@ -101,7 +108,9 @@ func (s *Server) CreateWallet(ctx context.Context, req *pb.CreateWalletRequest)
}); err != nil {
return nil, err
}
if err := writeWalletPasswordToDisk(walletDir, req.WalletPassword); err != nil {
return nil, status.Error(codes.Internal, "Could not write wallet password to disk")
}
return &pb.CreateWalletResponse{
Wallet: &pb.WalletResponse{
WalletPath: walletDir,
@@ -272,3 +281,14 @@ func (s *Server) initializeWallet(ctx context.Context, cfg *wallet.Config) error
}
return nil
}
func writeWalletPasswordToDisk(walletDir string, password string) error {
if !featureconfig.Get().WriteWalletPasswordOnWebOnboarding {
return nil
}
passwordFilePath := filepath.Join(walletDir, wallet.DefaultWalletPasswordFile)
if fileutil.FileExists(passwordFilePath) {
return fmt.Errorf("cannot write wallet password file as it already exists %s", passwordFilePath)
}
return fileutil.WriteFile(passwordFilePath, []byte(password))
}

View File

@@ -5,20 +5,24 @@ import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"testing"
ptypes "github.com/gogo/protobuf/types"
"github.com/google/uuid"
keystorev4 "github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4"
pb "github.com/prysmaticlabs/prysm/proto/validator/accounts/v2"
"github.com/prysmaticlabs/prysm/shared/bls"
"github.com/prysmaticlabs/prysm/shared/event"
"github.com/prysmaticlabs/prysm/shared/featureconfig"
"github.com/prysmaticlabs/prysm/shared/fileutil"
"github.com/prysmaticlabs/prysm/shared/testutil/assert"
"github.com/prysmaticlabs/prysm/shared/testutil/require"
"github.com/prysmaticlabs/prysm/validator/accounts"
"github.com/prysmaticlabs/prysm/validator/accounts/wallet"
"github.com/prysmaticlabs/prysm/validator/keymanager"
"github.com/prysmaticlabs/prysm/validator/keymanager/imported"
keystorev4 "github.com/wealdtech/go-eth2-wallet-encryptor-keystorev4"
)
func TestServer_CreateWallet_Imported(t *testing.T) {
@@ -277,3 +281,30 @@ func TestServer_ImportKeystores_OK(t *testing.T) {
require.NoError(t, err)
assert.Equal(t, 3, len(keys))
}
func Test_writeWalletPasswordToDisk(t *testing.T) {
walletDir := setupWalletDir(t)
resetCfg := featureconfig.InitWithReset(&featureconfig.Flags{
WriteWalletPasswordOnWebOnboarding: false,
})
defer resetCfg()
err := writeWalletPasswordToDisk(walletDir, "somepassword")
require.NoError(t, err)
// Expected a silent failure if the feature flag is not enabled.
passwordFilePath := filepath.Join(walletDir, wallet.DefaultWalletPasswordFile)
assert.Equal(t, false, fileutil.FileExists(passwordFilePath))
resetCfg = featureconfig.InitWithReset(&featureconfig.Flags{
WriteWalletPasswordOnWebOnboarding: true,
})
defer resetCfg()
err = writeWalletPasswordToDisk(walletDir, "somepassword")
require.NoError(t, err)
// File should have been written.
assert.Equal(t, true, fileutil.FileExists(passwordFilePath))
// Attempting to write again should trigger an error.
err = writeWalletPasswordToDisk(walletDir, "somepassword")
require.NotNil(t, err)
}