db.go 3.76 KB
Newer Older
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
}