Skip to content

Commit 922e358

Browse files
authored
receipts: FetchReceipts (#330)
1 parent 9708f4a commit 922e358

File tree

2 files changed

+159
-0
lines changed

2 files changed

+159
-0
lines changed

receipts/fetch.go

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
package receipts
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"math/big"
7+
8+
"github.com/0xsequence/ethkit/ethrpc"
9+
"github.com/0xsequence/ethkit/go-ethereum"
10+
"github.com/0xsequence/ethkit/go-ethereum/common"
11+
"github.com/0xsequence/ethkit/go-ethereum/core/types"
12+
sequence "github.com/0xsequence/go-sequence"
13+
)
14+
15+
const MaxFilterBlockRange = 10_000
16+
17+
// FetchReceipts looks up the transaction that emitted Call* events for the given
18+
// digest and returns all decoded Sequence receipts along with the native receipt.
19+
//
20+
// The `opHash` is also known as the "MetaTxnID"
21+
//
22+
// NOTE: toBlock can also be nil, in which case the latest block is used.
23+
//
24+
// Finally, please note that this method will not find meta-transactions if there is a
25+
// native transaction which fails. In that case, the Call* events are not emitted and
26+
// thus cannot be found. In such cases, you will need to look up the native transaction
27+
// receipt directly. However, this is not to be confused with where a "Call" inside
28+
// of the native transaction fails, but the native transaction itself succeeds which
29+
// is more common and works fine.
30+
func FetchReceipts(ctx context.Context, opHash common.Hash, provider *ethrpc.Provider, fromBlock, toBlock *big.Int) (Receipts, *types.Receipt, error) {
31+
if provider == nil {
32+
return Receipts{}, nil, fmt.Errorf("no provider")
33+
}
34+
35+
fromBlock_ := fromBlock
36+
if fromBlock_ == nil {
37+
fromBlock_ = new(big.Int)
38+
}
39+
40+
toBlock_ := toBlock
41+
if toBlock_ == nil {
42+
latest, err := provider.BlockNumber(ctx)
43+
if err != nil {
44+
return Receipts{}, nil, fmt.Errorf("unable to get latest block: %w", err)
45+
}
46+
toBlock_ = new(big.Int).SetUint64(latest)
47+
}
48+
49+
if new(big.Int).Sub(toBlock_, fromBlock_).Cmp(big.NewInt(MaxFilterBlockRange)) > 0 {
50+
return Receipts{}, nil, fmt.Errorf("block range %v to %v exceeds %v, reduce block range", fromBlock_, toBlock_, MaxFilterBlockRange)
51+
}
52+
53+
query := ethereum.FilterQuery{
54+
FromBlock: fromBlock,
55+
ToBlock: toBlock,
56+
Topics: [][]common.Hash{
57+
{sequence.V3CallSucceeded, sequence.V3CallFailed, sequence.V3CallAborted, sequence.V3CallSkipped},
58+
{opHash},
59+
},
60+
}
61+
62+
logs, err := provider.FilterLogs(ctx, query)
63+
if err != nil {
64+
return Receipts{}, nil, fmt.Errorf("unable to filter logs: %w", err)
65+
}
66+
if len(logs) == 0 {
67+
// Fallback for legacy events where the digest is not indexed.
68+
query.Topics = [][]common.Hash{{sequence.V3CallSucceeded, sequence.V3CallFailed, sequence.V3CallAborted, sequence.V3CallSkipped}}
69+
logs, err = provider.FilterLogs(ctx, query)
70+
if err != nil {
71+
return Receipts{}, nil, fmt.Errorf("unable to filter logs without digest topic: %w", err)
72+
}
73+
}
74+
75+
log, err := findDigestLog(logs, opHash)
76+
if err != nil {
77+
return Receipts{}, nil, err
78+
}
79+
80+
receipt, err := provider.TransactionReceipt(ctx, log.TxHash)
81+
if err != nil {
82+
return Receipts{}, nil, fmt.Errorf("unable to get transaction receipt %v: %w", log.TxHash, err)
83+
}
84+
85+
decoded, err := TransactionReceiptsForReceipt(ctx, receipt, provider)
86+
if err != nil {
87+
return Receipts{}, receipt, fmt.Errorf("unable to decode transaction receipt %v: %w", receipt.TxHash, err)
88+
}
89+
90+
receipts := decoded.Find(opHash)
91+
if receipts == nil {
92+
return Receipts{}, receipt, fmt.Errorf("decoded receipts do not include digest %v", opHash)
93+
}
94+
95+
return *receipts, receipt, nil
96+
}
97+
98+
func findDigestLog(logs []types.Log, digest common.Hash) (*types.Log, error) {
99+
var selected *types.Log
100+
101+
for i := range logs {
102+
log := &logs[i]
103+
104+
if !matchesDigest(log, digest) {
105+
continue
106+
}
107+
108+
if selected == nil || isNewerLog(log, selected) {
109+
selected = log
110+
}
111+
}
112+
113+
if selected == nil {
114+
return nil, fmt.Errorf("no Call* events found for digest %v", digest)
115+
}
116+
117+
return selected, nil
118+
}
119+
120+
func matchesDigest(log *types.Log, digest common.Hash) bool {
121+
if hash, _, err := sequence.V3DecodeCallSucceededEvent(log); err == nil && hash == digest {
122+
return true
123+
}
124+
if hash, _, _, err := sequence.V3DecodeCallFailedEvent(log); err == nil && hash == digest {
125+
return true
126+
}
127+
if hash, _, _, err := sequence.V3DecodeCallAbortedEvent(log); err == nil && hash == digest {
128+
return true
129+
}
130+
if hash, _, err := sequence.V3DecodeCallSkippedEvent(log); err == nil && hash == digest {
131+
return true
132+
}
133+
return false
134+
}
135+
136+
func isNewerLog(a, b *types.Log) bool {
137+
if a.BlockNumber != b.BlockNumber {
138+
return a.BlockNumber > b.BlockNumber
139+
}
140+
if a.TxIndex != b.TxIndex {
141+
return a.TxIndex > b.TxIndex
142+
}
143+
return a.Index > b.Index
144+
}

receipts/receipts.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,21 @@ type Receipts struct {
3131
Receipts []Receipt
3232
}
3333

34+
func (r *Receipts) IsSuccess() bool {
35+
for _, receipt := range r.Receipts {
36+
switch receipt.Status {
37+
case StatusNotExecuted, StatusFailed, StatusAborted:
38+
return false
39+
}
40+
41+
if receipt.Receipts != nil && !receipt.Receipts.IsSuccess() {
42+
return false
43+
}
44+
}
45+
46+
return true
47+
}
48+
3449
func (r *Receipts) Find(digest common.Hash) *Receipts {
3550
if digest == r.Digest {
3651
return r

0 commit comments

Comments
 (0)