From: Paladz Date: Thu, 18 Jan 2018 01:59:10 +0000 (+0800) Subject: stand tx is eveywhere (#298) X-Git-Tag: v1.0.5~352 X-Git-Url: http://git.osdn.net/view?a=commitdiff_plain;h=2dde81a1857f2dd9e1e7413ad0efe681966e685f;p=bytom%2Fbytom.git stand tx is eveywhere (#298) * stand tx is eveywhere * add unit test --- diff --git a/blockchain/account/accounts.go b/blockchain/account/accounts.go index 02175ff7..d21debea 100755 --- a/blockchain/account/accounts.go +++ b/blockchain/account/accounts.go @@ -228,18 +228,20 @@ func (m *Manager) GetAliasByID(id string) string { return account.Alias } -// CreateAddress generate an address for the select account func (m *Manager) CreateAddress(ctx context.Context, accountID string, change bool) (cp *CtrlProgram, err error) { account, err := m.findByID(ctx, accountID) if err != nil { return nil, err } + return m.createAddress(ctx, account, change) +} - expiresAt := time.Now().Add(defaultReceiverExpiry) +// CreateAddress generate an address for the select account +func (m *Manager) createAddress(ctx context.Context, account *Account, change bool) (cp *CtrlProgram, err error) { if len(account.XPubs) == 1 { - cp, err = m.createP2PKH(ctx, account, change, expiresAt) + cp, err = m.createP2PKH(ctx, account, change) } else { - cp, err = m.createP2SH(ctx, account, change, expiresAt) + cp, err = m.createP2SH(ctx, account, change) } if err != nil { return nil, err @@ -251,7 +253,7 @@ func (m *Manager) CreateAddress(ctx context.Context, accountID string, change bo return cp, nil } -func (m *Manager) createP2PKH(ctx context.Context, account *Account, change bool, expiresAt time.Time) (*CtrlProgram, error) { +func (m *Manager) createP2PKH(ctx context.Context, account *Account, change bool) (*CtrlProgram, error) { idx := m.nextIndex() path := signers.Path(account.Signer, signers.AccountKeySpace, idx) derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path) @@ -275,11 +277,10 @@ func (m *Manager) createP2PKH(ctx context.Context, account *Account, change bool KeyIndex: idx, ControlProgram: control, Change: change, - ExpiresAt: expiresAt, }, nil } -func (m *Manager) createP2SH(ctx context.Context, account *Account, change bool, expiresAt time.Time) (*CtrlProgram, error) { +func (m *Manager) createP2SH(ctx context.Context, account *Account, change bool) (*CtrlProgram, error) { idx := m.nextIndex() path := signers.Path(account.Signer, signers.AccountKeySpace, idx) derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path) @@ -307,48 +308,9 @@ func (m *Manager) createP2SH(ctx context.Context, account *Account, change bool, KeyIndex: idx, ControlProgram: control, Change: change, - ExpiresAt: expiresAt, }, nil } -func (m *Manager) createControlProgram(ctx context.Context, accountID string, change bool, expiresAt time.Time) (*CtrlProgram, error) { - account, err := m.findByID(ctx, accountID) - if err != nil { - return nil, err - } - - idx := m.nextIndex() - path := signers.Path(account.Signer, signers.AccountKeySpace, idx) - derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path) - derivedPKs := chainkd.XPubKeys(derivedXPubs) - control, err := vmutil.P2SPMultiSigProgram(derivedPKs, account.Quorum) - if err != nil { - return nil, err - } - - return &CtrlProgram{ - AccountID: account.ID, - KeyIndex: idx, - ControlProgram: control, - Change: change, - ExpiresAt: expiresAt, - }, nil -} - -// CreateControlProgram creates a control program -// that is tied to the Account and stores it in the database. -func (m *Manager) CreateControlProgram(ctx context.Context, accountID string, change bool, expiresAt time.Time) ([]byte, error) { - cp, err := m.createControlProgram(ctx, accountID, change, expiresAt) - if err != nil { - return nil, err - } - - if err = m.insertAccountControlProgram(ctx, cp); err != nil { - return nil, err - } - return cp.ControlProgram, nil -} - //CtrlProgram is structure of account control program type CtrlProgram struct { AccountID string @@ -356,7 +318,6 @@ type CtrlProgram struct { KeyIndex uint64 ControlProgram []byte Change bool - ExpiresAt time.Time } func (m *Manager) insertAccountControlProgram(ctx context.Context, progs ...*CtrlProgram) error { @@ -374,42 +335,25 @@ func (m *Manager) insertAccountControlProgram(ctx context.Context, progs ...*Ctr } // GetCoinbaseControlProgram will return a coinbase script -func (m *Manager) GetCoinbaseControlProgram(height uint64) ([]byte, error) { +func (m *Manager) GetCoinbaseControlProgram() ([]byte, error) { accountIter := m.db.IteratorPrefix([]byte(accountPrefix)) defer accountIter.Release() if !accountIter.Next() { log.Warningf("GetCoinbaseControlProgram: can't find any account in db") - return vmutil.CoinbaseProgram(nil, 0, height) + return vmutil.DefaultCoinbaseProgram() } rawAccount := accountIter.Value() account := &Account{} if err := json.Unmarshal(rawAccount, account); err != nil { - log.Errorf("GetCoinbaseControlProgram: fail to unmarshal account %v", err) - return vmutil.CoinbaseProgram(nil, 0, height) - } - - ctx := context.Background() - idx := m.nextIndex() - path := signers.Path(account.Signer, signers.AccountKeySpace, idx) - derivedXPubs := chainkd.DeriveXPubs(account.XPubs, path) - derivedPKs := chainkd.XPubKeys(derivedXPubs) - - script, err := vmutil.CoinbaseProgram(derivedPKs, account.Quorum, height) - if err != nil { - return script, err + return nil, err } - err = m.insertAccountControlProgram(ctx, &CtrlProgram{ - AccountID: account.ID, - KeyIndex: idx, - ControlProgram: script, - Change: false, - }) + program, err := m.createAddress(nil, account, false) if err != nil { - log.Errorf("GetCoinbaseControlProgram: fail to insertAccountControlProgram %v", err) + return nil, err } - return script, nil + return program.ControlProgram, nil } func (m *Manager) nextIndex() uint64 { diff --git a/blockchain/account/builder.go b/blockchain/account/builder.go index be1a7d8d..bd7b4523 100755 --- a/blockchain/account/builder.go +++ b/blockchain/account/builder.go @@ -74,7 +74,7 @@ func (a *spendAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) e } if res.Change > 0 { - acp, err := a.accounts.createControlProgram(ctx, a.AccountID, true, b.MaxTime()) + acp, err := a.accounts.CreateAddress(ctx, a.AccountID, true) if err != nil { return errors.Wrap(err, "creating control program") } @@ -214,7 +214,7 @@ func (a *controlAction) Build(ctx context.Context, b *txbuilder.TemplateBuilder) } // Produce a control program, but don't insert it into the database yet. - acp, err := a.accounts.createControlProgram(ctx, a.AccountID, false, b.MaxTime()) + acp, err := a.accounts.CreateAddress(ctx, a.AccountID, false) if err != nil { return err } diff --git a/blockchain/account/receivers.go b/blockchain/account/receivers.go index 0f9a84b4..a4188a53 100755 --- a/blockchain/account/receivers.go +++ b/blockchain/account/receivers.go @@ -5,37 +5,10 @@ import ( "time" "github.com/bytom/blockchain/txbuilder" - "github.com/bytom/errors" ) const defaultReceiverExpiry = 30 * 24 * time.Hour // 30 days -// CreateReceiver creates a new account receiver for an account -// with the provided expiry. If a zero time is provided for the -// expiry, a default expiry of 30 days from the current time is -// used. -func (m *Manager) CreateReceiver(ctx context.Context, accountInfo string) (*txbuilder.Receiver, error) { - expiresAt := time.Now().Add(defaultReceiverExpiry) - - accountID := accountInfo - - if s, err := m.FindByAlias(ctx, accountInfo); err == nil { - accountID = s.ID - } - - cp, err := m.CreateControlProgram(ctx, accountID, false, expiresAt) - if err != nil { - return nil, errors.Wrap(err) - } - - receiver := &txbuilder.Receiver{ - ControlProgram: cp, - ExpiresAt: expiresAt, - } - - return receiver, nil -} - // CreateAddressReceiver creates a new address receiver for an account func (m *Manager) CreateAddressReceiver(ctx context.Context, accountInfo string) (*txbuilder.Receiver, error) { accountID := accountInfo @@ -51,6 +24,5 @@ func (m *Manager) CreateAddressReceiver(ctx context.Context, accountInfo string) return &txbuilder.Receiver{ ControlProgram: program.ControlProgram, Address: program.Address, - ExpiresAt: program.ExpiresAt, }, nil } diff --git a/blockchain/account/receivers_test.go b/blockchain/account/receivers_test.go index 585c52d7..fa5367c3 100644 --- a/blockchain/account/receivers_test.go +++ b/blockchain/account/receivers_test.go @@ -8,26 +8,6 @@ import ( "github.com/bytom/testutil" ) -func TestCreateReceiver(t *testing.T) { - m := mockAccountManager(t) - ctx := context.Background() - - account, err := m.Create([]chainkd.XPub{testutil.TestXPub}, 1, "test-alias", nil) - if err != nil { - testutil.FatalErr(t, err) - } - - _, err = m.CreateReceiver(ctx, account.ID) - if err != nil { - testutil.FatalErr(t, err) - } - - _, err = m.CreateReceiver(ctx, account.Alias) - if err != nil { - testutil.FatalErr(t, err) - } -} - func TestCreateAddressReceiver(t *testing.T) { m := mockAccountManager(t) ctx := context.Background() diff --git a/blockchain/control_programs.go b/blockchain/control_programs.go deleted file mode 100644 index 96628c09..00000000 --- a/blockchain/control_programs.go +++ /dev/null @@ -1,80 +0,0 @@ -package blockchain - -import ( - "context" - stdjson "encoding/json" - "sync" - "time" - - "github.com/bytom/encoding/json" - "github.com/bytom/errors" - "github.com/bytom/net/http/httpjson" - "github.com/bytom/net/http/reqid" -) - -// POST /create-control-program -func (bcr *BlockchainReactor) createControlProgram(ctx context.Context, ins []struct { - Type string - Params stdjson.RawMessage -}) interface{} { - responses := make([]interface{}, len(ins)) - var wg sync.WaitGroup - wg.Add(len(responses)) - - for i := 0; i < len(responses); i++ { - go func(i int) { - subctx := reqid.NewSubContext(ctx, reqid.New()) - defer wg.Done() - defer batchRecover(subctx, &responses[i]) - - var ( - prog interface{} - err error - ) - switch ins[i].Type { - case "account": - prog, err = bcr.createAccountControlProgram(subctx, ins[i].Params) - default: - err = errors.WithDetailf(httpjson.ErrBadRequest, "unknown control program type %q", ins[i].Type) - } - if err != nil { - responses[i] = err - } else { - responses[i] = prog - } - }(i) - } - - wg.Wait() - return responses -} - -func (bcr *BlockchainReactor) createAccountControlProgram(ctx context.Context, input []byte) (interface{}, error) { - var parsed struct { - AccountAlias string `json:"account_alias"` - AccountID string `json:"account_id"` - } - err := stdjson.Unmarshal(input, &parsed) - if err != nil { - return nil, errors.WithDetailf(httpjson.ErrBadRequest, "bad parameters for account control program") - } - - accountID := parsed.AccountID - if accountID == "" { - acc, err := bcr.accounts.FindByAlias(ctx, parsed.AccountAlias) - if err != nil { - return nil, err - } - accountID = acc.ID - } - - controlProgram, err := bcr.accounts.CreateControlProgram(ctx, accountID, false, time.Time{}) - if err != nil { - return nil, err - } - - ret := map[string]interface{}{ - "control_program": json.HexBytes(controlProgram), - } - return ret, nil -} diff --git a/blockchain/receivers.go b/blockchain/receivers.go index ccda8de3..8ab958e4 100755 --- a/blockchain/receivers.go +++ b/blockchain/receivers.go @@ -4,20 +4,8 @@ import ( "context" ) -// POST /create-account-receiver -func (bcr *BlockchainReactor) createAccountReceiver(ctx context.Context, ins struct { - AccountInfo string `json:"account_info"` -}) Response { - receiver, err := bcr.accounts.CreateReceiver(nil, ins.AccountInfo) - if err != nil { - return NewErrorResponse(err) - } - - return NewSuccessResponse(*receiver) -} - func (bcr *BlockchainReactor) createAccountAddress(ctx context.Context, ins struct { - AccountInfo string `json:"account_info"` + AccountInfo string `json:"account_info"` }) Response { receiver, err := bcr.accounts.CreateAddressReceiver(ctx, ins.AccountInfo) if err != nil { diff --git a/blockchain/rpc_reactor.go b/blockchain/rpc_reactor.go index 84c02548..6626d18a 100644 --- a/blockchain/rpc_reactor.go +++ b/blockchain/rpc_reactor.go @@ -54,7 +54,6 @@ func (bcr *BlockchainReactor) BuildHandler() { if bcr.accounts != nil && bcr.assets != nil { m.Handle("/create-account", jsonHandler(bcr.createAccount)) m.Handle("/update-account-tags", jsonHandler(bcr.updateAccountTags)) - m.Handle("/create-account-receiver", jsonHandler(bcr.createAccountReceiver)) m.Handle("/create-account-address", jsonHandler(bcr.createAccountAddress)) m.Handle("/list-accounts", jsonHandler(bcr.listAccounts)) m.Handle("/delete-account", jsonHandler(bcr.deleteAccount)) @@ -81,7 +80,6 @@ func (bcr *BlockchainReactor) BuildHandler() { m.Handle("/submit-transaction", jsonHandler(bcr.submit)) m.Handle("/sign-submit-transaction", jsonHandler(bcr.signSubmit)) - m.Handle("/create-control-program", jsonHandler(bcr.createControlProgram)) m.Handle("/create-transaction-feed", jsonHandler(bcr.createTxFeed)) m.Handle("/get-transaction-feed", jsonHandler(bcr.getTxFeed)) m.Handle("/update-transaction-feed", jsonHandler(bcr.updateTxFeed)) diff --git a/blockchain/txbuilder/actions.go b/blockchain/txbuilder/actions.go index 96a07790..066ceef6 100644 --- a/blockchain/txbuilder/actions.go +++ b/blockchain/txbuilder/actions.go @@ -37,9 +37,6 @@ func (a *controlReceiverAction) Build(ctx context.Context, b *TemplateBuilder) e if len(a.Receiver.ControlProgram) == 0 { missing = append(missing, "receiver.control_program") } - if a.Receiver.ExpiresAt.IsZero() { - missing = append(missing, "receiver.expires_at") - } } if a.AssetId.IsZero() { missing = append(missing, "asset_id") @@ -48,7 +45,6 @@ func (a *controlReceiverAction) Build(ctx context.Context, b *TemplateBuilder) e return MissingFieldsError(missing...) } - b.RestrictMaxTime(a.Receiver.ExpiresAt) out := legacy.NewTxOutput(*a.AssetId, a.Amount, a.Receiver.ControlProgram, a.ReferenceData) return b.AddOutput(out) } diff --git a/blockchain/txbuilder/types.go b/blockchain/txbuilder/types.go index f764db50..2ebf1b55 100755 --- a/blockchain/txbuilder/types.go +++ b/blockchain/txbuilder/types.go @@ -2,7 +2,6 @@ package txbuilder import ( "context" - "time" chainjson "github.com/bytom/encoding/json" "github.com/bytom/protocol/bc" @@ -40,5 +39,4 @@ type Action interface { type Receiver struct { ControlProgram chainjson.HexBytes `json:"control_program,omitempty"` Address string `json:"address,omitempty"` - ExpiresAt time.Time `json:"expires_at"` } diff --git a/cmd/bytomcli/commands/account.go b/cmd/bytomcli/commands/account.go index 91b81c89..e07a07ba 100755 --- a/cmd/bytomcli/commands/account.go +++ b/cmd/bytomcli/commands/account.go @@ -138,25 +138,6 @@ var updateAccountTagsCmd = &cobra.Command{ }, } -var createAccountReceiverCmd = &cobra.Command{ - Use: "create-account-receiver ", - Short: "Create an account receiver control program", - Args: cobra.ExactArgs(1), - Run: func(cmd *cobra.Command, args []string) { - var ins = struct { - AccountInfo string `json:"account_info"` - ExpiresAt time.Time `json:"expires_at,omitempty"` - }{AccountInfo: args[0]} - - data, exitCode := util.ClientCall("/create-account-receiver", &ins) - if exitCode != util.Success { - os.Exit(exitCode) - } - - printJSON(data) - }, -} - var createAccountAddressCmd = &cobra.Command{ Use: "create-account-address ", Short: "Create an account address", diff --git a/cmd/bytomcli/commands/bytomcli.go b/cmd/bytomcli/commands/bytomcli.go index 6e7b01c8..26c55015 100644 --- a/cmd/bytomcli/commands/bytomcli.go +++ b/cmd/bytomcli/commands/bytomcli.go @@ -82,7 +82,6 @@ func AddCommands() { BytomcliCmd.AddCommand(deleteAccountCmd) BytomcliCmd.AddCommand(listAccountsCmd) BytomcliCmd.AddCommand(updateAccountTagsCmd) - BytomcliCmd.AddCommand(createAccountReceiverCmd) BytomcliCmd.AddCommand(createAccountAddressCmd) BytomcliCmd.AddCommand(createAssetCmd) @@ -129,5 +128,3 @@ func AddCommands() { BytomcliCmd.AddCommand(versionCmd) } - - diff --git a/mining/mining.go b/mining/mining.go index ef78ce03..42d1a6e5 100644 --- a/mining/mining.go +++ b/mining/mining.go @@ -28,13 +28,12 @@ import ( // is nil, the coinbase transaction will instead be redeemable by anyone. func createCoinbaseTx(accountManager *account.Manager, amount uint64, blockHeight uint64) (tx *legacy.Tx, err error) { amount += consensus.BlockSubsidy(blockHeight) - unlockHeight := blockHeight + consensus.CoinbasePendingBlockNumber var script []byte if accountManager == nil { - script, err = vmutil.CoinbaseProgram(nil, 0, unlockHeight) + script, err = vmutil.DefaultCoinbaseProgram() } else { - script, err = accountManager.GetCoinbaseControlProgram(unlockHeight) + script, err = accountManager.GetCoinbaseControlProgram() } if err != nil { return diff --git a/protocol/patricia/patricia.go b/protocol/patricia/patricia.go deleted file mode 100644 index 1e0cba6c..00000000 --- a/protocol/patricia/patricia.go +++ /dev/null @@ -1,287 +0,0 @@ -// Package patricia computes the Merkle Patricia Tree Hash of a -// set of bit strings, as described in the Chain Protocol spec. -// See https://chain.com/docs/protocol/specifications/data#merkle-patricia-tree. -// Because a patricia tree (a radix tree with a radix of 2) -// provides efficient incremental updates, so does the Merkle -// Patricia Tree Hash computation, making this structure suitable -// for the blockchain full-state commitment. -// -// Type Tree represents a set, where the elements are bit strings. -// The set must be prefix-free -- no item can be a prefix of -// any other -- enforced by Insert. -// The length of each bit string must also be a multiple of eight, -// because the interface uses []byte to represent an item. -// -// The nodes in the tree form an immutable persistent data -// structure. It is okay to copy a Tree struct, -// which contains the root of the tree, to obtain a new tree -// with the same contents. The time to make such a copy is -// independent of the size of the tree. -package patricia - -import ( - "bytes" - - "github.com/bytom/crypto/sha3pool" - "github.com/bytom/errors" - "github.com/bytom/protocol/bc" -) - -var ( - leafPrefix = []byte{0x00} - interiorPrefix = []byte{0x01} -) - -// Tree implements a patricia tree. -type Tree struct { - root *node -} - -// WalkFunc is the type of the function called for each item -// visited by Walk. If an error is returned, processing stops. -type WalkFunc func(item []byte) error - -// Walk walks t calling walkFn for each item. -// If an error is returned by walkFn at any point, -// processing is stopped and the error is returned. -func Walk(t *Tree, walkFn WalkFunc) error { - if t.root == nil { - return nil - } - return walk(t.root, walkFn) -} - -func walk(n *node, walkFn WalkFunc) error { - if n.isLeaf { - return walkFn(n.Key()) - } - - if err := walk(n.children[0], walkFn); err != nil { - return err - } - - err := walk(n.children[1], walkFn) - return err -} - -// Contains returns whether t contains item. -func (t *Tree) Contains(item []byte) bool { - if t.root == nil { - return false - } - - key := bitKey(item) - n := lookup(t.root, key) - - var hash bc.Hash - h := sha3pool.Get256() - h.Write(leafPrefix) - h.Write(item) - hash.ReadFrom(h) - sha3pool.Put256(h) - return n != nil && n.Hash() == hash -} - -func lookup(n *node, key []uint8) *node { - if bytes.Equal(n.key, key) { - if !n.isLeaf { - return nil - } - return n - } - if !bytes.HasPrefix(key, n.key) { - return nil - } - - bit := key[len(n.key)] - return lookup(n.children[bit], key) -} - -// Insert inserts item into t. -// -// It is an error for item to be a prefix of an element -// in t or to contain an element in t as a prefix. -// If item itself is already in t, Insert does nothing -// (and this is not an error). -func (t *Tree) Insert(item []byte) (err error) { - key := bitKey(item) - - var hash bc.Hash - h := sha3pool.Get256() - h.Write(leafPrefix) - h.Write(item) - hash.ReadFrom(h) - sha3pool.Put256(h) - - if t.root == nil { - t.root = &node{key: key, hash: &hash, isLeaf: true} - return nil - } - - t.root, err = insert(t.root, key, &hash) - return err -} - -func insert(n *node, key []uint8, hash *bc.Hash) (*node, error) { - if bytes.Equal(n.key, key) { - if !n.isLeaf { - return n, errors.Wrap(errors.New("key provided is a prefix to other keys")) - } - - n = &node{ - isLeaf: true, - key: n.key, - hash: hash, - } - return n, nil - } - - if bytes.HasPrefix(key, n.key) { - if n.isLeaf { - return n, errors.Wrap(errors.New("key provided is a prefix to other keys")) - } - bit := key[len(n.key)] - - child := n.children[bit] - child, err := insert(child, key, hash) - if err != nil { - return n, err - } - newNode := new(node) - *newNode = *n - newNode.children[bit] = child // mutation is ok because newNode hasn't escaped yet - newNode.hash = nil - return newNode, nil - } - - common := commonPrefixLen(n.key, key) - newNode := &node{ - key: key[:common], - } - newNode.children[key[common]] = &node{ - key: key, - hash: hash, - isLeaf: true, - } - newNode.children[1-key[common]] = n - return newNode, nil -} - -// Delete removes item from t, if present. -func (t *Tree) Delete(item []byte) { - key := bitKey(item) - - if t.root != nil { - t.root = delete(t.root, key) - } -} - -func delete(n *node, key []uint8) *node { - if bytes.Equal(key, n.key) { - if !n.isLeaf { - return n - } - return nil - } - - if !bytes.HasPrefix(key, n.key) { - return n - } - - bit := key[len(n.key)] - newChild := delete(n.children[bit], key) - - if newChild == nil { - return n.children[1-bit] - } - - newNode := new(node) - *newNode = *n - newNode.key = newChild.key[:len(n.key)] // only use slices of leaf node keys - newNode.children[bit] = newChild - newNode.hash = nil - - return newNode -} - -// RootHash returns the Merkle root of the tree. -func (t *Tree) RootHash() bc.Hash { - root := t.root - if root == nil { - return bc.Hash{} - } - return root.Hash() -} - -// bitKey takes a byte array and returns a key that can -// be used inside insert and delete operations. -func bitKey(byteKey []byte) []uint8 { - key := make([]uint8, 0, len(byteKey)*8) - for _, b := range byteKey { - for i := uint(0); i < 8; i++ { - key = append(key, (b>>(7-i))&1) - } - } - return key -} - -// byteKey is the inverse of bitKey. -func byteKey(bitKey []uint8) (key []byte) { - key = make([]byte, len(bitKey)/8) - for i := uint(0); i < uint(len(key)); i++ { - var b byte - for j := uint(0); j < 8; j++ { - bit := bitKey[i*8+j] - b |= bit << (7 - j) - } - key[i] = b - } - return key -} - -func commonPrefixLen(a, b []uint8) int { - var common int - for i := 0; i < len(a) && i < len(b); i++ { - if a[i] != b[i] { - break - } - common++ - } - return common -} - -// node is a leaf or branch node in a tree -type node struct { - key []uint8 - hash *bc.Hash - isLeaf bool - children [2]*node -} - -// Key returns the key for the current node as bytes, as it -// was provided to Insert. -func (n *node) Key() []byte { return byteKey(n.key) } - -// Hash will return the hash for this node. -func (n *node) Hash() bc.Hash { - n.calcHash() - return *n.hash -} - -func (n *node) calcHash() { - if n.hash != nil { - return - } - - h := sha3pool.Get256() - h.Write(interiorPrefix) - for _, c := range n.children { - c.calcHash() - c.hash.WriteTo(h) - } - - var hash bc.Hash - hash.ReadFrom(h) - n.hash = &hash - sha3pool.Put256(h) -} diff --git a/protocol/patricia/patricia_test.go b/protocol/patricia/patricia_test.go deleted file mode 100644 index 1c423e69..00000000 --- a/protocol/patricia/patricia_test.go +++ /dev/null @@ -1,599 +0,0 @@ -package patricia - -import ( - "fmt" - "log" - "math/rand" - "strconv" - "strings" - "testing" - "testing/quick" - - "golang.org/x/crypto/sha3" - - "github.com/bytom/protocol/bc" - "github.com/bytom/testutil" -) - -func BenchmarkInserts(b *testing.B) { - const nodes = 10000 - for i := 0; i < b.N; i++ { - r := rand.New(rand.NewSource(12345)) - tr := new(Tree) - for j := 0; j < nodes; j++ { - var h [32]byte - _, err := r.Read(h[:]) - if err != nil { - b.Fatal(err) - } - - err = tr.Insert(h[:]) - if err != nil { - b.Fatal(err) - } - } - } -} - -func BenchmarkInsertsRootHash(b *testing.B) { - const nodes = 10000 - for i := 0; i < b.N; i++ { - r := rand.New(rand.NewSource(12345)) - tr := new(Tree) - for j := 0; j < nodes; j++ { - var h [32]byte - _, err := r.Read(h[:]) - if err != nil { - b.Fatal(err) - } - - err = tr.Insert(h[:]) - if err != nil { - b.Fatal(err) - } - } - tr.RootHash() - } -} - -func TestRootHashBug(t *testing.T) { - tr := new(Tree) - - err := tr.Insert([]byte{0x94}) - if err != nil { - t.Fatal(err) - } - err = tr.Insert([]byte{0x36}) - if err != nil { - t.Fatal(err) - } - before := tr.RootHash() - err = tr.Insert([]byte{0xba}) - if err != nil { - t.Fatal(err) - } - if tr.RootHash() == before { - t.Errorf("before and after root hash is %s", before.String()) - } -} - -func TestLeafVsInternalNodes(t *testing.T) { - tr0 := new(Tree) - - err := tr0.Insert([]byte{0x01}) - if err != nil { - t.Fatal(err) - } - err = tr0.Insert([]byte{0x02}) - if err != nil { - t.Fatal(err) - } - err = tr0.Insert([]byte{0x03}) - if err != nil { - t.Fatal(err) - } - err = tr0.Insert([]byte{0x04}) - if err != nil { - t.Fatal(err) - } - - // Force calculation of all the hashes. - tr0.RootHash() - t.Logf("first child = %s, %t", tr0.root.children[0].hash, tr0.root.children[0].isLeaf) - t.Logf("second child = %s, %t", tr0.root.children[1].hash, tr0.root.children[1].isLeaf) - - // Create a second tree using an internal node from tr1. - tr1 := new(Tree) - err = tr1.Insert(tr0.root.children[0].hash.Bytes()) // internal node of tr0 - if err != nil { - t.Fatal(err) - } - err = tr1.Insert(tr0.root.children[1].hash.Bytes()) // sibling leaf node of above node ^ - if err != nil { - t.Fatal(err) - } - - if tr1.RootHash() == tr0.RootHash() { - t.Errorf("tr0 and tr1 have matching root hashes: %x", tr1.RootHash().Bytes()) - } -} - -func TestRootHashInsertQuickCheck(t *testing.T) { - tr := new(Tree) - - f := func(b [32]byte) bool { - before := tr.RootHash() - err := tr.Insert(b[:]) - if err != nil { - return false - } - return before != tr.RootHash() - } - if err := quick.Check(f, nil); err != nil { - t.Error(err) - } -} - -func TestLookup(t *testing.T) { - tr := &Tree{ - root: &node{key: bools("11111111"), hash: hashPtr(hashForLeaf(bits("11111111"))), isLeaf: true}, - } - got := lookup(tr.root, bitKey(bits("11111111"))) - if !testutil.DeepEqual(got, tr.root) { - t.Log("lookup on 1-node tree") - t.Fatalf("got:\n%swant:\n%s", prettyNode(got, 0), prettyNode(tr.root, 0)) - } - - tr = &Tree{ - root: &node{key: bools("11111110"), hash: hashPtr(hashForLeaf(bits("11111110"))), isLeaf: true}, - } - got = lookup(tr.root, bitKey(bits("11111111"))) - if got != nil { - t.Log("lookup nonexistent key on 1-node tree") - t.Fatalf("got:\n%swant nil", prettyNode(got, 0)) - } - - tr = &Tree{ - root: &node{ - key: bools("1111"), - hash: hashPtr(hashForNonLeaf(hashForLeaf(bits("11110000")), hashForLeaf(bits("11111111")))), - children: [2]*node{ - {key: bools("11110000"), hash: hashPtr(hashForLeaf(bits("11110000"))), isLeaf: true}, - {key: bools("11111111"), hash: hashPtr(hashForLeaf(bits("11111111"))), isLeaf: true}, - }, - }, - } - got = lookup(tr.root, bitKey(bits("11110000"))) - if !testutil.DeepEqual(got, tr.root.children[0]) { - t.Log("lookup root's first child") - t.Fatalf("got:\n%swant:\n%s", prettyNode(got, 0), prettyNode(tr.root.children[0], 0)) - } - - tr = &Tree{ - root: &node{ - key: bools("1111"), - hash: hashPtr(hashForNonLeaf( - hashForLeaf(bits("11110000")), - hashForNonLeaf(hashForLeaf(bits("11111100")), hashForLeaf(bits("11111111"))), - )), - children: [2]*node{ - {key: bools("11110000"), hash: hashPtr(hashForLeaf(bits("11110000"))), isLeaf: true}, - { - key: bools("111111"), - hash: hashPtr(hashForNonLeaf(hashForLeaf(bits("11111100")), hashForLeaf(bits("11111111")))), - children: [2]*node{ - {key: bools("11111100"), hash: hashPtr(hashForLeaf(bits("11111100"))), isLeaf: true}, - {key: bools("11111111"), hash: hashPtr(hashForLeaf(bits("11111111"))), isLeaf: true}, - }, - }, - }, - }, - } - got = lookup(tr.root, bitKey(bits("11111100"))) - if !testutil.DeepEqual(got, tr.root.children[1].children[0]) { - t.Fatalf("got:\n%swant:\n%s", prettyNode(got, 0), prettyNode(tr.root.children[1].children[0], 0)) - } -} - -func TestContains(t *testing.T) { - tr := new(Tree) - tr.Insert(bits("00000011")) - tr.Insert(bits("00000010")) - - if v := bits("00000011"); !tr.Contains(v) { - t.Errorf("expected tree to contain %x, but did not", v) - } - if v := bits("00000000"); tr.Contains(v) { - t.Errorf("expected tree to not contain %x, but did", v) - } - if v := bits("00000010"); !tr.Contains(v) { - t.Errorf("expected tree to contain %x, but did not", v) - } -} - -func TestInsert(t *testing.T) { - tr := new(Tree) - - tr.Insert(bits("11111111")) - tr.RootHash() - want := &Tree{ - root: &node{key: bools("11111111"), hash: hashPtr(hashForLeaf(bits("11111111"))), isLeaf: true}, - } - if !testutil.DeepEqual(tr.root, want.root) { - log.Printf("want hash? %x", hashForLeaf(bits("11111111")).Bytes()) - t.Log("insert into empty tree") - t.Fatalf("got:\n%swant:\n%s", pretty(tr), pretty(want)) - } - - tr.Insert(bits("11111111")) - tr.RootHash() - want = &Tree{ - root: &node{key: bools("11111111"), hash: hashPtr(hashForLeaf(bits("11111111"))), isLeaf: true}, - } - if !testutil.DeepEqual(tr.root, want.root) { - t.Log("inserting the same key does not modify the tree") - t.Fatalf("got:\n%swant:\n%s", pretty(tr), pretty(want)) - } - - tr.Insert(bits("11110000")) - tr.RootHash() - want = &Tree{ - root: &node{ - key: bools("1111"), - hash: hashPtr(hashForNonLeaf(hashForLeaf(bits("11110000")), hashForLeaf(bits("11111111")))), - children: [2]*node{ - {key: bools("11110000"), hash: hashPtr(hashForLeaf(bits("11110000"))), isLeaf: true}, - {key: bools("11111111"), hash: hashPtr(hashForLeaf(bits("11111111"))), isLeaf: true}, - }, - }, - } - if !testutil.DeepEqual(tr.root, want.root) { - t.Log("different key creates a fork") - t.Fatalf("got:\n%swant:\n%s", pretty(tr), pretty(want)) - } - - tr.Insert(bits("11111100")) - tr.RootHash() - want = &Tree{ - root: &node{ - key: bools("1111"), - hash: hashPtr(hashForNonLeaf( - hashForLeaf(bits("11110000")), - hashForNonLeaf(hashForLeaf(bits("11111100")), hashForLeaf(bits("11111111"))), - )), - children: [2]*node{ - {key: bools("11110000"), hash: hashPtr(hashForLeaf(bits("11110000"))), isLeaf: true}, - { - key: bools("111111"), - hash: hashPtr(hashForNonLeaf(hashForLeaf(bits("11111100")), hashForLeaf(bits("11111111")))), - children: [2]*node{ - {key: bools("11111100"), hash: hashPtr(hashForLeaf(bits("11111100"))), isLeaf: true}, - {key: bools("11111111"), hash: hashPtr(hashForLeaf(bits("11111111"))), isLeaf: true}, - }, - }, - }, - }, - } - if !testutil.DeepEqual(tr.root, want.root) { - t.Fatalf("got:\n%swant:\n%s", pretty(tr), pretty(want)) - } - - tr.Insert(bits("11111110")) - tr.RootHash() - want = &Tree{ - root: &node{ - key: bools("1111"), - hash: hashPtr(hashForNonLeaf( - hashForLeaf(bits("11110000")), - hashForNonLeaf( - hashForLeaf(bits("11111100")), - hashForNonLeaf(hashForLeaf(bits("11111110")), hashForLeaf(bits("11111111"))), - ), - )), - children: [2]*node{ - {key: bools("11110000"), hash: hashPtr(hashForLeaf(bits("11110000"))), isLeaf: true}, - { - key: bools("111111"), - hash: hashPtr(hashForNonLeaf( - hashForLeaf(bits("11111100")), - hashForNonLeaf(hashForLeaf(bits("11111110")), hashForLeaf(bits("11111111"))))), - children: [2]*node{ - {key: bools("11111100"), hash: hashPtr(hashForLeaf(bits("11111100"))), isLeaf: true}, - { - key: bools("1111111"), - hash: hashPtr(hashForNonLeaf(hashForLeaf(bits("11111110")), hashForLeaf(bits("11111111")))), - children: [2]*node{ - {key: bools("11111110"), hash: hashPtr(hashForLeaf(bits("11111110"))), isLeaf: true}, - {key: bools("11111111"), hash: hashPtr(hashForLeaf(bits("11111111"))), isLeaf: true}, - }, - }, - }, - }, - }, - }, - } - if !testutil.DeepEqual(tr.root, want.root) { - t.Log("a fork is created for each level of similar key") - t.Fatalf("got:\n%swant:\n%s", pretty(tr), pretty(want)) - } - - tr.Insert(bits("11111011")) - tr.RootHash() - want = &Tree{ - root: &node{ - key: bools("1111"), - hash: hashPtr(hashForNonLeaf( - hashForLeaf(bits("11110000")), - hashForNonLeaf( - hashForLeaf(bits("11111011")), - hashForNonLeaf( - hashForLeaf(bits("11111100")), - hashForNonLeaf(hashForLeaf(bits("11111110")), hashForLeaf(bits("11111111"))), - ), - ), - )), - children: [2]*node{ - {key: bools("11110000"), hash: hashPtr(hashForLeaf(bits("11110000"))), isLeaf: true}, - { - key: bools("11111"), - hash: hashPtr(hashForNonLeaf( - hashForLeaf(bits("11111011")), - hashForNonLeaf( - hashForLeaf(bits("11111100")), - hashForNonLeaf(hashForLeaf(bits("11111110")), hashForLeaf(bits("11111111"))), - ))), - children: [2]*node{ - {key: bools("11111011"), hash: hashPtr(hashForLeaf(bits("11111011"))), isLeaf: true}, - { - key: bools("111111"), - hash: hashPtr(hashForNonLeaf( - hashForLeaf(bits("11111100")), - hashForNonLeaf(hashForLeaf(bits("11111110")), hashForLeaf(bits("11111111"))), - )), - children: [2]*node{ - {key: bools("11111100"), hash: hashPtr(hashForLeaf(bits("11111100"))), isLeaf: true}, - { - key: bools("1111111"), - hash: hashPtr(hashForNonLeaf(hashForLeaf(bits("11111110")), hashForLeaf(bits("11111111")))), - children: [2]*node{ - {key: bools("11111110"), hash: hashPtr(hashForLeaf(bits("11111110"))), isLeaf: true}, - {key: bools("11111111"), hash: hashPtr(hashForLeaf(bits("11111111"))), isLeaf: true}, - }, - }, - }, - }, - }, - }, - }, - }, - } - if !testutil.DeepEqual(tr.root, want.root) { - t.Log("compressed branch node is split") - t.Fatalf("got:\n%swant:\n%s", pretty(tr), pretty(want)) - } -} - -func TestDelete(t *testing.T) { - tr := new(Tree) - tr.root = &node{ - key: bools("1111"), - hash: hashPtr(hashForNonLeaf( - hashForLeaf(bits("11110000")), - hashForNonLeaf( - hashForLeaf(bits("11111100")), - hashForNonLeaf(hashForLeaf(bits("11111110")), hashForLeaf(bits("11111111"))), - ), - )), - children: [2]*node{ - {key: bools("11110000"), hash: hashPtr(hashForLeaf(bits("11110000"))), isLeaf: true}, - { - key: bools("111111"), - hash: hashPtr(hashForNonLeaf( - hashForLeaf(bits("11111100")), - hashForNonLeaf(hashForLeaf(bits("11111110")), hashForLeaf(bits("11111111"))), - )), - children: [2]*node{ - {key: bools("11111100"), hash: hashPtr(hashForLeaf(bits("11111100"))), isLeaf: true}, - { - key: bools("1111111"), - hash: hashPtr(hashForNonLeaf(hashForLeaf(bits("11111110")), hashForLeaf(bits("11111111")))), - children: [2]*node{ - {key: bools("11111110"), hash: hashPtr(hashForLeaf(bits("11111110"))), isLeaf: true}, - {key: bools("11111111"), hash: hashPtr(hashForLeaf(bits("11111111"))), isLeaf: true}, - }, - }, - }, - }, - }, - } - - tr.Delete(bits("11111110")) - tr.RootHash() - want := &Tree{ - root: &node{ - key: bools("1111"), - hash: hashPtr(hashForNonLeaf( - hashForLeaf(bits("11110000")), - hashForNonLeaf(hashForLeaf(bits("11111100")), hashForLeaf(bits("11111111"))), - )), - children: [2]*node{ - {key: bools("11110000"), hash: hashPtr(hashForLeaf(bits("11110000"))), isLeaf: true}, - { - key: bools("111111"), - hash: hashPtr(hashForNonLeaf(hashForLeaf(bits("11111100")), hashForLeaf(bits("11111111")))), - children: [2]*node{ - {key: bools("11111100"), hash: hashPtr(hashForLeaf(bits("11111100"))), isLeaf: true}, - {key: bools("11111111"), hash: hashPtr(hashForLeaf(bits("11111111"))), isLeaf: true}, - }, - }, - }, - }, - } - if !testutil.DeepEqual(tr.root, want.root) { - t.Fatalf("got:\n%swant:\n%s", pretty(tr), pretty(want)) - } - - tr.Delete(bits("11111100")) - tr.RootHash() - want = &Tree{ - root: &node{ - key: bools("1111"), - hash: hashPtr(hashForNonLeaf(hashForLeaf(bits("11110000")), hashForLeaf(bits("11111111")))), - children: [2]*node{ - {key: bools("11110000"), hash: hashPtr(hashForLeaf(bits("11110000"))), isLeaf: true}, - {key: bools("11111111"), hash: hashPtr(hashForLeaf(bits("11111111"))), isLeaf: true}, - }, - }, - } - if !testutil.DeepEqual(tr.root, want.root) { - t.Fatalf("got:\n%swant:\n%s", pretty(tr), pretty(want)) - } - - tr.Delete(bits("11110011")) // nonexistent value - tr.RootHash() - if !testutil.DeepEqual(tr.root, want.root) { - t.Fatalf("got:\n%swant:\n%s", pretty(tr), pretty(want)) - } - - tr.Delete(bits("11110000")) - tr.RootHash() - want = &Tree{ - root: &node{key: bools("11111111"), hash: hashPtr(hashForLeaf(bits("11111111"))), isLeaf: true}, - } - if !testutil.DeepEqual(tr.root, want.root) { - t.Fatalf("got:\n%swant:\n%s", pretty(tr), pretty(want)) - } - - tr.Delete(bits("11111111")) - tr.RootHash() - want = &Tree{} - if !testutil.DeepEqual(tr.root, want.root) { - t.Fatalf("got:\n%swant:\n%s", pretty(tr), pretty(want)) - } -} - -func TestDeletePrefix(t *testing.T) { - root := &node{ - key: bools("111111"), - hash: hashPtr(hashForNonLeaf(hashForLeaf(bits("11111110")), hashForLeaf(bits("11111111")))), - children: [2]*node{ - {key: bools("11111110"), hash: hashPtr(hashForLeaf(bits("11111110"))), isLeaf: true}, - {key: bools("11111111"), hash: hashPtr(hashForLeaf(bits("11111111"))), isLeaf: true}, - }, - } - - got := delete(root, bools("111111")) - got.calcHash() - if !testutil.DeepEqual(got, root) { - t.Fatalf("got:\n%swant:\n%s", prettyNode(got, 0), prettyNode(root, 0)) - } -} - -func TestBoolKey(t *testing.T) { - cases := []struct { - b []byte - w []uint8 - }{{ - b: nil, - w: []uint8{}, - }, { - b: []byte{0x8f}, - w: []uint8{1, 0, 0, 0, 1, 1, 1, 1}, - }, { - b: []byte{0x81}, - w: []uint8{1, 0, 0, 0, 0, 0, 0, 1}, - }} - - for _, c := range cases { - g := bitKey(c.b) - - if !testutil.DeepEqual(g, c.w) { - t.Errorf("Key(0x%x) = %v want %v", c.b, g, c.w) - } - } -} - -func TestByteKey(t *testing.T) { - cases := []struct { - b []uint8 - w []byte - }{{ - b: []uint8{}, - w: []byte{}, - }, { - b: []uint8{1, 0, 0, 0, 1, 1, 1, 1}, - w: []byte{0x8f}, - }, { - b: []uint8{1, 0, 0, 0, 0, 0, 0, 1}, - w: []byte{0x81}, - }, { - b: []uint8{1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1}, - w: []byte{0x81, 0x8f}, - }} - - for _, c := range cases { - g := byteKey(c.b) - - if !testutil.DeepEqual(g, c.w) { - t.Errorf("byteKey(%#v) = %x want %x", c.b, g, c.w) - } - } -} - -func pretty(t *Tree) string { - if t.root == nil { - return "" - } - return prettyNode(t.root, 0) -} - -func prettyNode(n *node, depth int) string { - prettyStr := strings.Repeat(" ", depth) - if n == nil { - prettyStr += "nil\n" - return prettyStr - } - var b int - if len(n.key) > 31*8 { - b = 31 * 8 - } - prettyStr += fmt.Sprintf("key=%+v", n.key[b:]) - if n.hash != nil { - prettyStr += fmt.Sprintf(" hash=%+v", n.hash) - } - prettyStr += "\n" - - for _, c := range n.children { - if c != nil { - prettyStr += prettyNode(c, depth+1) - } - } - - return prettyStr -} - -func bits(lit string) []byte { - var b [31]byte - n, _ := strconv.ParseUint(lit, 2, 8) - return append(b[:], byte(n)) -} - -func bools(lit string) []uint8 { - b := bitKey(bits(lit)) - return append(b[:31*8], b[32*8-len(lit):]...) -} - -func hashForLeaf(item []byte) bc.Hash { - return bc.NewHash(sha3.Sum256(append([]byte{0x00}, item...))) -} - -func hashForNonLeaf(a, b bc.Hash) bc.Hash { - d := []byte{0x01} - d = append(d, a.Bytes()...) - d = append(d, b.Bytes()...) - return bc.NewHash(sha3.Sum256(d)) -} - -func hashPtr(h bc.Hash) *bc.Hash { - return &h -} diff --git a/protocol/state/utxo_view.go b/protocol/state/utxo_view.go index ddd5141b..c9e20152 100644 --- a/protocol/state/utxo_view.go +++ b/protocol/state/utxo_view.go @@ -4,6 +4,7 @@ import ( "errors" "github.com/bytom/blockchain/txdb/storage" + "github.com/bytom/consensus" "github.com/bytom/protocol/bc" ) @@ -33,6 +34,9 @@ func (view *UtxoViewpoint) ApplyTransaction(block *bc.Block, tx *bc.Tx) error { if entry.Spent { return errors.New("utxo has been spent") } + if entry.IsCoinBase && entry.BlockHeight+consensus.CoinbasePendingBlockNumber > block.Height { + return errors.New("coinbase utxo is not ready for use") + } entry.SpendOutput() } diff --git a/protocol/state/utxo_view_test.go b/protocol/state/utxo_view_test.go index 296cbbb8..237a4f83 100644 --- a/protocol/state/utxo_view_test.go +++ b/protocol/state/utxo_view_test.go @@ -71,6 +71,62 @@ func TestApplyBlock(t *testing.T) { }, err: false, }, + { + block: &bc.Block{ + BlockHeader: &bc.BlockHeader{ + Height: 7, + }, + Transactions: []*bc.Tx{ + &bc.Tx{ + TxHeader: &bc.TxHeader{ + ResultIds: []*bc.Hash{}, + }, + SpentOutputIDs: []bc.Hash{ + bc.Hash{V0: 0}, + }, + }, + }, + }, + inputView: &UtxoViewpoint{ + Entries: map[bc.Hash]*storage.UtxoEntry{ + bc.Hash{V0: 0}: storage.NewUtxoEntry(true, 0, false), + }, + }, + fetchView: &UtxoViewpoint{ + Entries: map[bc.Hash]*storage.UtxoEntry{ + bc.Hash{V0: 0}: storage.NewUtxoEntry(true, 0, true), + }, + }, + err: false, + }, + { + block: &bc.Block{ + BlockHeader: &bc.BlockHeader{ + Height: 0, + }, + Transactions: []*bc.Tx{ + &bc.Tx{ + TxHeader: &bc.TxHeader{ + ResultIds: []*bc.Hash{}, + }, + SpentOutputIDs: []bc.Hash{ + bc.Hash{V0: 0}, + }, + }, + }, + }, + inputView: &UtxoViewpoint{ + Entries: map[bc.Hash]*storage.UtxoEntry{ + bc.Hash{V0: 0}: storage.NewUtxoEntry(true, 0, false), + }, + }, + fetchView: &UtxoViewpoint{ + Entries: map[bc.Hash]*storage.UtxoEntry{ + bc.Hash{V0: 0}: storage.NewUtxoEntry(true, 0, true), + }, + }, + err: true, + }, } for i, c := range cases { diff --git a/protocol/vm/vmutil/script.go b/protocol/vm/vmutil/script.go index 2e5627f9..a3fcd1a1 100644 --- a/protocol/vm/vmutil/script.go +++ b/protocol/vm/vmutil/script.go @@ -35,20 +35,10 @@ func (b *Builder) addP2SPMultiSig(pubkeys []ed25519.PublicKey, nrequired int) er return nil } -// CoinbaseProgram generates the script for contorl coinbase output -func CoinbaseProgram(pubkeys []ed25519.PublicKey, nrequired int, height uint64) ([]byte, error) { +// DefaultCoinbaseProgram generates the script for contorl coinbase output +func DefaultCoinbaseProgram() ([]byte, error) { builder := NewBuilder() - builder.AddOp(vm.OP_BLOCKHEIGHT) - builder.AddInt64(int64(height)) - builder.AddOp(vm.OP_GREATERTHAN) - builder.AddOp(vm.OP_VERIFY) - - if nrequired == 0 { - return builder.Build() - } - if err := builder.addP2SPMultiSig(pubkeys, nrequired); err != nil { - return nil, err - } + builder.AddOp(vm.OP_TRUE) return builder.Build() }