Skip to content

Commit

Permalink
Merge pull request #350 from qianbin/allow-get-pending-tx
Browse files Browse the repository at this point in the history
extends API GET /transactions/{id}
  • Loading branch information
qianbin authored Apr 15, 2020
2 parents fb403a0 + b1b2610 commit 419e32e
Show file tree
Hide file tree
Showing 7 changed files with 99 additions and 44 deletions.
2 changes: 1 addition & 1 deletion api/doc/bindata.go

Large diffs are not rendered by default.

15 changes: 13 additions & 2 deletions api/doc/thor.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ info:
license:
name: LGPL 3.0
url: 'https://www.gnu.org/licenses/lgpl-3.0.en.html'
version: 1.3.1
version: 1.3.2

servers:
- url: '/'
Expand Down Expand Up @@ -171,12 +171,13 @@ paths:
- $ref: '#/components/parameters/TxIDInPath'
- $ref: '#/components/parameters/RawInQuery'
- $ref: '#/components/parameters/HeadInQuery'
- $ref: '#/components/parameters/PendingInQuery'
get:
tags:
- Transactions
summary: Retrieve transaction
description: |
by ID.
by ID. When `pending` is true, a pending tx with null `meta` might be returned.
responses:
'200':
description: OK
Expand All @@ -188,11 +189,13 @@ paths:
- $ref: '#/components/schemas/Tx'
properties:
meta:
required: false
$ref: '#/components/schemas/TxMeta'
- allOf:
- $ref: '#/components/schemas/RawTx'
properties:
meta:
required: false
$ref: '#/components/schemas/TxMeta'
description: raw transaction
example:
Expand Down Expand Up @@ -1365,3 +1368,11 @@ components:
schema:
type: boolean

PendingInQuery:
name: pending
in: query
required: false
description: |
whether to return tx, even it's pending
schema:
type: boolean
33 changes: 27 additions & 6 deletions api/transactions/transactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,22 @@ func New(repo *chain.Repository, pool *txpool.TxPool) *Transactions {
}
}

func (t *Transactions) getRawTransaction(txID thor.Bytes32, head thor.Bytes32) (*rawTransaction, error) {
func (t *Transactions) getRawTransaction(txID thor.Bytes32, head thor.Bytes32, allowPending bool) (*rawTransaction, error) {

tx, meta, err := t.repo.NewChain(head).GetTransaction(txID)
if err != nil {
if t.repo.IsNotFound(err) {
if allowPending {
if pending := t.pool.Get(txID); pending != nil {
raw, err := rlp.EncodeToBytes(pending)
if err != nil {
return nil, err
}
return &rawTransaction{
RawTx: RawTx{hexutil.Encode(raw)},
}, nil
}
}
return nil, nil
}
return nil, err
Expand All @@ -50,18 +61,23 @@ func (t *Transactions) getRawTransaction(txID thor.Bytes32, head thor.Bytes32) (
}
return &rawTransaction{
RawTx: RawTx{hexutil.Encode(raw)},
Meta: TxMeta{
Meta: &TxMeta{
BlockID: summary.Header.ID(),
BlockNumber: summary.Header.Number(),
BlockTimestamp: summary.Header.Timestamp(),
},
}, nil
}

func (t *Transactions) getTransactionByID(txID thor.Bytes32, head thor.Bytes32) (*Transaction, error) {
func (t *Transactions) getTransactionByID(txID thor.Bytes32, head thor.Bytes32, allowPending bool) (*Transaction, error) {
tx, meta, err := t.repo.NewChain(head).GetTransaction(txID)
if err != nil {
if t.repo.IsNotFound(err) {
if allowPending {
if pending := t.pool.Get(txID); pending != nil {
return convertTransaction(pending, nil), nil
}
}
return nil, nil
}
return nil, err
Expand All @@ -71,7 +87,7 @@ func (t *Transactions) getTransactionByID(txID thor.Bytes32, head thor.Bytes32)
if err != nil {
return nil, err
}
return convertTransaction(tx, summary.Header)
return convertTransaction(tx, summary.Header), nil
}

//GetTransactionReceiptByID get tx's receipt
Expand Down Expand Up @@ -141,14 +157,19 @@ func (t *Transactions) handleGetTransactionByID(w http.ResponseWriter, req *http
if raw != "" && raw != "false" && raw != "true" {
return utils.BadRequest(errors.WithMessage(errors.New("should be boolean"), "raw"))
}
pending := req.URL.Query().Get("pending")
if pending != "" && pending != "false" && pending != "true" {
return utils.BadRequest(errors.WithMessage(errors.New("should be boolean"), "pending"))
}

if raw == "true" {
tx, err := t.getRawTransaction(txID, head)
tx, err := t.getRawTransaction(txID, head, pending == "true")
if err != nil {
return err
}
return utils.WriteJSON(w, tx)
}
tx, err := t.getTransactionByID(txID, head)
tx, err := t.getTransactionByID(txID, head, pending == "true")
if err != nil {
return err
}
Expand Down
15 changes: 9 additions & 6 deletions api/transactions/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ type Transaction struct {
Nonce math.HexOrDecimal64 `json:"nonce"`
DependsOn *thor.Bytes32 `json:"dependsOn"`
Size uint32 `json:"size"`
Meta TxMeta `json:"meta"`
Meta *TxMeta `json:"meta"`
}

type RawTx struct {
Expand All @@ -80,11 +80,11 @@ func (rtx *RawTx) decode() (*tx.Transaction, error) {

type rawTransaction struct {
RawTx
Meta TxMeta `json:"meta"`
Meta *TxMeta `json:"meta"`
}

//convertTransaction convert a raw transaction into a json format transaction
func convertTransaction(tx *tx.Transaction, header *block.Header) (*Transaction, error) {
func convertTransaction(tx *tx.Transaction, header *block.Header) *Transaction {
//tx origin
origin, _ := tx.Origin()
delegator, _ := tx.Delegator()
Expand All @@ -107,13 +107,16 @@ func convertTransaction(tx *tx.Transaction, header *block.Header) (*Transaction,
DependsOn: tx.DependsOn(),
Clauses: cls,
Delegator: delegator,
Meta: TxMeta{
}

if header != nil {
t.Meta = &TxMeta{
BlockID: header.ID(),
BlockNumber: header.Number(),
BlockTimestamp: header.Timestamp(),
},
}
}
return t, nil
return t
}

type TxMeta struct {
Expand Down
50 changes: 31 additions & 19 deletions txpool/tx_object_map.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,30 +15,33 @@ import (

// txObjectMap to maintain mapping of tx hash to tx object, and account quota.
type txObjectMap struct {
lock sync.RWMutex
txObjMap map[thor.Bytes32]*txObject
quota map[thor.Address]int
lock sync.RWMutex
mapByHash map[thor.Bytes32]*txObject
mapByID map[thor.Bytes32]*txObject
quota map[thor.Address]int
}

func newTxObjectMap() *txObjectMap {
return &txObjectMap{
txObjMap: make(map[thor.Bytes32]*txObject),
quota: make(map[thor.Address]int),
mapByHash: make(map[thor.Bytes32]*txObject),
mapByID: make(map[thor.Bytes32]*txObject),
quota: make(map[thor.Address]int),
}
}

func (m *txObjectMap) Contains(txHash thor.Bytes32) bool {
func (m *txObjectMap) ContainsHash(txHash thor.Bytes32) bool {
m.lock.RLock()
defer m.lock.RUnlock()
_, found := m.txObjMap[txHash]
_, found := m.mapByHash[txHash]
return found
}

func (m *txObjectMap) Add(txObj *txObject, limitPerAccount int) error {
m.lock.Lock()
defer m.lock.Unlock()

if _, found := m.txObjMap[txObj.Hash()]; found {
hash := txObj.Hash()
if _, found := m.mapByHash[hash]; found {
return nil
}

Expand All @@ -47,21 +50,29 @@ func (m *txObjectMap) Add(txObj *txObject, limitPerAccount int) error {
}

m.quota[txObj.Origin()]++
m.txObjMap[txObj.Hash()] = txObj
m.mapByHash[hash] = txObj
m.mapByID[txObj.ID()] = txObj
return nil
}

func (m *txObjectMap) Remove(txHash thor.Bytes32) bool {
func (m *txObjectMap) GetByID(id thor.Bytes32) *txObject {
m.lock.Lock()
defer m.lock.Unlock()
return m.mapByID[id]
}

func (m *txObjectMap) RemoveByHash(txHash thor.Bytes32) bool {
m.lock.Lock()
defer m.lock.Unlock()

if txObj, ok := m.txObjMap[txHash]; ok {
if txObj, ok := m.mapByHash[txHash]; ok {
if m.quota[txObj.Origin()] > 1 {
m.quota[txObj.Origin()]--
} else {
delete(m.quota, txObj.Origin())
}
delete(m.txObjMap, txHash)
delete(m.mapByHash, txHash)
delete(m.mapByID, txObj.ID())
return true
}
return false
Expand All @@ -71,8 +82,8 @@ func (m *txObjectMap) ToTxObjects() []*txObject {
m.lock.RLock()
defer m.lock.RUnlock()

txObjs := make([]*txObject, 0, len(m.txObjMap))
for _, txObj := range m.txObjMap {
txObjs := make([]*txObject, 0, len(m.mapByHash))
for _, txObj := range m.mapByHash {
txObjs = append(txObjs, txObj)
}
return txObjs
Expand All @@ -82,8 +93,8 @@ func (m *txObjectMap) ToTxs() tx.Transactions {
m.lock.RLock()
defer m.lock.RUnlock()

txs := make(tx.Transactions, 0, len(m.txObjMap))
for _, txObj := range m.txObjMap {
txs := make(tx.Transactions, 0, len(m.mapByHash))
for _, txObj := range m.mapByHash {
txs = append(txs, txObj.Transaction)
}
return txs
Expand All @@ -93,19 +104,20 @@ func (m *txObjectMap) Fill(txObjs []*txObject) {
m.lock.Lock()
defer m.lock.Unlock()
for _, txObj := range txObjs {
if _, found := m.txObjMap[txObj.Hash()]; found {
if _, found := m.mapByHash[txObj.Hash()]; found {
continue
}
// skip account limit check

m.quota[txObj.Origin()]++
m.txObjMap[txObj.Hash()] = txObj
m.mapByHash[txObj.Hash()] = txObj
m.mapByID[txObj.ID()] = txObj
}
}

func (m *txObjectMap) Len() int {
m.lock.RLock()
defer m.lock.RUnlock()

return len(m.txObjMap)
return len(m.mapByHash)
}
12 changes: 6 additions & 6 deletions txpool/tx_object_map_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,13 @@ func TestTxObjMap(t *testing.T) {
assert.Nil(t, m.Add(txObj3, 1))
assert.Equal(t, 2, m.Len())

assert.True(t, m.Contains(tx1.Hash()))
assert.False(t, m.Contains(tx2.Hash()))
assert.True(t, m.Contains(tx3.Hash()))
assert.True(t, m.ContainsHash(tx1.Hash()))
assert.False(t, m.ContainsHash(tx2.Hash()))
assert.True(t, m.ContainsHash(tx3.Hash()))

assert.True(t, m.Remove(tx1.Hash()))
assert.False(t, m.Contains(tx1.Hash()))
assert.False(t, m.Remove(tx2.Hash()))
assert.True(t, m.RemoveByHash(tx1.Hash()))
assert.False(t, m.ContainsHash(tx1.Hash()))
assert.False(t, m.RemoveByHash(tx2.Hash()))

assert.Equal(t, []*txObject{txObj3}, m.ToTxObjects())
assert.Equal(t, tx.Transactions{tx3}, m.ToTxs())
Expand Down
16 changes: 12 additions & 4 deletions txpool/tx_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ func (p *TxPool) SubscribeTxEvent(ch chan *TxEvent) event.Subscription {
}

func (p *TxPool) add(newTx *tx.Transaction, rejectNonexecutable bool) error {
if p.all.Contains(newTx.Hash()) {
if p.all.ContainsHash(newTx.Hash()) {
// tx already in the pool
return nil
}
Expand Down Expand Up @@ -279,14 +279,22 @@ func (p *TxPool) Add(newTx *tx.Transaction) error {
return p.add(newTx, false)
}

// Get get pooled tx by id.
func (p *TxPool) Get(id thor.Bytes32) *tx.Transaction {
if txObj := p.all.GetByID(id); txObj != nil {
return txObj.Transaction
}
return nil
}

// StrictlyAdd add new tx into pool. A rejection error will be returned, if tx is not executable at this time.
func (p *TxPool) StrictlyAdd(newTx *tx.Transaction) error {
return p.add(newTx, true)
}

// Remove removes tx from pool by its Hash.
func (p *TxPool) Remove(txHash thor.Bytes32, txID thor.Bytes32) bool {
if p.all.Remove(txHash) {
if p.all.RemoveByHash(txHash) {
log.Debug("tx removed", "id", txID)
return true
}
Expand Down Expand Up @@ -335,11 +343,11 @@ func (p *TxPool) wash(headBlock *block.Header) (executables tx.Transactions, rem
break
}
removed++
p.all.Remove(txObj.Hash())
p.all.RemoveByHash(txObj.Hash())
}
} else {
for _, txObj := range toRemove {
p.all.Remove(txObj.Hash())
p.all.RemoveByHash(txObj.Hash())
}
removed = len(toRemove)
}
Expand Down

0 comments on commit 419e32e

Please sign in to comment.