Skip to content

Commit

Permalink
Add missing heartbeat proposal validation
Browse files Browse the repository at this point in the history
  • Loading branch information
lukasz-zimnoch committed Dec 6, 2023
1 parent 4759203 commit d34b9e8
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 21 deletions.
28 changes: 27 additions & 1 deletion pkg/tbtc/chain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ type localChain struct {
redemptionProposalValidationsMutex sync.Mutex
redemptionProposalValidations map[[32]byte]bool

heartbeatProposalValidationsMutex sync.Mutex
heartbeatProposalValidations map[[16]byte]bool

blockCounter chain.BlockCounter
operatorPrivateKey *operator.PrivateKey
}
Expand Down Expand Up @@ -827,7 +830,29 @@ func (lc *localChain) ValidateHeartbeatProposal(
walletPublicKeyHash [20]byte,
proposal *HeartbeatProposal,
) error {
panic("unsupported")
lc.heartbeatProposalValidationsMutex.Lock()
defer lc.heartbeatProposalValidationsMutex.Unlock()

result, ok := lc.heartbeatProposalValidations[proposal.Message]
if !ok {
return fmt.Errorf("validation result unknown")
}

if !result {
return fmt.Errorf("validation failed")
}

return nil
}

func (lc *localChain) setHeartbeatProposalValidationResult(
proposal *HeartbeatProposal,
result bool,
) {
lc.heartbeatProposalValidationsMutex.Lock()
defer lc.heartbeatProposalValidationsMutex.Unlock()

lc.heartbeatProposalValidations[proposal.Message] = result
}

// Connect sets up the local chain.
Expand Down Expand Up @@ -865,6 +890,7 @@ func ConnectWithKey(
depositSweepProposalValidations: make(map[[32]byte]bool),
pendingRedemptionRequests: make(map[[32]byte]*RedemptionRequest),
redemptionProposalValidations: make(map[[32]byte]bool),
heartbeatProposalValidations: make(map[[16]byte]bool),
blockCounter: blockCounter,
operatorPrivateKey: operatorPrivateKey,
}
Expand Down
31 changes: 22 additions & 9 deletions pkg/tbtc/heartbeat.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,29 +50,35 @@ type heartbeatSigningExecutor interface {
// heartbeatAction is a walletAction implementation handling heartbeat requests
// from the wallet coordinator.
type heartbeatAction struct {
logger log.StandardLogger
logger log.StandardLogger
chain Chain

executingWallet wallet
signingExecutor heartbeatSigningExecutor
message []byte
startBlock uint64
expiryBlock uint64
waitForBlockFn waitForBlockFn

proposal *HeartbeatProposal
startBlock uint64
expiryBlock uint64

waitForBlockFn waitForBlockFn
}

func newHeartbeatAction(
logger log.StandardLogger,
chain Chain,
executingWallet wallet,
signingExecutor heartbeatSigningExecutor,
message []byte,
proposal *HeartbeatProposal,
startBlock uint64,
expiryBlock uint64,
waitForBlockFn waitForBlockFn,
) *heartbeatAction {
return &heartbeatAction{
logger: logger,
chain: chain,
executingWallet: executingWallet,
signingExecutor: signingExecutor,
message: message,
proposal: proposal,
startBlock: startBlock,
expiryBlock: expiryBlock,
waitForBlockFn: waitForBlockFn,
Expand All @@ -83,7 +89,14 @@ func (ha *heartbeatAction) execute() error {
// TODO: When implementing the moving funds action we should make sure
// heartbeats are not executed by unstaking clients.

messageBytes := bitcoin.ComputeHash(ha.message)
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.
Expand All @@ -106,7 +119,7 @@ func (ha *heartbeatAction) execute() error {
logger.Infof(
"generated signature [%s] for heartbeat message [0x%x]",
signature,
ha.message,
ha.proposal.Message[:],
)

return nil
Expand Down
55 changes: 45 additions & 10 deletions pkg/tbtc/heartbeat_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,42 @@ import (
)

func TestHeartbeatAction_HappyPath(t *testing.T) {
startBlock := uint64(10)
expiryBlock := startBlock + heartbeatProposalValidityBlocks
messageToSign, err := hex.DecodeString("FFFFFFFFFFFFFFFF0000000000000001")
walletPublicKeyHex, err := hex.DecodeString(
"0471e30bca60f6548d7b42582a478ea37ada63b402af7b3ddd57f0c95bb6843175" +
"aa0d2053a91a050a6797d85c38f2909cb7027f2344a01986aa2f9f8ca7a0c289",
)
if err != nil {
t.Fatal(err)
}

startBlock := uint64(10)
expiryBlock := startBlock + heartbeatProposalValidityBlocks

proposal := &HeartbeatProposal{
Message: [16]byte{
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
},
}

// sha256(sha256(messageToSign))
sha256d, err := hex.DecodeString("38d30dacec5083c902952ce99fc0287659ad0b1ca2086827a8e78b0bef2c8bc1")
if err != nil {
t.Fatal(err)
}

hostChain := Connect()
hostChain.setHeartbeatProposalValidationResult(proposal, true)

mockExecutor := &mockHeartbeatSigningExecutor{}
action := newHeartbeatAction(
logger,
wallet{},
hostChain,
wallet{
publicKey: unmarshalPublicKey(walletPublicKeyHex),
},
mockExecutor,
messageToSign,
proposal,
startBlock,
expiryBlock,
func(ctx context.Context, blockHeight uint64) error {
Expand Down Expand Up @@ -56,21 +74,38 @@ func TestHeartbeatAction_HappyPath(t *testing.T) {
}

func TestHeartbeatAction_SigningError(t *testing.T) {
startBlock := uint64(10)
expiryBlock := startBlock + heartbeatProposalValidityBlocks
messageToSign, err := hex.DecodeString("FFFFFFFFFFFFFFFF0000000000000001")
walletPublicKeyHex, err := hex.DecodeString(
"0471e30bca60f6548d7b42582a478ea37ada63b402af7b3ddd57f0c95bb6843175" +
"aa0d2053a91a050a6797d85c38f2909cb7027f2344a01986aa2f9f8ca7a0c289",
)
if err != nil {
t.Fatal(err)
}

startBlock := uint64(10)
expiryBlock := startBlock + heartbeatProposalValidityBlocks

proposal := &HeartbeatProposal{
Message: [16]byte{
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
},
}

hostChain := Connect()
hostChain.setHeartbeatProposalValidationResult(proposal, true)

mockExecutor := &mockHeartbeatSigningExecutor{}
mockExecutor.shouldFail = true

action := newHeartbeatAction(
logger,
wallet{},
hostChain,
wallet{
publicKey: unmarshalPublicKey(walletPublicKeyHex),
},
mockExecutor,
messageToSign,
proposal,
startBlock,
expiryBlock,
func(ctx context.Context, blockHeight uint64) error {
Expand Down
3 changes: 2 additions & 1 deletion pkg/tbtc/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -454,9 +454,10 @@ func (n *node) handleHeartbeatProposal(

action := newHeartbeatAction(
walletActionLogger,
n.chain,
wallet,
signingExecutor,
proposal.Message[:],
proposal,
startBlock,
expiryBlock,
n.waitForBlockHeight,
Expand Down

0 comments on commit d34b9e8

Please sign in to comment.