Skip to content

Commit 34ffd31

Browse files
attentepkieltyka
andcommitted
transaction receipt decoder
Co-authored-by: Peter Kieltyka <[email protected]>
1 parent fd0b0f7 commit 34ffd31

File tree

5 files changed

+439
-42
lines changed

5 files changed

+439
-42
lines changed

abi.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,14 @@ var abiTransactionsType = ethcoder.MustNewArrayTypeTuple([]abi.ArgumentMarshalin
1515
{Name: "data", Type: "bytes"},
1616
})
1717

18-
// abiTransactionsDigestType represents abi coder of []Transaction digest pre-image
18+
// abiTransactionsDigestType represents abi coder of []Transaction nonce digest pre-image
1919
var abiTransactionsDigestType = abi.Arguments{
2020
abi.Argument{Type: ethcoder.MustNewType("uint256")},
2121
abi.Argument{Type: abiTransactionsType},
2222
}
23+
24+
// abiTransactionsStringDigestType represents abi coder of []Transaction string digest pre-image
25+
var abiTransactionsStringDigestType = abi.Arguments{
26+
abi.Argument{Type: ethcoder.MustNewType("string")},
27+
abi.Argument{Type: abiTransactionsType},
28+
}

receipts.go

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
package sequence
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"math/big"
7+
8+
"github.com/0xsequence/ethkit/ethrpc"
9+
"github.com/0xsequence/ethkit/go-ethereum/common"
10+
"github.com/0xsequence/ethkit/go-ethereum/core/types"
11+
)
12+
13+
type Receipt struct {
14+
*types.Receipt
15+
16+
MetaTxnID MetaTxnID
17+
Status MetaTxnStatus
18+
Reason string
19+
Logs []*types.Log
20+
21+
Index uint
22+
Transaction *Transaction
23+
Receipts []*Receipt
24+
}
25+
26+
func (r *Receipt) setNativeReceipt(receipt *types.Receipt) {
27+
r.Receipt = receipt
28+
29+
for _, child := range r.Receipts {
30+
child.setNativeReceipt(receipt)
31+
}
32+
}
33+
34+
func DecodeReceipt(ctx context.Context, receipt *types.Receipt, provider *ethrpc.Provider) ([]*Receipt, error) {
35+
transaction, isPending, err := provider.TransactionByHash(ctx, receipt.TxHash)
36+
if err != nil {
37+
return nil, err
38+
} else if isPending {
39+
return nil, fmt.Errorf("transaction %v is pending", receipt.TxHash.Hex())
40+
}
41+
42+
decoded, err := DecodeTransaction(transaction.Data())
43+
if err != nil {
44+
return nil, err
45+
}
46+
47+
transactions, err := prepareTransactionsForEncoding(decoded.Transactions)
48+
if err != nil {
49+
return nil, err
50+
}
51+
52+
_, receipts, err := decodeReceipt(receipt.Logs, transactions, decoded.Nonce, *transaction.To(), transaction.ChainId(), isGuestExecuteTransaction(decoded), "")
53+
if err != nil {
54+
return nil, err
55+
}
56+
57+
for _, child := range receipts {
58+
child.setNativeReceipt(receipt)
59+
}
60+
61+
return receipts, nil
62+
}
63+
64+
func decodeReceipt(logs []*types.Log, transactions Transactions, nonce *big.Int, address common.Address, chainID *big.Int, isGuestExecute bool, metaTxnID MetaTxnID) ([]*types.Log, []*Receipt, error) {
65+
isSelfExecute := nonce == nil
66+
67+
// compute the logged transaction hash
68+
var hash common.Hash
69+
if !isGuestExecute {
70+
bundle := Transaction{
71+
Transactions: transactions,
72+
Nonce: nonce,
73+
}
74+
digest, err := bundle.Digest()
75+
if err != nil {
76+
return nil, nil, err
77+
}
78+
subDigest, err := SubDigest(address, chainID, digest)
79+
if err != nil {
80+
return nil, nil, err
81+
}
82+
hash = common.BytesToHash(subDigest)
83+
} else {
84+
bundle := Transaction{
85+
Transactions: transactions,
86+
Nonce: nonce,
87+
}
88+
digest, err := bundle.GuestDigest()
89+
if err != nil {
90+
return nil, nil, err
91+
}
92+
subDigest, err := SubDigest(address, chainID, digest)
93+
if err != nil {
94+
return nil, nil, err
95+
}
96+
hash = common.BytesToHash(subDigest)
97+
}
98+
99+
// compute the meta-transaction ID
100+
if !isSelfExecute && !isGuestExecute {
101+
var err error
102+
metaTxnID, err = ComputeMetaTxnID(address, chainID, transactions, nonce)
103+
if err != nil {
104+
return nil, nil, err
105+
}
106+
}
107+
108+
var topLevelLogs []*types.Log
109+
var receipts []*Receipt
110+
111+
// check if we should expect a NonceChange event
112+
if !isSelfExecute && !isGuestExecute {
113+
if len(logs) > 0 {
114+
if _, _, err := DecodeNonceChangeEvent(logs[0]); err != nil {
115+
return nil, nil, fmt.Errorf("expected NonceChange event")
116+
}
117+
118+
// consume the NonceChange event
119+
topLevelLogs = append(topLevelLogs, logs[0])
120+
logs = logs[1:]
121+
}
122+
}
123+
124+
// create a receipt for each transaction
125+
for i, transaction := range transactions {
126+
receipt := Receipt{
127+
MetaTxnID: metaTxnID,
128+
Status: MetaTxnReverted,
129+
Index: uint(i),
130+
Transaction: transaction,
131+
}
132+
133+
for len(logs) > 0 {
134+
var log *types.Log
135+
log, logs = logs[0], logs[1:]
136+
137+
isTxExecuted := IsTxExecutedEvent(log, hash)
138+
failedHash, failedReason, err := DecodeTxFailedEvent(log)
139+
isTxFailed := err == nil && failedHash == hash
140+
141+
if isTxExecuted || isTxFailed {
142+
if isTxExecuted {
143+
receipt.Status = MetaTxnExecuted
144+
} else if isTxFailed {
145+
receipt.Status = MetaTxnFailed
146+
receipt.Reason = failedReason
147+
}
148+
149+
topLevelLogs = append(topLevelLogs, log)
150+
break
151+
} else {
152+
receipt.Logs = append(receipt.Logs, log)
153+
}
154+
}
155+
156+
if transaction.Transactions != nil {
157+
var err error
158+
receipt.Logs, receipt.Receipts, err = decodeReceipt(receipt.Logs, transaction.Transactions, transaction.Nonce, transaction.To, chainID, isGuestExecuteTransaction(transaction), metaTxnID)
159+
if err != nil {
160+
return nil, nil, err
161+
}
162+
}
163+
164+
receipts = append(receipts, &receipt)
165+
}
166+
167+
if len(logs) > 0 {
168+
return nil, nil, fmt.Errorf("%v unexpected events", len(logs))
169+
}
170+
171+
return topLevelLogs, receipts, nil
172+
}
173+
174+
func isGuestExecuteTransaction(transaction *Transaction) bool {
175+
return transaction.Nonce != nil && len(transaction.Signature) == 0
176+
}

receipts_test.go

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
package sequence_test
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"math/big"
7+
"testing"
8+
9+
"github.com/0xsequence/ethkit/go-ethereum/common"
10+
"github.com/0xsequence/ethkit/go-ethereum/core/types"
11+
"github.com/0xsequence/go-sequence"
12+
"github.com/0xsequence/go-sequence/testutil"
13+
"github.com/stretchr/testify/assert"
14+
)
15+
16+
func TestReceiptDecoding(t *testing.T) {
17+
// Ensure a dummy sequence wallet is deployed
18+
wallets, err := testChain.DummySequenceWallets(1, 1)
19+
assert.NoError(t, err)
20+
assert.NotNil(t, wallets)
21+
assert.Len(t, wallets, 1)
22+
wallet := wallets[0]
23+
24+
// Create normal txn of: callmockContract.mockMint(wallet, 10)
25+
callmockContract, _ := testChain.Deploy(t, "ERC20Mock")
26+
calldata, err := callmockContract.Encode("mockMint", wallet.Address(), big.NewInt(10))
27+
assert.NoError(t, err)
28+
29+
// Sign and send the transaction
30+
err = testutil.SignAndSend(t, wallet, callmockContract.Address, calldata)
31+
assert.NoError(t, err)
32+
33+
var send [][]byte
34+
for i := 0; i <= 10; i++ {
35+
data, err := callmockContract.Encode("transfer", common.Address{1}, big.NewInt(int64(i)))
36+
assert.NoError(t, err)
37+
send = append(send, data)
38+
}
39+
40+
// These are wallet's intended transactions
41+
bundle := sequence.Transactions{
42+
{
43+
To: callmockContract.Address,
44+
Data: send[1],
45+
}, // logs: 3 (1 from NonceChange), balance: 9, executed
46+
{
47+
To: callmockContract.Address,
48+
Data: send[1],
49+
}, // logs: 5, balance: 8, executed
50+
{
51+
To: wallet.Address(),
52+
Transactions: sequence.Transactions{
53+
{
54+
To: callmockContract.Address,
55+
Data: send[2],
56+
}, // logs: 7, balance: 6, executed
57+
{
58+
To: callmockContract.Address,
59+
Data: send[10],
60+
}, // logs: 8, balance: -4, failed
61+
},
62+
}, // logs: 9, balance: 6, executed
63+
{
64+
To: wallet.Address(),
65+
Transactions: sequence.Transactions{
66+
{
67+
To: callmockContract.Address,
68+
Data: send[3],
69+
RevertOnError: true,
70+
}, // logs: 11, balance: 3, reverted
71+
{
72+
To: callmockContract.Address,
73+
Data: send[10],
74+
RevertOnError: true,
75+
}, // logs: 12, balance: -7, reverted
76+
},
77+
}, // logs: 10, balance: 6, failed
78+
{
79+
To: callmockContract.Address,
80+
Data: send[10],
81+
}, // logs: 11, balance: -4, failed
82+
{
83+
To: callmockContract.Address,
84+
Data: send[4],
85+
}, // logs: 13, balance: 2, executed
86+
{
87+
To: callmockContract.Address,
88+
Data: send[4],
89+
}, // logs: 14, balance: -2, failed
90+
{
91+
To: callmockContract.Address,
92+
Data: send[2],
93+
}, // logs: 16, balance: 0, executed
94+
}
95+
96+
signedBundle, err := wallet.SignTransactions(context.Background(), bundle)
97+
assert.NoError(t, err)
98+
99+
_, _, waitReceipt, err := wallet.SendTransaction(context.Background(), signedBundle)
100+
assert.NoError(t, err)
101+
receipt, err := waitReceipt(context.Background())
102+
assert.NoError(t, err)
103+
assert.Len(t, receipt.Logs, 16)
104+
105+
receipts, err := sequence.DecodeReceipt(context.Background(), receipt, wallet.GetProvider())
106+
assert.NoError(t, err)
107+
assert.Len(t, receipts, len(bundle))
108+
109+
metaTxnID := receipts[0].MetaTxnID
110+
assert.NotEqual(t, "", metaTxnID)
111+
112+
for _, child := range receipts {
113+
assert.True(t, hasNativeReceipt(child, receipt))
114+
assert.True(t, hasMetaTxnID(child, metaTxnID))
115+
assert.NoError(t, areIsomorphic(child, child.Transaction))
116+
}
117+
118+
assert.Equal(t, sequence.MetaTxnExecuted, receipts[0].Status)
119+
assert.Equal(t, sequence.MetaTxnExecuted, receipts[1].Status)
120+
assert.Equal(t, sequence.MetaTxnExecuted, receipts[2].Receipts[0].Status)
121+
assert.Equal(t, sequence.MetaTxnFailed, receipts[2].Receipts[1].Status)
122+
assert.Equal(t, sequence.MetaTxnExecuted, receipts[2].Status)
123+
assert.Equal(t, sequence.MetaTxnReverted, receipts[3].Receipts[0].Status)
124+
assert.Equal(t, sequence.MetaTxnReverted, receipts[3].Receipts[1].Status)
125+
assert.Equal(t, sequence.MetaTxnFailed, receipts[3].Status)
126+
assert.Equal(t, sequence.MetaTxnFailed, receipts[4].Status)
127+
assert.Equal(t, sequence.MetaTxnExecuted, receipts[5].Status)
128+
assert.Equal(t, sequence.MetaTxnFailed, receipts[6].Status)
129+
assert.Equal(t, sequence.MetaTxnExecuted, receipts[7].Status)
130+
131+
assert.Len(t, receipts[0].Logs, 1)
132+
assert.Len(t, receipts[1].Logs, 1)
133+
assert.Len(t, receipts[2].Receipts[0].Logs, 1)
134+
assert.Len(t, receipts[2].Receipts[1].Logs, 0)
135+
assert.Len(t, receipts[2].Logs, 2)
136+
assert.Len(t, receipts[3].Receipts[0].Logs, 0)
137+
assert.Len(t, receipts[3].Receipts[1].Logs, 0)
138+
assert.Len(t, receipts[3].Logs, 0)
139+
assert.Len(t, receipts[4].Logs, 0)
140+
assert.Len(t, receipts[5].Logs, 1)
141+
assert.Len(t, receipts[6].Logs, 0)
142+
assert.Len(t, receipts[7].Logs, 1)
143+
144+
_, reason, err := sequence.DecodeTxFailedEvent(receipts[2].Logs[1])
145+
assert.NoError(t, err)
146+
assert.Equal(t, reason, receipts[2].Receipts[1].Reason)
147+
}
148+
149+
func hasNativeReceipt(receipt *sequence.Receipt, native *types.Receipt) bool {
150+
if receipt.Receipt != native {
151+
return false
152+
}
153+
154+
for _, child := range receipt.Receipts {
155+
if !hasNativeReceipt(child, native) {
156+
return false
157+
}
158+
}
159+
160+
return true
161+
}
162+
163+
func hasMetaTxnID(receipt *sequence.Receipt, metaTxnID sequence.MetaTxnID) bool {
164+
if receipt.MetaTxnID != metaTxnID {
165+
return false
166+
}
167+
168+
for _, child := range receipt.Receipts {
169+
if !hasMetaTxnID(child, metaTxnID) {
170+
return false
171+
}
172+
}
173+
174+
return true
175+
}
176+
177+
func areIsomorphic(receipt *sequence.Receipt, transaction *sequence.Transaction) error {
178+
if receipt.Transaction != transaction {
179+
return fmt.Errorf("receipt.Transaction != transaction")
180+
}
181+
182+
if len(receipt.Receipts) != len(transaction.Transactions) {
183+
return fmt.Errorf("have %v receipts, expected %v", len(receipt.Receipts), len(transaction.Transactions))
184+
}
185+
186+
for i := 0; i < len(receipt.Receipts); i++ {
187+
if err := areIsomorphic(receipt.Receipts[i], transaction.Transactions[i]); err != nil {
188+
return err
189+
}
190+
}
191+
192+
return nil
193+
}

relayer.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ const (
4646
MetaTxnStatusUnknown MetaTxnStatus = iota
4747
MetaTxnExecuted
4848
MetaTxnFailed
49+
MetaTxnReverted
4950
)
5051

5152
func ComputeMetaTxnID(walletAddress common.Address, chainID *big.Int, txns Transactions, nonce *big.Int) (MetaTxnID, error) {

0 commit comments

Comments
 (0)