Skip to content

Commit

Permalink
Check veto processing delay during redemption proposal generation
Browse files Browse the repository at this point in the history
The optimistic redemption upgrade introduces a veto mechanism that
enforces a processing delay on each redemption request. The exact
delay value depends on the number of objections raised against the given
redemption request. The `WalletProposalValidator` contract has been
modified to include validation of that delay factor. Here we introduce
the same for the redemption proposal generator. This ensures the generator
issues proposals that conform the on-chain validation rules and coordination
windows are not being wasted.

See: keep-network/tbtc-v2#788
  • Loading branch information
lukasz-zimnoch committed Feb 22, 2024
1 parent e278aaa commit ed9c4ae
Show file tree
Hide file tree
Showing 8 changed files with 204 additions and 6 deletions.
52 changes: 52 additions & 0 deletions pkg/chain/ethereum/tbtc.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ type TbtcChain struct {
walletRegistry *ecdsacontract.WalletRegistry
sortitionPool *ecdsacontract.EcdsaSortitionPool
walletProposalValidator *tbtccontract.WalletProposalValidator
redemptionWatchtower *tbtccontract.RedemptionWatchtower
}

// NewTbtcChain construct a new instance of the TBTC-specific Ethereum
Expand Down Expand Up @@ -194,13 +195,43 @@ func newTbtcChain(
)
}

redemptionWatchtowerAddress, err := bridge.GetRedemptionWatchtower()
if err != nil {
return nil, fmt.Errorf(
"failed to get RedemptionWatchtower address from Bridge: [%v]",
err,
)
}

var redemptionWatchtower *tbtccontract.RedemptionWatchtower
if redemptionWatchtowerAddress != [20]byte{} {
redemptionWatchtower, err =
tbtccontract.NewRedemptionWatchtower(
redemptionWatchtowerAddress,
baseChain.chainID,
baseChain.key,
baseChain.client,
baseChain.nonceManager,
baseChain.miningWaiter,
baseChain.blockCounter,
baseChain.transactionMutex,
)
if err != nil {
return nil, fmt.Errorf(
"failed to attach to RedemptionWatchtower contract: [%v]",
err,
)
}
}

return &TbtcChain{
baseChain: baseChain,
bridge: bridge,
maintainerProxy: maintainerProxy,
walletRegistry: walletRegistry,
sortitionPool: sortitionPool,
walletProposalValidator: walletProposalValidator,
redemptionWatchtower: redemptionWatchtower,
}, nil
}

Expand Down Expand Up @@ -1898,3 +1929,24 @@ func (tc *TbtcChain) ValidateMovingFundsProposal(

return nil
}

func (tc *TbtcChain) GetRedemptionDelay(
walletPublicKeyHash [20]byte,
redeemerOutputScript bitcoin.Script,
) (time.Duration, error) {
if tc.redemptionWatchtower == nil {
return 0, nil
}

redemptionKey, err := tc.BuildRedemptionKey(walletPublicKeyHash, redeemerOutputScript)
if err != nil {
return 0, fmt.Errorf("cannot build redemption key: [%v]", err)
}

delay, err := tc.redemptionWatchtower.GetRedemptionDelay(redemptionKey)
if err != nil {
return 0, fmt.Errorf("cannot get redemption delay: [%v]", err)
}

return time.Duration(delay) * time.Second, nil
}
6 changes: 6 additions & 0 deletions pkg/tbtcpg/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,4 +178,10 @@ type Chain interface {
// Computes the moving funds commitment hash from the provided public key
// hashes of target wallets.
ComputeMovingFundsCommitmentHash(targetWallets [][20]byte) [32]byte

// GetRedemptionDelay returns the processing delay for the given redemption.
GetRedemptionDelay(
walletPublicKeyHash [20]byte,
redeemerOutputScript bitcoin.Script,
) (time.Duration, error)
}
32 changes: 32 additions & 0 deletions pkg/tbtcpg/chain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ type LocalChain struct {
movingFundsProposalValidations map[[32]byte]bool
movingFundsCommitmentSubmissions []*movingFundsCommitmentSubmission
operatorIDs map[chain.Address]uint32
redemptionDelays map[[32]byte]time.Duration
}

func NewLocalChain() *LocalChain {
Expand All @@ -107,6 +108,7 @@ func NewLocalChain() *LocalChain {
movingFundsProposalValidations: make(map[[32]byte]bool),
movingFundsCommitmentSubmissions: make([]*movingFundsCommitmentSubmission, 0),
operatorIDs: make(map[chain.Address]uint32),
redemptionDelays: make(map[[32]byte]time.Duration),
}
}

Expand Down Expand Up @@ -1087,6 +1089,36 @@ func (lc *LocalChain) GetMovingFundsSubmissions() []*movingFundsCommitmentSubmis
return lc.movingFundsCommitmentSubmissions
}

func (lc *LocalChain) GetRedemptionDelay(
walletPublicKeyHash [20]byte,
redeemerOutputScript bitcoin.Script,
) (time.Duration, error) {
lc.mutex.Lock()
defer lc.mutex.Unlock()

key := buildRedemptionRequestKey(walletPublicKeyHash, redeemerOutputScript)

delay, ok := lc.redemptionDelays[key]
if !ok {
return 0, fmt.Errorf("redemption delay not found")
}

return delay, nil
}

func (lc *LocalChain) SetRedemptionDelay(
walletPublicKeyHash [20]byte,
redeemerOutputScript bitcoin.Script,
delay time.Duration,
) {
lc.mutex.Lock()
defer lc.mutex.Unlock()

key := buildRedemptionRequestKey(walletPublicKeyHash, redeemerOutputScript)

lc.redemptionDelays[key] = delay
}

type MockBlockCounter struct {
mutex sync.Mutex
currentBlock uint64
Expand Down
2 changes: 2 additions & 0 deletions pkg/tbtcpg/internal/test/marshaling.go
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@ func (fprts *FindPendingRedemptionsTestScenario) UnmarshalJSON(data []byte) erro
RedeemerOutputScript string
RequestedAmount uint64
Age int64
Delay int64
}
ExpectedRedeemersOutputScripts []string
}
Expand Down Expand Up @@ -324,6 +325,7 @@ func (fprts *FindPendingRedemptionsTestScenario) UnmarshalJSON(data []byte) erro
RequestedAmount: pr.RequestedAmount,
RequestedAt: requestedAt,
RequestBlock: requestBlock,
Delay: time.Duration(pr.Delay) * time.Second,
},
)
}
Expand Down
1 change: 1 addition & 0 deletions pkg/tbtcpg/internal/test/tbtcpgtest.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ type RedemptionRequest struct {
RequestedAmount uint64
RequestedAt time.Time
RequestBlock uint64
Delay time.Duration
}

// FindPendingRedemptionsTestScenario represents a test scenario of finding
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
{
"Title": "pending redemptions with different processing delays exist",
"ChainParameters":{
"AverageBlockTime": 10,
"CurrentBlock": 100000,
"RequestTimeout": 86400,
"RequestMinAge": 3600
},
"MaxNumberOfRequests": 10,
"WalletPublicKeyHash": "0x928d992e5f5b71de51a1b40fcc4056b99a88a647",
"PendingRedemptions": [
{
"WalletPublicKeyHash": "0x928d992e5f5b71de51a1b40fcc4056b99a88a647",
"RedeemerOutputScript": "0x00140000000000000000000000000000000000000001",
"RequestedAmount": 1000000000,
"Age": 3000,
"Delay": 0
},
{
"WalletPublicKeyHash": "0x928d992e5f5b71de51a1b40fcc4056b99a88a647",
"RedeemerOutputScript": "0x00140000000000000000000000000000000000000002",
"RequestedAmount": 2000000000,
"Age": 4000,
"Delay": 0
},
{
"WalletPublicKeyHash": "0x928d992e5f5b71de51a1b40fcc4056b99a88a647",
"RedeemerOutputScript": "0x00140000000000000000000000000000000000000003",
"RequestedAmount": 3000000000,
"Age": 4000,
"Delay": 7200
},
{
"WalletPublicKeyHash": "0x928d992e5f5b71de51a1b40fcc4056b99a88a647",
"RedeemerOutputScript": "0x00140000000000000000000000000000000000000004",
"RequestedAmount": 4000000000,
"Age": 8000,
"Delay": 7200
},
{
"WalletPublicKeyHash": "0x928d992e5f5b71de51a1b40fcc4056b99a88a647",
"RedeemerOutputScript": "0x00140000000000000000000000000000000000000005",
"RequestedAmount": 5000000000,
"Age": 8000,
"Delay": 14400
},
{
"WalletPublicKeyHash": "0x928d992e5f5b71de51a1b40fcc4056b99a88a647",
"RedeemerOutputScript": "0x00140000000000000000000000000000000000000006",
"RequestedAmount": 6000000000,
"Age": 15000,
"Delay": 14400
}
],
"ExpectedRedeemersOutputScripts": [
"0x00140000000000000000000000000000000000000006",
"0x00140000000000000000000000000000000000000004",
"0x00140000000000000000000000000000000000000002"
]
}
50 changes: 44 additions & 6 deletions pkg/tbtcpg/redemptions.go
Original file line number Diff line number Diff line change
Expand Up @@ -366,15 +366,42 @@ redemptionRequestedLoop:
},
)

// Capture time now for computations.
timeNow := time.Now()

// Only redemption requests in range:
// [now - requestTimeout, now - requestMinAge]
// [now - requestTimeout, now - minAge]
// should be taken into consideration.
redemptionRequestsRangeStartTimestamp := time.Now().Add(
redemptionRequestsRangeStartTimestamp := timeNow.Add(
-time.Duration(requestTimeout) * time.Second,
)
redemptionRequestsRangeEndTimestamp := time.Now().Add(
-time.Duration(requestMinAge) * time.Second,
)
redemptionRequestsRangeEndTimestampFn := func(
redemption *RedemptionRequest,
) (time.Time, error) {
delay, err := chain.GetRedemptionDelay(
redemption.WalletPublicKeyHash,
redemption.RedeemerOutputScript,
)
if err != nil {
return time.Time{}, fmt.Errorf(
"failed to get redemption delay: [%w]",
err,
)
}

minAge := time.Duration(requestMinAge) * time.Second
if delay > minAge {
minAge = delay
}

fnLogger.Infof(
"minimum age for redemption request [%s] is [%v]",
redemption.RedemptionKey,
minAge,
)

return timeNow.Add(-minAge), nil
}

result := make([]*RedemptionRequest, 0, resultSliceCapacity)
for _, pendingRedemption := range pendingRedemptions {
Expand All @@ -391,8 +418,19 @@ redemptionRequestedLoop:
continue
}

rangeEndTimestamp, err := redemptionRequestsRangeEndTimestampFn(
pendingRedemption,
)
if err != nil {
return nil, fmt.Errorf(
"cannot get minimum age for redemption request [%s]: [%w]",
pendingRedemption.RedemptionKey,
err,
)
}

// Check if enough time elapsed since the redemption request.
if pendingRedemption.RequestedAt.After(redemptionRequestsRangeEndTimestamp) {
if pendingRedemption.RequestedAt.After(rangeEndTimestamp) {
fnLogger.Infof(
"redemption request [%s] is not old enough",
pendingRedemption.RedemptionKey,
Expand Down
7 changes: 7 additions & 0 deletions pkg/tbtcpg/redemptions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,13 @@ func TestRedemptionAction_FindPendingRedemptions(t *testing.T) {
RequestedAt: pendingRedemption.RequestedAt,
},
)

// Record the redemption processing delay.
tbtcChain.SetRedemptionDelay(
pendingRedemption.WalletPublicKeyHash,
pendingRedemption.RedeemerOutputScript,
pendingRedemption.Delay,
)
}

task := tbtcpg.NewRedemptionTask(tbtcChain, nil)
Expand Down

0 comments on commit ed9c4ae

Please sign in to comment.