From d050a32cb27bed138935ffbc1973d0e300a2efd3 Mon Sep 17 00:00:00 2001 From: Guilherme Salgado Date: Wed, 18 Feb 2015 12:22:03 +0000 Subject: [PATCH] votingpool: API to store withdrawal txs in the txstore --- votingpool/error.go | 5 ++ votingpool/error_test.go | 1 + votingpool/withdrawal.go | 26 ++++++++ votingpool/withdrawal_wb_test.go | 100 +++++++++++++++++++++++++++++++ 4 files changed, 132 insertions(+) diff --git a/votingpool/error.go b/votingpool/error.go index f2a9e94..8ac5ce9 100644 --- a/votingpool/error.go +++ b/votingpool/error.go @@ -147,6 +147,10 @@ const ( // invalid ID. ErrSeriesIDInvalid + // ErrWithdrawalTxStorage indicates an error when storing withdrawal + // transactions. + ErrWithdrawalTxStorage + // lastErr is used for testing, making it possible to iterate over // the error codes in order to check that they all have proper // translations in errorCodeStrings. @@ -187,6 +191,7 @@ var errorCodeStrings = map[ErrorCode]string{ ErrTxSigning: "ErrTxSigning", ErrInvalidScriptHash: "ErrInvalidScriptHash", ErrWithdrawFromUnusedAddr: "ErrWithdrawFromUnusedAddr", + ErrWithdrawalTxStorage: "ErrWithdrawalTxStorage", } // String returns the ErrorCode as a human-readable name. diff --git a/votingpool/error_test.go b/votingpool/error_test.go index b65a6ef..99e9ccf 100644 --- a/votingpool/error_test.go +++ b/votingpool/error_test.go @@ -64,6 +64,7 @@ func TestErrorCodeStringer(t *testing.T) { {vp.ErrTxSigning, "ErrTxSigning"}, {vp.ErrInvalidScriptHash, "ErrInvalidScriptHash"}, {vp.ErrWithdrawFromUnusedAddr, "ErrWithdrawFromUnusedAddr"}, + {vp.ErrWithdrawalTxStorage, "ErrWithdrawalTxStorage"}, {0xffff, "Unknown ErrorCode (65535)"}, } diff --git a/votingpool/withdrawal.go b/votingpool/withdrawal.go index 4479640..e21bd80 100644 --- a/votingpool/withdrawal.go +++ b/votingpool/withdrawal.go @@ -147,6 +147,23 @@ func (s outputStatus) String() string { return strings[s] } +func (tx *changeAwareTx) addSelfToStore(store *wtxmgr.Store) error { + rec, err := wtxmgr.NewTxRecordFromMsgTx(tx.MsgTx, time.Now()) + if err != nil { + return newError(ErrWithdrawalTxStorage, "error constructing TxRecord for storing", err) + } + + if err := store.InsertTx(rec, nil); err != nil { + return newError(ErrWithdrawalTxStorage, "error adding tx to store", err) + } + if tx.changeIdx != -1 { + if err = store.AddCredit(rec, nil, uint32(tx.changeIdx), true); err != nil { + return newError(ErrWithdrawalTxStorage, "error adding tx credits to store", err) + } + } + return nil +} + // Outputs returns a map of outbailment IDs to WithdrawalOutputs for all outputs // requested in this withdrawal. func (s *WithdrawalStatus) Outputs() map[OutBailmentID]*WithdrawalOutput { @@ -924,3 +941,12 @@ func nextChangeAddress(a ChangeAddress) (ChangeAddress, error) { addr, err := a.pool.ChangeAddress(seriesID, index) return *addr, err } + +func storeTransactions(store *wtxmgr.Store, transactions []*changeAwareTx) error { + for _, tx := range transactions { + if err := tx.addSelfToStore(store); err != nil { + return err + } + } + return nil +} diff --git a/votingpool/withdrawal_wb_test.go b/votingpool/withdrawal_wb_test.go index c0f2007..213c133 100644 --- a/votingpool/withdrawal_wb_test.go +++ b/votingpool/withdrawal_wb_test.go @@ -28,6 +28,7 @@ import ( "github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil/hdkeychain" "github.com/btcsuite/btcwallet/waddrmgr" + "github.com/btcsuite/btcwallet/wtxmgr" ) // TestOutputSplittingNotEnoughInputs checks that an output will get split if we @@ -972,6 +973,105 @@ func TestTxFeeEstimationForLargeTx(t *testing.T) { } } +func TestStoreTransactionsWithoutChangeOutput(t *testing.T) { + tearDown, pool, store := TstCreatePoolAndTxStore(t) + defer tearDown() + + wtx := createWithdrawalTxWithStoreCredits(t, store, pool, []int64{4e6}, []int64{3e6}) + tx := &changeAwareTx{MsgTx: wtx.toMsgTx(), changeIdx: int32(-1)} + if err := storeTransactions(store, []*changeAwareTx{tx}); err != nil { + t.Fatal(err) + } + + credits, err := store.UnspentOutputs() + if err != nil { + t.Fatal(err) + } + if len(credits) != 0 { + t.Fatalf("Unexpected number of credits in txstore; got %d, want 0", len(credits)) + } +} + +func TestStoreTransactionsWithChangeOutput(t *testing.T) { + tearDown, pool, store := TstCreatePoolAndTxStore(t) + defer tearDown() + + wtx := createWithdrawalTxWithStoreCredits(t, store, pool, []int64{5e6}, []int64{1e6, 1e6}) + wtx.changeOutput = wire.NewTxOut(int64(3e6), []byte{}) + msgtx := wtx.toMsgTx() + tx := &changeAwareTx{MsgTx: msgtx, changeIdx: int32(len(msgtx.TxOut) - 1)} + + if err := storeTransactions(store, []*changeAwareTx{tx}); err != nil { + t.Fatal(err) + } + + sha := msgtx.TxSha() + txDetails, err := store.TxDetails(&sha) + if err != nil { + t.Fatal(err) + } + if txDetails == nil { + t.Fatal("The new tx doesn't seem to have been stored") + } + + storedTx := txDetails.TxRecord.MsgTx + outputTotal := int64(0) + for i, txOut := range storedTx.TxOut { + if int32(i) != tx.changeIdx { + outputTotal += txOut.Value + } + } + if outputTotal != int64(2e6) { + t.Fatalf("Unexpected output amount; got %v, want %v", outputTotal, int64(2e6)) + } + + inputTotal := btcutil.Amount(0) + for _, debit := range txDetails.Debits { + inputTotal += debit.Amount + } + if inputTotal != btcutil.Amount(5e6) { + t.Fatalf("Unexpected input amount; got %v, want %v", inputTotal, btcutil.Amount(5e6)) + } + + credits, err := store.UnspentOutputs() + if err != nil { + t.Fatal(err) + } + if len(credits) != 1 { + t.Fatalf("Unexpected number of credits in txstore; got %d, want 1", len(credits)) + } + changeOutpoint := wire.OutPoint{Hash: sha, Index: uint32(tx.changeIdx)} + if credits[0].OutPoint != changeOutpoint { + t.Fatalf("Credit's outpoint (%v) doesn't match the one from change output (%v)", + credits[0].OutPoint, changeOutpoint) + } +} + +// createWithdrawalTxWithStoreCredits creates a new Credit in the given store +// for each entry in inputAmounts, and uses them to construct a withdrawalTx +// with one output for every entry in outputAmounts. +func createWithdrawalTxWithStoreCredits(t *testing.T, store *wtxmgr.Store, pool *Pool, + inputAmounts []int64, outputAmounts []int64) *withdrawalTx { + masters := []*hdkeychain.ExtendedKey{ + TstCreateMasterKey(t, bytes.Repeat(uint32ToBytes(getUniqueID()), 4)), + TstCreateMasterKey(t, bytes.Repeat(uint32ToBytes(getUniqueID()), 4)), + TstCreateMasterKey(t, bytes.Repeat(uint32ToBytes(getUniqueID()), 4)), + } + def := TstCreateSeriesDef(t, pool, 2, masters) + TstCreateSeries(t, pool, []TstSeriesDef{def}) + net := pool.Manager().ChainParams() + tx := newWithdrawalTx() + for _, c := range TstCreateSeriesCreditsOnStore(t, pool, def.SeriesID, inputAmounts, store) { + tx.addInput(c) + } + for i, amount := range outputAmounts { + request := TstNewOutputRequest( + t, uint32(i), "34eVkREKgvvGASZW7hkgE2uNc1yycntMK6", btcutil.Amount(amount), net) + tx.addOutput(request) + } + return tx +} + // checkNonEmptySigsForPrivKeys checks that every signature list in txSigs has // one non-empty signature for every non-nil private key in the given list. This // is to make sure every signature list matches the specification at