return err
}
- if len(tx.GasInputIDs) == 0 {
- return ErrNoGasInput
- }
-
// This part is use for prevent tx size is 0
data, err := tx.TxData.MarshalText()
if err != nil {
InputIDs []Hash // 1:1 correspondence with TxData.Inputs
SpentOutputIDs []Hash
- GasInputIDs []Hash
}
// SigHash ...
package types
import (
- "github.com/bytom/bytom/consensus"
"github.com/bytom/bytom/protocol/bc"
"github.com/bytom/bytom/protocol/vm"
"github.com/bytom/bytom/protocol/vm/vmutil"
)
-// MapTx converts a types TxData object into its entries-based
-// representation.
-func MapTx(oldTx *TxData) *bc.Tx {
- txID, txHeader, entries := mapTx(oldTx)
- tx := &bc.Tx{
- TxHeader: txHeader,
- ID: txID,
- Entries: entries,
- InputIDs: make([]bc.Hash, len(oldTx.Inputs)),
- }
-
- spentOutputIDs := make(map[bc.Hash]bool)
- for id, e := range entries {
- var ord uint64
- switch e := e.(type) {
- case *bc.Issuance:
- ord = e.Ordinal
-
- case *bc.Spend:
- ord = e.Ordinal
- spentOutputIDs[*e.SpentOutputId] = true
- if *e.WitnessDestination.Value.AssetId == *consensus.BTMAssetID {
- tx.GasInputIDs = append(tx.GasInputIDs, id)
- }
-
- case *bc.VetoInput:
- ord = e.Ordinal
- spentOutputIDs[*e.SpentOutputId] = true
- if *e.WitnessDestination.Value.AssetId == *consensus.BTMAssetID {
- tx.GasInputIDs = append(tx.GasInputIDs, id)
- }
-
- case *bc.Coinbase:
- ord = 0
- tx.GasInputIDs = append(tx.GasInputIDs, id)
-
- default:
- continue
- }
-
- if ord >= uint64(len(oldTx.Inputs)) {
- continue
- }
- tx.InputIDs[ord] = id
- }
-
- for id := range spentOutputIDs {
- tx.SpentOutputIDs = append(tx.SpentOutputIDs, id)
- }
- return tx
-}
-
type mapHelper struct {
txData *TxData
entryMap map[bc.Hash]bc.Entry
muxSources []*bc.ValueSource
mux *bc.Mux
- resultIDs []*bc.Hash
+ inputIDs []bc.Hash
+ spentOutputIDs []bc.Hash
+ resultIDs []*bc.Hash
}
func newMapHelper(txData *TxData) *mapHelper {
return &mapHelper{
- txData: txData,
- entryMap: make(map[bc.Hash]bc.Entry),
- spends: []*bc.Spend{},
- issuances: []*bc.Issuance{},
- vetos: []*bc.VetoInput{},
- muxSources: make([]*bc.ValueSource, len(txData.Inputs)),
- resultIDs: []*bc.Hash{},
+ txData: txData,
+ entryMap: make(map[bc.Hash]bc.Entry),
+ spends: []*bc.Spend{},
+ issuances: []*bc.Issuance{},
+ vetos: []*bc.VetoInput{},
+ muxSources: make([]*bc.ValueSource, len(txData.Inputs)),
+ inputIDs: make([]bc.Hash, len(txData.Inputs)),
+ spentOutputIDs: []bc.Hash{},
+ resultIDs: []*bc.Hash{},
}
}
return id
}
+func (mh *mapHelper) generateTx() *bc.Tx {
+ header := bc.NewTxHeader(mh.txData.Version, mh.txData.SerializedSize, mh.txData.TimeRange, mh.resultIDs)
+ return &bc.Tx{
+ TxHeader: header,
+ ID: mh.addEntry(header),
+ Entries: mh.entryMap,
+ InputIDs: mh.inputIDs,
+ SpentOutputIDs: mh.spentOutputIDs,
+ }
+
+}
+
func (mh *mapHelper) mapCoinbaseInput(i int, input *CoinbaseInput) {
mh.coinbase = bc.NewCoinbase(input.Arbitrary)
id := mh.addEntry(mh.coinbase)
+ mh.inputIDs[i] = id
mh.muxSources[i] = &bc.ValueSource{
Ref: &id,
Value: &mh.txData.Outputs[0].AssetAmount,
issuance.WitnessArguments = input.Arguments
mh.issuances = append(mh.issuances, issuance)
id := mh.addEntry(issuance)
+ mh.inputIDs[i] = id
mh.muxSources[i] = &bc.ValueSource{
Ref: &id,
Value: &value,
spend.WitnessArguments = input.Arguments
mh.spends = append(mh.spends, spend)
id := mh.addEntry(spend)
+ mh.inputIDs[i] = id
+ mh.spentOutputIDs = append(mh.spentOutputIDs, prevoutID)
mh.muxSources[i] = &bc.ValueSource{
Ref: &id,
Value: &input.AssetAmount,
vetoInput.WitnessArguments = input.Arguments
mh.vetos = append(mh.vetos, vetoInput)
id := mh.addEntry(vetoInput)
+ mh.inputIDs[i] = id
+ mh.spentOutputIDs = append(mh.spentOutputIDs, prevoutID)
mh.muxSources[i] = &bc.ValueSource{
Ref: &id,
Value: &input.AssetAmount,
}
}
-func mapTx(txData *TxData) (headerID bc.Hash, hdr *bc.TxHeader, entryMap map[bc.Hash]bc.Entry) {
+// MapTx converts a types TxData object into its entries-based
+// representation.
+func MapTx(txData *TxData) *bc.Tx {
mh := newMapHelper(txData)
mh.mapInputs()
mh.initMux()
mh.mapOutputs()
-
- h := bc.NewTxHeader(txData.Version, txData.SerializedSize, txData.TimeRange, mh.resultIDs)
- return mh.addEntry(h), h, mh.entryMap
+ return mh.generateTx()
}
func mapBlockHeader(old *BlockHeader) (bc.Hash, *bc.BlockHeader) {
if len(tx.SpentOutputIDs) != 0 {
t.Errorf("coinbase tx doesn't spend any utxo")
}
- if len(tx.GasInputIDs) != 1 {
- t.Errorf("coinbase tx should have 1 gas input")
- }
if len(tx.ResultIds) != 1 {
t.Errorf("expect to only have one output")
}
"sync"
"github.com/bytom/bytom/consensus"
- "github.com/bytom/bytom/consensus/segwit"
"github.com/bytom/bytom/errors"
"github.com/bytom/bytom/math/checked"
"github.com/bytom/bytom/protocol/bc"
"github.com/bytom/bytom/protocol/vm"
)
-const ruleAA = 142500
-
// validate transaction error
var (
ErrTxVersion = errors.New("invalid transaction version")
ErrWrongTransactionSize = errors.New("invalid transaction size")
ErrBadTimeRange = errors.New("invalid transaction time range")
- ErrEmptyInputIDs = errors.New("got the empty InputIDs")
+ ErrInputDoubleSend = errors.New("got the double spend input")
ErrNotStandardTx = errors.New("not standard transaction")
ErrWrongCoinbaseTransaction = errors.New("wrong coinbase transaction")
ErrWrongCoinbaseAsset = errors.New("wrong coinbase assetID")
}
}
- for _, BTMInputID := range vs.tx.GasInputIDs {
- e, ok := vs.tx.Entries[BTMInputID]
- if !ok {
- return errors.Wrapf(bc.ErrMissingEntry, "entry for bytom input %x not found", BTMInputID)
- }
-
- vs2 := *vs
- vs2.entryID = BTMInputID
- if err := checkValid(&vs2, e); err != nil {
- return errors.Wrap(err, "checking gas input")
- }
- }
-
for i, dest := range e.WitnessDestinations {
vs2 := *vs
vs2.destPos = uint64(i)
}
}
- if err := vs.gasStatus.setGasValid(); err != nil {
- return err
- }
-
for i, src := range e.Sources {
vs2 := *vs
vs2.sourcePos = uint64(i)
}
}
+ if err := vs.gasStatus.setGasValid(); err != nil {
+ return err
+ }
+
case *bc.Output:
vs2 := *vs
vs2.sourcePos = 0
return nil
}
-func checkStandardTx(tx *bc.Tx, blockHeight uint64) error {
+func checkDoubleSpend(tx *bc.Tx) error {
+ usedInputMap := make(map[bc.Hash]bool)
for _, id := range tx.InputIDs {
- if blockHeight >= ruleAA && id.IsZero() {
- return ErrEmptyInputIDs
- }
- }
-
- for _, id := range tx.GasInputIDs {
- spend, err := tx.Spend(id)
- if err != nil {
- continue
- }
- spentOutput, err := tx.Output(*spend.SpentOutputId)
- if err != nil {
- return err
+ if _, ok := usedInputMap[id]; ok {
+ return ErrInputDoubleSend
}
- if !segwit.IsP2WScript(spentOutput.ControlProgram.Code) {
- return ErrNotStandardTx
- }
+ usedInputMap[id] = true
}
- for _, id := range tx.ResultIds {
- e, ok := tx.Entries[*id]
- if !ok {
- return errors.Wrapf(bc.ErrMissingEntry, "id %x", id.Bytes())
- }
-
- output, ok := e.(*bc.Output)
- if !ok || *output.Source.Value.AssetId != *consensus.BTMAssetID {
- continue
- }
-
- if !segwit.IsP2WScript(output.ControlProgram.Code) {
- return ErrNotStandardTx
- }
- }
return nil
}
return nil, err
}
- if err := checkStandardTx(tx, block.Height); err != nil {
+ if err := checkDoubleSpend(tx); err != nil {
return nil, err
}
txInputs := make([]*types.TxInput, 0, len(inputs))
txOutputs := make([]*types.TxOutput, 0, len(outputs))
- for _, amount := range inputs {
- txInput := types.NewSpendInput(nil, *sourceID, *consensus.BTMAssetID, amount, 0, ctrlProgram, nil)
+ for i, amount := range inputs {
+ txInput := types.NewSpendInput(nil, *sourceID, *consensus.BTMAssetID, amount, uint64(i), ctrlProgram, nil)
txInputs = append(txInputs, txInput)
}
err: ErrOverGasCredit,
},
{
- desc: "can't find gas spend input in entries",
- f: func() {
- spendID := mux.Sources[len(mux.Sources)-1].Ref
- delete(tx.Entries, *spendID)
- mux.Sources = mux.Sources[:len(mux.Sources)-1]
- },
- err: bc.ErrMissingEntry,
- },
- {
desc: "no gas spend input",
f: func() {
spendID := mux.Sources[len(mux.Sources)-1].Ref
delete(tx.Entries, *spendID)
mux.Sources = mux.Sources[:len(mux.Sources)-1]
- tx.GasInputIDs = nil
vs.gasStatus.GasLeft = 0
},
err: vm.ErrRunLimitExceeded,
spendID := mux.Sources[len(mux.Sources)-1].Ref
delete(tx.Entries, *spendID)
mux.Sources = mux.Sources[:len(mux.Sources)-1]
- tx.GasInputIDs = nil
},
err: nil,
},
}
}
-func TestRuleAA(t *testing.T) {
+func TestDoubleSpend(t *testing.T) {
testData := "07010004016201609bc47dda88eee18c7433340c16e054cabee4318a8d638e873be19e979df81dc7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0e3f9f5c80e0101160014f233267911e94dc74df706fe3b697273e212d5450063024088b5e730136407312980d3b1446004a8c552111721a4ba48044365cf7f7785542f2d7799f73d7cba1be2301fdfb91ad6ea99559b1857a25336eaefd90675870f207642ba797fd89d1f98a8559b4ca74123697dd4dee882955acd0da9010a80d64e0161015fe334d4fe18398f0232d2aca7050388ce4ee5ae82c8148d7f0cea748438b65135ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80ace684200101160014f233267911e94dc74df706fe3b697273e212d545006302404a17a5995b8163ee448719b462a5694b22a35522dd9883333fd462cc3d0aabf049445c5cbb911a40e1906a5bea99b23b1a79e215eeb1a818d8b1dd27e06f3004207642ba797fd89d1f98a8559b4ca74123697dd4dee882955acd0da9010a80d64e016201609bc47dda88eee18c7433340c16e054cabee4318a8d638e873be19e979df81dc7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0e3f9f5c80e0101160014f233267911e94dc74df706fe3b697273e212d5450063024088b5e730136407312980d3b1446004a8c552111721a4ba48044365cf7f7785542f2d7799f73d7cba1be2301fdfb91ad6ea99559b1857a25336eaefd90675870f207642ba797fd89d1f98a8559b4ca74123697dd4dee882955acd0da9010a80d64e0161015fe334d4fe18398f0232d2aca7050388ce4ee5ae82c8148d7f0cea748438b65135ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80ace684200101160014f233267911e94dc74df706fe3b697273e212d545006302409278702c74eb3ae7666f9da4841443a4b001d6c7d7de631faf9f26eb464f6cdd741dcd4c2f3a1eb47cbc345f56a16902380b8f74b7a559f9bec854bd0e955b0c207642ba797fd89d1f98a8559b4ca74123697dd4dee882955acd0da9010a80d64e0201003fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa08ba3fae80e01160014aac0345165045e612b3d7363f39a372bead80ce7000001003fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08fe0fae80e01160014aac0345165045e612b3d7363f39a372bead80ce70000"
/*
07 // serflags
{
block: &bc.Block{
BlockHeader: &bc.BlockHeader{
- Height: ruleAA - 1,
- },
- },
- err: ErrMismatchedPosition,
- },
- {
- block: &bc.Block{
- BlockHeader: &bc.BlockHeader{
- Height: ruleAA,
+ Height: 5000,
},
},
- err: ErrEmptyInputIDs,
+ err: ErrInputDoubleSend,
},
}
}
}
-func TestStandardTx(t *testing.T) {
- fixture := sample(t, nil)
- tx := types.NewTx(*fixture.tx).Tx
-
- cases := []struct {
- desc string
- f func()
- err error
- }{
- {
- desc: "normal standard tx",
- err: nil,
- },
- {
- desc: "not standard tx in spend input",
- f: func() {
- inputID := tx.GasInputIDs[0]
- spend := tx.Entries[inputID].(*bc.Spend)
- spentOutput, err := tx.Output(*spend.SpentOutputId)
- if err != nil {
- t.Fatal(err)
- }
- spentOutput.ControlProgram = &bc.Program{Code: []byte{0}}
- },
- err: ErrNotStandardTx,
- },
- {
- desc: "not standard tx in output",
- f: func() {
- outputID := tx.ResultIds[0]
- output := tx.Entries[*outputID].(*bc.Output)
- output.ControlProgram = &bc.Program{Code: []byte{0}}
- },
- err: ErrNotStandardTx,
- },
- }
-
- for i, c := range cases {
- if c.f != nil {
- c.f()
- }
- if err := checkStandardTx(tx, 0); err != c.err {
- t.Errorf("case #%d (%s) got error %t, want %t", i, c.desc, err, c.err)
- }
- }
-}
-
func TestValidateTxVersion(t *testing.T) {
converter := func(prog []byte) ([]byte, error) { return nil, nil }
cases := []struct {