Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .ubsignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Deploy scripts use console.log intentionally for progress output and
# readFileSync/writeFileSync appropriately for synchronous deploy workflows.
# These are not production application code; UBS warnings are false positives.
solidity/ecdsa/deploy/16_initialize_allowlist_weights.ts
13 changes: 12 additions & 1 deletion pkg/bitcoin/transaction_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,18 @@ func (tb *TransactionBuilder) getScript(
)
}

return transaction.Outputs[utxo.Outpoint.OutputIndex].PublicKeyScript, nil
outputIndex := utxo.Outpoint.OutputIndex
if outputIndex >= uint32(len(transaction.Outputs)) {
return nil, fmt.Errorf(
"output index [%d] out of range for transaction [%s] "+
"with [%d] outputs",
outputIndex,
hash.Hex(InternalByteOrder),
len(transaction.Outputs),
)
}

return transaction.Outputs[outputIndex].PublicKeyScript, nil
}

// AddOutput adds a new transaction's output.
Expand Down
30 changes: 30 additions & 0 deletions pkg/bitcoin/transaction_builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"math/big"
"reflect"
"strings"
"testing"

"github.com/keep-network/keep-core/internal/testutils"
Expand Down Expand Up @@ -110,6 +111,35 @@ func TestTransactionBuilder_AddPublicKeyHashInput(t *testing.T) {
}
}

func TestTransactionBuilder_AddInputReturnsErrorForOutOfRangeOutputIndex(
t *testing.T,
) {
localChain := newLocalChain()
builder := NewTransactionBuilder(localChain)

inputTransaction := transactionFrom(
t,
"01000000012d4e0b1ef0bf21eed32f6e2f11353b78534dcf21852d506f6f53b64bb5c6b4c500000000c84730440220590e998a5c28965fd442e700445a60c494124fdbb8aa39cc20c04f2aedadb1a602206acb2f852cd7adea65fe9209024e18d2d6ccac0b1e45c61d80c9bcd62f3e5a12012103989d253b17a6a0f41838b84ff0d20e8898f9d7b1a98f2564da4cc29dcf8581d94c5c14934b98637ca318a4d6e7ca6ffd1690b8e77df6377508f9f0c90d000395237576a9148db50eb52063ea9d98b3eac91489a90f738986f68763ac6776a914e257eccafbc07c381642ce6e7e55120fb077fbed880448f2b262b175ac68ffffffff0110400000000000001976a9148db50eb52063ea9d98b3eac91489a90f738986f688ac00000000",
)
if err := localChain.addTransaction(inputTransaction); err != nil {
t.Fatal(err)
}

err := builder.AddPublicKeyHashInput(&UnspentTransactionOutput{
Outpoint: &TransactionOutpoint{
TransactionHash: inputTransaction.Hash(),
OutputIndex: 3,
},
Value: 16400,
})
if err == nil {
t.Fatal("expected out-of-range output index error")
}
if !strings.Contains(err.Error(), "output index [3] out of range") {
t.Fatalf("unexpected error: [%v]", err)
}
}

func TestTransactionBuilder_AddScriptHashInput(t *testing.T) {
var tests = map[string]struct {
inputTransactionHex string
Expand Down
31 changes: 8 additions & 23 deletions pkg/firewall/firewall.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,6 @@ func (al *AllowList) Contains(operatorPublicKey *operator.PublicKey) bool {
var EmptyAllowList = NewAllowList([]*operator.PublicKey{})

const (
// PositiveIsRecognizedCachePeriod is the time period the cache maintains
// the positive result of the last `IsRecognized` checks.
// We use the cache to minimize calls to the on-chain client.
PositiveIsRecognizedCachePeriod = 12 * time.Hour

// NegativeIsRecognizedCachePeriod is the time period the cache maintains
// the negative result of the last `IsRecognized` checks.
// We use the cache to minimize calls to the on-chain client.
Expand All @@ -74,24 +69,24 @@ func AnyApplicationPolicy(
return &anyApplicationPolicy{
applications: applications,
allowList: allowList,
positiveResultCache: cache.NewTimeCache(PositiveIsRecognizedCachePeriod),
negativeResultCache: cache.NewTimeCache(NegativeIsRecognizedCachePeriod),
}
}

type anyApplicationPolicy struct {
applications []Application
allowList *AllowList
positiveResultCache *cache.TimeCache
negativeResultCache *cache.TimeCache
}

// Validate checks whether the given operator meets the conditions to join
// the network. The operator can join the network if it is an allowlisted node
// or it is a non-allowlisted node but it is recognized as eligible by any of
// the applications. Nil is returned on a successful validation, error otherwise.
// Due to performance reasons, the results of validations for non-allowlisted
// nodes are stored in a cache for a certain amount of time.
// Due to performance reasons, negative validation results for non-allowlisted
// nodes are stored in a cache for a certain amount of time. Positive results
// are intentionally not cached so that operator revocations take effect on the
// next Validate call rather than after a cache TTL.
func (aap *anyApplicationPolicy) Validate(
remotePeerPublicKey *operator.PublicKey,
) error {
Expand All @@ -100,23 +95,17 @@ func (aap *anyApplicationPolicy) Validate(
return nil
}

// First, check in the in-memory time caches to minimize hits to the ETH client.
// If the Keep client with the given chain address is in the positive result
// cache it means it has been recognized when the last `IsRecognized` was
// executed and caching period has not elapsed yet. Similarly, if the client
// is in the negative result cache it means it hasn't been recognized.
// First, check in the in-memory time cache to minimize hits to the ETH client.
// If the client is in the negative result cache it means it hasn't been
// recognized when the last `IsRecognized` was executed and caching period
// has not elapsed yet.
//
// If the caching period elapsed, cache checks will return false and we
// have to ask the chain about the current status.
aap.positiveResultCache.Sweep()
aap.negativeResultCache.Sweep()

remotePeerPublicKeyHex := remotePeerPublicKey.String()

if aap.positiveResultCache.Has(remotePeerPublicKeyHex) {
return nil
}

if aap.negativeResultCache.Has(remotePeerPublicKeyHex) {
return errNotRecognized
}
Expand All @@ -143,9 +132,5 @@ func (aap *anyApplicationPolicy) Validate(
return errNotRecognized
}

// Add this address to the positive result cache.
// `IsRecognized` will not be called again for the entire caching period.
aap.positiveResultCache.Add(remotePeerPublicKeyHex)

return nil
}
19 changes: 4 additions & 15 deletions pkg/firewall/firewall_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ func TestValidate_PeerNotRecognized_NoApplications(t *testing.T) {
policy := &anyApplicationPolicy{
applications: []Application{},
allowList: EmptyAllowList,
positiveResultCache: cache.NewTimeCache(cachingPeriod),
negativeResultCache: cache.NewTimeCache(cachingPeriod),
}

Expand Down Expand Up @@ -45,7 +44,6 @@ func TestValidate_PeerNotRecognized_MultipleApplications(t *testing.T) {
newMockApplication(),
newMockApplication()},
allowList: EmptyAllowList,
positiveResultCache: cache.NewTimeCache(cachingPeriod),
negativeResultCache: cache.NewTimeCache(cachingPeriod),
}

Expand All @@ -72,7 +70,6 @@ func TestValidate_PeerRecognized_FirstApplicationRecognizes(t *testing.T) {
application,
newMockApplication()},
allowList: EmptyAllowList,
positiveResultCache: cache.NewTimeCache(cachingPeriod),
negativeResultCache: cache.NewTimeCache(cachingPeriod),
}

Expand Down Expand Up @@ -101,7 +98,6 @@ func TestValidate_PeerRecognized_SecondApplicationRecognizes(t *testing.T) {
newMockApplication(),
application},
allowList: EmptyAllowList,
positiveResultCache: cache.NewTimeCache(cachingPeriod),
negativeResultCache: cache.NewTimeCache(cachingPeriod),
}

Expand Down Expand Up @@ -140,15 +136,14 @@ func TestValidate_PeerNotRecognized_FirstApplicationReturnedError(t *testing.T)
application1,
application2},
allowList: EmptyAllowList,
positiveResultCache: cache.NewTimeCache(cachingPeriod),
negativeResultCache: cache.NewTimeCache(cachingPeriod),
}

err = policy.Validate(peerOperatorPublicKey)
testutils.AssertAnyErrorInChainMatchesTarget(t, applicationError, err)
}

func TestValidate_PeerRecognized_Cached(t *testing.T) {
func TestValidate_PeerRecognized_Rechecked(t *testing.T) {
_, peerOperatorPublicKey, err := operator.GenerateKeyPair(
local_v1.DefaultCurve,
)
Expand All @@ -165,7 +160,6 @@ func TestValidate_PeerRecognized_Cached(t *testing.T) {
policy := &anyApplicationPolicy{
applications: []Application{application},
allowList: EmptyAllowList,
positiveResultCache: cache.NewTimeCache(cachingPeriod),
negativeResultCache: cache.NewTimeCache(cachingPeriod),
}

Expand All @@ -175,16 +169,15 @@ func TestValidate_PeerRecognized_Cached(t *testing.T) {
}

// Ensure the application does not recognize the operator anymore.
// Validation should still succeed, since the cached result should be used.
// Validation should fail because positive results are rechecked to avoid
// accepting peers whose application recognition was revoked.
application.setIsRecognized(peerOperatorPublicKey, result{
isRecognized: false,
err: nil,
})

err = policy.Validate(peerOperatorPublicKey)
if err != nil {
t.Fatal(err)
}
testutils.AssertErrorsSame(t, errNotRecognized, err)
}

func TestValidate_PeerNotRecognized_CacheEmptied(t *testing.T) {
Expand All @@ -204,7 +197,6 @@ func TestValidate_PeerNotRecognized_CacheEmptied(t *testing.T) {
policy := &anyApplicationPolicy{
applications: []Application{application},
allowList: EmptyAllowList,
positiveResultCache: cache.NewTimeCache(cachingPeriod),
negativeResultCache: cache.NewTimeCache(cachingPeriod),
}

Expand Down Expand Up @@ -239,7 +231,6 @@ func TestValidate_PeerNotRecognized_Cached(t *testing.T) {
policy := &anyApplicationPolicy{
applications: []Application{application},
allowList: EmptyAllowList,
positiveResultCache: cache.NewTimeCache(cachingPeriod),
negativeResultCache: cache.NewTimeCache(cachingPeriod),
}

Expand Down Expand Up @@ -274,7 +265,6 @@ func TestValidate_PeerRecognized_CacheEmptied(t *testing.T) {
policy := &anyApplicationPolicy{
applications: []Application{application},
allowList: EmptyAllowList,
positiveResultCache: cache.NewTimeCache(cachingPeriod),
negativeResultCache: cache.NewTimeCache(cachingPeriod),
}

Expand Down Expand Up @@ -312,7 +302,6 @@ func TestValidate_PeerIsAllowlistedNode(t *testing.T) {
policy := &anyApplicationPolicy{
applications: []Application{newMockApplication()},
allowList: allowList,
positiveResultCache: cache.NewTimeCache(cachingPeriod),
negativeResultCache: cache.NewTimeCache(cachingPeriod),
}

Expand Down
9 changes: 9 additions & 0 deletions pkg/maintainer/spv/spv.go
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,15 @@ func isInputCurrentWalletsMainUTXO(
if err != nil {
return false, fmt.Errorf("failed to get previous transaction: [%v]", err)
}
if fundingOutputIndex >= uint32(len(previousTransaction.Outputs)) {
return false, fmt.Errorf(
"funding output index [%d] out of range for transaction [%s] "+
"with [%d] outputs",
fundingOutputIndex,
fundingTxHash.String(),
len(previousTransaction.Outputs),
)
}
fundingOutputValue := previousTransaction.Outputs[fundingOutputIndex].Value

// Assume the input is the main UTXO and calculate hash.
Expand Down
50 changes: 50 additions & 0 deletions pkg/maintainer/spv/spv_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/hex"
"math/big"
"reflect"
"strings"
"testing"

"github.com/keep-network/keep-core/internal/testutils"
Expand Down Expand Up @@ -217,6 +218,55 @@ func TestUniqueWalletPublicKeyHashes(t *testing.T) {
}
}

func TestIsInputCurrentWalletsMainUTXO_OutOfRangeFundingOutput(t *testing.T) {
bytesFromHex := func(str string) []byte {
value, err := hex.DecodeString(str)
if err != nil {
t.Fatal(err)
}

return value
}

txFromHex := func(str string) *bitcoin.Transaction {
transaction := new(bitcoin.Transaction)
err := transaction.Deserialize(bytesFromHex(str))
if err != nil {
t.Fatal(err)
}

return transaction
}

btcChain := newLocalBitcoinChain()
fundingTransaction := txFromHex(
"0100000000010110a15e879b7e8b07df62772579a64bf2b409409bbcc8bc2c7f6e39" +
"31dc615e920100000000ffffffff02042900000000000017a9143ec459d0f3c29286" +
"ae5df5fcc421e2786024277e87b4121600000000001600148db50eb52063ea9d98b3" +
"eac91489a90f738986f6024830450221009740ad12d2e74c00ccb4741d533d2ecd69" +
"02289144c4626508afb61eed790c97022006e67179e8e2a63dc4f1ab758867d8bbfe" +
"0a2b67682be6dadfa8e07d3b7ba04d012103989d253b17a6a0f41838b84ff0d20e88" +
"98f9d7b1a98f2564da4cc29dcf8581d900000000",
)
if err := btcChain.BroadcastTransaction(fundingTransaction); err != nil {
t.Fatal(err)
}

_, err := isInputCurrentWalletsMainUTXO(
fundingTransaction.Hash(),
2,
[20]byte{},
btcChain,
newLocalChain(),
)
if err == nil {
t.Fatal("expected out-of-range funding output error")
}
if !strings.Contains(err.Error(), "funding output index [2] out of range") {
t.Fatalf("unexpected error: [%v]", err)
}
}

func TestIsInputCurrentWalletsMainUTXO(t *testing.T) {
bytesFromHex := func(str string) []byte {
value, err := hex.DecodeString(str)
Expand Down
Loading
Loading