github.com/codingfuture/orig-energi3@v0.8.4/energi/api/governance.go (about)

     1  // Copyright 2019 The Energi Core Authors
     2  // This file is part of the Energi Core library.
     3  //
     4  // The Energi Core 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 Energi Core 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 Energi Core library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package api
    18  
    19  import (
    20  	"errors"
    21  	"math/big"
    22  
    23  	"github.com/pborman/uuid"
    24  
    25  	"github.com/ethereum/go-ethereum/accounts/abi/bind"
    26  	"github.com/ethereum/go-ethereum/common"
    27  	"github.com/ethereum/go-ethereum/common/hexutil"
    28  	"github.com/ethereum/go-ethereum/log"
    29  	"github.com/ethereum/go-ethereum/rpc"
    30  
    31  	energi_abi "energi.world/core/gen3/energi/abi"
    32  	energi_common "energi.world/core/gen3/energi/common"
    33  	energi_params "energi.world/core/gen3/energi/params"
    34  )
    35  
    36  const (
    37  	proposalCallGas uint64 = 3000000
    38  	upgradeCallGas  uint64 = 5000000
    39  )
    40  
    41  type GovernanceAPI struct {
    42  	backend    Backend
    43  	uInfoCache *energi_common.CacheStorage
    44  	bInfoCache *energi_common.CacheStorage
    45  }
    46  
    47  func NewGovernanceAPI(b Backend) *GovernanceAPI {
    48  	r := &GovernanceAPI{
    49  		backend:    b,
    50  		uInfoCache: energi_common.NewCacheStorage(),
    51  		bInfoCache: energi_common.NewCacheStorage(),
    52  	}
    53  	b.OnSyncedHeadUpdates(func() {
    54  		r.UpgradeInfo()
    55  		r.BudgetInfo()
    56  	})
    57  	return r
    58  }
    59  
    60  //=============================================================================
    61  
    62  func (g *GovernanceAPI) proposal(
    63  	password *string,
    64  	owner common.Address,
    65  	proposal common.Address,
    66  ) (session *energi_abi.IProposalSession, err error) {
    67  	contract, err := energi_abi.NewIProposal(proposal, g.backend.(bind.ContractBackend))
    68  	if err != nil {
    69  		return nil, err
    70  	}
    71  
    72  	session = &energi_abi.IProposalSession{
    73  		Contract: contract,
    74  		CallOpts: bind.CallOpts{
    75  			Pending:  true,
    76  			From:     owner,
    77  			GasLimit: energi_params.UnlimitedGas,
    78  		},
    79  		TransactOpts: bind.TransactOpts{
    80  			From:     owner,
    81  			Signer:   createSignerCallback(g.backend, password),
    82  			Value:    common.Big0,
    83  			GasLimit: proposalCallGas,
    84  		},
    85  	}
    86  	return
    87  }
    88  
    89  //=============================================================================
    90  // VT-5 Voting API
    91  //=============================================================================
    92  
    93  func (g *GovernanceAPI) checkCanVote(
    94  	contract *energi_abi.IProposalSession,
    95  	mn_owner common.Address,
    96  ) error {
    97  	if can, err := contract.CanVote(mn_owner); err != nil {
    98  		return err
    99  	} else if !can {
   100  		return errors.New("This account is not allowed to vote!")
   101  	}
   102  
   103  	return nil
   104  }
   105  
   106  func (g *GovernanceAPI) VoteAccept(
   107  	proposal common.Address,
   108  	mn_owner common.Address,
   109  	password *string,
   110  ) (txhash common.Hash, err error) {
   111  	contract, err := g.proposal(password, mn_owner, proposal)
   112  	if err != nil {
   113  		log.Error("Failed", "err", err)
   114  		return
   115  	}
   116  
   117  	if err = g.checkCanVote(contract, mn_owner); err != nil {
   118  		log.Error("Failed", "err", err)
   119  		return
   120  	}
   121  
   122  	tx, err := contract.VoteAccept()
   123  
   124  	if tx != nil {
   125  		txhash = tx.Hash()
   126  		log.Info("Note: please wait until the proposal TX gets into a block!", "tx", txhash.Hex())
   127  	}
   128  
   129  	return
   130  }
   131  
   132  func (g *GovernanceAPI) VoteReject(
   133  	proposal common.Address,
   134  	mn_owner common.Address,
   135  	password *string,
   136  ) (txhash common.Hash, err error) {
   137  	contract, err := g.proposal(password, mn_owner, proposal)
   138  	if err != nil {
   139  		log.Error("Failed", "err", err)
   140  		return
   141  	}
   142  
   143  	if err = g.checkCanVote(contract, mn_owner); err != nil {
   144  		log.Error("Failed", "err", err)
   145  		return
   146  	}
   147  
   148  	tx, err := contract.VoteReject()
   149  
   150  	if tx != nil {
   151  		txhash = tx.Hash()
   152  		log.Info("Note: please wait until the proposal TX gets into a block!", "tx", txhash.Hex())
   153  	}
   154  
   155  	return
   156  }
   157  
   158  func (g *GovernanceAPI) WithdrawFee(
   159  	proposal common.Address,
   160  	payer common.Address,
   161  	password *string,
   162  ) (txhash common.Hash, err error) {
   163  	contract, err := g.proposal(password, payer, proposal)
   164  	if err != nil {
   165  		log.Error("Failed", "err", err)
   166  		return
   167  	}
   168  
   169  	if res, _ := contract.IsAccepted(); !res {
   170  		err = errors.New("The proposal is not accepted!")
   171  		log.Error("Failed", "err", err)
   172  		return
   173  	}
   174  
   175  	tx, err := contract.Withdraw()
   176  
   177  	if tx != nil {
   178  		txhash = tx.Hash()
   179  		log.Info("Note: please wait until the proposal TX gets into a block!", "tx", txhash.Hex())
   180  	}
   181  
   182  	return
   183  }
   184  
   185  //=============================================================================
   186  // Generic proposal info
   187  //=============================================================================
   188  
   189  type ProposalInfo struct {
   190  	Proposal     common.Address
   191  	Proposer     common.Address
   192  	CreatedBlock uint64
   193  	Deadline     uint64
   194  	QuorumWeight *hexutil.Big
   195  	TotalWeight  *hexutil.Big
   196  	RejectWeight *hexutil.Big
   197  	AcceptWeight *hexutil.Big
   198  	Finished     bool
   199  	Accepted     bool
   200  	Balance      *hexutil.Big
   201  }
   202  
   203  func getBalance(backend Backend, address common.Address) (*hexutil.Big, error) {
   204  	curr_block := backend.CurrentBlock()
   205  
   206  	state, _, err := backend.StateAndHeaderByNumber(
   207  		nil, rpc.BlockNumber(curr_block.Number().Int64()))
   208  	if err != nil {
   209  		log.Error("Failed at state", "err", err)
   210  		return nil, err
   211  	}
   212  
   213  	return (*hexutil.Big)(state.GetBalance(address)), nil
   214  }
   215  
   216  func proposalInfo(backend Backend, address common.Address) (*ProposalInfo, error) {
   217  	if (address == common.Address{}) {
   218  		return nil, nil
   219  	}
   220  
   221  	proposal, err := energi_abi.NewIProposalCaller(
   222  		address, backend.(bind.ContractCaller))
   223  	if err != nil {
   224  		log.Error("Failed at NewIProposalCaller", "err", err)
   225  		return nil, err
   226  	}
   227  
   228  	call_opts := &bind.CallOpts{
   229  		GasLimit: energi_params.UnlimitedGas,
   230  	}
   231  
   232  	proposer, err := proposal.FeePayer(call_opts)
   233  	if err != nil {
   234  		log.Error("Failed at FeePayer", "err", err)
   235  		return nil, err
   236  	}
   237  
   238  	block, err := proposal.CreatedBlock(call_opts)
   239  	if err != nil {
   240  		log.Error("Failed at CreatedBlock", "err", err)
   241  		return nil, err
   242  	}
   243  
   244  	deadline, err := proposal.Deadline(call_opts)
   245  	if err != nil {
   246  		log.Error("Failed at Deadline", "err", err)
   247  		return nil, err
   248  	}
   249  
   250  	quorum_w, err := proposal.QuorumWeight(call_opts)
   251  	if err != nil {
   252  		log.Error("Failed at QuorumWeight", "err", err)
   253  		return nil, err
   254  	}
   255  
   256  	total_w, err := proposal.TotalWeight(call_opts)
   257  	if err != nil {
   258  		log.Error("Failed at TotalWeight", "err", err)
   259  		return nil, err
   260  	}
   261  
   262  	rejected_w, err := proposal.RejectedWeight(call_opts)
   263  	if err != nil {
   264  		log.Error("Failed at RejectedWeight", "err", err)
   265  		return nil, err
   266  	}
   267  
   268  	accepted_w, err := proposal.AcceptedWeight(call_opts)
   269  	if err != nil {
   270  		log.Error("Failed at AcceptedWeight", "err", err)
   271  		return nil, err
   272  	}
   273  
   274  	finished, err := proposal.IsFinished(call_opts)
   275  	if err != nil {
   276  		log.Error("Failed at IsFinished", "err", err)
   277  		return nil, err
   278  	}
   279  
   280  	accepted, err := proposal.IsAccepted(call_opts)
   281  	if err != nil {
   282  		log.Error("Failed at IsAccepted", "err", err)
   283  		return nil, err
   284  	}
   285  
   286  	balance, err := getBalance(backend, address)
   287  	if err != nil {
   288  		log.Error("Failed at getBalance", "err", err)
   289  		return nil, err
   290  	}
   291  
   292  	p := &ProposalInfo{
   293  		Proposal:     address,
   294  		Proposer:     proposer,
   295  		CreatedBlock: block.Uint64(),
   296  		Deadline:     deadline.Uint64(),
   297  		QuorumWeight: (*hexutil.Big)(quorum_w),
   298  		TotalWeight:  (*hexutil.Big)(total_w),
   299  		RejectWeight: (*hexutil.Big)(rejected_w),
   300  		AcceptWeight: (*hexutil.Big)(accepted_w),
   301  		Finished:     finished,
   302  		Accepted:     accepted,
   303  		Balance:      balance,
   304  	}
   305  	return p, nil
   306  }
   307  
   308  //=============================================================================
   309  // SC-15: Upgrade API
   310  //=============================================================================
   311  
   312  type UpgradeProposalInfo struct {
   313  	ProposalInfo
   314  	Impl  common.Address
   315  	Proxy common.Address
   316  }
   317  
   318  func (g *GovernanceAPI) upgradeProposalInfo(num *big.Int, proxy common.Address) ([]UpgradeProposalInfo, error) {
   319  	proxy_obj, err := energi_abi.NewIGovernedProxyCaller(
   320  		proxy, g.backend.(bind.ContractCaller))
   321  	if err != nil {
   322  		log.Error("Failed NewIGovernedProxyCaller", "err", err)
   323  		return nil, err
   324  	}
   325  
   326  	call_opts := &bind.CallOpts{
   327  		BlockNumber: num,
   328  		GasLimit:    energi_params.UnlimitedGas,
   329  	}
   330  	proposals, err := proxy_obj.ListUpgradeProposals(call_opts)
   331  	if err != nil {
   332  		log.Error("Failed ListUpgradeProposals", "err", err)
   333  		return nil, err
   334  	}
   335  
   336  	ret := make([]UpgradeProposalInfo, 0, len(proposals))
   337  	for i, p := range proposals {
   338  		pInfo, err := proposalInfo(g.backend, p)
   339  		if err != nil {
   340  			log.Error("Failed at proposalInfo", "err", err)
   341  			continue
   342  		}
   343  
   344  		ret = append(ret, UpgradeProposalInfo{ProposalInfo: *pInfo})
   345  		impl, err := proxy_obj.UpgradeProposalImpl(call_opts, p)
   346  		if err != nil {
   347  			log.Error("Failed UpgradeProposalImpl", "err", err)
   348  			continue
   349  		}
   350  		ret[i].Impl = impl
   351  		ret[i].Proxy = proxy
   352  	}
   353  
   354  	return ret, nil
   355  }
   356  
   357  func (g *GovernanceAPI) governedProxy(
   358  	password *string,
   359  	owner common.Address,
   360  	proxy common.Address,
   361  ) (session *energi_abi.IGovernedProxySession, err error) {
   362  	contract, err := energi_abi.NewIGovernedProxy(
   363  		proxy, g.backend.(bind.ContractBackend))
   364  	if err != nil {
   365  		return nil, err
   366  	}
   367  
   368  	session = &energi_abi.IGovernedProxySession{
   369  		Contract: contract,
   370  		CallOpts: bind.CallOpts{
   371  			From:     owner,
   372  			GasLimit: energi_params.UnlimitedGas,
   373  		},
   374  		TransactOpts: bind.TransactOpts{
   375  			From:     owner,
   376  			Signer:   createSignerCallback(g.backend, password),
   377  			Value:    common.Big0,
   378  			GasLimit: upgradeCallGas,
   379  		},
   380  	}
   381  	return
   382  }
   383  
   384  type UpgradeProposals struct {
   385  	Treasury           []UpgradeProposalInfo
   386  	MasternodeRegistry []UpgradeProposalInfo
   387  	StakerReward       []UpgradeProposalInfo
   388  	BackboneReward     []UpgradeProposalInfo
   389  	SporkRegistry      []UpgradeProposalInfo
   390  	CheckpointRegistry []UpgradeProposalInfo
   391  	BlacklistRegistry  []UpgradeProposalInfo
   392  	MasternodeToken    []UpgradeProposalInfo
   393  }
   394  
   395  func (g *GovernanceAPI) UpgradeInfo() *UpgradeProposals {
   396  	data, err := g.uInfoCache.Get(g.backend, g.upgradeInfo)
   397  	if err != nil || data == nil {
   398  		log.Error("UpgradeInfo failed", "err", err)
   399  		return nil
   400  	}
   401  
   402  	return data.(*UpgradeProposals)
   403  }
   404  
   405  func (g *GovernanceAPI) upgradeInfo(num *big.Int) (interface{}, error) {
   406  	var err error
   407  	ret := new(UpgradeProposals)
   408  	ret.Treasury, err = g.upgradeProposalInfo(num, energi_params.Energi_Treasury)
   409  	if err != nil {
   410  		log.Error("Treasury info fetch failed", "err", err)
   411  	}
   412  
   413  	ret.MasternodeRegistry, err = g.upgradeProposalInfo(num, energi_params.Energi_MasternodeRegistry)
   414  	if err != nil {
   415  		log.Error("MasternodeRegistry info fetch failed", "err", err)
   416  	}
   417  
   418  	ret.StakerReward, err = g.upgradeProposalInfo(num, energi_params.Energi_StakerReward)
   419  	if err != nil {
   420  		log.Error("StakerReward info fetch failed", "err", err)
   421  	}
   422  
   423  	ret.BackboneReward, err = g.upgradeProposalInfo(num, energi_params.Energi_BackboneReward)
   424  	if err != nil {
   425  		log.Error("BackboneReward info fetch failed", "err", err)
   426  	}
   427  
   428  	ret.SporkRegistry, err = g.upgradeProposalInfo(num, energi_params.Energi_SporkRegistry)
   429  	if err != nil {
   430  		log.Error("SporkRegistry info fetch failed", "err", err)
   431  	}
   432  
   433  	ret.CheckpointRegistry, err = g.upgradeProposalInfo(num, energi_params.Energi_CheckpointRegistry)
   434  	if err != nil {
   435  		log.Error("CheckpointRegistry info fetch failed", "err", err)
   436  	}
   437  
   438  	ret.BlacklistRegistry, err = g.upgradeProposalInfo(num, energi_params.Energi_BlacklistRegistry)
   439  	if err != nil {
   440  		log.Error("BlacklistRegistry info fetch failed", "err", err)
   441  	}
   442  
   443  	ret.MasternodeToken, err = g.upgradeProposalInfo(num, energi_params.Energi_MasternodeToken)
   444  	if err != nil {
   445  		log.Error("MasternodeToken info fetch failed", "err", err)
   446  	}
   447  
   448  	return ret, nil
   449  }
   450  
   451  func (g *GovernanceAPI) UpgradePropose(
   452  	proxy common.Address,
   453  	new_impl common.Address,
   454  	period uint64,
   455  	fee *hexutil.Big,
   456  	payer common.Address,
   457  	password *string,
   458  ) (txhash common.Hash, err error) {
   459  	session, err := g.governedProxy(password, payer, proxy)
   460  	if err != nil {
   461  		log.Error("Failed", "err", err)
   462  		return
   463  	}
   464  
   465  	session.TransactOpts.Value = fee.ToInt()
   466  	tx, err := session.ProposeUpgrade(new_impl, new(big.Int).SetUint64(period))
   467  
   468  	if tx != nil {
   469  		txhash = tx.Hash()
   470  		log.Info("Note: please wait until the proposal TX gets into a block!", "tx", txhash.Hex())
   471  	}
   472  
   473  	return
   474  }
   475  
   476  func (g *GovernanceAPI) UpgradePerform(
   477  	proxy common.Address,
   478  	proposal common.Address,
   479  	payer common.Address,
   480  	password *string,
   481  ) (txhash common.Hash, err error) {
   482  	session, err := g.governedProxy(password, payer, proxy)
   483  	if err != nil {
   484  		log.Error("Failed", "err", err)
   485  		return
   486  	}
   487  
   488  	tx, err := session.Upgrade(proposal)
   489  
   490  	if tx != nil {
   491  		txhash = tx.Hash()
   492  		log.Info("Note: please wait until the upgrade TX gets into a block!", "tx", txhash.Hex())
   493  	}
   494  
   495  	return
   496  }
   497  
   498  func (g *GovernanceAPI) UpgradeCollect(
   499  	proxy common.Address,
   500  	proposal common.Address,
   501  	payer common.Address,
   502  	password *string,
   503  ) (txhash common.Hash, err error) {
   504  	session, err := g.governedProxy(password, payer, proxy)
   505  	if err != nil {
   506  		log.Error("Failed", "err", err)
   507  		return
   508  	}
   509  
   510  	tx, err := session.CollectUpgradeProposal(proposal)
   511  
   512  	if tx != nil {
   513  		txhash = tx.Hash()
   514  		log.Info("Note: please wait until the proposal TX gets into a block!", "tx", txhash.Hex())
   515  	}
   516  
   517  	return
   518  }
   519  
   520  //=============================================================================
   521  // GOV-9: Treasury API
   522  //=============================================================================
   523  
   524  func (g *GovernanceAPI) treasury(
   525  	password *string,
   526  	payer common.Address,
   527  ) (session *energi_abi.ITreasurySession, err error) {
   528  	contract, err := energi_abi.NewITreasury(
   529  		energi_params.Energi_Treasury, g.backend.(bind.ContractBackend))
   530  	if err != nil {
   531  		return nil, err
   532  	}
   533  
   534  	session = &energi_abi.ITreasurySession{
   535  		Contract: contract,
   536  		CallOpts: bind.CallOpts{
   537  			From:     payer,
   538  			GasLimit: energi_params.UnlimitedGas,
   539  		},
   540  		TransactOpts: bind.TransactOpts{
   541  			From:     payer,
   542  			Signer:   createSignerCallback(g.backend, password),
   543  			Value:    common.Big0,
   544  			GasLimit: proposalCallGas,
   545  		},
   546  	}
   547  	return
   548  }
   549  
   550  type BudgetProposalInfo struct {
   551  	ProposalInfo
   552  	ProposedAmount *hexutil.Big
   553  	PaidAmount     *hexutil.Big
   554  	RefUUID        string
   555  }
   556  
   557  type BudgetInfo struct {
   558  	Balance   *hexutil.Big
   559  	Proposals []BudgetProposalInfo
   560  }
   561  
   562  func (g *GovernanceAPI) BudgetInfo() (*BudgetInfo, error) {
   563  	data, err := g.bInfoCache.Get(g.backend, g.budgetInfo)
   564  	if err != nil || data == nil {
   565  		log.Error("BudgetInfo failed", "err", err)
   566  		return nil, err
   567  	}
   568  
   569  	return data.(*BudgetInfo), nil
   570  }
   571  
   572  func (g *GovernanceAPI) budgetInfo(num *big.Int) (interface{}, error) {
   573  	treasury, err := energi_abi.NewITreasuryCaller(
   574  		energi_params.Energi_Treasury, g.backend.(bind.ContractCaller))
   575  	if err != nil {
   576  		log.Error("Failed NewITreasuryCaller", "err", err)
   577  		return nil, err
   578  	}
   579  
   580  	proxy, err := energi_abi.NewIGovernedProxyCaller(
   581  		energi_params.Energi_Treasury, g.backend.(bind.ContractCaller))
   582  	if err != nil {
   583  		log.Error("Failed NewITreasuryCaller", "err", err)
   584  		return nil, err
   585  	}
   586  
   587  	call_opts := &bind.CallOpts{
   588  		BlockNumber: num,
   589  		GasLimit:    energi_params.UnlimitedGas,
   590  	}
   591  
   592  	proposals, err := treasury.ListProposals(call_opts)
   593  	if err != nil {
   594  		log.Error("Failed ListProposals", "err", err)
   595  		return nil, err
   596  	}
   597  
   598  	impl, err := proxy.Impl(call_opts)
   599  	if err != nil {
   600  		log.Error("Failed Impl", "err", err)
   601  		return nil, err
   602  	}
   603  
   604  	ret := make([]BudgetProposalInfo, 0, len(proposals))
   605  	for i, p := range proposals {
   606  		pInfo, err := proposalInfo(g.backend, p)
   607  		if err != nil {
   608  			log.Error("Failed at proposalInfo", "err", err)
   609  			continue
   610  		}
   611  
   612  		ret = append(ret, BudgetProposalInfo{ProposalInfo: *pInfo})
   613  
   614  		budger_proposal, err := energi_abi.NewIBudgetProposalCaller(
   615  			p, g.backend.(bind.ContractCaller))
   616  		if err != nil {
   617  			log.Error("Failed at NewIBudgetProposalCaller", "err", err)
   618  			return nil, err
   619  		}
   620  
   621  		proposed_amount, err := budger_proposal.ProposedAmount(call_opts)
   622  		if err != nil {
   623  			log.Error("Failed ProposedAmount", "err", err)
   624  			continue
   625  		}
   626  		paid_amount, err := budger_proposal.PaidAmount(call_opts)
   627  		if err != nil {
   628  			log.Error("Failed ProposedAmount", "err", err)
   629  			continue
   630  		}
   631  		ref_uuid, err := budger_proposal.RefUuid(call_opts)
   632  		if err != nil {
   633  			log.Error("Failed ProposedAmount", "err", err)
   634  			continue
   635  		}
   636  		ret[i].ProposedAmount = (*hexutil.Big)(proposed_amount)
   637  		ret[i].PaidAmount = (*hexutil.Big)(paid_amount)
   638  		ret[i].RefUUID = uuid.UUID(common.LeftPadBytes(ref_uuid.Bytes(), 16)).String()
   639  	}
   640  
   641  	balance, err := getBalance(g.backend, impl)
   642  	if err != nil {
   643  		log.Error("Failed at getBalance", "err", err)
   644  	}
   645  
   646  	budget := &BudgetInfo{
   647  		Balance:   balance,
   648  		Proposals: ret,
   649  	}
   650  
   651  	return budget, nil
   652  }
   653  
   654  func (g *GovernanceAPI) BudgetPropose(
   655  	amount *hexutil.Big,
   656  	ref_uuid string,
   657  	period uint64,
   658  	fee *hexutil.Big,
   659  	payer common.Address,
   660  	password *string,
   661  ) (txhash common.Hash, err error) {
   662  	session, err := g.treasury(password, payer)
   663  	if err != nil {
   664  		log.Error("Failed", "err", err)
   665  		return
   666  	}
   667  
   668  	ref_uuid_b := uuid.Parse(ref_uuid)
   669  	if ref_uuid_b == nil {
   670  		err = errors.New("Failed to parse UUID")
   671  		log.Error("Failed", "err", err)
   672  		return
   673  	}
   674  
   675  	session.TransactOpts.Value = fee.ToInt()
   676  	tx, err := session.Propose(
   677  		(*big.Int)(amount),
   678  		new(big.Int).SetBytes(ref_uuid_b),
   679  		new(big.Int).SetUint64(period))
   680  
   681  	if tx != nil {
   682  		txhash = tx.Hash()
   683  		log.Info("Note: please wait until the proposal TX gets into a block!", "tx", txhash.Hex())
   684  	}
   685  
   686  	return
   687  }