From 0c069102c5f8d5ff54f026b75cf4c9148223c845 Mon Sep 17 00:00:00 2001 From: Christopher Tarry Date: Mon, 18 Mar 2024 16:54:14 -0400 Subject: [PATCH 01/16] add initial test --- persist/sqlite/consensus_test.go | 147 +++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 persist/sqlite/consensus_test.go diff --git a/persist/sqlite/consensus_test.go b/persist/sqlite/consensus_test.go new file mode 100644 index 00000000..cdb86182 --- /dev/null +++ b/persist/sqlite/consensus_test.go @@ -0,0 +1,147 @@ +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" + "go.sia.tech/explored/persist/sqlite" + "go.uber.org/zap/zaptest" +) + +func testV1Network() (*consensus.Network, types.Block) { + // use a modified version of Zen + n, genesisBlock := chain.TestnetZen() + n.InitialTarget = types.BlockID{0xFF} + n.HardforkDevAddr.Height = 1 + n.HardforkTax.Height = 1 + n.HardforkStorageProof.Height = 1 + n.HardforkOak.Height = 1 + n.HardforkASIC.Height = 1 + n.HardforkFoundation.Height = 1 + n.HardforkV2.AllowHeight = 1000 + n.HardforkV2.RequireHeight = 1000 + return n, genesisBlock +} + +func testV2Network() (*consensus.Network, types.Block) { + // use a modified version of Zen + n, genesisBlock := chain.TestnetZen() + n.InitialTarget = types.BlockID{0xFF} + n.HardforkDevAddr.Height = 1 + n.HardforkTax.Height = 1 + n.HardforkStorageProof.Height = 1 + n.HardforkOak.Height = 1 + n.HardforkASIC.Height = 1 + n.HardforkFoundation.Height = 1 + n.HardforkV2.AllowHeight = 100 + n.HardforkV2.RequireHeight = 110 + return n, genesisBlock +} + +func mineBlock(state consensus.State, txns []types.Transaction, minerAddr types.Address) types.Block { + b := types.Block{ + ParentID: state.Index.ID, + Timestamp: types.CurrentTimestamp(), + Transactions: txns, + MinerPayouts: []types.SiacoinOutput{{Address: minerAddr, Value: state.BlockReward()}}, + } + for b.ID().CmpWork(state.ChildTarget) < 0 { + b.Nonce += state.NonceFactor() + } + return b +} + +func mineV2Block(state consensus.State, txns []types.V2Transaction, minerAddr types.Address) types.Block { + b := types.Block{ + ParentID: state.Index.ID, + Timestamp: types.CurrentTimestamp(), + MinerPayouts: []types.SiacoinOutput{{Address: minerAddr, Value: state.BlockReward()}}, + + V2: &types.V2BlockData{ + Transactions: txns, + Height: state.Index.Height + 1, + }, + } + b.V2.Commitment = state.Commitment(state.TransactionsCommitment(b.Transactions, b.V2Transactions()), b.MinerPayouts[0].Address) + for b.ID().CmpWork(state.ChildTarget) < 0 { + b.Nonce += state.NonceFactor() + } + return b +} + +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 := testV1Network() + + store, genesisState, err := chain.NewDBStore(bdb, network, genesisBlock) + if err != nil { + t.Fatal(err) + } + defer store.Close() + + cm := chain.NewManager(store, genesisState) + + if err := cm.AddSubscriber(db, types.ChainIndex{}); err != nil { + t.Fatal(err) + } + + checkBalance := func(addr types.Address, expectSC, expectImmatureSC types.Currency, expectSF uint64) { + sc, immatureSC, sf, err := db.Balance(addr) + if err != nil { + t.Fatal(err) + } else if sc != expectSC { + t.Fatalf("expected %v siacoins, got %v", expectSC, sc) + } else if immatureSC != expectImmatureSC { + t.Fatalf("expected %v immature siacoins, got %v", expectImmatureSC, immatureSC) + } else if sf != expectSF { + t.Fatalf("expected %d siafunds, got %d", expectSF, sf) + } + } + + pk := types.GeneratePrivateKey() + addr := types.StandardUnlockHash(pk.PublicKey()) + + expectedPayout := cm.TipState().BlockReward() + maturityHeight := cm.TipState().MaturityHeight() + 1 + + // mine a block sending the payout to the wallet + if err := cm.AddBlocks([]types.Block{mineBlock(cm.TipState(), nil, addr)}); err != nil { + t.Fatal(err) + } + + utxos, err := db.UnspentSiacoinOutputs(addr, 100, 0) + if err != nil { + t.Fatal(err) + } else if len(utxos) != 1 { + t.Fatalf("expected 1 utxo, got %d", len(utxos)) + } else if utxos[0].SiacoinOutput.Value != expectedPayout { + t.Fatalf("expected value %v, got %v", expectedPayout, utxos[0].SiacoinOutput.Value) + } + + // mine until the payout matures + for i := cm.TipState().Index.Height; i < maturityHeight; i++ { + checkBalance(addr, types.ZeroCurrency, expectedPayout, 0) + if err := cm.AddBlocks([]types.Block{mineBlock(cm.TipState(), nil, types.VoidAddress)}); err != nil { + t.Fatal(err) + } + } + + checkBalance(addr, expectedPayout, types.ZeroCurrency, 0) +} From c7eaad723b2db8e2d2e9eb08815a3dd2bc179501 Mon Sep 17 00:00:00 2001 From: Christopher Tarry Date: Mon, 18 Mar 2024 17:11:57 -0400 Subject: [PATCH 02/16] fix updateMaturedBalances height calculation --- persist/sqlite/consensus.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/persist/sqlite/consensus.go b/persist/sqlite/consensus.go index 2f5b2ebc..a4d358be 100644 --- a/persist/sqlite/consensus.go +++ b/persist/sqlite/consensus.go @@ -371,6 +371,8 @@ func (s *Store) updateMaturedBalances(dbTxn txn, update consensusUpdate, height _, isRevert := update.(*chain.RevertUpdate) if isRevert { height++ + } else { + height-- } rows, err := dbTxn.Query(`SELECT address, value From ce552f5a60811823015755175ec4dbc00949fa8d Mon Sep 17 00:00:00 2001 From: Christopher Tarry Date: Tue, 19 Mar 2024 14:59:06 -0400 Subject: [PATCH 03/16] make balance test more complicated --- persist/sqlite/consensus_test.go | 72 +++++++++++++++++++++++++++++--- 1 file changed, 66 insertions(+), 6 deletions(-) diff --git a/persist/sqlite/consensus_test.go b/persist/sqlite/consensus_test.go index cdb86182..3cf34c1c 100644 --- a/persist/sqlite/consensus_test.go +++ b/persist/sqlite/consensus_test.go @@ -115,18 +115,24 @@ func TestBalance(t *testing.T) { } } - pk := types.GeneratePrivateKey() - addr := types.StandardUnlockHash(pk.PublicKey()) + pk1 := types.GeneratePrivateKey() + addr1 := types.StandardUnlockHash(pk1.PublicKey()) + + pk2 := types.GeneratePrivateKey() + addr2 := types.StandardUnlockHash(pk2.PublicKey()) + + pk3 := types.GeneratePrivateKey() + addr3 := types.StandardUnlockHash(pk3.PublicKey()) expectedPayout := cm.TipState().BlockReward() maturityHeight := cm.TipState().MaturityHeight() + 1 // mine a block sending the payout to the wallet - if err := cm.AddBlocks([]types.Block{mineBlock(cm.TipState(), nil, addr)}); err != nil { + if err := cm.AddBlocks([]types.Block{mineBlock(cm.TipState(), nil, addr1)}); err != nil { t.Fatal(err) } - utxos, err := db.UnspentSiacoinOutputs(addr, 100, 0) + utxos, err := db.UnspentSiacoinOutputs(addr1, 100, 0) if err != nil { t.Fatal(err) } else if len(utxos) != 1 { @@ -137,11 +143,65 @@ func TestBalance(t *testing.T) { // mine until the payout matures for i := cm.TipState().Index.Height; i < maturityHeight; i++ { - checkBalance(addr, types.ZeroCurrency, expectedPayout, 0) + checkBalance(addr1, types.ZeroCurrency, expectedPayout, 0) if err := cm.AddBlocks([]types.Block{mineBlock(cm.TipState(), nil, types.VoidAddress)}); err != nil { t.Fatal(err) } } - checkBalance(addr, expectedPayout, types.ZeroCurrency, 0) + checkBalance(addr1, expectedPayout, types.ZeroCurrency, 0) + + unlockConditions := types.StandardUnlockConditions(pk1.PublicKey()) + parentTxn := types.Transaction{ + SiacoinInputs: []types.SiacoinInput{ + { + ParentID: types.SiacoinOutputID(utxos[0].ID), + UnlockConditions: unlockConditions, + }, + }, + SiacoinOutputs: []types.SiacoinOutput{ + {Address: addr1, Value: types.Siacoins(100)}, + {Address: addr2, Value: utxos[0].SiacoinOutput.Value.Sub(types.Siacoins(100))}, + }, + Signatures: []types.TransactionSignature{ + { + ParentID: utxos[0].ID, + PublicKeyIndex: 0, + CoveredFields: types.CoveredFields{WholeTransaction: true}, + }, + }, + } + parentSigHash := cm.TipState().WholeSigHash(parentTxn, utxos[0].ID, 0, 0, nil) + parentSig := pk1.SignHash(parentSigHash) + parentTxn.Signatures[0].Signature = parentSig[:] + + outputID := parentTxn.SiacoinOutputID(0) + txn := types.Transaction{ + SiacoinInputs: []types.SiacoinInput{ + { + ParentID: outputID, + UnlockConditions: unlockConditions, + }, + }, + SiacoinOutputs: []types.SiacoinOutput{ + {Address: addr3, Value: types.Siacoins(100)}, + }, + Signatures: []types.TransactionSignature{ + { + ParentID: types.Hash256(outputID), + PublicKeyIndex: 0, + CoveredFields: types.CoveredFields{WholeTransaction: true}, + }, + }, + } + sigHash := cm.TipState().WholeSigHash(txn, types.Hash256(outputID), 0, 0, nil) + sig := pk1.SignHash(sigHash) + txn.Signatures[0].Signature = sig[:] + + if err := cm.AddBlocks([]types.Block{mineBlock(cm.TipState(), []types.Transaction{parentTxn, txn}, types.VoidAddress)}); err != nil { + t.Fatal(err) + } + + checkBalance(addr2, utxos[0].SiacoinOutput.Value.Sub(types.Siacoins(100)), types.ZeroCurrency, 0) + checkBalance(addr3, types.Siacoins(100), types.ZeroCurrency, 0) } From 8cb5f6c1386454d7d6c76509103350d99c43ff42 Mon Sep 17 00:00:00 2001 From: Christopher Tarry Date: Tue, 19 Mar 2024 16:15:23 -0400 Subject: [PATCH 04/16] add basic test for retrieving block --- persist/sqlite/consensus_test.go | 62 ++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/persist/sqlite/consensus_test.go b/persist/sqlite/consensus_test.go index 3cf34c1c..8cf61b21 100644 --- a/persist/sqlite/consensus_test.go +++ b/persist/sqlite/consensus_test.go @@ -205,3 +205,65 @@ func TestBalance(t *testing.T) { checkBalance(addr2, utxos[0].SiacoinOutput.Value.Sub(types.Siacoins(100)), types.ZeroCurrency, 0) checkBalance(addr3, types.Siacoins(100), types.ZeroCurrency, 0) } + +func TestBlock(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 := testV1Network() + + store, genesisState, err := chain.NewDBStore(bdb, network, genesisBlock) + if err != nil { + t.Fatal(err) + } + defer store.Close() + + cm := chain.NewManager(store, genesisState) + + if err := cm.AddSubscriber(db, types.ChainIndex{}); err != nil { + t.Fatal(err) + } + + pk1 := types.GeneratePrivateKey() + addr1 := types.StandardUnlockHash(pk1.PublicKey()) + + for i := 0; i < 100; i++ { + // mine a block sending the payout to the wallet + b := mineBlock(cm.TipState(), nil, addr1) + if err := cm.AddBlocks([]types.Block{b}); err != nil { + t.Fatal(err) + } + + block, err := db.Block(cm.TipState().Index.ID) + if err != nil { + t.Fatal(err) + } else if len(b.Transactions) != len(block.Transactions) { + t.Fatalf("expected %d transactions, got %d", len(b.Transactions), len(block.Transactions)) + } else if b.Nonce != block.Nonce { + t.Fatalf("expected nonce %d, got %d", b.Nonce, block.Nonce) + } else if b.Timestamp != block.Timestamp { + t.Fatalf("expected timestamp %d, got %d", b.Timestamp.Unix(), block.Timestamp.Unix()) + } else if len(b.MinerPayouts) != len(block.MinerPayouts) { + t.Fatalf("expected %d miner payouts, got %d", len(b.MinerPayouts), len(block.MinerPayouts)) + } + + for i := range b.MinerPayouts { + if b.MinerPayouts[i].Address != block.MinerPayouts[i].SiacoinOutput.Address { + t.Fatalf("expected address %v, got %v", b.MinerPayouts[i].Address, block.MinerPayouts[i].SiacoinOutput.Address) + } else if b.MinerPayouts[i].Value != block.MinerPayouts[i].SiacoinOutput.Value { + t.Fatalf("expected value %v, got %v", b.MinerPayouts[i].Value, block.MinerPayouts[i].SiacoinOutput.Value) + } + } + } +} From 67b37340c530fc87f9c5b4cd7f28313ab6e23800 Mon Sep 17 00:00:00 2001 From: Christopher Tarry Date: Tue, 19 Mar 2024 20:05:41 -0400 Subject: [PATCH 05/16] generate transactions sending sc --- persist/sqlite/consensus_test.go | 83 +++++++++++++++++++++++++++++++- 1 file changed, 81 insertions(+), 2 deletions(-) diff --git a/persist/sqlite/consensus_test.go b/persist/sqlite/consensus_test.go index 8cf61b21..e47c63bd 100644 --- a/persist/sqlite/consensus_test.go +++ b/persist/sqlite/consensus_test.go @@ -238,14 +238,72 @@ func TestBlock(t *testing.T) { pk1 := types.GeneratePrivateKey() addr1 := types.StandardUnlockHash(pk1.PublicKey()) + pk2 := types.GeneratePrivateKey() + addr2 := types.StandardUnlockHash(pk2.PublicKey()) + + pk3 := types.GeneratePrivateKey() + addr3 := types.StandardUnlockHash(pk3.PublicKey()) + + expectedPayout := cm.TipState().BlockReward() + maturityHeight := cm.TipState().MaturityHeight() + 1 + + // mine a block sending the payout to the wallet + if err := cm.AddBlocks([]types.Block{mineBlock(cm.TipState(), nil, addr1)}); err != nil { + t.Fatal(err) + } + + // mine until the payout matures + for i := cm.TipState().Index.Height; i < maturityHeight; i++ { + if err := cm.AddBlocks([]types.Block{mineBlock(cm.TipState(), nil, types.VoidAddress)}); err != nil { + t.Fatal(err) + } + } + + utxos, err := db.UnspentSiacoinOutputs(addr1, 100, 0) + if err != nil { + t.Fatal(err) + } else if len(utxos) != 1 { + t.Fatalf("expected 1 utxo, got %d", len(utxos)) + } else if utxos[0].SiacoinOutput.Value != expectedPayout { + t.Fatalf("expected value %v, got %v", expectedPayout, utxos[0].SiacoinOutput.Value) + } + + outputID := utxos[0].ID + unlockConditions := types.StandardUnlockConditions(pk1.PublicKey()) for i := 0; i < 100; i++ { + parentTxn := types.Transaction{ + SiacoinInputs: []types.SiacoinInput{ + { + ParentID: types.SiacoinOutputID(outputID), + UnlockConditions: unlockConditions, + }, + }, + SiacoinOutputs: []types.SiacoinOutput{ + {Address: addr2, Value: types.Siacoins(1)}, + {Address: addr3, Value: types.Siacoins(2)}, + {Address: addr1, Value: expectedPayout.Sub(types.Siacoins(1 + 2).Mul64(uint64(i + 1)))}, + }, + Signatures: []types.TransactionSignature{ + { + ParentID: outputID, + PublicKeyIndex: 0, + CoveredFields: types.CoveredFields{WholeTransaction: true}, + }, + }, + } + + parentSigHash := cm.TipState().WholeSigHash(parentTxn, outputID, 0, 0, nil) + parentSig := pk1.SignHash(parentSigHash) + parentTxn.Signatures[0].Signature = parentSig[:] + outputID = types.Hash256(parentTxn.SiacoinOutputID(2)) + // mine a block sending the payout to the wallet - b := mineBlock(cm.TipState(), nil, addr1) + b := mineBlock(cm.TipState(), []types.Transaction{parentTxn}, addr1) if err := cm.AddBlocks([]types.Block{b}); err != nil { t.Fatal(err) } - block, err := db.Block(cm.TipState().Index.ID) + block, err := db.Block(b.ID()) if err != nil { t.Fatal(err) } else if len(b.Transactions) != len(block.Transactions) { @@ -256,6 +314,8 @@ func TestBlock(t *testing.T) { t.Fatalf("expected timestamp %d, got %d", b.Timestamp.Unix(), block.Timestamp.Unix()) } else if len(b.MinerPayouts) != len(block.MinerPayouts) { t.Fatalf("expected %d miner payouts, got %d", len(b.MinerPayouts), len(block.MinerPayouts)) + } else if len(b.Transactions) != len(block.Transactions) { + t.Fatalf("expected %d transactions, got %d", len(b.Transactions), len(block.Transactions)) } for i := range b.MinerPayouts { @@ -265,5 +325,24 @@ func TestBlock(t *testing.T) { t.Fatalf("expected value %v, got %v", b.MinerPayouts[i].Value, block.MinerPayouts[i].SiacoinOutput.Value) } } + for i := range b.Transactions { + bTxn := b.Transactions[i] + blockTxn := block.Transactions[i] + if len(bTxn.SiacoinOutputs) != len(bTxn.SiacoinOutputs) { + t.Fatalf("expected %d siacoin outputs, got %d", len(bTxn.SiacoinOutputs), len(bTxn.SiacoinOutputs)) + } + t.Logf("bTxn: %+v", bTxn) + t.Logf("blockTxn: %+v", blockTxn) + + for j := range bTxn.SiacoinOutputs { + bSco := bTxn.SiacoinOutputs[j] + blockSco := blockTxn.SiacoinOutputs[j].SiacoinOutput + if bSco.Address != blockSco.Address { + t.Fatalf("expected address %v, got %v", bSco.Address, blockSco.Address) + } else if bSco.Value != blockSco.Value { + t.Fatalf("expected value %v, got %v", bSco.Value, blockSco.Value) + } + } + } } } From 3168738cb1f191a3df15f83609b12d265c36e7fe Mon Sep 17 00:00:00 2001 From: Christopher Tarry Date: Tue, 19 Mar 2024 20:07:28 -0400 Subject: [PATCH 06/16] fix transaction_order DESC -> transaction_order ASC --- persist/sqlite/consensus_test.go | 2 -- persist/sqlite/transactions.go | 16 ++++++++-------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/persist/sqlite/consensus_test.go b/persist/sqlite/consensus_test.go index e47c63bd..8d5016c5 100644 --- a/persist/sqlite/consensus_test.go +++ b/persist/sqlite/consensus_test.go @@ -331,8 +331,6 @@ func TestBlock(t *testing.T) { if len(bTxn.SiacoinOutputs) != len(bTxn.SiacoinOutputs) { t.Fatalf("expected %d siacoin outputs, got %d", len(bTxn.SiacoinOutputs), len(bTxn.SiacoinOutputs)) } - t.Logf("bTxn: %+v", bTxn) - t.Logf("blockTxn: %+v", blockTxn) for j := range bTxn.SiacoinOutputs { bSco := bTxn.SiacoinOutputs[j] diff --git a/persist/sqlite/transactions.go b/persist/sqlite/transactions.go index f937d21f..9b630857 100644 --- a/persist/sqlite/transactions.go +++ b/persist/sqlite/transactions.go @@ -20,7 +20,7 @@ func transactionArbitraryData(tx txn, txnIDs []int64) (map[int64][][]byte, error query := `SELECT transaction_id, data FROM transaction_arbitrary_data WHERE transaction_id IN (` + queryPlaceHolders(len(txnIDs)) + `) -ORDER BY transaction_order DESC` +ORDER BY transaction_order ASC` rows, err := tx.Query(query, queryArgs(txnIDs)...) if err != nil { return nil, err @@ -45,7 +45,7 @@ func transactionSiacoinOutputs(tx txn, txnIDs []int64) (map[int64][]explorer.Sia FROM siacoin_elements sc INNER JOIN transaction_siacoin_outputs ts ON (ts.output_id = sc.id) WHERE ts.transaction_id IN (` + queryPlaceHolders(len(txnIDs)) + `) -ORDER BY ts.transaction_order DESC` +ORDER BY ts.transaction_order ASC` rows, err := tx.Query(query, queryArgs(txnIDs)...) if err != nil { return nil, fmt.Errorf("failed to query siacoin output ids: %w", err) @@ -70,7 +70,7 @@ func transactionSiacoinInputs(tx txn, txnIDs []int64) (map[int64][]types.Siacoin query := `SELECT transaction_id, parent_id, unlock_conditions FROM transaction_siacoin_inputs WHERE transaction_id IN (` + queryPlaceHolders(len(txnIDs)) + `) -ORDER BY transaction_order DESC` +ORDER BY transaction_order ASC` rows, err := tx.Query(query, queryArgs(txnIDs)...) if err != nil { return nil, err @@ -94,7 +94,7 @@ func transactionSiafundInputs(tx txn, txnIDs []int64) (map[int64][]types.Siafund query := `SELECT transaction_id, parent_id, unlock_conditions, claim_address FROM transaction_siafund_inputs WHERE transaction_id IN (` + queryPlaceHolders(len(txnIDs)) + `) -ORDER BY transaction_order DESC` +ORDER BY transaction_order ASC` rows, err := tx.Query(query, queryArgs(txnIDs)...) if err != nil { return nil, err @@ -119,7 +119,7 @@ func transactionSiafundOutputs(tx txn, txnIDs []int64) (map[int64][]explorer.Sia FROM siafund_elements sf INNER JOIN transaction_siafund_outputs ts ON (ts.output_id = sf.id) WHERE ts.transaction_id IN (` + queryPlaceHolders(len(txnIDs)) + `) -ORDER BY ts.transaction_order DESC` +ORDER BY ts.transaction_order ASC` rows, err := tx.Query(query, queryArgs(txnIDs)...) if err != nil { return nil, fmt.Errorf("failed to query siafund output ids: %w", err) @@ -205,7 +205,7 @@ func transactionFileContracts(tx txn, txnIDs []int64) (map[int64][]explorer.File FROM file_contract_elements fc INNER JOIN transaction_file_contracts ts ON (ts.contract_id = fc.id) WHERE ts.transaction_id IN (` + queryPlaceHolders(len(txnIDs)) + `) -ORDER BY ts.transaction_order DESC` +ORDER BY ts.transaction_order ASC` rows, err := tx.Query(query, queryArgs(txnIDs)...) if err != nil { return nil, fmt.Errorf("failed to query contract output ids: %w", err) @@ -248,7 +248,7 @@ func transactionFileContractRevisions(tx txn, txnIDs []int64) (map[int64][]explo FROM file_contract_elements fc INNER JOIN transaction_file_contract_revisions ts ON (ts.contract_id = fc.id) WHERE ts.transaction_id IN (` + queryPlaceHolders(len(txnIDs)) + `) -ORDER BY ts.transaction_order DESC` +ORDER BY ts.transaction_order ASC` rows, err := tx.Query(query, queryArgs(txnIDs)...) if err != nil { return nil, fmt.Errorf("failed to query contract output ids: %w", err) @@ -310,7 +310,7 @@ func blockMinerPayouts(tx txn, blockID types.BlockID) ([]explorer.SiacoinOutput, FROM siacoin_elements sc INNER JOIN miner_payouts mp ON (mp.output_id = sc.id) WHERE mp.block_id = ? -ORDER BY mp.block_order DESC` +ORDER BY mp.block_order ASC` rows, err := tx.Query(query, dbEncode(blockID)) if err != nil { return nil, fmt.Errorf("failed to query miner payout ids: %w", err) From e555fd54d6dffe252bedd1f54d922339d02b4c41 Mon Sep 17 00:00:00 2001 From: Christopher Tarry Date: Wed, 20 Mar 2024 14:56:02 -0400 Subject: [PATCH 07/16] add comments to tests --- persist/sqlite/consensus_test.go | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/persist/sqlite/consensus_test.go b/persist/sqlite/consensus_test.go index 8d5016c5..569decc4 100644 --- a/persist/sqlite/consensus_test.go +++ b/persist/sqlite/consensus_test.go @@ -102,6 +102,7 @@ func TestBalance(t *testing.T) { t.Fatal(err) } + // checkBalance checks that an address has the balances we expect checkBalance := func(addr types.Address, expectSC, expectImmatureSC types.Currency, expectSF uint64) { sc, immatureSC, sf, err := db.Balance(addr) if err != nil { @@ -115,6 +116,7 @@ func TestBalance(t *testing.T) { } } + // Generate three addresses: addr1, addr2, addr3 pk1 := types.GeneratePrivateKey() addr1 := types.StandardUnlockHash(pk1.PublicKey()) @@ -127,11 +129,12 @@ func TestBalance(t *testing.T) { expectedPayout := cm.TipState().BlockReward() maturityHeight := cm.TipState().MaturityHeight() + 1 - // mine a block sending the payout to the wallet + // Mine a block sending the payout to addr1 if err := cm.AddBlocks([]types.Block{mineBlock(cm.TipState(), nil, addr1)}); err != nil { t.Fatal(err) } + // Check that addr1 has the miner payout output utxos, err := db.UnspentSiacoinOutputs(addr1, 100, 0) if err != nil { t.Fatal(err) @@ -141,7 +144,7 @@ func TestBalance(t *testing.T) { t.Fatalf("expected value %v, got %v", expectedPayout, utxos[0].SiacoinOutput.Value) } - // mine until the payout matures + // Mine until the payout matures for i := cm.TipState().Index.Height; i < maturityHeight; i++ { checkBalance(addr1, types.ZeroCurrency, expectedPayout, 0) if err := cm.AddBlocks([]types.Block{mineBlock(cm.TipState(), nil, types.VoidAddress)}); err != nil { @@ -151,6 +154,7 @@ func TestBalance(t *testing.T) { checkBalance(addr1, expectedPayout, types.ZeroCurrency, 0) + // Send all of the payout except 100 SC to addr2 unlockConditions := types.StandardUnlockConditions(pk1.PublicKey()) parentTxn := types.Transaction{ SiacoinInputs: []types.SiacoinInput{ @@ -175,6 +179,8 @@ func TestBalance(t *testing.T) { parentSig := pk1.SignHash(parentSigHash) parentTxn.Signatures[0].Signature = parentSig[:] + // In the same block, have addr1 send the 100 SC it still has left to + // addr3 outputID := parentTxn.SiacoinOutputID(0) txn := types.Transaction{ SiacoinInputs: []types.SiacoinInput{ @@ -235,6 +241,7 @@ func TestBlock(t *testing.T) { t.Fatal(err) } + // Generate three addresses: addr1, addr2, addr3 pk1 := types.GeneratePrivateKey() addr1 := types.StandardUnlockHash(pk1.PublicKey()) @@ -247,18 +254,19 @@ func TestBlock(t *testing.T) { expectedPayout := cm.TipState().BlockReward() maturityHeight := cm.TipState().MaturityHeight() + 1 - // mine a block sending the payout to the wallet + // Mine a block sending the payout to the addr1 if err := cm.AddBlocks([]types.Block{mineBlock(cm.TipState(), nil, addr1)}); err != nil { t.Fatal(err) } - // mine until the payout matures + // Mine until the payout matures for i := cm.TipState().Index.Height; i < maturityHeight; i++ { if err := cm.AddBlocks([]types.Block{mineBlock(cm.TipState(), nil, types.VoidAddress)}); err != nil { t.Fatal(err) } } + // Check that addr1 has the miner payout output utxos, err := db.UnspentSiacoinOutputs(addr1, 100, 0) if err != nil { t.Fatal(err) @@ -270,6 +278,7 @@ func TestBlock(t *testing.T) { outputID := utxos[0].ID unlockConditions := types.StandardUnlockConditions(pk1.PublicKey()) + // Send 1 SC to addr2 and 2 SC to addr3 100 times in consecutive blocks for i := 0; i < 100; i++ { parentTxn := types.Transaction{ SiacoinInputs: []types.SiacoinInput{ @@ -297,12 +306,14 @@ func TestBlock(t *testing.T) { parentTxn.Signatures[0].Signature = parentSig[:] outputID = types.Hash256(parentTxn.SiacoinOutputID(2)) - // mine a block sending the payout to the wallet + // Mine a block with the above transaction b := mineBlock(cm.TipState(), []types.Transaction{parentTxn}, addr1) if err := cm.AddBlocks([]types.Block{b}); err != nil { t.Fatal(err) } + // Ensure the block we retrieved from the database is the same as the + // actual block block, err := db.Block(b.ID()) if err != nil { t.Fatal(err) @@ -318,6 +329,7 @@ func TestBlock(t *testing.T) { t.Fatalf("expected %d transactions, got %d", len(b.Transactions), len(block.Transactions)) } + // Ensure the miner payouts in the block match for i := range b.MinerPayouts { if b.MinerPayouts[i].Address != block.MinerPayouts[i].SiacoinOutput.Address { t.Fatalf("expected address %v, got %v", b.MinerPayouts[i].Address, block.MinerPayouts[i].SiacoinOutput.Address) @@ -325,6 +337,8 @@ func TestBlock(t *testing.T) { t.Fatalf("expected value %v, got %v", b.MinerPayouts[i].Value, block.MinerPayouts[i].SiacoinOutput.Value) } } + + // Ensure the transactions in the block match for i := range b.Transactions { bTxn := b.Transactions[i] blockTxn := block.Transactions[i] From a24780b6891505c7ffc985fd940a3ce74ed2d37a Mon Sep 17 00:00:00 2001 From: Christopher Tarry Date: Wed, 20 Mar 2024 16:23:57 -0400 Subject: [PATCH 08/16] test retrieving transactions too --- persist/sqlite/consensus_test.go | 42 ++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/persist/sqlite/consensus_test.go b/persist/sqlite/consensus_test.go index 569decc4..0b7c9451 100644 --- a/persist/sqlite/consensus_test.go +++ b/persist/sqlite/consensus_test.go @@ -8,6 +8,7 @@ import ( "go.sia.tech/core/types" "go.sia.tech/coreutils" "go.sia.tech/coreutils/chain" + "go.sia.tech/explored/explorer" "go.sia.tech/explored/persist/sqlite" "go.uber.org/zap/zaptest" ) @@ -212,7 +213,7 @@ func TestBalance(t *testing.T) { checkBalance(addr3, types.Siacoins(100), types.ZeroCurrency, 0) } -func TestBlock(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")) @@ -276,6 +277,22 @@ func TestBlock(t *testing.T) { t.Fatalf("expected value %v, got %v", expectedPayout, utxos[0].SiacoinOutput.Value) } + checkTransaction := func(expectTxn types.Transaction, gotTxn explorer.Transaction) { + if len(expectTxn.SiacoinOutputs) != len(expectTxn.SiacoinOutputs) { + t.Fatalf("expected %d siacoin outputs, got %d", len(expectTxn.SiacoinOutputs), len(expectTxn.SiacoinOutputs)) + } + + for j := range expectTxn.SiacoinOutputs { + expectSco := expectTxn.SiacoinOutputs[j] + gotSco := gotTxn.SiacoinOutputs[j].SiacoinOutput + if expectSco.Address != gotSco.Address { + t.Fatalf("expected address %v, got %v", expectSco.Address, gotSco.Address) + } else if expectSco.Value != gotSco.Value { + t.Fatalf("expected value %v, got %v", expectSco.Value, gotSco.Value) + } + } + } + outputID := utxos[0].ID unlockConditions := types.StandardUnlockConditions(pk1.PublicKey()) // Send 1 SC to addr2 and 2 SC to addr3 100 times in consecutive blocks @@ -338,23 +355,18 @@ func TestBlock(t *testing.T) { } } - // Ensure the transactions in the block match + // Ensure the transactions in the block and retrieved separately match + // with the actual transactions for i := range b.Transactions { - bTxn := b.Transactions[i] - blockTxn := block.Transactions[i] - if len(bTxn.SiacoinOutputs) != len(bTxn.SiacoinOutputs) { - t.Fatalf("expected %d siacoin outputs, got %d", len(bTxn.SiacoinOutputs), len(bTxn.SiacoinOutputs)) - } + checkTransaction(b.Transactions[i], block.Transactions[i]) - for j := range bTxn.SiacoinOutputs { - bSco := bTxn.SiacoinOutputs[j] - blockSco := blockTxn.SiacoinOutputs[j].SiacoinOutput - if bSco.Address != blockSco.Address { - t.Fatalf("expected address %v, got %v", bSco.Address, blockSco.Address) - } else if bSco.Value != blockSco.Value { - t.Fatalf("expected value %v, got %v", bSco.Value, blockSco.Value) - } + txns, err := db.Transactions([]types.TransactionID{b.Transactions[i].ID()}) + if err != nil { + t.Fatal(err) + } else if len(txns) != 1 { + t.Fatal("failed to get transaction") } + checkTransaction(b.Transactions[i], txns[0]) } } } From 91b83a2230245e2f07c5e8c93cace93d97c1f4a0 Mon Sep 17 00:00:00 2001 From: Christopher Tarry Date: Wed, 20 Mar 2024 16:42:03 -0400 Subject: [PATCH 09/16] add tip test --- persist/sqlite/consensus_test.go | 55 ++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/persist/sqlite/consensus_test.go b/persist/sqlite/consensus_test.go index 0b7c9451..9256736c 100644 --- a/persist/sqlite/consensus_test.go +++ b/persist/sqlite/consensus_test.go @@ -370,3 +370,58 @@ 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 := testV1Network() + + store, genesisState, err := chain.NewDBStore(bdb, network, genesisBlock) + if err != nil { + t.Fatal(err) + } + defer store.Close() + + cm := chain.NewManager(store, genesisState) + + if err := cm.AddSubscriber(db, types.ChainIndex{}); err != nil { + t.Fatal(err) + } + + const n = 100 + for i := cm.TipState().Index.Height; i < n; i++ { + if err := cm.AddBlocks([]types.Block{mineBlock(cm.TipState(), nil, types.VoidAddress)}); err != nil { + t.Fatal(err) + } + + tip, err := db.Tip() + if err != nil { + t.Fatal(err) + } + if cm.Tip() != tip { + t.Fatal("tip mismatch") + } + } + + for i := 0; i < n; i++ { + best, err := db.BestTip(uint64(i)) + if err != nil { + t.Fatal(err) + } + if cmBest, ok := cm.BestIndex(uint64(i)); !ok || cmBest != best { + t.Fatal("best tip mismatch") + } + } +} From cf9f0a9a29dc592d6d860b23b08871375977baf3 Mon Sep 17 00:00:00 2001 From: Christopher Tarry Date: Wed, 20 Mar 2024 17:18:17 -0400 Subject: [PATCH 10/16] add balance checks to TestSendTransactions --- persist/sqlite/consensus_test.go | 54 ++++++++++++++++++++++---------- 1 file changed, 38 insertions(+), 16 deletions(-) diff --git a/persist/sqlite/consensus_test.go b/persist/sqlite/consensus_test.go index 9256736c..7b34f3b2 100644 --- a/persist/sqlite/consensus_test.go +++ b/persist/sqlite/consensus_test.go @@ -242,6 +242,36 @@ func TestSendTransactions(t *testing.T) { t.Fatal(err) } + // checkBalance checks that an address has the balances we expect + checkBalance := func(addr types.Address, expectSC, expectImmatureSC types.Currency, expectSF uint64) { + sc, immatureSC, sf, err := db.Balance(addr) + if err != nil { + t.Fatal(err) + } else if sc != expectSC { + t.Fatalf("expected %v siacoins, got %v", expectSC, sc) + } else if immatureSC != expectImmatureSC { + t.Fatalf("expected %v immature siacoins, got %v", expectImmatureSC, immatureSC) + } else if sf != expectSF { + t.Fatalf("expected %d siafunds, got %d", expectSF, sf) + } + } + + checkTransaction := func(expectTxn types.Transaction, gotTxn explorer.Transaction) { + if len(expectTxn.SiacoinOutputs) != len(expectTxn.SiacoinOutputs) { + t.Fatalf("expected %d siacoin outputs, got %d", len(expectTxn.SiacoinOutputs), len(expectTxn.SiacoinOutputs)) + } + + for j := range expectTxn.SiacoinOutputs { + expectSco := expectTxn.SiacoinOutputs[j] + gotSco := gotTxn.SiacoinOutputs[j].SiacoinOutput + if expectSco.Address != gotSco.Address { + t.Fatalf("expected address %v, got %v", expectSco.Address, gotSco.Address) + } else if expectSco.Value != gotSco.Value { + t.Fatalf("expected value %v, got %v", expectSco.Value, gotSco.Value) + } + } + } + // Generate three addresses: addr1, addr2, addr3 pk1 := types.GeneratePrivateKey() addr1 := types.StandardUnlockHash(pk1.PublicKey()) @@ -267,6 +297,10 @@ func TestSendTransactions(t *testing.T) { } } + checkBalance(addr1, expectedPayout, types.ZeroCurrency, 0) + checkBalance(addr2, types.ZeroCurrency, types.ZeroCurrency, 0) + checkBalance(addr3, types.ZeroCurrency, types.ZeroCurrency, 0) + // Check that addr1 has the miner payout output utxos, err := db.UnspentSiacoinOutputs(addr1, 100, 0) if err != nil { @@ -277,22 +311,6 @@ func TestSendTransactions(t *testing.T) { t.Fatalf("expected value %v, got %v", expectedPayout, utxos[0].SiacoinOutput.Value) } - checkTransaction := func(expectTxn types.Transaction, gotTxn explorer.Transaction) { - if len(expectTxn.SiacoinOutputs) != len(expectTxn.SiacoinOutputs) { - t.Fatalf("expected %d siacoin outputs, got %d", len(expectTxn.SiacoinOutputs), len(expectTxn.SiacoinOutputs)) - } - - for j := range expectTxn.SiacoinOutputs { - expectSco := expectTxn.SiacoinOutputs[j] - gotSco := gotTxn.SiacoinOutputs[j].SiacoinOutput - if expectSco.Address != gotSco.Address { - t.Fatalf("expected address %v, got %v", expectSco.Address, gotSco.Address) - } else if expectSco.Value != gotSco.Value { - t.Fatalf("expected value %v, got %v", expectSco.Value, gotSco.Value) - } - } - } - outputID := utxos[0].ID unlockConditions := types.StandardUnlockConditions(pk1.PublicKey()) // Send 1 SC to addr2 and 2 SC to addr3 100 times in consecutive blocks @@ -329,6 +347,10 @@ func TestSendTransactions(t *testing.T) { t.Fatal(err) } + checkBalance(addr1, types.Siacoins(1).Mul64(uint64(i)), types.ZeroCurrency, 0) + checkBalance(addr2, types.Siacoins(2).Mul64(uint64(i)), types.ZeroCurrency, 0) + checkBalance(addr3, expectedPayout.Sub(types.Siacoins(1+2).Mul64(uint64(i+1))), types.ZeroCurrency, 0) + // Ensure the block we retrieved from the database is the same as the // actual block block, err := db.Block(b.ID()) From 79461865103c3241beed2a94c23a5c16df877f9a Mon Sep 17 00:00:00 2001 From: Christopher Tarry Date: Wed, 20 Mar 2024 17:36:56 -0400 Subject: [PATCH 11/16] return 0 balance if we have no records of address yet --- persist/sqlite/consensus_test.go | 8 ++++---- persist/sqlite/outputs.go | 5 ++++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/persist/sqlite/consensus_test.go b/persist/sqlite/consensus_test.go index 7b34f3b2..9d83115e 100644 --- a/persist/sqlite/consensus_test.go +++ b/persist/sqlite/consensus_test.go @@ -342,14 +342,14 @@ func TestSendTransactions(t *testing.T) { outputID = types.Hash256(parentTxn.SiacoinOutputID(2)) // Mine a block with the above transaction - b := mineBlock(cm.TipState(), []types.Transaction{parentTxn}, addr1) + b := mineBlock(cm.TipState(), []types.Transaction{parentTxn}, types.VoidAddress) if err := cm.AddBlocks([]types.Block{b}); err != nil { t.Fatal(err) } - checkBalance(addr1, types.Siacoins(1).Mul64(uint64(i)), types.ZeroCurrency, 0) - checkBalance(addr2, types.Siacoins(2).Mul64(uint64(i)), types.ZeroCurrency, 0) - checkBalance(addr3, expectedPayout.Sub(types.Siacoins(1+2).Mul64(uint64(i+1))), types.ZeroCurrency, 0) + checkBalance(addr1, expectedPayout.Sub(types.Siacoins(1+2).Mul64(uint64(i+1))), types.ZeroCurrency, 0) + checkBalance(addr2, types.Siacoins(1).Mul64(uint64(i+1)), types.ZeroCurrency, 0) + checkBalance(addr3, types.Siacoins(2).Mul64(uint64(i+1)), types.ZeroCurrency, 0) // Ensure the block we retrieved from the database is the same as the // actual block diff --git a/persist/sqlite/outputs.go b/persist/sqlite/outputs.go index e914d5d0..1998ba1a 100644 --- a/persist/sqlite/outputs.go +++ b/persist/sqlite/outputs.go @@ -1,6 +1,7 @@ package sqlite import ( + "database/sql" "fmt" "go.sia.tech/core/types" @@ -53,7 +54,9 @@ func (s *Store) UnspentSiafundOutputs(address types.Address, limit, offset uint6 func (s *Store) Balance(address types.Address) (sc types.Currency, immatureSC types.Currency, sf uint64, err error) { err = s.transaction(func(tx txn) error { err = tx.QueryRow(`SELECT siacoin_balance, immature_siacoin_balance, siafund_balance FROM address_balance WHERE address = ?`, dbEncode(address)).Scan(dbDecode(&sc), dbDecode(&immatureSC), dbDecode(&sf)) - if err != nil { + if err == sql.ErrNoRows { + return nil + } else if err != nil { return fmt.Errorf("failed to query balances: %w", err) } return nil From fbc13b1fb9bb45ad8613c78d6a9711e7e32f10de Mon Sep 17 00:00:00 2001 From: Christopher Tarry Date: Thu, 21 Mar 2024 15:37:09 -0400 Subject: [PATCH 12/16] check siacoin inputs in TestSendTransactions --- persist/sqlite/consensus_test.go | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/persist/sqlite/consensus_test.go b/persist/sqlite/consensus_test.go index 9d83115e..c57da2ab 100644 --- a/persist/sqlite/consensus_test.go +++ b/persist/sqlite/consensus_test.go @@ -2,6 +2,7 @@ package sqlite_test import ( "path/filepath" + "reflect" "testing" "go.sia.tech/core/consensus" @@ -257,13 +258,24 @@ func TestSendTransactions(t *testing.T) { } checkTransaction := func(expectTxn types.Transaction, gotTxn explorer.Transaction) { - if len(expectTxn.SiacoinOutputs) != len(expectTxn.SiacoinOutputs) { + if len(expectTxn.SiacoinInputs) != len(expectTxn.SiacoinInputs) { + t.Fatalf("expected %d siacoin inputs, got %d", len(expectTxn.SiacoinInputs), len(expectTxn.SiacoinInputs)) + } else if len(expectTxn.SiacoinOutputs) != len(expectTxn.SiacoinOutputs) { t.Fatalf("expected %d siacoin outputs, got %d", len(expectTxn.SiacoinOutputs), len(expectTxn.SiacoinOutputs)) } - for j := range expectTxn.SiacoinOutputs { - expectSco := expectTxn.SiacoinOutputs[j] - gotSco := gotTxn.SiacoinOutputs[j].SiacoinOutput + for i := range expectTxn.SiacoinInputs { + expectSci := expectTxn.SiacoinInputs[i] + gotSci := gotTxn.SiacoinInputs[i] + if expectSci.ParentID != gotSci.ParentID { + t.Fatalf("expected parent ID %v, got %v", expectSci.ParentID, gotSci.ParentID) + } else if !reflect.DeepEqual(expectSci.UnlockConditions, gotSci.UnlockConditions) { + t.Fatalf("expected unlock conditions %v, got %v", expectSci.UnlockConditions, gotSci.UnlockConditions) + } + } + for i := range expectTxn.SiacoinOutputs { + expectSco := expectTxn.SiacoinOutputs[i] + gotSco := gotTxn.SiacoinOutputs[i].SiacoinOutput if expectSco.Address != gotSco.Address { t.Fatalf("expected address %v, got %v", expectSco.Address, gotSco.Address) } else if expectSco.Value != gotSco.Value { From 138caa2e87ff8a39f2e606abd88252f53e4be7ea Mon Sep 17 00:00:00 2001 From: Christopher Tarry Date: Thu, 21 Mar 2024 16:25:21 -0400 Subject: [PATCH 13/16] add sending siafunds to TestSendTransactions --- persist/sqlite/consensus_test.go | 107 +++++++++++++++++++++++-------- 1 file changed, 82 insertions(+), 25 deletions(-) diff --git a/persist/sqlite/consensus_test.go b/persist/sqlite/consensus_test.go index c57da2ab..02e79496 100644 --- a/persist/sqlite/consensus_test.go +++ b/persist/sqlite/consensus_test.go @@ -14,7 +14,9 @@ import ( "go.uber.org/zap/zaptest" ) -func testV1Network() (*consensus.Network, types.Block) { +const giftSF = 10000 + +func testV1Network(giftAddr types.Address) (*consensus.Network, types.Block) { // use a modified version of Zen n, genesisBlock := chain.TestnetZen() n.InitialTarget = types.BlockID{0xFF} @@ -26,6 +28,12 @@ func testV1Network() (*consensus.Network, types.Block) { n.HardforkFoundation.Height = 1 n.HardforkV2.AllowHeight = 1000 n.HardforkV2.RequireHeight = 1000 + genesisBlock.Transactions = []types.Transaction{{ + SiafundOutputs: []types.SiafundOutput{{ + Address: giftAddr, + Value: giftSF, + }}, + }} return n, genesisBlock } @@ -90,7 +98,7 @@ func TestBalance(t *testing.T) { } defer bdb.Close() - network, genesisBlock := testV1Network() + network, genesisBlock := testV1Network(types.VoidAddress) store, genesisState, err := chain.NewDBStore(bdb, network, genesisBlock) if err != nil { @@ -229,7 +237,17 @@ func TestSendTransactions(t *testing.T) { } defer bdb.Close() - network, genesisBlock := testV1Network() + // Generate three addresses: addr1, addr2, addr3 + pk1 := types.GeneratePrivateKey() + addr1 := types.StandardUnlockHash(pk1.PublicKey()) + + pk2 := types.GeneratePrivateKey() + addr2 := types.StandardUnlockHash(pk2.PublicKey()) + + pk3 := types.GeneratePrivateKey() + addr3 := types.StandardUnlockHash(pk3.PublicKey()) + + network, genesisBlock := testV1Network(addr1) store, genesisState, err := chain.NewDBStore(bdb, network, genesisBlock) if err != nil { @@ -262,6 +280,10 @@ func TestSendTransactions(t *testing.T) { t.Fatalf("expected %d siacoin inputs, got %d", len(expectTxn.SiacoinInputs), len(expectTxn.SiacoinInputs)) } else if len(expectTxn.SiacoinOutputs) != len(expectTxn.SiacoinOutputs) { t.Fatalf("expected %d siacoin outputs, got %d", len(expectTxn.SiacoinOutputs), len(expectTxn.SiacoinOutputs)) + } else if len(expectTxn.SiafundInputs) != len(expectTxn.SiafundInputs) { + t.Fatalf("expected %d siafund inputs, got %d", len(expectTxn.SiafundInputs), len(expectTxn.SiafundInputs)) + } else if len(expectTxn.SiafundOutputs) != len(expectTxn.SiafundOutputs) { + t.Fatalf("expected %d siafund outputs, got %d", len(expectTxn.SiafundOutputs), len(expectTxn.SiafundOutputs)) } for i := range expectTxn.SiacoinInputs { @@ -282,18 +304,28 @@ func TestSendTransactions(t *testing.T) { t.Fatalf("expected value %v, got %v", expectSco.Value, gotSco.Value) } } + for i := range expectTxn.SiafundInputs { + expectSfi := expectTxn.SiafundInputs[i] + gotSfi := gotTxn.SiafundInputs[i] + if expectSfi.ParentID != gotSfi.ParentID { + t.Fatalf("expected parent ID %v, got %v", expectSfi.ParentID, gotSfi.ParentID) + } else if expectSfi.ClaimAddress != gotSfi.ClaimAddress { + t.Fatalf("expected claim address %v, got %v", expectSfi.ClaimAddress, gotSfi.ClaimAddress) + } else if !reflect.DeepEqual(expectSfi.UnlockConditions, gotSfi.UnlockConditions) { + t.Fatalf("expected unlock conditions %v, got %v", expectSfi.UnlockConditions, gotSfi.UnlockConditions) + } + } + for i := range expectTxn.SiafundOutputs { + expectSfo := expectTxn.SiafundOutputs[i] + gotSfo := gotTxn.SiafundOutputs[i].SiafundOutput + if expectSfo.Address != gotSfo.Address { + t.Fatalf("expected address %v, got %v", expectSfo.Address, gotSfo.Address) + } else if expectSfo.Value != gotSfo.Value { + t.Fatalf("expected value %v, got %v", expectSfo.Value, gotSfo.Value) + } + } } - // Generate three addresses: addr1, addr2, addr3 - pk1 := types.GeneratePrivateKey() - addr1 := types.StandardUnlockHash(pk1.PublicKey()) - - pk2 := types.GeneratePrivateKey() - addr2 := types.StandardUnlockHash(pk2.PublicKey()) - - pk3 := types.GeneratePrivateKey() - addr3 := types.StandardUnlockHash(pk3.PublicKey()) - expectedPayout := cm.TipState().BlockReward() maturityHeight := cm.TipState().MaturityHeight() + 1 @@ -309,7 +341,7 @@ func TestSendTransactions(t *testing.T) { } } - checkBalance(addr1, expectedPayout, types.ZeroCurrency, 0) + checkBalance(addr1, expectedPayout, types.ZeroCurrency, giftSF) checkBalance(addr2, types.ZeroCurrency, types.ZeroCurrency, 0) checkBalance(addr3, types.ZeroCurrency, types.ZeroCurrency, 0) @@ -323,14 +355,21 @@ func TestSendTransactions(t *testing.T) { t.Fatalf("expected value %v, got %v", expectedPayout, utxos[0].SiacoinOutput.Value) } - outputID := utxos[0].ID + sfOutputID := genesisBlock.Transactions[0].SiafundOutputID(0) + scOutputID := utxos[0].ID unlockConditions := types.StandardUnlockConditions(pk1.PublicKey()) // Send 1 SC to addr2 and 2 SC to addr3 100 times in consecutive blocks for i := 0; i < 100; i++ { parentTxn := types.Transaction{ SiacoinInputs: []types.SiacoinInput{ { - ParentID: types.SiacoinOutputID(outputID), + ParentID: types.SiacoinOutputID(scOutputID), + UnlockConditions: unlockConditions, + }, + }, + SiafundInputs: []types.SiafundInput{ + { + ParentID: sfOutputID, UnlockConditions: unlockConditions, }, }, @@ -339,19 +378,37 @@ func TestSendTransactions(t *testing.T) { {Address: addr3, Value: types.Siacoins(2)}, {Address: addr1, Value: expectedPayout.Sub(types.Siacoins(1 + 2).Mul64(uint64(i + 1)))}, }, + SiafundOutputs: []types.SiafundOutput{ + {Address: addr2, Value: 1}, + {Address: addr3, Value: 2}, + {Address: addr1, Value: giftSF - (1+2)*uint64(i+1)}, + }, Signatures: []types.TransactionSignature{ { - ParentID: outputID, + ParentID: scOutputID, + PublicKeyIndex: 0, + CoveredFields: types.CoveredFields{WholeTransaction: true}, + }, + { + ParentID: types.Hash256(sfOutputID), PublicKeyIndex: 0, CoveredFields: types.CoveredFields{WholeTransaction: true}, }, }, } - parentSigHash := cm.TipState().WholeSigHash(parentTxn, outputID, 0, 0, nil) - parentSig := pk1.SignHash(parentSigHash) - parentTxn.Signatures[0].Signature = parentSig[:] - outputID = types.Hash256(parentTxn.SiacoinOutputID(2)) + { + parentSigHash := cm.TipState().WholeSigHash(parentTxn, scOutputID, 0, 0, nil) + parentSig := pk1.SignHash(parentSigHash) + parentTxn.Signatures[0].Signature = parentSig[:] + } + { + parentSigHash := cm.TipState().WholeSigHash(parentTxn, types.Hash256(sfOutputID), 0, 0, nil) + parentSig := pk1.SignHash(parentSigHash) + parentTxn.Signatures[1].Signature = parentSig[:] + } + scOutputID = types.Hash256(parentTxn.SiacoinOutputID(2)) + sfOutputID = parentTxn.SiafundOutputID(2) // Mine a block with the above transaction b := mineBlock(cm.TipState(), []types.Transaction{parentTxn}, types.VoidAddress) @@ -359,9 +416,9 @@ func TestSendTransactions(t *testing.T) { t.Fatal(err) } - checkBalance(addr1, expectedPayout.Sub(types.Siacoins(1+2).Mul64(uint64(i+1))), types.ZeroCurrency, 0) - checkBalance(addr2, types.Siacoins(1).Mul64(uint64(i+1)), types.ZeroCurrency, 0) - checkBalance(addr3, types.Siacoins(2).Mul64(uint64(i+1)), types.ZeroCurrency, 0) + checkBalance(addr1, expectedPayout.Sub(types.Siacoins(1+2).Mul64(uint64(i+1))), types.ZeroCurrency, giftSF-(1+2)*uint64(i+1)) + checkBalance(addr2, types.Siacoins(1).Mul64(uint64(i+1)), types.ZeroCurrency, 1*uint64(i+1)) + checkBalance(addr3, types.Siacoins(2).Mul64(uint64(i+1)), types.ZeroCurrency, 2*uint64(i+1)) // Ensure the block we retrieved from the database is the same as the // actual block @@ -420,7 +477,7 @@ func TestTip(t *testing.T) { } defer bdb.Close() - network, genesisBlock := testV1Network() + network, genesisBlock := testV1Network(types.VoidAddress) store, genesisState, err := chain.NewDBStore(bdb, network, genesisBlock) if err != nil { From 4eee6f8ba5de367bc0776c363efe198308fe56b2 Mon Sep 17 00:00:00 2001 From: Christopher Tarry Date: Thu, 21 Mar 2024 16:38:46 -0400 Subject: [PATCH 14/16] linter fix --- .golangci.yml | 4 +++- persist/sqlite/consensus_test.go | 16 ++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 6adc4e55..0d11f13f 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -154,7 +154,9 @@ issues: # But independently from this option we use default exclude patterns, # it can be disabled by `exclude-use-default: false`. To list all # excluded by default patterns execute `golangci-lint run --help` - exclude: [] + exclude: + - "ifElseChain:.*" + - "exitAfterDefer:.*" # Independently from option `exclude` we use default exclude patterns, # it can be disabled by this option. To list all diff --git a/persist/sqlite/consensus_test.go b/persist/sqlite/consensus_test.go index 02e79496..d16d5bee 100644 --- a/persist/sqlite/consensus_test.go +++ b/persist/sqlite/consensus_test.go @@ -276,14 +276,14 @@ func TestSendTransactions(t *testing.T) { } checkTransaction := func(expectTxn types.Transaction, gotTxn explorer.Transaction) { - if len(expectTxn.SiacoinInputs) != len(expectTxn.SiacoinInputs) { - t.Fatalf("expected %d siacoin inputs, got %d", len(expectTxn.SiacoinInputs), len(expectTxn.SiacoinInputs)) - } else if len(expectTxn.SiacoinOutputs) != len(expectTxn.SiacoinOutputs) { - t.Fatalf("expected %d siacoin outputs, got %d", len(expectTxn.SiacoinOutputs), len(expectTxn.SiacoinOutputs)) - } else if len(expectTxn.SiafundInputs) != len(expectTxn.SiafundInputs) { - t.Fatalf("expected %d siafund inputs, got %d", len(expectTxn.SiafundInputs), len(expectTxn.SiafundInputs)) - } else if len(expectTxn.SiafundOutputs) != len(expectTxn.SiafundOutputs) { - t.Fatalf("expected %d siafund outputs, got %d", len(expectTxn.SiafundOutputs), len(expectTxn.SiafundOutputs)) + if len(expectTxn.SiacoinInputs) != len(gotTxn.SiacoinInputs) { + t.Fatalf("expected %d siacoin inputs, got %d", len(expectTxn.SiacoinInputs), len(gotTxn.SiacoinInputs)) + } else if len(expectTxn.SiacoinOutputs) != len(gotTxn.SiacoinOutputs) { + t.Fatalf("expected %d siacoin outputs, got %d", len(expectTxn.SiacoinOutputs), len(gotTxn.SiacoinOutputs)) + } else if len(expectTxn.SiafundInputs) != len(gotTxn.SiafundInputs) { + t.Fatalf("expected %d siafund inputs, got %d", len(expectTxn.SiafundInputs), len(gotTxn.SiafundInputs)) + } else if len(expectTxn.SiafundOutputs) != len(gotTxn.SiafundOutputs) { + t.Fatalf("expected %d siafund outputs, got %d", len(expectTxn.SiafundOutputs), len(gotTxn.SiafundOutputs)) } for i := range expectTxn.SiacoinInputs { From 06f810aed51671e76a05d429b2679e8739469c2d Mon Sep 17 00:00:00 2001 From: Christopher Tarry Date: Fri, 22 Mar 2024 12:49:42 -0400 Subject: [PATCH 15/16] check source --- persist/sqlite/consensus_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/persist/sqlite/consensus_test.go b/persist/sqlite/consensus_test.go index d16d5bee..bb22a03c 100644 --- a/persist/sqlite/consensus_test.go +++ b/persist/sqlite/consensus_test.go @@ -302,6 +302,8 @@ func TestSendTransactions(t *testing.T) { t.Fatalf("expected address %v, got %v", expectSco.Address, gotSco.Address) } else if expectSco.Value != gotSco.Value { t.Fatalf("expected value %v, got %v", expectSco.Value, gotSco.Value) + } else if gotTxn.SiacoinOutputs[i].Source != explorer.SourceTransaction { + t.Fatalf("expected source %v, got %v", explorer.SourceTransaction, gotTxn.SiacoinOutputs[i].Source) } } for i := range expectTxn.SiafundInputs { @@ -353,6 +355,8 @@ func TestSendTransactions(t *testing.T) { t.Fatalf("expected 1 utxo, got %d", len(utxos)) } else if utxos[0].SiacoinOutput.Value != expectedPayout { t.Fatalf("expected value %v, got %v", expectedPayout, utxos[0].SiacoinOutput.Value) + } else if utxos[0].Source != explorer.SourceMinerPayout { + t.Fatalf("expected source %v, got %v", explorer.SourceMinerPayout, utxos[0].Source) } sfOutputID := genesisBlock.Transactions[0].SiafundOutputID(0) From 2c7c7ca5b4aefa67cc0dfa764a5665e4c294821b Mon Sep 17 00:00:00 2001 From: Christopher Tarry Date: Fri, 22 Mar 2024 13:08:23 -0400 Subject: [PATCH 16/16] check outputs for each address in TestSendTransactions --- persist/sqlite/consensus_test.go | 63 +++++++++++++++++++++++++++++--- 1 file changed, 58 insertions(+), 5 deletions(-) diff --git a/persist/sqlite/consensus_test.go b/persist/sqlite/consensus_test.go index bb22a03c..5311f479 100644 --- a/persist/sqlite/consensus_test.go +++ b/persist/sqlite/consensus_test.go @@ -347,8 +347,10 @@ func TestSendTransactions(t *testing.T) { checkBalance(addr2, types.ZeroCurrency, types.ZeroCurrency, 0) checkBalance(addr3, types.ZeroCurrency, types.ZeroCurrency, 0) + const n = 100 + // Check that addr1 has the miner payout output - utxos, err := db.UnspentSiacoinOutputs(addr1, 100, 0) + utxos, err := db.UnspentSiacoinOutputs(addr1, n, 0) if err != nil { t.Fatal(err) } else if len(utxos) != 1 { @@ -363,7 +365,10 @@ func TestSendTransactions(t *testing.T) { scOutputID := utxos[0].ID unlockConditions := types.StandardUnlockConditions(pk1.PublicKey()) // Send 1 SC to addr2 and 2 SC to addr3 100 times in consecutive blocks - for i := 0; i < 100; i++ { + for i := 0; i < n; i++ { + addr1SCs := expectedPayout.Sub(types.Siacoins(1 + 2).Mul64(uint64(i + 1))) + addr1SFs := giftSF - (1+2)*uint64(i+1) + parentTxn := types.Transaction{ SiacoinInputs: []types.SiacoinInput{ { @@ -380,12 +385,12 @@ func TestSendTransactions(t *testing.T) { SiacoinOutputs: []types.SiacoinOutput{ {Address: addr2, Value: types.Siacoins(1)}, {Address: addr3, Value: types.Siacoins(2)}, - {Address: addr1, Value: expectedPayout.Sub(types.Siacoins(1 + 2).Mul64(uint64(i + 1)))}, + {Address: addr1, Value: addr1SCs}, }, SiafundOutputs: []types.SiafundOutput{ {Address: addr2, Value: 1}, {Address: addr3, Value: 2}, - {Address: addr1, Value: giftSF - (1+2)*uint64(i+1)}, + {Address: addr1, Value: addr1SFs}, }, Signatures: []types.TransactionSignature{ { @@ -420,7 +425,7 @@ func TestSendTransactions(t *testing.T) { t.Fatal(err) } - checkBalance(addr1, expectedPayout.Sub(types.Siacoins(1+2).Mul64(uint64(i+1))), types.ZeroCurrency, giftSF-(1+2)*uint64(i+1)) + checkBalance(addr1, addr1SCs, types.ZeroCurrency, addr1SFs) checkBalance(addr2, types.Siacoins(1).Mul64(uint64(i+1)), types.ZeroCurrency, 1*uint64(i+1)) checkBalance(addr3, types.Siacoins(2).Mul64(uint64(i+1)), types.ZeroCurrency, 2*uint64(i+1)) @@ -463,6 +468,54 @@ func TestSendTransactions(t *testing.T) { } checkTransaction(b.Transactions[i], txns[0]) } + + type expectedUTXOs struct { + addr types.Address + + sc int + scValue types.Currency + + sf int + sfValue uint64 + } + expected := []expectedUTXOs{ + {addr1, 1, addr1SCs, 1, addr1SFs}, + {addr2, i + 1, types.Siacoins(1), i + 1, 1}, + {addr3, i + 1, types.Siacoins(2), i + 1, 2}, + } + for _, e := range expected { + sc, err := db.UnspentSiacoinOutputs(e.addr, n, 0) + if err != nil { + t.Fatal(err) + } + sf, err := db.UnspentSiafundOutputs(e.addr, n, 0) + if err != nil { + t.Fatal(err) + } + + if e.sc != len(sc) { + t.Fatalf("expected %d siacoin utxos, got %d", e.sc, len(sc)) + } else if e.sf != len(sf) { + t.Fatalf("expected %d siafund utxos, got %d", e.sf, len(sf)) + } + + for _, sco := range sc { + if e.addr != sco.SiacoinOutput.Address { + t.Fatalf("expected address %v, got %v", e.addr, sco.SiacoinOutput.Address) + } else if e.scValue != sco.SiacoinOutput.Value { + t.Fatalf("expected value %v, got %v", e.scValue, sco.SiacoinOutput.Value) + } else if explorer.SourceTransaction != sco.Source { + t.Fatalf("expected source %v, got %v", explorer.SourceTransaction, sco.Source) + } + } + for _, sfo := range sf { + if e.addr != sfo.SiafundOutput.Address { + t.Fatalf("expected address %v, got %v", e.addr, sfo.SiafundOutput.Address) + } else if e.sfValue != sfo.SiafundOutput.Value { + t.Fatalf("expected value %v, got %v", e.sfValue, sfo.SiafundOutput.Value) + } + } + } } }