github.com/jeffallen/go-ethereum@v1.1.4-0.20150910155051-571d3236c49c/common/natspec/natspec_e2e_test.go (about)

     1  // Copyright 2015 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package natspec
    18  
    19  import (
    20  	"fmt"
    21  	"io/ioutil"
    22  	"math/big"
    23  	"os"
    24  	"runtime"
    25  	"strings"
    26  	"testing"
    27  	"time"
    28  
    29  	"github.com/ethereum/go-ethereum/accounts"
    30  	"github.com/ethereum/go-ethereum/common"
    31  	"github.com/ethereum/go-ethereum/common/docserver"
    32  	"github.com/ethereum/go-ethereum/common/registrar"
    33  	"github.com/ethereum/go-ethereum/core"
    34  	"github.com/ethereum/go-ethereum/crypto"
    35  	"github.com/ethereum/go-ethereum/eth"
    36  	"github.com/ethereum/go-ethereum/ethdb"
    37  	xe "github.com/ethereum/go-ethereum/xeth"
    38  )
    39  
    40  const (
    41  	testBalance = "10000000000000000000"
    42  
    43  	testFileName = "long_file_name_for_testing_registration_of_URLs_longer_than_32_bytes.content"
    44  
    45  	testNotice = "Register key `utils.toHex(_key)` <- content `utils.toHex(_content)`"
    46  
    47  	testExpNotice = "Register key 0xadd1a7d961cff0242089674ec2ef6fca671ab15e1fe80e38859fc815b98d88ab <- content 0xb3a2dea218de5d8bbe6c4645aadbf67b5ab00ecb1a9ec95dbdad6a0eed3e41a7"
    48  
    49  	testExpNotice2 = `About to submit transaction (NatSpec notice error: abi key does not match any method): {"params":[{"to":"%s","data": "0x31e12c20"}]}`
    50  
    51  	testExpNotice3 = `About to submit transaction (no NatSpec info found for contract: content hash not found for '0x1392c62d05b2d149e22a339c531157ae06b44d39a674cce500064b12b9aeb019'): {"params":[{"to":"%s","data": "0x300a3bbfb3a2dea218de5d8bbe6c4645aadbf67b5ab00ecb1a9ec95dbdad6a0eed3e41a7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000066696c653a2f2f2f746573742e636f6e74656e74"}]}`
    52  )
    53  
    54  const (
    55  	testUserDoc = `
    56  {
    57    "methods": {
    58      "register(uint256,uint256)": {
    59        "notice":  "` + testNotice + `"
    60      }
    61    },
    62    "invariants": [
    63      { "notice": "" }
    64    ],
    65    "construction": [
    66      { "notice": "" }
    67    ]
    68  }
    69  `
    70  	testAbiDefinition = `
    71  [{
    72    "name": "register",
    73    "constant": false,
    74    "type": "function",
    75    "inputs": [{
    76      "name": "_key",
    77      "type": "uint256"
    78    }, {
    79      "name": "_content",
    80      "type": "uint256"
    81    }],
    82    "outputs": []
    83  }]
    84  `
    85  
    86  	testContractInfo = `
    87  {
    88  	"userDoc": ` + testUserDoc + `,
    89  	"abiDefinition": ` + testAbiDefinition + `
    90  }
    91  `
    92  )
    93  
    94  type testFrontend struct {
    95  	t           *testing.T
    96  	ethereum    *eth.Ethereum
    97  	xeth        *xe.XEth
    98  	wait        chan *big.Int
    99  	lastConfirm string
   100  	wantNatSpec bool
   101  }
   102  
   103  func (self *testFrontend) UnlockAccount(acc []byte) bool {
   104  	self.ethereum.AccountManager().Unlock(common.BytesToAddress(acc), "password")
   105  	return true
   106  }
   107  
   108  func (self *testFrontend) ConfirmTransaction(tx string) bool {
   109  	if self.wantNatSpec {
   110  		ds := docserver.New("/tmp/")
   111  		self.lastConfirm = GetNotice(self.xeth, tx, ds)
   112  	}
   113  	return true
   114  }
   115  
   116  func testEth(t *testing.T) (ethereum *eth.Ethereum, err error) {
   117  
   118  	os.RemoveAll("/tmp/eth-natspec/")
   119  
   120  	err = os.MkdirAll("/tmp/eth-natspec/keystore", os.ModePerm)
   121  	if err != nil {
   122  		panic(err)
   123  	}
   124  
   125  	// create a testAddress
   126  	ks := crypto.NewKeyStorePassphrase("/tmp/eth-natspec/keystore")
   127  	am := accounts.NewManager(ks)
   128  	testAccount, err := am.NewAccount("password")
   129  	if err != nil {
   130  		panic(err)
   131  	}
   132  
   133  	testAddress := strings.TrimPrefix(testAccount.Address.Hex(), "0x")
   134  
   135  	db, _ := ethdb.NewMemDatabase()
   136  	// set up mock genesis with balance on the testAddress
   137  	core.WriteGenesisBlockForTesting(db, common.HexToAddress(testAddress), common.String2Big(testBalance))
   138  
   139  	// only use minimalistic stack with no networking
   140  	ethereum, err = eth.New(&eth.Config{
   141  		DataDir:        "/tmp/eth-natspec",
   142  		AccountManager: am,
   143  		MaxPeers:       0,
   144  		PowTest:        true,
   145  		Etherbase:      common.HexToAddress(testAddress),
   146  		NewDB:          func(path string) (common.Database, error) { return db, nil },
   147  	})
   148  
   149  	if err != nil {
   150  		panic(err)
   151  	}
   152  
   153  	return
   154  }
   155  
   156  func testInit(t *testing.T) (self *testFrontend) {
   157  	// initialise and start minimal ethereum stack
   158  	ethereum, err := testEth(t)
   159  	if err != nil {
   160  		t.Errorf("error creating ethereum: %v", err)
   161  		return
   162  	}
   163  	err = ethereum.Start()
   164  	if err != nil {
   165  		t.Errorf("error starting ethereum: %v", err)
   166  		return
   167  	}
   168  
   169  	// mock frontend
   170  	self = &testFrontend{t: t, ethereum: ethereum}
   171  	self.xeth = xe.New(ethereum, self)
   172  	self.wait = self.xeth.UpdateState()
   173  	addr, _ := self.ethereum.Etherbase()
   174  
   175  	// initialise the registry contracts
   176  	reg := registrar.New(self.xeth)
   177  	var registrarTxhash, hashRegTxhash, urlHintTxhash string
   178  	registrarTxhash, err = reg.SetGlobalRegistrar("", addr)
   179  	if err != nil {
   180  		t.Errorf("error creating GlobalRegistrar: %v", err)
   181  	}
   182  
   183  	hashRegTxhash, err = reg.SetHashReg("", addr)
   184  	if err != nil {
   185  		t.Errorf("error creating HashReg: %v", err)
   186  	}
   187  	urlHintTxhash, err = reg.SetUrlHint("", addr)
   188  	if err != nil {
   189  		t.Errorf("error creating UrlHint: %v", err)
   190  	}
   191  	if !processTxs(self, t, 3) {
   192  		t.Errorf("error mining txs")
   193  	}
   194  	_ = registrarTxhash
   195  	_ = hashRegTxhash
   196  	_ = urlHintTxhash
   197  
   198  	/* TODO:
   199  	* lookup receipt and contract addresses by tx hash
   200  	* name registration for HashReg and UrlHint addresses
   201  	* mine those transactions
   202  	* then set once more SetHashReg SetUrlHint
   203  	 */
   204  
   205  	return
   206  
   207  }
   208  
   209  // end to end test
   210  func TestNatspecE2E(t *testing.T) {
   211  	t.Skip()
   212  
   213  	tf := testInit(t)
   214  	defer tf.ethereum.Stop()
   215  	addr, _ := tf.ethereum.Etherbase()
   216  
   217  	// create a contractInfo file (mock cloud-deployed contract metadocs)
   218  	// incidentally this is the info for the registry contract itself
   219  	ioutil.WriteFile("/tmp/"+testFileName, []byte(testContractInfo), os.ModePerm)
   220  	dochash := crypto.Sha3Hash([]byte(testContractInfo))
   221  
   222  	// take the codehash for the contract we wanna test
   223  	codeb := tf.xeth.CodeAtBytes(registrar.HashRegAddr)
   224  	codehash := crypto.Sha3Hash(codeb)
   225  
   226  	// use resolver to register codehash->dochash->url
   227  	// test if globalregistry works
   228  	// registrar.HashRefAddr = "0x0"
   229  	// registrar.UrlHintAddr = "0x0"
   230  	reg := registrar.New(tf.xeth)
   231  	_, err := reg.SetHashToHash(addr, codehash, dochash)
   232  	if err != nil {
   233  		t.Errorf("error registering: %v", err)
   234  	}
   235  	_, err = reg.SetUrlToHash(addr, dochash, "file:///"+testFileName)
   236  	if err != nil {
   237  		t.Errorf("error registering: %v", err)
   238  	}
   239  	if !processTxs(tf, t, 5) {
   240  		return
   241  	}
   242  
   243  	// NatSpec info for register method of HashReg contract installed
   244  	// now using the same transactions to check confirm messages
   245  
   246  	tf.wantNatSpec = true // this is set so now the backend uses natspec confirmation
   247  	_, err = reg.SetHashToHash(addr, codehash, dochash)
   248  	if err != nil {
   249  		t.Errorf("error calling contract registry: %v", err)
   250  	}
   251  
   252  	fmt.Printf("GlobalRegistrar: %v, HashReg: %v, UrlHint: %v\n", registrar.GlobalRegistrarAddr, registrar.HashRegAddr, registrar.UrlHintAddr)
   253  	if tf.lastConfirm != testExpNotice {
   254  		t.Errorf("Wrong confirm message. expected\n'%v', got\n'%v'", testExpNotice, tf.lastConfirm)
   255  	}
   256  
   257  	// test unknown method
   258  	exp := fmt.Sprintf(testExpNotice2, registrar.HashRegAddr)
   259  	_, err = reg.SetOwner(addr)
   260  	if err != nil {
   261  		t.Errorf("error setting owner: %v", err)
   262  	}
   263  
   264  	if tf.lastConfirm != exp {
   265  		t.Errorf("Wrong confirm message, expected\n'%v', got\n'%v'", exp, tf.lastConfirm)
   266  	}
   267  
   268  	// test unknown contract
   269  	exp = fmt.Sprintf(testExpNotice3, registrar.UrlHintAddr)
   270  
   271  	_, err = reg.SetUrlToHash(addr, dochash, "file:///test.content")
   272  	if err != nil {
   273  		t.Errorf("error registering: %v", err)
   274  	}
   275  
   276  	if tf.lastConfirm != exp {
   277  		t.Errorf("Wrong confirm message, expected '%v', got '%v'", exp, tf.lastConfirm)
   278  	}
   279  
   280  }
   281  
   282  func pendingTransactions(repl *testFrontend, t *testing.T) (txc int64, err error) {
   283  	txs := repl.ethereum.TxPool().GetTransactions()
   284  	return int64(len(txs)), nil
   285  }
   286  
   287  func processTxs(repl *testFrontend, t *testing.T, expTxc int) bool {
   288  	var txc int64
   289  	var err error
   290  	for i := 0; i < 50; i++ {
   291  		txc, err = pendingTransactions(repl, t)
   292  		if err != nil {
   293  			t.Errorf("unexpected error checking pending transactions: %v", err)
   294  			return false
   295  		}
   296  		if expTxc < int(txc) {
   297  			t.Errorf("too many pending transactions: expected %v, got %v", expTxc, txc)
   298  			return false
   299  		} else if expTxc == int(txc) {
   300  			break
   301  		}
   302  		time.Sleep(100 * time.Millisecond)
   303  	}
   304  	if int(txc) != expTxc {
   305  		t.Errorf("incorrect number of pending transactions, expected %v, got %v", expTxc, txc)
   306  		return false
   307  	}
   308  
   309  	err = repl.ethereum.StartMining(runtime.NumCPU())
   310  	if err != nil {
   311  		t.Errorf("unexpected error mining: %v", err)
   312  		return false
   313  	}
   314  	defer repl.ethereum.StopMining()
   315  
   316  	timer := time.NewTimer(100 * time.Second)
   317  	height := new(big.Int).Add(repl.xeth.CurrentBlock().Number(), big.NewInt(1))
   318  	repl.wait <- height
   319  	select {
   320  	case <-timer.C:
   321  		// if times out make sure the xeth loop does not block
   322  		go func() {
   323  			select {
   324  			case repl.wait <- nil:
   325  			case <-repl.wait:
   326  			}
   327  		}()
   328  	case <-repl.wait:
   329  	}
   330  	txc, err = pendingTransactions(repl, t)
   331  	if err != nil {
   332  		t.Errorf("unexpected error checking pending transactions: %v", err)
   333  		return false
   334  	}
   335  	if txc != 0 {
   336  		t.Errorf("%d trasactions were not mined", txc)
   337  		return false
   338  	}
   339  	return true
   340  }