forked from algorand/indexer
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathrewind.go
186 lines (167 loc) · 5.73 KB
/
rewind.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
package accounting
import (
"context"
"fmt"
"github.com/algorand/go-algorand/data/basics"
"github.com/algorand/go-algorand/data/transactions"
"github.com/algorand/go-algorand/protocol"
models "github.com/algorand/indexer/api/generated/v2"
"github.com/algorand/indexer/idb"
)
// ConsistencyError is returned when the database returns inconsistent (stale) results.
type ConsistencyError struct {
msg string
}
func (e ConsistencyError) Error() string {
return e.msg
}
func assetUpdate(account *models.Account, assetid uint64, add, sub uint64) {
if account.Assets == nil {
account.Assets = new([]models.AssetHolding)
}
assets := *account.Assets
for i, ah := range assets {
if ah.AssetId == assetid {
ah.Amount += add
ah.Amount -= sub
assets[i] = ah
// found and updated asset, done
return
}
}
// add asset to list
assets = append(assets, models.AssetHolding{
Amount: add - sub,
AssetId: assetid,
//Creator: base32 addr string of asset creator, TODO
//IsFrozen: leave nil? // TODO: on close record frozen state for rewind
})
*account.Assets = assets
}
// SpecialAccountRewindError indicates that an attempt was made to rewind one of the special accounts.
type SpecialAccountRewindError struct {
account string
}
// MakeSpecialAccountRewindError helper to initialize a SpecialAccountRewindError.
func MakeSpecialAccountRewindError(account string) *SpecialAccountRewindError {
return &SpecialAccountRewindError{account: account}
}
// Error is part of the error interface.
func (sare *SpecialAccountRewindError) Error() string {
return fmt.Sprintf("unable to rewind the %s", sare.account)
}
var specialAccounts *transactions.SpecialAddresses
// AccountAtRound queries the idb.IndexerDb object for transactions and rewinds most fields of the account back to
// their values at the requested round.
// `round` must be <= `account.Round`
func AccountAtRound(ctx context.Context, account models.Account, round uint64, db idb.IndexerDb) (acct models.Account, err error) {
// Make sure special accounts cache has been initialized.
if specialAccounts == nil {
var accounts transactions.SpecialAddresses
accounts, err = db.GetSpecialAccounts(ctx)
if err != nil {
return models.Account{}, fmt.Errorf("unable to get special accounts: %v", err)
}
specialAccounts = &accounts
}
acct = account
var addr basics.Address
addr, err = basics.UnmarshalChecksumAddress(account.Address)
if err != nil {
return
}
// ensure that the don't attempt to rewind a special account.
if specialAccounts.FeeSink == addr {
err = MakeSpecialAccountRewindError("FeeSink")
return
}
if specialAccounts.RewardsPool == addr {
err = MakeSpecialAccountRewindError("RewardsPool")
return
}
// Get transactions and rewind account.
tf := idb.TransactionFilter{
Address: addr[:],
MinRound: round + 1,
MaxRound: account.Round,
}
ctx2, cf := context.WithCancel(ctx)
// In case of a panic before the next defer, call cf() here.
defer cf()
txns, r := db.Transactions(ctx2, tf)
// In case of an error, make sure the context is cancelled, and the channel is cleaned up.
defer func() {
cf()
for range txns {
}
}()
if r < account.Round {
err = ConsistencyError{fmt.Sprintf("queried round r: %d < account.Round: %d", r, account.Round)}
return
}
txcount := 0
for txnrow := range txns {
if txnrow.Error != nil {
err = txnrow.Error
return
}
txcount++
stxn := txnrow.Txn
if stxn == nil {
return models.Account{},
fmt.Errorf("rewinding past inner transactions is not supported")
}
if addr == stxn.Txn.Sender {
acct.AmountWithoutPendingRewards += stxn.Txn.Fee.ToUint64()
acct.AmountWithoutPendingRewards -= stxn.SenderRewards.ToUint64()
}
switch stxn.Txn.Type {
case protocol.PaymentTx:
if addr == stxn.Txn.Sender {
acct.AmountWithoutPendingRewards += stxn.Txn.Amount.ToUint64()
}
if addr == stxn.Txn.Receiver {
acct.AmountWithoutPendingRewards -= stxn.Txn.Amount.ToUint64()
acct.AmountWithoutPendingRewards -= stxn.ReceiverRewards.ToUint64()
}
if addr == stxn.Txn.CloseRemainderTo {
// unwind receiving a close-to
acct.AmountWithoutPendingRewards -= stxn.ClosingAmount.ToUint64()
acct.AmountWithoutPendingRewards -= stxn.CloseRewards.ToUint64()
} else if !stxn.Txn.CloseRemainderTo.IsZero() {
// unwind sending a close-to
acct.AmountWithoutPendingRewards += stxn.ClosingAmount.ToUint64()
}
case protocol.KeyRegistrationTx:
// TODO: keyreg does not rewind. workaround: query for txns on an account with typeenum=2 to find previous values it was set to.
case protocol.AssetConfigTx:
if stxn.Txn.ConfigAsset == 0 {
// create asset, unwind the application of the value
assetUpdate(&acct, txnrow.AssetID, 0, stxn.Txn.AssetParams.Total)
}
case protocol.AssetTransferTx:
if addr == stxn.Txn.AssetSender || addr == stxn.Txn.Sender {
assetUpdate(&acct, uint64(stxn.Txn.XferAsset), stxn.Txn.AssetAmount+txnrow.Extra.AssetCloseAmount, 0)
}
if addr == stxn.Txn.AssetReceiver {
assetUpdate(&acct, uint64(stxn.Txn.XferAsset), 0, stxn.Txn.AssetAmount)
}
if addr == stxn.Txn.AssetCloseTo {
assetUpdate(&acct, uint64(stxn.Txn.XferAsset), 0, txnrow.Extra.AssetCloseAmount)
}
case protocol.AssetFreezeTx:
default:
err = fmt.Errorf("%s[%d,%d]: rewinding past txn type %s is not currently supported", account.Address, txnrow.Round, txnrow.Intra, stxn.Txn.Type)
return
}
}
acct.Round = round
// Due to accounts being closed and re-opened, we cannot always rewind Rewards. So clear it out.
acct.Rewards = 0
// Computing pending rewards is not supported.
acct.PendingRewards = 0
acct.Amount = acct.AmountWithoutPendingRewards
// TODO: Clear out the closed-at field as well. Like Rewards we cannot know this value for all accounts.
//acct.ClosedAt = 0
return
}