diff --git a/gossip/c_block_callbacks_test.go b/gossip/c_block_callbacks_test.go index 687f2ff11..769b85520 100644 --- a/gossip/c_block_callbacks_test.go +++ b/gossip/c_block_callbacks_test.go @@ -17,9 +17,10 @@ func TestConsensusCallback(t *testing.T) { logger.SetTestMode(t) require := require.New(t) - const rounds = 30 - - const validatorsNum = 3 + const ( + rounds = 30 + validatorsNum = 3 + ) env := newTestEnv(2, validatorsNum) defer env.Close() @@ -32,11 +33,10 @@ func TestConsensusCallback(t *testing.T) { for n := uint64(0); n < rounds; n++ { // transfers - txs := make([]*types.Transaction, validatorsNum) - for i := idx.Validator(0); i < validatorsNum; i++ { - from := i % validatorsNum - to := 0 - txs[i] = env.Transfer(idx.ValidatorID(from+1), idx.ValidatorID(to+1), utils.ToFtm(100)) + txs := make([]*types.Transaction, 0, validatorsNum-1) + for to, from := 0, 1; from < validatorsNum; from++ { + transfer := env.Transfer(idx.ValidatorID(from+1), idx.ValidatorID(to+1), utils.ToFtm(100)) + txs = append(txs, transfer) } tm := sameEpoch if n%10 == 0 { @@ -47,7 +47,7 @@ func TestConsensusCallback(t *testing.T) { // subtract fees for i, r := range rr { fee := big.NewInt(0).Mul(new(big.Int).SetUint64(r.GasUsed), txs[i].GasPrice()) - balances[i] = big.NewInt(0).Sub(balances[i], fee) + balances[i+1] = big.NewInt(0).Sub(balances[i+1], fee) } // balance movements balances[0].Add(balances[0], utils.ToFtm(200)) diff --git a/gossip/common_test.go b/gossip/common_test.go index 5d2c4b82c..bbed36242 100644 --- a/gossip/common_test.go +++ b/gossip/common_test.go @@ -8,12 +8,14 @@ import ( "math" "math/big" "sync" + "sync/atomic" "time" "github.com/Fantom-foundation/lachesis-base/abft" "github.com/Fantom-foundation/lachesis-base/hash" "github.com/Fantom-foundation/lachesis-base/inter/dag" "github.com/Fantom-foundation/lachesis-base/inter/idx" + "github.com/Fantom-foundation/lachesis-base/lachesis" "github.com/Fantom-foundation/lachesis-base/utils/cachescale" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi/bind" @@ -25,6 +27,7 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" + "github.com/Fantom-foundation/go-opera/ethapi" "github.com/Fantom-foundation/go-opera/evmcore" "github.com/Fantom-foundation/go-opera/gossip/blockproc" "github.com/Fantom-foundation/go-opera/gossip/emitter" @@ -40,8 +43,8 @@ import ( ) const ( - gasLimit = uint64(21000) - maxGasLimit = uint64(6000000) + gasPrice = uint64(1e12) + genesisBalance = 1e18 genesisStake = 2 * 4e6 @@ -60,8 +63,9 @@ type testEnv struct { nonces map[common.Address]uint64 callback callbacks *Service - signer valkeystore.SignerI - pubkeys []validatorpk.PubKey + transactor *ethapi.PublicTransactionPoolAPI + signer valkeystore.SignerI + pubkeys []validatorpk.PubKey } func panics(name string) func(error) { @@ -103,10 +107,15 @@ func (em testEmitterWorldExternal) Build(e *inter.MutableEventPayload, onIndexed if em.env.callback.buildEvent != nil { em.env.callback.buildEvent(e) } - return em.External.Build(e, onIndexed) + err := em.External.Build(e, onIndexed) + + return err } -func (em testEmitterWorldExternal) Broadcast(*inter.EventPayload) {} +func (em testEmitterWorldExternal) Broadcast(emitted *inter.EventPayload) { + // PM listens and will broadcast it + em.env.feed.newEmittedEvent.Send(emitted) +} type testConfirmedEventsProcessor struct { blockproc.ConfirmedEventsProcessor @@ -117,6 +126,7 @@ func (p testConfirmedEventsProcessor) ProcessConfirmedEvent(e inter.EventI) { if p.env.callback.onEventConfirmed != nil { p.env.callback.onEventConfirmed(e) } + p.ConfirmedEventsProcessor.ProcessConfirmedEvent(e) } @@ -155,14 +165,16 @@ func newTestEnv(firstEpoch idx.Epoch, validatorsNum idx.Validator) *testEnv { engine, vecClock := makeTestEngine(store) // create the service - txPool := &dummyTxPool{} + txPool := newDummyTxPool() env.Service, err = newService(DefaultConfig(cachescale.Identity), store, blockProc, engine, vecClock, func(_ evmcore.StateReader) TxPool { return txPool }) if err != nil { panic(err) } - txPool.signer = env.EthAPI.signer + txPool.Signer = env.Service.EthAPI.signer + env.transactor = ethapi.NewPublicTransactionPoolAPI(env.Service.EthAPI, new(ethapi.AddrLocker)) + err = engine.Bootstrap(env.GetConsensusCallbacks()) if err != nil { panic(err) @@ -191,6 +203,7 @@ func newTestEnv(firstEpoch idx.Epoch, validatorsNum idx.Validator) *testEnv { env.RegisterEmitter(em) env.pubkeys = append(env.pubkeys, pubkey) em.Start() + em.Stop() // to control emitting manually } _ = env.store.GenerateSnapshotAt(common.Hash(store.GetBlockState().FinalizedStateRoot), false) @@ -212,44 +225,127 @@ func (env *testEnv) GetEvmStateReader() *EvmStateReader { } } +func (env *testEnv) BlockTxs(spent time.Duration, txs ...*types.Transaction) (types.Receipts, error) { + // Just `env.applyTxs(spent, true, txs...)` does not work guaranteed because of gas rules. + // So we make single-event block and skip emitting process. + env.t = env.t.Add(spent) + + mutEvent := &inter.MutableEventPayload{} + mutEvent.SetVersion(1) // LLR + mutEvent.SetEpoch(env.store.GetEpoch()) + mutEvent.SetCreationTime(inter.Timestamp(env.t.UnixNano())) + mutEvent.SetTxs(types.Transactions(txs)) + event := mutEvent.Build() + env.store.SetEvent(event) + + consensus := env.Service.GetConsensusCallbacks() + blockCallback := consensus.BeginBlock(&lachesis.Block{ + Atropos: event.ID(), + Cheaters: make([]idx.ValidatorID, 0), + }) + blockCallback.ApplyEvent(event) + blockCallback.EndBlock() + env.blockProcWg.Wait() + + number := env.store.GetBlockIndex(event.ID()) + block := env.GetEvmStateReader().GetBlock(common.Hash{}, uint64(*number)) + receipts := env.store.evm.GetReceipts(*number, env.EthAPI.signer, block.Hash, block.Transactions) + return receipts, nil +} + func (env *testEnv) ApplyTxs(spent time.Duration, txs ...*types.Transaction) (types.Receipts, error) { + return env.applyTxs(spent, txs...) +} + +func (env *testEnv) applyTxs(spent time.Duration, txs ...*types.Transaction) (types.Receipts, error) { env.t = env.t.Add(spent) - externalReceipts := make(types.Receipts, 0, len(txs)) + waitForCount := int64(len(txs)) + waitForTxs := make(map[common.Hash]struct{}, len(txs)) + for _, tx := range txs { + waitForTxs[tx.Hash()] = struct{}{} + } + + wg := new(sync.WaitGroup) + wg.Add(1) + defer wg.Wait() + + receipts := make(types.Receipts, 0, len(txs)) - env.txpool.AddRemotes(txs) - defer env.txpool.(*dummyTxPool).Clear() newBlocks := make(chan evmcore.ChainHeadNotify) - chainHeadSub := env.feed.SubscribeNewBlock(newBlocks) - mu := &sync.Mutex{} + defer close(newBlocks) + blocksSub := env.feed.SubscribeNewBlock(newBlocks) + defer blocksSub.Unsubscribe() go func() { + defer wg.Done() for b := range newBlocks { - if len(b.Block.Transactions) == 0 { - continue - } - receipts := env.store.evm.GetReceipts(idx.Block(b.Block.Number.Uint64()), env.EthAPI.signer, b.Block.Hash, b.Block.Transactions) - for i, tx := range b.Block.Transactions { - if r, _, _ := tx.RawSignatureValues(); r.Sign() != 0 { - mu.Lock() - externalReceipts = append(externalReceipts, receipts[i]) - mu.Unlock() - env.txpool.(*dummyTxPool).Delete(tx.Hash()) + + n := idx.Block(b.Block.Number.Uint64()) + // valid txs + if len(b.Block.Transactions) > 0 { + rr := env.store.evm.GetReceipts(n, env.EthAPI.signer, b.Block.Hash, b.Block.Transactions) + for _, r := range rr { + env.txpool.(*dummyTxPool).Delete(r.TxHash) + if _, ok := waitForTxs[r.TxHash]; ok { + receipts = append(receipts, r) + delete(waitForTxs, r.TxHash) + atomic.AddInt64(&waitForCount, -1) + } } } - if externalReceipts.Len() == len(txs) { - chainHeadSub.Unsubscribe() - close(newBlocks) - break + // invalid txs + block := env.store.GetBlock(n) + if len(block.SkippedTxs) > 0 { + var ( + internalsLen = uint32(len(block.InternalTxs)) + externalsLen = uint32(len(block.InternalTxs) + len(block.Txs)) + eventstxsLen = uint32(len(block.InternalTxs) + len(block.Txs) + 0) + e = 0 + ) + for _, txi := range block.SkippedTxs { + var tx common.Hash + + switch { + case txi < internalsLen: + tx = block.InternalTxs[txi+0] + case txi < externalsLen: + tx = block.Txs[txi-internalsLen] + default: + for { + etxs := env.store.GetEventPayload(block.Events[e]).Txs() + if txi < (eventstxsLen + uint32(len(etxs))) { + tx = etxs[txi-eventstxsLen].Hash() + break + } else { + e += 1 // next event + eventstxsLen += uint32(len(etxs)) + } + } + } + env.txpool.(*dummyTxPool).Delete(tx) + if _, ok := waitForTxs[tx]; ok { + delete(waitForTxs, tx) + atomic.AddInt64(&waitForCount, -1) + } + } } } }() - err := env.EmitUntil(func() bool { - mu.Lock() - defer mu.Unlock() - return externalReceipts.Len() == len(txs) - }) - return externalReceipts, err + byEmitters := func() []*emitter.Emitter { + if atomic.LoadInt64(&waitForCount) > 0 { + // allow block creation + return env.emitters + } + // ready to stop + return nil + } + + env.txpool.AddRemotes(txs) + defer env.txpool.(*dummyTxPool).Clear() + err := env.EmitUntil(byEmitters) + + return receipts, err } func (env *testEnv) ApplyMPs(spent time.Duration, mps ...inter.MisbehaviourProof) error { @@ -280,16 +376,24 @@ func (env *testEnv) ApplyMPs(spent time.Duration, mps ...inter.MisbehaviourProof env.callback.buildEvent = nil }() - return env.EmitUntil(func() bool { - return confirmed - }) -} + byEmitters := func() []*emitter.Emitter { + if !confirmed { + return env.emitters + } + return nil + } -func (env *testEnv) EmitUntil(stop func() bool) error { - t := time.Now() + return env.EmitUntil(byEmitters) +} - for !stop() { - for _, em := range env.emitters { +func (env *testEnv) EmitUntil(by func() []*emitter.Emitter) error { + start := time.Now() + for { + emitters := by() + if len(emitters) < 1 { + break + } + for _, em := range emitters { _, err := em.EmitEvent() if err != nil { return err @@ -297,7 +401,7 @@ func (env *testEnv) EmitUntil(stop func() bool) error { } env.WaitBlockEnd() env.t = env.t.Add(time.Second) - if time.Since(t) > 30*time.Second { + if time.Since(start) > 30*time.Second { panic("block doesn't get processed") } } @@ -306,13 +410,24 @@ func (env *testEnv) EmitUntil(stop func() bool) error { func (env *testEnv) Transfer(from, to idx.ValidatorID, amount *big.Int) *types.Transaction { sender := env.Address(from) - nonce, _ := env.PendingNonceAt(nil, sender) - env.incNonce(sender) - key := env.privateKey(from) + nonce := env.nextNonce(sender) receiver := env.Address(to) - gp := new(big.Int).SetUint64(1e12) - tx := types.NewTransaction(nonce, receiver, amount, gasLimit, gp, nil) - tx, err := types.SignTx(tx, env.EthAPI.signer, key) + gasLimit := env.GetEvmStateReader().MaxGasLimit() + + raw, err := env.transactor.FillTransaction(context.Background(), ethapi.TransactionArgs{ + From: &sender, + To: &receiver, + Value: (*hexutil.Big)(amount), + Nonce: (*hexutil.Uint64)(&nonce), + Gas: (*hexutil.Uint64)(&gasLimit), + GasPrice: (*hexutil.Big)(new(big.Int).SetUint64(gasPrice)), + }) + if err != nil { + panic(err) + } + + key := env.privateKey(from) + tx, err := types.SignTx(raw.Tx, env.EthAPI.signer, key) if err != nil { panic(err) } @@ -322,13 +437,24 @@ func (env *testEnv) Transfer(from, to idx.ValidatorID, amount *big.Int) *types.T func (env *testEnv) Contract(from idx.ValidatorID, amount *big.Int, hex string) *types.Transaction { sender := env.Address(from) - nonce, _ := env.PendingNonceAt(nil, sender) - env.incNonce(sender) - key := env.privateKey(from) - gp := env.store.GetRules().Economy.MinGasPrice + nonce := env.nextNonce(sender) + gasLimit := env.GetEvmStateReader().MaxGasLimit() data := hexutil.MustDecode(hex) - tx := types.NewContractCreation(nonce, amount, maxGasLimit, gp, data) - tx, err := types.SignTx(tx, env.EthAPI.signer, key) + + raw, err := env.transactor.FillTransaction(context.Background(), ethapi.TransactionArgs{ + From: &sender, + Value: (*hexutil.Big)(amount), + Nonce: (*hexutil.Uint64)(&nonce), + Gas: (*hexutil.Uint64)(&gasLimit), + GasPrice: (*hexutil.Big)(new(big.Int).SetUint64(gasPrice)), + Data: (*hexutil.Bytes)(&data), + }) + if err != nil { + panic(err) + } + + key := env.privateKey(from) + tx, err := types.SignTx(raw.Tx, env.EthAPI.signer, key) if err != nil { panic(err) } @@ -347,26 +473,66 @@ func (env *testEnv) Address(n idx.ValidatorID) common.Address { return addr } -func (env *testEnv) Payer(n idx.ValidatorID, amounts ...*big.Int) *bind.TransactOpts { - key := env.privateKey(n) - t, _ := bind.NewKeyedTransactorWithChainID(key, new(big.Int).SetUint64(env.store.GetRules().NetworkID)) - nonce, _ := env.PendingNonceAt(nil, env.Address(n)) - t.Nonce = big.NewInt(int64(nonce)) - t.Value = big.NewInt(0) - for _, amount := range amounts { - t.Value.Add(t.Value, amount) +func (env *testEnv) Pay(from idx.ValidatorID, amounts ...*big.Int) *bind.TransactOpts { + sender := env.Address(from) + nonce := env.nextNonce(sender) + amount := big.NewInt(0) + for _, a := range amounts { + amount.Add(amount, a) + } + gasLimit := env.GetEvmStateReader().MaxGasLimit() + data := []byte{0} // fake + + raw, err := env.transactor.FillTransaction(context.Background(), ethapi.TransactionArgs{ + From: &sender, + Value: (*hexutil.Big)(amount), + Nonce: (*hexutil.Uint64)(&nonce), + Gas: (*hexutil.Uint64)(&gasLimit), + GasPrice: (*hexutil.Big)(new(big.Int).SetUint64(gasPrice)), + Data: (*hexutil.Bytes)(&data), + }) + if err != nil { + panic(err) } - t.GasLimit = env.GetEvmStateReader().MaxGasLimit() - t.GasPrice = env.GetEvmStateReader().MinGasPrice() + key := env.privateKey(from) + t, _ := bind.NewKeyedTransactorWithChainID(key, new(big.Int).SetUint64(env.store.GetRules().NetworkID)) + { + t.Nonce = big.NewInt(int64(nonce)) + t.Value = amount + t.NoSend = true + t.GasLimit = raw.Tx.Gas() + t.GasPrice = raw.Tx.GasPrice() + //t.GasFeeCap = raw.Tx.GasFeeCap() + //t.GasTipCap = raw.Tx.GasTipCap() + } return t } -func (env *testEnv) Pay(n idx.ValidatorID, amounts ...*big.Int) *bind.TransactOpts { - t := env.Payer(n, amounts...) - env.incNonce(t.From) +func withLowGas(opts *bind.TransactOpts) *bind.TransactOpts { + originSigner := opts.Signer + + opts.Signer = func(from common.Address, tx *types.Transaction) (*types.Transaction, error) { + gas, err := evmcore.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil) + if err != nil { + return nil, err + } + if tx.Gas() >= gas { + repack := &types.LegacyTx{ + To: tx.To(), + Nonce: tx.Nonce(), + GasPrice: tx.GasPrice(), + Gas: gas + 1, + Value: tx.Value(), + Data: tx.Data(), + } + tx = types.NewTx(repack) + } + return originSigner(from, tx) + } + + return opts - return t } func (env *testEnv) ReadOnly() *bind.CallOpts { @@ -378,8 +544,10 @@ func (env *testEnv) State() *state.StateDB { return statedb } -func (env *testEnv) incNonce(account common.Address) { - env.nonces[account] += 1 +func (env *testEnv) nextNonce(account common.Address) uint64 { + nonce := env.nonces[account] + env.nonces[account] = nonce + 1 + return nonce } /* @@ -496,12 +664,8 @@ func (env *testEnv) SuggestGasPrice(ctx context.Context) (*big.Int, error) { // transactions may be added or removed by miners, but it should provide a basis // for setting a reasonable default. func (env *testEnv) EstimateGas(ctx context.Context, call ethereum.CallMsg) (gas uint64, err error) { - if call.To == nil { - gas = gasLimit * 10000 - } else { - gas = gasLimit * 10 - } - return + panic("is not implemented") + return 0, nil } // SendTransaction injects the transaction into the pending pool for execution. diff --git a/gossip/dummy_tx_pool.go b/gossip/dummy_tx_pool.go index b9397ceeb..035bb5465 100644 --- a/gossip/dummy_tx_pool.go +++ b/gossip/dummy_tx_pool.go @@ -1,7 +1,6 @@ package gossip import ( - "math/rand" "sort" "sync" @@ -14,13 +13,17 @@ import ( // dummyTxPool is a fake, helper transaction pool for testing purposes type dummyTxPool struct { - txFeed notify.Feed - pool []*types.Transaction // Collection of all transactions - added chan<- []*types.Transaction // Notification channel for new transactions + lock sync.RWMutex // Protects the transaction pool + Signer types.Signer - signer types.Signer + index map[common.Hash]common.Address + pending map[common.Address]types.Transactions +} - lock sync.RWMutex // Protects the transaction pool +func newDummyTxPool() *dummyTxPool { + p := new(dummyTxPool) + p.Clear() + return p } // AddRemotes appends a batch of transactions to the pool, and notifies any @@ -29,35 +32,40 @@ func (p *dummyTxPool) AddRemotes(txs []*types.Transaction) []error { p.lock.Lock() defer p.lock.Unlock() - p.pool = append(p.pool, txs...) - if p.added != nil { - p.added <- txs + errs := make([]error, 0, len(txs)) + for _, tx := range txs { + txid := tx.Hash() + if _, ok := p.index[txid]; ok { + continue + } + from, err := types.Sender(p.Signer, tx) + if err == nil { + p.index[txid] = from + p.pending[from] = append(p.pending[from], tx) + sort.Sort(types.TxByNonce(p.pending[from])) + } + errs = append(errs, err) } - return make([]error, len(txs)) + return errs } -func (p *dummyTxPool) AddLocals(txs []*types.Transaction) []error { - return p.AddRemotes(txs) -} +func (p *dummyTxPool) Count() int { + p.lock.Lock() + defer p.lock.Unlock() -func (p *dummyTxPool) AddLocal(tx *types.Transaction) error { - return p.AddLocals([]*types.Transaction{tx})[0] -} - -func (p *dummyTxPool) Nonce(addr common.Address) uint64 { - return 0 + return len(p.index) } func (p *dummyTxPool) Stats() (int, int) { return p.Count(), 0 } -func (p *dummyTxPool) Content() (map[common.Address]types.Transactions, map[common.Address]types.Transactions) { - return nil, nil -} +func (p *dummyTxPool) Has(txid common.Hash) bool { + p.lock.RLock() + defer p.lock.RUnlock() -func (p *dummyTxPool) ContentFrom(addr common.Address) (types.Transactions, types.Transactions) { - return nil, nil + _, ok := p.index[txid] + return ok } // Pending returns all the transactions known to the pool @@ -65,105 +73,101 @@ func (p *dummyTxPool) Pending(enforceTips bool) (map[common.Address]types.Transa p.lock.RLock() defer p.lock.RUnlock() - batches := make(map[common.Address]types.Transactions) - for _, tx := range p.pool { - from, _ := types.Sender(p.signer, tx) - batches[from] = append(batches[from], tx) - } - for _, batch := range batches { - sort.Sort(types.TxByNonce(batch)) + return p.clonePending(), nil +} + +func (p *dummyTxPool) clonePending() map[common.Address]types.Transactions { + clone := make(map[common.Address]types.Transactions, len(p.pending)) + + for from, txs := range p.pending { + tt := make(types.Transactions, len(txs)) + for i, tx := range txs { + tt[i] = tx + } + clone[from] = tt } - return batches, nil + + return clone } -func (p *dummyTxPool) SubscribeNewTxsNotify(ch chan<- evmcore.NewTxsNotify) notify.Subscription { - return p.txFeed.Subscribe(ch) +func (p *dummyTxPool) Clear() { + p.lock.Lock() + defer p.lock.Unlock() + + p.index = make(map[common.Hash]common.Address) + p.pending = make(map[common.Address]types.Transactions) } -func (p *dummyTxPool) Map() map[common.Hash]*types.Transaction { - p.lock.RLock() - defer p.lock.RUnlock() - res := make(map[common.Hash]*types.Transaction, len(p.pool)) - for _, tx := range p.pool { - res[tx.Hash()] = tx +func (p *dummyTxPool) Delete(needle common.Hash) { + p.lock.Lock() + defer p.lock.Unlock() + + from, ok := p.index[needle] + if !ok { + return } - return nil -} -func (p *dummyTxPool) Get(txid common.Hash) *types.Transaction { - p.lock.RLock() - defer p.lock.RUnlock() - for _, tx := range p.pool { - if tx.Hash() == txid { - return tx + txs := p.pending[from] + if len(txs) < 2 { + delete(p.pending, from) + } else { + notErased := make(types.Transactions, 0, len(txs)-1) + for _, tx := range txs { + if tx.Hash() != needle { + notErased = append(notErased, tx) + } } + p.pending[from] = notErased } + delete(p.index, needle) +} + +func (p *dummyTxPool) AddLocals(txs []*types.Transaction) []error { + panic("is not implemented") return nil } -func (p *dummyTxPool) Has(txid common.Hash) bool { - p.lock.RLock() - defer p.lock.RUnlock() - for _, tx := range p.pool { - if tx.Hash() == txid { - return true - } - } - return false +func (p *dummyTxPool) AddLocal(tx *types.Transaction) error { + panic("is not implemented") + return nil } -func (p *dummyTxPool) OnlyNotExisting(txids []common.Hash) []common.Hash { - m := p.Map() - notExisting := make([]common.Hash, 0, len(txids)) - for _, txid := range txids { - if m[txid] == nil { - notExisting = append(notExisting, txid) - } - } - return notExisting +func (p *dummyTxPool) Nonce(addr common.Address) uint64 { + panic("is not implemented") + return 0 } -func (p *dummyTxPool) SampleHashes(max int) []common.Hash { - p.lock.RLock() - defer p.lock.RUnlock() - res := make([]common.Hash, 0, max) - skip := 0 - if len(p.pool) > max { - skip = rand.Intn(len(p.pool) - max) - } - for _, tx := range p.pool { - if len(res) >= max { - break - } - if skip > 0 { - skip-- - continue - } - res = append(res, tx.Hash()) - } - return res +func (p *dummyTxPool) Content() (map[common.Address]types.Transactions, map[common.Address]types.Transactions) { + panic("is not implemented") + return nil, nil } -func (p *dummyTxPool) Count() int { - return len(p.pool) +func (p *dummyTxPool) ContentFrom(addr common.Address) (types.Transactions, types.Transactions) { + panic("is not implemented") + return nil, nil } -func (p *dummyTxPool) Clear() { - p.lock.Lock() - defer p.lock.Unlock() - if len(p.pool) != 0 { - p.pool = p.pool[:0] - } +func (p *dummyTxPool) SubscribeNewTxsNotify(ch chan<- evmcore.NewTxsNotify) notify.Subscription { + panic("is not implemented") + return nil } -func (p *dummyTxPool) Delete(needle common.Hash) { - p.lock.Lock() - defer p.lock.Unlock() - notErased := make([]*types.Transaction, 0, len(p.pool)-1) - for _, tx := range p.pool { - if tx.Hash() != needle { - notErased = append(notErased, tx) - } - } - p.pool = notErased +func (p *dummyTxPool) Map() map[common.Hash]*types.Transaction { + panic("is not implemented") + return nil +} + +func (p *dummyTxPool) Get(txid common.Hash) *types.Transaction { + panic("is not implemented") + return nil +} + +func (p *dummyTxPool) OnlyNotExisting(txids []common.Hash) []common.Hash { + panic("is not implemented") + return nil +} + +func (p *dummyTxPool) SampleHashes(max int) []common.Hash { + panic("is not implemented") + return nil } diff --git a/gossip/txposition_test.go b/gossip/txposition_test.go new file mode 100644 index 000000000..683e36d76 --- /dev/null +++ b/gossip/txposition_test.go @@ -0,0 +1,105 @@ +package gossip + +import ( + "math/big" + "testing" + + "github.com/Fantom-foundation/lachesis-base/inter/idx" + "github.com/ethereum/go-ethereum/core/types" + "github.com/stretchr/testify/require" + + "github.com/Fantom-foundation/go-opera/gossip/contract/ballot" + "github.com/Fantom-foundation/go-opera/logger" +) + +func TestTxIndexing(t *testing.T) { + logger.SetTestMode(t) + logger.SetLevel("debug") + require := require.New(t) + + var ( + epoch = idx.Epoch(256) + validators = idx.Validator(3) + proposals = [][32]byte{ + ballotOption("Option 1"), + ballotOption("Option 2"), + ballotOption("Option 3"), + } + ) + + env := newTestEnv(epoch, validators) + defer env.Close() + + // Preparing: + + _, tx1pre, cBallot, err := ballot.DeployBallot(env.Pay(1), env, proposals) + require.NoError(err) + require.NotNil(cBallot) + require.NotNil(tx1pre) + tx2pre, err := cBallot.GiveRightToVote(env.Pay(1), env.Address(3)) + require.NoError(err) + require.NotNil(tx2pre) + receipts, err := env.ApplyTxs(sameEpoch, + tx1pre, + tx2pre, + ) + require.NoError(err) + require.Len(receipts, 2) + for i, r := range receipts { + require.Equal(types.ReceiptStatusSuccessful, r.Status, i) + } + + // Testing: + + // invalid tx + tx1reverted, err := cBallot.Vote(env.Pay(2), big.NewInt(0)) + require.NoError(err) + require.NotNil(tx1reverted) + // valid tx + tx2ok, err := cBallot.Vote(env.Pay(3), big.NewInt(0)) + require.NoError(err) + require.NotNil(tx2ok) + // skipped tx + tx3skipped := tx2ok + // valid tx + tx4ok, err := cBallot.GiveRightToVote(env.Pay(1), env.Address(2)) + require.NoError(err) + require.NotNil(tx1reverted) + + receipts, err = env.BlockTxs(sameEpoch, + tx1reverted, + tx2ok, + tx3skipped, + tx4ok) + require.NoError(err) + require.Len(receipts, 3) + + var blockN *big.Int + for i, r := range receipts { + if blockN == nil { + blockN = r.BlockNumber + } + require.Equal(blockN.Uint64(), r.BlockNumber.Uint64(), i) + + txPos := env.store.evm.GetTxPosition(r.TxHash) + require.NotNil(txPos) + + switch r.TxHash { + case tx1reverted.Hash(): + require.Equal(types.ReceiptStatusFailed, r.Status, i) + require.Equal(txPos.BlockOffset, uint32(0)) + case tx2ok.Hash(): + require.Equal(types.ReceiptStatusSuccessful, r.Status, i) + require.Equal(txPos.BlockOffset, uint32(1)) + case tx3skipped.Hash(): + t.Fatal("skipped tx's receipt found") + case tx4ok.Hash(): + require.Equal(types.ReceiptStatusSuccessful, r.Status, i) + require.Equal(txPos.BlockOffset, uint32(2)) // skipped txs aren't counted + } + + for j, l := range r.Logs { + require.Equal(txPos.BlockOffset, l.TxIndex, j) + } + } +}