Skip to content

feat: align account transaction validation with other checks - #7087

Open
halibobo1205 wants to merge 2 commits into
tronprotocol:release_v4.8.3from
halibobo1205:feature/strengthen-transaction-validation
Open

halibobo1205 wants to merge 2 commits into
tronprotocol:release_v4.8.3from
halibobo1205:feature/strengthen-transaction-validation

Conversation

@halibobo1205

Copy link
Copy Markdown
Collaborator

What does this PR do?

Adds two account transaction checks:

  • AccountPermissionUpdateActuator.validate() now ends with a fee check: the owner's balance must cover calcFee(). execute() is unchanged.
  • Manager.pushTransaction now rejects an AccountCreateContract whose type isn't Normal.

Why are these changes required?

  • Every other actuator whose calcFee() returns a configured fee (AssetIssue, CreateAccount, ExchangeCreate, MarketSellAsset, MarketCancelOrder, WitnessCreate) already makes this fee check in validate(), and AGENTS.md requires it. AccountPermissionUpdate reported a short balance from execute() instead.
  • Normal is the default type of AccountCreateContract. Other types are now rejected at the start of pushTransaction.

Impact

AccountPermissionUpdateContract whose owner can't pay a fee:

Path Before After
Build: HTTP /wallet/accountpermissionupdate, /wallet/createCommonTransaction; gRPC AccountPermissionUpdate, CreateCommonTransaction Returns an unsigned transaction Returns no transaction. HTTP: {"Error":"<owner> insufficient balance, balance: <b>, amount: <fee>"}. gRPC: CONTRACT_VALIDATE_ERROR
Broadcast: HTTP /wallet/broadcasttransaction, /wallet/broadcasthex; gRPC BroadcastTransaction CONTRACT_EXE_ERROR, Contract execute error : <owner> insufficient balance, ... CONTRACT_VALIDATE_ERROR, Contract validate error : <owner> insufficient balance, ...
Transaction execution: pushTransaction, rePush, TronNetDelegate.pushTransaction Rejected in execute() with ContractExeException; not added to pending Rejected in validate() with ContractValidateException; not added to pending. TronNetDelegate still throws P2pException type TRX_EXE_FAILED
Packing: generateBlock Transaction is skipped and packing continues Unchanged
Block execution: pushBlock, TronNetDelegate.processBlock Block is rejected; TronNetDelegate throws P2pException type BAD_BLOCK Block is rejected with the same P2pException type; the underlying exception is now ContractValidateException

AccountCreateContract with type AssetIssue or Contract:

Path Before After
Build: HTTP /wallet/createaccount, /wallet/createCommonTransaction; gRPC CreateAccount, CreateAccount2, CreateCommonTransaction Returns an unsigned transaction Unchanged
Broadcast: HTTP /wallet/broadcasttransaction, /wallet/broadcasthex; gRPC BroadcastTransaction Succeeds CONTRACT_VALIDATE_ERROR, Contract validate error : AccountCreateContract only supports the Normal account type.
Transaction execution: pushTransaction, rePush, TronNetDelegate.pushTransaction Added to pending; Wallet and TransactionsMsgHandler then relay it Rejected with ContractValidateException before signature validation, so it isn't added to pending or relayed. TronNetDelegate throws P2pException type TRX_EXE_FAILED
Packing: generateBlock No type check Unchanged
Block execution: pushBlock, TronNetDelegate.processBlock Accepted Unchanged

API clients:

  • Fund the owner before building an AccountPermissionUpdateContract transaction. A flow that builds first and funds before broadcasting now fails at build.
  • Update any handling that expects CONTRACT_EXE_ERROR for that case.
  • An AccountCreateContract with another type still builds, but a node running this PR rejects its broadcast. Callers that leave type unset aren't affected.

Proposal:

  • AccountPermissionUpdate at a fee of 0: execute() skips the deduction, so a negative balance used to pass and is now rejected. Mainnet's fee is currently 100 TRX and its only negative genesis balance is Blackhole, so reaching this there takes a proposal setting the fee to 0 and a signature from Blackhole, whose key no one is known to hold. A private chain at a fee of 0 that holds the key of a negative balance should upgrade all nodes together.
  • AccountCreateContract: the check runs only in pushTransaction, so block execution doesn't change and this doesn't need a proposal.

This PR has been tested by:

  • Unit tests
  • Manual testing

Follow up

None.

Extra details

None.

@waynercheung waynercheung left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Checkstyle/CI green, no debug code, no Math usage, no new logs, no DB/config/dependency change. Impact tables match the code paths I traced (Wallet.createTransactionCapsule, Manager.pushTransaction/rePush, TronNetDelegate, generateBlock, processBlock). Non-blocking items below; a follow-up PR is fine.

[SHOULD] AccountPermissionUpdateActuator.execute() still writes the new permissions before charging the fee. With the new validate() check this path should no longer fail, but charging first (adjustBalance before updatePermissions/put) keeps execute() from leaving writes that are later rolled back, and the final state is identical.

[SHOULD] Tests for isAccountCreateNonNormalType: add an unrecognized enum value (e.g. setTypeValue(3)), a non-AccountCreateContract transaction, and an Any that fails to unpack. Changing the catch branch to return true currently passes all tests.

[NIT]

  • Build path: /wallet/createaccount, CreateAccount2 and createCommonTransaction still build an AccountCreateContract with a non-Normal type that this node then refuses to broadcast; rejecting it in Wallet.createTransactionCapsule would let callers fail at build time.
  • Please mention the build-time AccountPermissionUpdate change and CONTRACT_EXE_ERROR -> CONTRACT_VALIDATE_ERROR in the release notes for clients that build before funding.
  • insufficientBalanceToCoverFee: use assertThrows and assert the full message; the owner-permission-unchanged assertion adds nothing since validate() does not write state.
  • isAccountCreateNonNormalType is a pending-pool rule only; consider a private helper in Manager next to isShieldedTransaction/isExchangeTransaction rather than a public method on TransactionCapsule.
  • The two changes are unrelated; splitting them into separate PRs would keep history and release notes clearer.

throw new ContractValidateException("Transaction auths are not supported.");
}

if (trx.isAccountCreateNonNormalType()) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[DISCUSS] This only gates pushTransaction, so an AccountCreateContract with a non-Normal type is still valid in a block (CreateAccountActuator copies type into the new account). Is a validate-level check behind a proposal planned for the fork line? If yes, please open an issue and list it under "Follow up" instead of "None".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

3 participants