Skip to content

Commit 4edb6e8

Browse files
refactor: switch zetaclient to confirmation params; unify inbound observation as block range based (#3496)
* switch zetaclient to confirmation params; refactor evm, btc inbound observation using block range; add confirmation related helper functions to base observer * add changelog entry * clean unused code and revert unneeded change * always use wrapper function InboundConfirmationFast() to ensure fallback to safe confirmation * fix unit test failures * add InboundConfirmationSafe and OutboundConfirmationSafe to make API consistent * remove redundant log prints which can be printed by caller * change ObserverTSSReceive -> ObserveTSSReceive * correct misspelling in changelog * simplify function signature Co-authored-by: Dmitry S <[email protected]> * function, variable remanings; using log fields, etc. * fix CI unit test failure --------- Co-authored-by: Dmitry S <[email protected]>
1 parent c2fd4bd commit 4edb6e8

23 files changed

+914
-377
lines changed

changelog.md

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
### Refactor
1212

1313
* [3381](https://github.com/zeta-chain/node/pull/3381) - split Bitcoin observer and signer into small files and organize outbound logic into reusable/testable functions; renaming, type unification, etc.
14+
* [3496](https://github.com/zeta-chain/node/pull/3496) - zetaclient uses `ConfirmationParams` instead of old `ConfirmationCount`; use block ranged based observation for btc and evm chain.
1415

1516
### Fixes
1617

cmd/zetatool/cctx/inbound.go

+17-2
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ func (c *TrackingDetails) btcInboundBallotIdentifier(ctx *context.Context) error
128128
if err != nil {
129129
return fmt.Errorf("failed to get chain params: %w", err)
130130
}
131+
createConfirmationParamsIfAbsent(chainParams)
131132

132133
cctxIdentifier, isConfirmed, err := zetatoolchains.BitcoinBallotIdentifier(
133134
ctx,
@@ -137,7 +138,7 @@ func (c *TrackingDetails) btcInboundBallotIdentifier(ctx *context.Context) error
137138
inboundHash,
138139
inboundChain.ChainId,
139140
zetaChainID,
140-
chainParams.ConfirmationCount,
141+
chainParams.InboundConfirmationSafe(),
141142
)
142143
if err != nil {
143144
return fmt.Errorf("failed to get bitcoin ballot identifier: %w", err)
@@ -161,6 +162,7 @@ func (c *TrackingDetails) evmInboundBallotIdentifier(ctx *context.Context) error
161162
if err != nil {
162163
return fmt.Errorf("failed to get chain params: %w", err)
163164
}
165+
createConfirmationParamsIfAbsent(chainParams)
164166

165167
evmClient, err := zetatoolchains.GetEvmClient(ctx, inboundChain)
166168
if err != nil {
@@ -173,7 +175,7 @@ func (c *TrackingDetails) evmInboundBallotIdentifier(ctx *context.Context) error
173175
}
174176
// Signer is unused
175177
zetaEvmClient := zetaevmclient.New(evmClient, ethtypes.NewLondonSigner(tx.ChainId()))
176-
isConfirmed, err := zetaEvmClient.IsTxConfirmed(goCtx, inboundHash, chainParams.ConfirmationCount)
178+
isConfirmed, err := zetaEvmClient.IsTxConfirmed(goCtx, inboundHash, chainParams.InboundConfirmationSafe())
177179
if err != nil {
178180
return fmt.Errorf("unable to confirm tx: %w", err)
179181
}
@@ -310,6 +312,7 @@ func (c *TrackingDetails) solanaInboundBallotIdentifier(ctx *context.Context) er
310312
if err != nil {
311313
return fmt.Errorf("failed to get chain params: %w", err)
312314
}
315+
createConfirmationParamsIfAbsent(chainParams)
313316

314317
gatewayID, _, err := solanacontracts.ParseGatewayWithPDA(chainParams.GatewayAddress)
315318
if err != nil {
@@ -371,3 +374,15 @@ func compareAddress(a string, b string) bool {
371374
lowerB := strings.ToLower(b)
372375
return strings.EqualFold(lowerA, lowerB)
373376
}
377+
378+
// createConfirmationParamsIfAbsent sets the confirmation params if they are not already set
379+
// TODO: Remove this once the confirmation migration is done
380+
// https://github.com/zeta-chain/node/issues/3466
381+
func createConfirmationParamsIfAbsent(chainParams *types.ChainParams) {
382+
if chainParams != nil && chainParams.ConfirmationParams == nil {
383+
chainParams.ConfirmationParams = &types.ConfirmationParams{
384+
SafeInboundCount: chainParams.ConfirmationCount,
385+
SafeOutboundCount: chainParams.ConfirmationCount,
386+
}
387+
}
388+
}

cmd/zetatool/cctx/outbound.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ func (c *TrackingDetails) checkEvmOutboundTx(ctx *context.Context) error {
6969
}
7070
// Signer is unused
7171
c := zetaevmclient.New(evmClient, ethtypes.NewLondonSigner(tx.ChainId()))
72-
confirmed, err := c.IsTxConfirmed(goCtx, hash, chainParams.ConfirmationCount)
72+
confirmed, err := c.IsTxConfirmed(goCtx, hash, chainParams.OutboundConfirmationSafe())
7373
if err != nil {
7474
continue
7575
}
@@ -125,7 +125,7 @@ func (c *TrackingDetails) checkBitcoinOutboundTx(ctx *context.Context) error {
125125
if err != nil {
126126
return fmt.Errorf("failed to get chain params: %w", err)
127127
}
128-
confirmationCount := chainParams.ConfirmationCount
128+
confirmationCount := chainParams.OutboundConfirmationSafe()
129129

130130
params, err := chains.BitcoinNetParamsFromChainID(outboundChain.ChainId)
131131
if err != nil {

x/observer/types/chain_params.go

+41-1
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,34 @@ func ValidateChainParams(params *ChainParams) error {
155155
return nil
156156
}
157157

158+
// InboundConfirmationSafe returns the safe number of confirmation for inbound observation.
159+
func (cp ChainParams) InboundConfirmationSafe() uint64 {
160+
return cp.ConfirmationParams.SafeInboundCount
161+
}
162+
163+
// InboundConfirmationFast returns the fast number of confirmation for inbound observation.
164+
// It falls back to safe confirmation count if fast mode is disabled.
165+
func (cp ChainParams) InboundConfirmationFast() uint64 {
166+
if cp.ConfirmationParams.FastInboundCount > 0 {
167+
return cp.ConfirmationParams.FastInboundCount
168+
}
169+
return cp.ConfirmationParams.SafeInboundCount
170+
}
171+
172+
// OutboundConfirmationSafe returns the safe number of confirmation for outbound observation.
173+
func (cp ChainParams) OutboundConfirmationSafe() uint64 {
174+
return cp.ConfirmationParams.SafeOutboundCount
175+
}
176+
177+
// OutboundConfirmationFast returns the fast number of confirmation for outbound observation.
178+
// It falls back to safe confirmation count if fast mode is disabled.
179+
func (cp ChainParams) OutboundConfirmationFast() uint64 {
180+
if cp.ConfirmationParams.FastOutboundCount > 0 {
181+
return cp.ConfirmationParams.FastOutboundCount
182+
}
183+
return cp.ConfirmationParams.SafeOutboundCount
184+
}
185+
158186
func validChainContractAddress(address string) bool {
159187
if !strings.HasPrefix(address, "0x") {
160188
return false
@@ -435,5 +463,17 @@ func ChainParamsEqual(params1, params2 ChainParams) bool {
435463
params1.MinObserverDelegation.Equal(params2.MinObserverDelegation) &&
436464
params1.IsSupported == params2.IsSupported &&
437465
params1.GatewayAddress == params2.GatewayAddress &&
438-
params1.ConfirmationParams == params2.ConfirmationParams
466+
confirmationParamsEqual(params1.ConfirmationParams, params2.ConfirmationParams)
467+
}
468+
469+
// confirmationParamsEqual returns true if two confirmation params are equal
470+
func confirmationParamsEqual(a, b *ConfirmationParams) bool {
471+
if a == b {
472+
return true
473+
}
474+
if (a == nil) || (b == nil) {
475+
return false
476+
}
477+
478+
return *a == *b
439479
}

x/observer/types/chain_params_test.go

+152-3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"github.com/stretchr/testify/suite"
99
. "gopkg.in/check.v1"
1010

11+
"github.com/zeta-chain/node/testutil/sample"
1112
"github.com/zeta-chain/node/x/observer/types"
1213
)
1314

@@ -48,9 +49,105 @@ func TestUpdateChainParamsSuiteSuite(t *testing.T) {
4849
}
4950

5051
func TestChainParamsEqual(t *testing.T) {
51-
params := types.GetDefaultChainParams()
52-
require.True(t, types.ChainParamsEqual(*params.ChainParams[0], *params.ChainParams[0]))
53-
require.False(t, types.ChainParamsEqual(*params.ChainParams[0], *params.ChainParams[1]))
52+
params := sample.ChainParams(1)
53+
54+
require.True(t, types.ChainParamsEqual(*params, *params))
55+
56+
// ChainId matters
57+
copy := copyParams(params)
58+
copy.ChainId = 2
59+
require.False(t, types.ChainParamsEqual(*params, *copy))
60+
61+
// ConfirmationCount matters
62+
copy = copyParams(params)
63+
copy.ConfirmationCount = params.ConfirmationCount + 1
64+
require.False(t, types.ChainParamsEqual(*params, *copy))
65+
66+
// GasPriceTicker matters
67+
copy = copyParams(params)
68+
copy.GasPriceTicker = params.GasPriceTicker + 1
69+
require.False(t, types.ChainParamsEqual(*params, *copy))
70+
71+
// InboundTicker matters
72+
copy = copyParams(params)
73+
copy.InboundTicker = params.InboundTicker + 1
74+
require.False(t, types.ChainParamsEqual(*params, *copy))
75+
76+
// OutboundTicker matters
77+
copy = copyParams(params)
78+
copy.OutboundTicker = params.OutboundTicker + 1
79+
require.False(t, types.ChainParamsEqual(*params, *copy))
80+
81+
// WatchUtxoTicker matters
82+
copy = copyParams(params)
83+
copy.WatchUtxoTicker = params.WatchUtxoTicker + 1
84+
require.False(t, types.ChainParamsEqual(*params, *copy))
85+
86+
// ZetaTokenContractAddress matters
87+
copy = copyParams(params)
88+
copy.ZetaTokenContractAddress = "0x_something_else"
89+
require.False(t, types.ChainParamsEqual(*params, *copy))
90+
91+
// ConnectorContractAddress matters
92+
copy = copyParams(params)
93+
copy.ConnectorContractAddress = "0x_something_else"
94+
require.False(t, types.ChainParamsEqual(*params, *copy))
95+
96+
// Erc20CustodyContractAddress matters
97+
copy = copyParams(params)
98+
copy.Erc20CustodyContractAddress = "0x_something_else"
99+
require.False(t, types.ChainParamsEqual(*params, *copy))
100+
101+
// OutboundScheduleInterval matters
102+
copy = copyParams(params)
103+
copy.OutboundScheduleInterval = params.OutboundScheduleInterval + 1
104+
require.False(t, types.ChainParamsEqual(*params, *copy))
105+
106+
// OutboundScheduleLookahead matters
107+
copy = copyParams(params)
108+
copy.OutboundScheduleLookahead = params.OutboundScheduleLookahead + 1
109+
require.False(t, types.ChainParamsEqual(*params, *copy))
110+
111+
// BallotThreshold matters
112+
copy = copyParams(params)
113+
copy.BallotThreshold = params.BallotThreshold.Add(sdkmath.LegacySmallestDec())
114+
require.False(t, types.ChainParamsEqual(*params, *copy))
115+
116+
// MinObserverDelegation matters
117+
copy = copyParams(params)
118+
copy.MinObserverDelegation = params.MinObserverDelegation.Add(sdkmath.LegacySmallestDec())
119+
require.False(t, types.ChainParamsEqual(*params, *copy))
120+
121+
// IsSupported matters
122+
copy = copyParams(params)
123+
copy.IsSupported = !params.IsSupported
124+
require.False(t, types.ChainParamsEqual(*params, *copy))
125+
126+
// GatewayAddress matters
127+
copy = copyParams(params)
128+
copy.GatewayAddress = "0x_something_else"
129+
require.False(t, types.ChainParamsEqual(*params, *copy))
130+
131+
// ConfirmationParams matters
132+
copy = copyParams(params)
133+
copy.ConfirmationParams = nil
134+
require.False(t, types.ChainParamsEqual(*params, *copy))
135+
136+
copy = copyParams(params)
137+
copy.ConfirmationParams.SafeInboundCount = params.ConfirmationParams.SafeInboundCount + 1
138+
require.False(t, types.ChainParamsEqual(*params, *copy))
139+
140+
copy = copyParams(params)
141+
copy.ConfirmationParams.FastInboundCount = params.ConfirmationParams.FastInboundCount + 1
142+
require.False(t, types.ChainParamsEqual(*params, *copy))
143+
144+
copy = copyParams(params)
145+
copy.ConfirmationParams.SafeOutboundCount = params.ConfirmationParams.SafeOutboundCount + 1
146+
require.False(t, types.ChainParamsEqual(*params, *copy))
147+
148+
copy = copyParams(params)
149+
copy.ConfirmationParams.FastOutboundCount = params.ConfirmationParams.FastOutboundCount + 1
150+
require.False(t, types.ChainParamsEqual(*params, *copy))
54151
}
55152

56153
func (s *UpdateChainParamsSuite) SetupTest() {
@@ -171,6 +268,58 @@ func (s *UpdateChainParamsSuite) TestCoreContractAddresses() {
171268
require.NotNil(s.T(), err)
172269
}
173270

271+
func Test_InboundConfirmationSafe(t *testing.T) {
272+
cp := sample.ChainParams(1)
273+
274+
// set and check safe inbound count
275+
cp.ConfirmationParams.SafeInboundCount = 10
276+
require.Equal(t, uint64(10), cp.InboundConfirmationSafe())
277+
}
278+
279+
func Test_OutboundConfirmationSafe(t *testing.T) {
280+
cp := sample.ChainParams(1)
281+
282+
// set and check safe outbound count
283+
cp.ConfirmationParams.SafeOutboundCount = 10
284+
require.Equal(t, uint64(10), cp.OutboundConfirmationSafe())
285+
}
286+
287+
func Test_InboundConfirmationFast(t *testing.T) {
288+
t.Run("should return fast inbound confirmation count if enabled", func(t *testing.T) {
289+
cp := sample.ChainParams(1)
290+
cp.ConfirmationParams.SafeInboundCount = 2
291+
cp.ConfirmationParams.FastInboundCount = 1
292+
confirmation := cp.InboundConfirmationFast()
293+
require.Equal(t, uint64(1), confirmation)
294+
})
295+
296+
t.Run("should fallback to safe inbound confirmation count if fast confirmation is disabled", func(t *testing.T) {
297+
cp := sample.ChainParams(1)
298+
cp.ConfirmationParams.SafeInboundCount = 2
299+
cp.ConfirmationParams.FastInboundCount = 0
300+
confirmation := cp.InboundConfirmationFast()
301+
require.Equal(t, uint64(2), confirmation)
302+
})
303+
}
304+
305+
func Test_OutboundConfirmationFast(t *testing.T) {
306+
t.Run("should return fast outbound confirmation count if enabled", func(t *testing.T) {
307+
cp := sample.ChainParams(1)
308+
cp.ConfirmationParams.SafeOutboundCount = 2
309+
cp.ConfirmationParams.FastOutboundCount = 1
310+
confirmation := cp.OutboundConfirmationFast()
311+
require.Equal(t, uint64(1), confirmation)
312+
})
313+
314+
t.Run("should fallback to safe outbound confirmation count if fast confirmation is disabled", func(t *testing.T) {
315+
cp := sample.ChainParams(1)
316+
cp.ConfirmationParams.SafeOutboundCount = 2
317+
cp.ConfirmationParams.FastOutboundCount = 0
318+
confirmation := cp.OutboundConfirmationFast()
319+
require.Equal(t, uint64(2), confirmation)
320+
})
321+
}
322+
174323
func (s *UpdateChainParamsSuite) Validate(params *types.ChainParams) {
175324
copy := copyParams(params)
176325
copy.ConfirmationCount = 0
+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package base
2+
3+
// GetScanRangeInboundSafe calculates the block range to scan using inbound safe confirmation count.
4+
// It returns a range of blocks [from, end (exclusive)) that need to be scanned.
5+
func (ob *Observer) GetScanRangeInboundSafe(blockLimit uint64) (from uint64, end uint64) {
6+
lastBlock := ob.LastBlock()
7+
lastScanned := ob.LastBlockScanned()
8+
confirmation := ob.ChainParams().InboundConfirmationSafe()
9+
10+
return calcUnscannedBlockRange(lastBlock, lastScanned, confirmation, blockLimit)
11+
}
12+
13+
// GetScanRangeInboundFast calculates the block range to scan using inbound fast confirmation count.
14+
// It returns a range of blocks [from, end (exclusive)) that need to be scanned.
15+
func (ob *Observer) GetScanRangeInboundFast(blockLimit uint64) (from uint64, end uint64) {
16+
lastBlock := ob.LastBlock()
17+
lastScanned := ob.LastBlockScanned()
18+
confirmation := ob.ChainParams().InboundConfirmationFast()
19+
20+
return calcUnscannedBlockRange(lastBlock, lastScanned, confirmation, blockLimit)
21+
}
22+
23+
// IsBlockConfirmedForInboundSafe checks if the block number is confirmed using inbound safe confirmation count.
24+
func (ob *Observer) IsBlockConfirmedForInboundSafe(blockNumber uint64) bool {
25+
lastBlock := ob.LastBlock()
26+
confirmation := ob.ChainParams().InboundConfirmationSafe()
27+
return isBlockConfirmed(blockNumber, confirmation, lastBlock)
28+
}
29+
30+
// IsBlockConfirmedForInboundFast checks if the block number is confirmed using inbound fast confirmation count.
31+
// It falls back to safe confirmation count if fast confirmation is disabled.
32+
func (ob *Observer) IsBlockConfirmedForInboundFast(blockNumber uint64) bool {
33+
lastBlock := ob.LastBlock()
34+
confirmation := ob.ChainParams().InboundConfirmationFast()
35+
return isBlockConfirmed(blockNumber, confirmation, lastBlock)
36+
}
37+
38+
// IsBlockConfirmedForOutboundSafe checks if the block number is confirmed using outbound safe confirmation count.
39+
func (ob *Observer) IsBlockConfirmedForOutboundSafe(blockNumber uint64) bool {
40+
lastBlock := ob.LastBlock()
41+
confirmation := ob.ChainParams().OutboundConfirmationSafe()
42+
return isBlockConfirmed(blockNumber, confirmation, lastBlock)
43+
}
44+
45+
// IsBlockConfirmedForOutboundFast checks if the block number is confirmed using outbound fast confirmation count.
46+
// It falls back to safe confirmation count if fast confirmation is disabled.
47+
func (ob *Observer) IsBlockConfirmedForOutboundFast(blockNumber uint64) bool {
48+
lastBlock := ob.LastBlock()
49+
confirmation := ob.ChainParams().OutboundConfirmationFast()
50+
return isBlockConfirmed(blockNumber, confirmation, lastBlock)
51+
}
52+
53+
// calcUnscannedBlockRange calculates the unscanned block range [from, end (exclusive)) within given block limit.
54+
//
55+
// example 1: given lastBlock = 99, lastScanned = 90, confirmation = 10, then no unscanned block
56+
// example 2: given lastBlock = 100, lastScanned = 90, confirmation = 10, then 1 unscanned block (block 91)
57+
func calcUnscannedBlockRange(lastBlock, lastScanned, confirmation, blockLimit uint64) (from uint64, end uint64) {
58+
// got unscanned blocks or not?
59+
if lastBlock < lastScanned+confirmation {
60+
return 0, 0
61+
}
62+
63+
// calculate the highest confirmed block
64+
// example: given lastBlock = 101, confirmation = 10, then the highest confirmed block is 92
65+
highestConfirmed := lastBlock - confirmation + 1
66+
67+
// calculate a range of unscanned blocks within block limit
68+
from = lastScanned + 1
69+
end = from + blockLimit
70+
71+
// 'end' is exclusive, so ensure it is not greater than (highestConfirmed+1)
72+
if end > highestConfirmed+1 {
73+
end = highestConfirmed + 1
74+
}
75+
76+
return from, end
77+
}
78+
79+
// isBlockConfirmed checks if the block number is confirmed.
80+
//
81+
// Note: block 100 is confirmed if the last block is 100 and confirmation count is 1.
82+
func isBlockConfirmed(blockNumber, confirmation, lastBlock uint64) bool {
83+
confHeight := blockNumber + confirmation - 1
84+
return lastBlock >= confHeight
85+
}

0 commit comments

Comments
 (0)