Skip to content

Commit

Permalink
Prevent unnecessary wallet rescans. (decred#519)
Browse files Browse the repository at this point in the history
* Prevent unnecessary wallet rescans.

This is acheived by adding a new stakepoold RPC. Previously we used a single RPC
to import redeem scripts, regardless of whether they were new or historic. A
rescan was triggered every time a script was imported. This was problematic
because on startup, dcrstakepool performs consistency checks against each
back-end and may import multiple scripts into a single wallet simultaneously.

I have split the `ImportScript` RPC into two `ImportNewScript` and
`ImportMissingScripts`.

`ImportNewScript` will never trigger a rescan because a new script should have
no associated history. This is used when a new wallet is connected to the VSP.
`ImportMissingScripts` is used by the consistency checks, and will import
multiple scripts before triggering a single rescan.
  • Loading branch information
jholdstock authored and jyap808 committed Dec 16, 2019
1 parent ab02b5e commit 36e897c
Show file tree
Hide file tree
Showing 6 changed files with 381 additions and 211 deletions.
16 changes: 11 additions & 5 deletions backend/stakepoold/rpc/api.proto
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ service StakepooldService {
rpc GetLiveTickets (GetLiveTicketsRequest) returns (GetLiveTicketsResponse);
rpc SetAddedLowFeeTickets (SetAddedLowFeeTicketsRequest) returns (SetAddedLowFeeTicketsResponse);
rpc SetUserVotingPrefs (SetUserVotingPrefsRequest) returns (SetUserVotingPrefsResponse);
rpc ImportScript (ImportScriptRequest) returns (ImportScriptResponse);
rpc ImportNewScript (ImportNewScriptRequest) returns (ImportNewScriptResponse);
rpc ImportMissingScripts (ImportMissingScriptsRequest) returns (ImportMissingScriptsResponse);
rpc StakePoolUserInfo (StakePoolUserInfoRequest) returns (StakePoolUserInfoResponse);
rpc WalletInfo (WalletInfoRequest) returns (WalletInfoResponse);
rpc ValidateAddress (ValidateAddressRequest) returns (ValidateAddressResponse);
Expand Down Expand Up @@ -78,12 +79,17 @@ message AccountSyncAddressIndexRequest {
message AccountSyncAddressIndexResponse {
}

message ImportScriptRequest {
message ImportMissingScriptsRequest {
repeated bytes Scripts = 1;
int64 RescanHeight = 2;
}
message ImportMissingScriptsResponse {
}

message ImportNewScriptRequest {
bytes Script = 1;
bool Rescan = 2;
int64 RescanHeight = 3;
}
message ImportScriptResponse {
message ImportNewScriptResponse {
int64 HeightImported = 1;
}

Expand Down
46 changes: 40 additions & 6 deletions backend/stakepoold/rpc/rpcserver/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -277,19 +277,53 @@ func (ctx *AppContext) UpdateTicketDataFromMySQL() error {
return nil
}

func (ctx *AppContext) ImportScript(script []byte, rescan bool, rescanHeight int64) (int64, error) {
err := ctx.WalletConnection.ImportScriptRescanFrom(script, rescan, int(rescanHeight))
// ImportNewScript will import a redeem script into dcrwallet. No rescan is
// performed because we are importing a brand new script, it shouldn't have any
// associated history. Current block height is returned to indicate which height
// the new user has registered.
func (ctx *AppContext) ImportNewScript(script []byte) (int64, error) {

err := ctx.WalletConnection.ImportScriptRescanFrom(script, false, 0)
if err != nil {
log.Errorf("ImportScript: ImportScript rpc failed: %v", err)
log.Errorf("ImportNewScript: ImportScriptRescanFrom rpc failed: %v", err)
return -1, err
}

_, block, err := ctx.WalletConnection.GetBestBlock()
_, bestBlockHeight, err := ctx.WalletConnection.GetBestBlock()
if err != nil {
log.Errorf("ImportScript: getBetBlock rpc failed: %v", err)
log.Errorf("ImportNewScript: GetBestBlock rpc failed: %v", err)
return -1, err
}
return block, nil
return bestBlockHeight, nil
}

// ImportMissingScripts accepts a list of redeem scripts and a rescan height. It
// will import all but one of the scripts without triggering a wallet rescan,
// and finally trigger a rescan from the provided height after importing the
// last one.
func (ctx *AppContext) ImportMissingScripts(scripts [][]byte, rescanHeight int) error {

// Import n-1 scripts without a rescan.
allButOne := scripts[:len(scripts)-1]
for _, script := range allButOne {
err := ctx.WalletConnection.ImportScriptRescanFrom(script, false, 0)
if err != nil {
log.Errorf("ImportMissingScripts: ImportScript rpc failed: %v", err)
return err
}
}

// Import the last script and trigger a rescan
lastOne := scripts[len(scripts)-1]
err := ctx.WalletConnection.ImportScriptRescanFrom(lastOne, true, rescanHeight)
if err != nil {
log.Errorf("ImportMissingScripts: ImportScriptRescanFrom rpc failed: %v", err)
return err
}

log.Infof("ImportMissingScripts: Imported %d scripts and triggered a rescan from height %d", len(scripts), rescanHeight)

return nil
}

func (ctx *AppContext) AddMissingTicket(ticketHash []byte) error {
Expand Down
22 changes: 17 additions & 5 deletions backend/stakepoold/rpc/rpcserver/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ const (
// collection cycle to also trigger a timeout but the current allocation
// pattern of stakepoold is not known to cause such conditions at this time.
GRPCCommandTimeout = time.Millisecond * 100
semverString = "7.0.0"
semverMajor = 7
semverString = "8.0.0"
semverMajor = 8
semverMinor = 0
semverPatch = 0
)
Expand Down Expand Up @@ -184,20 +184,32 @@ func (s *stakepooldServer) SetUserVotingPrefs(ctx context.Context, req *pb.SetUs
return &pb.SetUserVotingPrefsResponse{}, nil
}

func (s *stakepooldServer) ImportScript(ctx context.Context, req *pb.ImportScriptRequest) (*pb.ImportScriptResponse, error) {
func (s *stakepooldServer) ImportNewScript(ctx context.Context, req *pb.ImportNewScriptRequest) (*pb.ImportNewScriptResponse, error) {
if !s.walletConnected() {
return nil, ErrWalletNotConnected
}

heightImported, err := s.appContext.ImportScript(req.Script, req.Rescan, req.RescanHeight)
heightImported, err := s.appContext.ImportNewScript(req.Script)
if err != nil {
return nil, err
}
return &pb.ImportScriptResponse{
return &pb.ImportNewScriptResponse{
HeightImported: heightImported,
}, nil
}

func (s *stakepooldServer) ImportMissingScripts(ctx context.Context, req *pb.ImportMissingScriptsRequest) (*pb.ImportMissingScriptsResponse, error) {
if !s.walletConnected() {
return nil, ErrWalletNotConnected
}

err := s.appContext.ImportMissingScripts(req.Scripts, int(req.RescanHeight))
if err != nil {
return nil, err
}
return &pb.ImportMissingScriptsResponse{}, nil
}

func (s *stakepooldServer) ListScripts(ctx context.Context, req *pb.ListScriptsRequest) (*pb.ListScriptsResponse, error) {
if !s.walletConnected() {
return nil, ErrWalletNotConnected
Expand Down
Loading

0 comments on commit 36e897c

Please sign in to comment.