[lbry] blockchain: connect to ClaimTrie
Co-authored-by: Brannon King <countprimes@gmail.com>
This commit is contained in:
parent
56c21c6bd6
commit
2dcdb458e8
3 changed files with 239 additions and 0 deletions
|
@ -17,6 +17,8 @@ import (
|
|||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
|
||||
"github.com/btcsuite/btcd/claimtrie"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -180,6 +182,8 @@ type BlockChain struct {
|
|||
// certain blockchain events.
|
||||
notificationsLock sync.RWMutex
|
||||
notifications []NotificationCallback
|
||||
|
||||
claimTrie *claimtrie.ClaimTrie
|
||||
}
|
||||
|
||||
// HaveBlock returns whether or not the chain instance has the block represented
|
||||
|
@ -570,6 +574,11 @@ func (b *BlockChain) connectBlock(node *blockNode, block *btcutil.Block,
|
|||
"spent transaction out information")
|
||||
}
|
||||
|
||||
// Handle LBRY Claim Scripts
|
||||
if err := b.ParseClaimScripts(block, node, view, false); err != nil {
|
||||
return ruleError(ErrBadClaimTrie, err.Error())
|
||||
}
|
||||
|
||||
// No warnings about unknown rules until the chain is current.
|
||||
if b.isCurrent() {
|
||||
// Warn if any unknown new rules are either about to activate or
|
||||
|
@ -761,6 +770,10 @@ func (b *BlockChain) disconnectBlock(node *blockNode, block *btcutil.Block, view
|
|||
return err
|
||||
}
|
||||
|
||||
if err = b.claimTrie.ResetHeight(node.parent.height); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Prune fully spent entries and mark all entries in the view unmodified
|
||||
// now that the modifications have been committed to the database.
|
||||
view.commit()
|
||||
|
@ -1614,6 +1627,11 @@ func (b *BlockChain) LocateHeaders(locator BlockLocator, hashStop *chainhash.Has
|
|||
return headers
|
||||
}
|
||||
|
||||
// ClaimTrie returns the claimTrie associated wit hthe chain.
|
||||
func (b *BlockChain) ClaimTrie() *claimtrie.ClaimTrie {
|
||||
return b.claimTrie
|
||||
}
|
||||
|
||||
// IndexManager provides a generic interface that the is called when blocks are
|
||||
// connected and disconnected to and from the tip of the main chain for the
|
||||
// purpose of supporting optional indexes.
|
||||
|
@ -1796,6 +1814,20 @@ func New(config *Config) (*BlockChain, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
ct, err := claimtrie.New()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b.claimTrie = ct
|
||||
|
||||
// ct.ResetHeight(760140) // TODO: add an optional CLI parameter for this
|
||||
|
||||
err = rebuildMissingClaimTrieData(&b, config.Interrupt)
|
||||
if err != nil {
|
||||
ct.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bestNode := b.bestChain.Tip()
|
||||
log.Infof("Chain state (height %d, hash %v, totaltx %d, work %v)",
|
||||
bestNode.height, bestNode.hash, b.stateSnapshot.TotalTxns,
|
||||
|
@ -1803,3 +1835,61 @@ func New(config *Config) (*BlockChain, error) {
|
|||
|
||||
return &b, nil
|
||||
}
|
||||
|
||||
func rebuildMissingClaimTrieData(b *BlockChain, done <-chan struct{}) error {
|
||||
target := b.bestChain.Height()
|
||||
if b.claimTrie.Height() == target {
|
||||
return nil
|
||||
}
|
||||
if b.claimTrie.Height() > target {
|
||||
return b.claimTrie.ResetHeight(target)
|
||||
}
|
||||
|
||||
start := time.Now().Add(-6 * time.Second)
|
||||
// TODO: move this view inside the loop (or recreate it every 5 sec.)
|
||||
// as accumulating all inputs has potential to use a huge amount of RAM
|
||||
// but we need to get the spent inputs working for that to be possible
|
||||
view := NewUtxoViewpoint()
|
||||
for h := int32(0); h < target; h++ {
|
||||
select {
|
||||
case <-done:
|
||||
return fmt.Errorf("rebuild unfinished at height %d", b.claimTrie.Height())
|
||||
default:
|
||||
}
|
||||
|
||||
n := b.bestChain.NodeByHeight(h + 1)
|
||||
|
||||
var block *btcutil.Block
|
||||
err := b.db.View(func(dbTx database.Tx) error {
|
||||
var err error
|
||||
block, err = dbFetchBlockByNode(dbTx, n)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = view.fetchInputUtxos(b.db, block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = view.connectTransactions(block, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if h >= b.claimTrie.Height() {
|
||||
err = b.ParseClaimScripts(block, n, view, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if time.Since(start).Seconds() > 5.0 {
|
||||
start = time.Now()
|
||||
log.Infof("Rebuilding claim trie data to %d. At: %d", target, h)
|
||||
}
|
||||
}
|
||||
log.Infof("Completed rebuilding claim trie data to %d", b.claimTrie.Height())
|
||||
return nil
|
||||
}
|
||||
|
|
144
blockchain/claimtrie.go
Normal file
144
blockchain/claimtrie.go
Normal file
|
@ -0,0 +1,144 @@
|
|||
package blockchain
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
|
||||
"github.com/btcsuite/btcd/claimtrie"
|
||||
"github.com/btcsuite/btcd/claimtrie/node"
|
||||
)
|
||||
|
||||
// Hack: print which block mismatches happened, but keep recording.
|
||||
var mismatchedPrinted bool
|
||||
|
||||
func (b *BlockChain) ParseClaimScripts(block *btcutil.Block, node *blockNode, view *UtxoViewpoint, failOnHashMiss bool) error {
|
||||
ht := block.Height()
|
||||
|
||||
for _, tx := range block.Transactions() {
|
||||
h := handler{ht, tx, view, map[string][]byte{}}
|
||||
if err := h.handleTxIns(b.claimTrie); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := h.handleTxOuts(b.claimTrie); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Hack: let the claimtrie know the expected Hash.
|
||||
b.claimTrie.ReportHash(ht, node.claimTrie)
|
||||
|
||||
err := b.claimTrie.AppendBlock()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hash := b.claimTrie.MerkleHash()
|
||||
|
||||
if node.claimTrie != *hash {
|
||||
if failOnHashMiss {
|
||||
return fmt.Errorf("height: %d, ct.MerkleHash: %s != node.ClaimTrie: %s", ht, *hash, node.claimTrie)
|
||||
}
|
||||
if !mismatchedPrinted {
|
||||
fmt.Printf("\n\nHeight: %d, ct.MerkleHash: %s != node.ClaimTrie: %s, Error: %s\n", ht, *hash, node.claimTrie, err)
|
||||
mismatchedPrinted = true
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type handler struct {
|
||||
ht int32
|
||||
tx *btcutil.Tx
|
||||
view *UtxoViewpoint
|
||||
spent map[string][]byte
|
||||
}
|
||||
|
||||
func (h *handler) handleTxIns(ct *claimtrie.ClaimTrie) error {
|
||||
if IsCoinBase(h.tx) {
|
||||
return nil
|
||||
}
|
||||
for _, txIn := range h.tx.MsgTx().TxIn {
|
||||
op := txIn.PreviousOutPoint
|
||||
e := h.view.LookupEntry(op)
|
||||
if e == nil {
|
||||
return fmt.Errorf("missing input in view for %s", op.String())
|
||||
}
|
||||
cs, err := txscript.DecodeClaimScript(e.pkScript)
|
||||
if err == txscript.ErrNotClaimScript {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var id node.ClaimID
|
||||
name := cs.Name() // name of the previous one (that we're now spending)
|
||||
|
||||
switch cs.Opcode() {
|
||||
case txscript.OP_CLAIMNAME: // OP code from previous transaction
|
||||
id = node.NewClaimID(op) // claimID of the previous item now being spent
|
||||
h.spent[id.String()] = node.NormalizeIfNecessary(name, ct.Height())
|
||||
err = ct.SpendClaim(name, op, id)
|
||||
case txscript.OP_UPDATECLAIM:
|
||||
copy(id[:], cs.ClaimID())
|
||||
h.spent[id.String()] = node.NormalizeIfNecessary(name, ct.Height())
|
||||
err = ct.SpendClaim(name, op, id)
|
||||
case txscript.OP_SUPPORTCLAIM:
|
||||
copy(id[:], cs.ClaimID())
|
||||
err = ct.SpendSupport(name, op, id)
|
||||
}
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "handleTxIns")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *handler) handleTxOuts(ct *claimtrie.ClaimTrie) error {
|
||||
for i, txOut := range h.tx.MsgTx().TxOut {
|
||||
op := wire.NewOutPoint(h.tx.Hash(), uint32(i))
|
||||
cs, err := txscript.DecodeClaimScript(txOut.PkScript)
|
||||
if err == txscript.ErrNotClaimScript {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var id node.ClaimID
|
||||
name := cs.Name()
|
||||
amt := txOut.Value
|
||||
value := cs.Value()
|
||||
|
||||
switch cs.Opcode() {
|
||||
case txscript.OP_CLAIMNAME:
|
||||
id = node.NewClaimID(*op)
|
||||
err = ct.AddClaim(name, *op, id, amt, value)
|
||||
case txscript.OP_SUPPORTCLAIM:
|
||||
copy(id[:], cs.ClaimID())
|
||||
err = ct.AddSupport(name, value, *op, amt, id)
|
||||
case txscript.OP_UPDATECLAIM:
|
||||
// old code wouldn't run the update if name or claimID didn't match existing data
|
||||
// that was a safety feature, but it should have rejected the transaction instead
|
||||
// TODO: reject transactions with invalid update commands
|
||||
copy(id[:], cs.ClaimID())
|
||||
normName := node.NormalizeIfNecessary(name, ct.Height())
|
||||
if !bytes.Equal(h.spent[id.String()], normName) {
|
||||
fmt.Printf("Invalid update operation: name or ID mismatch for %s, %s\n", normName, id.String())
|
||||
continue
|
||||
}
|
||||
|
||||
delete(h.spent, id.String())
|
||||
err = ct.UpdateClaim(name, *op, amt, id, value)
|
||||
}
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "handleTxOuts")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
5
btcd.go
5
btcd.go
|
@ -16,6 +16,7 @@ import (
|
|||
"runtime/pprof"
|
||||
|
||||
"github.com/btcsuite/btcd/blockchain/indexers"
|
||||
"github.com/btcsuite/btcd/claimtrie/param"
|
||||
"github.com/btcsuite/btcd/database"
|
||||
"github.com/btcsuite/btcd/limits"
|
||||
|
||||
|
@ -147,6 +148,8 @@ func btcdMain(serverChan chan<- *server) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
param.SetNetwork(activeNetParams.Params.Net, netName(activeNetParams)) // prep the claimtrie params
|
||||
|
||||
// Create server and start it.
|
||||
server, err := newServer(cfg.Listeners, cfg.AgentBlacklist,
|
||||
cfg.AgentWhitelist, db, activeNetParams.Params, interrupt)
|
||||
|
@ -161,6 +164,8 @@ func btcdMain(serverChan chan<- *server) error {
|
|||
server.Stop()
|
||||
server.WaitForShutdown()
|
||||
srvrLog.Infof("Server shutdown complete")
|
||||
// TODO: tie into the sync manager for shutdown instead
|
||||
server.chain.ClaimTrie().Close()
|
||||
}()
|
||||
server.Start()
|
||||
if serverChan != nil {
|
||||
|
|
Loading…
Add table
Reference in a new issue