"github.com/vapor/asset"
"github.com/vapor/blockchain/query"
"github.com/vapor/blockchain/signers"
- "github.com/vapor/blockchain/txbuilder"
"github.com/vapor/consensus"
"github.com/vapor/crypto/ed25519"
"github.com/vapor/crypto/ed25519/chainkd"
"github.com/vapor/errors"
"github.com/vapor/protocol/bc"
"github.com/vapor/protocol/bc/types"
+ "github.com/vapor/protocol/state"
)
// POST /list-accounts
tx.Outputs = append(tx.Outputs, a.wallet.BuildAnnotatedOutput(&ins.Tx, i))
}
- tx.Fee = txbuilder.CalculateTxFee(&ins.Tx)
+ tx.Fee = state.CalculateTxFee(&ins.Tx)
return NewSuccessResponse(tx)
}
"github.com/vapor/errors"
"github.com/vapor/protocol/bc/types"
+ "github.com/vapor/protocol/state"
)
// NewBuilder return new TemplateBuilder instance
}
tpl.Transaction = types.NewTx(*tx)
- tpl.Fee = CalculateTxFee(tpl.Transaction)
+ tpl.Fee = state.CalculateTxFee(tpl.Transaction)
return tpl, tx, nil
}
"context"
cfg "github.com/vapor/config"
- "github.com/vapor/consensus"
"github.com/vapor/errors"
"github.com/vapor/protocol"
"github.com/vapor/protocol/bc/types"
+ "github.com/vapor/protocol/state"
"github.com/vapor/protocol/vm"
)
// assembles a fully signed tx, and stores the effects of
// its changes on the UTXO set.
func FinalizeTx(ctx context.Context, c *protocol.Chain, tx *types.Tx) error {
- if fee := CalculateTxFee(tx); fee > cfg.CommonConfig.Wallet.MaxTxFee {
+ if fee := state.CalculateTxFee(tx); fee > cfg.CommonConfig.Wallet.MaxTxFee {
return ErrExtTxFee
}
return lastError
}
-// CalculateTxFee calculate transaction fee
-func CalculateTxFee(tx *types.Tx) (fee uint64) {
- totalInputBTM := uint64(0)
- totalOutputBTM := uint64(0)
-
- for _, input := range tx.Inputs {
- if input.InputType() == types.CoinbaseInputType {
- return 0
- }
- if input.AssetID() == *consensus.BTMAssetID {
- totalInputBTM += input.Amount()
- }
- }
-
- for _, output := range tx.Outputs {
- if *output.AssetAmount().AssetId == *consensus.BTMAssetID {
- totalOutputBTM += output.AssetAmount().Amount
- }
- }
-
- fee = totalInputBTM - totalOutputBTM
- return
-}
-
func checkGasInputIDs(tx *types.Tx) error {
for _, inp := range tx.Inputs {
switch inp.InputType() {
// createCoinbaseTx returns a coinbase transaction paying an appropriate subsidy
// based on the passed block height to the provided address. When the address
// is nil, the coinbase transaction will instead be redeemable by anyone.
-func createCoinbaseTx(consensusResult *state.ConsensusResult, accountManager *account.Manager, amount uint64) (tx *types.Tx, err error) {
- blockHeight := consensusResult.BlockHeight + 1
- amount += consensus.BlockSubsidy(blockHeight)
+func createCoinbaseTx(accountManager *account.Manager, blockHeight uint64) (tx *types.Tx, err error) {
arbitrary := append([]byte{0x00}, []byte(strconv.FormatUint(blockHeight, 10))...)
-
var script []byte
if accountManager == nil {
script, err = vmutil.DefaultCoinbaseProgram()
return nil, err
}
- coinbaseReward := &state.CoinbaseReward{
- Amount: amount,
- ControlProgram: script,
+ _, txData, err := builder.Build()
+ if err != nil {
+ return nil, err
}
- rewards, err := state.AddCoinbaseRewards(consensusResult, coinbaseReward, blockHeight)
+
+ byteData, err := txData.MarshalText()
if err != nil {
return nil, err
}
+ txData.SerializedSize = uint64(len(byteData))
+
+ tx = &types.Tx{
+ TxData: *txData,
+ Tx: types.MapTx(txData),
+ }
+ return tx, nil
+}
+
+// restructCoinbaseTx build coinbase transaction with aggregate outputs when it achieved the specified block height
+func restructCoinbaseTx(tx *types.Tx, rewards []state.CoinbaseReward) (*types.Tx, error) {
+ var arbitrary []byte
+ if len(tx.InputIDs) > 0 {
+ e, ok := tx.Entries[tx.Tx.InputIDs[0]].(*bc.Coinbase)
+ if ok {
+ arbitrary = e.Arbitrary
+ }
+ }
+
+ if arbitrary == nil {
+ return nil, errors.New("bad origin coinbase input")
+ }
+
+ builder := txbuilder.NewBuilder(time.Now())
+ if err := builder.AddInput(types.NewCoinbaseInput(arbitrary), &txbuilder.SigningInstruction{}); err != nil {
+ return nil, err
+ }
// add the aggregate coinbase rewards
for _, r := range rewards {
- if err = builder.AddOutput(types.NewIntraChainOutput(*consensus.BTMAssetID, r.Amount, r.ControlProgram)); err != nil {
+ if err := builder.AddOutput(types.NewIntraChainOutput(*consensus.BTMAssetID, r.Amount, r.ControlProgram)); err != nil {
return nil, err
}
}
}
txData.SerializedSize = uint64(len(byteData))
- tx = &types.Tx{
+ return &types.Tx{
TxData: *txData,
Tx: types.MapTx(txData),
- }
- return tx, nil
+ }, nil
}
// NewBlockTemplate returns a new block template that is ready to be solved
}
txEntries := []*bc.Tx{nil}
gasUsed := uint64(0)
- txFee := uint64(0)
// get preblock info for generate next block
preBlockHeader := c.BestBlockHeader()
b.Transactions = append(b.Transactions, txDesc.Tx)
txEntries = append(txEntries, tx)
gasUsed += uint64(gasStatus.GasUsed)
- txFee += txDesc.Fee
-
if gasUsed == consensus.MaxBlockGas {
break
}
}
+ // create coinbase transaction
+ b.Transactions[0], err = createCoinbaseTx(accountManager, nextBlockHeight)
+ if err != nil {
+ return nil, errors.Wrap(err, "fail on createCoinbaseTx")
+ }
+
consensusResult, err := c.GetConsensusResultByHash(&preBlockHash)
if err != nil {
return nil, err
}
- // creater coinbase transaction
- b.Transactions[0], err = createCoinbaseTx(consensusResult, accountManager, txFee)
+ coinbaseReceiver, err := consensusResult.AttachCoinbaseReward(b)
if err != nil {
- return nil, errors.Wrap(err, "fail on createCoinbaseTx")
+ return nil, err
+ }
+
+ rewards, err := consensusResult.GetCoinbaseRewards(nextBlockHeight)
+ if err != nil {
+ return nil, err
}
- txEntries[0] = b.Transactions[0].Tx
+ // restruct coinbase transaction
+ if len(rewards) > 0 {
+ rewards = append([]state.CoinbaseReward{state.CoinbaseReward{ControlProgram: coinbaseReceiver}}, rewards...)
+ b.Transactions[0], err = restructCoinbaseTx(b.Transactions[0], rewards)
+ if err != nil {
+ return nil, errors.Wrap(err, "fail on createCoinbaseTx")
+ }
+ }
+
+ txEntries[0] = b.Transactions[0].Tx
b.BlockHeader.BlockCommitment.TransactionsMerkleRoot, err = types.TxMerkleRoot(txEntries)
if err != nil {
return nil, err
"github.com/vapor/testutil"
)
-func TestCreateCoinbaseTx(t *testing.T) {
+func TestRestructCoinbaseTx(t *testing.T) {
reductionInterval := uint64(840000)
cases := []struct {
desc string
return err
}
- coinbaseReward, err := state.CalCoinbaseReward(block)
+ coinbaseReceiver, err := consensusResult.AttachCoinbaseReward(block)
if err != nil {
return err
}
- rewards, err := state.AddCoinbaseRewards(consensusResult, coinbaseReward, block.Height)
+ rewards, err := consensusResult.GetCoinbaseRewards(block.Height)
if err != nil {
return err
}
- rewards = append([]state.CoinbaseReward{state.CoinbaseReward{ControlProgram: coinbaseReward.ControlProgram}}, rewards...)
+ rewards = append([]state.CoinbaseReward{state.CoinbaseReward{ControlProgram: coinbaseReceiver}}, rewards...)
bcBlock := types.MapBlock(block)
if err := validation.ValidateBlock(bcBlock, parent, rewards); err != nil {
package state
import (
- "bytes"
"encoding/hex"
"sort"
// ApplyBlock calculate the consensus result for new block
func (c *ConsensusResult) ApplyBlock(block *types.Block) error {
- var ok bool
if c.BlockHash != block.PreviousBlockHash {
return errors.New("block parent hash is not equals last block hash of vote result")
}
- reward, err := CalCoinbaseReward(block)
- if err != nil {
+ if _, err := c.AttachCoinbaseReward(block); err != nil {
return err
}
- if c.IsFinalize() {
- c.CoinbaseReward = map[string]uint64{}
- }
-
- program := hex.EncodeToString(reward.ControlProgram)
- c.CoinbaseReward[program], ok = checked.AddUint64(c.CoinbaseReward[program], reward.Amount)
- if !ok {
- return errMathOperationOverFlow
- }
-
for _, tx := range block.Transactions {
for _, input := range tx.Inputs {
vetoInput, ok := input.TypedInput.(*types.VetoInput)
// DetachBlock calculate the consensus result for detach block
func (c *ConsensusResult) DetachBlock(block *types.Block) error {
- var ok bool
if c.BlockHash != block.Hash() {
return errors.New("block hash is not equals last block hash of vote result")
}
- if block.Height%consensus.RoundVoteBlockNums == 0 {
- if len(c.CoinbaseReward) != 0 {
- return errCoinbaseReward
- }
-
- c.CoinbaseReward = map[string]uint64{}
- for i, output := range block.Transactions[0].Outputs {
- if i == 0 && output.AssetAmount().Amount == 0 {
- continue
- }
- program := output.ControlProgram()
- c.CoinbaseReward[hex.EncodeToString(program)] = output.AssetAmount().Amount
- }
- }
-
- reward, err := CalCoinbaseReward(block)
- if err != nil {
+ if err := c.DetachCoinbaseReward(block); err != nil {
return err
}
- program := hex.EncodeToString(reward.ControlProgram)
- if c.CoinbaseReward[program], ok = checked.SubUint64(c.CoinbaseReward[program], reward.Amount); !ok {
- return errMathOperationOverFlow
- }
-
- if c.CoinbaseReward[program] == 0 {
- delete(c.CoinbaseReward, program)
- }
-
for i := len(block.Transactions) - 1; i >= 0; i-- {
tx := block.Transactions[i]
for _, input := range tx.Inputs {
func (a SortByAmount) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a SortByAmount) Less(i, j int) bool { return a[i].Amount < a[j].Amount }
+// AttachCoinbaseReward attach coinbase reward
+func (c *ConsensusResult) AttachCoinbaseReward(block *types.Block) ([]byte, error) {
+ reward, err := CalCoinbaseReward(block)
+ if err != nil {
+ return nil, err
+ }
+
+ if (block.Height-1)%consensus.RoundVoteBlockNums == 0 {
+ c.CoinbaseReward = map[string]uint64{}
+ }
+
+ var ok bool
+ program := hex.EncodeToString(reward.ControlProgram)
+ c.CoinbaseReward[program], ok = checked.AddUint64(c.CoinbaseReward[program], reward.Amount)
+ if !ok {
+ return nil, errMathOperationOverFlow
+ }
+ return reward.ControlProgram, nil
+}
+
+// DetachCoinbaseReward detach coinbase reward
+func (c *ConsensusResult) DetachCoinbaseReward(block *types.Block) error {
+ if block.Height%consensus.RoundVoteBlockNums == 0 {
+ if len(c.CoinbaseReward) != 0 {
+ return errCoinbaseReward
+ }
+
+ c.CoinbaseReward = map[string]uint64{}
+ for i, output := range block.Transactions[0].Outputs {
+ if i == 0 && output.AssetAmount().Amount == 0 {
+ continue
+ }
+ program := output.ControlProgram()
+ c.CoinbaseReward[hex.EncodeToString(program)] = output.AssetAmount().Amount
+ }
+ }
+
+ reward, err := CalCoinbaseReward(block)
+ if err != nil {
+ return err
+ }
+
+ var ok bool
+ program := hex.EncodeToString(reward.ControlProgram)
+ if c.CoinbaseReward[program], ok = checked.SubUint64(c.CoinbaseReward[program], reward.Amount); !ok {
+ return errMathOperationOverFlow
+ }
+
+ if c.CoinbaseReward[program] == 0 {
+ delete(c.CoinbaseReward, program)
+ }
+ return nil
+}
+
// CalCoinbaseReward calculate the coinbase reward for block
func CalCoinbaseReward(block *types.Block) (*CoinbaseReward, error) {
var coinbaseReceiver []byte
- if len(block.Transactions) > 0 && len(block.Transactions[0].Outputs) > 0 {
- coinbaseReceiver = block.Transactions[0].Outputs[0].ControlProgram()
+ coinbaseTx := block.Transactions[0]
+ if len(block.Transactions) > 0 && len(coinbaseTx.InputIDs) > 0 && len(coinbaseTx.Outputs) > 0 {
+ if _, ok := coinbaseTx.Entries[coinbaseTx.InputIDs[0]].(*bc.Coinbase); ok {
+ coinbaseReceiver = coinbaseTx.Outputs[0].ControlProgram()
+ }
}
if coinbaseReceiver == nil {
- return nil, errors.New("invalid block coinbase transaction with receiver address is empty")
+ return nil, errors.New("not found coinbase receiver")
}
coinbaseAmount := consensus.BlockSubsidy(block.BlockHeader.Height)
for _, tx := range block.Transactions {
- txFee, err := calTxFee(tx)
- if err != nil {
- return nil, err
+ txFee := CalculateTxFee(tx)
+ if txFee < 0 {
+ return nil, errors.Wrap(errMathOperationOverFlow, "calculate transaction fee")
}
coinbaseAmount += txFee
}
}, nil
}
-func calTxFee(tx *types.Tx) (uint64, error) {
+// CalculateTxFee calculate transaction fee
+func CalculateTxFee(tx *types.Tx) uint64 {
var totalInputBTM, totalOutputBTM uint64
for _, input := range tx.Inputs {
if input.InputType() == types.CoinbaseInputType {
- return 0, nil
+ return 0
}
if input.AssetID() == *consensus.BTMAssetID {
totalInputBTM += input.Amount()
totalOutputBTM += output.AssetAmount().Amount
}
}
-
- txFee, ok := checked.SubUint64(totalInputBTM, totalOutputBTM)
- if !ok {
- return 0, errMathOperationOverFlow
- }
- return txFee, nil
+ return totalInputBTM - totalOutputBTM
}
-// AddCoinbaseRewards add block coinbase reward and sort rewards by amount
-func AddCoinbaseRewards(consensusResult *ConsensusResult, reward *CoinbaseReward, blockHeight uint64) ([]CoinbaseReward, error) {
+// GetCoinbaseRewards convert into CoinbaseReward array and sort it by amount
+func (c *ConsensusResult) GetCoinbaseRewards(blockHeight uint64) ([]CoinbaseReward, error) {
rewards := []CoinbaseReward{}
if blockHeight%consensus.RoundVoteBlockNums != 0 {
return []CoinbaseReward{}, nil
}
- aggregateFlag := false
- for p, amount := range consensusResult.CoinbaseReward {
+ for p, amount := range c.CoinbaseReward {
coinbaseAmount := amount
program, err := hex.DecodeString(p)
if err != nil {
return nil, err
}
- if res := bytes.Compare(program, reward.ControlProgram); res == 0 {
- var ok bool
- if coinbaseAmount, ok = checked.AddUint64(coinbaseAmount, reward.Amount); !ok {
- return nil, errMathOperationOverFlow
- }
- aggregateFlag = true
- }
-
rewards = append(rewards, CoinbaseReward{
Amount: coinbaseAmount,
ControlProgram: program,
})
}
-
- if !aggregateFlag {
- rewards = append(rewards, *reward)
- }
sort.Sort(SortByAmount(rewards))
return rewards, nil
}