Skip to content

Commit

Permalink
Merge pull request #24 from SiaFoundation/refactor
Browse files Browse the repository at this point in the history
Refactor to be more like walletd
  • Loading branch information
n8maninger authored Apr 30, 2024
2 parents abe0200 + f247b60 commit 236e7cf
Show file tree
Hide file tree
Showing 15 changed files with 641 additions and 536 deletions.
17 changes: 8 additions & 9 deletions explorer/explorer.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,17 @@ type ChainManager interface {
// A Store is a database that stores information about elements, contracts,
// and blocks.
type Store interface {
ProcessChainUpdates(crus []chain.RevertUpdate, caus []chain.ApplyUpdate) error
UpdateChainState(reverted []chain.RevertUpdate, applied []chain.ApplyUpdate) error

Tip() (types.ChainIndex, error)
Block(id types.BlockID) (Block, error)
BestTip(height uint64) (types.ChainIndex, error)
MerkleProof(leafIndex uint64) ([]types.Hash256, error)
Transactions(ids []types.TransactionID) ([]Transaction, error)
UnspentSiacoinOutputs(address types.Address, limit, offset uint64) ([]SiacoinOutput, error)
UnspentSiafundOutputs(address types.Address, limit, offset uint64) ([]SiafundOutput, error)
Balance(address types.Address) (sc types.Currency, immatureSC types.Currency, sf uint64, err error)
Contracts(ids []types.FileContractID) (result []FileContract, err error)

MerkleProof(leafIndex uint64) ([]types.Hash256, error)
}

// Explorer implements a Sia explorer.
Expand All @@ -58,7 +57,7 @@ func syncStore(store Store, cm ChainManager, index types.ChainIndex) error {
return fmt.Errorf("failed to subscribe to chain manager: %w", err)
}

if err := store.ProcessChainUpdates(crus, caus); err != nil {
if err := store.UpdateChainState(crus, caus); err != nil {
return fmt.Errorf("failed to process updates: %w", err)
}
if len(crus) > 0 {
Expand Down Expand Up @@ -111,11 +110,6 @@ func NewExplorer(cm ChainManager, store Store, log *zap.Logger) (*Explorer, erro
return e, nil
}

// MerkleProof gets the merkle proof with the given leaf index.
func (e *Explorer) MerkleProof(leafIndex uint64) ([]types.Hash256, error) {
return e.s.MerkleProof(leafIndex)
}

// Tip returns the tip of the best known valid chain.
func (e *Explorer) Tip() (types.ChainIndex, error) {
return e.s.Tip()
Expand All @@ -131,6 +125,11 @@ func (e *Explorer) BestTip(height uint64) (types.ChainIndex, error) {
return e.s.BestTip(height)
}

// MerkleProof returns the proof of a given leaf.
func (e *Explorer) MerkleProof(leafIndex uint64) ([]types.Hash256, error) {
return e.s.MerkleProof(leafIndex)
}

// Transactions returns the transactions with the specified IDs.
func (e *Explorer) Transactions(ids []types.TransactionID) ([]Transaction, error) {
return e.s.Transactions(ids)
Expand Down
284 changes: 284 additions & 0 deletions explorer/update.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,284 @@
package explorer

import (
"fmt"

"go.sia.tech/core/types"
"go.sia.tech/coreutils/chain"
)

type (
// FileContractUpdate represents a file contract from a consensus update.
FileContractUpdate struct {
FileContractElement types.FileContractElement
Revision *types.FileContractElement
Resolved, Valid bool
}

// A DBFileContract represents a file contract element in the DB.
DBFileContract struct {
ID types.FileContractID
RevisionNumber uint64
}

// A TreeNodeUpdate is a change to a merkle tree node.
TreeNodeUpdate struct {
Row uint64
Column uint64
Hash types.Hash256
}

// An UpdateState contains information relevant to the block being applied
// or reverted.
UpdateState struct {
Block types.Block
Index types.ChainIndex
TreeUpdates []TreeNodeUpdate

NewSiacoinElements []SiacoinOutput
SpentSiacoinElements []SiacoinOutput
EphemeralSiacoinElements []SiacoinOutput

NewSiafundElements []types.SiafundElement
SpentSiafundElements []types.SiafundElement
EphemeralSiafundElements []types.SiafundElement

FileContractElements []FileContractUpdate
}

// An UpdateTx atomically updates the state of a store.
UpdateTx interface {
ApplyIndex(state UpdateState) error
RevertIndex(state UpdateState) error
}
)

// applyChainUpdate atomically applies a chain update to a store
func applyChainUpdate(tx UpdateTx, cau chain.ApplyUpdate) error {
sources := make(map[types.SiacoinOutputID]Source)
for i := range cau.Block.MinerPayouts {
sources[cau.Block.ID().MinerOutputID(i)] = SourceMinerPayout
}

for _, txn := range cau.Block.Transactions {
for i := range txn.SiacoinOutputs {
sources[txn.SiacoinOutputID(i)] = SourceTransaction
}

for i := range txn.FileContracts {
fcid := txn.FileContractID(i)
for j := range txn.FileContracts[i].ValidProofOutputs {
sources[fcid.ValidOutputID(j)] = SourceValidProofOutput
}
for j := range txn.FileContracts[i].MissedProofOutputs {
sources[fcid.MissedOutputID(j)] = SourceMissedProofOutput
}
}
}

created := make(map[types.Hash256]bool)
ephemeral := make(map[types.Hash256]bool)
for _, txn := range cau.Block.Transactions {
for i := range txn.SiacoinOutputs {
created[types.Hash256(txn.SiacoinOutputID(i))] = true
}
for _, input := range txn.SiacoinInputs {
ephemeral[types.Hash256(input.ParentID)] = created[types.Hash256(input.ParentID)]
}
for i := range txn.SiafundOutputs {
created[types.Hash256(txn.SiafundOutputID(i))] = true
}
for _, input := range txn.SiafundInputs {
ephemeral[types.Hash256(input.ParentID)] = created[types.Hash256(input.ParentID)]
}
}

// add new siacoin elements to the store
var newSiacoinElements, spentSiacoinElements []SiacoinOutput
var ephemeralSiacoinElements []SiacoinOutput
cau.ForEachSiacoinElement(func(se types.SiacoinElement, spent bool) {
if ephemeral[se.ID] {
ephemeralSiacoinElements = append(ephemeralSiacoinElements, SiacoinOutput{
SiacoinElement: se,
Source: sources[types.SiacoinOutputID(se.StateElement.ID)],
})
return
}

if spent {
spentSiacoinElements = append(spentSiacoinElements, SiacoinOutput{
SiacoinElement: se,
Source: sources[types.SiacoinOutputID(se.StateElement.ID)],
})
} else {
newSiacoinElements = append(newSiacoinElements, SiacoinOutput{
SiacoinElement: se,
Source: sources[types.SiacoinOutputID(se.StateElement.ID)],
})
}
})

var newSiafundElements, spentSiafundElements []types.SiafundElement
var ephemeralSiafundElements []types.SiafundElement
cau.ForEachSiafundElement(func(se types.SiafundElement, spent bool) {
if ephemeral[se.ID] {
ephemeralSiafundElements = append(ephemeralSiafundElements, se)
return
}

if spent {
spentSiafundElements = append(spentSiafundElements, se)
} else {
newSiafundElements = append(newSiafundElements, se)
}
})

var fces []FileContractUpdate
cau.ForEachFileContractElement(func(fce types.FileContractElement, rev *types.FileContractElement, resolved, valid bool) {
fces = append(fces, FileContractUpdate{
FileContractElement: fce,
Revision: rev,
Resolved: resolved,
Valid: valid,
})
})

var treeUpdates []TreeNodeUpdate
cau.ForEachTreeNode(func(row, column uint64, hash types.Hash256) {
treeUpdates = append(treeUpdates, TreeNodeUpdate{
Row: row,
Column: column,
Hash: hash,
})
})

state := UpdateState{
Block: cau.Block,
Index: cau.State.Index,
TreeUpdates: treeUpdates,

NewSiacoinElements: newSiacoinElements,
SpentSiacoinElements: spentSiacoinElements,
EphemeralSiacoinElements: ephemeralSiacoinElements,

NewSiafundElements: newSiafundElements,
SpentSiafundElements: spentSiafundElements,
EphemeralSiafundElements: ephemeralSiafundElements,

FileContractElements: fces,
}
return tx.ApplyIndex(state)
}

// revertChainUpdate atomically reverts a chain update from a store
func revertChainUpdate(tx UpdateTx, cru chain.RevertUpdate, revertedIndex types.ChainIndex) error {
created := make(map[types.Hash256]bool)
ephemeral := make(map[types.Hash256]bool)
for _, txn := range cru.Block.Transactions {
for i := range txn.SiacoinOutputs {
created[types.Hash256(txn.SiacoinOutputID(i))] = true
}
for _, input := range txn.SiacoinInputs {
ephemeral[types.Hash256(input.ParentID)] = created[types.Hash256(input.ParentID)]
}
for i := range txn.SiafundOutputs {
created[types.Hash256(txn.SiafundOutputID(i))] = true
}
for _, input := range txn.SiafundInputs {
ephemeral[types.Hash256(input.ParentID)] = created[types.Hash256(input.ParentID)]
}
}

// add new siacoin elements to the store
var newSiacoinElements, spentSiacoinElements []SiacoinOutput
var ephemeralSiacoinElements []SiacoinOutput
cru.ForEachSiacoinElement(func(se types.SiacoinElement, spent bool) {
if ephemeral[se.ID] {
ephemeralSiacoinElements = append(ephemeralSiacoinElements, SiacoinOutput{
SiacoinElement: se,
})
return
}

if spent {
newSiacoinElements = append(newSiacoinElements, SiacoinOutput{
SiacoinElement: se,
})
} else {
spentSiacoinElements = append(spentSiacoinElements, SiacoinOutput{
SiacoinElement: se,
})
}
})

var newSiafundElements, spentSiafundElements []types.SiafundElement
var ephemeralSiafundElements []types.SiafundElement
cru.ForEachSiafundElement(func(se types.SiafundElement, spent bool) {
if ephemeral[se.ID] {
ephemeralSiafundElements = append(ephemeralSiafundElements, se)
return
}

if spent {
newSiafundElements = append(newSiafundElements, se)
} else {
spentSiafundElements = append(spentSiafundElements, se)
}
})

var fces []FileContractUpdate
cru.ForEachFileContractElement(func(fce types.FileContractElement, rev *types.FileContractElement, resolved, valid bool) {
fces = append(fces, FileContractUpdate{
FileContractElement: fce,
Revision: rev,
Resolved: resolved,
Valid: valid,
})
})

var treeUpdates []TreeNodeUpdate
cru.ForEachTreeNode(func(row, column uint64, hash types.Hash256) {
treeUpdates = append(treeUpdates, TreeNodeUpdate{
Row: row,
Column: column,
Hash: hash,
})
})

state := UpdateState{
Block: cru.Block,
Index: revertedIndex,
TreeUpdates: treeUpdates,

NewSiacoinElements: newSiacoinElements,
SpentSiacoinElements: spentSiacoinElements,
EphemeralSiacoinElements: ephemeralSiacoinElements,

NewSiafundElements: newSiafundElements,
SpentSiafundElements: spentSiafundElements,
EphemeralSiafundElements: ephemeralSiafundElements,

FileContractElements: fces,
}
return tx.RevertIndex(state)
}

// UpdateChainState applies the reverts and updates.
func UpdateChainState(tx UpdateTx, crus []chain.RevertUpdate, caus []chain.ApplyUpdate) error {
for _, cru := range crus {
revertedIndex := types.ChainIndex{
ID: cru.Block.ID(),
Height: cru.State.Index.Height + 1,
}
if err := revertChainUpdate(tx, cru, revertedIndex); err != nil {
return fmt.Errorf("failed to revert chain update %q: %w", revertedIndex, err)
}
}

for _, cau := range caus {
if err := applyChainUpdate(tx, cau); err != nil {
return fmt.Errorf("failed to apply chain update %q: %w", cau.State.Index, err)
}
}
return nil
}
8 changes: 4 additions & 4 deletions persist/sqlite/blocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import (

// Block implements explorer.Store.
func (s *Store) Block(id types.BlockID) (result explorer.Block, err error) {
err = s.transaction(func(tx txn) error {
err = tx.QueryRow(`SELECT parent_id, nonce, timestamp, height FROM blocks WHERE id=?`, dbEncode(id)).Scan(dbDecode(&result.ParentID), dbDecode(&result.Nonce), dbDecode(&result.Timestamp), &result.Height)
err = s.transaction(func(tx *txn) error {
err = tx.QueryRow(`SELECT parent_id, nonce, timestamp, height FROM blocks WHERE id=?`, encode(id)).Scan(decode(&result.ParentID), decode(&result.Nonce), decode(&result.Timestamp), &result.Height)
if err != nil {
return err
}
Expand Down Expand Up @@ -38,8 +38,8 @@ func (s *Store) Block(id types.BlockID) (result explorer.Block, err error) {

// BestTip implements explorer.Store.
func (s *Store) BestTip(height uint64) (result types.ChainIndex, err error) {
err = s.transaction(func(tx txn) error {
err = tx.QueryRow(`SELECT id, height FROM blocks WHERE height=?`, height).Scan(dbDecode(&result.ID), dbDecode(&result.Height))
err = s.transaction(func(tx *txn) error {
err = tx.QueryRow(`SELECT id, height FROM blocks WHERE height=?`, height).Scan(decode(&result.ID), decode(&result.Height))
if err != nil {
return err
}
Expand Down
Loading

0 comments on commit 236e7cf

Please sign in to comment.