diff --git a/consensus/state.go b/consensus/state.go index 93d60c9..7b45225 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -91,11 +91,11 @@ func (n *Network) GenesisState() State { return State{ Network: n, - Index: types.ChainIndex{Height: ^uint64(0)}, - PrevTimestamps: [11]time.Time{}, - Depth: intToTarget(maxTarget), - ChildTarget: n.InitialTarget, - SiafundPool: types.ZeroCurrency, + Index: types.ChainIndex{Height: ^uint64(0)}, + PrevTimestamps: [11]time.Time{}, + Depth: intToTarget(maxTarget), + ChildTarget: n.InitialTarget, + SiafundTaxRevenue: types.ZeroCurrency, OakTime: 0, OakTarget: intToTarget(maxTarget), @@ -111,11 +111,11 @@ func (n *Network) GenesisState() State { type State struct { Network *Network `json:"-"` // network parameters are not encoded - Index types.ChainIndex `json:"index"` - PrevTimestamps [11]time.Time `json:"prevTimestamps"` // newest -> oldest - Depth types.BlockID `json:"depth"` - ChildTarget types.BlockID `json:"childTarget"` - SiafundPool types.Currency `json:"siafundPool"` + Index types.ChainIndex `json:"index"` + PrevTimestamps [11]time.Time `json:"prevTimestamps"` // newest -> oldest + Depth types.BlockID `json:"depth"` + ChildTarget types.BlockID `json:"childTarget"` + SiafundTaxRevenue types.Currency `json:"siafundTaxRevenue"` // Oak hardfork state OakTime time.Duration `json:"oakTime"` @@ -139,7 +139,7 @@ func (s State) EncodeTo(e *types.Encoder) { } s.Depth.EncodeTo(e) s.ChildTarget.EncodeTo(e) - types.V2Currency(s.SiafundPool).EncodeTo(e) + types.V2Currency(s.SiafundTaxRevenue).EncodeTo(e) e.WriteUint64(uint64(s.OakTime)) s.OakTarget.EncodeTo(e) @@ -160,7 +160,7 @@ func (s *State) DecodeFrom(d *types.Decoder) { } s.Depth.DecodeFrom(d) s.ChildTarget.DecodeFrom(d) - (*types.V2Currency)(&s.SiafundPool).DecodeFrom(d) + (*types.V2Currency)(&s.SiafundTaxRevenue).DecodeFrom(d) s.OakTime = time.Duration(d.ReadUint64()) s.OakTarget.DecodeFrom(d) @@ -374,12 +374,7 @@ func (s State) FileContractTax(fc types.FileContract) types.Currency { // V2FileContractTax computes the tax levied on a given v2 contract. func (s State) V2FileContractTax(fc types.V2FileContract) types.Currency { - sum := fc.RenterOutput.Value.Add(fc.HostOutput.Value) - tax := sum.Div64(25) // 4% - // round down to nearest multiple of SiafundCount - _, r := bits.Div64(0, tax.Hi, s.SiafundCount()) - _, r = bits.Div64(r, tax.Lo, s.SiafundCount()) - return tax.Sub(types.NewCurrency64(r)) + return fc.RenterOutput.Value.Add(fc.HostOutput.Value).Div64(25) // 4% } // StorageProofLeafIndex returns the leaf index used when computing or @@ -537,7 +532,7 @@ func (s State) PartialSigHash(txn types.Transaction, cf types.CoveredFields) typ // TransactionsCommitment returns the commitment hash covering the transactions // that comprise a child block. -func (s *State) TransactionsCommitment(txns []types.Transaction, v2txns []types.V2Transaction) types.Hash256 { +func (s State) TransactionsCommitment(txns []types.Transaction, v2txns []types.V2Transaction) types.Hash256 { var acc blake2b.Accumulator for _, txn := range txns { acc.AddLeaf(txn.FullHash()) @@ -598,7 +593,7 @@ type MidState struct { res map[types.FileContractID]bool v2revs map[types.FileContractID]*types.V2FileContractElement v2res map[types.FileContractID]types.V2FileContractResolutionType - siafundPool types.Currency + siafundTaxRevenue types.Currency foundationSubsidy types.Address foundationManagement types.Address @@ -660,7 +655,7 @@ func NewMidState(s State) *MidState { res: make(map[types.FileContractID]bool), v2revs: make(map[types.FileContractID]*types.V2FileContractElement), v2res: make(map[types.FileContractID]types.V2FileContractResolutionType), - siafundPool: s.SiafundPool, + siafundTaxRevenue: s.SiafundTaxRevenue, foundationSubsidy: s.FoundationSubsidyAddress, foundationManagement: s.FoundationManagementAddress, } diff --git a/consensus/update.go b/consensus/update.go index 1c3e3c6..22e23a5 100644 --- a/consensus/update.go +++ b/consensus/update.go @@ -367,7 +367,7 @@ func (ms *MidState) addSiafundElement(id types.SiafundOutputID, sfo types.Siafun StateElement: types.StateElement{LeafIndex: types.UnassignedLeafIndex}, ID: id, SiafundOutput: sfo, - ClaimStart: ms.siafundPool, + ClaimStart: ms.siafundTaxRevenue, } ms.sfes = append(ms.sfes, sfe) ms.created[ms.sfes[len(ms.sfes)-1].ID] = len(ms.sfes) - 1 @@ -389,7 +389,7 @@ func (ms *MidState) addFileContractElement(id types.FileContractID, fc types.Fil } ms.fces = append(ms.fces, fce) ms.created[ms.fces[len(ms.fces)-1].ID] = len(ms.fces) - 1 - ms.siafundPool = ms.siafundPool.Add(ms.base.FileContractTax(fce.FileContract)) + ms.siafundTaxRevenue = ms.siafundTaxRevenue.Add(ms.base.FileContractTax(fce.FileContract)) } func (ms *MidState) reviseFileContractElement(fce types.FileContractElement, rev types.FileContract) { @@ -426,7 +426,7 @@ func (ms *MidState) addV2FileContractElement(id types.FileContractID, fc types.V } ms.v2fces = append(ms.v2fces, fce) ms.created[ms.v2fces[len(ms.v2fces)-1].ID] = len(ms.v2fces) - 1 - ms.siafundPool = ms.siafundPool.Add(ms.base.V2FileContractTax(fce.V2FileContract)) + ms.siafundTaxRevenue = ms.siafundTaxRevenue.Add(ms.base.V2FileContractTax(fce.V2FileContract)) } func (ms *MidState) reviseV2FileContractElement(fce types.V2FileContractElement, rev types.V2FileContract) { @@ -477,7 +477,7 @@ func (ms *MidState) ApplyTransaction(txn types.Transaction, ts V1TransactionSupp if !ok { panic("missing SiafundElement") } - claimPortion := ms.siafundPool.Sub(sfe.ClaimStart).Div64(ms.base.SiafundCount()).Mul64(sfe.SiafundOutput.Value) + claimPortion := ms.siafundTaxRevenue.Sub(sfe.ClaimStart).Div64(ms.base.SiafundCount()).Mul64(sfe.SiafundOutput.Value) ms.spendSiafundElement(sfe, txid) ms.addImmatureSiacoinElement(sfi.ParentID.ClaimOutputID(), types.SiacoinOutput{Value: claimPortion, Address: sfi.ClaimAddress}) } @@ -528,7 +528,7 @@ func (ms *MidState) ApplyV2Transaction(txn types.V2Transaction) { } for _, sfi := range txn.SiafundInputs { ms.spendSiafundElement(sfi.Parent, txid) - claimPortion := ms.siafundPool.Sub(sfi.Parent.ClaimStart).Div64(ms.base.SiafundCount()).Mul64(sfi.Parent.SiafundOutput.Value) + claimPortion := ms.siafundTaxRevenue.Sub(sfi.Parent.ClaimStart).Div64(ms.base.SiafundCount()).Mul64(sfi.Parent.SiafundOutput.Value) ms.addImmatureSiacoinElement(sfi.Parent.ID.V2ClaimOutputID(), types.SiacoinOutput{Value: claimPortion, Address: sfi.ClaimAddress}) } for i, sfo := range txn.SiafundOutputs { @@ -739,7 +739,7 @@ func ApplyBlock(s State, b types.Block, bs V1BlockSupplement, targetTimestamp ti ms := NewMidState(s) ms.ApplyBlock(b, bs) - s.SiafundPool = ms.siafundPool + s.SiafundTaxRevenue = ms.siafundTaxRevenue s.Attestations += uint64(len(ms.aes)) s.FoundationSubsidyAddress = ms.foundationSubsidy s.FoundationManagementAddress = ms.foundationManagement diff --git a/consensus/update_test.go b/consensus/update_test.go index c20280a..d0fb88d 100644 --- a/consensus/update_test.go +++ b/consensus/update_test.go @@ -1089,11 +1089,6 @@ func TestApplyRevertBlockV2(t *testing.T) { checkUpdateElements(au, addedSCEs, spentSCEs, addedSFEs, spentSFEs) } - _ = renterPublicKey - _ = hostPublicKey - _ = checkRevertElements - _ = prev - // revert block spending sc and sf ru := RevertBlock(prev, b2, V1BlockSupplement{}) cs = prev @@ -1297,6 +1292,135 @@ func TestApplyRevertBlockV2(t *testing.T) { } } +func TestSiafunds(t *testing.T) { + n, genesisBlock := testnet() + n.HardforkV2.AllowHeight = 1 + n.HardforkV2.RequireHeight = 2 + + key := types.GeneratePrivateKey() + + giftAddress := types.StandardAddress(key.PublicKey()) + giftAmountSC := types.Siacoins(100e3) + giftAmountSF := uint64(1000) + giftTxn := types.Transaction{ + SiacoinOutputs: []types.SiacoinOutput{ + {Address: giftAddress, Value: giftAmountSC}, + }, + SiafundOutputs: []types.SiafundOutput{ + {Address: giftAddress, Value: giftAmountSF}, + }, + } + genesisBlock.Transactions = []types.Transaction{giftTxn} + db, cs := newConsensusDB(n, genesisBlock) + + signTxn := func(cs State, txn *types.V2Transaction) { + for i := range txn.SiacoinInputs { + txn.SiacoinInputs[i].SatisfiedPolicy = types.SatisfiedPolicy{ + Policy: types.PolicyPublicKey(key.PublicKey()), + Signatures: []types.Signature{key.SignHash(cs.InputSigHash(*txn))}, + } + } + for i := range txn.SiafundInputs { + txn.SiafundInputs[i].SatisfiedPolicy = types.SatisfiedPolicy{ + Policy: types.PolicyPublicKey(key.PublicKey()), + Signatures: []types.Signature{key.SignHash(cs.InputSigHash(*txn))}, + } + } + for i := range txn.FileContracts { + txn.FileContracts[i].RenterSignature = key.SignHash(cs.ContractSigHash(txn.FileContracts[i])) + txn.FileContracts[i].HostSignature = key.SignHash(cs.ContractSigHash(txn.FileContracts[i])) + } + } + mineTxns := func(txns []types.Transaction, v2txns []types.V2Transaction) (au ApplyUpdate, err error) { + b := types.Block{ + ParentID: cs.Index.ID, + Timestamp: types.CurrentTimestamp(), + MinerPayouts: []types.SiacoinOutput{{Address: types.VoidAddress, Value: cs.BlockReward()}}, + Transactions: txns, + } + if len(v2txns) > 0 { + b.V2 = &types.V2BlockData{ + Height: cs.Index.Height + 1, + Commitment: cs.Commitment(cs.TransactionsCommitment(txns, v2txns), b.MinerPayouts[0].Address), + Transactions: v2txns, + } + } + findBlockNonce(cs, &b) + if err = ValidateBlock(cs, b, V1BlockSupplement{}); err != nil { + return + } + cs, au = ApplyBlock(cs, b, V1BlockSupplement{}, db.ancestorTimestamp(b.ParentID)) + db.applyBlock(au) + return + } + + fc := types.V2FileContract{ + ProofHeight: 20, + ExpirationHeight: 30, + RenterOutput: types.SiacoinOutput{Value: types.Siacoins(5000)}, + HostOutput: types.SiacoinOutput{Value: types.Siacoins(5000)}, + RenterPublicKey: key.PublicKey(), + HostPublicKey: key.PublicKey(), + } + fcValue := fc.RenterOutput.Value.Add(fc.HostOutput.Value).Add(cs.V2FileContractTax(fc)) + + txn := types.V2Transaction{ + SiacoinInputs: []types.V2SiacoinInput{{ + Parent: db.sces[giftTxn.SiacoinOutputID(0)], + }}, + SiacoinOutputs: []types.SiacoinOutput{{ + Address: giftAddress, + Value: giftAmountSC.Sub(fcValue), + }}, + FileContracts: []types.V2FileContract{fc}, + } + signTxn(cs, &txn) + prev := cs + if _, err := mineTxns(nil, []types.V2Transaction{txn}); err != nil { + t.Fatal(err) + } + // siafund revenue should have increased + if cs.SiafundTaxRevenue != prev.SiafundTaxRevenue.Add(cs.V2FileContractTax(fc)) { + t.Fatalf("expected %v siafund revenue, got %v", prev.SiafundTaxRevenue.Add(cs.V2FileContractTax(fc)), cs.SiafundTaxRevenue) + } + + // make a siafund claim + txn = types.V2Transaction{ + SiafundInputs: []types.V2SiafundInput{{ + Parent: db.sfes[giftTxn.SiafundOutputID(0)], + ClaimAddress: giftAddress, + }}, + SiafundOutputs: []types.SiafundOutput{{ + Address: giftAddress, + Value: giftAmountSF, + }}, + } + signTxn(cs, &txn) + prev = cs + if au, err := mineTxns(nil, []types.V2Transaction{txn}); err != nil { + t.Fatal(err) + } else { + // siafund revenue should be unchanged + if cs.SiafundTaxRevenue != prev.SiafundTaxRevenue { + t.Fatalf("expected %v siafund revenue, got %v", prev.SiafundTaxRevenue, cs.SiafundTaxRevenue) + } + // should have received a timelocked siafund claim output + var claimOutput *types.SiacoinElement + au.ForEachSiacoinElement(func(sce types.SiacoinElement, _, _ bool) { + if sce.ID == txn.SiafundInputs[0].Parent.ID.V2ClaimOutputID() { + claimOutput = &sce + } + }) + if claimOutput == nil { + t.Fatal("expected siafund claim output") + } else if claimOutput.MaturityHeight != cs.MaturityHeight()-1 { + t.Fatalf("expected siafund claim output to mature at height %v, got %v", cs.MaturityHeight()-1, claimOutput.MaturityHeight) + } else if exp := cs.V2FileContractTax(fc).Div64(cs.SiafundCount() / giftAmountSF); claimOutput.SiacoinOutput.Value != exp { + t.Fatalf("expected siafund claim output value %v, got %v", exp, claimOutput.SiacoinOutput.Value) + } + } +} + func TestFoundationSubsidy(t *testing.T) { key := types.GeneratePrivateKey() addr := types.StandardAddress(key.PublicKey()) diff --git a/types/types.go b/types/types.go index b41ff69..48ab5c4 100644 --- a/types/types.go +++ b/types/types.go @@ -636,7 +636,7 @@ type SiafundElement struct { ID SiafundOutputID `json:"id"` StateElement StateElement `json:"stateElement"` SiafundOutput SiafundOutput `json:"siafundOutput"` - ClaimStart Currency `json:"claimStart"` // value of SiafundPool when element was created + ClaimStart Currency `json:"claimStart"` // value of SiafundTaxRevenue when element was created } // A FileContractElement is a record of a FileContract within the state