genapi.go raw

   1  package main
   2  
   3  import (
   4  	"os"
   5  	"sort"
   6  	"text/template"
   7  
   8  	"github.com/p9c/p9/pkg/log"
   9  )
  10  
  11  type handler struct {
  12  	Method, Handler, HandlerWithChain, Cmd, ResType string
  13  }
  14  
  15  type handlersT []handler
  16  
  17  func (h handlersT) Len() int {
  18  	return len(h)
  19  }
  20  
  21  func (h handlersT) Less(i, j int) bool {
  22  	return h[i].Method < h[j].Method
  23  }
  24  
  25  func (h handlersT) Swap(i, j int) {
  26  	h[i], h[j] = h[j], h[i]
  27  }
  28  
  29  func main() {
  30  	log.SetLogLevel("trace")
  31  	if fd, e := os.Create("rpchandlers.go"); E.Chk(e) {
  32  	} else {
  33  		defer fd.Close()
  34  		t := template.Must(template.New("noderpc").Parse(NodeRPCHandlerTpl))
  35  		sort.Sort(handlers)
  36  		if e = t.Execute(fd, handlers); E.Chk(e) {
  37  		}
  38  	}
  39  }
  40  
  41  const (
  42  	RPCMapName = "RPCHandlers"
  43  	Worker     = "CAPI"
  44  )
  45  
  46  var NodeRPCHandlerTpl = `// generated by go run ./genapi/.; DO NOT EDIT
  47  //
  48  `+`//go:generate go run ./genapi/.
  49  
  50  package wallet
  51  
  52  import (
  53  	"io"
  54  	"net/rpc"
  55  	"time"
  56  
  57  	"github.com/p9c/p9/pkg/qu"
  58  
  59  	"github.com/p9c/p9/pkg/btcjson"
  60  	"github.com/p9c/p9/pkg/chainclient"
  61  )
  62  
  63  // API stores the channel, parameters and result values from calls via the channel
  64  type API struct {
  65  	Ch     interface{}
  66  	Params interface{}
  67  	Result interface{}
  68  }
  69  
  70  // CAPI is the central structure for configuration and access to a net/rpc API access endpoint for this RPC API
  71  type CAPI struct {
  72  	Timeout time.Duration
  73  	quit    qu.C
  74  }
  75  
  76  // NewCAPI returns a new CAPI 
  77  func NewCAPI(quit qu.C, timeout ...time.Duration) (c *CAPI) {
  78  	c = &CAPI{quit: quit}
  79  	if len(timeout)>0 {
  80  		c.Timeout = timeout[0]
  81  	} else {
  82  		c.Timeout = time.Second * 5
  83  	}
  84  	return 
  85  }
  86  
  87  // CAPIClient is a wrapper around RPC calls
  88  type CAPIClient struct {
  89  	*rpc.Client
  90  }
  91  
  92  // NewCAPIClient creates a new client for a kopach_worker. Note that any kind of connection can be used here,
  93  // other than the StdConn
  94  func NewCAPIClient(conn io.ReadWriteCloser) *CAPIClient {
  95  	return &CAPIClient{rpc.NewClient(conn)}
  96  }
  97  
  98  type (
  99  	// None means no parameters it is not checked so it can be nil
 100  	None struct{} {{range .}}
 101  	// {{.Handler}}Res is the result from a call to {{.Handler}}
 102  	{{.Handler}}Res struct { Res *{{.ResType}}; e error }{{end}}
 103  )
 104  
 105  // RequestHandler is a handler function to handle an unmarshaled and parsed request into a marshalable response.  If the 
 106  // error is a *json.RPCError or any of the above special error classes, the server will respond with the JSON-RPC 
 107  // appropriate error code.  All other errors use the wallet catch-all error code, json.ErrRPCWallet.
 108  type RequestHandler func(interface{}, *Wallet, 
 109  	...*chainclient.RPCClient) (interface{}, error)
 110  
 111  // ` + RPCMapName + ` is all of the RPC calls available
 112  //
 113  // - Handler is the handler function
 114  // 
 115  // - Call is a channel carrying a struct containing parameters and error that is listened to in RunAPI to dispatch the 
 116  //   calls
 117  // 
 118  // - Result is a bundle of command parameters and a channel that the result will be sent back on
 119  //
 120  // Get and save the Result function's return, and you can then call the call functions check, result and wait functions 
 121  // for asynchronous and synchronous calls to RPC functions
 122  var ` + RPCMapName + ` = map[string]struct {
 123  	Handler          RequestHandler
 124  	// Function variables cannot be compared against anything but nil, so use a boolean to record whether help 
 125      // generation is necessary.  This is used by the tests to ensure that help can be generated for every implemented 
 126      // method.
 127  	//
 128  	// A single map and this bool is here is used rather than several maps for the unimplemented handlers so every 
 129      // method has exactly one handler function.
 130  	//
 131  	// The Return field returns a new channel of the type returned by this function. This makes it possible to use this 
 132      // for callers to receive a response in the cpc library which implements the functions as channel pipes
 133  	NoHelp bool
 134  	Call   chan API
 135  	Params interface{}
 136  	Result func() API
 137  }{
 138  {{range .}}	"{{.Method}}":{ 
 139  		Handler: {{.Handler}}, Call: make(chan API, 32),
 140  		Result: func() API { return API{Ch: make(chan {{.Handler}}Res)} }}, 
 141  {{end}}
 142  }
 143  
 144  // API functions
 145  //
 146  // The functions here provide access to the RPC through a convenient set of functions generated for each call in the RPC 
 147  // API to request, check for, access the results and wait on results
 148  
 149  {{range .}}
 150  // {{.Handler}} calls the method with the given parameters
 151  func (a API) {{.Handler}}(cmd {{.Cmd}}) (e error) {
 152  	` + RPCMapName + `["{{.Method}}"].Call <- API{a.Ch, cmd, nil}
 153  	return
 154  }
 155  
 156  // {{.Handler}}Check checks if a new message arrived on the result channel and returns true if it does, as well as 
 157  // storing the value in the Result field
 158  func (a API) {{.Handler}}Check() (isNew bool) {
 159  	select {
 160  	case o := <- a.Ch.(chan {{.Handler}}Res):
 161  		if o.e != nil {
 162  			a.Result = o.e
 163  		} else {
 164  			a.Result = o.Res
 165  		}
 166  		isNew = true
 167  	default:
 168  	}
 169  	return
 170  }
 171  
 172  // {{.Handler}}GetRes returns a pointer to the value in the Result field
 173  func (a API) {{.Handler}}GetRes() (out *{{.ResType}}, e error) {
 174  	out, _ = a.Result.(*{{.ResType}})
 175  	e, _ = a.Result.(error)
 176  	return 
 177  }
 178  
 179  // {{.Handler}}Wait calls the method and blocks until it returns or 5 seconds passes
 180  func (a API) {{.Handler}}Wait(cmd {{.Cmd}}) (out *{{.ResType}}, e error) {
 181  	` + RPCMapName + `["{{.Method}}"].Call <- API{a.Ch, cmd, nil}
 182  	select {
 183  	case <-time.After(time.Second*5):
 184  		break
 185  	case o := <- a.Ch.(chan {{.Handler}}Res):
 186  		out, e = o.Res, o.e
 187  	}
 188  	return
 189  }
 190  {{end}}
 191  
 192  // RunAPI starts up the api handler server that receives rpc.API messages and runs the handler and returns the result
 193  // Note that the parameters are type asserted to prevent the consumer of the API from sending wrong message types not
 194  // because it's necessary since they are interfaces end to end
 195  func RunAPI(chainRPC *chainclient.RPCClient, wallet *Wallet, 
 196  	quit qu.C) {
 197  	nrh := ` + RPCMapName + `
 198  	go func() {
 199  		D.Ln("starting up wallet cAPI")
 200  		var e error
 201  		var res interface{}
 202  		for {
 203  			select { {{range .}}
 204  			case msg := <-nrh["{{.Method}}"].Call:
 205  				if res, e = nrh["{{.Method}}"].
 206  					Handler(msg.Params.({{.Cmd}}), wallet, 
 207  						chainRPC); E.Chk(e) {
 208  				}
 209  				if r, ok := res.({{.ResType}}); ok { 
 210  					msg.Ch.(chan {{.Handler}}Res) <- {{.Handler}}Res{&r, e} } {{end}}
 211  			case <-quit.Wait():
 212  				D.Ln("stopping wallet cAPI")
 213  				return
 214  			}
 215  		}
 216  	}()
 217  }
 218  
 219  // RPC API functions to use with net/rpc
 220  {{range .}}
 221  func (c *CAPI) {{.Handler}}(req {{.Cmd}}, resp {{.ResType}}) (e error) {
 222  	nrh := ` + RPCMapName + `
 223  	res := nrh["{{.Method}}"].Result()
 224  	res.Params = req
 225  	nrh["{{.Method}}"].Call <- res
 226  	select {
 227  	case resp = <-res.Ch.(chan {{.ResType}}):
 228  	case <-time.After(c.Timeout):
 229  	case <-c.quit.Wait():
 230  	} 
 231  	return 
 232  }
 233  {{end}}
 234  // Client call wrappers for a CAPI client with a given Conn
 235  {{range .}}
 236  func (r *CAPIClient) {{.Handler}}(cmd ...{{.Cmd}}) (res {{.ResType}}, e error) {
 237  	var c {{.Cmd}}
 238  	if len(cmd) > 0 {
 239  		c = cmd[0]
 240  	}
 241  	if e = r.Call("` + Worker + `.{{.Handler}}", c, &res); E.Chk(e) {
 242  	}
 243  	return
 244  }
 245  {{end}}
 246  `
 247  
 248  var handlers = handlersT{
 249  	{
 250  		Method:  "addmultisigaddress",
 251  		Handler: "AddMultiSigAddress",
 252  		Cmd:     "*btcjson.AddMultisigAddressCmd",
 253  		ResType: "string",
 254  	},
 255  	{
 256  		Method:  "createmultisig",
 257  		Handler: "CreateMultiSig",
 258  		Cmd:     "*btcjson.CreateMultisigCmd",
 259  		ResType: "btcjson.CreateMultiSigResult",
 260  	},
 261  	{
 262  		Method:  "dumpprivkey",
 263  		Handler: "DumpPrivKey",
 264  		Cmd:     "*btcjson.DumpPrivKeyCmd",
 265  		ResType: "string",
 266  	},
 267  	{
 268  		Method:  "getaccount",
 269  		Handler: "GetAccount",
 270  		Cmd:     "*btcjson.GetAccountCmd",
 271  		ResType: "string",
 272  	},
 273  	{
 274  		Method:  "getaccountaddress",
 275  		Handler: "GetAccountAddress",
 276  		Cmd:     "*btcjson.GetAccountAddressCmd",
 277  		ResType: "string",
 278  	},
 279  	{
 280  		Method:  "getaddressesbyaccount",
 281  		Handler: "GetAddressesByAccount",
 282  		Cmd:     "*btcjson.GetAddressesByAccountCmd",
 283  		ResType: "[]string",
 284  	},
 285  	{
 286  		Method:  "getbalance",
 287  		Handler: "GetBalance",
 288  		Cmd:     "*btcjson.GetBalanceCmd",
 289  		ResType: "float64",
 290  	},
 291  	{
 292  		Method:  "getbestblockhash",
 293  		Handler: "GetBestBlockHash",
 294  		Cmd:     "*None",
 295  		ResType: "string",
 296  	},
 297  	{
 298  		Method:  "getblockcount",
 299  		Handler: "GetBlockCount",
 300  		Cmd:     "*None",
 301  		ResType: "int32",
 302  	},
 303  	{
 304  		Method:  "getinfo",
 305  		Handler: "GetInfo",
 306  		Cmd:     "*None",
 307  		ResType: "btcjson.InfoWalletResult",
 308  	},
 309  	{
 310  		Method:  "getnewaddress",
 311  		Handler: "GetNewAddress",
 312  		Cmd:     "*btcjson.GetNewAddressCmd",
 313  		ResType: "string",
 314  	},
 315  	{
 316  		Method:  "getrawchangeaddress",
 317  		Handler: "GetRawChangeAddress",
 318  		Cmd:     "*btcjson.GetRawChangeAddressCmd",
 319  		ResType: "string",
 320  	},
 321  	{
 322  		Method:  "getreceivedbyaccount",
 323  		Handler: "GetReceivedByAccount",
 324  		Cmd:     "*btcjson.GetReceivedByAccountCmd",
 325  		ResType: "float64",
 326  	},
 327  	{
 328  		Method:  "getreceivedbyaddress",
 329  		Handler: "GetReceivedByAddress",
 330  		Cmd:     "*btcjson.GetReceivedByAddressCmd",
 331  		ResType: "float64",
 332  	},
 333  	{
 334  		Method:  "gettransaction",
 335  		Handler: "GetTransaction",
 336  		Cmd:     "*btcjson.GetTransactionCmd",
 337  		ResType: "btcjson.GetTransactionResult",
 338  	},
 339  	{
 340  		Method:           "help",
 341  		Handler:          "HelpNoChainRPC",
 342  		HandlerWithChain: "HelpWithChainRPC",
 343  		Cmd:              "btcjson.HelpCmd",
 344  		ResType:          "string",
 345  	},
 346  	{
 347  		Method:  "importprivkey",
 348  		Handler: "ImportPrivKey",
 349  		Cmd:     "*btcjson.ImportPrivKeyCmd",
 350  		ResType: "None",
 351  	},
 352  	{
 353  		Method:  "keypoolrefill",
 354  		Handler: "KeypoolRefill",
 355  		Cmd:     "*None",
 356  		ResType: "None",
 357  	},
 358  	{
 359  		Method:  "listaccounts",
 360  		Handler: "ListAccounts",
 361  		Cmd:     "*btcjson.ListAccountsCmd",
 362  		ResType: "map[string]float64",
 363  	},
 364  	{
 365  		Method:  "listlockunspent",
 366  		Handler: "ListLockUnspent",
 367  		Cmd:     "*None",
 368  		ResType: "[]btcjson.TransactionInput",
 369  	},
 370  	{
 371  		Method:  "listreceivedbyaccount",
 372  		Handler: "ListReceivedByAccount",
 373  		Cmd:     "*btcjson.ListReceivedByAccountCmd",
 374  		ResType: "[]btcjson.ListReceivedByAccountResult",
 375  	},
 376  	{
 377  		Method:  "listreceivedbyaddress",
 378  		Handler: "ListReceivedByAddress",
 379  		Cmd:     "*btcjson.ListReceivedByAddressCmd",
 380  		ResType: "btcjson.ListReceivedByAddressResult",
 381  	},
 382  	{
 383  		Method:           "listsinceblock",
 384  		Handler:          "ListSinceBlock",
 385  		HandlerWithChain: "ListSinceBlock",
 386  		Cmd:              "btcjson.ListSinceBlockCmd",
 387  		ResType:          "btcjson.ListSinceBlockResult",
 388  	},
 389  	{
 390  		Method:  "listtransactions",
 391  		Handler: "ListTransactions",
 392  		Cmd:     "*btcjson.ListTransactionsCmd",
 393  		ResType: "[]btcjson.ListTransactionsResult",
 394  	},
 395  	{
 396  		Method:  "listunspent",
 397  		Handler: "ListUnspent",
 398  		Cmd:     "*btcjson.ListUnspentCmd",
 399  		ResType: "[]btcjson.ListUnspentResult",
 400  	},
 401  	{
 402  		Method:           "sendfrom",
 403  		Handler:          "LockUnspent",
 404  		HandlerWithChain: "LockUnspent",
 405  		Cmd:              "btcjson.LockUnspentCmd",
 406  		ResType:          "bool",
 407  	},
 408  	{
 409  		Method:  "sendmany",
 410  		Handler: "SendMany",
 411  		Cmd:     "*btcjson.SendManyCmd",
 412  		ResType: "string",
 413  	},
 414  	{
 415  		Method:  "sendtoaddress",
 416  		Handler: "SendToAddress",
 417  		Cmd:     "*btcjson.SendToAddressCmd",
 418  		ResType: "string",
 419  	},
 420  	{
 421  		Method:  "settxfee",
 422  		Handler: "SetTxFee",
 423  		Cmd:     "*btcjson.SetTxFeeCmd",
 424  		ResType: "bool",
 425  	},
 426  	{
 427  		Method:  "signmessage",
 428  		Handler: "SignMessage",
 429  		Cmd:     "*btcjson.SignMessageCmd",
 430  		ResType: "string",
 431  	},
 432  	{
 433  		Method:           "signrawtransaction",
 434  		Handler:          "SignRawTransaction",
 435  		HandlerWithChain: "SignRawTransaction",
 436  		Cmd:              "btcjson.SignRawTransactionCmd",
 437  		ResType:          "btcjson.SignRawTransactionResult",
 438  	},
 439  	{
 440  		Method:  "validateaddress",
 441  		Handler: "ValidateAddress",
 442  		Cmd:     "*btcjson.ValidateAddressCmd",
 443  		ResType: "btcjson.ValidateAddressWalletResult",
 444  	},
 445  	{
 446  		Method:  "verifymessage",
 447  		Handler: "VerifyMessage",
 448  		Cmd:     "*btcjson.VerifyMessageCmd",
 449  		ResType: "bool",
 450  	},
 451  	{
 452  		Method:  "walletlock",
 453  		Handler: "WalletLock",
 454  		Cmd:     "*None",
 455  		ResType: "None",
 456  	},
 457  	{
 458  		Method:  "walletpassphrase",
 459  		Handler: "WalletPassphrase",
 460  		Cmd:     "*btcjson.WalletPassphraseCmd",
 461  		ResType: "None",
 462  	},
 463  	{
 464  		Method:  "walletpassphrasechange",
 465  		Handler: "WalletPassphraseChange",
 466  		Cmd:     "*btcjson.WalletPassphraseChangeCmd",
 467  		ResType: "None",
 468  	},
 469  	{
 470  		Method:  "createnewaccount",
 471  		Handler: "CreateNewAccount",
 472  		Cmd:     "*btcjson.CreateNewAccountCmd",
 473  		ResType: "None",
 474  	},
 475  	{
 476  		Method:  "getbestblock",
 477  		Handler: "GetBestBlock",
 478  		Cmd:     "*None",
 479  		ResType: "btcjson.GetBestBlockResult",
 480  	},
 481  	{
 482  		Method:  "getunconfirmedbalance",
 483  		Handler: "GetUnconfirmedBalance",
 484  		Cmd:     "*btcjson.GetUnconfirmedBalanceCmd",
 485  		ResType: "float64",
 486  	},
 487  	{
 488  		Method:  "listaddresstransactions",
 489  		Handler: "ListAddressTransactions",
 490  		Cmd:     "*btcjson.ListAddressTransactionsCmd",
 491  		ResType: "[]btcjson.ListTransactionsResult",
 492  	},
 493  	{
 494  		Method:  "listalltransactions",
 495  		Handler: "ListAllTransactions",
 496  		Cmd:     "*btcjson.ListAllTransactionsCmd",
 497  		ResType: "[]btcjson.ListTransactionsResult",
 498  	},
 499  	{
 500  		Method:  "renameaccount",
 501  		Handler: "RenameAccount",
 502  		Cmd:     "*btcjson.RenameAccountCmd",
 503  		ResType: "None",
 504  	},
 505  	{
 506  		Method:  "walletislocked",
 507  		Handler: "WalletIsLocked",
 508  		Cmd:     "*None",
 509  		ResType: "bool",
 510  	},
 511  	{
 512  		Method:  "dropwallethistory",
 513  		Handler: "HandleDropWalletHistory",
 514  		Cmd:     "*None",
 515  		ResType: "string",
 516  	},
 517  }
 518