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
package diffdb
import (
"github.com/ethereum/go-ethereum/common"
_ "github.com/mattn/go-sqlite3"
"database/sql"
"math/big"
)
type Key struct {
Key common.Hash
Mutated bool
}
type Diff map[common.Address][]Key
/// A DiffDb is a thin wrapper around an Sqlite3 connection.
///
/// Its purpose is to store and fetch the storage keys corresponding to an address that was
/// touched in a block.
type DiffDb struct {
db *sql.DB
tx *sql.Tx
stmt *sql.Stmt
cache uint64
// We have a db-wide counter for the number of db calls made which we reset
// whenever it hits `cache`.
numCalls uint64
}
/// This key is used to mark that an account's state has been modified (e.g. nonce or balance)
/// and that an account proof is required.
var accountKey = common.HexToHash("0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF")
var insertStatement = `
INSERT INTO diffs
(block, address, key, mutated)
VALUES
($1, $2, $3, $4)
ON CONFLICT DO NOTHING
`
var createStmt = `
CREATE TABLE IF NOT EXISTS diffs (
block INTEGER,
address STRING,
key STRING,
mutated BOOL,
PRIMARY KEY (block, address, key)
)
`
var selectStmt = `
SELECT * from diffs WHERE block = $1
`
/// Inserts a new row to the sqlite with the provided diff data.
func (diff *DiffDb) SetDiffKey(block *big.Int, address common.Address, key common.Hash, mutated bool) error {
// add 1 more insertion to the transaction
_, err := diff.stmt.Exec(block.Uint64(), address, key, mutated)
if err != nil {
return err
}
// increment number of calls
diff.numCalls += 1
// if we had enough calls, commit it
if diff.numCalls >= diff.cache {
if err := diff.ForceCommit(); err != nil {
return err
}
}
return nil
}
/// Inserts a new row to the sqlite indicating that the account was modified in that block
/// at a pre-set key
func (diff *DiffDb) SetDiffAccount(block *big.Int, address common.Address) error {
return diff.SetDiffKey(block, address, accountKey, true)
}
/// Commits a pending diffdb transaction
func (diff *DiffDb) ForceCommit() error {
if err := diff.tx.Commit(); err != nil {
return err
}
return diff.resetTx()
}
/// Gets all the rows for the matching block and converts them to a Diff map.
func (diff *DiffDb) GetDiff(blockNum *big.Int) (Diff, error) {
// make the query
rows, err := diff.db.Query(selectStmt, blockNum.Uint64())
if err != nil {
return nil, err
}
// initialize our data
res := make(Diff)
var block uint64
var address common.Address
var key common.Hash
var mutated bool
for rows.Next() {
// deserialize the line
err = rows.Scan(&block, &address, &key, &mutated)
if err != nil {
return nil, err
}
// add the data to the map
res[address] = append(res[address], Key{key, mutated})
}
return res, rows.Err()
}
// Initializes the transaction which we will be using to commit data to the db
func (diff *DiffDb) resetTx() error {
// reset the number of calls made
diff.numCalls = 0
// start a new tx
tx, err := diff.db.Begin()
if err != nil {
return err
}
diff.tx = tx
// the tx is about inserts
stmt, err := diff.tx.Prepare(insertStatement)
if err != nil {
return err
}
diff.stmt = stmt
return nil
}
func (diff *DiffDb) Close() error {
return diff.db.Close()
}
/// Instantiates a new DiffDb using sqlite at `path`, with `cache` insertions
/// done in a transaction before it gets committed to the database.
func NewDiffDb(path string, cache uint64) (*DiffDb, error) {
// get a handle
db, err := sql.Open("sqlite3", path)
if err != nil {
return nil, err
}
// create the table if it does not exist
_, err = db.Exec(createStmt)
if err != nil {
return nil, err
}
diffdb := &DiffDb{db: db, cache: cache}
// initialize the transaction
if err := diffdb.resetTx(); err != nil {
return nil, err
}
return diffdb, nil
}