OSDN Git Service

feat(bcrp): implement wallet api for BCRP (#1916)
authorDeKaiju <longjinglv@gmail.com>
Sun, 25 Apr 2021 03:47:57 +0000 (11:47 +0800)
committerGitHub <noreply@github.com>
Sun, 25 Apr 2021 03:47:57 +0000 (11:47 +0800)
* feat(bcrp): implement wallet api for BCRP

* refactor(bcrp): code refactoring

* refactor(bcrp): code refactoring

Co-authored-by: Paladz <yzhu101@uottawa.ca>
api/api.go
api/contract.go [new file with mode: 0644]
api/errors.go
contract/contract.go [new file with mode: 0644]
node/node.go
protocol/vm/vmutil/script.go
test/wallet_test_util.go
wallet/wallet.go
wallet/wallet_test.go

index 533b45a..ab4dc87 100644 (file)
@@ -257,6 +257,11 @@ func (a *API) buildHandler() {
        m.Handle("/delete-access-token", jsonHandler(a.deleteAccessToken))
        m.Handle("/check-access-token", jsonHandler(a.checkAccessToken))
 
+       m.Handle("/create-contract", jsonHandler(a.createContract))
+       m.Handle("/update-contract-alias", jsonHandler(a.updateContractAlias))
+       m.Handle("/get-contract", jsonHandler(a.getContract))
+       m.Handle("/list-contracts", jsonHandler(a.listContracts))
+
        m.Handle("/create-transaction-feed", jsonHandler(a.createTxFeed))
        m.Handle("/get-transaction-feed", jsonHandler(a.getTxFeed))
        m.Handle("/update-transaction-feed", jsonHandler(a.updateTxFeed))
diff --git a/api/contract.go b/api/contract.go
new file mode 100644 (file)
index 0000000..e1e9baf
--- /dev/null
@@ -0,0 +1,107 @@
+package api
+
+import (
+       "context"
+       "strings"
+
+       "github.com/bytom/bytom/contract"
+       "github.com/bytom/bytom/crypto/sha3pool"
+       chainjson "github.com/bytom/bytom/encoding/json"
+       "github.com/bytom/bytom/errors"
+       "github.com/bytom/bytom/protocol/vm/vmutil"
+)
+
+// pre-define errors for supporting bytom errorFormatter
+var (
+       ErrNullContract      = errors.New("contract is empty")
+       ErrNullContractID    = errors.New("contract id is empty")
+       ErrNullContractAlias = errors.New("contract alias is empty")
+)
+
+// POST /create-asset
+func (a *API) createContract(_ context.Context, ins struct {
+       Alias    string             `json:"alias"`
+       Contract chainjson.HexBytes `json:"contract"`
+}) Response {
+       ins.Alias = strings.TrimSpace(ins.Alias)
+       if ins.Alias == "" {
+               return NewErrorResponse(ErrNullContractAlias)
+       }
+
+       if ins.Contract == nil {
+               return NewErrorResponse(ErrNullContract)
+       }
+
+       var hash [32]byte
+       sha3pool.Sum256(hash[:], ins.Contract)
+
+       registerProgram, err := vmutil.RegisterProgram(ins.Contract)
+       if err != nil {
+               return NewErrorResponse(err)
+       }
+
+       callProgram, err := vmutil.CallContractProgram(hash[:])
+       if err != nil {
+               return NewErrorResponse(err)
+       }
+
+       c := &contract.Contract{
+               Hash:            hash[:],
+               Alias:           ins.Alias,
+               Contract:        ins.Contract,
+               CallProgram:     callProgram,
+               RegisterProgram: registerProgram,
+       }
+       if err := a.wallet.ContractReg.SaveContract(c); err != nil {
+               return NewErrorResponse(err)
+       }
+
+       return NewSuccessResponse(c)
+}
+
+// POST /update-contract-alias
+func (a *API) updateContractAlias(_ context.Context, ins struct {
+       ID    chainjson.HexBytes `json:"id"`
+       Alias string             `json:"alias"`
+}) Response {
+       if ins.ID == nil {
+               return NewErrorResponse(ErrNullContractID)
+       }
+
+       ins.Alias = strings.TrimSpace(ins.Alias)
+       if ins.Alias == "" {
+               return NewErrorResponse(ErrNullContractAlias)
+       }
+
+       if err := a.wallet.ContractReg.UpdateContract(ins.ID, ins.Alias); err != nil {
+               return NewErrorResponse(err)
+       }
+
+       return NewSuccessResponse(nil)
+}
+
+// POST /get-contract
+func (a *API) getContract(_ context.Context, ins struct {
+       ID chainjson.HexBytes `json:"id"`
+}) Response {
+       if ins.ID == nil {
+               return NewErrorResponse(ErrNullContractID)
+       }
+
+       c, err := a.wallet.ContractReg.GetContract(ins.ID)
+       if err != nil {
+               return NewErrorResponse(err)
+       }
+
+       return NewSuccessResponse(c)
+}
+
+// POST /list-contracts
+func (a *API) listContracts(_ context.Context) Response {
+       cs, err := a.wallet.ContractReg.ListContracts()
+       if err != nil {
+               return NewErrorResponse(err)
+       }
+
+       return NewSuccessResponse(cs)
+}
index 41c498b..0cc6136 100644 (file)
@@ -9,6 +9,7 @@ import (
        "github.com/bytom/bytom/blockchain/rpc"
        "github.com/bytom/bytom/blockchain/signers"
        "github.com/bytom/bytom/blockchain/txbuilder"
+       "github.com/bytom/bytom/contract"
        "github.com/bytom/bytom/errors"
        "github.com/bytom/bytom/net/http/httperror"
        "github.com/bytom/bytom/net/http/httpjson"
@@ -51,8 +52,10 @@ var respErrFormatter = map[error]httperror.Info{
        signers.ErrDupeXPub:  {400, "BTM203", "Root XPubs cannot contain the same key more than once"},
 
        // Contract error namespace (3xx)
-       ErrCompileContract: {400, "BTM300", "Compile contract failed"},
-       ErrInstContract:    {400, "BTM301", "Instantiate contract failed"},
+       ErrCompileContract:             {400, "BTM300", "Compile contract failed"},
+       ErrInstContract:                {400, "BTM301", "Instantiate contract failed"},
+       contract.ErrContractDuplicated: {400, "BTM302", "Contract is duplicated"},
+       contract.ErrContractNotFound:   {400, "BTM303", "Contract not found"},
 
        // Transaction error namespace (7xx)
        // Build transaction error namespace (70x ~ 72x)
diff --git a/contract/contract.go b/contract/contract.go
new file mode 100644 (file)
index 0000000..53988e1
--- /dev/null
@@ -0,0 +1,112 @@
+package contract
+
+import (
+       "encoding/json"
+       "sync"
+
+       dbm "github.com/bytom/bytom/database/leveldb"
+       chainjson "github.com/bytom/bytom/encoding/json"
+       "github.com/bytom/bytom/errors"
+)
+
+var (
+       userContractPrefix = []byte("UC:")
+)
+
+// pre-define errors for supporting bytom errorFormatter
+var (
+       ErrContractDuplicated = errors.New("contract is duplicated")
+       ErrContractNotFound   = errors.New("contract not found")
+)
+
+// userContractKey return user contract key
+func userContractKey(hash chainjson.HexBytes) []byte {
+       return append(userContractPrefix, hash[:]...)
+}
+
+// Registry tracks and stores all user contract.
+type Registry struct {
+       db         dbm.DB
+       contractMu sync.Mutex
+}
+
+//NewRegistry create new registry
+func NewRegistry(db dbm.DB) *Registry {
+       return &Registry{
+               db: db,
+       }
+}
+
+//Contract describe user contract
+type Contract struct {
+       Hash            chainjson.HexBytes `json:"id"`
+       Alias           string             `json:"alias"`
+       Contract        chainjson.HexBytes `json:"contract"`
+       CallProgram     chainjson.HexBytes `json:"call_program"`
+       RegisterProgram chainjson.HexBytes `json:"register_program"`
+}
+
+// SaveContract save user contract
+func (reg *Registry) SaveContract(contract *Contract) error {
+       reg.contractMu.Lock()
+       defer reg.contractMu.Unlock()
+
+       contractKey := userContractKey(contract.Hash)
+       if existContract := reg.db.Get(contractKey); existContract != nil {
+               return ErrContractDuplicated
+       }
+
+       rawContract, err := json.Marshal(contract)
+       if err != nil {
+               return err
+       }
+
+       reg.db.Set(contractKey, rawContract)
+       return nil
+}
+
+//UpdateContract updates user contract alias
+func (reg *Registry) UpdateContract(hash chainjson.HexBytes, alias string) error {
+       reg.contractMu.Lock()
+       defer reg.contractMu.Unlock()
+
+       contract, err := reg.GetContract(hash)
+       if err != nil {
+               return err
+       }
+
+       contract.Alias = alias
+       rawContract, err := json.Marshal(contract)
+       if err != nil {
+               return err
+       }
+
+       reg.db.Set(userContractKey(hash), rawContract)
+       return nil
+}
+
+// GetContract get user contract
+func (reg *Registry) GetContract(hash chainjson.HexBytes) (*Contract, error) {
+       contract := &Contract{}
+       if rawContract := reg.db.Get(userContractKey(hash)); rawContract != nil {
+               return contract, json.Unmarshal(rawContract, contract)
+       }
+       return nil, ErrContractNotFound
+}
+
+// ListContracts returns user contracts
+func (reg *Registry) ListContracts() ([]*Contract, error) {
+       contracts := []*Contract{}
+       contractIter := reg.db.IteratorPrefix(userContractPrefix)
+       defer contractIter.Release()
+
+       for contractIter.Next() {
+               contract := &Contract{}
+               if err := json.Unmarshal(contractIter.Value(), contract); err != nil {
+                       return nil, err
+               }
+
+               contracts = append(contracts, contract)
+       }
+       return contracts, nil
+}
index c39df69..b94d69f 100644 (file)
@@ -21,6 +21,7 @@ import (
        "github.com/bytom/bytom/blockchain/txfeed"
        cfg "github.com/bytom/bytom/config"
        "github.com/bytom/bytom/consensus"
+       "github.com/bytom/bytom/contract"
        "github.com/bytom/bytom/database"
        dbm "github.com/bytom/bytom/database/leveldb"
        "github.com/bytom/bytom/env"
@@ -107,7 +108,8 @@ func NewNode(config *cfg.Config) *Node {
                walletDB := dbm.NewDB("wallet", config.DBBackend, config.DBDir())
                accounts = account.NewManager(walletDB, chain)
                assets = asset.NewRegistry(walletDB, chain)
-               wallet, err = w.NewWallet(walletDB, accounts, assets, hsm, chain, dispatcher, config.Wallet.TxIndex)
+               contracts := contract.NewRegistry(walletDB)
+               wallet, err = w.NewWallet(walletDB, accounts, assets, contracts, hsm, chain, dispatcher, config.Wallet.TxIndex)
                if err != nil {
                        log.WithFields(log.Fields{"module": logModule, "error": err}).Error("init NewWallet")
                }
index 709ee89..e7999bf 100644 (file)
@@ -83,11 +83,11 @@ func RegisterProgram(contract []byte) ([]byte, error) {
 
 // CallContractProgram generates the script for control contract output
 // follow BCRP(bytom contract register protocol)
-func CallContractProgram(contractID []byte) ([]byte, error) {
+func CallContractProgram(hash []byte) ([]byte, error) {
        builder := NewBuilder()
        builder.AddOp(vm.OP_1)
        builder.AddOp(vm.OP_PUSHDATA1)
-       builder.AddData(contractID)
+       builder.AddData(hash)
        return builder.Build()
 }
 
index b28313f..79f080f 100644 (file)
@@ -11,6 +11,7 @@ import (
        "github.com/bytom/bytom/asset"
        "github.com/bytom/bytom/blockchain/pseudohsm"
        "github.com/bytom/bytom/blockchain/signers"
+       "github.com/bytom/bytom/contract"
        "github.com/bytom/bytom/crypto/ed25519/chainkd"
        dbm "github.com/bytom/bytom/database/leveldb"
        "github.com/bytom/bytom/event"
@@ -259,8 +260,9 @@ func (cfg *walletTestConfig) Run() error {
        walletDB := dbm.NewDB("wallet", "leveldb", path.Join(dirPath, "wallet_db"))
        accountManager := account.NewManager(walletDB, chain)
        assets := asset.NewRegistry(walletDB, chain)
+       contracts := contract.NewRegistry(walletDB)
        dispatcher := event.NewDispatcher()
-       wallet, err := w.NewWallet(walletDB, accountManager, assets, hsm, chain, dispatcher, false)
+       wallet, err := w.NewWallet(walletDB, accountManager, assets, contracts, hsm, chain, dispatcher, false)
        if err != nil {
                return err
        }
index c3fba83..06fcbb2 100644 (file)
@@ -9,6 +9,7 @@ import (
        "github.com/bytom/bytom/account"
        "github.com/bytom/bytom/asset"
        "github.com/bytom/bytom/blockchain/pseudohsm"
+       "github.com/bytom/bytom/contract"
        dbm "github.com/bytom/bytom/database/leveldb"
        "github.com/bytom/bytom/errors"
        "github.com/bytom/bytom/event"
@@ -48,6 +49,7 @@ type Wallet struct {
        TxIndexFlag     bool
        AccountMgr      *account.Manager
        AssetReg        *asset.Registry
+       ContractReg     *contract.Registry
        Hsm             *pseudohsm.HSM
        chain           *protocol.Chain
        RecoveryMgr     *recoveryManager
@@ -58,11 +60,12 @@ type Wallet struct {
 }
 
 //NewWallet return a new wallet instance
-func NewWallet(walletDB dbm.DB, account *account.Manager, asset *asset.Registry, hsm *pseudohsm.HSM, chain *protocol.Chain, dispatcher *event.Dispatcher, txIndexFlag bool) (*Wallet, error) {
+func NewWallet(walletDB dbm.DB, account *account.Manager, asset *asset.Registry, contract *contract.Registry, hsm *pseudohsm.HSM, chain *protocol.Chain, dispatcher *event.Dispatcher, txIndexFlag bool) (*Wallet, error) {
        w := &Wallet{
                DB:              walletDB,
                AccountMgr:      account,
                AssetReg:        asset,
+               ContractReg:     contract,
                chain:           chain,
                Hsm:             hsm,
                RecoveryMgr:     newRecoveryManager(walletDB, account),
index 5c87fa8..211cb10 100644 (file)
@@ -15,6 +15,7 @@ import (
        "github.com/bytom/bytom/blockchain/txbuilder"
        "github.com/bytom/bytom/config"
        "github.com/bytom/bytom/consensus"
+       "github.com/bytom/bytom/contract"
        "github.com/bytom/bytom/crypto/ed25519/chainkd"
        "github.com/bytom/bytom/database"
        dbm "github.com/bytom/bytom/database/leveldb"
@@ -290,6 +291,7 @@ func TestMemPoolTxQueryLoop(t *testing.T) {
        controlProg.KeyIndex = 1
 
        reg := asset.NewRegistry(testDB, chain)
+       contractReg := contract.NewRegistry(testDB)
        asset, err := reg.Define([]chainkd.XPub{xpub1.XPub}, 1, nil, 0, "TESTASSET", nil)
        if err != nil {
                t.Fatal(err)
@@ -308,7 +310,7 @@ func TestMemPoolTxQueryLoop(t *testing.T) {
 
        tx := types.NewTx(*txData)
        //block := mockSingleBlock(tx)
-       w, err := NewWallet(testDB, accountManager, reg, hsm, chain, dispatcher, false)
+       w, err := NewWallet(testDB, accountManager, reg, contractReg, hsm, chain, dispatcher, false)
        go w.memPoolTxQueryLoop()
        w.eventDispatcher.Post(protocol.TxMsgEvent{TxMsg: &protocol.TxPoolMsg{TxDesc: &protocol.TxDesc{Tx: tx}, MsgType: protocol.MsgNewTx}})
        time.Sleep(time.Millisecond * 10)