OSDN Git Service

init push for pay-to-script-hash (#235)
authorPaladz <yzhu101@uottawa.ca>
Wed, 3 Jan 2018 09:40:25 +0000 (17:40 +0800)
committerGitHub <noreply@github.com>
Wed, 3 Jan 2018 09:40:25 +0000 (17:40 +0800)
* init push for pay-to-script-hash

* edit for code review

* edit the test code struct

* edit for golint

* add the cli part

13 files changed:
blockchain/account/accounts.go
blockchain/account/builder.go
blockchain/account/receivers.go
blockchain/receivers.go
blockchain/txbuilder/actions.go
cmd/bytomcli/commands/bytomcli.go
cmd/bytomcli/commands/transaction.go
common/address.go
config/genesis.go
config/genesis_test.go
integration_test/standard_transaction_test.go
protocol/vm/control.go
protocol/vm/vmutil/script.go

index 6656042..5cfecac 100755 (executable)
@@ -226,6 +226,7 @@ func (m *Manager) findByID(ctx context.Context, id string) (*signers.Signer, err
        return account.Signer, nil
 }
 
+// GetAliasByID return the account alias by given ID
 func (m *Manager) GetAliasByID(id string) string {
        var account Account
 
@@ -243,9 +244,18 @@ func (m *Manager) GetAliasByID(id string) string {
        return account.Alias
 }
 
-// CreateP2PKH generate an address for the select account
-func (m *Manager) CreateP2PKH(ctx context.Context, accountID string, change bool, expiresAt time.Time) (*CtrlProgram, error) {
-       cp, err := m.createP2PKH(ctx, accountID, change, expiresAt)
+// CreateAddress generate an address for the select account
+func (m *Manager) CreateAddress(ctx context.Context, accountID string, change bool, expiresAt time.Time) (cp *CtrlProgram, err error) {
+       account, err := m.findByID(ctx, accountID)
+       if err != nil {
+               return nil, err
+       }
+
+       if account.Quorum == 1 {
+               cp, err = m.createP2PKH(ctx, account, change, expiresAt)
+       } else {
+               cp, err = m.createP2SH(ctx, account, change, expiresAt)
+       }
        if err != nil {
                return nil, err
        }
@@ -256,31 +266,59 @@ func (m *Manager) CreateP2PKH(ctx context.Context, accountID string, change bool
        return cp, nil
 }
 
-func (m *Manager) createP2PKH(ctx context.Context, accountID string, change bool, expiresAt time.Time) (*CtrlProgram, error) {
-       account, err := m.findByID(ctx, accountID)
+func (m *Manager) createP2PKH(ctx context.Context, account *signers.Signer, change bool, expiresAt time.Time) (*CtrlProgram, error) {
+       idx, err := m.nextIndex(ctx)
        if err != nil {
                return nil, err
        }
-       if account.Quorum != 1 {
-               return nil, ErrStandardQuorum
+       path := signers.Path(account, signers.AccountKeySpace, idx)
+       derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path)
+       derivedPK := derivedXPubs[0].PublicKey()
+       pubHash := crypto.Ripemd160(derivedPK)
+
+       // TODO: pass different params due to config
+       address, err := common.NewAddressWitnessPubKeyHash(pubHash, &consensus.MainNetParams)
+       if err != nil {
+               return nil, err
        }
 
+       control, err := vmutil.P2PKHSigProgram([]byte(pubHash))
+       if err != nil {
+               return nil, err
+       }
+
+       return &CtrlProgram{
+               AccountID:      account.ID,
+               Address:        address.EncodeAddress(),
+               KeyIndex:       idx,
+               ControlProgram: control,
+               Change:         change,
+               ExpiresAt:      expiresAt,
+       }, nil
+}
+
+func (m *Manager) createP2SH(ctx context.Context, account *signers.Signer, change bool, expiresAt time.Time) (*CtrlProgram, error) {
        idx, err := m.nextIndex(ctx)
        if err != nil {
                return nil, err
        }
+
        path := signers.Path(account, signers.AccountKeySpace, idx)
        derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path)
-       derivedPK := derivedXPubs[0].PublicKey()
-       pubHash := crypto.Ripemd160(derivedPK)
+       derivedPKs := chainkd.XPubKeys(derivedXPubs)
+       signScript, err := vmutil.P2SPMultiSigProgram(derivedPKs, account.Quorum)
+       if err != nil {
+               return nil, err
+       }
+       scriptHash := crypto.Sha256(signScript)
 
        // TODO: pass different params due to config
-       address, err := common.NewAddressWitnessPubKeyHash(pubHash, &consensus.MainNetParams)
+       address, err := common.NewAddressWitnessScriptHash(scriptHash, &consensus.MainNetParams)
        if err != nil {
                return nil, err
        }
 
-       control, err := vmutil.P2PKHSigProgram([]byte(pubHash))
+       control, err := vmutil.P2SHProgram(scriptHash)
        if err != nil {
                return nil, err
        }
index d16e0bf..019f436 100755 (executable)
@@ -8,11 +8,14 @@ import (
 
        "github.com/bytom/blockchain/signers"
        "github.com/bytom/blockchain/txbuilder"
+       "github.com/bytom/common"
+       "github.com/bytom/consensus"
        "github.com/bytom/crypto/ed25519/chainkd"
        chainjson "github.com/bytom/encoding/json"
        "github.com/bytom/errors"
        "github.com/bytom/protocol/bc"
        "github.com/bytom/protocol/bc/legacy"
+       "github.com/bytom/protocol/vm/vmutil"
 )
 
 //DecodeSpendAction unmarshal JSON-encoded data of spend action
@@ -143,19 +146,40 @@ func canceler(ctx context.Context, m *Manager, rid uint64) func() {
 // UtxoToInputs convert an utxo to the txinput
 func UtxoToInputs(account *signers.Signer, u *UTXO, refData []byte) (*legacy.TxInput, *txbuilder.SigningInstruction, error) {
        txInput := legacy.NewSpendInput(nil, u.SourceID, u.AssetID, u.Amount, u.SourcePos, u.ControlProgram, u.RefDataHash, refData)
-
        path := signers.Path(account, signers.AccountKeySpace, u.ControlProgramIndex)
        sigInst := &txbuilder.SigningInstruction{}
+       if u.Address == "" {
+               sigInst.AddWitnessKeys(account.XPubs, path, account.Quorum)
+               return txInput, sigInst, nil
+       }
 
-       //TODO: handle pay to script hash address
-       if u.Address != "" {
+       address, err := common.DecodeAddress(u.Address, &consensus.MainNetParams)
+       if err != nil {
+               return nil, nil, err
+       }
+
+       switch address.(type) {
+       case *common.AddressWitnessPubKeyHash:
                sigInst.AddRawWitnessKeys(account.XPubs, path, account.Quorum)
                derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path)
                derivedPK := derivedXPubs[0].PublicKey()
                sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness([]byte(derivedPK)))
-       } else {
+
+       case *common.AddressWitnessScriptHash:
                sigInst.AddWitnessKeys(account.XPubs, path, account.Quorum)
+               path := signers.Path(account, signers.AccountKeySpace, u.ControlProgramIndex)
+               derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path)
+               derivedPKs := chainkd.XPubKeys(derivedXPubs)
+               script, err := vmutil.P2SPMultiSigProgram(derivedPKs, account.Quorum)
+               if err != nil {
+                       return nil, nil, err
+               }
+               sigInst.WitnessComponents = append(sigInst.WitnessComponents, txbuilder.DataWitness(script))
+
+       default:
+               return nil, nil, errors.New("unsupport address type")
        }
+
        return txInput, sigInst, nil
 }
 
index 6bf58b7..44312fc 100755 (executable)
@@ -38,19 +38,18 @@ func (m *Manager) CreateReceiver(ctx context.Context, accountInfo string, expire
        return receiver, nil
 }
 
-// CreateAddress creates a new address receiver for an account
-func (m *Manager) CreateAddress(ctx context.Context, accountInfo string, expiresAt time.Time) (*txbuilder.Receiver, error) {
+// CreateAddressReceiver creates a new address receiver for an account
+func (m *Manager) CreateAddressReceiver(ctx context.Context, accountInfo string, expiresAt time.Time) (*txbuilder.Receiver, error) {
        if expiresAt.IsZero() {
                expiresAt = time.Now().Add(defaultReceiverExpiry)
        }
 
        accountID := accountInfo
-
        if s, err := m.FindByAlias(ctx, accountInfo); err == nil {
                accountID = s.ID
        }
 
-       program, err := m.CreateP2PKH(ctx, accountID, false, expiresAt)
+       program, err := m.CreateAddress(ctx, accountID, false, expiresAt)
        if err != nil {
                return nil, err
        }
index 9ce0fa7..68c72c2 100755 (executable)
@@ -22,7 +22,7 @@ func (bcr *BlockchainReactor) createAccountAddress(ctx context.Context, ins stru
        AccountInfo string    `json:"account_info"`
        ExpiresAt   time.Time `json:"expires_at"`
 }) Response {
-       receiver, err := bcr.accounts.CreateAddress(ctx, ins.AccountInfo, ins.ExpiresAt)
+       receiver, err := bcr.accounts.CreateAddressReceiver(ctx, ins.AccountInfo, ins.ExpiresAt)
        if err != nil {
                return resWrapper(nil, err)
        }
index 5813b06..68b0261 100644 (file)
@@ -3,6 +3,7 @@ package txbuilder
 import (
        "context"
        stdjson "encoding/json"
+       "errors"
 
        "github.com/bytom/common"
        "github.com/bytom/consensus"
@@ -15,6 +16,7 @@ import (
 
 var retirementProgram = []byte{byte(vm.OP_FAIL)}
 
+// DecodeControlReceiverAction convert input data to action struct
 func DecodeControlReceiverAction(data []byte) (Action, error) {
        a := new(controlReceiverAction)
        err := stdjson.Unmarshal(data, a)
@@ -51,6 +53,7 @@ func (a *controlReceiverAction) Build(ctx context.Context, b *TemplateBuilder) e
        return b.AddOutput(out)
 }
 
+// DecodeControlAddressAction convert input data to action struct
 func DecodeControlAddressAction(data []byte) (Action, error) {
        a := new(controlAddressAction)
        err := stdjson.Unmarshal(data, a)
@@ -75,10 +78,21 @@ func (a *controlAddressAction) Build(ctx context.Context, b *TemplateBuilder) er
                return MissingFieldsError(missing...)
        }
 
-       // TODO: call different stand script generate due to address start with 1 or 3
        address, err := common.DecodeAddress(a.Address, &consensus.MainNetParams)
-       pubkeyHash := address.ScriptAddress()
-       program, err := vmutil.P2PKHSigProgram(pubkeyHash)
+       if err != nil {
+               return err
+       }
+       redeemContract := address.ScriptAddress()
+       program := []byte{}
+
+       switch address.(type) {
+       case *common.AddressWitnessPubKeyHash:
+               program, err = vmutil.P2PKHSigProgram(redeemContract)
+       case *common.AddressWitnessScriptHash:
+               program, err = vmutil.P2SHProgram(redeemContract)
+       default:
+               return errors.New("unsupport address type")
+       }
        if err != nil {
                return err
        }
@@ -87,6 +101,7 @@ func (a *controlAddressAction) Build(ctx context.Context, b *TemplateBuilder) er
        return b.AddOutput(out)
 }
 
+// DecodeControlProgramAction convert input data to action struct
 func DecodeControlProgramAction(data []byte) (Action, error) {
        a := new(controlProgramAction)
        err := stdjson.Unmarshal(data, a)
@@ -115,6 +130,7 @@ func (a *controlProgramAction) Build(ctx context.Context, b *TemplateBuilder) er
        return b.AddOutput(out)
 }
 
+// DecodeSetTxRefDataAction convert input data to action struct
 func DecodeSetTxRefDataAction(data []byte) (Action, error) {
        a := new(setTxRefDataAction)
        err := stdjson.Unmarshal(data, a)
@@ -132,6 +148,7 @@ func (a *setTxRefDataAction) Build(ctx context.Context, b *TemplateBuilder) erro
        return b.setReferenceData(a.Data)
 }
 
+// DecodeRetireAction convert input data to action struct
 func DecodeRetireAction(data []byte) (Action, error) {
        a := new(retireAction)
        err := stdjson.Unmarshal(data, a)
index 4ba00a8..0d7348a 100644 (file)
@@ -105,6 +105,7 @@ func AddCommands() {
        BytomcliCmd.AddCommand(listAccountsCmd)
        BytomcliCmd.AddCommand(updateAccountTagsCmd)
        BytomcliCmd.AddCommand(createAccountReceiverCmd)
+       BytomcliCmd.AddCommand(createAccountAddressCmd)
 
        BytomcliCmd.AddCommand(createAssetCmd)
        BytomcliCmd.AddCommand(listAssetsCmd)
index 34b923e..6953b02 100644 (file)
@@ -15,6 +15,7 @@ import (
 func init() {
        buildTransactionCmd.PersistentFlags().StringVarP(&buildType, "type", "t", "", "transaction type, valid types: 'issue', 'spend'")
        buildTransactionCmd.PersistentFlags().StringVarP(&receiverProgram, "receiver", "r", "", "program of receiver")
+       buildTransactionCmd.PersistentFlags().StringVarP(&address, "address", "a", "", "address of receiver")
        buildTransactionCmd.PersistentFlags().StringVarP(&btmGas, "gas", "g", "20000000", "program of receiver")
        buildTransactionCmd.PersistentFlags().BoolVar(&pretty, "pretty", false, "pretty print json result")
        buildTransactionCmd.PersistentFlags().BoolVar(&alias, "alias", false, "use alias build transaction")
@@ -32,6 +33,7 @@ var (
        buildType       = ""
        btmGas          = ""
        receiverProgram = ""
+       address         = ""
        password        = ""
        pretty          = false
        alias           = false
@@ -81,6 +83,20 @@ var buildRetireReqFmtByAlias = `
                {"type": "retire", "asset_alias": "%s","amount": %s,"account_alias": "%s"}
        ]}`
 
+var buildControlAddressReqFmt = `
+       {"actions": [
+               {"type": "spend_account", "asset_id": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "amount":%s, "account_id": "%s"},
+               {"type": "spend_account", "asset_id": "%s","amount": %s,"account_id": "%s"},
+               {"type": "control_address", "asset_id": "%s", "amount": %s,"address": "%s"}
+       ]}`
+
+var buildControlAddressReqFmtByAlias = `
+       {"actions": [
+               {"type": "spend_account", "asset_id": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "amount":%s, "account_alias": "%s"},
+               {"type": "spend_account", "asset_id": "%s","amount": %s, "account_alias": "%s"},
+               {"type": "control_address", "asset_id": "%s", "amount": %s,"address": "%s"}
+       ]}`
+
 var buildTransactionCmd = &cobra.Command{
        Use:   "build-transaction <accountID|alias> <assetID|alias> <amount>",
        Short: "Build one transaction template,default use account id and asset id",
@@ -114,7 +130,13 @@ var buildTransactionCmd = &cobra.Command{
                                buildReqStr = fmt.Sprintf(buildRetireReqFmtByAlias, btmGas, accountInfo, assetInfo, amount, accountInfo, assetInfo, amount, accountInfo)
                                break
                        }
-                       buildReqStr = fmt.Sprintf(buildRetireReqFmt, btmGas, accountInfo, assetInfo, amount, accountInfo, assetInfo, amount, accountInfo)
+                       buildReqStr = fmt.Sprintf(buildControlAddressReqFmt, btmGas, accountInfo, assetInfo, amount, accountInfo, assetInfo, amount, accountInfo)
+               case "address":
+                       if alias {
+                               buildReqStr = fmt.Sprintf(buildControlAddressReqFmtByAlias, btmGas, accountInfo, assetInfo, amount, accountInfo, assetInfo, amount, address)
+                               break
+                       }
+                       buildReqStr = fmt.Sprintf(buildControlAddressReqFmt, btmGas, accountInfo, assetInfo, amount, accountInfo, assetInfo, amount, address)
                default:
                        jww.ERROR.Println("Invalid transaction template type")
                        os.Exit(ErrLocalExe)
index 0d99eb1..4c81d94 100644 (file)
@@ -209,8 +209,7 @@ func newAddressWitnessPubKeyHash(hrp string, witnessProg []byte) (*AddressWitnes
        // Check for valid program length for witness version 0, which is 20
        // for P2WPKH.
        if len(witnessProg) != 20 {
-               return nil, errors.New("witness program must be 20 " +
-                       "bytes for p2wpkh")
+               return nil, errors.New("witness program must be 20 bytes for p2wpkh")
        }
 
        addr := &AddressWitnessPubKeyHash{
@@ -300,8 +299,7 @@ func newAddressWitnessScriptHash(hrp string, witnessProg []byte) (*AddressWitnes
        // Check for valid program length for witness version 0, which is 32
        // for P2WSH.
        if len(witnessProg) != 32 {
-               return nil, errors.New("witness program must be 32 " +
-                       "bytes for p2wsh")
+               return nil, errors.New("witness program must be 32 bytes for p2wsh")
        }
 
        addr := &AddressWitnessScriptHash{
@@ -363,7 +361,7 @@ func (a *AddressWitnessScriptHash) WitnessProgram() []byte {
        return a.witnessProgram[:]
 }
 
-// Hash160 returns the witness program of the AddressWitnessPubKeyHash as a
+// Sha256 returns the witness program of the AddressWitnessPubKeyHash as a
 // byte array.
 func (a *AddressWitnessScriptHash) Sha256() *[32]byte {
        return &a.witnessProgram
index 14b3db7..febb23b 100644 (file)
@@ -45,10 +45,11 @@ func GenerateGenesisBlock() *legacy.Block {
        var seed [32]byte
        sha3pool.Sum256(seed[:], make([]byte, 32))
 
-       genesisBlock := &legacy.Block{
+       block := &legacy.Block{
                BlockHeader: legacy.BlockHeader{
                        Version:     1,
                        Height:      0,
+                       Nonce:       1267808,
                        Seed:        bc.NewHash(seed),
                        TimestampMS: 1511318565142,
                        BlockCommitment: legacy.BlockCommitment{
@@ -59,15 +60,13 @@ func GenerateGenesisBlock() *legacy.Block {
                Transactions: []*legacy.Tx{genesisCoinbaseTx},
        }
 
-       for i := uint64(0); i <= 10000000000000; i++ {
-               genesisBlock.Nonce = i
-               hash := genesisBlock.Hash()
-
-               if difficulty.CheckProofOfWork(&hash, genesisBlock.Bits) {
+       for {
+               hash := block.Hash()
+               if difficulty.CheckProofOfWork(&hash, block.Bits) {
+                       log.Info(block.Nonce)
                        break
                }
+               block.Nonce++
        }
-
-       log.Infof("genesisBlock:%v", genesisBlock)
-       return genesisBlock
+       return block
 }
index 5d73071..3b7a183 100644 (file)
@@ -5,8 +5,14 @@ import (
 )
 
 // test genesis
-func TestGenesis(t *testing.T) {
+func TestGenerateGenesisTx(t *testing.T) {
        if tx := GenerateGenesisTx(); tx == nil {
                t.Errorf("Generate genesis tx failed")
        }
 }
+
+func TestGenerateGenesisBlock(t *testing.T) {
+       if block := GenerateGenesisBlock(); block == nil {
+               t.Errorf("Generate genesis block failed")
+       }
+}
index 5527abc..85330fb 100644 (file)
@@ -53,11 +53,93 @@ func TestP2PKH(t *testing.T) {
                t.Fatal(err)
        }
 
-       controlProg, err := accountManager.CreateP2PKH(nil, testAccount.Signer.ID, false, time.Now())
+       controlProg, err := accountManager.CreateAddress(nil, testAccount.Signer.ID, false, time.Now())
        if err != nil {
                t.Fatal(err)
        }
 
+       utxo := mockUTXO(controlProg)
+       tpl, tx, err := mockTx(utxo, testAccount)
+       if err != nil {
+               t.Fatal(err)
+       }
+
+       if err := mockSign(tpl, hsm); err != nil {
+               t.Fatal(err)
+       }
+
+       if _, err = validation.ValidateTx(legacy.MapTx(tx), mockBlock()); err != nil {
+               t.Fatal(err)
+       }
+}
+
+func TestP2SH(t *testing.T) {
+       dirPath, err := ioutil.TempDir(".", "")
+       if err != nil {
+               t.Fatal(err)
+       }
+       defer os.RemoveAll(dirPath)
+
+       testDB := dbm.NewDB("testdb", "leveldb", "temp")
+       defer os.RemoveAll("temp")
+
+       chain, err := mockChain(testDB)
+       if err != nil {
+               t.Fatal(err)
+       }
+
+       accountManager := account.NewManager(testDB, chain)
+       hsm, err := pseudohsm.New(dirPath)
+       if err != nil {
+               t.Fatal(err)
+       }
+
+       xpub1, err := hsm.XCreate("test_pub1", "password")
+       if err != nil {
+               t.Fatal(err)
+       }
+
+       xpub2, err := hsm.XCreate("test_pub2", "password")
+       if err != nil {
+               t.Fatal(err)
+       }
+
+       testAccount, err := accountManager.Create(nil, []chainkd.XPub{xpub1.XPub, xpub2.XPub}, 2, "testAccount", nil, "")
+       if err != nil {
+               t.Fatal(err)
+       }
+
+       controlProg, err := accountManager.CreateAddress(nil, testAccount.Signer.ID, false, time.Now())
+       if err != nil {
+               t.Fatal(err)
+       }
+
+       utxo := mockUTXO(controlProg)
+       tpl, tx, err := mockTx(utxo, testAccount)
+       if err != nil {
+               t.Fatal(err)
+       }
+
+       if err := mockSign(tpl, hsm); err != nil {
+               t.Fatal(err)
+       }
+
+       if _, err = validation.ValidateTx(legacy.MapTx(tx), mockBlock()); err != nil {
+               t.Fatal(err)
+       }
+}
+
+func mockChain(testDB dbm.DB) (*protocol.Chain, error) {
+       store := txdb.NewStore(testDB)
+       txPool := protocol.NewTxPool()
+       chain, err := protocol.NewChain(bc.Hash{}, store, txPool)
+       if err != nil {
+               return nil, err
+       }
+       return chain, nil
+}
+
+func mockUTXO(controlProg *account.CtrlProgram) *account.UTXO {
        utxo := &account.UTXO{}
        utxo.OutputID = bc.Hash{V0: 1}
        utxo.SourceID = bc.Hash{V0: 2}
@@ -68,45 +150,34 @@ func TestP2PKH(t *testing.T) {
        utxo.AccountID = controlProg.AccountID
        utxo.Address = controlProg.Address
        utxo.ControlProgramIndex = controlProg.KeyIndex
+       return utxo
+}
+
+func mockTx(utxo *account.UTXO, testAccount *account.Account) (*txbuilder.Template, *legacy.TxData, error) {
        txInput, sigInst, err := account.UtxoToInputs(testAccount.Signer, utxo, nil)
        if err != nil {
-               t.Fatal(err)
+               return nil, nil, err
        }
 
        b := txbuilder.NewBuilder(time.Now())
        b.AddInput(txInput, sigInst)
        out := legacy.NewTxOutput(*consensus.BTMAssetID, 100, []byte{byte(vm.OP_FAIL)}, nil)
        b.AddOutput(out)
-       tpl, tx, err := b.Build()
-       if err != nil {
-               t.Fatal(err)
-       }
+       return b.Build()
+}
 
-       err = txbuilder.Sign(nil, tpl, nil, "password", func(_ context.Context, xpub chainkd.XPub, path [][]byte, data [32]byte, password string) ([]byte, error) {
+func mockSign(tpl *txbuilder.Template, hsm *pseudohsm.HSM) error {
+       return txbuilder.Sign(nil, tpl, nil, "password", func(_ context.Context, xpub chainkd.XPub, path [][]byte, data [32]byte, password string) ([]byte, error) {
                sigBytes, err := hsm.XSign(xpub, path, data[:], password)
                if err != nil {
                        return nil, nil
                }
                return sigBytes, err
        })
-       if err != nil {
-               t.Fatal(err)
-       }
-
-       bcBlock := &bc.Block{
-               BlockHeader: &bc.BlockHeader{Height: 1},
-       }
-       if _, err = validation.ValidateTx(legacy.MapTx(tx), bcBlock); err != nil {
-               t.Fatal(err)
-       }
 }
 
-func mockChain(testDB dbm.DB) (*protocol.Chain, error) {
-       store := txdb.NewStore(testDB)
-       txPool := protocol.NewTxPool()
-       chain, err := protocol.NewChain(bc.Hash{}, store, txPool)
-       if err != nil {
-               return nil, err
+func mockBlock() *bc.Block {
+       return &bc.Block{
+               BlockHeader: &bc.BlockHeader{Height: 1},
        }
-       return chain, nil
 }
index 5bbb4be..946b369 100644 (file)
@@ -49,6 +49,9 @@ func opCheckPredicate(vm *virtualMachine) error {
                return ErrBadValue
        }
        l := int64(len(vm.dataStack))
+       if n < 0 {
+               n = l
+       }
        if n > l {
                return ErrDataStackUnderflow
        }
index ade3fb8..103898f 100644 (file)
@@ -65,6 +65,20 @@ func P2PKHSigProgram(pubkeyHash []byte) ([]byte, error) {
        return builder.Build()
 }
 
+// P2SHProgram generates the script for control with script hash
+func P2SHProgram(scriptHash []byte) ([]byte, error) {
+       builder := NewBuilder()
+       builder.AddOp(vm.OP_DUP)
+       builder.AddOp(vm.OP_SHA3)
+       builder.AddData(scriptHash)
+       builder.AddOp(vm.OP_EQUALVERIFY)
+       builder.AddInt64(-1)
+       builder.AddOp(vm.OP_SWAP)
+       builder.AddInt64(0)
+       builder.AddOp(vm.OP_CHECKPREDICATE)
+       return builder.Build()
+}
+
 // P2SPMultiSigProgram generates the script for contorl transaction output
 func P2SPMultiSigProgram(pubkeys []ed25519.PublicKey, nrequired int) ([]byte, error) {
        builder := NewBuilder()