diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml index 0587c2a6..a9f21896 100644 --- a/.github/workflows/create-release.yml +++ b/.github/workflows/create-release.yml @@ -21,13 +21,13 @@ jobs: go-version: ${{ matrix.go }} - name: Checkout source uses: actions/checkout@v2 + - name: Install coreutils, ICU for macOS + if: matrix.os == 'macos-latest' + run: brew install coreutils icu4c - name: Build executables env: GO111MODULE: "on" run: go build -trimpath -o artifacts/ --tags use_icu_normalization . - - name: Install coreutils for macOS - if: matrix.os == 'macos-latest' - run: brew install coreutils icu4c - name: SHA256 sum run: sha256sum -b artifacts/* > artifacts/chain.sha256 - name: Upload artifacts diff --git a/claimtrie/claimtrie_test.go b/claimtrie/claimtrie_test.go index 42e409c8..ab77e287 100644 --- a/claimtrie/claimtrie_test.go +++ b/claimtrie/claimtrie_test.go @@ -72,7 +72,6 @@ func TestFixedHashes(t *testing.T) { } func TestNormalizationFork(t *testing.T) { - r := require.New(t) setup(t) @@ -108,6 +107,10 @@ func TestNormalizationFork(t *testing.T) { err = ct.AddClaim([]byte("test"), o6, change.NewClaimID(o6), 7) r.NoError(err) + o7 := wire.OutPoint{Hash: hash, Index: 7} + err = ct.AddSupport([]byte("test"), o7, 11, change.NewClaimID(o6)) + r.NoError(err) + err = ct.AppendBlock() r.NoError(err) r.NotEqual(merkletrie.EmptyTrieHash[:], ct.MerkleHash()[:]) @@ -117,8 +120,8 @@ func TestNormalizationFork(t *testing.T) { r.NotNil(n.BestClaim) r.Equal(int32(1), n.TakenOverAt) - o7 := wire.OutPoint{Hash: hash, Index: 7} - err = ct.AddClaim([]byte("aÑEJO"), o7, change.NewClaimID(o7), 8) + o8 := wire.OutPoint{Hash: hash, Index: 8} + err = ct.AddClaim([]byte("aÑEJO"), o8, change.NewClaimID(o8), 8) r.NoError(err) err = ct.AppendBlock() @@ -130,6 +133,10 @@ func TestNormalizationFork(t *testing.T) { r.Equal(3, len(n.Claims)) r.Equal(uint32(1), n.BestClaim.OutPoint.Index) r.Equal(int32(2), n.TakenOverAt) + + n, err = ct.nodeManager.NodeAt(ct.nodeManager.Height(), []byte("test")) + r.NoError(err) + r.Equal(int64(18), n.BestClaim.Amount+n.SupportSums[n.BestClaim.ClaimID.Key()]) } func TestActivationsOnNormalizationFork(t *testing.T) { @@ -345,3 +352,633 @@ func randomName() []byte { } return name } + +func TestClaimReplace(t *testing.T) { + r := require.New(t) + setup(t) + ct, err := New(cfg) + r.NoError(err) + r.NotNil(ct) + defer ct.Close() + + hash := chainhash.HashH([]byte{1, 2, 3}) + o1 := wire.OutPoint{Hash: hash, Index: 1} + err = ct.AddClaim([]byte("bass"), o1, change.NewClaimID(o1), 8) + r.NoError(err) + + o2 := wire.OutPoint{Hash: hash, Index: 2} + err = ct.AddClaim([]byte("basso"), o2, change.NewClaimID(o2), 10) + r.NoError(err) + + err = ct.AppendBlock() + r.NoError(err) + n, err := ct.NodeAt(ct.height, []byte("bass")) + r.Equal(o1.String(), n.BestClaim.OutPoint.String()) + + err = ct.SpendClaim([]byte("bass"), o1, n.BestClaim.ClaimID) + r.NoError(err) + + o4 := wire.OutPoint{Hash: hash, Index: 4} + err = ct.AddClaim([]byte("bassfisher"), o4, change.NewClaimID(o4), 12) + r.NoError(err) + + err = ct.AppendBlock() + r.NoError(err) + n, err = ct.NodeAt(ct.height, []byte("bass")) + r.NoError(err) + r.True(n == nil || !n.HasActiveBestClaim()) + n, err = ct.NodeAt(ct.height, []byte("bassfisher")) + r.Equal(o4.String(), n.BestClaim.OutPoint.String()) +} + +func TestGeneralClaim(t *testing.T) { + r := require.New(t) + setup(t) + ct, err := New(cfg) + r.NoError(err) + r.NotNil(ct) + defer ct.Close() + + err = ct.AppendBlock() + r.NoError(err) + + hash := chainhash.HashH([]byte{1, 2, 3}) + o1 := wire.OutPoint{Hash: hash, Index: 1} + err = ct.AddClaim([]byte("test"), o1, change.NewClaimID(o1), 8) + r.NoError(err) + + err = ct.AppendBlock() + r.NoError(err) + err = ct.ResetHeight(ct.height - 1) + r.NoError(err) + n, err := ct.NodeAt(ct.height, []byte("test")) + r.NoError(err) + r.True(n == nil || !n.HasActiveBestClaim()) + + err = ct.AddClaim([]byte("test"), o1, change.NewClaimID(o1), 8) + o2 := wire.OutPoint{Hash: hash, Index: 2} + err = ct.AddClaim([]byte("test"), o2, change.NewClaimID(o2), 8) + r.NoError(err) + + err = ct.AppendBlock() + r.NoError(err) + err = ct.ResetHeight(ct.height - 1) + r.NoError(err) + n, err = ct.NodeAt(ct.height, []byte("test")) + r.NoError(err) + r.True(n == nil || !n.HasActiveBestClaim()) + + err = ct.AddClaim([]byte("test"), o1, change.NewClaimID(o1), 8) + r.NoError(err) + err = ct.AppendBlock() + r.NoError(err) + err = ct.AddClaim([]byte("test"), o2, change.NewClaimID(o2), 8) + r.NoError(err) + err = ct.AppendBlock() + r.NoError(err) + + err = ct.ResetHeight(ct.height - 2) + r.NoError(err) + n, err = ct.NodeAt(ct.height, []byte("test")) + r.NoError(err) + r.True(n == nil || !n.HasActiveBestClaim()) +} + +func TestClaimTakeover(t *testing.T) { + r := require.New(t) + setup(t) + param.ActiveParams.ActiveDelayFactor = 1 + + ct, err := New(cfg) + r.NoError(err) + r.NotNil(ct) + defer ct.Close() + + err = ct.AppendBlock() + r.NoError(err) + + hash := chainhash.HashH([]byte{1, 2, 3}) + o1 := wire.OutPoint{Hash: hash, Index: 1} + err = ct.AddClaim([]byte("test"), o1, change.NewClaimID(o1), 8) + r.NoError(err) + + for i := 0; i < 10; i++ { + err = ct.AppendBlock() + r.NoError(err) + } + + o2 := wire.OutPoint{Hash: hash, Index: 2} + err = ct.AddClaim([]byte("test"), o2, change.NewClaimID(o2), 18) + r.NoError(err) + + for i := 0; i < 10; i++ { + err = ct.AppendBlock() + r.NoError(err) + } + + n, err := ct.NodeAt(ct.height, []byte("test")) + r.NoError(err) + r.Equal(o1.String(), n.BestClaim.OutPoint.String()) + + err = ct.AppendBlock() + r.NoError(err) + + n, err = ct.NodeAt(ct.height, []byte("test")) + r.NoError(err) + r.Equal(o2.String(), n.BestClaim.OutPoint.String()) + + err = ct.ResetHeight(ct.height - 1) + r.NoError(err) + n, err = ct.NodeAt(ct.height, []byte("test")) + r.NoError(err) + r.Equal(o1.String(), n.BestClaim.OutPoint.String()) +} + +func TestSpendClaim(t *testing.T) { + r := require.New(t) + setup(t) + param.ActiveParams.ActiveDelayFactor = 1 + + ct, err := New(cfg) + r.NoError(err) + r.NotNil(ct) + defer ct.Close() + + err = ct.AppendBlock() + r.NoError(err) + + hash := chainhash.HashH([]byte{1, 2, 3}) + o1 := wire.OutPoint{Hash: hash, Index: 1} + err = ct.AddClaim([]byte("test"), o1, change.NewClaimID(o1), 18) + r.NoError(err) + o2 := wire.OutPoint{Hash: hash, Index: 2} + err = ct.AddClaim([]byte("test"), o2, change.NewClaimID(o2), 8) + r.NoError(err) + + err = ct.AppendBlock() + r.NoError(err) + + err = ct.SpendClaim([]byte("test"), o1, change.NewClaimID(o1)) + r.NoError(err) + + err = ct.AppendBlock() + r.NoError(err) + + n, err := ct.NodeAt(ct.height, []byte("test")) + r.NoError(err) + r.Equal(o2.String(), n.BestClaim.OutPoint.String()) + + err = ct.ResetHeight(ct.height - 1) + r.NoError(err) + + o3 := wire.OutPoint{Hash: hash, Index: 3} + err = ct.AddClaim([]byte("test"), o3, change.NewClaimID(o3), 22) + r.NoError(err) + + for i := 0; i < 10; i++ { + err = ct.AppendBlock() + r.NoError(err) + } + + o4 := wire.OutPoint{Hash: hash, Index: 4} + err = ct.AddClaim([]byte("test"), o4, change.NewClaimID(o4), 28) + r.NoError(err) + + err = ct.AppendBlock() + r.NoError(err) + + n, err = ct.NodeAt(ct.height, []byte("test")) + r.NoError(err) + r.Equal(o3.String(), n.BestClaim.OutPoint.String()) + + err = ct.SpendClaim([]byte("test"), o3, n.BestClaim.ClaimID) + r.NoError(err) + + err = ct.AppendBlock() + r.NoError(err) + + n, err = ct.NodeAt(ct.height, []byte("test")) + r.NoError(err) + r.Equal(o4.String(), n.BestClaim.OutPoint.String()) + + err = ct.SpendClaim([]byte("test"), o1, change.NewClaimID(o1)) + r.NoError(err) + err = ct.SpendClaim([]byte("test"), o2, change.NewClaimID(o2)) + r.NoError(err) + err = ct.SpendClaim([]byte("test"), o3, change.NewClaimID(o3)) + r.NoError(err) + err = ct.SpendClaim([]byte("test"), o4, change.NewClaimID(o4)) + r.NoError(err) + + err = ct.AppendBlock() + r.NoError(err) + + n, err = ct.NodeAt(ct.height, []byte("test")) + r.NoError(err) + r.True(n == nil || !n.HasActiveBestClaim()) + + h := ct.MerkleHash() + r.Equal(merkletrie.EmptyTrieHash.String(), h.String()) +} + +func TestSupportDelay(t *testing.T) { + r := require.New(t) + setup(t) + param.ActiveParams.ActiveDelayFactor = 1 + + ct, err := New(cfg) + r.NoError(err) + r.NotNil(ct) + defer ct.Close() + + err = ct.AppendBlock() + r.NoError(err) + + hash := chainhash.HashH([]byte{1, 2, 3}) + o1 := wire.OutPoint{Hash: hash, Index: 1} + err = ct.AddClaim([]byte("test"), o1, change.NewClaimID(o1), 18) + r.NoError(err) + o2 := wire.OutPoint{Hash: hash, Index: 2} + err = ct.AddClaim([]byte("test"), o2, change.NewClaimID(o2), 8) + r.NoError(err) + + o3 := wire.OutPoint{Hash: hash, Index: 3} + err = ct.AddSupport([]byte("test"), o3, 18, change.NewClaimID(o3)) // using bad ClaimID on purpose + r.NoError(err) + o4 := wire.OutPoint{Hash: hash, Index: 4} + err = ct.AddSupport([]byte("test"), o4, 18, change.NewClaimID(o2)) + r.NoError(err) + + err = ct.AppendBlock() + r.NoError(err) + + n, err := ct.NodeAt(ct.height, []byte("test")) + r.NoError(err) + r.Equal(o2.String(), n.BestClaim.OutPoint.String()) + + for i := 0; i < 10; i++ { + err = ct.AppendBlock() + r.NoError(err) + } + + o5 := wire.OutPoint{Hash: hash, Index: 5} + err = ct.AddSupport([]byte("test"), o5, 18, change.NewClaimID(o1)) + r.NoError(err) + + err = ct.AppendBlock() + r.NoError(err) + + n, err = ct.NodeAt(ct.height, []byte("test")) + r.NoError(err) + r.Equal(o2.String(), n.BestClaim.OutPoint.String()) + + for i := 0; i < 11; i++ { + err = ct.AppendBlock() + r.NoError(err) + } + + n, err = ct.NodeAt(ct.height, []byte("test")) + r.NoError(err) + r.Equal(o1.String(), n.BestClaim.OutPoint.String()) + + err = ct.ResetHeight(ct.height - 1) + r.NoError(err) + + n, err = ct.NodeAt(ct.height, []byte("test")) + r.NoError(err) + r.Equal(o2.String(), n.BestClaim.OutPoint.String()) +} + +func TestSupportSpending(t *testing.T) { + r := require.New(t) + setup(t) + param.ActiveParams.ActiveDelayFactor = 1 + + ct, err := New(cfg) + r.NoError(err) + r.NotNil(ct) + defer ct.Close() + + err = ct.AppendBlock() + r.NoError(err) + + hash := chainhash.HashH([]byte{1, 2, 3}) + o1 := wire.OutPoint{Hash: hash, Index: 1} + err = ct.AddClaim([]byte("test"), o1, change.NewClaimID(o1), 18) + r.NoError(err) + + err = ct.AppendBlock() + r.NoError(err) + + o3 := wire.OutPoint{Hash: hash, Index: 3} + err = ct.AddSupport([]byte("test"), o3, 18, change.NewClaimID(o1)) + r.NoError(err) + + err = ct.SpendClaim([]byte("test"), o1, change.NewClaimID(o1)) + r.NoError(err) + + err = ct.AppendBlock() + r.NoError(err) + + n, err := ct.NodeAt(ct.height, []byte("test")) + r.NoError(err) + r.True(n == nil || !n.HasActiveBestClaim()) +} + +func TestSupportOnUpdate(t *testing.T) { + r := require.New(t) + setup(t) + param.ActiveParams.ActiveDelayFactor = 1 + + ct, err := New(cfg) + r.NoError(err) + r.NotNil(ct) + defer ct.Close() + + err = ct.AppendBlock() + r.NoError(err) + + hash := chainhash.HashH([]byte{1, 2, 3}) + o1 := wire.OutPoint{Hash: hash, Index: 1} + err = ct.AddClaim([]byte("test"), o1, change.NewClaimID(o1), 18) + r.NoError(err) + + err = ct.SpendClaim([]byte("test"), o1, change.NewClaimID(o1)) + r.NoError(err) + + o2 := wire.OutPoint{Hash: hash, Index: 2} + err = ct.UpdateClaim([]byte("test"), o2, 28, change.NewClaimID(o1)) + r.NoError(err) + + err = ct.AppendBlock() + r.NoError(err) + + n, err := ct.NodeAt(ct.height, []byte("test")) + r.NoError(err) + r.Equal(int64(28), n.BestClaim.Amount) + + err = ct.AppendBlock() + r.NoError(err) + + err = ct.SpendClaim([]byte("test"), o2, change.NewClaimID(o1)) + r.NoError(err) + + o3 := wire.OutPoint{Hash: hash, Index: 3} + err = ct.UpdateClaim([]byte("test"), o3, 38, change.NewClaimID(o1)) + r.NoError(err) + + o4 := wire.OutPoint{Hash: hash, Index: 4} + err = ct.AddSupport([]byte("test"), o4, 2, change.NewClaimID(o1)) + r.NoError(err) + + o5 := wire.OutPoint{Hash: hash, Index: 5} + err = ct.AddClaim([]byte("test"), o5, change.NewClaimID(o5), 39) + r.NoError(err) + + err = ct.AppendBlock() + r.NoError(err) + + n, err = ct.NodeAt(ct.height, []byte("test")) + r.NoError(err) + r.Equal(int64(40), n.BestClaim.Amount+n.SupportSums[n.BestClaim.ClaimID.Key()]) + + err = ct.SpendSupport([]byte("test"), o4, n.BestClaim.ClaimID) + r.NoError(err) + + err = ct.AppendBlock() + r.NoError(err) + + // NOTE: LBRYcrd did not test that supports can trigger a takeover correctly (and it doesn't work here): + // n, err = ct.NodeAt(ct.height, []byte("test")) + // r.NoError(err) + // r.Equal(int64(39), n.BestClaim.Amount + n.SupportSums[n.BestClaim.ClaimID.Key()]) +} + +func TestSupportPreservation(t *testing.T) { + r := require.New(t) + setup(t) + param.ActiveParams.ActiveDelayFactor = 1 + + ct, err := New(cfg) + r.NoError(err) + r.NotNil(ct) + defer ct.Close() + + err = ct.AppendBlock() + r.NoError(err) + + hash := chainhash.HashH([]byte{1, 2, 3}) + o1 := wire.OutPoint{Hash: hash, Index: 1} + o2 := wire.OutPoint{Hash: hash, Index: 2} + o3 := wire.OutPoint{Hash: hash, Index: 3} + o4 := wire.OutPoint{Hash: hash, Index: 4} + o5 := wire.OutPoint{Hash: hash, Index: 5} + + err = ct.AddSupport([]byte("test"), o2, 10, change.NewClaimID(o1)) + r.NoError(err) + + err = ct.AppendBlock() + r.NoError(err) + + err = ct.AddClaim([]byte("test"), o1, change.NewClaimID(o1), 18) + r.NoError(err) + + err = ct.AddClaim([]byte("test"), o3, change.NewClaimID(o3), 7) + r.NoError(err) + + for i := 0; i < 10; i++ { + err = ct.AppendBlock() + r.NoError(err) + } + + n, err := ct.NodeAt(ct.height, []byte("test")) + r.NoError(err) + r.Equal(int64(28), n.BestClaim.Amount+n.SupportSums[n.BestClaim.ClaimID.Key()]) + + err = ct.AddSupport([]byte("test"), o4, 10, change.NewClaimID(o1)) + r.NoError(err) + err = ct.AddSupport([]byte("test"), o5, 100, change.NewClaimID(o3)) + r.NoError(err) + + err = ct.AppendBlock() + r.NoError(err) + + n, err = ct.NodeAt(ct.height, []byte("test")) + r.NoError(err) + r.Equal(int64(38), n.BestClaim.Amount+n.SupportSums[n.BestClaim.ClaimID.Key()]) + + for i := 0; i < 10; i++ { + err = ct.AppendBlock() + r.NoError(err) + } + + n, err = ct.NodeAt(ct.height, []byte("test")) + r.NoError(err) + r.Equal(int64(107), n.BestClaim.Amount+n.SupportSums[n.BestClaim.ClaimID.Key()]) +} + +func TestInvalidClaimID(t *testing.T) { + r := require.New(t) + setup(t) + param.ActiveParams.ActiveDelayFactor = 1 + + ct, err := New(cfg) + r.NoError(err) + r.NotNil(ct) + defer ct.Close() + + err = ct.AppendBlock() + r.NoError(err) + + hash := chainhash.HashH([]byte{1, 2, 3}) + o1 := wire.OutPoint{Hash: hash, Index: 1} + o2 := wire.OutPoint{Hash: hash, Index: 2} + o3 := wire.OutPoint{Hash: hash, Index: 3} + + err = ct.AddClaim([]byte("test"), o1, change.NewClaimID(o1), 10) + r.NoError(err) + + err = ct.AppendBlock() + r.NoError(err) + + err = ct.SpendClaim([]byte("test"), o3, change.NewClaimID(o1)) + r.NoError(err) + + err = ct.UpdateClaim([]byte("test"), o2, 18, change.NewClaimID(o3)) + r.NoError(err) + + for i := 0; i < 12; i++ { + err = ct.AppendBlock() + r.NoError(err) + } + + n, err := ct.NodeAt(ct.height, []byte("test")) + r.NoError(err) + r.Len(n.Claims, 1) + r.Len(n.Supports, 0) + r.Equal(int64(10), n.BestClaim.Amount+n.SupportSums[n.BestClaim.ClaimID.Key()]) +} + +func TestStableTrieHash(t *testing.T) { + r := require.New(t) + setup(t) + param.ActiveParams.ActiveDelayFactor = 1 + param.ActiveParams.AllClaimsInMerkleForkHeight = 8 // changes on this one + + ct, err := New(cfg) + r.NoError(err) + r.NotNil(ct) + defer ct.Close() + + hash := chainhash.HashH([]byte{1, 2, 3}) + o1 := wire.OutPoint{Hash: hash, Index: 1} + + err = ct.AddClaim([]byte("test"), o1, change.NewClaimID(o1), 1) + r.NoError(err) + + err = ct.AppendBlock() + r.NoError(err) + + h := ct.MerkleHash() + r.NotEqual(merkletrie.EmptyTrieHash.String(), h.String()) + + for i := 0; i < 6; i++ { + err = ct.AppendBlock() + r.NoError(err) + r.Equal(h.String(), ct.MerkleHash().String()) + } + + err = ct.AppendBlock() + r.NoError(err) + + r.NotEqual(h.String(), ct.MerkleHash()) + h = ct.MerkleHash() + + for i := 0; i < 16; i++ { + err = ct.AppendBlock() + r.NoError(err) + r.Equal(h.String(), ct.MerkleHash().String()) + } +} + +func TestBlock884431(t *testing.T) { + r := require.New(t) + setup(t) + param.ActiveParams.ActiveDelayFactor = 1 + param.ActiveParams.MaxRemovalWorkaroundHeight = 0 + param.ActiveParams.AllClaimsInMerkleForkHeight = 0 + + ct, err := New(cfg) + r.NoError(err) + r.NotNil(ct) + defer ct.Close() + + // in this block we have a scenario where we update all the child names + // which, in the old code, caused a trie vertex to be removed + // which, in turn, would trigger a premature takeover + + c := byte(10) + + add := func(s string, amt int64) wire.OutPoint { + h := chainhash.HashH([]byte{c}) + c++ + o := wire.OutPoint{Hash: h, Index: 1} + err := ct.AddClaim([]byte(s), o, change.NewClaimID(o), amt) + r.NoError(err) + return o + } + + update := func(s string, o wire.OutPoint, amt int64) wire.OutPoint { + err = ct.SpendClaim([]byte(s), o, change.NewClaimID(o)) + r.NoError(err) + + h := chainhash.HashH([]byte{c}) + c++ + o2 := wire.OutPoint{Hash: h, Index: 2} + + err = ct.UpdateClaim([]byte(s), o2, amt, change.NewClaimID(o)) + r.NoError(err) + return o2 + } + + o1a := add("go", 10) + o1b := add("go", 20) + o2 := add("goop", 10) + o3 := add("gog", 20) + + o4a := add("test", 10) + o4b := add("test", 20) + o5 := add("tester", 10) + o6 := add("testing", 20) + + for i := 0; i < 10; i++ { + err = ct.AppendBlock() + r.NoError(err) + } + n, err := ct.NodeAt(ct.height, []byte("go")) + r.NoError(err) + r.Equal(o1b.String(), n.BestClaim.OutPoint.String()) + n, err = ct.NodeAt(ct.height, []byte("test")) + r.NoError(err) + r.Equal(o4b.String(), n.BestClaim.OutPoint.String()) + + update("go", o1b, 30) + o10 := update("go", o1a, 40) + update("gog", o3, 30) + update("goop", o2, 30) + + update("testing", o6, 30) + o11 := update("test", o4b, 30) + update("test", o4a, 40) + update("tester", o5, 30) + + err = ct.AppendBlock() + r.NoError(err) + + n, err = ct.NodeAt(ct.height, []byte("go")) + r.NoError(err) + r.Equal(o10.String(), n.BestClaim.OutPoint.String()) + n, err = ct.NodeAt(ct.height, []byte("test")) + r.NoError(err) + r.Equal(o11.String(), n.BestClaim.OutPoint.String()) +} diff --git a/txscript/nameclaim.go b/txscript/nameclaim.go index b4630037..bcdf01eb 100644 --- a/txscript/nameclaim.go +++ b/txscript/nameclaim.go @@ -197,7 +197,7 @@ func isUpdateClaim(pops []parsedOpcode) bool { pops[5].opcode.value == OP_2DROP } -const illegalChars = "=&#:*$@%?/\x00" +const illegalChars = "=&#:*$@%?/;\\\b\n\t\r\x00" func AllClaimsAreSane(script []byte, enforceSoftFork bool) error { cs, err := DecodeClaimScript(script)