aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorzsfelfoldi <zsfelfoldi@gmail.com>2015-04-07 17:50:17 +0800
committerzelig <viktor.tron@gmail.com>2015-04-20 03:57:49 +0800
commitb635cad9fe127e6b0ca6d993ce9a3b6c61ce79c6 (patch)
tree81d44673f4510e13f85f9aa9d5e0009b6638eacd
parent94489b2269133c545aa3e9580737b2bd93f3ead0 (diff)
downloaddexon-b635cad9fe127e6b0ca6d993ce9a3b6c61ce79c6.tar
dexon-b635cad9fe127e6b0ca6d993ce9a3b6c61ce79c6.tar.gz
dexon-b635cad9fe127e6b0ca6d993ce9a3b6c61ce79c6.tar.bz2
dexon-b635cad9fe127e6b0ca6d993ce9a3b6c61ce79c6.tar.lz
dexon-b635cad9fe127e6b0ca6d993ce9a3b6c61ce79c6.tar.xz
dexon-b635cad9fe127e6b0ca6d993ce9a3b6c61ce79c6.tar.zst
dexon-b635cad9fe127e6b0ca6d993ce9a3b6c61ce79c6.zip
NatSpec passing end to end test
-rw-r--r--common/natspec/natspec.go11
-rw-r--r--common/natspec/natspec_e2e_test.go184
-rw-r--r--common/resolver/resolver.go30
-rw-r--r--common/resolver/resolver_test.go20
-rw-r--r--core/contracts.go42
-rw-r--r--core/genesis.go5
-rw-r--r--rpc/api.go7
7 files changed, 226 insertions, 73 deletions
diff --git a/common/natspec/natspec.go b/common/natspec/natspec.go
index b047f19d6..0253ebd81 100644
--- a/common/natspec/natspec.go
+++ b/common/natspec/natspec.go
@@ -45,18 +45,19 @@ func New(xeth *xeth.XEth, tx string, http *docserver.DocServer) (self *NatSpec,
err = fmt.Errorf("NatSpec error: contract not found")
return
}
- codeHash := xeth.CodeAt(contractAddress)
+ codehex := xeth.CodeAt(contractAddress)
+ codeHash := common.BytesToHash(crypto.Sha3(common.Hex2Bytes(codehex)))
// parse out host/domain
// set up nameresolver with natspecreg + urlhint contract addresses
res := resolver.New(
xeth,
- resolver.NameRegContractAddress,
resolver.URLHintContractAddress,
+ resolver.HashRegContractAddress,
)
- // resolve host via nameReg/UrlHint Resolver
- uri, hash, err := res.NameToUrl(codeHash)
+ // resolve host via HashReg/UrlHint Resolver
+ uri, hash, err := res.KeyToUrl(codeHash)
if err != nil {
return
}
@@ -165,6 +166,7 @@ func (self *NatSpec) Notice() (notice string, err error) {
}
copy(abiKey[:], self.data[2:10])
meth := self.makeAbi2method(abiKey)
+
if meth == nil {
err = fmt.Errorf("abi key %x does not match any method %v")
return
@@ -174,6 +176,7 @@ func (self *NatSpec) Notice() (notice string, err error) {
}
func (self *NatSpec) noticeForMethod(tx string, name, expression string) (notice string, err error) {
+
if _, err = self.jsvm.Run("var transaction = " + tx + ";"); err != nil {
return "", fmt.Errorf("natspec.js error setting transaction: %v", err)
}
diff --git a/common/natspec/natspec_e2e_test.go b/common/natspec/natspec_e2e_test.go
index 230fd5e99..43a1fdf1c 100644
--- a/common/natspec/natspec_e2e_test.go
+++ b/common/natspec/natspec_e2e_test.go
@@ -6,16 +6,19 @@ import (
"math/big"
"os"
"testing"
+ "time"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/docserver"
+ "github.com/ethereum/go-ethereum/common/natspec"
"github.com/ethereum/go-ethereum/common/resolver"
"github.com/ethereum/go-ethereum/core"
- "github.com/ethereum/go-ethereum/core/state"
+ //"github.com/ethereum/go-ethereum/core/state"
//"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth"
+ "github.com/ethereum/go-ethereum/rpc"
xe "github.com/ethereum/go-ethereum/xeth"
)
@@ -23,19 +26,81 @@ type testFrontend struct {
t *testing.T
ethereum *eth.Ethereum
xeth *xe.XEth
- stateDb *state.StateDB
+ api *rpc.EthereumApi
coinbase string
+ txc int
lastConfirm string
+ makeNatSpec bool
}
+const testNotice = "Register key `_key` <- content `_content`"
+const testExpNotice = "Register key 8.9477152217924674838424037953991966239322087453347756267410168184682657981552e+76 <- content 2.9345842665291639932787537650068479186757226656217642728276414736233000517704e+76"
+
+const testUserDoc = `
+{
+ "source": "...",
+ "language": "Solidity",
+ "languageVersion": 1,
+ "methods": {
+ "register(uint256,uint256)": {
+ "notice": "` + testNotice + `"
+ }
+ },
+ "invariants": [
+ { "notice": "" }
+ ],
+ "construction": [
+ { "notice": "" }
+ ]
+}
+`
+
+const testABI = `
+[{
+ "name": "register",
+ "constant": false,
+ "type": "function",
+ "inputs": [{
+ "name": "_key",
+ "type": "uint256"
+ }, {
+ "name": "_content",
+ "type": "uint256"
+ }],
+ "outputs": []
+}]
+`
+
+const testDocs = `
+{
+ "userdoc": ` + testUserDoc + `,
+ "abi": ` + testABI + `
+}
+`
+
func (f *testFrontend) UnlockAccount(acc []byte) bool {
f.t.Logf("Unlocking account %v\n", common.Bytes2Hex(acc))
f.ethereum.AccountManager().Unlock(acc, "password")
return true
}
-func (f *testFrontend) ConfirmTransaction(message string) bool {
- f.lastConfirm = message
+func (f *testFrontend) ConfirmTransaction(tx string) bool {
+ //f.t.Logf("ConfirmTransaction called tx = %v", tx)
+ if f.makeNatSpec {
+ ds, err := docserver.New("/tmp/")
+ if err != nil {
+ f.t.Errorf("Error creating DocServer: %v", err)
+ }
+ nat, err2 := natspec.New(f.xeth, tx, ds)
+ if err2 == nil {
+ f.lastConfirm, err = nat.Notice()
+ if err != nil {
+ f.t.Errorf("Notice error: %v", err)
+ }
+ } else {
+ f.t.Errorf("Error creating NatSpec: %v", err2)
+ }
+ }
return true
}
@@ -89,6 +154,7 @@ func testInit(t *testing.T) (self *testFrontend) {
self = &testFrontend{t: t, ethereum: ethereum}
self.xeth = xe.New(ethereum, self)
+ self.api = rpc.NewEthereumApi(self.xeth)
addr := self.xeth.Coinbase()
self.coinbase = addr
@@ -103,8 +169,6 @@ func testInit(t *testing.T) (self *testFrontend) {
}
t.Logf("Balance is %v", balance)
- self.stateDb = self.ethereum.ChainManager().State().Copy()
-
return
}
@@ -117,33 +181,87 @@ func (self *testFrontend) insertTx(addr, contract, fnsig string, args []string)
data = data + common.Bytes2Hex(common.Hex2BytesFixed(arg, 32))
}
self.t.Logf("Tx data: %v", data)
- self.xeth.Transact(addr, contract, "100000000000", "100000", "100000", data)
- cb := common.HexToAddress(addr)
+ jsontx := `
+[{
+ "from": "` + addr + `",
+ "to": "0x` + contract + `",
+ "value": "100000000000",
+ "gas": "100000",
+ "gasPrice": "100000",
+ "data": "` + data + `"
+}]
+`
+ self.txc++
+ req := &rpc.RpcRequest{
+ Jsonrpc: "2.0",
+ Method: "eth_transact",
+ Params: []byte(jsontx),
+ Id: self.txc,
+ }
+
+ txcount := self.ethereum.TxPool().Size()
+
+ var reply interface{}
+ err0 := self.api.GetRequestReply(req, &reply)
+ if err0 != nil {
+ self.t.Errorf("GetRequestReply error: %v", err0)
+ }
+
+ for txcount == self.ethereum.TxPool().Size() {
+ time.Sleep(time.Millisecond)
+ }
+
+ //self.xeth.Transact(addr, contract, "100000000000", "100000", "100000", data)
+}
+
+func (self *testFrontend) applyTxs() {
- coinbase := self.stateDb.GetStateObject(cb)
- coinbase.SetGasPool(big.NewInt(100000))
+ cb := common.HexToAddress(self.coinbase)
+ stateDb := self.ethereum.ChainManager().State().Copy()
block := self.ethereum.ChainManager().NewBlock(cb)
+ coinbase := stateDb.GetStateObject(cb)
+ coinbase.SetGasPool(big.NewInt(1000000))
txs := self.ethereum.TxPool().GetTransactions()
- tx := txs[0]
- _, gas, err := core.ApplyMessage(core.NewEnv(self.stateDb, self.ethereum.ChainManager(), tx, block), tx, coinbase)
-
- self.t.Logf("ApplyMessage: gas %v err %v", gas, err)
+ for _, tx := range txs {
+ _, gas, err := core.ApplyMessage(core.NewEnv(stateDb, self.ethereum.ChainManager(), tx, block), tx, coinbase)
+ //self.ethereum.TxPool().RemoveSet([]*types.Transaction{tx})
+ time.Sleep(time.Millisecond * 100)
+ self.t.Logf("ApplyMessage: gas %v err %v", gas, err)
+ }
self.ethereum.TxPool().RemoveSet(txs)
- self.xeth = self.xeth.WithState(self.stateDb)
+ self.xeth = self.xeth.WithState(stateDb)
}
func (self *testFrontend) registerURL(hash common.Hash, url string) {
hashHex := common.Bytes2Hex(hash[:])
urlHex := common.Bytes2Hex([]byte(url))
- self.insertTx(self.coinbase, core.ContractAddrURLhint, "register(bytes32,bytes32)", []string{hashHex, urlHex})
+ self.insertTx(self.coinbase, core.ContractAddrURLhint, "register(uint256,uint256)", []string{hashHex, urlHex})
+}
+
+func (self *testFrontend) setOwner() {
+
+ self.insertTx(self.coinbase, core.ContractAddrHashReg, "setowner()", []string{})
+
+ /*owner := self.xeth.StorageAt("0x"+core.ContractAddrHashReg, "0x0000000000000000000000000000000000000000000000000000000000000000")
+ self.t.Logf("owner = %v", owner)
+ if owner != self.coinbase {
+ self.t.Errorf("setowner() unsuccessful, owner != coinbase")
+ }*/
+}
+
+func (self *testFrontend) registerNatSpec(codehash, dochash common.Hash) {
+
+ codeHex := common.Bytes2Hex(codehash[:])
+ docHex := common.Bytes2Hex(dochash[:])
+ self.insertTx(self.coinbase, core.ContractAddrHashReg, "register(uint256,uint256)", []string{codeHex, docHex})
}
func (self *testFrontend) testResolver() *resolver.Resolver {
- return resolver.New(self.xeth, resolver.URLHintContractAddress, resolver.NameRegContractAddress)
+ return resolver.New(self.xeth, resolver.URLHintContractAddress, resolver.HashRegContractAddress)
}
func TestNatspecE2E(t *testing.T) {
@@ -151,16 +269,34 @@ func TestNatspecE2E(t *testing.T) {
tf := testInit(t)
defer tf.ethereum.Stop()
- ds, _ := docserver.New("/tmp/")
+ ioutil.WriteFile("/tmp/test.content", []byte(testDocs), os.ModePerm)
+ dochash := common.BytesToHash(crypto.Sha3([]byte(testDocs)))
- chash := common.BytesToHash(crypto.Sha3([]byte("kutyafasza")))
- tf.registerURL(chash, "file:///test.content")
- tf.registerURL(chash, "kf")
+ codehex := tf.xeth.CodeAt(core.ContractAddrHashReg)
+ codehash := common.BytesToHash(crypto.Sha3(common.Hex2Bytes(codehex)))
- url, err2 := tf.testResolver().ContentHashToUrl(chash)
+ tf.setOwner()
+ tf.registerNatSpec(codehash, dochash)
+ tf.registerURL(dochash, "file:///test.content")
+ tf.applyTxs()
- t.Logf("URL: %v err: %v", url, err2)
+ chash, err := tf.testResolver().KeyToContentHash(codehash)
+ if err != nil {
+ t.Errorf("Can't find content hash")
+ }
+ t.Logf("chash = %x err = %v", chash, err)
+ url, err2 := tf.testResolver().ContentHashToUrl(dochash)
+ if err2 != nil {
+ t.Errorf("Can't find URL hint")
+ }
+ t.Logf("url = %v err = %v", url, err2)
+
+ tf.makeNatSpec = true
+ tf.registerNatSpec(codehash, dochash) // call again just to get a confirm message
+ t.Logf("Confirm message: %v\n", tf.lastConfirm)
- ds.GetAuthContent(url, chash)
+ if tf.lastConfirm != testExpNotice {
+ t.Errorf("Wrong confirm message, expected '%v', got '%v'", testExpNotice, tf.lastConfirm)
+ }
}
diff --git a/common/resolver/resolver.go b/common/resolver/resolver.go
index 060e9dbb5..9c71ac85f 100644
--- a/common/resolver/resolver.go
+++ b/common/resolver/resolver.go
@@ -11,7 +11,7 @@ import (
/*
Resolver implements the Ethereum DNS mapping
-NameReg : Domain Name (or Code hash of Contract) -> Content Hash
+HashReg : Key Hash (hash of domain name or contract code) -> Content Hash
UrlHint : Content Hash -> Url Hint
The resolver is meant to be called by the roundtripper transport implementation
@@ -19,13 +19,13 @@ of a url scheme
*/
const (
URLHintContractAddress = core.ContractAddrURLhint
- NameRegContractAddress = core.ContractAddrHashReg
+ HashRegContractAddress = core.ContractAddrHashReg
)
type Resolver struct {
backend Backend
urlHintContractAddress string
- nameRegContractAddress string
+ hashRegContractAddress string
}
type Backend interface {
@@ -36,17 +36,23 @@ func New(eth Backend, uhca, nrca string) *Resolver {
return &Resolver{eth, uhca, nrca}
}
-func (self *Resolver) NameToContentHash(name string) (chash common.Hash, err error) {
- // look up in nameReg
- key := storageAddress(1, common.Hex2BytesFixed(name, 32))
- hash := self.backend.StorageAt("0x"+self.nameRegContractAddress, key)
- copy(chash[:], common.Hex2Bytes(hash))
+func (self *Resolver) KeyToContentHash(khash common.Hash) (chash common.Hash, err error) {
+ // look up in hashReg
+ key := storageAddress(1, khash[:])
+ hash := self.backend.StorageAt("0x"+self.hashRegContractAddress, key)
+
+ if hash == "0x0" || len(hash) < 3 {
+ err = fmt.Errorf("GetHashReg: content hash not found")
+ return
+ }
+
+ copy(chash[:], common.Hex2BytesFixed(hash[2:], 32))
return
}
func (self *Resolver) ContentHashToUrl(chash common.Hash) (uri string, err error) {
- // look up in nameReg
- key := storageAddress(2, chash[:])
+ // look up in URL reg
+ key := storageAddress(1, chash[:])
hex := self.backend.StorageAt("0x"+self.urlHintContractAddress, key)
uri = string(common.Hex2Bytes(hex[2:]))
l := len(uri)
@@ -61,9 +67,9 @@ func (self *Resolver) ContentHashToUrl(chash common.Hash) (uri string, err error
return
}
-func (self *Resolver) NameToUrl(name string) (uri string, hash common.Hash, err error) {
+func (self *Resolver) KeyToUrl(key common.Hash) (uri string, hash common.Hash, err error) {
// look up in urlHint
- hash, err = self.NameToContentHash(name)
+ hash, err = self.KeyToContentHash(key)
if err != nil {
return
}
diff --git a/common/resolver/resolver_test.go b/common/resolver/resolver_test.go
index 5652213f6..17a1c4f94 100644
--- a/common/resolver/resolver_test.go
+++ b/common/resolver/resolver_test.go
@@ -23,12 +23,12 @@ func NewTestBackend() *testBackend {
self := &testBackend{}
self.contracts = make(map[string](map[string]string))
- self.contracts[NameRegContractAddress] = make(map[string]string)
- key := storageAddress(0, common.Hex2Bytes(codehash))
- self.contracts[NameRegContractAddress][key] = hash
+ self.contracts[HashRegContractAddress] = make(map[string]string)
+ key := storageAddress(1, common.Hex2Bytes(codehash))
+ self.contracts[HashRegContractAddress][key] = hash
self.contracts[URLHintContractAddress] = make(map[string]string)
- key = storageAddress(0, common.Hex2Bytes(hash))
+ key = storageAddress(1, common.Hex2Bytes(hash))
self.contracts[URLHintContractAddress][key] = url
return self
@@ -43,10 +43,12 @@ func (self *testBackend) StorageAt(ca, sa string) (res string) {
return
}
-func TestNameToContentHash(t *testing.T) {
+func TestKeyToContentHash(t *testing.T) {
b := NewTestBackend()
- res := New(b, URLHintContractAddress, NameRegContractAddress)
- got, err := res.NameToContentHash(codehash)
+ res := New(b, URLHintContractAddress, HashRegContractAddress)
+ chash := common.Hash{}
+ copy(chash[:], common.Hex2BytesFixed(codehash, 32))
+ got, err := res.KeyToContentHash(chash)
if err != nil {
t.Errorf("expected no error, got %v", err)
} else {
@@ -58,7 +60,7 @@ func TestNameToContentHash(t *testing.T) {
func TestContentHashToUrl(t *testing.T) {
b := NewTestBackend()
- res := New(b, URLHintContractAddress, NameRegContractAddress)
+ res := New(b, URLHintContractAddress, HashRegContractAddress)
chash := common.Hash{}
copy(chash[:], common.Hex2Bytes(hash))
got, err := res.ContentHashToUrl(chash)
@@ -71,5 +73,5 @@ func TestContentHashToUrl(t *testing.T) {
}
}
-func TestNameToUrl(t *testing.T) {
+func TestKeyToUrl(t *testing.T) {
}
diff --git a/core/contracts.go b/core/contracts.go
index ffd98da98..9d63e4043 100644
--- a/core/contracts.go
+++ b/core/contracts.go
@@ -2,33 +2,43 @@ package core
const ( // built-in contracts address and code
ContractAddrURLhint = "0000000000000000000000000000000000000008"
- //ContractCodeURLhint = "0x60bd80600c6000396000f30060003560e060020a900480632f92673214601557005b60216004356024356027565b60006000f35b6000805490816001019055506001600083815260200190815260200160002054600160a060020a0316600014806081575033600160a060020a03166001600084815260200190815260200160002054600160a060020a0316145b60885760b9565b3360016000848152602001908152602001600020819055508060026000848152602001908152602001600020819055505b505056"
- ContractCodeURLhint = "0x60003560e060020a900480632f92673214601557005b60216004356024356027565b60006000f35b6000805490816001019055506001600083815260200190815260200160002054600160a060020a0316600014806081575033600160a060020a03166001600084815260200190815260200160002054600160a060020a0316145b60885760b9565b3360016000848152602001908152602001600020819055508060026000848152602001908152602001600020819055505b505056"
- //"0x60b180600c6000396000f30060003560e060020a900480632f92673214601557005b60216004356024356027565b60006000f35b6000600083815260200190815260200160002054600160a060020a0316600014806075575033600160a060020a03166000600084815260200190815260200160002054600160a060020a0316145b607c5760ad565b3360006000848152602001908152602001600020819055508060016000848152602001908152602001600020819055505b505056"
+ //ContractCodeURLhint = "0x60b180600c6000396000f30060003560e060020a90048063d66d6c1014601557005b60216004356024356027565b60006000f35b6000600083815260200190815260200160002054600160a060020a0316600014806075575033600160a060020a03166000600084815260200190815260200160002054600160a060020a0316145b607c5760ad565b3360006000848152602001908152602001600020819055508060016000848152602001908152602001600020819055505b505056"
+ ContractCodeURLhint = "0x60003560e060020a90048063d66d6c1014601557005b60216004356024356027565b60006000f35b6000600083815260200190815260200160002054600160a060020a0316600014806075575033600160a060020a03166000600084815260200190815260200160002054600160a060020a0316145b607c5760ad565b3360006000848152602001908152602001600020819055508060016000848152602001908152602001600020819055505b505056"
/*
contract URLhint {
- function register(bytes32 _hash, bytes32 _url) {
- testcnt++;
+ function register(uint256 _hash, uint256 _url) {
if (owner[_hash] == 0 || owner[_hash] == msg.sender) {
owner[_hash] = msg.sender;
url[_hash] = _url;
}
}
- uint32 testcnt;
- mapping (bytes32 => address) owner;
- mapping (bytes32 => bytes32) url;
+ mapping (uint256 => address) owner;
+ mapping (uint256 => uint256) url;
}
*/
ContractAddrHashReg = "0000000000000000000000000000000000000009"
- ContractCodeHashReg = "0x3360008190555060628060136000396000f30060003560e060020a900480632f92673214601557005b60216004356024356027565b60006000f35b600054600160a060020a031633600160a060020a031614604557605e565b8060016000848152602001908152602001600020819055505b505056"
+ ContractCodeHashReg = "0x60003560e060020a9004806331e12c2014601f578063d66d6c1014602b57005b6025603d565b60006000f35b6037600435602435605d565b60006000f35b600054600160a060020a0316600014605357605b565b336000819055505b565b600054600160a060020a031633600160a060020a031614607b576094565b8060016000848152602001908152602001600020819055505b505056"
+ //ContractCodeHashReg = "0x609880600c6000396000f30060003560e060020a9004806331e12c2014601f578063d66d6c1014602b57005b6025603d565b60006000f35b6037600435602435605d565b60006000f35b600054600160a060020a0316600014605357605b565b336000819055505b565b600054600160a060020a031633600160a060020a031614607b576094565b8060016000848152602001908152602001600020819055505b505056"
/*
- import "owned";
- contract HashReg is owned {
- function register(bytes32 _code, bytes32 _abi) onlyowner {
- abis[_code] = _abi;
- }
- mapping (bytes32 => bytes32) abis;
- }
+ contract HashReg {
+ function setowner() {
+ if (owner == 0) {
+ owner = msg.sender;
+ }
+ }
+ function register(uint256 _key, uint256 _content) {
+ if (msg.sender == owner) {
+ content[_key] = _content;
+ }
+ }
+ address owner;
+ mapping (uint256 => uint256) content;
+ }
*/
+
+ BuiltInContracts = `
+ "` + ContractAddrURLhint + `": {"balance": "0", "code": "` + ContractCodeURLhint + `" },
+ "` + ContractAddrHashReg + `": {"balance": "0", "code": "` + ContractCodeHashReg + `" },
+ `
)
diff --git a/core/genesis.go b/core/genesis.go
index 14ac2ec0f..c475ca637 100644
--- a/core/genesis.go
+++ b/core/genesis.go
@@ -58,13 +58,12 @@ func GenesisBlock(db common.Database) *types.Block {
const (
TestAccount = "e273f01c99144c438695e10f24926dc1f9fbf62d"
- TestBalance = "1000000000000000000"
+ TestBalance = "1000000000000"
)
var genesisData = []byte(`{
"` + TestAccount + `": {"balance": "` + TestBalance + `"},
- "` + ContractAddrURLhint + `": {"balance": "` + TestBalance + `", "code": "` + ContractCodeURLhint + `" },
- "` + ContractAddrHashReg + `": {"balance": "` + TestBalance + `", "code": "` + ContractCodeHashReg + `" },
+ ` + BuiltInContracts + `
"0000000000000000000000000000000000000001": {"balance": "1"},
"0000000000000000000000000000000000000002": {"balance": "1"},
"0000000000000000000000000000000000000003": {"balance": "1"},
diff --git a/rpc/api.go b/rpc/api.go
index c02b03fb6..66283752b 100644
--- a/rpc/api.go
+++ b/rpc/api.go
@@ -168,11 +168,8 @@ func (api *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) err
}
// call ConfirmTransaction first
- var obj []json.RawMessage
- if err := json.Unmarshal(req.Params, &obj); err != nil {
- return NewDecodeParamError(err.Error())
- }
- if !api.xeth().ConfirmTransaction(string(obj[0])) {
+ tx, _ := json.Marshal(req)
+ if !api.xeth().ConfirmTransaction(string(tx)) {
return fmt.Errorf("Transaction not confirmed")
}