Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions blockproducer/interfaces/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,23 @@ const (
TransactionStateNotFound
)

func (s TransactionState) String() string {
switch s {
case TransactionStatePending:
return "Pending"
case TransactionStatePacked:
return "Packed"
case TransactionStateConfirmed:
return "Confirmed"
case TransactionStateExpired:
return "Expired"
case TransactionStateNotFound:
return "Not Found"
default:
return "Unknown"
}
}

// Transaction is the interface implemented by an object that can be verified and processed by
// block producers.
type Transaction interface {
Expand Down
55 changes: 53 additions & 2 deletions client/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import (
"github.com/CovenantSQL/CovenantSQL/conf"
"github.com/CovenantSQL/CovenantSQL/crypto"
"github.com/CovenantSQL/CovenantSQL/crypto/asymmetric"
"github.com/CovenantSQL/CovenantSQL/crypto/hash"
"github.com/CovenantSQL/CovenantSQL/crypto/kms"
"github.com/CovenantSQL/CovenantSQL/proto"
"github.com/CovenantSQL/CovenantSQL/route"
Expand Down Expand Up @@ -260,7 +261,7 @@ func GetTokenBalance(tt types.TokenType) (balance uint64, err error) {

// UpdatePermission sends UpdatePermission transaction to chain.
func UpdatePermission(targetUser proto.AccountAddress,
targetChain proto.AccountAddress, perm types.UserPermission) (err error) {
targetChain proto.AccountAddress, perm types.UserPermission) (txHash hash.Hash, err error) {
if atomic.LoadUint32(&driverInitialized) == 0 {
err = ErrNotInitialized
return
Expand Down Expand Up @@ -307,11 +308,14 @@ func UpdatePermission(targetUser proto.AccountAddress,
return
}

txHash = up.Hash()
return
}

// TransferToken send Transfer transaction to chain.
func TransferToken(targetUser proto.AccountAddress, amount uint64, tokenType types.TokenType) (err error) {
func TransferToken(targetUser proto.AccountAddress, amount uint64, tokenType types.TokenType) (
txHash hash.Hash, err error,
) {
if atomic.LoadUint32(&driverInitialized) == 0 {
err = ErrNotInitialized
return
Expand Down Expand Up @@ -359,6 +363,53 @@ func TransferToken(targetUser proto.AccountAddress, amount uint64, tokenType typ
return
}

txHash = tran.Hash()
return
}

// WaitTxConfirmation waits for the transaction with target hash txHash to be confirmed. It also
// returns if any error occurs or a final state is returned from BP.
func WaitTxConfirmation(
ctx context.Context, txHash hash.Hash) (state interfaces.TransactionState, err error,
) {
var (
ticker = time.NewTicker(1 * time.Second)
method = route.MCCQueryTxState
req = &types.QueryTxStateReq{Hash: txHash}
resp = &types.QueryTxStateResp{}
)
defer ticker.Stop()
for {
if err = requestBP(method, req, resp); err != nil {
err = errors.Wrapf(err, "failed to call %s", method)
return
}

state = resp.State
log.WithFields(log.Fields{
"tx_hash": txHash,
"tx_state": state,
}).Debug("waiting for tx confirmation")

switch state {
case interfaces.TransactionStatePending:
case interfaces.TransactionStatePacked:
case interfaces.TransactionStateConfirmed,
interfaces.TransactionStateExpired,
interfaces.TransactionStateNotFound:
return
default:
err = errors.Errorf("unknown transaction state %d", state)
return
}

select {
case <-ticker.C:
case <-ctx.Done():
err = ctx.Err()
return
}
}
return
}

Expand Down
48 changes: 44 additions & 4 deletions cmd/cql-utils/rpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"fmt"
"reflect"
"strings"
"time"

bp "github.com/CovenantSQL/CovenantSQL/blockproducer"
pi "github.com/CovenantSQL/CovenantSQL/blockproducer/interfaces"
Expand All @@ -46,9 +47,10 @@ var (
route.SQLChainRPCName: &sqlchain.MuxService{},
route.BlockProducerRPCName: &bp.ChainRPCService{},
}
rpcName string
rpcEndpoint string
rpcReq string
rpcName string
rpcEndpoint string
rpcReq string
rpcTxWaitConfirm bool
)

type canSign interface {
Expand All @@ -59,6 +61,7 @@ func init() {
flag.StringVar(&rpcName, "rpc", "", "rpc name to do test call")
flag.StringVar(&rpcEndpoint, "rpc-endpoint", "", "rpc endpoint to do test call")
flag.StringVar(&rpcReq, "rpc-req", "", "rpc request to do test call, in json format")
flag.BoolVar(&rpcTxWaitConfirm, "rpc-tx-wait-confirm", false, "wait for transaction confirmation")
}

func runRPC() {
Expand Down Expand Up @@ -95,9 +98,10 @@ func runRPC() {
}

// fill nonce if this is a AddTx request
var tx pi.Transaction
if rpcName == route.MCCAddTx.String() {
if addTxReqType, ok := req.(*types.AddTxReq); ok {
var tx = addTxReqType.Tx
tx = addTxReqType.Tx
for {
if txWrapper, ok := tx.(*pi.TransactionWrapper); ok {
tx = txWrapper.Unwrap()
Expand Down Expand Up @@ -138,6 +142,42 @@ func runRPC() {
// print the response
log.Info("got response")
spewCfg.Dump(resp)

if rpcName == route.MCCAddTx.String() && rpcTxWaitConfirm {
log.Info("waiting for transaction confirmation...")
var (
err error
ticker = time.NewTicker(1 * time.Second)
req = &types.QueryTxStateReq{Hash: tx.Hash()}
resp = &types.QueryTxStateResp{}
)
defer ticker.Stop()
for {
if err = rpc.NewCaller().CallNode(
proto.NodeID(rpcEndpoint),
route.MCCQueryTxState.String(),
req, resp,
); err != nil {
log.Fatalf("query transaction state failed: %v", err)
}
switch resp.State {
case pi.TransactionStatePending:
fmt.Print(".")
case pi.TransactionStatePacked:
fmt.Print("+")
case pi.TransactionStateConfirmed:
fmt.Print("✔\n")
return
case pi.TransactionStateExpired, pi.TransactionStateNotFound:
fmt.Print("✘\n")
log.Fatalf("bad transaction state: %s", resp.State)
default:
fmt.Print("✘\n")
log.Fatal("unknown transaction state")
}
<-ticker.C
}
}
}

func checkAndSign(req interface{}) (err error) {
Expand Down
39 changes: 37 additions & 2 deletions cmd/cql/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,13 @@ import (
"runtime"
"strconv"
"strings"
"time"

pi "github.com/CovenantSQL/CovenantSQL/blockproducer/interfaces"
"github.com/CovenantSQL/CovenantSQL/client"
"github.com/CovenantSQL/CovenantSQL/conf"
"github.com/CovenantSQL/CovenantSQL/crypto/asymmetric"
"github.com/CovenantSQL/CovenantSQL/crypto/hash"
"github.com/CovenantSQL/CovenantSQL/proto"
"github.com/CovenantSQL/CovenantSQL/types"
"github.com/CovenantSQL/CovenantSQL/utils/log"
Expand Down Expand Up @@ -67,6 +71,9 @@ var (
transferToken string // transfer token to target account
getBalance bool // get balance of current account
getBalanceWithTokenName string // get specific token's balance of current account
waitTxConfirmation bool // wait for transaction confirmation before exiting

waitTxConfirmationMaxDuration time.Duration
)

type userPermission struct {
Expand Down Expand Up @@ -216,6 +223,7 @@ func init() {
flag.StringVar(&transferToken, "transfer", "", "transfer token to target account")
flag.BoolVar(&getBalance, "get-balance", false, "get balance of current account")
flag.StringVar(&getBalanceWithTokenName, "token-balance", "", "get specific token's balance of current account, e.g. Particle, Wave, and etc.")
flag.BoolVar(&waitTxConfirmation, "wait-tx-confirm", false, "wait for transaction confirmation")
}

func main() {
Expand All @@ -235,6 +243,11 @@ func main() {
return
}

// TODO(leventeliu): discover more specific confirmation duration from config. We don't have
// enough informations from config to do that currently, so just use a fixed and long enough
// duration.
waitTxConfirmationMaxDuration = 10 * conf.GConf.BPPeriod

if getBalance {
var stableCoinBalance, covenantCoinBalance uint64

Expand Down Expand Up @@ -342,14 +355,18 @@ func main() {
return
}

err := client.UpdatePermission(perm.TargetUser, perm.TargetChain, p)
txHash, err := client.UpdatePermission(perm.TargetUser, perm.TargetChain, p)

if err != nil {
log.WithError(err).Error("update permission failed")
os.Exit(-1)
return
}

if waitTxConfirmation {
wait(txHash)
}

log.Info("succeed in sending transaction to CovenantSQL")
return
}
Expand Down Expand Up @@ -391,13 +408,18 @@ func main() {
return
}

err = client.TransferToken(tran.TargetUser, amount, unit)
var txHash hash.Hash
txHash, err = client.TransferToken(tran.TargetUser, amount, unit)
if err != nil {
log.WithError(err).Error("transfer token failed")
os.Exit(-1)
return
}

if waitTxConfirmation {
wait(txHash)
}

log.Info("succeed in sending transaction to CovenantSQL")
return
}
Expand Down Expand Up @@ -445,6 +467,19 @@ func main() {
}
}

func wait(txHash hash.Hash) {
var ctx, cancel = context.WithTimeout(context.Background(), waitTxConfirmationMaxDuration)
defer cancel()
var state, err = client.WaitTxConfirmation(ctx, txHash)
log.WithFields(log.Fields{
"tx_hash": txHash,
"tx_state": state,
}).WithError(err).Info("wait transaction confirmation")
if err != nil || state != pi.TransactionStateConfirmed {
os.Exit(1)
}
}

func run(u *user.User) (err error) {
// get working directory
wd, err := os.Getwd()
Expand Down
6 changes: 5 additions & 1 deletion route/acl.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,12 @@ const (
MCCNextAccountNonce
// MCCAddTx is used by block producer main chain to upload transaction
MCCAddTx
// MCCQuerySQLChainProfile is used by nodes to to query SQLChainProfile.
// MCCQuerySQLChainProfile is used by nodes to query SQLChainProfile.
MCCQuerySQLChainProfile
// MCCQueryAccountTokenBalance is used by block producer to provide account token balance
MCCQueryAccountTokenBalance
// MCCQueryTxState is used by client to query transaction state.
MCCQueryTxState
// DHTRPCName defines the block producer dh-rpc service name
DHTRPCName = "DHT"
// BlockProducerRPCName defines main chain rpc name
Expand Down Expand Up @@ -186,6 +188,8 @@ func (s RemoteFunc) String() string {
return "MCC.QuerySQLChainProfile"
case MCCQueryAccountTokenBalance:
return "MCC.QueryAccountTokenBalance"
case MCCQueryTxState:
return "MCC.QueryTxState"
}
return "Unknown"
}
Expand Down