Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Heartbeats: Notify operator inactivity on failure #3800

Merged
merged 41 commits into from
May 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
531d62a
Prevented heartbeat execution if operator is unstaking
tomaszslabon Apr 8, 2024
d3ec5a9
Returned number of active members count when signing
tomaszslabon Apr 8, 2024
6c57105
Added counter of consecutive heartbeat failure
tomaszslabon Apr 8, 2024
ca9ba4e
Added inactivity operator notifier
tomaszslabon Apr 16, 2024
e2b4f34
Added heartbeat failure counter per wallet
tomaszslabon Apr 19, 2024
d6e9219
Added inactivity claim executor caching
tomaszslabon Apr 22, 2024
147d82d
Handled publishing jobs for signers
tomaszslabon Apr 22, 2024
36c20d2
Added claim signature message
tomaszslabon Apr 24, 2024
17df0d8
Added inactivity claim signature hash calculation
tomaszslabon Apr 24, 2024
bd181a9
Implemented inactivity claim submitting
tomaszslabon Apr 24, 2024
abad9ad
Improved inactivity claim submission process
tomaszslabon Apr 29, 2024
9b1786a
Updated package structure
tomaszslabon May 2, 2024
6c9fad6
Modified the required number of signatures during inactivity claim
tomaszslabon May 3, 2024
4db1804
Fixed build problem
tomaszslabon May 3, 2024
c4b504e
Fixed issues related to launching inactivity submission
tomaszslabon May 3, 2024
ae51571
Improved logging
tomaszslabon May 6, 2024
2f3f7df
Improved logging
tomaszslabon May 6, 2024
2695f77
Refactored wallet heartbeat failure counters
tomaszslabon May 6, 2024
78960d2
Improved inactivity-related logging
tomaszslabon May 7, 2024
e9e6b0f
Adjusted received messages condition
tomaszslabon May 7, 2024
c0ec7f7
Renames
tomaszslabon May 7, 2024
acd49ee
Moved heartbeat failure counter
tomaszslabon May 7, 2024
6fb3bb7
Simplified code
tomaszslabon May 7, 2024
e25efaa
Updated error message
tomaszslabon May 7, 2024
278c2eb
Added unit tests for inactivity claim signer
tomaszslabon May 7, 2024
0e3b9e5
Renamed variables related to inactivity claim
tomaszslabon May 8, 2024
4105cd1
Cleaned file structure for inactivity package
tomaszslabon May 8, 2024
b2fcba0
Renamed functions related to publishing inactivity claim
tomaszslabon May 8, 2024
18fe362
Added tbtc chain interface for staking
tomaszslabon May 9, 2024
90b3d91
Added index sorting at inactivity claim creation
tomaszslabon May 9, 2024
e76d60d
Fixed concurrency error
tomaszslabon May 9, 2024
9ad6371
Improved inactivity time synchronization
tomaszslabon May 9, 2024
4eafccb
Context-related renames
tomaszslabon May 13, 2024
7137d4f
Fixed typos
tomaszslabon May 13, 2024
8b92d0e
Added unit tests for inactivity executor
tomaszslabon May 13, 2024
60da8cc
Added unit tests for inactivity claim submitter
tomaszslabon May 13, 2024
4e32ec6
Merge branch 'main' into heartbeats-update
tomaszslabon May 13, 2024
82dc68a
Adjusted existing unit tests for heartbeats
tomaszslabon May 14, 2024
64c02b4
Added unit tests for inactivity claim signing and submitting members
tomaszslabon May 14, 2024
01d69fc
Added unit tests for inactivity submission states
tomaszslabon May 14, 2024
e5d61e7
Added check for unstaking operators
tomaszslabon May 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ COPY ./pkg/tecdsa/dkg/gen $APP_DIR/pkg/tecdsa/dkg/gen
COPY ./pkg/tecdsa/signing/gen $APP_DIR/pkg/tecdsa/signing/gen
COPY ./pkg/tecdsa/gen $APP_DIR/pkg/tecdsa/gen
COPY ./pkg/protocol/announcer/gen $APP_DIR/pkg/protocol/announcer/gen
COPY ./pkg/protocol/inactivity/gen $APP_DIR/pkg/protocol/inactivity/gen

# Environment is to download published and tagged NPM packages versions.
ARG ENVIRONMENT
Expand Down
185 changes: 184 additions & 1 deletion pkg/chain/ethereum/tbtc.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ import (
"crypto/elliptic"
"encoding/binary"
"fmt"
"github.com/keep-network/keep-common/pkg/cache"
"math/big"
"reflect"
"sort"
"time"

"github.com/keep-network/keep-common/pkg/cache"

"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
Expand All @@ -26,6 +27,7 @@ import (
"github.com/keep-network/keep-core/pkg/internal/byteutils"
"github.com/keep-network/keep-core/pkg/operator"
"github.com/keep-network/keep-core/pkg/protocol/group"
"github.com/keep-network/keep-core/pkg/protocol/inactivity"
"github.com/keep-network/keep-core/pkg/subscription"
"github.com/keep-network/keep-core/pkg/tbtc"
"github.com/keep-network/keep-core/pkg/tecdsa/dkg"
Expand Down Expand Up @@ -1001,6 +1003,187 @@ func (tc *TbtcChain) DKGParameters() (*tbtc.DKGParameters, error) {
}, nil
}

func (tc *TbtcChain) OnInactivityClaimed(
handler func(event *tbtc.InactivityClaimedEvent),
) subscription.EventSubscription {
onEvent := func(
walletID [32]byte,
nonce *big.Int,
notifier common.Address,
blockNumber uint64,
) {
handler(&tbtc.InactivityClaimedEvent{
WalletID: walletID,
Nonce: nonce,
Notifier: chain.Address(notifier.Hex()),
BlockNumber: blockNumber,
})
}

return tc.walletRegistry.InactivityClaimedEvent(nil, nil).OnEvent(onEvent)
}

func (tc *TbtcChain) AssembleInactivityClaim(
walletID [32]byte,
inactiveMembersIndices []group.MemberIndex,
signatures map[group.MemberIndex][]byte,
heartbeatFailed bool,
) (
*tbtc.InactivityClaim,
error,
) {
signingMemberIndices, signatureBytes, err := convertSignaturesToChainFormat(
signatures,
)
if err != nil {
return nil, fmt.Errorf(
"could not convert signatures to chain format: [%v]",
err,
)
}

return &tbtc.InactivityClaim{
WalletID: walletID,
InactiveMembersIndices: inactiveMembersIndices,
HeartbeatFailed: heartbeatFailed,
Signatures: signatureBytes,
SigningMembersIndices: signingMemberIndices,
}, nil
}

// convertInactivityClaimToAbiType converts the TBTC-specific inactivity claim
// to the format applicable for the WalletRegistry ABI.
func convertInactivityClaimToAbiType(
claim *tbtc.InactivityClaim,
) ecdsaabi.EcdsaInactivityClaim {
inactiveMembersIndices := make([]*big.Int, len(claim.InactiveMembersIndices))
for i, memberIndex := range claim.InactiveMembersIndices {
inactiveMembersIndices[i] = big.NewInt(int64(memberIndex))
}

signingMembersIndices := make([]*big.Int, len(claim.SigningMembersIndices))
for i, memberIndex := range claim.SigningMembersIndices {
signingMembersIndices[i] = big.NewInt(int64(memberIndex))
}

return ecdsaabi.EcdsaInactivityClaim{
WalletID: claim.WalletID,
InactiveMembersIndices: inactiveMembersIndices,
HeartbeatFailed: claim.HeartbeatFailed,
Signatures: claim.Signatures,
SigningMembersIndices: signingMembersIndices,
}
}

func (tc *TbtcChain) SubmitInactivityClaim(
claim *tbtc.InactivityClaim,
nonce *big.Int,
groupMembers []uint32,
) error {
_, err := tc.walletRegistry.NotifyOperatorInactivity(
convertInactivityClaimToAbiType(claim),
nonce,
groupMembers,
)

return err
}

func (tc *TbtcChain) CalculateInactivityClaimHash(
claim *inactivity.ClaimPreimage,
) (inactivity.ClaimHash, error) {
walletPublicKeyBytes := elliptic.Marshal(
claim.WalletPublicKey.Curve,
claim.WalletPublicKey.X,
claim.WalletPublicKey.Y,
)
// Crop the 04 prefix as the calculateInactivityClaimHash function expects
// an unprefixed 64-byte public key,
unprefixedGroupPublicKeyBytes := walletPublicKeyBytes[1:]

// The type representing inactive member index should be `big.Int` as the
// smart contract reading the calculated hash uses `uint256` for inactive
// member indexes.
inactiveMembersIndexes := make([]*big.Int, len(claim.InactiveMembersIndexes))
for i, index := range claim.InactiveMembersIndexes {
inactiveMembersIndexes[i] = big.NewInt(int64(index))
}

return calculateInactivityClaimHash(
tc.chainID,
claim.Nonce,
unprefixedGroupPublicKeyBytes,
inactiveMembersIndexes,
claim.HeartbeatFailed,
)
}

func calculateInactivityClaimHash(
chainID *big.Int,
nonce *big.Int,
walletPublicKey []byte,
inactiveMembersIndexes []*big.Int,
heartbeatFailed bool,
) (inactivity.ClaimHash, error) {
publicKeySize := 64

if len(walletPublicKey) != publicKeySize {
return inactivity.ClaimHash{}, fmt.Errorf(
"wrong wallet public key length",
)
}

uint256Type, err := abi.NewType("uint256", "uint256", nil)
if err != nil {
return inactivity.ClaimHash{}, err
}
bytesType, err := abi.NewType("bytes", "bytes", nil)
if err != nil {
return inactivity.ClaimHash{}, err
}
uint256SliceType, err := abi.NewType("uint256[]", "uint256[]", nil)
if err != nil {
return inactivity.ClaimHash{}, err
}
boolType, err := abi.NewType("bool", "bool", nil)
if err != nil {
return inactivity.ClaimHash{}, err
}

bytes, err := abi.Arguments{
{Type: uint256Type},
{Type: uint256Type},
{Type: bytesType},
{Type: uint256SliceType},
{Type: boolType},
}.Pack(
chainID,
nonce,
walletPublicKey,
inactiveMembersIndexes,
heartbeatFailed,
)
if err != nil {
return inactivity.ClaimHash{}, err
}

return inactivity.ClaimHash(crypto.Keccak256Hash(bytes)), nil
}

func (tc *TbtcChain) GetInactivityClaimNonce(
walletID [32]byte,
) (*big.Int, error) {
nonce, err := tc.walletRegistry.InactivityClaimNonce(walletID)
if err != nil {
return nil, fmt.Errorf(
"failed to get inactivity claim nonce: [%w]",
err,
)
}

return nonce, nil
}

func (tc *TbtcChain) PastDepositRevealedEvents(
filter *tbtc.DepositRevealedEventFilter,
) ([]*tbtc.DepositRevealedEvent, error) {
Expand Down
39 changes: 39 additions & 0 deletions pkg/chain/ethereum/tbtc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,45 @@ func TestCalculateDKGResultSignatureHash(t *testing.T) {
)
}

func TestCalculateInactivityClaimHash(t *testing.T) {
chainID := big.NewInt(31337)
nonce := big.NewInt(3)

walletPublicKey, err := hex.DecodeString(
"9a0544440cc47779235ccb76d669590c2cd20c7e431f97e17a1093faf03291c473e" +
"661a208a8a565ca1e384059bd2ff7ff6886df081ff1229250099d388c83df",
)
if err != nil {
t.Fatal(err)
}

inactiveMembersIndexes := []*big.Int{
big.NewInt(1), big.NewInt(2), big.NewInt(30),
}

heartbeatFailed := true

hash, err := calculateInactivityClaimHash(
chainID,
nonce,
walletPublicKey,
inactiveMembersIndexes,
heartbeatFailed,
)
if err != nil {
t.Fatal(err)
}

expectedHash := "f3210008cba186e90386a1bd0c63b6f29a67666f632350be22ce63ab39fc506e"

testutils.AssertStringsEqual(
t,
"hash",
expectedHash,
hex.EncodeToString(hash[:]),
)
}

func TestParseDkgResultValidationOutcome(t *testing.T) {
isValid, err := parseDkgResultValidationOutcome(
&struct {
Expand Down
Loading
Loading