Skip to content

Commit

Permalink
store and update state element merkle tree
Browse files Browse the repository at this point in the history
  • Loading branch information
chris124567 committed Feb 12, 2024
1 parent ff9bd68 commit ab32eb7
Show file tree
Hide file tree
Showing 3 changed files with 195 additions and 4 deletions.
18 changes: 17 additions & 1 deletion cmd/explored/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package main
import (
"context"
"errors"
"io/fs"
"net"
"os"
"path/filepath"
"strconv"
"time"
Expand All @@ -15,6 +17,7 @@ import (
"go.sia.tech/coreutils/chain"
"go.sia.tech/coreutils/syncer"
"go.sia.tech/explored/explorer"
"go.sia.tech/explored/internal/explorerutil"
"go.sia.tech/explored/internal/syncerutil"
"go.sia.tech/explored/persist/sqlite"
"go.uber.org/zap"
Expand Down Expand Up @@ -159,7 +162,7 @@ func newNode(addr, dir string, chainNetwork string, useUPNP bool, logger *zap.Lo
if err != nil {
return nil, err
}
e := explorer.NewExplorer(store)

tip, err := store.Tip()
if errors.Is(err, sqlite.ErrNoTip) {
tip = types.ChainIndex{
Expand All @@ -171,6 +174,18 @@ func newNode(addr, dir string, chainNetwork string, useUPNP bool, logger *zap.Lo
}
cm.AddSubscriber(store, tip)

hashPath := filepath.Join(dir, "./hash")
if err := os.MkdirAll(hashPath, fs.ModePerm); err != nil {
return nil, err
}
hashStore, err := explorerutil.NewHashStore(hashPath)
if err != nil {
return nil, err
}
cm.AddSubscriber(hashStore, tip)

e := explorer.NewExplorer(store, hashStore)

l, err := net.Listen("tcp", addr)
if err != nil {
return nil, err
Expand Down Expand Up @@ -233,6 +248,7 @@ func newNode(addr, dir string, chainNetwork string, useUPNP bool, logger *zap.Lo
l.Close()
<-ch
db.Close()
hashStore.Commit()
}
},
}, nil
Expand Down
27 changes: 24 additions & 3 deletions explorer/explorer.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@ import (
"go.sia.tech/coreutils/chain"
)

// A HashStore is a database that stores the state element merkle tree.
type HashStore interface {
chain.Subscriber

Commit() error
MerkleProof(leafIndex uint64) ([]types.Hash256, error)
ModifyLeaf(elem types.StateElement) error
}

// A Store is a database that stores information about elements, contracts,
// and blocks.
type Store interface {
Expand All @@ -21,12 +30,24 @@ type Store interface {

// Explorer implements a Sia explorer.
type Explorer struct {
s Store
s Store
hs HashStore
}

// NewExplorer returns a Sia explorer.
func NewExplorer(s Store) *Explorer {
return &Explorer{s: s}
func NewExplorer(s Store, hs HashStore) *Explorer {
return &Explorer{s: s, hs: hs}
}

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

// ModifyLeaf overwrites hashes in the tree with the proof hashes in the
// provided element.
func (e *Explorer) ModifyLeaf(elem types.StateElement) error {
return e.hs.ModifyLeaf(elem)
}

// Tip returns the tip of the best known valid chain.
Expand Down
154 changes: 154 additions & 0 deletions internal/explorerutil/hashstore.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package explorerutil

import (
"errors"
"fmt"
"math/bits"
"os"
"path/filepath"

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

type HashStore struct {
hashFiles [64]*os.File
numLeaves uint64
}

const hashSize = 32

type consensusUpdate interface {
ForEachSiacoinElement(fn func(sce types.SiacoinElement, spent bool))
ForEachSiafundElement(fn func(sfe types.SiafundElement, spent bool))
ForEachFileContractElement(fn func(fce types.FileContractElement, rev *types.FileContractElement, resolved, valid bool))
}

func (hs *HashStore) updateLeaves(update consensusUpdate) error {
var err error
update.ForEachSiacoinElement(func(sce types.SiacoinElement, spent bool) {
if err != nil {
return
}
err = hs.ModifyLeaf(sce.StateElement)
return
})
if err != nil {
return err
}

update.ForEachSiafundElement(func(sce types.SiafundElement, spent bool) {
if err != nil {
return
}
err = hs.ModifyLeaf(sce.StateElement)
return
})
if err != nil {
return err
}

update.ForEachFileContractElement(func(sce types.FileContractElement, rev *types.FileContractElement, resolved, valid bool) {
if err != nil {
return
}
err = hs.ModifyLeaf(sce.StateElement)
return
})
if err != nil {
return err
}

return nil
}

// ProcessChainApplyUpdate implements chain.Subscriber.
func (hs *HashStore) ProcessChainApplyUpdate(cau *chain.ApplyUpdate, mayCommit bool) error {
if err := hs.updateLeaves(cau); err != nil {
return err
}
if mayCommit {
return hs.Commit()
}
return nil
}

// ProcessChainRevertUpdate implements chain.Subscriber.
func (hs *HashStore) ProcessChainRevertUpdate(cru *chain.RevertUpdate) error {
if err := hs.updateLeaves(cru); err != nil {
return err
}
return hs.Commit()
}

// MerkleProof implements explorer.HashStore.
func (hs *HashStore) MerkleProof(leafIndex uint64) ([]types.Hash256, error) {
pos := leafIndex
proof := make([]types.Hash256, bits.Len64(leafIndex^hs.numLeaves)-1)
for i := range proof {
subtreeSize := uint64(1 << i)
if leafIndex&(1<<i) == 0 {
pos += subtreeSize
} else {
pos -= subtreeSize
}
if _, err := hs.hashFiles[i].ReadAt(proof[i][:], int64(pos/subtreeSize)*hashSize); err != nil {
return nil, err
}
}
return proof, nil
}

// ModifyLeaf implements explorer.HashStore.
func (hs *HashStore) ModifyLeaf(elem types.StateElement) error {
pos := elem.LeafIndex
for i, h := range elem.MerkleProof {
n := uint64(1 << i)
subtreeSize := uint64(1 << i)
if elem.LeafIndex&(1<<i) == 0 {
pos += subtreeSize
} else {
pos -= subtreeSize
}
if _, err := hs.hashFiles[i].WriteAt(h[:], int64(pos/n)*hashSize); err != nil {
return err
}
}
if elem.LeafIndex+1 > hs.numLeaves {
hs.numLeaves = elem.LeafIndex + 1
}
return nil
}

// Commit implements explorer.HashStore.
func (hs *HashStore) Commit() error {
for _, f := range hs.hashFiles {
if err := f.Sync(); err != nil {
return err
}
}
return nil
}

// NewHashStore returns a new HashStore.
func NewHashStore(dir string) (*HashStore, error) {
var hs HashStore
for i := range hs.hashFiles {
f, err := os.OpenFile(filepath.Join(dir, fmt.Sprintf("tree_level_%d.dat", i)), os.O_CREATE|os.O_RDWR, 0666)
if err != nil {
return nil, err
}
stat, err := f.Stat()
if err != nil {
return nil, err
} else if stat.Size()%hashSize != 0 {
// TODO: attempt to repair automatically
return nil, errors.New("tree contains a partially-written hash")
}
if i == 0 {
hs.numLeaves = uint64(stat.Size()) / hashSize
}
hs.hashFiles[i] = f
}
return &hs, nil
}

0 comments on commit ab32eb7

Please sign in to comment.