diff --git a/.gitignore b/.gitignore index 484a31e06..d34b0e5f9 100644 --- a/.gitignore +++ b/.gitignore @@ -21,7 +21,7 @@ _cgo_export.* _testmain.go -cmd/bytom/bytom +cmd/bytomd/bytomd cmd/bytomcli/bytomcli cmd/bytom/.bytom diff --git a/Dockerfile b/Dockerfile index a9d216ba4..a384d00ce 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,15 +4,14 @@ FROM golang:1.9-alpine as builder RUN apk add --no-cache make git ADD . /go/src/github.com/bytom -RUN cd /go/src/github.com/bytom && make install && cd ./cmd/bytom && go build && cd ../bytomcli && go build - +RUN cd /go/src/github.com/bytom && make install && cd ./cmd/bytomd && go build && cd ../bytomcli && go build # Pull Bytom into a second stage deploy alpine container FROM alpine:latest RUN apk add --no-cache ca-certificates -COPY --from=builder /go/src/github.com/bytom/cmd/bytom/bytom /usr/local/bin/ +COPY --from=builder /go/src/github.com/bytom/cmd/bytomd/bytomd /usr/local/bin/ COPY --from=builder /go/src/github.com/bytom/cmd/bytomcli/bytomcli /usr/local/bin/ EXPOSE 1999 46656 46657 -CMD ["bytom"] +CMD ["bytomd"] diff --git a/Makefile b/Makefile index 1a9fbf50e..7c591ddb3 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,11 @@ -GOTOOLS = github.com/mitchellh/gox \ +GOTOOLS = \ + github.com/mitchellh/gox \ github.com/Masterminds/glide PACKAGES = $(shell go list ./... | grep -v '/vendor/' | grep -v '/rpc/') all: install test -install: get_vendor_deps copy +install: get_vendor_deps @go install --ldflags '-extldflags "-static"' \ --ldflags "-X github.com/Bytom/blockchain/version.GitCommit=`git rev-parse HEAD`" ./node/ @echo "====> Done!" @@ -17,16 +18,8 @@ get_vendor_deps: ensure_tools ensure_tools: go get $(GOTOOLS) -# In case of the terrible network condition -copy: - @cp -r vendor/github.com/golang/crypto vendor/golang.org/x/crypto - @cp -r vendor/github.com/golang/net vendor/golang.org/x/net - @cp -r vendor/github.com/golang/text vendor/golang.org/x/text - @cp -r vendor/github.com/golang/tools vendor/golang.org/x/tools - @cp -r vendor/github.com/golang/time vendor/golang.org/x/time - test: - @echo "=====> Running go test" + @echo "====> Running go test" @go test $(PACKAGES) -.PHONY: install get_vendor_deps ensure_tools copy test +.PHONY: install get_vendor_deps ensure_tools test diff --git a/README.md b/README.md index f30e6c860..185fd45af 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ Bytom ===== -[![Build Status](https://travis-ci.org/Bytom/bytom.png)](https://travis-ci.org/Bytom/bytom) +[![Build Status](https://travis-ci.org/Bytom/bytom.svg)](https://travis-ci.org/Bytom/bytom) [![AGPL v3](https://img.shields.io/badge/license-AGPL%20v3-brightgreen.svg)](./LICENSE) ## Table of Contents @@ -14,9 +14,13 @@ Bytom * [Build](#build) * [Example](#example) * [Create and launch a single node](#create-and-launch-a-single-node) + * [Create an account](#create-an-account) + * [Create an asset](#create-an-asset) * [Asset issuance test](#asset-issuance-test) + * [Expenditure test](#expenditure-test) * [Set up a wallet and manage the key](#set-up-a-wallet-and-manage-the-key) * [Multiple node](#multiple-node) +* [Running Bytom in Docker](#running-bytom-in-docker) * [Contributing](#contributing) * [License](#license) @@ -30,6 +34,7 @@ In the current state `bytom` is able to: - Issue assets - Manage account as well as asset +- Spend assets ## Build from source @@ -76,7 +81,7 @@ Currently, bytom is still in active development and a ton of work needs to be do ### Create and launch a single node -When successfully building the project, the `bytom` and `bytomcli` binary should be present in `cmd/bytom/bytom` and `cmd/bytomcli/bytomcli`, respectively. The next step is to initialize the node: +When successfully building the project, the `bytom` and `bytomcli` binary should be present in `cmd/bytomd` and `cmd/bytomcli` directory, respectively. The next step is to initialize the node: ```bash $ cd ./cmd/bytomd @@ -89,8 +94,6 @@ After that, you'll see `.bytom` generated in current directory, then launch the $ ./bytomd node --wallet.enable ``` -#### Asset issuance test - Given the `bytom` node is running, the general workflow is as follows: - create an account @@ -98,45 +101,83 @@ Given the `bytom` node is running, the general workflow is as follows: - create/sign/submit a transaction to transfer an asset - query the assets on-chain + +#### Create an account + Create an account named `alice`: ```bash $ ./bytomcli create-account alice xprv: -responses:{acc04K9MCFBG0A04 alice [0xc4200966e0] 1 0xc4204be220} +responses: account id: ``` +Check out the new created account: + +```bash +$ ./bytomcli list-accounts +``` + +#### Create an asset + Create an asset named `gold`: ```bash $ ./bytomcli create-asset gold xprv: -xpub:[98 55 100 48 102 100 53 101 54 55 55 49 48 52 97 100 50 100 51 51 98 49 56 98 98 100 55 55 50 51 98 53 102 51 101 97 56 55 49 52 48 53 57 54 50 56 55 48 49 97 50 99 97 100 101 51 51 102 100 100 97 53 56 51 49 54 97 50 57 54 101 49 102 100 48 102 53 57 99 55 50 49 53 98 50 54 55 102 50 52 102 52 54 50 48 101 51 48 102 55 99 51 50 56 49 102 97 52 99 55 97 53 102 50 57 97 100 53 51 100 56 100 55 56 50 50 98 98] -responses:[{{4131000809721133708 15036469059929217352 9712753415038655527 16992088508821480533} gold [118 107 170 32 152 106 231 249 212 15 215 121 94 191 102 23 231 61 38 211 121 176 221 199 48 173 145 207 243 201 82 0 215 2 72 243 81 81 173 105 108 0 192] [0xc420020850] 1 0xc4204c1960 0xc4204c1980 true}] +xpub: +responses: asset id: ``` -Now we can transafer `` gold to `alice` using a single command `sub-create-issue-tx`: +Check out the new created asset: + +```bash +$ ./bytomcli list-assets +``` + +#### Asset issuance test + +Since the account `alice` and the asset `gold` are ready, we need to create another account `bob`, which is also neccessary for the following Expenditure test: + +```bash +$ ./bytomcli create-account bob +``` + +Firstly, Alice issue ``, e.g., 10000, `gold`: ```bash -$ ./bytomcli sub-create-issue-tx -To build transaction: ------------tpl:{version:1 serialized_size:314 result_ids:<71c3b949750c887e466422007cdd1a6a9f3449e3bacd43307e361e84d76fe37b> data:<130994550772:/* unknown wire type 7 */ 1642:/* unknown wire type 7 */ 10:17681930801800169409 159728:7652 9:4897805654558278394 9:/* unexpected EOF */ >min_time_ms:1506587706078 max_time_ms:1506588006078 [0xc4204c9060 0xc4204c91e0] true false} -----------tpl transaction:version:1 serialized_size:314 result_ids:<71c3b949750c887e466422007cdd1a6a9f3449e3bacd43307e361e84d76fe37b> data:<130994550772:/* unknown wire type 7 */ 1642:/* unknown wire type 7 */ 10:17681930801800169409 159728:7652 9:4897805654558278394 9:/* unexpected EOF */ >min_time_ms:1506587706078 max_time_ms:1506588006078 -----------btm inputs:&{1 [123 125] asset_id:amount:1470000000000000000 [] []} -----------issue inputs:&{1 [] 0xc4204c4120 [] []} -xprv_asset:a89d5d5fa68af8ca8408d405db180bc5b2652d7f34bca753531861be3c1cbb6216a296e1fd0f59c7215b267f24f4620e30f7c3281fa4c7a5f29ad53d8d7822bb -sign tpl:{version:1 serialized_size:314 result_ids:<71c3b949750c887e466422007cdd1a6a9f3449e3bacd43307e361e84d76fe37b> data:<130994550772:/* unknown wire type 7 */ 1642:/* unknown wire type 7 */ 10:17681930801800169409 159728:7652 9:4897805654558278394 9:/* unexpected EOF */ >min_time_ms:1506587706078 max_time_ms:1506588006078 [0xc4204c9060 0xc4204c91e0] true false} -sign tpl's SigningInstructions:&{0 [0xc420010670]} -SigningInstructions's SignatureWitnesses:&{0 [] [32 254 83 225 251 124 27 13 126 32 0 93 132 151 197 166 125 64 222 168 154 133 219 122 187 130 169 176 160 166 8 49 145 174 135] []} -submit transaction:[map[id:cc4313fbae424bb945029adef193154f34de324316036e510bcc751d0013ccb7]] +$ ./bytomcli sub-create-issue-tx ``` -Query the assets on-chain: +When the transaction above is mined, query the balances: + +```bash +# Alice should have 10000 gold now +$ ./bytomcli list-balances +``` + +#### Expenditure test + +- Alice -> Bob + +Alice pays Bob ``, e.g., 1000, `gold`: + ```bash +$ ./bytomcli sub-spend-account-tx +# In our case, after Alice pays Bob 1000 gold, the amount of Alice's gold should be 9000, Bob's balances should be 1000 +$ ./bytomcli list-balances +``` + +- Bob -> Alice + +Bob pays Alice ``, e.g., 500, `gold`: + +```bash +$ ./bytomcli sub-spend-account-tx +# In our case, after Bob pays Alice 500 gold, the amount of Alice's gold should be 9500, Bob's balances should be 500 $ ./bytomcli list-balances -0 ----- map[:] ``` ### Set up a wallet and manage the key @@ -168,36 +209,16 @@ $ ./test.sh bytomd1 # Start the second node Then we have two nodes: ```bash -$ curl -X POST --data '{"jsonrpc":"2.0", "method": "net_info", "params":[], "id":"67"}' http://127.0.0.1:46657 -``` - -If everything goes well, we'll see the following response: - -```bash -{ - "jsonrpc": "2.0", - "id": "67", - "result": { - "listening": true, - "listeners": [ - "Listener(@192.168.199.178:3332)" - ], - "peers": [ - { - "node_info": { - "pub_key": "03571A5CE8B35E95E2357DB2823E9EB76EB42D5CCC5F8E68315388832878C011", - "moniker": "anonymous", - "network": "chain0", - "remote_addr": "127.0.0.1:51058", - "listen_addr": "192.168.199.178:3333", - "version": "0.1.0", - "other": [ - "wire_version=0.6.2", - "p2p_version=0.5.0", - "rpc_addr=tcp://0.0.0.0:46658" - ] - }, -...... +$ ./bytomcli net-info +net-info:map[listening:true listeners:[Listener(@192.168.199.43:3332)] peers:[map[node_info:map[listen_addr:192.168.199.43:3333 version:0.1.2 other:[wire_version=0.6.2 p2p_version=0.5.0] pub_key:D6B76D1B4E9D7E4D81BA5FAAE9359302446488495A29D7E70AF84CDFEA186D66 moniker:anonymous network:bytom remote_addr:127.0.0.1:51036] is_outbound:false connection_status:map[RecvMonitor:map[Start:2017-10-30T13:45:47.18+08:00 Bytes:425130 AvgRate:27010 Progress:0 Active:true Idle:1.04e+09 Samples:42 InstRate:4591 CurRate:3540 PeakRate:114908 BytesRem:0 TimeRem:0 Duration:1.574e+10] Channels:[map[RecentlySent:5332 ID:64 SendQueueCapacity:100 SendQueueSize:0 Priority:5]] SendMonitor:map[Active:true Idle:1.24e+09 Bytes:16240 Samples:41 CurRate:125 AvgRate:1032 Progress:0 Start:2017-10-30T13:45:47.18+08:00 Duration:1.574e+10 InstRate:147 PeakRate:4375 BytesRem:0 TimeRem:0]]]]] +``` + +## Running Bytom in Docker + +Ensure your [Docker](https://www.docker.com/) version is 17.05 or higher. + +```bash +$ docker build -t bytom . ``` ## Contributing diff --git a/blockchain/accesstokens.go b/blockchain/accesstokens.go index 733158e3f..e2014e989 100644 --- a/blockchain/accesstokens.go +++ b/blockchain/accesstokens.go @@ -6,7 +6,6 @@ import ( "github.com/bytom/blockchain/accesstoken" "github.com/bytom/errors" - "github.com/bytom/log" // "github.com/bytom/net/http/authz" "github.com/bytom/net/http/httpjson" ) @@ -24,7 +23,6 @@ func (bcr *BlockchainReactor) createAccessToken(ctx context.Context, x struct{ I return nil, errors.Wrap(err) } - log.Printf(ctx, "-------createAccessToken-------") if x.Type == "" { return token, nil @@ -81,7 +79,6 @@ func (bcr *BlockchainReactor) listAccessTokens(ctx context.Context, x requestQue return nil, err } - log.Printf(ctx, "--------listAccessTokens-------") outQuery := x outQuery.After = next @@ -103,7 +100,6 @@ func (bcr *BlockchainReactor) deleteAccessToken(ctx context.Context, x struct{ I return err } - log.Printf(ctx, "-------deleteAccessToken-------") /* err = a.sdb.Exec(ctx, a.deleteGrantsByAccessToken(x.ID)) if err != nil { diff --git a/blockchain/account/accounts.go b/blockchain/account/accounts.go index e466b9089..d208efdcb 100644 --- a/blockchain/account/accounts.go +++ b/blockchain/account/accounts.go @@ -2,24 +2,24 @@ package account import ( + "context" + "encoding/json" "fmt" "sync" "time" - "context" - "encoding/json" - "github.com/bytom/log" - "github.com/bytom/errors" - "github.com/bytom/protocol" - "github.com/bytom/blockchain/pin" "github.com/golang/groupcache/lru" - "github.com/bytom/crypto/sha3pool" - "github.com/bytom/protocol/vm/vmutil" + log "github.com/sirupsen/logrus" + dbm "github.com/tendermint/tmlibs/db" + + "github.com/bytom/blockchain/pin" "github.com/bytom/blockchain/signers" "github.com/bytom/blockchain/txbuilder" "github.com/bytom/crypto/ed25519/chainkd" - - dbm "github.com/tendermint/tmlibs/db" + "github.com/bytom/crypto/sha3pool" + "github.com/bytom/errors" + "github.com/bytom/protocol" + "github.com/bytom/protocol/vm/vmutil" ) const maxAccountCache = 1000 @@ -29,11 +29,11 @@ var ( ErrBadIdentifier = errors.New("either ID or alias must be specified, and not both") ) -func NewManager(db dbm.DB, chain *protocol.Chain , pinStore *pin.Store) *Manager { +func NewManager(db dbm.DB, chain *protocol.Chain, pinStore *pin.Store) *Manager { return &Manager{ - db: db, - chain: chain, - utxoDB: newReserver(db, chain), + db: db, + chain: chain, + utxoDB: newReserver(db, chain, pinStore), pinStore: pinStore, cache: lru.New(maxAccountCache), aliasCache: lru.New(maxAccountCache), @@ -43,10 +43,10 @@ func NewManager(db dbm.DB, chain *protocol.Chain , pinStore *pin.Store) *Manager // Manager stores accounts and their associated control programs. type Manager struct { - db dbm.DB - chain *protocol.Chain - utxoDB *reserver - indexer Saver + db dbm.DB + chain *protocol.Chain + utxoDB *reserver + indexer Saver pinStore *pin.Store cacheMu sync.Mutex @@ -72,12 +72,12 @@ func (m *Manager) ExpireReservations(ctx context.Context, period time.Duration) for { select { case <-ctx.Done(): - log.Printf(ctx, "Deposed, ExpireReservations exiting") + log.Info("Deposed, ExpireReservations exiting") return case <-ticks: err := m.utxoDB.ExpireReservations(ctx) if err != nil { - log.Error(ctx, err) + log.WithField("error", err).Error("Expire reservations") } } } @@ -91,16 +91,16 @@ type Account struct { // Create creates a new Account. func (m *Manager) Create(ctx context.Context, xpubs []chainkd.XPub, quorum int, alias string, tags map[string]interface{}, clientToken string) (*Account, error) { - //if ret := m.db.Get([]byte(alias));ret != nil { - //return nil,errors.New("alias already exists") - //} + if ret := m.db.Get(json.RawMessage("ali" + alias)); ret != nil { + return nil, errors.New(fmt.Sprintf("alias:%s already exists", alias)) + } accountSigner, err := signers.Create(ctx, m.db, "account", xpubs, quorum, clientToken) if err != nil { return nil, errors.Wrap(err) } - account_id := json.RawMessage(accountSigner.ID) + accountID := json.RawMessage(accountSigner.ID) account := &Account{ Signer: accountSigner, Alias: alias, @@ -112,8 +112,8 @@ func (m *Manager) Create(ctx context.Context, xpubs []chainkd.XPub, quorum int, return nil, errors.Wrap(err, "failed marshal account") } if len(acc) > 0 { - m.db.Set(account_id, acc) - m.db.Set(json.RawMessage("ali"+alias), account_id) + m.db.Set(accountID, acc) + m.db.Set(json.RawMessage("ali"+alias), accountID) } err = m.indexAnnotatedAccount(ctx, account) @@ -132,22 +132,22 @@ func (m *Manager) UpdateTags(ctx context.Context, id, alias *string, tags map[st return errors.Wrap(ErrBadIdentifier) } - var key_id []byte + var keyID []byte if alias != nil { - key_id = m.db.Get([]byte(*alias)) + keyID = m.db.Get(json.RawMessage("ali" + *alias)) } else { - key_id = json.RawMessage(*id) + keyID = json.RawMessage(*id) } - bytes := m.db.Get(key_id) + bytes := m.db.Get(keyID) if bytes == nil { - return errors.New("no exit this account.") + return errors.New("no exit this account") } var account Account err := json.Unmarshal(bytes, &account) if err != nil { - return errors.New("this account can't be unmarshal.") + return errors.New("this account can't be unmarshal") } for k, v := range tags { @@ -171,7 +171,7 @@ func (m *Manager) UpdateTags(ctx context.Context, id, alias *string, tags map[st } else { - m.db.Set(key_id, acc) + m.db.Set(keyID, acc) return nil } @@ -187,15 +187,7 @@ func (m *Manager) FindByAlias(ctx context.Context, alias string) (*signers.Signe if ok { accountID = cachedID.(string) } else { - /*const q = `SELECT account_id FROM accounts WHERE alias=$1` - err := m.db.QueryRowContext(ctx, q, alias).Scan(&accountID) - if err == stdsql.ErrNoRows { - return nil, errors.WithDetailf(pg.ErrUserInputNotFound, "alias: %s", alias) - } - if err != nil { - return nil, errors.Wrap(err) - }*/ - bytez := m.db.Get([]byte(fmt.Sprintf("alias_account:%v", alias))) + bytez := m.db.Get([]byte("ali" + alias)) accountID = string(bytez[:]) m.cacheMu.Lock() m.aliasCache.Add(alias, accountID) @@ -215,16 +207,15 @@ func (m *Manager) findByID(ctx context.Context, id string) (*signers.Signer, err bytes := m.db.Get(json.RawMessage(id)) if bytes == nil { - return nil,errors.New("not find this account.") + return nil, errors.New("not find this account.") } var account Account err := json.Unmarshal(bytes, &account) if err != nil { - return nil,errors.New("failed unmarshal this account.") + return nil, errors.New("failed unmarshal this account.") } - m.cacheMu.Lock() m.cache.Add(id, account.Signer) m.cacheMu.Unlock() @@ -293,17 +284,18 @@ func (m *Manager) insertAccountControlProgram(ctx context.Context, progs ...*con var b32 [32]byte for _, p := range progs { - acp, err := json.Marshal(&struct{ + acp, err := json.Marshal(&struct { AccountID string KeyIndex uint64 ControlProgram []byte Change bool - ExpiresAt time.Time}{ - AccountID: p.accountID, - KeyIndex: p.keyIndex, - ControlProgram: p.controlProgram, - Change: p.change, - ExpiresAt: p.expiresAt}) + ExpiresAt time.Time + }{ + AccountID: p.accountID, + KeyIndex: p.keyIndex, + ControlProgram: p.controlProgram, + Change: p.change, + ExpiresAt: p.expiresAt}) if err != nil { return errors.Wrap(err, "failed marshal controlProgram") @@ -324,9 +316,9 @@ func (m *Manager) nextIndex(ctx context.Context) (uint64, error) { if m.acpIndexNext >= m.acpIndexCap { const incrby = 10000 // start 1,increments by 10,000 - if(m.acpIndexCap <= incrby){ + if m.acpIndexCap <= incrby { m.acpIndexCap = incrby + 1 - }else{ + } else { m.acpIndexCap += incrby } m.acpIndexNext = m.acpIndexCap - incrby @@ -340,12 +332,8 @@ func (m *Manager) nextIndex(ctx context.Context) (uint64, error) { func (m *Manager) QueryAll(ctx context.Context) (interface{}, error) { ret := make([]interface{}, 0) - iter := m.db.Iterator() + iter := m.db.IteratorPrefix([]byte("acc")) for iter.Next() { - key := string(iter.Key()) - if key[:3] != "acc" { - continue - } ret = append(ret, string(iter.Value())) } diff --git a/blockchain/account/builder.go b/blockchain/account/builder.go index ec015ceac..02d7d8891 100644 --- a/blockchain/account/builder.go +++ b/blockchain/account/builder.go @@ -8,9 +8,10 @@ import ( "github.com/bytom/blockchain/txbuilder" chainjson "github.com/bytom/encoding/json" "github.com/bytom/errors" - "github.com/bytom/log" "github.com/bytom/protocol/bc" "github.com/bytom/protocol/bc/legacy" + + log "github.com/sirupsen/logrus" ) func (m *Manager) NewSpendAction(amt bc.AssetAmount, accountID string, refData chainjson.Map, clientToken *string) txbuilder.Action { @@ -126,11 +127,11 @@ func (a *spendUTXOAction) Build(ctx context.Context, b *txbuilder.TemplateBuilde } b.OnRollback(canceler(ctx, a.accounts, res.ID)) - var acct *signers.Signer - if res.Source.AccountID == ""{ + var acct *signers.Signer + if res.Source.AccountID == "" { //TODO coinbase acct = &signers.Signer{} - }else{ + } else { acct, err = a.accounts.findByID(ctx, res.Source.AccountID) if err != nil { return err @@ -149,7 +150,7 @@ func canceler(ctx context.Context, m *Manager, rid uint64) func() { return func() { err := m.utxoDB.Cancel(ctx, rid) if err != nil { - log.Error(ctx, err) + log.WithField("error", err).Error("Best-effort cancellation attempt to put in txbuilder.BuildResult.Rollback") } } } diff --git a/blockchain/account/indexer.go b/blockchain/account/indexer.go index 48970cda6..052eb4103 100644 --- a/blockchain/account/indexer.go +++ b/blockchain/account/indexer.go @@ -1,18 +1,17 @@ package account import ( - "time" "context" "encoding/json" + "time" "github.com/bytom/blockchain/query" "github.com/bytom/blockchain/signers" "github.com/bytom/crypto/sha3pool" + chainjson "github.com/bytom/encoding/json" "github.com/bytom/errors" "github.com/bytom/protocol/bc" "github.com/bytom/protocol/bc/legacy" - - chainjson "github.com/bytom/encoding/json" ) const ( @@ -28,17 +27,17 @@ const ( ) type AccountUTXOs struct { - OutputID []byte - AssetID []byte - Amount int64 - AccountID string - CpIndex int64 - Program []byte - Confirmed int64 - SourceID []byte - SourcePos int64 - RefData []byte - Change bool + OutputID []byte + AssetID []byte + Amount uint64 + AccountID string + ProgramIndex uint64 + Program []byte + BlockHeight uint64 + SourceID []byte + SourcePos uint64 + RefData []byte + Change bool } var emptyJSONObject = json.RawMessage(`{}`) @@ -240,16 +239,16 @@ func (m *Manager) upsertConfirmedAccountOutputs(ctx context.Context, var au *AccountUTXOs for _, out := range outs { au = &AccountUTXOs{OutputID: out.OutputID.Bytes(), - AssetID: out.AssetId.Bytes(), - Amount: int64(out.Amount), - AccountID: out.AccountID, - CpIndex: int64(out.keyIndex), - Program: out.ControlProgram, - Confirmed: int64(block.Height), - SourceID: out.sourceID.Bytes(), - SourcePos: int64(out.sourcePos), - RefData: out.refData.Bytes(), - Change: out.change} + AssetID: out.AssetId.Bytes(), + Amount: out.Amount, + AccountID: out.AccountID, + ProgramIndex: out.keyIndex, + Program: out.ControlProgram, + BlockHeight: block.Height, + SourceID: out.sourceID.Bytes(), + SourcePos: out.sourcePos, + RefData: out.refData.Bytes(), + Change: out.change} accountutxo, err := json.Marshal(au) if err != nil { diff --git a/blockchain/account/reserve.go b/blockchain/account/reserve.go index f92710672..50a033c9f 100644 --- a/blockchain/account/reserve.go +++ b/blockchain/account/reserve.go @@ -1,22 +1,23 @@ package account import ( + "bytes" "context" - // "database/sql" + "encoding/json" "fmt" "sync" "sync/atomic" "time" - // "chain/core/pin" - //"github.com/blockchain/database/pg" + dbm "github.com/tendermint/tmlibs/db" + + "github.com/bytom/blockchain/pin" "github.com/bytom/consensus" "github.com/bytom/errors" "github.com/bytom/protocol" "github.com/bytom/protocol/bc" "github.com/bytom/protocol/bc/legacy" - //"github.com/blockchain/sync/idempotency" - dbm "github.com/tendermint/tmlibs/db" + "github.com/bytom/sync/idempotency" ) var ( @@ -74,11 +75,11 @@ type reservation struct { ClientToken *string } -func newReserver(db dbm.DB, c *protocol.Chain /*pinStore *pin.Store*/) *reserver { +func newReserver(db dbm.DB, c *protocol.Chain, pinStore *pin.Store) *reserver { return &reserver{ - c: c, - db: db, - //pinStore: pinStore, + c: c, + db: pinStore.DB, + pinStore: pinStore, reservations: make(map[uint64]*reservation), sources: make(map[source]*sourceReserver), } @@ -95,11 +96,11 @@ func newReserver(db dbm.DB, c *protocol.Chain /*pinStore *pin.Store*/) *reserver // reserver ensures idempotency of reservations until the reservation // expiration. type reserver struct { - c *protocol.Chain - db dbm.DB - //pinStore *pin.Store + c *protocol.Chain + db dbm.DB + pinStore *pin.Store nextReservationID uint64 - // idempotency idempotency.Group + idempotency idempotency.Group reservationsMu sync.Mutex reservations map[uint64]*reservation @@ -111,17 +112,15 @@ type reserver struct { // Reserve selects and reserves UTXOs according to the criteria provided // in source. The resulting reservation expires at exp. func (re *reserver) Reserve(ctx context.Context, src source, amount uint64, clientToken *string, exp time.Time) (*reservation, error) { - /* - if clientToken == nil { - return re.reserve(ctx, src, amount, clientToken, exp) - } - untypedRes, err := re.idempotency.Once(*clientToken, func() (interface{}, error) { - return re.reserve(ctx, src, amount, clientToken, exp) - }) - return untypedRes.(*reservation), err - */ - return re.reserve(ctx, src, amount, clientToken, exp) + if clientToken == nil { + return re.reserve(ctx, src, amount, clientToken, exp) + } + + untypedRes, err := re.idempotency.Once(*clientToken, func() (interface{}, error) { + return re.reserve(ctx, src, amount, clientToken, exp) + }) + return untypedRes.(*reservation), err } func (re *reserver) reserve(ctx context.Context, src source, amount uint64, clientToken *string, exp time.Time) (res *reservation, err error) { @@ -157,17 +156,14 @@ func (re *reserver) reserve(ctx context.Context, src source, amount uint64, clie // ReserveUTXO reserves a specific utxo for spending. The resulting // reservation expires at exp. func (re *reserver) ReserveUTXO(ctx context.Context, out bc.Hash, clientToken *string, exp time.Time) (*reservation, error) { - /* - if clientToken == nil { - return re.reserveUTXO(ctx, out, exp, nil) - } + if clientToken == nil { + return re.reserveUTXO(ctx, out, exp, nil) + } - untypedRes, err := re.idempotency.Once(*clientToken, func() (interface{}, error) { - return re.reserveUTXO(ctx, out, exp, clientToken) - }) - return untypedRes.(*reservation), err - */ - return re.reserveUTXO(ctx, out, exp, nil) + untypedRes, err := re.idempotency.Once(*clientToken, func() (interface{}, error) { + return re.reserveUTXO(ctx, out, exp, clientToken) + }) + return untypedRes.(*reservation), err } func (re *reserver) reserveUTXO(ctx context.Context, out bc.Hash, exp time.Time, clientToken *string) (*reservation, error) { @@ -265,8 +261,7 @@ func (re *reserver) source(src source) *sourceReserver { src: src, validFn: re.checkUTXO, heightFn: func() uint64 { - //return re.pinStore.Height(PinName) - return 0 + return re.pinStore.Height(PinName) }, cached: make(map[bc.Hash]*utxo), reserved: make(map[bc.Hash]uint64), @@ -398,65 +393,97 @@ func (sr *sourceReserver) refillCache(ctx context.Context) error { } func findMatchingUTXOs(ctx context.Context, db dbm.DB, src source, height uint64) ([]*utxo, error) { - /*const q = ` - SELECT output_id, amount, control_program_index, control_program, - source_id, source_pos, ref_data_hash - FROM account_utxos - WHERE account_id = $1 AND asset_id = $2 AND confirmed_in > $3 - `*/ - var utxos []*utxo - /*err := pg.ForQueryRows(ctx, db, q, src.AccountID, src.AssetID, height, - func(oid bc.Hash, amount uint64, cpIndex uint64, controlProg []byte, sourceID bc.Hash, sourcePos uint64, refData bc.Hash) { + + var ( + utxos []*utxo + au AccountUTXOs + rawOutputID [32]byte + rawSourceID [32]byte + rawRefData [32]byte + ) + + iter := db.IteratorPrefix([]byte("acu")) + for iter.Next() { + + if err := json.Unmarshal(iter.Value(), &au); err != nil { + return nil, errors.Wrap(err) + } + + if (au.AccountID == src.AccountID) && + (bytes.Equal(au.AssetID, src.AssetID.Bytes())) && + (au.BlockHeight > height) { + + copy(rawOutputID[:], au.OutputID) + copy(rawSourceID[:], au.SourceID) + copy(rawRefData[:], au.RefData) + utxos = append(utxos, &utxo{ - OutputID: oid, - SourceID: sourceID, + OutputID: bc.NewHash(rawOutputID), + SourceID: bc.NewHash(rawSourceID), AssetID: src.AssetID, - Amount: amount, - SourcePos: sourcePos, - ControlProgram: controlProg, - RefDataHash: refData, + Amount: au.Amount, + SourcePos: au.SourcePos, + ControlProgram: au.Program, + RefDataHash: bc.NewHash(rawRefData), AccountID: src.AccountID, - ControlProgramIndex: cpIndex, + ControlProgramIndex: au.ProgramIndex, }) - }) - if err != nil { - return nil, errors.Wrap(err) - }*/ + + } + + } + + if len(utxos) == 0 { + return nil, errors.New("can't match utxo") + } + return utxos, nil } func findSpecificUTXO(ctx context.Context, db dbm.DB, outHash bc.Hash) (*utxo, error) { - /*const q = ` - SELECT account_id, asset_id, amount, control_program_index, control_program, - source_id, source_pos, ref_data_hash - FROM account_utxos - WHERE output_id = $1 - `*/ u := new(utxo) + au := new(AccountUTXOs) - // TODO(oleg): maybe we need to scan txid:index too from here... - /*err := db.QueryRowContext(ctx, q, out).Scan( - &u.AccountID, - &u.AssetID, - &u.Amount, - &u.ControlProgramIndex, - &u.ControlProgram, - &u.SourceID, - &u.SourcePos, - &u.RefDataHash, - ) - if err == sql.ErrNoRows { - return nil, pg.ErrUserInputNotFound - } else if err != nil { - return nil, errors.Wrap(err) + //temp fix for coinbase UTXO isn't add to accountUTXO db, will be remove later + if outHash.String() == "73d1e97c7bcf2b084f936a40f4f2a72e909417f2b46699e8659fa4c4feddb98d" { + return genesisBlockUTXO(), nil } - u.OutputID = out - */ - if outHash.String() != "73d1e97c7bcf2b084f936a40f4f2a72e909417f2b46699e8659fa4c4feddb98d" { - return u, nil + // make sure accountUTXO existed in the db + accUTXOValue := db.Get(json.RawMessage("acu" + string(outHash.Bytes()))) + if accUTXOValue == nil { + return u, errors.New(fmt.Sprintf("can't find utxo: %s", outHash.String())) } + if err := json.Unmarshal(accUTXOValue, &au); err != nil { + return nil, errors.Wrap(err) + } + + rawOutputID := new([32]byte) + rawAssetID := new([32]byte) + rawSourceID := new([32]byte) + rawRefData := new([32]byte) + + copy(rawOutputID[:], au.OutputID) + copy(rawAssetID[:], au.AssetID) + copy(rawSourceID[:], au.SourceID) + copy(rawRefData[:], au.RefData) + + u.OutputID = bc.NewHash(*rawOutputID) + u.AccountID = au.AccountID + u.AssetID = bc.NewAssetID(*rawAssetID) + u.Amount = au.Amount + u.ControlProgramIndex = au.ProgramIndex + u.ControlProgram = au.Program + u.SourceID = bc.NewHash(*rawSourceID) + u.SourcePos = au.SourcePos + u.RefDataHash = bc.NewHash(*rawRefData) + return u, nil +} + +//temp fix for coinbase UTXO isn't add to accountUTXO db, will be remove later +func genesisBlockUTXO() *utxo { + u := new(utxo) genesisBlock := &legacy.Block{ BlockHeader: legacy.BlockHeader{}, Transactions: []*legacy.Tx{}, @@ -476,6 +503,5 @@ func findSpecificUTXO(ctx context.Context, db dbm.DB, outHash bc.Hash) (*utxo, e u.SourceID = *resOut.Source.Ref u.SourcePos = resOut.Source.Position u.RefDataHash = *resOut.Data - - return u, nil + return u } diff --git a/blockchain/accounts.go b/blockchain/accounts.go index dbd4b39c7..f6d56eac1 100644 --- a/blockchain/accounts.go +++ b/blockchain/accounts.go @@ -6,9 +6,10 @@ import ( "github.com/bytom/blockchain/account" "github.com/bytom/crypto/ed25519/chainkd" - "github.com/bytom/log" "github.com/bytom/net/http/httpjson" "github.com/bytom/net/http/reqid" + + log "github.com/sirupsen/logrus" ) // POST /create-account @@ -40,7 +41,7 @@ func (a *BlockchainReactor) createAccount(ctx context.Context, ins []struct { return } aa, err := account.Annotated(acc) - log.Printf(ctx, "-------createAccount-----Annotated accout:%v", aa) + log.WithField("account", aa).Info("Created account") if err != nil { responses[i] = err return @@ -50,7 +51,7 @@ func (a *BlockchainReactor) createAccount(ctx context.Context, ins []struct { } wg.Wait() - log.Printf(ctx, "-------createAccount-----%v", responses) + log.WithField("responses", responses).Info("Responses of created account") return responses } @@ -60,7 +61,7 @@ func (a *BlockchainReactor) updateAccountTags(ctx context.Context, ins []struct Alias *string Tags map[string]interface{} `json:"tags"` }) interface{} { - log.Printf(ctx, "-------update-account-tags---------") + log.Info("Updating account tags") responses := make([]interface{}, len(ins)) var wg sync.WaitGroup wg.Add(len(responses)) diff --git a/blockchain/asset/asset.go b/blockchain/asset/asset.go index 49df5a7f9..3b6311b57 100644 --- a/blockchain/asset/asset.go +++ b/blockchain/asset/asset.go @@ -5,11 +5,10 @@ import ( "encoding/json" "sync" - "golang.org/x/crypto/sha3" - "github.com/golang/groupcache/lru" "github.com/golang/groupcache/singleflight" dbm "github.com/tendermint/tmlibs/db" + "golang.org/x/crypto/sha3" "github.com/bytom/blockchain/signers" "github.com/bytom/crypto/ed25519" @@ -18,7 +17,6 @@ import ( "github.com/bytom/protocol" "github.com/bytom/protocol/bc" "github.com/bytom/protocol/vm/vmutil" - //"github.com/bytom/log" ) const maxAssetCache = 1000 @@ -138,16 +136,6 @@ func (reg *Registry) Define(ctx context.Context, xpubs []chainkd.XPub, quorum in reg.db.Set(asset_id, json.RawMessage(ass)) } - /* asset, err = reg.insertAsset(ctx, asset, clientToken) - if err != nil { - return nil, errors.Wrap(err, "inserting asset") - } - - err = insertAssetTags(ctx, reg.db, asset.AssetID, tags) - if err != nil { - return nil, errors.Wrap(err, "inserting asset tags") - } - */ err = reg.indexAnnotatedAsset(ctx, asset) if err != nil { return nil, errors.Wrap(err, "indexing annotated asset") @@ -194,20 +182,6 @@ func (reg *Registry) UpdateTags(ctx context.Context, id, alias *string, tags map asset.Tags = tags - // Perform persistent updates - /* - err = insertAssetTags(ctx, reg.db, asset.AssetID, asset.Tags) - if err != nil { - return errors.Wrap(err, "inserting asset tags") - } - - err = reg.indexAnnotatedAsset(ctx, asset) - if err != nil { - return errors.Wrap(err, "update asset index") - } - */ - // Revise cache - reg.cacheMu.Lock() reg.cache.Add(asset.AssetID, asset) reg.cacheMu.Unlock() @@ -237,7 +211,7 @@ func (reg *Registry) findByID(ctx context.Context, id bc.AssetID) (*Asset, error } reg.cacheMu.Lock() - reg.cache.Add(id, asset) + reg.cache.Add(id, &asset) reg.cacheMu.Unlock() return &asset, nil } @@ -254,8 +228,6 @@ func (reg *Registry) FindByAlias(ctx context.Context, alias string) (*Asset, err } untypedAsset, err := reg.aliasGroup.Do(alias, func() (interface{}, error) { - // asset, err := assetQuery(ctx, reg.db, "assets.alias=$1", alias) - // return asset, err return nil, nil }) @@ -285,149 +257,6 @@ func (reg *Registry) QueryAll(ctx context.Context) (interface{}, error) { return ret, nil } -// insertAsset adds the asset to the database. If the asset has a client token, -// and there already exists an asset with that client token, insertAsset will -// lookup and return the existing asset instead. -/* -func (reg *Registry) insertAsset(ctx context.Context, asset *Asset, clientToken string) (*Asset, error) { - const q = ` - INSERT INTO assets - (id, alias, signer_id, initial_block_hash, vm_version, issuance_program, definition, client_token) - VALUES($1::bytea, $2, $3, $4, $5, $6, $7, $8) - ON CONFLICT (client_token) DO NOTHING - RETURNING sort_id - ` - var signerID sql.NullString - if asset.Signer != nil { - signerID = sql.NullString{Valid: true, String: asset.Signer.ID} - } - - nullToken := sql.NullString{ - String: clientToken, - Valid: clientToken != "", - } - - err := reg.db.QueryRowContext( - ctx, q, - asset.AssetID, asset.Alias, signerID, - asset.InitialBlockHash, asset.VMVersion, asset.IssuanceProgram, - asset.rawDefinition, nullToken, - ).Scan(&asset.sortID) - - if pg.IsUniqueViolation(err) { - return nil, errors.WithDetail(ErrDuplicateAlias, "an asset with the provided alias already exists") - } else if err == sql.ErrNoRows && clientToken != "" { - // There is already an asset with the provided client - // token. We should return the existing asset. - asset, err = assetByClientToken(ctx, reg.db, clientToken) - if err != nil { - return nil, errors.Wrap(err, "retrieving existing asset") - } - } else if err != nil { - return nil, errors.Wrap(err) - } - return asset, nil -} - -// insertAssetTags inserts a set of tags for the given assetID. -// It must take place inside a database transaction. - -func insertAssetTags(ctx context.Context, db pg.DB, assetID bc.AssetID, tags map[string]interface{}) error { - tagsParam, err := mapToNullString(tags) - if err != nil { - return errors.Wrap(err) - } - - const q = ` - INSERT INTO asset_tags (asset_id, tags) VALUES ($1, $2) - ON CONFLICT (asset_id) DO UPDATE SET tags = $2 - ` - _, err = db.ExecContext(ctx, q, assetID, tagsParam) - if err != nil { - return errors.Wrap(err) - } - - return nil -} - -// assetByClientToken loads an asset from the database using its client token. - -func assetByClientToken(ctx context.Context, db pg.DB, clientToken string) (*Asset, error) { - return assetQuery(ctx, db, "assets.client_token=$1", clientToken) -} - -func assetQuery(ctx context.Context, db pg.DB, pred string, args ...interface{}) (*Asset, error) { - const baseQ = ` - SELECT assets.id, assets.alias, assets.vm_version, assets.issuance_program, assets.definition, - assets.initial_block_hash, assets.sort_id, - signers.id, COALESCE(signers.type, ''), COALESCE(signers.xpubs, '{}'), - COALESCE(signers.quorum, 0), COALESCE(signers.key_index, 0), - asset_tags.tags - FROM assets - LEFT JOIN signers ON signers.id=assets.signer_id - LEFT JOIN asset_tags ON asset_tags.asset_id=assets.id - WHERE %s - LIMIT 1 - ` - - var ( - a Asset - alias sql.NullString - signerID sql.NullString - signerType string - quorum int - keyIndex uint64 - xpubs [][]byte - tags []byte - ) - err := db.QueryRowContext(ctx, fmt.Sprintf(baseQ, pred), args...).Scan( - &a.AssetID, - &a.Alias, - &a.VMVersion, - &a.IssuanceProgram, - &a.rawDefinition, - &a.InitialBlockHash, - &a.sortID, - &signerID, - &signerType, - (*pq.ByteaArray)(&xpubs), - &quorum, - &keyIndex, - &tags, - ) - if err == sql.ErrNoRows { - return nil, pg.ErrUserInputNotFound - } else if err != nil { - return nil, err - } - - if signerID.Valid { - a.Signer, err = signers.New(signerID.String, signerType, xpubs, quorum, keyIndex) - if err != nil { - return nil, err - } - } - - if alias.Valid { - a.Alias = &alias.String - } - - if len(tags) > 0 { - err := json.Unmarshal(tags, &a.Tags) - if err != nil { - return nil, errors.Wrap(err) - } - } - if len(a.rawDefinition) > 0 { - // ignore errors; non-JSON asset definitions can still end up - // on the blockchain from non-Chain Core clients. - _ = json.Unmarshal(a.rawDefinition, &a.definition) - } - - return &a, nil - -} -*/ // serializeAssetDef produces a canonical byte representation of an asset // definition. Currently, this is implemented using pretty-printed JSON. // As is the standard for Go's map[string] serialization, object keys will @@ -451,17 +280,3 @@ func multisigIssuanceProgram(pubkeys []ed25519.PublicKey, nrequired int) (progra prog, err := builder.Build() return prog, 1, err } - -/* -func mapToNullString(in map[string]interface{}) (*sql.NullString, error) { - var mapJSON []byte - if len(in) != 0 { - var err error - mapJSON, err = json.Marshal(in) - if err != nil { - return nil, errors.Wrap(err) - } - } - return &sql.NullString{String: string(mapJSON), Valid: len(mapJSON) > 0}, nil -} -*/ diff --git a/blockchain/asset/builder.go b/blockchain/asset/builder.go index 63e12ca93..584530c31 100644 --- a/blockchain/asset/builder.go +++ b/blockchain/asset/builder.go @@ -8,10 +8,11 @@ import ( "github.com/bytom/blockchain/signers" "github.com/bytom/blockchain/txbuilder" - chainjson "github.com/bytom/encoding/json" - "github.com/bytom/log" "github.com/bytom/protocol/bc" "github.com/bytom/protocol/bc/legacy" + + log "github.com/sirupsen/logrus" + chainjson "github.com/bytom/encoding/json" ) func (reg *Registry) NewIssueAction(assetAmount bc.AssetAmount, referenceData chainjson.Map) txbuilder.Action { @@ -38,7 +39,6 @@ func (a *issueAction) Build(ctx context.Context, builder *txbuilder.TemplateBuil if a.AssetId.IsZero() { return txbuilder.MissingFieldsError("asset_id") } - log.Printf(ctx, "AssetId:%v\n", a.AssetId) asset, err := a.assets.findByID(ctx, *a.AssetId) if err != nil { @@ -59,8 +59,7 @@ func (a *issueAction) Build(ctx context.Context, builder *txbuilder.TemplateBuil path := signers.Path(asset.Signer, signers.AssetKeySpace) tplIn.AddWitnessKeys(asset.Signer.XPubs, path, asset.Signer.Quorum) - log.Printf(ctx, "txin:%v\n", txin) - log.Printf(ctx, "tplIn:%v\n", tplIn) + log.WithFields(log.Fields{"txin": txin, "tplIn": tplIn}).Info("Issue action build") builder.RestrictMinTime(time.Now()) return builder.AddInput(txin, tplIn) } diff --git a/blockchain/assets.go b/blockchain/assets.go index 01937ca23..c4c76848b 100644 --- a/blockchain/assets.go +++ b/blockchain/assets.go @@ -6,9 +6,10 @@ import ( "github.com/bytom/blockchain/asset" "github.com/bytom/crypto/ed25519/chainkd" - "github.com/bytom/log" "github.com/bytom/net/http/httpjson" "github.com/bytom/net/http/reqid" + + log "github.com/sirupsen/logrus" ) // POST /create-asset @@ -49,7 +50,7 @@ func (a *BlockchainReactor) createAsset(ctx context.Context, ins []struct { return } aa, err := asset.Annotated(a) - log.Printf(ctx, "-------createAsset-----Annotated asset:%v", aa) + log.WithField("asset", aa).Info("Created asset") if err != nil { responses[i] = err return @@ -59,7 +60,7 @@ func (a *BlockchainReactor) createAsset(ctx context.Context, ins []struct { } wg.Wait() - log.Printf(ctx, "-------createAsset-----%v", responses) + log.WithField("responses", responses).Info("Responses of created asset") return responses, nil } @@ -69,7 +70,7 @@ func (a *BlockchainReactor) updateAssetTags(ctx context.Context, ins []struct { Alias *string Tags map[string]interface{} `json:"tags"` }) interface{} { - log.Printf(ctx, "------updateAssetTags-----") + log.Info("Update asset tags") responses := make([]interface{}, len(ins)) var wg sync.WaitGroup wg.Add(len(responses)) diff --git a/blockchain/control_programs.go b/blockchain/control_programs.go index 2d7fbd990..d19fd3cd2 100644 --- a/blockchain/control_programs.go +++ b/blockchain/control_programs.go @@ -8,7 +8,6 @@ import ( "github.com/bytom/encoding/json" "github.com/bytom/errors" - "github.com/bytom/log" "github.com/bytom/net/http/httpjson" "github.com/bytom/net/http/reqid" ) @@ -18,7 +17,6 @@ func (a *BlockchainReactor) createControlProgram(ctx context.Context, ins []stru Type string Params stdjson.RawMessage }) interface{} { - log.Printf(ctx, "-------create-control-program-------") responses := make([]interface{}, len(ins)) var wg sync.WaitGroup wg.Add(len(responses)) diff --git a/blockchain/hsm.go b/blockchain/hsm.go index c3669343d..9875109c1 100644 --- a/blockchain/hsm.go +++ b/blockchain/hsm.go @@ -56,11 +56,21 @@ func (a *BlockchainReactor) pseudohsmDeleteKey(ctx context.Context, x struct { func (a *BlockchainReactor) pseudohsmSignTemplates(ctx context.Context, x struct { Auth string Txs []*txbuilder.Template `json:"transactions"` - XPubs []chainkd.XPub `json:"xpubs"` + XPub chainkd.XPub `json:"xpubs"` + XPrv chainkd.XPrv `json:"xprv"` }) interface{} { resp := make([]interface{}, len(x.Txs)) + var err error for _, tx := range x.Txs { - err := txbuilder.Sign(ctx, tx, x.XPubs, x.Auth, a.pseudohsmSignTemplate) + if x.Auth == "" { + err = txbuilder.Sign(context.Background(), tx, []chainkd.XPub{x.XPrv.XPub()}, "", func(_ context.Context, _ chainkd.XPub, path [][]byte, data [32]byte, _ string) ([]byte, error) { + derived := x.XPrv.Derive(path) + return derived.Sign(data[:]), nil + }) + }else{ + err = txbuilder.Sign(ctx, tx, []chainkd.XPub{x.XPub}, x.Auth, a.pseudohsmSignTemplate) + } + log.WithFields(log.Fields{"tx": tx, "build err": err}).Info("After sign transaction.") if err != nil { info := errorFormatter.Format(err) diff --git a/blockchain/net.go b/blockchain/net.go new file mode 100644 index 000000000..ce280bee4 --- /dev/null +++ b/blockchain/net.go @@ -0,0 +1,11 @@ +package blockchain + +import ( + "github.com/bytom/blockchain/rpc" + + ctypes "github.com/bytom/blockchain/rpc/types" +) + +func (a *BlockchainReactor) getNetInfo() (*ctypes.ResultNetInfo, error) { + return rpc.NetInfo(a.sw) +} diff --git a/blockchain/pin/pin.go b/blockchain/pin/pin.go index f2a4f4bc1..2db17afa6 100644 --- a/blockchain/pin/pin.go +++ b/blockchain/pin/pin.go @@ -6,12 +6,12 @@ import ( "sort" "sync" + log "github.com/sirupsen/logrus" + dbm "github.com/tendermint/tmlibs/db" + "github.com/bytom/errors" - "github.com/bytom/log" "github.com/bytom/protocol" "github.com/bytom/protocol/bc/legacy" - - dbm "github.com/tendermint/tmlibs/db" ) const processorWorkers = 10 @@ -39,12 +39,12 @@ func (s *Store) ProcessBlocks(ctx context.Context, c *protocol.Chain, pinName st for { select { case <-ctx.Done(): - log.Error(ctx, ctx.Err()) + log.WithField("err", ctx.Err()).Error("Process blocks, received done signal") return case <-c.BlockWaiter(height + 1): select { case <-ctx.Done(): - log.Error(ctx, ctx.Err()) + log.WithField("err", ctx.Err()).Error("Process blocks, received done signal") return case p.sem <- true: go p.processBlock(ctx, c, height+1, cb) @@ -91,12 +91,10 @@ func (s *Store) LoadAll(ctx context.Context) error { Name string Height uint64 }{} - iter := s.DB.Iterator() + + iter := s.DB.IteratorPrefix([]byte("blp")) for iter.Next() { - key := string(iter.Key()) - if key[:3] != "blp" { - continue - } + err := json.Unmarshal(iter.Value(), &block_processor) if err != nil { return errors.New("failed unmarshal this block_processor.") @@ -184,7 +182,7 @@ func (p *pin) processBlock(ctx context.Context, c *protocol.Chain, height uint64 for { block, err := c.GetBlock(height) if err != nil { - log.Error(ctx, err) + log.WithField("error", err).Error("Process block") continue } diff --git a/blockchain/pool.go b/blockchain/pool.go index ea8d88162..514fa3285 100644 --- a/blockchain/pool.go +++ b/blockchain/pool.go @@ -7,9 +7,9 @@ import ( // "github.com/blockchain/types" "github.com/bytom/protocol/bc/legacy" + log "github.com/sirupsen/logrus" . "github.com/tendermint/tmlibs/common" flow "github.com/tendermint/tmlibs/flowrate" - "github.com/tendermint/tmlibs/log" ) const ( @@ -97,7 +97,10 @@ func (pool *BlockPool) removeTimedoutPeers() { // XXX remove curRate != 0 if curRate != 0 && curRate < minRecvRate { pool.sendTimeout(peer.id) - pool.Logger.Error("SendTimeout", "peer", peer.id, "reason", "curRate too low") + log.WithFields(log.Fields{ + "peer": peer.id, + "reason": "curRate too low", + }).Error("SendTimeout") peer.didTimeout = true } } @@ -123,7 +126,7 @@ func (pool *BlockPool) IsCaughtUp() bool { // Need at least 1 peer to be considered caught up. if len(pool.peers) == 0 && time.Now().Sub(pool.startTime) > 60*time.Second { - pool.Logger.Debug("Blockpool has no peers") + log.Debug("Blockpool has no peers") return false } @@ -133,7 +136,10 @@ func (pool *BlockPool) IsCaughtUp() bool { } isCaughtUp := (height > 0 || time.Now().Sub(pool.startTime) > 5*time.Second) && (maxPeerHeight == 0 || height >= maxPeerHeight) - pool.Logger.Info(Fmt("IsCaughtUp: %v", isCaughtUp), "height", height, "maxPeerHeight", maxPeerHeight) + log.WithFields(log.Fields{ + "height": height, + "maxPeerHeight": maxPeerHeight, + }).Infof("IsCaughtUp: %v", isCaughtUp) return isCaughtUp } @@ -217,7 +223,6 @@ func (pool *BlockPool) SetPeerHeight(peerID string, height uint64) { peer.height = height } else { peer = newBPPeer(pool, peerID, height) - peer.setLogger(pool.Logger.With("peer", peerID)) pool.peers[peerID] = peer } } @@ -322,8 +327,6 @@ type bpPeer struct { numPending int32 timeout *time.Timer didTimeout bool - - logger log.Logger } func newBPPeer(pool *BlockPool, peerID string, height uint64) *bpPeer { @@ -332,15 +335,10 @@ func newBPPeer(pool *BlockPool, peerID string, height uint64) *bpPeer { id: peerID, height: height, numPending: 0, - logger: log.NewNopLogger(), } return peer } -func (peer *bpPeer) setLogger(l log.Logger) { - peer.logger = l -} - func (peer *bpPeer) resetMonitor() { peer.recvMonitor = flow.New(time.Second, time.Second*40) var initialValue = float64(minRecvRate) * math.E @@ -378,7 +376,7 @@ func (peer *bpPeer) onTimeout() { defer peer.pool.mtx.Unlock() peer.pool.sendTimeout(peer.id) - peer.logger.Error("SendTimeout", "reason", "onTimeout") + log.WithField("error", "onTimeout").Error("SendTimeout") peer.didTimeout = true } @@ -468,7 +466,6 @@ OUTER_LOOP: } peer = bpr.pool.pickIncrAvailablePeer(bpr.height) if peer == nil { - //log.Info("No peers available", "height", height) time.Sleep(requestIntervalMS * time.Millisecond) continue PICK_PEER_LOOP } diff --git a/blockchain/pseudohsm/addrcache.go b/blockchain/pseudohsm/addrcache.go index e474c1712..7da268874 100644 --- a/blockchain/pseudohsm/addrcache.go +++ b/blockchain/pseudohsm/addrcache.go @@ -29,6 +29,8 @@ import ( "time" "github.com/bytom/common" + log "github.com/sirupsen/logrus" + _ "github.com/bytom/errors" ) @@ -173,9 +175,7 @@ func (ac *addrCache) find(xpub XPub) (XPub, error) { func (ac *addrCache) reload() { keys, err := ac.scan() if err != nil { - //log.Printf("can't load keys: %v", err.Error()) - fmt.Printf("can't load keys: %v\n", err.Error()) - + log.WithField("error", err).Error("can't load keys") } ac.all = keys sort.Sort(ac.all) @@ -185,8 +185,7 @@ func (ac *addrCache) reload() { for _, k := range keys { ac.byAddr[k.Address] = append(ac.byAddr[k.Address], k) } - //log.Printf("reloaded keys, cache has %d keys", len(ac.all)) - fmt.Printf("reloaded keys, cache has %d keys\n", len(ac.all)) + log.WithField("cache has key", len(ac.all)).Info("reloaded keys") } func (ac *addrCache) scan() ([]XPub, error) { @@ -205,14 +204,11 @@ func (ac *addrCache) scan() ([]XPub, error) { for _, fi := range files { path := filepath.Join(ac.keydir, fi.Name()) if skipKeyFile(fi) { - //log.Printf("ignoring file %v", path) - //fmt.Printf("ignoring file %v", path) continue } fd, err := os.Open(path) if err != nil { - //log.Printf(err) - fmt.Printf("err") + log.WithField("error", err).Info("Os open file error") continue } buf.Reset(fd) @@ -221,10 +217,9 @@ func (ac *addrCache) scan() ([]XPub, error) { err = json.NewDecoder(buf).Decode(&keyJSON) switch { case err != nil: - //log.Printf("can't decode key %s: %v", path, err) - fmt.Printf("can't decode key %s: %v", path, err) + log.WithFields(log.Fields{"path": path, "error":err,}).Error("Can't decode key") case (keyJSON.Address == common.Address{}): - fmt.Printf("can't decode key %s: missing or zero address", path) + log.WithFields(log.Fields{"path": path}).Info("Can't decode key, missing or zero address") default: keys = append(keys, XPub{Address: keyJSON.Address, Alias: keyJSON.Alias, File: path}) } diff --git a/blockchain/pseudohsm/pseudohsm_test.go b/blockchain/pseudohsm/pseudohsm_test.go index aac0ae006..9f213e5c8 100644 --- a/blockchain/pseudohsm/pseudohsm_test.go +++ b/blockchain/pseudohsm/pseudohsm_test.go @@ -1,16 +1,15 @@ package pseudohsm import ( - "testing" - _"github.com/davecgh/go-spew/spew" "github.com/bytom/errors" + _ "github.com/davecgh/go-spew/spew" + "testing" ) const dirPath = "testdata/pseudo" - func TestPseudoHSMChainKDKeys(t *testing.T) { - hsm , _:= New(dirPath) + hsm, _ := New(dirPath) xpub, err := hsm.XCreate("password", "") if err != nil { t.Fatal(err) @@ -41,13 +40,13 @@ func TestPseudoHSMChainKDKeys(t *testing.T) { if !xpub2.XPub.Derive(path).Verify(msg, sig) { t.Error("expected verify with derived pubkey of sig from derived privkey to succeed") } -/* xpubs, _, err := hsm.ListKeys(0, 100) - if err != nil { - t.Fatal(err) - } - if len(xpubs) != 2 { - t.Error("expected 2 entries in the db") - }*/ + /* xpubs, _, err := hsm.ListKeys(0, 100) + if err != nil { + t.Fatal(err) + } + if len(xpubs) != 2 { + t.Error("expected 2 entries in the db") + }*/ err = hsm.UpdateAlias(xpub.XPub, "password", "updatealias") if err != nil { t.Fatal(err) @@ -67,7 +66,7 @@ func TestPseudoHSMChainKDKeys(t *testing.T) { } func TestKeyWithEmptyAlias(t *testing.T) { - hsm, _:= New(dirPath) + hsm, _ := New(dirPath) for i := 0; i < 2; i++ { xpub, err := hsm.XCreate("xx", "") if errors.Root(err) != nil { @@ -80,12 +79,11 @@ func TestKeyWithEmptyAlias(t *testing.T) { } } - func BenchmarkSign(b *testing.B) { b.StopTimer() auth := "nowpasswd" - hsm, _:= New(dirPath) + hsm, _ := New(dirPath) xpub, err := hsm.XCreate(auth, "") if err != nil { b.Fatal(err) diff --git a/blockchain/query.go b/blockchain/query.go index e0b948220..ab0d20fc0 100644 --- a/blockchain/query.go +++ b/blockchain/query.go @@ -21,8 +21,8 @@ var ( AccountUTXOFmt = ` { "OutputID":"%x","AssetID":"%x","Amount":"%d", - "AccountID":"%s","CpIndex":"%d","Program":"%x", - "Confirmed":"%d","SourceID":"%x","SourcePos":"%d", + "AccountID":"%s","ProgramIndex":"%d","Program":"%x", + "BlockHeight":"%d","SourceID":"%x","SourcePos":"%d", "RefData":"%x","Change":"%t" }` ) @@ -53,12 +53,8 @@ func (bcr *BlockchainReactor) GetAccountUTXOs() []account.AccountUTXOs { accutoxs = []account.AccountUTXOs{} ) - iter := bcr.pinStore.DB.Iterator() + iter := bcr.pinStore.DB.IteratorPrefix([]byte("acu")) for iter.Next() { - key := string(iter.Key()) - if key[:3] != "acu" { - continue - } err := json.Unmarshal(iter.Value(), &au) if err != nil { @@ -71,53 +67,51 @@ func (bcr *BlockchainReactor) GetAccountUTXOs() []account.AccountUTXOs { return accutoxs } -// POST /list-balances func (bcr *BlockchainReactor) listBalances(ctx context.Context, in requestQuery) interface{} { - type assetAmount struct { AssetID string - Amount int64 + Amount uint64 } - var ( - aa = assetAmount{} - accBalances = make(map[string][]assetAmount, 0) - accBalancesSort = make(map[string][]assetAmount, 0) - keys = make([]string, 0) - response = make([]interface{}, 0) - ) - accoutUTXOs := bcr.GetAccountUTXOs() + accountUTXOs := bcr.GetAccountUTXOs() + accBalance := make(map[string]map[string]uint64) + response := make([]string, 0) - for _, res := range accoutUTXOs { + for _, accountUTXO := range accountUTXOs { - aa.AssetID = fmt.Sprintf("%x", res.AssetID) - aa.Amount = res.Amount - if _, ok := accBalances[res.AccountID]; ok { - for _, amentry := range accBalances[res.AccountID] { - if amentry.AssetID == aa.AssetID { - amentry.Amount += aa.Amount - } else { - accBalances[res.AccountID] = append(accBalances[res.AccountID], aa) - } + assetID := fmt.Sprintf("%x", accountUTXO.AssetID) + if _, ok := accBalance[accountUTXO.AccountID]; ok { + if _, ok := accBalance[accountUTXO.AccountID][assetID]; ok { + accBalance[accountUTXO.AccountID][assetID] += accountUTXO.Amount + } else { + accBalance[accountUTXO.AccountID][assetID] = accountUTXO.Amount } } else { - accBalances[res.AccountID] = append(accBalances[res.AccountID], aa) + accBalance[accountUTXO.AccountID] = map[string]uint64{assetID: accountUTXO.Amount} } - } - for k := range accBalances { - keys = append(keys, k) + sortedAccount := []string{} + for k, _ := range accBalance { + sortedAccount = append(sortedAccount, k) } + sort.Strings(sortedAccount) - sort.Strings(keys) + for _, account := range sortedAccount { + sortedAsset := []string{} + for k := range accBalance[account] { + sortedAsset = append(sortedAsset, k) + } + sort.Strings(sortedAsset) - for _, k := range keys { - accBalancesSort[k] = accBalances[k] - } + assetAmounts := []assetAmount{} + for _, asset := range sortedAsset { + assetAmounts = append(assetAmounts, assetAmount{AssetID: asset, Amount: accBalance[account][asset]}) + } - if len(accBalancesSort) != 0 { - response = append(response, accBalancesSort) + balanceString, _ := json.Marshal(assetAmounts) + accBalancesString := fmt.Sprintf(`{"AccountID":"%s","Balances":"%s"}`, account, balanceString) + response = append(response, accBalancesString) } return response @@ -215,8 +209,8 @@ func (bcr *BlockchainReactor) listUnspentOutputs(ctx context.Context, in request restring = fmt.Sprintf(AccountUTXOFmt, res.OutputID, res.AssetID, res.Amount, - res.AccountID, res.CpIndex, res.Program, - res.Confirmed, res.SourceID, res.SourcePos, + res.AccountID, res.ProgramIndex, res.Program, + res.BlockHeight, res.SourceID, res.SourcePos, res.RefData, res.Change) response = append(response, restring) diff --git a/blockchain/reactor.go b/blockchain/reactor.go index 5e71dc40b..b9b6c88fc 100644 --- a/blockchain/reactor.go +++ b/blockchain/reactor.go @@ -15,7 +15,6 @@ import ( "github.com/bytom/blockchain/txdb" "github.com/bytom/blockchain/txfeed" "github.com/bytom/encoding/json" - "github.com/bytom/log" "github.com/bytom/mining/cpuminer" "github.com/bytom/net/http/httpjson" "github.com/bytom/p2p" @@ -27,6 +26,7 @@ import ( "github.com/bytom/blockchain/pin" "github.com/bytom/errors" + log "github.com/sirupsen/logrus" ) const ( @@ -64,6 +64,7 @@ type BlockchainReactor struct { hsm *pseudohsm.HSM mining *cpuminer.CPUMiner mux *http.ServeMux + sw *p2p.Switch handler http.Handler fastSync bool requestsCh chan BlockRequest @@ -89,7 +90,6 @@ func batchRecover(ctx context.Context, v *interface{}) { // Convert errors into error responses (including errors // from recovered panics above). if err, ok := (*v).(error); ok { - errorFormatter.Log(ctx, err) *v = errorFormatter.Format(err) } } @@ -113,7 +113,6 @@ func (bcr *BlockchainReactor) ServeHTTP(rw http.ResponseWriter, req *http.Reques func (bcr *BlockchainReactor) info(ctx context.Context) (map[string]interface{}, error) { //if a.config == nil { // never configured - log.Printf(ctx, "-------info-----") return map[string]interface{}{ "is_configured": false, "version": "0.001", @@ -125,7 +124,6 @@ func (bcr *BlockchainReactor) info(ctx context.Context) (map[string]interface{}, } func (bcr *BlockchainReactor) createblockkey(ctx context.Context) { - log.Printf(ctx, "creat-block-key") } func maxBytes(h http.Handler) http.Handler { @@ -148,7 +146,7 @@ func (bcr *BlockchainReactor) BuildHander() { m.Handle("/create-account-receiver", jsonHandler(bcr.createAccountReceiver)) m.Handle("/list-accounts", jsonHandler(bcr.listAccounts)) } else { - log.Printf(context.Background(), "Warning: Please enable wallet") + log.Warn("Please enable wallet") } if bcr.assets != nil { @@ -156,7 +154,7 @@ func (bcr *BlockchainReactor) BuildHander() { m.Handle("/update-asset-tags", jsonHandler(bcr.updateAssetTags)) m.Handle("/list-assets", jsonHandler(bcr.listAssets)) } else { - log.Printf(context.Background(), "Warning: Please enable wallet") + log.Warn("Please enable wallet") } m.Handle("/build-transaction", jsonHandler(bcr.build)) m.Handle("/create-control-program", jsonHandler(bcr.createControlProgram)) @@ -182,6 +180,7 @@ func (bcr *BlockchainReactor) BuildHander() { m.Handle("/sign-transactions", jsonHandler(bcr.pseudohsmSignTemplates)) m.Handle("/reset-password", jsonHandler(bcr.pseudohsmResetPassword)) m.Handle("/update-alias", jsonHandler(bcr.pseudohsmUpdateAlias)) + m.Handle("/net-info", jsonHandler(bcr.getNetInfo)) latencyHandler := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { if l := latency(m, req); l != nil { @@ -239,6 +238,7 @@ func NewBlockchainReactor(store *txdb.Store, txPool *protocol.TxPool, accounts *account.Manager, assets *asset.Registry, + sw *p2p.Switch, hsm *pseudohsm.HSM, fastSync bool, pinStore *pin.Store) *BlockchainReactor { @@ -260,6 +260,7 @@ func NewBlockchainReactor(store *txdb.Store, txPool: txPool, mining: mining, mux: http.NewServeMux(), + sw: sw, hsm: hsm, fastSync: fastSync, requestsCh: requestsCh, @@ -496,8 +497,6 @@ func (m *bcTransactionMessage) GetTransaction() *legacy.Tx { //------------------------------------- -//------------------------------------- - // NOTE: keep up-to-date with maxBlockchainResponseSize type bcBlockResponseMessage struct { RawBlock []byte diff --git a/blockchain/receivers.go b/blockchain/receivers.go index 5bd70046c..af1dd99a8 100644 --- a/blockchain/receivers.go +++ b/blockchain/receivers.go @@ -5,7 +5,6 @@ import ( "sync" "time" - "github.com/bytom/log" "github.com/bytom/net/http/reqid" ) @@ -15,7 +14,6 @@ func (a *BlockchainReactor) createAccountReceiver(ctx context.Context, ins []str AccountAlias string `json:"account_alias"` ExpiresAt time.Time `json:"expires_at"` }) []interface{} { - log.Printf(ctx, "-------create-Account-Receiver-------") responses := make([]interface{}, len(ins)) var wg sync.WaitGroup wg.Add(len(responses)) diff --git a/blockchain/rpc/info.go b/blockchain/rpc/info.go new file mode 100644 index 000000000..fe24810e0 --- /dev/null +++ b/blockchain/rpc/info.go @@ -0,0 +1,11 @@ +package rpc + +import ( + "github.com/bytom/blockchain/txdb" + + ctypes "github.com/bytom/blockchain/rpc/types" +) + +func BlockHeight(blockStore *txdb.Store) (*ctypes.ResultBlockchainInfo, error) { + return &ctypes.ResultBlockchainInfo{LastHeight: blockStore.Height()}, nil +} diff --git a/rpc/core/net.go b/blockchain/rpc/net.go similarity index 83% rename from rpc/core/net.go rename to blockchain/rpc/net.go index 30caa7e39..48918058c 100644 --- a/rpc/core/net.go +++ b/blockchain/rpc/net.go @@ -1,14 +1,13 @@ -package core +package rpc import ( - "fmt" - - ctypes "github.com/bytom/rpc/core/types" + ctypes "github.com/bytom/blockchain/rpc/types" + "github.com/bytom/p2p" ) //----------------------------------------------------------------------------- -func NetInfo() (*ctypes.ResultNetInfo, error) { +func NetInfo(p2pSwitch *p2p.Switch) (*ctypes.ResultNetInfo, error) { listening := p2pSwitch.IsListening() listeners := []string{} for _, listener := range p2pSwitch.Listeners() { @@ -32,7 +31,7 @@ func NetInfo() (*ctypes.ResultNetInfo, error) { //----------------------------------------------------------------------------- // Dial given list of seeds -func UnsafeDialSeeds(seeds []string) (*ctypes.ResultDialSeeds, error) { +/*func UnsafeDialSeeds(seeds []string) (*ctypes.ResultDialSeeds, error) { if len(seeds) == 0 { return &ctypes.ResultDialSeeds{}, fmt.Errorf("No seeds provided") @@ -44,4 +43,4 @@ func UnsafeDialSeeds(seeds []string) (*ctypes.ResultDialSeeds, error) { return &ctypes.ResultDialSeeds{}, err } return &ctypes.ResultDialSeeds{"Dialing seeds in progress. See /net_info for details"}, nil -} +}*/ diff --git a/blockchain/rpc/rpc.go b/blockchain/rpc/rpc.go index 76a101697..1465a2044 100644 --- a/blockchain/rpc/rpc.go +++ b/blockchain/rpc/rpc.go @@ -1,4 +1,3 @@ -// Package rpc implements Chain Core's RPC client. package rpc import ( @@ -17,10 +16,10 @@ import ( "github.com/bytom/net/http/reqid" ) -// Chain-specific header fields +// Bytom-specific header fields const ( HeaderBlockchainID = "Blockchain-ID" - HeaderCoreID = "Chain-Core-ID" + HeaderCoreID = "Bytom-Core-ID" HeaderTimeout = "RPC-Timeout" ) @@ -28,7 +27,7 @@ const ( // the RPC client's blockchain ID. var ErrWrongNetwork = errors.New("connected to a peer on a different network") -// A Client is a Chain RPC client. It performs RPCs over HTTP using JSON +// A Client is a Bytom RPC client. It performs RPCs over HTTP using JSON // request and responses. A Client must be configured with a secret token // to authenticate with other Cores on the network. type Client struct { @@ -45,7 +44,7 @@ type Client struct { } func (c Client) userAgent() string { - return fmt.Sprintf("Chain; process=%s; buildtag=%s; blockchainID=%s", + return fmt.Sprintf("Bytom; process=%s; buildtag=%s; blockchainID=%s", c.Username, c.BuildTag, c.BlockchainID) } diff --git a/rpc/core/types/responses.go b/blockchain/rpc/types/responses.go similarity index 68% rename from rpc/core/types/responses.go rename to blockchain/rpc/types/responses.go index 63a5e5500..b991693aa 100644 --- a/rpc/core/types/responses.go +++ b/blockchain/rpc/types/responses.go @@ -7,7 +7,6 @@ import ( "github.com/bytom/p2p" "github.com/bytom/protocol/bc" "github.com/bytom/types" - abci "github.com/tendermint/abci/types" "github.com/tendermint/go-crypto" "github.com/tendermint/go-wire/data" ) @@ -72,39 +71,3 @@ type Peer struct { IsOutbound bool `json:"is_outbound"` ConnectionStatus p2p.ConnectionStatus `json:"connection_status"` } - -type ResultDumpConsensusState struct { - RoundState string `json:"round_state"` - PeerRoundStates []string `json:"peer_round_states"` -} - -type ResultBroadcastTx struct { - Code abci.CodeType `json:"code"` - Data data.Bytes `json:"data"` - Log string `json:"log"` - - Hash data.Bytes `json:"hash"` -} - -type ResultBroadcastTxCommit struct { - CheckTx abci.Result `json:"check_tx"` - DeliverTx abci.Result `json:"deliver_tx"` - Hash data.Bytes `json:"hash"` - Height int `json:"height"` -} - -type ResultABCIInfo struct { - Response abci.ResponseInfo `json:"response"` -} - -type ResultABCIQuery struct { - *abci.ResultQuery `json:"response"` -} - -type ResultUnsafeFlushMempool struct{} - -type ResultUnsafeProfile struct{} - -type ResultSubscribe struct{} - -type ResultUnsubscribe struct{} diff --git a/rpc/core/types/responses_test.go b/blockchain/rpc/types/responses_test.go similarity index 100% rename from rpc/core/types/responses_test.go rename to blockchain/rpc/types/responses_test.go diff --git a/blockchain/signers/idgenerate.go b/blockchain/signers/idgenerate.go index 11505b79b..c9d8e9430 100644 --- a/blockchain/signers/idgenerate.go +++ b/blockchain/signers/idgenerate.go @@ -8,7 +8,7 @@ import ( ) //1 } diff --git a/cmd/bytomcli/example/spend.go b/cmd/bytomcli/example/spend.go index fd3fb7063..1aff6aea5 100644 --- a/cmd/bytomcli/example/spend.go +++ b/cmd/bytomcli/example/spend.go @@ -4,99 +4,226 @@ import ( "context" stdjson "encoding/json" "fmt" + "os" + "strconv" + "time" bchain "github.com/bytom/blockchain" - "github.com/bytom/blockchain/query" "github.com/bytom/blockchain/rpc" "github.com/bytom/blockchain/txbuilder" - "github.com/bytom/crypto/ed25519/chainkd" + "github.com/bytom/encoding/json" ) +type accUTXOShort struct { + OutputID string `json:"OutputID"` + AccountID string `json:"AccountID"` + AssetID string `json:"AssetID"` + Amount string `json:"Amount"` +} + +type requestQuery struct { + Filter string `json:"filter,omitempty"` + FilterParams []interface{} `json:"filter_params,omitempty"` + SumBy []string `json:"sum_by,omitempty"` + PageSize int `json:"page_size"` + AscLongPoll bool `json:"ascending_with_long_poll,omitempty"` + Timeout json.Duration `json:"timeout"` + After string `json:"after"` + StartTimeMS uint64 `json:"start_time,omitempty"` + EndTimeMS uint64 `json:"end_time,omitempty"` + TimestampMS uint64 `json:"timestamp,omitempty"` + Type string `json:"type"` + Aliases []string `json:"aliases,omitempty"` +} + func SpendTest(client *rpc.Client, args []string) { // Create Account. fmt.Printf("To create Account:\n") - xprv, _ := chainkd.NewXPrv(nil) - xpub := xprv.XPub() - fmt.Printf("xprv_account:%v\n", xprv) - fmt.Printf("xpub_account:%v\n", xpub) - type Ins struct { - RootXPubs []chainkd.XPub `json:"root_xpubs"` - Quorum int - Alias string - Tags map[string]interface{} - ClientToken string `json:"client_token"` - } - var ins Ins - ins.RootXPubs = []chainkd.XPub{xpub} - ins.Quorum = 1 - ins.Alias = "alice" - ins.Tags = map[string]interface{}{"test_tag": "v0"} - ins.ClientToken = "account" - account := make([]query.AnnotatedAccount, 1) - client.Call(context.Background(), "/create-account", &[]Ins{ins}, &account) - fmt.Printf("account:%v\n", account) + aliceIns, xprvAlice := NewInstance("alice", Account) + bobIns, _ := NewInstance("bob", Account) + accounts, _ := NewAnnotate(client, Account, aliceIns, bobIns) // Create Asset. fmt.Printf("To create Asset:\n") - xprv_asset, _ := chainkd.NewXPrv(nil) - xpub_asset := xprv_asset.XPub() - fmt.Printf("xprv_asset:%v\n", xprv_asset) - fmt.Printf("xpub_asset:%v\n", xpub_asset) - type Ins_asset struct { - RootXPubs []chainkd.XPub `json:"root_xpubs"` - Quorum int - Alias string - Tags map[string]interface{} - Definition map[string]interface{} - ClientToken string `json:"client_token"` - } - var ins_asset Ins_asset - ins_asset.RootXPubs = []chainkd.XPub{xpub_asset} - ins_asset.Quorum = 1 - ins_asset.Alias = "gold" - ins_asset.Tags = map[string]interface{}{"test_tag": "v0"} - ins_asset.Definition = map[string]interface{}{"test_definition": "v0"} - ins_asset.ClientToken = "asset" - asset := make([]query.AnnotatedAsset, 1) - client.Call(context.Background(), "/create-asset", &[]Ins_asset{ins_asset}, &asset) - fmt.Printf("asset:%v\n", asset) - - // Build Transaction. + goldIns, xprvGold := NewInstance("gold", Asset) + _, assets := NewAnnotate(client, Asset, goldIns) + + // Build Transaction1-Issue fmt.Printf("To build transaction:\n") - // Now spend actions buildReqFmt := ` {"actions": [ - {"type": "spend", "asset_id": "%s", "amount": 100}, - {"type": "control_account", "asset_id": "%s", "amount": 100, "account_id": "%s"} + { + "type":"spend_account_unspent_output", + "receiver":null, + "output_id":"73d1e97c7bcf2b084f936a40f4f2a72e909417f2b46699e8659fa4c4feddb98d", + "reference_data":{} + }, + {"type": "issue", "asset_id": "%s", "amount": 100}, + {"type": "control_account", "asset_id": "%s", "amount": 100, "account_id": "%s"}, + {"type": "control_account", "asset_id": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "amount": 8888888888, "account_id": "%s"} ]}` - buildReqStr := fmt.Sprintf(buildReqFmt, asset[0].ID.String(), asset[0].ID.String(), account[0].ID) + buildReqStr := fmt.Sprintf(buildReqFmt, assets[0].ID.String(), assets[0].ID.String(), accounts[0].ID, accounts[0].ID) var buildReq bchain.BuildRequest err := stdjson.Unmarshal([]byte(buildReqStr), &buildReq) if err != nil { - fmt.Printf("json Unmarshal error.") + fmt.Println(err) } tpl := make([]txbuilder.Template, 1) client.Call(context.Background(), "/build-transaction", []*bchain.BuildRequest{&buildReq}, &tpl) fmt.Printf("tpl:%v\n", tpl) - /* // sign-transaction - err = txbuilder.Sign(context.Background(), &tpl[0], []chainkd.XPub{xprv_asset.XPub()}, func(_ context.Context, _ chainkd.XPub, path [][]byte, data [32]byte) ([]byte, error) { - derived := xprv_asset.Derive(path) - return derived.Sign(data[:]), nil - }) - if err != nil { - fmt.Printf("sign-transaction error. err:%v\n", err) + // sign-transaction1-Issue + err = txbuilder.Sign(context.Background(), &tpl[0], []chainkd.XPub{xprvGold.XPub()}, "", func(_ context.Context, _ chainkd.XPub, path [][]byte, data [32]byte, _ string) ([]byte, error) { + derived := xprvGold.Derive(path) + return derived.Sign(data[:]), nil + }) + if err != nil { + fmt.Printf("sign-transaction error. err:%v\n", err) + os.Exit(0) + } + + fmt.Printf("sign tpl:%v\n", tpl[0]) + fmt.Printf("sign tpl's SigningInstructions:%v\n", tpl[0].SigningInstructions[0]) + fmt.Printf("SigningInstructions's SignatureWitnesses:%v\n", tpl[0].SigningInstructions[0].SignatureWitnesses[0]) + + // submit-transaction1-Issue + var submitResponse interface{} + submitArg := bchain.SubmitArg{Transactions: tpl, Wait: json.Duration{Duration: time.Duration(1000000)}, WaitUntil: "none"} + client.Call(context.Background(), "/submit-transaction", submitArg, &submitResponse) + fmt.Printf("submit transaction:%v\n", submitResponse) + + //Issue result: + //alice + + fmt.Println("===========================wait to buid accountutxos.db===============================================") + time.Sleep(time.Second * 2) + + // Build Transaction2-Spend_account + fmt.Printf("To build transaction2:\n") + buildReqFmt2 := ` + {"actions": [ + {"type": "spend_account", "asset_id": "%s", "amount": 40, "account_id": "%s"}, + {"type": "spend_account", "asset_id": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "amount": 10000000, "account_id": "%s"}, + {"type": "control_account", "asset_id": "%s", "amount": 40, "account_id": "%s"}, + {"type": "control_account", "asset_id": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "amount": 5000000, "account_id": "%s"} + ]}` + buildReqStr2 := fmt.Sprintf(buildReqFmt2, assets[0].ID.String(), accounts[0].ID, accounts[0].ID, assets[0].ID.String(), accounts[1].ID, accounts[1].ID) + + var buildReq2 bchain.BuildRequest + err = stdjson.Unmarshal([]byte(buildReqStr2), &buildReq2) + if err != nil { + fmt.Println(err) + } + + tpl2 := make([]txbuilder.Template, 1) + client.Call(context.Background(), "/build-transaction", []*bchain.BuildRequest{&buildReq2}, &tpl2) + fmt.Printf("tpl2:%v\n", tpl2) + + // sign-transaction2-Spend_account + err = txbuilder.Sign(context.Background(), &tpl2[0], []chainkd.XPub{xprvAlice.XPub()}, "", func(_ context.Context, _ chainkd.XPub, path [][]byte, data [32]byte, _ string) ([]byte, error) { + derived := xprvAlice.Derive(path) + return derived.Sign(data[:]), nil + }) + if err != nil { + fmt.Printf("sign-transaction2 error. err:%v\n", err) + os.Exit(0) + } + + fmt.Printf("sign tpl2:%v\n", tpl2[0]) + fmt.Printf("sign tpl2's SigningInstructions:%v\n", tpl2[0].SigningInstructions[0]) + fmt.Printf("SigningInstructions's SignatureWitnesses:%v\n", tpl2[0].SigningInstructions[0].SignatureWitnesses[0]) + + // submit-transaction2-Spend_account + var submitResponse2 interface{} + submitArg2 := bchain.SubmitArg{Transactions: tpl2, Wait: json.Duration{Duration: time.Duration(1000000)}, WaitUntil: "none"} + client.Call(context.Background(), "/submit-transaction", submitArg2, &submitResponse2) + fmt.Printf("submit2 transaction:%v\n", submitResponse2) + + //Spend_account result: + //alice + //bob + //fee 10000000-5000000 + + fmt.Println("===========================wait to buid accountutxos.db===============================================") + time.Sleep(time.Second * 2) + + // Build Transaction3-Spend_account_utxo + fmt.Printf("To build transaction3:\n") + + // Get one UTXO + var tmp accUTXOShort + var in requestQuery + var amount uint64 + + responses := make([]interface{}, 0) + + client.Call(context.Background(), "/list-unspent-outputs", in, &responses) + if len(responses) > 0 { + for i, item := range responses { + itemString, _ := item.(string) + err = stdjson.Unmarshal(stdjson.RawMessage(itemString), &tmp) + if err != nil { + fmt.Printf("Spend_account_utxo: test fail, err:%v\n", err) + os.Exit(0) + } + if accounts[0].ID == tmp.AccountID && + assets[0].ID.String() == tmp.AssetID { + //get one alice gold utxo + fmt.Println(i, "-----", item) + break + } } - fmt.Printf("sign tpl:%v\n", tpl[0]) - fmt.Printf("sign tpl's SigningInstructions:%v\n", tpl[0].SigningInstructions[0]) - fmt.Printf("SigningInstructions's SignatureWitnesses:%v\n", tpl[0].SigningInstructions[0].SignatureWitnesses[0]) - - // submit-transaction - var submitResponse interface{} - submitArg := bc.SubmitArg{tpl, json.Duration{time.Duration(1000000)}, "none"} - client.Call(context.Background(), "/submit-transaction", submitArg, &submitResponse) - fmt.Printf("submit transaction:%v\n", submitResponse) - */ + } + + if tmp.AccountID == "" { + fmt.Printf("Spend_account_utxo: get on utxo fail\n") + os.Exit(0) + } + amount, _ = strconv.ParseUint(tmp.Amount, 10, 64) + fmt.Printf("Get one accUTXOShort: %v\n", tmp) + + buildReqFmt3 := ` + {"actions": [ + {"type": "spend_account", "asset_id": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "amount": 10000000, "account_id": "%s"}, + {"type": "spend_account_unspent_output", "output_id": "%s"}, + {"type": "control_account", "asset_id": "%s", "amount": %d, "account_id": "%s"} + ]}` + buildReqStr3 := fmt.Sprintf(buildReqFmt3, accounts[0].ID, tmp.OutputID, tmp.AssetID, amount, accounts[1].ID) + + var buildReq3 bchain.BuildRequest + err = stdjson.Unmarshal([]byte(buildReqStr3), &buildReq3) + if err != nil { + fmt.Println(err) + } + + tpl3 := make([]txbuilder.Template, 1) + client.Call(context.Background(), "/build-transaction", []*bchain.BuildRequest{&buildReq3}, &tpl3) + fmt.Printf("tpl3:%v\n", tpl3) + + // sign-transaction3-Spend_account_utxo + err = txbuilder.Sign(context.Background(), &tpl3[0], []chainkd.XPub{xprvAlice.XPub()}, "", func(_ context.Context, _ chainkd.XPub, path [][]byte, data [32]byte, _ string) ([]byte, error) { + derived := xprvAlice.Derive(path) + return derived.Sign(data[:]), nil + }) + if err != nil { + fmt.Printf("sign-transaction3 error. err:%v\n", err) + os.Exit(0) + } + + fmt.Printf("sign tpl3:%v\n", tpl2[0]) + fmt.Printf("sign tpl3's SigningInstructions:%v\n", tpl3[0].SigningInstructions[0]) + fmt.Printf("SigningInstructions's SignatureWitnesses:%v\n", tpl3[0].SigningInstructions[0].SignatureWitnesses[0]) + + // submit-transaction3-Spend_account_utxo + var submitResponse3 interface{} + submitArg3 := bchain.SubmitArg{Transactions: tpl3, Wait: json.Duration{Duration: time.Duration(1000000)}, WaitUntil: "none"} + client.Call(context.Background(), "/submit-transaction", submitArg3, &submitResponse3) + fmt.Printf("submit3 transaction:%v\n", submitResponse3) + fmt.Println("==============test end===============") + //Spend_account_utxo result: + //alice + //bob + //fee 10000000 } diff --git a/cmd/bytomcli/main.go b/cmd/bytomcli/main.go index 33dd348d5..38892cc78 100644 --- a/cmd/bytomcli/main.go +++ b/cmd/bytomcli/main.go @@ -26,7 +26,6 @@ import ( "github.com/bytom/encoding/json" "github.com/bytom/env" "github.com/bytom/errors" - "github.com/bytom/log" ) // config vars @@ -40,10 +39,6 @@ var ( buildDate = "?" ) -// We collect log output in this buffer, -// and display it only when there's an error. -var logbuf bytes.Buffer - type command struct { f func(*rpc.Client, []string) } @@ -60,11 +55,11 @@ var commands = map[string]*command{ "grant": {grant}, "revoke": {revoke}, "wait": {wait}, - "create-account": {createAccount}, - "bind-account": {bindAccount}, + "create-account": {createAccount}, + "bind-account": {bindAccount}, "update-account-tags": {updateAccountTags}, "create-asset": {createAsset}, - "bind-asset": {bindAsset}, + "bind-asset": {bindAsset}, "update-asset-tags": {updateAssetTags}, "build-transaction": {buildTransaction}, "create-control-program": {createControlProgram}, @@ -90,12 +85,13 @@ var commands = map[string]*command{ "delete-key": {deleteKey}, "sign-transactions": {signTransactions}, "sub-create-issue-tx": {submitCreateIssueTransaction}, + "sub-spend-account-tx": {submitSpendTransaction}, "reset-password": {resetPassword}, "update-alias": {updateAlias}, + "net-info": {netInfo}, } func main() { - log.SetOutput(&logbuf) env.Parse() if len(os.Args) >= 2 && os.Args[1] == "-version" { @@ -246,8 +242,7 @@ func mustRPCClient() *rpc.Client { } func fatalln(v ...interface{}) { - io.Copy(os.Stderr, &logbuf) - fmt.Fprintln(os.Stderr, v...) + fmt.Printf("%v", v) os.Exit(2) } @@ -256,8 +251,6 @@ func dieOnRPCError(err error, prefixes ...interface{}) { return } - io.Copy(os.Stderr, &logbuf) - if len(prefixes) > 0 { fmt.Fprintln(os.Stderr, prefixes...) } @@ -439,7 +432,6 @@ func bindAsset(client *rpc.Client, args []string) { fmt.Printf("asset id:%v\n", assets[0].ID.String()) } - func updateAccountTags(client *rpc.Client, args []string) { if len(args) != 2 { fatalln("update-account-tags [|] [tags_key:]") @@ -528,8 +520,8 @@ func buildTransaction(client *rpc.Client, args []string) { } func submitCreateIssueTransaction(client *rpc.Client, args []string) { - if len(args) != 4 { - fatalln("error: need args: [account id] [asset id] [asset xprv] [issue amount]") + if len(args) != 5 { + fatalln("error: need args: [account1 id] [account2 id] [asset id] [asset xprv] [issue amount]") } // Build Transaction. fmt.Printf("To build transaction:\n") @@ -543,9 +535,11 @@ func submitCreateIssueTransaction(client *rpc.Client, args []string) { "reference_data":{} }, {"type": "issue", "asset_id": "%s", "amount": %s}, - {"type": "control_account", "asset_id": "%s", "amount": %s, "account_id": "%s"} + {"type": "control_account", "asset_id": "%s", "amount": %s, "account_id": "%s"}, + {"type": "control_account", "asset_id": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "amount": 8888888888, "account_id": "%s"}, + {"type": "control_account", "asset_id": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "amount": 8888888888, "account_id": "%s"} ]}` - buildReqStr := fmt.Sprintf(buildReqFmt, args[1], args[3], args[1], args[3], args[0]) + buildReqStr := fmt.Sprintf(buildReqFmt, args[2], args[4], args[2], args[4], args[0], args[0], args[1]) var buildReq blockchain.BuildRequest err := stdjson.Unmarshal([]byte(buildReqStr), &buildReq) if err != nil { @@ -560,8 +554,8 @@ func submitCreateIssueTransaction(client *rpc.Client, args []string) { fmt.Printf("----------issue inputs:%v\n", tpl[0].Transaction.Inputs[1]) var xprv_asset chainkd.XPrv - fmt.Printf("xprv_asset:%v\n", args[2]) - xprv_asset.UnmarshalText([]byte(args[2])) + fmt.Printf("xprv_asset:%v\n", args[3]) + xprv_asset.UnmarshalText([]byte(args[3])) // sign-transaction err = txbuilder.Sign(context.Background(), &tpl[0], []chainkd.XPub{xprv_asset.XPub()}, "", func(_ context.Context, _ chainkd.XPub, path [][]byte, data [32]byte, _ string) ([]byte, error) { derived := xprv_asset.Derive(path) @@ -581,6 +575,61 @@ func submitCreateIssueTransaction(client *rpc.Client, args []string) { fmt.Printf("submit transaction:%v\n", submitResponse) } +func submitSpendTransaction(client *rpc.Client, args []string) { + if len(args) != 5 { + fatalln("error: need args: [account1 id] [account2 id] [asset id] [account1 xprv] [spend amount]") + } + + var xprvAccount1 chainkd.XPrv + + err := xprvAccount1.UnmarshalText([]byte(args[3])) + if err == nil { + fmt.Printf("xprv:%v\n", xprvAccount1) + } else { + fmt.Printf("xprv unmarshal error:%v\n", xprvAccount1) + } + // Build Transaction-Spend_account + fmt.Printf("To build transaction:\n") + buildReqFmt := ` + {"actions": [ + {"type": "spend_account", "asset_id": "%s", "amount": %s, "account_id": "%s"}, + {"type": "spend_account", "asset_id": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "amount":20000000, "account_id": "%s"}, + {"type": "control_account", "asset_id": "%s", "amount": %s, "account_id": "%s"} + ]}` + + buildReqStr := fmt.Sprintf(buildReqFmt, args[2], args[4], args[0], args[0], args[2], args[4], args[1]) + + var buildReq blockchain.BuildRequest + err = stdjson.Unmarshal([]byte(buildReqStr), &buildReq) + if err != nil { + fmt.Println(err) + } + + tpl := make([]txbuilder.Template, 1) + client.Call(context.Background(), "/build-transaction", []*blockchain.BuildRequest{&buildReq}, &tpl) + fmt.Printf("tpl:%v\n", tpl) + + // sign-transaction-Spend_account + err = txbuilder.Sign(context.Background(), &tpl[0], []chainkd.XPub{xprvAccount1.XPub()}, "", func(_ context.Context, _ chainkd.XPub, path [][]byte, data [32]byte, _ string) ([]byte, error) { + derived := xprvAccount1.Derive(path) + return derived.Sign(data[:]), nil + }) + if err != nil { + fmt.Printf("sign-transaction error. err:%v\n", err) + os.Exit(0) + } + + fmt.Printf("sign tpl:%v\n", tpl[0]) + fmt.Printf("sign tpl's SigningInstructions:%v\n", tpl[0].SigningInstructions[0]) + fmt.Printf("SigningInstructions's SignatureWitnesses:%v\n", tpl[0].SigningInstructions[0].SignatureWitnesses[0]) + + // submit-transaction-Spend_account + var submitResponse interface{} + submitArg := blockchain.SubmitArg{Transactions: tpl, Wait: json.Duration{Duration: time.Duration(1000000)}, WaitUntil: "none"} + client.Call(context.Background(), "/submit-transaction", submitArg, &submitResponse) + fmt.Printf("submit transaction:%v\n", submitResponse) +} + func createControlProgram(client *rpc.Client, args []string) { if len(args) != 0 { fatalln("error:createControlProgram not use args") @@ -961,25 +1010,44 @@ func listKeys(client *rpc.Client, args []string) { } func signTransactions(client *rpc.Client, args []string) { - if len(args) != 3 { - fatalln("error: signTransaction need args: [tpl file name] [xPub] [password], 3 args not equal ", len(args)) - } + // sign-transaction type param struct { - Auth string + Auth string Txs []*txbuilder.Template `json:"transactions"` - XPubs []chainkd.XPub `json:"xpubs"` + XPubs chainkd.XPub `json:"xpubs"` + XPrv chainkd.XPrv `json:"xprv"` } + var in param - in.Auth = args[2] + var xprv chainkd.XPrv var xpub chainkd.XPub - err := xpub.UnmarshalText([]byte(args[1])) - if err == nil { - fmt.Printf("xpub:%v\n", xpub) + var err error + + if len(args) == 3 { + err = xpub.UnmarshalText([]byte(args[1])) + if err == nil { + fmt.Printf("xpub:%v\n", xpub) + } else { + fmt.Printf("xpub unmarshal error:%v\n", xpub) + } + in.XPubs = xpub + in.Auth = args[2] + + } else if len(args) == 2 { + err = xprv.UnmarshalText([]byte(args[1])) + if err == nil { + fmt.Printf("xprv:%v\n", xprv) + } else { + fmt.Printf("xprv unmarshal error:%v\n", xprv) + } + in.XPrv = xprv + } else { - fmt.Printf("xpub unmarshal error:%v\n", xpub) + fatalln("error: signTransaction need args: [tpl file name] [xPub] [password], 3 args not equal"+ + "or [tpl file name] [xPrv], 2 args not equal ", len(args)) } - in.XPubs = []chainkd.XPub{xpub} + var tpl txbuilder.Template file, _ := os.Open(args[0]) tpl_byte := make([]byte, 10000) @@ -1037,3 +1105,9 @@ func updateAlias(client *rpc.Client, args []string) { key.XPub = *xpub client.Call(context.Background(), "/update-alias", &key, nil) } + +func netInfo(client *rpc.Client, args []string) { + var response interface{} + client.Call(context.Background(), "/net-info", nil, &response) + fmt.Printf("net info:%v\n", response) +} diff --git a/cmd/bytomd/commands/run_node.go b/cmd/bytomd/commands/run_node.go index 5a2ce7796..076561ab1 100644 --- a/cmd/bytomd/commands/run_node.go +++ b/cmd/bytomd/commands/run_node.go @@ -51,7 +51,7 @@ func runNode(cmd *cobra.Command, args []string) error { } // Create & start node - n := node.NewNodeDefault(config, logger.With("module", "node_p2p")) + n := node.NewNodeDefault(config) if _, err := n.Start(); err != nil { return fmt.Errorf("Failed to start node: %v", err) } else { diff --git a/config/config.go b/config/config.go index cfea436dd..401d0e24d 100644 --- a/config/config.go +++ b/config/config.go @@ -11,8 +11,8 @@ type Config struct { BaseConfig `mapstructure:",squash"` // Options for services - RPC *RPCConfig `mapstructure:"rpc"` - P2P *P2PConfig `mapstructure:"p2p"` + RPC *RPCConfig `mapstructure:"rpc"` + P2P *P2PConfig `mapstructure:"p2p"` Wallet *WalletConfig `mapstructure:"wallet"` } @@ -21,7 +21,7 @@ func DefaultConfig() *Config { BaseConfig: DefaultBaseConfig(), RPC: DefaultRPCConfig(), P2P: DefaultP2PConfig(), - Wallet: DefaultWalletConfig(), + Wallet: DefaultWalletConfig(), } } @@ -30,7 +30,7 @@ func TestConfig() *Config { BaseConfig: TestBaseConfig(), RPC: TestRPCConfig(), P2P: TestP2PConfig(), - Wallet: TestWalletConfig(), + Wallet: TestWalletConfig(), } } @@ -191,7 +191,7 @@ func DefaultP2PConfig() *P2PConfig { ListenAddress: "tcp://0.0.0.0:46656", AddrBook: "addrbook.json", AddrBookStrict: true, - SkipUPNP: false, + SkipUPNP: false, MaxNumPeers: 50, } } @@ -211,7 +211,7 @@ func (p *P2PConfig) AddrBookFile() string { // WalletConfig type WalletConfig struct { - Enable bool `mapstructure:"enable"` + Enable bool `mapstructure:"enable"` } func DefaultWalletConfig() *WalletConfig { diff --git a/consensus/general.go b/consensus/general.go index c694fb05f..8eefff03b 100644 --- a/consensus/general.go +++ b/consensus/general.go @@ -36,5 +36,5 @@ func BlockSubsidy(height uint64) uint64 { } func InitBlock() []byte { - return []byte("0301000000000000000000000000000000000000000000000000000000000000000000ece090e7eb2b4078a79ed5c640a026361c4af77a37342e503cc68493229996e11dd9be38b18f5b492159980684155da19e87de0d1b37b35c1a1123770ec1dcc710aabe77607cce00b1c5a181808080802e0107010700ece090e7eb2b000001012cffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8080ccdee2a69fb314010151000000") + return []byte("0301000000000000000000000000000000000000000000000000000000000000000000d9a9c883f72b408b6eb2c2fb757ece7d5b7bf36c978e2edeb5ff98a4e8cccfa0cc8b1ed6cacdfd492159980684155da19e87de0d1b37b35c1a1123770ec1dcc710aabe77607cceacb68c0293fcb680808080801e0107010700d9a9c883f72b000001012cffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8080ccdee2a69fb314010151000000") } diff --git a/crypto/crypto_test.go b/crypto/crypto_test.go index 7b8660541..4950317a8 100644 --- a/crypto/crypto_test.go +++ b/crypto/crypto_test.go @@ -18,10 +18,10 @@ package crypto import ( _ "bytes" - _ "github.com/bytom/common" _ "crypto/ecdsa" _ "encoding/hex" _ "fmt" + _ "github.com/bytom/common" _ "io/ioutil" _ "math/big" _ "os" diff --git a/glide.lock b/glide.lock index 6e1a3a4d5..c2a673040 100644 --- a/glide.lock +++ b/glide.lock @@ -1,14 +1,12 @@ -hash: 93f15c9766ea826c29a91f545c42172eafd8c61e39c1d81617114ad1a9c9eaf2 -updated: 2017-05-18T06:13:24.295793122-04:00 +hash: 4f15d2d062129c7ddc5d485e2180c9fad7d56e4006e1d3ff0fb1601711ca3f6d +updated: 2017-10-28T10:18:01.957773712+08:00 imports: - name: github.com/btcsuite/btcd version: 53f55a46349aa8f44b90895047e843666991cf24 subpackages: - btcec -- name: github.com/davecgh/go-spew - version: 04cdfd42973bb9c8589fd6a731800cf222fde1a9 - subpackages: - - spew +- name: github.com/codahale/hdrhistogram + version: 3a0bb77429bd3a61596f5e8a3172445844342120 - name: github.com/ebuchman/fail-test version: 95f809107225be108efcf10a3509e4ea6ceef3c4 - name: github.com/fsnotify/fsnotify @@ -21,18 +19,38 @@ imports: - log/term - name: github.com/go-logfmt/logfmt version: 390ab7935ee28ec6b286364bba9b4dd6410cb3d5 +- name: github.com/go-playground/locales + version: e4cbcb5d0652150d40ad0646651076b6bd2be4f6 + subpackages: + - currency +- name: github.com/go-playground/universal-translator + version: 71201497bace774495daed26a3874fd339e0b538 - name: github.com/go-stack/stack version: 7a2f19628aabfe68f0766b59e74d6315f8347d22 - name: github.com/gogo/protobuf version: 9df9efe4c742f1a2bfdedf1c3b6902fc6e814c6b subpackages: - proto +- name: github.com/golang/crypto + version: 2509b142fb2b797aa7587dad548f113b2c0f20ce + subpackages: + - nacl/box + - nacl/secretbox + - ripemd160 +- name: github.com/golang/groupcache + version: b710c8433bd175204919eb38776e944233235d03 + subpackages: + - lru + - singleflight +- name: github.com/golang/net + version: c73622c77280266305273cb545f54516ced95b93 + subpackages: + - context - name: github.com/golang/protobuf version: fec3b39b059c0f88fa6b20f5ed012b1aa203a8b4 subpackages: - proto - ptypes/any -- name: github.com/golang/groupcache - name: github.com/golang/snappy version: 553a641470496b2327abcac10b36396bd98e45c9 - name: github.com/gorilla/websocket @@ -54,20 +72,24 @@ imports: version: c42d9e0ca023e2198120196f842701bb4c55d7b9 - name: github.com/kr/logfmt version: b84e30acd515aadc4b783ad4ff83aff3299bdfe0 +- name: github.com/kr/secureheader + version: a4295c4b19606af8e3366057e129204fb8b77411 - name: github.com/magiconair/properties version: 51463bfca2576e06c62a8504b5c0f06d61312647 - name: github.com/mitchellh/mapstructure version: cc8532a8e9a55ea36402aa21efdf403a60d34096 +- name: github.com/pborman/uuid + version: e790cca94e6cc75c7064b1332e63811d4aae1a53 - name: github.com/pelletier/go-buffruneio version: c37440a7cf42ac63b919c752ca73a85067e05992 - name: github.com/pelletier/go-toml version: 5c26a6ff6fd178719e15decac1c8196da0d7d6d1 - name: github.com/pkg/errors version: c605e284fe17294bda444b34710735b29d1a9d90 -- name: github.com/pmezard/go-difflib - version: d8ed2627bdf02c080bf22230dbb337003b7aba2d - subpackages: - - difflib +- name: github.com/rjeczalik/notify + version: 767eb674ef14b09119b2fff3601e64558d530c47 +- name: github.com/sirupsen/logrus + version: 89742aefa4b206dcf400792f3bd35b542998eb3b - name: github.com/spf13/afero version: 9be650865eab0c12963d8753212f4f9c66cdcf12 subpackages: @@ -106,9 +128,7 @@ imports: version: 864d1f80b36b440bde030a5c18d8ac3aa8c2949d subpackages: - client - - example/counter - example/dummy - - server - types - name: github.com/tendermint/ed25519 version: 1f52c6f8b8a5c7908aff4497c186af344b428925 @@ -122,15 +142,8 @@ imports: subpackages: - data - data/base58 -- name: github.com/tendermint/merkleeyes - version: a0e73e1ac3e18e12a007520a4ea2c9822256e307 - subpackages: - - app - - client - - iavl - - testutil - name: github.com/tendermint/tmlibs - version: 306795ae1d8e4f4a10dcc8bdb32a00455843c9d5 + version: d9525c0fb671204450b160807480e1263053fb20 subpackages: - autofile - cli @@ -141,21 +154,46 @@ imports: - flowrate - log - merkle - - test -- name: github.com/codahale/hdrhistogram -- name: github.com/golang/crypto -- name: github.com/golang/net -- name: github.com/golang/text -- name: github.com/golang/tools -- name: github.com/golang/time -- name: github.com/kr/secureheader -- name: github.com/pborman/uuid -- name: github.com/rjeczalik/notify -- name: github.com/sirupsen/logrus +- name: golang.org/x/crypto + version: 84f24dfdf3c414ed893ca1b318d0045ef5a1f607 + subpackages: + - curve25519 + - nacl/box + - nacl/secretbox + - openpgp/armor + - openpgp/errors + - pbkdf2 + - poly1305 + - ripemd160 + - salsa20/salsa + - scrypt + - sha3 + - ssh/terminal +- name: golang.org/x/net + version: 8663ed5da4fd087c3cfb99a996e628b72e2f0948 + subpackages: + - context + - http2 + - http2/hpack + - idna + - internal/timeseries + - lex/httplex + - trace - name: golang.org/x/sys version: e62c3de784db939836898e5c19ffd41bece347da subpackages: - unix +- name: golang.org/x/text + version: 6353ef0f924300eea566d3438817aa4d3374817e + subpackages: + - secure/bidirule + - transform + - unicode/bidi + - unicode/norm +- name: golang.org/x/time + version: 6dc17368e09b0e8634d71cac8168d853e869a0c7 + subpackages: + - rate - name: google.golang.org/genproto version: bb3573be0c484136831138976d444b8754777aff subpackages: @@ -176,6 +214,22 @@ imports: - status - tap - transport +- name: gopkg.in/go-playground/validator.v9 + version: 1304298bf10d085adec514b076772a79c9cadb6b - name: gopkg.in/yaml.v2 version: cd8b52f8269e0feb286dfeef29f8fe4d5b397e0b -testImports: [] +testImports: +- name: github.com/davecgh/go-spew + version: 04cdfd42973bb9c8589fd6a731800cf222fde1a9 + subpackages: + - spew +- name: github.com/pmezard/go-difflib + version: d8ed2627bdf02c080bf22230dbb337003b7aba2d + subpackages: + - difflib +- name: github.com/tendermint/merkleeyes + version: 2f6e5d31e7a35045d8d0a5895cb1fec33dd4d32b + subpackages: + - app + - iavl + - testutil diff --git a/glide.yaml b/glide.yaml index 74e717453..f8d175e00 100644 --- a/glide.yaml +++ b/glide.yaml @@ -27,7 +27,7 @@ import: subpackages: - data - package: github.com/tendermint/tmlibs - version: v0.2.0 + version: v0.4.0 subpackages: - autofile - cli diff --git a/log/log.go b/log/log.go deleted file mode 100644 index 2e45c9702..000000000 --- a/log/log.go +++ /dev/null @@ -1,247 +0,0 @@ -// Package log implements a standard convention for structured logging. -// Log entries are formatted as K=V pairs. -// By default, output is written to stdout; this can be changed with SetOutput. -package log - -import ( - "context" - "fmt" - "io" - "os" - "runtime" - "strconv" - "strings" - "sync" - "time" - - "github.com/bytom/errors" -) - -const rfc3339NanoFixed = "2006-01-02T15:04:05.000000000Z07:00" - -// context key type -type key int - -var ( - logWriterMu sync.Mutex // protects the following - logWriter io.Writer = os.Stdout - procPrefix []byte // process-global prefix; see SetPrefix vs AddPrefixkv - - // pairDelims contains a list of characters that may be used as delimeters - // between key-value pairs in a log entry. Keys and values will be quoted or - // otherwise formatted to ensure that key-value extraction is unambiguous. - // - // The list of pair delimiters follows Splunk conventions, described here: - // http://answers.splunk.com/answers/143368/default-delimiters-for-key-value-extraction.html - pairDelims = " ,;|&\t\n\r" - illegalKeyChars = pairDelims + `="` - - // context key for log line prefixes - prefixKey key = 0 -) - -// Conventional key names for log entries -const ( - KeyCaller = "at" // location of caller - KeyTime = "t" // time of call - - KeyMessage = "message" // produced by Message - KeyError = "error" // produced by Error - KeyStack = "stack" // used by Printkv to print stack on subsequent lines - - keyLogError = "log-error" // for errors produced by the log package itself -) - -// SetOutput sets the log output to w. -// If SetOutput hasn't been called, -// the default behavior is to write to stdout. -func SetOutput(w io.Writer) { - logWriterMu.Lock() - logWriter = w - logWriterMu.Unlock() -} - -func appendPrefix(b []byte, keyval ...interface{}) []byte { - // Invariant: len(keyval) is always even. - if len(keyval)%2 != 0 { - panic(fmt.Sprintf("odd-length prefix args: %v", keyval)) - } - for i := 0; i < len(keyval); i += 2 { - k := formatKey(keyval[i]) - v := formatValue(keyval[i+1]) - b = append(b, k...) - b = append(b, '=') - b = append(b, v...) - b = append(b, ' ') - } - return b -} - -// SetPrefix sets the global output prefix. -func SetPrefix(keyval ...interface{}) { - b := appendPrefix(nil, keyval...) - logWriterMu.Lock() - procPrefix = b - logWriterMu.Unlock() -} - -// AddPrefixkv appends keyval to any prefix stored in ctx, -// and returns a new context with the longer prefix. -func AddPrefixkv(ctx context.Context, keyval ...interface{}) context.Context { - p := appendPrefix(prefix(ctx), keyval...) - // Note: subsequent calls will append to p, so set cap(p) here. - // See TestAddPrefixkvAppendTwice. - p = p[0:len(p):len(p)] - return context.WithValue(ctx, prefixKey, p) -} - -func prefix(ctx context.Context) []byte { - b, _ := ctx.Value(prefixKey).([]byte) - return b -} - -// Printkv prints a structured log entry to stdout. Log fields are -// specified as a variadic sequence of alternating keys and values. -// -// Duplicate keys will be preserved. -// -// Two fields are automatically added to the log entry: t=[time] -// and at=[file:line] indicating the location of the caller. -// Use SkipFunc to prevent helper functions from showing up in the -// at=[file:line] field. -// -// Printkv will also print the stack trace, if any, on separate lines -// following the message. The stack is obtained from the following, -// in order of preference: -// - a KeyStack value with type []byte or []errors.StackFrame -// - a KeyError value with type error, using the result of errors.Stack -func Printkv(ctx context.Context, keyvals ...interface{}) { - // Invariant: len(keyvals) is always even. - if len(keyvals)%2 != 0 { - keyvals = append(keyvals, "", keyLogError, "odd number of log params") - } - - t := time.Now().UTC() - - // Prepend the log entry with auto-generated fields. - out := fmt.Sprintf( - "%s=%s %s=%s", - KeyCaller, caller(), - KeyTime, formatValue(t.Format(rfc3339NanoFixed)), - ) - - var stack interface{} - for i := 0; i < len(keyvals); i += 2 { - k := keyvals[i] - v := keyvals[i+1] - if k == KeyStack && isStackVal(v) { - stack = v - continue - } - if k == KeyError { - if e, ok := v.(error); ok && stack == nil { - stack = errors.Stack(errors.Wrap(e)) // wrap to ensure callstack - } - } - out += " " + formatKey(k) + "=" + formatValue(v) - } - - logWriterMu.Lock() - logWriter.Write(procPrefix) - logWriter.Write(prefix(ctx)) - logWriter.Write([]byte(out)) // ignore errors - logWriter.Write([]byte{'\n'}) - writeRawStack(logWriter, stack) - logWriterMu.Unlock() -} - -// Fatalkv is equivalent to Printkv() followed by a call to os.Exit(1). -func Fatalkv(ctx context.Context, keyvals ...interface{}) { - Printkv(ctx, keyvals...) - os.Exit(1) -} - -func writeRawStack(w io.Writer, v interface{}) { - switch v := v.(type) { - case []byte: - if len(v) > 0 { - w.Write(v) - w.Write([]byte{'\n'}) - } - case []errors.StackFrame: - for _, s := range v { - io.WriteString(w, s.String()) - w.Write([]byte{'\n'}) - } - } -} - -func isStackVal(v interface{}) bool { - switch v.(type) { - case []byte: - return true - case []errors.StackFrame: - return true - } - return false -} - -// Printf prints a log entry containing a message assigned to the -// "message" key. Arguments are handled as in fmt.Printf. -func Printf(ctx context.Context, format string, a ...interface{}) { - Printkv(ctx, KeyMessage, fmt.Sprintf(format, a...)) -} - -// Error prints a log entry containing an error message assigned to the -// "error" key. -// Optionally, an error message prefix can be included. Prefix arguments are -// handled as in fmt.Print. -func Error(ctx context.Context, err error, a ...interface{}) { - if len(a) > 0 && len(errors.Stack(err)) > 0 { - err = errors.Wrap(err, a...) // keep err's stack - } else if len(a) > 0 { - err = fmt.Errorf("%s: %s", fmt.Sprint(a...), err) // don't add a stack here - } - Printkv(ctx, KeyError, err) -} - -// formatKey ensures that the stringified key is valid for use in a -// Splunk-style K=V format. It stubs out delimeter and quoter characters in -// the key string with hyphens. -func formatKey(k interface{}) string { - s := fmt.Sprint(k) - if s == "" { - return "?" - } - - for _, c := range illegalKeyChars { - s = strings.Replace(s, string(c), "-", -1) - } - - return s -} - -// formatValue ensures that the stringified value is valid for use in a -// Splunk-style K=V format. It quotes the string value if delimeter or quoter -// characters are present in the value string. -func formatValue(v interface{}) string { - s := fmt.Sprint(v) - if strings.ContainsAny(s, pairDelims) { - return strconv.Quote(s) - } - return s -} - -// RecoverAndLogError must be used inside a defer. -func RecoverAndLogError(ctx context.Context) { - if err := recover(); err != nil { - const size = 64 << 10 - buf := make([]byte, size) - buf = buf[:runtime.Stack(buf, false)] - Printkv(ctx, - KeyMessage, "panic", - KeyError, err, - KeyStack, buf, - ) - } -} diff --git a/log/rotation/file.go b/log/rotation/file.go deleted file mode 100644 index 4e14a109e..000000000 --- a/log/rotation/file.go +++ /dev/null @@ -1,109 +0,0 @@ -// Package rotation writes and rotates log files. -package rotation - -import ( - "bytes" - "os" - "strconv" -) - -// A File is a log file with associated rotation files. -// The rotation files are named after the base file -// with a numeric suffix: base.1, base.2, and so on. -// Calls to Write write data to the base file. -// When the base file reaches the given size, -// it is renamed to base.1 -// (and base.1 is renamed to base.2, and so on) -// and a new base file is opened for subsequent writes. -// -// Any errors encountered while rotating files are ignored. -// Only errors opening and writing the base file are reported. -type File struct { - base string // file name - size int64 // max size of f (limit on w) - n int // number of rotated files - buf []byte // partial line from last write - f *os.File // current base file - w int64 // bytes written to f -} - -// Create creates a log writing to the named file -// with mode 0644 (before umask), -// appending to it if it already exists. -// It will rotate to files name.1, name.2, -// up to name.n. -// The minimum value for n is 1; -// lesser values will be taken as 1. -func Create(name string, size, n int) *File { - return &File{ - base: name, - size: int64(size), - n: n, - } -} - -var dropmsg = []byte("\nlog write error; some data dropped\n") - -// Write writes p to the log file f. -// It writes only complete lines to the underlying file. -// Incomplete lines are buffered in memory -// and written once a NL is encountered. -func (f *File) Write(p []byte) (n int, err error) { - f.buf = append(f.buf, p...) - n = len(p) - if i := bytes.LastIndexByte(f.buf, '\n'); i >= 0 { - _, err = f.write(f.buf[:i+1]) - // Even if the write failed, discard the entire - // requested write payload. If we kept it around, - // then a failure to open the log file would - // cause us to accumulate data in memory - // without bound. - f.buf = f.buf[i+1:] - if err != nil { - // If we recover and resume logging, - // leave a message to indicate we dropped - // some lines. - f.buf = append(dropmsg, f.buf...) - } - } - return -} - -// write writes the given data to f, -// rotating files if necessary. -func (f *File) write(p []byte) (int, error) { - // If p would increase the file over the - // max size, it is time to rotate. - if f.w+int64(len(p)) > f.size { - // best-effort; ignore errors - f.rotate() - f.f.Close() - f.f = nil - f.w = 0 - } - if f.f == nil { - var err error - f.f, err = os.OpenFile(f.base, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644) // #nosec - if err != nil { - return 0, err - } - f.w, err = f.f.Seek(0, os.SEEK_END) - if err != nil { - return 0, err - } - } - n, err := f.f.Write(p) - f.w += int64(n) - return n, err -} - -func (f *File) rotate() { - for i := f.n - 1; i > 0; i-- { - os.Rename(f.name(i), f.name(i+1)) - } - os.Rename(f.base, f.name(1)) -} - -func (f *File) name(i int) string { - return f.base + "." + strconv.Itoa(i) -} diff --git a/log/rotation/file_test.go b/log/rotation/file_test.go deleted file mode 100644 index b751c2bcf..000000000 --- a/log/rotation/file_test.go +++ /dev/null @@ -1,244 +0,0 @@ -package rotation - -import ( - "bytes" - "io/ioutil" - "os" - "testing" -) - -func TestRotate(t *testing.T) { - defer os.Remove("x") - defer os.Remove("x.1") - defer os.Remove("x.2") - defer os.Remove("x.3") // just in case - os.Remove("x") - os.Remove("x.1") - os.Remove("x.2") - os.Remove("x.3") // just in case - - f := Create("x", 1e6, 2) - - touch("x") - f.rotate() - if !isRegular("x.1") { - t.Fatal("want rotated file x.1") - } - if isRegular("x.2") { - t.Fatal("want no rotated file x.2") - } - if isRegular("x.3") { - t.Fatal("want no rotated file x.3") - } - - touch("x") - f.rotate() - if !isRegular("x.1") { - t.Fatal("want rotated file x.1") - } - if !isRegular("x.2") { - t.Fatal("want rotated file x.2") - } - if isRegular("x.3") { - t.Fatal("want no rotated file x.3") - } - - touch("x") - f.rotate() - if !isRegular("x.1") { - t.Fatal("want rotated file x.1") - } - if !isRegular("x.2") { - t.Fatal("want rotated file x.2") - } - if isRegular("x.3") { - t.Fatal("want no rotated file x.3") - } -} - -func TestRotate0(t *testing.T) { - defer os.Remove("x") - defer os.Remove("x.1") - defer os.Remove("x.2") // just in case - os.Remove("x") - os.Remove("x.1") - os.Remove("x.2") // just in case - - f := Create("x", 1e6, 0) - - touch("x") - f.rotate() - if !isRegular("x.1") { - t.Fatal("want rotated file x.1") - } - if isRegular("x.2") { - t.Fatal("want no rotated file x.2") - } - - touch("x") - f.rotate() - if !isRegular("x.1") { - t.Fatal("want rotated file x.1") - } - if isRegular("x.2") { - t.Fatal("want no rotated file x.2") - } -} - -func TestInternalWriteNoRotate(t *testing.T) { - defer os.Remove("x") - defer os.Remove("x.1") // just in case - os.Remove("x") - os.Remove("x.1") // just in case - - f := &File{ - base: "x", - n: 1, - size: 10, - w: 0, - } - b := []byte("abc\n") - n, err := f.write(b) - if err != nil { - t.Fatal(err) - } - if n != len(b) { - t.Fatalf("write(%q) = %d want %d", b, n, len(b)) - } - if isRegular("x.1") { - t.Fatal("want no rotated file x.1") - } - if f.w != int64(len(b)) { - t.Fatalf("f.w = %d want %d", f.w, len(b)) - } -} - -func TestInternalWriteRotate(t *testing.T) { - defer os.Remove("x") - defer os.Remove("x.1") - os.Remove("x") - os.Remove("x.1") - - f0, err := os.Create("x") - if err != nil { - t.Fatal(err) - } - - f := &File{ - base: "x", - n: 1, - size: 10, - w: 9, - f: f0, - } - b := []byte("abc\n") - n, err := f.write(b) - if err != nil { - t.Fatal(err) - } - if n != len(b) { - t.Fatalf("write(%q) = %d want %d", b, n, 4) - } - if !isRegular("x.1") { - t.Fatal("want rotated file x.1") - } - if f.f == f0 { - t.Fatal("want new file object") - } - if f.w != int64(len(b)) { - t.Fatalf("f.w = %d want %d", f.w, len(b)) - } -} - -func TestWrite(t *testing.T) { - defer os.Remove("x") - defer os.Remove("x.1") - os.Remove("x") - os.Remove("x.1") - f := Create("x", 1e6, 1) - - b0 := []byte("ab") - n, err := f.Write(b0) - if err != nil { - t.Fatal(err) - } - if n != len(b0) { - t.Fatalf("n = %d want %d", n, len(b0)) - } - if !bytes.Equal(f.buf, b0) { - t.Fatalf("buf = %q want %q", f.buf, b0) - } - if f.w != 0 { - t.Fatalf("w = %d want 0", f.w) - } - - b1 := []byte("c\n") - n, err = f.Write(b1) - if err != nil { - t.Fatal(err) - } - if n != len(b1) { - t.Fatalf("n = %d want %d", n, len(b1)) - } - if !bytes.Equal(f.buf, nil) { - t.Fatalf("buf = %q want %q", f.buf, "") - } - if f.w != int64(len(b0)+len(b1)) { - t.Fatalf("w = %d want %d", f.w, len(b0)+len(b1)) - } -} - -func TestOpenFail(t *testing.T) { - f := Create("/tmp/improbable-test-path/second-level/x", 1e6, 1) - b := []byte("abc\nd") - want := append(dropmsg, 'd') - n, err := f.Write(b) - if err == nil { - t.Error("want error") - } - if n != len(b) { - t.Errorf("n = %d want %d", n, len(b)) - } - if !bytes.Equal(f.buf, want) { - t.Fatalf("buf = %q want %q", f.buf, want) - } - - n, err = f.Write(b) - if err == nil { - t.Error("want error") - } - if n != len(b) { - t.Errorf("n = %d want %d", n, len(b)) - } - // don't accumulate multiple dropped-log messages - if !bytes.Equal(f.buf, want) { - t.Fatalf("buf = %q want %q", f.buf, want) - } -} - -func TestAppend(t *testing.T) { - defer os.Remove("x") - b0 := []byte("abc\n") - b1 := []byte("def\n") - err := ioutil.WriteFile("x", b0, 0666) - if err != nil { - t.Fatal(err) - } - f := Create("x", 100, 1) - f.Write(b1) - if want := int64(len(b0) + len(b1)); f.w != want { - t.Fatalf("w = %d want %d", f.w, want) - } -} - -func isRegular(name string) bool { - fi, err := os.Stat(name) - return err == nil && fi.Mode().IsRegular() -} - -func touch(name string) { - f, _ := os.Create(name) - if f != nil { - f.Close() - } -} diff --git a/log/splunk/splunk.go b/log/splunk/splunk.go deleted file mode 100644 index bf9b494a8..000000000 --- a/log/splunk/splunk.go +++ /dev/null @@ -1,78 +0,0 @@ -// Package splunk sends log data to a splunk server. -package splunk - -import ( - "io" - "net" - "time" -) - -const ( - // DialTimeout limits how long a write will block - // while dialing the splunk server. Assuming the - // connection stays open for a long time, this will - // happen only rarely. - DialTimeout = 50 * time.Millisecond - - // WriteTimeout limits how long a write will block - // sending data on the network. It is deliberately - // very small, so that writes can only be satisfied - // by the local network buffers. It should never - // block waiting for a TCP ACK for an appreciable - // amount of time. - WriteTimeout = 100 * time.Microsecond -) - -type splunk struct { - addr string - conn net.Conn - dropmsg []byte - err error // last write error -} - -// New creates a new writer that sends data -// to the given TCP address. -// It connects on the first call to Write, -// and attempts to reconnect when necessary, -// with a timeout of DialTimeout. -// -// Every write has a timeout of WriteTimeout. -// If the write doesn't complete in that time, -// the writer drops the unwritten data. -// For every contiguous run of dropped data, -// it writes dropmsg before resuming ordinary writes. -// As long as the remote endpoint can keep up -// with the averate data rate and the local -// network buffers in the kernel and NIC are -// big enough to hold traffic bursts, no data -// will be lost. -func New(addr string, dropmsg []byte) io.Writer { - return &splunk{ - addr: addr, - dropmsg: dropmsg, - } -} - -func (s *splunk) Write(p []byte) (n int, err error) { - if s.conn == nil { - s.conn, err = net.DialTimeout("tcp", s.addr, DialTimeout) - if err != nil { - return 0, err - } - } - - if s.err != nil { - s.conn.SetDeadline(time.Now().Add(WriteTimeout)) - _, s.err = s.conn.Write(s.dropmsg) - } - if s.err == nil { - s.conn.SetDeadline(time.Now().Add(WriteTimeout)) - n, s.err = s.conn.Write(p) - } - - if t, ok := s.err.(net.Error); s.err != nil && (!ok || !t.Temporary()) { - s.conn.Close() - s.conn = nil - } - return n, s.err -} diff --git a/log/stack.go b/log/stack.go deleted file mode 100644 index 7114b5167..000000000 --- a/log/stack.go +++ /dev/null @@ -1,44 +0,0 @@ -package log - -import ( - "path/filepath" - "runtime" - "strconv" -) - -var skipFunc = map[string]bool{ - "bytom/log.Printkv": true, - "bytom/log.Printf": true, - "bytom/log.Error": true, - "bytom/log.Fatalkv": true, - "bytom/log.RecoverAndLogError": true, -} - -// SkipFunc removes the named function from stack traces -// and at=[file:line] entries printed to the log output. -// The provided name should be a fully-qualified function name -// comprising the import path and identifier separated by a dot. -// For example, chain/log.Printkv. -// SkipFunc must not be called concurrently with any function -// in this package (including itself). -func SkipFunc(name string) { - skipFunc[name] = true -} - -// caller returns a string containing filename and line number of -// the deepest function invocation on the calling goroutine's stack, -// after skipping functions in skipFunc. -// If no stack information is available, it returns "?:?". -func caller() string { - for i := 1; ; i++ { - // NOTE(kr): This is quadratic in the number of frames we - // ultimately have to skip. Consider using Callers instead. - pc, file, line, ok := runtime.Caller(i) - if !ok { - return "?:?" - } - if !skipFunc[runtime.FuncForPC(pc).Name()] { - return filepath.Base(file) + ":" + strconv.Itoa(line) - } - } -} diff --git a/net/http/httperror/httperror.go b/net/http/httperror/httperror.go index d2ff5c6c1..d495a7d11 100644 --- a/net/http/httperror/httperror.go +++ b/net/http/httperror/httperror.go @@ -7,16 +7,9 @@ import ( "net/http" "github.com/bytom/errors" - "github.com/bytom/log" "github.com/bytom/net/http/httpjson" - "github.com/bytom/net/http/reqid" ) -func init() { - log.SkipFunc("chain/net/http/httperror.Formatter.Log") - log.SkipFunc("chain/net/http/httperror.Formatter.Write") -} - // Info contains a set of error codes to send to the user. type Info struct { HTTPStatus int `json:"-"` @@ -71,29 +64,6 @@ func (f Formatter) Format(err error) (body Response) { // // Write may be used as an ErrorWriter in the httpjson package. func (f Formatter) Write(ctx context.Context, w http.ResponseWriter, err error) { - f.Log(ctx, err) resp := f.Format(err) httpjson.Write(ctx, w, resp.HTTPStatus, resp) } - -// Log writes a structured log entry to the chain/log logger with -// information about the error and the HTTP response. -func (f Formatter) Log(ctx context.Context, err error) { - var errorMessage string - if err != nil { - // strip the stack trace, if there is one - errorMessage = err.Error() - } - - resp := f.Format(err) - keyvals := []interface{}{ - "status", resp.HTTPStatus, - "bytomcode", resp.ChainCode, - "path", reqid.PathFromContext(ctx), - log.KeyError, errorMessage, - } - if resp.HTTPStatus == 500 { - keyvals = append(keyvals, log.KeyStack, errors.Stack(err)) - } - log.Printkv(ctx, keyvals...) -} diff --git a/net/http/httperror/httperror_test.go b/net/http/httperror/httperror_test.go index b74771e16..4ecf2fed9 100644 --- a/net/http/httperror/httperror_test.go +++ b/net/http/httperror/httperror_test.go @@ -1,14 +1,11 @@ package httperror import ( - "bytes" "context" "fmt" - "strings" "testing" "github.com/bytom/errors" - "github.com/bytom/log" ) var ( @@ -49,27 +46,3 @@ func TestInfo(t *testing.T) { } } } - -func TestLogSkip(t *testing.T) { - var buf bytes.Buffer - log.SetOutput(&buf) - - formatter := Formatter{ - Default: Info{500, "CH000", "Internal server error"}, - IsTemporary: func(Info, error) bool { return false }, - Errors: map[error]Info{}, - } - formatter.Log(context.Background(), errors.New("an unmapped error")) - - logStr := string(buf.Bytes()) - if len(logStr) == 0 { - t.Error("expected error to be logged") - } - if strings.Contains(logStr, "at=httperror.go") { - t.Errorf("expected httperror stack frames to be skipped but got:\n%s", logStr) - } - if !strings.Contains(logStr, "status=500") { - t.Errorf("expected status code of default error info but got:\n%s", logStr) - } - t.Log(logStr) -} diff --git a/net/http/reqid/reqid.go b/net/http/reqid/reqid.go index cf655c289..6156741aa 100644 --- a/net/http/reqid/reqid.go +++ b/net/http/reqid/reqid.go @@ -7,7 +7,7 @@ import ( "encoding/hex" "net/http" - "github.com/bytom/log" + log "github.com/sirupsen/logrus" ) // key is an unexported type for keys defined in this package. @@ -43,17 +43,16 @@ func New() string { b := make([]byte, l) _, err := rand.Read(b) if err != nil { - log.Printf(context.Background(), "error making reqID") + log.WithField("error", err).Info("error making reqID") } return hex.EncodeToString(b) } // NewContext returns a new Context that carries reqid. // It also adds a log prefix to print the request ID using -// package chain/log. +// package bytom/log. func NewContext(ctx context.Context, reqid string) context.Context { ctx = context.WithValue(ctx, reqIDKey, reqid) - ctx = log.AddPrefixkv(ctx, "reqid", reqid) return ctx } @@ -78,12 +77,8 @@ func PathFromContext(ctx context.Context) string { return path } -// NewSubContext returns a new Context that carries subreqid -// It also adds a log prefix to print the sub-request ID using -// package chain/log. func NewSubContext(ctx context.Context, reqid string) context.Context { ctx = context.WithValue(ctx, subReqIDKey, reqid) - ctx = log.AddPrefixkv(ctx, "subreqid", reqid) return ctx } @@ -96,26 +91,5 @@ func FromSubContext(ctx context.Context) string { func Handler(handler http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - ctx := req.Context() - // TODO(kr): take half of request ID from the client - id := New() - ctx = NewContext(ctx, id) - ctx = context.WithValue(ctx, pathKey, req.URL.Path) - if coreID := req.Header.Get("Chain-Core-ID"); coreID != "" { - ctx = context.WithValue(ctx, coreIDKey, coreID) - ctx = log.AddPrefixkv(ctx, "coreid", coreID) - } - - defer func() { - if err := recover(); err != nil { - log.Printkv(ctx, - "message", "panic", - "remote-addr", req.RemoteAddr, - "error", err, - ) - } - }() - w.Header().Add("Chain-Request-Id", id) - handler.ServeHTTP(w, req.WithContext(ctx)) }) } diff --git a/net/http/reqid/reqid_test.go b/net/http/reqid/reqid_test.go deleted file mode 100644 index 7be59fd37..000000000 --- a/net/http/reqid/reqid_test.go +++ /dev/null @@ -1,25 +0,0 @@ -package reqid - -import ( - "bytes" - "context" - "os" - "strings" - "testing" - - "github.com/bytom/log" -) - -func TestPrintkvRequestID(t *testing.T) { - buf := new(bytes.Buffer) - log.SetOutput(buf) - defer log.SetOutput(os.Stdout) - - log.Printkv(NewContext(context.Background(), "example-request-id")) - - got := buf.String() - want := "reqid=example-request-id" - if !strings.Contains(got, want) { - t.Errorf("Result did not contain string:\ngot: %s\nwant: %s", got, want) - } -} diff --git a/node/node.go b/node/node.go index 5b22a114c..f4d413eeb 100644 --- a/node/node.go +++ b/node/node.go @@ -10,34 +10,29 @@ import ( "sync" "time" + "github.com/kr/secureheader" + log "github.com/sirupsen/logrus" + crypto "github.com/tendermint/go-crypto" + wire "github.com/tendermint/go-wire" + cmn "github.com/tendermint/tmlibs/common" + dbm "github.com/tendermint/tmlibs/db" + _ "net/http/pprof" + + bc "github.com/bytom/blockchain" "github.com/bytom/blockchain/account" "github.com/bytom/blockchain/asset" "github.com/bytom/blockchain/pin" "github.com/bytom/blockchain/pseudohsm" "github.com/bytom/blockchain/txdb" + cfg "github.com/bytom/config" "github.com/bytom/consensus" "github.com/bytom/env" "github.com/bytom/errors" - "github.com/bytom/net/http/reqid" + p2p "github.com/bytom/p2p" "github.com/bytom/protocol" "github.com/bytom/protocol/bc/legacy" "github.com/bytom/types" "github.com/bytom/version" - "github.com/kr/secureheader" - "github.com/tendermint/tmlibs/log" - - bc "github.com/bytom/blockchain" - cfg "github.com/bytom/config" - bytomlog "github.com/bytom/log" - p2p "github.com/bytom/p2p" - rpccore "github.com/bytom/rpc/core" - grpccore "github.com/bytom/rpc/grpc" - rpcserver "github.com/bytom/rpc/lib/server" - crypto "github.com/tendermint/go-crypto" - wire "github.com/tendermint/go-wire" - cmn "github.com/tendermint/tmlibs/common" - dbm "github.com/tendermint/tmlibs/db" - _ "net/http/pprof" ) const ( @@ -59,11 +54,10 @@ type Node struct { // services evsw types.EventSwitch // pub/sub for services // blockStore *bc.MemStore - blockStore *txdb.Store - bcReactor *bc.BlockchainReactor - accounts *account.Manager - assets *asset.Registry - rpcListeners []net.Listener // rpc servers + blockStore *txdb.Store + bcReactor *bc.BlockchainReactor + accounts *account.Manager + assets *asset.Registry } var ( @@ -87,8 +81,8 @@ var ( race []interface{} // initialized in race.go ) -func NewNodeDefault(config *cfg.Config, logger log.Logger) *Node { - return NewNode(config, logger) +func NewNodeDefault(config *cfg.Config) *Node { + return NewNode(config) } func RedirectHandler(next http.Handler) http.Handler { @@ -125,9 +119,7 @@ func rpcInit(h *bc.BlockchainReactor, config *cfg.Config) { mux.Handle("/", &coreHandler) var handler http.Handler = mux - //handler = core.AuthHandler(handler, raftDB, accessTokens, tlsConfig) handler = RedirectHandler(handler) - handler = reqid.Handler(handler) secureheader.DefaultConfig.PermitClearLoopback = true secureheader.DefaultConfig.HTTPSRedirect = false @@ -152,32 +144,28 @@ func rpcInit(h *bc.BlockchainReactor, config *cfg.Config) { // we call it. go func() { err := server.Serve(listener) - bytomlog.Fatalkv(context.Background(), bytomlog.KeyError, errors.Wrap(err, "Serve")) + log.WithField("error", errors.Wrap(err, "Serve")).Error("Rpc server") }() coreHandler.Set(h) } -func NewNode(config *cfg.Config, logger log.Logger) *Node { +func NewNode(config *cfg.Config) *Node { ctx := context.Background() // Get store - tx_db := dbm.NewDB("txdb", config.DBBackend, config.DBDir()) - store := txdb.NewStore(tx_db) + txDB := dbm.NewDB("txdb", config.DBBackend, config.DBDir()) + store := txdb.NewStore(txDB) privKey := crypto.GenPrivKeyEd25519() // Make event switch eventSwitch := types.NewEventSwitch() - eventSwitch.SetLogger(logger.With("module", "types")) _, err := eventSwitch.Start() if err != nil { cmn.Exit(cmn.Fmt("Failed to start switch: %v", err)) } - p2pLogger := logger.With("module", "p2p") - sw := p2p.NewSwitch(config.P2P) - sw.SetLogger(p2pLogger) fastSync := config.FastSync @@ -201,12 +189,12 @@ func NewNode(config *cfg.Config, logger log.Logger) *Node { var pinStore *pin.Store = nil if config.Wallet.Enable { - accounts_db := dbm.NewDB("account", config.DBBackend, config.DBDir()) - acc_utxos_db := dbm.NewDB("accountutxos", config.DBBackend, config.DBDir()) - pinStore = pin.NewStore(acc_utxos_db) + accountsDB := dbm.NewDB("account", config.DBBackend, config.DBDir()) + accUTXODB := dbm.NewDB("accountutxos", config.DBBackend, config.DBDir()) + pinStore = pin.NewStore(accUTXODB) err = pinStore.LoadAll(ctx) if err != nil { - bytomlog.Error(ctx, err) + log.WithField("error", err).Error("load pin store") return nil } @@ -219,22 +207,21 @@ func NewNode(config *cfg.Config, logger log.Logger) *Node { for _, p := range pins { err = pinStore.CreatePin(ctx, p, pinHeight) if err != nil { - bytomlog.Fatalkv(ctx, bytomlog.KeyError, err) + log.WithField("error", err).Error("Create pin") } } - accounts = account.NewManager(accounts_db, chain, pinStore) + accounts = account.NewManager(accountsDB, chain, pinStore) go accounts.ProcessBlocks(ctx) - assets_db := dbm.NewDB("asset", config.DBBackend, config.DBDir()) - assets = asset.NewRegistry(assets_db, chain) + assetsDB := dbm.NewDB("asset", config.DBBackend, config.DBDir()) + assets = asset.NewRegistry(assetsDB, chain) } //Todo HSM /* if config.HsmUrl != ""{ // todo remoteHSM cmn.Exit(cmn.Fmt("not implement")) - } else { hsm, err = pseudohsm.New(config.KeysDir()) if err != nil { @@ -252,11 +239,11 @@ func NewNode(config *cfg.Config, logger log.Logger) *Node { txPool, accounts, assets, + sw, hsm, fastSync, pinStore) - bcReactor.SetLogger(logger.With("module", "blockchain")) sw.AddReactor("BLOCKCHAIN", bcReactor) rpcInit(bcReactor, config) @@ -264,9 +251,7 @@ func NewNode(config *cfg.Config, logger log.Logger) *Node { var addrBook *p2p.AddrBook if config.P2P.PexReactor { addrBook = p2p.NewAddrBook(config.P2P.AddrBookFile(), config.P2P.AddrBookStrict) - addrBook.SetLogger(p2pLogger.With("book", config.P2P.AddrBookFile())) pexReactor := p2p.NewPEXReactor(addrBook) - pexReactor.SetLogger(p2pLogger) sw.AddReactor("PEX", pexReactor) } @@ -279,7 +264,7 @@ func NewNode(config *cfg.Config, logger log.Logger) *Node { if profileHost != "" { go func() { - logger.Error("Profile server", "error", http.ListenAndServe(profileHost, nil)) + log.WithField("error", http.ListenAndServe(profileHost, nil)).Error("Profile server") }() } @@ -296,14 +281,14 @@ func NewNode(config *cfg.Config, logger log.Logger) *Node { accounts: accounts, assets: assets, } - node.BaseService = *cmn.NewBaseService(logger, "Node", node) + node.BaseService = *cmn.NewBaseService(nil, "Node", node) return node } func (n *Node) OnStart() error { // Create & add listener protocol, address := ProtocolAndAddress(n.config.P2P.ListenAddress) - l := p2p.NewDefaultListener(protocol, address, n.config.P2P.SkipUPNP, n.Logger.With("module", "p2p")) + l := p2p.NewDefaultListener(protocol, address, n.config.P2P.SkipUPNP, nil) n.sw.AddListener(l) // Start the switch @@ -322,31 +307,16 @@ func (n *Node) OnStart() error { return err } } - // Run the RPC server - if n.config.RPC.ListenAddress != "" { - listeners, err := n.startRPC() - if err != nil { - return err - } - n.rpcListeners = listeners - } - return nil } func (n *Node) OnStop() { n.BaseService.OnStop() - n.Logger.Info("Stopping Node") + log.Info("Stopping Node") // TODO: gracefully disconnect from peers. n.sw.Stop() - for _, l := range n.rpcListeners { - n.Logger.Info("Closing rpc listener", "listener", l) - if err := l.Close(); err != nil { - n.Logger.Error("Error closing listener", "listener", l, "error", err) - } - } } func (n *Node) RunForever() { @@ -370,52 +340,6 @@ func (n *Node) AddListener(l p2p.Listener) { n.sw.AddListener(l) } -// ConfigureRPC sets all variables in rpccore so they will serve -// rpc calls from this node -func (n *Node) ConfigureRPC() { - rpccore.SetEventSwitch(n.evsw) - rpccore.SetBlockStore(n.blockStore) - rpccore.SetSwitch(n.sw) - rpccore.SetAddrBook(n.addrBook) - rpccore.SetLogger(n.Logger.With("module", "rpc")) -} - -func (n *Node) startRPC() ([]net.Listener, error) { - n.ConfigureRPC() - listenAddrs := strings.Split(n.config.RPC.ListenAddress, ",") - - if n.config.RPC.Unsafe { - rpccore.AddUnsafeRoutes() - } - - // we may expose the rpc over both a unix and tcp socket - listeners := make([]net.Listener, len(listenAddrs)) - for i, listenAddr := range listenAddrs { - mux := http.NewServeMux() - wm := rpcserver.NewWebsocketManager(rpccore.Routes, n.evsw) - rpcLogger := n.Logger.With("module", "rpc-server") - wm.SetLogger(rpcLogger) - mux.HandleFunc("/websocket", wm.WebsocketHandler) - rpcserver.RegisterRPCFuncs(mux, rpccore.Routes, rpcLogger) - listener, err := rpcserver.StartHTTPServer(listenAddr, mux, rpcLogger) - if err != nil { - return nil, err - } - listeners[i] = listener - } - - // we expose a simplified api over grpc for convenience to app devs - grpcListenAddr := n.config.RPC.GRPCListenAddress - if grpcListenAddr != "" { - listener, err := grpccore.StartGRPCServer(grpcListenAddr) - if err != nil { - return nil, err - } - listeners = append(listeners, listener) - } - return listeners, nil -} - func (n *Node) Switch() *p2p.Switch { return n.sw } @@ -443,13 +367,13 @@ func (n *Node) makeNodeInfo() *p2p.NodeInfo { p2pListener := n.sw.Listeners()[0] p2pHost := p2pListener.ExternalAddress().IP.String() p2pPort := p2pListener.ExternalAddress().Port - rpcListenAddr := n.config.RPC.ListenAddress + //rpcListenAddr := n.config.RPC.ListenAddress // We assume that the rpcListener has the same ExternalAddress. // This is probably true because both P2P and RPC listeners use UPnP, // except of course if the rpc is only bound to localhost nodeInfo.ListenAddr = cmn.Fmt("%v:%v", p2pHost, p2pPort) - nodeInfo.Other = append(nodeInfo.Other, cmn.Fmt("rpc_addr=%v", rpcListenAddr)) + //nodeInfo.Other = append(nodeInfo.Other, cmn.Fmt("rpc_addr=%v", rpcListenAddr)) return nodeInfo } diff --git a/protocol/bc/legacy/map_test.go b/protocol/bc/legacy/map_test.go index 3ab5df9d9..da342cc2f 100644 --- a/protocol/bc/legacy/map_test.go +++ b/protocol/bc/legacy/map_test.go @@ -63,12 +63,12 @@ func TestMapTx(t *testing.T) { } func TestMapCoinbaseTx(t *testing.T) { -// define the BTM asset id, the soul asset of Bytom - var BTMAssetID = &bc.AssetID{ - V0: uint64(18446744073709551615), - V1: uint64(18446744073709551615), - V2: uint64(18446744073709551615), - V3: uint64(18446744073709551615), + // define the BTM asset id, the soul asset of Bytom + var BTMAssetID = &bc.AssetID{ + V0: uint64(18446744073709551615), + V1: uint64(18446744073709551615), + V2: uint64(18446744073709551615), + V3: uint64(18446744073709551615), } oldTx := &TxData{ Version: 1, diff --git a/protocol/block.go b/protocol/block.go index 49e346886..6a48f65ba 100644 --- a/protocol/block.go +++ b/protocol/block.go @@ -5,10 +5,11 @@ import ( "time" "github.com/bytom/errors" - "github.com/bytom/log" "github.com/bytom/protocol/bc/legacy" "github.com/bytom/protocol/state" "github.com/bytom/protocol/validation" + + log "github.com/sirupsen/logrus" ) // maxBlockTxs limits the number of transactions @@ -54,7 +55,8 @@ func (c *Chain) ValidateBlock(block, prev *legacy.Block) error { // ApplyValidBlock creates an updated snapshot without validating the // block. func (c *Chain) ApplyValidBlock(block *legacy.Block) (*state.Snapshot, error) { - newSnapshot := state.Copy(c.state.snapshot) + _, curSnapshot := c.State() + newSnapshot := state.Copy(curSnapshot) err := newSnapshot.ApplyBlock(legacy.MapBlock(block)) if err != nil { return nil, err @@ -136,8 +138,7 @@ func (c *Chain) queueSnapshot(ctx context.Context, height uint64, timestamp time c.lastQueuedSnapshot = timestamp default: // Skip it; saving snapshots is taking longer than the snapshotting period. - log.Printf(ctx, "snapshot storage is taking too long; last queued at %s", - c.lastQueuedSnapshot) + log.WithField("last queued", c.lastQueuedSnapshot).Info("snapshot storage is taking too long") } } diff --git a/protocol/protocol.go b/protocol/protocol.go index d60c8b5a9..f91480e6a 100644 --- a/protocol/protocol.go +++ b/protocol/protocol.go @@ -5,8 +5,9 @@ import ( "sync" "time" + log "github.com/sirupsen/logrus" + "github.com/bytom/errors" - "github.com/bytom/log" "github.com/bytom/protocol/bc" "github.com/bytom/protocol/bc/legacy" "github.com/bytom/protocol/state" @@ -57,7 +58,7 @@ type Chain struct { lastQueuedSnapshot time.Time pendingSnapshots chan pendingSnapshot - txPool *TxPool + txPool *TxPool } type pendingSnapshot struct { @@ -75,7 +76,7 @@ func NewChain(ctx context.Context, initialBlockHash bc.Hash, store Store, txPool } c.state.cond.L = new(sync.Mutex) - log.Printf(ctx, "bytom's Height:%v.", store.Height()) + log.WithField("current height", store.Height()).Info("Resume from the database") c.state.height = store.Height() if c.state.height < 1 { @@ -102,7 +103,7 @@ func NewChain(ctx context.Context, initialBlockHash bc.Hash, store Store, txPool case ps := <-c.pendingSnapshots: err := store.SaveSnapshot(ctx, ps.height, ps.snapshot) if err != nil { - log.Error(ctx, err, "at", "saving snapshot") + log.WithField("error", err).Error("Error occurs when saving snapshot") } } } diff --git a/rpc/client/event_test.go b/rpc/client/event_test.go deleted file mode 100644 index 87c32d274..000000000 --- a/rpc/client/event_test.go +++ /dev/null @@ -1,65 +0,0 @@ -package client_test - -import ( - "testing" - "time" - - "github.com/bytom/rpc/client" - "github.com/bytom/types" - "github.com/stretchr/testify/require" - merktest "github.com/tendermint/merkleeyes/testutil" -) - -func TestHeaderEvents(t *testing.T) { - require := require.New(t) - for i, c := range GetClients() { - // start for this test it if it wasn't already running - if !c.IsRunning() { - // if so, then we start it, listen, and stop it. - st, err := c.Start() - require.Nil(err, "%d: %+v", i, err) - require.True(st, "%d", i) - defer c.Stop() - } - - evtTyp := types.EventStringNewBlockHeader() - evt, err := client.WaitForOneEvent(c, evtTyp, 1*time.Second) - require.Nil(err, "%d: %+v", i, err) - _, ok := evt.Unwrap().(types.EventDataNewBlockHeader) - require.True(ok, "%d: %#v", i, evt) - // TODO: more checks... - } -} - -func TestTxEvents(t *testing.T) { - require := require.New(t) - for i, c := range GetClients() { - // start for this test it if it wasn't already running - if !c.IsRunning() { - // if so, then we start it, listen, and stop it. - st, err := c.Start() - require.Nil(err, "%d: %+v", i, err) - require.True(st, "%d", i) - defer c.Stop() - } - - // make the tx - _, _, tx := merktest.MakeTxKV() - evtTyp := types.EventStringTx(types.Tx(tx)) - - // send async - txres, err := c.BroadcastTxAsync(tx) - require.Nil(err, "%+v", err) - require.True(txres.Code.IsOK()) - - // and wait for confirmation - evt, err := client.WaitForOneEvent(c, evtTyp, 1*time.Second) - require.Nil(err, "%d: %+v", i, err) - // and make sure it has the proper info - txe, ok := evt.Unwrap().(types.EventDataTx) - require.True(ok, "%d: %#v", i, evt) - // make sure this is the proper tx - require.EqualValues(tx, txe.Tx) - require.True(txe.Code.IsOK()) - } -} diff --git a/rpc/client/helpers.go b/rpc/client/helpers.go deleted file mode 100644 index ad83e0204..000000000 --- a/rpc/client/helpers.go +++ /dev/null @@ -1,88 +0,0 @@ -package client - -import ( - "time" - - "github.com/bytom/types" - "github.com/pkg/errors" - cmn "github.com/tendermint/tmlibs/common" - events "github.com/tendermint/tmlibs/events" -) - -// Waiter is informed of current height, decided whether to quit early -type Waiter func(delta int) (abort error) - -// DefaultWaitStrategy is the standard backoff algorithm, -// but you can plug in another one -func DefaultWaitStrategy(delta int) (abort error) { - if delta > 10 { - return errors.Errorf("Waiting for %d blocks... aborting", delta) - } else if delta > 0 { - // estimate of wait time.... - // wait half a second for the next block (in progress) - // plus one second for every full block - delay := time.Duration(delta-1)*time.Second + 500*time.Millisecond - time.Sleep(delay) - } - return nil -} - -// Wait for height will poll status at reasonable intervals until -// the block at the given height is available. -// -// If waiter is nil, we use DefaultWaitStrategy, but you can also -// provide your own implementation -func WaitForHeight(c StatusClient, h int, waiter Waiter) error { - if waiter == nil { - waiter = DefaultWaitStrategy - } - delta := 1 - for delta > 0 { - s, err := c.Status() - if err != nil { - return err - } - delta = h - s.LatestBlockHeight - // wait for the time, or abort early - if err := waiter(delta); err != nil { - return err - } - } - return nil -} - -// WaitForOneEvent subscribes to a websocket event for the given -// event time and returns upon receiving it one time, or -// when the timeout duration has expired. -// -// This handles subscribing and unsubscribing under the hood -func WaitForOneEvent(evsw types.EventSwitch, - evtTyp string, timeout time.Duration) (types.TMEventData, error) { - listener := cmn.RandStr(12) - - evts, quit := make(chan events.EventData, 10), make(chan bool, 1) - // start timeout count-down - go func() { - time.Sleep(timeout) - quit <- true - }() - - // register for the next event of this type - evsw.AddListenerForEvent(listener, evtTyp, func(data events.EventData) { - evts <- data - }) - // make sure to unregister after the test is over - defer evsw.RemoveListenerForEvent(evtTyp, listener) - // defer evsw.RemoveListener(listener) // this also works - - select { - case <-quit: - return types.TMEventData{}, errors.New("timed out waiting for event") - case evt := <-evts: - tmevt, ok := evt.(types.TMEventData) - if ok { - return tmevt, nil - } - return types.TMEventData{}, errors.Errorf("Got unexpected event type: %#v", evt) - } -} diff --git a/rpc/client/helpers_test.go b/rpc/client/helpers_test.go deleted file mode 100644 index 2424fa682..000000000 --- a/rpc/client/helpers_test.go +++ /dev/null @@ -1,76 +0,0 @@ -package client_test - -import ( - "errors" - "strings" - "testing" - - "github.com/bytom/rpc/client" - "github.com/bytom/rpc/client/mock" - ctypes "github.com/bytom/rpc/core/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestWaitForHeight(t *testing.T) { - assert, require := assert.New(t), require.New(t) - - // test with error result - immediate failure - m := &mock.StatusMock{ - Call: mock.Call{ - Error: errors.New("bye"), - }, - } - r := mock.NewStatusRecorder(m) - - // connection failure always leads to error - err := client.WaitForHeight(r, 8, nil) - require.NotNil(err) - require.Equal("bye", err.Error()) - // we called status once to check - require.Equal(1, len(r.Calls)) - - // now set current block height to 10 - m.Call = mock.Call{ - Response: &ctypes.ResultStatus{LatestBlockHeight: 10}, - } - - // we will not wait for more than 10 blocks - err = client.WaitForHeight(r, 40, nil) - require.NotNil(err) - require.True(strings.Contains(err.Error(), "aborting")) - // we called status once more to check - require.Equal(2, len(r.Calls)) - - // waiting for the past returns immediately - err = client.WaitForHeight(r, 5, nil) - require.Nil(err) - // we called status once more to check - require.Equal(3, len(r.Calls)) - - // since we can't update in a background goroutine (test --race) - // we use the callback to update the status height - myWaiter := func(delta int) error { - // update the height for the next call - m.Call.Response = &ctypes.ResultStatus{LatestBlockHeight: 15} - return client.DefaultWaitStrategy(delta) - } - - // we wait for a few blocks - err = client.WaitForHeight(r, 12, myWaiter) - require.Nil(err) - // we called status once to check - require.Equal(5, len(r.Calls)) - - pre := r.Calls[3] - require.Nil(pre.Error) - prer, ok := pre.Response.(*ctypes.ResultStatus) - require.True(ok) - assert.Equal(10, prer.LatestBlockHeight) - - post := r.Calls[4] - require.Nil(post.Error) - postr, ok := post.Response.(*ctypes.ResultStatus) - require.True(ok) - assert.Equal(15, postr.LatestBlockHeight) -} diff --git a/rpc/client/httpclient.go b/rpc/client/httpclient.go deleted file mode 100644 index 2ac9faef7..000000000 --- a/rpc/client/httpclient.go +++ /dev/null @@ -1,363 +0,0 @@ -package client - -import ( - "encoding/json" - "fmt" - - ctypes "github.com/bytom/rpc/core/types" - "github.com/bytom/rpc/lib/client" - "github.com/bytom/types" - "github.com/pkg/errors" - data "github.com/tendermint/go-wire/data" - events "github.com/tendermint/tmlibs/events" -) - -/* -HTTP is a Client implementation that communicates -with a tendermint node over json rpc and websockets. - -This is the main implementation you probably want to use in -production code. There are other implementations when calling -the tendermint node in-process (local), or when you want to mock -out the server for test code (mock). -*/ -type HTTP struct { - remote string - rpc *rpcclient.JSONRPCClient - *WSEvents -} - -// New takes a remote endpoint in the form tcp://: -// and the websocket path (which always seems to be "/websocket") -func NewHTTP(remote, wsEndpoint string) *HTTP { - return &HTTP{ - rpc: rpcclient.NewJSONRPCClient(remote), - remote: remote, - WSEvents: newWSEvents(remote, wsEndpoint), - } -} - -func (c *HTTP) _assertIsClient() Client { - return c -} - -func (c *HTTP) _assertIsNetworkClient() NetworkClient { - return c -} - -func (c *HTTP) _assertIsEventSwitch() types.EventSwitch { - return c -} - -func (c *HTTP) Status() (*ctypes.ResultStatus, error) { - result := new(ctypes.ResultStatus) - _, err := c.rpc.Call("status", map[string]interface{}{}, result) - if err != nil { - return nil, errors.Wrap(err, "Status") - } - return result, nil -} - -func (c *HTTP) ABCIInfo() (*ctypes.ResultABCIInfo, error) { - result := new(ctypes.ResultABCIInfo) - _, err := c.rpc.Call("abci_info", map[string]interface{}{}, result) - if err != nil { - return nil, errors.Wrap(err, "ABCIInfo") - } - return result, nil -} - -func (c *HTTP) ABCIQuery(path string, data data.Bytes, prove bool) (*ctypes.ResultABCIQuery, error) { - result := new(ctypes.ResultABCIQuery) - _, err := c.rpc.Call("abci_query", - map[string]interface{}{"path": path, "data": data, "prove": prove}, - result) - if err != nil { - return nil, errors.Wrap(err, "ABCIQuery") - } - return result, nil -} - -func (c *HTTP) BroadcastTxCommit(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) { - result := new(ctypes.ResultBroadcastTxCommit) - _, err := c.rpc.Call("broadcast_tx_commit", map[string]interface{}{"tx": tx}, result) - if err != nil { - return nil, errors.Wrap(err, "broadcast_tx_commit") - } - return result, nil -} - -func (c *HTTP) BroadcastTxAsync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) { - return c.broadcastTX("broadcast_tx_async", tx) -} - -func (c *HTTP) BroadcastTxSync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) { - return c.broadcastTX("broadcast_tx_sync", tx) -} - -func (c *HTTP) broadcastTX(route string, tx types.Tx) (*ctypes.ResultBroadcastTx, error) { - result := new(ctypes.ResultBroadcastTx) - _, err := c.rpc.Call(route, map[string]interface{}{"tx": tx}, result) - if err != nil { - return nil, errors.Wrap(err, route) - } - return result, nil -} - -func (c *HTTP) NetInfo() (*ctypes.ResultNetInfo, error) { - result := new(ctypes.ResultNetInfo) - _, err := c.rpc.Call("net_info", map[string]interface{}{}, result) - if err != nil { - return nil, errors.Wrap(err, "NetInfo") - } - return result, nil -} - -func (c *HTTP) DumpConsensusState() (*ctypes.ResultDumpConsensusState, error) { - result := new(ctypes.ResultDumpConsensusState) - _, err := c.rpc.Call("dump_consensus_state", map[string]interface{}{}, result) - if err != nil { - return nil, errors.Wrap(err, "DumpConsensusState") - } - return result, nil -} - -func (c *HTTP) BlockchainInfo(minHeight, maxHeight int) (*ctypes.ResultBlockchainInfo, error) { - result := new(ctypes.ResultBlockchainInfo) - _, err := c.rpc.Call("blockchain", - map[string]interface{}{"minHeight": minHeight, "maxHeight": maxHeight}, - result) - if err != nil { - return nil, errors.Wrap(err, "BlockchainInfo") - } - return result, nil -} - -func (c *HTTP) Genesis() (*ctypes.ResultGenesis, error) { - result := new(ctypes.ResultGenesis) - _, err := c.rpc.Call("genesis", map[string]interface{}{}, result) - if err != nil { - return nil, errors.Wrap(err, "Genesis") - } - return result, nil -} - -func (c *HTTP) Block(height int) (*ctypes.ResultBlock, error) { - result := new(ctypes.ResultBlock) - _, err := c.rpc.Call("block", map[string]interface{}{"height": height}, result) - if err != nil { - return nil, errors.Wrap(err, "Block") - } - return result, nil -} - -func (c *HTTP) Commit(height int) (*ctypes.ResultCommit, error) { - result := new(ctypes.ResultCommit) - _, err := c.rpc.Call("commit", map[string]interface{}{"height": height}, result) - if err != nil { - return nil, errors.Wrap(err, "Commit") - } - return result, nil -} - -func (c *HTTP) Tx(hash []byte, prove bool) (*ctypes.ResultTx, error) { - result := new(ctypes.ResultTx) - query := map[string]interface{}{ - "hash": hash, - "prove": prove, - } - _, err := c.rpc.Call("tx", query, result) - if err != nil { - return nil, errors.Wrap(err, "Tx") - } - return result, nil -} - -func (c *HTTP) Validators() (*ctypes.ResultValidators, error) { - result := new(ctypes.ResultValidators) - _, err := c.rpc.Call("validators", map[string]interface{}{}, result) - if err != nil { - return nil, errors.Wrap(err, "Validators") - } - return result, nil -} - -/** websocket event stuff here... **/ - -type WSEvents struct { - types.EventSwitch - remote string - endpoint string - ws *rpcclient.WSClient - - // used for signaling the goroutine that feeds ws -> EventSwitch - quit chan bool - done chan bool - - // used to maintain counts of actively listened events - // so we can properly subscribe/unsubscribe - // FIXME: thread-safety??? - // FIXME: reuse code from tmlibs/events??? - evtCount map[string]int // count how many time each event is subscribed - listeners map[string][]string // keep track of which events each listener is listening to -} - -func newWSEvents(remote, endpoint string) *WSEvents { - return &WSEvents{ - EventSwitch: types.NewEventSwitch(), - endpoint: endpoint, - remote: remote, - quit: make(chan bool, 1), - done: make(chan bool, 1), - evtCount: map[string]int{}, - listeners: map[string][]string{}, - } -} - -func (w *WSEvents) _assertIsEventSwitch() types.EventSwitch { - return w -} - -// Start is the only way I could think the extend OnStart from -// events.eventSwitch. If only it wasn't private... -// BaseService.Start -> eventSwitch.OnStart -> WSEvents.Start -func (w *WSEvents) Start() (bool, error) { - st, err := w.EventSwitch.Start() - // if we did start, then OnStart here... - if st && err == nil { - ws := rpcclient.NewWSClient(w.remote, w.endpoint) - _, err = ws.Start() - if err == nil { - w.ws = ws - go w.eventListener() - } - } - return st, errors.Wrap(err, "StartWSEvent") -} - -// Stop wraps the BaseService/eventSwitch actions as Start does -func (w *WSEvents) Stop() bool { - stop := w.EventSwitch.Stop() - if stop { - // send a message to quit to stop the eventListener - w.quit <- true - <-w.done - w.ws.Stop() - w.ws = nil - } - return stop -} - -/** TODO: more intelligent subscriptions! **/ -func (w *WSEvents) AddListenerForEvent(listenerID, event string, cb events.EventCallback) { - // no one listening -> subscribe - if w.evtCount[event] == 0 { - w.subscribe(event) - } - // if this listener was already listening to this event, return early - for _, s := range w.listeners[listenerID] { - if event == s { - return - } - } - // otherwise, add this event to this listener - w.evtCount[event] += 1 - w.listeners[listenerID] = append(w.listeners[listenerID], event) - w.EventSwitch.AddListenerForEvent(listenerID, event, cb) -} - -func (w *WSEvents) RemoveListenerForEvent(event string, listenerID string) { - // if this listener is listening already, splice it out - found := false - l := w.listeners[listenerID] - for i, s := range l { - if event == s { - found = true - w.listeners[listenerID] = append(l[:i], l[i+1:]...) - break - } - } - // if the listener wasn't already listening to the event, exit early - if !found { - return - } - - // now we can update the subscriptions - w.evtCount[event] -= 1 - if w.evtCount[event] == 0 { - w.unsubscribe(event) - } - w.EventSwitch.RemoveListenerForEvent(event, listenerID) -} - -func (w *WSEvents) RemoveListener(listenerID string) { - // remove all counts for this listener - for _, s := range w.listeners[listenerID] { - w.evtCount[s] -= 1 - if w.evtCount[s] == 0 { - w.unsubscribe(s) - } - } - w.listeners[listenerID] = nil - - // then let the switch do it's magic - w.EventSwitch.RemoveListener(listenerID) -} - -// eventListener is an infinite loop pulling all websocket events -// and pushing them to the EventSwitch. -// -// the goroutine only stops by closing quit -func (w *WSEvents) eventListener() { - for { - select { - case res := <-w.ws.ResultsCh: - // res is json.RawMessage - err := w.parseEvent(res) - if err != nil { - // FIXME: better logging/handling of errors?? - fmt.Printf("ws result: %+v\n", err) - } - case err := <-w.ws.ErrorsCh: - // FIXME: better logging/handling of errors?? - fmt.Printf("ws err: %+v\n", err) - case <-w.quit: - // send a message so we can wait for the routine to exit - // before cleaning up the w.ws stuff - w.done <- true - return - } - } -} - -// parseEvent unmarshals the json message and converts it into -// some implementation of types.TMEventData, and sends it off -// on the merry way to the EventSwitch -func (w *WSEvents) parseEvent(data []byte) (err error) { - result := new(ctypes.ResultEvent) - err = json.Unmarshal(data, result) - if err != nil { - // ignore silently (eg. subscribe, unsubscribe and maybe other events) - // TODO: ? - return nil - } - // looks good! let's fire this baby! - w.EventSwitch.FireEvent(result.Name, result.Data) - return nil -} - -// no way of exposing these failures, so we panic. -// is this right? or silently ignore??? -func (w *WSEvents) subscribe(event string) { - err := w.ws.Subscribe(event) - if err != nil { - panic(err) - } -} - -func (w *WSEvents) unsubscribe(event string) { - err := w.ws.Unsubscribe(event) - if err != nil { - panic(err) - } -} diff --git a/rpc/client/interface.go b/rpc/client/interface.go deleted file mode 100644 index 143384572..000000000 --- a/rpc/client/interface.go +++ /dev/null @@ -1,84 +0,0 @@ -/* -package client provides a general purpose interface (Client) for connecting -to a tendermint node, as well as higher-level functionality. - -The main implementation for production code is client.HTTP, which -connects via http to the jsonrpc interface of the tendermint node. - -For connecting to a node running in the same process (eg. when -compiling the abci app in the same process), you can use the client.Local -implementation. - -For mocking out server responses during testing to see behavior for -arbitrary return values, use the mock package. - -In addition to the Client interface, which should be used externally -for maximum flexibility and testability, and two implementations, -this package also provides helper functions that work on any Client -implementation. -*/ -package client - -import ( - ctypes "github.com/bytom/rpc/core/types" - "github.com/bytom/types" - data "github.com/tendermint/go-wire/data" -) - -// ABCIClient groups together the functionality that principally -// affects the ABCI app. In many cases this will be all we want, -// so we can accept an interface which is easier to mock -type ABCIClient interface { - // reading from abci app - ABCIInfo() (*ctypes.ResultABCIInfo, error) - ABCIQuery(path string, data data.Bytes, prove bool) (*ctypes.ResultABCIQuery, error) - - // writing to abci app - BroadcastTxCommit(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) - BroadcastTxAsync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) - BroadcastTxSync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) -} - -// SignClient groups together the interfaces need to get valid -// signatures and prove anything about the chain -type SignClient interface { - Block(height int) (*ctypes.ResultBlock, error) - Commit(height int) (*ctypes.ResultCommit, error) - Validators() (*ctypes.ResultValidators, error) - Tx(hash []byte, prove bool) (*ctypes.ResultTx, error) -} - -// HistoryClient shows us data from genesis to now in large chunks. -type HistoryClient interface { - Genesis() (*ctypes.ResultGenesis, error) - BlockchainInfo(minHeight, maxHeight int) (*ctypes.ResultBlockchainInfo, error) -} - -type StatusClient interface { - // general chain info - Status() (*ctypes.ResultStatus, error) -} - -// Client wraps most important rpc calls a client would make -// if you want to listen for events, test if it also -// implements events.EventSwitch -type Client interface { - ABCIClient - SignClient - HistoryClient - StatusClient - - // this Client is reactive, you can subscribe to any TMEventData - // type, given the proper string. see tendermint/types/events.go - types.EventSwitch -} - -// NetworkClient is general info about the network state. May not -// be needed usually. -// -// Not included in the Client interface, but generally implemented -// by concrete implementations. -type NetworkClient interface { - NetInfo() (*ctypes.ResultNetInfo, error) - DumpConsensusState() (*ctypes.ResultDumpConsensusState, error) -} diff --git a/rpc/client/localclient.go b/rpc/client/localclient.go deleted file mode 100644 index 41dae37ed..000000000 --- a/rpc/client/localclient.go +++ /dev/null @@ -1,110 +0,0 @@ -package client - -import ( - nm "github.com/bytom/node" - "github.com/bytom/rpc/core" - ctypes "github.com/bytom/rpc/core/types" - "github.com/bytom/types" - data "github.com/tendermint/go-wire/data" -) - -/* -Local is a Client implementation that directly executes the rpc -functions on a given node, without going through HTTP or GRPC - -This implementation is useful for: - -* Running tests against a node in-process without the overhead -of going through an http server -* Communication between an ABCI app and tendermin core when they -are compiled in process. - -For real clients, you probably want to use client.HTTP. For more -powerful control during testing, you probably want the "client/mock" package. -*/ -type Local struct { - node *nm.Node - types.EventSwitch -} - -// NewLocal configures a client that calls the Node directly. -// -// Note that given how rpc/core works with package singletons, that -// you can only have one node per process. So make sure test cases -// don't run in parallel, or try to simulate an entire network in -// one process... -func NewLocal(node *nm.Node) Local { - node.ConfigureRPC() - return Local{ - node: node, - EventSwitch: node.EventSwitch(), - } -} - -func (c Local) _assertIsClient() Client { - return c -} - -func (c Local) _assertIsNetworkClient() NetworkClient { - return c -} - -func (c Local) Status() (*ctypes.ResultStatus, error) { - return core.Status() -} - -func (c Local) ABCIInfo() (*ctypes.ResultABCIInfo, error) { - return core.ABCIInfo() -} - -func (c Local) ABCIQuery(path string, data data.Bytes, prove bool) (*ctypes.ResultABCIQuery, error) { - return core.ABCIQuery(path, data, prove) -} - -func (c Local) BroadcastTxCommit(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) { - return core.BroadcastTxCommit(tx) -} - -func (c Local) BroadcastTxAsync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) { - return core.BroadcastTxAsync(tx) -} - -func (c Local) BroadcastTxSync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) { - return core.BroadcastTxSync(tx) -} - -func (c Local) NetInfo() (*ctypes.ResultNetInfo, error) { - return core.NetInfo() -} - -func (c Local) DumpConsensusState() (*ctypes.ResultDumpConsensusState, error) { - return core.DumpConsensusState() -} - -func (c Local) DialSeeds(seeds []string) (*ctypes.ResultDialSeeds, error) { - return core.UnsafeDialSeeds(seeds) -} - -func (c Local) BlockchainInfo(minHeight, maxHeight int) (*ctypes.ResultBlockchainInfo, error) { - return core.BlockchainInfo(minHeight, maxHeight) -} - -func (c Local) Genesis() (*ctypes.ResultGenesis, error) { - return core.Genesis() -} - -func (c Local) Block(height int) (*ctypes.ResultBlock, error) { - return core.Block(height) -} - -func (c Local) Commit(height int) (*ctypes.ResultCommit, error) { - return core.Commit(height) -} - -func (c Local) Validators() (*ctypes.ResultValidators, error) { - return core.Validators() -} - -func (c Local) Tx(hash []byte, prove bool) (*ctypes.ResultTx, error) { - return core.Tx(hash, prove) -} diff --git a/rpc/client/main_test.go b/rpc/client/main_test.go deleted file mode 100644 index 275a3bb7a..000000000 --- a/rpc/client/main_test.go +++ /dev/null @@ -1,24 +0,0 @@ -package client_test - -import ( - "os" - "testing" - - nm "github.com/bytom/node" - rpctest "github.com/bytom/rpc/test" - meapp "github.com/tendermint/merkleeyes/app" -) - -var node *nm.Node - -func TestMain(m *testing.M) { - // start a tendermint node (and merkleeyes) in the background to test against - app := meapp.NewMerkleEyesApp("", 100) - node = rpctest.StartTendermint(app) - code := m.Run() - - // and shut down proper at the end - node.Stop() - node.Wait() - os.Exit(code) -} diff --git a/rpc/client/mock/abci.go b/rpc/client/mock/abci.go deleted file mode 100644 index 0d1012557..000000000 --- a/rpc/client/mock/abci.go +++ /dev/null @@ -1,194 +0,0 @@ -package mock - -import ( - abci "github.com/tendermint/abci/types" - data "github.com/tendermint/go-wire/data" - "github.com/tendermint/tendermint/rpc/client" - ctypes "github.com/tendermint/tendermint/rpc/core/types" - "github.com/tendermint/tendermint/types" -) - -// ABCIApp will send all abci related request to the named app, -// so you can test app behavior from a client without needing -// an entire tendermint node -type ABCIApp struct { - App abci.Application -} - -func (a ABCIApp) _assertABCIClient() client.ABCIClient { - return a -} - -func (a ABCIApp) ABCIInfo() (*ctypes.ResultABCIInfo, error) { - return &ctypes.ResultABCIInfo{a.App.Info()}, nil -} - -func (a ABCIApp) ABCIQuery(path string, data data.Bytes, prove bool) (*ctypes.ResultABCIQuery, error) { - q := a.App.Query(abci.RequestQuery{data, path, 0, prove}) - return &ctypes.ResultABCIQuery{q.Result()}, nil -} - -func (a ABCIApp) BroadcastTxCommit(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) { - res := ctypes.ResultBroadcastTxCommit{} - res.CheckTx = a.App.CheckTx(tx) - if !res.CheckTx.IsOK() { - return &res, nil - } - res.DeliverTx = a.App.DeliverTx(tx) - return &res, nil -} - -func (a ABCIApp) BroadcastTxAsync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) { - c := a.App.CheckTx(tx) - // and this gets writen in a background thread... - if c.IsOK() { - go func() { a.App.DeliverTx(tx) }() - } - return &ctypes.ResultBroadcastTx{c.Code, c.Data, c.Log, tx.Hash()}, nil -} - -func (a ABCIApp) BroadcastTxSync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) { - c := a.App.CheckTx(tx) - // and this gets writen in a background thread... - if c.IsOK() { - go func() { a.App.DeliverTx(tx) }() - } - return &ctypes.ResultBroadcastTx{c.Code, c.Data, c.Log, tx.Hash()}, nil -} - -// ABCIMock will send all abci related request to the named app, -// so you can test app behavior from a client without needing -// an entire tendermint node -type ABCIMock struct { - Info Call - Query Call - BroadcastCommit Call - Broadcast Call -} - -func (m ABCIMock) _assertABCIClient() client.ABCIClient { - return m -} - -func (m ABCIMock) ABCIInfo() (*ctypes.ResultABCIInfo, error) { - res, err := m.Info.GetResponse(nil) - if err != nil { - return nil, err - } - return &ctypes.ResultABCIInfo{res.(abci.ResponseInfo)}, nil -} - -func (m ABCIMock) ABCIQuery(path string, data data.Bytes, prove bool) (*ctypes.ResultABCIQuery, error) { - res, err := m.Query.GetResponse(QueryArgs{path, data, prove}) - if err != nil { - return nil, err - } - resQuery := res.(abci.ResponseQuery) - return &ctypes.ResultABCIQuery{resQuery.Result()}, nil -} - -func (m ABCIMock) BroadcastTxCommit(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) { - res, err := m.BroadcastCommit.GetResponse(tx) - if err != nil { - return nil, err - } - return res.(*ctypes.ResultBroadcastTxCommit), nil -} - -func (m ABCIMock) BroadcastTxAsync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) { - res, err := m.Broadcast.GetResponse(tx) - if err != nil { - return nil, err - } - return res.(*ctypes.ResultBroadcastTx), nil -} - -func (m ABCIMock) BroadcastTxSync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) { - res, err := m.Broadcast.GetResponse(tx) - if err != nil { - return nil, err - } - return res.(*ctypes.ResultBroadcastTx), nil -} - -// ABCIRecorder can wrap another type (ABCIApp, ABCIMock, or Client) -// and record all ABCI related calls. -type ABCIRecorder struct { - Client client.ABCIClient - Calls []Call -} - -func NewABCIRecorder(client client.ABCIClient) *ABCIRecorder { - return &ABCIRecorder{ - Client: client, - Calls: []Call{}, - } -} - -func (r *ABCIRecorder) _assertABCIClient() client.ABCIClient { - return r -} - -type QueryArgs struct { - Path string - Data data.Bytes - Prove bool -} - -func (r *ABCIRecorder) addCall(call Call) { - r.Calls = append(r.Calls, call) -} - -func (r *ABCIRecorder) ABCIInfo() (*ctypes.ResultABCIInfo, error) { - res, err := r.Client.ABCIInfo() - r.addCall(Call{ - Name: "abci_info", - Response: res, - Error: err, - }) - return res, err -} - -func (r *ABCIRecorder) ABCIQuery(path string, data data.Bytes, prove bool) (*ctypes.ResultABCIQuery, error) { - res, err := r.Client.ABCIQuery(path, data, prove) - r.addCall(Call{ - Name: "abci_query", - Args: QueryArgs{path, data, prove}, - Response: res, - Error: err, - }) - return res, err -} - -func (r *ABCIRecorder) BroadcastTxCommit(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) { - res, err := r.Client.BroadcastTxCommit(tx) - r.addCall(Call{ - Name: "broadcast_tx_commit", - Args: tx, - Response: res, - Error: err, - }) - return res, err -} - -func (r *ABCIRecorder) BroadcastTxAsync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) { - res, err := r.Client.BroadcastTxAsync(tx) - r.addCall(Call{ - Name: "broadcast_tx_async", - Args: tx, - Response: res, - Error: err, - }) - return res, err -} - -func (r *ABCIRecorder) BroadcastTxSync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) { - res, err := r.Client.BroadcastTxSync(tx) - r.addCall(Call{ - Name: "broadcast_tx_sync", - Args: tx, - Response: res, - Error: err, - }) - return res, err -} diff --git a/rpc/client/mock/abci_test.go b/rpc/client/mock/abci_test.go deleted file mode 100644 index 935f9ff94..000000000 --- a/rpc/client/mock/abci_test.go +++ /dev/null @@ -1,170 +0,0 @@ -package mock_test - -import ( - "fmt" - "testing" - - "github.com/pkg/errors" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/tendermint/abci/example/dummy" - abci "github.com/tendermint/abci/types" - data "github.com/tendermint/go-wire/data" - ctypes "github.com/tendermint/tendermint/rpc/core/types" - "github.com/tendermint/tendermint/types" - - "github.com/tendermint/tendermint/rpc/client/mock" -) - -func TestABCIMock(t *testing.T) { - assert, require := assert.New(t), require.New(t) - - key, value := []byte("foo"), []byte("bar") - height := uint64(10) - goodTx := types.Tx{0x01, 0xff} - badTx := types.Tx{0x12, 0x21} - - m := mock.ABCIMock{ - Info: mock.Call{Error: errors.New("foobar")}, - Query: mock.Call{Response: abci.ResponseQuery{ - Key: key, - Value: value, - Height: height, - }}, - // Broadcast commit depends on call - BroadcastCommit: mock.Call{ - Args: goodTx, - Response: &ctypes.ResultBroadcastTxCommit{ - CheckTx: abci.Result{Data: data.Bytes("stand")}, - DeliverTx: abci.Result{Data: data.Bytes("deliver")}, - }, - Error: errors.New("bad tx"), - }, - Broadcast: mock.Call{Error: errors.New("must commit")}, - } - - // now, let's try to make some calls - _, err := m.ABCIInfo() - require.NotNil(err) - assert.Equal("foobar", err.Error()) - - // query always returns the response - query, err := m.ABCIQuery("/", nil, false) - require.Nil(err) - require.NotNil(query) - assert.EqualValues(key, query.Key) - assert.EqualValues(value, query.Value) - assert.Equal(height, query.Height) - - // non-commit calls always return errors - _, err = m.BroadcastTxSync(goodTx) - require.NotNil(err) - assert.Equal("must commit", err.Error()) - _, err = m.BroadcastTxAsync(goodTx) - require.NotNil(err) - assert.Equal("must commit", err.Error()) - - // commit depends on the input - _, err = m.BroadcastTxCommit(badTx) - require.NotNil(err) - assert.Equal("bad tx", err.Error()) - bres, err := m.BroadcastTxCommit(goodTx) - require.Nil(err, "%+v", err) - assert.EqualValues(0, bres.CheckTx.Code) - assert.EqualValues("stand", bres.CheckTx.Data) - assert.EqualValues("deliver", bres.DeliverTx.Data) -} - -func TestABCIRecorder(t *testing.T) { - assert, require := assert.New(t), require.New(t) - m := mock.ABCIMock{ - Info: mock.Call{Response: abci.ResponseInfo{ - Data: "data", - Version: "v0.9.9", - }}, - Query: mock.Call{Error: errors.New("query")}, - Broadcast: mock.Call{Error: errors.New("broadcast")}, - BroadcastCommit: mock.Call{Error: errors.New("broadcast_commit")}, - } - r := mock.NewABCIRecorder(m) - - require.Equal(0, len(r.Calls)) - - r.ABCIInfo() - r.ABCIQuery("path", data.Bytes("data"), true) - require.Equal(2, len(r.Calls)) - - info := r.Calls[0] - assert.Equal("abci_info", info.Name) - assert.Nil(info.Error) - assert.Nil(info.Args) - require.NotNil(info.Response) - ir, ok := info.Response.(*ctypes.ResultABCIInfo) - require.True(ok) - assert.Equal("data", ir.Response.Data) - assert.Equal("v0.9.9", ir.Response.Version) - - query := r.Calls[1] - assert.Equal("abci_query", query.Name) - assert.Nil(query.Response) - require.NotNil(query.Error) - assert.Equal("query", query.Error.Error()) - require.NotNil(query.Args) - qa, ok := query.Args.(mock.QueryArgs) - require.True(ok) - assert.Equal("path", qa.Path) - assert.EqualValues("data", qa.Data) - assert.True(qa.Prove) - - // now add some broadcasts - txs := []types.Tx{{1}, {2}, {3}} - r.BroadcastTxCommit(txs[0]) - r.BroadcastTxSync(txs[1]) - r.BroadcastTxAsync(txs[2]) - - require.Equal(5, len(r.Calls)) - - bc := r.Calls[2] - assert.Equal("broadcast_tx_commit", bc.Name) - assert.Nil(bc.Response) - require.NotNil(bc.Error) - assert.EqualValues(bc.Args, txs[0]) - - bs := r.Calls[3] - assert.Equal("broadcast_tx_sync", bs.Name) - assert.Nil(bs.Response) - require.NotNil(bs.Error) - assert.EqualValues(bs.Args, txs[1]) - - ba := r.Calls[4] - assert.Equal("broadcast_tx_async", ba.Name) - assert.Nil(ba.Response) - require.NotNil(ba.Error) - assert.EqualValues(ba.Args, txs[2]) -} - -func TestABCIApp(t *testing.T) { - assert, require := assert.New(t), require.New(t) - app := dummy.NewDummyApplication() - m := mock.ABCIApp{app} - - // get some info - info, err := m.ABCIInfo() - require.Nil(err) - assert.Equal(`{"size":0}`, info.Response.GetData()) - - // add a key - key, value := "foo", "bar" - tx := fmt.Sprintf("%s=%s", key, value) - res, err := m.BroadcastTxCommit(types.Tx(tx)) - require.Nil(err) - assert.True(res.CheckTx.Code.IsOK()) - require.NotNil(res.DeliverTx) - assert.True(res.DeliverTx.Code.IsOK()) - - // check the key - qres, err := m.ABCIQuery("/key", data.Bytes(key), false) - require.Nil(err) - assert.EqualValues(value, qres.Value) -} diff --git a/rpc/client/mock/client.go b/rpc/client/mock/client.go deleted file mode 100644 index bf8d78dce..000000000 --- a/rpc/client/mock/client.go +++ /dev/null @@ -1,129 +0,0 @@ -/* -package mock returns a Client implementation that -accepts various (mock) implementations of the various methods. - -This implementation is useful for using in tests, when you don't -need a real server, but want a high-level of control about -the server response you want to mock (eg. error handling), -or if you just want to record the calls to verify in your tests. - -For real clients, you probably want the "http" package. If you -want to directly call a tendermint node in process, you can use the -"local" package. -*/ -package mock - -import ( - "reflect" - - data "github.com/tendermint/go-wire/data" - "github.com/tendermint/tendermint/rpc/client" - "github.com/tendermint/tendermint/rpc/core" - ctypes "github.com/tendermint/tendermint/rpc/core/types" - "github.com/tendermint/tendermint/types" -) - -// Client wraps arbitrary implementations of the various interfaces. -// -// We provide a few choices to mock out each one in this package. -// Nothing hidden here, so no New function, just construct it from -// some parts, and swap them out them during the tests. -type Client struct { - client.ABCIClient - client.SignClient - client.HistoryClient - client.StatusClient - // create a mock with types.NewEventSwitch() - types.EventSwitch -} - -func (c Client) _assertIsClient() client.Client { - return c -} - -// Call is used by recorders to save a call and response. -// It can also be used to configure mock responses. -// -type Call struct { - Name string - Args interface{} - Response interface{} - Error error -} - -// GetResponse will generate the apporiate response for us, when -// using the Call struct to configure a Mock handler. -// -// When configuring a response, if only one of Response or Error is -// set then that will always be returned. If both are set, then -// we return Response if the Args match the set args, Error otherwise. -func (c Call) GetResponse(args interface{}) (interface{}, error) { - // handle the case with no response - if c.Response == nil { - if c.Error == nil { - panic("Misconfigured call, you must set either Response or Error") - } - return nil, c.Error - } - // response without error - if c.Error == nil { - return c.Response, nil - } - // have both, we must check args.... - if reflect.DeepEqual(args, c.Args) { - return c.Response, nil - } - return nil, c.Error -} - -func (c Client) Status() (*ctypes.ResultStatus, error) { - return core.Status() -} - -func (c Client) ABCIInfo() (*ctypes.ResultABCIInfo, error) { - return core.ABCIInfo() -} - -func (c Client) ABCIQuery(path string, data data.Bytes, prove bool) (*ctypes.ResultABCIQuery, error) { - return core.ABCIQuery(path, data, prove) -} - -func (c Client) BroadcastTxCommit(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) { - return core.BroadcastTxCommit(tx) -} - -func (c Client) BroadcastTxAsync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) { - return core.BroadcastTxAsync(tx) -} - -func (c Client) BroadcastTxSync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) { - return core.BroadcastTxSync(tx) -} - -func (c Client) NetInfo() (*ctypes.ResultNetInfo, error) { - return core.NetInfo() -} - -func (c Client) DialSeeds(seeds []string) (*ctypes.ResultDialSeeds, error) { - return core.UnsafeDialSeeds(seeds) -} - -func (c Client) BlockchainInfo(minHeight, maxHeight int) (*ctypes.ResultBlockchainInfo, error) { - return core.BlockchainInfo(minHeight, maxHeight) -} - -func (c Client) Genesis() (*ctypes.ResultGenesis, error) { - return core.Genesis() -} - -func (c Client) Block(height int) (*ctypes.ResultBlock, error) { - return core.Block(height) -} - -func (c Client) Commit(height int) (*ctypes.ResultCommit, error) { - return core.Commit(height) -} - -func (c Client) Validators() (*ctypes.ResultValidators, error) { - return core.Validators() -} diff --git a/rpc/client/mock/status.go b/rpc/client/mock/status.go deleted file mode 100644 index af0f5335d..000000000 --- a/rpc/client/mock/status.go +++ /dev/null @@ -1,55 +0,0 @@ -package mock - -import ( - "github.com/tendermint/tendermint/rpc/client" - ctypes "github.com/tendermint/tendermint/rpc/core/types" -) - -// StatusMock returns the result specified by the Call -type StatusMock struct { - Call -} - -func (m *StatusMock) _assertStatusClient() client.StatusClient { - return m -} - -func (m *StatusMock) Status() (*ctypes.ResultStatus, error) { - res, err := m.GetResponse(nil) - if err != nil { - return nil, err - } - return res.(*ctypes.ResultStatus), nil -} - -// StatusRecorder can wrap another type (StatusMock, full client) -// and record the status calls -type StatusRecorder struct { - Client client.StatusClient - Calls []Call -} - -func NewStatusRecorder(client client.StatusClient) *StatusRecorder { - return &StatusRecorder{ - Client: client, - Calls: []Call{}, - } -} - -func (r *StatusRecorder) _assertStatusClient() client.StatusClient { - return r -} - -func (r *StatusRecorder) addCall(call Call) { - r.Calls = append(r.Calls, call) -} - -func (r *StatusRecorder) Status() (*ctypes.ResultStatus, error) { - res, err := r.Client.Status() - r.addCall(Call{ - Name: "status", - Response: res, - Error: err, - }) - return res, err -} diff --git a/rpc/client/mock/status_test.go b/rpc/client/mock/status_test.go deleted file mode 100644 index e4adf52ba..000000000 --- a/rpc/client/mock/status_test.go +++ /dev/null @@ -1,46 +0,0 @@ -package mock_test - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - data "github.com/tendermint/go-wire/data" - ctypes "github.com/tendermint/tendermint/rpc/core/types" - - "github.com/tendermint/tendermint/rpc/client/mock" -) - -func TestStatus(t *testing.T) { - assert, require := assert.New(t), require.New(t) - - m := &mock.StatusMock{ - Call: mock.Call{ - Response: &ctypes.ResultStatus{ - LatestBlockHash: data.Bytes("block"), - LatestAppHash: data.Bytes("app"), - LatestBlockHeight: 10, - }}, - } - - r := mock.NewStatusRecorder(m) - require.Equal(0, len(r.Calls)) - - // make sure response works proper - status, err := r.Status() - require.Nil(err, "%+v", err) - assert.EqualValues("block", status.LatestBlockHash) - assert.EqualValues(10, status.LatestBlockHeight) - - // make sure recorder works properly - require.Equal(1, len(r.Calls)) - rs := r.Calls[0] - assert.Equal("status", rs.Name) - assert.Nil(rs.Args) - assert.Nil(rs.Error) - require.NotNil(rs.Response) - st, ok := rs.Response.(*ctypes.ResultStatus) - require.True(ok) - assert.EqualValues("block", st.LatestBlockHash) - assert.EqualValues(10, st.LatestBlockHeight) -} diff --git a/rpc/client/rpc_test.go b/rpc/client/rpc_test.go deleted file mode 100644 index 994ee15b7..000000000 --- a/rpc/client/rpc_test.go +++ /dev/null @@ -1,184 +0,0 @@ -package client_test - -import ( - "strings" - "testing" - - "github.com/bytom/rpc/client" - rpctest "github.com/bytom/rpc/test" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/tendermint/merkleeyes/iavl" - merktest "github.com/tendermint/merkleeyes/testutil" -) - -func getHTTPClient() *client.HTTP { - rpcAddr := rpctest.GetConfig().RPC.ListenAddress - return client.NewHTTP(rpcAddr, "/websocket") -} - -func getLocalClient() client.Local { - return client.NewLocal(node) -} - -// GetClients returns a slice of clients for table-driven tests -func GetClients() []client.Client { - return []client.Client{ - getHTTPClient(), - getLocalClient(), - } -} - -// Make sure status is correct (we connect properly) -func TestStatus(t *testing.T) { - for i, c := range GetClients() { - moniker := rpctest.GetConfig().Moniker - status, err := c.Status() - require.Nil(t, err, "%d: %+v", i, err) - assert.Equal(t, moniker, status.NodeInfo.Moniker) - } -} - -// Make sure info is correct (we connect properly) -func TestInfo(t *testing.T) { - for i, c := range GetClients() { - // status, err := c.Status() - // require.Nil(t, err, "%+v", err) - info, err := c.ABCIInfo() - require.Nil(t, err, "%d: %+v", i, err) - // TODO: this is not correct - fix merkleeyes! - // assert.EqualValues(t, status.LatestBlockHeight, info.Response.LastBlockHeight) - assert.True(t, strings.HasPrefix(info.Response.Data, "size")) - } -} - -func TestNetInfo(t *testing.T) { - for i, c := range GetClients() { - nc, ok := c.(client.NetworkClient) - require.True(t, ok, "%d", i) - netinfo, err := nc.NetInfo() - require.Nil(t, err, "%d: %+v", i, err) - assert.True(t, netinfo.Listening) - assert.Equal(t, 0, len(netinfo.Peers)) - } -} - -func TestDumpConsensusState(t *testing.T) { - for i, c := range GetClients() { - // FIXME: fix server so it doesn't panic on invalid input - nc, ok := c.(client.NetworkClient) - require.True(t, ok, "%d", i) - cons, err := nc.DumpConsensusState() - require.Nil(t, err, "%d: %+v", i, err) - assert.NotEmpty(t, cons.RoundState) - assert.Empty(t, cons.PeerRoundStates) - } -} - -func TestGenesisAndValidators(t *testing.T) { - for i, c := range GetClients() { - - // make sure this is the right genesis file - gen, err := c.Genesis() - require.Nil(t, err, "%d: %+v", i, err) - // get the genesis validator - require.Equal(t, 1, len(gen.Genesis.Validators)) - gval := gen.Genesis.Validators[0] - - // get the current validators - vals, err := c.Validators() - require.Nil(t, err, "%d: %+v", i, err) - require.Equal(t, 1, len(vals.Validators)) - val := vals.Validators[0] - - // make sure the current set is also the genesis set - assert.Equal(t, gval.Amount, val.VotingPower) - assert.Equal(t, gval.PubKey, val.PubKey) - } -} - -// Make some app checks -func TestAppCalls(t *testing.T) { - assert, require := assert.New(t), require.New(t) - for i, c := range GetClients() { - - // get an offset of height to avoid racing and guessing - s, err := c.Status() - require.Nil(err, "%d: %+v", i, err) - // sh is start height or status height - sh := s.LatestBlockHeight - - // look for the future - _, err = c.Block(sh + 2) - assert.NotNil(err) // no block yet - - // write something - k, v, tx := merktest.MakeTxKV() - bres, err := c.BroadcastTxCommit(tx) - require.Nil(err, "%d: %+v", i, err) - require.True(bres.DeliverTx.Code.IsOK()) - txh := bres.Height - apph := txh + 1 // this is where the tx will be applied to the state - - // wait before querying - client.WaitForHeight(c, apph, nil) - qres, err := c.ABCIQuery("/key", k, false) - if assert.Nil(err) && assert.True(qres.Code.IsOK()) { - // assert.Equal(k, data.GetKey()) // only returned for proofs - assert.EqualValues(v, qres.Value) - } - - // make sure we can lookup the tx with proof - // ptx, err := c.Tx(bres.Hash, true) - ptx, err := c.Tx(bres.Hash, true) - require.Nil(err, "%d: %+v", i, err) - assert.Equal(txh, ptx.Height) - assert.EqualValues(tx, ptx.Tx) - - // and we can even check the block is added - block, err := c.Block(apph) - require.Nil(err, "%d: %+v", i, err) - appHash := block.BlockMeta.Header.AppHash - assert.True(len(appHash) > 0) - assert.EqualValues(apph, block.BlockMeta.Header.Height) - - // check blockchain info, now that we know there is info - // TODO: is this commented somewhere that they are returned - // in order of descending height??? - info, err := c.BlockchainInfo(apph, apph) - require.Nil(err, "%d: %+v", i, err) - assert.True(info.LastHeight >= apph) - if assert.Equal(1, len(info.BlockMetas)) { - lastMeta := info.BlockMetas[0] - assert.EqualValues(apph, lastMeta.Header.Height) - bMeta := block.BlockMeta - assert.Equal(bMeta.Header.AppHash, lastMeta.Header.AppHash) - assert.Equal(bMeta.BlockID, lastMeta.BlockID) - } - - // and get the corresponding commit with the same apphash - commit, err := c.Commit(apph) - require.Nil(err, "%d: %+v", i, err) - cappHash := commit.Header.AppHash - assert.Equal(appHash, cappHash) - assert.NotNil(commit.Commit) - - // compare the commits (note Commit(2) has commit from Block(3)) - commit2, err := c.Commit(apph - 1) - require.Nil(err, "%d: %+v", i, err) - assert.Equal(block.Block.LastCommit, commit2.Commit) - - // and we got a proof that works! - pres, err := c.ABCIQuery("/key", k, true) - if assert.Nil(err) && assert.True(pres.Code.IsOK()) { - proof, err := iavl.ReadProof(pres.Proof) - if assert.Nil(err) { - key := pres.Key - value := pres.Value - assert.EqualValues(appHash, proof.RootHash) - valid := proof.Verify(key, value, appHash) - assert.True(valid) - } - } - } -} diff --git a/rpc/core/info.go b/rpc/core/info.go deleted file mode 100644 index 7a7da332c..000000000 --- a/rpc/core/info.go +++ /dev/null @@ -1,9 +0,0 @@ -package core - -import ( - ctypes "github.com/bytom/rpc/core/types" -) - -func BlockHeight() (*ctypes.ResultBlockchainInfo, error) { - return &ctypes.ResultBlockchainInfo{LastHeight: blockStore.Height()}, nil -} diff --git a/rpc/core/pipe.go b/rpc/core/pipe.go deleted file mode 100644 index 9cb9d3fee..000000000 --- a/rpc/core/pipe.go +++ /dev/null @@ -1,48 +0,0 @@ -package core - -import ( - "github.com/bytom/blockchain/txdb" - p2p "github.com/bytom/p2p" - "github.com/bytom/types" - "github.com/tendermint/tmlibs/log" -) - -type P2P interface { - Listeners() []p2p.Listener - Peers() p2p.IPeerSet - NumPeers() (outbound, inbound, dialig int) - NodeInfo() *p2p.NodeInfo - IsListening() bool - DialSeeds(*p2p.AddrBook, []string) error -} - -var ( - // external, thread safe interfaces - eventSwitch types.EventSwitch - blockStore *txdb.Store - p2pSwitch P2P - - addrBook *p2p.AddrBook - - logger log.Logger -) - -func SetEventSwitch(evsw types.EventSwitch) { - eventSwitch = evsw -} - -func SetBlockStore(bs *txdb.Store) { - blockStore = bs -} - -func SetSwitch(sw P2P) { - p2pSwitch = sw -} - -func SetAddrBook(book *p2p.AddrBook) { - addrBook = book -} - -func SetLogger(l log.Logger) { - logger = l -} diff --git a/rpc/core/pow.go b/rpc/core/pow.go deleted file mode 100644 index 3f39709a4..000000000 --- a/rpc/core/pow.go +++ /dev/null @@ -1,21 +0,0 @@ -package core - -import ( - "github.com/bytom/protocol/bc/legacy" - ctypes "github.com/bytom/rpc/core/types" -) - -func GetWork() (*ctypes.ResultBlockHeaderInfo, error) { - return &ctypes.ResultBlockHeaderInfo{}, nil -} - -func SubmitWork(height uint64) (bool, error) { - block := legacy.Block{ - BlockHeader: legacy.BlockHeader{ - Version: 1, - Height: height, - }, - } - blockStore.SaveBlock(&block) - return true, nil -} diff --git a/rpc/core/routes.go b/rpc/core/routes.go deleted file mode 100644 index d668d3c2f..000000000 --- a/rpc/core/routes.go +++ /dev/null @@ -1,19 +0,0 @@ -package core - -import ( - rpc "github.com/bytom/rpc/lib/server" -) - -// TODO: better system than "unsafe" prefix -var Routes = map[string]*rpc.RPCFunc{ - // subscribe/unsubscribe are reserved for websocket events. - "net_info": rpc.NewRPCFunc(NetInfo, ""), - "getwork": rpc.NewRPCFunc(GetWork, ""), - "submitwork": rpc.NewRPCFunc(SubmitWork, "height"), - "getBlockHeight": rpc.NewRPCFunc(BlockHeight, ""), -} - -func AddUnsafeRoutes() { - // control API - Routes["dial_seeds"] = rpc.NewRPCFunc(UnsafeDialSeeds, "seeds") -} diff --git a/rpc/core/version.go b/rpc/core/version.go deleted file mode 100644 index e283de479..000000000 --- a/rpc/core/version.go +++ /dev/null @@ -1,5 +0,0 @@ -package core - -// a single integer is sufficient here - -const Version = "3" // rpc routes for profiling, setting config diff --git a/rpc/grpc/client_server.go b/rpc/grpc/client_server.go deleted file mode 100644 index c6fdc50a0..000000000 --- a/rpc/grpc/client_server.go +++ /dev/null @@ -1,44 +0,0 @@ -package core_grpc - -import ( - "fmt" - "net" - "strings" - "time" - - "google.golang.org/grpc" - - . "github.com/tendermint/tmlibs/common" -) - -// Start the grpcServer in a go routine -func StartGRPCServer(protoAddr string) (net.Listener, error) { - parts := strings.SplitN(protoAddr, "://", 2) - if len(parts) != 2 { - return nil, fmt.Errorf("Invalid listen address for grpc server (did you forget a tcp:// prefix?) : %s", protoAddr) - } - proto, addr := parts[0], parts[1] - ln, err := net.Listen(proto, addr) - if err != nil { - return nil, err - } - - grpcServer := grpc.NewServer() - //RegisterBroadcastAPIServer(grpcServer, &broadcastAPI{}) - go grpcServer.Serve(ln) - - return ln, nil -} - -// Start the client by dialing the server -func StartGRPCClient(protoAddr string) BroadcastAPIClient { - conn, err := grpc.Dial(protoAddr, grpc.WithInsecure(), grpc.WithDialer(dialerFunc)) - if err != nil { - panic(err) - } - return NewBroadcastAPIClient(conn) -} - -func dialerFunc(addr string, timeout time.Duration) (net.Conn, error) { - return Connect(addr) -} diff --git a/rpc/grpc/compile.sh b/rpc/grpc/compile.sh deleted file mode 100644 index 2c4629c8e..000000000 --- a/rpc/grpc/compile.sh +++ /dev/null @@ -1,3 +0,0 @@ -#! /bin/bash - -protoc --go_out=plugins=grpc:. -I $GOPATH/src/ -I . types.proto diff --git a/rpc/grpc/types.pb.go b/rpc/grpc/types.pb.go deleted file mode 100644 index 43b43c87a..000000000 --- a/rpc/grpc/types.pb.go +++ /dev/null @@ -1,174 +0,0 @@ -// Code generated by protoc-gen-go. -// source: types.proto -// DO NOT EDIT! - -/* -Package core_grpc is a generated protocol buffer package. - -It is generated from these files: - types.proto - -It has these top-level messages: - RequestBroadcastTx - ResponseBroadcastTx -*/ -package core_grpc - -import proto "github.com/golang/protobuf/proto" -import fmt "fmt" -import math "math" -import types "github.com/tendermint/abci/types" - -import ( - context "golang.org/x/net/context" - grpc "google.golang.org/grpc" -) - -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the proto package it is being compiled against. -// A compilation error at this line likely means your copy of the -// proto package needs to be updated. -const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package - -type RequestBroadcastTx struct { - Tx []byte `protobuf:"bytes,1,opt,name=tx,proto3" json:"tx,omitempty"` -} - -func (m *RequestBroadcastTx) Reset() { *m = RequestBroadcastTx{} } -func (m *RequestBroadcastTx) String() string { return proto.CompactTextString(m) } -func (*RequestBroadcastTx) ProtoMessage() {} -func (*RequestBroadcastTx) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } - -func (m *RequestBroadcastTx) GetTx() []byte { - if m != nil { - return m.Tx - } - return nil -} - -type ResponseBroadcastTx struct { - CheckTx *types.ResponseCheckTx `protobuf:"bytes,1,opt,name=check_tx,json=checkTx" json:"check_tx,omitempty"` - DeliverTx *types.ResponseDeliverTx `protobuf:"bytes,2,opt,name=deliver_tx,json=deliverTx" json:"deliver_tx,omitempty"` -} - -func (m *ResponseBroadcastTx) Reset() { *m = ResponseBroadcastTx{} } -func (m *ResponseBroadcastTx) String() string { return proto.CompactTextString(m) } -func (*ResponseBroadcastTx) ProtoMessage() {} -func (*ResponseBroadcastTx) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } - -func (m *ResponseBroadcastTx) GetCheckTx() *types.ResponseCheckTx { - if m != nil { - return m.CheckTx - } - return nil -} - -func (m *ResponseBroadcastTx) GetDeliverTx() *types.ResponseDeliverTx { - if m != nil { - return m.DeliverTx - } - return nil -} - -func init() { - proto.RegisterType((*RequestBroadcastTx)(nil), "core_grpc.RequestBroadcastTx") - proto.RegisterType((*ResponseBroadcastTx)(nil), "core_grpc.ResponseBroadcastTx") -} - -// Reference imports to suppress errors if they are not otherwise used. -var _ context.Context -var _ grpc.ClientConn - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the grpc package it is being compiled against. -const _ = grpc.SupportPackageIsVersion4 - -// Client API for BroadcastAPI service - -type BroadcastAPIClient interface { - BroadcastTx(ctx context.Context, in *RequestBroadcastTx, opts ...grpc.CallOption) (*ResponseBroadcastTx, error) -} - -type broadcastAPIClient struct { - cc *grpc.ClientConn -} - -func NewBroadcastAPIClient(cc *grpc.ClientConn) BroadcastAPIClient { - return &broadcastAPIClient{cc} -} - -func (c *broadcastAPIClient) BroadcastTx(ctx context.Context, in *RequestBroadcastTx, opts ...grpc.CallOption) (*ResponseBroadcastTx, error) { - out := new(ResponseBroadcastTx) - err := grpc.Invoke(ctx, "/core_grpc.BroadcastAPI/BroadcastTx", in, out, c.cc, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -// Server API for BroadcastAPI service - -type BroadcastAPIServer interface { - BroadcastTx(context.Context, *RequestBroadcastTx) (*ResponseBroadcastTx, error) -} - -func RegisterBroadcastAPIServer(s *grpc.Server, srv BroadcastAPIServer) { - s.RegisterService(&_BroadcastAPI_serviceDesc, srv) -} - -func _BroadcastAPI_BroadcastTx_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(RequestBroadcastTx) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(BroadcastAPIServer).BroadcastTx(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/core_grpc.BroadcastAPI/BroadcastTx", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(BroadcastAPIServer).BroadcastTx(ctx, req.(*RequestBroadcastTx)) - } - return interceptor(ctx, in, info, handler) -} - -var _BroadcastAPI_serviceDesc = grpc.ServiceDesc{ - ServiceName: "core_grpc.BroadcastAPI", - HandlerType: (*BroadcastAPIServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "BroadcastTx", - Handler: _BroadcastAPI_BroadcastTx_Handler, - }, - }, - Streams: []grpc.StreamDesc{}, - Metadata: "types.proto", -} - -func init() { proto.RegisterFile("types.proto", fileDescriptor0) } - -var fileDescriptor0 = []byte{ - // 226 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0x2e, 0xa9, 0x2c, 0x48, - 0x2d, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x4c, 0xce, 0x2f, 0x4a, 0x8d, 0x4f, 0x2f, - 0x2a, 0x48, 0x96, 0xd2, 0x49, 0xcf, 0x2c, 0xc9, 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x2f, - 0x49, 0xcd, 0x4b, 0x49, 0x2d, 0xca, 0xcd, 0xcc, 0x2b, 0xd1, 0x2f, 0xc9, 0x2d, 0x2e, 0xd0, 0x07, - 0x6b, 0xd1, 0x47, 0xd2, 0xa8, 0xa4, 0xc2, 0x25, 0x14, 0x94, 0x5a, 0x58, 0x9a, 0x5a, 0x5c, 0xe2, - 0x54, 0x94, 0x9f, 0x98, 0x92, 0x9c, 0x58, 0x5c, 0x12, 0x52, 0x21, 0xc4, 0xc7, 0xc5, 0x54, 0x52, - 0x21, 0xc1, 0xa8, 0xc0, 0xa8, 0xc1, 0x13, 0xc4, 0x54, 0x52, 0xa1, 0x54, 0xc7, 0x25, 0x1c, 0x94, - 0x5a, 0x5c, 0x90, 0x9f, 0x57, 0x9c, 0x8a, 0xac, 0xcc, 0x90, 0x8b, 0x23, 0x39, 0x23, 0x35, 0x39, - 0x3b, 0x1e, 0xaa, 0x98, 0xdb, 0x48, 0x4c, 0x0f, 0x62, 0x38, 0x4c, 0xb5, 0x33, 0x48, 0x3a, 0xa4, - 0x22, 0x88, 0x3d, 0x19, 0xc2, 0x10, 0x32, 0xe1, 0xe2, 0x4c, 0x2c, 0x28, 0x48, 0xcd, 0x4b, 0x01, - 0xe9, 0x61, 0x02, 0xeb, 0x11, 0x47, 0xd3, 0xe3, 0x08, 0x96, 0x0f, 0xa9, 0x08, 0xe2, 0x48, 0x84, - 0xb2, 0x8c, 0x62, 0xb8, 0x78, 0xe0, 0xf6, 0x3a, 0x06, 0x78, 0x0a, 0xf9, 0x70, 0x71, 0x23, 0xbb, - 0x43, 0x56, 0x0f, 0xee, 0x7d, 0x3d, 0x4c, 0xdf, 0x48, 0xc9, 0xa1, 0x48, 0x63, 0x78, 0x23, 0x89, - 0x0d, 0x1c, 0x14, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0x68, 0x73, 0x87, 0xb0, 0x52, 0x01, - 0x00, 0x00, -} diff --git a/rpc/grpc/types.proto b/rpc/grpc/types.proto deleted file mode 100644 index 3090e3d06..000000000 --- a/rpc/grpc/types.proto +++ /dev/null @@ -1,29 +0,0 @@ -syntax = "proto3"; -package core_grpc; - -import "github.com/tendermint/abci/types/types.proto"; - -//---------------------------------------- -// Message types - -//---------------------------------------- -// Request types - -message RequestBroadcastTx { - bytes tx = 1; -} - -//---------------------------------------- -// Response types - -message ResponseBroadcastTx{ - types.ResponseCheckTx check_tx = 1; - types.ResponseDeliverTx deliver_tx = 2; -} - -//---------------------------------------- -// Service Definition - -service BroadcastAPI { - rpc BroadcastTx(RequestBroadcastTx) returns (ResponseBroadcastTx) ; -} diff --git a/rpc/lib/Dockerfile b/rpc/lib/Dockerfile deleted file mode 100644 index a194711bf..000000000 --- a/rpc/lib/Dockerfile +++ /dev/null @@ -1,12 +0,0 @@ -FROM golang:latest - -RUN mkdir -p /go/src/github.com/tendermint/tendermint/rpc/lib -WORKDIR /go/src/github.com/tendermint/tendermint/rpc/lib - -COPY Makefile /go/src/github.com/tendermint/tendermint/rpc/lib/ -# COPY glide.yaml /go/src/github.com/tendermint/tendermint/rpc/lib/ -# COPY glide.lock /go/src/github.com/tendermint/tendermint/rpc/lib/ - -COPY . /go/src/github.com/tendermint/tendermint/rpc/lib - -RUN make get_deps diff --git a/rpc/lib/Makefile b/rpc/lib/Makefile deleted file mode 100644 index 0937558a8..000000000 --- a/rpc/lib/Makefile +++ /dev/null @@ -1,18 +0,0 @@ -PACKAGES=$(shell go list ./... | grep -v "test") - -all: get_deps test - -test: - @echo "--> Running go test --race" - @go test --race $(PACKAGES) - @echo "--> Running integration tests" - @bash ./test/integration_test.sh - -get_deps: - @echo "--> Running go get" - @go get -v -d $(PACKAGES) - @go list -f '{{join .TestImports "\n"}}' ./... | \ - grep -v /vendor/ | sort | uniq | \ - xargs go get -v -d - -.PHONY: all test get_deps diff --git a/rpc/lib/README.md b/rpc/lib/README.md deleted file mode 100644 index de481c2f6..000000000 --- a/rpc/lib/README.md +++ /dev/null @@ -1,121 +0,0 @@ -# tendermint/rpc/lib - -[![CircleCI](https://circleci.com/gh/tendermint/tendermint/rpc/lib.svg?style=svg)](https://circleci.com/gh/tendermint/tendermint/rpc/lib) - -HTTP RPC server supporting calls via uri params, jsonrpc, and jsonrpc over websockets - -# Client Requests - -Suppose we want to expose the rpc function `HelloWorld(name string, num int)`. - -## GET (URI) - -As a GET request, it would have URI encoded parameters, and look like: - -``` -curl 'http://localhost:8008/hello_world?name="my_world"&num=5' -``` - -Note the `'` around the url, which is just so bash doesn't ignore the quotes in `"my_world"`. -This should also work: - -``` -curl http://localhost:8008/hello_world?name=\"my_world\"&num=5 -``` - -A GET request to `/` returns a list of available endpoints. -For those which take arguments, the arguments will be listed in order, with `_` where the actual value should be. - -## POST (JSONRPC) - -As a POST request, we use JSONRPC. For instance, the same request would have this as the body: - -``` -{ - "jsonrpc": "2.0", - "id": "anything", - "method": "hello_world", - "params": { - "name": "my_world", - "num": 5 - } -} -``` - -With the above saved in file `data.json`, we can make the request with - -``` -curl --data @data.json http://localhost:8008 -``` - -## WebSocket (JSONRPC) - -All requests are exposed over websocket in the same form as the POST JSONRPC. -Websocket connections are available at their own endpoint, typically `/websocket`, -though this is configurable when starting the server. - -# Server Definition - -Define some types and routes: - -``` -type ResultStatus struct { - Value string -} - -// Define some routes -var Routes = map[string]*rpcserver.RPCFunc{ - "status": rpcserver.NewRPCFunc(Status, "arg"), -} - -// an rpc function -func Status(v string) (*ResultStatus, error) { - return &ResultStatus{v}, nil -} - -``` - -Now start the server: - -``` -mux := http.NewServeMux() -rpcserver.RegisterRPCFuncs(mux, Routes) -wm := rpcserver.NewWebsocketManager(Routes, nil) -mux.HandleFunc("/websocket", wm.WebsocketHandler) -logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) -go func() { - _, err := rpcserver.StartHTTPServer("0.0.0.0:8008", mux, logger) - if err != nil { - panic(err) - } -}() - -``` - -Note that unix sockets are supported as well (eg. `/path/to/socket` instead of `0.0.0.0:8008`) - -Now see all available endpoints by sending a GET request to `0.0.0.0:8008`. -Each route is available as a GET request, as a JSONRPCv2 POST request, and via JSONRPCv2 over websockets. - - -# Examples - -* [Tendermint](https://github.com/tendermint/tendermint/blob/master/rpc/core/routes.go) -* [tm-monitor](https://github.com/tendermint/tools/blob/master/tm-monitor/rpc.go) - -## CHANGELOG - -### 0.7.0 - -BREAKING CHANGES: - -- removed `Client` empty interface -- `ClientJSONRPC#Call` `params` argument became a map -- rename `ClientURI` -> `URIClient`, `ClientJSONRPC` -> `JSONRPCClient` - -IMPROVEMENTS: - -- added `HTTPClient` interface, which can be used for both `ClientURI` -and `ClientJSONRPC` -- all params are now optional (Golang's default will be used if some param is missing) -- added `Call` method to `WSClient` (see method's doc for details) diff --git a/rpc/lib/circle.yml b/rpc/lib/circle.yml deleted file mode 100644 index 0308a4e79..000000000 --- a/rpc/lib/circle.yml +++ /dev/null @@ -1,21 +0,0 @@ -machine: - environment: - GOPATH: /home/ubuntu/.go_workspace - REPO: $GOPATH/src/github.com/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME - hosts: - circlehost: 127.0.0.1 - localhost: 127.0.0.1 - -checkout: - post: - - rm -rf $REPO - - mkdir -p $HOME/.go_workspace/src/github.com/$CIRCLE_PROJECT_USERNAME - - mv $HOME/$CIRCLE_PROJECT_REPONAME $REPO - -dependencies: - override: - - "cd $REPO && make get_deps" - -test: - override: - - "cd $REPO && make test" diff --git a/rpc/lib/client/args_test.go b/rpc/lib/client/args_test.go deleted file mode 100644 index ccabd0d2c..000000000 --- a/rpc/lib/client/args_test.go +++ /dev/null @@ -1,39 +0,0 @@ -package rpcclient - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -type Tx []byte - -type Foo struct { - Bar int - Baz string -} - -func TestArgToJSON(t *testing.T) { - assert := assert.New(t) - require := require.New(t) - - cases := []struct { - input interface{} - expected string - }{ - {[]byte("1234"), "0x31323334"}, - {Tx("654"), "0x363534"}, - {Foo{7, "hello"}, `{"Bar":7,"Baz":"hello"}`}, - } - - for i, tc := range cases { - args := map[string]interface{}{"data": tc.input} - err := argsToJson(args) - require.Nil(err, "%d: %+v", i, err) - require.Equal(1, len(args), "%d", i) - data, ok := args["data"].(string) - require.True(ok, "%d: %#v", i, args["data"]) - assert.Equal(tc.expected, data, "%d", i) - } -} diff --git a/rpc/lib/client/http_client.go b/rpc/lib/client/http_client.go deleted file mode 100644 index 2d0c9e863..000000000 --- a/rpc/lib/client/http_client.go +++ /dev/null @@ -1,185 +0,0 @@ -package rpcclient - -import ( - "bytes" - "encoding/json" - "fmt" - "io/ioutil" - "net" - "net/http" - "net/url" - "reflect" - "strings" - - types "github.com/bytom/rpc/lib/types" - "github.com/pkg/errors" - cmn "github.com/tendermint/tmlibs/common" -) - -// HTTPClient is a common interface for JSONRPCClient and URIClient. -type HTTPClient interface { - Call(method string, params map[string]interface{}, result interface{}) (interface{}, error) -} - -// TODO: Deprecate support for IP:PORT or /path/to/socket -func makeHTTPDialer(remoteAddr string) (string, func(string, string) (net.Conn, error)) { - - parts := strings.SplitN(remoteAddr, "://", 2) - var protocol, address string - if len(parts) != 2 { - cmn.PanicSanity(fmt.Sprintf("Expected fully formed listening address, including the tcp:// or unix:// prefix, given %s", remoteAddr)) - } else { - protocol, address = parts[0], parts[1] - } - - trimmedAddress := strings.Replace(address, "/", ".", -1) // replace / with . for http requests (dummy domain) - return trimmedAddress, func(proto, addr string) (net.Conn, error) { - return net.Dial(protocol, address) - } -} - -// We overwrite the http.Client.Dial so we can do http over tcp or unix. -// remoteAddr should be fully featured (eg. with tcp:// or unix://) -func makeHTTPClient(remoteAddr string) (string, *http.Client) { - address, dialer := makeHTTPDialer(remoteAddr) - return "http://" + address, &http.Client{ - Transport: &http.Transport{ - Dial: dialer, - }, - } -} - -//------------------------------------------------------------------------------------ - -// JSON rpc takes params as a slice -type JSONRPCClient struct { - address string - client *http.Client -} - -func NewJSONRPCClient(remote string) *JSONRPCClient { - address, client := makeHTTPClient(remote) - return &JSONRPCClient{ - address: address, - client: client, - } -} - -func (c *JSONRPCClient) Call(method string, params map[string]interface{}, result interface{}) (interface{}, error) { - request, err := types.MapToRequest("", method, params) - if err != nil { - return nil, err - } - requestBytes, err := json.Marshal(request) - if err != nil { - return nil, err - } - // log.Info(string(requestBytes)) - requestBuf := bytes.NewBuffer(requestBytes) - // log.Info(Fmt("RPC request to %v (%v): %v", c.remote, method, string(requestBytes))) - httpResponse, err := c.client.Post(c.address, "text/json", requestBuf) - if err != nil { - return nil, err - } - defer httpResponse.Body.Close() - responseBytes, err := ioutil.ReadAll(httpResponse.Body) - if err != nil { - return nil, err - } - // log.Info(Fmt("RPC response: %v", string(responseBytes))) - return unmarshalResponseBytes(responseBytes, result) -} - -//------------------------------------------------------------- - -// URI takes params as a map -type URIClient struct { - address string - client *http.Client -} - -func NewURIClient(remote string) *URIClient { - address, client := makeHTTPClient(remote) - return &URIClient{ - address: address, - client: client, - } -} - -func (c *URIClient) Call(method string, params map[string]interface{}, result interface{}) (interface{}, error) { - values, err := argsToURLValues(params) - if err != nil { - return nil, err - } - // log.Info(Fmt("URI request to %v (%v): %v", c.address, method, values)) - resp, err := c.client.PostForm(c.address+"/"+method, values) - if err != nil { - return nil, err - } - defer resp.Body.Close() - responseBytes, err := ioutil.ReadAll(resp.Body) - if err != nil { - return nil, err - } - return unmarshalResponseBytes(responseBytes, result) -} - -//------------------------------------------------ - -func unmarshalResponseBytes(responseBytes []byte, result interface{}) (interface{}, error) { - // read response - // if rpc/core/types is imported, the result will unmarshal - // into the correct type - // log.Notice("response", "response", string(responseBytes)) - var err error - response := &types.RPCResponse{} - err = json.Unmarshal(responseBytes, response) - if err != nil { - return nil, errors.Errorf("Error unmarshalling rpc response: %v", err) - } - errorStr := response.Error - if errorStr != "" { - return nil, errors.Errorf("Response error: %v", errorStr) - } - // unmarshal the RawMessage into the result - err = json.Unmarshal(*response.Result, result) - if err != nil { - return nil, errors.Errorf("Error unmarshalling rpc response result: %v", err) - } - return result, nil -} - -func argsToURLValues(args map[string]interface{}) (url.Values, error) { - values := make(url.Values) - if len(args) == 0 { - return values, nil - } - err := argsToJson(args) - if err != nil { - return nil, err - } - for key, val := range args { - values.Set(key, val.(string)) - } - return values, nil -} - -func argsToJson(args map[string]interface{}) error { - for k, v := range args { - rt := reflect.TypeOf(v) - isByteSlice := rt.Kind() == reflect.Slice && rt.Elem().Kind() == reflect.Uint8 - if isByteSlice { - bytes := reflect.ValueOf(v).Bytes() - args[k] = fmt.Sprintf("0x%X", bytes) - continue - } - - // Pass everything else to go-wire - data, err := json.Marshal(v) - if err != nil { - return err - } - args[k] = string(data) - } - return nil -} diff --git a/rpc/lib/client/ws_client.go b/rpc/lib/client/ws_client.go deleted file mode 100644 index 332391126..000000000 --- a/rpc/lib/client/ws_client.go +++ /dev/null @@ -1,160 +0,0 @@ -package rpcclient - -import ( - "encoding/json" - "net" - "net/http" - "time" - - types "github.com/bytom/rpc/lib/types" - "github.com/gorilla/websocket" - "github.com/pkg/errors" - cmn "github.com/tendermint/tmlibs/common" -) - -const ( - wsResultsChannelCapacity = 10 - wsErrorsChannelCapacity = 1 - wsWriteTimeoutSeconds = 10 -) - -type WSClient struct { - cmn.BaseService - Address string // IP:PORT or /path/to/socket - Endpoint string // /websocket/url/endpoint - Dialer func(string, string) (net.Conn, error) - *websocket.Conn - ResultsCh chan json.RawMessage // closes upon WSClient.Stop() - ErrorsCh chan error // closes upon WSClient.Stop() -} - -// create a new connection -func NewWSClient(remoteAddr, endpoint string) *WSClient { - addr, dialer := makeHTTPDialer(remoteAddr) - wsClient := &WSClient{ - Address: addr, - Dialer: dialer, - Endpoint: endpoint, - Conn: nil, - } - wsClient.BaseService = *cmn.NewBaseService(nil, "WSClient", wsClient) - return wsClient -} - -func (wsc *WSClient) String() string { - return wsc.Address + ", " + wsc.Endpoint -} - -// OnStart implements cmn.BaseService interface -func (wsc *WSClient) OnStart() error { - wsc.BaseService.OnStart() - err := wsc.dial() - if err != nil { - return err - } - wsc.ResultsCh = make(chan json.RawMessage, wsResultsChannelCapacity) - wsc.ErrorsCh = make(chan error, wsErrorsChannelCapacity) - go wsc.receiveEventsRoutine() - return nil -} - -// OnReset implements cmn.BaseService interface -func (wsc *WSClient) OnReset() error { - return nil -} - -func (wsc *WSClient) dial() error { - - // Dial - dialer := &websocket.Dialer{ - NetDial: wsc.Dialer, - Proxy: http.ProxyFromEnvironment, - } - rHeader := http.Header{} - con, _, err := dialer.Dial("ws://"+wsc.Address+wsc.Endpoint, rHeader) - if err != nil { - return err - } - // Set the ping/pong handlers - con.SetPingHandler(func(m string) error { - // NOTE: https://github.com/gorilla/websocket/issues/97 - go con.WriteControl(websocket.PongMessage, []byte(m), time.Now().Add(time.Second*wsWriteTimeoutSeconds)) - return nil - }) - con.SetPongHandler(func(m string) error { - // NOTE: https://github.com/gorilla/websocket/issues/97 - return nil - }) - wsc.Conn = con - return nil -} - -// OnStop implements cmn.BaseService interface -func (wsc *WSClient) OnStop() { - wsc.BaseService.OnStop() - wsc.Conn.Close() - // ResultsCh/ErrorsCh is closed in receiveEventsRoutine. -} - -func (wsc *WSClient) receiveEventsRoutine() { - for { - _, data, err := wsc.ReadMessage() - if err != nil { - wsc.Logger.Info("WSClient failed to read message", "error", err, "data", string(data)) - wsc.Stop() - break - } else { - var response types.RPCResponse - err := json.Unmarshal(data, &response) - if err != nil { - wsc.Logger.Info("WSClient failed to parse message", "error", err, "data", string(data)) - wsc.ErrorsCh <- err - continue - } - if response.Error != "" { - wsc.ErrorsCh <- errors.Errorf(response.Error) - continue - } - wsc.ResultsCh <- *response.Result - } - } - // this must be modified in the same go-routine that reads from the - // connection to avoid race conditions - wsc.Conn = nil - - // Cleanup - close(wsc.ResultsCh) - close(wsc.ErrorsCh) -} - -// Subscribe to an event. Note the server must have a "subscribe" route -// defined. -func (wsc *WSClient) Subscribe(eventid string) error { - params := map[string]interface{}{"event": eventid} - request, err := types.MapToRequest("", "subscribe", params) - if err == nil { - err = wsc.WriteJSON(request) - } - return err -} - -// Unsubscribe from an event. Note the server must have a "unsubscribe" route -// defined. -func (wsc *WSClient) Unsubscribe(eventid string) error { - params := map[string]interface{}{"event": eventid} - request, err := types.MapToRequest("", "unsubscribe", params) - if err == nil { - err = wsc.WriteJSON(request) - } - return err -} - -// Call asynchronously calls a given method by sending an RPCRequest to the -// server. Results will be available on ResultsCh, errors, if any, on ErrorsCh. -func (wsc *WSClient) Call(method string, params map[string]interface{}) error { - request, err := types.MapToRequest("", method, params) - if err == nil { - err = wsc.WriteJSON(request) - } - return err -} diff --git a/rpc/lib/rpc_test.go b/rpc/lib/rpc_test.go deleted file mode 100644 index c7bed052f..000000000 --- a/rpc/lib/rpc_test.go +++ /dev/null @@ -1,335 +0,0 @@ -package rpc - -import ( - "bytes" - crand "crypto/rand" - "encoding/json" - "fmt" - "math/rand" - "net/http" - "os/exec" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/tendermint/go-wire/data" - client "github.com/tendermint/tendermint/rpc/lib/client" - server "github.com/tendermint/tendermint/rpc/lib/server" - types "github.com/tendermint/tendermint/rpc/lib/types" - "github.com/tendermint/tmlibs/log" -) - -// Client and Server should work over tcp or unix sockets -const ( - tcpAddr = "tcp://0.0.0.0:47768" - - unixSocket = "/tmp/rpc_test.sock" - unixAddr = "unix://" + unixSocket - - websocketEndpoint = "/websocket/endpoint" -) - -type ResultEcho struct { - Value string `json:"value"` -} - -type ResultEchoInt struct { - Value int `json:"value"` -} - -type ResultEchoBytes struct { - Value []byte `json:"value"` -} - -type ResultEchoDataBytes struct { - Value data.Bytes `json:"value"` -} - -// Define some routes -var Routes = map[string]*server.RPCFunc{ - "echo": server.NewRPCFunc(EchoResult, "arg"), - "echo_ws": server.NewWSRPCFunc(EchoWSResult, "arg"), - "echo_bytes": server.NewRPCFunc(EchoBytesResult, "arg"), - "echo_data_bytes": server.NewRPCFunc(EchoDataBytesResult, "arg"), - "echo_int": server.NewRPCFunc(EchoIntResult, "arg"), -} - -func EchoResult(v string) (*ResultEcho, error) { - return &ResultEcho{v}, nil -} - -func EchoWSResult(wsCtx types.WSRPCContext, v string) (*ResultEcho, error) { - return &ResultEcho{v}, nil -} - -func EchoIntResult(v int) (*ResultEchoInt, error) { - return &ResultEchoInt{v}, nil -} - -func EchoBytesResult(v []byte) (*ResultEchoBytes, error) { - return &ResultEchoBytes{v}, nil -} - -func EchoDataBytesResult(v data.Bytes) (*ResultEchoDataBytes, error) { - return &ResultEchoDataBytes{v}, nil -} - -// launch unix and tcp servers -func init() { - cmd := exec.Command("rm", "-f", unixSocket) - err := cmd.Start() - if err != nil { - panic(err) - } - if err = cmd.Wait(); err != nil { - panic(err) - } - - mux := http.NewServeMux() - server.RegisterRPCFuncs(mux, Routes, log.TestingLogger()) - wm := server.NewWebsocketManager(Routes, nil) - wm.SetLogger(log.TestingLogger()) - mux.HandleFunc(websocketEndpoint, wm.WebsocketHandler) - go func() { - _, err := server.StartHTTPServer(tcpAddr, mux, log.TestingLogger()) - if err != nil { - panic(err) - } - }() - - mux2 := http.NewServeMux() - server.RegisterRPCFuncs(mux2, Routes, log.TestingLogger()) - wm = server.NewWebsocketManager(Routes, nil) - wm.SetLogger(log.TestingLogger()) - mux2.HandleFunc(websocketEndpoint, wm.WebsocketHandler) - go func() { - _, err := server.StartHTTPServer(unixAddr, mux2, log.TestingLogger()) - if err != nil { - panic(err) - } - }() - - // wait for servers to start - time.Sleep(time.Second * 2) -} - -func echoViaHTTP(cl client.HTTPClient, val string) (string, error) { - params := map[string]interface{}{ - "arg": val, - } - result := new(ResultEcho) - if _, err := cl.Call("echo", params, result); err != nil { - return "", err - } - return result.Value, nil -} - -func echoIntViaHTTP(cl client.HTTPClient, val int) (int, error) { - params := map[string]interface{}{ - "arg": val, - } - result := new(ResultEchoInt) - if _, err := cl.Call("echo_int", params, result); err != nil { - return 0, err - } - return result.Value, nil -} - -func echoBytesViaHTTP(cl client.HTTPClient, bytes []byte) ([]byte, error) { - params := map[string]interface{}{ - "arg": bytes, - } - result := new(ResultEchoBytes) - if _, err := cl.Call("echo_bytes", params, result); err != nil { - return []byte{}, err - } - return result.Value, nil -} - -func echoDataBytesViaHTTP(cl client.HTTPClient, bytes data.Bytes) (data.Bytes, error) { - params := map[string]interface{}{ - "arg": bytes, - } - result := new(ResultEchoDataBytes) - if _, err := cl.Call("echo_data_bytes", params, result); err != nil { - return []byte{}, err - } - return result.Value, nil -} - -func testWithHTTPClient(t *testing.T, cl client.HTTPClient) { - val := "acbd" - got, err := echoViaHTTP(cl, val) - require.Nil(t, err) - assert.Equal(t, got, val) - - val2 := randBytes(t) - got2, err := echoBytesViaHTTP(cl, val2) - require.Nil(t, err) - assert.Equal(t, got2, val2) - - val3 := data.Bytes(randBytes(t)) - got3, err := echoDataBytesViaHTTP(cl, val3) - require.Nil(t, err) - assert.Equal(t, got3, val3) - - val4 := rand.Intn(10000) - got4, err := echoIntViaHTTP(cl, val4) - require.Nil(t, err) - assert.Equal(t, got4, val4) -} - -func echoViaWS(cl *client.WSClient, val string) (string, error) { - params := map[string]interface{}{ - "arg": val, - } - err := cl.Call("echo", params) - if err != nil { - return "", err - } - - select { - case msg := <-cl.ResultsCh: - result := new(ResultEcho) - err = json.Unmarshal(msg, result) - if err != nil { - return "", nil - } - return result.Value, nil - case err := <-cl.ErrorsCh: - return "", err - } -} - -func echoBytesViaWS(cl *client.WSClient, bytes []byte) ([]byte, error) { - params := map[string]interface{}{ - "arg": bytes, - } - err := cl.Call("echo_bytes", params) - if err != nil { - return []byte{}, err - } - - select { - case msg := <-cl.ResultsCh: - result := new(ResultEchoBytes) - err = json.Unmarshal(msg, result) - if err != nil { - return []byte{}, nil - } - return result.Value, nil - case err := <-cl.ErrorsCh: - return []byte{}, err - } -} - -func testWithWSClient(t *testing.T, cl *client.WSClient) { - val := "acbd" - got, err := echoViaWS(cl, val) - require.Nil(t, err) - assert.Equal(t, got, val) - - val2 := randBytes(t) - got2, err := echoBytesViaWS(cl, val2) - require.Nil(t, err) - assert.Equal(t, got2, val2) -} - -//------------- - -func TestServersAndClientsBasic(t *testing.T) { - serverAddrs := [...]string{tcpAddr, unixAddr} - for _, addr := range serverAddrs { - cl1 := client.NewURIClient(addr) - fmt.Printf("=== testing server on %s using %v client", addr, cl1) - testWithHTTPClient(t, cl1) - - cl2 := client.NewJSONRPCClient(tcpAddr) - fmt.Printf("=== testing server on %s using %v client", addr, cl2) - testWithHTTPClient(t, cl2) - - cl3 := client.NewWSClient(tcpAddr, websocketEndpoint) - _, err := cl3.Start() - require.Nil(t, err) - fmt.Printf("=== testing server on %s using %v client", addr, cl3) - testWithWSClient(t, cl3) - cl3.Stop() - } -} - -func TestHexStringArg(t *testing.T) { - cl := client.NewURIClient(tcpAddr) - // should NOT be handled as hex - val := "0xabc" - got, err := echoViaHTTP(cl, val) - require.Nil(t, err) - assert.Equal(t, got, val) -} - -func TestQuotedStringArg(t *testing.T) { - cl := client.NewURIClient(tcpAddr) - // should NOT be unquoted - val := "\"abc\"" - got, err := echoViaHTTP(cl, val) - require.Nil(t, err) - assert.Equal(t, got, val) -} - -func TestWSNewWSRPCFunc(t *testing.T) { - cl := client.NewWSClient(tcpAddr, websocketEndpoint) - _, err := cl.Start() - require.Nil(t, err) - defer cl.Stop() - - val := "acbd" - params := map[string]interface{}{ - "arg": val, - } - err = cl.Call("echo_ws", params) - require.Nil(t, err) - - select { - case msg := <-cl.ResultsCh: - result := new(ResultEcho) - err = json.Unmarshal(msg, result) - require.Nil(t, err) - got := result.Value - assert.Equal(t, got, val) - case err := <-cl.ErrorsCh: - t.Fatal(err) - } -} - -func TestWSHandlesArrayParams(t *testing.T) { - cl := client.NewWSClient(tcpAddr, websocketEndpoint) - _, err := cl.Start() - require.Nil(t, err) - defer cl.Stop() - - val := "acbd" - params := []interface{}{val} - request, err := types.ArrayToRequest("", "echo_ws", params) - require.Nil(t, err) - err = cl.WriteJSON(request) - require.Nil(t, err) - - select { - case msg := <-cl.ResultsCh: - result := new(ResultEcho) - err = json.Unmarshal(msg, result) - require.Nil(t, err) - got := result.Value - assert.Equal(t, got, val) - case err := <-cl.ErrorsCh: - t.Fatalf("%+v", err) - } -} - -func randBytes(t *testing.T) []byte { - n := rand.Intn(10) + 2 - buf := make([]byte, n) - _, err := crand.Read(buf) - require.Nil(t, err) - return bytes.Replace(buf, []byte("="), []byte{100}, -1) -} diff --git a/rpc/lib/server/handlers.go b/rpc/lib/server/handlers.go deleted file mode 100644 index 58c2f0272..000000000 --- a/rpc/lib/server/handlers.go +++ /dev/null @@ -1,659 +0,0 @@ -package rpcserver - -import ( - "bytes" - "encoding/hex" - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "reflect" - "sort" - "strings" - "time" - - "github.com/gorilla/websocket" - "github.com/pkg/errors" - - types "github.com/bytom/rpc/lib/types" - cmn "github.com/tendermint/tmlibs/common" - events "github.com/tendermint/tmlibs/events" - "github.com/tendermint/tmlibs/log" -) - -// Adds a route for each function in the funcMap, as well as general jsonrpc and websocket handlers for all functions. -// "result" is the interface on which the result objects are registered, and is popualted with every RPCResponse -func RegisterRPCFuncs(mux *http.ServeMux, funcMap map[string]*RPCFunc, logger log.Logger) { - // HTTP endpoints - for funcName, rpcFunc := range funcMap { - mux.HandleFunc("/"+funcName, makeHTTPHandler(rpcFunc, logger)) - } - - // JSONRPC endpoints - mux.HandleFunc("/", makeJSONRPCHandler(funcMap, logger)) -} - -//------------------------------------- -// function introspection - -// holds all type information for each function -type RPCFunc struct { - f reflect.Value // underlying rpc function - args []reflect.Type // type of each function arg - returns []reflect.Type // type of each return arg - argNames []string // name of each argument - ws bool // websocket only -} - -// wraps a function for quicker introspection -// f is the function, args are comma separated argument names -func NewRPCFunc(f interface{}, args string) *RPCFunc { - return newRPCFunc(f, args, false) -} - -func NewWSRPCFunc(f interface{}, args string) *RPCFunc { - return newRPCFunc(f, args, true) -} - -func newRPCFunc(f interface{}, args string, ws bool) *RPCFunc { - var argNames []string - if args != "" { - argNames = strings.Split(args, ",") - } - return &RPCFunc{ - f: reflect.ValueOf(f), - args: funcArgTypes(f), - returns: funcReturnTypes(f), - argNames: argNames, - ws: ws, - } -} - -// return a function's argument types -func funcArgTypes(f interface{}) []reflect.Type { - t := reflect.TypeOf(f) - n := t.NumIn() - typez := make([]reflect.Type, n) - for i := 0; i < n; i++ { - typez[i] = t.In(i) - } - return typez -} - -// return a function's return types -func funcReturnTypes(f interface{}) []reflect.Type { - t := reflect.TypeOf(f) - n := t.NumOut() - typez := make([]reflect.Type, n) - for i := 0; i < n; i++ { - typez[i] = t.Out(i) - } - return typez -} - -// function introspection -//----------------------------------------------------------------------------- -// rpc.json - -// jsonrpc calls grab the given method's function info and runs reflect.Call -func makeJSONRPCHandler(funcMap map[string]*RPCFunc, logger log.Logger) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - b, _ := ioutil.ReadAll(r.Body) - // if its an empty request (like from a browser), - // just display a list of functions - if len(b) == 0 { - writeListOfEndpoints(w, r, funcMap) - return - } - - var request types.RPCRequest - err := json.Unmarshal(b, &request) - if err != nil { - WriteRPCResponseHTTPError(w, http.StatusBadRequest, types.NewRPCResponse("", nil, fmt.Sprintf("Error unmarshalling request: %v", err.Error()))) - return - } - if len(r.URL.Path) > 1 { - WriteRPCResponseHTTPError(w, http.StatusNotFound, types.NewRPCResponse(request.ID, nil, fmt.Sprintf("Invalid JSONRPC endpoint %s", r.URL.Path))) - return - } - rpcFunc := funcMap[request.Method] - if rpcFunc == nil { - WriteRPCResponseHTTPError(w, http.StatusNotFound, types.NewRPCResponse(request.ID, nil, "RPC method unknown: "+request.Method)) - return - } - if rpcFunc.ws { - WriteRPCResponseHTTPError(w, http.StatusMethodNotAllowed, types.NewRPCResponse(request.ID, nil, "RPC method is only for websockets: "+request.Method)) - return - } - args, err := jsonParamsToArgsRPC(rpcFunc, request.Params) - if err != nil { - WriteRPCResponseHTTPError(w, http.StatusBadRequest, types.NewRPCResponse(request.ID, nil, fmt.Sprintf("Error converting json params to arguments: %v", err.Error()))) - return - } - returns := rpcFunc.f.Call(args) - logger.Info("HTTPJSONRPC", "method", request.Method, "args", args, "returns", returns) - result, err := unreflectResult(returns) - if err != nil { - WriteRPCResponseHTTPError(w, http.StatusInternalServerError, types.NewRPCResponse(request.ID, result, err.Error())) - return - } - WriteRPCResponseHTTP(w, types.NewRPCResponse(request.ID, result, "")) - } -} - -func mapParamsToArgs(rpcFunc *RPCFunc, params map[string]*json.RawMessage, argsOffset int) ([]reflect.Value, error) { - values := make([]reflect.Value, len(rpcFunc.argNames)) - for i, argName := range rpcFunc.argNames { - argType := rpcFunc.args[i+argsOffset] - - if p, ok := params[argName]; ok && p != nil && len(*p) > 0 { - val := reflect.New(argType) - err := json.Unmarshal(*p, val.Interface()) - if err != nil { - return nil, err - } - values[i] = val.Elem() - } else { // use default for that type - values[i] = reflect.Zero(argType) - } - } - - return values, nil -} - -func arrayParamsToArgs(rpcFunc *RPCFunc, params []*json.RawMessage, argsOffset int) ([]reflect.Value, error) { - if len(rpcFunc.argNames) != len(params) { - return nil, errors.Errorf("Expected %v parameters (%v), got %v (%v)", - len(rpcFunc.argNames), rpcFunc.argNames, len(params), params) - } - - values := make([]reflect.Value, len(params)) - for i, p := range params { - argType := rpcFunc.args[i+argsOffset] - val := reflect.New(argType) - err := json.Unmarshal(*p, val.Interface()) - if err != nil { - return nil, err - } - values[i] = val.Elem() - } - return values, nil -} - -// raw is unparsed json (from json.RawMessage) encoding either a map or an array. -// -// argsOffset should be 0 for RPC calls, and 1 for WS requests, where len(rpcFunc.args) != len(rpcFunc.argNames). -// Example: -// rpcFunc.args = [rpctypes.WSRPCContext string] -// rpcFunc.argNames = ["arg"] -func jsonParamsToArgs(rpcFunc *RPCFunc, raw []byte, argsOffset int) ([]reflect.Value, error) { - // first, try to get the map.. - var m map[string]*json.RawMessage - err := json.Unmarshal(raw, &m) - if err == nil { - return mapParamsToArgs(rpcFunc, m, argsOffset) - } - - // otherwise, try an array - var a []*json.RawMessage - err = json.Unmarshal(raw, &a) - if err == nil { - return arrayParamsToArgs(rpcFunc, a, argsOffset) - } - - // otherwise, bad format, we cannot parse - return nil, errors.Errorf("Unknown type for JSON params: %v. Expected map or array", err) -} - -// Convert a []interface{} OR a map[string]interface{} to properly typed values -func jsonParamsToArgsRPC(rpcFunc *RPCFunc, params *json.RawMessage) ([]reflect.Value, error) { - return jsonParamsToArgs(rpcFunc, *params, 0) -} - -// Same as above, but with the first param the websocket connection -func jsonParamsToArgsWS(rpcFunc *RPCFunc, params *json.RawMessage, wsCtx types.WSRPCContext) ([]reflect.Value, error) { - values, err := jsonParamsToArgs(rpcFunc, *params, 1) - if err != nil { - return nil, err - } - return append([]reflect.Value{reflect.ValueOf(wsCtx)}, values...), nil -} - -// rpc.json -//----------------------------------------------------------------------------- -// rpc.http - -// convert from a function name to the http handler -func makeHTTPHandler(rpcFunc *RPCFunc, logger log.Logger) func(http.ResponseWriter, *http.Request) { - // Exception for websocket endpoints - if rpcFunc.ws { - return func(w http.ResponseWriter, r *http.Request) { - WriteRPCResponseHTTPError(w, http.StatusMethodNotAllowed, types.NewRPCResponse("", nil, "This RPC method is only for websockets")) - } - } - // All other endpoints - return func(w http.ResponseWriter, r *http.Request) { - logger.Debug("HTTP HANDLER", "req", r) - args, err := httpParamsToArgs(rpcFunc, r) - if err != nil { - WriteRPCResponseHTTPError(w, http.StatusBadRequest, types.NewRPCResponse("", nil, fmt.Sprintf("Error converting http params to args: %v", err.Error()))) - return - } - returns := rpcFunc.f.Call(args) - logger.Info("HTTPRestRPC", "method", r.URL.Path, "args", args, "returns", returns) - result, err := unreflectResult(returns) - if err != nil { - WriteRPCResponseHTTPError(w, http.StatusInternalServerError, types.NewRPCResponse("", nil, err.Error())) - return - } - WriteRPCResponseHTTP(w, types.NewRPCResponse("", result, "")) - } -} - -// Covert an http query to a list of properly typed values. -// To be properly decoded the arg must be a concrete type from tendermint (if its an interface). -func httpParamsToArgs(rpcFunc *RPCFunc, r *http.Request) ([]reflect.Value, error) { - values := make([]reflect.Value, len(rpcFunc.args)) - - for i, name := range rpcFunc.argNames { - argType := rpcFunc.args[i] - - values[i] = reflect.Zero(argType) // set default for that type - - arg := GetParam(r, name) - // log.Notice("param to arg", "argType", argType, "name", name, "arg", arg) - - if "" == arg { - continue - } - - v, err, ok := nonJsonToArg(argType, arg) - if err != nil { - return nil, err - } - if ok { - values[i] = v - continue - } - - values[i], err = _jsonStringToArg(argType, arg) - if err != nil { - return nil, err - } - } - - return values, nil -} - -func _jsonStringToArg(ty reflect.Type, arg string) (reflect.Value, error) { - v := reflect.New(ty) - err := json.Unmarshal([]byte(arg), v.Interface()) - if err != nil { - return v, err - } - v = v.Elem() - return v, nil -} - -func nonJsonToArg(ty reflect.Type, arg string) (reflect.Value, error, bool) { - isQuotedString := strings.HasPrefix(arg, `"`) && strings.HasSuffix(arg, `"`) - isHexString := strings.HasPrefix(strings.ToLower(arg), "0x") - expectingString := ty.Kind() == reflect.String - expectingByteSlice := ty.Kind() == reflect.Slice && ty.Elem().Kind() == reflect.Uint8 - - if isHexString { - if !expectingString && !expectingByteSlice { - err := errors.Errorf("Got a hex string arg, but expected '%s'", - ty.Kind().String()) - return reflect.ValueOf(nil), err, false - } - - var value []byte - value, err := hex.DecodeString(arg[2:]) - if err != nil { - return reflect.ValueOf(nil), err, false - } - if ty.Kind() == reflect.String { - return reflect.ValueOf(string(value)), nil, true - } - return reflect.ValueOf([]byte(value)), nil, true - } - - if isQuotedString && expectingByteSlice { - v := reflect.New(reflect.TypeOf("")) - err := json.Unmarshal([]byte(arg), v.Interface()) - if err != nil { - return reflect.ValueOf(nil), err, false - } - v = v.Elem() - return reflect.ValueOf([]byte(v.String())), nil, true - } - - return reflect.ValueOf(nil), nil, false -} - -// rpc.http -//----------------------------------------------------------------------------- -// rpc.websocket - -const ( - writeChanCapacity = 1000 - wsWriteTimeoutSeconds = 30 // each write times out after this - wsReadTimeoutSeconds = 30 // connection times out if we haven't received *anything* in this long, not even pings. - wsPingTickerSeconds = 10 // send a ping every PingTickerSeconds. -) - -// a single websocket connection -// contains listener id, underlying ws connection, -// and the event switch for subscribing to events -type wsConnection struct { - cmn.BaseService - - remoteAddr string - baseConn *websocket.Conn - writeChan chan types.RPCResponse - readTimeout *time.Timer - pingTicker *time.Ticker - - funcMap map[string]*RPCFunc - evsw events.EventSwitch -} - -// new websocket connection wrapper -func NewWSConnection(baseConn *websocket.Conn, funcMap map[string]*RPCFunc, evsw events.EventSwitch) *wsConnection { - wsc := &wsConnection{ - remoteAddr: baseConn.RemoteAddr().String(), - baseConn: baseConn, - writeChan: make(chan types.RPCResponse, writeChanCapacity), // error when full. - funcMap: funcMap, - evsw: evsw, - } - wsc.BaseService = *cmn.NewBaseService(nil, "wsConnection", wsc) - return wsc -} - -// wsc.Start() blocks until the connection closes. -func (wsc *wsConnection) OnStart() error { - wsc.BaseService.OnStart() - - // these must be set before the readRoutine is created, as it may - // call wsc.Stop(), which accesses these timers - wsc.readTimeout = time.NewTimer(time.Second * wsReadTimeoutSeconds) - wsc.pingTicker = time.NewTicker(time.Second * wsPingTickerSeconds) - - // Read subscriptions/unsubscriptions to events - go wsc.readRoutine() - - // Custom Ping handler to touch readTimeout - wsc.baseConn.SetPingHandler(func(m string) error { - // NOTE: https://github.com/gorilla/websocket/issues/97 - go wsc.baseConn.WriteControl(websocket.PongMessage, []byte(m), time.Now().Add(time.Second*wsWriteTimeoutSeconds)) - wsc.readTimeout.Reset(time.Second * wsReadTimeoutSeconds) - return nil - }) - wsc.baseConn.SetPongHandler(func(m string) error { - // NOTE: https://github.com/gorilla/websocket/issues/97 - wsc.readTimeout.Reset(time.Second * wsReadTimeoutSeconds) - return nil - }) - go wsc.readTimeoutRoutine() - - // Write responses, BLOCKING. - wsc.writeRoutine() - return nil -} - -func (wsc *wsConnection) OnStop() { - wsc.BaseService.OnStop() - if wsc.evsw != nil { - wsc.evsw.RemoveListener(wsc.remoteAddr) - } - wsc.readTimeout.Stop() - wsc.pingTicker.Stop() - // The write loop closes the websocket connection - // when it exits its loop, and the read loop - // closes the writeChan -} - -func (wsc *wsConnection) readTimeoutRoutine() { - select { - case <-wsc.readTimeout.C: - wsc.Logger.Info("Stopping connection due to read timeout") - wsc.Stop() - case <-wsc.Quit: - return - } -} - -// Implements WSRPCConnection -func (wsc *wsConnection) GetRemoteAddr() string { - return wsc.remoteAddr -} - -// Implements WSRPCConnection -func (wsc *wsConnection) GetEventSwitch() events.EventSwitch { - return wsc.evsw -} - -// Implements WSRPCConnection -// Blocking write to writeChan until service stops. -// Goroutine-safe -func (wsc *wsConnection) WriteRPCResponse(resp types.RPCResponse) { - select { - case <-wsc.Quit: - return - case wsc.writeChan <- resp: - } -} - -// Implements WSRPCConnection -// Nonblocking write. -// Goroutine-safe -func (wsc *wsConnection) TryWriteRPCResponse(resp types.RPCResponse) bool { - select { - case <-wsc.Quit: - return false - case wsc.writeChan <- resp: - return true - default: - return false - } -} - -// Read from the socket and subscribe to or unsubscribe from events -func (wsc *wsConnection) readRoutine() { - // Do not close writeChan, to allow WriteRPCResponse() to fail. - // defer close(wsc.writeChan) - - for { - select { - case <-wsc.Quit: - return - default: - var in []byte - // Do not set a deadline here like below: - // wsc.baseConn.SetReadDeadline(time.Now().Add(time.Second * wsReadTimeoutSeconds)) - // The client may not send anything for a while. - // We use `readTimeout` to handle read timeouts. - _, in, err := wsc.baseConn.ReadMessage() - if err != nil { - wsc.Logger.Info("Failed to read from connection", "remote", wsc.remoteAddr, "err", err.Error()) - // an error reading the connection, - // kill the connection - wsc.Stop() - return - } - var request types.RPCRequest - err = json.Unmarshal(in, &request) - if err != nil { - errStr := fmt.Sprintf("Error unmarshaling data: %s", err.Error()) - wsc.WriteRPCResponse(types.NewRPCResponse(request.ID, nil, errStr)) - continue - } - - // Now, fetch the RPCFunc and execute it. - - rpcFunc := wsc.funcMap[request.Method] - if rpcFunc == nil { - wsc.WriteRPCResponse(types.NewRPCResponse(request.ID, nil, "RPC method unknown: "+request.Method)) - continue - } - var args []reflect.Value - if rpcFunc.ws { - wsCtx := types.WSRPCContext{Request: request, WSRPCConnection: wsc} - args, err = jsonParamsToArgsWS(rpcFunc, request.Params, wsCtx) - } else { - args, err = jsonParamsToArgsRPC(rpcFunc, request.Params) - } - if err != nil { - wsc.WriteRPCResponse(types.NewRPCResponse(request.ID, nil, err.Error())) - continue - } - returns := rpcFunc.f.Call(args) - wsc.Logger.Info("WSJSONRPC", "method", request.Method, "args", args, "returns", returns) - result, err := unreflectResult(returns) - if err != nil { - wsc.WriteRPCResponse(types.NewRPCResponse(request.ID, nil, err.Error())) - continue - } else { - wsc.WriteRPCResponse(types.NewRPCResponse(request.ID, result, "")) - continue - } - - } - } -} - -// receives on a write channel and writes out on the socket -func (wsc *wsConnection) writeRoutine() { - defer wsc.baseConn.Close() - for { - select { - case <-wsc.Quit: - return - case <-wsc.pingTicker.C: - err := wsc.baseConn.WriteMessage(websocket.PingMessage, []byte{}) - if err != nil { - wsc.Logger.Error("Failed to write ping message on websocket", "error", err) - wsc.Stop() - return - } - case msg := <-wsc.writeChan: - jsonBytes, err := json.MarshalIndent(msg, "", " ") - if err != nil { - wsc.Logger.Error("Failed to marshal RPCResponse to JSON", "error", err) - } else { - wsc.baseConn.SetWriteDeadline(time.Now().Add(time.Second * wsWriteTimeoutSeconds)) - if err = wsc.baseConn.WriteMessage(websocket.TextMessage, jsonBytes); err != nil { - wsc.Logger.Error("Failed to write response on websocket", "error", err) - wsc.Stop() - return - } - } - } - } -} - -//---------------------------------------- - -// Main manager for all websocket connections -// Holds the event switch -// NOTE: The websocket path is defined externally, e.g. in node/node.go -type WebsocketManager struct { - websocket.Upgrader - funcMap map[string]*RPCFunc - evsw events.EventSwitch - logger log.Logger -} - -func NewWebsocketManager(funcMap map[string]*RPCFunc, evsw events.EventSwitch) *WebsocketManager { - return &WebsocketManager{ - funcMap: funcMap, - evsw: evsw, - Upgrader: websocket.Upgrader{ - ReadBufferSize: 1024, - WriteBufferSize: 1024, - CheckOrigin: func(r *http.Request) bool { - // TODO - return true - }, - }, - logger: log.NewNopLogger(), - } -} - -func (wm *WebsocketManager) SetLogger(l log.Logger) { - wm.logger = l -} - -// Upgrade the request/response (via http.Hijack) and starts the wsConnection. -func (wm *WebsocketManager) WebsocketHandler(w http.ResponseWriter, r *http.Request) { - wsConn, err := wm.Upgrade(w, r, nil) - if err != nil { - // TODO - return http error - wm.logger.Error("Failed to upgrade to websocket connection", "error", err) - return - } - - // register connection - con := NewWSConnection(wsConn, wm.funcMap, wm.evsw) - wm.logger.Info("New websocket connection", "remote", con.remoteAddr) - con.Start() // Blocking -} - -// rpc.websocket -//----------------------------------------------------------------------------- - -// NOTE: assume returns is result struct and error. If error is not nil, return it -func unreflectResult(returns []reflect.Value) (interface{}, error) { - errV := returns[1] - if errV.Interface() != nil { - return nil, errors.Errorf("%v", errV.Interface()) - } - rv := returns[0] - // the result is a registered interface, - // we need a pointer to it so we can marshal with type byte - rvp := reflect.New(rv.Type()) - rvp.Elem().Set(rv) - return rvp.Interface(), nil -} - -// writes a list of available rpc endpoints as an html page -func writeListOfEndpoints(w http.ResponseWriter, r *http.Request, funcMap map[string]*RPCFunc) { - noArgNames := []string{} - argNames := []string{} - for name, funcData := range funcMap { - if len(funcData.args) == 0 { - noArgNames = append(noArgNames, name) - } else { - argNames = append(argNames, name) - } - } - sort.Strings(noArgNames) - sort.Strings(argNames) - buf := new(bytes.Buffer) - buf.WriteString("") - buf.WriteString("
Available endpoints:
") - - for _, name := range noArgNames { - link := fmt.Sprintf("http://%s/%s", r.Host, name) - buf.WriteString(fmt.Sprintf("%s
", link, link)) - } - - buf.WriteString("
Endpoints that require arguments:
") - for _, name := range argNames { - link := fmt.Sprintf("http://%s/%s?", r.Host, name) - funcData := funcMap[name] - for i, argName := range funcData.argNames { - link += argName + "=_" - if i < len(funcData.argNames)-1 { - link += "&" - } - } - buf.WriteString(fmt.Sprintf("%s
", link, link)) - } - buf.WriteString("") - w.Header().Set("Content-Type", "text/html") - w.WriteHeader(200) - w.Write(buf.Bytes()) -} diff --git a/rpc/lib/server/http_params.go b/rpc/lib/server/http_params.go deleted file mode 100644 index 565060678..000000000 --- a/rpc/lib/server/http_params.go +++ /dev/null @@ -1,90 +0,0 @@ -package rpcserver - -import ( - "encoding/hex" - "net/http" - "regexp" - "strconv" - - "github.com/pkg/errors" -) - -var ( - // Parts of regular expressions - atom = "[A-Z0-9!#$%&'*+\\-/=?^_`{|}~]+" - dotAtom = atom + `(?:\.` + atom + `)*` - domain = `[A-Z0-9.-]+\.[A-Z]{2,4}` - - RE_HEX = regexp.MustCompile(`^(?i)[a-f0-9]+$`) - RE_EMAIL = regexp.MustCompile(`^(?i)(` + dotAtom + `)@(` + dotAtom + `)$`) - RE_ADDRESS = regexp.MustCompile(`^(?i)[a-z0-9]{25,34}$`) - RE_HOST = regexp.MustCompile(`^(?i)(` + domain + `)$`) - - //RE_ID12 = regexp.MustCompile(`^[a-zA-Z0-9]{12}$`) -) - -func GetParam(r *http.Request, param string) string { - s := r.URL.Query().Get(param) - if s == "" { - s = r.FormValue(param) - } - return s -} - -func GetParamByteSlice(r *http.Request, param string) ([]byte, error) { - s := GetParam(r, param) - return hex.DecodeString(s) -} - -func GetParamInt64(r *http.Request, param string) (int64, error) { - s := GetParam(r, param) - i, err := strconv.ParseInt(s, 10, 64) - if err != nil { - return 0, errors.Errorf(param, err.Error()) - } - return i, nil -} - -func GetParamInt32(r *http.Request, param string) (int32, error) { - s := GetParam(r, param) - i, err := strconv.ParseInt(s, 10, 32) - if err != nil { - return 0, errors.Errorf(param, err.Error()) - } - return int32(i), nil -} - -func GetParamUint64(r *http.Request, param string) (uint64, error) { - s := GetParam(r, param) - i, err := strconv.ParseUint(s, 10, 64) - if err != nil { - return 0, errors.Errorf(param, err.Error()) - } - return i, nil -} - -func GetParamUint(r *http.Request, param string) (uint, error) { - s := GetParam(r, param) - i, err := strconv.ParseUint(s, 10, 64) - if err != nil { - return 0, errors.Errorf(param, err.Error()) - } - return uint(i), nil -} - -func GetParamRegexp(r *http.Request, param string, re *regexp.Regexp) (string, error) { - s := GetParam(r, param) - if !re.MatchString(s) { - return "", errors.Errorf(param, "Did not match regular expression %v", re.String()) - } - return s, nil -} - -func GetParamFloat64(r *http.Request, param string) (float64, error) { - s := GetParam(r, param) - f, err := strconv.ParseFloat(s, 64) - if err != nil { - return 0, errors.Errorf(param, err.Error()) - } - return f, nil -} diff --git a/rpc/lib/server/http_server.go b/rpc/lib/server/http_server.go deleted file mode 100644 index 442b7c42c..000000000 --- a/rpc/lib/server/http_server.go +++ /dev/null @@ -1,136 +0,0 @@ -// Commons for HTTP handling -package rpcserver - -import ( - "bufio" - "encoding/json" - "fmt" - "net" - "net/http" - "runtime/debug" - "strings" - "time" - - types "github.com/bytom/rpc/lib/types" - "github.com/pkg/errors" - "github.com/tendermint/tmlibs/log" -) - -func StartHTTPServer(listenAddr string, handler http.Handler, logger log.Logger) (listener net.Listener, err error) { - // listenAddr should be fully formed including tcp:// or unix:// prefix - var proto, addr string - parts := strings.SplitN(listenAddr, "://", 2) - if len(parts) != 2 { - logger.Error("WARNING (tendermint/rpc/lib): Please use fully formed listening addresses, including the tcp:// or unix:// prefix") - // we used to allow addrs without tcp/unix prefix by checking for a colon - // TODO: Deprecate - proto = types.SocketType(listenAddr) - addr = listenAddr - // return nil, errors.Errorf("Invalid listener address %s", lisenAddr) - } else { - proto, addr = parts[0], parts[1] - } - - logger.Info(fmt.Sprintf("Starting RPC HTTP server on %s socket %v", proto, addr)) - listener, err = net.Listen(proto, addr) - if err != nil { - return nil, errors.Errorf("Failed to listen to %v: %v", listenAddr, err) - } - - go func() { - res := http.Serve( - listener, - RecoverAndLogHandler(handler, logger), - ) - logger.Error("RPC HTTP server stopped", "result", res) - }() - return listener, nil -} - -func WriteRPCResponseHTTPError(w http.ResponseWriter, httpCode int, res types.RPCResponse) { - jsonBytes, err := json.MarshalIndent(res, "", " ") - if err != nil { - panic(err) - } - - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(httpCode) - w.Write(jsonBytes) -} - -func WriteRPCResponseHTTP(w http.ResponseWriter, res types.RPCResponse) { - jsonBytes, err := json.MarshalIndent(res, "", " ") - if err != nil { - panic(err) - } - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(200) - w.Write(jsonBytes) -} - -//----------------------------------------------------------------------------- - -// Wraps an HTTP handler, adding error logging. -// If the inner function panics, the outer function recovers, logs, sends an -// HTTP 500 error response. -func RecoverAndLogHandler(handler http.Handler, logger log.Logger) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Wrap the ResponseWriter to remember the status - rww := &ResponseWriterWrapper{-1, w} - begin := time.Now() - - // Common headers - origin := r.Header.Get("Origin") - rww.Header().Set("Access-Control-Allow-Origin", origin) - rww.Header().Set("Access-Control-Allow-Credentials", "true") - rww.Header().Set("Access-Control-Expose-Headers", "X-Server-Time") - rww.Header().Set("X-Server-Time", fmt.Sprintf("%v", begin.Unix())) - - defer func() { - // Send a 500 error if a panic happens during a handler. - // Without this, Chrome & Firefox were retrying aborted ajax requests, - // at least to my localhost. - if e := recover(); e != nil { - - // If RPCResponse - if res, ok := e.(types.RPCResponse); ok { - WriteRPCResponseHTTP(rww, res) - } else { - // For the rest, - logger.Error("Panic in RPC HTTP handler", "error", e, "stack", string(debug.Stack())) - rww.WriteHeader(http.StatusInternalServerError) - WriteRPCResponseHTTP(rww, types.NewRPCResponse("", nil, fmt.Sprintf("Internal Server Error: %v", e))) - } - } - - // Finally, log. - durationMS := time.Since(begin).Nanoseconds() / 1000000 - if rww.Status == -1 { - rww.Status = 200 - } - logger.Info("Served RPC HTTP response", - "method", r.Method, "url", r.URL, - "status", rww.Status, "duration", durationMS, - "remoteAddr", r.RemoteAddr, - ) - }() - - handler.ServeHTTP(rww, r) - }) -} - -// Remember the status for logging -type ResponseWriterWrapper struct { - Status int - http.ResponseWriter -} - -func (w *ResponseWriterWrapper) WriteHeader(status int) { - w.Status = status - w.ResponseWriter.WriteHeader(status) -} - -// implements http.Hijacker -func (w *ResponseWriterWrapper) Hijack() (net.Conn, *bufio.ReadWriter, error) { - return w.ResponseWriter.(http.Hijacker).Hijack() -} diff --git a/rpc/lib/server/parse_test.go b/rpc/lib/server/parse_test.go deleted file mode 100644 index 3c6d6edde..000000000 --- a/rpc/lib/server/parse_test.go +++ /dev/null @@ -1,174 +0,0 @@ -package rpcserver - -import ( - "encoding/json" - "strconv" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/tendermint/go-wire/data" -) - -func TestParseJSONMap(t *testing.T) { - assert := assert.New(t) - - input := []byte(`{"value":"1234","height":22}`) - - // naive is float,string - var p1 map[string]interface{} - err := json.Unmarshal(input, &p1) - if assert.Nil(err) { - h, ok := p1["height"].(float64) - if assert.True(ok, "%#v", p1["height"]) { - assert.EqualValues(22, h) - } - v, ok := p1["value"].(string) - if assert.True(ok, "%#v", p1["value"]) { - assert.EqualValues("1234", v) - } - } - - // preloading map with values doesn't help - tmp := 0 - p2 := map[string]interface{}{ - "value": &data.Bytes{}, - "height": &tmp, - } - err = json.Unmarshal(input, &p2) - if assert.Nil(err) { - h, ok := p2["height"].(float64) - if assert.True(ok, "%#v", p2["height"]) { - assert.EqualValues(22, h) - } - v, ok := p2["value"].(string) - if assert.True(ok, "%#v", p2["value"]) { - assert.EqualValues("1234", v) - } - } - - // preload here with *pointers* to the desired types - // struct has unknown types, but hard-coded keys - tmp = 0 - p3 := struct { - Value interface{} `json:"value"` - Height interface{} `json:"height"` - }{ - Height: &tmp, - Value: &data.Bytes{}, - } - err = json.Unmarshal(input, &p3) - if assert.Nil(err) { - h, ok := p3.Height.(*int) - if assert.True(ok, "%#v", p3.Height) { - assert.Equal(22, *h) - } - v, ok := p3.Value.(*data.Bytes) - if assert.True(ok, "%#v", p3.Value) { - assert.EqualValues([]byte{0x12, 0x34}, *v) - } - } - - // simplest solution, but hard-coded - p4 := struct { - Value data.Bytes `json:"value"` - Height int `json:"height"` - }{} - err = json.Unmarshal(input, &p4) - if assert.Nil(err) { - assert.EqualValues(22, p4.Height) - assert.EqualValues([]byte{0x12, 0x34}, p4.Value) - } - - // so, let's use this trick... - // dynamic keys on map, and we can deserialize to the desired types - var p5 map[string]*json.RawMessage - err = json.Unmarshal(input, &p5) - if assert.Nil(err) { - var h int - err = json.Unmarshal(*p5["height"], &h) - if assert.Nil(err) { - assert.Equal(22, h) - } - - var v data.Bytes - err = json.Unmarshal(*p5["value"], &v) - if assert.Nil(err) { - assert.Equal(data.Bytes{0x12, 0x34}, v) - } - } -} - -func TestParseJSONArray(t *testing.T) { - assert := assert.New(t) - - input := []byte(`["1234",22]`) - - // naive is float,string - var p1 []interface{} - err := json.Unmarshal(input, &p1) - if assert.Nil(err) { - v, ok := p1[0].(string) - if assert.True(ok, "%#v", p1[0]) { - assert.EqualValues("1234", v) - } - h, ok := p1[1].(float64) - if assert.True(ok, "%#v", p1[1]) { - assert.EqualValues(22, h) - } - } - - // preloading map with values helps here (unlike map - p2 above) - tmp := 0 - p2 := []interface{}{&data.Bytes{}, &tmp} - err = json.Unmarshal(input, &p2) - if assert.Nil(err) { - v, ok := p2[0].(*data.Bytes) - if assert.True(ok, "%#v", p2[0]) { - assert.EqualValues([]byte{0x12, 0x34}, *v) - } - h, ok := p2[1].(*int) - if assert.True(ok, "%#v", p2[1]) { - assert.EqualValues(22, *h) - } - } -} - -func TestParseRPC(t *testing.T) { - assert := assert.New(t) - - demo := func(height int, name string) {} - call := NewRPCFunc(demo, "height,name") - - cases := []struct { - raw string - height int64 - name string - fail bool - }{ - // should parse - {`[7, "flew"]`, 7, "flew", false}, - {`{"name": "john", "height": 22}`, 22, "john", false}, - // defaults - {`{"name": "solo", "unused": "stuff"}`, 0, "solo", false}, - // should fail - wrong types/lenght - {`["flew", 7]`, 0, "", true}, - {`[7,"flew",100]`, 0, "", true}, - {`{"name": -12, "height": "fred"}`, 0, "", true}, - } - for idx, tc := range cases { - i := strconv.Itoa(idx) - data := []byte(tc.raw) - vals, err := jsonParamsToArgs(call, data, 0) - if tc.fail { - assert.NotNil(err, i) - } else { - assert.Nil(err, "%s: %+v", i, err) - if assert.Equal(2, len(vals), i) { - assert.Equal(tc.height, vals[0].Int(), i) - assert.Equal(tc.name, vals[1].String(), i) - } - } - - } - -} diff --git a/rpc/lib/test/data.json b/rpc/lib/test/data.json deleted file mode 100644 index 83283ec33..000000000 --- a/rpc/lib/test/data.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "jsonrpc": "2.0", - "id": "", - "method": "hello_world", - "params": { - "name": "my_world", - "num": 5 - } -} diff --git a/rpc/lib/test/integration_test.sh b/rpc/lib/test/integration_test.sh deleted file mode 100755 index 7c23be7d3..000000000 --- a/rpc/lib/test/integration_test.sh +++ /dev/null @@ -1,95 +0,0 @@ -#!/usr/bin/env bash -set -e - -# Get the directory of where this script is. -SOURCE="${BASH_SOURCE[0]}" -while [ -h "$SOURCE" ] ; do SOURCE="$(readlink "$SOURCE")"; done -DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" - -# Change into that dir because we expect that. -pushd "$DIR" - -echo "==> Building the server" -go build -o rpcserver main.go - -echo "==> (Re)starting the server" -PID=$(pgrep rpcserver || echo "") -if [[ $PID != "" ]]; then - kill -9 "$PID" -fi -./rpcserver & -PID=$! -sleep 2 - -echo "==> simple request" -R1=$(curl -s 'http://localhost:8008/hello_world?name="my_world"&num=5') -R2=$(curl -s --data @data.json http://localhost:8008) -if [[ "$R1" != "$R2" ]]; then - echo "responses are not identical:" - echo "R1: $R1" - echo "R2: $R2" - echo "FAIL" - exit 1 -else - echo "OK" -fi - -echo "==> request with 0x-prefixed hex string arg" -R1=$(curl -s 'http://localhost:8008/hello_world?name=0x41424344&num=123') -R2='{"jsonrpc":"2.0","id":"","result":{"Result":"hi ABCD 123"},"error":""}' -if [[ "$R1" != "$R2" ]]; then - echo "responses are not identical:" - echo "R1: $R1" - echo "R2: $R2" - echo "FAIL" - exit 1 -else - echo "OK" -fi - -echo "==> request with missing params" -R1=$(curl -s 'http://localhost:8008/hello_world') -R2='{"jsonrpc":"2.0","id":"","result":{"Result":"hi 0"},"error":""}' -if [[ "$R1" != "$R2" ]]; then - echo "responses are not identical:" - echo "R1: $R1" - echo "R2: $R2" - echo "FAIL" - exit 1 -else - echo "OK" -fi - -echo "==> request with unquoted string arg" -R1=$(curl -s 'http://localhost:8008/hello_world?name=abcd&num=123') -R2="{\"jsonrpc\":\"2.0\",\"id\":\"\",\"result\":null,\"error\":\"Error converting http params to args: invalid character 'a' looking for beginning of value\"}" -if [[ "$R1" != "$R2" ]]; then - echo "responses are not identical:" - echo "R1: $R1" - echo "R2: $R2" - echo "FAIL" - exit 1 -else - echo "OK" -fi - -echo "==> request with string type when expecting number arg" -R1=$(curl -s 'http://localhost:8008/hello_world?name="abcd"&num=0xabcd') -R2="{\"jsonrpc\":\"2.0\",\"id\":\"\",\"result\":null,\"error\":\"Error converting http params to args: Got a hex string arg, but expected 'int'\"}" -if [[ "$R1" != "$R2" ]]; then - echo "responses are not identical:" - echo "R1: $R1" - echo "R2: $R2" - echo "FAIL" - exit 1 -else - echo "OK" -fi - -echo "==> Stopping the server" -kill -9 $PID - -rm -f rpcserver - -popd -exit 0 diff --git a/rpc/lib/test/main.go b/rpc/lib/test/main.go deleted file mode 100644 index 047a1d866..000000000 --- a/rpc/lib/test/main.go +++ /dev/null @@ -1,38 +0,0 @@ -package main - -import ( - "fmt" - "net/http" - "os" - - rpcserver "github.com/bytom/rpc/lib/server" - cmn "github.com/tendermint/tmlibs/common" - "github.com/tendermint/tmlibs/log" -) - -var routes = map[string]*rpcserver.RPCFunc{ - "hello_world": rpcserver.NewRPCFunc(HelloWorld, "name,num"), -} - -func HelloWorld(name string, num int) (Result, error) { - return Result{fmt.Sprintf("hi %s %d", name, num)}, nil -} - -type Result struct { - Result string -} - -func main() { - mux := http.NewServeMux() - logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) - rpcserver.RegisterRPCFuncs(mux, routes, logger) - _, err := rpcserver.StartHTTPServer("0.0.0.0:8008", mux, logger) - if err != nil { - cmn.Exit(err.Error()) - } - - // Wait forever - cmn.TrapSignal(func() { - }) - -} diff --git a/rpc/lib/types/types.go b/rpc/lib/types/types.go deleted file mode 100644 index 8076e4b0d..000000000 --- a/rpc/lib/types/types.go +++ /dev/null @@ -1,100 +0,0 @@ -package rpctypes - -import ( - "encoding/json" - "strings" - - events "github.com/tendermint/tmlibs/events" -) - -type RPCRequest struct { - JSONRPC string `json:"jsonrpc"` - ID string `json:"id"` - Method string `json:"method"` - Params *json.RawMessage `json:"params"` // must be map[string]interface{} or []interface{} -} - -func NewRPCRequest(id string, method string, params json.RawMessage) RPCRequest { - return RPCRequest{ - JSONRPC: "2.0", - ID: id, - Method: method, - Params: ¶ms, - } -} - -func MapToRequest(id string, method string, params map[string]interface{}) (RPCRequest, error) { - payload, err := json.Marshal(params) - if err != nil { - return RPCRequest{}, err - } - request := NewRPCRequest(id, method, payload) - return request, nil -} - -func ArrayToRequest(id string, method string, params []interface{}) (RPCRequest, error) { - payload, err := json.Marshal(params) - if err != nil { - return RPCRequest{}, err - } - request := NewRPCRequest(id, method, payload) - return request, nil -} - -//---------------------------------------- - -type RPCResponse struct { - JSONRPC string `json:"jsonrpc"` - ID string `json:"id"` - Result *json.RawMessage `json:"result"` - Error string `json:"error"` -} - -func NewRPCResponse(id string, res interface{}, err string) RPCResponse { - var raw *json.RawMessage - if res != nil { - var js []byte - js, err2 := json.Marshal(res) - if err2 == nil { - rawMsg := json.RawMessage(js) - raw = &rawMsg - } else { - err = err2.Error() - } - } - return RPCResponse{ - JSONRPC: "2.0", - ID: id, - Result: raw, - Error: err, - } -} - -//---------------------------------------- - -// *wsConnection implements this interface. -type WSRPCConnection interface { - GetRemoteAddr() string - GetEventSwitch() events.EventSwitch - WriteRPCResponse(resp RPCResponse) - TryWriteRPCResponse(resp RPCResponse) bool -} - -// websocket-only RPCFuncs take this as the first parameter. -type WSRPCContext struct { - Request RPCRequest - WSRPCConnection -} - -//---------------------------------------- -// sockets -// -// Determine if its a unix or tcp socket. -// If tcp, must specify the port; `0.0.0.0` will return incorrectly as "unix" since there's no port -func SocketType(listenAddr string) string { - socketType := "unix" - if len(strings.Split(listenAddr, ":")) >= 2 { - socketType = "tcp" - } - return socketType -} diff --git a/rpc/lib/version.go b/rpc/lib/version.go deleted file mode 100644 index 8828f260b..000000000 --- a/rpc/lib/version.go +++ /dev/null @@ -1,7 +0,0 @@ -package rpc - -const Maj = "0" -const Min = "7" -const Fix = "0" - -const Version = Maj + "." + Min + "." + Fix diff --git a/rpc/test/client_test.go b/rpc/test/client_test.go deleted file mode 100644 index b7df67841..000000000 --- a/rpc/test/client_test.go +++ /dev/null @@ -1,352 +0,0 @@ -package rpctest - -import ( - "bytes" - crand "crypto/rand" - "fmt" - "math/rand" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - abci "github.com/tendermint/abci/types" - "github.com/tendermint/go-wire/data" - . "github.com/tendermint/tmlibs/common" - - "github.com/tendermint/tendermint/rpc/core" - ctypes "github.com/tendermint/tendermint/rpc/core/types" - rpc "github.com/tendermint/tendermint/rpc/lib/client" - "github.com/tendermint/tendermint/state/txindex/null" - "github.com/tendermint/tendermint/types" -) - -//-------------------------------------------------------------------------------- -// Test the HTTP client -// These tests assume the dummy app -//-------------------------------------------------------------------------------- - -//-------------------------------------------------------------------------------- -// status - -func TestURIStatus(t *testing.T) { - testStatus(t, GetURIClient()) -} - -func TestJSONStatus(t *testing.T) { - testStatus(t, GetJSONClient()) -} - -func testStatus(t *testing.T, client rpc.HTTPClient) { - moniker := GetConfig().Moniker - result := new(ctypes.ResultStatus) - _, err := client.Call("status", map[string]interface{}{}, result) - require.Nil(t, err) - assert.Equal(t, moniker, result.NodeInfo.Moniker) -} - -//-------------------------------------------------------------------------------- -// broadcast tx sync - -// random bytes (excluding byte('=')) -func randBytes(t *testing.T) []byte { - n := rand.Intn(10) + 2 - buf := make([]byte, n) - _, err := crand.Read(buf) - require.Nil(t, err) - return bytes.Replace(buf, []byte("="), []byte{100}, -1) -} - -func TestURIBroadcastTxSync(t *testing.T) { - testBroadcastTxSync(t, GetURIClient()) -} - -func TestJSONBroadcastTxSync(t *testing.T) { - testBroadcastTxSync(t, GetJSONClient()) -} - -func testBroadcastTxSync(t *testing.T, client rpc.HTTPClient) { - mem := node.MempoolReactor().Mempool - initMemSize := mem.Size() - result := new(ctypes.ResultBroadcastTx) - tx := randBytes(t) - _, err := client.Call("broadcast_tx_sync", map[string]interface{}{"tx": tx}, result) - require.Nil(t, err) - - require.Equal(t, abci.CodeType_OK, result.Code) - require.Equal(t, initMemSize+1, mem.Size()) - txs := mem.Reap(1) - require.EqualValues(t, tx, txs[0]) - mem.Flush() -} - -//-------------------------------------------------------------------------------- -// query - -func testTxKV(t *testing.T) ([]byte, []byte, types.Tx) { - k := randBytes(t) - v := randBytes(t) - return k, v, types.Tx(Fmt("%s=%s", k, v)) -} - -func sendTx(t *testing.T, client rpc.HTTPClient) ([]byte, []byte) { - result := new(ctypes.ResultBroadcastTxCommit) - k, v, tx := testTxKV(t) - _, err := client.Call("broadcast_tx_commit", map[string]interface{}{"tx": tx}, result) - require.Nil(t, err) - require.NotNil(t, 0, result.DeliverTx, "%#v", result) - require.EqualValues(t, 0, result.CheckTx.Code, "%#v", result) - require.EqualValues(t, 0, result.DeliverTx.Code, "%#v", result) - return k, v -} - -func TestURIABCIQuery(t *testing.T) { - testABCIQuery(t, GetURIClient()) -} - -func TestJSONABCIQuery(t *testing.T) { - testABCIQuery(t, GetJSONClient()) -} - -func testABCIQuery(t *testing.T, client rpc.HTTPClient) { - k, _ := sendTx(t, client) - time.Sleep(time.Millisecond * 500) - result := new(ctypes.ResultABCIQuery) - _, err := client.Call("abci_query", - map[string]interface{}{"path": "", "data": data.Bytes(k), "prove": false}, result) - require.Nil(t, err) - - require.EqualValues(t, 0, result.Code) - - // XXX: specific to value returned by the dummy - require.NotEqual(t, 0, len(result.Value)) -} - -//-------------------------------------------------------------------------------- -// broadcast tx commit - -func TestURIBroadcastTxCommit(t *testing.T) { - testBroadcastTxCommit(t, GetURIClient()) -} - -func TestJSONBroadcastTxCommit(t *testing.T) { - testBroadcastTxCommit(t, GetJSONClient()) -} - -func testBroadcastTxCommit(t *testing.T, client rpc.HTTPClient) { - require := require.New(t) - - result := new(ctypes.ResultBroadcastTxCommit) - tx := randBytes(t) - _, err := client.Call("broadcast_tx_commit", map[string]interface{}{"tx": tx}, result) - require.Nil(err) - - checkTx := result.CheckTx - require.Equal(abci.CodeType_OK, checkTx.Code) - deliverTx := result.DeliverTx - require.Equal(abci.CodeType_OK, deliverTx.Code) - mem := node.MempoolReactor().Mempool - require.Equal(0, mem.Size()) - // TODO: find tx in block -} - -//-------------------------------------------------------------------------------- -// query tx - -func TestURITx(t *testing.T) { - testTx(t, GetURIClient(), true) - - core.SetTxIndexer(&null.TxIndex{}) - defer core.SetTxIndexer(node.ConsensusState().GetState().TxIndexer) - - testTx(t, GetURIClient(), false) -} - -func TestJSONTx(t *testing.T) { - testTx(t, GetJSONClient(), true) - - core.SetTxIndexer(&null.TxIndex{}) - testTx(t, GetJSONClient(), false) - core.SetTxIndexer(node.ConsensusState().GetState().TxIndexer) -} - -func testTx(t *testing.T, client rpc.HTTPClient, withIndexer bool) { - assert, require := assert.New(t), require.New(t) - - // first we broadcast a tx - result := new(ctypes.ResultBroadcastTxCommit) - txBytes := randBytes(t) - tx := types.Tx(txBytes) - _, err := client.Call("broadcast_tx_commit", map[string]interface{}{"tx": txBytes}, result) - require.Nil(err) - - checkTx := result.CheckTx - require.Equal(abci.CodeType_OK, checkTx.Code) - deliverTx := result.DeliverTx - require.Equal(abci.CodeType_OK, deliverTx.Code) - mem := node.MempoolReactor().Mempool - require.Equal(0, mem.Size()) - - txHash := tx.Hash() - txHash2 := types.Tx("a different tx").Hash() - - cases := []struct { - valid bool - hash []byte - prove bool - }{ - // only valid if correct hash provided - {true, txHash, false}, - {true, txHash, true}, - {false, txHash2, false}, - {false, txHash2, true}, - {false, nil, false}, - {false, nil, true}, - } - - for i, tc := range cases { - idx := fmt.Sprintf("%d", i) - - // now we query for the tx. - // since there's only one tx, we know index=0. - result2 := new(ctypes.ResultTx) - query := map[string]interface{}{ - "hash": tc.hash, - "prove": tc.prove, - } - _, err = client.Call("tx", query, result2) - valid := (withIndexer && tc.valid) - if !valid { - require.NotNil(err, idx) - } else { - require.Nil(err, idx) - assert.Equal(tx, result2.Tx, idx) - assert.Equal(result.Height, result2.Height, idx) - assert.Equal(0, result2.Index, idx) - assert.Equal(abci.CodeType_OK, result2.TxResult.Code, idx) - // time to verify the proof - proof := result2.Proof - if tc.prove && assert.Equal(tx, proof.Data, idx) { - assert.True(proof.Proof.Verify(proof.Index, proof.Total, tx.Hash(), proof.RootHash), idx) - } - } - } - -} - -//-------------------------------------------------------------------------------- -// Test the websocket service - -var wsTyp = "JSONRPC" - -// make a simple connection to the server -func TestWSConnect(t *testing.T) { - wsc := GetWSClient() - wsc.Stop() -} - -// receive a new block message -func TestWSNewBlock(t *testing.T) { - wsc := GetWSClient() - eid := types.EventStringNewBlock() - require.Nil(t, wsc.Subscribe(eid)) - - defer func() { - require.Nil(t, wsc.Unsubscribe(eid)) - wsc.Stop() - }() - waitForEvent(t, wsc, eid, true, func() {}, func(eid string, b interface{}) error { - // fmt.Println("Check:", b) - return nil - }) -} - -// receive a few new block messages in a row, with increasing height -func TestWSBlockchainGrowth(t *testing.T) { - if testing.Short() { - t.Skip("skipping test in short mode.") - } - wsc := GetWSClient() - eid := types.EventStringNewBlock() - require.Nil(t, wsc.Subscribe(eid)) - - defer func() { - require.Nil(t, wsc.Unsubscribe(eid)) - wsc.Stop() - }() - - // listen for NewBlock, ensure height increases by 1 - - var initBlockN int - for i := 0; i < 3; i++ { - waitForEvent(t, wsc, eid, true, func() {}, func(eid string, eventData interface{}) error { - block := eventData.(types.TMEventData).Unwrap().(types.EventDataNewBlock).Block - if i == 0 { - initBlockN = block.Header.Height - } else { - if block.Header.Height != initBlockN+i { - return fmt.Errorf("Expected block %d, got block %d", initBlockN+i, block.Header.Height) - } - } - - return nil - }) - } -} - -func TestWSTxEvent(t *testing.T) { - require := require.New(t) - wsc := GetWSClient() - tx := randBytes(t) - - // listen for the tx I am about to submit - eid := types.EventStringTx(types.Tx(tx)) - require.Nil(wsc.Subscribe(eid)) - - defer func() { - require.Nil(wsc.Unsubscribe(eid)) - wsc.Stop() - }() - - // send an tx - result := new(ctypes.ResultBroadcastTx) - _, err := GetJSONClient().Call("broadcast_tx_sync", map[string]interface{}{"tx": tx}, result) - require.Nil(err) - - waitForEvent(t, wsc, eid, true, func() {}, func(eid string, b interface{}) error { - evt, ok := b.(types.TMEventData).Unwrap().(types.EventDataTx) - require.True(ok, "Got wrong event type: %#v", b) - require.Equal(tx, []byte(evt.Tx), "Returned different tx") - require.Equal(abci.CodeType_OK, evt.Code) - return nil - }) -} - -/* TODO: this with dummy app.. -func TestWSDoubleFire(t *testing.T) { - if testing.Short() { - t.Skip("skipping test in short mode.") - } - con := newWSCon(t) - eid := types.EventStringAccInput(user[0].Address) - subscribe(t, con, eid) - defer func() { - unsubscribe(t, con, eid) - con.Close() - }() - amt := int64(100) - toAddr := user[1].Address - // broadcast the transaction, wait to hear about it - waitForEvent(t, con, eid, true, func() { - tx := makeDefaultSendTxSigned(t, wsTyp, toAddr, amt) - broadcastTx(t, wsTyp, tx) - }, func(eid string, b []byte) error { - return nil - }) - // but make sure we don't hear about it twice - waitForEvent(t, con, eid, false, func() { - }, func(eid string, b []byte) error { - return nil - }) -}*/ diff --git a/rpc/test/grpc_test.go b/rpc/test/grpc_test.go deleted file mode 100644 index 4935a09d9..000000000 --- a/rpc/test/grpc_test.go +++ /dev/null @@ -1,20 +0,0 @@ -package rpctest - -import ( - "testing" - - "golang.org/x/net/context" - - "github.com/stretchr/testify/require" - "github.com/tendermint/tendermint/rpc/grpc" -) - -//------------------------------------------- - -func TestBroadcastTx(t *testing.T) { - require := require.New(t) - res, err := GetGRPCClient().BroadcastTx(context.Background(), &core_grpc.RequestBroadcastTx{[]byte("this is a tx")}) - require.Nil(err) - require.EqualValues(0, res.CheckTx.Code) - require.EqualValues(0, res.DeliverTx.Code) -} diff --git a/rpc/test/helpers.go b/rpc/test/helpers.go deleted file mode 100644 index 51bb0b8fe..000000000 --- a/rpc/test/helpers.go +++ /dev/null @@ -1,178 +0,0 @@ -package rpctest - -import ( - "encoding/json" - "fmt" - "math/rand" - "os" - "path/filepath" - "strings" - "testing" - "time" - - "github.com/stretchr/testify/require" - "github.com/tendermint/tmlibs/log" - - abci "github.com/tendermint/abci/types" - cfg "github.com/tendermint/tendermint/config" - nm "github.com/tendermint/tendermint/node" - "github.com/tendermint/tendermint/proxy" - ctypes "github.com/tendermint/tendermint/rpc/core/types" - core_grpc "github.com/tendermint/tendermint/rpc/grpc" - client "github.com/tendermint/tendermint/rpc/lib/client" - "github.com/tendermint/tendermint/types" -) - -var config *cfg.Config - -// f**ing long, but unique for each test -func makePathname() string { - // get path - p, err := os.Getwd() - if err != nil { - panic(err) - } - fmt.Println(p) - sep := string(filepath.Separator) - return strings.Replace(p, sep, "_", -1) -} - -func randPort() int { - // returns between base and base + spread - base, spread := 20000, 20000 - return base + rand.Intn(spread) -} - -func makeAddrs() (string, string, string) { - start := randPort() - return fmt.Sprintf("tcp://0.0.0.0:%d", start), - fmt.Sprintf("tcp://0.0.0.0:%d", start+1), - fmt.Sprintf("tcp://0.0.0.0:%d", start+2) -} - -// GetConfig returns a config for the test cases as a singleton -func GetConfig() *cfg.Config { - if config == nil { - pathname := makePathname() - config = cfg.ResetTestRoot(pathname) - - // and we use random ports to run in parallel - tm, rpc, grpc := makeAddrs() - config.P2P.ListenAddress = tm - config.RPC.ListenAddress = rpc - config.RPC.GRPCListenAddress = grpc - } - return config -} - -// GetURIClient gets a uri client pointing to the test tendermint rpc -func GetURIClient() *client.URIClient { - rpcAddr := GetConfig().RPC.ListenAddress - return client.NewURIClient(rpcAddr) -} - -// GetJSONClient gets a http/json client pointing to the test tendermint rpc -func GetJSONClient() *client.JSONRPCClient { - rpcAddr := GetConfig().RPC.ListenAddress - return client.NewJSONRPCClient(rpcAddr) -} - -func GetGRPCClient() core_grpc.BroadcastAPIClient { - grpcAddr := config.RPC.GRPCListenAddress - return core_grpc.StartGRPCClient(grpcAddr) -} - -func GetWSClient() *client.WSClient { - rpcAddr := GetConfig().RPC.ListenAddress - wsc := client.NewWSClient(rpcAddr, "/websocket") - if _, err := wsc.Start(); err != nil { - panic(err) - } - return wsc -} - -// StartTendermint starts a test tendermint server in a go routine and returns when it is initialized -func StartTendermint(app abci.Application) *nm.Node { - node := NewTendermint(app) - node.Start() - fmt.Println("Tendermint running!") - return node -} - -// NewTendermint creates a new tendermint server and sleeps forever -func NewTendermint(app abci.Application) *nm.Node { - // Create & start node - config := GetConfig() - logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) - logger = log.NewFilter(logger, log.AllowError()) - privValidatorFile := config.PrivValidatorFile() - privValidator := types.LoadOrGenPrivValidator(privValidatorFile, logger) - papp := proxy.NewLocalClientCreator(app) - node := nm.NewNode(config, privValidator, papp, logger) - return node -} - -//-------------------------------------------------------------------------------- -// Utilities for testing the websocket service - -// wait for an event; do things that might trigger events, and check them when they are received -// the check function takes an event id and the byte slice read off the ws -func waitForEvent(t *testing.T, wsc *client.WSClient, eventid string, dieOnTimeout bool, f func(), check func(string, interface{}) error) { - // go routine to wait for webscoket msg - goodCh := make(chan interface{}) - errCh := make(chan error) - - // Read message - go func() { - var err error - LOOP: - for { - select { - case r := <-wsc.ResultsCh: - result := new(ctypes.ResultEvent) - err = json.Unmarshal(r, result) - if err != nil { - // cant distinguish between error and wrong type ... - continue - } - if result.Name == eventid { - goodCh <- result.Data - break LOOP - } - case err := <-wsc.ErrorsCh: - errCh <- err - break LOOP - case <-wsc.Quit: - break LOOP - } - } - }() - - // do stuff (transactions) - f() - - // wait for an event or timeout - timeout := time.NewTimer(10 * time.Second) - select { - case <-timeout.C: - if dieOnTimeout { - wsc.Stop() - require.True(t, false, "%s event was not received in time", eventid) - } - // else that's great, we didn't hear the event - // and we shouldn't have - case eventData := <-goodCh: - if dieOnTimeout { - // message was received and expected - // run the check - require.Nil(t, check(eventid, eventData)) - } else { - wsc.Stop() - require.True(t, false, "%s event was not expected", eventid) - } - case err := <-errCh: - panic(err) // Show the stack trace. - } -} - -//-------------------------------------------------------------------------------- diff --git a/rpc/test/main_test.go b/rpc/test/main_test.go deleted file mode 100644 index f95176d9b..000000000 --- a/rpc/test/main_test.go +++ /dev/null @@ -1,36 +0,0 @@ -/* -package tests contain integration tests and helper functions for testing -the RPC interface - -In particular, it allows us to spin up a tendermint node in process, with -a live RPC server, which we can use to verify our rpc calls. It provides -all data structures, enabling us to do more complex tests (like node_test.go) -that introspect the blocks themselves to validate signatures and the like. - -It currently only spins up one node, it would be interesting to expand it -to multiple nodes to see the real effects of validating partially signed -blocks. -*/ -package rpctest - -import ( - "os" - "testing" - - "github.com/tendermint/abci/example/dummy" - nm "github.com/tendermint/tendermint/node" -) - -var node *nm.Node - -func TestMain(m *testing.M) { - // start a tendermint node (and merkleeyes) in the background to test against - app := dummy.NewDummyApplication() - node = StartTendermint(app) - code := m.Run() - - // and shut down proper at the end - node.Stop() - node.Wait() - os.Exit(code) -} diff --git a/rpc/test/net_info.sh b/rpc/test/net_info.sh deleted file mode 100755 index 1c7879a2d..000000000 --- a/rpc/test/net_info.sh +++ /dev/null @@ -1 +0,0 @@ -curl -X POST --data '{"jsonrpc":"2.0", "method": "net_info", "params":[], "id":"67"}' http://127.0.0.1:46657 diff --git a/sync/idempotency/group.go b/sync/idempotency/group.go new file mode 100644 index 000000000..b35c2045d --- /dev/null +++ b/sync/idempotency/group.go @@ -0,0 +1,75 @@ +/* +Copyright 2012 Google Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package idempotency provides a duplicate function call suppression +// mechanism. It is a lightly modified version of groupcache's +// singleflight package that does not forget keys until explicitly +// told to. +package idempotency + +import "sync" + +// call is an in-flight or completed Once call +type call struct { + wg sync.WaitGroup + val interface{} + err error +} + +// Group represents a class of work and forms a namespace in which +// units of work can be executed with duplicate suppression. +type Group struct { + mu sync.Mutex // protects m + m map[string]*call // lazily initialized +} + +// Once executes and returns the results of the given function, making +// sure that only one execution for a given key happens until the +// key is explicitly forgotten. If a duplicate comes in, the duplicate +// caller waits for the original to complete and receives the same results. +func (g *Group) Once(key string, fn func() (interface{}, error)) (interface{}, error) { + g.mu.Lock() + if g.m == nil { + g.m = make(map[string]*call) + } + if c, ok := g.m[key]; ok { + g.mu.Unlock() + c.wg.Wait() + return c.val, c.err + } + c := new(call) + c.wg.Add(1) + g.m[key] = c + g.mu.Unlock() + + c.val, c.err = fn() + if c.err != nil { + g.mu.Lock() + delete(g.m, key) + g.mu.Unlock() + } + c.wg.Done() + + return c.val, c.err +} + +// Forget forgets a key, allowing the next call for the key to execute +// the function. +func (g *Group) Forget(key string) { + g.mu.Lock() + delete(g.m, key) + g.mu.Unlock() +} diff --git a/sync/idempotency/group_test.go b/sync/idempotency/group_test.go new file mode 100644 index 000000000..06fd17db3 --- /dev/null +++ b/sync/idempotency/group_test.go @@ -0,0 +1,85 @@ +/* +Copyright 2012 Google Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package idempotency + +import ( + "errors" + "fmt" + "sync" + "sync/atomic" + "testing" + "time" +) + +func TestOnce(t *testing.T) { + var g Group + v, err := g.Once("key", func() (interface{}, error) { + return "bar", nil + }) + if got, want := fmt.Sprintf("%v (%T)", v, v), "bar (string)"; got != want { + t.Errorf("Once = %v; want %v", got, want) + } + if err != nil { + t.Errorf("Once error = %v", err) + } +} + +func TestOnceErr(t *testing.T) { + var g Group + someErr := errors.New("Some error") + v, err := g.Once("key", func() (interface{}, error) { + return nil, someErr + }) + if err != someErr { + t.Errorf("Once error = %v; want someErr", err) + } + if v != nil { + t.Errorf("unexpected non-nil value %#v", v) + } +} + +func TestOnceDupSuppress(t *testing.T) { + var g Group + c := make(chan string) + var calls int32 + fn := func() (interface{}, error) { + atomic.AddInt32(&calls, 1) + return <-c, nil + } + + const n = 10 + var wg sync.WaitGroup + for i := 0; i < n; i++ { + wg.Add(1) + go func() { + v, err := g.Once("key", fn) + if err != nil { + t.Errorf("Once error: %v", err) + } + if v.(string) != "bar" { + t.Errorf("got %q; want %q", v, "bar") + } + wg.Done() + }() + } + time.Sleep(100 * time.Millisecond) // let goroutines above block + c <- "bar" + wg.Wait() + if got := atomic.LoadInt32(&calls); got != 1 { + t.Errorf("number of calls = %d; want 1", got) + } +}