-
Notifications
You must be signed in to change notification settings - Fork 76
/
Copy pathheartbeat.go
134 lines (112 loc) · 3.74 KB
/
heartbeat.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
package tbtc
import (
"context"
"fmt"
"github.com/ipfs/go-log/v2"
"github.com/keep-network/keep-core/pkg/bitcoin"
"github.com/keep-network/keep-core/pkg/tecdsa"
"math/big"
)
const (
// heartbeatProposalValidityBlocks determines the wallet heartbeat proposal
// validity time expressed in blocks. In other words, this is the worst-case
// time for a wallet heartbeat during which the wallet is busy and cannot
// take another actions. The value of 300 blocks is roughly 1 hour, assuming
// 12 seconds per block.
heartbeatProposalValidityBlocks = 300
// heartbeatRequestTimeoutSafetyMarginBlocks determines the duration of the
// safety margin that must be preserved between the signing timeout
// and the timeout of the entire heartbeat action. This safety
// margin prevents against the case where signing completes too late and
// another action has been already requested by the coordinator.
// The value of 25 blocks is roughly 5 minutes, assuming 12 seconds per block.
heartbeatRequestTimeoutSafetyMarginBlocks = 25
)
type HeartbeatProposal struct {
Message [16]byte
}
func (hp *HeartbeatProposal) ActionType() WalletActionType {
return ActionHeartbeat
}
func (hp *HeartbeatProposal) ValidityBlocks() uint64 {
return heartbeatProposalValidityBlocks
}
// heartbeatSigningExecutor is an interface meant to decouple the specific
// implementation of the signing executor from the heartbeat action.
type heartbeatSigningExecutor interface {
sign(
ctx context.Context,
message *big.Int,
startBlock uint64,
) (*tecdsa.Signature, uint64, error)
}
// heartbeatAction is a walletAction implementation handling heartbeat requests
// from the wallet coordinator.
type heartbeatAction struct {
logger log.StandardLogger
chain Chain
executingWallet wallet
signingExecutor heartbeatSigningExecutor
proposal *HeartbeatProposal
startBlock uint64
expiryBlock uint64
waitForBlockFn waitForBlockFn
}
func newHeartbeatAction(
logger log.StandardLogger,
chain Chain,
executingWallet wallet,
signingExecutor heartbeatSigningExecutor,
proposal *HeartbeatProposal,
startBlock uint64,
expiryBlock uint64,
waitForBlockFn waitForBlockFn,
) *heartbeatAction {
return &heartbeatAction{
logger: logger,
chain: chain,
executingWallet: executingWallet,
signingExecutor: signingExecutor,
proposal: proposal,
startBlock: startBlock,
expiryBlock: expiryBlock,
waitForBlockFn: waitForBlockFn,
}
}
func (ha *heartbeatAction) execute() error {
// TODO: When implementing the moving funds action we should make sure
// heartbeats are not executed by unstaking clients.
walletPublicKeyHash := bitcoin.PublicKeyHash(ha.wallet().publicKey)
err := ha.chain.ValidateHeartbeatProposal(walletPublicKeyHash, ha.proposal)
if err != nil {
return fmt.Errorf("heartbeat proposal is invalid: [%v]", err)
}
messageBytes := bitcoin.ComputeHash(ha.proposal.Message[:])
messageToSign := new(big.Int).SetBytes(messageBytes[:])
// Just in case. This should never happen.
if ha.expiryBlock < heartbeatRequestTimeoutSafetyMarginBlocks {
return fmt.Errorf("invalid proposal expiry block")
}
heartbeatCtx, cancelHeartbeatCtx := withCancelOnBlock(
context.Background(),
ha.expiryBlock-heartbeatRequestTimeoutSafetyMarginBlocks,
ha.waitForBlockFn,
)
defer cancelHeartbeatCtx()
signature, _, err := ha.signingExecutor.sign(heartbeatCtx, messageToSign, ha.startBlock)
if err != nil {
return fmt.Errorf("cannot sign heartbeat message: [%v]", err)
}
logger.Infof(
"generated signature [%s] for heartbeat message [0x%x]",
signature,
ha.proposal.Message[:],
)
return nil
}
func (ha *heartbeatAction) wallet() wallet {
return ha.executingWallet
}
func (ha *heartbeatAction) actionType() WalletActionType {
return ActionHeartbeat
}