Skip to content

Commit ec9a86e

Browse files
committed
Merge branch '311-aggregator-wait-for-receipt-for-1-minute-if-not-bump-the-fee-v2' into test-aggregator-bump-fee
2 parents 41ce36a + dbc7d60 commit ec9a86e

File tree

7 files changed

+181
-38
lines changed

7 files changed

+181
-38
lines changed

core/chainio/avs_writer.go

Lines changed: 11 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -93,54 +93,28 @@ func (w *AvsWriter) SendAggregatedResponse(batchIdentifierHash [32]byte, batchMe
9393
txNonce := new(big.Int).SetUint64(tx.Nonce())
9494
txOpts.NoSend = false
9595
txOpts.Nonce = txNonce
96+
var i uint64 = 1
9697

97-
// Send the transaction
98-
var maxRetries uint64 = 5
99-
var i uint64
100-
for i = 1; i < maxRetries; i++ {
101-
// bump the fee here
102-
// factor = (100 + i * 10) / 100, so 1,1 <= x <= 1,5
103-
factor := (new(big.Int).Add(big.NewInt(100), new(big.Int).Mul(big.NewInt(int64(i)), big.NewInt(10))))
104-
gasPrice := new(big.Int).Mul(tx.GasPrice(), factor)
105-
gasPrice = gasPrice.Div(gasPrice, big.NewInt(100))
98+
beforeTransaction := func(gasPrice *big.Int) error {
10699
txOpts.GasPrice = gasPrice
107-
108-
w.logger.Infof("Sending ResponseToTask transaction for %vth with a gas price of %v", i, txOpts.GasPrice)
100+
w.logger.Infof("Sending ResponseToTask transaction with a gas price of %v", txOpts.GasPrice)
109101
err = w.checkRespondToTaskFeeLimit(tx, txOpts, batchIdentifierHash, senderAddress)
110-
if err != nil {
111-
return nil, err
112-
}
102+
return err
103+
}
113104

105+
executeTransaction := func(gasPrice *big.Int) (*types.Transaction, error) {
114106
tx, err = w.AvsContractBindings.ServiceManager.RespondToTaskV2(&txOpts, batchMerkleRoot, senderAddress, nonSignerStakesAndSignature, new(big.Int).SetUint64(i))
115107
if err != nil {
116108
// Retry with fallback
117109
tx, err = w.AvsContractBindings.ServiceManagerFallback.RespondToTaskV2(&txOpts, batchMerkleRoot, senderAddress, nonSignerStakesAndSignature, new(big.Int).SetUint64(i))
118-
if err != nil {
119-
return nil, err
120-
}
110+
i++
111+
return tx, err
121112
}
122-
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*2000)
123-
defer cancel()
124-
receipt, err := utils.WaitForTransactionReceipt(w.Client, ctx, tx.Hash())
125-
126-
if receipt != nil {
127-
if receipt.Status == 0 {
128-
return receipt, fmt.Errorf("transaction failed")
129-
} else {
130-
// transaction was included in block
131-
return receipt, nil
132-
}
133-
}
134-
135-
// this means we have reached the timeout (after three blocks it hasn't been included)
136-
// so we try again by bumping the fee to make sure its included
137-
if err != nil {
138-
continue
139-
}
140-
113+
i++
114+
return tx, err
141115
}
142116

143-
return nil, fmt.Errorf("could not send transaction")
117+
return utils.SendTransactionWithInfiniteRetryAndBumpingGasPrice(beforeTransaction, executeTransaction, w.Client, tx.GasPrice())
144118
}
145119

146120
func (w *AvsWriter) checkRespondToTaskFeeLimit(tx *types.Transaction, txOpts bind.TransactOpts, batchIdentifierHash [32]byte, senderAddress [20]byte) error {

core/connection.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package connection
2+
3+
import (
4+
"time"
5+
6+
"github.com/cenkalti/backoff/v4"
7+
)
8+
9+
type PermanentError struct {
10+
Inner error
11+
}
12+
13+
func (e PermanentError) Error() string { return e.Inner.Error() }
14+
func (e PermanentError) Unwrap() error {
15+
return e.Inner
16+
}
17+
func (e PermanentError) Is(err error) bool {
18+
_, ok := err.(PermanentError)
19+
return ok
20+
}
21+
22+
// Same as Retry only that the functionToRetry can return a value upon correct execution
23+
func RetryWithData[T any](functionToRetry func() (*T, error), minDelay uint64, factor float64, maxTries uint64) (*T, error) {
24+
i := 0
25+
f := func() (*T, error) {
26+
val, err := functionToRetry()
27+
i++
28+
if perm, ok := err.(PermanentError); err != nil && ok {
29+
return nil, backoff.Permanent(perm.Inner)
30+
}
31+
return val, err
32+
}
33+
34+
randomOption := backoff.WithRandomizationFactor(0)
35+
36+
initialRetryOption := backoff.WithInitialInterval(time.Millisecond * time.Duration(minDelay))
37+
multiplierOption := backoff.WithMultiplier(factor)
38+
expBackoff := backoff.NewExponentialBackOff(randomOption, multiplierOption, initialRetryOption)
39+
var maxRetriesBackoff backoff.BackOff
40+
41+
if maxTries > 0 {
42+
maxRetriesBackoff = backoff.WithMaxRetries(expBackoff, maxTries)
43+
} else {
44+
maxRetriesBackoff = expBackoff
45+
}
46+
47+
return backoff.RetryWithData(f, maxRetriesBackoff)
48+
}
49+
50+
// Retries a given function in an exponential backoff manner.
51+
// It will retry calling the function while it returns an error, until the max retries.
52+
// If maxTries == 0 then the retry function will run indefinitely until success
53+
// from the configuration are reached, or until a `PermanentError` is returned.
54+
// The function to be retried should return `PermanentError` when the condition for stop retrying
55+
// is met.
56+
func Retry(functionToRetry func() error, minDelay uint64, factor float64, maxTries uint64) error {
57+
i := 0
58+
f := func() error {
59+
err := functionToRetry()
60+
i++
61+
if perm, ok := err.(PermanentError); err != nil && ok {
62+
return backoff.Permanent(perm.Inner)
63+
}
64+
return err
65+
}
66+
67+
randomOption := backoff.WithRandomizationFactor(0)
68+
69+
initialRetryOption := backoff.WithInitialInterval(time.Millisecond * time.Duration(minDelay))
70+
multiplierOption := backoff.WithMultiplier(factor)
71+
expBackoff := backoff.NewExponentialBackOff(randomOption, multiplierOption, initialRetryOption)
72+
maxRetriesBackoff := backoff.WithMaxRetries(expBackoff, maxTries)
73+
74+
return backoff.Retry(f, maxRetriesBackoff)
75+
}

core/connection_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package connection_test
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
connection "github.com/yetanotherco/aligned_layer/core"
8+
)
9+
10+
func DummyFunction(x uint64) (uint64, error) {
11+
fmt.Println("Doing some work...")
12+
if x == 42 {
13+
return 0, connection.PermanentError{Inner: fmt.Errorf("Permanent error!")}
14+
} else if x < 42 {
15+
return 0, fmt.Errorf("Transient error!")
16+
}
17+
return x, nil
18+
}
19+
20+
func TestRetryWithData(t *testing.T) {
21+
function := func() (*uint64, error) {
22+
x, err := DummyFunction(43)
23+
return &x, err
24+
}
25+
data, err := connection.RetryWithData(function, 1000, 2, 3)
26+
if err != nil {
27+
t.Errorf("Retry error!: %s", err)
28+
} else {
29+
fmt.Printf("DATA: %d\n", data)
30+
}
31+
}
32+
33+
func TestRetry(t *testing.T) {
34+
function := func() error {
35+
_, err := DummyFunction(43)
36+
return err
37+
}
38+
err := connection.Retry(function, 1000, 2, 3)
39+
if err != nil {
40+
t.Errorf("Retry error!: %s", err)
41+
}
42+
}

core/utils/eth_client_utils.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package utils
22

33
import (
44
"context"
5+
"math/big"
56
"time"
67

78
"fmt"
@@ -10,6 +11,7 @@ import (
1011
eigentypes "github.com/Layr-Labs/eigensdk-go/types"
1112
gethcommon "github.com/ethereum/go-ethereum/common"
1213
"github.com/ethereum/go-ethereum/core/types"
14+
connection "github.com/yetanotherco/aligned_layer/core"
1315
)
1416

1517
const maxRetries = 25
@@ -47,3 +49,51 @@ func BytesToQuorumThresholdPercentages(quorumThresholdPercentagesBytes []byte) e
4749
}
4850
return quorumThresholdPercentages
4951
}
52+
53+
// Very basic algorithm to calculate the gasPrice bump based on the currentGasPrice and retry iteration.
54+
// It adds a i/10 percentage to the current prices, where i represents the iteration.
55+
func CalculateGasPriceBumpBasedOnRetry(currentGasPrice *big.Int, iteration int) *big.Int {
56+
factor := (new(big.Int).Add(big.NewInt(100), new(big.Int).Mul(big.NewInt(int64(iteration)), big.NewInt(10))))
57+
gasPrice := new(big.Int).Mul(currentGasPrice, factor)
58+
gasPrice = gasPrice.Div(gasPrice, big.NewInt(100))
59+
60+
return gasPrice
61+
}
62+
63+
// Sends a transaction and waits for the receipt for three blocks, if not received
64+
// it will try again bumping the gas price based on `CalculateGasPriceBumpBasedOnRetry`
65+
// and pass it to beforeTransaction and executeTransaction (make sure you update the txOpts with the new price)
66+
// This process happens indefinitely until we get the receipt or the receipt status is an err.
67+
func SendTransactionWithInfiniteRetryAndBumpingGasPrice(beforeTransaction func(*big.Int) error, executeTransaction func(*big.Int) (*types.Transaction, error), client eth.InstrumentedClient, baseGasPrice *big.Int) (*types.Receipt, error) {
68+
i := 0
69+
sendTransaction := func() (*types.Receipt, error) {
70+
i++
71+
gasPrice := CalculateGasPriceBumpBasedOnRetry(baseGasPrice, i)
72+
73+
err := beforeTransaction(gasPrice)
74+
if err != nil {
75+
return nil, err
76+
}
77+
78+
tx, err := executeTransaction(gasPrice)
79+
if err != nil {
80+
return nil, err
81+
}
82+
83+
ctx, cancel := context.WithTimeout(context.Background(), time.Second*36)
84+
defer cancel()
85+
receipt, err := WaitForTransactionReceipt(client, ctx, tx.Hash())
86+
87+
if receipt != nil {
88+
return receipt, nil
89+
}
90+
// if we are here, this means we have reached the timeout (after three blocks it hasn't been included)
91+
// so we try again by bumping the fee to make sure its included
92+
if err != nil {
93+
return nil, err
94+
}
95+
return nil, fmt.Errorf("transaction failed")
96+
97+
}
98+
return connection.RetryWithData(sendTransaction, 1000, 2, 0)
99+
}

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ require (
4040
github.com/bits-and-blooms/bitset v1.10.0 // indirect
4141
github.com/blang/semver/v4 v4.0.0 // indirect
4242
github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect
43+
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
4344
github.com/cespare/xxhash/v2 v2.3.0 // indirect
4445
github.com/consensys/bavard v0.1.13 // indirect
4546
github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOF
5656
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
5757
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
5858
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
59+
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
60+
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
5961
github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk=
6062
github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s=
6163
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=

operator/sp1/sp1_test.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
)
99

1010
const ProofFilePath = "../../scripts/test_files/sp1/sp1_fibonacci.proof"
11-
1211
const ElfFilePath = "../../scripts/test_files/sp1/sp1_fibonacci.elf"
1312

1413
func TestFibonacciSp1ProofVerifies(t *testing.T) {

0 commit comments

Comments
 (0)