Commit 982d73c3 authored by Ralph Pichler's avatar Ralph Pichler Committed by GitHub

feat: add greylist to sender matcher (#2161)

parent 55518ade
......@@ -4,6 +4,18 @@
package transaction
import "time"
var (
StoredTransactionKey = storedTransactionKey
)
func (s *Matcher) SetTimeNow(f func() time.Time) {
s.timeNow = f
}
func (s *Matcher) SetTime(k int64) {
s.SetTimeNow(func() time.Time {
return time.Unix(k, 0)
})
}
......@@ -19,28 +19,30 @@ type Matcher struct {
backend Backend
storage storage.StateStorer
signer types.Signer
timeNow func() time.Time
}
const (
overlayPrefix = "verified_overlay_"
)
func peerOverlayKey(peer swarm.Address) string {
return fmt.Sprintf("%s%s", overlayPrefix, peer.String())
func peerOverlayKey(peer swarm.Address, txHash common.Hash) string {
return fmt.Sprintf("%s%s_%s", overlayPrefix, peer.String(), txHash.String())
}
var (
ErrTransactionNotFound = errors.New("transaction not found")
ErrTransactionPending = errors.New("transaction in pending status")
ErrTransactionSenderInvalid = errors.New("invalid transaction sender")
ErrGreylisted = errors.New("overlay and transaction greylisted")
ErrBlockHashMismatch = errors.New("block hash mismatch")
ErrOverlayMismatch = errors.New("overlay mismatch")
)
type overlayVerification struct {
nextBlockHash []byte
verified bool
timeStamp time.Time
NextBlockHash []byte
Verified bool
TimeStamp time.Time
}
func NewMatcher(backend Backend, signer types.Signer, storage storage.StateStorer) *Matcher {
......@@ -48,23 +50,34 @@ func NewMatcher(backend Backend, signer types.Signer, storage storage.StateStore
storage: storage,
backend: backend,
signer: signer,
timeNow: time.Now,
}
}
func (m *Matcher) Matches(ctx context.Context, tx []byte, networkID uint64, senderOverlay swarm.Address) ([]byte, error) {
var val overlayVerification
incomingTx := common.BytesToHash(tx)
err := m.storage.Get(peerOverlayKey(senderOverlay), &val)
if err != nil && !errors.Is(err, storage.ErrNotFound) {
return nil, err
} else if val.verified {
var val overlayVerification
err := m.storage.Get(peerOverlayKey(senderOverlay, incomingTx), &val)
if err != nil {
if !errors.Is(err, storage.ErrNotFound) {
return nil, err
}
} else if val.Verified {
// add cache invalidation
return val.nextBlockHash, nil
return val.NextBlockHash, nil
} else if val.TimeStamp.Add(5 * time.Minute).After(m.timeNow()) {
return nil, ErrGreylisted
}
incomingTx := common.BytesToHash(tx)
err = m.storage.Put(peerOverlayKey(senderOverlay, incomingTx), &overlayVerification{
TimeStamp: m.timeNow(),
Verified: false,
})
if err != nil {
return nil, err
}
nTx, isPending, err := m.backend.TransactionByHash(ctx, incomingTx)
if err != nil {
......@@ -104,10 +117,10 @@ func (m *Matcher) Matches(ctx context.Context, tx []byte, networkID uint64, send
return nil, ErrOverlayMismatch
}
err = m.storage.Put(peerOverlayKey(senderOverlay), &overlayVerification{
timeStamp: time.Now(),
verified: true,
nextBlockHash: nextBlockHash,
err = m.storage.Put(peerOverlayKey(senderOverlay, incomingTx), &overlayVerification{
TimeStamp: m.timeNow(),
Verified: true,
NextBlockHash: nextBlockHash,
})
if err != nil {
......
......@@ -137,6 +137,83 @@ func TestMatchesSender(t *testing.T) {
t.Fatalf("expected match")
}
})
t.Run("cached", func(t *testing.T) {
trxBlock := common.HexToHash("0x2")
nextBlockHeader := &types.Header{
ParentHash: trxBlock,
}
calls := 0
trxReceipt := backendmock.WithTransactionReceiptFunc(func(ctx context.Context, txHash common.Hash) (*types.Receipt, error) {
calls++
return &types.Receipt{
BlockNumber: big.NewInt(0),
BlockHash: trxBlock,
}, nil
})
headerByNum := backendmock.WithHeaderbyNumberFunc(func(ctx context.Context, number *big.Int) (*types.Header, error) {
calls++
return nextBlockHeader, nil
})
txByHash := backendmock.WithTransactionByHashFunc(func(ctx context.Context, txHash common.Hash) (*types.Transaction, bool, error) {
calls++
return signedTx, false, nil
})
signer := &mockSigner{
addr: common.HexToAddress("0xff"),
}
matcher := transaction.NewMatcher(backendmock.New(trxReceipt, headerByNum, txByHash), signer, statestore.NewStateStore())
senderOverlay := crypto.NewOverlayFromEthereumAddress(signer.addr.Bytes(), 0, nextBlockHeader.Hash().Bytes())
_, err := matcher.Matches(context.Background(), trx, 0, senderOverlay)
if err != nil {
t.Fatalf("expected match")
}
_, err = matcher.Matches(context.Background(), trx, 0, senderOverlay)
if err != nil {
t.Fatalf("expected match")
}
if calls != 3 {
t.Fatal("too many calls")
}
})
t.Run("greylisted", func(t *testing.T) {
txByHash := backendmock.WithTransactionByHashFunc(func(ctx context.Context, txHash common.Hash) (*types.Transaction, bool, error) {
return nil, false, errors.New("transaction not found by hash")
})
matcher := transaction.NewMatcher(backendmock.New(txByHash), nil, statestore.NewStateStore())
matcher.SetTime(0)
_, err := matcher.Matches(context.Background(), trx, 0, swarm.NewAddress([]byte{}))
if !errors.Is(err, transaction.ErrTransactionNotFound) {
t.Fatalf("bad error type, want %v, got %v", transaction.ErrTransactionNotFound, err)
}
_, err = matcher.Matches(context.Background(), trx, 0, swarm.NewAddress([]byte{}))
if !errors.Is(err, transaction.ErrGreylisted) {
t.Fatalf("bad error type, want %v, got %v", transaction.ErrGreylisted, err)
}
// greylist expires
matcher.SetTime(5 * 60)
_, err = matcher.Matches(context.Background(), trx, 0, swarm.NewAddress([]byte{}))
if !errors.Is(err, transaction.ErrTransactionNotFound) {
t.Fatalf("bad error type, want %v, got %v", transaction.ErrTransactionNotFound, err)
}
})
}
type mockSigner struct {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment