diff --git a/action/protocol/context.go b/action/protocol/context.go index 4d724cce9a..85ef6084b6 100644 --- a/action/protocol/context.go +++ b/action/protocol/context.go @@ -110,7 +110,7 @@ type ( CorrectGasRefund bool FixRewardErroCheckPosition bool EnableWeb3Rewarding bool - EnableNodeInfo bool + SkipSystemActionNonce bool } // FeatureWithHeightCtx provides feature check functions. @@ -247,7 +247,7 @@ func WithFeatureCtx(ctx context.Context) context.Context { CorrectGasRefund: g.IsOkhotsk(height), FixRewardErroCheckPosition: g.IsOkhotsk(height), EnableWeb3Rewarding: g.IsPalau(height), - EnableNodeInfo: g.IsPalau(height), + SkipSystemActionNonce: g.IsPalau(height), }, ) } diff --git a/blockchain/integrity/benchmark_test.go b/blockchain/integrity/benchmark_test.go index fb98b24a6e..6bc972d072 100644 --- a/blockchain/integrity/benchmark_test.go +++ b/blockchain/integrity/benchmark_test.go @@ -289,7 +289,6 @@ func newChainInDB() (blockchain.Blockchain, actpool.ActPool, error) { var genesisNonce uint64 = 0 // make a transfer from genesisAccount to a and b,because stateTX cannot store data in height 0 - genesisNonce++ tsf, err := action.SignedTransfer(userA.String(), genesisPriKey, genesisNonce, big.NewInt(1e17), []byte{}, testutil.TestGasLimit, big.NewInt(testutil.TestGasPriceInt64)) if err != nil { return nil, nil, err diff --git a/blockchain/integrity/integrity_test.go b/blockchain/integrity/integrity_test.go index 1678e5e5da..77ce5a42a1 100644 --- a/blockchain/integrity/integrity_test.go +++ b/blockchain/integrity/integrity_test.go @@ -1466,9 +1466,6 @@ func TestBlockchain_AccountState(t *testing.T) { require := require.New(t) cfg := config.Default - // disable account-based testing - // create chain - cfg.Genesis.InitBalanceMap[identityset.Address(0).String()] = "100" ctx := genesis.WithGenesisContext(context.Background(), cfg.Genesis) registry := protocol.NewRegistry() acc := account.NewProtocol(rewarding.DepositGas) @@ -1480,14 +1477,104 @@ func TestBlockchain_AccountState(t *testing.T) { require.NoError(err) dao := blockdao.NewBlockDAOInMemForTest([]blockdao.BlockIndexer{sf}) bc := blockchain.NewBlockchain(cfg.Chain, cfg.Genesis, dao, factory.NewMinter(sf, ap)) - require.NoError(bc.Start(context.Background())) + require.NoError(bc.Start(ctx)) require.NotNil(bc) + defer func() { + require.NoError(bc.Stop(ctx)) + }() s, err := accountutil.AccountState(ctx, sf, identityset.Address(0)) require.NoError(err) require.Equal(uint64(1), s.PendingNonce()) - require.Equal(big.NewInt(100), s.Balance) - require.Equal(hash.ZeroHash256, s.Root) - require.Equal([]byte(nil), s.CodeHash) + require.Equal(unit.ConvertIotxToRau(100000000), s.Balance) + require.Zero(s.Root) + require.Nil(s.CodeHash) +} + +func TestNewAccountAction(t *testing.T) { + require := require.New(t) + + cfg := config.Default + cfg.Genesis.OkhotskBlockHeight = 1 + ctx := genesis.WithGenesisContext(context.Background(), cfg.Genesis) + registry := protocol.NewRegistry() + acc := account.NewProtocol(rewarding.DepositGas) + require.NoError(acc.Register(registry)) + factoryCfg := factory.GenerateConfig(cfg.Chain, cfg.Genesis) + sf, err := factory.NewFactory(factoryCfg, db.NewMemKVStore(), factory.RegistryOption(registry)) + require.NoError(err) + ap, err := actpool.NewActPool(cfg.Genesis, sf, cfg.ActPool) + require.NoError(err) + dao := blockdao.NewBlockDAOInMemForTest([]blockdao.BlockIndexer{sf}) + bc := blockchain.NewBlockchain(cfg.Chain, cfg.Genesis, dao, factory.NewMinter(sf, ap)) + require.NoError(bc.Start(ctx)) + require.NotNil(bc) + defer func() { + require.NoError(bc.Stop(ctx)) + }() + + // create a new address, transfer 4 IOTX + newSk, err := iotexcrypto.HexStringToPrivateKey("55499c1b09f687488af9e4ee9e2bd53c7c8c3ddc69d4d9345a04b13030cffabe") + require.NoError(err) + newAddr := newSk.PublicKey().Address() + tx, err := action.SignedTransfer(newAddr.String(), identityset.PrivateKey(0), 1, big.NewInt(4*unit.Iotx), nil, testutil.TestGasLimit, testutil.TestGasPrice) + require.NoError(err) + require.NoError(ap.Add(ctx, tx)) + blk, err := bc.MintNewBlock(testutil.TimestampNow()) + require.NoError(err) + require.NoError(bc.CommitBlock(blk)) + ap.Reset() + + // initiate transfer from new address + tx, err = action.SignedTransfer(identityset.Address(0).String(), newSk, 0, big.NewInt(unit.Iotx), nil, testutil.TestGasLimit, testutil.TestGasPrice) + require.NoError(err) + require.NoError(ap.Add(ctx, tx)) + tx1, err := action.SignedTransfer(identityset.Address(1).String(), newSk, 1, big.NewInt(unit.Iotx), nil, testutil.TestGasLimit, testutil.TestGasPrice) + require.NoError(err) + require.NoError(ap.Add(ctx, tx1)) + blk1, err := bc.MintNewBlock(testutil.TimestampNow()) + require.NoError(err) + require.NoError(bc.CommitBlock(blk1)) + ap.Reset() + + // commit 2 blocks into a new chain + for _, validateNonce := range []bool{false, true} { + if validateNonce { + cfg.Genesis.PalauBlockHeight = 2 + } else { + cfg.Genesis.PalauBlockHeight = 20 + } + ctx = genesis.WithGenesisContext(context.Background(), cfg.Genesis) + factoryCfg = factory.GenerateConfig(cfg.Chain, cfg.Genesis) + sf1, err := factory.NewFactory(factoryCfg, db.NewMemKVStore(), factory.RegistryOption(registry)) + require.NoError(err) + dao1 := blockdao.NewBlockDAOInMemForTest([]blockdao.BlockIndexer{sf1}) + bc1 := blockchain.NewBlockchain(cfg.Chain, cfg.Genesis, dao1, factory.NewMinter(sf1, ap)) + require.NoError(bc1.Start(ctx)) + require.NotNil(bc1) + defer func() { + require.NoError(bc1.Stop(ctx)) + }() + require.NoError(bc1.CommitBlock(blk)) + err = bc1.CommitBlock(blk1) + if validateNonce { + require.NoError(err) + } else { + require.Equal(action.ErrNonceTooHigh, errors.Cause(err)) + } + + // verify new addr + s, err := accountutil.AccountState(ctx, sf1, newAddr) + require.NoError(err) + if validateNonce { + require.EqualValues(2, s.PendingNonce()) + require.Equal(big.NewInt(2*unit.Iotx), s.Balance) + } else { + require.Zero(s.PendingNonce()) + require.Equal(big.NewInt(4*unit.Iotx), s.Balance) + } + require.Zero(s.Root) + require.Nil(s.CodeHash) + } } func TestBlocks(t *testing.T) { diff --git a/state/factory/workingset.go b/state/factory/workingset.go index b47f3cee0d..4ef6ad264a 100644 --- a/state/factory/workingset.go +++ b/state/factory/workingset.go @@ -333,11 +333,30 @@ func (ws *workingSet) validateNonce(ctx context.Context, blk *block.Block) error } appendActionIndex(accountNonceMap, caller.String(), selp.Nonce()) } + return ws.checkNonceContinuity(ctx, accountNonceMap) +} - // Special handling for genesis block - if blk.Height() == 0 { - return nil +func (ws *workingSet) validateNonceSkipSystemAction(ctx context.Context, blk *block.Block) error { + accountNonceMap := make(map[string][]uint64) + for _, selp := range blk.Actions { + if action.IsSystemAction(selp) { + continue + } + + caller := selp.SenderAddress() + if caller == nil { + return errors.New("failed to get address") + } + srcAddr := caller.String() + if _, ok := accountNonceMap[srcAddr]; !ok { + accountNonceMap[srcAddr] = make([]uint64, 0) + } + accountNonceMap[srcAddr] = append(accountNonceMap[srcAddr], selp.Nonce()) } + return ws.checkNonceContinuity(ctx, accountNonceMap) +} + +func (ws *workingSet) checkNonceContinuity(ctx context.Context, accountNonceMap map[string][]uint64) error { // Verify each account's Nonce for srcAddr, receivedNonces := range accountNonceMap { addr, _ := address.FromString(srcAddr) @@ -345,14 +364,13 @@ func (ws *workingSet) validateNonce(ctx context.Context, blk *block.Block) error if err != nil { return errors.Wrapf(err, "failed to get the confirmed nonce of address %s", srcAddr) } - receivedNonces := receivedNonces sort.Slice(receivedNonces, func(i, j int) bool { return receivedNonces[i] < receivedNonces[j] }) pendingNonce := confirmedState.PendingNonce() for i, nonce := range receivedNonces { if nonce != pendingNonce+uint64(i) { return errors.Wrapf( action.ErrNonceTooHigh, - "the %d nonce %d of address %s (init pending nonce %d) is not continuously increasing", + "the %d-th nonce %d of address %s (init pending nonce %d) is not continuously increasing", i, nonce, srcAddr, @@ -517,8 +535,14 @@ func updateReceiptIndex(receipts []*action.Receipt) { } func (ws *workingSet) ValidateBlock(ctx context.Context, blk *block.Block) error { - if err := ws.validateNonce(ctx, blk); err != nil { - return errors.Wrap(err, "failed to validate nonce") + if protocol.MustGetFeatureCtx(ctx).SkipSystemActionNonce { + if err := ws.validateNonceSkipSystemAction(ctx, blk); err != nil { + return errors.Wrap(err, "failed to validate nonce") + } + } else { + if err := ws.validateNonce(ctx, blk); err != nil { + return errors.Wrap(err, "failed to validate nonce") + } } if err := ws.process(ctx, blk.RunnableActions().Actions()); err != nil { log.L().Error("Failed to update state.", zap.Uint64("height", ws.height), zap.Error(err)) diff --git a/testutil/timestamp_test.go b/testutil/timestamp_test.go index 4a471cc1ee..7f84b0518a 100644 --- a/testutil/timestamp_test.go +++ b/testutil/timestamp_test.go @@ -15,5 +15,5 @@ import ( func TestTimestamp(t *testing.T) { assert := assert.New(t) time1 := TimestampNow() - assert.True(time.Now().After(time1)) + assert.False(time.Now().Before(time1)) }