From 5e8ecff6bc892698e9d9f915c38b9a7222439f89 Mon Sep 17 00:00:00 2001 From: tmohay <37158202+rain-on@users.noreply.github.com> Date: Tue, 29 Jan 2019 20:25:59 +1100 Subject: [PATCH] Allow missing accounts to create zero-cost transactions (#685) Messages which originate with the current node are logged in the gossiper such that if a remote peer sends a packet which originated from the local back to the local node, it should not go back out again. It was found that if a non-existent account sent a transaction via JSON RPC - the transction would be rejected, even if the upfront cost of the transaction was 0 (0 gasprice, and 0 value). This was because the sender was deemed to not exist, therefore not have the required funds. If the tranasaction was received via block propogation, this problem would not be hit (as the sender account would be created in the world state prior to validating the transaction). Local/remote transactions did not have access to the world state to do this. MainnetTransactionValidator has been updated to allow a 'null' sender to create a transaction if the price is zero (and the nonce is acceptable from the standpoint of a default/initial account state). Signed-off-by: Adrian Sutton --- .../pantheon/ethereum/core/Account.java | 3 +++ .../pantheon/ethereum/core/WorldUpdater.java | 2 +- .../mainnet/MainnetTransactionValidator.java | 25 ++++++++++------- .../MainnetTransactionValidatorTest.java | 27 ++++++++++++------- 4 files changed, 36 insertions(+), 21 deletions(-) diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/core/Account.java b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/core/Account.java index 031cc6b8d3..6a9afb8e75 100644 --- a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/core/Account.java +++ b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/core/Account.java @@ -35,6 +35,9 @@ import java.util.NavigableMap; */ public interface Account { + long DEFAULT_NONCE = 0L; + Wei DEFAULT_BALANCE = Wei.ZERO; + /** * The Keccak-256 hash of the account address. * diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/core/WorldUpdater.java b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/core/WorldUpdater.java index bca48e81df..71696471c7 100644 --- a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/core/WorldUpdater.java +++ b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/core/WorldUpdater.java @@ -51,7 +51,7 @@ public interface WorldUpdater extends MutableWorldView { * and storage. */ default MutableAccount createAccount(final Address address) { - return createAccount(address, 0L, Wei.ZERO); + return createAccount(address, Account.DEFAULT_NONCE, Account.DEFAULT_BALANCE); } /** diff --git a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/MainnetTransactionValidator.java b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/MainnetTransactionValidator.java index 72c5aebb55..88d5be0248 100644 --- a/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/MainnetTransactionValidator.java +++ b/ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/MainnetTransactionValidator.java @@ -24,6 +24,7 @@ import tech.pegasys.pantheon.crypto.SECP256K1; import tech.pegasys.pantheon.ethereum.core.Account; import tech.pegasys.pantheon.ethereum.core.Gas; import tech.pegasys.pantheon.ethereum.core.Transaction; +import tech.pegasys.pantheon.ethereum.core.Wei; import tech.pegasys.pantheon.ethereum.vm.GasCalculator; import java.util.OptionalInt; @@ -86,32 +87,36 @@ public class MainnetTransactionValidator implements TransactionValidator { @Override public ValidationResult validateForSender( final Transaction transaction, final Account sender, final OptionalLong maximumNonce) { - if (sender == null) { - return ValidationResult.invalid(UPFRONT_COST_EXCEEDS_BALANCE, "Unknown sender account"); + + Wei balance = Account.DEFAULT_BALANCE; + long nonce = Account.DEFAULT_NONCE; + + if (sender != null) { + balance = sender.getBalance(); + nonce = sender.getNonce(); } - if (transaction.getUpfrontCost().compareTo(sender.getBalance()) > 0) { + + if (transaction.getUpfrontCost().compareTo(balance) > 0) { return ValidationResult.invalid( UPFRONT_COST_EXCEEDS_BALANCE, String.format( "transaction up-front cost %s exceeds transaction sender account balance %s", - transaction.getUpfrontCost(), sender.getBalance())); + transaction.getUpfrontCost(), balance)); } - if (transaction.getNonce() < sender.getNonce()) { + if (transaction.getNonce() < nonce) { return ValidationResult.invalid( NONCE_TOO_LOW, String.format( - "transaction nonce %s below sender account nonce %s", - transaction.getNonce(), sender.getNonce())); + "transaction nonce %s below sender account nonce %s", transaction.getNonce(), nonce)); } - if (violatesMaximumNonce(transaction, maximumNonce) - && sender.getNonce() != transaction.getNonce()) { + if (violatesMaximumNonce(transaction, maximumNonce) && nonce != transaction.getNonce()) { return ValidationResult.invalid( INCORRECT_NONCE, String.format( "transaction nonce %s does not match sender account nonce %s.", - transaction.getNonce(), sender.getNonce())); + transaction.getNonce(), nonce)); } return ValidationResult.valid(); diff --git a/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/mainnet/MainnetTransactionValidatorTest.java b/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/mainnet/MainnetTransactionValidatorTest.java index ed38460784..d68859b2f9 100644 --- a/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/mainnet/MainnetTransactionValidatorTest.java +++ b/ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/mainnet/MainnetTransactionValidatorTest.java @@ -24,6 +24,7 @@ import static tech.pegasys.pantheon.ethereum.mainnet.TransactionValidator.Transa import tech.pegasys.pantheon.crypto.SECP256K1.KeyPair; import tech.pegasys.pantheon.ethereum.core.Account; +import tech.pegasys.pantheon.ethereum.core.Address; import tech.pegasys.pantheon.ethereum.core.Gas; import tech.pegasys.pantheon.ethereum.core.Transaction; import tech.pegasys.pantheon.ethereum.core.TransactionTestFixture; @@ -83,16 +84,6 @@ public class MainnetTransactionValidatorTest { .isEqualTo(ValidationResult.invalid(UPFRONT_COST_EXCEEDS_BALANCE)); } - @Test - public void shouldRejectTransactionWhenSenderAccountHasInsufficentBalance() { - final MainnetTransactionValidator validator = - new MainnetTransactionValidator(gasCalculator, false, 1); - - final Account account = accountWithBalance(basicTransaction.getUpfrontCost().minus(Wei.of(1))); - assertThat(validator.validateForSender(basicTransaction, account, OptionalLong.empty())) - .isEqualTo(ValidationResult.invalid(UPFRONT_COST_EXCEEDS_BALANCE)); - } - @Test public void shouldRejectTransactionWhenTransactionNonceBelowAccountNonce() { final MainnetTransactionValidator validator = @@ -152,6 +143,22 @@ public class MainnetTransactionValidatorTest { .isEqualTo(ValidationResult.invalid(INCORRECT_NONCE)); } + @Test + public void transactionWithNullSenderCanBeValidIfGasPriceAndValueIsZero() { + final MainnetTransactionValidator validator = + new MainnetTransactionValidator(gasCalculator, false, 1); + + final TransactionTestFixture builder = new TransactionTestFixture(); + final KeyPair senderKeyPair = KeyPair.generate(); + final Address arbitrarySender = Address.fromHexString("1"); + builder.gasPrice(Wei.ZERO).nonce(0).sender(arbitrarySender).value(Wei.ZERO); + + assertThat( + validator.validateForSender( + builder.createTransaction(senderKeyPair), null, OptionalLong.of(10))) + .isEqualTo(ValidationResult.valid()); + } + private Account accountWithNonce(final long nonce) { return account(basicTransaction.getUpfrontCost(), nonce); }