OSDN Git Service

core upgrade (#310)
authorPaladz <yzhu101@uottawa.ca>
Wed, 24 Jan 2018 06:44:07 +0000 (14:44 +0800)
committerGitHub <noreply@github.com>
Wed, 24 Jan 2018 06:44:07 +0000 (14:44 +0800)
* tmp save

* fix the small bug

* add bit map status struct

* add unit test

* init push for core upgrade

* update the wallet part for core upgrade

* fix bug

* add utxo update

* fix for unit test

* fix mining part bug

* first version of elegant

* fix a mining part bug

* vm verify small change

* fix wallet part get transactions pointer bug

* code review variable rename

26 files changed:
blockchain/query.go
blockchain/query/annotated.go
blockchain/txdb/utxo_view.go
blockchain/txdb/utxo_view_test.go
blockchain/wallet/annotated.go
blockchain/wallet/indexer.go
config/genesis.go
consensus/general.go
consensus/segwit/segwit.go
mining/mining.go
protocol/bc/bc.pb.go
protocol/bc/bc.proto
protocol/bc/blockheader.go
protocol/bc/legacy/block_header.go
protocol/bc/legacy/block_test.go
protocol/bc/legacy/map.go
protocol/bc/merkle_test.go
protocol/bc/tx.go
protocol/bc/tx_status.go [new file with mode: 0644]
protocol/bc/tx_status_test.go [new file with mode: 0644]
protocol/state/utxo_view.go
protocol/state/utxo_view_test.go
protocol/tx.go
protocol/validation/validation.go
protocol/validation/validation_test.go
test/integration/standard_transaction_test.go

index db7b35c..418b5f7 100755 (executable)
@@ -110,7 +110,7 @@ func (bcr *BlockchainReactor) listTransactions(ctx context.Context, filter struc
        AccountID string `json:"account_id"`
        Detail    bool   `json:"detail"`
 }) Response {
-       var transactions []query.AnnotatedTx
+       var transactions []*query.AnnotatedTx
        var err error
 
        if filter.AccountID != "" {
index de04f4f..d6eaf05 100755 (executable)
@@ -20,6 +20,7 @@ type AnnotatedTx struct {
        ReferenceData          *json.RawMessage   `json:"reference_data"`
        Inputs                 []*AnnotatedInput  `json:"inputs"`
        Outputs                []*AnnotatedOutput `json:"outputs"`
+       StatusFail             bool               `json:"status_fail"`
 }
 
 //AnnotatedInput means an annotated transaction input.
index b68bd7a..acf4971 100644 (file)
@@ -25,7 +25,7 @@ func getTransactionsUtxo(db dbm.DB, view *state.UtxoViewpoint, txs []*bc.Tx) err
 
                        data := db.Get(calcUtxoKey(&prevout))
                        if data == nil {
-                               return errors.New("can't find utxo in db")
+                               continue
                        }
 
                        var utxo storage.UtxoEntry
index 5c95fdd..a406495 100644 (file)
@@ -7,9 +7,9 @@ import (
        dbm "github.com/tendermint/tmlibs/db"
 
        "github.com/bytom/blockchain/txdb/storage"
-       "github.com/bytom/testutil"
        "github.com/bytom/protocol/bc"
        "github.com/bytom/protocol/state"
+       "github.com/bytom/testutil"
 )
 
 func TestSaveUtxoView(t *testing.T) {
@@ -95,7 +95,7 @@ func TestGetTransactionsUtxo(t *testing.T) {
                        },
                        inputView: state.NewUtxoViewpoint(),
                        fetchView: state.NewUtxoViewpoint(),
-                       err:       true,
+                       err:       false,
                },
                {
                        txs: []*bc.Tx{
@@ -175,7 +175,7 @@ func TestGetTransactionsUtxo(t *testing.T) {
 
        for i, c := range cases {
                if err := getTransactionsUtxo(testDB, c.inputView, c.txs); c.err != (err != nil) {
-                       t.Errorf("want err = %v, get err = %v", c.err, err)
+                       t.Errorf("test case %d, want err = %v, get err = %v", i, c.err, err)
                }
                if !testutil.DeepEqual(c.inputView, c.fetchView) {
                        t.Errorf("test case %d, want %v, get %v", i, c.fetchView, c.inputView)
index d86a586..967e1ec 100755 (executable)
@@ -181,17 +181,19 @@ func isValidJSON(b []byte) bool {
        return err == nil
 }
 
-func buildAnnotatedTransaction(orig *legacy.Tx, b *legacy.Block, indexInBlock uint32) *query.AnnotatedTx {
+func buildAnnotatedTransaction(orig *legacy.Tx, b *legacy.Block, indexInBlock int) *query.AnnotatedTx {
+       statusFail, _ := b.TransactionStatus.GetStatus(indexInBlock)
        tx := &query.AnnotatedTx{
                ID:                     orig.ID,
                Timestamp:              b.Time(),
                BlockID:                b.Hash(),
                BlockHeight:            b.Height,
-               Position:               indexInBlock,
+               Position:               uint32(indexInBlock),
                BlockTransactionsCount: uint32(len(b.Transactions)),
                ReferenceData:          &emptyJSONObject,
                Inputs:                 make([]*query.AnnotatedInput, 0, len(orig.Inputs)),
                Outputs:                make([]*query.AnnotatedOutput, 0, len(orig.Outputs)),
+               StatusFail:             statusFail,
        }
        if isValidJSON(orig.ReferenceData) {
                referenceData := json.RawMessage(orig.ReferenceData)
index 604ce38..7d5dfaf 100755 (executable)
@@ -11,6 +11,7 @@ import (
        "github.com/bytom/blockchain/account"
        "github.com/bytom/blockchain/asset"
        "github.com/bytom/blockchain/query"
+       "github.com/bytom/consensus"
        "github.com/bytom/crypto/sha3pool"
        chainjson "github.com/bytom/encoding/json"
        "github.com/bytom/errors"
@@ -85,7 +86,7 @@ func (w *Wallet) reverseAccountUTXOs(batch db.Batch, b *legacy.Block) {
        reverseOuts := make([]*rawOutput, 0)
 
        //handle spent UTXOs
-       for _, tx := range b.Transactions {
+       for txIndex, tx := range b.Transactions {
                for _, inpID := range tx.Tx.InputIDs {
                        //spend and retire
                        sp, err := tx.Spend(inpID)
@@ -98,6 +99,11 @@ func (w *Wallet) reverseAccountUTXOs(batch db.Batch, b *legacy.Block) {
                                continue
                        }
 
+                       statusFail, _ := b.TransactionStatus.GetStatus(txIndex)
+                       if statusFail && *resOut.Source.Value.AssetId != *consensus.BTMAssetID {
+                               continue
+                       }
+
                        out := &rawOutput{
                                OutputID:       *sp.SpentOutputId,
                                AssetAmount:    *resOut.Source.Value,
@@ -192,20 +198,24 @@ func (w *Wallet) buildAccountUTXOs(batch db.Batch, b *legacy.Block) {
        var err error
 
        //handle spent UTXOs
-       delOutputIDs := prevoutDBKeys(b.Transactions...)
+       delOutputIDs := prevoutDBKeys(b)
        for _, delOutputID := range delOutputIDs {
                batch.Delete(account.UTXOKey(delOutputID))
        }
 
        //handle new UTXOs
        outs := make([]*rawOutput, 0, len(b.Transactions))
-       for _, tx := range b.Transactions {
+       for txIndex, tx := range b.Transactions {
                for j, out := range tx.Outputs {
                        resOutID := tx.ResultIds[j]
                        resOut, ok := tx.Entries[*resOutID].(*bc.Output)
                        if !ok {
                                continue
                        }
+                       statusFail, _ := b.TransactionStatus.GetStatus(txIndex)
+                       if statusFail && *resOut.Source.Value.AssetId != *consensus.BTMAssetID {
+                               continue
+                       }
                        out := &rawOutput{
                                OutputID:       *tx.OutputID(j),
                                AssetAmount:    out.AssetAmount,
@@ -227,10 +237,14 @@ func (w *Wallet) buildAccountUTXOs(batch db.Batch, b *legacy.Block) {
        }
 }
 
-func prevoutDBKeys(txs ...*legacy.Tx) (outputIDs []bc.Hash) {
-       for _, tx := range txs {
+func prevoutDBKeys(b *legacy.Block) (outputIDs []bc.Hash) {
+       for txIndex, tx := range b.Transactions {
                for _, inpID := range tx.Tx.InputIDs {
                        if sp, err := tx.Spend(inpID); err == nil {
+                               statusFail, _ := b.TransactionStatus.GetStatus(txIndex)
+                               if statusFail && *sp.WitnessDestination.Value.AssetId != *consensus.BTMAssetID {
+                                       continue
+                               }
                                outputIDs = append(outputIDs, *sp.SpentOutputId)
                        }
                }
@@ -323,7 +337,7 @@ func filterAccountTxs(b *legacy.Block, w *Wallet) []*query.AnnotatedTx {
 
                        sha3pool.Sum256(hash[:], v.ControlProgram)
                        if bytes := w.DB.Get(account.CPKey(hash)); bytes != nil {
-                               annotatedTxs = append(annotatedTxs, buildAnnotatedTransaction(tx, b, uint32(pos)))
+                               annotatedTxs = append(annotatedTxs, buildAnnotatedTransaction(tx, b, pos))
                                local = true
                                break
                        }
@@ -339,7 +353,7 @@ func filterAccountTxs(b *legacy.Block, w *Wallet) []*query.AnnotatedTx {
                                continue
                        }
                        if bytes := w.DB.Get(account.UTXOKey(outid)); bytes != nil {
-                               annotatedTxs = append(annotatedTxs, buildAnnotatedTransaction(tx, b, uint32(pos)))
+                               annotatedTxs = append(annotatedTxs, buildAnnotatedTransaction(tx, b, pos))
                                break
                        }
                }
@@ -349,8 +363,8 @@ func filterAccountTxs(b *legacy.Block, w *Wallet) []*query.AnnotatedTx {
 }
 
 //GetTransactionsByTxID get account txs by account tx ID
-func (w *Wallet) GetTransactionsByTxID(txID string) ([]query.AnnotatedTx, error) {
-       annotatedTxs := make([]query.AnnotatedTx, 0)
+func (w *Wallet) GetTransactionsByTxID(txID string) ([]*query.AnnotatedTx, error) {
+       annotatedTxs := []*query.AnnotatedTx{}
        formatKey := ""
 
        if txID != "" {
@@ -364,8 +378,8 @@ func (w *Wallet) GetTransactionsByTxID(txID string) ([]query.AnnotatedTx, error)
        txIter := w.DB.IteratorPrefix([]byte(TxPrefix + formatKey))
        defer txIter.Release()
        for txIter.Next() {
-               annotatedTx := query.AnnotatedTx{}
-               if err := json.Unmarshal(txIter.Value(), &annotatedTx); err != nil {
+               annotatedTx := &query.AnnotatedTx{}
+               if err := json.Unmarshal(txIter.Value(), annotatedTx); err != nil {
                        return nil, err
                }
                annotatedTxs = append(annotatedTxs, annotatedTx)
@@ -375,7 +389,7 @@ func (w *Wallet) GetTransactionsByTxID(txID string) ([]query.AnnotatedTx, error)
 }
 
 //GetTransactionsSummary get transactions summary
-func (w *Wallet) GetTransactionsSummary(transactions []query.AnnotatedTx) []TxSummary {
+func (w *Wallet) GetTransactionsSummary(transactions []*query.AnnotatedTx) []TxSummary {
        Txs := make([]TxSummary, 0)
 
        for _, annotatedTx := range transactions {
@@ -410,7 +424,7 @@ func (w *Wallet) GetTransactionsSummary(transactions []query.AnnotatedTx) []TxSu
        return Txs
 }
 
-func findTransactionsByAccount(annotatedTx query.AnnotatedTx, accountID string) bool {
+func findTransactionsByAccount(annotatedTx *query.AnnotatedTx, accountID string) bool {
        for _, input := range annotatedTx.Inputs {
                if input.AccountID == accountID {
                        return true
@@ -427,13 +441,13 @@ func findTransactionsByAccount(annotatedTx query.AnnotatedTx, accountID string)
 }
 
 //GetTransactionsByAccountID get account txs by account ID
-func (w *Wallet) GetTransactionsByAccountID(accountID string) ([]query.AnnotatedTx, error) {
-       annotatedTxs := make([]query.AnnotatedTx, 0)
+func (w *Wallet) GetTransactionsByAccountID(accountID string) ([]*query.AnnotatedTx, error) {
+       annotatedTxs := []*query.AnnotatedTx{}
 
        txIter := w.DB.IteratorPrefix([]byte(TxPrefix))
        defer txIter.Release()
        for txIter.Next() {
-               annotatedTx := query.AnnotatedTx{}
+               annotatedTx := &query.AnnotatedTx{}
                if err := json.Unmarshal(txIter.Value(), &annotatedTx); err != nil {
                        return nil, err
                }
index d023130..3554d2f 100644 (file)
@@ -51,13 +51,16 @@ func GenerateGenesisBlock() *legacy.Block {
                BlockHeader: legacy.BlockHeader{
                        Version:     1,
                        Height:      0,
-                       Nonce:       1523829,
+                       Nonce:       1656075,
                        Seed:        bc.NewHash(seed),
                        TimestampMS: 1511318565142,
                        BlockCommitment: legacy.BlockCommitment{
                                TransactionsMerkleRoot: merkleRoot,
                        },
                        Bits: 2161727821138738707,
+                       TransactionStatus: bc.TransactionStatus{
+                               Bitmap: []byte{0},
+                       },
                },
                Transactions: []*legacy.Tx{genesisCoinbaseTx},
        }
index 0e98370..b243171 100644 (file)
@@ -25,6 +25,8 @@ const (
 
        PayToWitnessPubKeyHashDataSize = 20
        PayToWitnessScriptHashDataSize = 32
+
+       CoinbaseArbitrarySizeLimit = 128
 )
 
 // BTMAssetID is BTM's asset id, the soul asset of Bytom
index 67f29de..eae3310 100644 (file)
@@ -8,6 +8,21 @@ import (
        "github.com/bytom/protocol/vm/vmutil"
 )
 
+func IsP2WScript(prog []byte) bool {
+       return IsP2WPKHScript(prog) || IsP2WSHScript(prog) || IsStraightforward(prog)
+}
+
+func IsStraightforward(prog []byte) bool {
+       insts, err := vm.ParseProgram(prog)
+       if err != nil {
+               return false
+       }
+       if len(insts) != 1 {
+               return false
+       }
+       return insts[0].Op == vm.OP_TRUE || insts[0].Op == vm.OP_FAIL
+}
+
 func IsP2WPKHScript(prog []byte) bool {
        insts, err := vm.ParseProgram(prog)
        if err != nil {
index 42d1a6e..0194218 100644 (file)
@@ -59,80 +59,80 @@ func createCoinbaseTx(accountManager *account.Manager, amount uint64, blockHeigh
 }
 
 // NewBlockTemplate returns a new block template that is ready to be solved
-func NewBlockTemplate(c *protocol.Chain, txPool *protocol.TxPool, accountManager *account.Manager) (*legacy.Block, error) {
-       // Extend the most recently known best block.
-       var err error
-       preBlock := c.BestBlock()
+func NewBlockTemplate(c *protocol.Chain, txPool *protocol.TxPool, accountManager *account.Manager) (b *legacy.Block, err error) {
        view := state.NewUtxoViewpoint()
+       txEntries := []*bc.Tx{nil}
+       blockWeight := uint64(0)
+       txFee := uint64(0)
 
+       // get preblock info for generate next block
+       preBlock := c.BestBlock()
        preBcBlock := legacy.MapBlock(preBlock)
        nextBlockHeight := preBlock.BlockHeader.Height + 1
        nextBlockSeed := algorithm.CreateSeed(nextBlockHeight, preBcBlock.Seed, []*bc.Hash{&preBcBlock.ID})
-       txDescs := txPool.GetTransactions()
-       txEntries := make([]*bc.Tx, 0, len(txDescs))
-       blockWeight := uint64(0)
-       txFee := uint64(0)
 
        var compareDiffBH *legacy.BlockHeader
        if compareDiffBlock, err := c.GetBlockByHeight(nextBlockHeight - consensus.BlocksPerRetarget); err == nil {
                compareDiffBH = &compareDiffBlock.BlockHeader
        }
 
-       b := &legacy.Block{
+       b = &legacy.Block{
                BlockHeader: legacy.BlockHeader{
                        Version:           1,
                        Height:            nextBlockHeight,
                        PreviousBlockHash: preBlock.Hash(),
                        Seed:              *nextBlockSeed,
                        TimestampMS:       bc.Millis(time.Now()),
+                       TransactionStatus: *bc.NewTransactionStatus(),
                        BlockCommitment:   legacy.BlockCommitment{},
                        Bits:              difficulty.CalcNextRequiredDifficulty(&preBlock.BlockHeader, compareDiffBH),
                },
-               Transactions: make([]*legacy.Tx, 0, len(txDescs)),
-       }
-
-       appendTx := func(tx *legacy.Tx, weight, fee uint64) {
-               b.Transactions = append([]*legacy.Tx{tx}, b.Transactions...)
-               txEntries = append([]*bc.Tx{tx.Tx}, txEntries...)
-               blockWeight += weight
-               txFee += fee
+               Transactions: []*legacy.Tx{nil},
        }
+       bcBlock := &bc.Block{BlockHeader: &bc.BlockHeader{Height: nextBlockHeight}}
 
-       bcBlock := legacy.MapBlock(b)
-       for _, txDesc := range txDescs {
+       for _, txDesc := range txPool.GetTransactions() {
                tx := txDesc.Tx.Tx
+               gasOnlyTx := false
                if blockWeight+txDesc.Weight > consensus.MaxBlockSzie-consensus.MaxTxSize {
                        break
                }
+
                if err := c.GetTransactionsUtxo(view, []*bc.Tx{tx}); err != nil {
                        log.WithField("error", err).Error("mining block generate skip tx due to")
                        txPool.RemoveTransaction(&tx.ID)
                        continue
                }
-               if err := view.ApplyTransaction(bcBlock, tx); err != nil {
-                       log.WithField("error", err).Error("mining block generate skip tx due to")
-                       txPool.RemoveTransaction(&tx.ID)
-                       continue
+
+               if _, gasVaild, err := validation.ValidateTx(tx, preBcBlock); err != nil {
+                       if !gasVaild {
+                               log.WithField("error", err).Error("mining block generate skip tx due to")
+                               txPool.RemoveTransaction(&tx.ID)
+                               continue
+                       }
+                       gasOnlyTx = true
                }
-               if _, err := validation.ValidateTx(tx, preBcBlock); err != nil {
+
+               if err := view.ApplyTransaction(bcBlock, tx, gasOnlyTx); err != nil {
                        log.WithField("error", err).Error("mining block generate skip tx due to")
                        txPool.RemoveTransaction(&tx.ID)
                        continue
                }
 
-               appendTx(txDesc.Tx, txDesc.Weight, txDesc.Fee)
+               b.BlockHeader.TransactionStatus.SetStatus(len(b.Transactions), gasOnlyTx)
+               b.Transactions = append(b.Transactions, txDesc.Tx)
+               txEntries = append(txEntries, tx)
+               blockWeight += txDesc.Weight
+               txFee += txDesc.Fee
        }
 
-       cbTx, err := createCoinbaseTx(accountManager, txFee, nextBlockHeight)
+       // creater coinbase transaction
+       b.Transactions[0], err = createCoinbaseTx(accountManager, txFee, nextBlockHeight)
        if err != nil {
                return nil, errors.Wrap(err, "fail on createCoinbaseTx")
        }
-       appendTx(cbTx, 0, 0)
+       txEntries[0] = b.Transactions[0].Tx
 
        b.BlockHeader.BlockCommitment.TransactionsMerkleRoot, err = bc.MerkleRoot(txEntries)
-       if err != nil {
-               return nil, errors.Wrap(err, "calculating tx merkle root")
-       }
-
-       return b, nil
+       return b, err
 }
index 2e2a92d..376a021 100644 (file)
@@ -17,6 +17,7 @@ It has these top-level messages:
        ValueDestination
        BlockHeader
        TxHeader
+       TransactionStatus
        Mux
        Nonce
        Coinbase
@@ -272,16 +273,17 @@ func (m *ValueDestination) GetPosition() uint64 {
 }
 
 type BlockHeader struct {
-       Version          uint64 `protobuf:"varint,1,opt,name=version" json:"version,omitempty"`
-       SerializedSize   uint64 `protobuf:"varint,2,opt,name=serialized_size,json=serializedSize" json:"serialized_size,omitempty"`
-       Height           uint64 `protobuf:"varint,3,opt,name=height" json:"height,omitempty"`
-       PreviousBlockId  *Hash  `protobuf:"bytes,4,opt,name=previous_block_id,json=previousBlockId" json:"previous_block_id,omitempty"`
-       Seed             *Hash  `protobuf:"bytes,5,opt,name=seed" json:"seed,omitempty"`
-       TimestampMs      uint64 `protobuf:"varint,6,opt,name=timestamp_ms,json=timestampMs" json:"timestamp_ms,omitempty"`
-       TransactionsRoot *Hash  `protobuf:"bytes,7,opt,name=transactions_root,json=transactionsRoot" json:"transactions_root,omitempty"`
-       AssetsRoot       *Hash  `protobuf:"bytes,8,opt,name=assets_root,json=assetsRoot" json:"assets_root,omitempty"`
-       Nonce            uint64 `protobuf:"varint,9,opt,name=nonce" json:"nonce,omitempty"`
-       Bits             uint64 `protobuf:"varint,10,opt,name=bits" json:"bits,omitempty"`
+       Version           uint64             `protobuf:"varint,1,opt,name=version" json:"version,omitempty"`
+       SerializedSize    uint64             `protobuf:"varint,2,opt,name=serialized_size,json=serializedSize" json:"serialized_size,omitempty"`
+       Height            uint64             `protobuf:"varint,3,opt,name=height" json:"height,omitempty"`
+       PreviousBlockId   *Hash              `protobuf:"bytes,4,opt,name=previous_block_id,json=previousBlockId" json:"previous_block_id,omitempty"`
+       Seed              *Hash              `protobuf:"bytes,5,opt,name=seed" json:"seed,omitempty"`
+       TimestampMs       uint64             `protobuf:"varint,6,opt,name=timestamp_ms,json=timestampMs" json:"timestamp_ms,omitempty"`
+       TransactionsRoot  *Hash              `protobuf:"bytes,7,opt,name=transactions_root,json=transactionsRoot" json:"transactions_root,omitempty"`
+       AssetsRoot        *Hash              `protobuf:"bytes,8,opt,name=assets_root,json=assetsRoot" json:"assets_root,omitempty"`
+       TransactionStatus *TransactionStatus `protobuf:"bytes,9,opt,name=transaction_status,json=transactionStatus" json:"transaction_status,omitempty"`
+       Nonce             uint64             `protobuf:"varint,10,opt,name=nonce" json:"nonce,omitempty"`
+       Bits              uint64             `protobuf:"varint,11,opt,name=bits" json:"bits,omitempty"`
 }
 
 func (m *BlockHeader) Reset()                    { *m = BlockHeader{} }
@@ -345,6 +347,13 @@ func (m *BlockHeader) GetAssetsRoot() *Hash {
        return nil
 }
 
+func (m *BlockHeader) GetTransactionStatus() *TransactionStatus {
+       if m != nil {
+               return m.TransactionStatus
+       }
+       return nil
+}
+
 func (m *BlockHeader) GetNonce() uint64 {
        if m != nil {
                return m.Nonce
@@ -407,6 +416,22 @@ func (m *TxHeader) GetExtHash() *Hash {
        return nil
 }
 
+type TransactionStatus struct {
+       Bitmap []byte `protobuf:"bytes,1,opt,name=bitmap,proto3" json:"bitmap,omitempty"`
+}
+
+func (m *TransactionStatus) Reset()                    { *m = TransactionStatus{} }
+func (m *TransactionStatus) String() string            { return proto.CompactTextString(m) }
+func (*TransactionStatus) ProtoMessage()               {}
+func (*TransactionStatus) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{9} }
+
+func (m *TransactionStatus) GetBitmap() []byte {
+       if m != nil {
+               return m.Bitmap
+       }
+       return nil
+}
+
 type Mux struct {
        Sources             []*ValueSource      `protobuf:"bytes,1,rep,name=sources" json:"sources,omitempty"`
        Program             *Program            `protobuf:"bytes,2,opt,name=program" json:"program,omitempty"`
@@ -418,7 +443,7 @@ type Mux struct {
 func (m *Mux) Reset()                    { *m = Mux{} }
 func (m *Mux) String() string            { return proto.CompactTextString(m) }
 func (*Mux) ProtoMessage()               {}
-func (*Mux) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{9} }
+func (*Mux) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{10} }
 
 func (m *Mux) GetSources() []*ValueSource {
        if m != nil {
@@ -465,7 +490,7 @@ type Nonce struct {
 func (m *Nonce) Reset()                    { *m = Nonce{} }
 func (m *Nonce) String() string            { return proto.CompactTextString(m) }
 func (*Nonce) ProtoMessage()               {}
-func (*Nonce) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{10} }
+func (*Nonce) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{11} }
 
 func (m *Nonce) GetProgram() *Program {
        if m != nil {
@@ -503,7 +528,7 @@ type Coinbase struct {
 func (m *Coinbase) Reset()                    { *m = Coinbase{} }
 func (m *Coinbase) String() string            { return proto.CompactTextString(m) }
 func (*Coinbase) ProtoMessage()               {}
-func (*Coinbase) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{11} }
+func (*Coinbase) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{12} }
 
 func (m *Coinbase) GetWitnessDestination() *ValueDestination {
        if m != nil {
@@ -530,7 +555,7 @@ type Output struct {
 func (m *Output) Reset()                    { *m = Output{} }
 func (m *Output) String() string            { return proto.CompactTextString(m) }
 func (*Output) ProtoMessage()               {}
-func (*Output) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{12} }
+func (*Output) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{13} }
 
 func (m *Output) GetSource() *ValueSource {
        if m != nil {
@@ -577,7 +602,7 @@ type Retirement struct {
 func (m *Retirement) Reset()                    { *m = Retirement{} }
 func (m *Retirement) String() string            { return proto.CompactTextString(m) }
 func (*Retirement) ProtoMessage()               {}
-func (*Retirement) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{13} }
+func (*Retirement) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{14} }
 
 func (m *Retirement) GetSource() *ValueSource {
        if m != nil {
@@ -622,7 +647,7 @@ type Issuance struct {
 func (m *Issuance) Reset()                    { *m = Issuance{} }
 func (m *Issuance) String() string            { return proto.CompactTextString(m) }
 func (*Issuance) ProtoMessage()               {}
-func (*Issuance) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{14} }
+func (*Issuance) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{15} }
 
 func (m *Issuance) GetAnchorId() *Hash {
        if m != nil {
@@ -700,7 +725,7 @@ type Spend struct {
 func (m *Spend) Reset()                    { *m = Spend{} }
 func (m *Spend) String() string            { return proto.CompactTextString(m) }
 func (*Spend) ProtoMessage()               {}
-func (*Spend) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{15} }
+func (*Spend) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{16} }
 
 func (m *Spend) GetSpentOutputId() *Hash {
        if m != nil {
@@ -761,6 +786,7 @@ func init() {
        proto.RegisterType((*ValueDestination)(nil), "bc.ValueDestination")
        proto.RegisterType((*BlockHeader)(nil), "bc.BlockHeader")
        proto.RegisterType((*TxHeader)(nil), "bc.TxHeader")
+       proto.RegisterType((*TransactionStatus)(nil), "bc.TransactionStatus")
        proto.RegisterType((*Mux)(nil), "bc.Mux")
        proto.RegisterType((*Nonce)(nil), "bc.Nonce")
        proto.RegisterType((*Coinbase)(nil), "bc.Coinbase")
@@ -773,64 +799,67 @@ func init() {
 func init() { proto.RegisterFile("bc.proto", fileDescriptor0) }
 
 var fileDescriptor0 = []byte{
-       // 941 bytes of a gzipped FileDescriptorProto
-       0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x56, 0x4f, 0x6f, 0xe3, 0x44,
-       0x14, 0x97, 0x63, 0x27, 0x76, 0x5e, 0x4a, 0x93, 0x4e, 0xab, 0x95, 0xb5, 0x2a, 0x52, 0x31, 0x2a,
-       0xdd, 0x15, 0x52, 0xd5, 0x4d, 0x17, 0xc4, 0x81, 0x4b, 0xa1, 0xc0, 0xe6, 0x50, 0x40, 0x2e, 0xda,
-       0xab, 0x35, 0xb1, 0x67, 0x9b, 0x11, 0x89, 0x27, 0xcc, 0x8c, 0x43, 0xe9, 0xc7, 0xe0, 0xca, 0xb7,
-       0xe0, 0x06, 0xe7, 0xfd, 0x36, 0xdc, 0xf8, 0x04, 0xc8, 0xcf, 0x63, 0xe7, 0x9f, 0xb3, 0x9b, 0x68,
-       0x97, 0x9b, 0xdf, 0x1f, 0xbf, 0x3f, 0xbf, 0xf7, 0x7b, 0x33, 0x03, 0xde, 0x30, 0x3e, 0x9f, 0x4a,
-       0xa1, 0x05, 0x69, 0x0c, 0xe3, 0xe0, 0x5b, 0x70, 0x5e, 0x50, 0x35, 0x22, 0xfb, 0xd0, 0x98, 0x5d,
-       0xf8, 0xd6, 0x89, 0xf5, 0xa4, 0x15, 0x36, 0x66, 0x17, 0x28, 0x3f, 0xf3, 0x1b, 0x46, 0x7e, 0x86,
-       0x72, 0xdf, 0xb7, 0x8d, 0xdc, 0x47, 0xf9, 0xd2, 0x77, 0x8c, 0x7c, 0x19, 0x7c, 0x09, 0xee, 0x8f,
-       0x52, 0xdc, 0x49, 0x3a, 0x21, 0x1f, 0x02, 0xcc, 0x26, 0xd1, 0x8c, 0x49, 0xc5, 0x45, 0x8a, 0x21,
-       0x9d, 0xb0, 0x3d, 0x9b, 0xbc, 0x2c, 0x14, 0x84, 0x80, 0x13, 0x8b, 0x84, 0x61, 0xec, 0xbd, 0x10,
-       0xbf, 0x83, 0x01, 0xb8, 0x57, 0x4a, 0x31, 0x3d, 0xb8, 0x7e, 0xe7, 0x42, 0x6e, 0xa0, 0x83, 0xa1,
-       0xae, 0x26, 0x22, 0x4b, 0x35, 0xf9, 0x04, 0x3c, 0x9a, 0x8b, 0x11, 0x4f, 0x30, 0x68, 0xa7, 0xdf,
-       0x39, 0x1f, 0xc6, 0xe7, 0x26, 0x5b, 0xe8, 0xa2, 0x71, 0x90, 0x90, 0x47, 0xd0, 0xa2, 0xf8, 0x07,
-       0xa6, 0x72, 0x42, 0x23, 0x05, 0x7f, 0x58, 0xd0, 0x45, 0xe7, 0x6b, 0xf6, 0x8a, 0xa7, 0x5c, 0xe7,
-       0x1d, 0xf4, 0xa1, 0x87, 0x9f, 0x74, 0x1c, 0x0d, 0xc7, 0x22, 0xfe, 0x79, 0x1e, 0xdb, 0xcb, 0x63,
-       0xe7, 0x78, 0x86, 0xfb, 0xc6, 0xe3, 0xab, 0xdc, 0x61, 0x90, 0x90, 0xcf, 0xa1, 0xc7, 0x95, 0xca,
-       0x68, 0x1a, 0xb3, 0x68, 0x5a, 0x00, 0x85, 0x99, 0x4c, 0x3d, 0x06, 0xbb, 0xb0, 0x5b, 0x3a, 0x95,
-       0x60, 0x1e, 0x83, 0x93, 0x50, 0x4d, 0xb1, 0xe1, 0xc5, 0xf8, 0xa8, 0x0d, 0xc6, 0xd0, 0x79, 0x49,
-       0xc7, 0x19, 0xbb, 0x15, 0x99, 0x8c, 0x19, 0x79, 0x0c, 0xb6, 0x64, 0xaf, 0xd6, 0x6a, 0xc9, 0x95,
-       0xe4, 0x14, 0x9a, 0xb3, 0xdc, 0xd5, 0x64, 0xed, 0x56, 0x28, 0x14, 0x40, 0x85, 0x85, 0x95, 0x3c,
-       0x06, 0x6f, 0x2a, 0x14, 0xf6, 0x89, 0x39, 0x9d, 0xb0, 0x92, 0x83, 0x5f, 0xa0, 0x87, 0xd9, 0xae,
-       0x99, 0xd2, 0x3c, 0xa5, 0x88, 0xc5, 0xff, 0x9c, 0xf2, 0x9f, 0x06, 0x74, 0x10, 0xc2, 0x17, 0x8c,
-       0x26, 0x4c, 0x12, 0x1f, 0xdc, 0x65, 0x62, 0x95, 0x22, 0x39, 0x83, 0xae, 0x62, 0x92, 0xd3, 0x31,
-       0x7f, 0x60, 0x49, 0xa4, 0xf8, 0x03, 0x33, 0x93, 0xdc, 0x9f, 0xab, 0x6f, 0xf9, 0x03, 0xcb, 0x27,
-       0x3d, 0x62, 0xfc, 0x6e, 0xa4, 0x4d, 0x32, 0x23, 0x91, 0xe7, 0x70, 0x30, 0x95, 0x6c, 0xc6, 0x45,
-       0xa6, 0xe6, 0x63, 0x75, 0x56, 0xfa, 0xea, 0x96, 0x2e, 0xe5, 0x5c, 0x8f, 0xc1, 0x51, 0x8c, 0x25,
-       0x7e, 0x73, 0x75, 0x3e, 0xb9, 0x96, 0x7c, 0x04, 0x7b, 0x9a, 0x4f, 0x98, 0xd2, 0x74, 0x32, 0x8d,
-       0x26, 0xca, 0x6f, 0x61, 0xc6, 0x4e, 0xa5, 0xbb, 0x51, 0xe4, 0x33, 0x38, 0xd0, 0x92, 0xa6, 0x8a,
-       0xc6, 0x79, 0xc3, 0x2a, 0x92, 0x42, 0x68, 0xdf, 0x5d, 0x89, 0xd6, 0x5b, 0x74, 0x09, 0x85, 0xd0,
-       0xe4, 0x29, 0x74, 0x90, 0xba, 0xe6, 0x07, 0x6f, 0xe5, 0x07, 0x28, 0x8c, 0xe8, 0x7a, 0x04, 0xcd,
-       0x54, 0xa4, 0x31, 0xf3, 0xdb, 0x98, 0xbd, 0x10, 0xf2, 0x35, 0x1c, 0x72, 0xad, 0x7c, 0x40, 0x25,
-       0x7e, 0x07, 0x7f, 0x59, 0xe0, 0xfd, 0x74, 0xff, 0xfe, 0xa0, 0x3e, 0x03, 0x90, 0x4c, 0x65, 0xe3,
-       0x7c, 0xfb, 0x94, 0x6f, 0x9f, 0xd8, 0x4b, 0x35, 0xb6, 0x0b, 0xdb, 0x20, 0x51, 0x15, 0xcb, 0x9d,
-       0x3a, 0x96, 0x93, 0x8f, 0xc1, 0x63, 0xf7, 0x3a, 0x1a, 0x51, 0x35, 0x5a, 0xc3, 0xd9, 0x65, 0xf7,
-       0x3a, 0xff, 0x08, 0xfe, 0xb5, 0xc0, 0xbe, 0xc9, 0xee, 0xc9, 0x53, 0x70, 0x15, 0x6e, 0x83, 0xf2,
-       0x2d, 0x4c, 0x88, 0xb4, 0x5b, 0xd8, 0x92, 0xb0, 0xb4, 0x93, 0x53, 0x70, 0xdf, 0xb0, 0x8a, 0xa5,
-       0x6d, 0x29, 0xbd, 0xbd, 0x21, 0x3d, 0xf9, 0x0e, 0x8e, 0x7e, 0xe5, 0x3a, 0x65, 0x4a, 0x45, 0xc9,
-       0x7c, 0x3d, 0x94, 0xef, 0x60, 0x0d, 0x47, 0x55, 0x0d, 0x0b, 0xbb, 0x13, 0x1e, 0x9a, 0x3f, 0x16,
-       0x74, 0x8a, 0x7c, 0x0a, 0x07, 0x65, 0x20, 0x2a, 0xef, 0xb2, 0x09, 0x4b, 0xb5, 0xf2, 0x9b, 0x27,
-       0xf6, 0x93, 0xbd, 0xb0, 0x67, 0x0c, 0x57, 0xa5, 0x3e, 0xf8, 0xdb, 0x82, 0xe6, 0xf7, 0x38, 0xce,
-       0x85, 0x5e, 0xac, 0x2d, 0x7b, 0x69, 0x6c, 0xea, 0xa5, 0xb6, 0x04, 0xbb, 0xbe, 0x04, 0xf2, 0x05,
-       0x1c, 0x56, 0xce, 0x69, 0x3c, 0x12, 0x92, 0x25, 0x75, 0x8b, 0x53, 0x46, 0xbc, 0x32, 0x3e, 0x83,
-       0x24, 0x10, 0xe0, 0x7d, 0x2d, 0x78, 0x3a, 0xa4, 0x8a, 0x91, 0x6f, 0xe6, 0x51, 0x16, 0xe0, 0x33,
-       0xad, 0xd4, 0xa3, 0x47, 0xd6, 0xd1, 0x23, 0xc7, 0xd0, 0xa6, 0x72, 0xc8, 0xb5, 0xa4, 0xf2, 0x37,
-       0x73, 0xc1, 0xcc, 0x15, 0xc1, 0x6b, 0x0b, 0x5a, 0x3f, 0x64, 0x7a, 0x9a, 0x69, 0x72, 0x06, 0xad,
-       0x82, 0x05, 0x26, 0xc5, 0x1a, 0x49, 0x8c, 0x99, 0x3c, 0x87, 0x6e, 0x2c, 0x52, 0x2d, 0xc5, 0xf8,
-       0x4d, 0xc7, 0xf6, 0xbe, 0xf1, 0xd9, 0xea, 0xd4, 0x5e, 0x1a, 0x82, 0xb3, 0x69, 0x08, 0x3e, 0xb8,
-       0x42, 0x26, 0x3c, 0xa5, 0x63, 0xe4, 0xbc, 0x13, 0x96, 0x62, 0xf0, 0xbb, 0x05, 0x10, 0x32, 0xcd,
-       0x25, 0xcb, 0x27, 0xb0, 0x7d, 0x2b, 0x65, 0x51, 0x8d, 0xb7, 0x16, 0x65, 0x6f, 0x51, 0x94, 0xb3,
-       0x5c, 0xd4, 0x9f, 0x36, 0x78, 0x03, 0x73, 0x77, 0x91, 0x53, 0x68, 0x17, 0x5c, 0xa8, 0xbb, 0x19,
-       0xbd, 0xc2, 0x34, 0x48, 0xb6, 0xbd, 0x1f, 0xde, 0x03, 0x98, 0x1b, 0xe8, 0xd5, 0xdc, 0x91, 0x5e,
-       0x37, 0xe0, 0x57, 0x5c, 0xc7, 0x47, 0x45, 0x52, 0x3d, 0x0a, 0xf0, 0x68, 0xef, 0xf4, 0x0f, 0xab,
-       0x1e, 0xe6, 0xef, 0x85, 0xf0, 0x51, 0xc9, 0xfd, 0x95, 0x77, 0x44, 0xed, 0x9e, 0xb9, 0xbb, 0xed,
-       0x99, 0xf7, 0xd6, 0x3d, 0x5b, 0x1c, 0x5a, 0x7b, 0x79, 0x68, 0xaf, 0x1b, 0xd0, 0xbc, 0x9d, 0xb2,
-       0x34, 0x21, 0x17, 0xd0, 0x55, 0x53, 0x96, 0xea, 0x48, 0xe0, 0x7e, 0xd4, 0xcd, 0xed, 0x03, 0x74,
-       0x28, 0xf6, 0xa7, 0xb8, 0xf8, 0xde, 0x95, 0x4d, 0x1b, 0xa6, 0xe2, 0xec, 0x38, 0x95, 0x5d, 0x4e,
-       0xcc, 0x4d, 0x30, 0xb6, 0x76, 0x82, 0xd1, 0x5d, 0x82, 0x71, 0xd8, 0xc2, 0xe7, 0xf4, 0xe5, 0x7f,
-       0x01, 0x00, 0x00, 0xff, 0xff, 0x0c, 0xb4, 0xe8, 0xef, 0x5a, 0x0b, 0x00, 0x00,
+       // 983 bytes of a gzipped FileDescriptorProto
+       0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x56, 0xdd, 0x6e, 0xe3, 0x44,
+       0x14, 0x96, 0x63, 0x27, 0x76, 0x4e, 0x4a, 0xd3, 0x4e, 0xcb, 0xca, 0x5a, 0x15, 0xa9, 0x18, 0x95,
+       0xee, 0x6a, 0xa5, 0xaa, 0x9b, 0x2e, 0x88, 0x0b, 0x6e, 0x0a, 0x05, 0x36, 0x17, 0x05, 0xe4, 0xae,
+       0xf6, 0xd6, 0x9a, 0xd8, 0xb3, 0xcd, 0x88, 0xc4, 0x13, 0x66, 0xc6, 0xa1, 0xf4, 0x31, 0xb8, 0xe5,
+       0x29, 0xe0, 0x0e, 0xae, 0xf7, 0x89, 0x78, 0x02, 0xe4, 0xe3, 0xb1, 0xe3, 0xfc, 0xed, 0x26, 0xda,
+       0xdd, 0x3b, 0x9f, 0x9f, 0x39, 0x7f, 0xdf, 0xf9, 0x3c, 0x03, 0xde, 0x20, 0x3e, 0x9b, 0x48, 0xa1,
+       0x05, 0x69, 0x0c, 0xe2, 0xe0, 0x7b, 0x70, 0x9e, 0x53, 0x35, 0x24, 0xbb, 0xd0, 0x98, 0x9e, 0xfb,
+       0xd6, 0xb1, 0xf5, 0xa8, 0x15, 0x36, 0xa6, 0xe7, 0x28, 0x3f, 0xf5, 0x1b, 0x46, 0x7e, 0x8a, 0x72,
+       0xcf, 0xb7, 0x8d, 0xdc, 0x43, 0xf9, 0xc2, 0x77, 0x8c, 0x7c, 0x11, 0x7c, 0x0d, 0xee, 0xcf, 0x52,
+       0xdc, 0x4a, 0x3a, 0x26, 0x9f, 0x00, 0x4c, 0xc7, 0xd1, 0x94, 0x49, 0xc5, 0x45, 0x8a, 0x21, 0x9d,
+       0xb0, 0x3d, 0x1d, 0xbf, 0x2c, 0x14, 0x84, 0x80, 0x13, 0x8b, 0x84, 0x61, 0xec, 0x9d, 0x10, 0xbf,
+       0x83, 0x3e, 0xb8, 0x97, 0x4a, 0x31, 0xdd, 0xbf, 0x7a, 0xe7, 0x42, 0xae, 0xa1, 0x83, 0xa1, 0x2e,
+       0xc7, 0x22, 0x4b, 0x35, 0xf9, 0x1c, 0x3c, 0x9a, 0x8b, 0x11, 0x4f, 0x30, 0x68, 0xa7, 0xd7, 0x39,
+       0x1b, 0xc4, 0x67, 0x26, 0x5b, 0xe8, 0xa2, 0xb1, 0x9f, 0x90, 0x07, 0xd0, 0xa2, 0x78, 0x02, 0x53,
+       0x39, 0xa1, 0x91, 0x82, 0x3f, 0x2d, 0xe8, 0xa2, 0xf3, 0x15, 0x7b, 0xc5, 0x53, 0xae, 0xf3, 0x0e,
+       0x7a, 0xb0, 0x87, 0x9f, 0x74, 0x14, 0x0d, 0x46, 0x22, 0xfe, 0x65, 0x16, 0xdb, 0xcb, 0x63, 0xe7,
+       0xf3, 0x0c, 0x77, 0x8d, 0xc7, 0x37, 0xb9, 0x43, 0x3f, 0x21, 0x5f, 0xc2, 0x1e, 0x57, 0x2a, 0xa3,
+       0x69, 0xcc, 0xa2, 0x49, 0x31, 0x28, 0xcc, 0x64, 0xea, 0x31, 0xb3, 0x0b, 0xbb, 0xa5, 0x53, 0x39,
+       0xcc, 0x23, 0x70, 0x12, 0xaa, 0x29, 0x36, 0x5c, 0x8f, 0x8f, 0xda, 0x60, 0x04, 0x9d, 0x97, 0x74,
+       0x94, 0xb1, 0x1b, 0x91, 0xc9, 0x98, 0x91, 0x87, 0x60, 0x4b, 0xf6, 0x6a, 0xa9, 0x96, 0x5c, 0x49,
+       0x4e, 0xa0, 0x39, 0xcd, 0x5d, 0x4d, 0xd6, 0x6e, 0x35, 0x85, 0x62, 0x50, 0x61, 0x61, 0x25, 0x0f,
+       0xc1, 0x9b, 0x08, 0x85, 0x7d, 0x62, 0x4e, 0x27, 0xac, 0xe4, 0xe0, 0x57, 0xd8, 0xc3, 0x6c, 0x57,
+       0x4c, 0x69, 0x9e, 0x52, 0x9c, 0xc5, 0x07, 0x4e, 0xf9, 0x97, 0x0d, 0x1d, 0x1c, 0xe1, 0x73, 0x46,
+       0x13, 0x26, 0x89, 0x0f, 0xee, 0xfc, 0x62, 0x95, 0x22, 0x39, 0x85, 0xae, 0x62, 0x92, 0xd3, 0x11,
+       0xbf, 0x67, 0x49, 0xa4, 0xf8, 0x3d, 0x33, 0x48, 0xee, 0xce, 0xd4, 0x37, 0xfc, 0x9e, 0xe5, 0x48,
+       0x0f, 0x19, 0xbf, 0x1d, 0x6a, 0x93, 0xcc, 0x48, 0xe4, 0x19, 0xec, 0x4f, 0x24, 0x9b, 0x72, 0x91,
+       0xa9, 0x19, 0xac, 0xce, 0x42, 0x5f, 0xdd, 0xd2, 0xa5, 0xc4, 0xf5, 0x08, 0x1c, 0xc5, 0x58, 0xe2,
+       0x37, 0x17, 0xf1, 0xc9, 0xb5, 0xe4, 0x53, 0xd8, 0xd1, 0x7c, 0xcc, 0x94, 0xa6, 0xe3, 0x49, 0x34,
+       0x56, 0x7e, 0x0b, 0x33, 0x76, 0x2a, 0xdd, 0xb5, 0x22, 0x5f, 0xc0, 0xbe, 0x96, 0x34, 0x55, 0x34,
+       0xce, 0x1b, 0x56, 0x91, 0x14, 0x42, 0xfb, 0xee, 0x42, 0xb4, 0xbd, 0xba, 0x4b, 0x28, 0x84, 0x26,
+       0x8f, 0xa1, 0x83, 0xab, 0x6b, 0x0e, 0x78, 0x0b, 0x07, 0xa0, 0x30, 0xa2, 0xeb, 0x15, 0x90, 0xda,
+       0xf1, 0x48, 0x69, 0xaa, 0x33, 0xe5, 0xb7, 0xf1, 0xc4, 0xc7, 0xf9, 0x89, 0x17, 0x33, 0xeb, 0x0d,
+       0x1a, 0xc3, 0x7a, 0x49, 0x85, 0x8a, 0x1c, 0x42, 0x33, 0x15, 0x69, 0xcc, 0x7c, 0xc0, 0x1e, 0x0a,
+       0x21, 0x27, 0xf3, 0x80, 0x6b, 0xe5, 0x77, 0x50, 0x89, 0xdf, 0xc1, 0x3f, 0x16, 0x78, 0x2f, 0xee,
+       0xde, 0x1f, 0x60, 0xa7, 0x00, 0x92, 0xa9, 0x6c, 0x94, 0x73, 0x58, 0xf9, 0xf6, 0xb1, 0x3d, 0xd7,
+       0x69, 0xbb, 0xb0, 0xf5, 0x13, 0x55, 0x71, 0xc5, 0x59, 0xc5, 0x15, 0xf2, 0x19, 0x78, 0xec, 0x4e,
+       0x47, 0x43, 0xaa, 0x86, 0x4b, 0x68, 0xb9, 0xec, 0x4e, 0xe7, 0x1f, 0xc1, 0x13, 0xd8, 0x5f, 0x9a,
+       0x46, 0xbe, 0x31, 0x03, 0xae, 0xc7, 0x74, 0x82, 0x2d, 0xec, 0x84, 0x46, 0x0a, 0xfe, 0xb3, 0xc0,
+       0xbe, 0xce, 0xee, 0xc8, 0x63, 0x70, 0x15, 0x12, 0x50, 0xf9, 0x16, 0x56, 0x87, 0x9b, 0x5e, 0x23,
+       0x66, 0x58, 0xda, 0xc9, 0x09, 0xb8, 0x6f, 0x60, 0x7f, 0x69, 0x9b, 0xab, 0xd5, 0x5e, 0x53, 0x2b,
+       0xf9, 0x01, 0x0e, 0x7f, 0xe3, 0x3a, 0x65, 0x4a, 0x45, 0xc9, 0x8c, 0x91, 0xca, 0x77, 0xb0, 0x86,
+       0xc3, 0xaa, 0x86, 0x1a, 0x5d, 0xc3, 0x03, 0x73, 0xa2, 0xa6, 0x53, 0xe4, 0x09, 0xec, 0x97, 0x81,
+       0xa8, 0xbc, 0xcd, 0xc6, 0x2c, 0xd5, 0xca, 0x6f, 0x1e, 0xdb, 0x8f, 0x76, 0xc2, 0x3d, 0x63, 0xb8,
+       0x2c, 0xf5, 0xc1, 0xbf, 0x16, 0x34, 0x7f, 0x44, 0xec, 0x6b, 0xbd, 0x58, 0x1b, 0xf6, 0xd2, 0x58,
+       0xd7, 0xcb, 0xca, 0x12, 0xec, 0xd5, 0x25, 0x90, 0xaf, 0xe0, 0xa0, 0x72, 0x4e, 0xe3, 0xa1, 0x90,
+       0x2c, 0x59, 0xc5, 0xd5, 0x32, 0xe2, 0xa5, 0xf1, 0xe9, 0x27, 0x81, 0x00, 0xef, 0x5b, 0xc1, 0xd3,
+       0x01, 0x55, 0x8c, 0x7c, 0x37, 0x8b, 0x52, 0x1b, 0x9f, 0x69, 0x65, 0xf5, 0xf4, 0xc8, 0xf2, 0xf4,
+       0xc8, 0x11, 0xb4, 0xa9, 0x1c, 0x70, 0x2d, 0xa9, 0xfc, 0xdd, 0xdc, 0x69, 0x33, 0x45, 0xf0, 0xda,
+       0x82, 0xd6, 0x4f, 0x99, 0x9e, 0x64, 0x9a, 0x9c, 0x42, 0xab, 0xd8, 0x02, 0x93, 0x62, 0x69, 0x49,
+       0x8c, 0x99, 0x3c, 0x83, 0x6e, 0x2c, 0x52, 0x2d, 0xc5, 0xe8, 0x4d, 0x37, 0xc5, 0xae, 0xf1, 0xd9,
+       0xe8, 0xa2, 0x98, 0x03, 0xc1, 0x59, 0x07, 0x82, 0x0f, 0xae, 0x90, 0x09, 0x4f, 0xe9, 0x08, 0x09,
+       0xe2, 0x84, 0xa5, 0x18, 0xfc, 0x61, 0x01, 0x84, 0x4c, 0x73, 0xc9, 0x72, 0x04, 0x36, 0x6f, 0xa5,
+       0x2c, 0xaa, 0xf1, 0xd6, 0xa2, 0xec, 0x0d, 0x8a, 0x72, 0xe6, 0x8b, 0xfa, 0xdb, 0x06, 0xaf, 0x6f,
+       0xae, 0x4b, 0x72, 0x02, 0xed, 0x62, 0x17, 0x56, 0x5d, 0xc6, 0x5e, 0x61, 0xea, 0x27, 0x9b, 0x5e,
+       0x49, 0xef, 0x61, 0x98, 0x6b, 0xd6, 0xab, 0xb9, 0xe5, 0x7a, 0x5d, 0x83, 0x5f, 0xed, 0x3a, 0xbe,
+       0x63, 0x92, 0xea, 0x1d, 0x82, 0xb7, 0x49, 0xa7, 0x77, 0x50, 0xf5, 0x30, 0x7b, 0xa2, 0x84, 0x0f,
+       0xca, 0xdd, 0x5f, 0x78, 0xba, 0xac, 0xe4, 0x99, 0xbb, 0x1d, 0xcf, 0xbc, 0xb7, 0xf2, 0xac, 0x0e,
+       0x5a, 0x7b, 0x1e, 0xb4, 0xd7, 0x0d, 0x68, 0xde, 0x4c, 0x58, 0x9a, 0x90, 0x73, 0xe8, 0xaa, 0x09,
+       0x4b, 0x75, 0x24, 0x90, 0x1f, 0xab, 0x70, 0xfb, 0x08, 0x1d, 0x0a, 0xfe, 0x14, 0x77, 0xed, 0xbb,
+       0x6e, 0xd3, 0x1a, 0x54, 0x9c, 0x2d, 0x51, 0xd9, 0xe6, 0x8f, 0xb9, 0x6e, 0x8c, 0xad, 0xad, 0xc6,
+       0xe8, 0xce, 0x8d, 0x71, 0xd0, 0xc2, 0x17, 0xfc, 0xc5, 0xff, 0x01, 0x00, 0x00, 0xff, 0xff, 0x14,
+       0x83, 0x0d, 0x5b, 0xcd, 0x0b, 0x00, 0x00,
 }
index d67b6a6..91ab9af 100644 (file)
@@ -50,16 +50,17 @@ message ValueDestination {
 }
 
 message BlockHeader {
-  uint64 version                   = 1;
-  uint64 serialized_size           = 2;
-  uint64 height                    = 3;
-  Hash   previous_block_id         = 4;
-  Hash   seed                      = 5;
-  uint64 timestamp_ms              = 6;
-  Hash   transactions_root         = 7;
-  Hash   assets_root               = 8;
-  uint64 nonce                     = 9;
-  uint64 bits                      = 10;
+  uint64            version            = 1;
+  uint64            serialized_size    = 2;
+  uint64            height             = 3;
+  Hash              previous_block_id  = 4;
+  Hash              seed               = 5;
+  uint64            timestamp_ms       = 6;
+  Hash              transactions_root  = 7;
+  Hash              assets_root        = 8;
+  TransactionStatus transaction_status = 9;
+  uint64            nonce              = 10;
+  uint64            bits               = 11;
 }
 
 message TxHeader {
@@ -70,6 +71,10 @@ message TxHeader {
   Hash          ext_hash        = 5;
 }
 
+message TransactionStatus {
+  bytes bitmap = 1;
+}
+
 message Mux {
   repeated ValueSource      sources              = 1; // issuances, spends, and muxes
   Program                   program              = 2;
index 046c01d..5e98630 100644 (file)
@@ -16,20 +16,22 @@ func (bh *BlockHeader) writeForHash(w io.Writer) {
        mustWriteForHash(w, bh.AssetsRoot)
        mustWriteForHash(w, bh.Nonce)
        mustWriteForHash(w, bh.Bits)
+       mustWriteForHash(w, bh.TransactionStatus)
 }
 
 // NewBlockHeader creates a new BlockHeader and populates
 // its body.
-func NewBlockHeader(version, height uint64, previousBlockID, seed *Hash, timestampMS uint64, transactionsRoot, assetsRoot *Hash, nonce, bits uint64) *BlockHeader {
+func NewBlockHeader(version, height uint64, previousBlockID, seed *Hash, timestampMS uint64, transactionsRoot, assetsRoot *Hash, ts *TransactionStatus, nonce, bits uint64) *BlockHeader {
        return &BlockHeader{
-               Version:          version,
-               Height:           height,
-               PreviousBlockId:  previousBlockID,
-               Seed:             seed,
-               TimestampMs:      timestampMS,
-               TransactionsRoot: transactionsRoot,
-               AssetsRoot:       assetsRoot,
-               Nonce:            nonce,
-               Bits:             bits,
+               Version:           version,
+               Height:            height,
+               PreviousBlockId:   previousBlockID,
+               Seed:              seed,
+               TimestampMs:       timestampMS,
+               TransactionsRoot:  transactionsRoot,
+               AssetsRoot:        assetsRoot,
+               TransactionStatus: ts,
+               Nonce:             nonce,
+               Bits:              bits,
        }
 }
index f69995a..a9fb35d 100644 (file)
@@ -31,6 +31,8 @@ type BlockHeader struct {
        // to the time in the previous block.
        TimestampMS uint64
 
+       TransactionStatus bc.TransactionStatus
+
        BlockCommitment
 
        Nonce uint64
@@ -114,6 +116,9 @@ func (bh *BlockHeader) readFrom(r *blockchain.Reader) (serflag uint8, err error)
        if _, err = blockchain.ReadExtensibleString(r, bh.BlockCommitment.readFrom); err != nil {
                return 0, err
        }
+       if _, err = blockchain.ReadExtensibleString(r, bh.TransactionStatus.ReadFrom); err != nil {
+               return 0, err
+       }
        if bh.Nonce, err = blockchain.ReadVarint63(r); err != nil {
                return 0, err
        }
@@ -152,6 +157,9 @@ func (bh *BlockHeader) writeTo(w io.Writer, serflags uint8) (err error) {
        if _, err = blockchain.WriteExtensibleString(w, nil, bh.BlockCommitment.writeTo); err != nil {
                return err
        }
+       if _, err = blockchain.WriteExtensibleString(w, nil, bh.TransactionStatus.WriteTo); err != nil {
+               return err
+       }
        if _, err = blockchain.WriteVarint63(w, bh.Nonce); err != nil {
                return err
        }
index b971e47..5fca6ed 100644 (file)
@@ -46,6 +46,8 @@ func TestMarshalBlock(t *testing.T) {
                "40" + // commitment extensible field length
                "0000000000000000000000000000000000000000000000000000000000000000" + // tx merkle root
                "0000000000000000000000000000000000000000000000000000000000000000" + // assets merkle root
+               "01" + // tx status
+               "00" +
                "00" + // nonce
                "00" + // bits
 
@@ -104,6 +106,8 @@ func TestEmptyBlock(t *testing.T) {
                "40" + // commitment extensible field length
                "0000000000000000000000000000000000000000000000000000000000000000" + // transactions merkle root
                "0000000000000000000000000000000000000000000000000000000000000000" + // assets merkle root
+               "01" + // tx status
+               "00" +
                "00" + // nonce
                "00" + // bits
                "00") // num transactions
@@ -122,6 +126,8 @@ func TestEmptyBlock(t *testing.T) {
                "40" + // commitment extensible field length
                "0000000000000000000000000000000000000000000000000000000000000000" + // transactions merkle root
                "0000000000000000000000000000000000000000000000000000000000000000" + // assets merkle root
+               "01" + // tx status
+               "00" +
                "00" + // nonce
                "00") // bits
        want, _ = hex.DecodeString(wantHex)
@@ -129,7 +135,7 @@ func TestEmptyBlock(t *testing.T) {
                t.Errorf("empty block header bytes = %x want %x", got, want)
        }
 
-       wantHash := mustDecodeHash("8c85ed997cfdd22185ab28b4e4942c74646176fb293bd18bd19c973c42cec915")
+       wantHash := mustDecodeHash("8ba2dd5c6f88f8e95e7647b9342ae473eff81105927b18832c2c884ab673b582")
        if h := block.Hash(); h != wantHash {
                t.Errorf("got block hash %x, want %x", h.Bytes(), wantHash.Bytes())
        }
@@ -159,6 +165,8 @@ func TestSmallBlock(t *testing.T) {
                "40" + // commitment extensible field length
                "0000000000000000000000000000000000000000000000000000000000000000" + // transactions merkle root
                "0000000000000000000000000000000000000000000000000000000000000000" + // assets merkle root
+               "01" + // tx status
+               "00" +
                "00" + // nonce
                "00" + // bits
                "01" + // num transactions
index 91625ad..d88a6d8 100644 (file)
@@ -1,6 +1,7 @@
 package legacy
 
 import (
+       "github.com/bytom/consensus"
        "github.com/bytom/crypto/sha3pool"
        "github.com/bytom/protocol/bc"
        "github.com/bytom/protocol/vm"
@@ -43,6 +44,9 @@ func MapTx(oldTx *TxData) *bc.Tx {
                        spentOutputIDs[*e.SpentOutputId] = true
                        ord = e.Ordinal
                        // resume below after the switch
+                       if *e.WitnessDestination.Value.AssetId == *consensus.BTMAssetID {
+                               tx.GasInputIDs = append(tx.GasInputIDs, id)
+                       }
 
                case *bc.Coinbase:
                default:
@@ -237,7 +241,7 @@ func mapTx(tx *TxData) (headerID bc.Hash, hdr *bc.TxHeader, entryMap map[bc.Hash
 }
 
 func mapBlockHeader(old *BlockHeader) (bhID bc.Hash, bh *bc.BlockHeader) {
-       bh = bc.NewBlockHeader(old.Version, old.Height, &old.PreviousBlockHash, &old.Seed, old.TimestampMS, &old.TransactionsMerkleRoot, &old.AssetsMerkleRoot, old.Nonce, old.Bits)
+       bh = bc.NewBlockHeader(old.Version, old.Height, &old.PreviousBlockHash, &old.Seed, old.TimestampMS, &old.TransactionsMerkleRoot, &old.AssetsMerkleRoot, &old.TransactionStatus, old.Nonce, old.Bits)
        bhID = bc.EntryID(bh)
        return
 }
index c14ffcd..a5e3458 100644 (file)
@@ -57,6 +57,11 @@ func TestMerkleRoot(t *testing.T) {
                                                AssetVersion: 1,
                                                TypedInput: &legacy.SpendInput{
                                                        Arguments: wit,
+                                                       SpendCommitment: legacy.SpendCommitment{
+                                                               AssetAmount: AssetAmount{
+                                                                       AssetId: &AssetID{V0: 0},
+                                                               },
+                                                       },
                                                },
                                        },
                                },
index dd678dc..ae14390 100644 (file)
@@ -15,6 +15,7 @@ type Tx struct {
        // IDs of reachable entries of various kinds
        NonceIDs       []Hash
        SpentOutputIDs []Hash
+       GasInputIDs    []Hash
 }
 
 func (tx *Tx) SigHash(n uint32) (hash Hash) {
diff --git a/protocol/bc/tx_status.go b/protocol/bc/tx_status.go
new file mode 100644 (file)
index 0000000..d6c3811
--- /dev/null
@@ -0,0 +1,64 @@
+package bc
+
+import (
+       "errors"
+       "io"
+
+       "github.com/bytom/encoding/blockchain"
+)
+
+const (
+       maxBitmapSize = 8388608
+       bitsPerByte   = 8
+)
+
+var errOverRange = errors.New("bitmap range exceed the limit")
+var errBadRange = errors.New("bitmap get a unexisted bit")
+
+func NewTransactionStatus() *TransactionStatus {
+       return &TransactionStatus{
+               Bitmap: []byte{0},
+       }
+}
+
+func (ts *TransactionStatus) SetStatus(i int, gasOnly bool) error {
+       if i >= maxBitmapSize {
+               return errOverRange
+       }
+
+       index, pos := i/bitsPerByte, i%bitsPerByte
+       for len(ts.Bitmap) < index+1 {
+               ts.Bitmap = append(ts.Bitmap, 0)
+       }
+
+       if gasOnly {
+               ts.Bitmap[index] |= 0x01 << uint8(pos)
+       } else {
+               ts.Bitmap[index] &^= 0x01 << uint8(pos)
+       }
+       return nil
+}
+
+func (ts *TransactionStatus) GetStatus(i int) (bool, error) {
+       if i >= maxBitmapSize {
+               return false, errOverRange
+       }
+
+       index, pos := i/bitsPerByte, i%bitsPerByte
+       for len(ts.Bitmap) < index+1 {
+               return false, errBadRange
+       }
+
+       result := (ts.Bitmap[index] >> uint8(pos)) & 0x01
+       return result == 1, nil
+}
+
+func (ts *TransactionStatus) ReadFrom(r *blockchain.Reader) (err error) {
+       ts.Bitmap, err = blockchain.ReadVarstr31(r)
+       return err
+}
+
+func (ts *TransactionStatus) WriteTo(w io.Writer) error {
+       _, err := blockchain.WriteVarstr31(w, ts.Bitmap)
+       return err
+}
diff --git a/protocol/bc/tx_status_test.go b/protocol/bc/tx_status_test.go
new file mode 100644 (file)
index 0000000..7b7ca85
--- /dev/null
@@ -0,0 +1,99 @@
+package bc
+
+import (
+       "testing"
+)
+
+func TestSetBits(t *testing.T) {
+       cases := []struct {
+               op     map[int]bool
+               result []bool
+       }{
+               {
+                       op: map[int]bool{
+                               0: true,
+                       },
+                       result: []bool{true},
+               },
+               {
+                       op: map[int]bool{
+                               0: false,
+                       },
+                       result: []bool{false},
+               },
+               {
+                       op: map[int]bool{
+                               1: true,
+                       },
+                       result: []bool{false, true},
+               },
+               {
+                       op: map[int]bool{
+                               0: true,
+                               1: false,
+                       },
+                       result: []bool{true, false},
+               },
+               {
+                       op: map[int]bool{
+                               7: true,
+                       },
+                       result: []bool{false, false, false, false, false, false, false, true},
+               },
+               {
+                       op: map[int]bool{
+                               7: false,
+                       },
+                       result: []bool{false, false, false, false, false, false, false, false},
+               },
+               {
+                       op: map[int]bool{
+                               8: true,
+                       },
+                       result: []bool{false, false, false, false, false, false, false, false, true},
+               },
+               {
+                       op: map[int]bool{
+                               8: false,
+                       },
+                       result: []bool{false, false, false, false, false, false, false, false, false},
+               },
+               {
+                       op: map[int]bool{
+                               0: true,
+                               1: false,
+                               2: false,
+                               3: true,
+                               4: true,
+                               5: true,
+                               6: false,
+                               7: true,
+                               8: false,
+                               9: true,
+                       },
+                       result: []bool{true, false, false, true, true, true, false, true, false, true},
+               },
+       }
+
+       for ci, c := range cases {
+               ts := NewTransactionStatus()
+               for k, v := range c.op {
+                       if err := ts.SetStatus(k, v); err != nil {
+                               t.Error(err)
+                       }
+               }
+
+               for i, v := range c.result {
+                       result, err := ts.GetStatus(i)
+                       if err != nil {
+                               t.Error(err)
+                       }
+                       if result != v {
+                               t.Errorf("bad result, %d want %s get %s", i, v, result)
+                       }
+               }
+               if len(ts.Bitmap) != (len(c.result)+7)/bitsPerByte {
+                       t.Errorf("wrong bitmap size, %d want %d get %d", ci, len(c.result)/bitsPerByte+1, len(ts.Bitmap))
+               }
+       }
+}
index c9e2015..61b6908 100644 (file)
@@ -25,8 +25,16 @@ func (view *UtxoViewpoint) HasUtxo(hash *bc.Hash) bool {
        return ok
 }
 
-func (view *UtxoViewpoint) ApplyTransaction(block *bc.Block, tx *bc.Tx) error {
+func (view *UtxoViewpoint) ApplyTransaction(block *bc.Block, tx *bc.Tx, statusFail bool) error {
        for _, prevout := range tx.SpentOutputIDs {
+               spentOutput, err := tx.Output(prevout)
+               if err != nil {
+                       return err
+               }
+               if statusFail && *spentOutput.Source.Value.AssetId != *consensus.BTMAssetID {
+                       continue
+               }
+
                entry, ok := view.Entries[prevout]
                if !ok {
                        return errors.New("fail to find utxo entry")
@@ -41,8 +49,11 @@ func (view *UtxoViewpoint) ApplyTransaction(block *bc.Block, tx *bc.Tx) error {
        }
 
        for _, id := range tx.TxHeader.ResultIds {
-               e := tx.Entries[*id]
-               if _, ok := e.(*bc.Output); !ok {
+               output, err := tx.Output(*id)
+               if err != nil {
+                       return err
+               }
+               if statusFail && *output.Source.Value.AssetId != *consensus.BTMAssetID {
                        continue
                }
 
@@ -56,32 +67,45 @@ func (view *UtxoViewpoint) ApplyTransaction(block *bc.Block, tx *bc.Tx) error {
 }
 
 func (view *UtxoViewpoint) ApplyBlock(block *bc.Block) error {
-       for _, tx := range block.Transactions {
-               if err := view.ApplyTransaction(block, tx); err != nil {
+       for i, tx := range block.Transactions {
+               statusFail, err := block.TransactionStatus.GetStatus(i)
+               if err != nil {
+                       return err
+               }
+               if err := view.ApplyTransaction(block, tx, statusFail); err != nil {
                        return err
                }
        }
        return nil
 }
 
-func (view *UtxoViewpoint) DetachTransaction(tx *bc.Tx) error {
+func (view *UtxoViewpoint) DetachTransaction(tx *bc.Tx, statusFail bool) error {
        for _, prevout := range tx.SpentOutputIDs {
+               spentOutput, err := tx.Output(prevout)
+               if err != nil {
+                       return err
+               }
+               if statusFail && *spentOutput.Source.Value.AssetId != *consensus.BTMAssetID {
+                       continue
+               }
+
                entry, ok := view.Entries[prevout]
-               if !ok || !entry.Spent {
+               if ok && !entry.Spent {
                        return errors.New("try to revert an unspent utxo")
                }
-
                if !ok {
                        view.Entries[prevout] = storage.NewUtxoEntry(false, 0, false)
                        continue
                }
-
                entry.UnspendOutput()
        }
 
        for _, id := range tx.TxHeader.ResultIds {
-               e := tx.Entries[*id]
-               if _, ok := e.(*bc.Output); !ok {
+               output, err := tx.Output(*id)
+               if err != nil {
+                       return err
+               }
+               if statusFail && *output.Source.Value.AssetId != *consensus.BTMAssetID {
                        continue
                }
 
@@ -91,8 +115,12 @@ func (view *UtxoViewpoint) DetachTransaction(tx *bc.Tx) error {
 }
 
 func (view *UtxoViewpoint) DetachBlock(block *bc.Block) error {
-       for _, tx := range block.Transactions {
-               if err := view.DetachTransaction(tx); err != nil {
+       for i, tx := range block.Transactions {
+               statusFail, err := block.TransactionStatus.GetStatus(i)
+               if err != nil {
+                       return err
+               }
+               if err := view.DetachTransaction(tx, statusFail); err != nil {
                        return err
                }
        }
index 237a4f8..9c60d04 100644 (file)
@@ -8,6 +8,16 @@ import (
        "github.com/bytom/testutil"
 )
 
+var defaultEntry = map[bc.Hash]bc.Entry{
+       bc.Hash{V0: 0}: &bc.Output{
+               Source: &bc.ValueSource{
+                       Value: &bc.AssetAmount{
+                               AssetId: &bc.AssetID{V0: 0},
+                       },
+               },
+       },
+}
+
 func TestApplyBlock(t *testing.T) {
        cases := []struct {
                block     *bc.Block
@@ -17,11 +27,15 @@ func TestApplyBlock(t *testing.T) {
        }{
                {
                        block: &bc.Block{
+                               BlockHeader: &bc.BlockHeader{
+                                       TransactionStatus: bc.NewTransactionStatus(),
+                               },
                                Transactions: []*bc.Tx{
                                        &bc.Tx{
                                                SpentOutputIDs: []bc.Hash{
                                                        bc.Hash{V0: 0},
                                                },
+                                               Entries: defaultEntry,
                                        },
                                },
                        },
@@ -31,11 +45,15 @@ func TestApplyBlock(t *testing.T) {
                },
                {
                        block: &bc.Block{
+                               BlockHeader: &bc.BlockHeader{
+                                       TransactionStatus: bc.NewTransactionStatus(),
+                               },
                                Transactions: []*bc.Tx{
                                        &bc.Tx{
                                                SpentOutputIDs: []bc.Hash{
                                                        bc.Hash{V0: 0},
                                                },
+                                               Entries: defaultEntry,
                                        },
                                },
                        },
@@ -48,6 +66,9 @@ func TestApplyBlock(t *testing.T) {
                },
                {
                        block: &bc.Block{
+                               BlockHeader: &bc.BlockHeader{
+                                       TransactionStatus: bc.NewTransactionStatus(),
+                               },
                                Transactions: []*bc.Tx{
                                        &bc.Tx{
                                                TxHeader: &bc.TxHeader{
@@ -56,6 +77,7 @@ func TestApplyBlock(t *testing.T) {
                                                SpentOutputIDs: []bc.Hash{
                                                        bc.Hash{V0: 0},
                                                },
+                                               Entries: defaultEntry,
                                        },
                                },
                        },
@@ -74,7 +96,8 @@ func TestApplyBlock(t *testing.T) {
                {
                        block: &bc.Block{
                                BlockHeader: &bc.BlockHeader{
-                                       Height: 7,
+                                       Height:            7,
+                                       TransactionStatus: bc.NewTransactionStatus(),
                                },
                                Transactions: []*bc.Tx{
                                        &bc.Tx{
@@ -84,6 +107,7 @@ func TestApplyBlock(t *testing.T) {
                                                SpentOutputIDs: []bc.Hash{
                                                        bc.Hash{V0: 0},
                                                },
+                                               Entries: defaultEntry,
                                        },
                                },
                        },
@@ -102,7 +126,8 @@ func TestApplyBlock(t *testing.T) {
                {
                        block: &bc.Block{
                                BlockHeader: &bc.BlockHeader{
-                                       Height: 0,
+                                       Height:            0,
+                                       TransactionStatus: bc.NewTransactionStatus(),
                                },
                                Transactions: []*bc.Tx{
                                        &bc.Tx{
@@ -112,6 +137,7 @@ func TestApplyBlock(t *testing.T) {
                                                SpentOutputIDs: []bc.Hash{
                                                        bc.Hash{V0: 0},
                                                },
+                                               Entries: defaultEntry,
                                        },
                                },
                        },
@@ -130,6 +156,7 @@ func TestApplyBlock(t *testing.T) {
        }
 
        for i, c := range cases {
+
                if err := c.inputView.ApplyBlock(c.block); c.err != (err != nil) {
                        t.Errorf("want err = %v, get err = %v", c.err, err)
                }
@@ -151,6 +178,9 @@ func TestDetachBlock(t *testing.T) {
        }{
                {
                        block: &bc.Block{
+                               BlockHeader: &bc.BlockHeader{
+                                       TransactionStatus: bc.NewTransactionStatus(),
+                               },
                                Transactions: []*bc.Tx{
                                        &bc.Tx{
                                                TxHeader: &bc.TxHeader{
@@ -159,14 +189,23 @@ func TestDetachBlock(t *testing.T) {
                                                SpentOutputIDs: []bc.Hash{
                                                        bc.Hash{V0: 0},
                                                },
+                                               Entries: defaultEntry,
                                        },
                                },
                        },
                        inputView: NewUtxoViewpoint(),
-                       err:       true,
+                       fetchView: &UtxoViewpoint{
+                               Entries: map[bc.Hash]*storage.UtxoEntry{
+                                       bc.Hash{V0: 0}: storage.NewUtxoEntry(false, 0, false),
+                               },
+                       },
+                       err: false,
                },
                {
                        block: &bc.Block{
+                               BlockHeader: &bc.BlockHeader{
+                                       TransactionStatus: bc.NewTransactionStatus(),
+                               },
                                Transactions: []*bc.Tx{
                                        &bc.Tx{
                                                TxHeader: &bc.TxHeader{
@@ -175,6 +214,7 @@ func TestDetachBlock(t *testing.T) {
                                                SpentOutputIDs: []bc.Hash{
                                                        bc.Hash{V0: 0},
                                                },
+                                               Entries: defaultEntry,
                                        },
                                },
                        },
@@ -187,6 +227,9 @@ func TestDetachBlock(t *testing.T) {
                },
                {
                        block: &bc.Block{
+                               BlockHeader: &bc.BlockHeader{
+                                       TransactionStatus: bc.NewTransactionStatus(),
+                               },
                                Transactions: []*bc.Tx{
                                        &bc.Tx{
                                                TxHeader: &bc.TxHeader{
@@ -195,6 +238,7 @@ func TestDetachBlock(t *testing.T) {
                                                SpentOutputIDs: []bc.Hash{
                                                        bc.Hash{V0: 0},
                                                },
+                                               Entries: defaultEntry,
                                        },
                                },
                        },
@@ -214,7 +258,7 @@ func TestDetachBlock(t *testing.T) {
 
        for i, c := range cases {
                if err := c.inputView.DetachBlock(c.block); c.err != (err != nil) {
-                       t.Errorf("want err = %v, get err = %v", c.err, err)
+                       t.Errorf("case %d want err = %v, get err = %v", i, c.err, err)
                }
                if c.err {
                        continue
index 2756134..3001239 100644 (file)
@@ -23,9 +23,9 @@ func (c *Chain) ValidateTx(tx *legacy.Tx) error {
                return err
        }
        block := legacy.MapBlock(oldBlock)
-       fee, err := validation.ValidateTx(newTx, block)
+       fee, gasVaild, err := validation.ValidateTx(newTx, block)
 
-       if err != nil {
+       if !gasVaild && err != nil {
                c.txPool.AddErrCache(&newTx.ID, err)
                return err
        }
index a2c6543..803f8c5 100644 (file)
@@ -6,6 +6,7 @@ import (
        "github.com/bytom/consensus"
        "github.com/bytom/consensus/algorithm"
        "github.com/bytom/consensus/difficulty"
+       "github.com/bytom/consensus/segwit"
        "github.com/bytom/errors"
        "github.com/bytom/math/checked"
        "github.com/bytom/protocol/bc"
@@ -79,6 +80,8 @@ type validationState struct {
        cache map[bc.Hash]error
 
        gas *gasState
+
+       gasVaild *int
 }
 
 var (
@@ -106,8 +109,10 @@ var (
        errVersionRegression        = errors.New("version regression")
        errWrongBlockSize           = errors.New("block size is too big")
        errWrongTransactionSize     = errors.New("transaction size is too big")
+       errWrongTransactionStatus   = errors.New("transaction status is wrong")
        errWrongCoinbaseTransaction = errors.New("wrong coinbase transaction")
        errWrongCoinbaseAsset       = errors.New("wrong coinbase asset id")
+       errNotStandardTx            = errors.New("gas transaction is not standard transaction")
 )
 
 func checkValid(vs *validationState, e bc.Entry) (err error) {
@@ -205,14 +210,19 @@ func checkValid(vs *validationState, e bc.Entry) (err error) {
                        return err
                }
 
-               for i, src := range e.Sources {
+               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.sourcePos = uint64(i)
-                       err = checkValidSrc(&vs2, src)
-                       if err != nil {
-                               return errors.Wrapf(err, "checking mux source %d", i)
+                       vs2.entryID = BTMInputID
+                       if err := checkValid(&vs2, e); err != nil {
+                               return errors.Wrap(err, "checking value source")
                        }
                }
+
                for i, dest := range e.WitnessDestinations {
                        vs2 := *vs
                        vs2.destPos = uint64(i)
@@ -225,6 +235,16 @@ func checkValid(vs *validationState, e bc.Entry) (err error) {
                if vs.tx.Version == 1 && e.ExtHash != nil && !e.ExtHash.IsZero() {
                        return errNonemptyExtHash
                }
+               *vs.gasVaild = 1
+
+               for i, src := range e.Sources {
+                       vs2 := *vs
+                       vs2.sourcePos = uint64(i)
+                       err = checkValidSrc(&vs2, src)
+                       if err != nil {
+                               return errors.Wrapf(err, "checking mux source %d", i)
+                       }
+               }
 
        case *bc.Nonce:
                //TODO: add block heigh range check on the control program
@@ -532,9 +552,16 @@ func ValidateBlock(b, prev *bc.Block, seedCaches *seed.SeedCaches) error {
                        return errors.WithDetailf(errTxVersion, "block version %d, transaction version %d", b.Version, tx.Version)
                }
 
-               txBTMValue, err := ValidateTx(tx, b)
+               txBTMValue, gasVaild, err := ValidateTx(tx, b)
+               gasOnlyTx := false
                if err != nil {
-                       return errors.Wrapf(err, "validity of transaction %d of %d", i, len(b.Transactions))
+                       if !gasVaild {
+                               return errors.Wrapf(err, "validity of transaction %d of %d", i, len(b.Transactions))
+                       }
+                       gasOnlyTx = true
+               }
+               if status, err := b.TransactionStatus.GetStatus(i); err != nil || status != gasOnlyTx {
+                       return errWrongTransactionStatus
                }
                coinbaseValue += txBTMValue
        }
@@ -567,7 +594,14 @@ func validateCoinbase(tx *bc.Tx, value uint64) error {
                return errors.Wrap(errWrongCoinbaseTransaction, "dismatch output value")
        }
 
-       //TODO: require coinbase control program verify
+       inputEntry := tx.Entries[tx.InputIDs[0]]
+       input, ok := inputEntry.(*bc.Coinbase)
+       if !ok {
+               return errors.Wrap(errWrongCoinbaseTransaction, "decode input")
+       }
+       if input.Arbitrary != nil && len(input.Arbitrary) > consensus.CoinbaseArbitrarySizeLimit {
+               return errors.Wrap(errWrongCoinbaseTransaction, "coinbase arbitrary is over size")
+       }
        return nil
 }
 
@@ -591,13 +625,60 @@ func validateBlockAgainstPrev(b, prev *bc.Block) error {
        return nil
 }
 
+func validateStandardTx(tx *bc.Tx) error {
+       for _, id := range tx.InputIDs {
+               e, ok := tx.Entries[id]
+               if !ok {
+                       return errors.New("miss tx input entry")
+               }
+               if spend, ok := e.(*bc.Spend); ok {
+                       if *spend.WitnessDestination.Value.AssetId != *consensus.BTMAssetID {
+                               continue
+                       }
+                       spentOutput, err := tx.Output(*spend.SpentOutputId)
+                       if err != nil {
+                               return errors.Wrap(err, "getting spend prevout")
+                       }
+
+                       if !segwit.IsP2WScript(spentOutput.ControlProgram.Code) {
+                               return errNotStandardTx
+                       }
+               }
+       }
+
+       for _, id := range tx.ResultIds {
+               e, ok := tx.Entries[*id]
+               if !ok {
+                       return errors.New("miss tx output entry")
+               }
+               if output, ok := e.(*bc.Output); ok {
+                       if *output.Source.Value.AssetId != *consensus.BTMAssetID {
+                               continue
+                       }
+                       if !segwit.IsP2WScript(output.ControlProgram.Code) {
+                               return errNotStandardTx
+                       }
+               }
+       }
+       return nil
+}
+
 // ValidateTx validates a transaction.
-func ValidateTx(tx *bc.Tx, block *bc.Block) (uint64, error) {
+func ValidateTx(tx *bc.Tx, block *bc.Block) (uint64, bool, error) {
        if tx.TxHeader.SerializedSize > consensus.MaxTxSize {
-               return 0, errWrongTransactionSize
+               return 0, false, errWrongTransactionSize
+       }
+
+       if len(tx.ResultIds) == 0 {
+               return 0, false, errors.New("tx didn't have any output")
+       }
+
+       if err := validateStandardTx(tx); err != nil {
+               return 0, false, err
        }
 
        //TODO: handle the gas limit
+       gasVaild := 0
        vs := &validationState{
                block:   block,
                tx:      tx,
@@ -605,9 +686,10 @@ func ValidateTx(tx *bc.Tx, block *bc.Block) (uint64, error) {
                gas: &gasState{
                        gasLeft: defaultGasLimit,
                },
-               cache: make(map[bc.Hash]error),
+               gasVaild: &gasVaild,
+               cache:    make(map[bc.Hash]error),
        }
 
        err := checkValid(vs, tx.TxHeader)
-       return uint64(vs.gas.BTMValue), err
+       return uint64(vs.gas.BTMValue), *vs.gasVaild == 1, err
 }
index c00d198..cb8cb0d 100644 (file)
@@ -5,6 +5,9 @@ import (
        "math"
        "testing"
 
+       "github.com/davecgh/go-spew/spew"
+       "github.com/golang/protobuf/proto"
+
        "github.com/bytom/consensus"
        "github.com/bytom/crypto/sha3pool"
        "github.com/bytom/errors"
@@ -12,12 +15,12 @@ import (
        "github.com/bytom/protocol/bc/legacy"
        "github.com/bytom/protocol/seed"
        "github.com/bytom/protocol/vm"
+       "github.com/bytom/protocol/vm/vmutil"
        "github.com/bytom/testutil"
-
-       "github.com/davecgh/go-spew/spew"
-       "github.com/golang/protobuf/proto"
 )
 
+const dirPath = "pseudohsm/testdata/pseudo"
+
 func init() {
        spew.Config.DisableMethods = true
 }
@@ -376,6 +379,7 @@ func TestTxValidation(t *testing.T) {
        }
 
        for _, c := range cases {
+               gasVaild := 0
                t.Run(c.desc, func(t *testing.T) {
                        fixture = sample(t, nil)
                        tx = legacy.NewTx(*fixture.tx).Tx
@@ -387,7 +391,8 @@ func TestTxValidation(t *testing.T) {
                                        gasLeft: int64(80000),
                                        gasUsed: 0,
                                },
-                               cache: make(map[bc.Hash]error),
+                               cache:    make(map[bc.Hash]error),
+                               gasVaild: &gasVaild,
                        }
                        out := tx.Entries[*tx.ResultIds[0]].(*bc.Output)
                        muxID := out.Source.Ref
@@ -447,6 +452,7 @@ func TestValidateBlock(t *testing.T) {
                        t.Errorf("computing transaction merkle root", err)
                        continue
                }
+               c.block.BlockHeader.TransactionStatus = bc.NewTransactionStatus()
                c.block.TransactionsRoot = &txRoot
 
                if err = ValidateBlock(c.block, nil, seedCaches); rootErr(err) != c.err {
@@ -471,9 +477,10 @@ func TestCoinbase(t *testing.T) {
                },
        })
        cases := []struct {
-               block *bc.Block
-               tx    *bc.Tx
-               err   error
+               block    *bc.Block
+               tx       *bc.Tx
+               gasVaild bool
+               err      error
        }{
                {
                        block: &bc.Block{
@@ -482,8 +489,9 @@ func TestCoinbase(t *testing.T) {
                                },
                                Transactions: []*bc.Tx{errCbTx},
                        },
-                       tx:  CbTx,
-                       err: errWrongCoinbaseTransaction,
+                       tx:       CbTx,
+                       gasVaild: true,
+                       err:      errWrongCoinbaseTransaction,
                },
                {
                        block: &bc.Block{
@@ -492,8 +500,9 @@ func TestCoinbase(t *testing.T) {
                                },
                                Transactions: []*bc.Tx{CbTx},
                        },
-                       tx:  CbTx,
-                       err: nil,
+                       tx:       CbTx,
+                       gasVaild: true,
+                       err:      nil,
                },
                {
                        block: &bc.Block{
@@ -502,22 +511,26 @@ func TestCoinbase(t *testing.T) {
                                },
                                Transactions: []*bc.Tx{errCbTx},
                        },
-                       tx:  errCbTx,
-                       err: errWrongCoinbaseAsset,
+                       tx:       errCbTx,
+                       gasVaild: true,
+                       err:      errWrongCoinbaseAsset,
                },
        }
 
        for _, c := range cases {
-               _, err := ValidateTx(c.tx, c.block)
+               _, gasVaild, err := ValidateTx(c.tx, c.block)
 
                if rootErr(err) != c.err {
                        t.Errorf("got error %s, want %s", err, c.err)
                }
+               if c.gasVaild != gasVaild {
+                       t.Errorf("got gasVaild %s, want %s", gasVaild, c.gasVaild)
+               }
        }
 }
 
 func TestBlockHeaderValid(t *testing.T) {
-       base := bc.NewBlockHeader(1, 1, &bc.Hash{}, &bc.Hash{}, 1, &bc.Hash{}, &bc.Hash{}, 0, 0)
+       base := bc.NewBlockHeader(1, 1, &bc.Hash{}, &bc.Hash{}, 1, &bc.Hash{}, &bc.Hash{}, nil, 0, 0)
        baseBytes, _ := proto.Marshal(base)
 
        var bh bc.BlockHeader
@@ -670,18 +683,20 @@ func mockBlock() *bc.Block {
 }
 
 func mockCoinbaseTx(amount uint64) *bc.Tx {
+       cp, _ := vmutil.DefaultCoinbaseProgram()
        return legacy.MapTx(&legacy.TxData{
                Inputs: []*legacy.TxInput{
                        legacy.NewCoinbaseInput(nil, nil),
                },
                Outputs: []*legacy.TxOutput{
-                       legacy.NewTxOutput(*consensus.BTMAssetID, amount, []byte{1}, nil),
+                       legacy.NewTxOutput(*consensus.BTMAssetID, amount, cp, nil),
                },
        })
 }
 
 func mockGasTxInput() *legacy.TxInput {
-       return legacy.NewSpendInput([][]byte{}, *newHash(8), *consensus.BTMAssetID, 100000000, 0, []byte{byte(vm.OP_TRUE)}, *newHash(9), []byte{})
+       cp, _ := vmutil.DefaultCoinbaseProgram()
+       return legacy.NewSpendInput([][]byte{}, *newHash(8), *consensus.BTMAssetID, 100000000, 0, cp, *newHash(9), []byte{})
 }
 
 // Like errors.Root, but also unwraps vm.Error objects.
index cf81660..063d02a 100644 (file)
@@ -10,8 +10,8 @@ import (
        "github.com/bytom/blockchain/account"
        "github.com/bytom/blockchain/pseudohsm"
        "github.com/bytom/crypto/ed25519/chainkd"
-       "github.com/bytom/protocol/validation"
        "github.com/bytom/protocol/bc/legacy"
+       "github.com/bytom/protocol/validation"
        "github.com/bytom/test"
 )
 
@@ -61,7 +61,7 @@ func TestP2PKH(t *testing.T) {
                t.Fatal(err)
        }
 
-       if _, err = validation.ValidateTx(legacy.MapTx(tx), test.MockBlock()); err != nil {
+       if _, _, err = validation.ValidateTx(legacy.MapTx(tx), test.MockBlock()); err != nil {
                t.Fatal(err)
        }
 }
@@ -117,7 +117,7 @@ func TestP2SH(t *testing.T) {
                t.Fatal(err)
        }
 
-       if _, err = validation.ValidateTx(legacy.MapTx(tx), test.MockBlock()); err != nil {
+       if _, _, err = validation.ValidateTx(legacy.MapTx(tx), test.MockBlock()); err != nil {
                t.Fatal(err)
        }
 }