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 <adrian.sutton@consensys.net>
pull/2/head
tmohay 6 years ago committed by GitHub
parent b28258b8c4
commit 5e8ecff6bc
  1. 3
      ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/core/Account.java
  2. 2
      ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/core/WorldUpdater.java
  3. 25
      ethereum/core/src/main/java/tech/pegasys/pantheon/ethereum/mainnet/MainnetTransactionValidator.java
  4. 27
      ethereum/core/src/test/java/tech/pegasys/pantheon/ethereum/mainnet/MainnetTransactionValidatorTest.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.
*

@ -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);
}
/**

@ -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<TransactionInvalidReason> 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();

@ -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);
}

Loading…
Cancel
Save