From 393126a53bdb4a1cc08e86e27af5618de68f900a Mon Sep 17 00:00:00 2001 From: Christopher Tarry Date: Wed, 16 Oct 2024 15:12:24 -0400 Subject: [PATCH 01/13] store new foundation address / miner fee --- explorer/types.go | 3 +++ persist/sqlite/init.sql | 5 +++- persist/sqlite/v2consensus.go | 9 +++++-- persist/sqlite/v2transactions.go | 42 ++++++++++++++++++++++++++++++-- 4 files changed, 54 insertions(+), 5 deletions(-) diff --git a/explorer/types.go b/explorer/types.go index 57733697..798432be 100644 --- a/explorer/types.go +++ b/explorer/types.go @@ -171,6 +171,9 @@ type V2Transaction struct { ID types.TransactionID `json:"id"` ArbitraryData []byte `json:"arbitraryData,omitempty"` + NewFoundationAddress *types.Address `json:"newFoundationAddress,omitempty"` + MinerFee types.Currency `json:"minerFee"` + HostAnnouncements []chain.HostAnnouncement `json:"hostAnnouncements,omitempty"` } diff --git a/persist/sqlite/init.sql b/persist/sqlite/init.sql index a34074ca..2ba74761 100644 --- a/persist/sqlite/init.sql +++ b/persist/sqlite/init.sql @@ -255,7 +255,10 @@ CREATE INDEX transaction_file_contract_revisions_transaction_id_index ON transac CREATE TABLE v2_transactions ( id INTEGER PRIMARY KEY, - transaction_id BLOB UNIQUE NOT NULL + transaction_id BLOB UNIQUE NOT NULL, + + new_foundation_address BLOB, + miner_fee BLOB NOT NULL ); CREATE INDEX v2_transactions_transaction_id_index ON v2_transactions(transaction_id); diff --git a/persist/sqlite/v2consensus.go b/persist/sqlite/v2consensus.go index 43874134..42347644 100644 --- a/persist/sqlite/v2consensus.go +++ b/persist/sqlite/v2consensus.go @@ -29,7 +29,7 @@ func addV2Transactions(tx *txn, bid types.BlockID, txns []types.V2Transaction) ( } defer checkTransactionStmt.Close() - insertTransactionStmt, err := tx.Prepare(`INSERT INTO v2_transactions (transaction_id) VALUES (?)`) + insertTransactionStmt, err := tx.Prepare(`INSERT INTO v2_transactions (transaction_id, new_foundation_address, miner_fee) VALUES (?, ?, ?)`) if err != nil { return nil, fmt.Errorf("failed to prepare insert v2_transaction statement: %v", err) } @@ -52,7 +52,12 @@ func addV2Transactions(tx *txn, bid types.BlockID, txns []types.V2Transaction) ( } if !exist { - result, err := insertTransactionStmt.Exec(encode(txn.ID())) + var newFoundationAddress any + if txn.NewFoundationAddress != nil { + newFoundationAddress = encode(txn.NewFoundationAddress) + } + + result, err := insertTransactionStmt.Exec(encode(txn.ID()), newFoundationAddress, encode(txn.MinerFee)) if err != nil { return nil, fmt.Errorf("failed to insert into v2_transactions: %w", err) } diff --git a/persist/sqlite/v2transactions.go b/persist/sqlite/v2transactions.go index 666dee26..36657998 100644 --- a/persist/sqlite/v2transactions.go +++ b/persist/sqlite/v2transactions.go @@ -83,12 +83,46 @@ WHERE transaction_id IN (` + queryPlaceHolders(len(txnIDs)) + `)` return result, nil } +type v2OtherFields struct { + newFoundationAddress *types.Address + minerFee types.Currency +} + +// v2TransactionOtherFields returns the new foundation address and miner fee of a v2 +// transaction. +func v2TransactionOtherFields(tx *txn, txnIDs []int64) (map[int64]v2OtherFields, error) { + query := `SELECT id, new_foundation_address, miner_fee +FROM v2_transactions +WHERE id IN (` + queryPlaceHolders(len(txnIDs)) + `)` + rows, err := tx.Query(query, queryArgs(txnIDs)...) + if err != nil { + return nil, err + } + defer rows.Close() + + result := make(map[int64]v2OtherFields) + for rows.Next() { + var txnID int64 + var fields v2OtherFields + if err := rows.Scan(&txnID, decodeNull(&fields.newFoundationAddress), decode(&fields.minerFee)); err != nil { + return nil, fmt.Errorf("failed to scan new foundation address and miner fee: %w", err) + } + result[txnID] = fields + } + return result, nil +} + func getV2Transactions(tx *txn, idMap map[int64]transactionID) ([]explorer.V2Transaction, error) { dbIDs := make([]int64, len(idMap)) for order, id := range idMap { dbIDs[order] = id.dbID } + txnOtherFields, err := v2TransactionOtherFields(tx, dbIDs) + if err != nil { + return nil, fmt.Errorf("getV2Transactions: failed to get other fields: %w", err) + } + txnArbitraryData, err := v2TransactionArbitraryData(tx, dbIDs) if err != nil { return nil, fmt.Errorf("getV2Transactions: failed to get arbitrary data: %w", err) @@ -96,9 +130,13 @@ func getV2Transactions(tx *txn, idMap map[int64]transactionID) ([]explorer.V2Tra var results []explorer.V2Transaction for order, dbID := range dbIDs { + otherFields := txnOtherFields[dbID] + txn := explorer.V2Transaction{ - ID: idMap[int64(order)].id, - ArbitraryData: txnArbitraryData[dbID], + ID: idMap[int64(order)].id, + ArbitraryData: txnArbitraryData[dbID], + NewFoundationAddress: otherFields.newFoundationAddress, + MinerFee: otherFields.minerFee, } // for _, attestation := range txn.Attestations { From e16f1ed7ba74bff23239d0dd3036fc9d2f9d134b Mon Sep 17 00:00:00 2001 From: Christopher Tarry Date: Wed, 16 Oct 2024 16:46:01 -0400 Subject: [PATCH 02/13] add miner fee test --- go.mod | 8 +- go.sum | 8 + internal/testutil/chain.go | 53 +++++- internal/testutil/check.go | 3 + persist/sqlite/consensus_test.go | 6 +- persist/sqlite/v2consensus_test.go | 250 ++++++++++++++++++++++++++++- 6 files changed, 320 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index a2602f86..f166c60c 100644 --- a/go.mod +++ b/go.mod @@ -6,8 +6,8 @@ toolchain go1.23.2 require ( github.com/mattn/go-sqlite3 v1.14.24 - go.sia.tech/core v0.4.7 - go.sia.tech/coreutils v0.4.1 + go.sia.tech/core v0.4.8-0.20241015191424-3a45c8b415e7 + go.sia.tech/coreutils v0.4.2-0.20241007200058-9a2654c61a97 go.sia.tech/jape v0.12.1 go.uber.org/zap v1.27.0 gopkg.in/yaml.v3 v3.0.1 @@ -23,8 +23,8 @@ require ( go.etcd.io/bbolt v1.3.11 // indirect go.sia.tech/mux v1.3.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.27.0 // indirect - golang.org/x/sys v0.25.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/sys v0.26.0 // indirect golang.org/x/tools v0.20.0 // indirect gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect ) diff --git a/go.sum b/go.sum index eb8ca55f..5323c9a1 100644 --- a/go.sum +++ b/go.sum @@ -21,8 +21,12 @@ go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= go.sia.tech/core v0.4.7 h1:UAyErZ3nk5/7N0gIG0OEEJJrxh7ru8lgGLlaNtT/Jq0= go.sia.tech/core v0.4.7/go.mod h1:j2Ke8ihV8or7d2VDrFZWcCkwSVHO0DNMQJAGs9Qop2M= +go.sia.tech/core v0.4.8-0.20241015191424-3a45c8b415e7 h1:xN8iVwYd5/qGYCBHejI5vzxXt8P7kAGelyFK+nKGfSc= +go.sia.tech/core v0.4.8-0.20241015191424-3a45c8b415e7/go.mod h1:CpiFY0jL5OlU6sm/6fwd6/LQe6Ao8G6OtHtq21ggIoA= go.sia.tech/coreutils v0.4.1 h1:ExQ9g6EtnFe70ptNBG+OtZyFU3aBoEzE/06rtbN6f4c= go.sia.tech/coreutils v0.4.1/go.mod h1:v60kPqZERsb1ZS0PVe4S8hr2ArNEwTdp7XTzErXnV2U= +go.sia.tech/coreutils v0.4.2-0.20241007200058-9a2654c61a97 h1:DPK4fA7HNdTgb02fsdFHbtaB2+ydy/78M1sHhznQkMw= +go.sia.tech/coreutils v0.4.2-0.20241007200058-9a2654c61a97/go.mod h1:JIaR+zdGZsqPLBM5mVsnwWJ7hBsES+SAEDQg5EFBitM= go.sia.tech/jape v0.12.1 h1:xr+o9V8FO8ScRqbSaqYf9bjj1UJ2eipZuNcI1nYousU= go.sia.tech/jape v0.12.1/go.mod h1:wU+h6Wh5olDjkPXjF0tbZ1GDgoZ6VTi4naFw91yyWC4= go.sia.tech/mux v1.3.0 h1:hgR34IEkqvfBKUJkAzGi31OADeW2y7D6Bmy/Jcbop9c= @@ -35,12 +39,16 @@ go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY= golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/testutil/chain.go b/internal/testutil/chain.go index c7e04fe9..9cd25128 100644 --- a/internal/testutil/chain.go +++ b/internal/testutil/chain.go @@ -78,11 +78,18 @@ func CreateAnnouncement(priv types.PrivateKey, netAddress string) []byte { // MineBlock mines sets the metadata fields of the block along with the // transactions and then generates a valid nonce for the block. func MineBlock(state consensus.State, txns []types.Transaction, minerAddr types.Address) types.Block { + reward := state.BlockReward() + for _, txn := range txns { + for _, fee := range txn.MinerFees { + reward = reward.Add(fee) + } + } + b := types.Block{ ParentID: state.Index.ID, Timestamp: types.CurrentTimestamp(), Transactions: txns, - MinerPayouts: []types.SiacoinOutput{{Address: minerAddr, Value: state.BlockReward()}}, + MinerPayouts: []types.SiacoinOutput{{Address: minerAddr, Value: reward}}, } if !coreutils.FindBlockNonce(state, &b, time.Minute) { panic("failed to mine test block quickly enough") @@ -93,10 +100,15 @@ func MineBlock(state consensus.State, txns []types.Transaction, minerAddr types. // MineV2Block mines sets the metadata fields of the block along with the // transactions and then generates a valid nonce for the block. func MineV2Block(state consensus.State, txns []types.V2Transaction, minerAddr types.Address) types.Block { + reward := state.BlockReward() + for _, txn := range txns { + reward = reward.Add(txn.MinerFee) + } + b := types.Block{ ParentID: state.Index.ID, Timestamp: types.CurrentTimestamp(), - MinerPayouts: []types.SiacoinOutput{{Address: minerAddr, Value: state.BlockReward()}}, + MinerPayouts: []types.SiacoinOutput{{Address: minerAddr, Value: reward}}, V2: &types.V2BlockData{ Transactions: txns, @@ -142,3 +154,40 @@ func SignTransaction(cs consensus.State, pk types.PrivateKey, txn *types.Transac } SignTransactionWithContracts(cs, pk, types.PrivateKey{}, types.PrivateKey{}, txn) } + +// SignTransactionWithContracts signs a transaction using the specified private +// keys, including contracts and revisions. +func SignV2TransactionWithContracts(cs consensus.State, pk, renterPK, hostPK types.PrivateKey, txn *types.V2Transaction) { + for i := range txn.SiacoinInputs { + txn.SiacoinInputs[i].SatisfiedPolicy.Signatures = []types.Signature{pk.SignHash(cs.InputSigHash(*txn))} + } + for i := range txn.SiafundInputs { + txn.SiafundInputs[i].SatisfiedPolicy.Signatures = []types.Signature{pk.SignHash(cs.InputSigHash(*txn))} + } + for i := range txn.FileContracts { + txn.FileContracts[i].RenterSignature = renterPK.SignHash(cs.ContractSigHash(txn.FileContracts[i])) + txn.FileContracts[i].HostSignature = hostPK.SignHash(cs.ContractSigHash(txn.FileContracts[i])) + } + for i := range txn.FileContractRevisions { + txn.FileContractRevisions[i].Revision.RenterSignature = renterPK.SignHash(cs.ContractSigHash(txn.FileContractRevisions[i].Revision)) + txn.FileContractRevisions[i].Revision.HostSignature = hostPK.SignHash(cs.ContractSigHash(txn.FileContractRevisions[i].Revision)) + } + for i := range txn.FileContractResolutions { + switch r := txn.FileContractResolutions[i].Resolution.(type) { + case *types.V2FileContractRenewal: + r.RenterSignature = renterPK.SignHash(cs.RenewalSigHash(*r)) + r.HostSignature = hostPK.SignHash(cs.RenewalSigHash(*r)) + case *types.V2FileContractFinalization: + *r = types.V2FileContractFinalization(renterPK.SignHash(cs.ContractSigHash(txn.FileContractResolutions[i].Parent.V2FileContract))) + } + } +} + +// SignV2Transaction signs a transaction that does not have any contracts with +// the specified private key. +func SignV2Transaction(cs consensus.State, pk types.PrivateKey, txn *types.V2Transaction) { + if len(txn.FileContracts) > 0 || len(txn.FileContractRevisions) > 0 { + panic("use SignV2TransactionWithContracts instead") + } + SignV2TransactionWithContracts(cs, pk, types.PrivateKey{}, types.PrivateKey{}, txn) +} diff --git a/internal/testutil/check.go b/internal/testutil/check.go index 95fbe4f3..9f112dad 100644 --- a/internal/testutil/check.go +++ b/internal/testutil/check.go @@ -108,6 +108,9 @@ func CheckTransaction(t *testing.T, expectTxn types.Transaction, gotTxn explorer func CheckV2Transaction(t *testing.T, expectTxn types.V2Transaction, gotTxn explorer.V2Transaction) { t.Helper() + Equal(t, "new foundation address", expectTxn.NewFoundationAddress, gotTxn.NewFoundationAddress) + Equal(t, "miner fee", expectTxn.MinerFee, gotTxn.MinerFee) + Equal(t, "arbitrary data", len(expectTxn.ArbitraryData), len(gotTxn.ArbitraryData)) for i := range expectTxn.ArbitraryData { diff --git a/persist/sqlite/consensus_test.go b/persist/sqlite/consensus_test.go index 485fd494..6c12eff8 100644 --- a/persist/sqlite/consensus_test.go +++ b/persist/sqlite/consensus_test.go @@ -2533,7 +2533,11 @@ func TestMetricCirculatingSupply(t *testing.T) { cm := chain.NewManager(store, genesisState) - circulatingSupply := genesisState.FoundationSubsidy().Value + var circulatingSupply types.Currency + if subsidy, ok := genesisState.FoundationSubsidy(); ok { + circulatingSupply = circulatingSupply.Add(subsidy.Value) + } + for _, txn := range genesisBlock.Transactions { for _, sco := range txn.SiacoinOutputs { circulatingSupply = circulatingSupply.Add(sco.Value) diff --git a/persist/sqlite/v2consensus_test.go b/persist/sqlite/v2consensus_test.go index a7688f94..365e32b3 100644 --- a/persist/sqlite/v2consensus_test.go +++ b/persist/sqlite/v2consensus_test.go @@ -1,18 +1,208 @@ package sqlite_test import ( + "errors" "path/filepath" "testing" + "time" + "go.sia.tech/core/consensus" "go.sia.tech/core/types" "go.sia.tech/coreutils" "go.sia.tech/coreutils/chain" ctestutil "go.sia.tech/coreutils/testutil" + "go.sia.tech/explored/explorer" "go.sia.tech/explored/internal/testutil" "go.sia.tech/explored/persist/sqlite" "go.uber.org/zap/zaptest" ) +type consensusDB struct { + sces map[types.SiacoinOutputID]types.SiacoinElement + sfes map[types.SiafundOutputID]types.SiafundElement + fces map[types.FileContractID]types.FileContractElement + v2fces map[types.FileContractID]types.V2FileContractElement +} + +func (db *consensusDB) applyBlock(au consensus.ApplyUpdate) { + for id, sce := range db.sces { + au.UpdateElementProof(&sce.StateElement) + db.sces[id] = sce + } + for id, sfe := range db.sfes { + au.UpdateElementProof(&sfe.StateElement) + db.sfes[id] = sfe + } + for id, fce := range db.fces { + au.UpdateElementProof(&fce.StateElement) + db.fces[id] = fce + } + for id, fce := range db.v2fces { + au.UpdateElementProof(&fce.StateElement) + db.v2fces[id] = fce + } + au.ForEachSiacoinElement(func(sce types.SiacoinElement, created, spent bool) { + if spent { + delete(db.sces, types.SiacoinOutputID(sce.ID)) + } else { + db.sces[types.SiacoinOutputID(sce.ID)] = sce + } + }) + au.ForEachSiafundElement(func(sfe types.SiafundElement, created, spent bool) { + if spent { + delete(db.sfes, types.SiafundOutputID(sfe.ID)) + } else { + db.sfes[types.SiafundOutputID(sfe.ID)] = sfe + } + }) + au.ForEachFileContractElement(func(fce types.FileContractElement, created bool, rev *types.FileContractElement, resolved, valid bool) { + if created { + db.fces[types.FileContractID(fce.ID)] = fce + } else if rev != nil { + db.fces[types.FileContractID(fce.ID)] = *rev + } else if resolved { + delete(db.fces, types.FileContractID(fce.ID)) + } + }) + au.ForEachV2FileContractElement(func(fce types.V2FileContractElement, created bool, rev *types.V2FileContractElement, res types.V2FileContractResolutionType) { + if created { + db.v2fces[types.FileContractID(fce.ID)] = fce + } else if rev != nil { + db.v2fces[types.FileContractID(fce.ID)] = *rev + } else if res != nil { + delete(db.v2fces, types.FileContractID(fce.ID)) + } + }) +} + +func (db *consensusDB) revertBlock(ru consensus.RevertUpdate) { + ru.ForEachSiacoinElement(func(sce types.SiacoinElement, created, spent bool) { + if spent { + db.sces[types.SiacoinOutputID(sce.ID)] = sce + } else { + delete(db.sces, types.SiacoinOutputID(sce.ID)) + } + }) + ru.ForEachSiafundElement(func(sfe types.SiafundElement, created, spent bool) { + if spent { + db.sfes[types.SiafundOutputID(sfe.ID)] = sfe + } else { + delete(db.sfes, types.SiafundOutputID(sfe.ID)) + } + }) + ru.ForEachFileContractElement(func(fce types.FileContractElement, created bool, rev *types.FileContractElement, resolved, valid bool) { + if created { + delete(db.fces, types.FileContractID(fce.ID)) + } else if rev != nil { + db.fces[types.FileContractID(fce.ID)] = fce + } else if resolved { + db.fces[types.FileContractID(fce.ID)] = fce + } + }) + ru.ForEachV2FileContractElement(func(fce types.V2FileContractElement, created bool, rev *types.V2FileContractElement, res types.V2FileContractResolutionType) { + if created { + delete(db.v2fces, types.FileContractID(fce.ID)) + } else if rev != nil { + db.v2fces[types.FileContractID(fce.ID)] = fce + } else if res != nil { + db.v2fces[types.FileContractID(fce.ID)] = fce + } + }) + for id, sce := range db.sces { + ru.UpdateElementProof(&sce.StateElement) + db.sces[id] = sce + } + for id, sfe := range db.sfes { + ru.UpdateElementProof(&sfe.StateElement) + db.sfes[id] = sfe + } + for id, fce := range db.fces { + ru.UpdateElementProof(&fce.StateElement) + db.fces[id] = fce + } + for id, fce := range db.v2fces { + ru.UpdateElementProof(&fce.StateElement) + db.v2fces[id] = fce + } +} + +func (db *consensusDB) supplementTipBlock(b types.Block) (bs consensus.V1BlockSupplement) { + bs = consensus.V1BlockSupplement{ + Transactions: make([]consensus.V1TransactionSupplement, len(b.Transactions)), + } + for i, txn := range b.Transactions { + ts := &bs.Transactions[i] + for _, sci := range txn.SiacoinInputs { + if sce, ok := db.sces[sci.ParentID]; ok { + ts.SiacoinInputs = append(ts.SiacoinInputs, sce) + } + } + for _, sfi := range txn.SiafundInputs { + if sfe, ok := db.sfes[sfi.ParentID]; ok { + ts.SiafundInputs = append(ts.SiafundInputs, sfe) + } + } + for _, fcr := range txn.FileContractRevisions { + if fce, ok := db.fces[fcr.ParentID]; ok { + ts.RevisedFileContracts = append(ts.RevisedFileContracts, fce) + } + } + } + return bs +} + +func (db *consensusDB) ancestorTimestamp(types.BlockID) time.Time { + return time.Time{} +} + +// v2SyncDB is the same as syncDB but it updates the consensusDB `edb` to keep +// track of elements and update their proofs to make teseting easier. +func v2SyncDB(t *testing.T, edb *consensusDB, db *sqlite.Store, cm *chain.Manager) { + index, err := db.Tip() + if err != nil && !errors.Is(err, explorer.ErrNoTip) { + t.Fatal(err) + } + + for index != cm.Tip() { + crus, caus, err := cm.UpdatesSince(index, 1000) + if err != nil { + t.Fatal(err) + } + + if err := db.UpdateChainState(crus, caus); err != nil { + t.Fatal("failed to process updates:", err) + } + + if edb != nil { + for _, cru := range crus { + edb.revertBlock(cru.RevertUpdate) + } + for _, cau := range caus { + edb.applyBlock(cau.ApplyUpdate) + } + } + + if len(crus) > 0 { + index = crus[len(crus)-1].State.Index + } + if len(caus) > 0 { + index = caus[len(caus)-1].State.Index + } + } +} + +func newConsensusDB(n *consensus.Network, genesisBlock types.Block) (*consensusDB, consensus.State) { + db := &consensusDB{ + sces: make(map[types.SiacoinOutputID]types.SiacoinElement), + sfes: make(map[types.SiafundOutputID]types.SiafundElement), + fces: make(map[types.FileContractID]types.FileContractElement), + v2fces: make(map[types.FileContractID]types.V2FileContractElement), + } + cs, au := consensus.ApplyBlock(n.GenesisState(), genesisBlock, db.supplementTipBlock(genesisBlock), time.Time{}) + db.applyBlock(au) + return db, cs +} + func TestV2ArbitraryData(t *testing.T) { log := zaptest.NewLogger(t) dir := t.TempDir() @@ -50,7 +240,7 @@ func TestV2ArbitraryData(t *testing.T) { if err := cm.AddBlocks([]types.Block{testutil.MineV2Block(cm.TipState(), []types.V2Transaction{txn1, txn2}, types.VoidAddress)}); err != nil { t.Fatal(err) } - syncDB(t, db, cm) + v2SyncDB(t, nil, db, cm) prev := cm.Tip() { @@ -106,3 +296,61 @@ func TestV2ArbitraryData(t *testing.T) { testutil.CheckV2ChainIndices(t, db, txn2.ID(), []types.ChainIndex{cm.Tip(), prev}) testutil.CheckV2ChainIndices(t, db, txn3.ID(), []types.ChainIndex{cm.Tip()}) } + +func TestV2MinerFee(t *testing.T) { + log := zaptest.NewLogger(t) + dir := t.TempDir() + db, err := sqlite.OpenDatabase(filepath.Join(dir, "explored.sqlite3"), log.Named("sqlite3")) + if err != nil { + t.Fatal(err) + } + defer db.Close() + + bdb, err := coreutils.OpenBoltChainDB(filepath.Join(dir, "consensus.db")) + if err != nil { + t.Fatal(err) + } + defer bdb.Close() + + pk1 := types.GeneratePrivateKey() + addr1 := types.StandardUnlockHash(pk1.PublicKey()) + addr1Policy := types.SpendPolicy{types.PolicyTypeUnlockConditions(types.StandardUnlockConditions(pk1.PublicKey()))} + + network, genesisBlock := ctestutil.V2Network() + network.HardforkV2.AllowHeight = 1 + network.HardforkV2.RequireHeight = 2 + + genesisBlock.Transactions[0].SiacoinOutputs[0].Address = addr1 + giftSC := genesisBlock.Transactions[0].SiacoinOutputs[0].Value + + edb, _ := newConsensusDB(network, genesisBlock) + store, genesisState, err := chain.NewDBStore(bdb, network, genesisBlock) + if err != nil { + t.Fatal(err) + } + + cm := chain.NewManager(store, genesisState) + + txn1 := types.V2Transaction{ + ArbitraryData: []byte("hello"), + MinerFee: giftSC, + SiacoinInputs: []types.V2SiacoinInput{{ + Parent: edb.sces[genesisBlock.Transactions[0].SiacoinOutputID(0)], + SatisfiedPolicy: types.SatisfiedPolicy{Policy: addr1Policy}, + }}, + } + testutil.SignV2Transaction(cm.TipState(), pk1, &txn1) + + if err := cm.AddBlocks([]types.Block{testutil.MineV2Block(cm.TipState(), []types.V2Transaction{txn1}, types.VoidAddress)}); err != nil { + t.Fatal(err) + } + v2SyncDB(t, edb, db, cm) + + { + dbTxns, err := db.V2Transactions([]types.TransactionID{txn1.ID()}) + if err != nil { + t.Fatal(err) + } + testutil.CheckV2Transaction(t, txn1, dbTxns[0]) + } +} From 33509cbd4fb8d7b623f030879eda9070a51e7fea Mon Sep 17 00:00:00 2001 From: Christopher Tarry Date: Wed, 16 Oct 2024 17:02:29 -0400 Subject: [PATCH 03/13] add foundation subsidy test --- persist/sqlite/init.sql | 3 +- persist/sqlite/v2consensus_test.go | 62 ++++++++++++++++++++++++++++++ persist/sqlite/v2transactions.go | 8 +++- 3 files changed, 70 insertions(+), 3 deletions(-) diff --git a/persist/sqlite/init.sql b/persist/sqlite/init.sql index 2ba74761..4d331c15 100644 --- a/persist/sqlite/init.sql +++ b/persist/sqlite/init.sql @@ -269,12 +269,11 @@ CREATE TABLE v2_block_transactions ( UNIQUE(block_id, block_order) ); CREATE INDEX v2_block_transactions_block_id_index ON v2_block_transactions(block_id); -CREATE INDEX v2_block_transactions_transaction_id_index ON v2_block_transactions(transaction_id); CREATE INDEX v2_block_transactions_transaction_id_block_id ON v2_block_transactions(transaction_id, block_id); CREATE TABLE v2_transaction_arbitrary_data ( transaction_id INTEGER REFERENCES v2_transactions(id) ON DELETE CASCADE NOT NULL, - data BLOB NOT NULL, + data BLOB, UNIQUE(transaction_id) ); diff --git a/persist/sqlite/v2consensus_test.go b/persist/sqlite/v2consensus_test.go index 365e32b3..6cf7c99a 100644 --- a/persist/sqlite/v2consensus_test.go +++ b/persist/sqlite/v2consensus_test.go @@ -354,3 +354,65 @@ func TestV2MinerFee(t *testing.T) { testutil.CheckV2Transaction(t, txn1, dbTxns[0]) } } + +func TestV2FoundationAddress(t *testing.T) { + log := zaptest.NewLogger(t) + dir := t.TempDir() + db, err := sqlite.OpenDatabase(filepath.Join(dir, "explored.sqlite3"), log.Named("sqlite3")) + if err != nil { + t.Fatal(err) + } + defer db.Close() + + bdb, err := coreutils.OpenBoltChainDB(filepath.Join(dir, "consensus.db")) + if err != nil { + t.Fatal(err) + } + defer bdb.Close() + + pk1 := types.GeneratePrivateKey() + addr1 := types.StandardUnlockHash(pk1.PublicKey()) + addr1Policy := types.SpendPolicy{types.PolicyTypeUnlockConditions(types.StandardUnlockConditions(pk1.PublicKey()))} + + pk2 := types.GeneratePrivateKey() + addr2 := types.StandardUnlockHash(pk2.PublicKey()) + + network, genesisBlock := ctestutil.V2Network() + network.HardforkFoundation.PrimaryAddress = addr1 + network.HardforkV2.AllowHeight = 1 + network.HardforkV2.RequireHeight = 2 + + genesisBlock.Transactions[0].SiacoinOutputs[0].Address = addr1 + giftSC := genesisBlock.Transactions[0].SiacoinOutputs[0].Value + + edb, _ := newConsensusDB(network, genesisBlock) + store, genesisState, err := chain.NewDBStore(bdb, network, genesisBlock) + if err != nil { + t.Fatal(err) + } + + cm := chain.NewManager(store, genesisState) + + txn1 := types.V2Transaction{ + SiacoinInputs: []types.V2SiacoinInput{{ + Parent: edb.sces[genesisBlock.Transactions[0].SiacoinOutputID(0)], + SatisfiedPolicy: types.SatisfiedPolicy{Policy: addr1Policy}, + }}, + MinerFee: giftSC, + NewFoundationAddress: &addr2, + } + testutil.SignV2Transaction(cm.TipState(), pk1, &txn1) + + if err := cm.AddBlocks([]types.Block{testutil.MineV2Block(cm.TipState(), []types.V2Transaction{txn1}, types.VoidAddress)}); err != nil { + t.Fatal(err) + } + v2SyncDB(t, edb, db, cm) + + { + dbTxns, err := db.V2Transactions([]types.TransactionID{txn1.ID()}) + if err != nil { + t.Fatal(err) + } + testutil.CheckV2Transaction(t, txn1, dbTxns[0]) + } +} diff --git a/persist/sqlite/v2transactions.go b/persist/sqlite/v2transactions.go index 36657998..c91f7717 100644 --- a/persist/sqlite/v2transactions.go +++ b/persist/sqlite/v2transactions.go @@ -104,9 +104,15 @@ WHERE id IN (` + queryPlaceHolders(len(txnIDs)) + `)` for rows.Next() { var txnID int64 var fields v2OtherFields - if err := rows.Scan(&txnID, decodeNull(&fields.newFoundationAddress), decode(&fields.minerFee)); err != nil { + var newFoundationAddress types.Address + if err := rows.Scan(&txnID, decodeNull(&newFoundationAddress), decode(&fields.minerFee)); err != nil { return nil, fmt.Errorf("failed to scan new foundation address and miner fee: %w", err) } + + if (newFoundationAddress != types.Address{}) { + fields.newFoundationAddress = &newFoundationAddress + } + result[txnID] = fields } return result, nil From 53ddb9728b1e481322246e8bd20a88d0705adb8e Mon Sep 17 00:00:00 2001 From: Christopher Tarry Date: Wed, 16 Oct 2024 17:07:19 -0400 Subject: [PATCH 04/13] fix lint --- internal/testutil/chain.go | 4 ++-- persist/sqlite/v2consensus_test.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/testutil/chain.go b/internal/testutil/chain.go index 9cd25128..4b2e1dbf 100644 --- a/internal/testutil/chain.go +++ b/internal/testutil/chain.go @@ -155,8 +155,8 @@ func SignTransaction(cs consensus.State, pk types.PrivateKey, txn *types.Transac SignTransactionWithContracts(cs, pk, types.PrivateKey{}, types.PrivateKey{}, txn) } -// SignTransactionWithContracts signs a transaction using the specified private -// keys, including contracts and revisions. +// SignV2TransactionWithContracts signs a transaction using the specified +// private keys, including contracts and revisions. func SignV2TransactionWithContracts(cs consensus.State, pk, renterPK, hostPK types.PrivateKey, txn *types.V2Transaction) { for i := range txn.SiacoinInputs { txn.SiacoinInputs[i].SatisfiedPolicy.Signatures = []types.Signature{pk.SignHash(cs.InputSigHash(*txn))} diff --git a/persist/sqlite/v2consensus_test.go b/persist/sqlite/v2consensus_test.go index 6cf7c99a..f437f864 100644 --- a/persist/sqlite/v2consensus_test.go +++ b/persist/sqlite/v2consensus_test.go @@ -314,7 +314,7 @@ func TestV2MinerFee(t *testing.T) { pk1 := types.GeneratePrivateKey() addr1 := types.StandardUnlockHash(pk1.PublicKey()) - addr1Policy := types.SpendPolicy{types.PolicyTypeUnlockConditions(types.StandardUnlockConditions(pk1.PublicKey()))} + addr1Policy := types.SpendPolicy{Type: types.PolicyTypeUnlockConditions(types.StandardUnlockConditions(pk1.PublicKey()))} network, genesisBlock := ctestutil.V2Network() network.HardforkV2.AllowHeight = 1 @@ -372,7 +372,7 @@ func TestV2FoundationAddress(t *testing.T) { pk1 := types.GeneratePrivateKey() addr1 := types.StandardUnlockHash(pk1.PublicKey()) - addr1Policy := types.SpendPolicy{types.PolicyTypeUnlockConditions(types.StandardUnlockConditions(pk1.PublicKey()))} + addr1Policy := types.SpendPolicy{Type: types.PolicyTypeUnlockConditions(types.StandardUnlockConditions(pk1.PublicKey()))} pk2 := types.GeneratePrivateKey() addr2 := types.StandardUnlockHash(pk2.PublicKey()) From 68bbbe4eef1fc6623c78c771d146164cbe13d2c2 Mon Sep 17 00:00:00 2001 From: Christopher Tarry Date: Fri, 18 Oct 2024 11:18:37 -0400 Subject: [PATCH 05/13] fix nits --- persist/sqlite/v2consensus_test.go | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/persist/sqlite/v2consensus_test.go b/persist/sqlite/v2consensus_test.go index f437f864..cf5dd920 100644 --- a/persist/sqlite/v2consensus_test.go +++ b/persist/sqlite/v2consensus_test.go @@ -151,13 +151,9 @@ func (db *consensusDB) supplementTipBlock(b types.Block) (bs consensus.V1BlockSu return bs } -func (db *consensusDB) ancestorTimestamp(types.BlockID) time.Time { - return time.Time{} -} - -// v2SyncDB is the same as syncDB but it updates the consensusDB `edb` to keep -// track of elements and update their proofs to make teseting easier. -func v2SyncDB(t *testing.T, edb *consensusDB, db *sqlite.Store, cm *chain.Manager) { +// v2SyncDB is the same as syncDB but it updates the consensusDB `elementDB` to +// keep track of elements and update their proofs to facilitate testing. +func v2SyncDB(t *testing.T, elementDB *consensusDB, db *sqlite.Store, cm *chain.Manager) { index, err := db.Tip() if err != nil && !errors.Is(err, explorer.ErrNoTip) { t.Fatal(err) @@ -173,12 +169,12 @@ func v2SyncDB(t *testing.T, edb *consensusDB, db *sqlite.Store, cm *chain.Manage t.Fatal("failed to process updates:", err) } - if edb != nil { + if elementDB != nil { for _, cru := range crus { - edb.revertBlock(cru.RevertUpdate) + elementDB.revertBlock(cru.RevertUpdate) } for _, cau := range caus { - edb.applyBlock(cau.ApplyUpdate) + elementDB.applyBlock(cau.ApplyUpdate) } } @@ -323,7 +319,7 @@ func TestV2MinerFee(t *testing.T) { genesisBlock.Transactions[0].SiacoinOutputs[0].Address = addr1 giftSC := genesisBlock.Transactions[0].SiacoinOutputs[0].Value - edb, _ := newConsensusDB(network, genesisBlock) + elementDB, _ := newConsensusDB(network, genesisBlock) store, genesisState, err := chain.NewDBStore(bdb, network, genesisBlock) if err != nil { t.Fatal(err) @@ -335,7 +331,7 @@ func TestV2MinerFee(t *testing.T) { ArbitraryData: []byte("hello"), MinerFee: giftSC, SiacoinInputs: []types.V2SiacoinInput{{ - Parent: edb.sces[genesisBlock.Transactions[0].SiacoinOutputID(0)], + Parent: elementDB.sces[genesisBlock.Transactions[0].SiacoinOutputID(0)], SatisfiedPolicy: types.SatisfiedPolicy{Policy: addr1Policy}, }}, } @@ -344,7 +340,7 @@ func TestV2MinerFee(t *testing.T) { if err := cm.AddBlocks([]types.Block{testutil.MineV2Block(cm.TipState(), []types.V2Transaction{txn1}, types.VoidAddress)}); err != nil { t.Fatal(err) } - v2SyncDB(t, edb, db, cm) + v2SyncDB(t, elementDB, db, cm) { dbTxns, err := db.V2Transactions([]types.TransactionID{txn1.ID()}) @@ -385,7 +381,7 @@ func TestV2FoundationAddress(t *testing.T) { genesisBlock.Transactions[0].SiacoinOutputs[0].Address = addr1 giftSC := genesisBlock.Transactions[0].SiacoinOutputs[0].Value - edb, _ := newConsensusDB(network, genesisBlock) + elementDB, _ := newConsensusDB(network, genesisBlock) store, genesisState, err := chain.NewDBStore(bdb, network, genesisBlock) if err != nil { t.Fatal(err) @@ -395,7 +391,7 @@ func TestV2FoundationAddress(t *testing.T) { txn1 := types.V2Transaction{ SiacoinInputs: []types.V2SiacoinInput{{ - Parent: edb.sces[genesisBlock.Transactions[0].SiacoinOutputID(0)], + Parent: elementDB.sces[genesisBlock.Transactions[0].SiacoinOutputID(0)], SatisfiedPolicy: types.SatisfiedPolicy{Policy: addr1Policy}, }}, MinerFee: giftSC, @@ -406,7 +402,7 @@ func TestV2FoundationAddress(t *testing.T) { if err := cm.AddBlocks([]types.Block{testutil.MineV2Block(cm.TipState(), []types.V2Transaction{txn1}, types.VoidAddress)}); err != nil { t.Fatal(err) } - v2SyncDB(t, edb, db, cm) + v2SyncDB(t, elementDB, db, cm) { dbTxns, err := db.V2Transactions([]types.TransactionID{txn1.ID()}) From 1d61a239ec3fc58cd0c2b9eec443dd9a2bc68a9b Mon Sep 17 00:00:00 2001 From: Christopher Tarry Date: Fri, 18 Oct 2024 14:03:50 -0400 Subject: [PATCH 06/13] rename other fields to metadata --- persist/sqlite/v2transactions.go | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/persist/sqlite/v2transactions.go b/persist/sqlite/v2transactions.go index c91f7717..7ed43891 100644 --- a/persist/sqlite/v2transactions.go +++ b/persist/sqlite/v2transactions.go @@ -83,14 +83,14 @@ WHERE transaction_id IN (` + queryPlaceHolders(len(txnIDs)) + `)` return result, nil } -type v2OtherFields struct { +type v2Metadata struct { newFoundationAddress *types.Address minerFee types.Currency } -// v2TransactionOtherFields returns the new foundation address and miner fee of a v2 +// v2TransactionMetadata returns the new foundation address and miner fee of a v2 // transaction. -func v2TransactionOtherFields(tx *txn, txnIDs []int64) (map[int64]v2OtherFields, error) { +func v2TransactionMetadata(tx *txn, txnIDs []int64) (map[int64]v2Metadata, error) { query := `SELECT id, new_foundation_address, miner_fee FROM v2_transactions WHERE id IN (` + queryPlaceHolders(len(txnIDs)) + `)` @@ -100,10 +100,10 @@ WHERE id IN (` + queryPlaceHolders(len(txnIDs)) + `)` } defer rows.Close() - result := make(map[int64]v2OtherFields) + result := make(map[int64]v2Metadata) for rows.Next() { var txnID int64 - var fields v2OtherFields + var fields v2Metadata var newFoundationAddress types.Address if err := rows.Scan(&txnID, decodeNull(&newFoundationAddress), decode(&fields.minerFee)); err != nil { return nil, fmt.Errorf("failed to scan new foundation address and miner fee: %w", err) @@ -124,9 +124,9 @@ func getV2Transactions(tx *txn, idMap map[int64]transactionID) ([]explorer.V2Tra dbIDs[order] = id.dbID } - txnOtherFields, err := v2TransactionOtherFields(tx, dbIDs) + txnMetadata, err := v2TransactionMetadata(tx, dbIDs) if err != nil { - return nil, fmt.Errorf("getV2Transactions: failed to get other fields: %w", err) + return nil, fmt.Errorf("getV2Transactions: failed to get metadata fields: %w", err) } txnArbitraryData, err := v2TransactionArbitraryData(tx, dbIDs) @@ -136,13 +136,12 @@ func getV2Transactions(tx *txn, idMap map[int64]transactionID) ([]explorer.V2Tra var results []explorer.V2Transaction for order, dbID := range dbIDs { - otherFields := txnOtherFields[dbID] - + metadata := txnMetadata[dbID] txn := explorer.V2Transaction{ ID: idMap[int64(order)].id, ArbitraryData: txnArbitraryData[dbID], - NewFoundationAddress: otherFields.newFoundationAddress, - MinerFee: otherFields.minerFee, + NewFoundationAddress: metadata.newFoundationAddress, + MinerFee: metadata.minerFee, } // for _, attestation := range txn.Attestations { From 17cfc605cff866040204afe1a093495dd96372cd Mon Sep 17 00:00:00 2001 From: Christopher Tarry Date: Fri, 18 Oct 2024 16:59:34 -0400 Subject: [PATCH 07/13] v2TransactionMetadata nit --- persist/sqlite/v2transactions.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/persist/sqlite/v2transactions.go b/persist/sqlite/v2transactions.go index 7ed43891..bad8344a 100644 --- a/persist/sqlite/v2transactions.go +++ b/persist/sqlite/v2transactions.go @@ -103,17 +103,17 @@ WHERE id IN (` + queryPlaceHolders(len(txnIDs)) + `)` result := make(map[int64]v2Metadata) for rows.Next() { var txnID int64 - var fields v2Metadata + var minerFee types.Currency var newFoundationAddress types.Address - if err := rows.Scan(&txnID, decodeNull(&newFoundationAddress), decode(&fields.minerFee)); err != nil { + if err := rows.Scan(&txnID, decodeNull(&newFoundationAddress), decode(&minerFee)); err != nil { return nil, fmt.Errorf("failed to scan new foundation address and miner fee: %w", err) } + metadata := v2Metadata{minerFee: minerFee} if (newFoundationAddress != types.Address{}) { - fields.newFoundationAddress = &newFoundationAddress + metadata.newFoundationAddress = &newFoundationAddress } - - result[txnID] = fields + result[txnID] = metadata } return result, nil } From 54cf1d9467516b52e53bfb4452d28f8ba40d4ea2 Mon Sep 17 00:00:00 2001 From: Christopher Tarry Date: Mon, 21 Oct 2024 13:22:10 -0400 Subject: [PATCH 08/13] change design of retrieval code for v2 transactions to progressively fill in fields on a slice of txns rather than returning and passing around maps --- persist/sqlite/addresses.go | 2 +- persist/sqlite/init.sql | 2 +- persist/sqlite/v2transactions.go | 162 +++++++++---------------------- 3 files changed, 46 insertions(+), 120 deletions(-) diff --git a/persist/sqlite/addresses.go b/persist/sqlite/addresses.go index 73166838..e4326b33 100644 --- a/persist/sqlite/addresses.go +++ b/persist/sqlite/addresses.go @@ -53,7 +53,7 @@ func scanEvent(tx *txn, s scanner) (ev explorer.Event, eventID int64, err error) if err != nil { return explorer.Event{}, 0, fmt.Errorf("failed to fetch v2 transaction ID: %w", err) } - txns, err := getV2Transactions(tx, map[int64]transactionID{txnID: {id: types.TransactionID(ev.ID)}}) + txns, err := getV2Transactions(tx, []types.TransactionID{types.TransactionID(ev.ID)}) if err != nil || len(txns) == 0 { return explorer.Event{}, 0, fmt.Errorf("failed to fetch v2 transaction: %w", err) } diff --git a/persist/sqlite/init.sql b/persist/sqlite/init.sql index 4d331c15..65ee39ea 100644 --- a/persist/sqlite/init.sql +++ b/persist/sqlite/init.sql @@ -264,8 +264,8 @@ CREATE INDEX v2_transactions_transaction_id_index ON v2_transactions(transaction CREATE TABLE v2_block_transactions ( block_id BLOB REFERENCES blocks(id) ON DELETE CASCADE NOT NULL, - transaction_id INTEGER REFERENCES v2_transactions(id) ON DELETE CASCADE NOT NULL, block_order INTEGER NOT NULL, + transaction_id INTEGER REFERENCES v2_transactions(id) ON DELETE CASCADE NOT NULL, UNIQUE(block_id, block_order) ); CREATE INDEX v2_block_transactions_block_id_index ON v2_block_transactions(block_id); diff --git a/persist/sqlite/v2transactions.go b/persist/sqlite/v2transactions.go index bad8344a..e2f9cdfe 100644 --- a/persist/sqlite/v2transactions.go +++ b/persist/sqlite/v2transactions.go @@ -35,10 +35,10 @@ LIMIT ? OFFSET ?`, encode(txnID), limit, offset) return } -// blockV2TransactionIDs returns the database ID for each v2 transaction in the -// block. -func blockV2TransactionIDs(tx *txn, blockID types.BlockID) (idMap map[int64]transactionID, err error) { - rows, err := tx.Query(`SELECT bt.transaction_id, block_order, t.transaction_id +// blockV2TransactionIDs returns the types.TransactionID for each v2 +// transaction in the block. +func blockV2TransactionIDs(tx *txn, blockID types.BlockID) (ids []types.TransactionID, err error) { + rows, err := tx.Query(`SELECT t.transaction_id FROM v2_block_transactions bt INNER JOIN v2_transactions t ON (t.id = bt.transaction_id) WHERE block_id = ? ORDER BY block_order ASC`, encode(blockID)) @@ -47,154 +47,80 @@ WHERE block_id = ? ORDER BY block_order ASC`, encode(blockID)) } defer rows.Close() - idMap = make(map[int64]transactionID) for rows.Next() { - var dbID int64 - var blockOrder int64 - var txnID types.TransactionID - if err := rows.Scan(&dbID, &blockOrder, decode(&txnID)); err != nil { + var id types.TransactionID + if err := rows.Scan(decode(&id)); err != nil { return nil, fmt.Errorf("failed to scan block transaction: %w", err) } - idMap[blockOrder] = transactionID{id: txnID, dbID: dbID} + ids = append(ids, id) } return } -// v2TransactionArbitraryData returns the arbitrary data for each v2 transaction. -func v2TransactionArbitraryData(tx *txn, txnIDs []int64) (map[int64][]byte, error) { - query := `SELECT transaction_id, data -FROM v2_transaction_arbitrary_data -WHERE transaction_id IN (` + queryPlaceHolders(len(txnIDs)) + `)` - rows, err := tx.Query(query, queryArgs(txnIDs)...) +// getV2Transactions fetches v2 transactions in the correct order using +// prepared statements. +func getV2Transactions(tx *txn, ids []types.TransactionID) ([]explorer.V2Transaction, error) { + dbIDs, txns, err := getV2TransactionBase(tx, ids) if err != nil { - return nil, err + return nil, fmt.Errorf("getV2Transactions: failed to get base transactions: %w", err) } - defer rows.Close() - - result := make(map[int64][]byte) - for rows.Next() { - var txnID int64 - var data []byte - if err := rows.Scan(&txnID, &data); err != nil { - return nil, fmt.Errorf("failed to scan arbitrary data: %w", err) - } - result[txnID] = data + if err := fillV2TransactionArbitraryData(tx, dbIDs, txns); err != nil { + return nil, fmt.Errorf("getV2Transactions: failed to get arbitrary data: %w", err) } - return result, nil -} -type v2Metadata struct { - newFoundationAddress *types.Address - minerFee types.Currency + return txns, nil } -// v2TransactionMetadata returns the new foundation address and miner fee of a v2 -// transaction. -func v2TransactionMetadata(tx *txn, txnIDs []int64) (map[int64]v2Metadata, error) { - query := `SELECT id, new_foundation_address, miner_fee -FROM v2_transactions -WHERE id IN (` + queryPlaceHolders(len(txnIDs)) + `)` - rows, err := tx.Query(query, queryArgs(txnIDs)...) +// getV2TransactionBase fetches the base transaction data for a given list of +// transaction IDs. +func getV2TransactionBase(tx *txn, txnIDs []types.TransactionID) (dbIDs []int64, txns []explorer.V2Transaction, err error) { + stmt, err := tx.Prepare(`SELECT id, transaction_id, new_foundation_address, miner_fee FROM v2_transactions WHERE transaction_id = ?`) if err != nil { - return nil, err + return nil, nil, fmt.Errorf("getV2TransactionBase: failed to prepare statement: %w", err) } - defer rows.Close() + defer stmt.Close() - result := make(map[int64]v2Metadata) - for rows.Next() { - var txnID int64 - var minerFee types.Currency + var dbID int64 + dbIDs = make([]int64, 0, len(txnIDs)) + txns = make([]explorer.V2Transaction, 0, len(txnIDs)) + for _, id := range txnIDs { + var txn explorer.V2Transaction var newFoundationAddress types.Address - if err := rows.Scan(&txnID, decodeNull(&newFoundationAddress), decode(&minerFee)); err != nil { - return nil, fmt.Errorf("failed to scan new foundation address and miner fee: %w", err) + if err := stmt.QueryRow(encode(id)).Scan(&dbID, decode(&txn.ID), decodeNull(&newFoundationAddress), decode(&txn.MinerFee)); err != nil { + return nil, nil, fmt.Errorf("failed to scan base transaction: %w", err) } - - metadata := v2Metadata{minerFee: minerFee} if (newFoundationAddress != types.Address{}) { - metadata.newFoundationAddress = &newFoundationAddress - } - result[txnID] = metadata - } - return result, nil -} - -func getV2Transactions(tx *txn, idMap map[int64]transactionID) ([]explorer.V2Transaction, error) { - dbIDs := make([]int64, len(idMap)) - for order, id := range idMap { - dbIDs[order] = id.dbID - } - - txnMetadata, err := v2TransactionMetadata(tx, dbIDs) - if err != nil { - return nil, fmt.Errorf("getV2Transactions: failed to get metadata fields: %w", err) - } - - txnArbitraryData, err := v2TransactionArbitraryData(tx, dbIDs) - if err != nil { - return nil, fmt.Errorf("getV2Transactions: failed to get arbitrary data: %w", err) - } - - var results []explorer.V2Transaction - for order, dbID := range dbIDs { - metadata := txnMetadata[dbID] - txn := explorer.V2Transaction{ - ID: idMap[int64(order)].id, - ArbitraryData: txnArbitraryData[dbID], - NewFoundationAddress: metadata.newFoundationAddress, - MinerFee: metadata.minerFee, + txn.NewFoundationAddress = &newFoundationAddress } - // for _, attestation := range txn.Attestations { - // var ha chain.HostAnnouncement - // if ha.FromAttestation(attestation) { - // txn.HostAnnouncements = append(txn.HostAnnouncements, ha) - // } - // } - - results = append(results, txn) + dbIDs = append(dbIDs, dbID) + txns = append(txns, txn) } - return results, nil + return dbIDs, txns, nil } -// v2TransactionDatabaseIDs returns the database ID for each transaction. -func v2TransactionDatabaseIDs(tx *txn, txnIDs []types.TransactionID) (dbIDs map[int64]transactionID, err error) { - encodedIDs := func(ids []types.TransactionID) []any { - result := make([]any, len(ids)) - for i, id := range ids { - result[i] = encode(id) - } - return result - } - - query := `SELECT id, transaction_id FROM v2_transactions WHERE transaction_id IN (` + queryPlaceHolders(len(txnIDs)) + `) ORDER BY id` - rows, err := tx.Query(query, encodedIDs(txnIDs)...) +// fillV2TransactionArbitraryData fills in the arbitrary data for each transaction using prepared statements. +func fillV2TransactionArbitraryData(tx *txn, dbIDs []int64, txns []explorer.V2Transaction) error { + stmt, err := tx.Prepare(`SELECT data FROM v2_transaction_arbitrary_data WHERE transaction_id = ?`) if err != nil { - return nil, err + return fmt.Errorf("failed to prepare arbitrary data statement: %w", err) } - defer rows.Close() + defer stmt.Close() - var i int64 - dbIDs = make(map[int64]transactionID) - for rows.Next() { - var dbID int64 - var txnID types.TransactionID - if err := rows.Scan(&dbID, decode(&txnID)); err != nil { - return nil, fmt.Errorf("failed to scan transaction: %w", err) + for i, dbID := range dbIDs { + var data []byte + if err := stmt.QueryRow(dbID).Scan(&data); err != nil { + return fmt.Errorf("failed to scan arbitrary data for txn %d: %w", dbID, err) } - dbIDs[i] = transactionID{id: txnID, dbID: dbID} - i++ + txns[i].ArbitraryData = data } - return + return nil } // V2Transactions implements explorer.Store. func (s *Store) V2Transactions(ids []types.TransactionID) (results []explorer.V2Transaction, err error) { err = s.transaction(func(tx *txn) error { - dbIDs, err := v2TransactionDatabaseIDs(tx, ids) - if err != nil { - return fmt.Errorf("failed to get transaction IDs: %w", err) - } - results, err = getV2Transactions(tx, dbIDs) + results, err = getV2Transactions(tx, ids) if err != nil { return fmt.Errorf("failed to get transactions: %w", err) } From ec5ed366c125e3e7d7119284c63d81100f124521 Mon Sep 17 00:00:00 2001 From: Christopher Tarry Date: Mon, 21 Oct 2024 13:53:45 -0400 Subject: [PATCH 09/13] move arbitrary data to v2_transactions --- persist/sqlite/init.sql | 11 ++--------- persist/sqlite/v2consensus.go | 22 ++-------------------- persist/sqlite/v2transactions.go | 27 +++------------------------ 3 files changed, 7 insertions(+), 53 deletions(-) diff --git a/persist/sqlite/init.sql b/persist/sqlite/init.sql index 65ee39ea..ee993c97 100644 --- a/persist/sqlite/init.sql +++ b/persist/sqlite/init.sql @@ -258,7 +258,8 @@ CREATE TABLE v2_transactions ( transaction_id BLOB UNIQUE NOT NULL, new_foundation_address BLOB, - miner_fee BLOB NOT NULL + miner_fee BLOB NOT NULL, + arbitrary_data BLOB ); CREATE INDEX v2_transactions_transaction_id_index ON v2_transactions(transaction_id); @@ -271,14 +272,6 @@ CREATE TABLE v2_block_transactions ( CREATE INDEX v2_block_transactions_block_id_index ON v2_block_transactions(block_id); CREATE INDEX v2_block_transactions_transaction_id_block_id ON v2_block_transactions(transaction_id, block_id); -CREATE TABLE v2_transaction_arbitrary_data ( - transaction_id INTEGER REFERENCES v2_transactions(id) ON DELETE CASCADE NOT NULL, - data BLOB, - UNIQUE(transaction_id) -); - -CREATE INDEX v2_transaction_arbitrary_data_transaction_id_index ON v2_transaction_arbitrary_data(transaction_id); - CREATE TABLE state_tree ( row INTEGER NOT NULL, column INTEGER NOT NULL, diff --git a/persist/sqlite/v2consensus.go b/persist/sqlite/v2consensus.go index 42347644..32561683 100644 --- a/persist/sqlite/v2consensus.go +++ b/persist/sqlite/v2consensus.go @@ -8,20 +8,6 @@ import ( "go.sia.tech/explored/explorer" ) -func addV2ArbitraryData(tx *txn, id int64, txn types.V2Transaction) error { - stmt, err := tx.Prepare(`INSERT INTO v2_transaction_arbitrary_data(transaction_id, data) VALUES (?, ?)`) - - if err != nil { - return fmt.Errorf("addV2ArbitraryData: failed to prepare statement: %w", err) - } - defer stmt.Close() - - if _, err := stmt.Exec(id, txn.ArbitraryData); err != nil { - return fmt.Errorf("addV2ArbitraryData: failed to execute statement: %w", err) - } - return nil -} - func addV2Transactions(tx *txn, bid types.BlockID, txns []types.V2Transaction) (map[types.TransactionID]txnDBId, error) { checkTransactionStmt, err := tx.Prepare(`SELECT id FROM v2_transactions WHERE transaction_id = ?`) if err != nil { @@ -29,7 +15,7 @@ func addV2Transactions(tx *txn, bid types.BlockID, txns []types.V2Transaction) ( } defer checkTransactionStmt.Close() - insertTransactionStmt, err := tx.Prepare(`INSERT INTO v2_transactions (transaction_id, new_foundation_address, miner_fee) VALUES (?, ?, ?)`) + insertTransactionStmt, err := tx.Prepare(`INSERT INTO v2_transactions (transaction_id, new_foundation_address, miner_fee, arbitrary_data) VALUES (?, ?, ?, ?)`) if err != nil { return nil, fmt.Errorf("failed to prepare insert v2_transaction statement: %v", err) } @@ -57,7 +43,7 @@ func addV2Transactions(tx *txn, bid types.BlockID, txns []types.V2Transaction) ( newFoundationAddress = encode(txn.NewFoundationAddress) } - result, err := insertTransactionStmt.Exec(encode(txn.ID()), newFoundationAddress, encode(txn.MinerFee)) + result, err := insertTransactionStmt.Exec(encode(txn.ID()), newFoundationAddress, encode(txn.MinerFee), txn.ArbitraryData) if err != nil { return nil, fmt.Errorf("failed to insert into v2_transactions: %w", err) } @@ -86,10 +72,6 @@ func addV2TransactionFields(tx *txn, txns []types.V2Transaction, scDBIds map[typ if dbID.exist { continue } - - if err := addV2ArbitraryData(tx, dbID.id, txn); err != nil { - return fmt.Errorf("failed to add arbitrary data: %w", err) - } } return nil diff --git a/persist/sqlite/v2transactions.go b/persist/sqlite/v2transactions.go index e2f9cdfe..659197ab 100644 --- a/persist/sqlite/v2transactions.go +++ b/persist/sqlite/v2transactions.go @@ -60,13 +60,10 @@ WHERE block_id = ? ORDER BY block_order ASC`, encode(blockID)) // getV2Transactions fetches v2 transactions in the correct order using // prepared statements. func getV2Transactions(tx *txn, ids []types.TransactionID) ([]explorer.V2Transaction, error) { - dbIDs, txns, err := getV2TransactionBase(tx, ids) + _, txns, err := getV2TransactionBase(tx, ids) if err != nil { return nil, fmt.Errorf("getV2Transactions: failed to get base transactions: %w", err) } - if err := fillV2TransactionArbitraryData(tx, dbIDs, txns); err != nil { - return nil, fmt.Errorf("getV2Transactions: failed to get arbitrary data: %w", err) - } return txns, nil } @@ -74,7 +71,7 @@ func getV2Transactions(tx *txn, ids []types.TransactionID) ([]explorer.V2Transac // getV2TransactionBase fetches the base transaction data for a given list of // transaction IDs. func getV2TransactionBase(tx *txn, txnIDs []types.TransactionID) (dbIDs []int64, txns []explorer.V2Transaction, err error) { - stmt, err := tx.Prepare(`SELECT id, transaction_id, new_foundation_address, miner_fee FROM v2_transactions WHERE transaction_id = ?`) + stmt, err := tx.Prepare(`SELECT id, transaction_id, new_foundation_address, miner_fee, arbitrary_data FROM v2_transactions WHERE transaction_id = ?`) if err != nil { return nil, nil, fmt.Errorf("getV2TransactionBase: failed to prepare statement: %w", err) } @@ -86,7 +83,7 @@ func getV2TransactionBase(tx *txn, txnIDs []types.TransactionID) (dbIDs []int64, for _, id := range txnIDs { var txn explorer.V2Transaction var newFoundationAddress types.Address - if err := stmt.QueryRow(encode(id)).Scan(&dbID, decode(&txn.ID), decodeNull(&newFoundationAddress), decode(&txn.MinerFee)); err != nil { + if err := stmt.QueryRow(encode(id)).Scan(&dbID, decode(&txn.ID), decodeNull(&newFoundationAddress), decode(&txn.MinerFee), &txn.ArbitraryData); err != nil { return nil, nil, fmt.Errorf("failed to scan base transaction: %w", err) } if (newFoundationAddress != types.Address{}) { @@ -99,24 +96,6 @@ func getV2TransactionBase(tx *txn, txnIDs []types.TransactionID) (dbIDs []int64, return dbIDs, txns, nil } -// fillV2TransactionArbitraryData fills in the arbitrary data for each transaction using prepared statements. -func fillV2TransactionArbitraryData(tx *txn, dbIDs []int64, txns []explorer.V2Transaction) error { - stmt, err := tx.Prepare(`SELECT data FROM v2_transaction_arbitrary_data WHERE transaction_id = ?`) - if err != nil { - return fmt.Errorf("failed to prepare arbitrary data statement: %w", err) - } - defer stmt.Close() - - for i, dbID := range dbIDs { - var data []byte - if err := stmt.QueryRow(dbID).Scan(&data); err != nil { - return fmt.Errorf("failed to scan arbitrary data for txn %d: %w", dbID, err) - } - txns[i].ArbitraryData = data - } - return nil -} - // V2Transactions implements explorer.Store. func (s *Store) V2Transactions(ids []types.TransactionID) (results []explorer.V2Transaction, err error) { err = s.transaction(func(tx *txn) error { From 3fcc51bbe146278ce84f01538fb7129212aef022 Mon Sep 17 00:00:00 2001 From: Christopher Tarry Date: Mon, 21 Oct 2024 14:57:30 -0400 Subject: [PATCH 10/13] get SCE from explorer --- persist/sqlite/merkle.go | 4 +- persist/sqlite/v2consensus_test.go | 206 +++-------------------------- persist/sqlite/v2transactions.go | 4 +- 3 files changed, 22 insertions(+), 192 deletions(-) diff --git a/persist/sqlite/merkle.go b/persist/sqlite/merkle.go index 4e8f19ef..ff4fd103 100644 --- a/persist/sqlite/merkle.go +++ b/persist/sqlite/merkle.go @@ -10,12 +10,12 @@ import ( func (s *Store) MerkleProof(leafIndex uint64) (proof []types.Hash256, err error) { err = s.transaction(func(tx *txn) error { var numLeaves uint64 - if err := tx.QueryRow("SELECT COUNT(*) FROM state_tree WHERE i = 0").Scan(&numLeaves); err != nil { + if err := tx.QueryRow("SELECT COUNT(*) FROM state_tree WHERE row = 0").Scan(&numLeaves); err != nil { return err } pos := leafIndex - stmt, err := tx.Prepare("SELECT hash FROM state_tree WHERE row = ? AND column = ?") + stmt, err := tx.Prepare("SELECT value FROM state_tree WHERE row = ? AND column = ?") if err != nil { return err } diff --git a/persist/sqlite/v2consensus_test.go b/persist/sqlite/v2consensus_test.go index cf5dd920..5f757d97 100644 --- a/persist/sqlite/v2consensus_test.go +++ b/persist/sqlite/v2consensus_test.go @@ -1,12 +1,9 @@ package sqlite_test import ( - "errors" "path/filepath" "testing" - "time" - "go.sia.tech/core/consensus" "go.sia.tech/core/types" "go.sia.tech/coreutils" "go.sia.tech/coreutils/chain" @@ -17,186 +14,21 @@ import ( "go.uber.org/zap/zaptest" ) -type consensusDB struct { - sces map[types.SiacoinOutputID]types.SiacoinElement - sfes map[types.SiafundOutputID]types.SiafundElement - fces map[types.FileContractID]types.FileContractElement - v2fces map[types.FileContractID]types.V2FileContractElement -} - -func (db *consensusDB) applyBlock(au consensus.ApplyUpdate) { - for id, sce := range db.sces { - au.UpdateElementProof(&sce.StateElement) - db.sces[id] = sce - } - for id, sfe := range db.sfes { - au.UpdateElementProof(&sfe.StateElement) - db.sfes[id] = sfe - } - for id, fce := range db.fces { - au.UpdateElementProof(&fce.StateElement) - db.fces[id] = fce - } - for id, fce := range db.v2fces { - au.UpdateElementProof(&fce.StateElement) - db.v2fces[id] = fce - } - au.ForEachSiacoinElement(func(sce types.SiacoinElement, created, spent bool) { - if spent { - delete(db.sces, types.SiacoinOutputID(sce.ID)) - } else { - db.sces[types.SiacoinOutputID(sce.ID)] = sce - } - }) - au.ForEachSiafundElement(func(sfe types.SiafundElement, created, spent bool) { - if spent { - delete(db.sfes, types.SiafundOutputID(sfe.ID)) - } else { - db.sfes[types.SiafundOutputID(sfe.ID)] = sfe - } - }) - au.ForEachFileContractElement(func(fce types.FileContractElement, created bool, rev *types.FileContractElement, resolved, valid bool) { - if created { - db.fces[types.FileContractID(fce.ID)] = fce - } else if rev != nil { - db.fces[types.FileContractID(fce.ID)] = *rev - } else if resolved { - delete(db.fces, types.FileContractID(fce.ID)) - } - }) - au.ForEachV2FileContractElement(func(fce types.V2FileContractElement, created bool, rev *types.V2FileContractElement, res types.V2FileContractResolutionType) { - if created { - db.v2fces[types.FileContractID(fce.ID)] = fce - } else if rev != nil { - db.v2fces[types.FileContractID(fce.ID)] = *rev - } else if res != nil { - delete(db.v2fces, types.FileContractID(fce.ID)) - } - }) -} - -func (db *consensusDB) revertBlock(ru consensus.RevertUpdate) { - ru.ForEachSiacoinElement(func(sce types.SiacoinElement, created, spent bool) { - if spent { - db.sces[types.SiacoinOutputID(sce.ID)] = sce - } else { - delete(db.sces, types.SiacoinOutputID(sce.ID)) - } - }) - ru.ForEachSiafundElement(func(sfe types.SiafundElement, created, spent bool) { - if spent { - db.sfes[types.SiafundOutputID(sfe.ID)] = sfe - } else { - delete(db.sfes, types.SiafundOutputID(sfe.ID)) - } - }) - ru.ForEachFileContractElement(func(fce types.FileContractElement, created bool, rev *types.FileContractElement, resolved, valid bool) { - if created { - delete(db.fces, types.FileContractID(fce.ID)) - } else if rev != nil { - db.fces[types.FileContractID(fce.ID)] = fce - } else if resolved { - db.fces[types.FileContractID(fce.ID)] = fce - } - }) - ru.ForEachV2FileContractElement(func(fce types.V2FileContractElement, created bool, rev *types.V2FileContractElement, res types.V2FileContractResolutionType) { - if created { - delete(db.v2fces, types.FileContractID(fce.ID)) - } else if rev != nil { - db.v2fces[types.FileContractID(fce.ID)] = fce - } else if res != nil { - db.v2fces[types.FileContractID(fce.ID)] = fce - } - }) - for id, sce := range db.sces { - ru.UpdateElementProof(&sce.StateElement) - db.sces[id] = sce - } - for id, sfe := range db.sfes { - ru.UpdateElementProof(&sfe.StateElement) - db.sfes[id] = sfe - } - for id, fce := range db.fces { - ru.UpdateElementProof(&fce.StateElement) - db.fces[id] = fce - } - for id, fce := range db.v2fces { - ru.UpdateElementProof(&fce.StateElement) - db.v2fces[id] = fce - } -} - -func (db *consensusDB) supplementTipBlock(b types.Block) (bs consensus.V1BlockSupplement) { - bs = consensus.V1BlockSupplement{ - Transactions: make([]consensus.V1TransactionSupplement, len(b.Transactions)), - } - for i, txn := range b.Transactions { - ts := &bs.Transactions[i] - for _, sci := range txn.SiacoinInputs { - if sce, ok := db.sces[sci.ParentID]; ok { - ts.SiacoinInputs = append(ts.SiacoinInputs, sce) - } - } - for _, sfi := range txn.SiafundInputs { - if sfe, ok := db.sfes[sfi.ParentID]; ok { - ts.SiafundInputs = append(ts.SiafundInputs, sfe) - } - } - for _, fcr := range txn.FileContractRevisions { - if fce, ok := db.fces[fcr.ParentID]; ok { - ts.RevisedFileContracts = append(ts.RevisedFileContracts, fce) - } - } - } - return bs -} - -// v2SyncDB is the same as syncDB but it updates the consensusDB `elementDB` to -// keep track of elements and update their proofs to facilitate testing. -func v2SyncDB(t *testing.T, elementDB *consensusDB, db *sqlite.Store, cm *chain.Manager) { - index, err := db.Tip() - if err != nil && !errors.Is(err, explorer.ErrNoTip) { +func getSCE(t *testing.T, db explorer.Store, scid types.SiacoinOutputID) types.SiacoinElement { + sces, err := db.SiacoinElements([]types.SiacoinOutputID{scid}) + if err != nil { t.Fatal(err) + } else if len(sces) == 0 { + t.Fatal("can't find sce") } + sce := sces[0] - for index != cm.Tip() { - crus, caus, err := cm.UpdatesSince(index, 1000) - if err != nil { - t.Fatal(err) - } - - if err := db.UpdateChainState(crus, caus); err != nil { - t.Fatal("failed to process updates:", err) - } - - if elementDB != nil { - for _, cru := range crus { - elementDB.revertBlock(cru.RevertUpdate) - } - for _, cau := range caus { - elementDB.applyBlock(cau.ApplyUpdate) - } - } - - if len(crus) > 0 { - index = crus[len(crus)-1].State.Index - } - if len(caus) > 0 { - index = caus[len(caus)-1].State.Index - } + sce.SiacoinElement.MerkleProof, err = db.MerkleProof(sce.StateElement.LeafIndex) + if err != nil { + t.Fatal(err) } -} -func newConsensusDB(n *consensus.Network, genesisBlock types.Block) (*consensusDB, consensus.State) { - db := &consensusDB{ - sces: make(map[types.SiacoinOutputID]types.SiacoinElement), - sfes: make(map[types.SiafundOutputID]types.SiafundElement), - fces: make(map[types.FileContractID]types.FileContractElement), - v2fces: make(map[types.FileContractID]types.V2FileContractElement), - } - cs, au := consensus.ApplyBlock(n.GenesisState(), genesisBlock, db.supplementTipBlock(genesisBlock), time.Time{}) - db.applyBlock(au) - return db, cs + return sce.SiacoinElement } func TestV2ArbitraryData(t *testing.T) { @@ -222,8 +54,8 @@ func TestV2ArbitraryData(t *testing.T) { if err != nil { t.Fatal(err) } - cm := chain.NewManager(store, genesisState) + syncDB(t, db, cm) txn1 := types.V2Transaction{ ArbitraryData: []byte("hello"), @@ -236,7 +68,7 @@ func TestV2ArbitraryData(t *testing.T) { if err := cm.AddBlocks([]types.Block{testutil.MineV2Block(cm.TipState(), []types.V2Transaction{txn1, txn2}, types.VoidAddress)}); err != nil { t.Fatal(err) } - v2SyncDB(t, nil, db, cm) + syncDB(t, db, cm) prev := cm.Tip() { @@ -319,19 +151,18 @@ func TestV2MinerFee(t *testing.T) { genesisBlock.Transactions[0].SiacoinOutputs[0].Address = addr1 giftSC := genesisBlock.Transactions[0].SiacoinOutputs[0].Value - elementDB, _ := newConsensusDB(network, genesisBlock) store, genesisState, err := chain.NewDBStore(bdb, network, genesisBlock) if err != nil { t.Fatal(err) } - cm := chain.NewManager(store, genesisState) + syncDB(t, db, cm) txn1 := types.V2Transaction{ ArbitraryData: []byte("hello"), MinerFee: giftSC, SiacoinInputs: []types.V2SiacoinInput{{ - Parent: elementDB.sces[genesisBlock.Transactions[0].SiacoinOutputID(0)], + Parent: getSCE(t, db, genesisBlock.Transactions[0].SiacoinOutputID(0)), SatisfiedPolicy: types.SatisfiedPolicy{Policy: addr1Policy}, }}, } @@ -340,7 +171,7 @@ func TestV2MinerFee(t *testing.T) { if err := cm.AddBlocks([]types.Block{testutil.MineV2Block(cm.TipState(), []types.V2Transaction{txn1}, types.VoidAddress)}); err != nil { t.Fatal(err) } - v2SyncDB(t, elementDB, db, cm) + syncDB(t, db, cm) { dbTxns, err := db.V2Transactions([]types.TransactionID{txn1.ID()}) @@ -381,17 +212,16 @@ func TestV2FoundationAddress(t *testing.T) { genesisBlock.Transactions[0].SiacoinOutputs[0].Address = addr1 giftSC := genesisBlock.Transactions[0].SiacoinOutputs[0].Value - elementDB, _ := newConsensusDB(network, genesisBlock) store, genesisState, err := chain.NewDBStore(bdb, network, genesisBlock) if err != nil { t.Fatal(err) } - cm := chain.NewManager(store, genesisState) + syncDB(t, db, cm) txn1 := types.V2Transaction{ SiacoinInputs: []types.V2SiacoinInput{{ - Parent: elementDB.sces[genesisBlock.Transactions[0].SiacoinOutputID(0)], + Parent: getSCE(t, db, genesisBlock.Transactions[0].SiacoinOutputID(0)), SatisfiedPolicy: types.SatisfiedPolicy{Policy: addr1Policy}, }}, MinerFee: giftSC, @@ -402,7 +232,7 @@ func TestV2FoundationAddress(t *testing.T) { if err := cm.AddBlocks([]types.Block{testutil.MineV2Block(cm.TipState(), []types.V2Transaction{txn1}, types.VoidAddress)}); err != nil { t.Fatal(err) } - v2SyncDB(t, elementDB, db, cm) + syncDB(t, db, cm) { dbTxns, err := db.V2Transactions([]types.TransactionID{txn1.ID()}) diff --git a/persist/sqlite/v2transactions.go b/persist/sqlite/v2transactions.go index 659197ab..eb835953 100644 --- a/persist/sqlite/v2transactions.go +++ b/persist/sqlite/v2transactions.go @@ -35,8 +35,8 @@ LIMIT ? OFFSET ?`, encode(txnID), limit, offset) return } -// blockV2TransactionIDs returns the types.TransactionID for each v2 -// transaction in the block. +// blockV2TransactionIDs returns the transaction id as a types.TransactionID +// for each v2 transaction in the block. func blockV2TransactionIDs(tx *txn, blockID types.BlockID) (ids []types.TransactionID, err error) { rows, err := tx.Query(`SELECT t.transaction_id FROM v2_block_transactions bt From 8a9aa9753250d38cc7c7a4834ed87bd1ed347104 Mon Sep 17 00:00:00 2001 From: Christopher Tarry Date: Mon, 21 Oct 2024 16:39:53 -0400 Subject: [PATCH 11/13] store num leaves in metrics --- explorer/types.go | 2 ++ explorer/update.go | 1 + persist/sqlite/consensus.go | 3 ++- persist/sqlite/init.sql | 1 + persist/sqlite/merkle.go | 2 +- persist/sqlite/metrics.go | 2 +- 6 files changed, 8 insertions(+), 3 deletions(-) diff --git a/explorer/types.go b/explorer/types.go index 798432be..4f859320 100644 --- a/explorer/types.go +++ b/explorer/types.go @@ -208,6 +208,8 @@ type Metrics struct { SiafundPool types.Currency `json:"siafundPool"` // Total announced hosts TotalHosts uint64 `json:"totalHosts"` + // Number of leaves in the accumulator + NumLeaves uint64 `json:"numLeaves"` // Number of active contracts ActiveContracts uint64 `json:"activeContracts"` // Number of failed contracts diff --git a/explorer/update.go b/explorer/update.go index f5447503..0db1701f 100644 --- a/explorer/update.go +++ b/explorer/update.go @@ -199,6 +199,7 @@ func applyChainUpdate(tx UpdateTx, cau chain.ApplyUpdate) error { state.Metrics.Index = cau.State.Index state.Metrics.Difficulty = cau.State.Difficulty state.Metrics.SiafundPool = cau.State.SiafundPool + state.Metrics.NumLeaves = cau.State.Elements.NumLeaves return tx.ApplyIndex(state) } diff --git a/persist/sqlite/consensus.go b/persist/sqlite/consensus.go index 65fec026..a66d3f08 100644 --- a/persist/sqlite/consensus.go +++ b/persist/sqlite/consensus.go @@ -1006,11 +1006,12 @@ func updateFileContractIndices(tx *txn, revert bool, index types.ChainIndex, fce } func addMetrics(tx *txn, s explorer.UpdateState) error { - _, err := tx.Exec(`INSERT INTO network_metrics(block_id, height, difficulty, siafund_pool, total_hosts, active_contracts, failed_contracts, successful_contracts, storage_utilization, circulating_supply, contract_revenue) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + _, err := tx.Exec(`INSERT INTO network_metrics(block_id, height, difficulty, siafund_pool, num_leaves, total_hosts, active_contracts, failed_contracts, successful_contracts, storage_utilization, circulating_supply, contract_revenue) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, encode(s.Metrics.Index.ID), s.Metrics.Index.Height, encode(s.Metrics.Difficulty), encode(s.Metrics.SiafundPool), + encode(s.Metrics.NumLeaves), s.Metrics.TotalHosts, s.Metrics.ActiveContracts, s.Metrics.FailedContracts, diff --git a/persist/sqlite/init.sql b/persist/sqlite/init.sql index ee993c97..446d9498 100644 --- a/persist/sqlite/init.sql +++ b/persist/sqlite/init.sql @@ -21,6 +21,7 @@ CREATE TABLE network_metrics ( height INTEGER NOT NULL, difficulty BLOB NOT NULL, siafund_pool BLOB NOT NULL, + num_leaves BLOB NOT NULL, total_hosts INTEGER NOT NULL, active_contracts INTEGER NOT NULL, failed_contracts INTEGER NOT NULL, diff --git a/persist/sqlite/merkle.go b/persist/sqlite/merkle.go index ff4fd103..bcbd47a8 100644 --- a/persist/sqlite/merkle.go +++ b/persist/sqlite/merkle.go @@ -10,7 +10,7 @@ import ( func (s *Store) MerkleProof(leafIndex uint64) (proof []types.Hash256, err error) { err = s.transaction(func(tx *txn) error { var numLeaves uint64 - if err := tx.QueryRow("SELECT COUNT(*) FROM state_tree WHERE row = 0").Scan(&numLeaves); err != nil { + if err := tx.QueryRow("SELECT num_leaves FROM network_metrics ORDER BY height DESC LIMIT 1").Scan(decode(&numLeaves)); err != nil { return err } diff --git a/persist/sqlite/metrics.go b/persist/sqlite/metrics.go index 8a0103af..6258e54f 100644 --- a/persist/sqlite/metrics.go +++ b/persist/sqlite/metrics.go @@ -11,7 +11,7 @@ import ( // Metrics implements explorer.Store func (s *Store) Metrics(id types.BlockID) (result explorer.Metrics, err error) { err = s.transaction(func(tx *txn) error { - err = tx.QueryRow(`SELECT block_id, height, difficulty, siafund_pool, total_hosts, active_contracts, failed_contracts, successful_contracts, storage_utilization, circulating_supply, contract_revenue FROM network_metrics WHERE block_id = ?`, encode(id)).Scan(decode(&result.Index.ID), &result.Index.Height, decode(&result.Difficulty), decode(&result.SiafundPool), &result.TotalHosts, &result.ActiveContracts, &result.FailedContracts, &result.SuccessfulContracts, &result.StorageUtilization, decode(&result.CirculatingSupply), decode(&result.ContractRevenue)) + err = tx.QueryRow(`SELECT block_id, height, difficulty, siafund_pool, num_leaves, total_hosts, active_contracts, failed_contracts, successful_contracts, storage_utilization, circulating_supply, contract_revenue FROM network_metrics WHERE block_id = ?`, encode(id)).Scan(decode(&result.Index.ID), &result.Index.Height, decode(&result.Difficulty), decode(&result.SiafundPool), decode(&result.NumLeaves), &result.TotalHosts, &result.ActiveContracts, &result.FailedContracts, &result.SuccessfulContracts, &result.StorageUtilization, decode(&result.CirculatingSupply), decode(&result.ContractRevenue)) if err != nil { return fmt.Errorf("failed to get metrics: %w", err) } From 65e80ff31ba6d22e2e1487d31477f9d593733034 Mon Sep 17 00:00:00 2001 From: Christopher Tarry Date: Tue, 22 Oct 2024 11:52:23 -0400 Subject: [PATCH 12/13] remove unnecessary named variables --- persist/sqlite/v2transactions.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/persist/sqlite/v2transactions.go b/persist/sqlite/v2transactions.go index eb835953..0c401571 100644 --- a/persist/sqlite/v2transactions.go +++ b/persist/sqlite/v2transactions.go @@ -70,7 +70,7 @@ func getV2Transactions(tx *txn, ids []types.TransactionID) ([]explorer.V2Transac // getV2TransactionBase fetches the base transaction data for a given list of // transaction IDs. -func getV2TransactionBase(tx *txn, txnIDs []types.TransactionID) (dbIDs []int64, txns []explorer.V2Transaction, err error) { +func getV2TransactionBase(tx *txn, txnIDs []types.TransactionID) ([]int64, []explorer.V2Transaction, error) { stmt, err := tx.Prepare(`SELECT id, transaction_id, new_foundation_address, miner_fee, arbitrary_data FROM v2_transactions WHERE transaction_id = ?`) if err != nil { return nil, nil, fmt.Errorf("getV2TransactionBase: failed to prepare statement: %w", err) @@ -78,8 +78,8 @@ func getV2TransactionBase(tx *txn, txnIDs []types.TransactionID) (dbIDs []int64, defer stmt.Close() var dbID int64 - dbIDs = make([]int64, 0, len(txnIDs)) - txns = make([]explorer.V2Transaction, 0, len(txnIDs)) + dbIDs := make([]int64, 0, len(txnIDs)) + txns := make([]explorer.V2Transaction, 0, len(txnIDs)) for _, id := range txnIDs { var txn explorer.V2Transaction var newFoundationAddress types.Address From 0bc023fc2f0bc59d17a5c13a4e39c20954be9438 Mon Sep 17 00:00:00 2001 From: Christopher Tarry Date: Tue, 22 Oct 2024 12:34:37 -0400 Subject: [PATCH 13/13] use helper to create store and db --- persist/sqlite/consensus_test.go | 358 ++++++----------------------- persist/sqlite/v2consensus_test.go | 99 ++------ 2 files changed, 90 insertions(+), 367 deletions(-) diff --git a/persist/sqlite/consensus_test.go b/persist/sqlite/consensus_test.go index 6c12eff8..e1235629 100644 --- a/persist/sqlite/consensus_test.go +++ b/persist/sqlite/consensus_test.go @@ -6,6 +6,7 @@ import ( "testing" "time" + "go.sia.tech/core/consensus" "go.sia.tech/core/types" "go.sia.tech/coreutils" "go.sia.tech/coreutils/chain" @@ -16,7 +17,7 @@ import ( "go.uber.org/zap/zaptest" ) -func syncDB(t *testing.T, db *sqlite.Store, cm *chain.Manager) { +func syncDB(t *testing.T, db explorer.Store, cm *chain.Manager) { index, err := db.Tip() if err != nil && !errors.Is(err, explorer.ErrNoTip) { t.Fatal(err) @@ -40,6 +41,44 @@ func syncDB(t *testing.T, db *sqlite.Store, cm *chain.Manager) { } } +func newStore(t *testing.T, v2 bool, f func(*consensus.Network, types.Block)) (*consensus.Network, types.Block, *chain.Manager, explorer.Store) { + log := zaptest.NewLogger(t) + dir := t.TempDir() + db, err := sqlite.OpenDatabase(filepath.Join(dir, "explored.sqlite3"), log.Named("sqlite3")) + if err != nil { + t.Fatal(err) + } + + bdb, err := coreutils.OpenBoltChainDB(filepath.Join(dir, "consensus.db")) + if err != nil { + t.Fatal(err) + } + + var network *consensus.Network + var genesisBlock types.Block + if v2 { + network, genesisBlock = ctestutil.V2Network() + } else { + network, genesisBlock = ctestutil.Network() + } + if f != nil { + f(network, genesisBlock) + } + + store, genesisState, err := chain.NewDBStore(bdb, network, genesisBlock) + if err != nil { + t.Fatal(err) + } + cm := chain.NewManager(store, genesisState) + syncDB(t, db, cm) + + t.Cleanup(func() { + db.Close() + bdb.Close() + }) + return network, genesisBlock, cm, db +} + // CheckMetrics checks the that the metrics from the DB match what we expect. func CheckMetrics(t *testing.T, db explorer.Store, cm *chain.Manager, expected explorer.Metrics) { t.Helper() @@ -94,28 +133,7 @@ func CheckFCRevisions(t *testing.T, confirmationIndex types.ChainIndex, confirma } func TestBalance(t *testing.T) { - log := zaptest.NewLogger(t) - dir := t.TempDir() - db, err := sqlite.OpenDatabase(filepath.Join(dir, "explored.sqlite3"), log.Named("sqlite3")) - if err != nil { - t.Fatal(err) - } - defer db.Close() - - bdb, err := coreutils.OpenBoltChainDB(filepath.Join(dir, "consensus.db")) - if err != nil { - t.Fatal(err) - } - defer bdb.Close() - - network, genesisBlock := ctestutil.Network() - - store, genesisState, err := chain.NewDBStore(bdb, network, genesisBlock) - if err != nil { - t.Fatal(err) - } - - cm := chain.NewManager(store, genesisState) + _, _, cm, db := newStore(t, false, nil) // Generate three addresses: addr1, addr2, addr3 pk1 := types.GeneratePrivateKey() @@ -198,20 +216,6 @@ func TestBalance(t *testing.T) { } func TestSiafundBalance(t *testing.T) { - log := zaptest.NewLogger(t) - dir := t.TempDir() - db, err := sqlite.OpenDatabase(filepath.Join(dir, "explored.sqlite3"), log.Named("sqlite3")) - if err != nil { - t.Fatal(err) - } - defer db.Close() - - bdb, err := coreutils.OpenBoltChainDB(filepath.Join(dir, "consensus.db")) - if err != nil { - t.Fatal(err) - } - defer bdb.Close() - // Generate three addresses: addr1, addr2, addr3 pk1 := types.GeneratePrivateKey() addr1 := types.StandardUnlockHash(pk1.PublicKey()) @@ -222,17 +226,11 @@ func TestSiafundBalance(t *testing.T) { pk3 := types.GeneratePrivateKey() addr3 := types.StandardUnlockHash(pk3.PublicKey()) - network, genesisBlock := ctestutil.Network() - genesisBlock.Transactions[0].SiafundOutputs[0].Address = addr1 + _, genesisBlock, cm, db := newStore(t, false, func(network *consensus.Network, genesisBlock types.Block) { + genesisBlock.Transactions[0].SiafundOutputs[0].Address = addr1 + }) giftSF := genesisBlock.Transactions[0].SiafundOutputs[0].Value - store, genesisState, err := chain.NewDBStore(bdb, network, genesisBlock) - if err != nil { - t.Fatal(err) - } - - cm := chain.NewManager(store, genesisState) - // Send all of the payout except 100 SF to addr2 unlockConditions := types.StandardUnlockConditions(pk1.PublicKey()) parentTxn := types.Transaction{ @@ -276,20 +274,6 @@ func TestSiafundBalance(t *testing.T) { } func TestSendTransactions(t *testing.T) { - log := zaptest.NewLogger(t) - dir := t.TempDir() - db, err := sqlite.OpenDatabase(filepath.Join(dir, "explored.sqlite3"), log.Named("sqlite3")) - if err != nil { - t.Fatal(err) - } - defer db.Close() - - bdb, err := coreutils.OpenBoltChainDB(filepath.Join(dir, "consensus.db")) - if err != nil { - t.Fatal(err) - } - defer bdb.Close() - // Generate three addresses: addr1, addr2, addr3 pk1 := types.GeneratePrivateKey() addr1 := types.StandardUnlockHash(pk1.PublicKey()) @@ -300,17 +284,11 @@ func TestSendTransactions(t *testing.T) { pk3 := types.GeneratePrivateKey() addr3 := types.StandardUnlockHash(pk3.PublicKey()) - network, genesisBlock := ctestutil.Network() - genesisBlock.Transactions[0].SiafundOutputs[0].Address = addr1 + _, genesisBlock, cm, db := newStore(t, false, func(network *consensus.Network, genesisBlock types.Block) { + genesisBlock.Transactions[0].SiafundOutputs[0].Address = addr1 + }) giftSF := genesisBlock.Transactions[0].SiafundOutputs[0].Value - store, genesisState, err := chain.NewDBStore(bdb, network, genesisBlock) - if err != nil { - t.Fatal(err) - } - - cm := chain.NewManager(store, genesisState) - expectedPayout := cm.TipState().BlockReward() maturityHeight := cm.TipState().MaturityHeight() @@ -465,28 +443,7 @@ func TestSendTransactions(t *testing.T) { } func TestTip(t *testing.T) { - log := zaptest.NewLogger(t) - dir := t.TempDir() - db, err := sqlite.OpenDatabase(filepath.Join(dir, "explored.sqlite3"), log.Named("sqlite3")) - if err != nil { - t.Fatal(err) - } - defer db.Close() - - bdb, err := coreutils.OpenBoltChainDB(filepath.Join(dir, "consensus.db")) - if err != nil { - t.Fatal(err) - } - defer bdb.Close() - - network, genesisBlock := ctestutil.Network() - - store, genesisState, err := chain.NewDBStore(bdb, network, genesisBlock) - if err != nil { - t.Fatal(err) - } - - cm := chain.NewManager(store, genesisState) + _, _, cm, db := newStore(t, false, nil) const n = 100 for i := cm.Tip().Height; i < n; i++ { @@ -514,21 +471,6 @@ func TestTip(t *testing.T) { } func TestFileContract(t *testing.T) { - log := zaptest.NewLogger(t) - dir := t.TempDir() - - db, err := sqlite.OpenDatabase(filepath.Join(dir, "explored.sqlite3"), log.Named("sqlite3")) - if err != nil { - t.Fatal(err) - } - defer db.Close() - - bdb, err := coreutils.OpenBoltChainDB(filepath.Join(dir, "consensus.db")) - if err != nil { - t.Fatal(err) - } - defer bdb.Close() - pk1 := types.GeneratePrivateKey() addr1 := types.StandardUnlockHash(pk1.PublicKey()) @@ -538,17 +480,11 @@ func TestFileContract(t *testing.T) { hostPrivateKey := types.GeneratePrivateKey() hostPublicKey := hostPrivateKey.PublicKey() - network, genesisBlock := ctestutil.Network() - genesisBlock.Transactions[0].SiacoinOutputs[0].Address = addr1 + _, genesisBlock, cm, db := newStore(t, false, func(network *consensus.Network, genesisBlock types.Block) { + genesisBlock.Transactions[0].SiacoinOutputs[0].Address = addr1 + }) giftSC := genesisBlock.Transactions[0].SiacoinOutputs[0].Value - store, genesisState, err := chain.NewDBStore(bdb, network, genesisBlock) - if err != nil { - t.Fatal(err) - } - - cm := chain.NewManager(store, genesisState) - scOutputID := genesisBlock.Transactions[0].SiacoinOutputID(0) unlockConditions := types.StandardUnlockConditions(pk1.PublicKey()) @@ -770,21 +706,6 @@ func TestFileContract(t *testing.T) { } func TestEphemeralFileContract(t *testing.T) { - log := zaptest.NewLogger(t) - dir := t.TempDir() - - db, err := sqlite.OpenDatabase(filepath.Join(dir, "explored.sqlite3"), log.Named("sqlite3")) - if err != nil { - t.Fatal(err) - } - defer db.Close() - - bdb, err := coreutils.OpenBoltChainDB(filepath.Join(dir, "consensus.db")) - if err != nil { - t.Fatal(err) - } - defer bdb.Close() - pk1 := types.GeneratePrivateKey() addr1 := types.StandardUnlockHash(pk1.PublicKey()) @@ -794,17 +715,11 @@ func TestEphemeralFileContract(t *testing.T) { hostPrivateKey := types.GeneratePrivateKey() hostPublicKey := hostPrivateKey.PublicKey() - network, genesisBlock := ctestutil.Network() - genesisBlock.Transactions[0].SiacoinOutputs[0].Address = addr1 + _, genesisBlock, cm, db := newStore(t, false, func(network *consensus.Network, genesisBlock types.Block) { + genesisBlock.Transactions[0].SiacoinOutputs[0].Address = addr1 + }) giftSC := genesisBlock.Transactions[0].SiacoinOutputs[0].Value - store, genesisState, err := chain.NewDBStore(bdb, network, genesisBlock) - if err != nil { - t.Fatal(err) - } - - cm := chain.NewManager(store, genesisState) - scOutputID := genesisBlock.Transactions[0].SiacoinOutputID(0) unlockConditions := types.StandardUnlockConditions(pk1.PublicKey()) @@ -1019,35 +934,15 @@ func TestEphemeralFileContract(t *testing.T) { } func TestRevertTip(t *testing.T) { - log := zaptest.NewLogger(t) - dir := t.TempDir() - db, err := sqlite.OpenDatabase(filepath.Join(dir, "explored.sqlite3"), log.Named("sqlite3")) - if err != nil { - t.Fatal(err) - } - defer db.Close() - - bdb, err := coreutils.OpenBoltChainDB(filepath.Join(dir, "consensus.db")) - if err != nil { - t.Fatal(err) - } - defer bdb.Close() - - network, genesisBlock := ctestutil.Network() - - store, genesisState, err := chain.NewDBStore(bdb, network, genesisBlock) - if err != nil { - t.Fatal(err) - } - - cm := chain.NewManager(store, genesisState) - pk1 := types.GeneratePrivateKey() addr1 := types.StandardUnlockHash(pk1.PublicKey()) pk2 := types.GeneratePrivateKey() addr2 := types.StandardUnlockHash(pk2.PublicKey()) + _, _, cm, db := newStore(t, false, nil) + genesisState := cm.TipState() + const n = 100 for i := cm.Tip().Height; i < n; i++ { if err := cm.AddBlocks([]types.Block{testutil.MineBlock(cm.TipState(), nil, addr1)}); err != nil { @@ -1107,29 +1002,6 @@ func TestRevertTip(t *testing.T) { } func TestRevertBalance(t *testing.T) { - log := zaptest.NewLogger(t) - dir := t.TempDir() - db, err := sqlite.OpenDatabase(filepath.Join(dir, "explored.sqlite3"), log.Named("sqlite3")) - if err != nil { - t.Fatal(err) - } - defer db.Close() - - bdb, err := coreutils.OpenBoltChainDB(filepath.Join(dir, "consensus.db")) - if err != nil { - t.Fatal(err) - } - defer bdb.Close() - - network, genesisBlock := ctestutil.Network() - - store, genesisState, err := chain.NewDBStore(bdb, network, genesisBlock) - if err != nil { - t.Fatal(err) - } - - cm := chain.NewManager(store, genesisState) - // Generate three addresses: addr1, addr2, addr3 pk1 := types.GeneratePrivateKey() addr1 := types.StandardUnlockHash(pk1.PublicKey()) @@ -1140,6 +1012,9 @@ func TestRevertBalance(t *testing.T) { pk3 := types.GeneratePrivateKey() addr3 := types.StandardUnlockHash(pk3.PublicKey()) + _, _, cm, db := newStore(t, false, nil) + genesisState := cm.TipState() + // t.Log("addr1:", addr1) // t.Log("addr2:", addr2) // t.Log("addr3:", addr3) @@ -1626,33 +1501,12 @@ func TestRevertSendTransactions(t *testing.T) { } func TestHostAnnouncement(t *testing.T) { - log := zaptest.NewLogger(t) - dir := t.TempDir() - db, err := sqlite.OpenDatabase(filepath.Join(dir, "explored.sqlite3"), log.Named("sqlite3")) - if err != nil { - t.Fatal(err) - } - defer db.Close() - - bdb, err := coreutils.OpenBoltChainDB(filepath.Join(dir, "consensus.db")) - if err != nil { - t.Fatal(err) - } - defer bdb.Close() - - network, genesisBlock := ctestutil.Network() - - store, genesisState, err := chain.NewDBStore(bdb, network, genesisBlock) - if err != nil { - t.Fatal(err) - } - - cm := chain.NewManager(store, genesisState) - pk1 := types.GeneratePrivateKey() pk2 := types.GeneratePrivateKey() pk3 := types.GeneratePrivateKey() + _, _, cm, db := newStore(t, false, nil) + checkHostAnnouncements := func(expectedArbitraryData [][]byte, got []chain.HostAnnouncement) { t.Helper() @@ -1833,20 +1687,6 @@ func TestHostAnnouncement(t *testing.T) { } func TestMultipleReorg(t *testing.T) { - log := zaptest.NewLogger(t) - dir := t.TempDir() - db, err := sqlite.OpenDatabase(filepath.Join(dir, "explored.sqlite3"), log.Named("sqlite3")) - if err != nil { - t.Fatal(err) - } - defer db.Close() - - bdb, err := coreutils.OpenBoltChainDB(filepath.Join(dir, "consensus.db")) - if err != nil { - t.Fatal(err) - } - defer bdb.Close() - // Generate three addresses: addr1, addr2, addr3 pk1 := types.GeneratePrivateKey() addr1 := types.StandardUnlockHash(pk1.PublicKey()) @@ -1857,23 +1697,13 @@ func TestMultipleReorg(t *testing.T) { pk3 := types.GeneratePrivateKey() addr3 := types.StandardUnlockHash(pk3.PublicKey()) - // t.Log("addr1:", addr1) - // t.Log("addr2:", addr2) - // t.Log("addr3:", addr3) - - network, genesisBlock := ctestutil.Network() - genesisBlock.Transactions[0].SiacoinOutputs[0].Address = addr1 - genesisBlock.Transactions[0].SiafundOutputs[0].Address = addr1 + _, genesisBlock, cm, db := newStore(t, false, func(network *consensus.Network, genesisBlock types.Block) { + genesisBlock.Transactions[0].SiacoinOutputs[0].Address = addr1 + genesisBlock.Transactions[0].SiafundOutputs[0].Address = addr1 + }) giftSC := genesisBlock.Transactions[0].SiacoinOutputs[0].Value giftSF := genesisBlock.Transactions[0].SiafundOutputs[0].Value - store, genesisState, err := chain.NewDBStore(bdb, network, genesisBlock) - if err != nil { - t.Fatal(err) - } - - cm := chain.NewManager(store, genesisState) - uc1 := types.StandardUnlockConditions(pk1.PublicKey()) // transfer gift from addr1 to addr2 // element gets added at height 1 @@ -2169,21 +1999,6 @@ func TestMultipleReorg(t *testing.T) { } func TestMultipleReorgFileContract(t *testing.T) { - log := zaptest.NewLogger(t) - dir := t.TempDir() - - db, err := sqlite.OpenDatabase(filepath.Join(dir, "explored.sqlite3"), log.Named("sqlite3")) - if err != nil { - t.Fatal(err) - } - defer db.Close() - - bdb, err := coreutils.OpenBoltChainDB(filepath.Join(dir, "consensus.db")) - if err != nil { - t.Fatal(err) - } - defer bdb.Close() - pk1 := types.GeneratePrivateKey() addr1 := types.StandardUnlockHash(pk1.PublicKey()) @@ -2193,17 +2008,12 @@ func TestMultipleReorgFileContract(t *testing.T) { hostPrivateKey := types.GeneratePrivateKey() hostPublicKey := hostPrivateKey.PublicKey() - network, genesisBlock := ctestutil.Network() - genesisBlock.Transactions[0].SiacoinOutputs[0].Address = addr1 + _, genesisBlock, cm, db := newStore(t, false, func(network *consensus.Network, genesisBlock types.Block) { + genesisBlock.Transactions[0].SiacoinOutputs[0].Address = addr1 + }) + genesisState := cm.TipState() giftSC := genesisBlock.Transactions[0].SiacoinOutputs[0].Value - store, genesisState, err := chain.NewDBStore(bdb, network, genesisBlock) - if err != nil { - t.Fatal(err) - } - - cm := chain.NewManager(store, genesisState) - scOutputID := genesisBlock.Transactions[0].SiacoinOutputID(0) unlockConditions := types.StandardUnlockConditions(pk1.PublicKey()) @@ -2505,33 +2315,13 @@ func TestMultipleReorgFileContract(t *testing.T) { } func TestMetricCirculatingSupply(t *testing.T) { - log := zaptest.NewLogger(t) - dir := t.TempDir() - - db, err := sqlite.OpenDatabase(filepath.Join(dir, "explored.sqlite3"), log.Named("sqlite3")) - if err != nil { - t.Fatal(err) - } - defer db.Close() - - bdb, err := coreutils.OpenBoltChainDB(filepath.Join(dir, "consensus.db")) - if err != nil { - t.Fatal(err) - } - defer bdb.Close() - pk1 := types.GeneratePrivateKey() addr1 := types.StandardUnlockHash(pk1.PublicKey()) - network, genesisBlock := ctestutil.Network() - genesisBlock.Transactions[0].SiacoinOutputs[0].Address = addr1 - - store, genesisState, err := chain.NewDBStore(bdb, network, genesisBlock) - if err != nil { - t.Fatal(err) - } - - cm := chain.NewManager(store, genesisState) + _, genesisBlock, cm, db := newStore(t, false, func(network *consensus.Network, genesisBlock types.Block) { + genesisBlock.Transactions[0].SiacoinOutputs[0].Address = addr1 + }) + genesisState := cm.TipState() var circulatingSupply types.Currency if subsidy, ok := genesisState.FoundationSubsidy(); ok { diff --git a/persist/sqlite/v2consensus_test.go b/persist/sqlite/v2consensus_test.go index 5f757d97..8e848b71 100644 --- a/persist/sqlite/v2consensus_test.go +++ b/persist/sqlite/v2consensus_test.go @@ -1,17 +1,12 @@ package sqlite_test import ( - "path/filepath" "testing" + "go.sia.tech/core/consensus" "go.sia.tech/core/types" - "go.sia.tech/coreutils" - "go.sia.tech/coreutils/chain" - ctestutil "go.sia.tech/coreutils/testutil" "go.sia.tech/explored/explorer" "go.sia.tech/explored/internal/testutil" - "go.sia.tech/explored/persist/sqlite" - "go.uber.org/zap/zaptest" ) func getSCE(t *testing.T, db explorer.Store, scid types.SiacoinOutputID) types.SiacoinElement { @@ -32,30 +27,10 @@ func getSCE(t *testing.T, db explorer.Store, scid types.SiacoinOutputID) types.S } func TestV2ArbitraryData(t *testing.T) { - log := zaptest.NewLogger(t) - dir := t.TempDir() - db, err := sqlite.OpenDatabase(filepath.Join(dir, "explored.sqlite3"), log.Named("sqlite3")) - if err != nil { - t.Fatal(err) - } - defer db.Close() - - bdb, err := coreutils.OpenBoltChainDB(filepath.Join(dir, "consensus.db")) - if err != nil { - t.Fatal(err) - } - defer bdb.Close() - - network, genesisBlock := ctestutil.V2Network() - network.HardforkV2.AllowHeight = 1 - network.HardforkV2.RequireHeight = 2 - - store, genesisState, err := chain.NewDBStore(bdb, network, genesisBlock) - if err != nil { - t.Fatal(err) - } - cm := chain.NewManager(store, genesisState) - syncDB(t, db, cm) + _, _, cm, db := newStore(t, true, func(network *consensus.Network, genesisBlock types.Block) { + network.HardforkV2.AllowHeight = 1 + network.HardforkV2.RequireHeight = 2 + }) txn1 := types.V2Transaction{ ArbitraryData: []byte("hello"), @@ -126,38 +101,17 @@ func TestV2ArbitraryData(t *testing.T) { } func TestV2MinerFee(t *testing.T) { - log := zaptest.NewLogger(t) - dir := t.TempDir() - db, err := sqlite.OpenDatabase(filepath.Join(dir, "explored.sqlite3"), log.Named("sqlite3")) - if err != nil { - t.Fatal(err) - } - defer db.Close() - - bdb, err := coreutils.OpenBoltChainDB(filepath.Join(dir, "consensus.db")) - if err != nil { - t.Fatal(err) - } - defer bdb.Close() - pk1 := types.GeneratePrivateKey() addr1 := types.StandardUnlockHash(pk1.PublicKey()) addr1Policy := types.SpendPolicy{Type: types.PolicyTypeUnlockConditions(types.StandardUnlockConditions(pk1.PublicKey()))} - network, genesisBlock := ctestutil.V2Network() - network.HardforkV2.AllowHeight = 1 - network.HardforkV2.RequireHeight = 2 - - genesisBlock.Transactions[0].SiacoinOutputs[0].Address = addr1 + _, genesisBlock, cm, db := newStore(t, true, func(network *consensus.Network, genesisBlock types.Block) { + network.HardforkV2.AllowHeight = 1 + network.HardforkV2.RequireHeight = 2 + genesisBlock.Transactions[0].SiacoinOutputs[0].Address = addr1 + }) giftSC := genesisBlock.Transactions[0].SiacoinOutputs[0].Value - store, genesisState, err := chain.NewDBStore(bdb, network, genesisBlock) - if err != nil { - t.Fatal(err) - } - cm := chain.NewManager(store, genesisState) - syncDB(t, db, cm) - txn1 := types.V2Transaction{ ArbitraryData: []byte("hello"), MinerFee: giftSC, @@ -183,20 +137,6 @@ func TestV2MinerFee(t *testing.T) { } func TestV2FoundationAddress(t *testing.T) { - log := zaptest.NewLogger(t) - dir := t.TempDir() - db, err := sqlite.OpenDatabase(filepath.Join(dir, "explored.sqlite3"), log.Named("sqlite3")) - if err != nil { - t.Fatal(err) - } - defer db.Close() - - bdb, err := coreutils.OpenBoltChainDB(filepath.Join(dir, "consensus.db")) - if err != nil { - t.Fatal(err) - } - defer bdb.Close() - pk1 := types.GeneratePrivateKey() addr1 := types.StandardUnlockHash(pk1.PublicKey()) addr1Policy := types.SpendPolicy{Type: types.PolicyTypeUnlockConditions(types.StandardUnlockConditions(pk1.PublicKey()))} @@ -204,21 +144,14 @@ func TestV2FoundationAddress(t *testing.T) { pk2 := types.GeneratePrivateKey() addr2 := types.StandardUnlockHash(pk2.PublicKey()) - network, genesisBlock := ctestutil.V2Network() - network.HardforkFoundation.PrimaryAddress = addr1 - network.HardforkV2.AllowHeight = 1 - network.HardforkV2.RequireHeight = 2 - - genesisBlock.Transactions[0].SiacoinOutputs[0].Address = addr1 + _, genesisBlock, cm, db := newStore(t, true, func(network *consensus.Network, genesisBlock types.Block) { + network.HardforkV2.AllowHeight = 1 + network.HardforkV2.RequireHeight = 2 + network.HardforkFoundation.PrimaryAddress = addr1 + genesisBlock.Transactions[0].SiacoinOutputs[0].Address = addr1 + }) giftSC := genesisBlock.Transactions[0].SiacoinOutputs[0].Value - store, genesisState, err := chain.NewDBStore(bdb, network, genesisBlock) - if err != nil { - t.Fatal(err) - } - cm := chain.NewManager(store, genesisState) - syncDB(t, db, cm) - txn1 := types.V2Transaction{ SiacoinInputs: []types.V2SiacoinInput{{ Parent: getSCE(t, db, genesisBlock.Transactions[0].SiacoinOutputID(0)),